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
package/src/server/Mppx.ts
DELETED
|
@@ -1,2919 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
|
-
import { isDeepStrictEqual } from 'node:util'
|
|
3
|
-
|
|
4
|
-
import * as AttestationServer from '../attestation/Server.js'
|
|
5
|
-
import type * as Attestation from '../attestation/Types.js'
|
|
6
|
-
import * as Challenge from '../Challenge.js'
|
|
7
|
-
import * as Constants from '../Constants.js'
|
|
8
|
-
import * as Credential from '../Credential.js'
|
|
9
|
-
import * as Errors from '../Errors.js'
|
|
10
|
-
import * as Expires from '../Expires.js'
|
|
11
|
-
import * as AcceptPayment from '../internal/AcceptPayment.js'
|
|
12
|
-
import * as Env from '../internal/env.js'
|
|
13
|
-
import type { DeepReadonly, MaybePromise } from '../internal/types.js'
|
|
14
|
-
import * as Method from '../Method.js'
|
|
15
|
-
import * as PaymentRequest from '../PaymentRequest.js'
|
|
16
|
-
import type * as Receipt from '../Receipt.js'
|
|
17
|
-
import * as x402_Header from '../x402/Header.js'
|
|
18
|
-
import * as x402_Types from '../x402/Types.js'
|
|
19
|
-
import * as z from '../zod.js'
|
|
20
|
-
import * as Html from './internal/html/config.js'
|
|
21
|
-
import { serviceWorker } from './internal/html/serviceWorker.gen.js'
|
|
22
|
-
import * as Scope from './internal/scope.js'
|
|
23
|
-
import * as NodeListener from './NodeListener.js'
|
|
24
|
-
import * as Request from './Request.js'
|
|
25
|
-
import * as Transport from './Transport.js'
|
|
26
|
-
|
|
27
|
-
const minimumSecretKeyBytes = 32
|
|
28
|
-
const secretKeyGenerationCommand = 'openssl rand -base64 32'
|
|
29
|
-
|
|
30
|
-
export type Methods = readonly (Method.AnyServer | readonly Method.AnyServer[])[]
|
|
31
|
-
|
|
32
|
-
export type ServerEventMap<
|
|
33
|
-
methods extends readonly Method.Method[] = readonly Method.Method[],
|
|
34
|
-
transport extends Transport.AnyTransport = Transport.AnyTransport,
|
|
35
|
-
> = {
|
|
36
|
-
'challenge.created': ChallengeContext<methods[number], transport>
|
|
37
|
-
'payment.failed': PaymentFailedContext<methods[number], transport>
|
|
38
|
-
'payment.success': PaymentSuccessContext<methods[number], transport>
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type ServerEventName<
|
|
42
|
-
methods extends readonly Method.Method[] = readonly Method.Method[],
|
|
43
|
-
transport extends Transport.AnyTransport = Transport.AnyTransport,
|
|
44
|
-
> = keyof ServerEventMap<methods, transport> | '*'
|
|
45
|
-
|
|
46
|
-
export type ServerEventEnvelope<
|
|
47
|
-
methods extends readonly Method.Method[] = readonly Method.Method[],
|
|
48
|
-
transport extends Transport.AnyTransport = Transport.AnyTransport,
|
|
49
|
-
> = {
|
|
50
|
-
[name in keyof ServerEventMap<methods, transport>]: Readonly<{
|
|
51
|
-
name: name
|
|
52
|
-
payload: ServerEventMap<methods, transport>[name]
|
|
53
|
-
}>
|
|
54
|
-
}[keyof ServerEventMap<methods, transport>]
|
|
55
|
-
|
|
56
|
-
export type ServerEventPayload<
|
|
57
|
-
methods extends readonly Method.Method[] = readonly Method.Method[],
|
|
58
|
-
transport extends Transport.AnyTransport = Transport.AnyTransport,
|
|
59
|
-
name extends ServerEventName<methods, transport> = ServerEventName<methods, transport>,
|
|
60
|
-
> = name extends '*'
|
|
61
|
-
? ServerEventEnvelope<methods, transport>
|
|
62
|
-
: name extends keyof ServerEventMap<methods, transport>
|
|
63
|
-
? ServerEventMap<methods, transport>[name]
|
|
64
|
-
: never
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Server event handler.
|
|
68
|
-
*
|
|
69
|
-
* Handlers are awaited inline and sequentially on the payment request path.
|
|
70
|
-
* Errors are swallowed so observers cannot change payment control flow, but
|
|
71
|
-
* slow handlers still delay the response.
|
|
72
|
-
*/
|
|
73
|
-
export type ServerEventHandler<
|
|
74
|
-
methods extends readonly Method.Method[] = readonly Method.Method[],
|
|
75
|
-
transport extends Transport.AnyTransport = Transport.AnyTransport,
|
|
76
|
-
name extends ServerEventName<methods, transport> = ServerEventName<methods, transport>,
|
|
77
|
-
> = (context: ServerEventPayload<methods, transport, name>) => MaybePromise<void>
|
|
78
|
-
|
|
79
|
-
/** Removes a registered server event handler. */
|
|
80
|
-
export type Unsubscribe = () => void
|
|
81
|
-
|
|
82
|
-
/** Inert method descriptor exposed to server event handlers. */
|
|
83
|
-
export type ServerMethodDescriptor<
|
|
84
|
-
method extends Pick<Method.Method, 'intent' | 'name'> = Method.Method,
|
|
85
|
-
> = Readonly<{
|
|
86
|
-
intent: method['intent']
|
|
87
|
-
name: method['name']
|
|
88
|
-
}>
|
|
89
|
-
|
|
90
|
-
/** A configured payment offer exposed to the server's `selectOffers` hook. */
|
|
91
|
-
export type ServerOffer<method extends Method.Method = Method.Method> = method extends unknown
|
|
92
|
-
? ServerOfferFor<method>
|
|
93
|
-
: never
|
|
94
|
-
|
|
95
|
-
type ServerOfferFor<method extends Method.Method> = Readonly<{
|
|
96
|
-
/** Canonical wire key for the configured method. */
|
|
97
|
-
key: `${method['name']}/${method['intent']}`
|
|
98
|
-
/** Immutable method metadata for this offer. */
|
|
99
|
-
method: ServerOfferMethodDescriptor<method>
|
|
100
|
-
/** Schema-normalized request configured for this offer. */
|
|
101
|
-
request: DeepReadonly<z.output<method['schema']['request']>>
|
|
102
|
-
}>
|
|
103
|
-
|
|
104
|
-
/** Immutable method metadata exposed to offer-selection hooks. */
|
|
105
|
-
export type ServerOfferMethodDescriptor<method extends Method.Method = Method.Method> = Readonly<{
|
|
106
|
-
/** Optional public alias used to register this server method. */
|
|
107
|
-
alias?: method extends { alias?: infer alias } ? alias : string
|
|
108
|
-
intent: method['intent']
|
|
109
|
-
name: method['name']
|
|
110
|
-
}>
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Selects a subset of configured offers before payment challenges are issued.
|
|
114
|
-
*
|
|
115
|
-
* The hook must select at least one offer. Returned offers must come from the input,
|
|
116
|
-
* contain no duplicates, and preserve their original order. Offers and their
|
|
117
|
-
* normalized requests are deeply immutable. The incoming request is cloned so
|
|
118
|
-
* the hook may safely inspect its body when it has not already been consumed.
|
|
119
|
-
*/
|
|
120
|
-
export type SelectOffers<methods extends readonly Method.Method[]> = (
|
|
121
|
-
offers: readonly ServerOffer<methods[number]>[],
|
|
122
|
-
context: Readonly<{ request: globalThis.Request }>,
|
|
123
|
-
) => MaybePromise<readonly ServerOffer<methods[number]>[]>
|
|
124
|
-
|
|
125
|
-
/** Context passed to `onChallengeCreated`. */
|
|
126
|
-
export type ChallengeContext<
|
|
127
|
-
method extends Method.Method = Method.Method,
|
|
128
|
-
transport extends Transport.AnyTransport = Transport.AnyTransport,
|
|
129
|
-
> = Readonly<{
|
|
130
|
-
capturedRequest?: Method.CapturedRequest | undefined
|
|
131
|
-
challenge: Challenge.Challenge
|
|
132
|
-
credential?: Credential.Credential | null | undefined
|
|
133
|
-
error?: Errors.PaymentError | undefined
|
|
134
|
-
input?: Transport.InputOf<transport> | undefined
|
|
135
|
-
method: ServerMethodDescriptor<method>
|
|
136
|
-
request: z.output<method['schema']['request']>
|
|
137
|
-
}>
|
|
138
|
-
|
|
139
|
-
/** Context passed to `onPaymentFailed`. */
|
|
140
|
-
export type PaymentFailedContext<
|
|
141
|
-
method extends Method.Method = Method.Method,
|
|
142
|
-
transport extends Transport.AnyTransport = Transport.AnyTransport,
|
|
143
|
-
> = Readonly<{
|
|
144
|
-
capturedRequest?: Method.CapturedRequest | undefined
|
|
145
|
-
challenge: Challenge.Challenge
|
|
146
|
-
credential: Credential.Credential | null
|
|
147
|
-
error: Errors.PaymentError
|
|
148
|
-
input?: Transport.InputOf<transport> | undefined
|
|
149
|
-
method: ServerMethodDescriptor<method>
|
|
150
|
-
request: z.output<method['schema']['request']>
|
|
151
|
-
retryChallenge?: Challenge.Challenge | undefined
|
|
152
|
-
submittedChallenge?: Challenge.Challenge | undefined
|
|
153
|
-
}>
|
|
154
|
-
|
|
155
|
-
/** Context passed to `onPaymentSuccess`. */
|
|
156
|
-
export type PaymentSuccessContext<
|
|
157
|
-
method extends Method.Method = Method.Method,
|
|
158
|
-
transport extends Transport.AnyTransport = Transport.AnyTransport,
|
|
159
|
-
> = Readonly<{
|
|
160
|
-
capturedRequest?: Method.CapturedRequest | undefined
|
|
161
|
-
challenge: Challenge.Challenge<
|
|
162
|
-
z.output<method['schema']['request']>,
|
|
163
|
-
method['intent'],
|
|
164
|
-
method['name']
|
|
165
|
-
>
|
|
166
|
-
credential?:
|
|
167
|
-
| Credential.Credential<
|
|
168
|
-
z.output<method['schema']['credential']['payload']>,
|
|
169
|
-
Challenge.Challenge<z.output<method['schema']['request']>, method['intent'], method['name']>
|
|
170
|
-
>
|
|
171
|
-
| undefined
|
|
172
|
-
envelope?:
|
|
173
|
-
| Method.VerifiedChallengeEnvelope<
|
|
174
|
-
z.output<method['schema']['request']>,
|
|
175
|
-
z.output<method['schema']['credential']['payload']>,
|
|
176
|
-
method['intent'],
|
|
177
|
-
method['name']
|
|
178
|
-
>
|
|
179
|
-
| undefined
|
|
180
|
-
input?: Transport.InputOf<transport> | undefined
|
|
181
|
-
method: ServerMethodDescriptor<method>
|
|
182
|
-
receipt: Receipt.Receipt
|
|
183
|
-
request: z.output<method['schema']['request']>
|
|
184
|
-
}>
|
|
185
|
-
|
|
186
|
-
/** Options for standalone credential verification. */
|
|
187
|
-
export type VerifyCredentialOptions = {
|
|
188
|
-
capturedRequest?: Method.CapturedRequest | undefined
|
|
189
|
-
meta?: Record<string, string> | undefined
|
|
190
|
-
realm?: string | undefined
|
|
191
|
-
request?: Record<string, unknown> | undefined
|
|
192
|
-
/** Optional expected route/resource scope bound via challenge `opaque`. */
|
|
193
|
-
scope?: string | undefined
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Payment handler.
|
|
198
|
-
*/
|
|
199
|
-
export type Mppx<
|
|
200
|
-
methods extends Methods = Methods,
|
|
201
|
-
transport extends Transport.AnyTransport = Transport.Http,
|
|
202
|
-
> = {
|
|
203
|
-
/** Methods to configure. */
|
|
204
|
-
methods: FlattenMethods<methods>
|
|
205
|
-
/** Server realm (e.g., hostname). */
|
|
206
|
-
realm: string
|
|
207
|
-
/** The transport used. */
|
|
208
|
-
transport: transport
|
|
209
|
-
/** Register a server event handler by canonical event name. */
|
|
210
|
-
on<name extends ServerEventName<FlattenMethods<methods>, transport>>(
|
|
211
|
-
name: name,
|
|
212
|
-
handler: ServerEventHandler<FlattenMethods<methods>, transport, name>,
|
|
213
|
-
): Unsubscribe
|
|
214
|
-
/** Register a handler for issued payment challenges. */
|
|
215
|
-
onChallengeCreated(
|
|
216
|
-
handler: ServerEventHandler<FlattenMethods<methods>, transport, 'challenge.created'>,
|
|
217
|
-
): Unsubscribe
|
|
218
|
-
/** Register a handler for failed submitted payment credentials. */
|
|
219
|
-
onPaymentFailed(
|
|
220
|
-
handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.failed'>,
|
|
221
|
-
): Unsubscribe
|
|
222
|
-
/** Register a handler for successful verified payments. */
|
|
223
|
-
onPaymentSuccess(
|
|
224
|
-
handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.success'>,
|
|
225
|
-
): Unsubscribe
|
|
226
|
-
} & (transport extends Transport.Http
|
|
227
|
-
? {
|
|
228
|
-
/**
|
|
229
|
-
* Combines multiple method handlers into a single route handler that presents
|
|
230
|
-
* all methods to the client via multiple `WWW-Authenticate` headers.
|
|
231
|
-
*
|
|
232
|
-
* Each entry is a `[method, options]` tuple where `method` is one of the
|
|
233
|
-
* server methods passed to `Mppx.create()`, looked up by `name`+`intent`.
|
|
234
|
-
*
|
|
235
|
-
* Only available on HTTP transports.
|
|
236
|
-
* No-credential authorize hooks run in entry order; the first 200 response
|
|
237
|
-
* wins, and earlier hooks may have already run side effects.
|
|
238
|
-
*
|
|
239
|
-
* @example
|
|
240
|
-
* ```ts
|
|
241
|
-
* import { Mppx, tempo, stripe } from 'mppx/server'
|
|
242
|
-
*
|
|
243
|
-
* const mppx = Mppx.create({
|
|
244
|
-
* methods: [
|
|
245
|
-
* tempo.charge({ currency: USDC, recipient: '0x...' }),
|
|
246
|
-
* stripe.charge({
|
|
247
|
-
* client: stripeClient,
|
|
248
|
-
* networkId: 'internal',
|
|
249
|
-
* currency: 'usd',
|
|
250
|
-
* decimals: 2,
|
|
251
|
-
* paymentMethodTypes: ['card'],
|
|
252
|
-
* }),
|
|
253
|
-
* ],
|
|
254
|
-
* secretKey,
|
|
255
|
-
* })
|
|
256
|
-
*
|
|
257
|
-
* app.get('/api/resource', async (req) => {
|
|
258
|
-
* const result = await mppx.compose(
|
|
259
|
-
* ['tempo/charge', { amount: '100' }],
|
|
260
|
-
* ['stripe/charge', { amount: '100' }],
|
|
261
|
-
* )(req)
|
|
262
|
-
* if (result.status === 402) return result.challenge
|
|
263
|
-
* return result.withReceipt(new Response('OK'))
|
|
264
|
-
* })
|
|
265
|
-
* ```
|
|
266
|
-
*/
|
|
267
|
-
compose(...entries: ComposeEntry<FlattenMethods<methods>>[]): ComposedHandler
|
|
268
|
-
}
|
|
269
|
-
: {}) &
|
|
270
|
-
Handlers<FlattenMethods<methods>, transport> & {
|
|
271
|
-
/**
|
|
272
|
-
* Generate Challenge objects for registered methods without going through
|
|
273
|
-
* the HTTP 402 request lifecycle. Uses the same options, defaults, and
|
|
274
|
-
* schema transforms as the corresponding intent handler.
|
|
275
|
-
*
|
|
276
|
-
* @example
|
|
277
|
-
* ```ts
|
|
278
|
-
* const challenge = await mppx.challenge.tempo.charge({ amount: '25.92' })
|
|
279
|
-
* ```
|
|
280
|
-
*/
|
|
281
|
-
challenge: ChallengeHandlers<FlattenMethods<methods>>
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Verify a credential string or object end-to-end: deserialize,
|
|
285
|
-
* HMAC-check, match to a registered method, validate payload schema,
|
|
286
|
-
* check expiry, and call the method's broadcast or legacy verify function.
|
|
287
|
-
*
|
|
288
|
-
* Method verification can broadcast payments and persist state. For example,
|
|
289
|
-
* subscription credentials may activate or renew a subscription. Failed
|
|
290
|
-
* standalone verification emits `payment.failed` once a credential challenge
|
|
291
|
-
* can be parsed; strings that cannot be deserialized have no challenge
|
|
292
|
-
* context to report.
|
|
293
|
-
*
|
|
294
|
-
* @deprecated Use `broadcastCredential()` instead. This legacy name maps
|
|
295
|
-
* to the same mutating operation and does not provide validation-only
|
|
296
|
-
* semantics.
|
|
297
|
-
*
|
|
298
|
-
* @example
|
|
299
|
-
* ```ts
|
|
300
|
-
* const receipt = await mppx.verifyCredential('eyJjaGFsbGVuZ2...')
|
|
301
|
-
* const receipt = await mppx.verifyCredential(credential)
|
|
302
|
-
* const receipt = await mppx.verifyCredential(credential, { request: { amount: '1000' } })
|
|
303
|
-
* ```
|
|
304
|
-
*/
|
|
305
|
-
verifyCredential(
|
|
306
|
-
credential: string | Credential.Credential,
|
|
307
|
-
options?: VerifyCredentialOptions | undefined,
|
|
308
|
-
): Promise<Receipt.Receipt>
|
|
309
|
-
/**
|
|
310
|
-
* Validate a credential without consuming or broadcasting it.
|
|
311
|
-
*
|
|
312
|
-
* This is an advisory pre-check. Methods that support it must not write
|
|
313
|
-
* replay state, sign fee-payer transactions, broadcast, or otherwise
|
|
314
|
-
* mutate payment state. Use `broadcastCredential()` when accepting payment.
|
|
315
|
-
*/
|
|
316
|
-
validateCredential(
|
|
317
|
-
credential: string | Credential.Credential,
|
|
318
|
-
options?: VerifyCredentialOptions | undefined,
|
|
319
|
-
): Promise<Method.Validation>
|
|
320
|
-
/**
|
|
321
|
-
* Re-validates and broadcasts a credential, returning a receipt.
|
|
322
|
-
*
|
|
323
|
-
* `verifyCredential()` is retained as a backwards-compatible alias for
|
|
324
|
-
* this mutating path.
|
|
325
|
-
*/
|
|
326
|
-
broadcastCredential(
|
|
327
|
-
credential: string | Credential.Credential,
|
|
328
|
-
options?: VerifyCredentialOptions | undefined,
|
|
329
|
-
): Promise<Receipt.Receipt>
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/** Extracts the transport override from a method, if any. */
|
|
333
|
-
type TransportOverrideOf<mi> = mi extends { transport?: infer transport }
|
|
334
|
-
? Exclude<transport, undefined> extends Transport.AnyTransport
|
|
335
|
-
? Exclude<transport, undefined>
|
|
336
|
-
: never
|
|
337
|
-
: never
|
|
338
|
-
|
|
339
|
-
/** Resolves the effective transport for a method: override if present, else global default. */
|
|
340
|
-
type EffectiveTransportOf<mi, defaultTransport extends Transport.AnyTransport> = [
|
|
341
|
-
TransportOverrideOf<mi>,
|
|
342
|
-
] extends [never]
|
|
343
|
-
? defaultTransport
|
|
344
|
-
: TransportOverrideOf<mi>
|
|
345
|
-
|
|
346
|
-
const reservedMppxKeyValues = [
|
|
347
|
-
'challenge',
|
|
348
|
-
'compose',
|
|
349
|
-
'methods',
|
|
350
|
-
'on',
|
|
351
|
-
'onChallengeCreated',
|
|
352
|
-
'onPaymentFailed',
|
|
353
|
-
'onPaymentSuccess',
|
|
354
|
-
'realm',
|
|
355
|
-
'broadcastCredential',
|
|
356
|
-
'transport',
|
|
357
|
-
'validateCredential',
|
|
358
|
-
'verifyCredential',
|
|
359
|
-
] as const
|
|
360
|
-
|
|
361
|
-
/** Public instance keys that payment method names and shorthand intents cannot shadow. */
|
|
362
|
-
export type ReservedKey = (typeof reservedMppxKeyValues)[number]
|
|
363
|
-
|
|
364
|
-
/** Public instance keys that payment method names and shorthand intents cannot shadow. */
|
|
365
|
-
export const reservedMppxKeys: ReadonlySet<ReservedKey> = new Set(reservedMppxKeyValues)
|
|
366
|
-
|
|
367
|
-
/** True when exactly one method has the given intent (no name collision). */
|
|
368
|
-
type IsUniqueIntent<methods extends readonly Method.AnyServer[], intent extends string> =
|
|
369
|
-
Extract<methods[number], { intent: intent }> extends infer M
|
|
370
|
-
? M extends M
|
|
371
|
-
? [Exclude<Extract<methods[number], { intent: intent }>, M>] extends [never]
|
|
372
|
-
? true
|
|
373
|
-
: false
|
|
374
|
-
: never
|
|
375
|
-
: never
|
|
376
|
-
|
|
377
|
-
/** Shorthand intent handlers. Single-method intents get direct handlers;
|
|
378
|
-
* multi-method intents get an implicit compose wrapper. */
|
|
379
|
-
type IntentHandlers<
|
|
380
|
-
methods extends readonly Method.AnyServer[],
|
|
381
|
-
transport extends Transport.AnyTransport,
|
|
382
|
-
> = {
|
|
383
|
-
[intent in methods[number]['intent'] as intent extends ReservedKey
|
|
384
|
-
? never
|
|
385
|
-
: intent]: IsUniqueIntent<methods, intent> extends true
|
|
386
|
-
? MethodFn<
|
|
387
|
-
Extract<methods[number], { intent: intent }>,
|
|
388
|
-
EffectiveTransportOf<Extract<methods[number], { intent: intent }>, transport>,
|
|
389
|
-
NonNullable<Extract<methods[number], { intent: intent }>['defaults']>
|
|
390
|
-
> &
|
|
391
|
-
MethodExtensions<Extract<methods[number], { intent: intent }>>
|
|
392
|
-
: MultiMethodFn<Extract<methods[number], { intent: intent }>, transport>
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/** Handler for an intent with multiple methods - takes the intersection of all methods' options. */
|
|
396
|
-
type MultiMethodFn<methods extends Method.AnyServer, transport extends Transport.AnyTransport> = (
|
|
397
|
-
options: UnionToIntersection<MultiMethodFnDistribute<methods>>,
|
|
398
|
-
) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>
|
|
399
|
-
|
|
400
|
-
type MultiMethodFnDistribute<M> =
|
|
401
|
-
M extends Method.Server<infer method, infer defaults, any, any, any>
|
|
402
|
-
? MethodFn.Options<method, NonNullable<defaults>>
|
|
403
|
-
: never
|
|
404
|
-
|
|
405
|
-
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void
|
|
406
|
-
? I
|
|
407
|
-
: never
|
|
408
|
-
|
|
409
|
-
/** Nested handlers: `mppx.tempo.charge(...)`, grouped by method name then intent. */
|
|
410
|
-
type PublicAlias<method extends Method.AnyServer> = method extends {
|
|
411
|
-
alias?: infer alias
|
|
412
|
-
}
|
|
413
|
-
? Exclude<alias, undefined> extends infer definedAlias
|
|
414
|
-
? definedAlias extends string
|
|
415
|
-
? string extends definedAlias
|
|
416
|
-
? never
|
|
417
|
-
: definedAlias
|
|
418
|
-
: never
|
|
419
|
-
: never
|
|
420
|
-
: never
|
|
421
|
-
|
|
422
|
-
type PublicIntent<method extends Method.AnyServer> = [PublicAlias<method>] extends [never]
|
|
423
|
-
? method extends { intent: infer intent extends string }
|
|
424
|
-
? intent
|
|
425
|
-
: never
|
|
426
|
-
: PublicAlias<method>
|
|
427
|
-
|
|
428
|
-
type NestedHandlers<
|
|
429
|
-
methods extends readonly Method.AnyServer[],
|
|
430
|
-
transport extends Transport.AnyTransport,
|
|
431
|
-
> = {
|
|
432
|
-
[name in methods[number]['name'] as name extends ReservedKey ? never : name]: {
|
|
433
|
-
[mi in Extract<methods[number], { name: name }> as PublicIntent<mi>]: MethodFn<
|
|
434
|
-
mi,
|
|
435
|
-
EffectiveTransportOf<mi, transport>,
|
|
436
|
-
NonNullable<mi['defaults']>
|
|
437
|
-
> &
|
|
438
|
-
MethodExtensions<mi> & { _method: mi }
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
type Handlers<
|
|
443
|
-
methods extends readonly Method.AnyServer[],
|
|
444
|
-
transport extends Transport.AnyTransport,
|
|
445
|
-
> = {
|
|
446
|
-
[mi in methods[number] as `${mi['name']}/${mi['intent']}`]: MethodFn<
|
|
447
|
-
mi,
|
|
448
|
-
EffectiveTransportOf<mi, transport>,
|
|
449
|
-
NonNullable<mi['defaults']>
|
|
450
|
-
> &
|
|
451
|
-
MethodExtensions<mi>
|
|
452
|
-
} & IntentHandlers<methods, transport> &
|
|
453
|
-
NestedHandlers<methods, transport> & {
|
|
454
|
-
[mi in methods[number] as PublicAlias<mi> extends string
|
|
455
|
-
? `${mi['name']}/${PublicAlias<mi>}`
|
|
456
|
-
: never]: MethodFn<mi, EffectiveTransportOf<mi, transport>, NonNullable<mi['defaults']>> &
|
|
457
|
-
MethodExtensions<mi>
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
type MethodExtensions<method extends Method.AnyServer> = method extends {
|
|
461
|
-
extensions?: (infer extensions) | undefined
|
|
462
|
-
}
|
|
463
|
-
? NonNullable<extensions> extends object
|
|
464
|
-
? NonNullable<extensions>
|
|
465
|
-
: {}
|
|
466
|
-
: {}
|
|
467
|
-
|
|
468
|
-
/** Nested challenge generators: `mppx.challenge.tempo.charge(...)`. */
|
|
469
|
-
type ChallengeHandlers<methods extends readonly Method.AnyServer[]> = {
|
|
470
|
-
[name in methods[number]['name']]: {
|
|
471
|
-
[mi in Extract<methods[number], { name: name }> as PublicIntent<mi>]: ChallengeFn<
|
|
472
|
-
mi,
|
|
473
|
-
NonNullable<mi['defaults']>
|
|
474
|
-
>
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/** A function that generates a Challenge object from intent options. */
|
|
479
|
-
type ChallengeFn<method extends Method.Method, defaults extends Record<string, unknown>> = (
|
|
480
|
-
options: MethodFn.Options<method, defaults>,
|
|
481
|
-
) => Promise<Challenge.Challenge>
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Creates a server-side payment handler from methods.
|
|
485
|
-
*
|
|
486
|
-
* It is highly recommended to set a `secretKey` to bind challenges to their contents,
|
|
487
|
-
* and allow the server to verify that incoming credentials match challenges it issued.
|
|
488
|
-
*
|
|
489
|
-
* @example
|
|
490
|
-
* ```ts
|
|
491
|
-
* import { Mppx, tempo } from 'mppx/server'
|
|
492
|
-
*
|
|
493
|
-
* const payment = Mppx.create({
|
|
494
|
-
* methods: [tempo()],
|
|
495
|
-
* secretKey: process.env.PAYMENT_SECRET_KEY,
|
|
496
|
-
* })
|
|
497
|
-
* ```
|
|
498
|
-
*/
|
|
499
|
-
export function create<
|
|
500
|
-
const methods extends Methods,
|
|
501
|
-
const transport extends Transport.AnyTransport = Transport.Http,
|
|
502
|
-
>(config: create.Config<methods, transport>): Mppx<methods, transport> {
|
|
503
|
-
const {
|
|
504
|
-
attestation,
|
|
505
|
-
realm = Env.get('realm'),
|
|
506
|
-
selectOffers,
|
|
507
|
-
secretKey = Env.get('secretKey'),
|
|
508
|
-
transport = Transport.http() as transport,
|
|
509
|
-
} = config
|
|
510
|
-
|
|
511
|
-
if (!secretKey) {
|
|
512
|
-
throw new Error(
|
|
513
|
-
'Missing secret key. Set the MPP_SECRET_KEY environment variable or pass `secretKey` to Mppx.create().',
|
|
514
|
-
)
|
|
515
|
-
}
|
|
516
|
-
assertSecretKey(secretKey)
|
|
517
|
-
|
|
518
|
-
const methods = config.methods.flat() as unknown as FlattenMethods<methods>
|
|
519
|
-
const serverEvents = createServerEventDispatcher<FlattenMethods<methods>, transport>()
|
|
520
|
-
const verifyAttestation = attestation
|
|
521
|
-
? createAttestationGuard(attestation as Attestation.VerifierMap)
|
|
522
|
-
: undefined
|
|
523
|
-
|
|
524
|
-
const handlers: Record<string, unknown> = {}
|
|
525
|
-
const intentCount: Record<string, number> = {}
|
|
526
|
-
|
|
527
|
-
for (const mi of methods) {
|
|
528
|
-
intentCount[mi.intent] = (intentCount[mi.intent] ?? 0) + 1
|
|
529
|
-
if (mi.onPaymentSuccess) {
|
|
530
|
-
serverEvents.on('payment.success', (async (ctx: PaymentSuccessContext) => {
|
|
531
|
-
if (ctx.method.name === mi.name && ctx.method.intent === mi.intent) {
|
|
532
|
-
await mi.onPaymentSuccess({
|
|
533
|
-
input: ctx.input,
|
|
534
|
-
receipt: ctx.receipt,
|
|
535
|
-
request: ctx.request,
|
|
536
|
-
})
|
|
537
|
-
}
|
|
538
|
-
}) as never)
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
assertNoReservedMppxKeys(methods as readonly Method.AnyServer[])
|
|
542
|
-
|
|
543
|
-
for (const mi of methods) {
|
|
544
|
-
const fn = createMethodFn({
|
|
545
|
-
...(verifyAttestation && { attest: verifyAttestation }),
|
|
546
|
-
authorize: mi.authorize as never,
|
|
547
|
-
defaults: mi.defaults,
|
|
548
|
-
method: mi,
|
|
549
|
-
realm,
|
|
550
|
-
events: serverEvents as never,
|
|
551
|
-
preflight: mi.preflight as never,
|
|
552
|
-
request: mi.request as never,
|
|
553
|
-
respond: mi.respond as never,
|
|
554
|
-
broadcast: mi.broadcast as never,
|
|
555
|
-
secretKey,
|
|
556
|
-
stableBinding: mi.stableBinding as never,
|
|
557
|
-
transport: (mi.transport ?? transport) as never,
|
|
558
|
-
validate: mi.validate as never,
|
|
559
|
-
verify: mi.verify as never,
|
|
560
|
-
})
|
|
561
|
-
const wireKey = `${mi.name}/${mi.intent}`
|
|
562
|
-
const aliasKey = mi.alias ? `${mi.name}/${mi.alias}` : undefined
|
|
563
|
-
if (mi.extensions) Object.assign(fn, mi.extensions)
|
|
564
|
-
if (!aliasKey || !handlers[wireKey]) handlers[wireKey] = fn
|
|
565
|
-
if (aliasKey) handlers[aliasKey] = fn
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Set shorthand intent key: single method gets direct handler,
|
|
569
|
-
// multiple methods get an implicit compose wrapper.
|
|
570
|
-
const processedIntents = new Set<string>()
|
|
571
|
-
for (const mi of methods) {
|
|
572
|
-
if (processedIntents.has(mi.intent)) continue
|
|
573
|
-
processedIntents.add(mi.intent)
|
|
574
|
-
|
|
575
|
-
if (intentCount[mi.intent] === 1) {
|
|
576
|
-
handlers[mi.intent] = handlers[`${mi.name}/${mi.intent}`]
|
|
577
|
-
} else {
|
|
578
|
-
const intentMethods = (methods as readonly Method.AnyServer[]).filter(
|
|
579
|
-
(m) => m.intent === mi.intent,
|
|
580
|
-
)
|
|
581
|
-
handlers[mi.intent] = (options: Record<string, unknown>) => {
|
|
582
|
-
const configured = intentMethods.map((m) => {
|
|
583
|
-
const key = `${m.name}/${m.intent}`
|
|
584
|
-
const handlerFn = handlers[key] as AnyMethodFn
|
|
585
|
-
return handlerFn(options)
|
|
586
|
-
})
|
|
587
|
-
return composeHandlers(
|
|
588
|
-
configured as ConfiguredHandler[],
|
|
589
|
-
undefined,
|
|
590
|
-
selectOffers as SelectOffers<readonly Method.AnyServer[]> | undefined,
|
|
591
|
-
)
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Build nested handlers: mppx.tempo.charge(...)
|
|
597
|
-
for (const mi of methods) {
|
|
598
|
-
if (!handlers[mi.name]) handlers[mi.name] = {}
|
|
599
|
-
const key = mi.alias ? `${mi.name}/${mi.alias}` : `${mi.name}/${mi.intent}`
|
|
600
|
-
const fn = handlers[key] as AnyMethodFn & { _method?: Method.AnyServer }
|
|
601
|
-
fn._method = mi
|
|
602
|
-
;(handlers[mi.name] as Record<string, unknown>)[mi.alias ?? mi.intent] = fn
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Build challenge generators: mppx.challenge.tempo.charge(...)
|
|
606
|
-
const challengeHandlers: Record<string, Record<string, unknown>> = {}
|
|
607
|
-
for (const mi of methods) {
|
|
608
|
-
if (!challengeHandlers[mi.name]) challengeHandlers[mi.name] = {}
|
|
609
|
-
challengeHandlers[mi.name]![mi.alias ?? mi.intent] = createChallengeFn({
|
|
610
|
-
defaults: mi.defaults,
|
|
611
|
-
method: mi,
|
|
612
|
-
realm,
|
|
613
|
-
request: mi.request as never,
|
|
614
|
-
secretKey,
|
|
615
|
-
})
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
async function prepareStandaloneCredential(
|
|
619
|
-
input: string | Credential.Credential,
|
|
620
|
-
options: VerifyCredentialOptions | undefined,
|
|
621
|
-
parameters: { emitFailures?: boolean | undefined; requireValidate?: boolean | undefined } = {},
|
|
622
|
-
) {
|
|
623
|
-
const credential = hydrateCredentialMeta(
|
|
624
|
-
typeof input === 'string' ? Credential.deserialize(input) : input,
|
|
625
|
-
)
|
|
626
|
-
|
|
627
|
-
const emitFailures = parameters.emitFailures === true
|
|
628
|
-
const { method: credMethod, intent: credIntent } = credential.challenge
|
|
629
|
-
const methodCandidates = (methods as readonly Method.AnyServer[]).filter(
|
|
630
|
-
(m) => m.name === credMethod && m.intent === credIntent,
|
|
631
|
-
)
|
|
632
|
-
const mi = methodCandidates[0]
|
|
633
|
-
const eventMethod =
|
|
634
|
-
mi ?? ({ intent: credIntent, name: credMethod } satisfies ServerMethodDescriptor)
|
|
635
|
-
|
|
636
|
-
const emitStandalonePaymentFailed = async (failure: {
|
|
637
|
-
challenge: Challenge.Challenge
|
|
638
|
-
credential: Credential.Credential | null
|
|
639
|
-
error: Errors.PaymentError
|
|
640
|
-
request: Record<string, unknown>
|
|
641
|
-
submittedChallenge?: Challenge.Challenge | undefined
|
|
642
|
-
}) => {
|
|
643
|
-
if (!emitFailures) return
|
|
644
|
-
await serverEvents.emit(
|
|
645
|
-
'payment.failed',
|
|
646
|
-
createPaymentFailedContext({
|
|
647
|
-
capturedRequest: options?.capturedRequest,
|
|
648
|
-
challenge: failure.challenge,
|
|
649
|
-
credential: failure.credential,
|
|
650
|
-
error: failure.error,
|
|
651
|
-
method: eventMethod,
|
|
652
|
-
request: failure.request,
|
|
653
|
-
submittedChallenge: failure.submittedChallenge,
|
|
654
|
-
}) as never,
|
|
655
|
-
)
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const fail = async (
|
|
659
|
-
error: Errors.PaymentError,
|
|
660
|
-
failure: {
|
|
661
|
-
credential?: Credential.Credential | null | undefined
|
|
662
|
-
request?: Record<string, unknown> | undefined
|
|
663
|
-
submittedChallenge?: Challenge.Challenge | undefined
|
|
664
|
-
} = {},
|
|
665
|
-
thrown: unknown = error,
|
|
666
|
-
): Promise<never> => {
|
|
667
|
-
await emitStandalonePaymentFailed({
|
|
668
|
-
challenge: credential.challenge,
|
|
669
|
-
credential: failure.credential ?? credential,
|
|
670
|
-
error,
|
|
671
|
-
request: failure.request ?? (credential.challenge.request as Record<string, unknown>),
|
|
672
|
-
submittedChallenge: failure.submittedChallenge ?? credential.challenge,
|
|
673
|
-
})
|
|
674
|
-
throw thrown
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
if (!mi) {
|
|
678
|
-
await fail(
|
|
679
|
-
new Errors.InvalidChallengeError({
|
|
680
|
-
id: credential.challenge.id,
|
|
681
|
-
reason: `no registered method for ${credMethod}/${credIntent}`,
|
|
682
|
-
}),
|
|
683
|
-
)
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (parameters.requireValidate && !mi.validate)
|
|
687
|
-
await fail(
|
|
688
|
-
new Errors.VerificationFailedError({
|
|
689
|
-
details: { intent: credIntent, method: credMethod },
|
|
690
|
-
reason: `${credMethod}/${credIntent} does not support non-mutating credential validation`,
|
|
691
|
-
}),
|
|
692
|
-
)
|
|
693
|
-
|
|
694
|
-
if (!Challenge.verify(credential.challenge, { secretKey: secretKey! })) {
|
|
695
|
-
await fail(
|
|
696
|
-
new Errors.InvalidChallengeError({
|
|
697
|
-
id: credential.challenge.id,
|
|
698
|
-
reason: 'challenge was not issued by this server',
|
|
699
|
-
}),
|
|
700
|
-
)
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
try {
|
|
704
|
-
Expires.assert(credential.challenge.expires, credential.challenge.id)
|
|
705
|
-
} catch (e) {
|
|
706
|
-
if (e instanceof Errors.PaymentError) await fail(e)
|
|
707
|
-
throw e
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
let parsedCredential!: Credential.Credential
|
|
711
|
-
try {
|
|
712
|
-
parsedCredential = withParsedCredentialPayload(
|
|
713
|
-
credential,
|
|
714
|
-
mi.schema.credential.payload.parse(credential.payload),
|
|
715
|
-
)
|
|
716
|
-
} catch (e) {
|
|
717
|
-
await fail(new Errors.InvalidPayloadError(), {}, e)
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
const expectedMeta = Scope.merge({ meta: options?.meta, scope: options?.scope })
|
|
721
|
-
|
|
722
|
-
if (options?.scope !== undefined && Scope.read(credential.challenge.meta) !== options.scope) {
|
|
723
|
-
await fail(
|
|
724
|
-
new Errors.InvalidChallengeError({
|
|
725
|
-
id: credential.challenge.id,
|
|
726
|
-
reason: "credential scope does not match this route's requirements",
|
|
727
|
-
}),
|
|
728
|
-
{ credential: parsedCredential },
|
|
729
|
-
)
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
const shouldValidateRoute =
|
|
733
|
-
options?.capturedRequest !== undefined ||
|
|
734
|
-
options?.meta !== undefined ||
|
|
735
|
-
options?.realm !== undefined ||
|
|
736
|
-
options?.request !== undefined
|
|
737
|
-
const expectedRealm =
|
|
738
|
-
options?.realm ??
|
|
739
|
-
realm ??
|
|
740
|
-
(options?.capturedRequest === undefined ? credential.challenge.realm : undefined)
|
|
741
|
-
|
|
742
|
-
let parsedRequest = credential.challenge.request as Record<string, unknown>
|
|
743
|
-
let request: z.input<typeof mi.schema.request>
|
|
744
|
-
try {
|
|
745
|
-
request = shouldValidateRoute
|
|
746
|
-
? await resolveRouteChallenge({
|
|
747
|
-
capturedRequest: options?.capturedRequest,
|
|
748
|
-
credential: parsedCredential,
|
|
749
|
-
defaults: mi.defaults,
|
|
750
|
-
expires: credential.challenge.expires,
|
|
751
|
-
meta: expectedMeta,
|
|
752
|
-
method: mi,
|
|
753
|
-
realm: expectedRealm,
|
|
754
|
-
request: mi.request as never,
|
|
755
|
-
routeRequest: options?.request ?? {},
|
|
756
|
-
secretKey: secretKey!,
|
|
757
|
-
}).then((resolved) => {
|
|
758
|
-
const mismatch = getChallengeBindingMismatch(
|
|
759
|
-
resolved.challenge,
|
|
760
|
-
credential.challenge,
|
|
761
|
-
mi.stableBinding as never,
|
|
762
|
-
)
|
|
763
|
-
if (mismatch)
|
|
764
|
-
throw new Errors.InvalidChallengeError({
|
|
765
|
-
id: credential.challenge.id,
|
|
766
|
-
reason: `credential ${mismatch} does not match this route's requirements`,
|
|
767
|
-
})
|
|
768
|
-
|
|
769
|
-
parsedRequest = resolved.parsedRequest
|
|
770
|
-
return resolved.request as z.input<typeof mi.schema.request>
|
|
771
|
-
})
|
|
772
|
-
: (credential.challenge.request as z.input<typeof mi.schema.request>)
|
|
773
|
-
} catch (e) {
|
|
774
|
-
if (e instanceof Errors.PaymentError)
|
|
775
|
-
await fail(e, {
|
|
776
|
-
credential: parsedCredential,
|
|
777
|
-
request: credential.challenge.request as Record<string, unknown>,
|
|
778
|
-
})
|
|
779
|
-
throw e
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
const envelope = options?.capturedRequest
|
|
783
|
-
? ({
|
|
784
|
-
capturedRequest: options.capturedRequest,
|
|
785
|
-
challenge: credential.challenge,
|
|
786
|
-
credential: parsedCredential,
|
|
787
|
-
request: parsedRequest,
|
|
788
|
-
} as Method.VerifiedChallengeEnvelope)
|
|
789
|
-
: undefined
|
|
790
|
-
|
|
791
|
-
return {
|
|
792
|
-
credential,
|
|
793
|
-
envelope,
|
|
794
|
-
eventMethod,
|
|
795
|
-
method: mi,
|
|
796
|
-
parsedCredential,
|
|
797
|
-
parsedRequest,
|
|
798
|
-
request,
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
async function validateCredentialFn(
|
|
803
|
-
input: string | Credential.Credential,
|
|
804
|
-
options?: VerifyCredentialOptions,
|
|
805
|
-
): Promise<Method.Validation> {
|
|
806
|
-
const prepared = await prepareStandaloneCredential(input, options, { requireValidate: true })
|
|
807
|
-
return prepared.method.validate!({
|
|
808
|
-
credential: prepared.parsedCredential,
|
|
809
|
-
envelope: prepared.envelope,
|
|
810
|
-
request: prepared.request,
|
|
811
|
-
} as never)
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// broadcastCredential: single-call end-to-end validation and broadcast
|
|
815
|
-
async function broadcastCredentialFn(
|
|
816
|
-
input: string | Credential.Credential,
|
|
817
|
-
options?: VerifyCredentialOptions,
|
|
818
|
-
): Promise<Receipt.Receipt> {
|
|
819
|
-
const prepared = await prepareStandaloneCredential(input, options, { emitFailures: true })
|
|
820
|
-
const { method: mi, parsedCredential, parsedRequest, request, envelope } = prepared
|
|
821
|
-
|
|
822
|
-
const emitStandalonePaymentFailed = async (parameters: {
|
|
823
|
-
challenge: Challenge.Challenge
|
|
824
|
-
credential: Credential.Credential | null
|
|
825
|
-
error: Errors.PaymentError
|
|
826
|
-
request: Record<string, unknown>
|
|
827
|
-
submittedChallenge?: Challenge.Challenge | undefined
|
|
828
|
-
}) => {
|
|
829
|
-
await serverEvents.emit(
|
|
830
|
-
'payment.failed',
|
|
831
|
-
createPaymentFailedContext({
|
|
832
|
-
capturedRequest: options?.capturedRequest,
|
|
833
|
-
challenge: parameters.challenge,
|
|
834
|
-
credential: parameters.credential,
|
|
835
|
-
error: parameters.error,
|
|
836
|
-
method: prepared.eventMethod,
|
|
837
|
-
request: parameters.request,
|
|
838
|
-
submittedChallenge: parameters.submittedChallenge,
|
|
839
|
-
}) as never,
|
|
840
|
-
)
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
let receipt: Receipt.Receipt
|
|
844
|
-
try {
|
|
845
|
-
if (mi.broadcast && mi.validate)
|
|
846
|
-
await mi.validate({ credential: parsedCredential, envelope, request } as never)
|
|
847
|
-
const broadcast = mi.broadcast ?? mi.verify
|
|
848
|
-
receipt = await broadcast({ credential: parsedCredential, envelope, request } as never)
|
|
849
|
-
} catch (e) {
|
|
850
|
-
const error = e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError()
|
|
851
|
-
await emitStandalonePaymentFailed({
|
|
852
|
-
challenge: prepared.credential.challenge,
|
|
853
|
-
credential: parsedCredential,
|
|
854
|
-
error,
|
|
855
|
-
request: parsedRequest,
|
|
856
|
-
submittedChallenge: prepared.credential.challenge,
|
|
857
|
-
})
|
|
858
|
-
throw e
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
await serverEvents.emit(
|
|
862
|
-
'payment.success',
|
|
863
|
-
createPaymentSuccessContext({
|
|
864
|
-
capturedRequest: options?.capturedRequest,
|
|
865
|
-
challenge: prepared.credential.challenge,
|
|
866
|
-
credential: parsedCredential,
|
|
867
|
-
envelope,
|
|
868
|
-
method: mi,
|
|
869
|
-
receipt,
|
|
870
|
-
request: parsedRequest,
|
|
871
|
-
}) as never,
|
|
872
|
-
)
|
|
873
|
-
|
|
874
|
-
return receipt
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
const verifyCredentialFn = broadcastCredentialFn
|
|
878
|
-
|
|
879
|
-
function composeFn(
|
|
880
|
-
...entries: readonly [
|
|
881
|
-
Method.AnyServer | AnyMethodFnWithMethod | string,
|
|
882
|
-
Record<string, unknown>,
|
|
883
|
-
][]
|
|
884
|
-
) {
|
|
885
|
-
if (transport.name !== 'http') throw new Error('compose() only supports HTTP transport')
|
|
886
|
-
if (entries.length === 0) throw new Error('compose() requires at least one entry')
|
|
887
|
-
const configured = entries.map(([methodOrKey, options]) => {
|
|
888
|
-
const key =
|
|
889
|
-
typeof methodOrKey === 'string'
|
|
890
|
-
? methodOrKey
|
|
891
|
-
: typeof methodOrKey === 'function' && '_method' in methodOrKey
|
|
892
|
-
? `${(methodOrKey._method as Method.AnyServer).name}/${(methodOrKey._method as Method.AnyServer).alias ?? (methodOrKey._method as Method.AnyServer).intent}`
|
|
893
|
-
: `${(methodOrKey as Method.AnyServer).name}/${(methodOrKey as Method.AnyServer).alias ?? (methodOrKey as Method.AnyServer).intent}`
|
|
894
|
-
const handlerFn = handlers[key] as AnyMethodFn | undefined
|
|
895
|
-
if (!handlerFn)
|
|
896
|
-
throw new Error(`No handler for "${key}". Is this method in your methods array?`)
|
|
897
|
-
return handlerFn(options)
|
|
898
|
-
})
|
|
899
|
-
return composeHandlers(
|
|
900
|
-
configured as ConfiguredHandler[],
|
|
901
|
-
undefined,
|
|
902
|
-
selectOffers as SelectOffers<readonly Method.AnyServer[]> | undefined,
|
|
903
|
-
)
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
function onChallengeCreated(
|
|
907
|
-
handler: ServerEventHandler<FlattenMethods<methods>, transport, 'challenge.created'>,
|
|
908
|
-
) {
|
|
909
|
-
return serverEvents.on('challenge.created', handler)
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
function onPaymentFailed(
|
|
913
|
-
handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.failed'>,
|
|
914
|
-
) {
|
|
915
|
-
return serverEvents.on('payment.failed', handler)
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
function onPaymentSuccess(
|
|
919
|
-
handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.success'>,
|
|
920
|
-
) {
|
|
921
|
-
return serverEvents.on('payment.success', handler)
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
return {
|
|
925
|
-
methods,
|
|
926
|
-
challenge: challengeHandlers,
|
|
927
|
-
compose: composeFn,
|
|
928
|
-
on: serverEvents.on,
|
|
929
|
-
onChallengeCreated,
|
|
930
|
-
onPaymentFailed,
|
|
931
|
-
onPaymentSuccess,
|
|
932
|
-
realm: realm as string | undefined,
|
|
933
|
-
broadcastCredential: broadcastCredentialFn,
|
|
934
|
-
transport,
|
|
935
|
-
validateCredential: validateCredentialFn,
|
|
936
|
-
verifyCredential: verifyCredentialFn,
|
|
937
|
-
...handlers,
|
|
938
|
-
} as never
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
function assertSecretKey(secretKey: string) {
|
|
942
|
-
const byteLength = new TextEncoder().encode(secretKey).byteLength
|
|
943
|
-
if (byteLength >= minimumSecretKeyBytes) return
|
|
944
|
-
|
|
945
|
-
throw new Error(
|
|
946
|
-
`Secret key must be at least ${minimumSecretKeyBytes} bytes. Generate one with \`${secretKeyGenerationCommand}\` and set MPP_SECRET_KEY or pass it to Mppx.create().`,
|
|
947
|
-
)
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
export declare namespace create {
|
|
951
|
-
type Config<
|
|
952
|
-
methods extends Methods = Methods,
|
|
953
|
-
transport extends Transport.AnyTransport = Transport.Http,
|
|
954
|
-
> = {
|
|
955
|
-
/** Request-attestation verifiers required before handling an HTTP payment request. */
|
|
956
|
-
attestation?: transport extends Transport.Http ? Attestation.VerifierMap | undefined : never
|
|
957
|
-
/** Array of configured methods. @example [tempo()] */
|
|
958
|
-
methods: methods
|
|
959
|
-
/** Server realm (e.g., hostname). Resolution order: explicit value > env vars (`MPP_REALM`, `FLY_APP_NAME`, `VERCEL_URL`, etc.) > request URL hostname > `"MPP Payment"`. */
|
|
960
|
-
realm?: string | undefined
|
|
961
|
-
/** Secret key for HMAC-bound challenge IDs for stateless verification. Must be at least 32 bytes. Auto-detected from `MPP_SECRET_KEY` environment variable. */
|
|
962
|
-
secretKey?: string | undefined
|
|
963
|
-
/** Selects a subset of composed HTTP offers before challenges are issued. Successfully matched credential dispatch bypasses this hook. */
|
|
964
|
-
selectOffers?: transport extends Transport.Http
|
|
965
|
-
? SelectOffers<FlattenMethods<methods>> | undefined
|
|
966
|
-
: never
|
|
967
|
-
/** Transport to use. @default Transport.http() */
|
|
968
|
-
transport?: transport | undefined
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
function createMethodFn<
|
|
973
|
-
method extends Method.Method,
|
|
974
|
-
transport extends Transport.AnyTransport,
|
|
975
|
-
defaults extends Record<string, unknown>,
|
|
976
|
-
>(
|
|
977
|
-
parameters: createMethodFn.Parameters<method, transport, defaults>,
|
|
978
|
-
): createMethodFn.ReturnType<method, transport, defaults>
|
|
979
|
-
// biome-ignore lint/correctness/noUnusedVariables: _
|
|
980
|
-
function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.ReturnType {
|
|
981
|
-
const {
|
|
982
|
-
attest,
|
|
983
|
-
authorize,
|
|
984
|
-
defaults,
|
|
985
|
-
events,
|
|
986
|
-
method,
|
|
987
|
-
preflight,
|
|
988
|
-
realm,
|
|
989
|
-
respond,
|
|
990
|
-
secretKey,
|
|
991
|
-
broadcast,
|
|
992
|
-
stableBinding,
|
|
993
|
-
transport,
|
|
994
|
-
validate,
|
|
995
|
-
verify,
|
|
996
|
-
} = parameters
|
|
997
|
-
|
|
998
|
-
return (options) => {
|
|
999
|
-
const { description, meta, scope, ...rest } = options
|
|
1000
|
-
const staticMeta = Scope.merge({ meta, scope })
|
|
1001
|
-
const internal = {
|
|
1002
|
-
...method,
|
|
1003
|
-
...defaults,
|
|
1004
|
-
...options,
|
|
1005
|
-
...(staticMeta !== undefined ? { meta: staticMeta } : {}),
|
|
1006
|
-
name: method.name,
|
|
1007
|
-
intent: method.intent,
|
|
1008
|
-
html: method.html,
|
|
1009
|
-
_canonicalRequest: PaymentRequest.fromMethod(method, { ...defaults, ...rest }),
|
|
1010
|
-
_stableBinding: stableBinding as never,
|
|
1011
|
-
} as unknown as ConfiguredHandler['_internal']
|
|
1012
|
-
Object.defineProperty(internal, '_method', { value: method })
|
|
1013
|
-
|
|
1014
|
-
const handler = async (input: Transport.InputOf): Promise<MethodFn.Response> => {
|
|
1015
|
-
if (attest && input instanceof globalThis.Request) {
|
|
1016
|
-
const response = await attest(input)
|
|
1017
|
-
if (response)
|
|
1018
|
-
return {
|
|
1019
|
-
status: 200,
|
|
1020
|
-
withReceipt() {
|
|
1021
|
-
return response
|
|
1022
|
-
},
|
|
1023
|
-
} as MethodFn.Response
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
if (method.html && isServiceWorkerRequest(input))
|
|
1027
|
-
return {
|
|
1028
|
-
status: 402,
|
|
1029
|
-
challenge: createServiceWorkerResponse(),
|
|
1030
|
-
} as MethodFn.Response<Transport.Http>
|
|
1031
|
-
|
|
1032
|
-
const expires =
|
|
1033
|
-
'expires' in options
|
|
1034
|
-
? normalizeExpires(options.expires as z.DatetimeInput | undefined)
|
|
1035
|
-
: Expires.minutes(5)
|
|
1036
|
-
const capturedRequest = await captureRequest(transport, input)
|
|
1037
|
-
const effectiveMeta =
|
|
1038
|
-
scope === undefined && input instanceof globalThis.Request
|
|
1039
|
-
? Scope.merge({ meta: staticMeta, scope: Scope.get(input) })
|
|
1040
|
-
: staticMeta
|
|
1041
|
-
|
|
1042
|
-
// Extract credential once — getCredential may have side effects (e.g. SSE transports).
|
|
1043
|
-
let [credential, credentialError] = (() => {
|
|
1044
|
-
try {
|
|
1045
|
-
const credential = transport.getCredential(input) as Credential.Credential | null
|
|
1046
|
-
return [credential ? hydrateCredentialMeta(credential) : null, undefined] as const
|
|
1047
|
-
} catch (e) {
|
|
1048
|
-
return [null, e as Error] as const
|
|
1049
|
-
}
|
|
1050
|
-
})()
|
|
1051
|
-
|
|
1052
|
-
if (preflight && input instanceof globalThis.Request) {
|
|
1053
|
-
const response = await preflight({
|
|
1054
|
-
capturedRequest,
|
|
1055
|
-
credential,
|
|
1056
|
-
expires,
|
|
1057
|
-
input,
|
|
1058
|
-
options: { ...defaults, ...rest },
|
|
1059
|
-
realm: realm ?? new URL(input.url).hostname ?? 'MPP Payment',
|
|
1060
|
-
secretKey,
|
|
1061
|
-
} as never)
|
|
1062
|
-
if (response) {
|
|
1063
|
-
if (response.status === 402) return { challenge: response, status: 402 }
|
|
1064
|
-
return {
|
|
1065
|
-
status: 200,
|
|
1066
|
-
withReceipt() {
|
|
1067
|
-
return response
|
|
1068
|
-
},
|
|
1069
|
-
} as MethodFn.Response
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
const emitChallenge = async (parameters: {
|
|
1074
|
-
challenge: Challenge.Challenge
|
|
1075
|
-
credential?: Credential.Credential | null | undefined
|
|
1076
|
-
error?: Errors.PaymentError | undefined
|
|
1077
|
-
html?: Method.Method['html'] | undefined
|
|
1078
|
-
request: Record<string, unknown>
|
|
1079
|
-
}) => {
|
|
1080
|
-
const response = await transport.respondChallenge({
|
|
1081
|
-
challenge: parameters.challenge,
|
|
1082
|
-
input,
|
|
1083
|
-
...(parameters.error && { error: parameters.error }),
|
|
1084
|
-
...(parameters.html && { html: parameters.html }),
|
|
1085
|
-
})
|
|
1086
|
-
if (isIssuedChallengeResponse(response))
|
|
1087
|
-
await events.emit(
|
|
1088
|
-
'challenge.created',
|
|
1089
|
-
createChallengeContext({
|
|
1090
|
-
capturedRequest,
|
|
1091
|
-
challenge: parameters.challenge,
|
|
1092
|
-
credential: parameters.credential,
|
|
1093
|
-
error: parameters.error,
|
|
1094
|
-
input,
|
|
1095
|
-
method,
|
|
1096
|
-
request: parameters.request,
|
|
1097
|
-
}) as never,
|
|
1098
|
-
)
|
|
1099
|
-
return response
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
const emitPaymentFailed = async (parameters: {
|
|
1103
|
-
challenge: Challenge.Challenge
|
|
1104
|
-
credential: Credential.Credential | null
|
|
1105
|
-
error: Errors.PaymentError
|
|
1106
|
-
request: Record<string, unknown>
|
|
1107
|
-
retryChallenge?: Challenge.Challenge | undefined
|
|
1108
|
-
submittedChallenge?: Challenge.Challenge | undefined
|
|
1109
|
-
}) => {
|
|
1110
|
-
await events.emit(
|
|
1111
|
-
'payment.failed',
|
|
1112
|
-
createPaymentFailedContext({
|
|
1113
|
-
capturedRequest,
|
|
1114
|
-
challenge: parameters.challenge,
|
|
1115
|
-
credential: parameters.credential,
|
|
1116
|
-
error: parameters.error,
|
|
1117
|
-
input,
|
|
1118
|
-
method,
|
|
1119
|
-
request: parameters.request,
|
|
1120
|
-
retryChallenge: parameters.retryChallenge,
|
|
1121
|
-
submittedChallenge: parameters.submittedChallenge,
|
|
1122
|
-
}) as never,
|
|
1123
|
-
)
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
const routeChallenge = await resolveRouteChallenge({
|
|
1127
|
-
capturedRequest,
|
|
1128
|
-
credential,
|
|
1129
|
-
defaults,
|
|
1130
|
-
description,
|
|
1131
|
-
expires,
|
|
1132
|
-
meta: effectiveMeta,
|
|
1133
|
-
method,
|
|
1134
|
-
realm,
|
|
1135
|
-
request: parameters.request,
|
|
1136
|
-
routeRequest: rest,
|
|
1137
|
-
secretKey,
|
|
1138
|
-
}).catch(async (e) => {
|
|
1139
|
-
if (!(e instanceof Errors.PaymentError)) throw e
|
|
1140
|
-
const challenge = createFallbackChallenge({
|
|
1141
|
-
capturedRequest,
|
|
1142
|
-
defaults: defaults ?? {},
|
|
1143
|
-
description,
|
|
1144
|
-
expires,
|
|
1145
|
-
meta: effectiveMeta,
|
|
1146
|
-
method,
|
|
1147
|
-
realm,
|
|
1148
|
-
routeRequest: rest,
|
|
1149
|
-
secretKey,
|
|
1150
|
-
})
|
|
1151
|
-
if (credential)
|
|
1152
|
-
await emitPaymentFailed({
|
|
1153
|
-
challenge,
|
|
1154
|
-
credential,
|
|
1155
|
-
error: e,
|
|
1156
|
-
request: challenge.request,
|
|
1157
|
-
retryChallenge: challenge,
|
|
1158
|
-
submittedChallenge: credential.challenge,
|
|
1159
|
-
})
|
|
1160
|
-
const response = await emitChallenge({
|
|
1161
|
-
challenge,
|
|
1162
|
-
credential,
|
|
1163
|
-
request: challenge.request,
|
|
1164
|
-
error: e,
|
|
1165
|
-
html: method.html,
|
|
1166
|
-
})
|
|
1167
|
-
return { response }
|
|
1168
|
-
})
|
|
1169
|
-
if ('response' in routeChallenge) return { challenge: routeChallenge.response, status: 402 }
|
|
1170
|
-
const { challenge, parsedRequest, request } = routeChallenge
|
|
1171
|
-
internal._canonicalRequest = parsedRequest
|
|
1172
|
-
|
|
1173
|
-
if (credential && transport.bindCredential) {
|
|
1174
|
-
try {
|
|
1175
|
-
credential = hydrateCredentialMeta(
|
|
1176
|
-
(await transport.bindCredential({
|
|
1177
|
-
challenge,
|
|
1178
|
-
credential,
|
|
1179
|
-
input,
|
|
1180
|
-
})) as Credential.Credential,
|
|
1181
|
-
)
|
|
1182
|
-
} catch (e) {
|
|
1183
|
-
credential = null
|
|
1184
|
-
credentialError = e as Error
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// Credential was provided but malformed
|
|
1189
|
-
if (credentialError) {
|
|
1190
|
-
const reason = getSafeCredentialReason(credentialError)
|
|
1191
|
-
const error = new Errors.MalformedCredentialError(reason ? { reason } : {})
|
|
1192
|
-
await emitPaymentFailed({
|
|
1193
|
-
challenge,
|
|
1194
|
-
credential: null,
|
|
1195
|
-
error,
|
|
1196
|
-
request: parsedRequest,
|
|
1197
|
-
retryChallenge: challenge,
|
|
1198
|
-
})
|
|
1199
|
-
const response = await emitChallenge({
|
|
1200
|
-
challenge,
|
|
1201
|
-
credential: null,
|
|
1202
|
-
request: parsedRequest,
|
|
1203
|
-
error,
|
|
1204
|
-
html: method.html,
|
|
1205
|
-
})
|
|
1206
|
-
return { challenge: response, status: 402 }
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
const success = (
|
|
1210
|
-
receiptData: Receipt.Receipt,
|
|
1211
|
-
options: {
|
|
1212
|
-
challengeId?: string | undefined
|
|
1213
|
-
credentialForReceipt?: Credential.Credential | undefined
|
|
1214
|
-
envelopeForReceipt?: Method.VerifiedChallengeEnvelope | undefined
|
|
1215
|
-
managementResponse?: globalThis.Response | undefined
|
|
1216
|
-
} = {},
|
|
1217
|
-
): MethodFn.Response => {
|
|
1218
|
-
const {
|
|
1219
|
-
challengeId = challenge.id,
|
|
1220
|
-
credentialForReceipt = { challenge, payload: {} } as Credential.Credential,
|
|
1221
|
-
envelopeForReceipt,
|
|
1222
|
-
managementResponse,
|
|
1223
|
-
} = options
|
|
1224
|
-
|
|
1225
|
-
return {
|
|
1226
|
-
status: 200,
|
|
1227
|
-
withReceipt<response>(response?: response) {
|
|
1228
|
-
if (managementResponse) {
|
|
1229
|
-
return transport.respondReceipt({
|
|
1230
|
-
challengeId,
|
|
1231
|
-
credential: credentialForReceipt,
|
|
1232
|
-
...(envelopeForReceipt ? { envelope: envelopeForReceipt } : {}),
|
|
1233
|
-
input,
|
|
1234
|
-
receipt: receiptData,
|
|
1235
|
-
response: managementResponse as never,
|
|
1236
|
-
}) as response
|
|
1237
|
-
}
|
|
1238
|
-
if (!response) throw new MissingReceiptResponseError()
|
|
1239
|
-
return transport.respondReceipt({
|
|
1240
|
-
challengeId,
|
|
1241
|
-
credential: credentialForReceipt,
|
|
1242
|
-
...(envelopeForReceipt ? { envelope: envelopeForReceipt } : {}),
|
|
1243
|
-
input,
|
|
1244
|
-
receipt: receiptData,
|
|
1245
|
-
response: response as never,
|
|
1246
|
-
}) as response
|
|
1247
|
-
},
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
// No credential provided—issue challenge
|
|
1252
|
-
if (!credential) {
|
|
1253
|
-
if (authorize && input instanceof globalThis.Request) {
|
|
1254
|
-
try {
|
|
1255
|
-
const authorized = await authorize({
|
|
1256
|
-
challenge,
|
|
1257
|
-
input,
|
|
1258
|
-
request: challenge.request,
|
|
1259
|
-
} as never)
|
|
1260
|
-
if (authorized) {
|
|
1261
|
-
await events.emit(
|
|
1262
|
-
'payment.success',
|
|
1263
|
-
createPaymentSuccessContext({
|
|
1264
|
-
capturedRequest,
|
|
1265
|
-
challenge,
|
|
1266
|
-
input,
|
|
1267
|
-
method,
|
|
1268
|
-
receipt: authorized.receipt,
|
|
1269
|
-
request: parsedRequest,
|
|
1270
|
-
}) as never,
|
|
1271
|
-
)
|
|
1272
|
-
return success(authorized.receipt, {
|
|
1273
|
-
managementResponse: authorized.response,
|
|
1274
|
-
})
|
|
1275
|
-
}
|
|
1276
|
-
} catch (e) {
|
|
1277
|
-
if (!(e instanceof Errors.PaymentError))
|
|
1278
|
-
console.error('mppx: internal authorization error', e)
|
|
1279
|
-
const error =
|
|
1280
|
-
e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError()
|
|
1281
|
-
await emitPaymentFailed({
|
|
1282
|
-
challenge,
|
|
1283
|
-
credential: null,
|
|
1284
|
-
error,
|
|
1285
|
-
request: parsedRequest,
|
|
1286
|
-
retryChallenge: challenge,
|
|
1287
|
-
})
|
|
1288
|
-
const response = await emitChallenge({
|
|
1289
|
-
challenge,
|
|
1290
|
-
request: parsedRequest,
|
|
1291
|
-
error,
|
|
1292
|
-
html: method.html,
|
|
1293
|
-
})
|
|
1294
|
-
return { challenge: response, status: 402 }
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
const error = new Errors.PaymentRequiredError({ description })
|
|
1299
|
-
const response = await emitChallenge({
|
|
1300
|
-
challenge,
|
|
1301
|
-
credential: null,
|
|
1302
|
-
request: parsedRequest,
|
|
1303
|
-
error,
|
|
1304
|
-
html: method.html,
|
|
1305
|
-
})
|
|
1306
|
-
return { challenge: response, status: 402 }
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// ── Tier 1: HMAC provenance check (primary gate) ──────────────────
|
|
1310
|
-
//
|
|
1311
|
-
// Recompute the HMAC-SHA256 over the credential's echoed challenge
|
|
1312
|
-
// parameters (realm|method|intent|request|expires|digest|opaque) and
|
|
1313
|
-
// compare to the echoed `id`. This proves the challenge was issued by
|
|
1314
|
-
// this server with these exact parameters — including opaque/meta,
|
|
1315
|
-
// expires, and the full serialized request blob.
|
|
1316
|
-
//
|
|
1317
|
-
// This is the authoritative binding per §5.1.2.1.1 of the spec
|
|
1318
|
-
// (https://paymentauth.org/draft-httpauth-payment-00.html#section-5.1.2.1.1).
|
|
1319
|
-
// No database lookup is needed; the HMAC is stateless verification.
|
|
1320
|
-
if (!Challenge.verify(credential.challenge, { secretKey })) {
|
|
1321
|
-
const error = new Errors.InvalidChallengeError({
|
|
1322
|
-
id: credential.challenge.id,
|
|
1323
|
-
reason: 'challenge was not issued by this server',
|
|
1324
|
-
})
|
|
1325
|
-
await emitPaymentFailed({
|
|
1326
|
-
challenge,
|
|
1327
|
-
credential,
|
|
1328
|
-
error,
|
|
1329
|
-
request: parsedRequest,
|
|
1330
|
-
retryChallenge: challenge,
|
|
1331
|
-
submittedChallenge: credential.challenge,
|
|
1332
|
-
})
|
|
1333
|
-
const response = await emitChallenge({
|
|
1334
|
-
challenge,
|
|
1335
|
-
credential,
|
|
1336
|
-
request: parsedRequest,
|
|
1337
|
-
error,
|
|
1338
|
-
html: method.html,
|
|
1339
|
-
})
|
|
1340
|
-
return { challenge: response, status: 402 }
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
// ── Tier 2: Pinned field safety net ──────────────────────────────
|
|
1344
|
-
//
|
|
1345
|
-
// The HMAC check above (Tier 1) is the primary gate — it already
|
|
1346
|
-
// covers ALL challenge fields including opaque, digest, and the full
|
|
1347
|
-
// serialized request. So why this second check?
|
|
1348
|
-
//
|
|
1349
|
-
// The `request()` hook can produce credential-dependent output: for
|
|
1350
|
-
// example, `feePayer` may differ between the 402 challenge call (no
|
|
1351
|
-
// credential) and the credential-bearing call. This means the
|
|
1352
|
-
// recomputed challenge here has a different `request` blob — and
|
|
1353
|
-
// thus a different HMAC — than the original challenge the client
|
|
1354
|
-
// echoes back. The HMAC check above verifies the *echoed* challenge
|
|
1355
|
-
// was signed by us, but it cannot verify that the echoed challenge
|
|
1356
|
-
// matches *this route's current configuration* when the request
|
|
1357
|
-
// hook transforms fields between calls.
|
|
1358
|
-
//
|
|
1359
|
-
// This check compares the fields that MUST be stable across both
|
|
1360
|
-
// calls. That includes the economically significant request fields
|
|
1361
|
-
// plus `opaque`, which can carry route-scoping metadata (for example,
|
|
1362
|
-
// sibling route identity) that must not be replayable across handlers.
|
|
1363
|
-
// `expires` still is not pinned here because its default is generated
|
|
1364
|
-
// per invocation, and `digest` is already bound by the echoed HMAC.
|
|
1365
|
-
{
|
|
1366
|
-
const mismatch = getChallengeBindingMismatch(
|
|
1367
|
-
challenge,
|
|
1368
|
-
credential.challenge,
|
|
1369
|
-
stableBinding as never,
|
|
1370
|
-
)
|
|
1371
|
-
if (mismatch) {
|
|
1372
|
-
const error = new Errors.InvalidChallengeError({
|
|
1373
|
-
id: credential.challenge.id,
|
|
1374
|
-
reason: `credential ${mismatch} does not match this route's requirements`,
|
|
1375
|
-
})
|
|
1376
|
-
await emitPaymentFailed({
|
|
1377
|
-
challenge,
|
|
1378
|
-
credential,
|
|
1379
|
-
error,
|
|
1380
|
-
request: parsedRequest,
|
|
1381
|
-
retryChallenge: challenge,
|
|
1382
|
-
submittedChallenge: credential.challenge,
|
|
1383
|
-
})
|
|
1384
|
-
const response = await emitChallenge({
|
|
1385
|
-
challenge,
|
|
1386
|
-
credential,
|
|
1387
|
-
request: parsedRequest,
|
|
1388
|
-
error,
|
|
1389
|
-
html: method.html,
|
|
1390
|
-
})
|
|
1391
|
-
return { challenge: response, status: 402 }
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
// Reject credentials without expires (fail-closed) or with expired timestamp
|
|
1396
|
-
try {
|
|
1397
|
-
Expires.assert(credential.challenge.expires, credential.challenge.id)
|
|
1398
|
-
} catch (error) {
|
|
1399
|
-
await emitPaymentFailed({
|
|
1400
|
-
challenge,
|
|
1401
|
-
credential,
|
|
1402
|
-
error: error as Errors.PaymentError,
|
|
1403
|
-
request: parsedRequest,
|
|
1404
|
-
retryChallenge: challenge,
|
|
1405
|
-
submittedChallenge: credential.challenge,
|
|
1406
|
-
})
|
|
1407
|
-
const response = await emitChallenge({
|
|
1408
|
-
challenge,
|
|
1409
|
-
credential,
|
|
1410
|
-
request: parsedRequest,
|
|
1411
|
-
error: error as Errors.PaymentError,
|
|
1412
|
-
})
|
|
1413
|
-
return { challenge: response, status: 402 }
|
|
1414
|
-
}
|
|
1415
|
-
// Validate payload structure against method schema
|
|
1416
|
-
let parsedCredential: Credential.Credential
|
|
1417
|
-
try {
|
|
1418
|
-
parsedCredential = withParsedCredentialPayload(
|
|
1419
|
-
credential,
|
|
1420
|
-
method.schema.credential.payload.parse(credential.payload),
|
|
1421
|
-
)
|
|
1422
|
-
} catch {
|
|
1423
|
-
const error = new Errors.InvalidPayloadError()
|
|
1424
|
-
await emitPaymentFailed({
|
|
1425
|
-
challenge,
|
|
1426
|
-
credential,
|
|
1427
|
-
error,
|
|
1428
|
-
request: parsedRequest,
|
|
1429
|
-
retryChallenge: challenge,
|
|
1430
|
-
submittedChallenge: credential.challenge,
|
|
1431
|
-
})
|
|
1432
|
-
const response = await emitChallenge({
|
|
1433
|
-
challenge,
|
|
1434
|
-
credential,
|
|
1435
|
-
request: parsedRequest,
|
|
1436
|
-
error,
|
|
1437
|
-
})
|
|
1438
|
-
return { challenge: response, status: 402 }
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
const envelope: Method.VerifiedChallengeEnvelope = Object.freeze({
|
|
1442
|
-
capturedRequest,
|
|
1443
|
-
challenge: credential.challenge,
|
|
1444
|
-
credential: parsedCredential,
|
|
1445
|
-
request: parsedRequest,
|
|
1446
|
-
})
|
|
1447
|
-
|
|
1448
|
-
// User-provided verification (e.g., check signature, submit tx, verify payment).
|
|
1449
|
-
// If verification fails, re-issue the challenge so the client can retry.
|
|
1450
|
-
let receiptData: Receipt.Receipt
|
|
1451
|
-
try {
|
|
1452
|
-
if (broadcast && validate)
|
|
1453
|
-
await validate({ credential: parsedCredential, envelope, request } as never)
|
|
1454
|
-
const broadcastCredential = broadcast ?? verify
|
|
1455
|
-
receiptData = await broadcastCredential({
|
|
1456
|
-
credential: parsedCredential,
|
|
1457
|
-
envelope,
|
|
1458
|
-
request,
|
|
1459
|
-
} as never)
|
|
1460
|
-
} catch (e) {
|
|
1461
|
-
if (!(e instanceof Errors.PaymentError))
|
|
1462
|
-
console.error('mppx: internal verification error', e)
|
|
1463
|
-
const error = e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError()
|
|
1464
|
-
await emitPaymentFailed({
|
|
1465
|
-
challenge,
|
|
1466
|
-
credential: parsedCredential,
|
|
1467
|
-
error,
|
|
1468
|
-
request: parsedRequest,
|
|
1469
|
-
retryChallenge: challenge,
|
|
1470
|
-
submittedChallenge: credential.challenge,
|
|
1471
|
-
})
|
|
1472
|
-
const response = await emitChallenge({
|
|
1473
|
-
challenge,
|
|
1474
|
-
credential: parsedCredential,
|
|
1475
|
-
request: parsedRequest,
|
|
1476
|
-
error,
|
|
1477
|
-
})
|
|
1478
|
-
return { challenge: response, status: 402 }
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
// If the method's `respond` hook returns a Response, it means this
|
|
1482
|
-
// request is a management action (e.g. channel open, voucher POST)
|
|
1483
|
-
// and the user's route handler should NOT run. `withReceipt()` will
|
|
1484
|
-
// return the management response directly. If undefined, `withReceipt()`
|
|
1485
|
-
// expects the caller to pass the user handler's response instead.
|
|
1486
|
-
const managementResponse = respond
|
|
1487
|
-
? await respond({
|
|
1488
|
-
credential: parsedCredential,
|
|
1489
|
-
envelope,
|
|
1490
|
-
input,
|
|
1491
|
-
receipt: receiptData,
|
|
1492
|
-
request,
|
|
1493
|
-
} as never)
|
|
1494
|
-
: undefined
|
|
1495
|
-
|
|
1496
|
-
await events.emit(
|
|
1497
|
-
'payment.success',
|
|
1498
|
-
createPaymentSuccessContext({
|
|
1499
|
-
capturedRequest,
|
|
1500
|
-
challenge: credential.challenge,
|
|
1501
|
-
credential: parsedCredential,
|
|
1502
|
-
envelope,
|
|
1503
|
-
input,
|
|
1504
|
-
method,
|
|
1505
|
-
receipt: receiptData,
|
|
1506
|
-
request: parsedRequest,
|
|
1507
|
-
}) as never,
|
|
1508
|
-
)
|
|
1509
|
-
|
|
1510
|
-
return success(receiptData, {
|
|
1511
|
-
challengeId: credential.challenge.id,
|
|
1512
|
-
credentialForReceipt: parsedCredential,
|
|
1513
|
-
envelopeForReceipt: envelope,
|
|
1514
|
-
managementResponse,
|
|
1515
|
-
})
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
return Object.assign(handler, { _internal: internal })
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
/**
|
|
1523
|
-
* Creates a challenge generator for a single method+intent.
|
|
1524
|
-
* Applies the same defaults and request transform as createMethodFn,
|
|
1525
|
-
* but returns a Challenge object directly instead of a request handler.
|
|
1526
|
-
*/
|
|
1527
|
-
function createChallengeFn(parameters: {
|
|
1528
|
-
defaults?: Record<string, unknown>
|
|
1529
|
-
method: Method.Method
|
|
1530
|
-
realm: string | undefined
|
|
1531
|
-
request?: Method.RequestFn<Method.Method>
|
|
1532
|
-
secretKey: string
|
|
1533
|
-
}): (options: Record<string, unknown>) => Promise<Challenge.Challenge> {
|
|
1534
|
-
const { defaults, method, realm, secretKey } = parameters
|
|
1535
|
-
|
|
1536
|
-
return async (options) => {
|
|
1537
|
-
const { description, meta, scope, ...rest } = options as {
|
|
1538
|
-
description?: string
|
|
1539
|
-
expires?: z.DatetimeInput
|
|
1540
|
-
meta?: Record<string, string>
|
|
1541
|
-
scope?: string
|
|
1542
|
-
[key: string]: unknown
|
|
1543
|
-
}
|
|
1544
|
-
const effectiveMeta = Scope.merge({ meta, scope })
|
|
1545
|
-
const expires =
|
|
1546
|
-
'expires' in options
|
|
1547
|
-
? normalizeExpires(options.expires as z.DatetimeInput | undefined)
|
|
1548
|
-
: Expires.minutes(5)
|
|
1549
|
-
|
|
1550
|
-
return resolveRouteChallenge({
|
|
1551
|
-
defaults,
|
|
1552
|
-
description,
|
|
1553
|
-
expires,
|
|
1554
|
-
meta: effectiveMeta,
|
|
1555
|
-
method,
|
|
1556
|
-
realm,
|
|
1557
|
-
request: parameters.request,
|
|
1558
|
-
routeRequest: rest,
|
|
1559
|
-
secretKey,
|
|
1560
|
-
}).then((resolved) => resolved.challenge)
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
declare namespace createMethodFn {
|
|
1565
|
-
type Parameters<
|
|
1566
|
-
method extends Method.Method = Method.Method,
|
|
1567
|
-
transport extends Transport.AnyTransport = Transport.Http,
|
|
1568
|
-
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
1569
|
-
> = {
|
|
1570
|
-
attest?: (request: globalThis.Request) => Promise<globalThis.Response | undefined>
|
|
1571
|
-
authorize?: Method.AuthorizeFn<method>
|
|
1572
|
-
defaults?: defaults
|
|
1573
|
-
method: method
|
|
1574
|
-
events: ServerEventDispatcher<readonly [method], transport>
|
|
1575
|
-
preflight?: Method.PreflightFn<method>
|
|
1576
|
-
realm: string | undefined
|
|
1577
|
-
request?: Method.RequestFn<method>
|
|
1578
|
-
respond?: Method.RespondFn<method>
|
|
1579
|
-
broadcast?: Method.BroadcastFn<method>
|
|
1580
|
-
secretKey: string
|
|
1581
|
-
stableBinding?: Method.StableBindingFn<method>
|
|
1582
|
-
transport: transport
|
|
1583
|
-
validate?: Method.ValidateFn<method>
|
|
1584
|
-
verify: Method.VerifyFn<method>
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
type ReturnType<
|
|
1588
|
-
method extends Method.Method = Method.Method,
|
|
1589
|
-
transport extends Transport.AnyTransport = Transport.Http,
|
|
1590
|
-
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
1591
|
-
> = MethodFn<method, transport, defaults>
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
function createAttestationGuard(verifiers: Attestation.VerifierMap) {
|
|
1595
|
-
const entries = Object.entries(verifiers)
|
|
1596
|
-
if (entries.length === 0)
|
|
1597
|
-
throw new TypeError('Mppx server attestation must configure at least one verifier.')
|
|
1598
|
-
|
|
1599
|
-
const cache = new WeakMap<globalThis.Request, Promise<globalThis.Response | undefined>>()
|
|
1600
|
-
return (request: globalThis.Request): Promise<globalThis.Response | undefined> => {
|
|
1601
|
-
const cached = cache.get(request)
|
|
1602
|
-
if (cached) return cached
|
|
1603
|
-
|
|
1604
|
-
const verification = AttestationServer.verify(request, verifiers).then((outcomes) => {
|
|
1605
|
-
for (const [name, outcome] of Object.entries(outcomes))
|
|
1606
|
-
if (outcome.status === 'invalid')
|
|
1607
|
-
return new globalThis.Response(
|
|
1608
|
-
`Request attestation "${name}" is invalid: ${outcome.reason}`,
|
|
1609
|
-
{ status: 401 },
|
|
1610
|
-
)
|
|
1611
|
-
|
|
1612
|
-
for (const [name, outcome] of Object.entries(outcomes)) {
|
|
1613
|
-
if (outcome.status === 'verified') continue
|
|
1614
|
-
const reason =
|
|
1615
|
-
outcome.status === 'unverified'
|
|
1616
|
-
? `Request attestation "${name}" could not be verified: ${outcome.reason}`
|
|
1617
|
-
: `Request attestation "${name}" is required.`
|
|
1618
|
-
return new globalThis.Response(reason, { status: 403 })
|
|
1619
|
-
}
|
|
1620
|
-
return undefined
|
|
1621
|
-
})
|
|
1622
|
-
cache.set(request, verification)
|
|
1623
|
-
return verification
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
type ServerEventDispatcher<
|
|
1628
|
-
methods extends readonly Method.Method[],
|
|
1629
|
-
transport extends Transport.AnyTransport,
|
|
1630
|
-
> = {
|
|
1631
|
-
emit<name extends keyof ServerEventMap<methods, transport>>(
|
|
1632
|
-
name: name,
|
|
1633
|
-
context: ServerEventMap<methods, transport>[name],
|
|
1634
|
-
): Promise<void>
|
|
1635
|
-
on<name extends ServerEventName<methods, transport>>(
|
|
1636
|
-
name: name,
|
|
1637
|
-
handler: ServerEventHandler<methods, transport, name>,
|
|
1638
|
-
): Unsubscribe
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
function createServerEventDispatcher<
|
|
1642
|
-
methods extends readonly Method.Method[],
|
|
1643
|
-
transport extends Transport.AnyTransport,
|
|
1644
|
-
>(): ServerEventDispatcher<methods, transport> {
|
|
1645
|
-
const handlers = {
|
|
1646
|
-
'*': new Set<ServerEventHandler<methods, transport, '*'>>(),
|
|
1647
|
-
'challenge.created': new Set<ServerEventHandler<methods, transport, 'challenge.created'>>(),
|
|
1648
|
-
'payment.failed': new Set<ServerEventHandler<methods, transport, 'payment.failed'>>(),
|
|
1649
|
-
'payment.success': new Set<ServerEventHandler<methods, transport, 'payment.success'>>(),
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
const on: ServerEventDispatcher<methods, transport>['on'] = (name, handler) => {
|
|
1653
|
-
switch (name) {
|
|
1654
|
-
case '*':
|
|
1655
|
-
case 'challenge.created':
|
|
1656
|
-
case 'payment.failed':
|
|
1657
|
-
case 'payment.success':
|
|
1658
|
-
handlers[name].add(handler as never)
|
|
1659
|
-
return () => handlers[name].delete(handler as never)
|
|
1660
|
-
default:
|
|
1661
|
-
throw new Error(`Unknown server event "${String(name)}".`)
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
return {
|
|
1666
|
-
async emit(name, context) {
|
|
1667
|
-
await emitServerEventHandlers(handlers[name], context)
|
|
1668
|
-
await emitServerEventHandlers(handlers['*'], toServerEventEnvelope(name, context))
|
|
1669
|
-
},
|
|
1670
|
-
on,
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
function toServerEventEnvelope<
|
|
1675
|
-
methods extends readonly Method.Method[],
|
|
1676
|
-
transport extends Transport.AnyTransport,
|
|
1677
|
-
name extends keyof ServerEventMap<methods, transport>,
|
|
1678
|
-
>(
|
|
1679
|
-
name: name,
|
|
1680
|
-
payload: ServerEventMap<methods, transport>[name],
|
|
1681
|
-
): ServerEventPayload<methods, transport, '*'> {
|
|
1682
|
-
return Object.freeze({ name, payload }) as ServerEventPayload<methods, transport, '*'>
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
async function emitServerEventHandlers(
|
|
1686
|
-
handlers: ReadonlySet<(context: never) => MaybePromise<void>>,
|
|
1687
|
-
context: unknown,
|
|
1688
|
-
): Promise<void> {
|
|
1689
|
-
for (const handler of handlers) {
|
|
1690
|
-
try {
|
|
1691
|
-
await handler(context as never)
|
|
1692
|
-
} catch {
|
|
1693
|
-
// Errors are isolated, but handlers are still awaited inline.
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
function assertNoReservedMppxKeys(methods: readonly Method.AnyServer[]) {
|
|
1699
|
-
for (const method of methods) {
|
|
1700
|
-
if (reservedMppxKeys.has(method.name as ReservedKey))
|
|
1701
|
-
throw new Error(`Method name "${method.name}" conflicts with a reserved Mppx property.`)
|
|
1702
|
-
if (reservedMppxKeys.has(method.intent as ReservedKey))
|
|
1703
|
-
throw new Error(`Method intent "${method.intent}" conflicts with a reserved Mppx property.`)
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
function createChallengeContext(parameters: {
|
|
1708
|
-
capturedRequest?: Method.CapturedRequest | undefined
|
|
1709
|
-
challenge: Challenge.Challenge
|
|
1710
|
-
credential?: Credential.Credential | null | undefined
|
|
1711
|
-
error?: Errors.PaymentError | undefined
|
|
1712
|
-
input?: unknown
|
|
1713
|
-
method: Method.Method | ServerMethodDescriptor
|
|
1714
|
-
request: Record<string, unknown>
|
|
1715
|
-
}): ChallengeContext {
|
|
1716
|
-
return Object.freeze({
|
|
1717
|
-
...(parameters.capturedRequest
|
|
1718
|
-
? { capturedRequest: snapshotCapturedRequest(parameters.capturedRequest) }
|
|
1719
|
-
: {}),
|
|
1720
|
-
challenge: snapshotValue(parameters.challenge),
|
|
1721
|
-
credential:
|
|
1722
|
-
parameters.credential === undefined
|
|
1723
|
-
? undefined
|
|
1724
|
-
: snapshotNullableValue(parameters.credential),
|
|
1725
|
-
error: snapshotError(parameters.error),
|
|
1726
|
-
...snapshotInputProperty(parameters.input),
|
|
1727
|
-
method: snapshotMethod(parameters.method),
|
|
1728
|
-
request: snapshotValue(parameters.request),
|
|
1729
|
-
}) as never
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
function createPaymentFailedContext(parameters: {
|
|
1733
|
-
capturedRequest?: Method.CapturedRequest | undefined
|
|
1734
|
-
challenge: Challenge.Challenge
|
|
1735
|
-
credential: Credential.Credential | null
|
|
1736
|
-
error: Errors.PaymentError
|
|
1737
|
-
input?: unknown
|
|
1738
|
-
method: Method.Method | ServerMethodDescriptor
|
|
1739
|
-
request: Record<string, unknown>
|
|
1740
|
-
retryChallenge?: Challenge.Challenge | undefined
|
|
1741
|
-
submittedChallenge?: Challenge.Challenge | undefined
|
|
1742
|
-
}): PaymentFailedContext {
|
|
1743
|
-
return Object.freeze({
|
|
1744
|
-
...(parameters.capturedRequest
|
|
1745
|
-
? { capturedRequest: snapshotCapturedRequest(parameters.capturedRequest) }
|
|
1746
|
-
: {}),
|
|
1747
|
-
challenge: snapshotValue(parameters.challenge),
|
|
1748
|
-
credential: snapshotNullableValue(parameters.credential),
|
|
1749
|
-
error: snapshotError(parameters.error),
|
|
1750
|
-
...snapshotInputProperty(parameters.input),
|
|
1751
|
-
method: snapshotMethod(parameters.method),
|
|
1752
|
-
request: snapshotValue(parameters.request),
|
|
1753
|
-
...(parameters.retryChallenge
|
|
1754
|
-
? { retryChallenge: snapshotValue(parameters.retryChallenge) }
|
|
1755
|
-
: {}),
|
|
1756
|
-
...(parameters.submittedChallenge
|
|
1757
|
-
? { submittedChallenge: snapshotValue(parameters.submittedChallenge) }
|
|
1758
|
-
: {}),
|
|
1759
|
-
}) as never
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
function createPaymentSuccessContext(parameters: {
|
|
1763
|
-
capturedRequest?: Method.CapturedRequest | undefined
|
|
1764
|
-
challenge: Challenge.Challenge
|
|
1765
|
-
credential?: Credential.Credential | undefined
|
|
1766
|
-
envelope?: Method.VerifiedChallengeEnvelope | undefined
|
|
1767
|
-
input?: unknown
|
|
1768
|
-
method: Method.Method | ServerMethodDescriptor
|
|
1769
|
-
receipt: Receipt.Receipt
|
|
1770
|
-
request: Record<string, unknown>
|
|
1771
|
-
}): PaymentSuccessContext {
|
|
1772
|
-
return Object.freeze({
|
|
1773
|
-
...(parameters.capturedRequest
|
|
1774
|
-
? { capturedRequest: snapshotCapturedRequest(parameters.capturedRequest) }
|
|
1775
|
-
: {}),
|
|
1776
|
-
challenge: snapshotValue(parameters.challenge),
|
|
1777
|
-
...(parameters.credential ? { credential: snapshotValue(parameters.credential) } : {}),
|
|
1778
|
-
...(parameters.envelope ? { envelope: snapshotVerifiedEnvelope(parameters.envelope) } : {}),
|
|
1779
|
-
...snapshotInputProperty(parameters.input),
|
|
1780
|
-
method: snapshotMethod(parameters.method),
|
|
1781
|
-
receipt: snapshotValue(parameters.receipt),
|
|
1782
|
-
request: snapshotValue(parameters.request),
|
|
1783
|
-
}) as never
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
function snapshotMethod<method extends Pick<Method.Method, 'intent' | 'name'>>(
|
|
1787
|
-
method: method,
|
|
1788
|
-
): ServerMethodDescriptor<method> {
|
|
1789
|
-
return Object.freeze({
|
|
1790
|
-
intent: method.intent,
|
|
1791
|
-
name: method.name,
|
|
1792
|
-
}) as ServerMethodDescriptor<method>
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
function snapshotError<error extends Errors.PaymentError | undefined>(error: error): error {
|
|
1796
|
-
if (!error) return error
|
|
1797
|
-
const snapshot = Object.assign(Object.create(Object.getPrototypeOf(error)), error)
|
|
1798
|
-
Object.defineProperties(snapshot, {
|
|
1799
|
-
message: { value: error.message, enumerable: false },
|
|
1800
|
-
name: { value: error.name, enumerable: false },
|
|
1801
|
-
})
|
|
1802
|
-
return Object.freeze(snapshot) as error
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
function snapshotVerifiedEnvelope(
|
|
1806
|
-
envelope: Method.VerifiedChallengeEnvelope,
|
|
1807
|
-
): Method.VerifiedChallengeEnvelope {
|
|
1808
|
-
return Object.freeze({
|
|
1809
|
-
capturedRequest: snapshotCapturedRequest(envelope.capturedRequest),
|
|
1810
|
-
challenge: snapshotValue(envelope.challenge),
|
|
1811
|
-
credential: snapshotValue(envelope.credential),
|
|
1812
|
-
request: snapshotValue(envelope.request),
|
|
1813
|
-
}) as Method.VerifiedChallengeEnvelope
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
function snapshotCapturedRequest(capturedRequest: Method.CapturedRequest): Method.CapturedRequest {
|
|
1817
|
-
return Object.freeze({
|
|
1818
|
-
headers: new Headers(capturedRequest.headers),
|
|
1819
|
-
hasBody: capturedRequest.hasBody,
|
|
1820
|
-
method: capturedRequest.method,
|
|
1821
|
-
url: new URL(capturedRequest.url),
|
|
1822
|
-
})
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
function snapshotNullableValue<value>(value: value | null): value | null {
|
|
1826
|
-
if (value === null) return null
|
|
1827
|
-
return snapshotValue(value)
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
function snapshotValue<value>(value: value): value {
|
|
1831
|
-
try {
|
|
1832
|
-
return freezeSnapshot(structuredClone(value))
|
|
1833
|
-
} catch {
|
|
1834
|
-
return freezeSnapshot(value)
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
function snapshotInputProperty(input: unknown): { input: unknown } | {} {
|
|
1839
|
-
if (input === undefined) return {}
|
|
1840
|
-
const snapshot = snapshotTransportInput(input)
|
|
1841
|
-
return snapshot === undefined ? {} : { input: snapshot }
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
function snapshotTransportInput<input>(input: input): input | undefined {
|
|
1845
|
-
if (input instanceof globalThis.Request) {
|
|
1846
|
-
try {
|
|
1847
|
-
return new globalThis.Request(input.url, {
|
|
1848
|
-
headers: new Headers(input.headers),
|
|
1849
|
-
method: input.method,
|
|
1850
|
-
}) as input
|
|
1851
|
-
} catch {
|
|
1852
|
-
return undefined
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
try {
|
|
1856
|
-
return freezeSnapshot(structuredClone(input))
|
|
1857
|
-
} catch {
|
|
1858
|
-
warnOnce(
|
|
1859
|
-
Warnings.transportInputSnapshot,
|
|
1860
|
-
'Could not clone server event input; omitting `context.input`. Use `capturedRequest` for request correlation.',
|
|
1861
|
-
)
|
|
1862
|
-
return undefined
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
// Event payloads are cloned before listeners see them; shallow freezing keeps
|
|
1867
|
-
// the guard simple while preventing top-level mutation of receipts/challenges.
|
|
1868
|
-
function freezeSnapshot<value>(value: value): value {
|
|
1869
|
-
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value
|
|
1870
|
-
Object.freeze(value)
|
|
1871
|
-
return value
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
function isServiceWorkerRequest(input: unknown): input is Request {
|
|
1875
|
-
return (
|
|
1876
|
-
input instanceof globalThis.Request &&
|
|
1877
|
-
new URL(input.url).searchParams.has(Html.params.serviceWorker)
|
|
1878
|
-
)
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
function createServiceWorkerResponse() {
|
|
1882
|
-
return new Response(serviceWorker, {
|
|
1883
|
-
status: 200,
|
|
1884
|
-
headers: {
|
|
1885
|
-
'Content-Type': 'application/javascript',
|
|
1886
|
-
'Cache-Control': 'no-store',
|
|
1887
|
-
},
|
|
1888
|
-
})
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
function isIssuedChallengeResponse(response: unknown): boolean {
|
|
1892
|
-
return !(response instanceof globalThis.Response) || response.status === 402
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
function getSafeCredentialReason(error: unknown): string | undefined {
|
|
1896
|
-
if (error instanceof Credential.InvalidCredentialEncodingError) return error.message
|
|
1897
|
-
if (error instanceof Credential.MissingAuthorizationHeaderError) return error.message
|
|
1898
|
-
if (error instanceof Credential.MissingPaymentSchemeError) return error.message
|
|
1899
|
-
return undefined
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
const defaultRealm = 'MPP Payment'
|
|
1903
|
-
const Warnings = {
|
|
1904
|
-
realmFallback: 'realm-fallback',
|
|
1905
|
-
transportInputSnapshot: 'transport-input-snapshot',
|
|
1906
|
-
} as const
|
|
1907
|
-
const missingReceiptResponseErrorName = 'MissingReceiptResponseError'
|
|
1908
|
-
const missingReceiptResponseErrorMessage = 'withReceipt() requires a response argument'
|
|
1909
|
-
|
|
1910
|
-
/** Error thrown when `withReceipt()` needs a response but none was provided. */
|
|
1911
|
-
export class MissingReceiptResponseError extends Error {
|
|
1912
|
-
override name = missingReceiptResponseErrorName
|
|
1913
|
-
|
|
1914
|
-
constructor() {
|
|
1915
|
-
super(missingReceiptResponseErrorMessage)
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
/** Returns true when an error is the typed `withReceipt()` no-response sentinel. */
|
|
1920
|
-
export function isMissingReceiptResponseError(
|
|
1921
|
-
error: unknown,
|
|
1922
|
-
): error is MissingReceiptResponseError {
|
|
1923
|
-
if (error instanceof MissingReceiptResponseError) return true
|
|
1924
|
-
if (!error || typeof error !== 'object') return false
|
|
1925
|
-
const value = error as { message?: unknown; name?: unknown }
|
|
1926
|
-
return (
|
|
1927
|
-
value.name === missingReceiptResponseErrorName &&
|
|
1928
|
-
value.message === missingReceiptResponseErrorMessage
|
|
1929
|
-
)
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
function normalizeExpires(expires: z.DatetimeInput | undefined): string | undefined {
|
|
1933
|
-
return expires === undefined ? undefined : z.toDatetimeString(expires)
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
const _warned = new Set<string>()
|
|
1937
|
-
function warnOnce(key: string, message: string) {
|
|
1938
|
-
if (_warned.has(key)) return
|
|
1939
|
-
_warned.add(key)
|
|
1940
|
-
console.warn(`[mppx] ${message}`)
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
/** Extracts hostname from the captured request URL, falling back to a default. */
|
|
1944
|
-
function resolveRealmFromCapturedRequest(capturedRequest: Method.CapturedRequest): string {
|
|
1945
|
-
try {
|
|
1946
|
-
const { protocol, hostname } = capturedRequest.url
|
|
1947
|
-
if (/^https?:$/.test(protocol) && hostname) return hostname
|
|
1948
|
-
} catch {}
|
|
1949
|
-
warnOnce(
|
|
1950
|
-
Warnings.realmFallback,
|
|
1951
|
-
`Could not auto-detect realm from request. Falling back to "${defaultRealm}". Set \`realm\` in Mppx.create() or the MPP_REALM env var.`,
|
|
1952
|
-
)
|
|
1953
|
-
return defaultRealm
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
async function resolveRouteChallenge(parameters: {
|
|
1957
|
-
capturedRequest?: Method.CapturedRequest | undefined
|
|
1958
|
-
credential?: Credential.Credential | null | undefined
|
|
1959
|
-
defaults?: Record<string, unknown> | undefined
|
|
1960
|
-
description?: string | undefined
|
|
1961
|
-
expires?: string | undefined
|
|
1962
|
-
meta?: Record<string, string> | undefined
|
|
1963
|
-
method: Method.Method
|
|
1964
|
-
realm?: string | undefined
|
|
1965
|
-
request?: Method.RequestFn<Method.Method> | undefined
|
|
1966
|
-
routeRequest: Record<string, unknown>
|
|
1967
|
-
secretKey: string
|
|
1968
|
-
}): Promise<{
|
|
1969
|
-
challenge: Challenge.Challenge
|
|
1970
|
-
parsedRequest: Record<string, unknown>
|
|
1971
|
-
request: Record<string, unknown>
|
|
1972
|
-
}> {
|
|
1973
|
-
// Resolve the route's canonical request exactly as the handler path does:
|
|
1974
|
-
const request = await (async () => {
|
|
1975
|
-
// start from defaults + route options, then let the method request hook
|
|
1976
|
-
const merged = { ...parameters.defaults, ...parameters.routeRequest }
|
|
1977
|
-
// normalize or enrich it using the captured request and credential.
|
|
1978
|
-
return parameters.request
|
|
1979
|
-
? ((await parameters.request({
|
|
1980
|
-
capturedRequest: parameters.capturedRequest,
|
|
1981
|
-
credential: parameters.credential,
|
|
1982
|
-
request: merged,
|
|
1983
|
-
} as never)) as Record<string, unknown>)
|
|
1984
|
-
: merged
|
|
1985
|
-
})()
|
|
1986
|
-
|
|
1987
|
-
const effectiveRealm =
|
|
1988
|
-
parameters.realm ??
|
|
1989
|
-
(parameters.capturedRequest
|
|
1990
|
-
? resolveRealmFromCapturedRequest(parameters.capturedRequest)
|
|
1991
|
-
: defaultRealm)
|
|
1992
|
-
|
|
1993
|
-
const challenge = Challenge.fromMethod(parameters.method, {
|
|
1994
|
-
description: parameters.description,
|
|
1995
|
-
expires: parameters.expires,
|
|
1996
|
-
meta: parameters.meta,
|
|
1997
|
-
realm: effectiveRealm,
|
|
1998
|
-
request: request as never,
|
|
1999
|
-
secretKey: parameters.secretKey,
|
|
2000
|
-
})
|
|
2001
|
-
|
|
2002
|
-
return {
|
|
2003
|
-
challenge,
|
|
2004
|
-
parsedRequest: challenge.request as Record<string, unknown>,
|
|
2005
|
-
request,
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
function createFallbackChallenge(parameters: {
|
|
2010
|
-
capturedRequest?: Method.CapturedRequest | undefined
|
|
2011
|
-
defaults: Record<string, unknown>
|
|
2012
|
-
description?: string | undefined
|
|
2013
|
-
expires?: string | undefined
|
|
2014
|
-
meta?: Record<string, string> | undefined
|
|
2015
|
-
method: Method.Method
|
|
2016
|
-
realm?: string | undefined
|
|
2017
|
-
routeRequest: Record<string, unknown>
|
|
2018
|
-
secretKey: string
|
|
2019
|
-
}) {
|
|
2020
|
-
return Challenge.fromMethod(parameters.method, {
|
|
2021
|
-
description: parameters.description,
|
|
2022
|
-
expires: parameters.expires,
|
|
2023
|
-
meta: parameters.meta,
|
|
2024
|
-
realm:
|
|
2025
|
-
parameters.realm ??
|
|
2026
|
-
(parameters.capturedRequest
|
|
2027
|
-
? resolveRealmFromCapturedRequest(parameters.capturedRequest)
|
|
2028
|
-
: defaultRealm),
|
|
2029
|
-
request: { ...parameters.defaults, ...parameters.routeRequest } as never,
|
|
2030
|
-
secretKey: parameters.secretKey,
|
|
2031
|
-
})
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
/**
|
|
2035
|
-
* Captures the transport request into a frozen snapshot at the start of the
|
|
2036
|
-
* verification flow. This snapshot is threaded through request() → verify() →
|
|
2037
|
-
* respond() → respondReceipt() so every hook sees the same authoritative
|
|
2038
|
-
* request state — preventing the raw transport input from being re-read or
|
|
2039
|
-
* mutated between verification steps.
|
|
2040
|
-
*
|
|
2041
|
-
* Note: Object.freeze is shallow — it prevents reassigning top-level properties
|
|
2042
|
-
* but does not deep-freeze mutable class instances like Headers or URL. This is
|
|
2043
|
-
* an accidental-mutation guard for trusted server events, not a security boundary.
|
|
2044
|
-
*/
|
|
2045
|
-
async function captureRequest(
|
|
2046
|
-
transport: Transport.AnyTransport,
|
|
2047
|
-
input: unknown,
|
|
2048
|
-
): Promise<Method.CapturedRequest> {
|
|
2049
|
-
const capturedRequest = transport.captureRequest
|
|
2050
|
-
? await transport.captureRequest(input)
|
|
2051
|
-
: captureRequestFromInput(input)
|
|
2052
|
-
|
|
2053
|
-
return Object.freeze(capturedRequest)
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
function captureRequestFromInput(input: unknown): Method.CapturedRequest {
|
|
2057
|
-
const source = input as {
|
|
2058
|
-
body?: unknown
|
|
2059
|
-
headers?: HeadersInit | undefined
|
|
2060
|
-
method?: string | undefined
|
|
2061
|
-
url?: string | URL | undefined
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
return {
|
|
2065
|
-
headers: new Headers(source.headers),
|
|
2066
|
-
hasBody: source.body === undefined ? undefined : source.body !== null,
|
|
2067
|
-
method: source.method ?? 'POST',
|
|
2068
|
-
url: Transport.safeUrl(source.url),
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
const coreBindingFields = ['amount', 'currency', 'recipient'] as const
|
|
2073
|
-
const methodBindingFields = ['chainId', 'memo', 'sessionProtocol', 'splits', 'unitType'] as const
|
|
2074
|
-
const pinnedRequestBindingFields = [...coreBindingFields, ...methodBindingFields] as const
|
|
2075
|
-
|
|
2076
|
-
type CoreBindingField = (typeof coreBindingFields)[number]
|
|
2077
|
-
type MethodBindingField = (typeof methodBindingFields)[number]
|
|
2078
|
-
type PinnedRequestBindingField = (typeof pinnedRequestBindingFields)[number]
|
|
2079
|
-
type PinnedChallengeField = 'method' | 'intent' | 'realm' | 'opaque' | PinnedRequestBindingField
|
|
2080
|
-
type StableBinding = Record<string, unknown>
|
|
2081
|
-
|
|
2082
|
-
function getChallengeBindingMismatch(
|
|
2083
|
-
expectedChallenge: Challenge.Challenge,
|
|
2084
|
-
actualChallenge: Challenge.Challenge,
|
|
2085
|
-
stableBinding?: Method.StableBindingFn<Method.Method> | undefined,
|
|
2086
|
-
): string | undefined {
|
|
2087
|
-
if (!stableBinding) return getPinnedChallengeMismatch(expectedChallenge, actualChallenge)
|
|
2088
|
-
|
|
2089
|
-
for (const field of ['method', 'intent', 'realm'] as const) {
|
|
2090
|
-
if (actualChallenge[field] !== expectedChallenge[field]) return field
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
if (!opaqueValuesMatch(expectedChallenge.meta, actualChallenge.meta)) return 'opaque'
|
|
2094
|
-
|
|
2095
|
-
return getRequestBindingMismatch(
|
|
2096
|
-
getStableBinding(expectedChallenge.request as Record<string, unknown>, stableBinding),
|
|
2097
|
-
getStableBinding(actualChallenge.request as Record<string, unknown>, stableBinding),
|
|
2098
|
-
)
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
/**
|
|
2102
|
-
* Compares only the fields that MUST be stable across request-hook transforms.
|
|
2103
|
-
*
|
|
2104
|
-
* This is NOT the primary integrity check — the HMAC binding (Challenge.verify)
|
|
2105
|
-
* already covers every challenge field including opaque, digest, and the full
|
|
2106
|
-
* serialized request. This function exists as a secondary safety net for the
|
|
2107
|
-
* case where the `request()` hook produces credential-dependent output, causing
|
|
2108
|
-
* the recomputed challenge to differ from the original in non-economic fields
|
|
2109
|
-
* (e.g. `feePayer`). We only need to verify that the economically significant
|
|
2110
|
-
* subset hasn't drifted.
|
|
2111
|
-
*/
|
|
2112
|
-
function getPinnedChallengeMismatch(
|
|
2113
|
-
expectedChallenge: Challenge.Challenge,
|
|
2114
|
-
actualChallenge: Challenge.Challenge,
|
|
2115
|
-
): PinnedChallengeField | undefined {
|
|
2116
|
-
for (const field of ['method', 'intent', 'realm'] as const) {
|
|
2117
|
-
if (actualChallenge[field] !== expectedChallenge[field]) return field
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
|
-
if (!opaqueValuesMatch(expectedChallenge.meta, actualChallenge.meta)) return 'opaque'
|
|
2121
|
-
|
|
2122
|
-
return getPinnedRequestBindingMismatch(
|
|
2123
|
-
expectedChallenge.request as Record<string, unknown>,
|
|
2124
|
-
actualChallenge.request as Record<string, unknown>,
|
|
2125
|
-
)
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
function getPinnedRequestBindingMismatch(
|
|
2129
|
-
expectedRequest: Record<string, unknown>,
|
|
2130
|
-
actualRequest: Record<string, unknown>,
|
|
2131
|
-
): PinnedRequestBindingField | undefined {
|
|
2132
|
-
const expected = getPinnedRequestBinding(expectedRequest)
|
|
2133
|
-
const actual = getPinnedRequestBinding(actualRequest)
|
|
2134
|
-
|
|
2135
|
-
return (
|
|
2136
|
-
getCoreBindingMismatch(expected.coreBinding, actual.coreBinding) ??
|
|
2137
|
-
getMethodBindingMismatch(expected.methodBinding, actual.methodBinding)
|
|
2138
|
-
)
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
function getCoreBindingMismatch(
|
|
2142
|
-
expected: CoreBinding,
|
|
2143
|
-
actual: CoreBinding,
|
|
2144
|
-
): CoreBindingField | undefined {
|
|
2145
|
-
return coreBindingFields.find((field) => !isDeepStrictEqual(expected[field], actual[field]))
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
function getMethodBindingMismatch(
|
|
2149
|
-
expected: MethodBinding,
|
|
2150
|
-
actual: MethodBinding,
|
|
2151
|
-
): MethodBindingField | undefined {
|
|
2152
|
-
return methodBindingFields.find((field) => !isDeepStrictEqual(expected[field], actual[field]))
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
function getPinnedRequestBinding(request: Record<string, unknown>): PinnedRequestBinding {
|
|
2156
|
-
const methodDetails = (request.methodDetails ?? {}) as Record<string, unknown>
|
|
2157
|
-
const amount = normalizeScalar(request.amount ?? methodDetails.amount)
|
|
2158
|
-
const chainId = normalizeScalar(request.chainId ?? methodDetails.chainId)
|
|
2159
|
-
const currency = normalizeScalar(request.currency ?? methodDetails.currency)
|
|
2160
|
-
const memo = normalizeHex(methodDetails.memo)
|
|
2161
|
-
const recipient = normalizeScalar(request.recipient ?? methodDetails.recipient)
|
|
2162
|
-
const sessionProtocol = normalizeScalar(methodDetails.sessionProtocol)
|
|
2163
|
-
const splits = normalizeComparable(methodDetails.splits)
|
|
2164
|
-
const unitType = normalizeScalar(request.unitType ?? methodDetails.unitType)
|
|
2165
|
-
|
|
2166
|
-
return {
|
|
2167
|
-
coreBinding: {
|
|
2168
|
-
...(amount !== undefined ? { amount } : {}),
|
|
2169
|
-
...(currency !== undefined ? { currency } : {}),
|
|
2170
|
-
...(recipient !== undefined ? { recipient } : {}),
|
|
2171
|
-
},
|
|
2172
|
-
methodBinding: {
|
|
2173
|
-
...(chainId !== undefined ? { chainId } : {}),
|
|
2174
|
-
...(memo !== undefined ? { memo } : {}),
|
|
2175
|
-
...(sessionProtocol !== undefined ? { sessionProtocol } : {}),
|
|
2176
|
-
...(splits !== undefined ? { splits } : {}),
|
|
2177
|
-
...(unitType !== undefined ? { unitType } : {}),
|
|
2178
|
-
},
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
function getRequestBindingMismatch(
|
|
2183
|
-
expected: StableBinding,
|
|
2184
|
-
actual: StableBinding,
|
|
2185
|
-
): string | undefined {
|
|
2186
|
-
const fields = [
|
|
2187
|
-
...Object.keys(expected),
|
|
2188
|
-
...Object.keys(actual).filter((key) => !(key in expected)),
|
|
2189
|
-
]
|
|
2190
|
-
|
|
2191
|
-
return fields.find(
|
|
2192
|
-
(field) =>
|
|
2193
|
-
!isDeepStrictEqual(normalizeComparable(expected[field]), normalizeComparable(actual[field])),
|
|
2194
|
-
)
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
function getStableBinding(
|
|
2198
|
-
request: Record<string, unknown>,
|
|
2199
|
-
stableBinding: Method.StableBindingFn<Method.Method>,
|
|
2200
|
-
): StableBinding {
|
|
2201
|
-
return stableBinding(request as never)
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
/** Top-level economic fields that should never drift after challenge issuance. */
|
|
2205
|
-
type CoreBinding = {
|
|
2206
|
-
[field in CoreBindingField]?: string
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
/** Method-specific fields that are pinned by the fallback binding check. */
|
|
2210
|
-
type MethodBinding = {
|
|
2211
|
-
[field in MethodBindingField]?: unknown
|
|
2212
|
-
}
|
|
2213
|
-
|
|
2214
|
-
/** Normalized request subset used when a method does not provide a custom stable binding. */
|
|
2215
|
-
type PinnedRequestBinding = {
|
|
2216
|
-
coreBinding: CoreBinding
|
|
2217
|
-
methodBinding: MethodBinding
|
|
2218
|
-
}
|
|
2219
|
-
|
|
2220
|
-
function normalizeScalar(value: unknown): string | undefined {
|
|
2221
|
-
return value === undefined ? undefined : String(value)
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
function normalizeHex(value: unknown): string | undefined {
|
|
2225
|
-
if (value === undefined) return undefined
|
|
2226
|
-
|
|
2227
|
-
const normalized = String(value)
|
|
2228
|
-
return normalized.startsWith('0x') ? normalized.toLowerCase() : normalized
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
function normalizeComparable(value: unknown): unknown {
|
|
2232
|
-
if (value === undefined) return undefined
|
|
2233
|
-
if (Array.isArray(value)) return value.map(normalizeComparable)
|
|
2234
|
-
|
|
2235
|
-
if (value && typeof value === 'object') {
|
|
2236
|
-
return Object.fromEntries(
|
|
2237
|
-
Object.entries(value as Record<string, unknown>)
|
|
2238
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
2239
|
-
.map(([key, nested]) => [key, normalizeComparable(nested)]),
|
|
2240
|
-
)
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
return typeof value === 'string' ? normalizeHex(value) : value
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
|
-
function opaqueValuesMatch(
|
|
2247
|
-
expected: Record<string, string> | undefined,
|
|
2248
|
-
actual: Record<string, string> | undefined,
|
|
2249
|
-
): boolean {
|
|
2250
|
-
return isDeepStrictEqual(expected, actual)
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
function hydrateCredentialMeta<payload>(
|
|
2254
|
-
credential: Credential.Credential<payload>,
|
|
2255
|
-
): Credential.Credential<payload> {
|
|
2256
|
-
const { challenge } = credential
|
|
2257
|
-
if (challenge.opaque === undefined) return credential
|
|
2258
|
-
const hydratedChallenge = Challenge.Schema.parse({
|
|
2259
|
-
...challenge,
|
|
2260
|
-
meta: PaymentRequest.deserialize(challenge.opaque),
|
|
2261
|
-
})
|
|
2262
|
-
return {
|
|
2263
|
-
...credential,
|
|
2264
|
-
challenge: hydratedChallenge,
|
|
2265
|
-
}
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
function withParsedCredentialPayload<payload>(
|
|
2269
|
-
credential: Credential.Credential,
|
|
2270
|
-
payload: payload,
|
|
2271
|
-
): Credential.Credential<payload> {
|
|
2272
|
-
return {
|
|
2273
|
-
...credential,
|
|
2274
|
-
payload,
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
export type MethodFn<
|
|
2278
|
-
method extends Method.Method,
|
|
2279
|
-
transport extends Transport.AnyTransport,
|
|
2280
|
-
defaults extends Record<string, unknown>,
|
|
2281
|
-
> = (
|
|
2282
|
-
options: MethodFn.Options<method, defaults>,
|
|
2283
|
-
) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>
|
|
2284
|
-
/** @internal */
|
|
2285
|
-
export type AnyMethodFn = (options: any) => (input: any) => Promise<any>
|
|
2286
|
-
/** A MethodFn tagged with its source Method (set by `create()`). @internal */
|
|
2287
|
-
type AnyMethodFnWithMethod = AnyMethodFn & { _method: Method.AnyServer }
|
|
2288
|
-
|
|
2289
|
-
/** @internal */
|
|
2290
|
-
declare namespace MethodFn {
|
|
2291
|
-
export type Options<
|
|
2292
|
-
method extends Method.Method,
|
|
2293
|
-
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
2294
|
-
> = {
|
|
2295
|
-
/** Optional human-readable description of the payment. */
|
|
2296
|
-
description?: string | undefined
|
|
2297
|
-
/** Optional challenge expiration timestamp (ISO 8601) or Date. */
|
|
2298
|
-
expires?: z.DatetimeInput | undefined
|
|
2299
|
-
/** Optional server-defined correlation data (serialized as `opaque` in the request). Flat string-to-string map; clients MUST NOT modify. */
|
|
2300
|
-
meta?: Record<string, string> | undefined
|
|
2301
|
-
/** Optional route/resource scope bound via reserved challenge metadata. */
|
|
2302
|
-
scope?: string | undefined
|
|
2303
|
-
} & Method.WithDefaults<z.input<method['schema']['request']>, defaults>
|
|
2304
|
-
|
|
2305
|
-
export type Response<transport extends Transport.AnyTransport = Transport.Http> =
|
|
2306
|
-
| {
|
|
2307
|
-
challenge: Transport.ChallengeOutputOf<transport>
|
|
2308
|
-
status: 402
|
|
2309
|
-
}
|
|
2310
|
-
| {
|
|
2311
|
-
status: 200
|
|
2312
|
-
withReceipt: Transport.WithReceipt<transport>
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
|
-
/** A configured handler — the return value of e.g. `mppx.charge({ ... })`. @internal */
|
|
2317
|
-
type ConfiguredHandler = ((input: Request) => Promise<MethodFn.Response<Transport.Http>>) & {
|
|
2318
|
-
_internal: {
|
|
2319
|
-
_method: Method.AnyServer
|
|
2320
|
-
description?: string | undefined
|
|
2321
|
-
name: string
|
|
2322
|
-
intent: string
|
|
2323
|
-
html: Html.Options | undefined
|
|
2324
|
-
meta?: Record<string, string> | undefined
|
|
2325
|
-
scope?: string | undefined
|
|
2326
|
-
_canonicalRequest: Record<string, unknown>
|
|
2327
|
-
_stableBinding?: Method.StableBindingFn<Method.Method> | undefined
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
|
|
2331
|
-
type ComposedHandler = ((input: Request) => Promise<MethodFn.Response<Transport.Http>>) & {
|
|
2332
|
-
_internal?: {
|
|
2333
|
-
offers: readonly ConfiguredHandler['_internal'][]
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
type HandlerDiscoveryMetadata =
|
|
2338
|
-
| ConfiguredHandler['_internal']
|
|
2339
|
-
| NonNullable<ComposedHandler['_internal']>
|
|
2340
|
-
|
|
2341
|
-
const paymentAuthChallengeHeader = Constants.Headers.wwwAuthenticate
|
|
2342
|
-
|
|
2343
|
-
const challengeHeaderMerges = [
|
|
2344
|
-
{
|
|
2345
|
-
name: paymentAuthChallengeHeader,
|
|
2346
|
-
values: (context) =>
|
|
2347
|
-
context.challengeEntries
|
|
2348
|
-
.map((entry) =>
|
|
2349
|
-
(entry.result.challenge as Response).headers.get(paymentAuthChallengeHeader),
|
|
2350
|
-
)
|
|
2351
|
-
.filter((value): value is string => value !== null),
|
|
2352
|
-
merge: (values) => values,
|
|
2353
|
-
},
|
|
2354
|
-
{
|
|
2355
|
-
name: x402_Types.paymentRequiredHeader,
|
|
2356
|
-
values: (context) =>
|
|
2357
|
-
context.negotiatedChallengeResponses
|
|
2358
|
-
.map((response) => response.headers.get(x402_Types.paymentRequiredHeader))
|
|
2359
|
-
.filter((value): value is string => value !== null),
|
|
2360
|
-
merge: mergeX402PaymentRequiredHeaders,
|
|
2361
|
-
},
|
|
2362
|
-
] satisfies readonly ChallengeHeaderMerge[]
|
|
2363
|
-
|
|
2364
|
-
/** An entry for `compose()`: a method reference, handler function ref, or string key paired with its options. */
|
|
2365
|
-
type ComposeEntry<methods extends readonly Method.AnyServer[]> =
|
|
2366
|
-
| {
|
|
2367
|
-
[i in keyof methods]: readonly [
|
|
2368
|
-
methods[i],
|
|
2369
|
-
MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
|
|
2370
|
-
]
|
|
2371
|
-
}[number]
|
|
2372
|
-
| {
|
|
2373
|
-
[i in keyof methods]: readonly [
|
|
2374
|
-
`${methods[i]['name']}/${methods[i]['intent']}`,
|
|
2375
|
-
MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
|
|
2376
|
-
]
|
|
2377
|
-
}[number]
|
|
2378
|
-
| {
|
|
2379
|
-
[i in keyof methods]: readonly [
|
|
2380
|
-
MethodFn<methods[i], any, any> & { _method: methods[i] },
|
|
2381
|
-
MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
|
|
2382
|
-
]
|
|
2383
|
-
}[number]
|
|
2384
|
-
|
|
2385
|
-
/**
|
|
2386
|
-
* Combines multiple configured payment handlers into a single route handler
|
|
2387
|
-
* that presents all methods to the client via multiple `WWW-Authenticate` headers.
|
|
2388
|
-
*
|
|
2389
|
-
* When no credential is present, all handlers are called and their challenges
|
|
2390
|
-
* are merged into a single 402 response. When a credential is present, it is
|
|
2391
|
-
* dispatched to the handler matching the credential's `method`+`intent`.
|
|
2392
|
-
*
|
|
2393
|
-
* @example
|
|
2394
|
-
* ```ts
|
|
2395
|
-
* import { Mppx, tempo, stripe } from 'mppx/server'
|
|
2396
|
-
*
|
|
2397
|
-
* const mppx = Mppx.create({
|
|
2398
|
-
* methods: [tempo(), stripe()],
|
|
2399
|
-
* secretKey: process.env.PAYMENT_SECRET_KEY,
|
|
2400
|
-
* })
|
|
2401
|
-
*
|
|
2402
|
-
* app.get('/api/resource', async (req) => {
|
|
2403
|
-
* const result = await Mppx.compose(
|
|
2404
|
-
* mppx['tempo/charge']({ amount: '100', currency: USDC, recipient: '0x...' }),
|
|
2405
|
-
* mppx['stripe/charge']({ amount: '100', currency: 'usd' }),
|
|
2406
|
-
* )(req)
|
|
2407
|
-
* if (result.status === 402) return result.challenge
|
|
2408
|
-
* return result.withReceipt(new Response('OK'))
|
|
2409
|
-
* })
|
|
2410
|
-
* ```
|
|
2411
|
-
*/
|
|
2412
|
-
type ComposeHtmlOptions = Html.Config
|
|
2413
|
-
|
|
2414
|
-
export function compose(...args: readonly unknown[]): ComposedHandler {
|
|
2415
|
-
// Extract optional html options from last argument
|
|
2416
|
-
const last = args[args.length - 1]
|
|
2417
|
-
const composeOptions: Html.Options | undefined =
|
|
2418
|
-
typeof last === 'object' &&
|
|
2419
|
-
last !== null &&
|
|
2420
|
-
typeof last !== 'function' &&
|
|
2421
|
-
!('_internal' in last)
|
|
2422
|
-
? (() => {
|
|
2423
|
-
const opts = last as ComposeHtmlOptions
|
|
2424
|
-
return {
|
|
2425
|
-
config: {},
|
|
2426
|
-
content: '',
|
|
2427
|
-
formatAmount: () => '',
|
|
2428
|
-
text: opts.text,
|
|
2429
|
-
theme: opts.theme,
|
|
2430
|
-
}
|
|
2431
|
-
})()
|
|
2432
|
-
: undefined
|
|
2433
|
-
const handlers = (composeOptions ? args.slice(0, -1) : args) as readonly ((
|
|
2434
|
-
input: Request,
|
|
2435
|
-
) => Promise<MethodFn.Response<Transport.Http>>)[]
|
|
2436
|
-
|
|
2437
|
-
return composeHandlers(handlers, composeOptions)
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
function composeHandlers(
|
|
2441
|
-
handlers: readonly ((input: Request) => Promise<MethodFn.Response<Transport.Http>>)[],
|
|
2442
|
-
composeOptions?: Html.Options,
|
|
2443
|
-
selectOffers?: SelectOffers<readonly Method.AnyServer[]>,
|
|
2444
|
-
): (input: Request) => Promise<MethodFn.Response<Transport.Http>> {
|
|
2445
|
-
if (handlers.length === 0) throw new Error('compose() requires at least one handler')
|
|
2446
|
-
|
|
2447
|
-
const offerSelector = selectOffers
|
|
2448
|
-
const offerEntries = handlers.map((handler) => {
|
|
2449
|
-
const internal = (handler as ConfiguredHandler)._internal
|
|
2450
|
-
const offer =
|
|
2451
|
-
internal && !isComposedHandlerMetadata(internal) ? createServerOffer(internal) : undefined
|
|
2452
|
-
const method = internal && !isComposedHandlerMetadata(internal) ? internal._method : undefined
|
|
2453
|
-
return { handler, method, offer }
|
|
2454
|
-
})
|
|
2455
|
-
const hasOfferPolicy =
|
|
2456
|
-
offerSelector !== undefined || offerEntries.some(({ method }) => method?.canOffer !== undefined)
|
|
2457
|
-
|
|
2458
|
-
const composed: ComposedHandler = async (input: Request) => {
|
|
2459
|
-
// Serve service worker for html-enabled compose
|
|
2460
|
-
if (new URL(input.url).searchParams.has(Html.params.serviceWorker)) {
|
|
2461
|
-
const hasHtml = handlers.some((h) => (h as ConfiguredHandler)._internal?.html)
|
|
2462
|
-
if (hasHtml)
|
|
2463
|
-
return {
|
|
2464
|
-
status: 402,
|
|
2465
|
-
challenge: new Response(serviceWorker, {
|
|
2466
|
-
status: 200,
|
|
2467
|
-
headers: {
|
|
2468
|
-
'Content-Type': 'application/javascript',
|
|
2469
|
-
'Cache-Control': 'no-store',
|
|
2470
|
-
},
|
|
2471
|
-
}),
|
|
2472
|
-
} as MethodFn.Response<Transport.Http>
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
// Try to extract a Payment credential to decide whether to dispatch or challenge.
|
|
2476
|
-
// Only gate on the Payment scheme — other auth schemes (Bearer, Basic, etc.)
|
|
2477
|
-
// should fall through to the merged-402 path so all offers are presented.
|
|
2478
|
-
const header = input.headers.get(Constants.Headers.authorization)
|
|
2479
|
-
const paymentHeader = header ? Credential.extractPaymentScheme(header) : null
|
|
2480
|
-
|
|
2481
|
-
if (paymentHeader) {
|
|
2482
|
-
// Parse the credential to find method+intent for dispatch.
|
|
2483
|
-
let credential: Credential.Credential | undefined
|
|
2484
|
-
try {
|
|
2485
|
-
credential = hydrateCredentialMeta(Credential.deserialize(paymentHeader))
|
|
2486
|
-
} catch {}
|
|
2487
|
-
|
|
2488
|
-
if (credential) {
|
|
2489
|
-
const { method: credMethod, intent: credIntent } = credential.challenge
|
|
2490
|
-
const credReq = credential.challenge.request as Record<string, unknown>
|
|
2491
|
-
|
|
2492
|
-
// Filter by name+intent, then narrow by comparing stable request fields
|
|
2493
|
-
// from the echoed challenge against each handler's configured offers.
|
|
2494
|
-
// Uses the schema-parsed canonical form (not raw options) so that
|
|
2495
|
-
// transformed fields (e.g. amount with decimals) match correctly.
|
|
2496
|
-
// Also checks inside methodDetails for fields moved there by transforms.
|
|
2497
|
-
const candidates = handlers.filter((handler) =>
|
|
2498
|
-
getConfiguredOffers(handler).some((internal) => {
|
|
2499
|
-
try {
|
|
2500
|
-
if (internal.name !== credMethod || internal.intent !== credIntent) return false
|
|
2501
|
-
const mismatch = internal._stableBinding
|
|
2502
|
-
? getRequestBindingMismatch(
|
|
2503
|
-
getStableBinding(internal._canonicalRequest, internal._stableBinding),
|
|
2504
|
-
getStableBinding(credReq, internal._stableBinding),
|
|
2505
|
-
)
|
|
2506
|
-
: getPinnedRequestBindingMismatch(internal._canonicalRequest, credReq)
|
|
2507
|
-
return !mismatch && opaqueValuesMatch(internal.meta, credential.challenge.meta)
|
|
2508
|
-
} catch {
|
|
2509
|
-
return false
|
|
2510
|
-
}
|
|
2511
|
-
}),
|
|
2512
|
-
)
|
|
2513
|
-
|
|
2514
|
-
const match =
|
|
2515
|
-
candidates[0] ??
|
|
2516
|
-
handlers.find((handler) =>
|
|
2517
|
-
getConfiguredOffers(handler).some(
|
|
2518
|
-
(internal) => internal.name === credMethod && internal.intent === credIntent,
|
|
2519
|
-
),
|
|
2520
|
-
)
|
|
2521
|
-
if (match) return match(input)
|
|
2522
|
-
}
|
|
2523
|
-
|
|
2524
|
-
// Unrecognized or malformed credentials still use the currently eligible
|
|
2525
|
-
// offer set before the first handler issues its rejection challenge.
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
const selectedHandlers = hasOfferPolicy
|
|
2529
|
-
? await selectOfferHandlers({
|
|
2530
|
-
entries: offerEntries,
|
|
2531
|
-
input,
|
|
2532
|
-
selectOffers: offerSelector,
|
|
2533
|
-
})
|
|
2534
|
-
: handlers
|
|
2535
|
-
|
|
2536
|
-
// No credential — evaluate handlers sequentially so authorize()/renewal hooks
|
|
2537
|
-
// can safely claim the request without racing each other.
|
|
2538
|
-
const results: MethodFn.Response<Transport.Http>[] = []
|
|
2539
|
-
for (const handler of selectedHandlers) {
|
|
2540
|
-
const result = await handler(input)
|
|
2541
|
-
if (result.status === 200) return result
|
|
2542
|
-
results.push(result)
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
const challengeEntries = (() => {
|
|
2546
|
-
const entries: {
|
|
2547
|
-
handler: ConfiguredHandler
|
|
2548
|
-
challenge: Challenge.Challenge
|
|
2549
|
-
result: Extract<MethodFn.Response<Transport.Http>, { status: 402 }>
|
|
2550
|
-
}[] = []
|
|
2551
|
-
|
|
2552
|
-
for (let i = 0; i < selectedHandlers.length; i++) {
|
|
2553
|
-
const result = results[i]
|
|
2554
|
-
if (result?.status !== 402) continue
|
|
2555
|
-
|
|
2556
|
-
const response = result.challenge as Response
|
|
2557
|
-
const wwwAuth = response.headers.get(paymentAuthChallengeHeader)
|
|
2558
|
-
if (!wwwAuth) continue
|
|
2559
|
-
|
|
2560
|
-
entries.push({
|
|
2561
|
-
handler: selectedHandlers[i] as ConfiguredHandler,
|
|
2562
|
-
challenge: Challenge.deserialize(wwwAuth),
|
|
2563
|
-
result,
|
|
2564
|
-
})
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2567
|
-
const acceptPayment = input.headers.get(Constants.Headers.acceptPayment)
|
|
2568
|
-
if (!acceptPayment) return entries
|
|
2569
|
-
|
|
2570
|
-
try {
|
|
2571
|
-
const ranked = AcceptPayment.rank(
|
|
2572
|
-
entries.map((entry) => entry.challenge),
|
|
2573
|
-
AcceptPayment.parse(acceptPayment),
|
|
2574
|
-
)
|
|
2575
|
-
if (ranked.length === 0) return entries
|
|
2576
|
-
|
|
2577
|
-
const entriesById = new Map(entries.map((entry) => [entry.challenge.id, entry] as const))
|
|
2578
|
-
return ranked.map((challenge) => entriesById.get(challenge.id)!)
|
|
2579
|
-
} catch {
|
|
2580
|
-
return entries
|
|
2581
|
-
}
|
|
2582
|
-
})()
|
|
2583
|
-
|
|
2584
|
-
const challengeResponses = results.flatMap((result) =>
|
|
2585
|
-
result.status === 402 ? [result.challenge as Response] : [],
|
|
2586
|
-
)
|
|
2587
|
-
const unnegotiatedX402Responses =
|
|
2588
|
-
input.headers.has(Constants.Headers.acceptPayment) || challengeEntries.length === 0
|
|
2589
|
-
? []
|
|
2590
|
-
: challengeResponses.filter(
|
|
2591
|
-
(response) =>
|
|
2592
|
-
response.headers.has(x402_Types.paymentRequiredHeader) &&
|
|
2593
|
-
!response.headers.has(paymentAuthChallengeHeader),
|
|
2594
|
-
)
|
|
2595
|
-
const negotiatedChallengeResponses =
|
|
2596
|
-
challengeEntries.length > 0
|
|
2597
|
-
? [
|
|
2598
|
-
...challengeEntries.map((entry) => entry.result.challenge as Response),
|
|
2599
|
-
...unnegotiatedX402Responses,
|
|
2600
|
-
]
|
|
2601
|
-
: challengeResponses
|
|
2602
|
-
|
|
2603
|
-
// Merge challenge headers from the negotiated 402 responses.
|
|
2604
|
-
const mergedHeaders = new Headers()
|
|
2605
|
-
mergedHeaders.set('Cache-Control', 'no-store')
|
|
2606
|
-
|
|
2607
|
-
for (const header of challengeHeaderMerges) {
|
|
2608
|
-
for (const value of header.merge(
|
|
2609
|
-
header.values({
|
|
2610
|
-
challengeEntries,
|
|
2611
|
-
challengeResponses,
|
|
2612
|
-
negotiatedChallengeResponses,
|
|
2613
|
-
}),
|
|
2614
|
-
)) {
|
|
2615
|
-
mergedHeaders.append(header.name, value)
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
|
|
2619
|
-
// Collect html-enabled handlers and their challenges
|
|
2620
|
-
const htmlEntries = challengeEntries.filter((entry) => entry.handler._internal?.html)
|
|
2621
|
-
|
|
2622
|
-
const wantsHtml = input.headers.get('Accept')?.includes('text/html')
|
|
2623
|
-
if (wantsHtml && htmlEntries.length > 0) {
|
|
2624
|
-
const { theme, text } = Html.resolveOptions(
|
|
2625
|
-
// Use compose-level options or first html-enabled method's config for the page shell
|
|
2626
|
-
composeOptions ?? htmlEntries[0]?.handler._internal.html ?? ({} as Html.Options),
|
|
2627
|
-
)
|
|
2628
|
-
|
|
2629
|
-
// Build data map keyed by challenge.id
|
|
2630
|
-
const dataMap: Record<string, Html.Data> = {}
|
|
2631
|
-
for (let i = 0; i < htmlEntries.length; i++) {
|
|
2632
|
-
const entry = htmlEntries[i]!
|
|
2633
|
-
dataMap[entry.challenge.id] = {
|
|
2634
|
-
label: entry.handler._internal.name,
|
|
2635
|
-
rootId: `${Html.ids.root}-${i}`,
|
|
2636
|
-
formattedAmount: await entry.handler._internal.html!.formatAmount(
|
|
2637
|
-
entry.challenge.request,
|
|
2638
|
-
),
|
|
2639
|
-
config: entry.handler._internal.html!.config,
|
|
2640
|
-
challenge: entry.challenge as never,
|
|
2641
|
-
text,
|
|
2642
|
-
theme,
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
mergedHeaders.set('Content-Type', 'text/html; charset=utf-8')
|
|
2647
|
-
|
|
2648
|
-
const firstData = Object.values(dataMap)[0]!
|
|
2649
|
-
const body = Html.render({
|
|
2650
|
-
entries: htmlEntries.map((entry) => ({
|
|
2651
|
-
challenge: entry.challenge,
|
|
2652
|
-
content: entry.handler._internal.html!.content,
|
|
2653
|
-
})),
|
|
2654
|
-
dataMap,
|
|
2655
|
-
formattedAmount: firstData.formattedAmount,
|
|
2656
|
-
panels: true,
|
|
2657
|
-
text,
|
|
2658
|
-
theme,
|
|
2659
|
-
})
|
|
2660
|
-
|
|
2661
|
-
return {
|
|
2662
|
-
status: 402,
|
|
2663
|
-
challenge: new Response(body, { status: 402, headers: mergedHeaders }),
|
|
2664
|
-
}
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
// Non-HTML fallback: prefer the first Payment-auth body, otherwise use
|
|
2668
|
-
// the first transport-specific 402 body.
|
|
2669
|
-
let body: string | null = null
|
|
2670
|
-
const bodyResponses =
|
|
2671
|
-
challengeEntries.length > 0
|
|
2672
|
-
? challengeEntries.map((entry) => entry.result.challenge as Response)
|
|
2673
|
-
: challengeResponses
|
|
2674
|
-
for (const response of bodyResponses) {
|
|
2675
|
-
if (!body) {
|
|
2676
|
-
const contentType = response.headers.get('Content-Type')
|
|
2677
|
-
if (contentType) mergedHeaders.set('Content-Type', contentType)
|
|
2678
|
-
body = await response.text()
|
|
2679
|
-
break
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
|
-
|
|
2683
|
-
return {
|
|
2684
|
-
status: 402,
|
|
2685
|
-
challenge: new Response(body, { status: 402, headers: mergedHeaders }),
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
const offers = handlers.flatMap(getConfiguredOffers)
|
|
2690
|
-
if (offers.length > 0) composed._internal = { offers }
|
|
2691
|
-
return composed
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
/** Returns the flattened configured offers accepted by a handler. */
|
|
2695
|
-
function getConfiguredOffers(
|
|
2696
|
-
handler: ConfiguredHandler | ComposedHandler,
|
|
2697
|
-
): readonly ConfiguredHandler['_internal'][] {
|
|
2698
|
-
const internal = handler._internal
|
|
2699
|
-
if (!internal) return []
|
|
2700
|
-
if (isComposedHandlerMetadata(internal)) return internal.offers
|
|
2701
|
-
return [internal]
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
function isComposedHandlerMetadata(
|
|
2705
|
-
internal: HandlerDiscoveryMetadata,
|
|
2706
|
-
): internal is NonNullable<ComposedHandler['_internal']> {
|
|
2707
|
-
return 'offers' in internal && !('_canonicalRequest' in internal)
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
function createServerOffer(internal: ConfiguredHandler['_internal']): ServerOffer {
|
|
2711
|
-
return Object.freeze({
|
|
2712
|
-
key: `${internal.name}/${internal.intent}`,
|
|
2713
|
-
method: Object.freeze({
|
|
2714
|
-
...(internal._method.alias ? { alias: internal._method.alias } : {}),
|
|
2715
|
-
intent: internal.intent,
|
|
2716
|
-
name: internal.name,
|
|
2717
|
-
}),
|
|
2718
|
-
request: immutableSnapshot(internal._canonicalRequest),
|
|
2719
|
-
}) as ServerOffer
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
/** Applies and validates the server-level offer selection policy. */
|
|
2723
|
-
async function selectOfferHandlers(parameters: {
|
|
2724
|
-
entries: readonly {
|
|
2725
|
-
handler: (input: Request) => Promise<MethodFn.Response<Transport.Http>>
|
|
2726
|
-
method: Method.AnyServer | undefined
|
|
2727
|
-
offer: ServerOffer | undefined
|
|
2728
|
-
}[]
|
|
2729
|
-
input: Request
|
|
2730
|
-
selectOffers: SelectOffers<readonly Method.AnyServer[]> | undefined
|
|
2731
|
-
}) {
|
|
2732
|
-
const { entries, input, selectOffers } = parameters
|
|
2733
|
-
const eligibleEntries: (typeof entries)[number][] = []
|
|
2734
|
-
for (const entry of entries) {
|
|
2735
|
-
const canOffer = entry.method?.canOffer
|
|
2736
|
-
if (canOffer) {
|
|
2737
|
-
const approved = await canOffer({
|
|
2738
|
-
input: cloneOfferInput(input),
|
|
2739
|
-
request: entry.offer!.request,
|
|
2740
|
-
})
|
|
2741
|
-
if (typeof approved !== 'boolean') throw new Error('canOffer() must return a boolean')
|
|
2742
|
-
if (!approved) continue
|
|
2743
|
-
}
|
|
2744
|
-
eligibleEntries.push(entry)
|
|
2745
|
-
}
|
|
2746
|
-
|
|
2747
|
-
if (eligibleEntries.length === 0)
|
|
2748
|
-
throw new Error('No payment offers are available for this request')
|
|
2749
|
-
if (!selectOffers) return eligibleEntries.map(({ handler }) => handler)
|
|
2750
|
-
|
|
2751
|
-
const offers = eligibleEntries.map(({ offer }) => {
|
|
2752
|
-
if (!offer) throw new Error('selectOffers() can only select configured payment offers')
|
|
2753
|
-
return offer
|
|
2754
|
-
})
|
|
2755
|
-
const selected = await selectOffers(
|
|
2756
|
-
Object.freeze(offers),
|
|
2757
|
-
Object.freeze({ request: cloneOfferInput(input) }),
|
|
2758
|
-
)
|
|
2759
|
-
|
|
2760
|
-
if (!Array.isArray(selected)) throw new Error('selectOffers() must return an array of offers')
|
|
2761
|
-
if (selected.length === 0) throw new Error('selectOffers() must select at least one offer')
|
|
2762
|
-
|
|
2763
|
-
let previousIndex = -1
|
|
2764
|
-
const selectedHandlers = selected.map((offer) => {
|
|
2765
|
-
const index = offers.indexOf(offer)
|
|
2766
|
-
if (index === -1) throw new Error('selectOffers() must return only offers from its input array')
|
|
2767
|
-
if (index <= previousIndex)
|
|
2768
|
-
throw new Error('selectOffers() must preserve offer order and must not return duplicates')
|
|
2769
|
-
previousIndex = index
|
|
2770
|
-
return eligibleEntries[index]!.handler
|
|
2771
|
-
})
|
|
2772
|
-
|
|
2773
|
-
return selectedHandlers
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
|
-
/** Clones a request for offer hooks, falling back to a bodyless metadata copy. */
|
|
2777
|
-
function cloneOfferInput(input: Request): Request {
|
|
2778
|
-
try {
|
|
2779
|
-
return input.clone()
|
|
2780
|
-
} catch {
|
|
2781
|
-
return new globalThis.Request(input.url, {
|
|
2782
|
-
headers: new Headers(input.headers),
|
|
2783
|
-
method: input.method,
|
|
2784
|
-
signal: input.signal,
|
|
2785
|
-
})
|
|
2786
|
-
}
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
/** Clones and recursively freezes protocol data exposed to offer hooks. */
|
|
2790
|
-
function immutableSnapshot<value>(value: value): DeepReadonly<value> {
|
|
2791
|
-
return deepFreeze(structuredClone(value))
|
|
2792
|
-
}
|
|
2793
|
-
|
|
2794
|
-
function deepFreeze<value>(value: value): DeepReadonly<value> {
|
|
2795
|
-
if (!value || typeof value !== 'object' || Object.isFrozen(value))
|
|
2796
|
-
return value as DeepReadonly<value>
|
|
2797
|
-
|
|
2798
|
-
for (const nested of Object.values(value)) deepFreeze(nested)
|
|
2799
|
-
return Object.freeze(value) as DeepReadonly<value>
|
|
2800
|
-
}
|
|
2801
|
-
|
|
2802
|
-
type ChallengeHeaderMerge = {
|
|
2803
|
-
name: string
|
|
2804
|
-
values(context: {
|
|
2805
|
-
challengeEntries: readonly {
|
|
2806
|
-
handler: ConfiguredHandler
|
|
2807
|
-
challenge: Challenge.Challenge
|
|
2808
|
-
result: Extract<MethodFn.Response<Transport.Http>, { status: 402 }>
|
|
2809
|
-
}[]
|
|
2810
|
-
challengeResponses: readonly Response[]
|
|
2811
|
-
negotiatedChallengeResponses: readonly Response[]
|
|
2812
|
-
}): readonly string[]
|
|
2813
|
-
merge(values: readonly string[]): readonly string[]
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
|
-
function mergeX402PaymentRequiredHeaders(values: readonly string[]): readonly string[] {
|
|
2817
|
-
if (values.length === 0) return []
|
|
2818
|
-
const [first, ...rest] = values.map((value) => x402_Header.decodePaymentRequired(value))
|
|
2819
|
-
if (!first) throw new Error('Expected at least one x402 payment-required header.')
|
|
2820
|
-
const incompatible = rest.some(
|
|
2821
|
-
(value) =>
|
|
2822
|
-
!isDeepStrictEqual(value.resource, first.resource) ||
|
|
2823
|
-
!isDeepStrictEqual(value.extensions, first.extensions),
|
|
2824
|
-
)
|
|
2825
|
-
if (incompatible)
|
|
2826
|
-
return [
|
|
2827
|
-
x402_Header.encodePaymentRequired({
|
|
2828
|
-
...first,
|
|
2829
|
-
error: [first.error, 'Cannot merge x402 payment requirements with different resources.']
|
|
2830
|
-
.filter((value): value is string => value !== undefined && value.length > 0)
|
|
2831
|
-
.join('; '),
|
|
2832
|
-
}),
|
|
2833
|
-
]
|
|
2834
|
-
const error = [first.error, ...rest.map((value) => value.error)]
|
|
2835
|
-
.filter((value): value is string => value !== undefined && value.length > 0)
|
|
2836
|
-
.join('; ')
|
|
2837
|
-
return [
|
|
2838
|
-
x402_Header.encodePaymentRequired({
|
|
2839
|
-
...first,
|
|
2840
|
-
accepts: [first.accepts, ...rest.map((value) => value.accepts)].flat(),
|
|
2841
|
-
...(error ? { error } : {}),
|
|
2842
|
-
}),
|
|
2843
|
-
]
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
/**
|
|
2847
|
-
* Wraps a payment handler to create a Node.js HTTP listener.
|
|
2848
|
-
*
|
|
2849
|
-
* On 402: writes the challenge response and ends the connection.
|
|
2850
|
-
* On 200: sets the Payment-Receipt header; caller should write response body.
|
|
2851
|
-
*
|
|
2852
|
-
* @example
|
|
2853
|
-
* ```ts
|
|
2854
|
-
* import * as http from 'node:http'
|
|
2855
|
-
* import { Mppx } from 'mppx/server'
|
|
2856
|
-
*
|
|
2857
|
-
* const payment = Mppx.create({ ... })
|
|
2858
|
-
*
|
|
2859
|
-
* http.createServer(async (req, res) => {
|
|
2860
|
-
* const result = await Mppx.toNodeListener(
|
|
2861
|
-
* payment.charge({
|
|
2862
|
-
* amount: '1', currency: '...', recipient: '0x...',
|
|
2863
|
-
* }),
|
|
2864
|
-
* )(req, res)
|
|
2865
|
-
* if (result.status === 402) return
|
|
2866
|
-
* res.end('OK')
|
|
2867
|
-
* })
|
|
2868
|
-
* ```
|
|
2869
|
-
*/
|
|
2870
|
-
export function toNodeListener(
|
|
2871
|
-
handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Http>>,
|
|
2872
|
-
): (req: IncomingMessage, res: ServerResponse) => Promise<MethodFn.Response<Transport.Http>> {
|
|
2873
|
-
return async (req, res) => {
|
|
2874
|
-
const result = await handler(Request.fromNodeListener(req, res))
|
|
2875
|
-
|
|
2876
|
-
if (result.status === 402) {
|
|
2877
|
-
await NodeListener.sendResponse(res, result.challenge as globalThis.Response)
|
|
2878
|
-
} else {
|
|
2879
|
-
const managementResponse = getManagementResponse(result)
|
|
2880
|
-
if (managementResponse) {
|
|
2881
|
-
await NodeListener.sendResponse(res, managementResponse)
|
|
2882
|
-
return { challenge: managementResponse, status: 402 }
|
|
2883
|
-
}
|
|
2884
|
-
|
|
2885
|
-
const wrapped = result.withReceipt(new globalThis.Response()) as globalThis.Response
|
|
2886
|
-
for (const [name, value] of wrapped.headers) res.setHeader(name, value)
|
|
2887
|
-
}
|
|
2888
|
-
|
|
2889
|
-
return result
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
|
|
2893
|
-
function getManagementResponse(
|
|
2894
|
-
result: Extract<MethodFn.Response<Transport.Http>, { status: 200 }>,
|
|
2895
|
-
): globalThis.Response | null {
|
|
2896
|
-
try {
|
|
2897
|
-
return (result.withReceipt as () => globalThis.Response)()
|
|
2898
|
-
} catch (error) {
|
|
2899
|
-
if (isMissingReceiptResponseError(error)) {
|
|
2900
|
-
return null
|
|
2901
|
-
}
|
|
2902
|
-
throw error
|
|
2903
|
-
}
|
|
2904
|
-
}
|
|
2905
|
-
|
|
2906
|
-
/**
|
|
2907
|
-
* Flattens a methods config tuple, preserving positional types.
|
|
2908
|
-
* @internal
|
|
2909
|
-
*/
|
|
2910
|
-
type FlattenMethods<methods extends Methods> = methods extends readonly [
|
|
2911
|
-
infer head,
|
|
2912
|
-
...infer tail extends Methods,
|
|
2913
|
-
]
|
|
2914
|
-
? head extends readonly Method.AnyServer[]
|
|
2915
|
-
? readonly [...head, ...FlattenMethods<tail>]
|
|
2916
|
-
: head extends Method.AnyServer
|
|
2917
|
-
? readonly [head, ...FlattenMethods<tail>]
|
|
2918
|
-
: never
|
|
2919
|
-
: readonly []
|