Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1f820d0
Add Tempo precompile channel primitives
0xrusowsky May 13, 2026
e9b948e
Wire Tempo precompile client sessions
0xrusowsky May 13, 2026
e419af4
Organize precompile session client
0xrusowsky May 13, 2026
2807d62
Add TIP-1034 precompile server verification
0xrusowsky May 13, 2026
dab9d37
Harden precompile client and receipt flows
0xrusowsky May 13, 2026
0b23e3f
Tighten TIP-1034 precompile types
0xrusowsky May 13, 2026
a9b1e37
Add precompile session manager
0xrusowsky May 13, 2026
4330de9
Run precompile integration tests on devnet
0xrusowsky May 13, 2026
0479d72
Add precompile fee payer close parity
0xrusowsky May 13, 2026
7f23a5c
chore: hardening
0xrusowsky May 14, 2026
69a00c5
Add devnet precompile test setup
0xrusowsky May 14, 2026
b1b8d58
Consolidate precompile changesets
0xrusowsky May 14, 2026
7f6266f
Clarify precompile changeset opt-in API
0xrusowsky May 14, 2026
48ad117
chore: consolidate types
0xrusowsky May 14, 2026
9895895
style: computeId.Parameters
0xrusowsky May 14, 2026
4b28345
style: fmt
0xrusowsky May 14, 2026
bea3fb3
Align precompile voucher signing inputs
0xrusowsky May 14, 2026
a823444
Align precompile chain helpers with legacy API
0xrusowsky May 14, 2026
096d34f
Handle stale test setup locks
0xrusowsky May 14, 2026
7b48c9b
Simplify precompile credential payload types
0xrusowsky May 14, 2026
20b182d
Align precompile client channel ops with legacy
0xrusowsky May 14, 2026
2ed882d
Align precompile client session layout
0xrusowsky May 14, 2026
e6eb6c4
Move precompile uint96 checks to payload helpers
0xrusowsky May 14, 2026
d514113
Extract precompile session chain helpers
0xrusowsky May 15, 2026
ab64607
Harden precompile session parity
0xrusowsky May 15, 2026
31cc340
Add precompile session HTTP SSE parity
0xrusowsky May 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .changeset/tip-1034-precompile-hash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
'mppx': patch
---

