mppx 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/BodyDigest.d.ts +42 -0
- package/dist/BodyDigest.d.ts.map +1 -0
- package/dist/BodyDigest.js +40 -0
- package/dist/BodyDigest.js.map +1 -0
- package/dist/Challenge.d.ts +271 -0
- package/dist/Challenge.d.ts.map +1 -0
- package/dist/Challenge.js +291 -0
- package/dist/Challenge.js.map +1 -0
- package/dist/Credential.d.ts +91 -0
- package/dist/Credential.d.ts.map +1 -0
- package/dist/Credential.js +122 -0
- package/dist/Credential.js.map +1 -0
- package/dist/Errors.d.ts +243 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Errors.js +201 -0
- package/dist/Errors.js.map +1 -0
- package/dist/Expires.d.ts +15 -0
- package/dist/Expires.d.ts.map +1 -0
- package/dist/Expires.js +29 -0
- package/dist/Expires.js.map +1 -0
- package/dist/Intent.d.ts +101 -0
- package/dist/Intent.d.ts.map +1 -0
- package/dist/Intent.js +83 -0
- package/dist/Intent.js.map +1 -0
- package/dist/Mcp.d.ts +74 -0
- package/dist/Mcp.d.ts.map +1 -0
- package/dist/Mcp.js +9 -0
- package/dist/Mcp.js.map +1 -0
- package/dist/MethodIntent.d.ts +225 -0
- package/dist/MethodIntent.d.ts.map +1 -0
- package/dist/MethodIntent.js +156 -0
- package/dist/MethodIntent.js.map +1 -0
- package/dist/PaymentRequest.d.ts +88 -0
- package/dist/PaymentRequest.d.ts.map +1 -0
- package/dist/PaymentRequest.js +81 -0
- package/dist/PaymentRequest.js.map +1 -0
- package/dist/Receipt.d.ts +110 -0
- package/dist/Receipt.d.ts.map +1 -0
- package/dist/Receipt.js +105 -0
- package/dist/Receipt.js.map +1 -0
- package/dist/Store.d.ts +28 -0
- package/dist/Store.d.ts.map +1 -0
- package/dist/Store.js +61 -0
- package/dist/Store.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1219 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/Methods.d.ts +4 -0
- package/dist/client/Methods.d.ts.map +1 -0
- package/dist/client/Methods.js +4 -0
- package/dist/client/Methods.js.map +1 -0
- package/dist/client/Mppx.d.ts +84 -0
- package/dist/client/Mppx.d.ts.map +1 -0
- package/dist/client/Mppx.js +64 -0
- package/dist/client/Mppx.js.map +1 -0
- package/dist/client/Transport.d.ts +56 -0
- package/dist/client/Transport.d.ts.map +1 -0
- package/dist/client/Transport.js +81 -0
- package/dist/client/Transport.js.map +1 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/internal/Fetch.d.ts +85 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -0
- package/dist/client/internal/Fetch.js +95 -0
- package/dist/client/internal/Fetch.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/types.d.ts +302 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/mcp-sdk/client/McpClient.d.ts +78 -0
- package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -0
- package/dist/mcp-sdk/client/McpClient.js +98 -0
- package/dist/mcp-sdk/client/McpClient.js.map +1 -0
- package/dist/mcp-sdk/client/index.d.ts +3 -0
- package/dist/mcp-sdk/client/index.d.ts.map +1 -0
- package/dist/mcp-sdk/client/index.js +3 -0
- package/dist/mcp-sdk/client/index.js.map +1 -0
- package/dist/mcp-sdk/server/Transport.d.ts +43 -0
- package/dist/mcp-sdk/server/Transport.d.ts.map +1 -0
- package/dist/mcp-sdk/server/Transport.js +71 -0
- package/dist/mcp-sdk/server/Transport.js.map +1 -0
- package/dist/mcp-sdk/server/index.d.ts +2 -0
- package/dist/mcp-sdk/server/index.d.ts.map +1 -0
- package/dist/mcp-sdk/server/index.js +2 -0
- package/dist/mcp-sdk/server/index.js.map +1 -0
- package/dist/middlewares/elysia.d.ts +51 -0
- package/dist/middlewares/elysia.d.ts.map +1 -0
- package/dist/middlewares/elysia.js +59 -0
- package/dist/middlewares/elysia.js.map +1 -0
- package/dist/middlewares/express.d.ts +46 -0
- package/dist/middlewares/express.d.ts.map +1 -0
- package/dist/middlewares/express.js +69 -0
- package/dist/middlewares/express.js.map +1 -0
- package/dist/middlewares/hono.d.ts +46 -0
- package/dist/middlewares/hono.d.ts.map +1 -0
- package/dist/middlewares/hono.js +57 -0
- package/dist/middlewares/hono.js.map +1 -0
- package/dist/middlewares/internal/mppx.d.ts +16 -0
- package/dist/middlewares/internal/mppx.d.ts.map +1 -0
- package/dist/middlewares/internal/mppx.js +16 -0
- package/dist/middlewares/internal/mppx.js.map +1 -0
- package/dist/middlewares/nextjs.d.ts +45 -0
- package/dist/middlewares/nextjs.d.ts.map +1 -0
- package/dist/middlewares/nextjs.js +57 -0
- package/dist/middlewares/nextjs.js.map +1 -0
- package/dist/proxy/Proxy.d.ts +47 -0
- package/dist/proxy/Proxy.d.ts.map +1 -0
- package/dist/proxy/Proxy.js +126 -0
- package/dist/proxy/Proxy.js.map +1 -0
- package/dist/proxy/Service.d.ts +100 -0
- package/dist/proxy/Service.d.ts.map +1 -0
- package/dist/proxy/Service.js +147 -0
- package/dist/proxy/Service.js.map +1 -0
- package/dist/proxy/index.d.ts +7 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +7 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/internal/Headers.d.ts +3 -0
- package/dist/proxy/internal/Headers.d.ts.map +1 -0
- package/dist/proxy/internal/Headers.js +41 -0
- package/dist/proxy/internal/Headers.js.map +1 -0
- package/dist/proxy/internal/Route.d.ts +14 -0
- package/dist/proxy/internal/Route.d.ts.map +1 -0
- package/dist/proxy/internal/Route.js +47 -0
- package/dist/proxy/internal/Route.js.map +1 -0
- package/dist/proxy/services/anthropic.d.ts +29 -0
- package/dist/proxy/services/anthropic.d.ts.map +1 -0
- package/dist/proxy/services/anthropic.js +30 -0
- package/dist/proxy/services/anthropic.js.map +1 -0
- package/dist/proxy/services/openai.d.ts +29 -0
- package/dist/proxy/services/openai.d.ts.map +1 -0
- package/dist/proxy/services/openai.js +30 -0
- package/dist/proxy/services/openai.js.map +1 -0
- package/dist/proxy/services/stripe.d.ts +29 -0
- package/dist/proxy/services/stripe.d.ts.map +1 -0
- package/dist/proxy/services/stripe.js +30 -0
- package/dist/proxy/services/stripe.js.map +1 -0
- package/dist/server/Methods.d.ts +3 -0
- package/dist/server/Methods.d.ts.map +1 -0
- package/dist/server/Methods.js +3 -0
- package/dist/server/Methods.js.map +1 -0
- package/dist/server/Mppx.d.ts +116 -0
- package/dist/server/Mppx.d.ts.map +1 -0
- package/dist/server/Mppx.js +207 -0
- package/dist/server/Mppx.js.map +1 -0
- package/dist/server/NodeListener.d.ts +3 -0
- package/dist/server/NodeListener.d.ts.map +1 -0
- package/dist/server/NodeListener.js +3 -0
- package/dist/server/NodeListener.js.map +1 -0
- package/dist/server/Request.d.ts +24 -0
- package/dist/server/Request.d.ts.map +1 -0
- package/dist/server/Request.js +26 -0
- package/dist/server/Request.js.map +1 -0
- package/dist/server/Response.d.ts +10 -0
- package/dist/server/Response.d.ts.map +1 -0
- package/dist/server/Response.js +15 -0
- package/dist/server/Response.js.map +1 -0
- package/dist/server/Transport.d.ts +93 -0
- package/dist/server/Transport.d.ts.map +1 -0
- package/dist/server/Transport.js +132 -0
- package/dist/server/Transport.js.map +1 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +9 -0
- package/dist/server/index.js.map +1 -0
- package/dist/stripe/Intents.d.ts +54 -0
- package/dist/stripe/Intents.d.ts.map +1 -0
- package/dist/stripe/Intents.js +27 -0
- package/dist/stripe/Intents.js.map +1 -0
- package/dist/stripe/client/Charge.d.ts +114 -0
- package/dist/stripe/client/Charge.d.ts.map +1 -0
- package/dist/stripe/client/Charge.js +77 -0
- package/dist/stripe/client/Charge.js.map +1 -0
- package/dist/stripe/client/MethodIntents.d.ts +80 -0
- package/dist/stripe/client/MethodIntents.d.ts.map +1 -0
- package/dist/stripe/client/MethodIntents.js +34 -0
- package/dist/stripe/client/MethodIntents.js.map +1 -0
- package/dist/stripe/client/index.d.ts +3 -0
- package/dist/stripe/client/index.d.ts.map +1 -0
- package/dist/stripe/client/index.js +3 -0
- package/dist/stripe/client/index.js.map +1 -0
- package/dist/stripe/index.d.ts +2 -0
- package/dist/stripe/index.d.ts.map +1 -0
- package/dist/stripe/index.js +2 -0
- package/dist/stripe/index.js.map +1 -0
- package/dist/stripe/server/Charge.d.ts +74 -0
- package/dist/stripe/server/Charge.d.ts.map +1 -0
- package/dist/stripe/server/Charge.js +79 -0
- package/dist/stripe/server/Charge.js.map +1 -0
- package/dist/stripe/server/MethodIntents.d.ts +65 -0
- package/dist/stripe/server/MethodIntents.d.ts.map +1 -0
- package/dist/stripe/server/MethodIntents.js +21 -0
- package/dist/stripe/server/MethodIntents.js.map +1 -0
- package/dist/stripe/server/index.d.ts +3 -0
- package/dist/stripe/server/index.d.ts.map +1 -0
- package/dist/stripe/server/index.js +3 -0
- package/dist/stripe/server/index.js.map +1 -0
- package/dist/tempo/Attribution.d.ts +101 -0
- package/dist/tempo/Attribution.d.ts.map +1 -0
- package/dist/tempo/Attribution.js +124 -0
- package/dist/tempo/Attribution.js.map +1 -0
- package/dist/tempo/Intents.d.ts +132 -0
- package/dist/tempo/Intents.d.ts.map +1 -0
- package/dist/tempo/Intents.js +81 -0
- package/dist/tempo/Intents.js.map +1 -0
- package/dist/tempo/client/ChannelOps.d.ts +54 -0
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -0
- package/dist/tempo/client/ChannelOps.js +138 -0
- package/dist/tempo/client/ChannelOps.js.map +1 -0
- package/dist/tempo/client/Charge.d.ts +76 -0
- package/dist/tempo/client/Charge.d.ts.map +1 -0
- package/dist/tempo/client/Charge.js +69 -0
- package/dist/tempo/client/Charge.js.map +1 -0
- package/dist/tempo/client/MethodIntents.d.ts +157 -0
- package/dist/tempo/client/MethodIntents.d.ts.map +1 -0
- package/dist/tempo/client/MethodIntents.js +25 -0
- package/dist/tempo/client/MethodIntents.js.map +1 -0
- package/dist/tempo/client/Session.d.ts +159 -0
- package/dist/tempo/client/Session.d.ts.map +1 -0
- package/dist/tempo/client/Session.js +263 -0
- package/dist/tempo/client/Session.js.map +1 -0
- package/dist/tempo/client/SessionManager.d.ts +62 -0
- package/dist/tempo/client/SessionManager.d.ts.map +1 -0
- package/dist/tempo/client/SessionManager.js +196 -0
- package/dist/tempo/client/SessionManager.js.map +1 -0
- package/dist/tempo/client/index.d.ts +6 -0
- package/dist/tempo/client/index.d.ts.map +1 -0
- package/dist/tempo/client/index.js +5 -0
- package/dist/tempo/client/index.js.map +1 -0
- package/dist/tempo/index.d.ts +3 -0
- package/dist/tempo/index.d.ts.map +1 -0
- package/dist/tempo/index.js +3 -0
- package/dist/tempo/index.js.map +1 -0
- package/dist/tempo/internal/account.d.ts +32 -0
- package/dist/tempo/internal/account.d.ts.map +1 -0
- package/dist/tempo/internal/account.js +33 -0
- package/dist/tempo/internal/account.js.map +1 -0
- package/dist/tempo/internal/defaults.d.ts +18 -0
- package/dist/tempo/internal/defaults.d.ts.map +1 -0
- package/dist/tempo/internal/defaults.js +18 -0
- package/dist/tempo/internal/defaults.js.map +1 -0
- package/dist/tempo/internal/types.d.ts +11 -0
- package/dist/tempo/internal/types.d.ts.map +1 -0
- package/dist/tempo/internal/types.js +2 -0
- package/dist/tempo/internal/types.js.map +1 -0
- package/dist/tempo/server/Charge.d.ts +77 -0
- package/dist/tempo/server/Charge.d.ts.map +1 -0
- package/dist/tempo/server/Charge.js +228 -0
- package/dist/tempo/server/Charge.js.map +1 -0
- package/dist/tempo/server/MethodIntents.d.ts +140 -0
- package/dist/tempo/server/MethodIntents.d.ts.map +1 -0
- package/dist/tempo/server/MethodIntents.js +26 -0
- package/dist/tempo/server/MethodIntents.js.map +1 -0
- package/dist/tempo/server/Session.d.ts +148 -0
- package/dist/tempo/server/Session.d.ts.map +1 -0
- package/dist/tempo/server/Session.js +529 -0
- package/dist/tempo/server/Session.js.map +1 -0
- package/dist/tempo/server/internal/transport.d.ts +47 -0
- package/dist/tempo/server/internal/transport.d.ts.map +1 -0
- package/dist/tempo/server/internal/transport.js +118 -0
- package/dist/tempo/server/internal/transport.js.map +1 -0
- package/dist/tempo/stream/Chain.d.ts +52 -0
- package/dist/tempo/stream/Chain.d.ts.map +1 -0
- package/dist/tempo/stream/Chain.js +215 -0
- package/dist/tempo/stream/Chain.js.map +1 -0
- package/dist/tempo/stream/Channel.d.ts +26 -0
- package/dist/tempo/stream/Channel.d.ts.map +1 -0
- package/dist/tempo/stream/Channel.js +27 -0
- package/dist/tempo/stream/Channel.js.map +1 -0
- package/dist/tempo/stream/ChannelStore.d.ts +103 -0
- package/dist/tempo/stream/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/stream/ChannelStore.js +100 -0
- package/dist/tempo/stream/ChannelStore.js.map +1 -0
- package/dist/tempo/stream/Receipt.d.ts +22 -0
- package/dist/tempo/stream/Receipt.d.ts.map +1 -0
- package/dist/tempo/stream/Receipt.js +34 -0
- package/dist/tempo/stream/Receipt.js.map +1 -0
- package/dist/tempo/stream/Sse.d.ts +134 -0
- package/dist/tempo/stream/Sse.d.ts.map +1 -0
- package/dist/tempo/stream/Sse.js +288 -0
- package/dist/tempo/stream/Sse.js.map +1 -0
- package/dist/tempo/stream/Types.d.ts +78 -0
- package/dist/tempo/stream/Types.d.ts.map +1 -0
- package/dist/tempo/stream/Types.js +2 -0
- package/dist/tempo/stream/Types.js.map +1 -0
- package/dist/tempo/stream/Voucher.d.ts +20 -0
- package/dist/tempo/stream/Voucher.d.ts.map +1 -0
- package/dist/tempo/stream/Voucher.js +98 -0
- package/dist/tempo/stream/Voucher.js.map +1 -0
- package/dist/tempo/stream/escrow.abi.d.ts +598 -0
- package/dist/tempo/stream/escrow.abi.d.ts.map +1 -0
- package/dist/tempo/stream/escrow.abi.js +760 -0
- package/dist/tempo/stream/escrow.abi.js.map +1 -0
- package/dist/tempo/stream/index.d.ts +8 -0
- package/dist/tempo/stream/index.d.ts.map +1 -0
- package/dist/tempo/stream/index.js +8 -0
- package/dist/tempo/stream/index.js.map +1 -0
- package/dist/viem/Account.d.ts +12 -0
- package/dist/viem/Account.d.ts.map +1 -0
- package/dist/viem/Account.js +14 -0
- package/dist/viem/Account.js.map +1 -0
- package/dist/viem/Client.d.ts +21 -0
- package/dist/viem/Client.d.ts.map +1 -0
- package/dist/viem/Client.js +19 -0
- package/dist/viem/Client.js.map +1 -0
- package/dist/zod.d.ts +17 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +35 -0
- package/dist/zod.js.map +1 -0
- package/package.json +117 -4
- package/src/BodyDigest.test.ts +43 -0
- package/src/BodyDigest.ts +53 -0
- package/src/Challenge.test-d.ts +81 -0
- package/src/Challenge.test.ts +414 -0
- package/src/Challenge.ts +429 -0
- package/src/Credential.test.ts +227 -0
- package/src/Credential.ts +154 -0
- package/src/Errors.test.ts +402 -0
- package/src/Errors.ts +348 -0
- package/src/Expires.ts +34 -0
- package/src/Intent.test.ts +180 -0
- package/src/Intent.ts +109 -0
- package/src/Mcp.ts +81 -0
- package/src/MethodIntent.test.ts +303 -0
- package/src/MethodIntent.ts +388 -0
- package/src/PaymentRequest.test.ts +152 -0
- package/src/PaymentRequest.ts +107 -0
- package/src/Receipt.test.ts +98 -0
- package/src/Receipt.ts +129 -0
- package/src/Store.ts +84 -0
- package/src/cli.test.ts +542 -0
- package/src/cli.ts +1319 -0
- package/src/client/Methods.ts +3 -0
- package/src/client/Mppx.test-d.ts +90 -0
- package/src/client/Mppx.test.ts +468 -0
- package/src/client/Mppx.ts +149 -0
- package/src/client/Transport.test.ts +283 -0
- package/src/client/Transport.ts +115 -0
- package/src/client/index.ts +4 -0
- package/src/client/internal/Fetch.test-d.ts +57 -0
- package/src/client/internal/Fetch.test.ts +281 -0
- package/src/client/internal/Fetch.ts +157 -0
- package/src/env.d.ts +11 -0
- package/src/index.ts +12 -0
- package/src/internal/types.ts +403 -0
- package/src/mcp-sdk/client/McpClient.test-d.ts +109 -0
- package/src/mcp-sdk/client/McpClient.test.ts +219 -0
- package/src/mcp-sdk/client/McpClient.ts +187 -0
- package/src/mcp-sdk/client/index.ts +2 -0
- package/src/mcp-sdk/server/Transport.ts +94 -0
- package/src/mcp-sdk/server/index.ts +1 -0
- package/src/middlewares/elysia.ts +66 -0
- package/src/middlewares/express.test.ts +155 -0
- package/src/middlewares/express.ts +82 -0
- package/src/middlewares/hono.test.ts +148 -0
- package/src/middlewares/hono.ts +62 -0
- package/src/middlewares/internal/mppx.ts +30 -0
- package/src/middlewares/nextjs.test.ts +164 -0
- package/src/middlewares/nextjs.ts +66 -0
- package/src/proxy/Proxy.test.ts +472 -0
- package/src/proxy/Proxy.ts +175 -0
- package/src/proxy/Service.test.ts +125 -0
- package/src/proxy/Service.ts +227 -0
- package/src/proxy/index.ts +6 -0
- package/src/proxy/internal/Headers.test.ts +100 -0
- package/src/proxy/internal/Headers.ts +40 -0
- package/src/proxy/internal/Route.test.ts +143 -0
- package/src/proxy/internal/Route.ts +54 -0
- package/src/proxy/services/anthropic.ts +45 -0
- package/src/proxy/services/openai.test.ts +97 -0
- package/src/proxy/services/openai.ts +48 -0
- package/src/proxy/services/stripe.ts +49 -0
- package/src/server/Methods.ts +2 -0
- package/src/server/Mppx.test-d.ts +343 -0
- package/src/server/Mppx.test.ts +342 -0
- package/src/server/Mppx.ts +378 -0
- package/src/server/NodeListener.test.ts +188 -0
- package/src/server/NodeListener.ts +3 -0
- package/src/server/Request.test.ts +102 -0
- package/src/server/Request.ts +33 -0
- package/src/server/Response.test.ts +31 -0
- package/src/server/Response.ts +27 -0
- package/src/server/Transport.test.ts +294 -0
- package/src/server/Transport.ts +222 -0
- package/src/server/index.ts +8 -0
- package/src/stripe/Charge.integration.test.ts +326 -0
- package/src/stripe/Intents.test.ts +52 -0
- package/src/stripe/Intents.ts +27 -0
- package/src/stripe/client/Charge.ts +119 -0
- package/src/stripe/client/MethodIntents.ts +37 -0
- package/src/stripe/client/index.ts +2 -0
- package/src/stripe/index.ts +1 -0
- package/src/stripe/server/Charge.ts +121 -0
- package/src/stripe/server/MethodIntents.ts +24 -0
- package/src/stripe/server/index.ts +2 -0
- package/src/tempo/Attribution.test.ts +187 -0
- package/src/tempo/Attribution.ts +156 -0
- package/src/tempo/Intents.test.ts +84 -0
- package/src/tempo/Intents.ts +93 -0
- package/src/tempo/client/ChannelOps.ts +233 -0
- package/src/tempo/client/Charge.ts +84 -0
- package/src/tempo/client/MethodIntents.ts +28 -0
- package/src/tempo/client/Session.ts +369 -0
- package/src/tempo/client/SessionManager.test.ts +223 -0
- package/src/tempo/client/SessionManager.ts +270 -0
- package/src/tempo/client/index.ts +5 -0
- package/src/tempo/index.ts +2 -0
- package/src/tempo/internal/account.ts +47 -0
- package/src/tempo/internal/defaults.ts +20 -0
- package/src/tempo/internal/types.ts +8 -0
- package/src/tempo/server/Charge.test.ts +847 -0
- package/src/tempo/server/Charge.ts +309 -0
- package/src/tempo/server/MethodIntents.ts +29 -0
- package/src/tempo/server/Session.test.ts +1349 -0
- package/src/tempo/server/Session.ts +773 -0
- package/src/tempo/server/Sse.test.ts +289 -0
- package/src/tempo/server/index.ts +5 -0
- package/src/tempo/server/internal/transport.ts +153 -0
- package/src/tempo/stream/Chain.ts +333 -0
- package/src/tempo/stream/Channel.ts +50 -0
- package/src/tempo/stream/ChannelStore.test.ts +473 -0
- package/src/tempo/stream/ChannelStore.ts +202 -0
- package/src/tempo/stream/Receipt.test.ts +84 -0
- package/src/tempo/stream/Receipt.ts +45 -0
- package/src/tempo/stream/Sse.test.ts +401 -0
- package/src/tempo/stream/Sse.ts +375 -0
- package/src/tempo/stream/Types.ts +86 -0
- package/src/tempo/stream/Voucher.test.ts +134 -0
- package/src/tempo/stream/Voucher.ts +123 -0
- package/src/tempo/stream/escrow.abi.ts +759 -0
- package/src/tempo/stream/index.ts +7 -0
- package/src/tsconfig.json +10 -0
- package/src/viem/Account.test.ts +71 -0
- package/src/viem/Account.ts +30 -0
- package/src/viem/Client.test.ts +58 -0
- package/src/viem/Client.ts +33 -0
- package/src/zod.ts +47 -0
package/src/Challenge.ts
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { Base64, Bytes, Hash } from 'ox'
|
|
2
|
+
import type { OneOf } from './internal/types.js'
|
|
3
|
+
import type * as MethodIntent from './MethodIntent.js'
|
|
4
|
+
import * as PaymentRequest from './PaymentRequest.js'
|
|
5
|
+
import * as z from './zod.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Schema for a payment challenge.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { Challenge } from 'mppx'
|
|
13
|
+
*
|
|
14
|
+
* const challenge = Challenge.Schema.parse(data)
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export const Schema = z.object({
|
|
18
|
+
/** Optional human-readable description of the payment. */
|
|
19
|
+
description: z.optional(z.string()),
|
|
20
|
+
/** Optional digest of the request body (format: "sha-256=base64hash"). */
|
|
21
|
+
digest: z.optional(z.string().check(z.regex(/^sha-256=/, 'Invalid digest format'))),
|
|
22
|
+
/** Optional expiration timestamp (ISO 8601). */
|
|
23
|
+
expires: z.optional(z.datetime()),
|
|
24
|
+
/** Unique challenge identifier (HMAC-bound). */
|
|
25
|
+
id: z.string(),
|
|
26
|
+
/** Intent type (e.g., "charge", "authorize"). */
|
|
27
|
+
intent: z.string(),
|
|
28
|
+
/** Payment method (e.g., "tempo", "stripe"). */
|
|
29
|
+
method: z.string(),
|
|
30
|
+
/** Server realm (e.g., hostname). */
|
|
31
|
+
realm: z.string(),
|
|
32
|
+
/** Method-specific request data. */
|
|
33
|
+
request: z.record(z.string(), z.unknown()),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A parsed payment challenge from a `WWW-Authenticate` header.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { Challenge } from 'mppx'
|
|
42
|
+
*
|
|
43
|
+
* const challenge: Challenge.Challenge = {
|
|
44
|
+
* id: 'abc123',
|
|
45
|
+
* realm: 'api.example.com',
|
|
46
|
+
* method: 'tempo',
|
|
47
|
+
* intent: 'charge',
|
|
48
|
+
* request: { amount: '1000000', currency: '0x...', recipient: '0x...' },
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export type Challenge<
|
|
53
|
+
request = Record<string, unknown>,
|
|
54
|
+
intent extends string = string,
|
|
55
|
+
method extends string = string,
|
|
56
|
+
> = Omit<z.infer<typeof Schema>, 'intent' | 'method' | 'request'> & {
|
|
57
|
+
intent: intent
|
|
58
|
+
method: method
|
|
59
|
+
request: request
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extracts a union of challenge types from an array of method intents.
|
|
64
|
+
*/
|
|
65
|
+
export type FromMethods<methods extends readonly MethodIntent.AnyMethodIntent[]> = {
|
|
66
|
+
[method in keyof methods]: Challenge<
|
|
67
|
+
z.output<methods[method]['schema']['request']>,
|
|
68
|
+
methods[method]['name'],
|
|
69
|
+
methods[method]['method']
|
|
70
|
+
>
|
|
71
|
+
}[number]
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates a challenge from the given parameters.
|
|
75
|
+
*
|
|
76
|
+
* If `secretKey` option is provided, the challenge ID is computed as HMAC-SHA256
|
|
77
|
+
* over the challenge parameters (realm|method|intent|request|expires|digest),
|
|
78
|
+
* cryptographically binding the ID to its contents.
|
|
79
|
+
*
|
|
80
|
+
* @param parameters - Challenge parameters.
|
|
81
|
+
* @param options - Optional settings including secretKey for HMAC-bound ID.
|
|
82
|
+
* @returns A challenge.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { Challenge } from 'mppx'
|
|
87
|
+
*
|
|
88
|
+
* // With HMAC-bound ID (recommended for servers)
|
|
89
|
+
* const challenge = Challenge.from(
|
|
90
|
+
* {
|
|
91
|
+
* realm: 'api.example.com',
|
|
92
|
+
* method: 'tempo',
|
|
93
|
+
* intent: 'charge',
|
|
94
|
+
* request: { amount: '1000000', currency: '0x...', recipient: '0x...' },
|
|
95
|
+
* },
|
|
96
|
+
* { secretKey: 'my-secret' },
|
|
97
|
+
* )
|
|
98
|
+
*
|
|
99
|
+
* // With explicit ID
|
|
100
|
+
* const challenge = Challenge.from({
|
|
101
|
+
* id: 'abc123',
|
|
102
|
+
* realm: 'api.example.com',
|
|
103
|
+
* method: 'tempo',
|
|
104
|
+
* intent: 'charge',
|
|
105
|
+
* request: { amount: '1000000', currency: '0x...', recipient: '0x...' },
|
|
106
|
+
* })
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function from<
|
|
110
|
+
const parameters extends from.Parameters,
|
|
111
|
+
const methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
|
|
112
|
+
>(parameters: parameters, options?: from.Options<methods>): from.ReturnType<parameters, methods> {
|
|
113
|
+
void options
|
|
114
|
+
const { description, digest, method: methodName, intent, realm, request, secretKey } = parameters
|
|
115
|
+
|
|
116
|
+
const expires = (parameters.expires ?? request.expires) as string
|
|
117
|
+
const id = secretKey
|
|
118
|
+
? computeId({ ...parameters, expires }, { secretKey })
|
|
119
|
+
: (parameters as { id: string }).id
|
|
120
|
+
|
|
121
|
+
return Schema.parse({
|
|
122
|
+
id,
|
|
123
|
+
realm,
|
|
124
|
+
method: methodName,
|
|
125
|
+
intent,
|
|
126
|
+
request,
|
|
127
|
+
...(description && { description }),
|
|
128
|
+
...(digest && { digest }),
|
|
129
|
+
...(expires && { expires }),
|
|
130
|
+
}) as from.ReturnType<parameters, methods>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export declare namespace from {
|
|
134
|
+
type Options<methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined> = {
|
|
135
|
+
methods?: methods
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type Parameters = OneOf<
|
|
139
|
+
| {
|
|
140
|
+
/** Explicit challenge ID. */
|
|
141
|
+
id: string
|
|
142
|
+
}
|
|
143
|
+
| {
|
|
144
|
+
/** Secret key for HMAC-bound challenge ID. */
|
|
145
|
+
secretKey: string
|
|
146
|
+
}
|
|
147
|
+
> & {
|
|
148
|
+
/** Optional human-readable description of the payment. */
|
|
149
|
+
description?: string | undefined
|
|
150
|
+
/** Optional digest of the request body. */
|
|
151
|
+
digest?: string | undefined
|
|
152
|
+
/** Optional expiration timestamp (ISO 8601). */
|
|
153
|
+
expires?: string | undefined
|
|
154
|
+
/** Intent type (e.g., "charge", "authorize"). */
|
|
155
|
+
intent: string
|
|
156
|
+
/** Payment method (e.g., "tempo", "stripe"). */
|
|
157
|
+
method: string
|
|
158
|
+
/** Server realm (e.g., hostname). */
|
|
159
|
+
realm: string
|
|
160
|
+
/** Method-specific request data. */
|
|
161
|
+
request: PaymentRequest.Request
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
type ReturnType<
|
|
165
|
+
parameters extends Parameters,
|
|
166
|
+
methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
|
|
167
|
+
> = methods extends readonly MethodIntent.AnyMethodIntent[]
|
|
168
|
+
? FromMethods<methods>
|
|
169
|
+
: Challenge<parameters['request']>
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Creates a validated challenge from a method intent.
|
|
174
|
+
*
|
|
175
|
+
* If `secretKey` option is provided, the challenge ID is computed as HMAC-SHA256
|
|
176
|
+
* over the challenge parameters, cryptographically binding the ID to its contents.
|
|
177
|
+
*
|
|
178
|
+
* @param intent - The method intent to validate against.
|
|
179
|
+
* @param parameters - Challenge parameters (realm, request, optional expires/digest, and id if no secretKey).
|
|
180
|
+
* @param options - Optional settings including secretKey for HMAC-bound ID.
|
|
181
|
+
* @returns A validated challenge.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* import { Challenge } from 'mppx'
|
|
186
|
+
* import { Intents } from 'mppx/tempo'
|
|
187
|
+
*
|
|
188
|
+
* // With HMAC-bound ID (recommended for servers)
|
|
189
|
+
* const challenge = Challenge.fromIntent(
|
|
190
|
+
* Intents.charge,
|
|
191
|
+
* {
|
|
192
|
+
* realm: 'api.example.com',
|
|
193
|
+
* request: {
|
|
194
|
+
* amount: '1000000',
|
|
195
|
+
* currency: '0x20c0000000000000000000000000000000000001',
|
|
196
|
+
* recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f8fE00',
|
|
197
|
+
* expires: '2025-01-06T12:00:00Z',
|
|
198
|
+
* },
|
|
199
|
+
* },
|
|
200
|
+
* { secretKey: 'my-secret' },
|
|
201
|
+
* )
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export function fromIntent<const intent extends MethodIntent.MethodIntent>(
|
|
205
|
+
intent: intent,
|
|
206
|
+
parameters: fromIntent.Parameters<intent>,
|
|
207
|
+
): fromIntent.ReturnType<intent> {
|
|
208
|
+
const { method, name } = intent
|
|
209
|
+
const { description, digest, expires, id, realm, secretKey } = parameters
|
|
210
|
+
|
|
211
|
+
const request = PaymentRequest.fromIntent(intent, parameters.request)
|
|
212
|
+
|
|
213
|
+
return from({
|
|
214
|
+
...(id ? { id } : { secretKey }),
|
|
215
|
+
realm,
|
|
216
|
+
method,
|
|
217
|
+
intent: name,
|
|
218
|
+
request,
|
|
219
|
+
description,
|
|
220
|
+
digest,
|
|
221
|
+
expires,
|
|
222
|
+
} as from.Parameters) as fromIntent.ReturnType<intent>
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export declare namespace fromIntent {
|
|
226
|
+
type Parameters<intent extends MethodIntent.MethodIntent> = OneOf<
|
|
227
|
+
| {
|
|
228
|
+
/** Explicit challenge ID. */
|
|
229
|
+
id: string
|
|
230
|
+
}
|
|
231
|
+
| {
|
|
232
|
+
/** Secret key for HMAC-bound challenge ID. */
|
|
233
|
+
secretKey: string
|
|
234
|
+
}
|
|
235
|
+
> & {
|
|
236
|
+
/** Optional human-readable description of the payment. */
|
|
237
|
+
description?: string | undefined
|
|
238
|
+
/** Optional digest of the request body. */
|
|
239
|
+
digest?: string | undefined
|
|
240
|
+
/** Optional expiration timestamp (ISO 8601). */
|
|
241
|
+
expires?: string | undefined
|
|
242
|
+
/** Server realm (e.g., hostname). */
|
|
243
|
+
realm: string
|
|
244
|
+
/** Method-specific request data. */
|
|
245
|
+
request: z.input<intent['schema']['request']>
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
type ReturnType<intent extends MethodIntent.MethodIntent> = Challenge<
|
|
249
|
+
z.output<intent['schema']['request']>
|
|
250
|
+
>
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Serializes a challenge to the WWW-Authenticate header format.
|
|
255
|
+
*
|
|
256
|
+
* @param challenge - The challenge to serialize.
|
|
257
|
+
* @returns A string suitable for the WWW-Authenticate header value.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```ts
|
|
261
|
+
* import { Challenge } from 'mppx'
|
|
262
|
+
*
|
|
263
|
+
* const header = Challenge.serialize(challenge)
|
|
264
|
+
* // => 'Payment id="abc123", realm="api.example.com", method="tempo", intent="charge", request="eyJhbW91bnQiOi..."'
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
export function serialize(challenge: Challenge): string {
|
|
268
|
+
const parts = [
|
|
269
|
+
`id="${challenge.id}"`,
|
|
270
|
+
`realm="${challenge.realm}"`,
|
|
271
|
+
`method="${challenge.method}"`,
|
|
272
|
+
`intent="${challenge.intent}"`,
|
|
273
|
+
`request="${PaymentRequest.serialize(challenge.request)}"`,
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
if (challenge.description !== undefined) parts.push(`description="${challenge.description}"`)
|
|
277
|
+
if (challenge.digest !== undefined) parts.push(`digest="${challenge.digest}"`)
|
|
278
|
+
if (challenge.expires !== undefined) parts.push(`expires="${challenge.expires}"`)
|
|
279
|
+
|
|
280
|
+
return `Payment ${parts.join(', ')}`
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Deserializes a WWW-Authenticate header value to a challenge.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```ts
|
|
288
|
+
* import { Challenge } from 'mppx'
|
|
289
|
+
*
|
|
290
|
+
* const challenge = Challenge.deserialize(header)
|
|
291
|
+
*
|
|
292
|
+
* // With methods for type narrowing
|
|
293
|
+
* const challenge = Challenge.deserialize(header, { methods })
|
|
294
|
+
* ```
|
|
295
|
+
*
|
|
296
|
+
* @param header - The WWW-Authenticate header value.
|
|
297
|
+
* @param options - Optional settings to narrow the challenge type.
|
|
298
|
+
* @returns The deserialized challenge.
|
|
299
|
+
*/
|
|
300
|
+
export function deserialize<
|
|
301
|
+
const methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
|
|
302
|
+
>(value: string, options?: from.Options<methods>): from.ReturnType<from.Parameters, methods> {
|
|
303
|
+
const prefixMatch = value.match(/^Payment\s+(.+)$/i)
|
|
304
|
+
if (!prefixMatch?.[1]) throw new Error('Missing Payment scheme.')
|
|
305
|
+
|
|
306
|
+
const params = prefixMatch[1]
|
|
307
|
+
const result: Record<string, string> = {}
|
|
308
|
+
|
|
309
|
+
for (const match of params.matchAll(/(\w+)="([^"]+)"/g)) {
|
|
310
|
+
const key = match[1]
|
|
311
|
+
const value = match[2]
|
|
312
|
+
if (key && value) result[key] = value
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const { request, ...rest } = result
|
|
316
|
+
if (!request) throw new Error('Missing request parameter.')
|
|
317
|
+
|
|
318
|
+
return from(
|
|
319
|
+
{
|
|
320
|
+
...rest,
|
|
321
|
+
request: PaymentRequest.deserialize(request),
|
|
322
|
+
} as from.Parameters,
|
|
323
|
+
options,
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Extracts the challenge from a Headers object.
|
|
329
|
+
*
|
|
330
|
+
* @param headers - The HTTP headers.
|
|
331
|
+
* @param options - Optional settings to narrow the challenge type.
|
|
332
|
+
* @returns The deserialized challenge.
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* import { Challenge } from 'mppx'
|
|
337
|
+
*
|
|
338
|
+
* const challenge = Challenge.fromHeaders(response.headers)
|
|
339
|
+
*
|
|
340
|
+
* // With methods for type narrowing
|
|
341
|
+
* const challenge = Challenge.fromHeaders(response.headers, { methods })
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
export function fromHeaders<
|
|
345
|
+
const methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
|
|
346
|
+
>(headers: Headers, options?: from.Options<methods>): from.ReturnType<from.Parameters, methods> {
|
|
347
|
+
const header = headers.get('WWW-Authenticate')
|
|
348
|
+
if (!header) throw new Error('Missing WWW-Authenticate header.')
|
|
349
|
+
return deserialize(header, options)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Extracts the challenge from a Response's WWW-Authenticate header.
|
|
354
|
+
*
|
|
355
|
+
* @param response - The HTTP response (must be 402 status).
|
|
356
|
+
* @param options - Optional settings to narrow the challenge type.
|
|
357
|
+
* @returns The deserialized challenge.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```ts
|
|
361
|
+
* import { Challenge } from 'mppx'
|
|
362
|
+
*
|
|
363
|
+
* const response = await fetch('/resource')
|
|
364
|
+
* if (response.status === 402)
|
|
365
|
+
* const challenge = Challenge.fromResponse(response)
|
|
366
|
+
*
|
|
367
|
+
* // With methods for type narrowing
|
|
368
|
+
* const challenge = Challenge.fromResponse(response, { methods })
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
371
|
+
export function fromResponse<
|
|
372
|
+
const methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
|
|
373
|
+
>(response: Response, options?: from.Options<methods>): from.ReturnType<from.Parameters, methods> {
|
|
374
|
+
if (response.status !== 402) throw new Error('Response status is not 402.')
|
|
375
|
+
return fromHeaders(response.headers, options)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Verifies that a challenge ID matches the expected HMAC for the given parameters.
|
|
380
|
+
*
|
|
381
|
+
* @param challenge - The challenge to verify.
|
|
382
|
+
* @param options - Options including the secret key.
|
|
383
|
+
* @returns True if the challenge ID is valid, false otherwise.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* import { Challenge } from 'mppx'
|
|
388
|
+
*
|
|
389
|
+
* const isValid = Challenge.verify(challenge, { secretKey: 'my-secret' })
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
export function verify(challenge: Challenge, options: verify.Options): boolean {
|
|
393
|
+
const expectedId = computeId(challenge, options)
|
|
394
|
+
return constantTimeEqual(challenge.id, expectedId)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export declare namespace verify {
|
|
398
|
+
type Options = {
|
|
399
|
+
/** Secret key for HMAC-bound challenge ID verification. */
|
|
400
|
+
secretKey: string
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** @internal Computes HMAC-SHA256 challenge ID from parameters. */
|
|
405
|
+
function computeId(challenge: Omit<Challenge, 'id'>, options: { secretKey: string }): string {
|
|
406
|
+
const input = [
|
|
407
|
+
challenge.realm,
|
|
408
|
+
challenge.method,
|
|
409
|
+
challenge.intent,
|
|
410
|
+
PaymentRequest.serialize(challenge.request),
|
|
411
|
+
challenge.expires ?? '',
|
|
412
|
+
challenge.digest ?? '',
|
|
413
|
+
]
|
|
414
|
+
.filter(Boolean)
|
|
415
|
+
.join('|')
|
|
416
|
+
|
|
417
|
+
const key = Bytes.fromString(options.secretKey)
|
|
418
|
+
const data = Bytes.fromString(input)
|
|
419
|
+
const mac = Hash.hmac256(key, data, { as: 'Bytes' })
|
|
420
|
+
return Base64.fromBytes(mac, { url: true, pad: false })
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** @internal Constant-time string comparison to prevent timing attacks. */
|
|
424
|
+
function constantTimeEqual(a: string, b: string): boolean {
|
|
425
|
+
if (a.length !== b.length) return false
|
|
426
|
+
let result = 0
|
|
427
|
+
for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i)
|
|
428
|
+
return result === 0
|
|
429
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { Challenge, Credential } from 'mppx'
|
|
2
|
+
import { describe, expect, test } from 'vitest'
|
|
3
|
+
|
|
4
|
+
const challenge = Challenge.from({
|
|
5
|
+
id: 'x7Tg2pLqR9mKvNwY3hBcZa',
|
|
6
|
+
realm: 'api.example.com',
|
|
7
|
+
method: 'tempo',
|
|
8
|
+
intent: 'charge',
|
|
9
|
+
request: { amount: '1000' },
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('from', () => {
|
|
13
|
+
test('behavior: creates credential with parsed request', () => {
|
|
14
|
+
const credential = Credential.from({
|
|
15
|
+
challenge,
|
|
16
|
+
payload: { signature: '0x1234' },
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
expect(credential).toMatchInlineSnapshot(`
|
|
20
|
+
{
|
|
21
|
+
"challenge": {
|
|
22
|
+
"id": "x7Tg2pLqR9mKvNwY3hBcZa",
|
|
23
|
+
"intent": "charge",
|
|
24
|
+
"method": "tempo",
|
|
25
|
+
"realm": "api.example.com",
|
|
26
|
+
"request": {
|
|
27
|
+
"amount": "1000",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
"payload": {
|
|
31
|
+
"signature": "0x1234",
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
`)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('behavior: creates credential with source', () => {
|
|
38
|
+
const credential = Credential.from({
|
|
39
|
+
challenge,
|
|
40
|
+
source: 'did:pkh:eip155:1:0x1234567890abcdef',
|
|
41
|
+
payload: { hash: '0xabcd' },
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
expect(credential).toMatchInlineSnapshot(`
|
|
45
|
+
{
|
|
46
|
+
"challenge": {
|
|
47
|
+
"id": "x7Tg2pLqR9mKvNwY3hBcZa",
|
|
48
|
+
"intent": "charge",
|
|
49
|
+
"method": "tempo",
|
|
50
|
+
"realm": "api.example.com",
|
|
51
|
+
"request": {
|
|
52
|
+
"amount": "1000",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
"payload": {
|
|
56
|
+
"hash": "0xabcd",
|
|
57
|
+
},
|
|
58
|
+
"source": "did:pkh:eip155:1:0x1234567890abcdef",
|
|
59
|
+
}
|
|
60
|
+
`)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('behavior: includes optional challenge fields', () => {
|
|
64
|
+
const credential = Credential.from({
|
|
65
|
+
challenge: {
|
|
66
|
+
...challenge,
|
|
67
|
+
expires: '2025-01-15T12:00:00Z',
|
|
68
|
+
digest: 'sha-256=abc123',
|
|
69
|
+
},
|
|
70
|
+
payload: { signature: '0x1234' },
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
expect(credential.challenge.expires).toBe('2025-01-15T12:00:00Z')
|
|
74
|
+
expect(credential.challenge.digest).toBe('sha-256=abc123')
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('serialize', () => {
|
|
79
|
+
test('behavior: serializes credential to Authorization header format', () => {
|
|
80
|
+
const credential = Credential.from({
|
|
81
|
+
challenge,
|
|
82
|
+
payload: { signature: '0x1234' },
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const header = Credential.serialize(credential)
|
|
86
|
+
|
|
87
|
+
expect(header).toMatch(/^Payment /)
|
|
88
|
+
const deserialized = Credential.deserialize(header)
|
|
89
|
+
expect(deserialized.challenge.request).toEqual({ amount: '1000' })
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('deserialize', () => {
|
|
94
|
+
test('behavior: deserializes to credential with parsed request', () => {
|
|
95
|
+
const original = Credential.from({
|
|
96
|
+
challenge,
|
|
97
|
+
payload: { signature: '0x1234' },
|
|
98
|
+
})
|
|
99
|
+
const header = Credential.serialize(original)
|
|
100
|
+
|
|
101
|
+
const credential = Credential.deserialize(header)
|
|
102
|
+
|
|
103
|
+
expect(credential).toMatchInlineSnapshot(`
|
|
104
|
+
{
|
|
105
|
+
"challenge": {
|
|
106
|
+
"id": "x7Tg2pLqR9mKvNwY3hBcZa",
|
|
107
|
+
"intent": "charge",
|
|
108
|
+
"method": "tempo",
|
|
109
|
+
"realm": "api.example.com",
|
|
110
|
+
"request": {
|
|
111
|
+
"amount": "1000",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
"payload": {
|
|
115
|
+
"signature": "0x1234",
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
`)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('behavior: roundtrip preserves data', () => {
|
|
122
|
+
const original = Credential.from({
|
|
123
|
+
challenge,
|
|
124
|
+
source: 'did:pkh:eip155:1:0x1234567890abcdef',
|
|
125
|
+
payload: { hash: '0xabcd' },
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const header = Credential.serialize(original)
|
|
129
|
+
const deserialized = Credential.deserialize(header)
|
|
130
|
+
|
|
131
|
+
expect(deserialized.challenge.id).toBe(original.challenge.id)
|
|
132
|
+
expect(deserialized.challenge.request).toEqual(original.challenge.request)
|
|
133
|
+
expect(deserialized.payload).toEqual(original.payload)
|
|
134
|
+
expect(deserialized.source).toBe(original.source)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('error: throws for missing Payment scheme', () => {
|
|
138
|
+
expect(() => Credential.deserialize('Bearer abc123')).toThrow('Missing Payment scheme.')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('error: throws for invalid base64url', () => {
|
|
142
|
+
expect(() => Credential.deserialize('Payment !!invalid!!')).toThrow(
|
|
143
|
+
'Invalid base64url or JSON.',
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('error: throws for invalid JSON', () => {
|
|
148
|
+
const invalidJson = btoa('not valid json')
|
|
149
|
+
expect(() => Credential.deserialize(`Payment ${invalidJson}`)).toThrow(
|
|
150
|
+
'Invalid base64url or JSON.',
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('error: throws for invalid challenge (missing required fields)', () => {
|
|
155
|
+
const invalidCredential = {
|
|
156
|
+
challenge: {
|
|
157
|
+
id: 'abc123',
|
|
158
|
+
// missing realm, method, intent, request
|
|
159
|
+
},
|
|
160
|
+
payload: { signature: '0x1234' },
|
|
161
|
+
}
|
|
162
|
+
const encoded = btoa(JSON.stringify(invalidCredential))
|
|
163
|
+
expect(() => Credential.deserialize(`Payment ${encoded}`)).toThrow()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('error: throws for invalid challenge (invalid digest format)', () => {
|
|
167
|
+
const invalidCredential = {
|
|
168
|
+
challenge: {
|
|
169
|
+
id: 'abc123',
|
|
170
|
+
realm: 'api.example.com',
|
|
171
|
+
method: 'tempo',
|
|
172
|
+
intent: 'charge',
|
|
173
|
+
request: 'eyJhbW91bnQiOiIxMDAwIn0',
|
|
174
|
+
digest: 'invalid-digest-format',
|
|
175
|
+
},
|
|
176
|
+
payload: { signature: '0x1234' },
|
|
177
|
+
}
|
|
178
|
+
const encoded = btoa(JSON.stringify(invalidCredential))
|
|
179
|
+
expect(() => Credential.deserialize(`Payment ${encoded}`)).toThrow()
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('fromRequest', () => {
|
|
184
|
+
test('behavior: extracts credential from Request', () => {
|
|
185
|
+
const original = Credential.from({
|
|
186
|
+
challenge,
|
|
187
|
+
payload: { signature: '0x1234' },
|
|
188
|
+
})
|
|
189
|
+
const request = new Request('https://api.example.com/resource', {
|
|
190
|
+
headers: { Authorization: Credential.serialize(original) },
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const credential = Credential.fromRequest(request)
|
|
194
|
+
|
|
195
|
+
expect(credential.challenge.id).toBe('x7Tg2pLqR9mKvNwY3hBcZa')
|
|
196
|
+
expect(credential.challenge.request).toEqual({ amount: '1000' })
|
|
197
|
+
expect(credential.payload).toEqual({ signature: '0x1234' })
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('behavior: extracts Payment from multiple Authorization schemes', () => {
|
|
201
|
+
const original = Credential.from({
|
|
202
|
+
challenge,
|
|
203
|
+
payload: { signature: '0x1234' },
|
|
204
|
+
})
|
|
205
|
+
const headers = new Headers()
|
|
206
|
+
headers.append('Authorization', 'Bearer some-jwt-token')
|
|
207
|
+
headers.append('Authorization', Credential.serialize(original))
|
|
208
|
+
const request = new Request('https://api.example.com/resource', { headers })
|
|
209
|
+
|
|
210
|
+
const credential = Credential.fromRequest(request)
|
|
211
|
+
|
|
212
|
+
expect(credential.challenge.id).toBe('x7Tg2pLqR9mKvNwY3hBcZa')
|
|
213
|
+
expect(credential.payload).toEqual({ signature: '0x1234' })
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('error: throws for missing Authorization header', () => {
|
|
217
|
+
const request = new Request('https://api.example.com/resource')
|
|
218
|
+
expect(() => Credential.fromRequest(request)).toThrow('Missing Authorization header.')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('error: throws when no Payment scheme present', () => {
|
|
222
|
+
const request = new Request('https://api.example.com/resource', {
|
|
223
|
+
headers: { Authorization: 'Bearer some-jwt-token' },
|
|
224
|
+
})
|
|
225
|
+
expect(() => Credential.fromRequest(request)).toThrow('Missing Payment scheme.')
|
|
226
|
+
})
|
|
227
|
+
})
|