mppx 0.6.31 → 0.7.0
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 +17 -0
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +9 -7
- package/dist/Challenge.js.map +1 -1
- package/dist/Constants.d.ts +46 -0
- package/dist/Constants.d.ts.map +1 -0
- package/dist/Constants.js +46 -0
- package/dist/Constants.js.map +1 -0
- package/dist/Credential.d.ts.map +1 -1
- package/dist/Credential.js +5 -4
- package/dist/Credential.js.map +1 -1
- package/dist/Method.d.ts +32 -4
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js +5 -2
- package/dist/Method.js.map +1 -1
- package/dist/Receipt.d.ts.map +1 -1
- package/dist/Receipt.js +3 -2
- package/dist/Receipt.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +19 -11
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/plugins/tempo.d.ts.map +1 -1
- package/dist/cli/plugins/tempo.js +17 -6
- package/dist/cli/plugins/tempo.js.map +1 -1
- package/dist/cli/utils.d.ts +5 -0
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +10 -0
- package/dist/cli/utils.js.map +1 -1
- package/dist/client/Methods.d.ts +5 -2
- package/dist/client/Methods.d.ts.map +1 -1
- package/dist/client/Methods.js +5 -2
- package/dist/client/Methods.js.map +1 -1
- package/dist/client/Transport.d.ts.map +1 -1
- package/dist/client/Transport.js +4 -5
- package/dist/client/Transport.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +14 -6
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/evm/server/Methods.d.ts +1 -1
- package/dist/evm/server/Methods.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/AcceptPayment.d.ts +3 -0
- package/dist/internal/AcceptPayment.d.ts.map +1 -1
- package/dist/internal/AcceptPayment.js +15 -11
- package/dist/internal/AcceptPayment.js.map +1 -1
- package/dist/mcp-sdk/client/McpClient.d.ts +12 -5
- package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.js +55 -42
- package/dist/mcp-sdk/client/McpClient.js.map +1 -1
- package/dist/server/Mppx.d.ts +11 -3
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +76 -27
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Response.d.ts.map +1 -1
- package/dist/server/Response.js +2 -1
- package/dist/server/Response.js.map +1 -1
- package/dist/server/Transport.d.ts.map +1 -1
- package/dist/server/Transport.js +4 -3
- package/dist/server/Transport.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/stripe/client/Charge.d.ts +1 -1
- package/dist/stripe/client/Charge.d.ts.map +1 -1
- package/dist/stripe/client/Charge.js +3 -1
- package/dist/stripe/client/Charge.js.map +1 -1
- package/dist/stripe/server/Charge.d.ts +1 -1
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js +9 -2
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/stripe/server/Methods.d.ts +1 -1
- package/dist/stripe/server/Methods.d.ts.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/Methods.d.ts +18 -0
- package/dist/tempo/Methods.d.ts.map +1 -1
- package/dist/tempo/Methods.js +16 -1
- package/dist/tempo/Methods.js.map +1 -1
- package/dist/tempo/client/Charge.d.ts +6 -0
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +9 -2
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +36 -7
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/client/Methods.js +12 -5
- package/dist/tempo/client/Methods.js.map +1 -1
- package/dist/tempo/client/index.d.ts +7 -4
- package/dist/tempo/client/index.d.ts.map +1 -1
- package/dist/tempo/client/index.js +5 -3
- package/dist/tempo/client/index.js.map +1 -1
- package/dist/tempo/index.d.ts +1 -0
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +1 -0
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts +21 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +109 -4
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/{client → legacy/client}/ChannelOps.d.ts +19 -6
- package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -0
- package/dist/tempo/{client → legacy/client}/ChannelOps.js +9 -3
- package/dist/tempo/legacy/client/ChannelOps.js.map +1 -0
- package/dist/tempo/{client → legacy/client}/Session.d.ts +23 -4
- package/dist/tempo/legacy/client/Session.d.ts.map +1 -0
- package/dist/tempo/{client → legacy/client}/Session.js +14 -7
- package/dist/tempo/legacy/client/Session.js.map +1 -0
- package/dist/tempo/{client → legacy/client}/SessionManager.d.ts +20 -5
- package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -0
- package/dist/tempo/{client → legacy/client}/SessionManager.js +20 -16
- package/dist/tempo/legacy/client/SessionManager.js.map +1 -0
- package/dist/tempo/legacy/client/index.d.ts +7 -0
- package/dist/tempo/legacy/client/index.d.ts.map +1 -0
- package/dist/tempo/legacy/client/index.js +5 -0
- package/dist/tempo/legacy/client/index.js.map +1 -0
- package/dist/tempo/legacy/index.d.ts +7 -0
- package/dist/tempo/legacy/index.d.ts.map +1 -0
- package/dist/tempo/legacy/index.js +7 -0
- package/dist/tempo/legacy/index.js.map +1 -0
- package/dist/tempo/{server → legacy/server}/Session.d.ts +28 -11
- package/dist/tempo/legacy/server/Session.d.ts.map +1 -0
- package/dist/tempo/{server → legacy/server}/Session.js +12 -10
- package/dist/tempo/legacy/server/Session.js.map +1 -0
- package/dist/tempo/legacy/server/index.d.ts +5 -0
- package/dist/tempo/legacy/server/index.d.ts.map +1 -0
- package/dist/tempo/legacy/server/index.js +5 -0
- package/dist/tempo/legacy/server/index.js.map +1 -0
- package/dist/tempo/{session → legacy/session}/Chain.d.ts +30 -23
- package/dist/tempo/legacy/session/Chain.d.ts.map +1 -0
- package/dist/tempo/{session → legacy/session}/Chain.js +12 -11
- package/dist/tempo/legacy/session/Chain.js.map +1 -0
- package/dist/tempo/{session → legacy/session}/Channel.d.ts +1 -0
- package/dist/tempo/legacy/session/Channel.d.ts.map +1 -0
- package/dist/tempo/legacy/session/Channel.js.map +1 -0
- package/dist/tempo/legacy/session/ChannelStore.d.ts +22 -0
- package/dist/tempo/legacy/session/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/legacy/session/ChannelStore.js +6 -0
- package/dist/tempo/legacy/session/ChannelStore.js.map +1 -0
- package/dist/tempo/legacy/session/Types.d.ts +73 -0
- package/dist/tempo/legacy/session/Types.d.ts.map +1 -0
- package/dist/tempo/legacy/session/Types.js.map +1 -0
- package/dist/tempo/{session → legacy/session}/Voucher.d.ts +4 -4
- package/dist/tempo/legacy/session/Voucher.d.ts.map +1 -0
- package/dist/tempo/{session → legacy/session}/Voucher.js +1 -1
- package/dist/tempo/legacy/session/Voucher.js.map +1 -0
- package/dist/tempo/{session → legacy/session}/escrow.abi.d.ts +1 -0
- package/dist/tempo/{session → legacy/session}/escrow.abi.d.ts.map +1 -1
- package/dist/tempo/{session → legacy/session}/escrow.abi.js +1 -0
- package/dist/tempo/legacy/session/escrow.abi.js.map +1 -0
- package/dist/tempo/legacy/session/index.d.ts +9 -0
- package/dist/tempo/legacy/session/index.d.ts.map +1 -0
- package/dist/tempo/legacy/session/index.js +9 -0
- package/dist/tempo/legacy/session/index.js.map +1 -0
- package/dist/tempo/server/Charge.d.ts +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +13 -16
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +63 -6
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +36 -8
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Subscription.d.ts +1 -1
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/index.d.ts +6 -5
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js +5 -5
- 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/server/internal/request-body.d.ts +7 -2
- package/dist/tempo/server/internal/request-body.d.ts.map +1 -1
- package/dist/tempo/server/internal/request-body.js +20 -3
- package/dist/tempo/server/internal/request-body.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts +8 -4
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +8 -7
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/Snapshot.d.ts +32 -0
- package/dist/tempo/session/Snapshot.d.ts.map +1 -0
- package/dist/tempo/session/Snapshot.js +37 -0
- package/dist/tempo/session/Snapshot.js.map +1 -0
- package/dist/tempo/session/client/ChannelOps.d.ts +82 -0
- package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -0
- package/dist/tempo/session/client/ChannelOps.js +204 -0
- package/dist/tempo/session/client/ChannelOps.js.map +1 -0
- package/dist/tempo/session/client/CredentialState.d.ts +262 -0
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -0
- package/dist/tempo/session/client/CredentialState.js +417 -0
- package/dist/tempo/session/client/CredentialState.js.map +1 -0
- package/dist/tempo/session/client/ReceiptCoordinator.d.ts +26 -0
- package/dist/tempo/session/client/ReceiptCoordinator.d.ts.map +1 -0
- package/dist/tempo/session/client/ReceiptCoordinator.js +61 -0
- package/dist/tempo/session/client/ReceiptCoordinator.js.map +1 -0
- package/dist/tempo/session/client/Runtime.d.ts +464 -0
- package/dist/tempo/session/client/Runtime.d.ts.map +1 -0
- package/dist/tempo/session/client/Runtime.js +499 -0
- package/dist/tempo/session/client/Runtime.js.map +1 -0
- package/dist/tempo/session/client/Session.d.ts +132 -0
- package/dist/tempo/session/client/Session.d.ts.map +1 -0
- package/dist/tempo/session/client/Session.js +55 -0
- package/dist/tempo/session/client/Session.js.map +1 -0
- package/dist/tempo/session/client/SessionManager.d.ts +120 -0
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -0
- package/dist/tempo/session/client/SessionManager.js +627 -0
- package/dist/tempo/session/client/SessionManager.js.map +1 -0
- package/dist/tempo/session/client/Transports.d.ts +449 -0
- package/dist/tempo/session/client/Transports.d.ts.map +1 -0
- package/dist/tempo/session/client/Transports.js +721 -0
- package/dist/tempo/session/client/Transports.js.map +1 -0
- package/dist/tempo/session/client/index.d.ts +12 -0
- package/dist/tempo/session/client/index.d.ts.map +1 -0
- package/dist/tempo/session/client/index.js +5 -0
- package/dist/tempo/session/client/index.js.map +1 -0
- package/dist/tempo/session/index.d.ts +7 -8
- package/dist/tempo/session/index.d.ts.map +1 -1
- package/dist/tempo/session/index.js +7 -8
- package/dist/tempo/session/index.js.map +1 -1
- package/dist/tempo/session/precompile/Chain.d.ts +319 -0
- package/dist/tempo/session/precompile/Chain.d.ts.map +1 -0
- package/dist/tempo/session/precompile/Chain.js +492 -0
- package/dist/tempo/session/precompile/Chain.js.map +1 -0
- package/dist/tempo/session/precompile/Channel.d.ts +46 -0
- package/dist/tempo/session/precompile/Channel.d.ts.map +1 -0
- package/dist/tempo/session/precompile/Channel.js +56 -0
- package/dist/tempo/session/precompile/Channel.js.map +1 -0
- package/dist/tempo/session/precompile/Protocol.d.ts +308 -0
- package/dist/tempo/session/precompile/Protocol.d.ts.map +1 -0
- package/dist/tempo/session/precompile/Protocol.js +264 -0
- package/dist/tempo/session/precompile/Protocol.js.map +1 -0
- package/dist/tempo/session/precompile/Voucher.d.ts +40 -0
- package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -0
- package/dist/tempo/session/precompile/Voucher.js +126 -0
- package/dist/tempo/session/precompile/Voucher.js.map +1 -0
- package/dist/tempo/session/precompile/escrow.abi.d.ts +522 -0
- package/dist/tempo/session/precompile/escrow.abi.d.ts.map +1 -0
- package/dist/tempo/session/precompile/escrow.abi.js +224 -0
- package/dist/tempo/session/precompile/escrow.abi.js.map +1 -0
- package/dist/tempo/session/precompile/index.d.ts +24 -0
- package/dist/tempo/session/precompile/index.d.ts.map +1 -0
- package/dist/tempo/session/precompile/index.js +22 -0
- package/dist/tempo/session/precompile/index.js.map +1 -0
- package/dist/tempo/session/server/ChannelOps.d.ts +56 -0
- package/dist/tempo/session/server/ChannelOps.d.ts.map +1 -0
- package/dist/tempo/session/server/ChannelOps.js +91 -0
- package/dist/tempo/session/server/ChannelOps.js.map +1 -0
- package/dist/tempo/session/server/ChannelStore.d.ts +347 -0
- package/dist/tempo/session/server/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/session/server/ChannelStore.js +404 -0
- package/dist/tempo/session/server/ChannelStore.js.map +1 -0
- package/dist/tempo/session/server/CredentialVerification.d.ts +85 -0
- package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -0
- package/dist/tempo/session/server/CredentialVerification.js +494 -0
- package/dist/tempo/session/server/CredentialVerification.js.map +1 -0
- package/dist/tempo/session/server/MeteredStream.d.ts +40 -0
- package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -0
- package/dist/tempo/session/server/MeteredStream.js +42 -0
- package/dist/tempo/session/server/MeteredStream.js.map +1 -0
- package/dist/tempo/session/server/RequestState.d.ts +208 -0
- package/dist/tempo/session/server/RequestState.d.ts.map +1 -0
- package/dist/tempo/session/server/RequestState.js +252 -0
- package/dist/tempo/session/server/RequestState.js.map +1 -0
- package/dist/tempo/session/server/Session.d.ts +169 -0
- package/dist/tempo/session/server/Session.d.ts.map +1 -0
- package/dist/tempo/session/server/Session.js +351 -0
- package/dist/tempo/session/server/Session.js.map +1 -0
- package/dist/tempo/session/server/Settlement.d.ts +185 -0
- package/dist/tempo/session/server/Settlement.d.ts.map +1 -0
- package/dist/tempo/session/server/Settlement.js +250 -0
- package/dist/tempo/session/server/Settlement.js.map +1 -0
- package/dist/tempo/session/{Sse.d.ts → server/Sse.d.ts} +9 -56
- package/dist/tempo/session/server/Sse.d.ts.map +1 -0
- package/dist/tempo/session/server/Sse.js +184 -0
- package/dist/tempo/session/server/Sse.js.map +1 -0
- package/dist/tempo/session/server/Transports.d.ts +89 -0
- package/dist/tempo/session/server/Transports.d.ts.map +1 -0
- package/dist/tempo/session/server/Transports.js +149 -0
- package/dist/tempo/session/server/Transports.js.map +1 -0
- package/dist/tempo/session/server/Ws.d.ts +48 -0
- package/dist/tempo/session/server/Ws.d.ts.map +1 -0
- package/dist/tempo/session/server/Ws.js +244 -0
- package/dist/tempo/session/server/Ws.js.map +1 -0
- package/dist/tempo/session/server/index.d.ts +4 -0
- package/dist/tempo/session/server/index.d.ts.map +1 -0
- package/dist/tempo/session/server/index.js +2 -0
- package/dist/tempo/session/server/index.js.map +1 -0
- package/package.json +6 -1
- package/src/Challenge.ts +9 -7
- package/src/Constants.ts +58 -0
- package/src/Credential.ts +5 -4
- package/src/Method.ts +46 -5
- package/src/Receipt.ts +3 -2
- package/src/cli/cli.test.ts +23 -28
- package/src/cli/cli.ts +23 -10
- package/src/cli/mcp.test.ts +21 -7
- package/src/cli/plugins/tempo.ts +21 -8
- package/src/cli/utils.test.ts +25 -1
- package/src/cli/utils.ts +10 -0
- package/src/client/Methods.ts +5 -2
- package/src/client/Mppx.test-d.ts +10 -0
- package/src/client/Mppx.test.ts +75 -0
- package/src/client/Transport.ts +4 -5
- package/src/client/index.ts +11 -1
- package/src/client/internal/Fetch.test.ts +29 -4
- package/src/client/internal/Fetch.ts +17 -5
- package/src/env.d.ts +1 -1
- package/src/index.ts +1 -0
- package/src/internal/AcceptPayment.test.ts +61 -0
- package/src/internal/AcceptPayment.ts +21 -14
- package/src/mcp-sdk/client/McpClient.integration.test.ts +8 -7
- package/src/mcp-sdk/client/McpClient.test-d.ts +7 -0
- package/src/mcp-sdk/client/McpClient.ts +99 -67
- package/src/mcp-sdk/client/McpClient.unit.test.ts +131 -0
- package/src/middlewares/elysia.test.ts +8 -4
- package/src/middlewares/express.test.ts +8 -4
- package/src/middlewares/hono.test.ts +4 -4
- package/src/middlewares/nextjs.test.ts +8 -4
- package/src/proxy/Proxy.test.ts +8 -8
- package/src/server/Mppx.test-d.ts +54 -0
- package/src/server/Mppx.test.ts +200 -7
- package/src/server/Mppx.ts +487 -406
- package/src/server/Response.ts +2 -1
- package/src/server/Transport.ts +4 -3
- package/src/server/index.ts +1 -0
- package/src/stripe/client/Charge.test.ts +20 -5
- package/src/stripe/client/Charge.ts +6 -2
- package/src/stripe/server/Charge.test.ts +114 -1
- package/src/stripe/server/Charge.ts +13 -2
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/AccessKeyAuthorization.test.ts +4 -94
- package/src/tempo/Methods.test.ts +45 -17
- package/src/tempo/Methods.ts +22 -0
- package/src/tempo/PublicExports.test-d.ts +105 -0
- package/src/tempo/client/Charge.test.ts +85 -0
- package/src/tempo/client/Charge.ts +19 -2
- package/src/tempo/client/Methods.ts +18 -6
- package/src/tempo/client/index.ts +15 -4
- package/src/tempo/index.ts +1 -0
- package/src/tempo/internal/fee-payer.test.ts +241 -17
- package/src/tempo/internal/fee-payer.ts +150 -4
- package/src/tempo/internal/fee-token.test.ts +14 -9
- package/src/tempo/legacy/AccessKeyAuthorization.test.ts +162 -0
- package/src/tempo/legacy/README.md +9 -0
- package/src/tempo/{client → legacy/client}/ChannelOps.test.ts +6 -7
- package/src/tempo/{client → legacy/client}/ChannelOps.ts +22 -9
- package/src/tempo/{client → legacy/client}/Session.test.ts +51 -9
- package/src/tempo/{client → legacy/client}/Session.ts +25 -11
- package/src/tempo/{client → legacy/client}/SessionManager.test.ts +81 -9
- package/src/tempo/{client → legacy/client}/SessionManager.ts +41 -20
- package/src/tempo/legacy/client/index.ts +6 -0
- package/src/tempo/legacy/index.ts +6 -0
- package/src/tempo/{server → legacy/server}/Session.test.ts +45 -45
- package/src/tempo/{server → legacy/server}/Session.ts +32 -23
- package/src/tempo/legacy/server/index.ts +4 -0
- package/src/tempo/{session → legacy/session}/Chain.test.ts +3 -4
- package/src/tempo/{session → legacy/session}/Chain.ts +94 -63
- package/src/tempo/{session → legacy/session}/Channel.ts +1 -0
- package/src/tempo/legacy/session/ChannelStore.test.ts +58 -0
- package/src/tempo/legacy/session/ChannelStore.ts +39 -0
- package/src/tempo/legacy/session/Types.ts +91 -0
- package/src/tempo/{session → legacy/session}/Voucher.ts +12 -8
- package/src/tempo/{session → legacy/session}/escrow.abi.ts +1 -0
- package/src/tempo/legacy/session/index.ts +8 -0
- package/src/tempo/server/AtomicStore.test-d.ts +16 -11
- package/src/tempo/server/Charge.test.ts +92 -14
- package/src/tempo/server/Charge.ts +18 -16
- package/src/tempo/server/Methods.ts +54 -8
- package/src/tempo/server/Sse.test.ts +2 -2
- package/src/tempo/server/index.ts +6 -5
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/server/internal/request-body.test.ts +37 -4
- package/src/tempo/server/internal/request-body.ts +25 -6
- package/src/tempo/server/internal/transport.test.ts +4 -4
- package/src/tempo/server/internal/transport.ts +19 -10
- package/src/tempo/session/Snapshot.test.ts +41 -0
- package/src/tempo/session/Snapshot.ts +74 -0
- package/src/tempo/session/client/ChannelOps.test.ts +163 -0
- package/src/tempo/session/client/ChannelOps.ts +344 -0
- package/src/tempo/session/client/CredentialState.test.ts +645 -0
- package/src/tempo/session/client/CredentialState.ts +814 -0
- package/src/tempo/session/client/ReceiptCoordinator.ts +95 -0
- package/src/tempo/session/client/Runtime.test.ts +1092 -0
- package/src/tempo/session/client/Runtime.ts +986 -0
- package/src/tempo/session/client/Session.test.ts +734 -0
- package/src/tempo/session/client/Session.ts +97 -0
- package/src/tempo/session/client/SessionManager.test.ts +1308 -0
- package/src/tempo/session/client/SessionManager.ts +845 -0
- package/src/tempo/session/client/Transports.test.ts +837 -0
- package/src/tempo/session/client/Transports.ts +1292 -0
- package/src/tempo/session/client/index.ts +37 -0
- package/src/tempo/session/index.ts +7 -8
- package/src/tempo/session/precompile/Chain.integration.test.ts +321 -0
- package/src/tempo/session/precompile/Chain.test.ts +1258 -0
- package/src/tempo/session/precompile/Chain.ts +979 -0
- package/src/tempo/session/precompile/Channel.test.ts +138 -0
- package/src/tempo/session/precompile/Channel.ts +103 -0
- package/src/tempo/session/precompile/Protocol.test.ts +358 -0
- package/src/tempo/session/precompile/Protocol.ts +520 -0
- package/src/tempo/session/precompile/Voucher.test.ts +316 -0
- package/src/tempo/session/precompile/Voucher.ts +160 -0
- package/src/tempo/session/precompile/escrow.abi.ts +226 -0
- package/src/tempo/session/precompile/index.ts +33 -0
- package/src/tempo/session/server/ChannelOps.test.ts +129 -0
- package/src/tempo/session/server/ChannelOps.ts +157 -0
- package/src/tempo/session/{ChannelStore.test.ts → server/ChannelStore.test.ts} +536 -29
- package/src/tempo/session/server/ChannelStore.ts +835 -0
- package/src/tempo/session/server/CredentialVerification.test.ts +146 -0
- package/src/tempo/session/server/CredentialVerification.ts +710 -0
- package/src/tempo/session/server/MeteredStream.ts +88 -0
- package/src/tempo/session/server/RequestState.test.ts +531 -0
- package/src/tempo/session/server/RequestState.ts +499 -0
- package/src/tempo/session/server/Session.integration.test.ts +444 -0
- package/src/tempo/session/server/Session.test.ts +3253 -0
- package/src/tempo/session/server/Session.ts +543 -0
- package/src/tempo/session/server/Settlement.test.ts +242 -0
- package/src/tempo/session/server/Settlement.ts +470 -0
- package/src/tempo/session/{Sse.test.ts → server/Sse.test.ts} +37 -3
- package/src/tempo/session/server/Sse.ts +256 -0
- package/src/tempo/session/server/Transports.test.ts +346 -0
- package/src/tempo/session/server/Transports.ts +255 -0
- package/src/tempo/session/{Ws.test.ts → server/Ws.test.ts} +4 -4
- package/src/tempo/session/server/Ws.ts +384 -0
- package/src/tempo/session/server/index.ts +8 -0
- package/dist/tempo/client/ChannelOps.d.ts.map +0 -1
- package/dist/tempo/client/ChannelOps.js.map +0 -1
- package/dist/tempo/client/Session.d.ts.map +0 -1
- package/dist/tempo/client/Session.js.map +0 -1
- package/dist/tempo/client/SessionManager.d.ts.map +0 -1
- package/dist/tempo/client/SessionManager.js.map +0 -1
- package/dist/tempo/server/Session.d.ts.map +0 -1
- package/dist/tempo/server/Session.js.map +0 -1
- package/dist/tempo/session/Chain.d.ts.map +0 -1
- package/dist/tempo/session/Chain.js.map +0 -1
- package/dist/tempo/session/Channel.d.ts.map +0 -1
- package/dist/tempo/session/Channel.js.map +0 -1
- package/dist/tempo/session/ChannelStore.d.ts +0 -117
- package/dist/tempo/session/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/session/ChannelStore.js +0 -172
- package/dist/tempo/session/ChannelStore.js.map +0 -1
- package/dist/tempo/session/Receipt.d.ts +0 -22
- package/dist/tempo/session/Receipt.d.ts.map +0 -1
- package/dist/tempo/session/Receipt.js +0 -34
- package/dist/tempo/session/Receipt.js.map +0 -1
- package/dist/tempo/session/Sse.d.ts.map +0 -1
- package/dist/tempo/session/Sse.js +0 -363
- package/dist/tempo/session/Sse.js.map +0 -1
- package/dist/tempo/session/Types.d.ts +0 -78
- package/dist/tempo/session/Types.d.ts.map +0 -1
- package/dist/tempo/session/Types.js.map +0 -1
- package/dist/tempo/session/Voucher.d.ts.map +0 -1
- package/dist/tempo/session/Voucher.js.map +0 -1
- package/dist/tempo/session/Ws.d.ts +0 -87
- package/dist/tempo/session/Ws.d.ts.map +0 -1
- package/dist/tempo/session/Ws.js +0 -443
- package/dist/tempo/session/Ws.js.map +0 -1
- package/dist/tempo/session/escrow.abi.js.map +0 -1
- package/src/tempo/session/ChannelStore.ts +0 -308
- package/src/tempo/session/Receipt.test.ts +0 -89
- package/src/tempo/session/Receipt.ts +0 -46
- package/src/tempo/session/Sse.ts +0 -462
- package/src/tempo/session/Types.ts +0 -86
- package/src/tempo/session/Ws.ts +0 -576
- /package/dist/tempo/{session → legacy/session}/Channel.js +0 -0
- /package/dist/tempo/{session → legacy/session}/Types.js +0 -0
- /package/src/tempo/{session → legacy/session}/Channel.test.ts +0 -0
- /package/src/tempo/{session → legacy/session}/Voucher.test.ts +0 -0
- /package/src/tempo/session/{Sse.fuzz.test.ts → server/Sse.fuzz.test.ts} +0 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
import type { Address, Hex } from 'viem'
|
|
2
|
+
|
|
3
|
+
import type * as Challenge from '../../../Challenge.js'
|
|
4
|
+
import {
|
|
5
|
+
AmountExceedsDepositError,
|
|
6
|
+
ChannelClosedError,
|
|
7
|
+
ChannelNotFoundError,
|
|
8
|
+
DeltaTooSmallError,
|
|
9
|
+
InvalidSignatureError,
|
|
10
|
+
VerificationFailedError,
|
|
11
|
+
} from '../../../Errors.js'
|
|
12
|
+
import type * as Store from '../../../Store.js'
|
|
13
|
+
import type * as Chain from '../precompile/Chain.js'
|
|
14
|
+
import type * as PrecompileChannel from '../precompile/Channel.js'
|
|
15
|
+
import { createSessionReceipt } from '../precompile/Protocol.js'
|
|
16
|
+
import type { SessionReceipt, SessionSignedVoucher } from '../precompile/Protocol.js'
|
|
17
|
+
import { uint96, type SignedVoucher } from '../precompile/Protocol.js'
|
|
18
|
+
import * as Voucher from '../precompile/Voucher.js'
|
|
19
|
+
import {
|
|
20
|
+
assertSameDescriptor,
|
|
21
|
+
validateChannelDescriptor,
|
|
22
|
+
validateChannelState,
|
|
23
|
+
} from './CredentialVerification.js'
|
|
24
|
+
import type { SessionMethodDetails } from './RequestState.js'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* State for an on-chain payment channel, including per-session accounting.
|
|
28
|
+
*
|
|
29
|
+
* Tracks the channel's identity, on-chain balance, the highest voucher
|
|
30
|
+
* the server has accepted, and the current session's spend counters.
|
|
31
|
+
* A channel is created when a payer opens an escrow on-chain and persists
|
|
32
|
+
* until the channel is finalized (closed/settled).
|
|
33
|
+
*
|
|
34
|
+
* One channel = one session. The client owns the key and can't race with
|
|
35
|
+
* itself, so concurrent session support is unnecessary.
|
|
36
|
+
*
|
|
37
|
+
* Monotonicity invariants (enforced by update callbacks):
|
|
38
|
+
* - `highestVoucherAmount` only increases
|
|
39
|
+
* - `settledOnChain` only increases
|
|
40
|
+
* - `deposit` reflects the latest on-chain value
|
|
41
|
+
*/
|
|
42
|
+
export type State = BaseState & BackendState
|
|
43
|
+
|
|
44
|
+
/** Result of an atomic channel deduction attempt. */
|
|
45
|
+
export type DeductResult = { ok: true; channel: State } | { ok: false; channel: State }
|
|
46
|
+
|
|
47
|
+
/** Persisted channel state after narrowing to the TIP-1034 precompile backend. */
|
|
48
|
+
export type StoredPrecompileChannel = BaseState & PrecompileBackendState
|
|
49
|
+
|
|
50
|
+
/** On-chain state fields read from the TIP-1034 precompile. */
|
|
51
|
+
export type OnChainChannelState = {
|
|
52
|
+
/** Current on-chain channel deposit. */
|
|
53
|
+
deposit: bigint
|
|
54
|
+
/** Cumulative amount settled on-chain. */
|
|
55
|
+
settled: bigint
|
|
56
|
+
/** Close-request timestamp, or zero when open. */
|
|
57
|
+
closeRequestedAt: number | bigint
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Persisted fields that mirror authoritative on-chain channel state. */
|
|
61
|
+
export type ActiveOnChainStateFields = Pick<
|
|
62
|
+
State,
|
|
63
|
+
'closeRequestedAt' | 'deposit' | 'settledOnChain'
|
|
64
|
+
>
|
|
65
|
+
|
|
66
|
+
/** Parameters for selecting the persisted highest accepted voucher. */
|
|
67
|
+
export type HighestVoucherParameters = {
|
|
68
|
+
/** Channel ID used when constructing a new voucher record. */
|
|
69
|
+
channelId: Hex
|
|
70
|
+
/** Existing persisted voucher amount and signature, when present. */
|
|
71
|
+
current?: Pick<State, 'highestVoucher' | 'highestVoucherAmount'> | null | undefined
|
|
72
|
+
/** Candidate cumulative voucher amount. */
|
|
73
|
+
cumulativeAmount: bigint
|
|
74
|
+
/** Candidate voucher signature. */
|
|
75
|
+
signature: Hex
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Highest accepted voucher amount plus signed voucher payload. */
|
|
79
|
+
export type HighestVoucher = {
|
|
80
|
+
/** Highest cumulative voucher amount to persist. */
|
|
81
|
+
highestVoucherAmount: bigint
|
|
82
|
+
/** Signed voucher corresponding to `highestVoucherAmount`. */
|
|
83
|
+
highestVoucher: SessionSignedVoucher | null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Inputs for resolving the amount a close transaction should capture. */
|
|
87
|
+
export type ResolveCloseCaptureAmountParameters = {
|
|
88
|
+
/** Close voucher cumulative amount. */
|
|
89
|
+
cumulativeAmount: bigint
|
|
90
|
+
/** Current on-chain deposit. */
|
|
91
|
+
onChainDeposit: bigint
|
|
92
|
+
/** Current on-chain settled amount. */
|
|
93
|
+
onChainSettled: bigint
|
|
94
|
+
/** Locally recorded paid spend. */
|
|
95
|
+
spent: bigint
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Inputs used when persisting or reconciling a successful open transaction. */
|
|
99
|
+
export type OpenChannelStateParameters = {
|
|
100
|
+
/** Address stored as the effective voucher signer for this channel. */
|
|
101
|
+
authorizedSigner: Address
|
|
102
|
+
/** Chain ID used by the opened channel. */
|
|
103
|
+
chainId: number
|
|
104
|
+
/** Normalized channel ID. */
|
|
105
|
+
channelId: Hex
|
|
106
|
+
/** Existing channel state, when one was already persisted. */
|
|
107
|
+
current: State | null
|
|
108
|
+
/** Descriptor verified from the open transaction. */
|
|
109
|
+
descriptor: PrecompileChannel.ChannelDescriptor
|
|
110
|
+
/** Escrow precompile address used by the channel. */
|
|
111
|
+
escrow: Address
|
|
112
|
+
/** Transaction-bound expiring nonce hash from the open call. */
|
|
113
|
+
expiringNonceHash: Hex
|
|
114
|
+
/** Initial or highest accepted cumulative voucher amount. */
|
|
115
|
+
cumulativeAmount: bigint
|
|
116
|
+
/** Voucher signature for `cumulativeAmount`. */
|
|
117
|
+
signature: Hex
|
|
118
|
+
/** Latest on-chain channel state read after open. */
|
|
119
|
+
state: Chain.ChannelState
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Inputs for merging a successful top-up transaction into persisted channel state. */
|
|
123
|
+
export type TopUpStateParameters = {
|
|
124
|
+
/** Existing channel state, when one is currently persisted. */
|
|
125
|
+
current: State | null
|
|
126
|
+
/** Latest verified on-chain channel state read after top-up. */
|
|
127
|
+
state: Chain.ChannelState
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Inputs for merging a newly accepted voucher with persisted channel state. */
|
|
131
|
+
export type AcceptVoucherStateUpdateParameters = {
|
|
132
|
+
/** Latest on-chain channel state used to validate the voucher. */
|
|
133
|
+
channelState: Chain.ChannelState
|
|
134
|
+
/** Existing channel state being updated. */
|
|
135
|
+
current: State | null
|
|
136
|
+
/** Verified signed voucher. */
|
|
137
|
+
voucher: SessionSignedVoucher
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Inputs for marking a channel as pending cooperative close. */
|
|
141
|
+
export type MarkPendingCloseParameters = {
|
|
142
|
+
/** Timestamp used to mark the local pending-close state. */
|
|
143
|
+
closeRequestedAt: bigint
|
|
144
|
+
/** Close voucher cumulative amount. */
|
|
145
|
+
cumulativeAmount: bigint
|
|
146
|
+
/** Existing channel state being updated. */
|
|
147
|
+
current: State | null
|
|
148
|
+
/** Current on-chain deposit. */
|
|
149
|
+
onChainDeposit: bigint
|
|
150
|
+
/** Current on-chain settled amount. */
|
|
151
|
+
onChainSettled: bigint
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Result of preparing local state for a cooperative close. */
|
|
155
|
+
export type PendingCloseUpdate = {
|
|
156
|
+
/** Amount that should be captured on-chain at close. */
|
|
157
|
+
captureAmount: bigint
|
|
158
|
+
/** Next local channel state, or null if no channel exists. */
|
|
159
|
+
state: State | null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Inputs for finalizing local state after a successful close transaction. */
|
|
163
|
+
export type FinalizeClosedChannelParameters = {
|
|
164
|
+
/** Amount captured to payee during close. */
|
|
165
|
+
captureAmount: bigint
|
|
166
|
+
/** Normalized channel ID. */
|
|
167
|
+
channelId: Hex
|
|
168
|
+
/** Close voucher cumulative amount. */
|
|
169
|
+
cumulativeAmount: bigint
|
|
170
|
+
/** Existing channel state being updated. */
|
|
171
|
+
current: State | null
|
|
172
|
+
/** Close voucher signature. */
|
|
173
|
+
signature: Hex
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Immutable channel identity fields derived from a verified TIP-1034 descriptor. */
|
|
177
|
+
export type PrecompileChannelIdentity = Pick<
|
|
178
|
+
BaseState & PrecompileBackendState,
|
|
179
|
+
| 'authorizedSigner'
|
|
180
|
+
| 'backend'
|
|
181
|
+
| 'chainId'
|
|
182
|
+
| 'channelId'
|
|
183
|
+
| 'descriptor'
|
|
184
|
+
| 'escrowContract'
|
|
185
|
+
| 'expiringNonceHash'
|
|
186
|
+
| 'operator'
|
|
187
|
+
| 'payee'
|
|
188
|
+
| 'payer'
|
|
189
|
+
| 'salt'
|
|
190
|
+
| 'token'
|
|
191
|
+
>
|
|
192
|
+
|
|
193
|
+
/** Inputs used to derive immutable persisted channel identity fields. */
|
|
194
|
+
export type ResolvePrecompileChannelIdentityParameters = {
|
|
195
|
+
/** Address stored as the effective voucher signer for this channel. */
|
|
196
|
+
authorizedSigner: Address
|
|
197
|
+
/** Chain ID used by the opened channel. */
|
|
198
|
+
chainId: number
|
|
199
|
+
/** Normalized channel ID. */
|
|
200
|
+
channelId: Hex
|
|
201
|
+
/** Descriptor verified from the open transaction. */
|
|
202
|
+
descriptor: PrecompileChannel.ChannelDescriptor
|
|
203
|
+
/** Escrow precompile address used by the channel. */
|
|
204
|
+
escrow: Address
|
|
205
|
+
/** Transaction-bound expiring nonce hash from the open call. */
|
|
206
|
+
expiringNonceHash: Hex
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Inputs for reading a persisted precompile channel and validating its descriptor. */
|
|
210
|
+
export type LoadPrecompileChannelParameters = {
|
|
211
|
+
/** Channel ID to read from the server store. */
|
|
212
|
+
channelId: Hex
|
|
213
|
+
/** Chain ID used to rederive the channel descriptor. */
|
|
214
|
+
chainId: number
|
|
215
|
+
/** Descriptor supplied by the credential payload. */
|
|
216
|
+
descriptor: StoredPrecompileChannel['descriptor']
|
|
217
|
+
/** Escrow precompile address used to derive the channel ID. */
|
|
218
|
+
escrow: Address
|
|
219
|
+
/** Server channel store. */
|
|
220
|
+
store: ChannelStore
|
|
221
|
+
/** Whether to also validate descriptor identity against chain/payee/token fields. */
|
|
222
|
+
validateDescriptor?: boolean | undefined
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Inputs for validating a signed voucher and merging it into channel state. */
|
|
226
|
+
export type VerifyAndAcceptVoucherParameters = {
|
|
227
|
+
/** Credential challenge used to bind the returned receipt. */
|
|
228
|
+
challenge: Challenge.Challenge
|
|
229
|
+
/** Persisted channel state before voucher acceptance. */
|
|
230
|
+
channel: State
|
|
231
|
+
/** Latest on-chain state used as the deposit/settled authority. */
|
|
232
|
+
channelState: Chain.ChannelState
|
|
233
|
+
/** Session method details used for voucher signature domain separation. */
|
|
234
|
+
methodDetails: SessionMethodDetails
|
|
235
|
+
/** Minimum allowed voucher delta in raw units. */
|
|
236
|
+
minVoucherDelta: bigint
|
|
237
|
+
/** Server channel store. */
|
|
238
|
+
store: ChannelStore
|
|
239
|
+
/** Signed cumulative voucher to verify and accept. */
|
|
240
|
+
voucher: SignedVoucher
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Channel backend-specific fields. */
|
|
244
|
+
export type BackendState = CompatibilityBackendState | PrecompileBackendState
|
|
245
|
+
|
|
246
|
+
/** State for records owned by a backend outside the TIP-1034 precompile path. */
|
|
247
|
+
export interface CompatibilityBackendState {
|
|
248
|
+
/** Optional backend marker for older stored records. */
|
|
249
|
+
backend?: 'contract' | 'external' | undefined
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** State for a TIP20EscrowChannel precompile-backed payment channel. */
|
|
253
|
+
export interface PrecompileBackendState {
|
|
254
|
+
/** Channel backend. */
|
|
255
|
+
backend: 'precompile'
|
|
256
|
+
/** Descriptor used to derive the channel's identity. */
|
|
257
|
+
descriptor: PrecompileChannel.ChannelDescriptor
|
|
258
|
+
/** Transaction-bound nonce hash used to derive the channel's identity. */
|
|
259
|
+
expiringNonceHash: Hex
|
|
260
|
+
/** Address authorized to operate the channel. */
|
|
261
|
+
operator: Address
|
|
262
|
+
/** Salt used to derive the channel's identity. */
|
|
263
|
+
salt: Hex
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Common persisted state for every session channel backend. */
|
|
267
|
+
export interface BaseState {
|
|
268
|
+
/** Address authorized to sign vouchers on behalf of the payer. */
|
|
269
|
+
authorizedSigner: Address
|
|
270
|
+
/** Chain ID the channel was opened on. */
|
|
271
|
+
chainId: number
|
|
272
|
+
/** Escrow contract address the channel was opened on. */
|
|
273
|
+
escrowContract: Address
|
|
274
|
+
/** Unique identifier for this payment channel. */
|
|
275
|
+
channelId: Hex
|
|
276
|
+
/** On-chain timestamp when a force-close was requested (0n if not requested). */
|
|
277
|
+
closeRequestedAt: bigint
|
|
278
|
+
/** ISO 8601 timestamp when the channel was created. */
|
|
279
|
+
createdAt: string
|
|
280
|
+
/** Current on-chain deposit in the escrow contract. */
|
|
281
|
+
deposit: bigint
|
|
282
|
+
/** Whether the channel has been finalized (closed) on-chain. */
|
|
283
|
+
finalized: boolean
|
|
284
|
+
/** The signed voucher corresponding to `highestVoucherAmount`. */
|
|
285
|
+
highestVoucher: SessionSignedVoucher | null
|
|
286
|
+
/** Highest cumulative voucher amount accepted by the server. */
|
|
287
|
+
highestVoucherAmount: bigint
|
|
288
|
+
/** Address of the payment recipient. */
|
|
289
|
+
payee: Address
|
|
290
|
+
/** Address of the payment sender. */
|
|
291
|
+
payer: Address
|
|
292
|
+
/** Cumulative amount settled on-chain so far. */
|
|
293
|
+
settledOnChain: bigint
|
|
294
|
+
/** Cumulative amount spent (charged) against this channel's current session. */
|
|
295
|
+
spent: bigint
|
|
296
|
+
/** Token contract address used for payments. */
|
|
297
|
+
token: Address
|
|
298
|
+
/** Number of charge operations (API requests) fulfilled in the current session. */
|
|
299
|
+
units: number
|
|
300
|
+
/** ISO 8601 timestamp of the last server-scheduled settlement. */
|
|
301
|
+
lastSettlementAt?: string | undefined
|
|
302
|
+
/** Cumulative spent value when the last server-scheduled settlement ran. */
|
|
303
|
+
lastSettlementSpent?: bigint | undefined
|
|
304
|
+
/** Charge operation count when the last server-scheduled settlement ran. */
|
|
305
|
+
lastSettlementUnits?: number | undefined
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Returns whether a channel is backed by the TIP20EscrowChannel precompile. */
|
|
309
|
+
export function isPrecompileState(state: State): state is BaseState & PrecompileBackendState {
|
|
310
|
+
return state.backend === 'precompile'
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Returns the greater bigint value without decreasing persisted monotonic state. */
|
|
314
|
+
export function keepGreater(current: bigint, next: bigint): bigint {
|
|
315
|
+
return current > next ? current : next
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Returns the greater close-request timestamp as a bigint. */
|
|
319
|
+
export function keepGreaterTimestamp(current: bigint, next: number | bigint): bigint {
|
|
320
|
+
return keepGreater(current, BigInt(next))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Keeps local active-channel state in sync with authoritative on-chain reads. */
|
|
324
|
+
export function mergeActiveOnChainState(
|
|
325
|
+
current: Partial<ActiveOnChainStateFields> | null | undefined,
|
|
326
|
+
state: OnChainChannelState,
|
|
327
|
+
): ActiveOnChainStateFields {
|
|
328
|
+
return {
|
|
329
|
+
closeRequestedAt: keepGreaterTimestamp(current?.closeRequestedAt ?? 0n, state.closeRequestedAt),
|
|
330
|
+
deposit: keepGreater(current?.deposit ?? 0n, state.deposit),
|
|
331
|
+
settledOnChain: keepGreater(current?.settledOnChain ?? 0n, state.settled),
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Keeps the existing higher voucher, otherwise stores the supplied candidate voucher. */
|
|
336
|
+
export function resolveHighestVoucher(parameters: HighestVoucherParameters): HighestVoucher {
|
|
337
|
+
const { channelId, current, cumulativeAmount, signature } = parameters
|
|
338
|
+
if (current?.highestVoucherAmount && current.highestVoucherAmount > cumulativeAmount) {
|
|
339
|
+
return {
|
|
340
|
+
highestVoucherAmount: current.highestVoucherAmount,
|
|
341
|
+
highestVoucher: current.highestVoucher,
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
highestVoucherAmount: cumulativeAmount,
|
|
347
|
+
highestVoucher: { channelId, cumulativeAmount, signature },
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Derives persisted identity fields from a verified precompile channel descriptor. */
|
|
352
|
+
export function resolvePrecompileChannelIdentity(
|
|
353
|
+
parameters: ResolvePrecompileChannelIdentityParameters,
|
|
354
|
+
): PrecompileChannelIdentity {
|
|
355
|
+
const { authorizedSigner, chainId, channelId, descriptor, escrow, expiringNonceHash } = parameters
|
|
356
|
+
return {
|
|
357
|
+
authorizedSigner,
|
|
358
|
+
backend: 'precompile',
|
|
359
|
+
chainId,
|
|
360
|
+
channelId,
|
|
361
|
+
descriptor,
|
|
362
|
+
escrowContract: escrow,
|
|
363
|
+
expiringNonceHash,
|
|
364
|
+
operator: descriptor.operator,
|
|
365
|
+
payee: descriptor.payee,
|
|
366
|
+
payer: descriptor.payer,
|
|
367
|
+
salt: descriptor.salt,
|
|
368
|
+
token: descriptor.token,
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Builds the persisted state for a verified precompile channel open. */
|
|
373
|
+
export function openChannelState(parameters: OpenChannelStateParameters): State {
|
|
374
|
+
const { authorizedSigner, chainId, channelId, current, descriptor, escrow, expiringNonceHash } =
|
|
375
|
+
parameters
|
|
376
|
+
const { state } = parameters
|
|
377
|
+
const { cumulativeAmount, signature } = parameters
|
|
378
|
+
const highestVoucher = resolveHighestVoucher({
|
|
379
|
+
channelId,
|
|
380
|
+
current,
|
|
381
|
+
cumulativeAmount,
|
|
382
|
+
signature,
|
|
383
|
+
})
|
|
384
|
+
const onChain = mergeActiveOnChainState(current, state)
|
|
385
|
+
return {
|
|
386
|
+
...(current ?? {}),
|
|
387
|
+
...resolvePrecompileChannelIdentity({
|
|
388
|
+
authorizedSigner,
|
|
389
|
+
chainId,
|
|
390
|
+
channelId,
|
|
391
|
+
descriptor,
|
|
392
|
+
escrow,
|
|
393
|
+
expiringNonceHash,
|
|
394
|
+
}),
|
|
395
|
+
closeRequestedAt: onChain.closeRequestedAt,
|
|
396
|
+
deposit: onChain.deposit,
|
|
397
|
+
settledOnChain: onChain.settledOnChain,
|
|
398
|
+
highestVoucherAmount: highestVoucher.highestVoucherAmount,
|
|
399
|
+
highestVoucher: highestVoucher.highestVoucher,
|
|
400
|
+
spent: keepGreater(current?.spent ?? 0n, state.settled),
|
|
401
|
+
units: current?.units ?? 0,
|
|
402
|
+
finalized: current?.finalized ?? false,
|
|
403
|
+
createdAt: current?.createdAt ?? new Date().toISOString(),
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Merges top-up on-chain state into persisted channel state without decreasing monotonic fields. */
|
|
408
|
+
export function topUpChannelState(parameters: TopUpStateParameters): State | null {
|
|
409
|
+
const { current, state } = parameters
|
|
410
|
+
if (!current) return current
|
|
411
|
+
return {
|
|
412
|
+
...current,
|
|
413
|
+
...mergeActiveOnChainState(current, state),
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Merges an accepted voucher into persisted channel state after signature and on-chain checks pass. */
|
|
418
|
+
export function acceptVoucherStateUpdate(parameters: AcceptVoucherStateUpdateParameters): State {
|
|
419
|
+
const { channelState, current, voucher } = parameters
|
|
420
|
+
if (!current) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
421
|
+
if (current.finalized) throw new ChannelClosedError({ reason: 'channel is finalized' })
|
|
422
|
+
if (current.closeRequestedAt !== 0n)
|
|
423
|
+
throw new ChannelClosedError({ reason: 'channel has a pending close request' })
|
|
424
|
+
|
|
425
|
+
const onChain = mergeActiveOnChainState(current, channelState)
|
|
426
|
+
|
|
427
|
+
if (voucher.cumulativeAmount <= current.highestVoucherAmount) {
|
|
428
|
+
return { ...current, ...onChain }
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
...current,
|
|
433
|
+
...onChain,
|
|
434
|
+
highestVoucherAmount: voucher.cumulativeAmount,
|
|
435
|
+
highestVoucher: voucher,
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Resolves the close capture amount and validates it is covered by both the
|
|
441
|
+
* close voucher and current on-chain deposit.
|
|
442
|
+
*/
|
|
443
|
+
export function resolveCloseCaptureAmount(parameters: ResolveCloseCaptureAmountParameters): bigint {
|
|
444
|
+
const { cumulativeAmount, onChainDeposit, onChainSettled, spent } = parameters
|
|
445
|
+
if (cumulativeAmount < spent) {
|
|
446
|
+
throw new VerificationFailedError({
|
|
447
|
+
reason: `close voucher amount must be >= ${spent} (spent)`,
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const captureAmount = uint96(spent > onChainSettled ? spent : onChainSettled)
|
|
452
|
+
if (captureAmount > cumulativeAmount) {
|
|
453
|
+
throw new VerificationFailedError({
|
|
454
|
+
reason: `close voucher amount must be >= ${captureAmount} (capture amount)`,
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
if (captureAmount > onChainDeposit) {
|
|
458
|
+
throw new AmountExceedsDepositError({
|
|
459
|
+
reason: 'close capture amount exceeds on-chain deposit',
|
|
460
|
+
})
|
|
461
|
+
}
|
|
462
|
+
return captureAmount
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Marks local channel state as pending close and returns the bounded capture amount. */
|
|
466
|
+
export function markPendingClose(parameters: MarkPendingCloseParameters): PendingCloseUpdate {
|
|
467
|
+
const { closeRequestedAt, cumulativeAmount, current, onChainSettled, onChainDeposit } = parameters
|
|
468
|
+
if (!current) return { captureAmount: 0n, state: null }
|
|
469
|
+
if (current.finalized) throw new ChannelClosedError({ reason: 'channel is already finalized' })
|
|
470
|
+
if (current.closeRequestedAt !== 0n)
|
|
471
|
+
throw new ChannelClosedError({ reason: 'channel has a pending close request' })
|
|
472
|
+
const captureAmount = resolveCloseCaptureAmount({
|
|
473
|
+
cumulativeAmount,
|
|
474
|
+
onChainDeposit,
|
|
475
|
+
onChainSettled,
|
|
476
|
+
spent: current.spent,
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
captureAmount,
|
|
481
|
+
state: { ...current, closeRequestedAt },
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** Finalizes local channel state after a successful close transaction. */
|
|
486
|
+
export function finalizeClosedChannelState(
|
|
487
|
+
parameters: FinalizeClosedChannelParameters,
|
|
488
|
+
): State | null {
|
|
489
|
+
const { captureAmount, channelId, cumulativeAmount, current, signature } = parameters
|
|
490
|
+
if (!current) return current
|
|
491
|
+
const highestVoucher = resolveHighestVoucher({
|
|
492
|
+
channelId,
|
|
493
|
+
current,
|
|
494
|
+
cumulativeAmount,
|
|
495
|
+
signature,
|
|
496
|
+
})
|
|
497
|
+
return {
|
|
498
|
+
...current,
|
|
499
|
+
finalized: true,
|
|
500
|
+
closeRequestedAt: 0n,
|
|
501
|
+
deposit: 0n,
|
|
502
|
+
settledOnChain: keepGreater(current.settledOnChain, captureAmount),
|
|
503
|
+
highestVoucherAmount: highestVoucher.highestVoucherAmount,
|
|
504
|
+
highestVoucher: highestVoucher.highestVoucher,
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** Loads a precompile-backed channel or throws a verification error. */
|
|
509
|
+
export async function loadPrecompileChannel(
|
|
510
|
+
parameters: LoadPrecompileChannelParameters,
|
|
511
|
+
): Promise<StoredPrecompileChannel> {
|
|
512
|
+
const { channelId, chainId, descriptor, escrow, store } = parameters
|
|
513
|
+
const channel = await store.getChannel(channelId)
|
|
514
|
+
if (!channel) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
515
|
+
if (!isPrecompileState(channel))
|
|
516
|
+
throw new VerificationFailedError({ reason: 'channel is not precompile-backed' })
|
|
517
|
+
assertSameDescriptor(descriptor, channel.descriptor)
|
|
518
|
+
if (parameters.validateDescriptor)
|
|
519
|
+
validateChannelDescriptor(descriptor, channelId, chainId, escrow, channel.payee, channel.token)
|
|
520
|
+
return channel
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** Verifies a cumulative voucher and returns a session receipt after store reconciliation. */
|
|
524
|
+
export async function verifyAndAcceptVoucher(
|
|
525
|
+
parameters: VerifyAndAcceptVoucherParameters,
|
|
526
|
+
): Promise<SessionReceipt> {
|
|
527
|
+
const { store, minVoucherDelta, challenge, channel, voucher, channelState, methodDetails } =
|
|
528
|
+
parameters
|
|
529
|
+
|
|
530
|
+
validateChannelState(channelState)
|
|
531
|
+
if (voucher.cumulativeAmount > channelState.deposit)
|
|
532
|
+
throw new AmountExceedsDepositError({ reason: 'voucher amount exceeds on-chain deposit' })
|
|
533
|
+
if (voucher.cumulativeAmount < channel.highestVoucherAmount)
|
|
534
|
+
throw new VerificationFailedError({
|
|
535
|
+
reason: 'voucher cumulativeAmount must be strictly greater than highest accepted voucher',
|
|
536
|
+
})
|
|
537
|
+
const valid = await Voucher.verifyVoucher(
|
|
538
|
+
methodDetails.escrowContract,
|
|
539
|
+
methodDetails.chainId,
|
|
540
|
+
voucher,
|
|
541
|
+
channel.authorizedSigner,
|
|
542
|
+
)
|
|
543
|
+
if (!valid) throw new InvalidSignatureError({ reason: 'invalid voucher signature' })
|
|
544
|
+
|
|
545
|
+
if (voucher.cumulativeAmount === channel.highestVoucherAmount)
|
|
546
|
+
return createSessionReceipt({
|
|
547
|
+
challengeId: challenge.id,
|
|
548
|
+
channelId: voucher.channelId,
|
|
549
|
+
acceptedCumulative: channel.highestVoucherAmount,
|
|
550
|
+
spent: channel.spent,
|
|
551
|
+
units: channel.units,
|
|
552
|
+
})
|
|
553
|
+
if (voucher.cumulativeAmount <= channelState.settled)
|
|
554
|
+
throw new VerificationFailedError({
|
|
555
|
+
reason: 'voucher cumulativeAmount is below on-chain settled amount',
|
|
556
|
+
})
|
|
557
|
+
const delta = voucher.cumulativeAmount - channel.highestVoucherAmount
|
|
558
|
+
if (delta < minVoucherDelta)
|
|
559
|
+
throw new DeltaTooSmallError({
|
|
560
|
+
reason: `voucher delta ${delta} below minimum ${minVoucherDelta}`,
|
|
561
|
+
})
|
|
562
|
+
const updated = await store.updateChannel(voucher.channelId, (current) =>
|
|
563
|
+
acceptVoucherStateUpdate({ channelState, current, voucher }),
|
|
564
|
+
)
|
|
565
|
+
if (!updated) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
566
|
+
return createSessionReceipt({
|
|
567
|
+
challengeId: challenge.id,
|
|
568
|
+
channelId: voucher.channelId,
|
|
569
|
+
acceptedCumulative: updated.highestVoucherAmount,
|
|
570
|
+
spent: updated.spent,
|
|
571
|
+
units: updated.units,
|
|
572
|
+
})
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Internal store interface for channel state persistence.
|
|
577
|
+
*
|
|
578
|
+
* ## Atomicity contract
|
|
579
|
+
*
|
|
580
|
+
* The `updateChannel` method uses an atomic read-modify-write callback.
|
|
581
|
+
* The callback receives the current state (or `null` if none exists), and
|
|
582
|
+
* returns the next state (or `null` to delete). Implementations must
|
|
583
|
+
* guarantee that no concurrent mutation occurs between reading `current`
|
|
584
|
+
* and writing the return value.
|
|
585
|
+
*
|
|
586
|
+
* Callbacks should be synchronous and deterministic. When a `ChannelStore`
|
|
587
|
+
* is backed by `Store.update()`, adapters may retry them internally.
|
|
588
|
+
*
|
|
589
|
+
* Backends implement this via their native mechanisms:
|
|
590
|
+
* - **In-memory / JS single-thread**: Synchronous callback execution
|
|
591
|
+
* - **Durable Objects**: Single-threaded execution model
|
|
592
|
+
* - **D1 / SQL**: Database transactions
|
|
593
|
+
*/
|
|
594
|
+
export type ChannelStore = {
|
|
595
|
+
getChannel(channelId: Hex): Promise<State | null>
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Atomic read-modify-write for channel state.
|
|
599
|
+
* Return `null` from `fn` to delete the channel.
|
|
600
|
+
*/
|
|
601
|
+
updateChannel(channelId: Hex, fn: (current: State | null) => State | null): Promise<State | null>
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Wait for the next update to a channel.
|
|
605
|
+
*
|
|
606
|
+
* Returns a `Promise` that resolves once `updateChannel` is called for
|
|
607
|
+
* `channelId`. Implementations should resolve immediately if the channel
|
|
608
|
+
* was updated between the call to `waitForUpdate` and the `Promise`
|
|
609
|
+
* being awaited.
|
|
610
|
+
*
|
|
611
|
+
* When not implemented, callers fall back to polling.
|
|
612
|
+
*/
|
|
613
|
+
waitForUpdate?(channelId: Hex): Promise<void>
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Atomic read-modify-write that returns the callback's `result` directly.
|
|
617
|
+
*
|
|
618
|
+
* Used by {@link deductFromChannel} to atomically compute the deduction
|
|
619
|
+
* outcome. When backed by `Store.update()`, this delegates to the store's
|
|
620
|
+
* native atomic primitive.
|
|
621
|
+
*/
|
|
622
|
+
updateChannelResult?<result>(
|
|
623
|
+
channelId: Hex,
|
|
624
|
+
fn: (current: State | null) => Store.Change<State, result>,
|
|
625
|
+
): Promise<result>
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** Normalizes and validates 32-byte channel IDs before store lookup or persistence. */
|
|
629
|
+
export function normalizeChannelId(channelId: string): Hex {
|
|
630
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(channelId)) throw new Error('Invalid session channel ID.')
|
|
631
|
+
return channelId.toLowerCase() as Hex
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
type StateStore = Store.Store<Record<string, State>> &
|
|
635
|
+
Partial<Store.AtomicActions<Record<string, State>>>
|
|
636
|
+
|
|
637
|
+
/** Atomic store change carrying normalized channel state. */
|
|
638
|
+
type ChannelStateChange<result> = Store.Change<State, result>
|
|
639
|
+
|
|
640
|
+
/** Store change carrying the result of an attempted spend deduction. */
|
|
641
|
+
type DeductionChange = Store.Change<State, DeductResult | null>
|
|
642
|
+
|
|
643
|
+
/** Mutable runtime state for one generic-store adapter instance. */
|
|
644
|
+
type StoreAdapterRuntime = {
|
|
645
|
+
/** Per-channel in-process locks used when the backend has no atomic update primitive. */
|
|
646
|
+
locks: Map<string, Promise<void>>
|
|
647
|
+
/** Waiters notified after the next mutation for a normalized channel ID. */
|
|
648
|
+
waiters: Map<string, Set<() => void>>
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function normalizeState(channelId: Hex, state: State): State {
|
|
652
|
+
return state.channelId === channelId ? state : { ...state, channelId }
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function normalizeMaybeState(channelId: Hex, state: State | null): State | null {
|
|
656
|
+
return state ? normalizeState(channelId, state) : null
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** Normalizes any stored channel value inside a store change. */
|
|
660
|
+
function normalizeChange<result>(
|
|
661
|
+
channelId: Hex,
|
|
662
|
+
change: ChannelStateChange<result>,
|
|
663
|
+
): ChannelStateChange<result> {
|
|
664
|
+
if (change.op !== 'set') return change
|
|
665
|
+
return {
|
|
666
|
+
...change,
|
|
667
|
+
value: normalizeState(channelId, change.value),
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Wraps a generic {@link Store} into the internal {@link Store}
|
|
673
|
+
* interface used by server handlers and the SSE metering loop.
|
|
674
|
+
*
|
|
675
|
+
* Provides `waitForUpdate` notifications so the SSE `chargeOrWait` loop
|
|
676
|
+
* can wake up without polling.
|
|
677
|
+
*
|
|
678
|
+
* ## Atomicity
|
|
679
|
+
*
|
|
680
|
+
* Mutations use `get` → `fn` → `set` guarded by a per-key in-process
|
|
681
|
+
* mutex. This serializes concurrent `updateChannel` calls within a
|
|
682
|
+
* single JS runtime but does **not** protect against races across
|
|
683
|
+
* multiple processes or instances.
|
|
684
|
+
*
|
|
685
|
+
* Backends that need true atomicity (e.g., Durable Objects, D1)
|
|
686
|
+
* should implement {@link Store} directly.
|
|
687
|
+
*/
|
|
688
|
+
const storeCache = new WeakMap<Store.Store, ChannelStore>()
|
|
689
|
+
|
|
690
|
+
/** Wraps a generic mppx store in the shared session channel-store interface. */
|
|
691
|
+
export function fromStore(store: Store.Store | Store.AtomicStore): ChannelStore {
|
|
692
|
+
const cached = storeCache.get(store)
|
|
693
|
+
if (cached) return cached
|
|
694
|
+
|
|
695
|
+
const stateStore = store as StateStore
|
|
696
|
+
const atomicUpdate = stateStore.update
|
|
697
|
+
|
|
698
|
+
const runtime: StoreAdapterRuntime = {
|
|
699
|
+
locks: new Map(),
|
|
700
|
+
waiters: new Map(),
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function notify(channelId: string) {
|
|
704
|
+
const set = runtime.waiters.get(channelId)
|
|
705
|
+
if (!set) return
|
|
706
|
+
for (const resolve of set) resolve()
|
|
707
|
+
runtime.waiters.delete(channelId)
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async function update(
|
|
711
|
+
channelId: Hex,
|
|
712
|
+
fn: (current: State | null) => State | null,
|
|
713
|
+
): Promise<State | null> {
|
|
714
|
+
return updateResult(channelId, (current) => {
|
|
715
|
+
const next = fn(current)
|
|
716
|
+
if (next) return { op: 'set', value: next, result: next }
|
|
717
|
+
return { op: 'delete', result: null }
|
|
718
|
+
})
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
async function updateResult<result>(
|
|
722
|
+
channelId: Hex,
|
|
723
|
+
fn: (current: State | null) => Store.Change<State, result>,
|
|
724
|
+
): Promise<result> {
|
|
725
|
+
const normalizedChannelId = normalizeChannelId(channelId)
|
|
726
|
+
let change: Store.Change<State, result> | undefined
|
|
727
|
+
|
|
728
|
+
if (atomicUpdate) {
|
|
729
|
+
const result = await atomicUpdate(normalizedChannelId, (current) => {
|
|
730
|
+
const normalizedCurrent = normalizeMaybeState(normalizedChannelId, current)
|
|
731
|
+
change = normalizeChange(normalizedChannelId, fn(normalizedCurrent))
|
|
732
|
+
return change
|
|
733
|
+
})
|
|
734
|
+
if (change?.op !== 'noop') notify(normalizedChannelId)
|
|
735
|
+
return result
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
while (runtime.locks.has(normalizedChannelId)) await runtime.locks.get(normalizedChannelId)
|
|
739
|
+
|
|
740
|
+
let release!: () => void
|
|
741
|
+
runtime.locks.set(
|
|
742
|
+
normalizedChannelId,
|
|
743
|
+
new Promise<void>((r) => {
|
|
744
|
+
release = r
|
|
745
|
+
}),
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
try {
|
|
749
|
+
const current = normalizeMaybeState(
|
|
750
|
+
normalizedChannelId,
|
|
751
|
+
await stateStore.get(normalizedChannelId),
|
|
752
|
+
)
|
|
753
|
+
change = normalizeChange(normalizedChannelId, fn(current))
|
|
754
|
+
if (change.op === 'set') {
|
|
755
|
+
await stateStore.put(normalizedChannelId, change.value)
|
|
756
|
+
}
|
|
757
|
+
if (change.op === 'delete') await stateStore.delete(normalizedChannelId)
|
|
758
|
+
if (change.op !== 'noop') notify(normalizedChannelId)
|
|
759
|
+
return change.result
|
|
760
|
+
} finally {
|
|
761
|
+
runtime.locks.delete(normalizedChannelId)
|
|
762
|
+
release()
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const cs: ChannelStore = {
|
|
767
|
+
async getChannel(channelId) {
|
|
768
|
+
const normalizedChannelId = normalizeChannelId(channelId)
|
|
769
|
+
return normalizeMaybeState(normalizedChannelId, await stateStore.get(normalizedChannelId))
|
|
770
|
+
},
|
|
771
|
+
async updateChannel(channelId, fn) {
|
|
772
|
+
return update(channelId, fn)
|
|
773
|
+
},
|
|
774
|
+
waitForUpdate(channelId) {
|
|
775
|
+
return new Promise<void>((resolve) => {
|
|
776
|
+
const normalizedChannelId = normalizeChannelId(channelId)
|
|
777
|
+
let set = runtime.waiters.get(normalizedChannelId)
|
|
778
|
+
if (!set) {
|
|
779
|
+
set = new Set()
|
|
780
|
+
runtime.waiters.set(normalizedChannelId, set)
|
|
781
|
+
}
|
|
782
|
+
set.add(resolve)
|
|
783
|
+
})
|
|
784
|
+
},
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
cs.updateChannelResult = updateResult
|
|
788
|
+
|
|
789
|
+
storeCache.set(store, cs)
|
|
790
|
+
return cs
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Atomically deducts `amount` from a channel's available voucher balance.
|
|
795
|
+
*
|
|
796
|
+
* Returns `{ ok: true, channel }` with updated spend/unit counters when the
|
|
797
|
+
* deduction succeeds. Returns `{ ok: false, channel }` without mutating state
|
|
798
|
+
* when the channel exists but cannot currently be charged.
|
|
799
|
+
*/
|
|
800
|
+
export async function deductFromChannel(
|
|
801
|
+
store: ChannelStore,
|
|
802
|
+
channelId: State['channelId'],
|
|
803
|
+
amount: bigint,
|
|
804
|
+
): Promise<DeductResult> {
|
|
805
|
+
if (store.updateChannelResult) {
|
|
806
|
+
const result = await store.updateChannelResult<DeductResult | null>(
|
|
807
|
+
channelId,
|
|
808
|
+
(current): DeductionChange => planDeduction(current, amount),
|
|
809
|
+
)
|
|
810
|
+
if (!result) throw new Error('channel not found')
|
|
811
|
+
return result
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
let result: DeductResult | null = null
|
|
815
|
+
const channel = await store.updateChannel(channelId, (current) => {
|
|
816
|
+
const change = planDeduction(current, amount)
|
|
817
|
+
result = change.result
|
|
818
|
+
if (change.op === 'set') return change.value
|
|
819
|
+
return current
|
|
820
|
+
})
|
|
821
|
+
if (!channel) throw new Error('channel not found')
|
|
822
|
+
return result ?? { ok: false, channel }
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function planDeduction(current: State | null, amount: bigint): DeductionChange {
|
|
826
|
+
if (!current) return { op: 'noop', result: null }
|
|
827
|
+
if (current.finalized) return { op: 'noop', result: { ok: false, channel: current } }
|
|
828
|
+
if (current.closeRequestedAt !== 0n)
|
|
829
|
+
return { op: 'noop', result: { ok: false, channel: current } }
|
|
830
|
+
if (current.highestVoucherAmount - current.spent < amount)
|
|
831
|
+
return { op: 'noop', result: { ok: false, channel: current } }
|
|
832
|
+
|
|
833
|
+
const next = { ...current, spent: current.spent + amount, units: current.units + 1 }
|
|
834
|
+
return { op: 'set', value: next, result: { ok: true, channel: next } }
|
|
835
|
+
}
|