From 567725788ea80619024ec039213ec13866591189 Mon Sep 17 00:00:00 2001 From: Steven Truong Date: Thu, 6 Aug 2026 12:26:34 -0400 Subject: [PATCH] fix(tempo): included withdrawal fallback nonce --- .changeset/tidy-zones-withdraw.md | 5 ++++ src/tempo/WithdrawalSenderTag.test-d.ts | 1 + src/tempo/WithdrawalSenderTag.test.ts | 22 +++++++++++------- src/tempo/WithdrawalSenderTag.ts | 31 ++++++++++++++++++++----- 4 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 .changeset/tidy-zones-withdraw.md diff --git a/.changeset/tidy-zones-withdraw.md b/.changeset/tidy-zones-withdraw.md new file mode 100644 index 00000000..bc830945 --- /dev/null +++ b/.changeset/tidy-zones-withdraw.md @@ -0,0 +1,5 @@ +--- +'ox': patch +--- + +Fixed `WithdrawalSenderTag.from` to include the Zone withdrawal fallback nonce. diff --git a/src/tempo/WithdrawalSenderTag.test-d.ts b/src/tempo/WithdrawalSenderTag.test-d.ts index 7f817e8f..3154c61d 100644 --- a/src/tempo/WithdrawalSenderTag.test-d.ts +++ b/src/tempo/WithdrawalSenderTag.test-d.ts @@ -5,6 +5,7 @@ import { expectTypeOf, test } from 'vp/test' test('from', () => { expectTypeOf( WithdrawalSenderTag.from({ + fallbackNonce: 19n, sender: '0x1234567890abcdef1234567890abcdef12345678', transactionHash: '0xabababababababababababababababababababababababababababababababab', diff --git a/src/tempo/WithdrawalSenderTag.test.ts b/src/tempo/WithdrawalSenderTag.test.ts index d7e3e99e..2679e69b 100644 --- a/src/tempo/WithdrawalSenderTag.test.ts +++ b/src/tempo/WithdrawalSenderTag.test.ts @@ -1,22 +1,26 @@ import { WithdrawalSenderTag } from 'ox/tempo' import { expect, test } from 'vp/test' -test('from', () => { +test('from: production withdrawal', () => { const senderTag = WithdrawalSenderTag.from({ - sender: '0x1234567890abcdef1234567890abcdef12345678', + fallbackNonce: 19n, + sender: '0x0F0896dbf0465E5c07963301dcFEA1101Fa91EaC', transactionHash: - '0xabababababababababababababababababababababababababababababababab', + '0xae628bdc4bd24a9f9a917825a208baa16c384ab8a96a40cd5146bd20d9b3f6d9', }) expect(senderTag).toMatchInlineSnapshot( - `"0x3362fade7333b56b9f3582089ec5915b8a6f6ac13e73a7f90c169e3eb81d8a5e"`, + `"0xf1acbae45cd689281144042331e3379cf631a8d2db83057ccf38754a0b0108f2"`, ) }) -test('deterministic', () => { - const value = { - sender: '0x1234567890abcdef1234567890abcdef12345678', +test('from: internal deposit bounce-back', () => { + const senderTag = WithdrawalSenderTag.from({ + fallbackNonce: 0n, + sender: '0x0000000000000000000000000000000000000000', transactionHash: '0xabababababababababababababababababababababababababababababababab', - } as const - expect(WithdrawalSenderTag.from(value)).toBe(WithdrawalSenderTag.from(value)) + }) + expect(senderTag).toMatchInlineSnapshot( + `"0xa86d54e9aab41ae5e520ff0062ff1b4cbd0b2192bb01080a058bb170d84e6457"`, + ) }) diff --git a/src/tempo/WithdrawalSenderTag.ts b/src/tempo/WithdrawalSenderTag.ts index 49b479c2..2029bcab 100644 --- a/src/tempo/WithdrawalSenderTag.ts +++ b/src/tempo/WithdrawalSenderTag.ts @@ -8,8 +8,11 @@ import * as Hex from '../core/Hex.js' * `WithdrawalProcessed` event for a Zone withdrawal. * * The `transactionHash` is the Zone transaction containing the - * `ZoneOutbox.requestWithdrawal` call. The protocol defines the sender tag as - * `keccak256(abi.encodePacked(sender, transactionHash))`. + * `ZoneOutbox.requestWithdrawal` call. The protocol defines a user withdrawal + * sender tag as + * `keccak256(abi.encodePacked(sender, transactionHash, fallbackNonce))`. + * Internal deposit bounce-backs use the canonical zero-sender tag derived from + * `address(0)` and `bytes32(0)` without a fallback nonce. * * [Authenticated Withdrawals Specification](https://github.com/tempoxyz/zones/blob/main/specs/spec.md#authenticated-withdrawals) * @@ -18,27 +21,43 @@ import * as Hex from '../core/Hex.js' * import { WithdrawalSenderTag } from 'ox/tempo' * * const senderTag = WithdrawalSenderTag.from({ + * fallbackNonce: 19n, * sender: '0x1234567890abcdef1234567890abcdef12345678', * transactionHash: * '0xabababababababababababababababababababababababababababababababab' * }) * ``` * - * @param value - Withdrawal sender and Zone transaction hash. + * @param value - Withdrawal sender, Zone transaction hash, and fallback nonce. * @returns The sender tag. */ export function from(value: from.Value): Hex.Hex { - const { sender, transactionHash } = value + const { fallbackNonce, sender, transactionHash } = value + if ( + sender === '0x0000000000000000000000000000000000000000' && + fallbackNonce === 0n + ) + return Hash.keccak256( + AbiParameters.encodePacked( + ['address', 'bytes32'], + [ + sender, + '0x0000000000000000000000000000000000000000000000000000000000000000', + ], + ), + ) return Hash.keccak256( AbiParameters.encodePacked( - ['address', 'bytes32'], - [sender, transactionHash], + ['address', 'bytes32', 'uint64'], + [sender, transactionHash, fallbackNonce], ), ) } export declare namespace from { export type Value = { + /** Public nonce assigned to the withdrawal's private fallback recipient. */ + fallbackNonce: bigint /** Address that requested the withdrawal. */ sender: Address.Address /** Hash of the Zone transaction containing `ZoneOutbox.requestWithdrawal`. */