mppx 0.8.12 → 0.8.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 +34 -0
- package/README.md +16 -1
- package/dist/Errors.d.ts +10 -0
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +11 -2
- package/dist/Errors.js.map +1 -1
- package/dist/Method.d.ts +105 -3
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js +102 -2
- package/dist/Method.js.map +1 -1
- package/dist/cli/account.d.ts +88 -0
- package/dist/cli/account.d.ts.map +1 -1
- package/dist/cli/account.js +39 -9
- package/dist/cli/account.js.map +1 -1
- package/dist/cli/cli.d.ts +2 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +59 -2
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/sessions/Manager.d.ts +31 -0
- package/dist/cli/sessions/Manager.d.ts.map +1 -0
- package/dist/cli/sessions/Manager.js +56 -0
- package/dist/cli/sessions/Manager.js.map +1 -0
- package/dist/cli/sessions/commands.d.ts +36 -0
- package/dist/cli/sessions/commands.d.ts.map +1 -0
- package/dist/cli/sessions/commands.js +407 -0
- package/dist/cli/sessions/commands.js.map +1 -0
- package/dist/cli/sessions/request.d.ts +35 -0
- package/dist/cli/sessions/request.d.ts.map +1 -0
- package/dist/cli/sessions/request.js +271 -0
- package/dist/cli/sessions/request.js.map +1 -0
- package/dist/cli/sessions/store.d.ts +134 -0
- package/dist/cli/sessions/store.d.ts.map +1 -0
- package/dist/cli/sessions/store.js +723 -0
- package/dist/cli/sessions/store.js.map +1 -0
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +12 -0
- package/dist/cli/utils.js.map +1 -1
- package/dist/cli/validate/index.d.ts.map +1 -1
- package/dist/cli/validate/index.js +12 -6
- package/dist/cli/validate/index.js.map +1 -1
- package/dist/cli/validate/messages.d.ts +2 -0
- package/dist/cli/validate/messages.d.ts.map +1 -0
- package/dist/cli/validate/messages.js +8 -0
- package/dist/cli/validate/messages.js.map +1 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +12 -2
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/MethodChallenge.d.ts +17 -0
- package/dist/client/internal/MethodChallenge.d.ts.map +1 -0
- package/dist/client/internal/MethodChallenge.js +11 -0
- package/dist/client/internal/MethodChallenge.js.map +1 -0
- package/dist/client/node.d.ts +29 -0
- package/dist/client/node.d.ts.map +1 -0
- package/dist/client/node.js +177 -0
- package/dist/client/node.js.map +1 -0
- package/dist/internal/types.d.ts +24 -0
- package/dist/internal/types.d.ts.map +1 -1
- package/dist/server/Mppx.d.ts +23 -4
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +91 -82
- package/dist/server/Mppx.js.map +1 -1
- package/dist/stripe/server/Charge.d.ts +8 -4
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +2 -1
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts +1 -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/internal/types.d.ts +10 -1
- package/dist/tempo/internal/types.d.ts.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.js +2 -1
- package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
- package/dist/tempo/legacy/client/Session.d.ts +2 -2
- package/dist/tempo/legacy/server/Session.d.ts +3 -3
- package/dist/tempo/legacy/server/Session.d.ts.map +1 -1
- package/dist/tempo/legacy/server/Session.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts +40 -78
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +289 -209
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +13 -23
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +6 -2
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Relay.d.ts +48 -0
- package/dist/tempo/server/Relay.d.ts.map +1 -0
- package/dist/tempo/server/Relay.js +177 -0
- package/dist/tempo/server/Relay.js.map +1 -0
- package/dist/tempo/server/SponsorBudget.d.ts +59 -0
- package/dist/tempo/server/SponsorBudget.d.ts.map +1 -0
- package/dist/tempo/server/SponsorBudget.js +144 -0
- package/dist/tempo/server/SponsorBudget.js.map +1 -0
- package/dist/tempo/server/Subscription.d.ts +3 -2
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js.map +1 -1
- package/dist/tempo/server/index.d.ts +1 -1
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/session/Snapshot.d.ts +9 -0
- package/dist/tempo/session/Snapshot.d.ts.map +1 -1
- package/dist/tempo/session/Snapshot.js +6 -0
- package/dist/tempo/session/Snapshot.js.map +1 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +7 -1
- package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +17 -3
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +26 -2
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +99 -16
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/Runtime.d.ts +8 -0
- package/dist/tempo/session/client/Runtime.d.ts.map +1 -1
- package/dist/tempo/session/client/Runtime.js +17 -0
- package/dist/tempo/session/client/Runtime.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +16 -6
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +93 -36
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +10 -3
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +176 -50
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/Transports.d.ts +18 -7
- package/dist/tempo/session/client/Transports.d.ts.map +1 -1
- package/dist/tempo/session/client/Transports.js +81 -33
- package/dist/tempo/session/client/Transports.js.map +1 -1
- package/dist/tempo/session/client/internal/SessionManager.d.ts +19 -0
- package/dist/tempo/session/client/internal/SessionManager.d.ts.map +1 -0
- package/dist/tempo/session/client/internal/SessionManager.js +13 -0
- package/dist/tempo/session/client/internal/SessionManager.js.map +1 -0
- package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Chain.js +109 -12
- package/dist/tempo/session/precompile/Chain.js.map +1 -1
- package/dist/tempo/session/precompile/Channel.d.ts +2 -0
- package/dist/tempo/session/precompile/Channel.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Channel.js +4 -0
- package/dist/tempo/session/precompile/Channel.js.map +1 -1
- package/dist/tempo/session/server/CredentialVerification.d.ts +3 -0
- package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
- package/dist/tempo/session/server/CredentialVerification.js +14 -0
- package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
- package/dist/tempo/session/server/MeteredStream.d.ts +7 -1
- package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -1
- package/dist/tempo/session/server/MeteredStream.js +5 -4
- package/dist/tempo/session/server/MeteredStream.js.map +1 -1
- package/dist/tempo/session/server/RequestState.d.ts +5 -1
- package/dist/tempo/session/server/RequestState.d.ts.map +1 -1
- package/dist/tempo/session/server/RequestState.js +13 -5
- package/dist/tempo/session/server/RequestState.js.map +1 -1
- package/dist/tempo/session/server/Session.d.ts +21 -6
- package/dist/tempo/session/server/Session.d.ts.map +1 -1
- package/dist/tempo/session/server/Session.js +4 -1
- package/dist/tempo/session/server/Session.js.map +1 -1
- package/dist/tempo/session/server/Settlement.d.ts +20 -0
- package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
- package/dist/tempo/session/server/Settlement.js +20 -0
- package/dist/tempo/session/server/Settlement.js.map +1 -1
- package/dist/tempo/session/server/Ws.d.ts +2 -0
- package/dist/tempo/session/server/Ws.d.ts.map +1 -1
- package/dist/tempo/session/server/Ws.js.map +1 -1
- package/dist/tempo/session/server/index.d.ts +1 -1
- package/dist/tempo/session/server/index.d.ts.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts +21 -21
- package/dist/validation/core.d.ts.map +1 -1
- package/dist/validation/core.js +6 -2
- package/dist/validation/core.js.map +1 -1
- package/dist/viem/Client.d.ts.map +1 -1
- package/dist/viem/Client.js +24 -17
- package/dist/viem/Client.js.map +1 -1
- package/package.json +6 -1
- package/src/Errors.test.ts +23 -0
- package/src/Errors.ts +21 -2
- package/src/Method.test.ts +102 -1
- package/src/Method.ts +241 -5
- package/src/cli/account.ts +45 -10
- package/src/cli/cli.test.ts +165 -70
- package/src/cli/cli.ts +67 -2
- package/src/cli/mcp.test.ts +11 -0
- package/src/cli/sessions/Manager.test.ts +249 -0
- package/src/cli/sessions/Manager.ts +93 -0
- package/src/cli/sessions/commands.ts +444 -0
- package/src/cli/sessions/request.test.ts +51 -0
- package/src/cli/sessions/request.ts +353 -0
- package/src/cli/sessions/store.test.ts +581 -0
- package/src/cli/sessions/store.ts +940 -0
- package/src/cli/utils.test.ts +23 -0
- package/src/cli/utils.ts +10 -0
- package/src/cli/validate/index.ts +12 -14
- package/src/cli/validate/messages.ts +7 -0
- package/src/cli/validate.test.ts +38 -5
- package/src/client/Mppx.test-d.ts +3 -3
- package/src/client/internal/Fetch.ts +16 -7
- package/src/client/internal/MethodChallenge.ts +30 -0
- package/src/client/node.test.ts +115 -0
- package/src/client/node.ts +247 -0
- package/src/internal/types.test-d.ts +21 -0
- package/src/internal/types.ts +33 -0
- package/src/server/Mppx.test-d.ts +44 -0
- package/src/server/Mppx.test.ts +205 -0
- package/src/server/Mppx.ts +167 -103
- package/src/stripe/Methods.test.ts +10 -0
- package/src/stripe/server/Charge.test-d.ts +66 -0
- package/src/stripe/server/Charge.ts +8 -5
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/PublicExports.test-d.ts +20 -0
- package/src/tempo/internal/fee-payer.ts +7 -3
- package/src/tempo/internal/types.ts +13 -2
- package/src/tempo/legacy/client/ChannelOps.test.ts +7 -0
- package/src/tempo/legacy/client/ChannelOps.ts +2 -1
- package/src/tempo/legacy/server/Defaults.test-d.ts +9 -0
- package/src/tempo/legacy/server/Session.ts +6 -5
- package/src/tempo/server/Charge.test.ts +337 -63
- package/src/tempo/server/Charge.ts +440 -270
- package/src/tempo/server/Methods.ts +16 -4
- package/src/tempo/server/Relay.test.ts +523 -0
- package/src/tempo/server/Relay.ts +288 -0
- package/src/tempo/server/SponsorBudget.test.ts +125 -0
- package/src/tempo/server/SponsorBudget.ts +213 -0
- package/src/tempo/server/Subscription.ts +6 -4
- package/src/tempo/server/index.ts +5 -1
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/README.md +201 -0
- package/src/tempo/session/Snapshot.ts +21 -0
- package/src/tempo/session/client/ChannelOps.test.ts +55 -0
- package/src/tempo/session/client/ChannelOps.ts +21 -3
- package/src/tempo/session/client/CredentialState.test.ts +2 -2
- package/src/tempo/session/client/CredentialState.ts +159 -16
- package/src/tempo/session/client/Runtime.test.ts +12 -0
- package/src/tempo/session/client/Runtime.ts +22 -0
- package/src/tempo/session/client/Session.test.ts +238 -5
- package/src/tempo/session/client/Session.ts +130 -41
- package/src/tempo/session/client/SessionManager.test.ts +239 -8
- package/src/tempo/session/client/SessionManager.ts +204 -54
- package/src/tempo/session/client/Transports.test.ts +181 -8
- package/src/tempo/session/client/Transports.ts +127 -45
- package/src/tempo/session/client/internal/SessionManager.ts +36 -0
- package/src/tempo/session/precompile/Chain.integration.test.ts +19 -1
- package/src/tempo/session/precompile/Chain.test.ts +223 -10
- package/src/tempo/session/precompile/Chain.ts +119 -12
- package/src/tempo/session/precompile/Channel.test.ts +9 -0
- package/src/tempo/session/precompile/Channel.ts +5 -0
- package/src/tempo/session/server/CredentialVerification.ts +18 -1
- package/src/tempo/session/server/MeteredStream.ts +12 -5
- package/src/tempo/session/server/RequestState.test.ts +47 -4
- package/src/tempo/session/server/RequestState.ts +15 -8
- package/src/tempo/session/server/Session.test.ts +461 -0
- package/src/tempo/session/server/Session.ts +31 -8
- package/src/tempo/session/server/Settlement.ts +45 -0
- package/src/tempo/session/server/Ws.test.ts +113 -0
- package/src/tempo/session/server/Ws.ts +2 -0
- package/src/tempo/session/server/index.ts +2 -0
- package/src/validation/core.ts +7 -1
- package/src/viem/Client.test.ts +44 -1
- package/src/viem/Client.ts +31 -17
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type Hex,
|
|
13
13
|
} from 'viem'
|
|
14
14
|
import { privateKeyToAccount } from 'viem/accounts'
|
|
15
|
-
import { Transaction } from 'viem/tempo'
|
|
15
|
+
import { Abis, Addresses, Transaction } from 'viem/tempo'
|
|
16
16
|
import { describe, expect, test } from 'vp/test'
|
|
17
17
|
|
|
18
18
|
import { VerificationFailedError } from '../../../Errors.js'
|
|
@@ -36,6 +36,7 @@ const descriptor = {
|
|
|
36
36
|
const deposit = Types.uint96(1_000_000n)
|
|
37
37
|
const chainId = 42431
|
|
38
38
|
const txHash = `0x${'ab'.repeat(32)}` as const
|
|
39
|
+
const sourceToken = '0x6666666666666666666666666666666666666666' as const
|
|
39
40
|
const feePayer = privateKeyToAccount(
|
|
40
41
|
'0x59c6995e998f97a5a0044966f0945389d1fc6e60e7346d6c36c49d32f75b9a1b',
|
|
41
42
|
)
|
|
@@ -62,6 +63,7 @@ function createMockClient(
|
|
|
62
63
|
parameters.rpcMethods?.push(args.method)
|
|
63
64
|
parameters.onRequest?.(args.method, args.params)
|
|
64
65
|
if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
|
|
66
|
+
if (args.method === 'eth_sendTransaction') return txHash
|
|
65
67
|
if (args.method === 'eth_sendRawTransaction') return txHash
|
|
66
68
|
if (args.method === 'eth_sendRawTransactionSync') return parameters.receipt ?? receipt([])
|
|
67
69
|
if (args.method === 'eth_getTransactionReceipt') return parameters.receipt ?? null
|
|
@@ -161,7 +163,11 @@ function topUpLog(parameters: { channelId: `0x${string}`; newDeposit: bigint })
|
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
async function createSerializedTransaction(parameters: {
|
|
164
|
-
calls: {
|
|
166
|
+
calls: {
|
|
167
|
+
to?: `0x${string}` | undefined
|
|
168
|
+
data?: `0x${string}` | undefined
|
|
169
|
+
value?: bigint | undefined
|
|
170
|
+
}[]
|
|
165
171
|
gas?: bigint | undefined
|
|
166
172
|
signed?: boolean | undefined
|
|
167
173
|
}) {
|
|
@@ -191,6 +197,28 @@ async function createSerializedTransaction(parameters: {
|
|
|
191
197
|
} as never)) as `0x${string}`
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
function autoSwapCalls(amountOut: bigint = deposit) {
|
|
201
|
+
const maxAmountIn = amountOut + 1_000n
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
to: sourceToken,
|
|
205
|
+
data: encodeFunctionData({
|
|
206
|
+
abi: Abis.tip20,
|
|
207
|
+
functionName: 'approve',
|
|
208
|
+
args: [Addresses.stablecoinDex, maxAmountIn],
|
|
209
|
+
}),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
to: Addresses.stablecoinDex,
|
|
213
|
+
data: encodeFunctionData({
|
|
214
|
+
abi: Abis.stablecoinDex,
|
|
215
|
+
functionName: 'swapExactAmountOut',
|
|
216
|
+
args: [sourceToken, descriptor.token, amountOut, maxAmountIn],
|
|
217
|
+
}),
|
|
218
|
+
},
|
|
219
|
+
] as const
|
|
220
|
+
}
|
|
221
|
+
|
|
194
222
|
async function createOpenTransaction(
|
|
195
223
|
parameters: {
|
|
196
224
|
authorizedSigner?: `0x${string}` | undefined
|
|
@@ -200,6 +228,13 @@ async function createOpenTransaction(
|
|
|
200
228
|
signed?: boolean | undefined
|
|
201
229
|
token?: `0x${string}` | undefined
|
|
202
230
|
to?: `0x${string}` | undefined
|
|
231
|
+
prefixCalls?:
|
|
232
|
+
| readonly {
|
|
233
|
+
to?: `0x${string}` | undefined
|
|
234
|
+
data?: `0x${string}` | undefined
|
|
235
|
+
value?: bigint | undefined
|
|
236
|
+
}[]
|
|
237
|
+
| undefined
|
|
203
238
|
} = {},
|
|
204
239
|
) {
|
|
205
240
|
const data = encodeFunctionData({
|
|
@@ -215,7 +250,7 @@ async function createOpenTransaction(
|
|
|
215
250
|
],
|
|
216
251
|
})
|
|
217
252
|
return createSerializedTransaction({
|
|
218
|
-
calls: [{ to: parameters.to ?? tip20ChannelEscrow, data }],
|
|
253
|
+
calls: [...(parameters.prefixCalls ?? []), { to: parameters.to ?? tip20ChannelEscrow, data }],
|
|
219
254
|
gas: parameters.gas,
|
|
220
255
|
signed: parameters.signed,
|
|
221
256
|
})
|
|
@@ -228,6 +263,13 @@ async function createTopUpTransaction(
|
|
|
228
263
|
gas?: bigint | undefined
|
|
229
264
|
signed?: boolean | undefined
|
|
230
265
|
to?: `0x${string}` | undefined
|
|
266
|
+
prefixCalls?:
|
|
267
|
+
| readonly {
|
|
268
|
+
to?: `0x${string}` | undefined
|
|
269
|
+
data?: `0x${string}` | undefined
|
|
270
|
+
value?: bigint | undefined
|
|
271
|
+
}[]
|
|
272
|
+
| undefined
|
|
231
273
|
} = {},
|
|
232
274
|
) {
|
|
233
275
|
const data = encodeFunctionData({
|
|
@@ -239,7 +281,7 @@ async function createTopUpTransaction(
|
|
|
239
281
|
],
|
|
240
282
|
})
|
|
241
283
|
return createSerializedTransaction({
|
|
242
|
-
calls: [{ to: parameters.to ?? tip20ChannelEscrow, data }],
|
|
284
|
+
calls: [...(parameters.prefixCalls ?? []), { to: parameters.to ?? tip20ChannelEscrow, data }],
|
|
243
285
|
gas: parameters.gas,
|
|
244
286
|
signed: parameters.signed,
|
|
245
287
|
})
|
|
@@ -290,6 +332,31 @@ describe('precompile receipt wait', () => {
|
|
|
290
332
|
})
|
|
291
333
|
})
|
|
292
334
|
|
|
335
|
+
describe('precompile server transactions', () => {
|
|
336
|
+
test('does not add a fee-payer signature when the sender and fee payer are the same account', async () => {
|
|
337
|
+
const rpcMethods: string[] = []
|
|
338
|
+
const client = createMockClient({ rpcMethods })
|
|
339
|
+
const sender = { address: feePayer.address, type: 'json-rpc' } as const
|
|
340
|
+
|
|
341
|
+
await Chain.closeOnChain(
|
|
342
|
+
client,
|
|
343
|
+
descriptor,
|
|
344
|
+
1n,
|
|
345
|
+
1n,
|
|
346
|
+
`0x${'11'.repeat(65)}`,
|
|
347
|
+
tip20ChannelEscrow,
|
|
348
|
+
{
|
|
349
|
+
account: sender,
|
|
350
|
+
candidateFeeTokens: [descriptor.token],
|
|
351
|
+
feePayer,
|
|
352
|
+
},
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
expect(rpcMethods).toContain('eth_sendTransaction')
|
|
356
|
+
expect(rpcMethods).not.toContain('eth_sendRawTransactionSync')
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
293
360
|
describe('precompile open calldata parsing', () => {
|
|
294
361
|
test('parseOpenCall accepts TIP-1034 open calldata', () => {
|
|
295
362
|
const data = encodeFunctionData({
|
|
@@ -420,8 +487,152 @@ describe('precompile broadcastOpenTransaction', () => {
|
|
|
420
487
|
expectedPayer: descriptor.payer,
|
|
421
488
|
serializedTransaction,
|
|
422
489
|
}),
|
|
423
|
-
).rejects.toThrow(
|
|
490
|
+
).rejects.toThrow(
|
|
491
|
+
'TIP-1034 open transaction must contain one management call, optionally preceded by an auto-swap',
|
|
492
|
+
)
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
const [validApprove, validSwap] = autoSwapCalls()
|
|
496
|
+
const wrongToken = '0x7777777777777777777777777777777777777777' as const
|
|
497
|
+
const maxAmountIn = deposit + 1_000n
|
|
498
|
+
const approve = (parameters: {
|
|
499
|
+
amount?: bigint | undefined
|
|
500
|
+
spender?: Address | undefined
|
|
501
|
+
token?: Address | undefined
|
|
502
|
+
}) => ({
|
|
503
|
+
to: parameters.token ?? sourceToken,
|
|
504
|
+
data: encodeFunctionData({
|
|
505
|
+
abi: Abis.tip20,
|
|
506
|
+
functionName: 'approve',
|
|
507
|
+
args: [parameters.spender ?? Addresses.stablecoinDex, parameters.amount ?? maxAmountIn],
|
|
508
|
+
}),
|
|
509
|
+
})
|
|
510
|
+
const swap = (parameters: {
|
|
511
|
+
amountOut?: bigint | undefined
|
|
512
|
+
maxAmount?: bigint | undefined
|
|
513
|
+
tokenIn?: Address | undefined
|
|
514
|
+
tokenOut?: Address | undefined
|
|
515
|
+
}) => ({
|
|
516
|
+
to: Addresses.stablecoinDex as Address,
|
|
517
|
+
data: encodeFunctionData({
|
|
518
|
+
abi: Abis.stablecoinDex,
|
|
519
|
+
functionName: 'swapExactAmountOut',
|
|
520
|
+
args: [
|
|
521
|
+
parameters.tokenIn ?? sourceToken,
|
|
522
|
+
parameters.tokenOut ?? descriptor.token,
|
|
523
|
+
parameters.amountOut ?? deposit,
|
|
524
|
+
parameters.maxAmount ?? maxAmountIn,
|
|
525
|
+
],
|
|
526
|
+
}),
|
|
424
527
|
})
|
|
528
|
+
const invalidAutoSwapPrefixes = [
|
|
529
|
+
{
|
|
530
|
+
name: 'missing call target',
|
|
531
|
+
prefixCalls: [{ data: validApprove.data }, validSwap],
|
|
532
|
+
reason: 'call is missing a target or calldata',
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: 'native value',
|
|
536
|
+
prefixCalls: [{ ...validApprove, value: 1n }, validSwap],
|
|
537
|
+
reason: 'calls must not transfer native value',
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: 'wrong DEX target',
|
|
541
|
+
prefixCalls: [validApprove, { ...validSwap, to: wrongToken }],
|
|
542
|
+
reason: 'targets the wrong DEX',
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: 'invalid approval calldata',
|
|
546
|
+
prefixCalls: [{ ...validApprove, data: '0x12345678' as const }, validSwap],
|
|
547
|
+
reason: 'approval calldata is invalid',
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: 'invalid swap calldata',
|
|
551
|
+
prefixCalls: [validApprove, { ...validSwap, data: '0x12345678' as const }],
|
|
552
|
+
reason: 'swap calldata is invalid',
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: 'wrong approval function',
|
|
556
|
+
prefixCalls: [
|
|
557
|
+
{
|
|
558
|
+
to: sourceToken,
|
|
559
|
+
data: encodeFunctionData({
|
|
560
|
+
abi: Abis.tip20,
|
|
561
|
+
functionName: 'transfer',
|
|
562
|
+
args: [Addresses.stablecoinDex, maxAmountIn],
|
|
563
|
+
}),
|
|
564
|
+
},
|
|
565
|
+
validSwap,
|
|
566
|
+
],
|
|
567
|
+
reason: 'must contain approve followed by swapExactAmountOut',
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: 'approval token mismatch',
|
|
571
|
+
prefixCalls: [approve({ token: wrongToken }), validSwap],
|
|
572
|
+
reason: 'approval token does not match swap input',
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
name: 'approval spender mismatch',
|
|
576
|
+
prefixCalls: [approve({ spender: wrongToken }), validSwap],
|
|
577
|
+
reason: 'approval spender is not the DEX',
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: 'approval amount mismatch',
|
|
581
|
+
prefixCalls: [approve({ amount: maxAmountIn + 1n }), validSwap],
|
|
582
|
+
reason: 'approval amount does not match swap maximum input',
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: 'output token mismatch',
|
|
586
|
+
prefixCalls: [validApprove, swap({ tokenOut: wrongToken })],
|
|
587
|
+
reason: 'output token does not match channel currency',
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
name: 'output amount mismatch',
|
|
591
|
+
prefixCalls: [
|
|
592
|
+
approve({ amount: maxAmountIn + 1n }),
|
|
593
|
+
swap({ amountOut: deposit + 1n, maxAmount: maxAmountIn + 1n }),
|
|
594
|
+
],
|
|
595
|
+
reason: 'output amount does not match channel deposit',
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: 'identical input and output tokens',
|
|
599
|
+
prefixCalls: [approve({ token: descriptor.token }), swap({ tokenIn: descriptor.token })],
|
|
600
|
+
reason: 'input and output tokens must differ',
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
name: 'non-canonical approval calldata',
|
|
604
|
+
prefixCalls: [{ ...validApprove, data: `${validApprove.data}00` as Hex }, validSwap],
|
|
605
|
+
reason: 'approval calldata is not canonical',
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
name: 'non-canonical swap calldata',
|
|
609
|
+
prefixCalls: [validApprove, { ...validSwap, data: `${validSwap.data}00` as Hex }],
|
|
610
|
+
reason: 'swap calldata is not canonical',
|
|
611
|
+
},
|
|
612
|
+
] as const
|
|
613
|
+
|
|
614
|
+
test.each(invalidAutoSwapPrefixes)(
|
|
615
|
+
'rejects an auto-swap with $name',
|
|
616
|
+
async ({ prefixCalls, reason }) => {
|
|
617
|
+
const serializedTransaction = await createOpenTransaction({ prefixCalls })
|
|
618
|
+
|
|
619
|
+
await expect(
|
|
620
|
+
Chain.broadcastOpenTransaction({
|
|
621
|
+
chainId,
|
|
622
|
+
client: createMockClient(),
|
|
623
|
+
escrowContract: tip20ChannelEscrow,
|
|
624
|
+
expectedAuthorizedSigner: descriptor.authorizedSigner,
|
|
625
|
+
expectedChannelId: `0x${'11'.repeat(32)}`,
|
|
626
|
+
expectedCurrency: descriptor.token,
|
|
627
|
+
expectedExpiringNonceHash: expectedExpiringNonceHash(serializedTransaction),
|
|
628
|
+
expectedOperator: descriptor.operator,
|
|
629
|
+
expectedPayee: descriptor.payee,
|
|
630
|
+
expectedPayer: descriptor.payer,
|
|
631
|
+
serializedTransaction,
|
|
632
|
+
}),
|
|
633
|
+
).rejects.toThrow(`TIP-1034 open auto-swap ${reason}`)
|
|
634
|
+
},
|
|
635
|
+
)
|
|
425
636
|
|
|
426
637
|
test('rejects open transactions targeting the wrong escrow contract', async () => {
|
|
427
638
|
const serializedTransaction = await createOpenTransaction({ to: descriptor.token })
|
|
@@ -689,8 +900,8 @@ describe('precompile broadcastOpenTransaction', () => {
|
|
|
689
900
|
).rejects.toThrow('credential expiringNonceHash does not match transaction')
|
|
690
901
|
})
|
|
691
902
|
|
|
692
|
-
test('
|
|
693
|
-
const serializedTransaction = await createOpenTransaction()
|
|
903
|
+
test('accepts an exact-output auto-swap before open', async () => {
|
|
904
|
+
const serializedTransaction = await createOpenTransaction({ prefixCalls: autoSwapCalls() })
|
|
694
905
|
const expiringNonceHash = expectedExpiringNonceHash(serializedTransaction)
|
|
695
906
|
const expectedDescriptor = { ...descriptor, expiringNonceHash }
|
|
696
907
|
const channelId = Channel.computeId({
|
|
@@ -832,7 +1043,9 @@ describe('precompile broadcastTopUpTransaction', () => {
|
|
|
832
1043
|
expectedCurrency: descriptor.token,
|
|
833
1044
|
serializedTransaction,
|
|
834
1045
|
}),
|
|
835
|
-
).rejects.toThrow(
|
|
1046
|
+
).rejects.toThrow(
|
|
1047
|
+
'TIP-1034 topUp transaction must contain one management call, optionally preceded by an auto-swap',
|
|
1048
|
+
)
|
|
836
1049
|
})
|
|
837
1050
|
|
|
838
1051
|
test('rejects top-up transactions targeting the wrong escrow contract', async () => {
|
|
@@ -999,8 +1212,8 @@ describe('precompile broadcastTopUpTransaction', () => {
|
|
|
999
1212
|
).rejects.toThrow('topUp deposit does not match credential')
|
|
1000
1213
|
})
|
|
1001
1214
|
|
|
1002
|
-
test('
|
|
1003
|
-
const serializedTransaction = await createTopUpTransaction()
|
|
1215
|
+
test('accepts an exact-output auto-swap before top-up', async () => {
|
|
1216
|
+
const serializedTransaction = await createTopUpTransaction({ prefixCalls: autoSwapCalls() })
|
|
1004
1217
|
const channelId = Channel.computeId({ ...descriptor, chainId, escrow: tip20ChannelEscrow })
|
|
1005
1218
|
const newDeposit = deposit * 2n
|
|
1006
1219
|
const state = { settled: 0n, deposit: newDeposit, closeRequestedAt: 0 }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Account, Address, Client, Hex } from 'viem'
|
|
2
|
-
import { encodeFunctionData, isAddressEqual, parseEventLogs } from 'viem'
|
|
2
|
+
import { decodeFunctionData, encodeFunctionData, isAddressEqual, parseEventLogs } from 'viem'
|
|
3
3
|
import {
|
|
4
4
|
call,
|
|
5
5
|
prepareTransactionRequest,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
signTransaction,
|
|
11
11
|
waitForTransactionReceipt,
|
|
12
12
|
} from 'viem/actions'
|
|
13
|
-
import { Transaction } from 'viem/tempo'
|
|
13
|
+
import { Abis, Addresses, Transaction } from 'viem/tempo'
|
|
14
14
|
|
|
15
15
|
import { BadRequestError, VerificationFailedError } from '../../../Errors.js'
|
|
16
16
|
import * as FeePayer from '../../internal/fee-payer.js'
|
|
@@ -401,6 +401,7 @@ export type ChannelTransactionOptions = {
|
|
|
401
401
|
|
|
402
402
|
type ParsedPrecompileCredentialTransaction = {
|
|
403
403
|
call: Transaction.TransactionTempo['calls'][number] & { data: Hex; to: Address }
|
|
404
|
+
prefixCalls: readonly Transaction.TransactionTempo['calls'][number][]
|
|
404
405
|
transaction: ReturnType<(typeof Transaction)['deserialize']>
|
|
405
406
|
}
|
|
406
407
|
|
|
@@ -418,11 +419,11 @@ function parsePrecompileCredentialTransaction(parameters: {
|
|
|
418
419
|
serializedTransaction as Transaction.TransactionSerializedTempo,
|
|
419
420
|
)
|
|
420
421
|
const calls = transaction.calls
|
|
421
|
-
if (calls.length !== 1)
|
|
422
|
+
if (calls.length !== 1 && calls.length !== 3)
|
|
422
423
|
throw new VerificationFailedError({
|
|
423
|
-
reason: `TIP-1034 ${label} transaction must contain
|
|
424
|
+
reason: `TIP-1034 ${label} transaction must contain one management call, optionally preceded by an auto-swap`,
|
|
424
425
|
})
|
|
425
|
-
const call = calls[
|
|
426
|
+
const call = calls[calls.length - 1]!
|
|
426
427
|
if (!call.to || !isAddressEqual(call.to, escrowContract))
|
|
427
428
|
throw new VerificationFailedError({
|
|
428
429
|
reason: `TIP-1034 ${label} transaction targets the wrong address`,
|
|
@@ -431,7 +432,88 @@ function parsePrecompileCredentialTransaction(parameters: {
|
|
|
431
432
|
throw new VerificationFailedError({
|
|
432
433
|
reason: `TIP-1034 ${label} transaction is missing calldata`,
|
|
433
434
|
})
|
|
434
|
-
return {
|
|
435
|
+
return {
|
|
436
|
+
transaction,
|
|
437
|
+
call: { ...call, data: call.data, to: call.to },
|
|
438
|
+
prefixCalls: calls.slice(0, -1),
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function validateAutoSwapPrefix(parameters: {
|
|
443
|
+
amountOut: bigint
|
|
444
|
+
currency: Address
|
|
445
|
+
label: 'open' | 'topUp'
|
|
446
|
+
prefixCalls: readonly Transaction.TransactionTempo['calls'][number][]
|
|
447
|
+
}) {
|
|
448
|
+
const { amountOut, currency, label, prefixCalls } = parameters
|
|
449
|
+
if (prefixCalls.length === 0) return
|
|
450
|
+
|
|
451
|
+
const fail = (reason: string): never => {
|
|
452
|
+
throw new VerificationFailedError({ reason: `TIP-1034 ${label} auto-swap ${reason}` })
|
|
453
|
+
}
|
|
454
|
+
if (prefixCalls.length !== 2) fail('must contain exactly approve and swap calls')
|
|
455
|
+
|
|
456
|
+
const approveCall = prefixCalls[0]!
|
|
457
|
+
const swapCall = prefixCalls[1]!
|
|
458
|
+
const approveTo = approveCall.to
|
|
459
|
+
const approveData = approveCall.data
|
|
460
|
+
const swapTo = swapCall.to
|
|
461
|
+
const swapData = swapCall.data
|
|
462
|
+
if (!approveTo || !approveData || !swapTo || !swapData)
|
|
463
|
+
fail('call is missing a target or calldata')
|
|
464
|
+
const checkedApproveTo = approveTo as Address
|
|
465
|
+
const checkedApproveData = approveData as Hex
|
|
466
|
+
const checkedSwapTo = swapTo as Address
|
|
467
|
+
const checkedSwapData = swapData as Hex
|
|
468
|
+
if ((approveCall.value ?? 0n) !== 0n || (swapCall.value ?? 0n) !== 0n)
|
|
469
|
+
fail('calls must not transfer native value')
|
|
470
|
+
if (!isAddressEqual(checkedSwapTo, Addresses.stablecoinDex)) fail('targets the wrong DEX')
|
|
471
|
+
|
|
472
|
+
const approve = (() => {
|
|
473
|
+
try {
|
|
474
|
+
return decodeFunctionData({ abi: Abis.tip20, data: checkedApproveData })
|
|
475
|
+
} catch {
|
|
476
|
+
return fail('approval calldata is invalid')
|
|
477
|
+
}
|
|
478
|
+
})()
|
|
479
|
+
const swap = (() => {
|
|
480
|
+
try {
|
|
481
|
+
return decodeFunctionData({ abi: Abis.stablecoinDex, data: checkedSwapData })
|
|
482
|
+
} catch {
|
|
483
|
+
return fail('swap calldata is invalid')
|
|
484
|
+
}
|
|
485
|
+
})()
|
|
486
|
+
if (approve.functionName !== 'approve' || swap.functionName !== 'swapExactAmountOut')
|
|
487
|
+
fail('must contain approve followed by swapExactAmountOut')
|
|
488
|
+
|
|
489
|
+
const [spender, approvedAmount] = approve.args as readonly [Address, bigint]
|
|
490
|
+
const [tokenIn, tokenOut, swapAmountOut, maxAmountIn] = swap.args as readonly [
|
|
491
|
+
Address,
|
|
492
|
+
Address,
|
|
493
|
+
bigint,
|
|
494
|
+
bigint,
|
|
495
|
+
]
|
|
496
|
+
if (!isAddressEqual(checkedApproveTo, tokenIn)) fail('approval token does not match swap input')
|
|
497
|
+
if (!isAddressEqual(spender, Addresses.stablecoinDex)) fail('approval spender is not the DEX')
|
|
498
|
+
if (approvedAmount !== maxAmountIn) fail('approval amount does not match swap maximum input')
|
|
499
|
+
if (!isAddressEqual(tokenOut, currency)) fail('output token does not match channel currency')
|
|
500
|
+
if (swapAmountOut !== amountOut) fail('output amount does not match channel deposit')
|
|
501
|
+
if (isAddressEqual(tokenIn, tokenOut)) fail('input and output tokens must differ')
|
|
502
|
+
|
|
503
|
+
const canonicalApprove = encodeFunctionData({
|
|
504
|
+
abi: Abis.tip20,
|
|
505
|
+
functionName: 'approve',
|
|
506
|
+
args: [spender, approvedAmount],
|
|
507
|
+
})
|
|
508
|
+
const canonicalSwap = encodeFunctionData({
|
|
509
|
+
abi: Abis.stablecoinDex,
|
|
510
|
+
functionName: 'swapExactAmountOut',
|
|
511
|
+
args: [tokenIn, tokenOut, swapAmountOut, maxAmountIn],
|
|
512
|
+
})
|
|
513
|
+
if (checkedApproveData.toLowerCase() !== canonicalApprove.toLowerCase())
|
|
514
|
+
fail('approval calldata is not canonical')
|
|
515
|
+
if (checkedSwapData.toLowerCase() !== canonicalSwap.toLowerCase())
|
|
516
|
+
fail('swap calldata is not canonical')
|
|
435
517
|
}
|
|
436
518
|
|
|
437
519
|
async function simulateTempoTransaction(client: Client, request: unknown) {
|
|
@@ -796,7 +878,7 @@ export type BroadcastOpenTransactionParameters = {
|
|
|
796
878
|
export async function broadcastOpenTransaction(
|
|
797
879
|
parameters: BroadcastOpenTransactionParameters,
|
|
798
880
|
): Promise<BroadcastOpenTransactionResult> {
|
|
799
|
-
const { transaction, call } = parsePrecompileCredentialTransaction({
|
|
881
|
+
const { transaction, call, prefixCalls } = parsePrecompileCredentialTransaction({
|
|
800
882
|
escrowContract: parameters.escrowContract,
|
|
801
883
|
feePayer: parameters.feePayer,
|
|
802
884
|
label: 'open',
|
|
@@ -812,6 +894,12 @@ export async function broadcastOpenTransaction(
|
|
|
812
894
|
authorizedSigner: parameters.expectedAuthorizedSigner,
|
|
813
895
|
},
|
|
814
896
|
})
|
|
897
|
+
validateAutoSwapPrefix({
|
|
898
|
+
amountOut: open.deposit,
|
|
899
|
+
currency: parameters.expectedCurrency,
|
|
900
|
+
label: 'open',
|
|
901
|
+
prefixCalls,
|
|
902
|
+
})
|
|
815
903
|
const descriptor = ChannelOps.descriptorFromOpen({
|
|
816
904
|
chainId: parameters.chainId,
|
|
817
905
|
escrow: parameters.escrowContract,
|
|
@@ -920,7 +1008,7 @@ export type BroadcastTopUpTransactionParameters = {
|
|
|
920
1008
|
export async function broadcastTopUpTransaction(
|
|
921
1009
|
parameters: BroadcastTopUpTransactionParameters,
|
|
922
1010
|
): Promise<BroadcastTopUpTransactionResult> {
|
|
923
|
-
const { transaction, call } = parsePrecompileCredentialTransaction({
|
|
1011
|
+
const { transaction, call, prefixCalls } = parsePrecompileCredentialTransaction({
|
|
924
1012
|
escrowContract: parameters.escrowContract,
|
|
925
1013
|
feePayer: parameters.feePayer,
|
|
926
1014
|
label: 'topUp',
|
|
@@ -933,6 +1021,12 @@ export async function broadcastTopUpTransaction(
|
|
|
933
1021
|
additionalDeposit: parameters.additionalDeposit,
|
|
934
1022
|
},
|
|
935
1023
|
})
|
|
1024
|
+
validateAutoSwapPrefix({
|
|
1025
|
+
amountOut: parameters.additionalDeposit,
|
|
1026
|
+
currency: parameters.expectedCurrency,
|
|
1027
|
+
label: 'topUp',
|
|
1028
|
+
prefixCalls,
|
|
1029
|
+
})
|
|
936
1030
|
const receipt = await sendCredentialTransaction({
|
|
937
1031
|
challengeExpires: parameters.challengeExpires,
|
|
938
1032
|
chainId: parameters.chainId,
|
|
@@ -1001,8 +1095,11 @@ async function sendPrecompileTransaction(
|
|
|
1001
1095
|
label: string,
|
|
1002
1096
|
options?: ChannelTransactionOptions,
|
|
1003
1097
|
): Promise<Hex> {
|
|
1004
|
-
|
|
1005
|
-
|
|
1098
|
+
const account = options?.account ?? client.account
|
|
1099
|
+
const selfSponsored =
|
|
1100
|
+
account && options?.feePayer && isAddressEqual(account.address, options.feePayer.address)
|
|
1101
|
+
|
|
1102
|
+
if (options?.feePayer && !selfSponsored) {
|
|
1006
1103
|
if (!account) throw new Error(`Cannot ${label} precompile channel: no account available.`)
|
|
1007
1104
|
const feeToken =
|
|
1008
1105
|
options.feeToken ??
|
|
@@ -1033,10 +1130,20 @@ async function sendPrecompileTransaction(
|
|
|
1033
1130
|
return receipt.transactionHash
|
|
1034
1131
|
}
|
|
1035
1132
|
|
|
1133
|
+
const feeToken =
|
|
1134
|
+
options?.feeToken ??
|
|
1135
|
+
(selfSponsored
|
|
1136
|
+
? await resolveFeeToken({
|
|
1137
|
+
account: account.address,
|
|
1138
|
+
candidateTokens: options?.candidateFeeTokens,
|
|
1139
|
+
client,
|
|
1140
|
+
})
|
|
1141
|
+
: undefined)
|
|
1142
|
+
|
|
1036
1143
|
return sendPrecompileContractCall(client, {
|
|
1037
|
-
account
|
|
1144
|
+
account,
|
|
1038
1145
|
to,
|
|
1039
1146
|
data,
|
|
1040
|
-
feeToken
|
|
1147
|
+
feeToken,
|
|
1041
1148
|
})
|
|
1042
1149
|
}
|
|
@@ -27,6 +27,15 @@ const transaction = {
|
|
|
27
27
|
} as const satisfies Channel.ExpiringNonceTransaction
|
|
28
28
|
|
|
29
29
|
describe('precompile Channel.computeId', () => {
|
|
30
|
+
test('uses the payer when the descriptor delegates to the zero address', () => {
|
|
31
|
+
expect(
|
|
32
|
+
Channel.resolveAuthorizedSigner({
|
|
33
|
+
...descriptor,
|
|
34
|
+
authorizedSigner: '0x0000000000000000000000000000000000000000',
|
|
35
|
+
}),
|
|
36
|
+
).toBe(descriptor.payer)
|
|
37
|
+
})
|
|
38
|
+
|
|
30
39
|
test('returns deterministic 32-byte hash for fixed inputs', () => {
|
|
31
40
|
const id = Channel.computeId({ ...descriptor, chainId })
|
|
32
41
|
expect(Channel.computeId({ ...descriptor, chainId })).toBe(id)
|
|
@@ -9,6 +9,11 @@ import type { ChannelDescriptor } from './Protocol.js'
|
|
|
9
9
|
/** Re-export of the TIP-1034 channel descriptor shape. */
|
|
10
10
|
export type { ChannelDescriptor } from './Protocol.js'
|
|
11
11
|
|
|
12
|
+
/** Resolves the descriptor's effective voucher signer; zero delegates to the payer. */
|
|
13
|
+
export function resolveAuthorizedSigner(descriptor: ChannelDescriptor): Address {
|
|
14
|
+
return BigInt(descriptor.authorizedSigner) === 0n ? descriptor.payer : descriptor.authorizedSigner
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
/** Tempo transaction shape used to derive the TIP-1034 `expiringNonceHash`. */
|
|
13
18
|
export type ExpiringNonceTransaction = (
|
|
14
19
|
| z_TransactionSerializableTempo
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
import * as Voucher from '../precompile/Voucher.js'
|
|
31
31
|
import * as ChannelStore from './ChannelStore.js'
|
|
32
32
|
import { getChallengePaymentFields } from './RequestState.js'
|
|
33
|
-
import { assertSettlementSender, getClientAccount } from './Settlement.js'
|
|
33
|
+
import { assertSettlementSender, getClientAccount, type OnSessionSettlement } from './Settlement.js'
|
|
34
34
|
|
|
35
35
|
/** Returns the effective voucher signer for a TIP-1034 descriptor. */
|
|
36
36
|
export function authorizedSigner(descriptor: Channel.ChannelDescriptor): Address {
|
|
@@ -344,6 +344,8 @@ export type VerifyCredentialPayloadParameters = {
|
|
|
344
344
|
lastOnChainVerified: Map<Hex, number>
|
|
345
345
|
/** Minimum allowed voucher delta in raw units. */
|
|
346
346
|
minVoucherDelta: bigint
|
|
347
|
+
/** Callback invoked after an on-chain settlement or close transaction is confirmed. */
|
|
348
|
+
onSessionSettlement?: OnSessionSettlement | undefined
|
|
347
349
|
/** Discriminated session credential payload to verify. */
|
|
348
350
|
payload: SessionCredentialPayload
|
|
349
351
|
/** Server-side channel store. */
|
|
@@ -747,6 +749,21 @@ async function handleCloseCredential(
|
|
|
747
749
|
signature: payload.signature,
|
|
748
750
|
}),
|
|
749
751
|
)
|
|
752
|
+
if (parameters.onSessionSettlement && txHash) {
|
|
753
|
+
try {
|
|
754
|
+
await parameters.onSessionSettlement(
|
|
755
|
+
Object.freeze({
|
|
756
|
+
txHash,
|
|
757
|
+
channelId,
|
|
758
|
+
trigger: 'close' as const,
|
|
759
|
+
amount: settledToPayee,
|
|
760
|
+
delta: settledToPayee - state.settled,
|
|
761
|
+
}),
|
|
762
|
+
)
|
|
763
|
+
} catch {
|
|
764
|
+
// Errors are isolated — observers cannot break the settlement flow.
|
|
765
|
+
}
|
|
766
|
+
}
|
|
750
767
|
return createSessionReceipt({
|
|
751
768
|
challengeId: challenge.id,
|
|
752
769
|
channelId,
|
|
@@ -12,8 +12,14 @@ export type SessionController = {
|
|
|
12
12
|
* The reservation blocks until sufficient voucher headroom exists, but the
|
|
13
13
|
* charge is only committed once a chunk is actually emitted. If the stream
|
|
14
14
|
* ends or aborts before that emission, the reservation is dropped.
|
|
15
|
+
*
|
|
16
|
+
* Pass an explicit raw-unit `amount` for request-aware or otherwise dynamic
|
|
17
|
+
* pricing. When omitted, the session challenge's configured tick cost is
|
|
18
|
+
* used.
|
|
15
19
|
*/
|
|
16
|
-
charge(): Promise<void>
|
|
20
|
+
charge(amount?: bigint): Promise<void>
|
|
21
|
+
/** Aborted when the client closes or requests the final session receipt. */
|
|
22
|
+
signal: AbortSignal
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
/** Async stream source accepted by paid session transports. */
|
|
@@ -49,7 +55,7 @@ export async function* meterIterable(options: MeteredStreamOptions): AsyncGenera
|
|
|
49
55
|
let reservedAmount = 0n
|
|
50
56
|
let reservedUnits = 0
|
|
51
57
|
|
|
52
|
-
const charge = async () => {
|
|
58
|
+
const charge = async (amount = options.tickCost) => {
|
|
53
59
|
if (prepaidUnits > 0) {
|
|
54
60
|
prepaidUnits -= 1
|
|
55
61
|
return
|
|
@@ -58,19 +64,20 @@ export async function* meterIterable(options: MeteredStreamOptions): AsyncGenera
|
|
|
58
64
|
await reserveChargeOrWait({
|
|
59
65
|
store: options.store,
|
|
60
66
|
channelId: options.channelId,
|
|
61
|
-
amount
|
|
67
|
+
amount,
|
|
62
68
|
reservedAmount,
|
|
63
69
|
emit: options.emitNeedVoucher,
|
|
64
70
|
formatNeedVoucher: options.formatNeedVoucher,
|
|
65
71
|
pollIntervalMs: options.pollIntervalMs,
|
|
66
72
|
signal: options.signal,
|
|
67
73
|
})
|
|
68
|
-
reservedAmount +=
|
|
74
|
+
reservedAmount += amount
|
|
69
75
|
reservedUnits += 1
|
|
70
76
|
}
|
|
71
77
|
|
|
78
|
+
const signal = options.signal ?? new AbortController().signal
|
|
72
79
|
const iterable =
|
|
73
|
-
typeof options.generate === 'function' ? options.generate({ charge }) : options.generate
|
|
80
|
+
typeof options.generate === 'function' ? options.generate({ charge, signal }) : options.generate
|
|
74
81
|
|
|
75
82
|
for await (const value of iterable) {
|
|
76
83
|
if (options.signal?.aborted) break
|