Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/veria-306-expiring-nonce-key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

Required expiring nonce keys for fee-sponsored transactions.
34 changes: 32 additions & 2 deletions src/tempo/internal/fee-payer.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { encodeFunctionData } from 'viem'
import { encodeFunctionData, maxUint256 } from 'viem'
import { Abis, Addresses } from 'viem/tempo'
import { describe, expect, test } from 'vp/test'

Expand Down Expand Up @@ -443,7 +443,7 @@ describe('prepareSponsoredTransaction', () => {
maxFeePerGas: 1_000_000_000n,
maxPriorityFeePerGas: 1_000_000_000n,
nonce: 1n,
nonceKey: 1n,
nonceKey: 'expiring',
signature: { r: 1n, s: 1n, yParity: 0 } as any,
validBefore: Math.floor(Date.now() / 1_000) + 300,
} as const
Expand All @@ -460,6 +460,36 @@ describe('prepareSponsoredTransaction', () => {
).not.toThrow()
})

test('accepts serialized expiring nonce key', () => {
expect(() =>
prepareSponsoredTransaction({
account: sponsor,
chainId: 42431,
details,
expectedFeeToken: bogus,
transaction: {
...baseTransaction,
nonceKey: maxUint256,
} as any,
}),
).not.toThrow()
})

test('error: rejects non-expiring nonce keys', () => {
expect(() =>
prepareSponsoredTransaction({
account: sponsor,
chainId: 42431,
details,
expectedFeeToken: bogus,
transaction: {
...baseTransaction,
nonceKey: 1n,
} as any,
}),
).toThrow('must use an expiring nonce')
})

test('accepts higher Moderato priority fees by default', () => {
expect(() =>
prepareSponsoredTransaction({
Expand Down
8 changes: 6 additions & 2 deletions src/tempo/internal/fee-payer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { TempoAddress } from 'ox/tempo'
import { TxEnvelopeTempo } from 'ox/tempo'
import type { Hex } from 'viem'
import type { Account } from 'viem'
import { decodeFunctionData } from 'viem'
import { decodeFunctionData, maxUint256 } from 'viem'
import { Abis, Addresses, Transaction } from 'viem/tempo'

import * as TempoAddress_internal from './address.js'
Expand Down Expand Up @@ -126,6 +126,10 @@ function getPolicy(chainId: number, overrides: Partial<Policy> | undefined): Pol
}
}

function isExpiringNonceKey(nonceKey: SponsoredTransaction['nonceKey']): boolean {
return nonceKey === 'expiring' || nonceKey === maxUint256
}

/** Validates that a set of transaction calls matches an allowed fee-payer pattern. */
export function validateCalls(
calls: readonly { data?: `0x${string}` | undefined; to?: TempoAddress.Address | undefined }[],
Expand Down Expand Up @@ -358,7 +362,7 @@ export function prepareSponsoredTransaction(parameters: {
maxPriorityFeePerGas: maxPriorityFeePerGas.toString(),
})

if (nonceKey === undefined) fail('fee-sponsored transaction must use an expiring nonce')
if (!isExpiringNonceKey(nonceKey)) fail('fee-sponsored transaction must use an expiring nonce')
if (validBefore === undefined)
fail('fee-sponsored transaction must declare validBefore for the expiring nonce')
const validBeforeValue = validBefore
Expand Down
Loading