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
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side session payment method for request/response flows.
|
|
3
|
+
*
|
|
4
|
+
* Handles the full channel lifecycle (open, voucher, top-up, close) and
|
|
5
|
+
* one-shot settlement. Each incoming request carries a stream credential
|
|
6
|
+
* with a cumulative voucher that the server validates and records.
|
|
7
|
+
*
|
|
8
|
+
* Use `session()` for standard HTTP request/response patterns where each
|
|
9
|
+
* request is a discrete paid unit (for example, a page scrape or API call).
|
|
10
|
+
* For long-lived connections that emit multiple paid events over a single
|
|
11
|
+
* request, use {@link ../stream/Sse} instead.
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
type Address,
|
|
15
|
+
type Hex,
|
|
16
|
+
parseUnits,
|
|
17
|
+
type Account as viem_Account,
|
|
18
|
+
type Client as viem_Client,
|
|
19
|
+
} from 'viem'
|
|
20
|
+
import { tempo as tempo_chain } from 'viem/chains'
|
|
21
|
+
import {
|
|
22
|
+
AmountExceedsDepositError,
|
|
23
|
+
BadRequestError,
|
|
24
|
+
ChannelClosedError,
|
|
25
|
+
ChannelNotFoundError,
|
|
26
|
+
DeltaTooSmallError,
|
|
27
|
+
InsufficientBalanceError,
|
|
28
|
+
InvalidSignatureError,
|
|
29
|
+
VerificationFailedError,
|
|
30
|
+
} from '../../Errors.js'
|
|
31
|
+
import type { Challenge, Credential } from '../../index.js'
|
|
32
|
+
import type { LooseOmit } from '../../internal/types.js'
|
|
33
|
+
import * as MethodIntent from '../../MethodIntent.js'
|
|
34
|
+
import * as Store from '../../Store.js'
|
|
35
|
+
import * as Client from '../../viem/Client.js'
|
|
36
|
+
import * as Intents from '../Intents.js'
|
|
37
|
+
import * as Account from '../internal/account.js'
|
|
38
|
+
import * as defaults from '../internal/defaults.js'
|
|
39
|
+
import type * as types from '../internal/types.js'
|
|
40
|
+
import {
|
|
41
|
+
broadcastOpenTransaction,
|
|
42
|
+
broadcastTopUpTransaction,
|
|
43
|
+
closeOnChain,
|
|
44
|
+
getOnChainChannel,
|
|
45
|
+
type OnChainChannel,
|
|
46
|
+
settleOnChain,
|
|
47
|
+
} from '../stream/Chain.js'
|
|
48
|
+
import * as ChannelStore from '../stream/ChannelStore.js'
|
|
49
|
+
import { createStreamReceipt } from '../stream/Receipt.js'
|
|
50
|
+
import type { SignedVoucher, StreamCredentialPayload, StreamReceipt } from '../stream/Types.js'
|
|
51
|
+
import { parseVoucherFromPayload, verifyVoucher } from '../stream/Voucher.js'
|
|
52
|
+
import * as Transport from './internal/transport.js'
|
|
53
|
+
|
|
54
|
+
/** Challenge methodDetails shape for stream intents. */
|
|
55
|
+
type StreamMethodDetails = {
|
|
56
|
+
escrowContract: Address
|
|
57
|
+
chainId: number
|
|
58
|
+
channelId?: Hex | undefined
|
|
59
|
+
minVoucherDelta?: string | undefined
|
|
60
|
+
feePayer?: boolean | undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates a stream payment server using the mppx Method.toServer() pattern.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* import { Mppx, tempo } from 'mppx/server'
|
|
69
|
+
*
|
|
70
|
+
* const mppx = Mppx.create({
|
|
71
|
+
* methods: [
|
|
72
|
+
* tempo.session({
|
|
73
|
+
* store: myStore,
|
|
74
|
+
* recipient: '0x...',
|
|
75
|
+
* currency: '0x...',
|
|
76
|
+
* escrowContract: '0x...',
|
|
77
|
+
* }),
|
|
78
|
+
* ],
|
|
79
|
+
* realm: 'my-app',
|
|
80
|
+
* secretKey: '...',
|
|
81
|
+
* })
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function session<const parameters extends session.Parameters>(p?: parameters) {
|
|
85
|
+
const parameters = p as parameters
|
|
86
|
+
const {
|
|
87
|
+
amount,
|
|
88
|
+
currency,
|
|
89
|
+
decimals = defaults.decimals,
|
|
90
|
+
store: rawStore = Store.memory(),
|
|
91
|
+
suggestedDeposit,
|
|
92
|
+
unitType,
|
|
93
|
+
} = parameters
|
|
94
|
+
|
|
95
|
+
const store = ChannelStore.fromStore(rawStore)
|
|
96
|
+
|
|
97
|
+
const getClient = Client.getResolver({
|
|
98
|
+
chain: tempo_chain,
|
|
99
|
+
getClient: parameters.getClient,
|
|
100
|
+
rpcUrl: defaults.rpcUrl,
|
|
101
|
+
})
|
|
102
|
+
const { account, recipient, feePayer } = Account.resolve(parameters)
|
|
103
|
+
|
|
104
|
+
type Transport = parameters['stream'] extends false | undefined ? undefined : Transport.Sse
|
|
105
|
+
const transport = parameters.stream
|
|
106
|
+
? Transport.sse({
|
|
107
|
+
store,
|
|
108
|
+
...(typeof parameters.stream === 'object' ? parameters.stream : undefined),
|
|
109
|
+
})
|
|
110
|
+
: undefined
|
|
111
|
+
|
|
112
|
+
type Defaults = session.DeriveDefaults<parameters>
|
|
113
|
+
return MethodIntent.toServer<typeof Intents.session, Defaults, Transport>(Intents.session, {
|
|
114
|
+
defaults: {
|
|
115
|
+
amount,
|
|
116
|
+
currency,
|
|
117
|
+
decimals,
|
|
118
|
+
recipient,
|
|
119
|
+
suggestedDeposit,
|
|
120
|
+
unitType,
|
|
121
|
+
} as unknown as Defaults,
|
|
122
|
+
|
|
123
|
+
transport: transport as never,
|
|
124
|
+
|
|
125
|
+
// TODO: dedupe `{charge,stream}.request`
|
|
126
|
+
async request({ credential, request }) {
|
|
127
|
+
// Extract chainId from request or default.
|
|
128
|
+
const chainId = await (async () => {
|
|
129
|
+
if (request.chainId) return request.chainId
|
|
130
|
+
if (parameters.testnet) return defaults.testnetChainId
|
|
131
|
+
return (await getClient({})).chain?.id
|
|
132
|
+
})()
|
|
133
|
+
|
|
134
|
+
// Validate chainId.
|
|
135
|
+
const client = await (async () => {
|
|
136
|
+
try {
|
|
137
|
+
return await getClient({ chainId })
|
|
138
|
+
} catch {
|
|
139
|
+
throw new Error(`No client configured with chainId ${chainId}.`)
|
|
140
|
+
}
|
|
141
|
+
})()
|
|
142
|
+
if (client.chain?.id !== chainId)
|
|
143
|
+
throw new Error(`Client not configured with chainId ${chainId}.`)
|
|
144
|
+
|
|
145
|
+
const resolvedEscrow =
|
|
146
|
+
request.escrowContract ??
|
|
147
|
+
parameters.escrowContract ??
|
|
148
|
+
defaults.escrowContract[chainId as keyof typeof defaults.escrowContract]
|
|
149
|
+
|
|
150
|
+
// Extract feePayer.
|
|
151
|
+
const resolvedFeePayer = (() => {
|
|
152
|
+
const account = typeof request.feePayer === 'object' ? request.feePayer : feePayer
|
|
153
|
+
const requested = request.feePayer !== false && (account ?? feePayer)
|
|
154
|
+
if (credential) return account
|
|
155
|
+
if (requested) return true
|
|
156
|
+
return undefined
|
|
157
|
+
})()
|
|
158
|
+
|
|
159
|
+
return { ...request, chainId, escrowContract: resolvedEscrow, feePayer: resolvedFeePayer }
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
async verify({ credential }) {
|
|
163
|
+
const { challenge, payload } = credential as Credential.Credential<StreamCredentialPayload>
|
|
164
|
+
|
|
165
|
+
const methodDetails = challenge.request.methodDetails as StreamMethodDetails
|
|
166
|
+
const client = await getClient({ chainId: methodDetails.chainId })
|
|
167
|
+
|
|
168
|
+
const resolvedFeePayer = methodDetails.feePayer === true ? feePayer : undefined
|
|
169
|
+
const minVoucherDelta = parseUnits(parameters.minVoucherDelta ?? '0', decimals)
|
|
170
|
+
const effectiveMinVoucherDelta = methodDetails.minVoucherDelta
|
|
171
|
+
? BigInt(methodDetails.minVoucherDelta)
|
|
172
|
+
: minVoucherDelta
|
|
173
|
+
|
|
174
|
+
let streamReceipt: StreamReceipt
|
|
175
|
+
|
|
176
|
+
switch (payload.action) {
|
|
177
|
+
case 'open':
|
|
178
|
+
streamReceipt = await handleOpen(
|
|
179
|
+
store,
|
|
180
|
+
client,
|
|
181
|
+
challenge,
|
|
182
|
+
payload,
|
|
183
|
+
methodDetails,
|
|
184
|
+
resolvedFeePayer,
|
|
185
|
+
)
|
|
186
|
+
break
|
|
187
|
+
|
|
188
|
+
case 'topUp':
|
|
189
|
+
streamReceipt = await handleTopUp(
|
|
190
|
+
store,
|
|
191
|
+
client,
|
|
192
|
+
challenge,
|
|
193
|
+
payload,
|
|
194
|
+
methodDetails,
|
|
195
|
+
resolvedFeePayer,
|
|
196
|
+
)
|
|
197
|
+
break
|
|
198
|
+
|
|
199
|
+
case 'voucher':
|
|
200
|
+
streamReceipt = await handleVoucher(
|
|
201
|
+
store,
|
|
202
|
+
client,
|
|
203
|
+
effectiveMinVoucherDelta,
|
|
204
|
+
challenge,
|
|
205
|
+
payload,
|
|
206
|
+
methodDetails,
|
|
207
|
+
)
|
|
208
|
+
break
|
|
209
|
+
|
|
210
|
+
case 'close':
|
|
211
|
+
streamReceipt = await handleClose(
|
|
212
|
+
store,
|
|
213
|
+
client,
|
|
214
|
+
challenge,
|
|
215
|
+
payload,
|
|
216
|
+
methodDetails,
|
|
217
|
+
account,
|
|
218
|
+
)
|
|
219
|
+
break
|
|
220
|
+
|
|
221
|
+
default:
|
|
222
|
+
throw new BadRequestError({
|
|
223
|
+
reason: `unknown action: ${(payload as { action: string }).action}`,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return streamReceipt
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// This hook acts as a gate: when it returns a Response, `withReceipt()`
|
|
231
|
+
// in Mppx.ts short-circuits and returns that response directly without
|
|
232
|
+
// invoking the user's route handler. When it returns undefined, the
|
|
233
|
+
// user's handler runs normally and serves content.
|
|
234
|
+
//
|
|
235
|
+
// Management actions (open, topUp, close) are always gated — they
|
|
236
|
+
// return 204 regardless of request method.
|
|
237
|
+
//
|
|
238
|
+
// Voucher POSTs are gated only when they have no request body, which
|
|
239
|
+
// signals a mid-stream voucher update (the client is just topping up
|
|
240
|
+
// the channel balance). Voucher POSTs WITH a body are content requests
|
|
241
|
+
// (e.g., an API call to a POST endpoint) and fall through to the
|
|
242
|
+
// user's handler. GET requests with vouchers always fall through so
|
|
243
|
+
// auto-mode clients (whose fetch wrapper bundles open+voucher into a
|
|
244
|
+
// single GET retry) receive content as expected.
|
|
245
|
+
respond({ credential, input }) {
|
|
246
|
+
const { payload } = credential as Credential.Credential<StreamCredentialPayload>
|
|
247
|
+
|
|
248
|
+
if (payload.action === 'close') return new Response(null, { status: 204 })
|
|
249
|
+
|
|
250
|
+
const isManagement = payload.action === 'open' || payload.action === 'topUp'
|
|
251
|
+
if (isManagement && input.method === 'POST') return new Response(null, { status: 204 })
|
|
252
|
+
|
|
253
|
+
const isVoucher = payload.action === 'voucher'
|
|
254
|
+
if (!isVoucher) return undefined
|
|
255
|
+
|
|
256
|
+
// Only gate voucher POSTs with no body (mid-stream balance updates).
|
|
257
|
+
// POSTs with a body are content requests that should reach the handler.
|
|
258
|
+
if (input.method !== 'POST') return undefined
|
|
259
|
+
const contentLength = input.headers.get('content-length')
|
|
260
|
+
if (contentLength !== null && contentLength !== '0') return undefined
|
|
261
|
+
if (input.headers.has('transfer-encoding')) return undefined
|
|
262
|
+
return new Response(null, { status: 204 })
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export declare namespace session {
|
|
268
|
+
type Defaults = LooseOmit<
|
|
269
|
+
MethodIntent.RequestDefaults<typeof Intents.session>,
|
|
270
|
+
'feePayer' | 'recipient'
|
|
271
|
+
>
|
|
272
|
+
|
|
273
|
+
type Parameters = {
|
|
274
|
+
/** Minimum voucher delta to accept (numeric string, default: "0"). */
|
|
275
|
+
minVoucherDelta?: string | undefined
|
|
276
|
+
/** Store backend for channel state. */
|
|
277
|
+
store?: Store.Store | undefined
|
|
278
|
+
/**
|
|
279
|
+
* Enable SSE streaming.
|
|
280
|
+
*
|
|
281
|
+
* Pass `true` to enable with defaults, or an options object
|
|
282
|
+
* to configure the stream (e.g. `{ poll: true }` for
|
|
283
|
+
* Cloudflare Workers compatibility).
|
|
284
|
+
*/
|
|
285
|
+
stream?: boolean | Transport.sse.Options | undefined
|
|
286
|
+
/** Testnet mode. */
|
|
287
|
+
testnet?: boolean | undefined
|
|
288
|
+
} & Account.resolve.Parameters &
|
|
289
|
+
Client.getResolver.Parameters &
|
|
290
|
+
Defaults
|
|
291
|
+
|
|
292
|
+
type DeriveDefaults<parameters extends Parameters> = types.DeriveDefaults<
|
|
293
|
+
parameters,
|
|
294
|
+
Defaults
|
|
295
|
+
> & {
|
|
296
|
+
decimals: number
|
|
297
|
+
escrowContract: Address
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* One-shot settle: reads highest voucher from store and submits on-chain.
|
|
303
|
+
*/
|
|
304
|
+
export async function settle(
|
|
305
|
+
store: ChannelStore.ChannelStore,
|
|
306
|
+
client: viem_Client,
|
|
307
|
+
channelId: Hex,
|
|
308
|
+
escrowContract?: Address | undefined,
|
|
309
|
+
): Promise<Hex> {
|
|
310
|
+
const channel = await store.getChannel(channelId)
|
|
311
|
+
if (!channel) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
312
|
+
if (!channel.highestVoucher) throw new VerificationFailedError({ reason: 'no voucher to settle' })
|
|
313
|
+
|
|
314
|
+
const chainId = client.chain?.id
|
|
315
|
+
const resolvedEscrow =
|
|
316
|
+
escrowContract ?? defaults.escrowContract[chainId as keyof typeof defaults.escrowContract]
|
|
317
|
+
if (!resolvedEscrow) throw new Error(`No escrow contract for chainId ${chainId}.`)
|
|
318
|
+
|
|
319
|
+
const settledAmount = channel.highestVoucher.cumulativeAmount
|
|
320
|
+
const txHash = await settleOnChain(client, resolvedEscrow, channel.highestVoucher)
|
|
321
|
+
|
|
322
|
+
await store.updateChannel(channelId, (current) => {
|
|
323
|
+
if (!current) return null
|
|
324
|
+
const nextSettled =
|
|
325
|
+
settledAmount > current.settledOnChain ? settledAmount : current.settledOnChain
|
|
326
|
+
return { ...current, settledOnChain: nextSettled }
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
return txHash
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Charge against a channel's balance.
|
|
334
|
+
*
|
|
335
|
+
* Exported so consumers can deduct from a channel outside the `stream()`
|
|
336
|
+
* handler (e.g., custom middleware, the SSE `serve()` loop, or direct tests).
|
|
337
|
+
*
|
|
338
|
+
* Delegates to the shared `deductFromChannel` atomic helper and translates
|
|
339
|
+
* failure modes into typed errors (`InsufficientBalanceError`, `ChannelClosedError`).
|
|
340
|
+
*/
|
|
341
|
+
export async function charge(
|
|
342
|
+
store: ChannelStore.ChannelStore,
|
|
343
|
+
channelId: Hex,
|
|
344
|
+
amount: bigint,
|
|
345
|
+
): Promise<ChannelStore.State> {
|
|
346
|
+
let result: Awaited<ReturnType<typeof ChannelStore.deductFromChannel>>
|
|
347
|
+
try {
|
|
348
|
+
result = await ChannelStore.deductFromChannel(store, channelId, amount)
|
|
349
|
+
} catch {
|
|
350
|
+
throw new ChannelClosedError({ reason: 'channel not found' })
|
|
351
|
+
}
|
|
352
|
+
if (!result.ok) {
|
|
353
|
+
const available = result.channel.highestVoucherAmount - result.channel.spent
|
|
354
|
+
throw new InsufficientBalanceError({
|
|
355
|
+
reason: `requested ${amount}, available ${available}`,
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
return result.channel
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Validate on-chain channel state.
|
|
363
|
+
*/
|
|
364
|
+
function validateOnChainChannel(
|
|
365
|
+
onChain: OnChainChannel,
|
|
366
|
+
recipient: Address,
|
|
367
|
+
currency: Address,
|
|
368
|
+
amount?: bigint,
|
|
369
|
+
): void {
|
|
370
|
+
if (onChain.deposit === 0n) {
|
|
371
|
+
throw new ChannelNotFoundError({ reason: 'channel not funded on-chain' })
|
|
372
|
+
}
|
|
373
|
+
if (onChain.finalized) {
|
|
374
|
+
throw new ChannelClosedError({ reason: 'channel is finalized on-chain' })
|
|
375
|
+
}
|
|
376
|
+
if (onChain.closeRequestedAt !== 0n) {
|
|
377
|
+
throw new ChannelClosedError({ reason: 'channel has a pending close request' })
|
|
378
|
+
}
|
|
379
|
+
if (onChain.payee.toLowerCase() !== recipient.toLowerCase()) {
|
|
380
|
+
throw new VerificationFailedError({
|
|
381
|
+
reason: 'on-chain payee does not match server destination',
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
if (onChain.token.toLowerCase() !== currency.toLowerCase()) {
|
|
385
|
+
throw new VerificationFailedError({ reason: 'on-chain token does not match server token' })
|
|
386
|
+
}
|
|
387
|
+
if (amount !== undefined && onChain.deposit - onChain.settled < amount) {
|
|
388
|
+
throw new InsufficientBalanceError({
|
|
389
|
+
reason: 'channel available balance insufficient for requested amount',
|
|
390
|
+
})
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Shared logic for verifying an incremental voucher and updating channel state.
|
|
396
|
+
* Used by both handleVoucher and (indirectly) handleOpen.
|
|
397
|
+
*/
|
|
398
|
+
async function verifyAndAcceptVoucher(parameters: {
|
|
399
|
+
store: ChannelStore.ChannelStore
|
|
400
|
+
minVoucherDelta: bigint
|
|
401
|
+
challenge: Challenge.Challenge
|
|
402
|
+
channel: ChannelStore.State
|
|
403
|
+
channelId: Hex
|
|
404
|
+
voucher: SignedVoucher
|
|
405
|
+
onChain: OnChainChannel
|
|
406
|
+
methodDetails: StreamMethodDetails
|
|
407
|
+
}): Promise<StreamReceipt> {
|
|
408
|
+
const { store, minVoucherDelta, challenge, channel, channelId, voucher, onChain, methodDetails } =
|
|
409
|
+
parameters
|
|
410
|
+
|
|
411
|
+
if (onChain.finalized) {
|
|
412
|
+
throw new ChannelClosedError({ reason: 'channel is finalized on-chain' })
|
|
413
|
+
}
|
|
414
|
+
if (onChain.closeRequestedAt !== 0n) {
|
|
415
|
+
throw new ChannelClosedError({ reason: 'channel has a pending close request' })
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (voucher.cumulativeAmount < onChain.settled) {
|
|
419
|
+
throw new VerificationFailedError({
|
|
420
|
+
reason: 'voucher cumulativeAmount is below on-chain settled amount',
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (voucher.cumulativeAmount > onChain.deposit) {
|
|
425
|
+
throw new AmountExceedsDepositError({ reason: 'voucher amount exceeds on-chain deposit' })
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (voucher.cumulativeAmount <= channel.highestVoucherAmount) {
|
|
429
|
+
return createStreamReceipt({
|
|
430
|
+
challengeId: challenge.id,
|
|
431
|
+
channelId,
|
|
432
|
+
acceptedCumulative: channel.highestVoucherAmount,
|
|
433
|
+
spent: channel.spent,
|
|
434
|
+
units: channel.units,
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const delta = voucher.cumulativeAmount - channel.highestVoucherAmount
|
|
439
|
+
if (delta < minVoucherDelta) {
|
|
440
|
+
throw new DeltaTooSmallError({
|
|
441
|
+
reason: `voucher delta ${delta} below minimum ${minVoucherDelta}`,
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const isValid = await verifyVoucher(
|
|
446
|
+
methodDetails.escrowContract,
|
|
447
|
+
methodDetails.chainId,
|
|
448
|
+
voucher,
|
|
449
|
+
channel.authorizedSigner,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
if (!isValid) {
|
|
453
|
+
throw new InvalidSignatureError({ reason: 'invalid voucher signature' })
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const updated = await store.updateChannel(channelId, (current) => {
|
|
457
|
+
if (!current) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
458
|
+
if (voucher.cumulativeAmount > current.highestVoucherAmount) {
|
|
459
|
+
return {
|
|
460
|
+
...current,
|
|
461
|
+
deposit: onChain.deposit,
|
|
462
|
+
highestVoucherAmount: voucher.cumulativeAmount,
|
|
463
|
+
highestVoucher: voucher,
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return current
|
|
467
|
+
})
|
|
468
|
+
if (!updated) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
469
|
+
|
|
470
|
+
return createStreamReceipt({
|
|
471
|
+
challengeId: challenge.id,
|
|
472
|
+
channelId,
|
|
473
|
+
acceptedCumulative: updated.highestVoucherAmount,
|
|
474
|
+
spent: updated.spent,
|
|
475
|
+
units: updated.units,
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Handle 'open' action - broadcast open transaction, verify voucher, and create channel.
|
|
481
|
+
*/
|
|
482
|
+
async function handleOpen(
|
|
483
|
+
store: ChannelStore.ChannelStore,
|
|
484
|
+
client: viem_Client,
|
|
485
|
+
challenge: Challenge.Challenge,
|
|
486
|
+
payload: StreamCredentialPayload & { action: 'open' },
|
|
487
|
+
methodDetails: StreamMethodDetails,
|
|
488
|
+
feePayer: viem_Account | undefined,
|
|
489
|
+
): Promise<StreamReceipt> {
|
|
490
|
+
const voucher = parseVoucherFromPayload(
|
|
491
|
+
payload.channelId,
|
|
492
|
+
payload.cumulativeAmount,
|
|
493
|
+
payload.signature,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
const recipient = challenge.request.recipient as Address
|
|
497
|
+
const currency = challenge.request.currency as Address
|
|
498
|
+
const amount = challenge.request.amount ? BigInt(challenge.request.amount as string) : undefined
|
|
499
|
+
|
|
500
|
+
const { onChain, txHash } = await broadcastOpenTransaction({
|
|
501
|
+
client,
|
|
502
|
+
serializedTransaction: payload.transaction,
|
|
503
|
+
escrowContract: methodDetails.escrowContract,
|
|
504
|
+
channelId: payload.channelId,
|
|
505
|
+
recipient,
|
|
506
|
+
currency,
|
|
507
|
+
feePayer,
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
validateOnChainChannel(onChain, recipient, currency, amount)
|
|
511
|
+
|
|
512
|
+
const authorizedSigner =
|
|
513
|
+
onChain.authorizedSigner === '0x0000000000000000000000000000000000000000'
|
|
514
|
+
? onChain.payer
|
|
515
|
+
: onChain.authorizedSigner
|
|
516
|
+
|
|
517
|
+
if (voucher.cumulativeAmount > onChain.deposit) {
|
|
518
|
+
throw new AmountExceedsDepositError({ reason: 'voucher amount exceeds on-chain deposit' })
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (voucher.cumulativeAmount < onChain.settled) {
|
|
522
|
+
throw new VerificationFailedError({
|
|
523
|
+
reason: 'voucher cumulativeAmount is below on-chain settled amount',
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const isValid = await verifyVoucher(
|
|
528
|
+
methodDetails.escrowContract,
|
|
529
|
+
methodDetails.chainId,
|
|
530
|
+
voucher,
|
|
531
|
+
authorizedSigner,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
if (!isValid) {
|
|
535
|
+
throw new InvalidSignatureError({ reason: 'invalid voucher signature' })
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const updated = await store.updateChannel(payload.channelId, (existing) => {
|
|
539
|
+
if (existing) {
|
|
540
|
+
if (voucher.cumulativeAmount < existing.settledOnChain) {
|
|
541
|
+
throw new VerificationFailedError({
|
|
542
|
+
reason: 'voucher amount is below settled on-chain amount',
|
|
543
|
+
})
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (voucher.cumulativeAmount > existing.highestVoucherAmount) {
|
|
547
|
+
return {
|
|
548
|
+
...existing,
|
|
549
|
+
deposit: onChain.deposit,
|
|
550
|
+
highestVoucherAmount: voucher.cumulativeAmount,
|
|
551
|
+
highestVoucher: voucher,
|
|
552
|
+
authorizedSigner,
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
...existing,
|
|
557
|
+
deposit: onChain.deposit,
|
|
558
|
+
authorizedSigner,
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
channelId: payload.channelId,
|
|
563
|
+
chainId: methodDetails.chainId,
|
|
564
|
+
escrowContract: methodDetails.escrowContract,
|
|
565
|
+
payer: onChain.payer,
|
|
566
|
+
payee: onChain.payee,
|
|
567
|
+
token: onChain.token,
|
|
568
|
+
authorizedSigner,
|
|
569
|
+
deposit: onChain.deposit,
|
|
570
|
+
settledOnChain: 0n,
|
|
571
|
+
highestVoucherAmount: voucher.cumulativeAmount,
|
|
572
|
+
highestVoucher: voucher,
|
|
573
|
+
spent: 0n,
|
|
574
|
+
units: 0,
|
|
575
|
+
finalized: false,
|
|
576
|
+
createdAt: new Date().toISOString(),
|
|
577
|
+
}
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
if (!updated) throw new VerificationFailedError({ reason: 'failed to create channel' })
|
|
581
|
+
|
|
582
|
+
return createStreamReceipt({
|
|
583
|
+
challengeId: challenge.id,
|
|
584
|
+
channelId: payload.channelId,
|
|
585
|
+
acceptedCumulative: updated.highestVoucherAmount,
|
|
586
|
+
spent: updated.spent,
|
|
587
|
+
units: updated.units,
|
|
588
|
+
txHash,
|
|
589
|
+
})
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Handle 'topUp' action - broadcast topUp transaction and update channel deposit.
|
|
594
|
+
*
|
|
595
|
+
* Per spec Section 8.3.2, topUp payloads contain only the transaction and
|
|
596
|
+
* additionalDeposit — no voucher. The client must send a separate 'voucher'
|
|
597
|
+
* action to authorize spending the new funds.
|
|
598
|
+
*/
|
|
599
|
+
async function handleTopUp(
|
|
600
|
+
store: ChannelStore.ChannelStore,
|
|
601
|
+
client: viem_Client,
|
|
602
|
+
challenge: Challenge.Challenge,
|
|
603
|
+
payload: StreamCredentialPayload & { action: 'topUp' },
|
|
604
|
+
methodDetails: StreamMethodDetails,
|
|
605
|
+
feePayer: viem_Account | undefined,
|
|
606
|
+
): Promise<StreamReceipt> {
|
|
607
|
+
const channel = await store.getChannel(payload.channelId)
|
|
608
|
+
if (!channel) {
|
|
609
|
+
throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const declaredDeposit = BigInt(payload.additionalDeposit)
|
|
613
|
+
|
|
614
|
+
const { newDeposit: onChainDeposit } = await broadcastTopUpTransaction({
|
|
615
|
+
client,
|
|
616
|
+
serializedTransaction: payload.transaction,
|
|
617
|
+
escrowContract: methodDetails.escrowContract,
|
|
618
|
+
channelId: payload.channelId,
|
|
619
|
+
declaredDeposit,
|
|
620
|
+
previousDeposit: channel.deposit,
|
|
621
|
+
feePayer,
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
const updated = await store.updateChannel(payload.channelId, (current) => {
|
|
625
|
+
if (!current) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
626
|
+
return { ...current, deposit: onChainDeposit }
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
return createStreamReceipt({
|
|
630
|
+
challengeId: challenge.id,
|
|
631
|
+
channelId: payload.channelId,
|
|
632
|
+
acceptedCumulative: updated?.highestVoucherAmount ?? channel.highestVoucherAmount,
|
|
633
|
+
spent: updated?.spent ?? channel.spent,
|
|
634
|
+
units: updated?.units ?? channel.units,
|
|
635
|
+
})
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Handle 'voucher' action - verify and accept a new voucher.
|
|
640
|
+
*/
|
|
641
|
+
async function handleVoucher(
|
|
642
|
+
store: ChannelStore.ChannelStore,
|
|
643
|
+
_client: viem_Client,
|
|
644
|
+
minVoucherDelta: bigint,
|
|
645
|
+
challenge: Challenge.Challenge,
|
|
646
|
+
payload: StreamCredentialPayload & { action: 'voucher' },
|
|
647
|
+
methodDetails: StreamMethodDetails,
|
|
648
|
+
): Promise<StreamReceipt> {
|
|
649
|
+
const channel = await store.getChannel(payload.channelId)
|
|
650
|
+
if (!channel) {
|
|
651
|
+
throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
652
|
+
}
|
|
653
|
+
if (channel.finalized) {
|
|
654
|
+
throw new ChannelClosedError({ reason: 'channel is finalized' })
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const voucher = parseVoucherFromPayload(
|
|
658
|
+
payload.channelId,
|
|
659
|
+
payload.cumulativeAmount,
|
|
660
|
+
payload.signature,
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
// Use locally-stored channel state as a trusted cache instead of
|
|
664
|
+
// reading on-chain for every voucher. The on-chain state is verified
|
|
665
|
+
// during `open` and `topUp` actions — subsequent vouchers within the
|
|
666
|
+
// same session can safely use the cached deposit/signer values.
|
|
667
|
+
// This avoids an RPC round-trip per voucher, which is critical for
|
|
668
|
+
// high-frequency SSE streaming where vouchers arrive per-token.
|
|
669
|
+
const cachedOnChain: OnChainChannel = {
|
|
670
|
+
payer: channel.payer,
|
|
671
|
+
payee: channel.payee,
|
|
672
|
+
token: channel.token,
|
|
673
|
+
deposit: channel.deposit,
|
|
674
|
+
settled: channel.settledOnChain,
|
|
675
|
+
finalized: channel.finalized,
|
|
676
|
+
authorizedSigner: channel.authorizedSigner,
|
|
677
|
+
closeRequestedAt: 0n,
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return verifyAndAcceptVoucher({
|
|
681
|
+
store,
|
|
682
|
+
minVoucherDelta,
|
|
683
|
+
challenge,
|
|
684
|
+
channel,
|
|
685
|
+
channelId: payload.channelId,
|
|
686
|
+
voucher,
|
|
687
|
+
onChain: cachedOnChain,
|
|
688
|
+
methodDetails,
|
|
689
|
+
})
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Handle 'close' action - verify final voucher and close channel.
|
|
694
|
+
*/
|
|
695
|
+
async function handleClose(
|
|
696
|
+
store: ChannelStore.ChannelStore,
|
|
697
|
+
client: viem_Client,
|
|
698
|
+
challenge: Challenge.Challenge,
|
|
699
|
+
payload: StreamCredentialPayload & { action: 'close' },
|
|
700
|
+
methodDetails: StreamMethodDetails,
|
|
701
|
+
account?: viem_Account,
|
|
702
|
+
): Promise<StreamReceipt> {
|
|
703
|
+
const channel = await store.getChannel(payload.channelId)
|
|
704
|
+
if (!channel) {
|
|
705
|
+
throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
706
|
+
}
|
|
707
|
+
if (channel.finalized) {
|
|
708
|
+
throw new ChannelClosedError({ reason: 'channel is already finalized' })
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const voucher = parseVoucherFromPayload(
|
|
712
|
+
payload.channelId,
|
|
713
|
+
payload.cumulativeAmount,
|
|
714
|
+
payload.signature,
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
if (voucher.cumulativeAmount < channel.highestVoucherAmount) {
|
|
718
|
+
throw new VerificationFailedError({
|
|
719
|
+
reason: 'close voucher amount must be >= highest accepted voucher',
|
|
720
|
+
})
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const onChain = await getOnChainChannel(client, methodDetails.escrowContract, payload.channelId)
|
|
724
|
+
|
|
725
|
+
if (onChain.finalized) {
|
|
726
|
+
throw new ChannelClosedError({ reason: 'channel is finalized on-chain' })
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (voucher.cumulativeAmount < onChain.settled) {
|
|
730
|
+
throw new VerificationFailedError({
|
|
731
|
+
reason: 'close voucher cumulativeAmount is below on-chain settled amount',
|
|
732
|
+
})
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (voucher.cumulativeAmount > onChain.deposit) {
|
|
736
|
+
throw new AmountExceedsDepositError({
|
|
737
|
+
reason: 'close voucher amount exceeds on-chain deposit',
|
|
738
|
+
})
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const isValid = await verifyVoucher(
|
|
742
|
+
methodDetails.escrowContract,
|
|
743
|
+
methodDetails.chainId,
|
|
744
|
+
voucher,
|
|
745
|
+
channel.authorizedSigner,
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
if (!isValid) {
|
|
749
|
+
throw new InvalidSignatureError({ reason: 'invalid voucher signature' })
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const txHash = await closeOnChain(client, methodDetails.escrowContract, voucher, account)
|
|
753
|
+
|
|
754
|
+
const updated = await store.updateChannel(payload.channelId, (current) => {
|
|
755
|
+
if (!current) return null
|
|
756
|
+
return {
|
|
757
|
+
...current,
|
|
758
|
+
deposit: onChain.deposit,
|
|
759
|
+
highestVoucherAmount: voucher.cumulativeAmount,
|
|
760
|
+
highestVoucher: voucher,
|
|
761
|
+
finalized: true,
|
|
762
|
+
}
|
|
763
|
+
})
|
|
764
|
+
|
|
765
|
+
return createStreamReceipt({
|
|
766
|
+
challengeId: challenge.id,
|
|
767
|
+
channelId: payload.channelId,
|
|
768
|
+
acceptedCumulative: voucher.cumulativeAmount,
|
|
769
|
+
spent: updated?.spent ?? channel.spent,
|
|
770
|
+
units: updated?.units ?? channel.units,
|
|
771
|
+
...(txHash !== undefined && { txHash }),
|
|
772
|
+
})
|
|
773
|
+
}
|