mppx 0.5.12 → 0.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/server/internal/html/config.d.ts.map +1 -1
  3. package/dist/server/internal/html/config.js +8 -1
  4. package/dist/server/internal/html/config.js.map +1 -1
  5. package/dist/tempo/Methods.d.ts +8 -0
  6. package/dist/tempo/Methods.d.ts.map +1 -1
  7. package/dist/tempo/Methods.js +6 -2
  8. package/dist/tempo/Methods.js.map +1 -1
  9. package/dist/tempo/client/Charge.d.ts +11 -1
  10. package/dist/tempo/client/Charge.d.ts.map +1 -1
  11. package/dist/tempo/client/Charge.js +14 -2
  12. package/dist/tempo/client/Charge.js.map +1 -1
  13. package/dist/tempo/client/Methods.d.ts +6 -0
  14. package/dist/tempo/client/Methods.d.ts.map +1 -1
  15. package/dist/tempo/internal/fee-payer.d.ts +8 -0
  16. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  17. package/dist/tempo/internal/fee-payer.js +31 -4
  18. package/dist/tempo/internal/fee-payer.js.map +1 -1
  19. package/dist/tempo/server/Charge.d.ts +17 -0
  20. package/dist/tempo/server/Charge.d.ts.map +1 -1
  21. package/dist/tempo/server/Charge.js +13 -1
  22. package/dist/tempo/server/Charge.js.map +1 -1
  23. package/dist/tempo/server/Methods.d.ts +6 -0
  24. package/dist/tempo/server/Methods.d.ts.map +1 -1
  25. package/dist/tempo/server/Session.d.ts +4 -0
  26. package/dist/tempo/server/Session.d.ts.map +1 -1
  27. package/dist/tempo/server/Session.js +36 -28
  28. package/dist/tempo/server/Session.js.map +1 -1
  29. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  30. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  31. package/dist/tempo/server/internal/html.gen.js +1 -1
  32. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  33. package/dist/tempo/session/Chain.d.ts +5 -0
  34. package/dist/tempo/session/Chain.d.ts.map +1 -1
  35. package/dist/tempo/session/Chain.js +202 -63
  36. package/dist/tempo/session/Chain.js.map +1 -1
  37. package/dist/tempo/session/ChannelStore.d.ts +1 -0
  38. package/dist/tempo/session/ChannelStore.d.ts.map +1 -1
  39. package/dist/tempo/session/ChannelStore.js +38 -15
  40. package/dist/tempo/session/ChannelStore.js.map +1 -1
  41. package/package.json +2 -2
  42. package/src/server/Transport.test.ts +20 -0
  43. package/src/server/internal/html/config.ts +9 -1
  44. package/src/tempo/Methods.test.ts +25 -0
  45. package/src/tempo/Methods.ts +30 -22
  46. package/src/tempo/client/Charge.ts +20 -6
  47. package/src/tempo/internal/fee-payer.test.ts +122 -12
  48. package/src/tempo/internal/fee-payer.ts +49 -4
  49. package/src/tempo/server/Charge.test.ts +259 -1
  50. package/src/tempo/server/Charge.ts +31 -0
  51. package/src/tempo/server/Session.test.ts +130 -1
  52. package/src/tempo/server/Session.ts +41 -35
  53. package/src/tempo/server/internal/html/main.ts +2 -2
  54. package/src/tempo/server/internal/html.gen.ts +1 -1
  55. package/src/tempo/session/Chain.test.ts +225 -2
  56. package/src/tempo/session/Chain.ts +250 -65
  57. package/src/tempo/session/ChannelStore.test.ts +23 -0
  58. package/src/tempo/session/ChannelStore.ts +46 -13
@@ -39,6 +39,31 @@ describe('charge', () => {
39
39
  expect(result.success).toBe(true)
40
40
  })
41
41
 
