mppx 0.8.17 → 0.8.18
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 +55 -0
- package/README.md +15 -0
- package/dist/Challenge.js +21 -4
- package/dist/bin.js +3 -3
- package/dist/cli/THIRD-PARTY-LICENSES.txt +328 -0
- package/dist/cli/cli.js +253 -1505
- package/dist/cli/plugins/index.js +19 -5
- package/dist/cli/plugins/tempo.js +7 -3
- package/dist/cli/sessions/Manager.js +7 -4
- package/dist/cli/sessions/request.d.ts +2 -0
- package/dist/cli/sessions/request.js +18 -0
- package/dist/cli/sessions/store.js +28 -14
- package/dist/discovery/Discovery.js +2 -1
- package/dist/evm/client/Charge.js +8 -0
- package/dist/server/core.d.ts +9 -0
- package/dist/server/core.js +9 -0
- package/dist/stripe/server/Charge.js +2 -4
- package/dist/stripe/server/Methods.d.ts +2 -2
- package/dist/stripe/server/Methods.js +17 -11
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/spt.d.ts +8 -0
- package/dist/stripe/server/spt.js +8 -0
- package/dist/tempo/Methods.d.ts +13 -0
- package/dist/tempo/Methods.js +14 -3
- package/dist/tempo/client/Charge.d.ts +3 -0
- package/dist/tempo/client/Charge.js +31 -9
- package/dist/tempo/client/Methods.d.ts +13 -0
- package/dist/tempo/internal/defaults.d.ts +11 -0
- package/dist/tempo/internal/defaults.js +11 -0
- package/dist/tempo/internal/machine-token-charge.d.ts +51 -0
- package/dist/tempo/internal/machine-token-charge.js +113 -0
- package/dist/tempo/internal/machine-token-session.d.ts +88 -0
- package/dist/tempo/internal/machine-token-session.js +173 -0
- package/dist/tempo/internal/signature-envelope.d.ts +14 -0
- package/dist/tempo/internal/signature-envelope.js +28 -0
- package/dist/tempo/legacy/client/ChannelOps.d.ts +2 -2
- package/dist/tempo/legacy/client/ChannelOps.js +11 -5
- package/dist/tempo/legacy/client/Session.d.ts +13 -1
- package/dist/tempo/legacy/client/Session.js +3 -3
- package/dist/tempo/legacy/client/SessionManager.d.ts +3 -1
- package/dist/tempo/legacy/client/SessionManager.js +2 -0
- package/dist/tempo/server/Charge.d.ts +3 -0
- package/dist/tempo/server/Charge.js +58 -17
- package/dist/tempo/server/Methods.d.ts +21 -4
- package/dist/tempo/server/Methods.js +8 -4
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +18 -3
- package/dist/tempo/session/client/ChannelOps.js +35 -6
- package/dist/tempo/session/client/ChannelStore.d.ts +0 -5
- package/dist/tempo/session/client/ChannelStore.js +2 -2
- package/dist/tempo/session/client/CredentialState.d.ts +17 -2
- package/dist/tempo/session/client/CredentialState.js +77 -76
- package/dist/tempo/session/client/Runtime.d.ts +4 -2
- package/dist/tempo/session/client/Runtime.js +21 -5
- package/dist/tempo/session/client/Session.d.ts +15 -3
- package/dist/tempo/session/client/Session.js +186 -63
- package/dist/tempo/session/client/SessionManager.d.ts +3 -1
- package/dist/tempo/session/client/SessionManager.js +35 -11
- package/dist/tempo/session/client/Transports.d.ts +2 -0
- package/dist/tempo/session/client/Transports.js +12 -2
- package/dist/tempo/session/precompile/Chain.d.ts +10 -2
- package/dist/tempo/session/precompile/Chain.js +50 -10
- package/dist/tempo/session/precompile/Protocol.d.ts +15 -0
- package/dist/tempo/session/precompile/Protocol.js +7 -0
- package/dist/tempo/session/precompile/Voucher.d.ts +3 -1
- package/dist/tempo/session/precompile/Voucher.js +6 -12
- package/dist/tempo/session/server/ChannelStore.d.ts +6 -0
- package/dist/tempo/session/server/ChannelStore.js +11 -4
- package/dist/tempo/session/server/CredentialVerification.d.ts +11 -9
- package/dist/tempo/session/server/CredentialVerification.js +185 -109
- package/dist/tempo/session/server/MeteredStream.d.ts +1 -1
- package/dist/tempo/session/server/MeteredStream.js +5 -4
- package/dist/tempo/session/server/RequestState.d.ts +19 -0
- package/dist/tempo/session/server/RequestState.js +61 -15
- package/dist/tempo/session/server/Session.d.ts +14 -2
- package/dist/tempo/session/server/Session.js +37 -6
- package/dist/tempo/session/server/Settlement.d.ts +11 -4
- package/dist/tempo/session/server/Settlement.js +53 -21
- package/dist/x402/client/Exact.d.ts +8 -0
- package/dist/x402/client/Exact.js +40 -10
- package/dist/x402/server/EvmCharge.d.ts +22 -0
- package/dist/x402/server/EvmCharge.js +23 -1
- package/package.json +15 -37
- package/dist/BodyDigest.d.ts.map +0 -1
- package/dist/BodyDigest.js.map +0 -1
- package/dist/Challenge.d.ts.map +0 -1
- package/dist/Challenge.js.map +0 -1
- package/dist/Constants.d.ts.map +0 -1
- package/dist/Constants.js.map +0 -1
- package/dist/Credential.d.ts.map +0 -1
- package/dist/Credential.js.map +0 -1
- package/dist/Errors.d.ts.map +0 -1
- package/dist/Errors.js.map +0 -1
- package/dist/Expires.d.ts.map +0 -1
- package/dist/Expires.js.map +0 -1
- package/dist/Html.d.ts.map +0 -1
- package/dist/Html.js.map +0 -1
- package/dist/Mcp.d.ts.map +0 -1
- package/dist/Mcp.js.map +0 -1
- package/dist/Method.d.ts.map +0 -1
- package/dist/Method.js.map +0 -1
- package/dist/PaymentRequest.d.ts.map +0 -1
- package/dist/PaymentRequest.js.map +0 -1
- package/dist/Receipt.d.ts.map +0 -1
- package/dist/Receipt.js.map +0 -1
- package/dist/Store.d.ts.map +0 -1
- package/dist/Store.js.map +0 -1
- package/dist/attestation/Client.d.ts.map +0 -1
- package/dist/attestation/Client.js.map +0 -1
- package/dist/attestation/Constants.d.ts.map +0 -1
- package/dist/attestation/Constants.js.map +0 -1
- package/dist/attestation/Server.d.ts.map +0 -1
- package/dist/attestation/Server.js.map +0 -1
- package/dist/attestation/Types.d.ts.map +0 -1
- package/dist/attestation/Types.js.map +0 -1
- package/dist/attestation/index.d.ts.map +0 -1
- package/dist/attestation/index.js.map +0 -1
- package/dist/attestation/internal/HttpMessageSignature.d.ts.map +0 -1
- package/dist/attestation/internal/HttpMessageSignature.js.map +0 -1
- package/dist/attestation/internal/SigningContext.d.ts.map +0 -1
- package/dist/attestation/internal/SigningContext.js.map +0 -1
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/cli/account.d.ts.map +0 -1
- package/dist/cli/account.js.map +0 -1
- package/dist/cli/cli.d.ts.map +0 -1
- package/dist/cli/cli.js.map +0 -1
- package/dist/cli/config.d.ts.map +0 -1
- package/dist/cli/config.js.map +0 -1
- package/dist/cli/internal.d.ts.map +0 -1
- package/dist/cli/internal.js.map +0 -1
- package/dist/cli/plugins/evm.d.ts.map +0 -1
- package/dist/cli/plugins/evm.js.map +0 -1
- package/dist/cli/plugins/index.d.ts.map +0 -1
- package/dist/cli/plugins/index.js.map +0 -1
- package/dist/cli/plugins/plugin.d.ts.map +0 -1
- package/dist/cli/plugins/plugin.js.map +0 -1
- package/dist/cli/plugins/stripe.d.ts.map +0 -1
- package/dist/cli/plugins/stripe.js.map +0 -1
- package/dist/cli/plugins/tempo.d.ts.map +0 -1
- package/dist/cli/plugins/tempo.js.map +0 -1
- package/dist/cli/sessions/Manager.d.ts.map +0 -1
- package/dist/cli/sessions/Manager.js.map +0 -1
- package/dist/cli/sessions/commands.d.ts.map +0 -1
- package/dist/cli/sessions/commands.js.map +0 -1
- package/dist/cli/sessions/request.d.ts.map +0 -1
- package/dist/cli/sessions/request.js.map +0 -1
- package/dist/cli/sessions/store.d.ts.map +0 -1
- package/dist/cli/sessions/store.js.map +0 -1
- package/dist/cli/utils.d.ts.map +0 -1
- package/dist/cli/utils.js.map +0 -1
- package/dist/cli/validate/challenge.d.ts.map +0 -1
- package/dist/cli/validate/challenge.js.map +0 -1
- package/dist/cli/validate/discovery.d.ts.map +0 -1
- package/dist/cli/validate/discovery.js.map +0 -1
- package/dist/cli/validate/helpers.d.ts.map +0 -1
- package/dist/cli/validate/helpers.js.map +0 -1
- package/dist/cli/validate/index.d.ts.map +0 -1
- package/dist/cli/validate/index.js.map +0 -1
- package/dist/cli/validate/messages.d.ts.map +0 -1
- package/dist/cli/validate/messages.js.map +0 -1
- package/dist/cli/validate/payment.d.ts.map +0 -1
- package/dist/cli/validate/payment.js.map +0 -1
- package/dist/client/Methods.d.ts.map +0 -1
- package/dist/client/Methods.js.map +0 -1
- package/dist/client/Mppx.d.ts.map +0 -1
- package/dist/client/Mppx.js.map +0 -1
- package/dist/client/Transport.d.ts.map +0 -1
- package/dist/client/Transport.js.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js.map +0 -1
- package/dist/client/internal/Fetch.d.ts.map +0 -1
- package/dist/client/internal/Fetch.js.map +0 -1
- package/dist/client/internal/MethodChallenge.d.ts.map +0 -1
- package/dist/client/internal/MethodChallenge.js.map +0 -1
- package/dist/client/internal/MethodResponse.d.ts.map +0 -1
- package/dist/client/internal/MethodResponse.js.map +0 -1
- package/dist/client/internal/protocols/Mcp.d.ts.map +0 -1
- package/dist/client/internal/protocols/Mcp.js.map +0 -1
- package/dist/client/internal/protocols/Mpp.d.ts.map +0 -1
- package/dist/client/internal/protocols/Mpp.js.map +0 -1
- package/dist/client/internal/protocols/Protocol.d.ts.map +0 -1
- package/dist/client/internal/protocols/Protocol.js.map +0 -1
- package/dist/client/internal/protocols/Shared.d.ts.map +0 -1
- package/dist/client/internal/protocols/Shared.js.map +0 -1
- package/dist/client/internal/protocols/X402.d.ts.map +0 -1
- package/dist/client/internal/protocols/X402.js.map +0 -1
- package/dist/client/node.d.ts.map +0 -1
- package/dist/client/node.js.map +0 -1
- package/dist/discovery/Discovery.d.ts.map +0 -1
- package/dist/discovery/Discovery.js.map +0 -1
- package/dist/discovery/OpenApi.d.ts.map +0 -1
- package/dist/discovery/OpenApi.js.map +0 -1
- package/dist/discovery/Validate.d.ts.map +0 -1
- package/dist/discovery/Validate.js.map +0 -1
- package/dist/discovery/index.d.ts.map +0 -1
- package/dist/discovery/index.js.map +0 -1
- package/dist/discovery/internal/Metadata.d.ts.map +0 -1
- package/dist/discovery/internal/Metadata.js.map +0 -1
- package/dist/evm/Assets.d.ts.map +0 -1
- package/dist/evm/Assets.js.map +0 -1
- package/dist/evm/Chains.d.ts.map +0 -1
- package/dist/evm/Chains.js.map +0 -1
- package/dist/evm/Methods.d.ts.map +0 -1
- package/dist/evm/Methods.js.map +0 -1
- package/dist/evm/Types.d.ts.map +0 -1
- package/dist/evm/Types.js.map +0 -1
- package/dist/evm/client/Charge.d.ts.map +0 -1
- package/dist/evm/client/Charge.js.map +0 -1
- package/dist/evm/client/Methods.d.ts.map +0 -1
- package/dist/evm/client/Methods.js.map +0 -1
- package/dist/evm/client/index.d.ts.map +0 -1
- package/dist/evm/client/index.js.map +0 -1
- package/dist/evm/index.d.ts.map +0 -1
- package/dist/evm/index.js.map +0 -1
- package/dist/evm/server/Charge.d.ts.map +0 -1
- package/dist/evm/server/Charge.js.map +0 -1
- package/dist/evm/server/Methods.d.ts.map +0 -1
- package/dist/evm/server/Methods.js.map +0 -1
- package/dist/evm/server/index.d.ts.map +0 -1
- package/dist/evm/server/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal/AcceptPayment.d.ts.map +0 -1
- package/dist/internal/AcceptPayment.js.map +0 -1
- package/dist/internal/HeaderCodec.d.ts.map +0 -1
- package/dist/internal/HeaderCodec.js.map +0 -1
- package/dist/internal/constantTimeEqual.d.ts.map +0 -1
- package/dist/internal/constantTimeEqual.js.map +0 -1
- package/dist/internal/env.d.ts.map +0 -1
- package/dist/internal/env.js.map +0 -1
- package/dist/internal/types.d.ts.map +0 -1
- package/dist/internal/types.js.map +0 -1
- package/dist/mcp/client/McpClient.d.ts.map +0 -1
- package/dist/mcp/client/McpClient.js.map +0 -1
- package/dist/mcp/client/index.d.ts.map +0 -1
- package/dist/mcp/client/index.js.map +0 -1
- package/dist/mcp/server/Transport.d.ts.map +0 -1
- package/dist/mcp/server/Transport.js.map +0 -1
- package/dist/mcp/server/index.d.ts.map +0 -1
- package/dist/mcp/server/index.js.map +0 -1
- package/dist/middlewares/elysia.d.ts.map +0 -1
- package/dist/middlewares/elysia.js.map +0 -1
- package/dist/middlewares/express.d.ts.map +0 -1
- package/dist/middlewares/express.js.map +0 -1
- package/dist/middlewares/hono.d.ts.map +0 -1
- package/dist/middlewares/hono.js.map +0 -1
- package/dist/middlewares/internal/mppx.d.ts.map +0 -1
- package/dist/middlewares/internal/mppx.js.map +0 -1
- package/dist/middlewares/nextjs.d.ts.map +0 -1
- package/dist/middlewares/nextjs.js.map +0 -1
- package/dist/proxy/Proxy.d.ts.map +0 -1
- package/dist/proxy/Proxy.js.map +0 -1
- package/dist/proxy/Service.d.ts.map +0 -1
- package/dist/proxy/Service.js.map +0 -1
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/internal/Headers.d.ts.map +0 -1
- package/dist/proxy/internal/Headers.js.map +0 -1
- package/dist/proxy/internal/Route.d.ts.map +0 -1
- package/dist/proxy/internal/Route.js.map +0 -1
- package/dist/proxy/services/anthropic.d.ts.map +0 -1
- package/dist/proxy/services/anthropic.js.map +0 -1
- package/dist/proxy/services/openai.d.ts.map +0 -1
- package/dist/proxy/services/openai.js.map +0 -1
- package/dist/proxy/services/stripe.d.ts.map +0 -1
- package/dist/proxy/services/stripe.js.map +0 -1
- package/dist/server/Methods.d.ts.map +0 -1
- package/dist/server/Methods.js.map +0 -1
- package/dist/server/Mppx.d.ts.map +0 -1
- package/dist/server/Mppx.js.map +0 -1
- package/dist/server/NodeListener.d.ts.map +0 -1
- package/dist/server/NodeListener.js.map +0 -1
- package/dist/server/Request.d.ts.map +0 -1
- package/dist/server/Request.js.map +0 -1
- package/dist/server/Response.d.ts.map +0 -1
- package/dist/server/Response.js.map +0 -1
- package/dist/server/Transport.d.ts.map +0 -1
- package/dist/server/Transport.js.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/server/internal/html/compose.main.gen.d.ts.map +0 -1
- package/dist/server/internal/html/compose.main.gen.js.map +0 -1
- package/dist/server/internal/html/config.d.ts.map +0 -1
- package/dist/server/internal/html/config.js.map +0 -1
- package/dist/server/internal/html/constants.d.ts.map +0 -1
- package/dist/server/internal/html/constants.js.map +0 -1
- package/dist/server/internal/html/serviceWorker.client.d.ts.map +0 -1
- package/dist/server/internal/html/serviceWorker.client.js.map +0 -1
- package/dist/server/internal/html/serviceWorker.gen.d.ts.map +0 -1
- package/dist/server/internal/html/serviceWorker.gen.js.map +0 -1
- package/dist/server/internal/scope.d.ts.map +0 -1
- package/dist/server/internal/scope.js.map +0 -1
- package/dist/stripe/Methods.d.ts.map +0 -1
- package/dist/stripe/Methods.js.map +0 -1
- package/dist/stripe/client/Charge.d.ts.map +0 -1
- package/dist/stripe/client/Charge.js.map +0 -1
- package/dist/stripe/client/Methods.d.ts.map +0 -1
- package/dist/stripe/client/Methods.js.map +0 -1
- package/dist/stripe/client/index.d.ts.map +0 -1
- package/dist/stripe/client/index.js.map +0 -1
- package/dist/stripe/index.d.ts.map +0 -1
- package/dist/stripe/index.js.map +0 -1
- package/dist/stripe/internal/constants.d.ts.map +0 -1
- package/dist/stripe/internal/constants.js.map +0 -1
- package/dist/stripe/internal/types.d.ts.map +0 -1
- package/dist/stripe/internal/types.js.map +0 -1
- package/dist/stripe/server/Charge.d.ts.map +0 -1
- package/dist/stripe/server/Charge.js.map +0 -1
- package/dist/stripe/server/Methods.d.ts.map +0 -1
- package/dist/stripe/server/Methods.js.map +0 -1
- package/dist/stripe/server/index.d.ts.map +0 -1
- package/dist/stripe/server/index.js.map +0 -1
- package/dist/stripe/server/internal/deposit-address.d.ts.map +0 -1
- package/dist/stripe/server/internal/deposit-address.js.map +0 -1
- package/dist/stripe/server/internal/html/types.d.ts.map +0 -1
- package/dist/stripe/server/internal/html/types.js.map +0 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +0 -1
- package/dist/stripe/server/internal/html.gen.js.map +0 -1
- package/dist/stripe/server/internal/record-payment.d.ts.map +0 -1
- package/dist/stripe/server/internal/record-payment.js.map +0 -1
- package/dist/stripe/server/internal/request.d.ts.map +0 -1
- package/dist/stripe/server/internal/request.js.map +0 -1
- package/dist/tap/Constants.d.ts.map +0 -1
- package/dist/tap/Constants.js.map +0 -1
- package/dist/tap/Types.d.ts.map +0 -1
- package/dist/tap/Types.js.map +0 -1
- package/dist/tap/client/index.d.ts.map +0 -1
- package/dist/tap/client/index.js.map +0 -1
- package/dist/tap/index.d.ts.map +0 -1
- package/dist/tap/index.js.map +0 -1
- package/dist/tap/server/index.d.ts.map +0 -1
- package/dist/tap/server/index.js.map +0 -1
- package/dist/tempo/Attribution.d.ts.map +0 -1
- package/dist/tempo/Attribution.js.map +0 -1
- package/dist/tempo/Methods.d.ts.map +0 -1
- package/dist/tempo/Methods.js.map +0 -1
- package/dist/tempo/Proof.d.ts.map +0 -1
- package/dist/tempo/Proof.js.map +0 -1
- package/dist/tempo/client/Charge.d.ts.map +0 -1
- package/dist/tempo/client/Charge.js.map +0 -1
- package/dist/tempo/client/Methods.d.ts.map +0 -1
- package/dist/tempo/client/Methods.js.map +0 -1
- package/dist/tempo/client/ResolveAccount.d.ts.map +0 -1
- package/dist/tempo/client/ResolveAccount.js.map +0 -1
- package/dist/tempo/client/Subscription.d.ts.map +0 -1
- package/dist/tempo/client/Subscription.js.map +0 -1
- package/dist/tempo/client/index.d.ts.map +0 -1
- package/dist/tempo/client/index.js.map +0 -1
- package/dist/tempo/index.d.ts.map +0 -1
- package/dist/tempo/index.js.map +0 -1
- package/dist/tempo/internal/account.d.ts.map +0 -1
- package/dist/tempo/internal/account.js.map +0 -1
- package/dist/tempo/internal/address.d.ts.map +0 -1
- package/dist/tempo/internal/address.js.map +0 -1
- package/dist/tempo/internal/auto-swap.d.ts.map +0 -1
- package/dist/tempo/internal/auto-swap.js.map +0 -1
- package/dist/tempo/internal/charge.d.ts.map +0 -1
- package/dist/tempo/internal/charge.js.map +0 -1
- package/dist/tempo/internal/defaults.d.ts.map +0 -1
- package/dist/tempo/internal/defaults.js.map +0 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +0 -1
- package/dist/tempo/internal/fee-payer.js.map +0 -1
- package/dist/tempo/internal/fee-token.d.ts.map +0 -1
- package/dist/tempo/internal/fee-token.js.map +0 -1
- package/dist/tempo/internal/proof.d.ts.map +0 -1
- package/dist/tempo/internal/proof.js.map +0 -1
- package/dist/tempo/internal/selectors.d.ts.map +0 -1
- package/dist/tempo/internal/selectors.js.map +0 -1
- package/dist/tempo/internal/types.d.ts.map +0 -1
- package/dist/tempo/internal/types.js.map +0 -1
- package/dist/tempo/legacy/client/ChannelOps.d.ts.map +0 -1
- package/dist/tempo/legacy/client/ChannelOps.js.map +0 -1
- package/dist/tempo/legacy/client/Session.d.ts.map +0 -1
- package/dist/tempo/legacy/client/Session.js.map +0 -1
- package/dist/tempo/legacy/client/SessionManager.d.ts.map +0 -1
- package/dist/tempo/legacy/client/SessionManager.js.map +0 -1
- package/dist/tempo/legacy/client/index.d.ts.map +0 -1
- package/dist/tempo/legacy/client/index.js.map +0 -1
- package/dist/tempo/legacy/index.d.ts.map +0 -1
- package/dist/tempo/legacy/index.js.map +0 -1
- package/dist/tempo/legacy/session/Chain.d.ts.map +0 -1
- package/dist/tempo/legacy/session/Chain.js.map +0 -1
- package/dist/tempo/legacy/session/Channel.d.ts.map +0 -1
- package/dist/tempo/legacy/session/Channel.js.map +0 -1
- package/dist/tempo/legacy/session/Types.d.ts.map +0 -1
- package/dist/tempo/legacy/session/Types.js.map +0 -1
- package/dist/tempo/legacy/session/Voucher.d.ts.map +0 -1
- package/dist/tempo/legacy/session/Voucher.js.map +0 -1
- package/dist/tempo/legacy/session/escrow.abi.d.ts.map +0 -1
- package/dist/tempo/legacy/session/escrow.abi.js.map +0 -1
- package/dist/tempo/legacy/session/index.d.ts.map +0 -1
- package/dist/tempo/legacy/session/index.js.map +0 -1
- package/dist/tempo/server/Charge.d.ts.map +0 -1
- package/dist/tempo/server/Charge.js.map +0 -1
- package/dist/tempo/server/Methods.d.ts.map +0 -1
- package/dist/tempo/server/Methods.js.map +0 -1
- package/dist/tempo/server/Relay.d.ts.map +0 -1
- package/dist/tempo/server/Relay.js.map +0 -1
- package/dist/tempo/server/SponsorBudget.d.ts.map +0 -1
- package/dist/tempo/server/SponsorBudget.js.map +0 -1
- package/dist/tempo/server/Subscription.d.ts.map +0 -1
- package/dist/tempo/server/Subscription.js.map +0 -1
- package/dist/tempo/server/index.d.ts.map +0 -1
- package/dist/tempo/server/index.js.map +0 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +0 -1
- package/dist/tempo/server/internal/html.gen.js.map +0 -1
- package/dist/tempo/server/internal/request-body.d.ts.map +0 -1
- package/dist/tempo/server/internal/request-body.js.map +0 -1
- package/dist/tempo/server/internal/transport.d.ts.map +0 -1
- package/dist/tempo/server/internal/transport.js.map +0 -1
- package/dist/tempo/session/Snapshot.d.ts.map +0 -1
- package/dist/tempo/session/Snapshot.js.map +0 -1
- package/dist/tempo/session/client/ChannelOps.d.ts.map +0 -1
- package/dist/tempo/session/client/ChannelOps.js.map +0 -1
- package/dist/tempo/session/client/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/session/client/ChannelStore.js.map +0 -1
- package/dist/tempo/session/client/CredentialState.d.ts.map +0 -1
- package/dist/tempo/session/client/CredentialState.js.map +0 -1
- package/dist/tempo/session/client/ReceiptCoordinator.d.ts.map +0 -1
- package/dist/tempo/session/client/ReceiptCoordinator.js.map +0 -1
- package/dist/tempo/session/client/Runtime.d.ts.map +0 -1
- package/dist/tempo/session/client/Runtime.js.map +0 -1
- package/dist/tempo/session/client/Session.d.ts.map +0 -1
- package/dist/tempo/session/client/Session.js.map +0 -1
- package/dist/tempo/session/client/SessionManager.d.ts.map +0 -1
- package/dist/tempo/session/client/SessionManager.js.map +0 -1
- package/dist/tempo/session/client/Transports.d.ts.map +0 -1
- package/dist/tempo/session/client/Transports.js.map +0 -1
- package/dist/tempo/session/client/index.d.ts.map +0 -1
- package/dist/tempo/session/client/index.js.map +0 -1
- package/dist/tempo/session/client/internal/SessionManager.d.ts.map +0 -1
- package/dist/tempo/session/client/internal/SessionManager.js.map +0 -1
- package/dist/tempo/session/index.d.ts.map +0 -1
- package/dist/tempo/session/index.js.map +0 -1
- package/dist/tempo/session/precompile/Chain.d.ts.map +0 -1
- package/dist/tempo/session/precompile/Chain.js.map +0 -1
- package/dist/tempo/session/precompile/Channel.d.ts.map +0 -1
- package/dist/tempo/session/precompile/Channel.js.map +0 -1
- package/dist/tempo/session/precompile/Protocol.d.ts.map +0 -1
- package/dist/tempo/session/precompile/Protocol.js.map +0 -1
- package/dist/tempo/session/precompile/Voucher.d.ts.map +0 -1
- package/dist/tempo/session/precompile/Voucher.js.map +0 -1
- package/dist/tempo/session/precompile/escrow.abi.d.ts.map +0 -1
- package/dist/tempo/session/precompile/escrow.abi.js.map +0 -1
- package/dist/tempo/session/precompile/index.d.ts.map +0 -1
- package/dist/tempo/session/precompile/index.js.map +0 -1
- package/dist/tempo/session/server/ChannelOps.d.ts.map +0 -1
- package/dist/tempo/session/server/ChannelOps.js.map +0 -1
- package/dist/tempo/session/server/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/session/server/ChannelStore.js.map +0 -1
- package/dist/tempo/session/server/CredentialVerification.d.ts.map +0 -1
- package/dist/tempo/session/server/CredentialVerification.js.map +0 -1
- package/dist/tempo/session/server/MeteredStream.d.ts.map +0 -1
- package/dist/tempo/session/server/MeteredStream.js.map +0 -1
- package/dist/tempo/session/server/RequestState.d.ts.map +0 -1
- package/dist/tempo/session/server/RequestState.js.map +0 -1
- package/dist/tempo/session/server/Session.d.ts.map +0 -1
- package/dist/tempo/session/server/Session.js.map +0 -1
- package/dist/tempo/session/server/Settlement.d.ts.map +0 -1
- package/dist/tempo/session/server/Settlement.js.map +0 -1
- package/dist/tempo/session/server/Sse.d.ts.map +0 -1
- package/dist/tempo/session/server/Sse.js.map +0 -1
- package/dist/tempo/session/server/Transports.d.ts.map +0 -1
- package/dist/tempo/session/server/Transports.js.map +0 -1
- package/dist/tempo/session/server/Ws.d.ts.map +0 -1
- package/dist/tempo/session/server/Ws.js.map +0 -1
- package/dist/tempo/session/server/index.d.ts.map +0 -1
- package/dist/tempo/session/server/index.js.map +0 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts.map +0 -1
- package/dist/tempo/subscription/KeyAuthorization.js.map +0 -1
- package/dist/tempo/subscription/Receipt.d.ts.map +0 -1
- package/dist/tempo/subscription/Receipt.js.map +0 -1
- package/dist/tempo/subscription/Store.d.ts.map +0 -1
- package/dist/tempo/subscription/Store.js.map +0 -1
- package/dist/tempo/subscription/Types.d.ts.map +0 -1
- package/dist/tempo/subscription/Types.js.map +0 -1
- package/dist/tempo/subscription/index.d.ts.map +0 -1
- package/dist/tempo/subscription/index.js.map +0 -1
- package/dist/validation/core.d.ts.map +0 -1
- package/dist/validation/core.js.map +0 -1
- package/dist/validation/index.d.ts.map +0 -1
- package/dist/validation/index.js.map +0 -1
- package/dist/viem/Account.d.ts.map +0 -1
- package/dist/viem/Account.js.map +0 -1
- package/dist/viem/Client.d.ts.map +0 -1
- package/dist/viem/Client.js.map +0 -1
- package/dist/web-bot-auth/Constants.d.ts.map +0 -1
- package/dist/web-bot-auth/Constants.js.map +0 -1
- package/dist/web-bot-auth/Types.d.ts.map +0 -1
- package/dist/web-bot-auth/Types.js.map +0 -1
- package/dist/web-bot-auth/client/index.d.ts.map +0 -1
- package/dist/web-bot-auth/client/index.js.map +0 -1
- package/dist/web-bot-auth/index.d.ts.map +0 -1
- package/dist/web-bot-auth/index.js.map +0 -1
- package/dist/web-bot-auth/internal/JwkThumbprint.d.ts.map +0 -1
- package/dist/web-bot-auth/internal/JwkThumbprint.js.map +0 -1
- package/dist/web-bot-auth/internal/SignatureAgent.d.ts.map +0 -1
- package/dist/web-bot-auth/internal/SignatureAgent.js.map +0 -1
- package/dist/web-bot-auth/server/index.d.ts.map +0 -1
- package/dist/web-bot-auth/server/index.js.map +0 -1
- package/dist/x402/Assets.d.ts.map +0 -1
- package/dist/x402/Assets.js.map +0 -1
- package/dist/x402/Header.d.ts.map +0 -1
- package/dist/x402/Header.js.map +0 -1
- package/dist/x402/Types.d.ts.map +0 -1
- package/dist/x402/Types.js.map +0 -1
- package/dist/x402/client/Exact.d.ts.map +0 -1
- package/dist/x402/client/Exact.js.map +0 -1
- package/dist/x402/index.d.ts.map +0 -1
- package/dist/x402/index.js.map +0 -1
- package/dist/x402/internal/ChallengeBrand.d.ts.map +0 -1
- package/dist/x402/internal/ChallengeBrand.js.map +0 -1
- package/dist/x402/internal/RouteBinding.d.ts.map +0 -1
- package/dist/x402/internal/RouteBinding.js.map +0 -1
- package/dist/x402/server/EvmCharge.d.ts.map +0 -1
- package/dist/x402/server/EvmCharge.js.map +0 -1
- package/dist/x402/server/Facilitator.d.ts.map +0 -1
- package/dist/x402/server/Facilitator.js.map +0 -1
- package/dist/zod.d.ts.map +0 -1
- package/dist/zod.js.map +0 -1
- package/src/BodyDigest.test.ts +0 -43
- package/src/BodyDigest.ts +0 -55
- package/src/Challenge.fuzz.test.ts +0 -154
- package/src/Challenge.test-d.ts +0 -82
- package/src/Challenge.test.ts +0 -1060
- package/src/Challenge.ts +0 -687
- package/src/Constants.ts +0 -58
- package/src/Credential.fuzz.test.ts +0 -62
- package/src/Credential.test.ts +0 -378
- package/src/Credential.ts +0 -217
- package/src/Errors.test.ts +0 -520
- package/src/Errors.ts +0 -448
- package/src/Expires.test.ts +0 -112
- package/src/Expires.ts +0 -59
- package/src/Html.ts +0 -67
- package/src/Mcp.ts +0 -85
- package/src/Method.test.ts +0 -177
- package/src/Method.ts +0 -631
- package/src/PaymentRequest.test.ts +0 -147
- package/src/PaymentRequest.ts +0 -108
- package/src/Receipt.test.ts +0 -151
- package/src/Receipt.ts +0 -146
- package/src/Store.test-d.ts +0 -211
- package/src/Store.test.ts +0 -407
- package/src/Store.ts +0 -397
- package/src/attestation/Attestation.integration.test.ts +0 -208
- package/src/attestation/Client.test.ts +0 -83
- package/src/attestation/Client.ts +0 -34
- package/src/attestation/Constants.ts +0 -15
- package/src/attestation/Server.test.ts +0 -60
- package/src/attestation/Server.ts +0 -12
- package/src/attestation/Types.test-d.ts +0 -54
- package/src/attestation/Types.ts +0 -77
- package/src/attestation/index.ts +0 -5
- package/src/attestation/internal/HttpMessageSignature.test.ts +0 -101
- package/src/attestation/internal/HttpMessageSignature.ts +0 -429
- package/src/attestation/internal/SigningContext.ts +0 -13
- package/src/bin.js +0 -2
- package/src/bin.ts +0 -4
- package/src/cli/account.ts +0 -269
- package/src/cli/cli.test.ts +0 -2221
- package/src/cli/cli.ts +0 -1650
- package/src/cli/config.test.ts +0 -89
- package/src/cli/config.ts +0 -50
- package/src/cli/internal.ts +0 -114
- package/src/cli/mcp.test.ts +0 -262
- package/src/cli/plugins/evm.ts +0 -55
- package/src/cli/plugins/index.ts +0 -4
- package/src/cli/plugins/plugin.ts +0 -84
- package/src/cli/plugins/stripe.ts +0 -174
- package/src/cli/plugins/tempo.ts +0 -1102
- package/src/cli/sessions/Manager.test.ts +0 -249
- package/src/cli/sessions/Manager.ts +0 -93
- package/src/cli/sessions/commands.ts +0 -444
- package/src/cli/sessions/request.test.ts +0 -51
- package/src/cli/sessions/request.ts +0 -353
- package/src/cli/sessions/store.test.ts +0 -581
- package/src/cli/sessions/store.ts +0 -940
- package/src/cli/utils.test.ts +0 -156
- package/src/cli/utils.ts +0 -394
- package/src/cli/validate/challenge.ts +0 -565
- package/src/cli/validate/discovery.test.ts +0 -435
- package/src/cli/validate/discovery.ts +0 -221
- package/src/cli/validate/helpers.ts +0 -166
- package/src/cli/validate/index.ts +0 -229
- package/src/cli/validate/messages.ts +0 -7
- package/src/cli/validate/payment.ts +0 -774
- package/src/cli/validate.test.ts +0 -843
- package/src/client/Methods.ts +0 -8
- package/src/client/Mppx.test-d.ts +0 -287
- package/src/client/Mppx.test.ts +0 -1065
- package/src/client/Mppx.ts +0 -487
- package/src/client/Transport.test.ts +0 -586
- package/src/client/Transport.ts +0 -170
- package/src/client/index.ts +0 -29
- package/src/client/internal/Fetch.browser.test.ts +0 -197
- package/src/client/internal/Fetch.test-d.ts +0 -109
- package/src/client/internal/Fetch.test.ts +0 -2336
- package/src/client/internal/Fetch.ts +0 -1075
- package/src/client/internal/MethodChallenge.ts +0 -35
- package/src/client/internal/MethodResponse.ts +0 -67
- package/src/client/internal/protocols/Mcp.test.ts +0 -220
- package/src/client/internal/protocols/Mcp.ts +0 -186
- package/src/client/internal/protocols/Mpp.ts +0 -21
- package/src/client/internal/protocols/Protocol.ts +0 -10
- package/src/client/internal/protocols/Shared.ts +0 -25
- package/src/client/internal/protocols/X402.ts +0 -46
- package/src/client/node.test.ts +0 -115
- package/src/client/node.ts +0 -247
- package/src/discovery/Discovery.test.ts +0 -214
- package/src/discovery/Discovery.ts +0 -105
- package/src/discovery/OpenApi.test.ts +0 -623
- package/src/discovery/OpenApi.ts +0 -192
- package/src/discovery/Validate.test.ts +0 -305
- package/src/discovery/Validate.ts +0 -77
- package/src/discovery/index.ts +0 -3
- package/src/discovery/internal/Metadata.ts +0 -51
- package/src/env.d.ts +0 -12
- package/src/evm/Assets.ts +0 -1
- package/src/evm/Chains.ts +0 -11
- package/src/evm/Methods.ts +0 -44
- package/src/evm/PublicInterface.test-d.ts +0 -134
- package/src/evm/Types.ts +0 -140
- package/src/evm/client/Charge.test.ts +0 -347
- package/src/evm/client/Charge.ts +0 -187
- package/src/evm/client/Methods.ts +0 -19
- package/src/evm/client/index.ts +0 -6
- package/src/evm/index.ts +0 -16
- package/src/evm/server/Charge.test.ts +0 -339
- package/src/evm/server/Charge.ts +0 -295
- package/src/evm/server/Methods.ts +0 -22
- package/src/evm/server/index.ts +0 -6
- package/src/index.ts +0 -14
- package/src/internal/AcceptPayment.test.ts +0 -347
- package/src/internal/AcceptPayment.ts +0 -366
- package/src/internal/HeaderCodec.ts +0 -36
- package/src/internal/changeset.test.ts +0 -106
- package/src/internal/constantTimeEqual.test.ts +0 -47
- package/src/internal/constantTimeEqual.ts +0 -10
- package/src/internal/env.test.ts +0 -42
- package/src/internal/env.ts +0 -49
- package/src/internal/types.test-d.ts +0 -21
- package/src/internal/types.ts +0 -479
- package/src/mcp/client/McpClient.integration.test.ts +0 -639
- package/src/mcp/client/McpClient.test-d.ts +0 -144
- package/src/mcp/client/McpClient.test.ts +0 -463
- package/src/mcp/client/McpClient.ts +0 -307
- package/src/mcp/client/McpClient.unit.test.ts +0 -135
- package/src/mcp/client/index.ts +0 -2
- package/src/mcp/server/Transport.test.ts +0 -191
- package/src/mcp/server/Transport.ts +0 -111
- package/src/mcp/server/index.ts +0 -1
- package/src/middlewares/elysia.test.ts +0 -285
- package/src/middlewares/elysia.ts +0 -116
- package/src/middlewares/express.test.ts +0 -361
- package/src/middlewares/express.ts +0 -140
- package/src/middlewares/hono.test.ts +0 -473
- package/src/middlewares/hono.ts +0 -153
- package/src/middlewares/internal/mppx.test.ts +0 -177
- package/src/middlewares/internal/mppx.ts +0 -67
- package/src/middlewares/nextjs.test.ts +0 -359
- package/src/middlewares/nextjs.ts +0 -107
- package/src/proxy/Proxy.test.ts +0 -1038
- package/src/proxy/Proxy.ts +0 -483
- package/src/proxy/Proxy.upstreamError.test.ts +0 -364
- package/src/proxy/Service.test.ts +0 -252
- package/src/proxy/Service.ts +0 -280
- package/src/proxy/index.ts +0 -6
- package/src/proxy/internal/Headers.test.ts +0 -139
- package/src/proxy/internal/Headers.ts +0 -66
- package/src/proxy/internal/Route.test.ts +0 -217
- package/src/proxy/internal/Route.ts +0 -64
- package/src/proxy/services/anthropic.test.ts +0 -132
- package/src/proxy/services/anthropic.ts +0 -53
- package/src/proxy/services/hooks.test-d.ts +0 -14
- package/src/proxy/services/hooks.test.ts +0 -44
- package/src/proxy/services/openai.test.ts +0 -157
- package/src/proxy/services/openai.ts +0 -60
- package/src/proxy/services/stripe.test.ts +0 -175
- package/src/proxy/services/stripe.ts +0 -59
- package/src/server/Methods.test-d.ts +0 -88
- package/src/server/Methods.test.ts +0 -69
- package/src/server/Methods.ts +0 -3
- package/src/server/Mppx.authorize.test.ts +0 -210
- package/src/server/Mppx.test-d.ts +0 -494
- package/src/server/Mppx.test.ts +0 -6711
- package/src/server/Mppx.ts +0 -2919
- package/src/server/NodeListener.test.ts +0 -293
- package/src/server/NodeListener.ts +0 -45
- package/src/server/Request.test.ts +0 -255
- package/src/server/Request.ts +0 -155
- package/src/server/Response.test.ts +0 -32
- package/src/server/Response.ts +0 -45
- package/src/server/Transport.test.ts +0 -598
- package/src/server/Transport.ts +0 -320
- package/src/server/index.ts +0 -9
- package/src/server/internal/html/compose.main.gen.ts +0 -2
- package/src/server/internal/html/compose.main.ts +0 -88
- package/src/server/internal/html/config.ts +0 -672
- package/src/server/internal/html/constants.ts +0 -28
- package/src/server/internal/html/serviceWorker.client.ts +0 -28
- package/src/server/internal/html/serviceWorker.gen.ts +0 -2
- package/src/server/internal/html/serviceWorker.ts +0 -27
- package/src/server/internal/html/tsconfig.compose.json +0 -8
- package/src/server/internal/html/tsconfig.worker.client.json +0 -8
- package/src/server/internal/html/tsconfig.worker.json +0 -8
- package/src/server/internal/scope.ts +0 -43
- package/src/stripe/Charge.integration.test.ts +0 -329
- package/src/stripe/Methods.test.ts +0 -62
- package/src/stripe/Methods.ts +0 -44
- package/src/stripe/client/Charge.test.ts +0 -205
- package/src/stripe/client/Charge.ts +0 -132
- package/src/stripe/client/Methods.ts +0 -37
- package/src/stripe/client/index.ts +0 -2
- package/src/stripe/index.ts +0 -1
- package/src/stripe/internal/constants.ts +0 -7
- package/src/stripe/internal/types.ts +0 -47
- package/src/stripe/server/Charge.test-d.ts +0 -81
- package/src/stripe/server/Charge.test.ts +0 -700
- package/src/stripe/server/Charge.ts +0 -487
- package/src/stripe/server/Methods.test-d.ts +0 -157
- package/src/stripe/server/Methods.test.ts +0 -403
- package/src/stripe/server/Methods.ts +0 -441
- package/src/stripe/server/index.ts +0 -2
- package/src/stripe/server/internal/deposit-address.ts +0 -57
- package/src/stripe/server/internal/html/main.ts +0 -165
- package/src/stripe/server/internal/html/node_modules/.bin/mppx +0 -22
- package/src/stripe/server/internal/html/node_modules/.bin/mppx.src +0 -22
- package/src/stripe/server/internal/html/package.json +0 -9
- package/src/stripe/server/internal/html/stripe-js-pure.d.ts +0 -7
- package/src/stripe/server/internal/html/tsconfig.json +0 -8
- package/src/stripe/server/internal/html/types.ts +0 -5
- package/src/stripe/server/internal/html.gen.ts +0 -2
- package/src/stripe/server/internal/record-payment.ts +0 -66
- package/src/stripe/server/internal/request.ts +0 -49
- package/src/tap/Constants.ts +0 -32
- package/src/tap/Tap.test.ts +0 -125
- package/src/tap/Types.ts +0 -11
- package/src/tap/client/index.ts +0 -54
- package/src/tap/index.ts +0 -4
- package/src/tap/server/index.ts +0 -48
- package/src/tempo/AccessKeyAuthorization.test.ts +0 -141
- package/src/tempo/Attribution.test.ts +0 -294
- package/src/tempo/Attribution.ts +0 -185
- package/src/tempo/Methods.test.ts +0 -425
- package/src/tempo/Methods.ts +0 -367
- package/src/tempo/Proof.conformance.test.ts +0 -146
- package/src/tempo/Proof.test-d.ts +0 -32
- package/src/tempo/Proof.test.ts +0 -40
- package/src/tempo/Proof.ts +0 -69
- package/src/tempo/PublicExports.test-d.ts +0 -130
- package/src/tempo/Subscription.integration.test.ts +0 -591
- package/src/tempo/client/Charge.test.ts +0 -501
- package/src/tempo/client/Charge.ts +0 -271
- package/src/tempo/client/Methods.ts +0 -45
- package/src/tempo/client/ResolveAccount.ts +0 -46
- package/src/tempo/client/Subscription.test.ts +0 -180
- package/src/tempo/client/Subscription.ts +0 -156
- package/src/tempo/client/index.ts +0 -17
- package/src/tempo/index.ts +0 -5
- package/src/tempo/internal/account.ts +0 -56
- package/src/tempo/internal/address.test.ts +0 -42
- package/src/tempo/internal/address.ts +0 -6
- package/src/tempo/internal/auto-swap.test.ts +0 -198
- package/src/tempo/internal/auto-swap.ts +0 -145
- package/src/tempo/internal/charge.test.ts +0 -66
- package/src/tempo/internal/charge.ts +0 -43
- package/src/tempo/internal/defaults.test.ts +0 -95
- package/src/tempo/internal/defaults.ts +0 -53
- package/src/tempo/internal/fee-payer.test.ts +0 -1177
- package/src/tempo/internal/fee-payer.ts +0 -808
- package/src/tempo/internal/fee-token.test.ts +0 -165
- package/src/tempo/internal/fee-token.ts +0 -72
- package/src/tempo/internal/proof.test.ts +0 -97
- package/src/tempo/internal/proof.ts +0 -87
- package/src/tempo/internal/selectors.ts +0 -10
- package/src/tempo/internal/types.ts +0 -19
- package/src/tempo/legacy/AccessKeyAuthorization.test.ts +0 -162
- package/src/tempo/legacy/README.md +0 -9
- package/src/tempo/legacy/client/ChannelOps.test.ts +0 -379
- package/src/tempo/legacy/client/ChannelOps.ts +0 -271
- package/src/tempo/legacy/client/Session.test.ts +0 -800
- package/src/tempo/legacy/client/Session.ts +0 -397
- package/src/tempo/legacy/client/SessionManager.test.ts +0 -468
- package/src/tempo/legacy/client/SessionManager.ts +0 -909
- package/src/tempo/legacy/client/index.ts +0 -6
- package/src/tempo/legacy/index.ts +0 -4
- package/src/tempo/legacy/session/Chain.test.ts +0 -1349
- package/src/tempo/legacy/session/Chain.ts +0 -830
- package/src/tempo/legacy/session/Channel.test.ts +0 -109
- package/src/tempo/legacy/session/Channel.ts +0 -51
- package/src/tempo/legacy/session/Types.ts +0 -91
- package/src/tempo/legacy/session/Voucher.test.ts +0 -410
- package/src/tempo/legacy/session/Voucher.ts +0 -183
- package/src/tempo/legacy/session/escrow.abi.ts +0 -760
- package/src/tempo/legacy/session/index.ts +0 -8
- package/src/tempo/server/AtomicStore.test-d.ts +0 -63
- package/src/tempo/server/Charge.test.ts +0 -6056
- package/src/tempo/server/Charge.ts +0 -1379
- package/src/tempo/server/Methods.ts +0 -86
- package/src/tempo/server/Relay.test.ts +0 -677
- package/src/tempo/server/Relay.ts +0 -297
- package/src/tempo/server/SponsorBudget.test.ts +0 -154
- package/src/tempo/server/SponsorBudget.ts +0 -219
- package/src/tempo/server/Sse.test.ts +0 -321
- package/src/tempo/server/Subscription.test.ts +0 -1944
- package/src/tempo/server/Subscription.ts +0 -1336
- package/src/tempo/server/index.ts +0 -12
- package/src/tempo/server/internal/html/main.ts +0 -115
- package/src/tempo/server/internal/html/node_modules/.bin/mppx +0 -22
- package/src/tempo/server/internal/html/node_modules/.bin/mppx.src +0 -22
- package/src/tempo/server/internal/html/package.json +0 -10
- package/src/tempo/server/internal/html/tsconfig.json +0 -8
- package/src/tempo/server/internal/html.gen.ts +0 -2
- package/src/tempo/server/internal/request-body.test.ts +0 -210
- package/src/tempo/server/internal/request-body.ts +0 -61
- package/src/tempo/server/internal/transport.test.ts +0 -922
- package/src/tempo/server/internal/transport.ts +0 -322
- package/src/tempo/session/README.md +0 -320
- package/src/tempo/session/Snapshot.test.ts +0 -41
- package/src/tempo/session/Snapshot.ts +0 -95
- package/src/tempo/session/client/ChannelOps.test.ts +0 -218
- package/src/tempo/session/client/ChannelOps.ts +0 -348
- package/src/tempo/session/client/ChannelStore.ts +0 -111
- package/src/tempo/session/client/CredentialState.test.ts +0 -789
- package/src/tempo/session/client/CredentialState.ts +0 -942
- package/src/tempo/session/client/ReceiptCoordinator.ts +0 -95
- package/src/tempo/session/client/Runtime.test.ts +0 -1104
- package/src/tempo/session/client/Runtime.ts +0 -1008
- package/src/tempo/session/client/Session.test.ts +0 -1355
- package/src/tempo/session/client/Session.ts +0 -420
- package/src/tempo/session/client/SessionManager.test.ts +0 -1899
- package/src/tempo/session/client/SessionManager.ts +0 -996
- package/src/tempo/session/client/Transports.test.ts +0 -1141
- package/src/tempo/session/client/Transports.ts +0 -1534
- package/src/tempo/session/client/index.ts +0 -40
- package/src/tempo/session/client/internal/SessionManager.ts +0 -36
- package/src/tempo/session/index.ts +0 -7
- package/src/tempo/session/precompile/Chain.integration.test.ts +0 -339
- package/src/tempo/session/precompile/Chain.test.ts +0 -1641
- package/src/tempo/session/precompile/Chain.ts +0 -1269
- package/src/tempo/session/precompile/Channel.test.ts +0 -147
- package/src/tempo/session/precompile/Channel.ts +0 -108
- package/src/tempo/session/precompile/Protocol.test.ts +0 -358
- package/src/tempo/session/precompile/Protocol.ts +0 -520
- package/src/tempo/session/precompile/Voucher.test.ts +0 -354
- package/src/tempo/session/precompile/Voucher.ts +0 -162
- package/src/tempo/session/precompile/escrow.abi.ts +0 -226
- package/src/tempo/session/precompile/index.ts +0 -33
- package/src/tempo/session/server/ChannelOps.test.ts +0 -129
- package/src/tempo/session/server/ChannelOps.ts +0 -157
- package/src/tempo/session/server/ChannelStore.test.ts +0 -1093
- package/src/tempo/session/server/ChannelStore.ts +0 -856
- package/src/tempo/session/server/CredentialVerification.test.ts +0 -299
- package/src/tempo/session/server/CredentialVerification.ts +0 -1087
- package/src/tempo/session/server/MeteredStream.ts +0 -99
- package/src/tempo/session/server/RequestState.test.ts +0 -574
- package/src/tempo/session/server/RequestState.ts +0 -507
- package/src/tempo/session/server/Session.integration.test.ts +0 -449
- package/src/tempo/session/server/Session.test.ts +0 -4247
- package/src/tempo/session/server/Session.ts +0 -669
- package/src/tempo/session/server/Settlement.test.ts +0 -345
- package/src/tempo/session/server/Settlement.ts +0 -527
- package/src/tempo/session/server/Sse.fuzz.test.ts +0 -138
- package/src/tempo/session/server/Sse.test.ts +0 -634
- package/src/tempo/session/server/Sse.ts +0 -261
- package/src/tempo/session/server/Transports.test.ts +0 -346
- package/src/tempo/session/server/Transports.ts +0 -256
- package/src/tempo/session/server/Ws.test.ts +0 -765
- package/src/tempo/session/server/Ws.ts +0 -391
- package/src/tempo/session/server/index.ts +0 -12
- package/src/tempo/subscription/KeyAuthorization.test.ts +0 -282
- package/src/tempo/subscription/KeyAuthorization.ts +0 -396
- package/src/tempo/subscription/Receipt.ts +0 -28
- package/src/tempo/subscription/Store.test.ts +0 -609
- package/src/tempo/subscription/Store.ts +0 -449
- package/src/tempo/subscription/Types.ts +0 -68
- package/src/tempo/subscription/index.ts +0 -23
- package/src/tsconfig.json +0 -10
- package/src/validation/core.ts +0 -386
- package/src/validation/index.ts +0 -11
- package/src/viem/Account.test.ts +0 -72
- package/src/viem/Account.ts +0 -30
- package/src/viem/Client.test.ts +0 -366
- package/src/viem/Client.ts +0 -93
- package/src/web-bot-auth/Constants.ts +0 -24
- package/src/web-bot-auth/Types.ts +0 -9
- package/src/web-bot-auth/WebBotAuth.test.ts +0 -182
- package/src/web-bot-auth/client/index.ts +0 -84
- package/src/web-bot-auth/index.ts +0 -6
- package/src/web-bot-auth/internal/JwkThumbprint.ts +0 -21
- package/src/web-bot-auth/internal/SignatureAgent.ts +0 -18
- package/src/web-bot-auth/server/index.ts +0 -113
- package/src/x402/Assets.test.ts +0 -253
- package/src/x402/Assets.ts +0 -221
- package/src/x402/Exact.e2e.test.ts +0 -488
- package/src/x402/Exact.localnet.test.ts +0 -475
- package/src/x402/Header.test.ts +0 -73
- package/src/x402/Header.ts +0 -79
- package/src/x402/PublicInterface.test-d.ts +0 -39
- package/src/x402/Types.ts +0 -248
- package/src/x402/client/Exact.test.ts +0 -309
- package/src/x402/client/Exact.ts +0 -186
- package/src/x402/index.ts +0 -6
- package/src/x402/internal/ChallengeBrand.ts +0 -14
- package/src/x402/internal/RouteBinding.ts +0 -18
- package/src/x402/server/EvmCharge.ts +0 -410
- package/src/x402/server/Facilitator.test.ts +0 -111
- package/src/x402/server/Facilitator.ts +0 -56
- package/src/zod.test.ts +0 -169
- package/src/zod.ts +0 -71
|
@@ -1,1944 +0,0 @@
|
|
|
1
|
-
import { Challenge, Credential, Receipt } from 'mppx'
|
|
2
|
-
import { Mppx } from 'mppx/server'
|
|
3
|
-
import { KeyAuthorization } from 'ox/tempo'
|
|
4
|
-
import {
|
|
5
|
-
createClient,
|
|
6
|
-
custom,
|
|
7
|
-
decodeFunctionData,
|
|
8
|
-
encodeAbiParameters,
|
|
9
|
-
encodeEventTopics,
|
|
10
|
-
type Address,
|
|
11
|
-
type Hex,
|
|
12
|
-
} from 'viem'
|
|
13
|
-
import { privateKeyToAccount } from 'viem/accounts'
|
|
14
|
-
import { Abis, Transaction } from 'viem/tempo'
|
|
15
|
-
import { tempo as tempo_chain } from 'viem/tempo/chains'
|
|
16
|
-
import { describe, expect, test } from 'vp/test'
|
|
17
|
-
|
|
18
|
-
import * as Store from '../../Store.js'
|
|
19
|
-
import * as Methods from '../Methods.js'
|
|
20
|
-
import { signSubscriptionKeyAuthorization } from '../subscription/KeyAuthorization.js'
|
|
21
|
-
import * as SubscriptionStore from '../subscription/Store.js'
|
|
22
|
-
import type { SubscriptionAccessKey } from '../subscription/Types.js'
|
|
23
|
-
import type { SubscriptionRecord } from '../subscription/Types.js'
|
|
24
|
-
import { renew, subscription } from './Subscription.js'
|
|
25
|
-
|
|
26
|
-
const realm = 'api.example.com'
|
|
27
|
-
const secretKey = 'test-secret-key-test-secret-key-32'
|
|
28
|
-
const activeBillingAnchor = new Date(Math.floor(Date.now() / 1_000) * 1_000).toISOString()
|
|
29
|
-
const activeSubscriptionExpires = new Date(
|
|
30
|
-
Math.ceil((Date.now() + 365 * 24 * 60 * 60 * 1_000) / 1_000) * 1_000,
|
|
31
|
-
).toISOString()
|
|
32
|
-
const chainId = 4217
|
|
33
|
-
const subscriptionDefaultChainId = 42431
|
|
34
|
-
const subscriptionAmount = '10'
|
|
35
|
-
const subscriptionCurrency = '0x20c0000000000000000000000000000000000001'
|
|
36
|
-
const subscriptionKey = 'user-1:plan:pro'
|
|
37
|
-
const subscriptionPeriodCount = '1'
|
|
38
|
-
const subscriptionPeriodUnit = 'day'
|
|
39
|
-
const subscriptionPeriodMilliseconds = 86_400_000
|
|
40
|
-
const subscriptionRecipient = '0x1234567890abcdef1234567890abcdef12345678'
|
|
41
|
-
const rootAccount = privateKeyToAccount(
|
|
42
|
-
'0x0000000000000000000000000000000000000000000000000000000000000001',
|
|
43
|
-
)
|
|
44
|
-
const otherRootAccount = privateKeyToAccount(
|
|
45
|
-
'0x0000000000000000000000000000000000000000000000000000000000000004',
|
|
46
|
-
)
|
|
47
|
-
const accessAccount = privateKeyToAccount(
|
|
48
|
-
'0x0000000000000000000000000000000000000000000000000000000000000002',
|
|
49
|
-
)
|
|
50
|
-
const otherAccessAccount = privateKeyToAccount(
|
|
51
|
-
'0x0000000000000000000000000000000000000000000000000000000000000003',
|
|
52
|
-
)
|
|
53
|
-
const accessKey = {
|
|
54
|
-
accessKeyAddress: accessAccount.address,
|
|
55
|
-
keyType: 'secp256k1',
|
|
56
|
-
} as const satisfies SubscriptionAccessKey
|
|
57
|
-
const hashActivate = `0x${'a'.repeat(64)}`
|
|
58
|
-
const hashRenewed = `0x${'b'.repeat(64)}`
|
|
59
|
-
const hashStale = `0x${'c'.repeat(64)}`
|
|
60
|
-
const hashBackground = `0x${'d'.repeat(64)}`
|
|
61
|
-
const hashOld = `0x${'e'.repeat(64)}`
|
|
62
|
-
|
|
63
|
-
function createReceipt(subscriptionId: string, reference = hashActivate) {
|
|
64
|
-
return {
|
|
65
|
-
method: 'tempo',
|
|
66
|
-
reference,
|
|
67
|
-
status: 'success',
|
|
68
|
-
subscriptionId,
|
|
69
|
-
timestamp: '2025-01-01T00:00:00.000Z',
|
|
70
|
-
} as const
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function createRecord(overrides: Partial<SubscriptionRecord> = {}): SubscriptionRecord {
|
|
74
|
-
return {
|
|
75
|
-
amount: '10000000',
|
|
76
|
-
billingAnchor: activeBillingAnchor,
|
|
77
|
-
chainId,
|
|
78
|
-
currency: subscriptionCurrency,
|
|
79
|
-
lastChargedPeriod: 0,
|
|
80
|
-
lookupKey: subscriptionKey,
|
|
81
|
-
periodCount: subscriptionPeriodCount,
|
|
82
|
-
periodUnit: subscriptionPeriodUnit,
|
|
83
|
-
recipient: subscriptionRecipient,
|
|
84
|
-
reference: hashActivate,
|
|
85
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
86
|
-
subscriptionId: 'sub_123',
|
|
87
|
-
timestamp: '2025-01-01T00:00:00.000Z',
|
|
88
|
-
...overrides,
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function createCredential(
|
|
93
|
-
challenge: Challenge.Challenge,
|
|
94
|
-
source = rootAccount.address,
|
|
95
|
-
key: SubscriptionAccessKey = accessKey,
|
|
96
|
-
account = rootAccount,
|
|
97
|
-
) {
|
|
98
|
-
const keyAuthorization = await signSubscriptionKeyAuthorization({
|
|
99
|
-
accessKey: key,
|
|
100
|
-
account,
|
|
101
|
-
chainId,
|
|
102
|
-
request: challenge.request as ReturnType<typeof Methods.subscription.schema.request.parse>,
|
|
103
|
-
})
|
|
104
|
-
if (!keyAuthorization) throw new Error('expected key authorization')
|
|
105
|
-
return Credential.from({
|
|
106
|
-
challenge,
|
|
107
|
-
payload: {
|
|
108
|
-
signature: KeyAuthorization.serialize(keyAuthorization),
|
|
109
|
-
type: 'keyAuthorization',
|
|
110
|
-
},
|
|
111
|
-
source: `did:pkh:eip155:${chainId}:${source.toLowerCase()}`,
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function createBillingClient(
|
|
116
|
-
hashes: readonly string[],
|
|
117
|
-
options: { account?: typeof rootAccount; failSimulation?: number | undefined } = {},
|
|
118
|
-
) {
|
|
119
|
-
const callRequests: Record<string, unknown>[] = []
|
|
120
|
-
const rpcMethods: string[] = []
|
|
121
|
-
let nextHash = 0
|
|
122
|
-
let simulations = 0
|
|
123
|
-
const client = createClient({
|
|
124
|
-
account: options.account,
|
|
125
|
-
chain: { ...tempo_chain, id: chainId },
|
|
126
|
-
transport: custom({
|
|
127
|
-
async request({ method, params }) {
|
|
128
|
-
rpcMethods.push(method)
|
|
129
|
-
if (method === 'eth_chainId') return `0x${chainId.toString(16)}`
|
|
130
|
-
if (method === 'eth_getTransactionCount') return '0x0'
|
|
131
|
-
if (method === 'eth_estimateGas') return '0x5208'
|
|
132
|
-
if (method === 'eth_maxPriorityFeePerGas') return '0x1'
|
|
133
|
-
if (method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
|
|
134
|
-
if (method === 'eth_call') {
|
|
135
|
-
callRequests.push((params as [Record<string, unknown>])[0])
|
|
136
|
-
if (options.failSimulation !== undefined && ++simulations >= options.failSimulation)
|
|
137
|
-
throw new Error('final sponsored simulation reverted')
|
|
138
|
-
return '0x'
|
|
139
|
-
}
|
|
140
|
-
if (method === 'eth_sendRawTransaction') return hashes[nextHash++] ?? hashActivate
|
|
141
|
-
throw new Error(`unexpected rpc method: ${method}`)
|
|
142
|
-
},
|
|
143
|
-
}),
|
|
144
|
-
})
|
|
145
|
-
return { callRequests, client, rpcMethods }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function activateFeeSponsoredSubscription(options?: { failSimulation?: number | undefined }) {
|
|
149
|
-
const store = Store.memory()
|
|
150
|
-
const billing = createBillingClient([hashActivate], {
|
|
151
|
-
account: rootAccount,
|
|
152
|
-
...options,
|
|
153
|
-
})
|
|
154
|
-
const method = subscription({
|
|
155
|
-
amount: subscriptionAmount,
|
|
156
|
-
chainId,
|
|
157
|
-
currency: subscriptionCurrency,
|
|
158
|
-
feePayer: rootAccount,
|
|
159
|
-
getClient: async () => billing.client,
|
|
160
|
-
periodCount: subscriptionPeriodCount,
|
|
161
|
-
periodUnit: subscriptionPeriodUnit,
|
|
162
|
-
recipient: subscriptionRecipient,
|
|
163
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
164
|
-
store,
|
|
165
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
166
|
-
waitForConfirmation: false,
|
|
167
|
-
})
|
|
168
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
169
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
170
|
-
new Request('https://example.com/resource'),
|
|
171
|
-
)
|
|
172
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
173
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
174
|
-
const accessKey = (
|
|
175
|
-
challenge.request as ReturnType<typeof Methods.subscription.schema.request.parse>
|
|
176
|
-
).methodDetails?.accessKey
|
|
177
|
-
if (!accessKey) throw new Error('expected generated access key')
|
|
178
|
-
const credential = await createCredential(challenge, rootAccount.address, accessKey)
|
|
179
|
-
const result = await mppx.tempo.subscription({})(
|
|
180
|
-
new Request('https://example.com/resource', {
|
|
181
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
182
|
-
}),
|
|
183
|
-
)
|
|
184
|
-
return { ...billing, result }
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const confirmedBlockHash = `0x${'f'.repeat(64)}` as const
|
|
188
|
-
// Canonical TIP-1028 ReceivePolicyGuard precompile address.
|
|
189
|
-
const receivePolicyGuard = '0xB10C000000000000000000000000000000000000' as Address
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Mock client whose `eth_sendRawTransactionSync` returns a confirmed receipt
|
|
193
|
-
* with a `TransferWithMemo` log derived from the broadcast transaction.
|
|
194
|
-
*
|
|
195
|
-
* `redirectTo` simulates a T6 (TIP-1028) held transfer (credits the guard, not
|
|
196
|
-
* the recipient); `addUnrelatedTransfer` adds a memo-less `Transfer` to the
|
|
197
|
-
* recipient. By default only the renewal (second) broadcast is affected so
|
|
198
|
-
* activation succeeds; `redirectActivation` also holds the first broadcast.
|
|
199
|
-
*/
|
|
200
|
-
function createConfirmingBillingClient(options?: {
|
|
201
|
-
addUnrelatedTransfer?: boolean
|
|
202
|
-
redirectActivation?: boolean
|
|
203
|
-
redirectTo?: Address
|
|
204
|
-
}) {
|
|
205
|
-
const rpcMethods: string[] = []
|
|
206
|
-
let broadcastIndex = 0
|
|
207
|
-
const client = createClient({
|
|
208
|
-
chain: { ...tempo_chain, id: chainId },
|
|
209
|
-
transport: custom({
|
|
210
|
-
async request({ method, params }) {
|
|
211
|
-
rpcMethods.push(method)
|
|
212
|
-
if (method === 'eth_chainId') return `0x${chainId.toString(16)}`
|
|
213
|
-
if (method === 'eth_call') return '0x'
|
|
214
|
-
if (method === 'eth_sendRawTransactionSync') {
|
|
215
|
-
const index = broadcastIndex++
|
|
216
|
-
const isRenewal = index >= 1
|
|
217
|
-
const serialized = (params as [Hex])[0]
|
|
218
|
-
const transaction = Transaction.deserialize(
|
|
219
|
-
serialized as Transaction.TransactionSerializedTempo,
|
|
220
|
-
) as unknown as {
|
|
221
|
-
from: Address
|
|
222
|
-
calls: readonly { data: Hex; to: Address }[]
|
|
223
|
-
}
|
|
224
|
-
const call = transaction.calls[0]!
|
|
225
|
-
const { args } = decodeFunctionData({
|
|
226
|
-
abi: Abis.tip20,
|
|
227
|
-
data: call.data,
|
|
228
|
-
})
|
|
229
|
-
const [recipient, amount, memo] = args as [Address, bigint, Hex]
|
|
230
|
-
const shouldRedirect = options?.redirectTo && (isRenewal || options?.redirectActivation)
|
|
231
|
-
const creditedTo = shouldRedirect ? options!.redirectTo! : recipient
|
|
232
|
-
const hash = `0x${index.toString(16).padStart(64, '0')}` as Hex
|
|
233
|
-
const baseLog = {
|
|
234
|
-
blockHash: confirmedBlockHash,
|
|
235
|
-
blockNumber: '0x1',
|
|
236
|
-
data: encodeAbiParameters([{ type: 'uint256' }], [amount]),
|
|
237
|
-
logIndex: '0x0',
|
|
238
|
-
removed: false,
|
|
239
|
-
transactionHash: hash,
|
|
240
|
-
transactionIndex: '0x0',
|
|
241
|
-
} as const
|
|
242
|
-
const logs: unknown[] = [
|
|
243
|
-
{
|
|
244
|
-
...baseLog,
|
|
245
|
-
address: call.to,
|
|
246
|
-
topics: encodeEventTopics({
|
|
247
|
-
abi: Abis.tip20,
|
|
248
|
-
args: { from: transaction.from, memo, to: creditedTo },
|
|
249
|
-
eventName: 'TransferWithMemo',
|
|
250
|
-
}),
|
|
251
|
-
},
|
|
252
|
-
]
|
|
253
|
-
// Memo-less Transfer to the recipient: must not satisfy the check.
|
|
254
|
-
if (isRenewal && options?.addUnrelatedTransfer) {
|
|
255
|
-
logs.push({
|
|
256
|
-
...baseLog,
|
|
257
|
-
address: call.to,
|
|
258
|
-
logIndex: '0x1',
|
|
259
|
-
topics: encodeEventTopics({
|
|
260
|
-
abi: Abis.tip20,
|
|
261
|
-
args: { from: transaction.from, to: recipient },
|
|
262
|
-
eventName: 'Transfer',
|
|
263
|
-
}),
|
|
264
|
-
})
|
|
265
|
-
}
|
|
266
|
-
return {
|
|
267
|
-
blockHash: confirmedBlockHash,
|
|
268
|
-
blockNumber: '0x1',
|
|
269
|
-
contractAddress: null,
|
|
270
|
-
cumulativeGasUsed: '0x0',
|
|
271
|
-
effectiveGasPrice: '0x0',
|
|
272
|
-
from: transaction.from,
|
|
273
|
-
gasUsed: '0x0',
|
|
274
|
-
logs,
|
|
275
|
-
logsBloom: `0x${'0'.repeat(512)}`,
|
|
276
|
-
status: '0x1',
|
|
277
|
-
to: call.to,
|
|
278
|
-
transactionHash: hash,
|
|
279
|
-
transactionIndex: '0x0',
|
|
280
|
-
type: '0x0',
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
throw new Error(`unexpected rpc method: ${method}`)
|
|
284
|
-
},
|
|
285
|
-
}),
|
|
286
|
-
})
|
|
287
|
-
return { client, rpcMethods }
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
describe('tempo.subscription', () => {
|
|
291
|
-
test('preflights sender and final envelopes for fee-sponsored activation', async () => {
|
|
292
|
-
const { callRequests, result, rpcMethods } = await activateFeeSponsoredSubscription()
|
|
293
|
-
|
|
294
|
-
expect(result.status).toBe(200)
|
|
295
|
-
expect(callRequests).toHaveLength(2)
|
|
296
|
-
expect(callRequests[0]).not.toHaveProperty('feePayer')
|
|
297
|
-
expect(callRequests[1]?.feePayer).toBe(rootAccount.address)
|
|
298
|
-
expect(rpcMethods).toContain('eth_sendRawTransaction')
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
test('does not broadcast a fee-sponsored activation after final simulation failure', async () => {
|
|
302
|
-
const { callRequests, result, rpcMethods } = await activateFeeSponsoredSubscription({
|
|
303
|
-
failSimulation: 2,
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
expect(result.status).not.toBe(200)
|
|
307
|
-
expect(callRequests.length).toBeGreaterThanOrEqual(2)
|
|
308
|
-
expect(rpcMethods).not.toContain('eth_sendRawTransaction')
|
|
309
|
-
expect(rpcMethods).not.toContain('eth_sendRawTransactionSync')
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
test('stores an activated subscription and reuses it on later requests', async () => {
|
|
313
|
-
const store = Store.memory()
|
|
314
|
-
let activationCount = 0
|
|
315
|
-
const method = subscription({
|
|
316
|
-
activate: async ({ request, resolved }) => {
|
|
317
|
-
activationCount += 1
|
|
318
|
-
return {
|
|
319
|
-
receipt: createReceipt('sub_123', hashActivate),
|
|
320
|
-
subscription: createRecord({
|
|
321
|
-
amount: request.amount,
|
|
322
|
-
chainId: request.methodDetails?.chainId,
|
|
323
|
-
currency: request.currency,
|
|
324
|
-
lookupKey: resolved.key,
|
|
325
|
-
periodCount: request.periodCount,
|
|
326
|
-
periodUnit: request.periodUnit,
|
|
327
|
-
recipient: request.recipient,
|
|
328
|
-
reference: hashActivate,
|
|
329
|
-
subscriptionExpires: request.subscriptionExpires,
|
|
330
|
-
}),
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
amount: subscriptionAmount,
|
|
334
|
-
chainId,
|
|
335
|
-
currency: subscriptionCurrency,
|
|
336
|
-
periodCount: subscriptionPeriodCount,
|
|
337
|
-
periodUnit: subscriptionPeriodUnit,
|
|
338
|
-
recipient: subscriptionRecipient,
|
|
339
|
-
resolve: async ({ input }) => {
|
|
340
|
-
const key = input.headers.get('X-Subscription-Key')
|
|
341
|
-
return key ? { accessKey, key } : null
|
|
342
|
-
},
|
|
343
|
-
store,
|
|
344
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
348
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
349
|
-
new Request('https://example.com/resource', {
|
|
350
|
-
headers: { 'X-Subscription-Key': subscriptionKey },
|
|
351
|
-
}),
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
expect(challengeResult.status).toBe(402)
|
|
355
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
356
|
-
|
|
357
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
358
|
-
const challengeRequest = challenge.request as ReturnType<
|
|
359
|
-
typeof Methods.subscription.schema.request.parse
|
|
360
|
-
>
|
|
361
|
-
expect(challengeRequest.methodDetails?.accessKey).toEqual({
|
|
362
|
-
...accessKey,
|
|
363
|
-
accessKeyAddress: accessKey.accessKeyAddress.toLowerCase(),
|
|
364
|
-
})
|
|
365
|
-
const credential = await createCredential(challenge)
|
|
366
|
-
|
|
367
|
-
const activated = await mppx.tempo.subscription({})(
|
|
368
|
-
new Request('https://example.com/resource', {
|
|
369
|
-
headers: {
|
|
370
|
-
Authorization: Credential.serialize(credential),
|
|
371
|
-
'X-Subscription-Key': subscriptionKey,
|
|
372
|
-
},
|
|
373
|
-
}),
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
expect(activated.status).toBe(200)
|
|
377
|
-
expect(activationCount).toBe(1)
|
|
378
|
-
|
|
379
|
-
const replayed = await mppx.tempo.subscription({})(
|
|
380
|
-
new Request('https://example.com/resource', {
|
|
381
|
-
headers: {
|
|
382
|
-
Authorization: Credential.serialize(credential),
|
|
383
|
-
'X-Subscription-Key': subscriptionKey,
|
|
384
|
-
},
|
|
385
|
-
}),
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
expect(replayed.status).toBe(402)
|
|
389
|
-
expect(activationCount).toBe(1)
|
|
390
|
-
|
|
391
|
-
const reused = await mppx.tempo.subscription({})(
|
|
392
|
-
new Request('https://example.com/resource', {
|
|
393
|
-
headers: {
|
|
394
|
-
'X-Subscription-Key': subscriptionKey,
|
|
395
|
-
},
|
|
396
|
-
}),
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
expect(reused.status).toBe(200)
|
|
400
|
-
if (reused.status !== 200) throw new Error('expected authorize reuse')
|
|
401
|
-
|
|
402
|
-
const response = reused.withReceipt(new Response('OK'))
|
|
403
|
-
const receipt = response.headers.get('Payment-Receipt')
|
|
404
|
-
expect(receipt).toBeTruthy()
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
test('automatically creates an access key and submits the activation payment', async () => {
|
|
408
|
-
const store = Store.memory()
|
|
409
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
410
|
-
const { client, rpcMethods } = createBillingClient([hashActivate])
|
|
411
|
-
const method = subscription({
|
|
412
|
-
amount: subscriptionAmount,
|
|
413
|
-
chainId,
|
|
414
|
-
currency: subscriptionCurrency,
|
|
415
|
-
getClient: async () => client,
|
|
416
|
-
periodCount: subscriptionPeriodCount,
|
|
417
|
-
periodUnit: subscriptionPeriodUnit,
|
|
418
|
-
recipient: subscriptionRecipient,
|
|
419
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
420
|
-
store,
|
|
421
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
422
|
-
waitForConfirmation: false,
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
426
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
427
|
-
new Request('https://example.com/resource'),
|
|
428
|
-
)
|
|
429
|
-
expect(challengeResult.status).toBe(402)
|
|
430
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
431
|
-
|
|
432
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
433
|
-
const challengeRequest = challenge.request as ReturnType<
|
|
434
|
-
typeof Methods.subscription.schema.request.parse
|
|
435
|
-
>
|
|
436
|
-
const generatedAccessKey = challengeRequest.methodDetails?.accessKey
|
|
437
|
-
expect(generatedAccessKey?.keyType).toBe('secp256k1')
|
|
438
|
-
if (!generatedAccessKey) throw new Error('expected generated access key')
|
|
439
|
-
|
|
440
|
-
const credential = await createCredential(challenge, rootAccount.address, generatedAccessKey)
|
|
441
|
-
const activated = await mppx.tempo.subscription({})(
|
|
442
|
-
new Request('https://example.com/resource', {
|
|
443
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
444
|
-
}),
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
expect(activated.status).toBe(200)
|
|
448
|
-
if (activated.status !== 200) throw new Error('expected activation')
|
|
449
|
-
const receipt = Receipt.fromResponse(activated.withReceipt(new Response('OK')))
|
|
450
|
-
expect(receipt.reference).toBe(hashActivate)
|
|
451
|
-
expect(receipt.subscriptionId).toMatch(/^[A-Za-z0-9_-]+$/)
|
|
452
|
-
expect(rpcMethods.filter((method) => method === 'eth_sendRawTransaction')).toHaveLength(1)
|
|
453
|
-
|
|
454
|
-
const record = await subscriptions.getByKey(subscriptionKey)
|
|
455
|
-
expect(record?.accessKey).toEqual(generatedAccessKey)
|
|
456
|
-
expect(record?.keyAuthorization).toBe(credential.payload.signature)
|
|
457
|
-
expect(record?.payer?.address.toLowerCase()).toBe(rootAccount.address.toLowerCase())
|
|
458
|
-
expect(record?.lastChargedPeriod).toBe(0)
|
|
459
|
-
|
|
460
|
-
const reused = await mppx.tempo.subscription({})(new Request('https://example.com/resource'))
|
|
461
|
-
expect(reused.status).toBe(200)
|
|
462
|
-
expect(rpcMethods.filter((method) => method === 'eth_sendRawTransaction')).toHaveLength(1)
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
test('requires a fresh credential for active subscription reuse when configured', async () => {
|
|
466
|
-
const store = Store.memory()
|
|
467
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
468
|
-
await subscriptions.put(
|
|
469
|
-
createRecord({
|
|
470
|
-
accessKey,
|
|
471
|
-
payer: { address: rootAccount.address, chainId },
|
|
472
|
-
}),
|
|
473
|
-
)
|
|
474
|
-
const method = subscription({
|
|
475
|
-
activate: async () => {
|
|
476
|
-
throw new Error('active subscription reuse should not activate')
|
|
477
|
-
},
|
|
478
|
-
amount: subscriptionAmount,
|
|
479
|
-
chainId,
|
|
480
|
-
currency: subscriptionCurrency,
|
|
481
|
-
periodCount: subscriptionPeriodCount,
|
|
482
|
-
periodUnit: subscriptionPeriodUnit,
|
|
483
|
-
recipient: subscriptionRecipient,
|
|
484
|
-
requireCredential: true,
|
|
485
|
-
resolve: async () => ({ accessKey, key: subscriptionKey }),
|
|
486
|
-
store,
|
|
487
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
488
|
-
})
|
|
489
|
-
|
|
490
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
491
|
-
const unauthenticated = await mppx.tempo.subscription({})(
|
|
492
|
-
new Request('https://example.com/resource'),
|
|
493
|
-
)
|
|
494
|
-
|
|
495
|
-
expect(unauthenticated.status).toBe(402)
|
|
496
|
-
if (unauthenticated.status !== 402) throw new Error('expected reuse challenge')
|
|
497
|
-
|
|
498
|
-
const challenge = Challenge.fromResponse(unauthenticated.challenge)
|
|
499
|
-
const credential = await createCredential(challenge)
|
|
500
|
-
const reused = await mppx.tempo.subscription({})(
|
|
501
|
-
new Request('https://example.com/resource', {
|
|
502
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
503
|
-
}),
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
expect(reused.status).toBe(200)
|
|
507
|
-
})
|
|
508
|
-
|
|
509
|
-
test('can derive the subscription lookup key only from the signed credential source', async () => {
|
|
510
|
-
const store = Store.memory()
|
|
511
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
512
|
-
const { client, rpcMethods } = createBillingClient([hashActivate])
|
|
513
|
-
const sourceKey = (source: { address: string; chainId: number }) =>
|
|
514
|
-
`payer:${source.chainId}:${source.address.toLowerCase()}:pro`
|
|
515
|
-
const method = subscription({
|
|
516
|
-
amount: subscriptionAmount,
|
|
517
|
-
chainId,
|
|
518
|
-
currency: subscriptionCurrency,
|
|
519
|
-
getClient: async () => client,
|
|
520
|
-
periodCount: subscriptionPeriodCount,
|
|
521
|
-
periodUnit: subscriptionPeriodUnit,
|
|
522
|
-
recipient: subscriptionRecipient,
|
|
523
|
-
requireCredential: true,
|
|
524
|
-
resolve: async ({ source }) => {
|
|
525
|
-
if (!source) return null
|
|
526
|
-
return { key: sourceKey(source) }
|
|
527
|
-
},
|
|
528
|
-
store,
|
|
529
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
530
|
-
waitForConfirmation: false,
|
|
531
|
-
})
|
|
532
|
-
|
|
533
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
534
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
535
|
-
new Request('https://example.com/resource'),
|
|
536
|
-
)
|
|
537
|
-
|
|
538
|
-
expect(challengeResult.status).toBe(402)
|
|
539
|
-
if (challengeResult.status !== 402) throw new Error('expected source challenge')
|
|
540
|
-
|
|
541
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
542
|
-
const challengeAccessKey = (
|
|
543
|
-
challenge.request as ReturnType<typeof Methods.subscription.schema.request.parse>
|
|
544
|
-
).methodDetails?.accessKey
|
|
545
|
-
if (!challengeAccessKey) throw new Error('expected challenge access key')
|
|
546
|
-
|
|
547
|
-
const credential = await createCredential(challenge, rootAccount.address, challengeAccessKey)
|
|
548
|
-
const activated = await mppx.tempo.subscription({})(
|
|
549
|
-
new Request('https://example.com/resource', {
|
|
550
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
551
|
-
}),
|
|
552
|
-
)
|
|
553
|
-
|
|
554
|
-
expect(activated.status).toBe(200)
|
|
555
|
-
const lookupKey = sourceKey({ address: rootAccount.address, chainId })
|
|
556
|
-
const record = await subscriptions.getByKey(lookupKey)
|
|
557
|
-
expect(record?.lookupKey).toBe(lookupKey)
|
|
558
|
-
expect(record?.payer?.address.toLowerCase()).toBe(rootAccount.address.toLowerCase())
|
|
559
|
-
expect(record?.accessKey).toEqual(challengeAccessKey)
|
|
560
|
-
expect(rpcMethods.filter((method) => method === 'eth_sendRawTransaction')).toHaveLength(1)
|
|
561
|
-
})
|
|
562
|
-
|
|
563
|
-
test('rejects credential reuse when the signer does not match the stored payer', async () => {
|
|
564
|
-
const store = Store.memory()
|
|
565
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
566
|
-
await subscriptions.put(
|
|
567
|
-
createRecord({
|
|
568
|
-
accessKey,
|
|
569
|
-
payer: { address: rootAccount.address, chainId },
|
|
570
|
-
}),
|
|
571
|
-
)
|
|
572
|
-
const method = subscription({
|
|
573
|
-
activate: async () => {
|
|
574
|
-
throw new Error('active subscription reuse should not activate')
|
|
575
|
-
},
|
|
576
|
-
amount: subscriptionAmount,
|
|
577
|
-
chainId,
|
|
578
|
-
currency: subscriptionCurrency,
|
|
579
|
-
periodCount: subscriptionPeriodCount,
|
|
580
|
-
periodUnit: subscriptionPeriodUnit,
|
|
581
|
-
recipient: subscriptionRecipient,
|
|
582
|
-
requireCredential: true,
|
|
583
|
-
resolve: async () => ({ accessKey, key: subscriptionKey }),
|
|
584
|
-
store,
|
|
585
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
586
|
-
})
|
|
587
|
-
|
|
588
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
589
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
590
|
-
new Request('https://example.com/resource'),
|
|
591
|
-
)
|
|
592
|
-
if (challengeResult.status !== 402) throw new Error('expected reuse challenge')
|
|
593
|
-
|
|
594
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
595
|
-
const credential = await createCredential(
|
|
596
|
-
challenge,
|
|
597
|
-
otherRootAccount.address,
|
|
598
|
-
accessKey,
|
|
599
|
-
otherRootAccount,
|
|
600
|
-
)
|
|
601
|
-
const rejected = await mppx.tempo.subscription({})(
|
|
602
|
-
new Request('https://example.com/resource', {
|
|
603
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
604
|
-
}),
|
|
605
|
-
)
|
|
606
|
-
|
|
607
|
-
expect(rejected.status).toBe(402)
|
|
608
|
-
if (rejected.status !== 402) throw new Error('expected payer mismatch challenge')
|
|
609
|
-
const body = await rejected.challenge.json()
|
|
610
|
-
expect(body.detail).toBe('Payment verification failed: subscription payer mismatch.')
|
|
611
|
-
})
|
|
612
|
-
|
|
613
|
-
test('renews an overdue active subscription after credential-required payer proof', async () => {
|
|
614
|
-
const store = Store.memory()
|
|
615
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
616
|
-
await subscriptions.put(
|
|
617
|
-
createRecord({
|
|
618
|
-
accessKey,
|
|
619
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
620
|
-
lastChargedPeriod: 0,
|
|
621
|
-
payer: { address: rootAccount.address, chainId },
|
|
622
|
-
reference: hashStale,
|
|
623
|
-
}),
|
|
624
|
-
)
|
|
625
|
-
const method = subscription({
|
|
626
|
-
activate: async () => {
|
|
627
|
-
throw new Error('active subscription renewal should not activate')
|
|
628
|
-
},
|
|
629
|
-
amount: subscriptionAmount,
|
|
630
|
-
chainId,
|
|
631
|
-
currency: subscriptionCurrency,
|
|
632
|
-
periodCount: subscriptionPeriodCount,
|
|
633
|
-
periodUnit: subscriptionPeriodUnit,
|
|
634
|
-
recipient: subscriptionRecipient,
|
|
635
|
-
renew: async ({ periodIndex, subscription }) => ({
|
|
636
|
-
receipt: createReceipt(subscription.subscriptionId, hashRenewed),
|
|
637
|
-
subscription: {
|
|
638
|
-
...subscription,
|
|
639
|
-
lastChargedPeriod: periodIndex,
|
|
640
|
-
reference: hashRenewed,
|
|
641
|
-
},
|
|
642
|
-
}),
|
|
643
|
-
requireCredential: true,
|
|
644
|
-
resolve: async () => ({ accessKey, key: subscriptionKey }),
|
|
645
|
-
store,
|
|
646
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
647
|
-
})
|
|
648
|
-
|
|
649
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
650
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
651
|
-
new Request('https://example.com/resource'),
|
|
652
|
-
)
|
|
653
|
-
if (challengeResult.status !== 402) throw new Error('expected reuse challenge')
|
|
654
|
-
|
|
655
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
656
|
-
const credential = await createCredential(challenge)
|
|
657
|
-
const renewed = await mppx.tempo.subscription({})(
|
|
658
|
-
new Request('https://example.com/resource', {
|
|
659
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
660
|
-
}),
|
|
661
|
-
)
|
|
662
|
-
|
|
663
|
-
expect(renewed.status).toBe(200)
|
|
664
|
-
if (renewed.status !== 200) throw new Error('expected credential renewal')
|
|
665
|
-
const receipt = Receipt.fromResponse(renewed.withReceipt(new Response('OK')))
|
|
666
|
-
expect(receipt.reference).toBe(hashRenewed)
|
|
667
|
-
expect((await subscriptions.getByKey(subscriptionKey))?.lastChargedPeriod).toBeGreaterThan(0)
|
|
668
|
-
})
|
|
669
|
-
|
|
670
|
-
test('verifyCredential activates a subscription credential with a canonical challenge request', async () => {
|
|
671
|
-
const store = Store.memory()
|
|
672
|
-
const { client } = createBillingClient([hashActivate])
|
|
673
|
-
const method = subscription({
|
|
674
|
-
amount: subscriptionAmount,
|
|
675
|
-
chainId,
|
|
676
|
-
currency: subscriptionCurrency,
|
|
677
|
-
getClient: async () => client,
|
|
678
|
-
periodCount: subscriptionPeriodCount,
|
|
679
|
-
periodUnit: subscriptionPeriodUnit,
|
|
680
|
-
recipient: subscriptionRecipient,
|
|
681
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
682
|
-
store,
|
|
683
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
684
|
-
waitForConfirmation: false,
|
|
685
|
-
})
|
|
686
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
687
|
-
const challenge = await mppx.challenge.tempo.subscription({})
|
|
688
|
-
const generatedAccessKey = (
|
|
689
|
-
challenge.request as ReturnType<typeof Methods.subscription.schema.request.parse>
|
|
690
|
-
).methodDetails?.accessKey
|
|
691
|
-
if (!generatedAccessKey) throw new Error('expected generated access key')
|
|
692
|
-
const credential = await createCredential(challenge, rootAccount.address, generatedAccessKey)
|
|
693
|
-
|
|
694
|
-
const receipt = await mppx.verifyCredential(credential)
|
|
695
|
-
|
|
696
|
-
expect(receipt.status).toBe('success')
|
|
697
|
-
expect(receipt.reference).toBe(hashActivate)
|
|
698
|
-
})
|
|
699
|
-
|
|
700
|
-
test('automatically renews overdue subscriptions on the request path', async () => {
|
|
701
|
-
const store = Store.memory()
|
|
702
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
703
|
-
const { client, rpcMethods } = createBillingClient([hashActivate, hashRenewed])
|
|
704
|
-
const method = subscription({
|
|
705
|
-
amount: subscriptionAmount,
|
|
706
|
-
chainId,
|
|
707
|
-
currency: subscriptionCurrency,
|
|
708
|
-
getClient: async () => client,
|
|
709
|
-
periodCount: subscriptionPeriodCount,
|
|
710
|
-
periodUnit: subscriptionPeriodUnit,
|
|
711
|
-
recipient: subscriptionRecipient,
|
|
712
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
713
|
-
store,
|
|
714
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
715
|
-
waitForConfirmation: false,
|
|
716
|
-
})
|
|
717
|
-
|
|
718
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
719
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
720
|
-
new Request('https://example.com/resource'),
|
|
721
|
-
)
|
|
722
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
723
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
724
|
-
const accessKey = (
|
|
725
|
-
challenge.request as ReturnType<typeof Methods.subscription.schema.request.parse>
|
|
726
|
-
).methodDetails?.accessKey
|
|
727
|
-
if (!accessKey) throw new Error('expected generated access key')
|
|
728
|
-
const credential = await createCredential(challenge, rootAccount.address, accessKey)
|
|
729
|
-
const activated = await mppx.tempo.subscription({})(
|
|
730
|
-
new Request('https://example.com/resource', {
|
|
731
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
732
|
-
}),
|
|
733
|
-
)
|
|
734
|
-
expect(activated.status).toBe(200)
|
|
735
|
-
|
|
736
|
-
const record = await subscriptions.getByKey(subscriptionKey)
|
|
737
|
-
if (!record) throw new Error('expected subscription record')
|
|
738
|
-
await subscriptions.put({
|
|
739
|
-
...record,
|
|
740
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
741
|
-
keyAuthorization: undefined,
|
|
742
|
-
lastChargedPeriod: 0,
|
|
743
|
-
reference: hashStale,
|
|
744
|
-
})
|
|
745
|
-
|
|
746
|
-
const renewed = await mppx.tempo.subscription({})(new Request('https://example.com/resource'))
|
|
747
|
-
expect(renewed.status).toBe(200)
|
|
748
|
-
if (renewed.status !== 200) throw new Error('expected renewal')
|
|
749
|
-
|
|
750
|
-
const receipt = Receipt.fromResponse(renewed.withReceipt(new Response('OK')))
|
|
751
|
-
expect(receipt.reference).toBe(hashRenewed)
|
|
752
|
-
expect(rpcMethods.filter((method) => method === 'eth_sendRawTransaction')).toHaveLength(2)
|
|
753
|
-
expect((await subscriptions.get(record.subscriptionId))?.lastChargedPeriod).toBeGreaterThan(0)
|
|
754
|
-
})
|
|
755
|
-
|
|
756
|
-
async function activateConfirmedSubscription(parameters: {
|
|
757
|
-
client: ReturnType<typeof createConfirmingBillingClient>['client']
|
|
758
|
-
store: ReturnType<typeof Store.memory>
|
|
759
|
-
}) {
|
|
760
|
-
const { client, store } = parameters
|
|
761
|
-
const method = subscription({
|
|
762
|
-
amount: subscriptionAmount,
|
|
763
|
-
chainId,
|
|
764
|
-
currency: subscriptionCurrency,
|
|
765
|
-
getClient: async () => client,
|
|
766
|
-
periodCount: subscriptionPeriodCount,
|
|
767
|
-
periodUnit: subscriptionPeriodUnit,
|
|
768
|
-
recipient: subscriptionRecipient,
|
|
769
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
770
|
-
store,
|
|
771
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
772
|
-
})
|
|
773
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
774
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
775
|
-
new Request('https://example.com/resource'),
|
|
776
|
-
)
|
|
777
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
778
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
779
|
-
const accessKey = (
|
|
780
|
-
challenge.request as ReturnType<typeof Methods.subscription.schema.request.parse>
|
|
781
|
-
).methodDetails?.accessKey
|
|
782
|
-
if (!accessKey) throw new Error('expected generated access key')
|
|
783
|
-
const credential = await createCredential(challenge, rootAccount.address, accessKey)
|
|
784
|
-
const activated = await mppx.tempo.subscription({})(
|
|
785
|
-
new Request('https://example.com/resource', {
|
|
786
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
787
|
-
}),
|
|
788
|
-
)
|
|
789
|
-
expect(activated.status).toBe(200)
|
|
790
|
-
return { mppx }
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
test('renews when the confirmed receipt credits the recipient', async () => {
|
|
794
|
-
const store = Store.memory()
|
|
795
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
796
|
-
const { client } = createConfirmingBillingClient()
|
|
797
|
-
const { mppx } = await activateConfirmedSubscription({ client, store })
|
|
798
|
-
|
|
799
|
-
const record = await subscriptions.getByKey(subscriptionKey)
|
|
800
|
-
if (!record) throw new Error('expected subscription record')
|
|
801
|
-
await subscriptions.put({
|
|
802
|
-
...record,
|
|
803
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
804
|
-
lastChargedPeriod: 0,
|
|
805
|
-
})
|
|
806
|
-
|
|
807
|
-
const renewed = await mppx.tempo.subscription({})(new Request('https://example.com/resource'))
|
|
808
|
-
expect(renewed.status).toBe(200)
|
|
809
|
-
expect((await subscriptions.get(record.subscriptionId))?.lastChargedPeriod).toBeGreaterThan(0)
|
|
810
|
-
})
|
|
811
|
-
|
|
812
|
-
test('rejects renewal when a receive policy holds the confirmed transfer', async () => {
|
|
813
|
-
const store = Store.memory()
|
|
814
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
815
|
-
const { client } = createConfirmingBillingClient({ redirectTo: receivePolicyGuard })
|
|
816
|
-
const { mppx } = await activateConfirmedSubscription({ client, store })
|
|
817
|
-
|
|
818
|
-
const record = await subscriptions.getByKey(subscriptionKey)
|
|
819
|
-
if (!record) throw new Error('expected subscription record')
|
|
820
|
-
await subscriptions.put({
|
|
821
|
-
...record,
|
|
822
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
823
|
-
lastChargedPeriod: 0,
|
|
824
|
-
})
|
|
825
|
-
|
|
826
|
-
const renewed = await mppx.tempo.subscription({})(new Request('https://example.com/resource'))
|
|
827
|
-
expect(renewed.status).toBe(402)
|
|
828
|
-
// The held renewal must not advance the billing period.
|
|
829
|
-
expect((await subscriptions.get(record.subscriptionId))?.lastChargedPeriod).toBe(0)
|
|
830
|
-
})
|
|
831
|
-
|
|
832
|
-
test('rejects a held renewal even when a memo-less Transfer credits the recipient', async () => {
|
|
833
|
-
const store = Store.memory()
|
|
834
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
835
|
-
// Settlement is held, but the receipt also has a memo-less Transfer to the
|
|
836
|
-
// recipient; the memo-bound check must still reject it.
|
|
837
|
-
const { client } = createConfirmingBillingClient({
|
|
838
|
-
addUnrelatedTransfer: true,
|
|
839
|
-
redirectTo: receivePolicyGuard,
|
|
840
|
-
})
|
|
841
|
-
const { mppx } = await activateConfirmedSubscription({ client, store })
|
|
842
|
-
|
|
843
|
-
const record = await subscriptions.getByKey(subscriptionKey)
|
|
844
|
-
if (!record) throw new Error('expected subscription record')
|
|
845
|
-
await subscriptions.put({
|
|
846
|
-
...record,
|
|
847
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
848
|
-
lastChargedPeriod: 0,
|
|
849
|
-
})
|
|
850
|
-
|
|
851
|
-
const renewed = await mppx.tempo.subscription({})(new Request('https://example.com/resource'))
|
|
852
|
-
expect(renewed.status).toBe(402)
|
|
853
|
-
expect((await subscriptions.get(record.subscriptionId))?.lastChargedPeriod).toBe(0)
|
|
854
|
-
})
|
|
855
|
-
|
|
856
|
-
test('rejects activation when a receive policy holds the confirmed transfer', async () => {
|
|
857
|
-
const store = Store.memory()
|
|
858
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
859
|
-
const { client } = createConfirmingBillingClient({
|
|
860
|
-
redirectActivation: true,
|
|
861
|
-
redirectTo: receivePolicyGuard,
|
|
862
|
-
})
|
|
863
|
-
const method = subscription({
|
|
864
|
-
amount: subscriptionAmount,
|
|
865
|
-
chainId,
|
|
866
|
-
currency: subscriptionCurrency,
|
|
867
|
-
getClient: async () => client,
|
|
868
|
-
periodCount: subscriptionPeriodCount,
|
|
869
|
-
periodUnit: subscriptionPeriodUnit,
|
|
870
|
-
recipient: subscriptionRecipient,
|
|
871
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
872
|
-
store,
|
|
873
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
874
|
-
})
|
|
875
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
876
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
877
|
-
new Request('https://example.com/resource'),
|
|
878
|
-
)
|
|
879
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
880
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
881
|
-
const generatedAccessKey = (
|
|
882
|
-
challenge.request as ReturnType<typeof Methods.subscription.schema.request.parse>
|
|
883
|
-
).methodDetails?.accessKey
|
|
884
|
-
if (!generatedAccessKey) throw new Error('expected generated access key')
|
|
885
|
-
const credential = await createCredential(challenge, rootAccount.address, generatedAccessKey)
|
|
886
|
-
const activated = await mppx.tempo.subscription({})(
|
|
887
|
-
new Request('https://example.com/resource', {
|
|
888
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
889
|
-
}),
|
|
890
|
-
)
|
|
891
|
-
|
|
892
|
-
expect(activated.status).toBe(402)
|
|
893
|
-
// A held activation must not persist an active subscription.
|
|
894
|
-
expect(await subscriptions.getByKey(subscriptionKey)).toBeNull()
|
|
895
|
-
})
|
|
896
|
-
|
|
897
|
-
test('returns a management response while renewal is already in flight', async () => {
|
|
898
|
-
const store = Store.memory()
|
|
899
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
900
|
-
const method = subscription({
|
|
901
|
-
accessKey: async () => accessKey,
|
|
902
|
-
activate: async () => ({
|
|
903
|
-
receipt: createReceipt('unused'),
|
|
904
|
-
subscription: createRecord({ subscriptionId: 'unused' }),
|
|
905
|
-
}),
|
|
906
|
-
amount: subscriptionAmount,
|
|
907
|
-
chainId,
|
|
908
|
-
currency: subscriptionCurrency,
|
|
909
|
-
periodCount: subscriptionPeriodCount,
|
|
910
|
-
periodUnit: subscriptionPeriodUnit,
|
|
911
|
-
recipient: subscriptionRecipient,
|
|
912
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
913
|
-
renew: async () => {
|
|
914
|
-
throw new Error('renew should not run')
|
|
915
|
-
},
|
|
916
|
-
store,
|
|
917
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
918
|
-
})
|
|
919
|
-
await subscriptions.put(
|
|
920
|
-
createRecord({
|
|
921
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
922
|
-
inFlightPeriod: 1,
|
|
923
|
-
inFlightStartedAt: new Date().toISOString(),
|
|
924
|
-
lastChargedPeriod: 0,
|
|
925
|
-
subscriptionId: 'sub_due',
|
|
926
|
-
}),
|
|
927
|
-
)
|
|
928
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
929
|
-
|
|
930
|
-
const result = await mppx.tempo.subscription({})(new Request('https://example.com/resource'))
|
|
931
|
-
|
|
932
|
-
expect(result.status).toBe(200)
|
|
933
|
-
if (result.status !== 200) throw new Error('expected management response')
|
|
934
|
-
const response = (result.withReceipt as () => Response)()
|
|
935
|
-
expect(response.status).toBe(409)
|
|
936
|
-
expect(response.headers.get('Retry-After')).toBe('1')
|
|
937
|
-
})
|
|
938
|
-
|
|
939
|
-
test('does not authorize an active subscription whose request binding differs', async () => {
|
|
940
|
-
const store = Store.memory()
|
|
941
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
942
|
-
await subscriptions.put(
|
|
943
|
-
createRecord({
|
|
944
|
-
accessKey,
|
|
945
|
-
amount: '1000000',
|
|
946
|
-
lookupKey: subscriptionKey,
|
|
947
|
-
subscriptionId: 'sub_basic',
|
|
948
|
-
}),
|
|
949
|
-
)
|
|
950
|
-
const method = subscription({
|
|
951
|
-
accessKey: async () => accessKey,
|
|
952
|
-
activate: async () => ({
|
|
953
|
-
receipt: createReceipt('sub_unused'),
|
|
954
|
-
subscription: createRecord({ subscriptionId: 'sub_unused' }),
|
|
955
|
-
}),
|
|
956
|
-
amount: subscriptionAmount,
|
|
957
|
-
chainId,
|
|
958
|
-
currency: subscriptionCurrency,
|
|
959
|
-
periodCount: subscriptionPeriodCount,
|
|
960
|
-
periodUnit: subscriptionPeriodUnit,
|
|
961
|
-
recipient: subscriptionRecipient,
|
|
962
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
963
|
-
store,
|
|
964
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
965
|
-
})
|
|
966
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
967
|
-
|
|
968
|
-
const result = await mppx.tempo.subscription({})(new Request('https://example.com/resource'))
|
|
969
|
-
|
|
970
|
-
expect(result.status).toBe(402)
|
|
971
|
-
})
|
|
972
|
-
|
|
973
|
-
test('requires an access key before issuing a subscription challenge', async () => {
|
|
974
|
-
const method = subscription({
|
|
975
|
-
activate: async () => ({
|
|
976
|
-
receipt: createReceipt('unused'),
|
|
977
|
-
subscription: createRecord({ subscriptionId: 'unused' }),
|
|
978
|
-
}),
|
|
979
|
-
amount: subscriptionAmount,
|
|
980
|
-
chainId,
|
|
981
|
-
currency: subscriptionCurrency,
|
|
982
|
-
periodCount: subscriptionPeriodCount,
|
|
983
|
-
periodUnit: subscriptionPeriodUnit,
|
|
984
|
-
recipient: subscriptionRecipient,
|
|
985
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
986
|
-
store: Store.memory(),
|
|
987
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
988
|
-
})
|
|
989
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
990
|
-
|
|
991
|
-
const result = await mppx.tempo.subscription({})(new Request('https://example.com/resource'))
|
|
992
|
-
expect(result.status).toBe(402)
|
|
993
|
-
if (result.status !== 402) throw new Error('expected challenge')
|
|
994
|
-
const body = (await result.challenge.json()) as { detail?: string }
|
|
995
|
-
expect(body.detail).toBe('Payment verification failed: subscription accessKey is missing.')
|
|
996
|
-
})
|
|
997
|
-
|
|
998
|
-
test('defaults omitted subscription chainId to Tempo testnet', async () => {
|
|
999
|
-
const method = subscription({
|
|
1000
|
-
accessKey: async () => accessKey,
|
|
1001
|
-
activate: async () => ({
|
|
1002
|
-
receipt: createReceipt('unused'),
|
|
1003
|
-
subscription: createRecord({ subscriptionId: 'unused' }),
|
|
1004
|
-
}),
|
|
1005
|
-
amount: subscriptionAmount,
|
|
1006
|
-
currency: subscriptionCurrency,
|
|
1007
|
-
periodCount: subscriptionPeriodCount,
|
|
1008
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1009
|
-
recipient: subscriptionRecipient,
|
|
1010
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1011
|
-
store: Store.memory(),
|
|
1012
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1013
|
-
})
|
|
1014
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1015
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1016
|
-
new Request('https://example.com/resource'),
|
|
1017
|
-
)
|
|
1018
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1019
|
-
|
|
1020
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
1021
|
-
const challengeRequest = challenge.request as ReturnType<
|
|
1022
|
-
typeof Methods.subscription.schema.request.parse
|
|
1023
|
-
>
|
|
1024
|
-
expect(challengeRequest.methodDetails?.chainId).toBe(subscriptionDefaultChainId)
|
|
1025
|
-
})
|
|
1026
|
-
|
|
1027
|
-
test('reuses a stored active subscription access key without a resolver callback', async () => {
|
|
1028
|
-
const store = Store.memory()
|
|
1029
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1030
|
-
await subscriptions.put(createRecord({ accessKey, lookupKey: subscriptionKey }))
|
|
1031
|
-
const method = subscription({
|
|
1032
|
-
activate: async () => ({
|
|
1033
|
-
receipt: createReceipt('unused'),
|
|
1034
|
-
subscription: createRecord({ subscriptionId: 'unused' }),
|
|
1035
|
-
}),
|
|
1036
|
-
amount: subscriptionAmount,
|
|
1037
|
-
chainId,
|
|
1038
|
-
currency: subscriptionCurrency,
|
|
1039
|
-
periodCount: subscriptionPeriodCount,
|
|
1040
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1041
|
-
recipient: subscriptionRecipient,
|
|
1042
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1043
|
-
store,
|
|
1044
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1045
|
-
})
|
|
1046
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1047
|
-
|
|
1048
|
-
const reused = await mppx.tempo.subscription({})(new Request('https://example.com/resource'))
|
|
1049
|
-
|
|
1050
|
-
expect(reused.status).toBe(200)
|
|
1051
|
-
})
|
|
1052
|
-
|
|
1053
|
-
test('serializes concurrent fresh activations for the same lookup key', async () => {
|
|
1054
|
-
const store = Store.memory()
|
|
1055
|
-
let activationCount = 0
|
|
1056
|
-
let releaseActivation!: () => void
|
|
1057
|
-
let markActivationStarted!: () => void
|
|
1058
|
-
const activationStarted = new Promise<void>((resolve) => {
|
|
1059
|
-
markActivationStarted = resolve
|
|
1060
|
-
})
|
|
1061
|
-
const activationReleased = new Promise<void>((resolve) => {
|
|
1062
|
-
releaseActivation = resolve
|
|
1063
|
-
})
|
|
1064
|
-
const method = subscription({
|
|
1065
|
-
accessKey: async () => accessKey,
|
|
1066
|
-
activate: async ({ request, resolved }) => {
|
|
1067
|
-
activationCount += 1
|
|
1068
|
-
markActivationStarted()
|
|
1069
|
-
await activationReleased
|
|
1070
|
-
return {
|
|
1071
|
-
receipt: createReceipt('sub_123', hashActivate),
|
|
1072
|
-
subscription: createRecord({
|
|
1073
|
-
amount: request.amount,
|
|
1074
|
-
chainId: request.methodDetails?.chainId,
|
|
1075
|
-
currency: request.currency,
|
|
1076
|
-
lookupKey: resolved.key,
|
|
1077
|
-
periodCount: request.periodCount,
|
|
1078
|
-
periodUnit: request.periodUnit,
|
|
1079
|
-
recipient: request.recipient,
|
|
1080
|
-
reference: hashActivate,
|
|
1081
|
-
subscriptionExpires: request.subscriptionExpires,
|
|
1082
|
-
}),
|
|
1083
|
-
}
|
|
1084
|
-
},
|
|
1085
|
-
amount: subscriptionAmount,
|
|
1086
|
-
chainId,
|
|
1087
|
-
currency: subscriptionCurrency,
|
|
1088
|
-
periodCount: subscriptionPeriodCount,
|
|
1089
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1090
|
-
recipient: subscriptionRecipient,
|
|
1091
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1092
|
-
store,
|
|
1093
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1094
|
-
})
|
|
1095
|
-
|
|
1096
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1097
|
-
const firstChallengeResult = await mppx.tempo.subscription({
|
|
1098
|
-
expires: '2027-01-01T00:01:00.000Z',
|
|
1099
|
-
})(new Request('https://example.com/resource'))
|
|
1100
|
-
const secondChallengeResult = await mppx.tempo.subscription({
|
|
1101
|
-
expires: '2027-01-01T00:02:00.000Z',
|
|
1102
|
-
})(new Request('https://example.com/resource'))
|
|
1103
|
-
if (firstChallengeResult.status !== 402 || secondChallengeResult.status !== 402) {
|
|
1104
|
-
throw new Error('expected activation challenges')
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
const firstChallenge = Challenge.fromResponse(firstChallengeResult.challenge)
|
|
1108
|
-
const secondChallenge = Challenge.fromResponse(secondChallengeResult.challenge)
|
|
1109
|
-
expect(firstChallenge.id).not.toBe(secondChallenge.id)
|
|
1110
|
-
|
|
1111
|
-
const firstCredential = await createCredential(firstChallenge)
|
|
1112
|
-
const secondCredential = await createCredential(secondChallenge)
|
|
1113
|
-
const firstActivation = mppx.tempo.subscription({})(
|
|
1114
|
-
new Request('https://example.com/resource', {
|
|
1115
|
-
headers: { Authorization: Credential.serialize(firstCredential) },
|
|
1116
|
-
}),
|
|
1117
|
-
)
|
|
1118
|
-
await activationStarted
|
|
1119
|
-
|
|
1120
|
-
const secondActivation = await mppx.tempo.subscription({})(
|
|
1121
|
-
new Request('https://example.com/resource', {
|
|
1122
|
-
headers: { Authorization: Credential.serialize(secondCredential) },
|
|
1123
|
-
}),
|
|
1124
|
-
)
|
|
1125
|
-
releaseActivation()
|
|
1126
|
-
const activated = await firstActivation
|
|
1127
|
-
|
|
1128
|
-
expect(activated.status).toBe(200)
|
|
1129
|
-
expect(secondActivation.status).toBe(402)
|
|
1130
|
-
expect(activationCount).toBe(1)
|
|
1131
|
-
})
|
|
1132
|
-
|
|
1133
|
-
test('allows retry after a stale failed activation attempt', async () => {
|
|
1134
|
-
const store = Store.memory()
|
|
1135
|
-
let activationCount = 0
|
|
1136
|
-
const method = subscription({
|
|
1137
|
-
accessKey: async () => accessKey,
|
|
1138
|
-
activationTimeoutMs: 0,
|
|
1139
|
-
activate: async ({ request, resolved }) => {
|
|
1140
|
-
activationCount += 1
|
|
1141
|
-
if (activationCount === 1) throw new Error('activation failed before charge')
|
|
1142
|
-
return {
|
|
1143
|
-
receipt: createReceipt('sub_123', hashActivate),
|
|
1144
|
-
subscription: createRecord({
|
|
1145
|
-
amount: request.amount,
|
|
1146
|
-
chainId: request.methodDetails?.chainId,
|
|
1147
|
-
currency: request.currency,
|
|
1148
|
-
lookupKey: resolved.key,
|
|
1149
|
-
periodCount: request.periodCount,
|
|
1150
|
-
periodUnit: request.periodUnit,
|
|
1151
|
-
recipient: request.recipient,
|
|
1152
|
-
reference: hashActivate,
|
|
1153
|
-
subscriptionExpires: request.subscriptionExpires,
|
|
1154
|
-
}),
|
|
1155
|
-
}
|
|
1156
|
-
},
|
|
1157
|
-
amount: subscriptionAmount,
|
|
1158
|
-
chainId,
|
|
1159
|
-
currency: subscriptionCurrency,
|
|
1160
|
-
periodCount: subscriptionPeriodCount,
|
|
1161
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1162
|
-
recipient: subscriptionRecipient,
|
|
1163
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1164
|
-
store,
|
|
1165
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1166
|
-
})
|
|
1167
|
-
|
|
1168
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1169
|
-
const firstChallengeResult = await mppx.tempo.subscription({
|
|
1170
|
-
expires: '2027-01-01T00:03:00.000Z',
|
|
1171
|
-
})(new Request('https://example.com/resource'))
|
|
1172
|
-
const secondChallengeResult = await mppx.tempo.subscription({
|
|
1173
|
-
expires: '2027-01-01T00:04:00.000Z',
|
|
1174
|
-
})(new Request('https://example.com/resource'))
|
|
1175
|
-
if (firstChallengeResult.status !== 402 || secondChallengeResult.status !== 402) {
|
|
1176
|
-
throw new Error('expected activation challenges')
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
const firstCredential = await createCredential(
|
|
1180
|
-
Challenge.fromResponse(firstChallengeResult.challenge),
|
|
1181
|
-
)
|
|
1182
|
-
const firstRejected = await mppx.tempo.subscription({})(
|
|
1183
|
-
new Request('https://example.com/resource', {
|
|
1184
|
-
headers: { Authorization: Credential.serialize(firstCredential) },
|
|
1185
|
-
}),
|
|
1186
|
-
)
|
|
1187
|
-
expect(firstRejected.status).toBe(402)
|
|
1188
|
-
|
|
1189
|
-
const secondCredential = await createCredential(
|
|
1190
|
-
Challenge.fromResponse(secondChallengeResult.challenge),
|
|
1191
|
-
)
|
|
1192
|
-
const retried = await mppx.tempo.subscription({})(
|
|
1193
|
-
new Request('https://example.com/resource', {
|
|
1194
|
-
headers: { Authorization: Credential.serialize(secondCredential) },
|
|
1195
|
-
}),
|
|
1196
|
-
)
|
|
1197
|
-
|
|
1198
|
-
expect(retried.status).toBe(200)
|
|
1199
|
-
expect(activationCount).toBe(2)
|
|
1200
|
-
})
|
|
1201
|
-
|
|
1202
|
-
test('new activation replaces the previous subscription for the same lookup key', async () => {
|
|
1203
|
-
const store = Store.memory()
|
|
1204
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1205
|
-
|
|
1206
|
-
// Seed an expired subscription so authorize() falls through to a new challenge.
|
|
1207
|
-
const expiredDate = new Date(Date.now() - 1_000).toISOString()
|
|
1208
|
-
await subscriptions.put(
|
|
1209
|
-
createRecord({
|
|
1210
|
-
lookupKey: subscriptionKey,
|
|
1211
|
-
subscriptionId: 'sub_old',
|
|
1212
|
-
reference: hashOld,
|
|
1213
|
-
subscriptionExpires: expiredDate,
|
|
1214
|
-
}),
|
|
1215
|
-
)
|
|
1216
|
-
|
|
1217
|
-
const method = subscription({
|
|
1218
|
-
accessKey: async () => accessKey,
|
|
1219
|
-
activate: async ({ request, resolved }) => ({
|
|
1220
|
-
receipt: createReceipt('sub_new', hashActivate),
|
|
1221
|
-
subscription: createRecord({
|
|
1222
|
-
amount: request.amount,
|
|
1223
|
-
chainId: request.methodDetails?.chainId,
|
|
1224
|
-
currency: request.currency,
|
|
1225
|
-
lookupKey: resolved.key,
|
|
1226
|
-
periodCount: request.periodCount,
|
|
1227
|
-
periodUnit: request.periodUnit,
|
|
1228
|
-
recipient: request.recipient,
|
|
1229
|
-
reference: hashActivate,
|
|
1230
|
-
subscriptionExpires: request.subscriptionExpires,
|
|
1231
|
-
subscriptionId: 'sub_new',
|
|
1232
|
-
}),
|
|
1233
|
-
}),
|
|
1234
|
-
amount: subscriptionAmount,
|
|
1235
|
-
chainId,
|
|
1236
|
-
currency: subscriptionCurrency,
|
|
1237
|
-
periodCount: subscriptionPeriodCount,
|
|
1238
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1239
|
-
recipient: subscriptionRecipient,
|
|
1240
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1241
|
-
store,
|
|
1242
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1243
|
-
})
|
|
1244
|
-
|
|
1245
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1246
|
-
|
|
1247
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1248
|
-
new Request('https://example.com/resource'),
|
|
1249
|
-
)
|
|
1250
|
-
expect(challengeResult.status).toBe(402)
|
|
1251
|
-
if (challengeResult.status !== 402) throw new Error('expected challenge')
|
|
1252
|
-
|
|
1253
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
1254
|
-
const credential = await createCredential(challenge)
|
|
1255
|
-
|
|
1256
|
-
const activated = await mppx.tempo.subscription({})(
|
|
1257
|
-
new Request('https://example.com/resource', {
|
|
1258
|
-
headers: {
|
|
1259
|
-
Authorization: Credential.serialize(credential),
|
|
1260
|
-
'X-Subscription-Key': subscriptionKey,
|
|
1261
|
-
},
|
|
1262
|
-
}),
|
|
1263
|
-
)
|
|
1264
|
-
expect(activated.status).toBe(200)
|
|
1265
|
-
if (activated.status !== 200) throw new Error('expected activation')
|
|
1266
|
-
|
|
1267
|
-
const receipt = Receipt.fromResponse(activated.withReceipt(new Response('OK')))
|
|
1268
|
-
expect(receipt.subscriptionId).toBe('sub_new')
|
|
1269
|
-
|
|
1270
|
-
const current = await subscriptions.getByKey(subscriptionKey)
|
|
1271
|
-
expect(current?.subscriptionId).toBe('sub_new')
|
|
1272
|
-
})
|
|
1273
|
-
|
|
1274
|
-
test('does not reuse an active subscription whose request binding differs during verify', async () => {
|
|
1275
|
-
const store = Store.memory()
|
|
1276
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1277
|
-
let activationCount = 0
|
|
1278
|
-
await subscriptions.put(
|
|
1279
|
-
createRecord({
|
|
1280
|
-
accessKey,
|
|
1281
|
-
amount: '1000000',
|
|
1282
|
-
lookupKey: subscriptionKey,
|
|
1283
|
-
subscriptionId: 'sub_basic',
|
|
1284
|
-
}),
|
|
1285
|
-
)
|
|
1286
|
-
const method = subscription({
|
|
1287
|
-
accessKey: async () => accessKey,
|
|
1288
|
-
activate: async ({ request, resolved }) => {
|
|
1289
|
-
activationCount += 1
|
|
1290
|
-
return {
|
|
1291
|
-
receipt: createReceipt('sub_premium'),
|
|
1292
|
-
subscription: createRecord({
|
|
1293
|
-
amount: request.amount,
|
|
1294
|
-
chainId: request.methodDetails?.chainId,
|
|
1295
|
-
currency: request.currency,
|
|
1296
|
-
lookupKey: resolved.key,
|
|
1297
|
-
periodCount: request.periodCount,
|
|
1298
|
-
periodUnit: request.periodUnit,
|
|
1299
|
-
recipient: request.recipient,
|
|
1300
|
-
subscriptionExpires: request.subscriptionExpires,
|
|
1301
|
-
subscriptionId: 'sub_premium',
|
|
1302
|
-
}),
|
|
1303
|
-
}
|
|
1304
|
-
},
|
|
1305
|
-
amount: subscriptionAmount,
|
|
1306
|
-
chainId,
|
|
1307
|
-
currency: subscriptionCurrency,
|
|
1308
|
-
periodCount: subscriptionPeriodCount,
|
|
1309
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1310
|
-
recipient: subscriptionRecipient,
|
|
1311
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1312
|
-
store,
|
|
1313
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1314
|
-
})
|
|
1315
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1316
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1317
|
-
new Request('https://example.com/resource'),
|
|
1318
|
-
)
|
|
1319
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1320
|
-
|
|
1321
|
-
const credential = await createCredential(Challenge.fromResponse(challengeResult.challenge))
|
|
1322
|
-
const activated = await mppx.tempo.subscription({})(
|
|
1323
|
-
new Request('https://example.com/resource', {
|
|
1324
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
1325
|
-
}),
|
|
1326
|
-
)
|
|
1327
|
-
|
|
1328
|
-
expect(activated.status).toBe(200)
|
|
1329
|
-
expect(activationCount).toBe(1)
|
|
1330
|
-
if (activated.status !== 200) throw new Error('expected activation')
|
|
1331
|
-
const receipt = Receipt.fromResponse(activated.withReceipt(new Response('OK')))
|
|
1332
|
-
expect(receipt.subscriptionId).toBe('sub_premium')
|
|
1333
|
-
})
|
|
1334
|
-
|
|
1335
|
-
test('does not reuse an overdue subscription during verify', async () => {
|
|
1336
|
-
const store = Store.memory()
|
|
1337
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1338
|
-
let activationCount = 0
|
|
1339
|
-
await subscriptions.put(
|
|
1340
|
-
createRecord({
|
|
1341
|
-
accessKey,
|
|
1342
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
1343
|
-
lastChargedPeriod: 0,
|
|
1344
|
-
lookupKey: subscriptionKey,
|
|
1345
|
-
reference: hashStale,
|
|
1346
|
-
subscriptionId: 'sub_due',
|
|
1347
|
-
}),
|
|
1348
|
-
)
|
|
1349
|
-
const method = subscription({
|
|
1350
|
-
accessKey: async () => accessKey,
|
|
1351
|
-
activate: async () => {
|
|
1352
|
-
activationCount += 1
|
|
1353
|
-
throw new Error('overdue subscription must renew')
|
|
1354
|
-
},
|
|
1355
|
-
amount: subscriptionAmount,
|
|
1356
|
-
chainId,
|
|
1357
|
-
currency: subscriptionCurrency,
|
|
1358
|
-
periodCount: subscriptionPeriodCount,
|
|
1359
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1360
|
-
recipient: subscriptionRecipient,
|
|
1361
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1362
|
-
store,
|
|
1363
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1364
|
-
})
|
|
1365
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1366
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1367
|
-
new Request('https://example.com/resource'),
|
|
1368
|
-
)
|
|
1369
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1370
|
-
|
|
1371
|
-
const credential = await createCredential(Challenge.fromResponse(challengeResult.challenge))
|
|
1372
|
-
const rejected = await mppx.tempo.subscription({})(
|
|
1373
|
-
new Request('https://example.com/resource', {
|
|
1374
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
1375
|
-
}),
|
|
1376
|
-
)
|
|
1377
|
-
|
|
1378
|
-
expect(rejected.status).toBe(402)
|
|
1379
|
-
expect(activationCount).toBe(1)
|
|
1380
|
-
})
|
|
1381
|
-
|
|
1382
|
-
test('rejects activation when the dynamic access key does not match the credential', async () => {
|
|
1383
|
-
const store = Store.memory()
|
|
1384
|
-
const activateCalls: unknown[] = []
|
|
1385
|
-
const method = subscription({
|
|
1386
|
-
accessKey: async () => ({
|
|
1387
|
-
accessKeyAddress: accessAccount.address,
|
|
1388
|
-
keyType: 'p256',
|
|
1389
|
-
}),
|
|
1390
|
-
activate: async (parameters) => {
|
|
1391
|
-
activateCalls.push(parameters)
|
|
1392
|
-
return {
|
|
1393
|
-
receipt: createReceipt('sub_unused'),
|
|
1394
|
-
subscription: createRecord({ subscriptionId: 'sub_unused' }),
|
|
1395
|
-
}
|
|
1396
|
-
},
|
|
1397
|
-
amount: subscriptionAmount,
|
|
1398
|
-
chainId,
|
|
1399
|
-
currency: subscriptionCurrency,
|
|
1400
|
-
periodCount: subscriptionPeriodCount,
|
|
1401
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1402
|
-
recipient: subscriptionRecipient,
|
|
1403
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1404
|
-
store,
|
|
1405
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1406
|
-
})
|
|
1407
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1408
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1409
|
-
new Request('https://example.com/resource'),
|
|
1410
|
-
)
|
|
1411
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1412
|
-
|
|
1413
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
1414
|
-
const credential = await createCredential(challenge)
|
|
1415
|
-
const rejected = await mppx.tempo.subscription({})(
|
|
1416
|
-
new Request('https://example.com/resource', {
|
|
1417
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
1418
|
-
}),
|
|
1419
|
-
)
|
|
1420
|
-
|
|
1421
|
-
expect(rejected.status).toBe(402)
|
|
1422
|
-
expect(activateCalls.length).toBe(0)
|
|
1423
|
-
})
|
|
1424
|
-
|
|
1425
|
-
test('rejects activation settlements that do not match the challenged request', async () => {
|
|
1426
|
-
const store = Store.memory()
|
|
1427
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1428
|
-
const method = subscription({
|
|
1429
|
-
accessKey: async () => accessKey,
|
|
1430
|
-
activate: async ({ request, resolved }) => ({
|
|
1431
|
-
receipt: createReceipt('sub_bad', hashActivate),
|
|
1432
|
-
subscription: createRecord({
|
|
1433
|
-
amount: String(BigInt(request.amount) + 1n),
|
|
1434
|
-
chainId: request.methodDetails?.chainId,
|
|
1435
|
-
currency: request.currency,
|
|
1436
|
-
lookupKey: resolved.key,
|
|
1437
|
-
periodCount: request.periodCount,
|
|
1438
|
-
periodUnit: request.periodUnit,
|
|
1439
|
-
recipient: request.recipient,
|
|
1440
|
-
reference: hashActivate,
|
|
1441
|
-
subscriptionExpires: request.subscriptionExpires,
|
|
1442
|
-
subscriptionId: 'sub_bad',
|
|
1443
|
-
}),
|
|
1444
|
-
}),
|
|
1445
|
-
amount: subscriptionAmount,
|
|
1446
|
-
chainId,
|
|
1447
|
-
currency: subscriptionCurrency,
|
|
1448
|
-
periodCount: subscriptionPeriodCount,
|
|
1449
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1450
|
-
recipient: subscriptionRecipient,
|
|
1451
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1452
|
-
store,
|
|
1453
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1454
|
-
})
|
|
1455
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1456
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1457
|
-
new Request('https://example.com/resource'),
|
|
1458
|
-
)
|
|
1459
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1460
|
-
|
|
1461
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
1462
|
-
const credential = await createCredential(challenge)
|
|
1463
|
-
const rejected = await mppx.tempo.subscription({})(
|
|
1464
|
-
new Request('https://example.com/resource', {
|
|
1465
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
1466
|
-
}),
|
|
1467
|
-
)
|
|
1468
|
-
|
|
1469
|
-
expect(rejected.status).toBe(402)
|
|
1470
|
-
expect(await subscriptions.getByKey(subscriptionKey)).toBe(null)
|
|
1471
|
-
})
|
|
1472
|
-
|
|
1473
|
-
test('rejects activation settlements with a mismatched chainId', async () => {
|
|
1474
|
-
const store = Store.memory()
|
|
1475
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1476
|
-
const method = subscription({
|
|
1477
|
-
accessKey: async () => accessKey,
|
|
1478
|
-
activate: async ({ request, resolved }) => ({
|
|
1479
|
-
receipt: createReceipt('sub_bad', hashActivate),
|
|
1480
|
-
subscription: createRecord({
|
|
1481
|
-
amount: request.amount,
|
|
1482
|
-
chainId: chainId + 1,
|
|
1483
|
-
currency: request.currency,
|
|
1484
|
-
lookupKey: resolved.key,
|
|
1485
|
-
periodCount: request.periodCount,
|
|
1486
|
-
periodUnit: request.periodUnit,
|
|
1487
|
-
recipient: request.recipient,
|
|
1488
|
-
reference: hashActivate,
|
|
1489
|
-
subscriptionExpires: request.subscriptionExpires,
|
|
1490
|
-
subscriptionId: 'sub_bad',
|
|
1491
|
-
}),
|
|
1492
|
-
}),
|
|
1493
|
-
amount: subscriptionAmount,
|
|
1494
|
-
chainId,
|
|
1495
|
-
currency: subscriptionCurrency,
|
|
1496
|
-
periodCount: subscriptionPeriodCount,
|
|
1497
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1498
|
-
recipient: subscriptionRecipient,
|
|
1499
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1500
|
-
store,
|
|
1501
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1502
|
-
})
|
|
1503
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1504
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1505
|
-
new Request('https://example.com/resource'),
|
|
1506
|
-
)
|
|
1507
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1508
|
-
|
|
1509
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
1510
|
-
const credential = await createCredential(challenge)
|
|
1511
|
-
const rejected = await mppx.tempo.subscription({})(
|
|
1512
|
-
new Request('https://example.com/resource', {
|
|
1513
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
1514
|
-
}),
|
|
1515
|
-
)
|
|
1516
|
-
|
|
1517
|
-
expect(rejected.status).toBe(402)
|
|
1518
|
-
expect(await subscriptions.getByKey(subscriptionKey)).toBe(null)
|
|
1519
|
-
})
|
|
1520
|
-
|
|
1521
|
-
test('rejects activation settlements with a mismatched externalId', async () => {
|
|
1522
|
-
const store = Store.memory()
|
|
1523
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1524
|
-
const method = subscription({
|
|
1525
|
-
accessKey: async () => accessKey,
|
|
1526
|
-
activate: async ({ request, resolved }) => ({
|
|
1527
|
-
receipt: createReceipt('sub_bad', hashActivate),
|
|
1528
|
-
subscription: createRecord({
|
|
1529
|
-
amount: request.amount,
|
|
1530
|
-
chainId: request.methodDetails?.chainId,
|
|
1531
|
-
currency: request.currency,
|
|
1532
|
-
externalId: 'external_2',
|
|
1533
|
-
lookupKey: resolved.key,
|
|
1534
|
-
periodCount: request.periodCount,
|
|
1535
|
-
periodUnit: request.periodUnit,
|
|
1536
|
-
recipient: request.recipient,
|
|
1537
|
-
reference: hashActivate,
|
|
1538
|
-
subscriptionExpires: request.subscriptionExpires,
|
|
1539
|
-
subscriptionId: 'sub_bad',
|
|
1540
|
-
}),
|
|
1541
|
-
}),
|
|
1542
|
-
amount: subscriptionAmount,
|
|
1543
|
-
chainId,
|
|
1544
|
-
currency: subscriptionCurrency,
|
|
1545
|
-
externalId: 'external_1',
|
|
1546
|
-
periodCount: subscriptionPeriodCount,
|
|
1547
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1548
|
-
recipient: subscriptionRecipient,
|
|
1549
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1550
|
-
store,
|
|
1551
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1552
|
-
})
|
|
1553
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1554
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1555
|
-
new Request('https://example.com/resource'),
|
|
1556
|
-
)
|
|
1557
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1558
|
-
|
|
1559
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
1560
|
-
const credential = await createCredential(challenge)
|
|
1561
|
-
const rejected = await mppx.tempo.subscription({})(
|
|
1562
|
-
new Request('https://example.com/resource', {
|
|
1563
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
1564
|
-
}),
|
|
1565
|
-
)
|
|
1566
|
-
|
|
1567
|
-
expect(rejected.status).toBe(402)
|
|
1568
|
-
expect(await subscriptions.getByKey(subscriptionKey)).toBe(null)
|
|
1569
|
-
})
|
|
1570
|
-
|
|
1571
|
-
test('rejects credentials when the current request externalId differs from the challenge', async () => {
|
|
1572
|
-
const store = Store.memory()
|
|
1573
|
-
let activationCount = 0
|
|
1574
|
-
const method = subscription({
|
|
1575
|
-
accessKey: async () => accessKey,
|
|
1576
|
-
activate: async ({ request, resolved }) => {
|
|
1577
|
-
activationCount += 1
|
|
1578
|
-
return {
|
|
1579
|
-
receipt: createReceipt('sub_unused'),
|
|
1580
|
-
subscription: createRecord({
|
|
1581
|
-
amount: request.amount,
|
|
1582
|
-
chainId: request.methodDetails?.chainId,
|
|
1583
|
-
currency: request.currency,
|
|
1584
|
-
externalId: request.externalId,
|
|
1585
|
-
lookupKey: resolved.key,
|
|
1586
|
-
periodCount: request.periodCount,
|
|
1587
|
-
periodUnit: request.periodUnit,
|
|
1588
|
-
recipient: request.recipient,
|
|
1589
|
-
subscriptionExpires: request.subscriptionExpires,
|
|
1590
|
-
subscriptionId: 'sub_unused',
|
|
1591
|
-
}),
|
|
1592
|
-
}
|
|
1593
|
-
},
|
|
1594
|
-
amount: subscriptionAmount,
|
|
1595
|
-
chainId,
|
|
1596
|
-
currency: subscriptionCurrency,
|
|
1597
|
-
periodCount: subscriptionPeriodCount,
|
|
1598
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1599
|
-
recipient: subscriptionRecipient,
|
|
1600
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1601
|
-
store,
|
|
1602
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1603
|
-
})
|
|
1604
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1605
|
-
const challengeResult = await mppx.tempo.subscription({ externalId: 'external_1' })(
|
|
1606
|
-
new Request('https://example.com/resource'),
|
|
1607
|
-
)
|
|
1608
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1609
|
-
|
|
1610
|
-
const credential = await createCredential(Challenge.fromResponse(challengeResult.challenge))
|
|
1611
|
-
const rejected = await mppx.tempo.subscription({ externalId: 'external_2' })(
|
|
1612
|
-
new Request('https://example.com/resource', {
|
|
1613
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
1614
|
-
}),
|
|
1615
|
-
)
|
|
1616
|
-
|
|
1617
|
-
expect(rejected.status).toBe(402)
|
|
1618
|
-
expect(activationCount).toBe(0)
|
|
1619
|
-
})
|
|
1620
|
-
|
|
1621
|
-
test('rejects credentials whose declared source does not match the key authorization signer', async () => {
|
|
1622
|
-
const store = Store.memory()
|
|
1623
|
-
const activateCalls: unknown[] = []
|
|
1624
|
-
const method = subscription({
|
|
1625
|
-
accessKey: async () => accessKey,
|
|
1626
|
-
activate: async (parameters) => {
|
|
1627
|
-
activateCalls.push(parameters)
|
|
1628
|
-
return {
|
|
1629
|
-
receipt: createReceipt('sub_unused'),
|
|
1630
|
-
subscription: createRecord({ subscriptionId: 'sub_unused' }),
|
|
1631
|
-
}
|
|
1632
|
-
},
|
|
1633
|
-
amount: subscriptionAmount,
|
|
1634
|
-
chainId,
|
|
1635
|
-
currency: subscriptionCurrency,
|
|
1636
|
-
periodCount: subscriptionPeriodCount,
|
|
1637
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1638
|
-
recipient: subscriptionRecipient,
|
|
1639
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1640
|
-
store,
|
|
1641
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1642
|
-
})
|
|
1643
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1644
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1645
|
-
new Request('https://example.com/resource'),
|
|
1646
|
-
)
|
|
1647
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1648
|
-
|
|
1649
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
1650
|
-
const credential = await createCredential(challenge, otherAccessAccount.address)
|
|
1651
|
-
const rejected = await mppx.tempo.subscription({})(
|
|
1652
|
-
new Request('https://example.com/resource', {
|
|
1653
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
1654
|
-
}),
|
|
1655
|
-
)
|
|
1656
|
-
|
|
1657
|
-
expect(rejected.status).toBe(402)
|
|
1658
|
-
expect(activateCalls.length).toBe(0)
|
|
1659
|
-
})
|
|
1660
|
-
|
|
1661
|
-
test('renews an overdue matching subscription before falling back to 402', async () => {
|
|
1662
|
-
const store = Store.memory()
|
|
1663
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1664
|
-
const renewCalls: number[] = []
|
|
1665
|
-
const renewalReferences: string[] = []
|
|
1666
|
-
const method = subscription({
|
|
1667
|
-
accessKey: async () => accessKey,
|
|
1668
|
-
activate: async () => ({
|
|
1669
|
-
receipt: createReceipt('unused'),
|
|
1670
|
-
subscription: createRecord({ subscriptionId: 'unused' }),
|
|
1671
|
-
}),
|
|
1672
|
-
amount: subscriptionAmount,
|
|
1673
|
-
chainId,
|
|
1674
|
-
currency: subscriptionCurrency,
|
|
1675
|
-
periodCount: subscriptionPeriodCount,
|
|
1676
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1677
|
-
recipient: subscriptionRecipient,
|
|
1678
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1679
|
-
renew: async ({ inFlightReference, periodIndex, subscription }) => {
|
|
1680
|
-
renewCalls.push(periodIndex)
|
|
1681
|
-
renewalReferences.push(inFlightReference)
|
|
1682
|
-
expect(subscription.inFlightReference).toBe(inFlightReference)
|
|
1683
|
-
return {
|
|
1684
|
-
receipt: createReceipt(subscription.subscriptionId, hashRenewed),
|
|
1685
|
-
subscription: {
|
|
1686
|
-
...subscription,
|
|
1687
|
-
lastChargedPeriod: periodIndex,
|
|
1688
|
-
reference: hashRenewed,
|
|
1689
|
-
},
|
|
1690
|
-
}
|
|
1691
|
-
},
|
|
1692
|
-
store,
|
|
1693
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1694
|
-
})
|
|
1695
|
-
|
|
1696
|
-
await subscriptions.put(
|
|
1697
|
-
createRecord({
|
|
1698
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
1699
|
-
lastChargedPeriod: 0,
|
|
1700
|
-
lookupKey: subscriptionKey,
|
|
1701
|
-
reference: hashStale,
|
|
1702
|
-
subscriptionId: 'sub_due',
|
|
1703
|
-
}),
|
|
1704
|
-
)
|
|
1705
|
-
|
|
1706
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1707
|
-
const result = await mppx.tempo.subscription({})(
|
|
1708
|
-
new Request('https://example.com/resource', {
|
|
1709
|
-
headers: { 'X-Subscription-Key': subscriptionKey },
|
|
1710
|
-
}),
|
|
1711
|
-
)
|
|
1712
|
-
|
|
1713
|
-
expect(result.status).toBe(200)
|
|
1714
|
-
expect(renewCalls.length).toBe(1)
|
|
1715
|
-
expect(renewCalls[0]).toBeGreaterThan(0)
|
|
1716
|
-
expect(renewalReferences[0]).toBe(`renewal:sub_due:${renewCalls[0]}`)
|
|
1717
|
-
if (result.status !== 200) throw new Error('expected renewal success')
|
|
1718
|
-
|
|
1719
|
-
const receipt = Receipt.fromResponse(result.withReceipt(new Response('OK')))
|
|
1720
|
-
expect(receipt.reference).toBe(hashRenewed)
|
|
1721
|
-
expect(receipt.subscriptionId).toBe('sub_due')
|
|
1722
|
-
expect((await subscriptions.get('sub_due'))?.inFlightReference).toBe(undefined)
|
|
1723
|
-
})
|
|
1724
|
-
|
|
1725
|
-
test('rejects renewals that change the active subscriptionId', async () => {
|
|
1726
|
-
const store = Store.memory()
|
|
1727
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1728
|
-
const method = subscription({
|
|
1729
|
-
accessKey: async () => accessKey,
|
|
1730
|
-
activate: async () => ({
|
|
1731
|
-
receipt: createReceipt('unused'),
|
|
1732
|
-
subscription: createRecord({ subscriptionId: 'unused' }),
|
|
1733
|
-
}),
|
|
1734
|
-
amount: subscriptionAmount,
|
|
1735
|
-
chainId,
|
|
1736
|
-
currency: subscriptionCurrency,
|
|
1737
|
-
periodCount: subscriptionPeriodCount,
|
|
1738
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1739
|
-
recipient: subscriptionRecipient,
|
|
1740
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1741
|
-
renew: async ({ periodIndex, subscription }) => {
|
|
1742
|
-
const record = {
|
|
1743
|
-
...subscription,
|
|
1744
|
-
lastChargedPeriod: periodIndex,
|
|
1745
|
-
reference: hashRenewed,
|
|
1746
|
-
subscriptionId: 'sub_other',
|
|
1747
|
-
}
|
|
1748
|
-
return {
|
|
1749
|
-
receipt: createReceipt(record.subscriptionId, hashRenewed),
|
|
1750
|
-
subscription: record,
|
|
1751
|
-
}
|
|
1752
|
-
},
|
|
1753
|
-
store,
|
|
1754
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1755
|
-
})
|
|
1756
|
-
|
|
1757
|
-
await subscriptions.put(
|
|
1758
|
-
createRecord({
|
|
1759
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
1760
|
-
lastChargedPeriod: 0,
|
|
1761
|
-
lookupKey: subscriptionKey,
|
|
1762
|
-
reference: hashStale,
|
|
1763
|
-
subscriptionId: 'sub_due',
|
|
1764
|
-
}),
|
|
1765
|
-
)
|
|
1766
|
-
|
|
1767
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1768
|
-
const rejected = await mppx.tempo.subscription({})(
|
|
1769
|
-
new Request('https://example.com/resource', {
|
|
1770
|
-
headers: { 'X-Subscription-Key': subscriptionKey },
|
|
1771
|
-
}),
|
|
1772
|
-
)
|
|
1773
|
-
|
|
1774
|
-
expect(rejected.status).toBe(402)
|
|
1775
|
-
expect((await subscriptions.getByKey(subscriptionKey))?.subscriptionId).toBe('sub_due')
|
|
1776
|
-
expect((await subscriptions.get('sub_due'))?.lastChargedPeriod).toBe(0)
|
|
1777
|
-
})
|
|
1778
|
-
|
|
1779
|
-
test('charges an overdue subscription outside the request path', async () => {
|
|
1780
|
-
const store = Store.memory()
|
|
1781
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1782
|
-
const renewCalls: number[] = []
|
|
1783
|
-
|
|
1784
|
-
await subscriptions.put(
|
|
1785
|
-
createRecord({
|
|
1786
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
1787
|
-
lastChargedPeriod: 0,
|
|
1788
|
-
lookupKey: subscriptionKey,
|
|
1789
|
-
amount: subscriptionAmount,
|
|
1790
|
-
reference: hashStale,
|
|
1791
|
-
subscriptionId: 'sub_background',
|
|
1792
|
-
}),
|
|
1793
|
-
)
|
|
1794
|
-
|
|
1795
|
-
const result = await renew({
|
|
1796
|
-
renew: async ({ periodIndex, subscription }) => {
|
|
1797
|
-
renewCalls.push(periodIndex)
|
|
1798
|
-
return {
|
|
1799
|
-
receipt: createReceipt(subscription.subscriptionId, hashBackground),
|
|
1800
|
-
subscription: {
|
|
1801
|
-
...subscription,
|
|
1802
|
-
lastChargedPeriod: periodIndex,
|
|
1803
|
-
reference: hashBackground,
|
|
1804
|
-
},
|
|
1805
|
-
}
|
|
1806
|
-
},
|
|
1807
|
-
store,
|
|
1808
|
-
subscriptionId: 'sub_background',
|
|
1809
|
-
})
|
|
1810
|
-
|
|
1811
|
-
expect(result?.receipt.reference).toBe(hashBackground)
|
|
1812
|
-
expect(renewCalls.length).toBe(1)
|
|
1813
|
-
expect((await subscriptions.get('sub_background'))?.reference).toBe(hashBackground)
|
|
1814
|
-
})
|
|
1815
|
-
|
|
1816
|
-
test('rejects background renewals that mutate economic fields', async () => {
|
|
1817
|
-
const store = Store.memory()
|
|
1818
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1819
|
-
|
|
1820
|
-
await subscriptions.put(
|
|
1821
|
-
createRecord({
|
|
1822
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
1823
|
-
lastChargedPeriod: 0,
|
|
1824
|
-
lookupKey: subscriptionKey,
|
|
1825
|
-
amount: subscriptionAmount,
|
|
1826
|
-
reference: hashStale,
|
|
1827
|
-
subscriptionId: 'sub_background',
|
|
1828
|
-
}),
|
|
1829
|
-
)
|
|
1830
|
-
|
|
1831
|
-
await expect(
|
|
1832
|
-
renew({
|
|
1833
|
-
renew: async ({ periodIndex, subscription }) => {
|
|
1834
|
-
const mutated = {
|
|
1835
|
-
...subscription,
|
|
1836
|
-
amount: '999',
|
|
1837
|
-
lastChargedPeriod: periodIndex,
|
|
1838
|
-
reference: hashBackground,
|
|
1839
|
-
}
|
|
1840
|
-
return {
|
|
1841
|
-
receipt: createReceipt(subscription.subscriptionId, hashBackground),
|
|
1842
|
-
subscription: mutated,
|
|
1843
|
-
}
|
|
1844
|
-
},
|
|
1845
|
-
store,
|
|
1846
|
-
subscriptionId: 'sub_background',
|
|
1847
|
-
}),
|
|
1848
|
-
).rejects.toThrow('subscription record does not match request')
|
|
1849
|
-
|
|
1850
|
-
expect((await subscriptions.get('sub_background'))?.inFlightReference).toBe(undefined)
|
|
1851
|
-
expect((await subscriptions.get('sub_background'))?.amount).toBe(subscriptionAmount)
|
|
1852
|
-
})
|
|
1853
|
-
|
|
1854
|
-
test('does not charge a superseded subscription outside the request path', async () => {
|
|
1855
|
-
const store = Store.memory()
|
|
1856
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1857
|
-
let renewCalls = 0
|
|
1858
|
-
|
|
1859
|
-
await subscriptions.put(
|
|
1860
|
-
createRecord({
|
|
1861
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
1862
|
-
lastChargedPeriod: 0,
|
|
1863
|
-
reference: hashStale,
|
|
1864
|
-
subscriptionId: 'sub_old',
|
|
1865
|
-
}),
|
|
1866
|
-
)
|
|
1867
|
-
await subscriptions.put(
|
|
1868
|
-
createRecord({
|
|
1869
|
-
reference: hashBackground,
|
|
1870
|
-
subscriptionId: 'sub_new',
|
|
1871
|
-
}),
|
|
1872
|
-
)
|
|
1873
|
-
|
|
1874
|
-
const result = await renew({
|
|
1875
|
-
renew: async ({ subscription }) => {
|
|
1876
|
-
renewCalls += 1
|
|
1877
|
-
return {
|
|
1878
|
-
receipt: createReceipt(subscription.subscriptionId, hashBackground),
|
|
1879
|
-
subscription,
|
|
1880
|
-
}
|
|
1881
|
-
},
|
|
1882
|
-
store,
|
|
1883
|
-
subscriptionId: 'sub_old',
|
|
1884
|
-
})
|
|
1885
|
-
|
|
1886
|
-
expect(result).toBe(null)
|
|
1887
|
-
expect(renewCalls).toBe(0)
|
|
1888
|
-
expect((await subscriptions.getByKey(subscriptionKey))?.subscriptionId).toBe('sub_new')
|
|
1889
|
-
})
|
|
1890
|
-
|
|
1891
|
-
test('automatically renews an overdue subscription outside the request path', async () => {
|
|
1892
|
-
const store = Store.memory()
|
|
1893
|
-
const subscriptions = SubscriptionStore.fromStore(store)
|
|
1894
|
-
const { client, rpcMethods } = createBillingClient([hashActivate, hashBackground])
|
|
1895
|
-
const method = subscription({
|
|
1896
|
-
amount: subscriptionAmount,
|
|
1897
|
-
chainId,
|
|
1898
|
-
currency: subscriptionCurrency,
|
|
1899
|
-
getClient: async () => client,
|
|
1900
|
-
periodCount: subscriptionPeriodCount,
|
|
1901
|
-
periodUnit: subscriptionPeriodUnit,
|
|
1902
|
-
recipient: subscriptionRecipient,
|
|
1903
|
-
resolve: async () => ({ key: subscriptionKey }),
|
|
1904
|
-
store,
|
|
1905
|
-
subscriptionExpires: activeSubscriptionExpires,
|
|
1906
|
-
waitForConfirmation: false,
|
|
1907
|
-
})
|
|
1908
|
-
const mppx = Mppx.create({ methods: [method], realm, secretKey })
|
|
1909
|
-
const challengeResult = await mppx.tempo.subscription({})(
|
|
1910
|
-
new Request('https://example.com/resource'),
|
|
1911
|
-
)
|
|
1912
|
-
if (challengeResult.status !== 402) throw new Error('expected activation challenge')
|
|
1913
|
-
const challenge = Challenge.fromResponse(challengeResult.challenge)
|
|
1914
|
-
const accessKey = (
|
|
1915
|
-
challenge.request as ReturnType<typeof Methods.subscription.schema.request.parse>
|
|
1916
|
-
).methodDetails?.accessKey
|
|
1917
|
-
if (!accessKey) throw new Error('expected generated access key')
|
|
1918
|
-
const credential = await createCredential(challenge, rootAccount.address, accessKey)
|
|
1919
|
-
const activated = await mppx.tempo.subscription({})(
|
|
1920
|
-
new Request('https://example.com/resource', {
|
|
1921
|
-
headers: { Authorization: Credential.serialize(credential) },
|
|
1922
|
-
}),
|
|
1923
|
-
)
|
|
1924
|
-
expect(activated.status).toBe(200)
|
|
1925
|
-
|
|
1926
|
-
const record = await subscriptions.getByKey(subscriptionKey)
|
|
1927
|
-
if (!record) throw new Error('expected subscription record')
|
|
1928
|
-
await subscriptions.put({
|
|
1929
|
-
...record,
|
|
1930
|
-
billingAnchor: new Date(Date.now() - 3 * subscriptionPeriodMilliseconds).toISOString(),
|
|
1931
|
-
lastChargedPeriod: 0,
|
|
1932
|
-
reference: hashStale,
|
|
1933
|
-
})
|
|
1934
|
-
|
|
1935
|
-
const result = await mppx.tempo.subscription.renew({
|
|
1936
|
-
subscriptionId: record.subscriptionId,
|
|
1937
|
-
})
|
|
1938
|
-
|
|
1939
|
-
expect(result?.receipt.reference).toBe(hashBackground)
|
|
1940
|
-
expect(rpcMethods.filter((method) => method === 'eth_sendRawTransaction')).toHaveLength(2)
|
|
1941
|
-
const renewed = await subscriptions.get(record.subscriptionId)
|
|
1942
|
-
expect(renewed?.reference).toBe(hashBackground)
|
|
1943
|
-
})
|
|
1944
|
-
})
|