mppx 0.5.13 → 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.
- package/CHANGELOG.md +8 -0
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +7 -2
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +6 -0
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Session.d.ts +4 -0
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +36 -28
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts +5 -0
- package/dist/tempo/session/Chain.d.ts.map +1 -1
- package/dist/tempo/session/Chain.js +202 -63
- package/dist/tempo/session/Chain.js.map +1 -1
- package/dist/tempo/session/ChannelStore.d.ts +1 -0
- package/dist/tempo/session/ChannelStore.d.ts.map +1 -1
- package/dist/tempo/session/ChannelStore.js +38 -15
- package/dist/tempo/session/ChannelStore.js.map +1 -1
- package/package.json +2 -2
- package/src/tempo/internal/fee-payer.test.ts +48 -12
- package/src/tempo/internal/fee-payer.ts +13 -2
- package/src/tempo/server/Charge.test.ts +73 -0
- package/src/tempo/server/Charge.ts +6 -0
- package/src/tempo/server/Session.test.ts +130 -1
- package/src/tempo/server/Session.ts +41 -35
- package/src/tempo/session/Chain.test.ts +225 -2
- package/src/tempo/session/Chain.ts +250 -65
- package/src/tempo/session/ChannelStore.test.ts +23 -0
- package/src/tempo/session/ChannelStore.ts +46 -13
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
278
|
+
data: swapData,
|
|
243
279
|
},
|
|
244
280
|
],
|
|
245
281
|
details,
|
|
@@ -98,14 +98,25 @@ export function validateCalls(
|
|
|
98
98
|
throw new FeePayerValidationError('disallowed call pattern in fee-payer transaction', details)
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
//
|
|
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
|
+
|
|
102
112
|
const approveCall = calls.find((c) => c.data?.slice(0, 10) === Selectors.approve)
|
|
103
113
|
if (approveCall) {
|
|
104
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)
|
|
105
117
|
if (!TempoAddress_internal.isEqual((args as [`0x${string}`])[0]!, Addresses.stablecoinDex))
|
|
106
118
|
throw new FeePayerValidationError('approve spender is not the DEX', details)
|
|
107
119
|
}
|
|
108
|
-
const buyCall = calls.find((c) => c.data?.slice(0, 10) === Selectors.swapExactAmountOut)
|
|
109
120
|
if (
|
|
110
121
|
buyCall &&
|
|
111
122
|
(!buyCall.to || !TempoAddress_internal.isEqual(buyCall.to, Addresses.stablecoinDex))
|
|
@@ -1504,6 +1504,79 @@ describe('tempo', () => {
|
|
|
1504
1504
|
httpServer.close()
|
|
1505
1505
|
})
|
|
1506
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
|
+
|
|
1507
1580
|
test('behavior: fee payer with splits', async () => {
|
|
1508
1581
|
const mppx = Mppx_client.create({
|
|
1509
1582
|
polyfill: false,
|
|
@@ -326,6 +326,12 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
326
326
|
})()
|
|
327
327
|
|
|
328
328
|
if (waitForConfirmation) {
|
|
329
|
+
await viem_call(client, {
|
|
330
|
+
...transaction,
|
|
331
|
+
account: transaction.from,
|
|
332
|
+
feeToken: resolvedFeeToken,
|
|
333
|
+
calls: transaction.calls,
|
|
334
|
+
} as never)
|
|
329
335
|
const receipt = await sendRawTransactionSync(client, {
|
|
330
336
|
serializedTransaction: serializedTransaction_final,
|
|
331
337
|
})
|
|
@@ -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
|
|