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
@@ -15,7 +15,7 @@ import { Abis, Account, Actions, Addresses, Secp256k1, Tick, Transaction } from
15
15
  import { beforeAll, describe, expect, test } from 'vp/test'
16
16
  import * as Http from '~test/Http.js'
17
17
  import { closeChannelOnChain, deployEscrow, openChannel } from '~test/tempo/session.js'
18
- import { accounts, asset, chain, client, fundAccount } from '~test/tempo/viem.js'
18
+ import { accounts, asset, chain, client, fundAccount, http } from '~test/tempo/viem.js'
19
19
 
20
20
  import * as Store from '../../Store.js'
21
21
  import * as Attribution from '../Attribution.js'
@@ -124,6 +124,166 @@ describe('tempo', () => {
124
124
  httpServer.close()
125
125
  })
126
126
 
127
+ test('behavior: client rejects unsupported explicit push mode', async () => {
128
+ const mppx = Mppx_client.create({
129
+ polyfill: false,
130
+ methods: [
131
+ tempo_client({
132
+ account: accounts[1],
133
+ mode: 'push',
134
+ getClient: () => client,
135
+ }),
136
+ ],
137
+ })
138
+
139
+ const httpServer = await Http.createServer(async (req, res) => {
140
+ const result = await Mppx_server.toNodeListener(
141
+ server.charge({ amount: '1', decimals: 6, supportedModes: ['pull'] }),
142
+ )(req, res)
143
+ if (result.status === 402) return
144
+ res.end('OK')
145
+ })
146
+
147
+ const response = await fetch(httpServer.url)
148
+ expect(response.status).toBe(402)
149
+
150
+ await expect(mppx.createCredential(response)).rejects.toThrow(
151
+ 'Challenge does not support push mode.',
152
+ )
153
+
154
+ httpServer.close()
155
+ })
156
+
157
+ test('behavior: falls back to pull when push is not advertised', async () => {
158
+ const jsonRpcClient = createClient({
159
+ account: accounts[0].address,
160
+ chain,
161
+ transport: http(),
162
+ })
163
+
164
+ const mppx = Mppx_client.create({
165
+ polyfill: false,
166
+ methods: [
167
+ tempo_client({
168
+ getClient: () => jsonRpcClient,
169
+ }),
170
+ ],
171
+ })
172
+
173
+ const httpServer = await Http.createServer(async (req, res) => {
174
+ const result = await Mppx_server.toNodeListener(
175
+ server.charge({
176
+ amount: '1',
177
+ decimals: 6,
178
+ recipient: accounts[2].address,
179
+ supportedModes: ['pull'],
180
+ }),
181
+ )(req, res)
182
+ if (result.status === 402) return
183
+ res.end('OK')
184
+ })
185
+
186
+ const response = await fetch(httpServer.url)
187
+ expect(response.status).toBe(402)
188
+
189
+ const credential = Credential.deserialize<{ type: 'hash' | 'proof' | 'transaction' }>(
190
+ await mppx.createCredential(response),
191
+ )
192
+ expect(credential.payload.type).toBe('transaction')
193
+
194
+ const authResponse = await fetch(httpServer.url, {
195
+ headers: { Authorization: Credential.serialize(credential) },
196
+ })
197
+ expect(authResponse.status).toBe(200)
198
+
199
+ httpServer.close()
200
+ })
201
+
202
+ test('behavior: rejects hash credential when challenge supports only pull', async () => {
203
+ const httpServer = await Http.createServer(async (req, res) => {
204
+ const result = await Mppx_server.toNodeListener(
205
+ server.charge({ amount: '1', decimals: 6, supportedModes: ['pull'] }),
206
+ )(req, res)
207
+ if (result.status === 402) return
208
+ res.end('OK')
209
+ })
210
+
211
+ const response = await fetch(httpServer.url)
212
+ expect(response.status).toBe(402)
213
+
214
+ const challenge = Challenge.fromResponse(response, {
215
+ methods: [tempo_client.charge()],
216
+ })
217
+
218
+ const { receipt } = await Actions.token.transferSync(client, {
219
+ account: accounts[1],
220
+ amount: BigInt(challenge.request.amount),
221
+ to: challenge.request.recipient as Hex.Hex,
222
+ token: challenge.request.currency as Hex.Hex,
223
+ })
224
+
225
+ const credential = Credential.from({
226
+ challenge,
227
+ payload: { hash: receipt.transactionHash, type: 'hash' as const },
228
+ })
229
+
230
+ const rejected = await fetch(httpServer.url, {
231
+ headers: { Authorization: Credential.serialize(credential) },
232
+ })
233
+ expect(rejected.status).toBe(402)
234
+
235
+ const body = (await rejected.json()) as { detail: string }
236
+ expect(body.detail).toContain('Hash credentials are not supported for this challenge.')
237
+
238
+ httpServer.close()
239
+ })
240
+
241
+ test('behavior: rejects transaction credential when challenge supports only push', async () => {
242
+ const httpServer = await Http.createServer(async (req, res) => {
243
+ const result = await Mppx_server.toNodeListener(
244
+ server.charge({ amount: '1', decimals: 6, supportedModes: ['push'] }),
245
+ )(req, res)
246
+ if (result.status === 402) return
247
+ res.end('OK')
248
+ })
249
+
250
+ const response = await fetch(httpServer.url)
251
+ expect(response.status).toBe(402)
252
+
253
+ const challenge = Challenge.fromResponse(response, {
254
+ methods: [tempo_client.charge()],
255
+ })
256
+
257
+ const prepared = await prepareTransactionRequest(client, {
258
+ account: accounts[1]!,
259
+ calls: [
260
+ Actions.token.transfer.call({
261
+ amount: BigInt(challenge.request.amount),
262
+ to: challenge.request.recipient as Hex.Hex,
263
+ token: challenge.request.currency as Hex.Hex,
264
+ }),
265
+ ],
266
+ nonceKey: 'expiring',
267
+ } as never)
268
+ prepared.gas = prepared.gas! + 5_000n
269
+ const signature = await signTransaction(client, prepared as never)
270
+
271
+ const credential = Credential.from({
272
+ challenge,
273
+ payload: { signature, type: 'transaction' as const },
274
+ })
275
+
276
+ const rejected = await fetch(httpServer.url, {
277
+ headers: { Authorization: Credential.serialize(credential) },
278
+ })
279
+ expect(rejected.status).toBe(402)
280
+
281
+ const body = (await rejected.json()) as { detail: string }
282
+ expect(body.detail).toContain('Transaction credentials are not supported for this challenge.')
283
+
284
+ httpServer.close()
285
+ })
286
+
127
287
  test('behavior: rejects replayed transaction hash', async () => {
128
288
  const dedupServer = Mppx_server.create({
129
289
  methods: [
@@ -1344,6 +1504,79 @@ describe('tempo', () => {
1344
1504
  httpServer.close()
1345
1505
  })
1346
1506
 
1507
+ test('behavior: fee payer simulates before broadcasting in confirmation mode', async () => {
1508
+ const rpcMethods: string[] = []
1509
+ const interceptingClient = createClient({
1510
+ account: accounts[0],
1511
+ chain: client.chain,
1512
+ transport: custom({
1513
+ async request(args: any) {
1514
+ rpcMethods.push(args.method)
1515
+ return client.transport.request(args)
1516
+ },
1517
+ }),
1518
+ })
1519
+
1520
+ const serverWithRpcTrace = Mppx_server.create({
1521
+ methods: [
1522
+ tempo_server.charge({
1523
+ getClient() {
1524
+ return interceptingClient
1525
+ },
1526
+ currency: asset,
1527
+ account: accounts[0],
1528
+ }),
1529
+ ],
1530
+ realm,
1531
+ secretKey,
1532
+ })
1533
+
1534
+ const mppx = Mppx_client.create({
1535
+ polyfill: false,
1536
+ methods: [
1537
+ tempo_client({
1538
+ account: accounts[1],
1539
+ getClient() {
1540
+ return client
1541
+ },
1542
+ }),
1543
+ ],
1544
+ })
1545
+
1546
+ const httpServer = await Http.createServer(async (req, res) => {
1547
+ const result = await Mppx_server.toNodeListener(
1548
+ serverWithRpcTrace.charge({
1549
+ feePayer: accounts[0],
1550
+ amount: '1',
1551
+ currency: asset,
1552
+ recipient: accounts[0].address,
1553
+ }),
1554
+ )(req, res)
1555
+ if (result.status === 402) return
1556
+ res.end('OK')
1557
+ })
1558
+
1559
+ const challengeResponse = await fetch(httpServer.url)
1560
+ expect(challengeResponse.status).toBe(402)
1561
+
1562
+ const credential = await mppx.createCredential(challengeResponse)
1563
+ rpcMethods.length = 0
1564
+
1565
+ const authResponse = await fetch(httpServer.url, {
1566
+ headers: { Authorization: credential },
1567
+ })
1568
+ expect(authResponse.status).toBe(200)
1569
+
1570
+ const broadcastIndex = rpcMethods.indexOf('eth_sendRawTransactionSync')
1571
+ const simulationIndex = rpcMethods.indexOf('eth_call')
1572
+
1573
+ expect(broadcastIndex).toBeGreaterThan(-1)
1574
+ expect(simulationIndex).toBeGreaterThan(-1)
1575
+ expect(simulationIndex).toBeLessThan(broadcastIndex)
1576
+
1577
+ httpServer.close()
1578
+ })
1579
+
1347
1580
  test('behavior: fee payer with splits', async () => {
1348
1581
  const mppx = Mppx_client.create({
1349
1582
  polyfill: false,
@@ -3116,6 +3349,31 @@ describe('tempo', () => {
3116
3349
  })
3117
3350
  expect(challenge.request.currency).toBe(asset)
3118
3351
  })
3352
+
3353
+ test('challenge contains supportedModes when configured', async () => {
3354
+ const handler = Mppx_server.create({
3355
+ methods: [
3356
+ tempo_server.charge({
3357
+ getClient: () => client,
3358
+ account: accounts[0].address,
3359
+ currency: asset,
3360
+ }),
3361
+ ],
3362
+ realm,
3363
+ secretKey,
3364
+ })
3365
+
3366
+ const result = await handler.charge({ amount: '1', supportedModes: ['pull'] })(
3367
+ new Request('https://example.com'),
3368
+ )
3369
+ expect(result.status).toBe(402)
3370
+ if (result.status !== 402) throw new Error()
3371
+
3372
+ const challenge = Challenge.fromResponse(result.challenge, {
3373
+ methods: [tempo_client.charge()],
3374
+ })
3375
+ expect(challenge.request.methodDetails?.supportedModes).toEqual(['pull'])
3376
+ })
3119
3377
  })
3120
3378
 
3121
3379
  describe('attribution memo', () => {
@@ -60,6 +60,7 @@ export function charge<const parameters extends charge.Parameters>(
60
60
  decimals = defaults.decimals,
61
61
  description,
62
62
  externalId,
63
+ feePayerPolicy,
63
64
  html,
64
65
  memo,
65
66
  waitForConfirmation = true,
@@ -162,6 +163,9 @@ export function charge<const parameters extends charge.Parameters>(
162
163
 
163
164
  const { amount, methodDetails } = resolvedRequest
164
165
  const expires = challenge.expires
166
+ const supportedModes = methodDetails?.supportedModes as
167
+ | readonly Methods.ChargeMode[]
168
+ | undefined
165
169
 
166
170
  const currency = resolvedRequest.currency as `0x${string}`
167
171
  const recipient = resolvedRequest.recipient as `0x${string}`
@@ -178,6 +182,9 @@ export function charge<const parameters extends charge.Parameters>(
178
182
 
179
183
  switch (payload.type) {
180
184
  case 'hash': {
185
+ if (supportedModes && !supportedModes.includes('push'))
186
+ throw new MismatchError('Hash credentials are not supported for this challenge.', {})
187
+
181
188
  const hash = payload.hash as `0x${string}`
182
189
  if (!(await markHashUsed(store, hash))) {
183
190
  throw new VerificationFailedError({ reason: 'Transaction hash has already been used' })
@@ -258,6 +265,12 @@ export function charge<const parameters extends charge.Parameters>(
258
265
  }
259
266
 
260
267
  case 'transaction': {
268
+ if (supportedModes && !supportedModes.includes('pull'))
269
+ throw new MismatchError(
270
+ 'Transaction credentials are not supported for this challenge.',
271
+ {},
272
+ )
273
+
261
274
  const serializedTransaction = payload.signature as Transaction.TransactionSerializedTempo
262
275
 
263
276
  // Pre-broadcast dedup: catch exact byte-for-byte replays early.
@@ -301,6 +314,7 @@ export function charge<const parameters extends charge.Parameters>(
301
314
  chainId: chainId ?? client.chain!.id,
302
315
  details: { amount, currency, recipient },
303
316
  expectedFeeToken,
317
+ policy: feePayerPolicy,
304
318
  transaction: {
305
319
  ...transaction,
306
320
  ...(resolvedFeeToken ? { feeToken: resolvedFeeToken } : {}),
@@ -312,6 +326,12 @@ export function charge<const parameters extends charge.Parameters>(
312
326
  })()
313
327
 
314
328
  if (waitForConfirmation) {
329
+ await viem_call(client, {
330
+ ...transaction,
331
+ account: transaction.from,
332
+ feeToken: resolvedFeeToken,
333
+ calls: transaction.calls,
334
+ } as never)
315
335
  const receipt = await sendRawTransactionSync(client, {
316
336
  serializedTransaction: serializedTransaction_final,
317
337
  })
@@ -385,6 +405,15 @@ export declare namespace charge {
385
405
  type Parameters = {
386
406
  /** Render payment page when Accept header is text/html (e.g. in browsers) */
387
407
  html?: boolean | Html.Config | undefined
408
+ /**
409
+ * Override the fee-sponsor policy used when co-signing Tempo charge
410
+ * transactions. Defaults resolve per chain, including a higher
411
+ * priority-fee ceiling on Moderato.
412
+ *
413
+ * If you increase `maxGas` or `maxFeePerGas`, you may also need to raise
414
+ * `maxTotalFee` so the combined fee budget remains valid.
415
+ */
416
+ feePayerPolicy?: FeePayerPolicy | undefined
388
417
  /** Testnet mode. */
389
418
  testnet?: boolean | undefined
390
419
  /**
@@ -424,6 +453,8 @@ export declare namespace charge {
424
453
  > & {
425
454
  decimals: number
426
455
  }
456
+
457
+ type FeePayerPolicy = Partial<FeePayer.Policy>
427
458
  }
428
459
 
429
460
  type ExpectedTransfer = {
@@ -149,6 +149,40 @@ describe.runIf(isLocalnet)('session', () => {
149
149
  expect(ch!.highestVoucherAmount).toBe(1000000n)
150
150
  })
151
151
 
152
+ test('fee-payer policy override is enforced for sponsored open', async () => {
153
+ const salt = nextSalt()
154
+ const { channelId, serializedTransaction } = await signOpenChannel({
155
+ escrow: escrowContract,
156
+ payer,
157
+ payee: recipient,
158
+ token: currency,
159
+ deposit: 10000000n,
160
+ salt,
161
+ feePayer: true,
162
+ })
163
+ const server = createServer({
164
+ feePayer: recipientAccount,
165
+ feePayerPolicy: { maxGas: 1n },
166
+ })
167
+
168
+ await expect(
169
+ server.verify({
170
+ credential: {
171
+ challenge: makeChallenge({ channelId }),
172
+ payload: {
173
+ action: 'open' as const,
174
+ type: 'transaction' as const,
175
+ channelId,
176
+ transaction: serializedTransaction,
177
+ cumulativeAmount: '1000000',
178
+ signature: await signTestVoucher(channelId, 1000000n),
179
+ },
180
+ },
181
+ request: makeRequest({ feePayer: true }),
182
+ }),
183
+ ).rejects.toThrow('gas exceeds sponsor policy')
184
+ })
185
+
152
186
  test('rejects open when payee mismatch', async () => {
153
187
  const wrongPayee = accounts[3].address
154
188
  const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n, {
@@ -299,6 +333,66 @@ describe.runIf(isLocalnet)('session', () => {
299
333
  expect(ch!.highestVoucherAmount).toBe(1000000n)
300
334
  })
301
335
 
336
+ test('reopen with a case-variant channelId does not reset available balance', async () => {
337
+ let open:
338
+ | {
339
+ channelId: Hex
340
+ serializedTransaction: Hex
341
+ }
342
+ | undefined
343
+ for (let i = 0; i < 10; i++) {
344
+ const candidate = await createSignedOpenTransaction(10000000n)
345
+ if (/[a-f]/.test(candidate.channelId)) {
346
+ open = candidate
347
+ break
348
+ }
349
+ }
350
+ if (!open) throw new Error('failed to generate channelId with alphabetic hex characters')
351
+
352
+ const { channelId, serializedTransaction } = open
353
+ const caseVariantChannelId = channelId.replace(/[a-f]/, (character) =>
354
+ character.toUpperCase(),
355
+ ) as Hex
356
+ const server = createServer()
357
+
358
+ await server.verify({
359
+ credential: {
360
+ challenge: makeChallenge({ id: 'open-1', channelId }),
361
+ payload: {
362
+ action: 'open' as const,
363
+ type: 'transaction' as const,
364
+ channelId,
365
+ transaction: serializedTransaction,
366
+ cumulativeAmount: '5000000',
367
+ signature: await signTestVoucher(channelId, 5000000n),
368
+ },
369
+ },
370
+ request: makeRequest(),
371
+ })
372
+
373
+ await charge(store, channelId, 4000000n)
374
+
375
+ const reopenReceipt = (await server.verify({
376
+ credential: {
377
+ challenge: makeChallenge({ id: 'open-2', channelId: caseVariantChannelId }),
378
+ payload: {
379
+ action: 'open' as const,
380
+ type: 'transaction' as const,
381
+ channelId: caseVariantChannelId,
382
+ transaction: serializedTransaction,
383
+ cumulativeAmount: '5000000',
384
+ signature: await signTestVoucher(caseVariantChannelId, 5000000n),
385
+ },
386
+ },
387
+ request: makeRequest(),
388
+ })) as SessionReceipt
389
+
390
+ expect(reopenReceipt.spent).toBe('4000000')
391
+ await expect(charge(store, caseVariantChannelId, 2000000n)).rejects.toThrow(
392
+ 'requested 2000000, available 1000000',
393
+ )
394
+ })
395
+
302
396
  test('rejects voucher below settledOnChain', async () => {
303
397
  const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n)
304
398
  const server = createServer()
@@ -1036,6 +1130,40 @@ describe.runIf(isLocalnet)('session', () => {
1036
1130
  expect(ch!.deposit).toBe(20000000n)
1037
1131
  })
1038
1132
 
1133
+ test('fee-payer policy override is enforced for sponsored topUp', async () => {
1134
+ const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n)
1135
+ const server = createServer({
1136
+ feePayer: recipientAccount,
1137
+ feePayerPolicy: { maxGas: 1n },
1138
+ })
1139
+ await openServerChannel(server, channelId, serializedTransaction)
1140
+
1141
+ const { serializedTransaction: topUpTx } = await signTopUpChannel({
1142
+ escrow: escrowContract,
1143
+ payer,
1144
+ channelId,
1145
+ token: currency,
1146
+ amount: 10000000n,
1147
+ feePayer: true,
1148
+ })
1149
+
1150
+ await expect(
1151
+ server.verify({
1152
+ credential: {
1153
+ challenge: makeChallenge({ id: 'challenge-topup-policy', channelId }),
1154
+ payload: {
1155
+ action: 'topUp' as const,
1156
+ type: 'transaction' as const,
1157
+ channelId,
1158
+ transaction: topUpTx,
1159
+ additionalDeposit: '10000000',
1160
+ },
1161
+ },
1162
+ request: makeRequest({ feePayer: true }),
1163
+ }),
1164
+ ).rejects.toThrow('gas exceeds sponsor policy')
1165
+ })
1166
+
1039
1167
  test('topUp receipt preserves spent and units from prior charges', async () => {
1040
1168
  const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n)
1041
1169
  const server = createServer()
@@ -4514,7 +4642,7 @@ function makeChallenge(opts: { id?: string; channelId: Hex }) {
4514
4642
  } as Challenge.Challenge<z.output<typeof Methods.session.schema.request>, 'session', 'tempo'>
4515
4643
  }
4516
4644
 
4517
- function makeRequest() {
4645
+ function makeRequest(overrides: Partial<Record<string, unknown>> = {}) {
4518
4646
  return {
4519
4647
  amount: '1000000',
4520
4648
  unitType: 'token',
@@ -4523,6 +4651,7 @@ function makeRequest() {
4523
4651
  recipient: recipient as string,
4524
4652
  escrowContract: escrowContract as string,
4525
4653
  chainId: chain.id,
4654
+ ...overrides,
4526
4655
  }
4527
4656
  }
4528
4657