mppx 0.8.11 → 0.8.13
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 +25 -0
- package/README.md +16 -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 +35 -6
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/MethodResponse.d.ts +23 -0
- package/dist/client/internal/MethodResponse.d.ts.map +1 -0
- package/dist/client/internal/MethodResponse.js +15 -0
- package/dist/client/internal/MethodResponse.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/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 +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 +2 -0
- package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.js +4 -0
- package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
- package/dist/tempo/legacy/client/Session.d.ts +2 -2
- package/dist/tempo/legacy/client/Session.d.ts.map +1 -1
- package/dist/tempo/legacy/client/Session.js +7 -0
- package/dist/tempo/legacy/client/Session.js.map +1 -1
- 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 +3 -2
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +2 -23
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +2 -2
- package/dist/tempo/server/Methods.js.map +1 -1
- 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.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +11 -2
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +21 -1
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +66 -1
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +1 -1
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +53 -1
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +2 -2
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +130 -30
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/Transports.d.ts +21 -2
- package/dist/tempo/session/client/Transports.d.ts.map +1 -1
- package/dist/tempo/session/client/Transports.js +129 -18
- 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.js +13 -4
- package/dist/tempo/session/precompile/Chain.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 +10 -4
- 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/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/internal/Fetch.test.ts +29 -0
- package/src/client/internal/Fetch.ts +51 -6
- package/src/client/internal/MethodResponse.ts +39 -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 +42 -0
- 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/internal/types.ts +13 -2
- package/src/tempo/legacy/client/ChannelOps.test.ts +21 -0
- package/src/tempo/legacy/client/ChannelOps.ts +7 -0
- package/src/tempo/legacy/client/Session.test.ts +102 -1
- package/src/tempo/legacy/client/Session.ts +10 -0
- 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.ts +6 -4
- package/src/tempo/server/Methods.ts +5 -4
- 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 +40 -0
- package/src/tempo/session/client/ChannelOps.ts +12 -2
- package/src/tempo/session/client/CredentialState.test.ts +2 -2
- package/src/tempo/session/client/CredentialState.ts +103 -1
- package/src/tempo/session/client/Session.test.ts +86 -4
- package/src/tempo/session/client/Session.ts +73 -1
- package/src/tempo/session/client/SessionManager.test.ts +137 -4
- package/src/tempo/session/client/SessionManager.ts +141 -33
- package/src/tempo/session/client/Transports.test.ts +225 -8
- package/src/tempo/session/client/Transports.ts +156 -21
- package/src/tempo/session/client/internal/SessionManager.ts +36 -0
- package/src/tempo/session/precompile/Chain.test.ts +26 -0
- package/src/tempo/session/precompile/Chain.ts +17 -4
- 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 +39 -3
- package/src/tempo/session/server/RequestState.ts +13 -6
- 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
|
@@ -556,8 +556,21 @@ describe('precompile server session unit guardrails', () => {
|
|
|
556
556
|
test('request returns a server session snapshot for a known precompile channel', async () => {
|
|
557
557
|
const { method, store } = createServer()
|
|
558
558
|
const openPayload = await createOpenPayload({ initialAmount: 100n })
|
|
559
|
+
const highestVoucher = await ClientOps.createVoucherPayload(
|
|
560
|
+
createSigningClient(),
|
|
561
|
+
payer,
|
|
562
|
+
openPayload.descriptor,
|
|
563
|
+
Types.uint96(250n),
|
|
564
|
+
chainId,
|
|
565
|
+
)
|
|
566
|
+
if (highestVoucher.action !== 'voucher') throw new Error('expected voucher payload')
|
|
559
567
|
await persistPrecompileChannel(store, openPayload, {
|
|
560
568
|
highestVoucherAmount: 250n,
|
|
569
|
+
highestVoucher: {
|
|
570
|
+
channelId: openPayload.channelId,
|
|
571
|
+
cumulativeAmount: 250n,
|
|
572
|
+
signature: highestVoucher.signature,
|
|
573
|
+
},
|
|
561
574
|
spent: 200n,
|
|
562
575
|
units: 2,
|
|
563
576
|
})
|
|
@@ -585,6 +598,11 @@ describe('precompile server session unit guardrails', () => {
|
|
|
585
598
|
deposit: '1000',
|
|
586
599
|
descriptor: openPayload.descriptor,
|
|
587
600
|
escrow: tip20ChannelEscrow,
|
|
601
|
+
highestVoucher: {
|
|
602
|
+
channelId: openPayload.channelId,
|
|
603
|
+
cumulativeAmount: '250',
|
|
604
|
+
signature: highestVoucher.signature,
|
|
605
|
+
},
|
|
588
606
|
requiredCumulative: '201',
|
|
589
607
|
settled: '0',
|
|
590
608
|
spent: '200',
|
|
@@ -594,6 +612,14 @@ describe('precompile server session unit guardrails', () => {
|
|
|
594
612
|
|
|
595
613
|
test('request can resolve a server session snapshot from request identity', async () => {
|
|
596
614
|
const openPayload = await createOpenPayload({ initialAmount: 100n })
|
|
615
|
+
const highestVoucher = await ClientOps.createVoucherPayload(
|
|
616
|
+
createSigningClient(),
|
|
617
|
+
payer,
|
|
618
|
+
openPayload.descriptor,
|
|
619
|
+
Types.uint96(250n),
|
|
620
|
+
chainId,
|
|
621
|
+
)
|
|
622
|
+
if (highestVoucher.action !== 'voucher') throw new Error('expected voucher payload')
|
|
597
623
|
const { method, store } = createServer({
|
|
598
624
|
resolveChannelId({ request, credential, paymentRequest, store: hookStore }) {
|
|
599
625
|
expect(request?.headers.get('cookie')).toBe('sid=session-1')
|
|
@@ -605,6 +631,11 @@ describe('precompile server session unit guardrails', () => {
|
|
|
605
631
|
})
|
|
606
632
|
await persistPrecompileChannel(store, openPayload, {
|
|
607
633
|
highestVoucherAmount: 250n,
|
|
634
|
+
highestVoucher: {
|
|
635
|
+
channelId: openPayload.channelId,
|
|
636
|
+
cumulativeAmount: 250n,
|
|
637
|
+
signature: highestVoucher.signature,
|
|
638
|
+
},
|
|
608
639
|
spent: 200n,
|
|
609
640
|
units: 2,
|
|
610
641
|
})
|
|
@@ -661,8 +692,21 @@ describe('precompile server session unit guardrails', () => {
|
|
|
661
692
|
test('request uses zero effective amount for non-content snapshot hints', async () => {
|
|
662
693
|
const { method, store } = createServer()
|
|
663
694
|
const openPayload = await createOpenPayload({ initialAmount: 100n })
|
|
695
|
+
const highestVoucher = await ClientOps.createVoucherPayload(
|
|
696
|
+
createSigningClient(),
|
|
697
|
+
payer,
|
|
698
|
+
openPayload.descriptor,
|
|
699
|
+
Types.uint96(250n),
|
|
700
|
+
chainId,
|
|
701
|
+
)
|
|
702
|
+
if (highestVoucher.action !== 'voucher') throw new Error('expected voucher payload')
|
|
664
703
|
await persistPrecompileChannel(store, openPayload, {
|
|
665
704
|
highestVoucherAmount: 250n,
|
|
705
|
+
highestVoucher: {
|
|
706
|
+
channelId: openPayload.channelId,
|
|
707
|
+
cumulativeAmount: 250n,
|
|
708
|
+
signature: highestVoucher.signature,
|
|
709
|
+
},
|
|
666
710
|
spent: 250n,
|
|
667
711
|
units: 2,
|
|
668
712
|
})
|
|
@@ -2032,8 +2076,21 @@ describe('precompile server session unit guardrails', () => {
|
|
|
2032
2076
|
const rawStore = Store.memory()
|
|
2033
2077
|
const store = channelStore(rawStore)
|
|
2034
2078
|
const openPayload = await createOpenPayload({ initialAmount: 1n })
|
|
2079
|
+
const highestVoucher = await ClientOps.createVoucherPayload(
|
|
2080
|
+
createSigningClient(),
|
|
2081
|
+
payer,
|
|
2082
|
+
openPayload.descriptor,
|
|
2083
|
+
Types.uint96(5n),
|
|
2084
|
+
chainId,
|
|
2085
|
+
)
|
|
2086
|
+
if (highestVoucher.action !== 'voucher') throw new Error('expected voucher payload')
|
|
2035
2087
|
await persistPrecompileChannel(store, openPayload, {
|
|
2036
2088
|
highestVoucherAmount: 5n,
|
|
2089
|
+
highestVoucher: {
|
|
2090
|
+
channelId: openPayload.channelId,
|
|
2091
|
+
cumulativeAmount: 5n,
|
|
2092
|
+
signature: highestVoucher.signature,
|
|
2093
|
+
},
|
|
2037
2094
|
spent: 3n,
|
|
2038
2095
|
units: 3,
|
|
2039
2096
|
})
|
|
@@ -2056,6 +2113,10 @@ describe('precompile server session unit guardrails', () => {
|
|
|
2056
2113
|
expect(snapshot).toMatchObject({
|
|
2057
2114
|
channelId: openPayload.channelId,
|
|
2058
2115
|
acceptedCumulative: '5',
|
|
2116
|
+
highestVoucher: {
|
|
2117
|
+
channelId: openPayload.channelId,
|
|
2118
|
+
cumulativeAmount: '5',
|
|
2119
|
+
},
|
|
2059
2120
|
requiredCumulative: '3',
|
|
2060
2121
|
spent: '3',
|
|
2061
2122
|
})
|
|
@@ -3337,3 +3398,403 @@ describe('precompile server session unit guardrails', () => {
|
|
|
3337
3398
|
).rejects.toThrow(/tx sender .* is not the channel payee/)
|
|
3338
3399
|
})
|
|
3339
3400
|
})
|
|
3401
|
+
|
|
3402
|
+
describe('onSessionSettlement', () => {
|
|
3403
|
+
function createSettleClient(channelId: Hex, settledAmount: bigint) {
|
|
3404
|
+
return createClient({
|
|
3405
|
+
account: payer,
|
|
3406
|
+
chain: testChain,
|
|
3407
|
+
transport: custom({
|
|
3408
|
+
async request(args) {
|
|
3409
|
+
if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
|
|
3410
|
+
if (args.method === 'eth_getTransactionCount') return '0x0'
|
|
3411
|
+
if (args.method === 'eth_estimateGas') return '0x5208'
|
|
3412
|
+
if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
|
|
3413
|
+
if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
|
|
3414
|
+
if (args.method === 'eth_sendRawTransaction') return `0x${'cc'.repeat(32)}`
|
|
3415
|
+
if (args.method === 'eth_sendTransaction') return `0x${'cc'.repeat(32)}`
|
|
3416
|
+
if (args.method === 'eth_getTransactionReceipt')
|
|
3417
|
+
return transactionReceipt([settledLog(channelId, settledAmount)])
|
|
3418
|
+
if (args.method === 'eth_call') {
|
|
3419
|
+
return encodeFunctionResult({
|
|
3420
|
+
abi: escrowAbi,
|
|
3421
|
+
functionName: 'getChannelState',
|
|
3422
|
+
result: { settled: settledAmount, deposit: 1_000n, closeRequestedAt: 0 },
|
|
3423
|
+
})
|
|
3424
|
+
}
|
|
3425
|
+
throw new Error(`unexpected rpc request: ${args.method}`)
|
|
3426
|
+
},
|
|
3427
|
+
}),
|
|
3428
|
+
})
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
test('fires onSessionSettlement for explicit settle() calls', async () => {
|
|
3432
|
+
const events: { trigger: string; txHash: string; amount: bigint; delta: bigint }[] = []
|
|
3433
|
+
const store = Store.memory()
|
|
3434
|
+
const openPayload = await createOpenPayload()
|
|
3435
|
+
await persistPrecompileChannel(channelStore(store), openPayload, {
|
|
3436
|
+
payee: payer.address,
|
|
3437
|
+
spent: 100n,
|
|
3438
|
+
highestVoucherAmount: 100n,
|
|
3439
|
+
highestVoucher: {
|
|
3440
|
+
channelId: openPayload.channelId,
|
|
3441
|
+
cumulativeAmount: 100n,
|
|
3442
|
+
signature: '0x1234',
|
|
3443
|
+
},
|
|
3444
|
+
})
|
|
3445
|
+
|
|
3446
|
+
const { settle } = await import('./Session.js')
|
|
3447
|
+
const client = createSettleClient(openPayload.channelId, 100n)
|
|
3448
|
+
await settle(store, client, openPayload.channelId, {
|
|
3449
|
+
onSessionSettlement: (ctx) => {
|
|
3450
|
+
events.push({
|
|
3451
|
+
trigger: ctx.trigger,
|
|
3452
|
+
txHash: ctx.txHash,
|
|
3453
|
+
amount: ctx.amount,
|
|
3454
|
+
delta: ctx.delta,
|
|
3455
|
+
})
|
|
3456
|
+
},
|
|
3457
|
+
})
|
|
3458
|
+
|
|
3459
|
+
expect(events).toHaveLength(1)
|
|
3460
|
+
expect(events[0]).toMatchObject({
|
|
3461
|
+
trigger: 'settle',
|
|
3462
|
+
txHash: `0x${'cc'.repeat(32)}`,
|
|
3463
|
+
amount: 100n,
|
|
3464
|
+
delta: 100n,
|
|
3465
|
+
})
|
|
3466
|
+
})
|
|
3467
|
+
|
|
3468
|
+
test('fires onSessionSettlement for auto-scheduled settlements', async () => {
|
|
3469
|
+
const events: { trigger: string; channelId: string; amount: bigint; delta: bigint }[] = []
|
|
3470
|
+
const rawStore = Store.memory()
|
|
3471
|
+
const openPayload = await createOpenPayload()
|
|
3472
|
+
const store = channelStore(rawStore)
|
|
3473
|
+
await persistPrecompileChannel(store, openPayload, {
|
|
3474
|
+
payee: payer.address,
|
|
3475
|
+
spent: 500n,
|
|
3476
|
+
units: 10,
|
|
3477
|
+
highestVoucherAmount: 500n,
|
|
3478
|
+
highestVoucher: {
|
|
3479
|
+
channelId: openPayload.channelId,
|
|
3480
|
+
cumulativeAmount: 500n,
|
|
3481
|
+
signature: '0x1234',
|
|
3482
|
+
},
|
|
3483
|
+
})
|
|
3484
|
+
|
|
3485
|
+
const { maybeSettleScheduled } = await import('./Settlement.js')
|
|
3486
|
+
const client = createSettleClient(openPayload.channelId, 500n)
|
|
3487
|
+
const channel = await store.getChannel(openPayload.channelId)
|
|
3488
|
+
|
|
3489
|
+
await maybeSettleScheduled({
|
|
3490
|
+
account: payer,
|
|
3491
|
+
channel: channel!,
|
|
3492
|
+
client,
|
|
3493
|
+
schedule: { units: 5 },
|
|
3494
|
+
store,
|
|
3495
|
+
onSessionSettlement: (ctx) => {
|
|
3496
|
+
events.push({
|
|
3497
|
+
trigger: ctx.trigger,
|
|
3498
|
+
channelId: ctx.channelId,
|
|
3499
|
+
amount: ctx.amount,
|
|
3500
|
+
delta: ctx.delta,
|
|
3501
|
+
})
|
|
3502
|
+
},
|
|
3503
|
+
})
|
|
3504
|
+
|
|
3505
|
+
expect(events).toHaveLength(1)
|
|
3506
|
+
expect(events[0]).toMatchObject({
|
|
3507
|
+
trigger: 'scheduled',
|
|
3508
|
+
channelId: openPayload.channelId,
|
|
3509
|
+
amount: 500n,
|
|
3510
|
+
delta: 500n,
|
|
3511
|
+
})
|
|
3512
|
+
})
|
|
3513
|
+
|
|
3514
|
+
test('delta reflects incremental scheduled settlement with prior on-chain settlement', async () => {
|
|
3515
|
+
const events: { trigger: string; amount: bigint; delta: bigint }[] = []
|
|
3516
|
+
const rawStore = Store.memory()
|
|
3517
|
+
const openPayload = await createOpenPayload()
|
|
3518
|
+
const store = channelStore(rawStore)
|
|
3519
|
+
await persistPrecompileChannel(store, openPayload, {
|
|
3520
|
+
payee: payer.address,
|
|
3521
|
+
spent: 500n,
|
|
3522
|
+
units: 10,
|
|
3523
|
+
settledOnChain: 200n,
|
|
3524
|
+
highestVoucherAmount: 500n,
|
|
3525
|
+
highestVoucher: {
|
|
3526
|
+
channelId: openPayload.channelId,
|
|
3527
|
+
cumulativeAmount: 500n,
|
|
3528
|
+
signature: '0x1234',
|
|
3529
|
+
},
|
|
3530
|
+
})
|
|
3531
|
+
|
|
3532
|
+
const { maybeSettleScheduled } = await import('./Settlement.js')
|
|
3533
|
+
const client = createSettleClient(openPayload.channelId, 500n)
|
|
3534
|
+
const channel = await store.getChannel(openPayload.channelId)
|
|
3535
|
+
|
|
3536
|
+
await maybeSettleScheduled({
|
|
3537
|
+
account: payer,
|
|
3538
|
+
channel: channel!,
|
|
3539
|
+
client,
|
|
3540
|
+
schedule: { units: 5 },
|
|
3541
|
+
store,
|
|
3542
|
+
onSessionSettlement: (ctx) => {
|
|
3543
|
+
events.push({ trigger: ctx.trigger, amount: ctx.amount, delta: ctx.delta })
|
|
3544
|
+
},
|
|
3545
|
+
})
|
|
3546
|
+
|
|
3547
|
+
expect(events).toHaveLength(1)
|
|
3548
|
+
expect(events[0]).toMatchObject({
|
|
3549
|
+
trigger: 'scheduled',
|
|
3550
|
+
amount: 500n,
|
|
3551
|
+
delta: 300n,
|
|
3552
|
+
})
|
|
3553
|
+
})
|
|
3554
|
+
|
|
3555
|
+
test('fires onSessionSettlement for cooperative close', async () => {
|
|
3556
|
+
const events: { trigger: string; txHash: string; amount: bigint; delta: bigint }[] = []
|
|
3557
|
+
const openPayload = await createOpenPayload()
|
|
3558
|
+
const closedAmount = 100n
|
|
3559
|
+
const refundedAmount = 900n
|
|
3560
|
+
const rawStore = Store.memory()
|
|
3561
|
+
|
|
3562
|
+
const closeClient = createClient({
|
|
3563
|
+
account: payer,
|
|
3564
|
+
chain: testChain,
|
|
3565
|
+
transport: custom({
|
|
3566
|
+
async request(args) {
|
|
3567
|
+
if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
|
|
3568
|
+
if (args.method === 'eth_getTransactionCount') return '0x0'
|
|
3569
|
+
if (args.method === 'eth_estimateGas') return '0x5208'
|
|
3570
|
+
if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
|
|
3571
|
+
if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
|
|
3572
|
+
if (args.method === 'eth_sendRawTransaction') return `0x${'dd'.repeat(32)}`
|
|
3573
|
+
if (args.method === 'eth_sendTransaction') return `0x${'dd'.repeat(32)}`
|
|
3574
|
+
if (args.method === 'eth_getTransactionReceipt')
|
|
3575
|
+
return transactionReceipt([
|
|
3576
|
+
closedLog(openPayload.channelId, closedAmount, refundedAmount),
|
|
3577
|
+
])
|
|
3578
|
+
if (args.method === 'eth_call') {
|
|
3579
|
+
return encodeFunctionResult({
|
|
3580
|
+
abi: escrowAbi,
|
|
3581
|
+
functionName: 'getChannelState',
|
|
3582
|
+
result: { settled: 0n, deposit: 1_000n, closeRequestedAt: 0 },
|
|
3583
|
+
})
|
|
3584
|
+
}
|
|
3585
|
+
throw new Error(`unexpected rpc request: ${args.method}`)
|
|
3586
|
+
},
|
|
3587
|
+
}),
|
|
3588
|
+
})
|
|
3589
|
+
|
|
3590
|
+
const method = session({
|
|
3591
|
+
amount: '1',
|
|
3592
|
+
chainId,
|
|
3593
|
+
currency: token,
|
|
3594
|
+
decimals: 0,
|
|
3595
|
+
recipient: payee,
|
|
3596
|
+
store: rawStore,
|
|
3597
|
+
unitType: 'request',
|
|
3598
|
+
getClient: () => closeClient,
|
|
3599
|
+
onSessionSettlement: (ctx) => {
|
|
3600
|
+
events.push({
|
|
3601
|
+
trigger: ctx.trigger,
|
|
3602
|
+
txHash: ctx.txHash,
|
|
3603
|
+
amount: ctx.amount,
|
|
3604
|
+
delta: ctx.delta,
|
|
3605
|
+
})
|
|
3606
|
+
},
|
|
3607
|
+
})
|
|
3608
|
+
|
|
3609
|
+
await persistPrecompileChannel(channelStore(rawStore), openPayload, {
|
|
3610
|
+
payee: payer.address,
|
|
3611
|
+
spent: 100n,
|
|
3612
|
+
highestVoucherAmount: 100n,
|
|
3613
|
+
highestVoucher: {
|
|
3614
|
+
channelId: openPayload.channelId,
|
|
3615
|
+
cumulativeAmount: 100n,
|
|
3616
|
+
signature: '0x1234',
|
|
3617
|
+
},
|
|
3618
|
+
})
|
|
3619
|
+
|
|
3620
|
+
const closeSignature = await Voucher.signVoucher(
|
|
3621
|
+
createSigningClient(),
|
|
3622
|
+
payer,
|
|
3623
|
+
{ channelId: openPayload.channelId, cumulativeAmount: 100n },
|
|
3624
|
+
tip20ChannelEscrow,
|
|
3625
|
+
chainId,
|
|
3626
|
+
)
|
|
3627
|
+
|
|
3628
|
+
await method.verify({
|
|
3629
|
+
credential: {
|
|
3630
|
+
challenge: makeChallenge(openPayload.channelId),
|
|
3631
|
+
payload: {
|
|
3632
|
+
action: 'close',
|
|
3633
|
+
channelId: openPayload.channelId,
|
|
3634
|
+
descriptor: openPayload.descriptor,
|
|
3635
|
+
cumulativeAmount: '100',
|
|
3636
|
+
signature: closeSignature,
|
|
3637
|
+
},
|
|
3638
|
+
},
|
|
3639
|
+
request: verifyRequest(openPayload.channelId),
|
|
3640
|
+
})
|
|
3641
|
+
|
|
3642
|
+
expect(events).toHaveLength(1)
|
|
3643
|
+
expect(events[0]).toMatchObject({
|
|
3644
|
+
trigger: 'close',
|
|
3645
|
+
txHash: `0x${'dd'.repeat(32)}`,
|
|
3646
|
+
amount: closedAmount,
|
|
3647
|
+
delta: closedAmount,
|
|
3648
|
+
})
|
|
3649
|
+
})
|
|
3650
|
+
|
|
3651
|
+
test('delta reflects incremental settlement when channel has prior on-chain settlement', async () => {
|
|
3652
|
+
const events: { trigger: string; amount: bigint; delta: bigint }[] = []
|
|
3653
|
+
const store = Store.memory()
|
|
3654
|
+
const openPayload = await createOpenPayload()
|
|
3655
|
+
await persistPrecompileChannel(channelStore(store), openPayload, {
|
|
3656
|
+
payee: payer.address,
|
|
3657
|
+
spent: 300n,
|
|
3658
|
+
settledOnChain: 200n,
|
|
3659
|
+
highestVoucherAmount: 300n,
|
|
3660
|
+
highestVoucher: {
|
|
3661
|
+
channelId: openPayload.channelId,
|
|
3662
|
+
cumulativeAmount: 300n,
|
|
3663
|
+
signature: '0x1234',
|
|
3664
|
+
},
|
|
3665
|
+
})
|
|
3666
|
+
|
|
3667
|
+
const { settle } = await import('./Session.js')
|
|
3668
|
+
const client = createSettleClient(openPayload.channelId, 300n)
|
|
3669
|
+
await settle(store, client, openPayload.channelId, {
|
|
3670
|
+
onSessionSettlement: (ctx) => {
|
|
3671
|
+
events.push({ trigger: ctx.trigger, amount: ctx.amount, delta: ctx.delta })
|
|
3672
|
+
},
|
|
3673
|
+
})
|
|
3674
|
+
|
|
3675
|
+
expect(events).toHaveLength(1)
|
|
3676
|
+
expect(events[0]).toMatchObject({
|
|
3677
|
+
trigger: 'settle',
|
|
3678
|
+
amount: 300n,
|
|
3679
|
+
delta: 100n,
|
|
3680
|
+
})
|
|
3681
|
+
})
|
|
3682
|
+
|
|
3683
|
+
test('delta reflects incremental close when channel has prior on-chain settlement', async () => {
|
|
3684
|
+
const events: { trigger: string; amount: bigint; delta: bigint }[] = []
|
|
3685
|
+
const openPayload = await createOpenPayload()
|
|
3686
|
+
const priorSettled = 50n
|
|
3687
|
+
const closedAmount = 100n
|
|
3688
|
+
const refundedAmount = 900n
|
|
3689
|
+
const rawStore = Store.memory()
|
|
3690
|
+
|
|
3691
|
+
const closeClient = createClient({
|
|
3692
|
+
account: payer,
|
|
3693
|
+
chain: testChain,
|
|
3694
|
+
transport: custom({
|
|
3695
|
+
async request(args) {
|
|
3696
|
+
if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
|
|
3697
|
+
if (args.method === 'eth_getTransactionCount') return '0x0'
|
|
3698
|
+
if (args.method === 'eth_estimateGas') return '0x5208'
|
|
3699
|
+
if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
|
|
3700
|
+
if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
|
|
3701
|
+
if (args.method === 'eth_sendRawTransaction') return `0x${'dd'.repeat(32)}`
|
|
3702
|
+
if (args.method === 'eth_sendTransaction') return `0x${'dd'.repeat(32)}`
|
|
3703
|
+
if (args.method === 'eth_getTransactionReceipt')
|
|
3704
|
+
return transactionReceipt([
|
|
3705
|
+
closedLog(openPayload.channelId, closedAmount, refundedAmount),
|
|
3706
|
+
])
|
|
3707
|
+
if (args.method === 'eth_call') {
|
|
3708
|
+
return encodeFunctionResult({
|
|
3709
|
+
abi: escrowAbi,
|
|
3710
|
+
functionName: 'getChannelState',
|
|
3711
|
+
result: { settled: priorSettled, deposit: 1_000n, closeRequestedAt: 0 },
|
|
3712
|
+
})
|
|
3713
|
+
}
|
|
3714
|
+
throw new Error(`unexpected rpc request: ${args.method}`)
|
|
3715
|
+
},
|
|
3716
|
+
}),
|
|
3717
|
+
})
|
|
3718
|
+
|
|
3719
|
+
const method = session({
|
|
3720
|
+
amount: '1',
|
|
3721
|
+
chainId,
|
|
3722
|
+
currency: token,
|
|
3723
|
+
decimals: 0,
|
|
3724
|
+
recipient: payee,
|
|
3725
|
+
store: rawStore,
|
|
3726
|
+
unitType: 'request',
|
|
3727
|
+
getClient: () => closeClient,
|
|
3728
|
+
onSessionSettlement: (ctx) => {
|
|
3729
|
+
events.push({ trigger: ctx.trigger, amount: ctx.amount, delta: ctx.delta })
|
|
3730
|
+
},
|
|
3731
|
+
})
|
|
3732
|
+
|
|
3733
|
+
await persistPrecompileChannel(channelStore(rawStore), openPayload, {
|
|
3734
|
+
payee: payer.address,
|
|
3735
|
+
spent: 100n,
|
|
3736
|
+
settledOnChain: priorSettled,
|
|
3737
|
+
highestVoucherAmount: 100n,
|
|
3738
|
+
highestVoucher: {
|
|
3739
|
+
channelId: openPayload.channelId,
|
|
3740
|
+
cumulativeAmount: 100n,
|
|
3741
|
+
signature: '0x1234',
|
|
3742
|
+
},
|
|
3743
|
+
})
|
|
3744
|
+
|
|
3745
|
+
const closeSignature = await Voucher.signVoucher(
|
|
3746
|
+
createSigningClient(),
|
|
3747
|
+
payer,
|
|
3748
|
+
{ channelId: openPayload.channelId, cumulativeAmount: 100n },
|
|
3749
|
+
tip20ChannelEscrow,
|
|
3750
|
+
chainId,
|
|
3751
|
+
)
|
|
3752
|
+
|
|
3753
|
+
await method.verify({
|
|
3754
|
+
credential: {
|
|
3755
|
+
challenge: makeChallenge(openPayload.channelId),
|
|
3756
|
+
payload: {
|
|
3757
|
+
action: 'close',
|
|
3758
|
+
channelId: openPayload.channelId,
|
|
3759
|
+
descriptor: openPayload.descriptor,
|
|
3760
|
+
cumulativeAmount: '100',
|
|
3761
|
+
signature: closeSignature,
|
|
3762
|
+
},
|
|
3763
|
+
},
|
|
3764
|
+
request: verifyRequest(openPayload.channelId),
|
|
3765
|
+
})
|
|
3766
|
+
|
|
3767
|
+
expect(events).toHaveLength(1)
|
|
3768
|
+
expect(events[0]).toMatchObject({
|
|
3769
|
+
trigger: 'close',
|
|
3770
|
+
amount: closedAmount,
|
|
3771
|
+
delta: closedAmount - priorSettled,
|
|
3772
|
+
})
|
|
3773
|
+
})
|
|
3774
|
+
|
|
3775
|
+
test('isolates errors in onSessionSettlement handler', async () => {
|
|
3776
|
+
const store = Store.memory()
|
|
3777
|
+
const openPayload = await createOpenPayload()
|
|
3778
|
+
await persistPrecompileChannel(channelStore(store), openPayload, {
|
|
3779
|
+
payee: payer.address,
|
|
3780
|
+
spent: 100n,
|
|
3781
|
+
highestVoucherAmount: 100n,
|
|
3782
|
+
highestVoucher: {
|
|
3783
|
+
channelId: openPayload.channelId,
|
|
3784
|
+
cumulativeAmount: 100n,
|
|
3785
|
+
signature: '0x1234',
|
|
3786
|
+
},
|
|
3787
|
+
})
|
|
3788
|
+
|
|
3789
|
+
const { settle } = await import('./Session.js')
|
|
3790
|
+
const client = createSettleClient(openPayload.channelId, 100n)
|
|
3791
|
+
|
|
3792
|
+
await expect(
|
|
3793
|
+
settle(store, client, openPayload.channelId, {
|
|
3794
|
+
onSessionSettlement: () => {
|
|
3795
|
+
throw new Error('observer exploded')
|
|
3796
|
+
},
|
|
3797
|
+
}),
|
|
3798
|
+
).resolves.toBe(`0x${'cc'.repeat(32)}`)
|
|
3799
|
+
})
|
|
3800
|
+
})
|
|
@@ -44,10 +44,16 @@ import {
|
|
|
44
44
|
import { respondToSessionCredential } from './RequestState.js'
|
|
45
45
|
import { applyVerifiedHttpAccounting, chargeSessionChannel } from './Settlement.js'
|
|
46
46
|
import { maybeSettleScheduled } from './Settlement.js'
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
resolveSettlementSchedule,
|
|
49
|
+
type OnSessionSettlement,
|
|
50
|
+
type SettlementSchedule,
|
|
51
|
+
} from './Settlement.js'
|
|
48
52
|
|
|
49
53
|
/** Server-side automatic settlement schedule. */
|
|
50
54
|
export type { SettlementSchedule } from './Settlement.js'
|
|
55
|
+
/** Server-side settlement event hook types. */
|
|
56
|
+
export type { OnSessionSettlement, SessionSettlementContext } from './Settlement.js'
|
|
51
57
|
/** Server-side hook types for request-identity channel bootstrap. */
|
|
52
58
|
export type {
|
|
53
59
|
ResolveSessionChannelId,
|
|
@@ -312,6 +318,7 @@ export function session<const parameters extends session.Parameters>(
|
|
|
312
318
|
unitType,
|
|
313
319
|
} = parameters
|
|
314
320
|
const settlementSchedule = resolveSettlementSchedule(parameters.settlementSchedule, decimals)
|
|
321
|
+
const onSessionSettlement = parameters.onSessionSettlement
|
|
315
322
|
|
|
316
323
|
const store = ChannelStore.fromStore(rawStore)
|
|
317
324
|
const lastOnChainVerified = new Map<Hex, number>()
|
|
@@ -424,6 +431,7 @@ export function session<const parameters extends session.Parameters>(
|
|
|
424
431
|
feeToken: parameters.feeToken,
|
|
425
432
|
lastOnChainVerified,
|
|
426
433
|
minVoucherDelta: context.minVoucherDelta,
|
|
434
|
+
onSessionSettlement,
|
|
427
435
|
payload,
|
|
428
436
|
store,
|
|
429
437
|
})
|
|
@@ -444,6 +452,7 @@ export function session<const parameters extends session.Parameters>(
|
|
|
444
452
|
...(typeof context.feePayer === 'object' ? { feePayer: context.feePayer } : {}),
|
|
445
453
|
feePayerPolicy: parameters.feePayerPolicy,
|
|
446
454
|
feeToken: parameters.feeToken,
|
|
455
|
+
onSessionSettlement,
|
|
447
456
|
schedule: settlementSchedule,
|
|
448
457
|
store,
|
|
449
458
|
channel,
|
|
@@ -492,9 +501,20 @@ export namespace session {
|
|
|
492
501
|
feePayerPolicy?: FeePayerPolicy | undefined
|
|
493
502
|
/** Minimum voucher delta to accept (numeric string, default: "0"). */
|
|
494
503
|
minVoucherDelta?: string | undefined
|
|
495
|
-
/**
|
|
504
|
+
/**
|
|
505
|
+
* Maps authenticated application identity and payment scope to an existing channel ID.
|
|
506
|
+
* Called only when the request does not already supply a channel ID. MPPx then loads that
|
|
507
|
+
* ID from `store` and validates the channel before including its snapshot in a challenge or
|
|
508
|
+
* bootstrap response. The store is not searched automatically and does not need to implement
|
|
509
|
+
* a secondary-index format.
|
|
510
|
+
*/
|
|
496
511
|
resolveChannelId?: ResolveSessionChannelId | undefined
|
|
497
|
-
/**
|
|
512
|
+
/**
|
|
513
|
+
* Enables same-route `HEAD` recovery before a paid request. The server issues a zero-amount
|
|
514
|
+
* identity challenge, verifies the returned proof, passes its authenticated `source` to
|
|
515
|
+
* `resolveChannelId`, validates the loaded channel's payment scope, and returns a snapshot
|
|
516
|
+
* containing the signed highest voucher for client rehydration.
|
|
517
|
+
*/
|
|
498
518
|
bootstrap?: boolean | undefined
|
|
499
519
|
/**
|
|
500
520
|
* Atomic store backend for channel state.
|
|
@@ -510,6 +530,8 @@ export namespace session {
|
|
|
510
530
|
chainId?: number | undefined
|
|
511
531
|
/** TIP20EscrowChannel precompile address override. */
|
|
512
532
|
escrowContract?: Address | undefined
|
|
533
|
+
/** Callback invoked after any on-chain settlement or close transaction is confirmed. */
|
|
534
|
+
onSessionSettlement?: OnSessionSettlement | undefined
|
|
513
535
|
/** Server-owned automatic settlement cadence. Clients do not receive or control this schedule. */
|
|
514
536
|
settlementSchedule?: SettlementSchedule | undefined
|
|
515
537
|
|
|
@@ -522,11 +544,12 @@ export namespace session {
|
|
|
522
544
|
/** Defaults derived from `session()` parameters for handler type inference. */
|
|
523
545
|
export type DeriveDefaults<parameters extends Parameters> = types.DeriveDefaults<
|
|
524
546
|
parameters,
|
|
525
|
-
Defaults
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
547
|
+
Defaults,
|
|
548
|
+
{
|
|
549
|
+
currency: string
|
|
550
|
+
decimals: number
|
|
551
|
+
}
|
|
552
|
+
>
|
|
530
553
|
}
|
|
531
554
|
|
|
532
555
|
/**
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
InsufficientBalanceError,
|
|
17
17
|
VerificationFailedError,
|
|
18
18
|
} from '../../../Errors.js'
|
|
19
|
+
import type { MaybePromise } from '../../../internal/types.js'
|
|
19
20
|
import type * as Method from '../../../Method.js'
|
|
20
21
|
import * as Store from '../../../Store.js'
|
|
21
22
|
import type * as FeePayer from '../../internal/fee-payer.js'
|
|
@@ -146,6 +147,23 @@ export type SettlementProgress = {
|
|
|
146
147
|
units: number
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
/** Context emitted when an on-chain settlement or close transaction is confirmed. */
|
|
151
|
+
export type SessionSettlementContext = Readonly<{
|
|
152
|
+
/** On-chain transaction hash (or signature on Solana). */
|
|
153
|
+
txHash: Hex
|
|
154
|
+
/** Channel ID that was settled. */
|
|
155
|
+
channelId: Hex
|
|
156
|
+
/** The trigger that caused settlement. */
|
|
157
|
+
trigger: 'settle' | 'close' | 'scheduled'
|
|
158
|
+
/** Cumulative amount settled on-chain to the payee (raw token units). */
|
|
159
|
+
amount: bigint
|
|
160
|
+
/** Incremental amount settled in this transaction (raw token units). */
|
|
161
|
+
delta: bigint
|
|
162
|
+
}>
|
|
163
|
+
|
|
164
|
+
/** Callback invoked after an on-chain settlement or close transaction is confirmed. */
|
|
165
|
+
export type OnSessionSettlement = (context: SessionSettlementContext) => MaybePromise<void>
|
|
166
|
+
|
|
149
167
|
/** Inputs used to mark a channel after automatic scheduled settlement succeeds. */
|
|
150
168
|
export type MarkSettlementCompleteParameters = {
|
|
151
169
|
channelId: ChannelStore.State['channelId']
|
|
@@ -340,6 +358,8 @@ export type SettlementTransactionOptions = {
|
|
|
340
358
|
feePayerPolicy?: Partial<FeePayer.Policy> | undefined
|
|
341
359
|
/** Optional fee token override for settlement. */
|
|
342
360
|
feeToken?: Address | undefined
|
|
361
|
+
/** Callback invoked after the settlement transaction is confirmed. */
|
|
362
|
+
onSessionSettlement?: OnSessionSettlement | undefined
|
|
343
363
|
}
|
|
344
364
|
|
|
345
365
|
/** Inputs for applying a server-owned automatic settlement schedule. */
|
|
@@ -356,6 +376,8 @@ export type MaybeSettleScheduledParameters = {
|
|
|
356
376
|
feePayerPolicy?: Partial<FeePayer.Policy> | undefined
|
|
357
377
|
/** Optional fee token override for settlement. */
|
|
358
378
|
feeToken?: Address | undefined
|
|
379
|
+
/** Callback invoked after the scheduled settlement transaction is confirmed. */
|
|
380
|
+
onSessionSettlement?: OnSessionSettlement | undefined
|
|
359
381
|
/** Resolved server-owned settlement cadence. */
|
|
360
382
|
schedule: ResolvedSettlementSchedule | undefined
|
|
361
383
|
/** Server-side channel store. */
|
|
@@ -400,6 +422,9 @@ export async function maybeSettleScheduled(
|
|
|
400
422
|
...(parameters.feePayer ? { feePayer: parameters.feePayer } : {}),
|
|
401
423
|
...(parameters.feePayerPolicy ? { feePayerPolicy: parameters.feePayerPolicy } : {}),
|
|
402
424
|
...(parameters.feeToken ? { feeToken: parameters.feeToken } : {}),
|
|
425
|
+
onSessionSettlement: parameters.onSessionSettlement
|
|
426
|
+
? (ctx) => parameters.onSessionSettlement!({ ...ctx, trigger: 'scheduled' })
|
|
427
|
+
: undefined,
|
|
403
428
|
})
|
|
404
429
|
await markSettlementComplete({ channelId: channel.channelId, store })
|
|
405
430
|
return txHash
|
|
@@ -466,6 +491,15 @@ export async function settle(
|
|
|
466
491
|
}
|
|
467
492
|
: current,
|
|
468
493
|
)
|
|
494
|
+
if (options?.onSessionSettlement) {
|
|
495
|
+
await emitSessionSettlement(options.onSessionSettlement, {
|
|
496
|
+
txHash,
|
|
497
|
+
channelId,
|
|
498
|
+
trigger: 'settle',
|
|
499
|
+
amount: newSettled,
|
|
500
|
+
delta: newSettled - channel.settledOnChain,
|
|
501
|
+
})
|
|
502
|
+
}
|
|
469
503
|
return txHash
|
|
470
504
|
}
|
|
471
505
|
|
|
@@ -480,3 +514,14 @@ export async function settleBatch(
|
|
|
480
514
|
for (const channelId of channelIds) hashes.push(await settle(store, client, channelId, options))
|
|
481
515
|
return hashes
|
|
482
516
|
}
|
|
517
|
+
|
|
518
|
+
async function emitSessionSettlement(
|
|
519
|
+
onSessionSettlement: OnSessionSettlement,
|
|
520
|
+
context: SessionSettlementContext,
|
|
521
|
+
): Promise<void> {
|
|
522
|
+
try {
|
|
523
|
+
await onSessionSettlement(Object.freeze(context))
|
|
524
|
+
} catch {
|
|
525
|
+
// Errors are isolated — observers cannot break the settlement flow.
|
|
526
|
+
}
|
|
527
|
+
}
|