Added opt-in Tempo [TIP-1034](https://github.com/tempoxyz/tempo/blob/main/tips/tip-1034.md) precompile support for payment-channel sessions.

The default `tempo.session(...)` method continued to use the existing session backend. Applications opted into the new precompile-backed flow explicitly by using `tempo.precompile.session(...)` on the client and `tempo.precompile.Server.session(...)` on the server.

```ts
import { Mppx, tempo } from 'mppx/client'

const client = Mppx.create({
methods: [tempo.precompile.session({ account, maxDeposit: '10' })],
})
```

```ts
import { Mppx, tempo } from 'mppx/server'

const server = Mppx.create({
methods: [
tempo.precompile.Server.session({
amount: '1',
chainId,
currency,
recipient,
store,
unitType: 'request',
}),
],
})
```

This added channel ID, expiring nonce hash, voucher, ABI calldata, open/top-up validation, descriptor persistence, credential payload parsing, client credential builder, session manager, server verification, and server-driven fee-payer settle/close helpers for TIP20EscrowChannel precompile channels. It also changed precompile chain helpers to use `*OnChain` names, updated amount APIs to accept plain bigint values while validating uint96 bounds at encoding boundaries, updated precompile voucher signing inputs to match legacy session voucher signing, aligned precompile client session modules with the legacy client layout, and hardened precompile channel validation, atomic store updates, voucher-signing compatibility, finalized channel bookkeeping, and devnet precompile integration test setup.
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"testcontainers": "^11.14.0",
"tsx": "^4.21.0",
"typescript": "~6.0.3",
"viem": "^2.47.6",
"viem": "file:/Users/rusowsky/dev/viem/src",
"vite": "^8.0.10",
"vp": "npm:vite-plus@~0.1.17",
"ws": "^8.20.0",
Expand Down Expand Up @@ -197,5 +197,10 @@
"bin": {
"mppx": "./dist/bin.js",
"mppx.src": "./src/bin.ts"
},
"pnpm": {
"overrides": {
"ox": "0.14.20"
}
}
}
277 changes: 277 additions & 0 deletions patches/ox@0.14.20.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
diff --git a/_cjs/tempo/TxEnvelopeTempo.js b/_cjs/tempo/TxEnvelopeTempo.js
index 3dd158b5bffddeedfd8e691fef0e21e54addae60..f8042d2c47918f2a619649f3a5c5b9dc8602a2a7 100644
--- a/_cjs/tempo/TxEnvelopeTempo.js
+++ b/_cjs/tempo/TxEnvelopeTempo.js
@@ -5,6 +5,7 @@ exports.assert = assert;
exports.deserialize = deserialize;
exports.from = from;
exports.serialize = serialize;
+exports.encodeForSigning = encodeForSigning;
exports.getSignPayload = getSignPayload;
exports.hash = hash;
exports.getFeePayerSignPayload = getFeePayerSignPayload;
@@ -249,6 +250,15 @@ function serialize(envelope, options = {}) {
];
return Hex.concat(options.format === 'feePayer' ? exports.feePayerMagic : exports.serializedType, Rlp.fromHex(serialized));
}
+function encodeForSigning(envelope) {
+ return serialize({
+ ...envelope,
+ signature: undefined,
+ ...(envelope.feePayerSignature !== undefined
+ ? { feePayerSignature: null }
+ : {}),
+ });
+}
function getSignPayload(envelope, options = {}) {
const sigHash = hash(envelope, { presign: true });
if (options.from)
@@ -256,17 +266,9 @@ function getSignPayload(envelope, options = {}) {
return sigHash;
}
function hash(envelope, options = {}) {
- const serialized = serialize({
- ...envelope,
- ...(options.presign
- ? {
- signature: undefined,
- ...(envelope.feePayerSignature !== undefined
- ? { feePayerSignature: null }
- : {}),
- }
- : {}),
- });
+ const serialized = options.presign
+ ? encodeForSigning(envelope)
+ : serialize(envelope);
return Hash.keccak256(serialized);
}
function getFeePayerSignPayload(envelope, options) {
diff --git a/_esm/tempo/TxEnvelopeTempo.js b/_esm/tempo/TxEnvelopeTempo.js
index fe972a877ce062642355c70132af2da7e0025baf..11b8e9c1c2990e84e70b3f3885ffaf8e065fb421 100644
--- a/_esm/tempo/TxEnvelopeTempo.js
+++ b/_esm/tempo/TxEnvelopeTempo.js
@@ -448,74 +448,31 @@ export function serialize(envelope, options = {}) {
return Hex.concat(options.format === 'feePayer' ? feePayerMagic : serializedType, Rlp.fromHex(serialized));
}
/**
- * Returns the payload to sign for a {@link ox#TxEnvelopeTempo.TxEnvelopeTempo}.
- *
- * Computes the keccak256 hash of the unsigned serialized transaction. Sign this payload
- * with secp256k1, P256, or WebAuthn, then attach the signature via {@link ox#TxEnvelopeTempo.(from:function)}.
- *
- * [Tempo Transaction Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction)
- *
- * @example
- * The example below demonstrates how to compute the sign payload which can be used
- * with ECDSA signing utilities like {@link ox#Secp256k1.(sign:function)}.
- *
- * ```ts twoslash
- * // @noErrors
- * import { Secp256k1 } from 'ox'
- * import { TxEnvelopeTempo } from 'ox/tempo'
- *
- * const envelope = TxEnvelopeTempo.from({
- * chainId: 1,
- * calls: [{
- * data: '0xdeadbeef',
- * to: 'tempox0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
- * }],
- * nonce: 0n,
- * maxFeePerGas: 1000000000n,
- * gas: 21000n,
- * })
+ * Encodes a {@link ox#TxEnvelopeTempo.TxEnvelopeTempo} for sender signing.
*
- * const payload = TxEnvelopeTempo.getSignPayload(envelope) // [!code focus]
- * // @log: '0x...'
+ * Returns the raw serialized transaction bytes that are hashed by
+ * {@link ox#TxEnvelopeTempo.(getSignPayload:function)}. Sender signatures are
+ * stripped, and fee payer signatures are normalized to the sender pre-sign
+ * marker.
*
- * const signature = Secp256k1.sign({ payload, privateKey: '0x...' })
- * ```
- *
- * @example
- * ### Access Keys
- *
- * When signing as an access key on behalf of a root account, pass the
- * `from` option with the root account address. This computes
- * `keccak256(0x04 || sigHash || from)` which binds the signature to the
- * specific user account (V2 keychain format).
- *
- * ```ts twoslash
- * // @noErrors
- * import { Secp256k1 } from 'ox'
- * import { TxEnvelopeTempo, SignatureEnvelope } from 'ox/tempo'
- *
- * const envelope = TxEnvelopeTempo.from({
- * chainId: 1,
- * calls: [{
- * data: '0xdeadbeef',
- * to: 'tempox0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
- * }],
- * nonce: 0n,
- * maxFeePerGas: 1000000000n,
- * gas: 21000n,
- * })
- *
- * const payload = TxEnvelopeTempo.getSignPayload(envelope, { from: '0x...' }) // [!code focus]
- *
- * const signature = Secp256k1.sign({ payload, privateKey: '0x...' })
+ * @param envelope - The transaction envelope to encode for signing.
+ * @returns The serialized transaction bytes used as the sender signing preimage.
+ */
+export function encodeForSigning(envelope) {
+ return serialize({
+ ...envelope,
+ signature: undefined,
+ // When a fee payer signature is present, normalize to `null`
+ // (the presign marker).
+ ...(envelope.feePayerSignature !== undefined
+ ? { feePayerSignature: null }
+ : {}),
+ });
+}
+/**
+ * Returns the payload to sign for a {@link ox#TxEnvelopeTempo.TxEnvelopeTempo}.
*
- * const signed = TxEnvelopeTempo.serialize(envelope, {
- * signature: SignatureEnvelope.from({
- * userAddress: from,
- * inner: SignatureEnvelope.from(signature),
- * }),
- * })
- * ```
+ * Computes the keccak256 hash of {@link ox#TxEnvelopeTempo.(encodeForSigning:function)}.
*
* @param envelope - The transaction envelope to get the sign payload for.
* @param options - Options.
@@ -562,19 +519,9 @@ export function getSignPayload(envelope, options = {}) {
* @returns The hash of the transaction envelope.
*/
export function hash(envelope, options = {}) {
- const serialized = serialize({
- ...envelope,
- ...(options.presign
- ? {
- signature: undefined,
- // When a fee payer signature is present, normalize to `null`
- // (the presign marker).
- ...(envelope.feePayerSignature !== undefined
- ? { feePayerSignature: null }
- : {}),
- }
- : {}),
- });
+ const serialized = options.presign
+ ? encodeForSigning(envelope)
+ : serialize(envelope);
return Hash.keccak256(serialized);
}
/**
diff --git a/_types/tempo/TxEnvelopeTempo.d.ts b/_types/tempo/TxEnvelopeTempo.d.ts
index 0ce3256711f45249b4d42cdc2816701adb1a5cfd..267e8eccef9716121456b9708935bb1bac8d9a47 100644
--- a/_types/tempo/TxEnvelopeTempo.d.ts
+++ b/_types/tempo/TxEnvelopeTempo.d.ts
@@ -352,74 +352,25 @@ export declare namespace serialize {
type ErrorType = assert.ErrorType | Hex.fromNumber.ErrorType | Signature.toTuple.ErrorType | Hex.concat.ErrorType | Rlp.fromHex.ErrorType | Errors.GlobalErrorType;
}
/**
- * Returns the payload to sign for a {@link ox#TxEnvelopeTempo.TxEnvelopeTempo}.
- *
- * Computes the keccak256 hash of the unsigned serialized transaction. Sign this payload
- * with secp256k1, P256, or WebAuthn, then attach the signature via {@link ox#TxEnvelopeTempo.(from:function)}.
- *
- * [Tempo Transaction Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction)
+ * Encodes a {@link ox#TxEnvelopeTempo.TxEnvelopeTempo} for sender signing.
*
- * @example
- * The example below demonstrates how to compute the sign payload which can be used
- * with ECDSA signing utilities like {@link ox#Secp256k1.(sign:function)}.
- *
- * ```ts twoslash
- * // @noErrors
- * import { Secp256k1 } from 'ox'
- * import { TxEnvelopeTempo } from 'ox/tempo'
+ * Returns the raw serialized transaction bytes that are hashed by
+ * {@link ox#TxEnvelopeTempo.(getSignPayload:function)}. Sender signatures are
+ * stripped, and fee payer signatures are normalized to the sender pre-sign
+ * marker.
*
- * const envelope = TxEnvelopeTempo.from({
- * chainId: 1,
- * calls: [{
- * data: '0xdeadbeef',
- * to: 'tempox0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
- * }],
- * nonce: 0n,
- * maxFeePerGas: 1000000000n,
- * gas: 21000n,
- * })
- *
- * const payload = TxEnvelopeTempo.getSignPayload(envelope) // [!code focus]
- * // @log: '0x...'
- *
- * const signature = Secp256k1.sign({ payload, privateKey: '0x...' })
- * ```
- *
- * @example
- * ### Access Keys
- *
- * When signing as an access key on behalf of a root account, pass the
- * `from` option with the root account address. This computes
- * `keccak256(0x04 || sigHash || from)` which binds the signature to the
- * specific user account (V2 keychain format).
- *
- * ```ts twoslash
- * // @noErrors
- * import { Secp256k1 } from 'ox'
- * import { TxEnvelopeTempo, SignatureEnvelope } from 'ox/tempo'
- *
- * const envelope = TxEnvelopeTempo.from({
- * chainId: 1,
- * calls: [{
- * data: '0xdeadbeef',
- * to: 'tempox0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
- * }],
- * nonce: 0n,
- * maxFeePerGas: 1000000000n,
- * gas: 21000n,
- * })
- *
- * const payload = TxEnvelopeTempo.getSignPayload(envelope, { from: '0x...' }) // [!code focus]
- *
- * const signature = Secp256k1.sign({ payload, privateKey: '0x...' })
+ * @param envelope - The transaction envelope to encode for signing.
+ * @returns The serialized transaction bytes used as the sender signing preimage.
+ */
+export declare function encodeForSigning(envelope: TxEnvelopeTempo): encodeForSigning.ReturnValue;
+export declare namespace encodeForSigning {
+ type ReturnValue = Hex.Hex;
+ type ErrorType = serialize.ErrorType | Errors.GlobalErrorType;
+}
+/**
+ * Returns the payload to sign for a {@link ox#TxEnvelopeTempo.TxEnvelopeTempo}.
*
- * const signed = TxEnvelopeTempo.serialize(envelope, {
- * signature: SignatureEnvelope.from({
- * userAddress: from,
- * inner: SignatureEnvelope.from(signature),
- * }),
- * })
- * ```
+ * Computes the keccak256 hash of {@link ox#TxEnvelopeTempo.(encodeForSigning:function)}.
*
* @param envelope - The transaction envelope to get the sign payload for.
* @param options - Options.
@@ -484,7 +435,7 @@ export declare namespace hash {
presign?: presign | boolean | undefined;
};
type ReturnValue = Hex.Hex;
- type ErrorType = Hash.keccak256.ErrorType | serialize.ErrorType | Errors.GlobalErrorType;
+ type ErrorType = Hash.keccak256.ErrorType | serialize.ErrorType | encodeForSigning.ErrorType | Errors.GlobalErrorType;
}
/**
* Returns the fee payer payload to sign for a {@link ox#TxEnvelopeTempo.TxEnvelopeTempo}.
Loading