diff --git a/.changeset/tempo-sender-scoped-hash.md b/.changeset/tempo-sender-scoped-hash.md new file mode 100644 index 0000000000..15a96dde5e --- /dev/null +++ b/.changeset/tempo-sender-scoped-hash.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Added `Transaction.encodeForSigning` and `Transaction.getSenderScopedHash` helpers for Tempo TIP-1034 sender-scoped transaction hashes. diff --git a/package.json b/package.json index 72a35eb166..e374fd97ff 100644 --- a/package.json +++ b/package.json @@ -311,6 +311,9 @@ "bun", "protobufjs", "simple-git-hooks" - ] + ], + "patchedDependencies": { + "ox@0.14.20": "patches/ox@0.14.20.patch" + } } } diff --git a/patches/ox@0.14.20.patch b/patches/ox@0.14.20.patch new file mode 100644 index 0000000000..6b37de03fd --- /dev/null +++ b/patches/ox@0.14.20.patch @@ -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}. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c66b15a717..a5b99df71d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,6 +85,11 @@ overrides: basic-ftp@<=5.3.0: 5.3.1 fast-uri@<=3.1.1: 3.1.2 +patchedDependencies: + ox@0.14.20: + hash: f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266 + path: patches/ox@0.14.20.patch + importers: .: @@ -142,10 +147,10 @@ importers: version: 0.14.0 ox: specifier: 0.14.20 - version: 0.14.20(typescript@5.9.3)(zod@4.3.6) + version: 0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@4.3.6) permissionless: specifier: ^0.2.57 - version: 0.2.57(ox@0.14.20(typescript@5.9.3)(zod@4.3.6)) + version: 0.2.57(ox@0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@4.3.6)) prool: specifier: ~0.2.3 version: 0.2.3(@pimlico/alto@0.0.18(typescript@5.9.3))(testcontainers@11.10.0) @@ -652,7 +657,7 @@ importers: version: 1.0.7(ws@8.18.3) ox: specifier: 0.14.20 - version: 0.14.20(typescript@5.9.3)(zod@4.3.6) + version: 0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@4.3.6) ws: specifier: 8.18.3 version: 8.18.3 @@ -8912,7 +8917,7 @@ snapshots: idb-keyval: 6.2.2 mipd: 0.0.7(typescript@5.9.3) mppx: 0.6.5(@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1))(express@5.2.1)(hono@4.12.18)(typescript@5.9.3)(viem@src) - ox: 0.14.20(typescript@5.9.3)(zod@4.3.6) + ox: 0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@4.3.6) webauthx: 0.1.2(typescript@5.9.3)(zod@4.3.6) zod: 4.3.6 zustand: 5.0.13(@types/react@19.0.8)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) @@ -11327,7 +11332,7 @@ snapshots: transitivePeerDependencies: - zod - ox@0.14.20(typescript@5.9.3)(zod@3.25.76): + ox@0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@3.25.76): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -11342,7 +11347,7 @@ snapshots: transitivePeerDependencies: - zod - ox@0.14.20(typescript@5.9.3)(zod@4.3.6): + ox@0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@4.3.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -11479,9 +11484,9 @@ snapshots: pend@1.2.0: {} - permissionless@0.2.57(ox@0.14.20(typescript@5.9.3)(zod@4.3.6)): + permissionless@0.2.57(ox@0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@4.3.6)): optionalDependencies: - ox: 0.14.20(typescript@5.9.3)(zod@4.3.6) + ox: 0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@4.3.6) picocolors@1.1.1: {} @@ -12715,7 +12720,7 @@ snapshots: '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) isows: 1.0.7(ws@8.18.3) - ox: 0.14.20(typescript@5.9.3)(zod@3.25.76) + ox: 0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@3.25.76) ws: 8.18.3 optionalDependencies: typescript: 5.9.3 @@ -12732,7 +12737,7 @@ snapshots: '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) isows: 1.0.7(ws@8.18.3) - ox: 0.14.20(typescript@5.9.3)(zod@4.3.6) + ox: 0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@4.3.6) ws: 8.18.3 optionalDependencies: typescript: 5.9.3 @@ -12973,7 +12978,7 @@ snapshots: webauthx@0.1.2(typescript@5.9.3)(zod@4.3.6): dependencies: - ox: 0.14.20(typescript@5.9.3)(zod@4.3.6) + ox: 0.14.20(patch_hash=f4c04f74f3d20c6b2b65c7eb9bae0c15423fcf3984995a7445fc78d438452266)(typescript@5.9.3)(zod@4.3.6) transitivePeerDependencies: - typescript - zod diff --git a/src/tempo/Transaction.test.ts b/src/tempo/Transaction.test.ts index fd7679d6f8..1be6b27705 100644 --- a/src/tempo/Transaction.test.ts +++ b/src/tempo/Transaction.test.ts @@ -1,3 +1,5 @@ +import * as Hash from 'ox/Hash' +import * as Hex from 'ox/Hex' import { describe, expect, test } from 'vitest' import { accounts, feeToken, getClient } from '~test/tempo/config.js' import { prepareTransactionRequest, signTransaction } from '../actions/index.js' @@ -248,6 +250,76 @@ describe('serialize', () => { }) }) +describe('encodeForSigning', () => { + test('behavior: matches sender signing payload preimage', async () => { + const transaction = { + chainId: 1, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + nonce: 0, + type: 'tempo', + } as const + + const encoded = Transaction.encodeForSigning(transaction) + + expect(Hash.keccak256(encoded)).toBe( + Transaction.z_TxEnvelopeTempo.getSignPayload( + Transaction.z_TxEnvelopeTempo.deserialize( + (await Transaction.serialize(transaction)) as never, + ), + ), + ) + }) + + test('behavior: normalizes signatures and fee payer-selected fee token', async () => { + const transaction = { + chainId: 1, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + feePayer: true, + feeToken, + nonce: 0, + type: 'tempo', + } as const + const signed = Transaction.deserialize( + (await Transaction.serialize(transaction, { + type: 'secp256k1', + signature: { r: 1n, s: 1n, yParity: 0 }, + })) as `0x78${string}`, + ) + + expect( + Transaction.encodeForSigning({ + ...signed, + feePayerSignature: { r: '0x01', s: '0x01', yParity: 0 }, + feeToken: '0x0000000000000000000000000000000000000001', + }), + ).toBe(Transaction.encodeForSigning(transaction)) + }) +}) + +describe('getSenderScopedHash', () => { + test('behavior: hashes encodeForSigning with sender', () => { + const transaction = { + chainId: 1, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + nonce: 0, + type: 'tempo', + } as const + const sender = accounts.at(0)!.address + + expect(Transaction.getSenderScopedHash(transaction, { sender })).toBe( + Hash.keccak256( + Hex.concat(Transaction.encodeForSigning(transaction), sender), + ), + ) + expect(Transaction.getExpiringNonceHash(transaction, { sender })).toBe( + Transaction.getSenderScopedHash(transaction, { sender }), + ) + expect(Transaction.getChannelOpenContextHash(transaction, { sender })).toBe( + Transaction.getSenderScopedHash(transaction, { sender }), + ) + }) +}) + describe('signTransaction', () => { test('behavior: `feePayer` same as sender preserves from', async () => { const account = accounts.at(0)! diff --git a/src/tempo/Transaction.ts b/src/tempo/Transaction.ts index 7963877174..c7c7cf6929 100644 --- a/src/tempo/Transaction.ts +++ b/src/tempo/Transaction.ts @@ -1,6 +1,7 @@ // TODO: Find opportunities to make this file less duplicated + more simplified with Viem v3. import type { Address } from 'abitype' +import * as Hash from 'ox/Hash' import * as Hex from 'ox/Hex' import * as Secp256k1 from 'ox/Secp256k1' import * as Signature from 'ox/Signature' @@ -9,7 +10,7 @@ import { type KeyAuthorization, type TransactionReceipt as ox_TransactionReceipt, SignatureEnvelope, - type TempoAddress, + TempoAddress, TxEnvelopeTempo as TxTempo, } from 'ox/tempo' import type { Account } from '../accounts/types.js' @@ -213,6 +214,43 @@ export declare namespace deserialize { : ParseTransactionReturnType } +export function encodeForSigning( + transaction: TransactionSerializableTempo & { + feePayer?: Account | true | undefined + }, +): encodeForSigning.ReturnValue { + return TxTempo.encodeForSigning(getTempoEnvelope(transaction)) +} + +export declare namespace encodeForSigning { + type ReturnValue = Hex.Hex +} + +export function getSenderScopedHash( + transaction: TransactionSerializableTempo & { + feePayer?: Account | true | undefined + }, + options: getSenderScopedHash.Options, +): getSenderScopedHash.ReturnValue { + return Hash.keccak256( + Hex.concat( + encodeForSigning(transaction), + TempoAddress.resolve(options.sender), + ), + ) +} + +export declare namespace getSenderScopedHash { + type Options = { + sender: TempoAddress.Address + } + + type ReturnValue = Hex.Hex +} + +export const getExpiringNonceHash = getSenderScopedHash +export const getChannelOpenContextHash = getSenderScopedHash + export async function serialize( transaction: TransactionSerializable & { feePayer?: Account | true | undefined @@ -270,26 +308,11 @@ function deserializeTempo( } satisfies TransactionSerializableTempo } -/** @internal */ -async function serializeTempo( +function getTempoEnvelope( transaction: TransactionSerializableTempo & { feePayer?: Account | true | undefined - from?: Address | undefined }, - sig?: OneOf | undefined, -) { - const signature = (() => { - if (transaction.signature) return transaction.signature - if (sig && 'type' in sig) return sig as SignatureEnvelope.SignatureEnvelope - if (sig) - return SignatureEnvelope.from({ - r: BigInt(sig.r!), - s: BigInt(sig.s!), - yParity: Number(sig.yParity!), - }) - return undefined - })() - +): TxTempo.TxEnvelopeTempo { const { chainId, feePayer, nonce, ...rest } = transaction const feePayerSignature = (() => { @@ -304,7 +327,7 @@ async function serializeTempo( return undefined })() - const transaction_ox = { + const envelope = { ...rest, calls: rest.calls?.length ? rest.calls @@ -328,7 +351,33 @@ async function serializeTempo( // If we have marked the transaction as intended to be paid // by a fee payer (feePayer: true), we will not use the fee token // as the fee payer will choose their fee token. - if (feePayer === true) delete transaction_ox.feeToken + if (feePayer === true) delete envelope.feeToken + + return envelope +} + +/** @internal */ +async function serializeTempo( + transaction: TransactionSerializableTempo & { + feePayer?: Account | true | undefined + from?: Address | undefined + }, + sig?: OneOf | undefined, +) { + const signature = (() => { + if (transaction.signature) return transaction.signature + if (sig && 'type' in sig) return sig as SignatureEnvelope.SignatureEnvelope + if (sig) + return SignatureEnvelope.from({ + r: BigInt(sig.r!), + s: BigInt(sig.s!), + yParity: Number(sig.yParity!), + }) + return undefined + })() + + const { feePayer } = transaction + const transaction_ox = getTempoEnvelope(transaction) if (signature && typeof transaction.feePayer === 'object') { const tx = TxTempo.from(transaction_ox, {