42
+ test('schema: validates request with supportedModes', () => {
43
+ const result = Methods.charge.schema.request.safeParse({
44
+ amount: '1',
45
+ currency: '0x20c0000000000000000000000000000000000001',
46
+ decimals: 6,
47
+ recipient: '0x1234567890abcdef1234567890abcdef12345678',
48
+ supportedModes: ['pull'],
49
+ })
50
+ expect(result.success).toBe(true)
51
+ if (!result.success) return
52
+
53
+ expect(result.data.methodDetails?.supportedModes).toEqual(['pull'])
54
+ })
55
+
56
+ test('schema: rejects empty supportedModes', () => {
57
+ const result = Methods.charge.schema.request.safeParse({
58
+ amount: '1',
59
+ currency: '0x20c0000000000000000000000000000000000001',
60
+ decimals: 6,
61
+ recipient: '0x1234567890abcdef1234567890abcdef12345678',
62
+ supportedModes: [],
63
+ })
64
+ expect(result.success).toBe(false)
65
+ })
66
+
42
67
  test('schema: validates request with memo', () => {
43
68
  const result = Methods.charge.schema.request.safeParse({
44
69
  amount: '1',
@@ -4,6 +4,9 @@ import { parseUnits } from 'viem'
4
4
  import * as Method from '../Method.js'
5
5
  import * as z from '../zod.js'
6
6
 
7
+ export const chargeModes = ['push', 'pull'] as const
8
+ export type ChargeMode = (typeof chargeModes)[number]
9
+
7
10
  const split = z.object({
8
11
  amount: z.amount(),
9
12
  memo: z.optional(z.hash()),
@@ -47,6 +50,7 @@ export const charge = Method.from({
47
50
  memo: z.optional(z.hash()),
48
51
  recipient: z.optional(z.string()),
49
52
  splits: z.optional(z.array(split).check(z.minLength(1), z.maxLength(10))),
53
+ supportedModes: z.optional(z.array(z.enum(chargeModes)).check(z.minLength(1))),
50
54
  })
51
55
  .check(
52
56
  z.refine(({ amount, decimals, splits }) => {
@@ -64,28 +68,32 @@ export const charge = Method.from({
64
68
  )
65
69
  }, 'Invalid splits'),
66
70
  ),
67
- z.transform(({ amount, chainId, decimals, feePayer, memo, splits, ...rest }) => ({
68
- ...rest,
69
- amount: parseUnits(amount, decimals).toString(),
70
- ...(chainId !== undefined ||
71
- feePayer !== undefined ||
72
- memo !== undefined ||
73
- splits !== undefined
74
- ? {
75
- methodDetails: {
76
- ...(chainId !== undefined && { chainId }),
77
- ...(feePayer !== undefined && { feePayer }),
78
- ...(memo !== undefined && { memo }),
79
- ...(splits !== undefined && {
80
- splits: splits.map((split) => ({
81
- ...split,
82
- amount: parseUnits(split.amount, decimals).toString(),
83
- })),
84
- }),
85
- },
86
- }
87
- : {}),
88
- })),
71
+ z.transform(
72
+ ({ amount, chainId, decimals, feePayer, memo, splits, supportedModes, ...rest }) => ({
73
+ ...rest,
74
+ amount: parseUnits(amount, decimals).toString(),
75
+ ...(chainId !== undefined ||
76
+ feePayer !== undefined ||
77
+ memo !== undefined ||
78
+ splits !== undefined ||
79
+ supportedModes !== undefined
80
+ ? {
81
+ methodDetails: {
82
+ ...(chainId !== undefined && { chainId }),
83
+ ...(feePayer !== undefined && { feePayer }),
84
+ ...(memo !== undefined && { memo }),
85
+ ...(splits !== undefined && {
86
+ splits: splits.map((split) => ({
87
+ ...split,
88
+ amount: parseUnits(split.amount, decimals).toString(),
89
+ })),
90
+ }),
91
+ ...(supportedModes !== undefined && { supportedModes }),
92
+ },
93
+ }
94
+ : {}),
95
+ }),
96
+ ),
89
97
  ),
90
98
  },
91
99
  })
@@ -47,7 +47,7 @@ export function charge(parameters: charge.Parameters = {}) {
47
47
  context: z.object({
48
48
  account: z.optional(z.custom<Account.getResolver.Parameters['account']>()),
49
49
  autoSwap: z.optional(z.custom<charge.AutoSwap>()),
50
- mode: z.optional(z.enum(['push', 'pull'])),
50
+ mode: z.optional(z.enum(Methods.chargeModes)),
51
51
  }),
52
52
 
53
53
  async createCredential({ challenge, context }) {
@@ -74,11 +74,7 @@ export function charge(parameters: charge.Parameters = {}) {
74
74
  })
75
75
  }
76
76
 
77
- const mode =
78
- context?.mode ?? parameters.mode ?? (account.type === 'json-rpc' ? 'push' : 'pull')
79
-
80
77
  const currency = request.currency as Address
81
-
82
78
  if (parameters.expectedRecipients) {
83
79
  const allowed = new Set(parameters.expectedRecipients.map((a) => a.toLowerCase()))
84
80
  const splits = methodDetails?.splits as readonly { recipient: string }[] | undefined
@@ -89,6 +85,21 @@ export function charge(parameters: charge.Parameters = {}) {
89
85
  }
90
86
  }
91
87
  }
88
+ const supportedModes = (methodDetails?.supportedModes as
89
+ | readonly Methods.ChargeMode[]
90
+ | undefined) ?? ['pull', 'push']
91
+ const mode = (() => {
92
+ const explicitMode = context?.mode ?? parameters.mode
93
+ if (explicitMode) {
94
+ if (!supportedModes.includes(explicitMode))
95
+ throw new Error(`Challenge does not support ${explicitMode} mode.`)
96
+ return explicitMode
97
+ }
98
+
99
+ const preferredMode = account.type === 'json-rpc' ? 'push' : 'pull'
100
+ if (supportedModes.includes(preferredMode)) return preferredMode
101
+ return supportedModes[0]!
102
+ })()
92
103
 
93
104
  const memo = methodDetails?.memo
94
105
  ? (methodDetails.memo as Hex.Hex)
@@ -193,9 +204,12 @@ export declare namespace charge {
193
204
  * - `'push'`: Client broadcasts the transaction and sends the tx hash to the server.
194
205
  * - `'pull'`: Client signs the transaction and sends the serialized tx to the server for broadcast.
195
206
  *
207
+ * If the server advertises `supportedModes`, this setting must be one of
208
+ * the supported values for the challenge.
209
+ *
196
210
  * @default `'push'` for JSON-RPC accounts, `'pull'` for local accounts.
197
211
  */
198
- mode?: 'push' | 'pull' | undefined
212
+ mode?: Methods.ChargeMode | undefined
199
213
  } & Account.getResolver.Parameters &
200
214
  Client.getResolver.Parameters
201
215
  }
@@ -12,6 +12,13 @@ import * as Selectors from './selectors.js'
12
12
 
13
13
  const details = { amount: '1', currency: '0x01', recipient: '0x02' }
14
14
  const bogus = '0x0000000000000000000000000000000000000001' as const
15
+ const swapTokenIn = '0x0000000000000000000000000000000000000003' as const
16
+ const swapTokenOut = '0x0000000000000000000000000000000000000004' as const
17
+ const swapData = encodeFunctionData({
18
+ abi: Abis.stablecoinDex,
19
+ functionName: 'swapExactAmountOut',
20
+ args: [swapTokenIn, swapTokenOut, 100n, 100n],
21
+ })
15
22
  const sponsor = { address: bogus, type: 'local' } as any
16
23
 
17
24
  describe('callScopes', () => {
@@ -48,11 +55,11 @@ describe('validateCalls', () => {
48
55
  })
49
56
 
50
57
  test('accepts approve + buy + transfer', () => {
51
- const swapSelector = Selectors.swapExactAmountOut
52
58
  expect(() =>
53
59
  validateCalls(
54
60
  [
55
61
  {
62
+ to: swapTokenIn,
56
63
  data: encodeFunctionData({
57
64
  abi: Abis.tip20,
58
65
  functionName: 'approve',
@@ -61,7 +68,7 @@ describe('validateCalls', () => {
61
68
  },
62
69
  {
63
70
  to: Addresses.stablecoinDex,
64
- data: `${swapSelector}${'00'.repeat(128)}` as `0x${string}`,
71
+ data: swapData,
65
72
  },
66
73
  {
67
74
  data: encodeFunctionData({
@@ -77,11 +84,11 @@ describe('validateCalls', () => {
77
84
  })
78
85
 
79
86
  test('accepts multiple transfers after swap prefix', () => {
80
- const swapSelector = Selectors.swapExactAmountOut
81
87
  expect(() =>
82
88
  validateCalls(
83
89
  [
84
90
  {
91
+ to: swapTokenIn,
85
92
  data: encodeFunctionData({
86
93
  abi: Abis.tip20,
87
94
  functionName: 'approve',
@@ -90,7 +97,7 @@ describe('validateCalls', () => {
90
97
  },
91
98
  {
92
99
  to: Addresses.stablecoinDex,
93
- data: `${swapSelector}${'00'.repeat(128)}` as `0x${string}`,
100
+ data: swapData,
94
101
  },
95
102
  {
96
103
  data: encodeFunctionData({
@@ -142,7 +149,6 @@ describe('validateCalls', () => {
142
149
  })
143
150
 
144
151
  test('error: rejects wrong order (transfer before approve + buy)', () => {
145
- const swapSelector = Selectors.swapExactAmountOut
146
152
  expect(() =>
147
153
  validateCalls(
148
154
  [
@@ -154,6 +160,7 @@ describe('validateCalls', () => {
154
160
  }),
155
161
  },
156
162
  {
163
+ to: swapTokenIn,
157
164
  data: encodeFunctionData({
158
165
  abi: Abis.tip20,
159
166
  functionName: 'approve',
@@ -162,7 +169,7 @@ describe('validateCalls', () => {
162
169
  },
163
170
  {
164
171
  to: Addresses.stablecoinDex,
165
- data: `${swapSelector}${'00'.repeat(128)}` as `0x${string}`,
172
+ data: swapData,
166
173
  },
167
174
  ],
168
175
  details,
@@ -171,11 +178,11 @@ describe('validateCalls', () => {
171
178
  })
172
179
 
173
180
  test('error: rejects approve with non-DEX spender', () => {
174
- const swapSelector = Selectors.swapExactAmountOut
175
181
  expect(() =>
176
182
  validateCalls(
177
183
  [
178
184
  {
185
+ to: swapTokenIn,
179
186
  data: encodeFunctionData({
180
187
  abi: Abis.tip20,
181
188
  functionName: 'approve',
@@ -184,7 +191,7 @@ describe('validateCalls', () => {
184
191
  },
185
192
  {
186
193
  to: Addresses.stablecoinDex,
187
- data: `${swapSelector}${'00'.repeat(128)}` as `0x${string}`,
194
+ data: swapData,
188
195
  },
189
196
  {
190
197
  data: encodeFunctionData({
@@ -199,19 +206,48 @@ describe('validateCalls', () => {
199
206
  ).toThrow('approve spender is not the DEX')
200
207
  })
201
208
 
209
+ test('behavior: rejects approve targeting a non-token contract', () => {
210
+ expect(() =>
211
+ validateCalls(
212
+ [
213
+ {
214
+ to: bogus,
215
+ data: encodeFunctionData({
216
+ abi: Abis.tip20,
217
+ functionName: 'approve',
218
+ args: [Addresses.stablecoinDex, 100n],
219
+ }),
220
+ },
221
+ {
222
+ to: Addresses.stablecoinDex,
223
+ data: swapData,
224
+ },
225
+ {
226
+ data: encodeFunctionData({
227
+ abi: Abis.tip20,
228
+ functionName: 'transfer',
229
+ args: [bogus, 100n],
230
+ }),
231
+ },
232
+ ],
233
+ details,
234
+ ),
235
+ ).toThrow(FeePayerValidationError)
236
+ })
237
+
202
238
  test('error: rejects buy targeting non-DEX address', () => {
203
- const swapSelector = Selectors.swapExactAmountOut
204
239
  expect(() =>
205
240
  validateCalls(
206
241
  [
207
242
  {
243
+ to: swapTokenIn,
208
244
  data: encodeFunctionData({
209
245
  abi: Abis.tip20,
210
246
  functionName: 'approve',
211
247
  args: [Addresses.stablecoinDex, 100n],
212
248
  }),
213
249
  },
214
- { to: bogus, data: `${swapSelector}${'00'.repeat(128)}` as `0x${string}` },
250
+ { to: bogus, data: swapData },
215
251
  {
216
252
  data: encodeFunctionData({
217
253
  abi: Abis.tip20,
@@ -226,11 +262,11 @@ describe('validateCalls', () => {
226
262
  })
227
263
 
228
264
  test('error: rejects approve + buy without transfer', () => {
229
- const swapSelector = Selectors.swapExactAmountOut
230
265
  expect(() =>
231
266
  validateCalls(
232
267
  [
233
268
  {
269
+ to: swapTokenIn,
234
270
  data: encodeFunctionData({
235
271
  abi: Abis.tip20,
236
272
  functionName: 'approve',
@@ -239,7 +275,7 @@ describe('validateCalls', () => {
239
275
  },
240
276
  {
241
277
  to: Addresses.stablecoinDex,
242
- data: `${swapSelector}${'00'.repeat(128)}` as `0x${string}`,
278
+ data: swapData,
243
279
  },
244
280
  ],
245
281
  details,
@@ -285,6 +321,80 @@ describe('prepareSponsoredTransaction', () => {
285
321
  ).not.toThrow()
286
322
  })
287
323
 
324
+ test('accepts higher Moderato priority fees by default', () => {
325
+ expect(() =>
326
+ prepareSponsoredTransaction({
327
+ account: sponsor,
328
+ chainId: 42431,
329
+ details,
330
+ expectedFeeToken: bogus,
331
+ transaction: {
332
+ ...baseTransaction,
333
+ gas: 626_497n,
334
+ maxFeePerGas: 24_000_000_000n,
335
+ maxPriorityFeePerGas: 24_000_000_000n,
336
+ } as any,
337
+ }),
338
+ ).not.toThrow()
339
+ })
340
+
341
+ test('accepts fee-payer policy overrides', () => {
342
+ expect(() =>
343
+ prepareSponsoredTransaction({
344
+ account: sponsor,
345
+ chainId: 4217,
346
+ details,
347
+ expectedFeeToken: bogus,
348
+ policy: { maxPriorityFeePerGas: 50_000_000_000n },
349
+ transaction: {
350
+ ...baseTransaction,
351
+ chainId: 4217,
352
+ gas: 626_497n,
353
+ maxFeePerGas: 24_000_000_000n,
354
+ maxPriorityFeePerGas: 24_000_000_000n,
355
+ } as any,
356
+ }),
357
+ ).not.toThrow()
358
+ })
359
+
360
+ test('error: rejects excessive priority fee under a custom policy override', () => {
361
+ expect(() =>
362
+ prepareSponsoredTransaction({
363
+ account: sponsor,
364
+ chainId: 4217,
365
+ details,
366
+ expectedFeeToken: bogus,
367
+ policy: { maxPriorityFeePerGas: 20_000_000_000n },
368
+ transaction: {
369
+ ...baseTransaction,
370
+ chainId: 4217,
371
+ gas: 626_497n,
372
+ maxFeePerGas: 24_000_000_000n,
373
+ maxPriorityFeePerGas: 24_000_000_000n,
374
+ } as any,
375
+ }),
376
+ ).toThrow('maxPriorityFeePerGas exceeds sponsor policy')
377
+ })
378
+
379
+ test('ignores undefined policy override values', () => {
380
+ expect(() =>
381
+ prepareSponsoredTransaction({
382
+ account: sponsor,
383
+ chainId: 4217,
384
+ details,
385
+ expectedFeeToken: bogus,
386
+ policy: { maxPriorityFeePerGas: undefined } as any,
387
+ transaction: {
388
+ ...baseTransaction,
389
+ chainId: 4217,
390
+ gas: 626_497n,
391
+ maxFeePerGas: 24_000_000_000n,
392
+ maxPriorityFeePerGas: 24_000_000_000n,
393
+ } as any,
394
+ }),
395
+ ).toThrow('maxPriorityFeePerGas exceeds sponsor policy')
396
+ })
397
+
288
398
  test('drops unknown top-level fields from the sponsored transaction', () => {
289
399
  const sponsored = prepareSponsoredTransaction({
290
400
  account: sponsor,
@@ -5,6 +5,7 @@ import { decodeFunctionData } from 'viem'
5
5
  import { Abis, Addresses, Transaction } from 'viem/tempo'
6
6
 
7
7
  import * as TempoAddress_internal from './address.js'
8
+ import * as defaults from './defaults.js'
8
9
  import * as Selectors from './selectors.js'
9
10
 
10
11
  /** Returns true if the serialized transaction has a Tempo envelope prefix. */
@@ -26,17 +27,47 @@ export const callScopes = [
26
27
  [Selectors.approve, Selectors.swapExactAmountOut, Selectors.transferWithMemo],
27
28
  ]
28
29
 
30
+ export type Policy = {
31
+ maxGas: bigint
32
+ maxFeePerGas: bigint
33
+ maxPriorityFeePerGas: bigint
34
+ maxTotalFee: bigint
35
+ maxValidityWindowSeconds: number
36
+ }
37
+
29
38
  /**
30
39
  * maxTotalFee must be high enough to cover `transferWithMemo` and
31
40
  * swap transactions at peak gas prices. Bumped from 0.01 ETH in #327.
32
41
  */
33
- const policy = {
42
+ const defaultPolicy: Policy = {
34
43
  maxGas: 2_000_000n,
35
44
  maxFeePerGas: 100_000_000_000n,
36
45
  maxPriorityFeePerGas: 10_000_000_000n,
37
46
  maxTotalFee: 50_000_000_000_000_000n,
38
47
  maxValidityWindowSeconds: 15 * 60,
39
- } as const
48
+ }
49
+
50
+ const policyByChainId = {
51
+ [defaults.chainId.mainnet]: defaultPolicy,
52
+ // Moderato regularly needs a higher priority fee than mainnet.
53
+ [defaults.chainId.testnet]: {
54
+ ...defaultPolicy,
55
+ maxPriorityFeePerGas: 50_000_000_000n,
56
+ },
57
+ } as const satisfies Record<defaults.ChainId, Policy>
58
+
59
+ function getPolicy(chainId: number, overrides: Partial<Policy> | undefined): Policy {
60
+ const base = policyByChainId[chainId as defaults.ChainId] ?? defaultPolicy
61
+ if (!overrides) return base
62
+
63
+ return {
64
+ maxGas: overrides.maxGas ?? base.maxGas,
65
+ maxFeePerGas: overrides.maxFeePerGas ?? base.maxFeePerGas,
66
+ maxPriorityFeePerGas: overrides.maxPriorityFeePerGas ?? base.maxPriorityFeePerGas,
67
+ maxTotalFee: overrides.maxTotalFee ?? base.maxTotalFee,
68
+ maxValidityWindowSeconds: overrides.maxValidityWindowSeconds ?? base.maxValidityWindowSeconds,
69
+ }
70
+ }
40
71
 
41
72
  /** Validates that a set of transaction calls matches an allowed fee-payer pattern. */
42
73
  export function validateCalls(
@@ -67,14 +98,25 @@ export function validateCalls(
67
98
  throw new FeePayerValidationError('disallowed call pattern in fee-payer transaction', details)
68
99
  }
69
100
 
70
- // Validate approve spender and buy target are the DEX.
101
+ // Bind the swap approval to the token the DEX call will actually spend.
102
+ const buyCall = calls.find((c) => c.data?.slice(0, 10) === Selectors.swapExactAmountOut)
103
+ const buyArgs = buyCall
104
+ ? (decodeFunctionData({ abi: Abis.stablecoinDex, data: buyCall.data! }).args as [
105
+ `0x${string}`,
106
+ `0x${string}`,
107
+ bigint,
108
+ bigint,
109
+ ])
110
+ : undefined
111
+
71
112
  const approveCall = calls.find((c) => c.data?.slice(0, 10) === Selectors.approve)
72
113
  if (approveCall) {
73
114
  const { args } = decodeFunctionData({ abi: Abis.tip20, data: approveCall.data! })
115
+ if (!approveCall.to || (buyArgs && !TempoAddress_internal.isEqual(approveCall.to, buyArgs[0])))
116
+ throw new FeePayerValidationError('approve target does not match swap tokenIn', details)
74
117
  if (!TempoAddress_internal.isEqual((args as [`0x${string}`])[0]!, Addresses.stablecoinDex))
75
118
  throw new FeePayerValidationError('approve spender is not the DEX', details)
76
119
  }
77
- const buyCall = calls.find((c) => c.data?.slice(0, 10) === Selectors.swapExactAmountOut)
78
120
  if (
79
121
  buyCall &&
80
122
  (!buyCall.to || !TempoAddress_internal.isEqual(buyCall.to, Addresses.stablecoinDex))
@@ -89,6 +131,7 @@ export function prepareSponsoredTransaction(parameters: {
89
131
  details: Record<string, string>
90
132
  expectedFeeToken?: TempoAddress.Address | undefined
91
133
  now?: Date | undefined
134
+ policy?: Partial<Policy> | undefined
92
135
  transaction: ReturnType<(typeof Transaction)['deserialize']>
93
136
  }) {
94
137
  const {
@@ -98,8 +141,10 @@ export function prepareSponsoredTransaction(parameters: {
98
141
  details,
99
142
  expectedFeeToken,
100
143
  now = new Date(),
144
+ policy: policyOverrides,
101
145
  transaction,
102
146
  } = parameters
147
+ const policy = getPolicy(chainId, policyOverrides)
103
148
 
104
149
  const {
105
150
  accessList,