@agent-score/commerce 1.8.1 → 2.0.1
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/README.md +73 -9
- package/dist/{_response-9yp6Fit2.d.mts → _response-BFYN3b6i.d.mts} +17 -19
- package/dist/{_response-CC6jNb8q.d.ts → _response-_iPD5AIj.d.ts} +17 -19
- package/dist/challenge/index.d.mts +106 -198
- package/dist/challenge/index.d.ts +106 -198
- package/dist/challenge/index.js +238 -111
- package/dist/challenge/index.js.map +1 -1
- package/dist/challenge/index.mjs +238 -111
- package/dist/challenge/index.mjs.map +1 -1
- package/dist/checkout-B1JuEcbx.d.ts +939 -0
- package/dist/checkout-BN5i1Fi7.d.mts +939 -0
- package/dist/core.d.mts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/core.mjs.map +1 -1
- package/dist/discovery/index.d.mts +453 -51
- package/dist/discovery/index.d.ts +453 -51
- package/dist/discovery/index.js +1092 -58
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +1060 -57
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/identity/express.d.mts +3 -3
- package/dist/identity/express.d.ts +3 -3
- package/dist/identity/express.js +30 -19
- package/dist/identity/express.js.map +1 -1
- package/dist/identity/express.mjs +30 -19
- package/dist/identity/express.mjs.map +1 -1
- package/dist/identity/fastify.d.mts +4 -4
- package/dist/identity/fastify.d.ts +4 -4
- package/dist/identity/fastify.js +30 -19
- package/dist/identity/fastify.js.map +1 -1
- package/dist/identity/fastify.mjs +30 -19
- package/dist/identity/fastify.mjs.map +1 -1
- package/dist/identity/hono.d.mts +3 -3
- package/dist/identity/hono.d.ts +3 -3
- package/dist/identity/hono.js +30 -19
- package/dist/identity/hono.js.map +1 -1
- package/dist/identity/hono.mjs +30 -19
- package/dist/identity/hono.mjs.map +1 -1
- package/dist/identity/nextjs.d.mts +6 -7
- package/dist/identity/nextjs.d.ts +6 -7
- package/dist/identity/nextjs.js +30 -19
- package/dist/identity/nextjs.js.map +1 -1
- package/dist/identity/nextjs.mjs +30 -19
- package/dist/identity/nextjs.mjs.map +1 -1
- package/dist/identity/policy.d.mts +41 -4
- package/dist/identity/policy.d.ts +41 -4
- package/dist/identity/policy.js +23307 -18
- package/dist/identity/policy.js.map +1 -1
- package/dist/identity/policy.mjs +23313 -3
- package/dist/identity/policy.mjs.map +1 -1
- package/dist/identity/web.d.mts +3 -3
- package/dist/identity/web.d.ts +3 -3
- package/dist/identity/web.js +30 -19
- package/dist/identity/web.js.map +1 -1
- package/dist/identity/web.mjs +30 -19
- package/dist/identity/web.mjs.map +1 -1
- package/dist/index.d.mts +72 -329
- package/dist/index.d.ts +72 -329
- package/dist/index.js +23301 -378
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23294 -362
- package/dist/index.mjs.map +1 -1
- package/dist/payment/index.d.mts +297 -265
- package/dist/payment/index.d.ts +297 -265
- package/dist/payment/index.js +605 -149
- package/dist/payment/index.js.map +1 -1
- package/dist/payment/index.mjs +590 -148
- package/dist/payment/index.mjs.map +1 -1
- package/dist/{agent_instructions-DiMSGkdm.d.mts → pricing-CQ9DIFaw.d.ts} +109 -56
- package/dist/{agent_instructions-DiMSGkdm.d.ts → pricing-CxzwyiO6.d.mts} +109 -56
- package/dist/rail_spec-XP0wKgJV.d.mts +132 -0
- package/dist/rail_spec-XP0wKgJV.d.ts +132 -0
- package/dist/{signer-CFVQsWjL.d.mts → signer-3FAit11j.d.mts} +27 -1
- package/dist/{signer-CFVQsWjL.d.ts → signer-3FAit11j.d.ts} +27 -1
- package/dist/solana-Cds87OTu.d.mts +67 -0
- package/dist/solana-Cds87OTu.d.ts +67 -0
- package/dist/stripe-multichain/index.d.mts +55 -66
- package/dist/stripe-multichain/index.d.ts +55 -66
- package/dist/stripe-multichain/index.js +68 -42
- package/dist/stripe-multichain/index.js.map +1 -1
- package/dist/stripe-multichain/index.mjs +68 -41
- package/dist/stripe-multichain/index.mjs.map +1 -1
- package/dist/{wwwauthenticate-CU1eNvMQ.d.mts → wwwauthenticate-D_FMnPgU.d.mts} +9 -10
- package/dist/{wwwauthenticate-CU1eNvMQ.d.ts → wwwauthenticate-D_FMnPgU.d.ts} +9 -10
- package/dist/x402_server-hgQzWQwB.d.mts +81 -0
- package/dist/x402_server-hgQzWQwB.d.ts +81 -0
- package/package.json +9 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/stripe-multichain/payment_intent.ts","../../src/stripe-multichain/simulate_deposit.ts","../../src/stripe-multichain/mppx_stripe.ts","../../src/stripe-multichain/pi-cache.ts"],"sourcesContent":["/**\n * Minimal Stripe client surface — only the methods we use. Vendors pass their actual\n * `Stripe` instance (peer dep on the `stripe` package); this interface keeps the SDK\n * decoupled from any specific Stripe version.\n */\nexport interface StripeClientLike {\n paymentIntents: {\n create(\n params: Record<string, unknown>,\n opts?: { idempotencyKey?: string },\n ): Promise<StripePaymentIntent>;\n };\n}\n\nexport interface StripePaymentIntent {\n id: string;\n next_action?: {\n crypto_display_details?: {\n deposit_addresses?: Record<string, { address?: string } | undefined>;\n };\n } | null;\n [key: string]: unknown;\n}\n\nexport interface CreateMultichainPaymentIntentInput {\n /** A configured Stripe SDK instance. */\n stripe: StripeClientLike;\n /** Amount in cents (Stripe convention — $1.00 = 100). */\n amount: number;\n /** Currency code. Default 'usd'. */\n currency?: string;\n /** Networks to advertise to Stripe deposit_options. Default ['tempo', 'base', 'solana']. */\n networks?: string[];\n /** Metadata to attach to the PI (visible in Stripe dashboard). */\n metadata?: Record<string, string>;\n /** Idempotency key — agent retries of the same purchase won't create duplicate PIs. */\n idempotencyKey?: string;\n}\n\nexport interface MultichainPaymentIntentResult {\n /** Stripe PaymentIntent ID. */\n paymentIntentId: string;\n /** Map of network name → on-chain deposit address. e.g., { tempo: '0x...', base: '0x...', solana: '...' }. */\n depositAddresses: Record<string, string>;\n}\n\n/**\n * Create a Stripe PaymentIntent with `deposit_options.networks` set to multiple chains,\n * returning the PI id + deposit addresses per network. The agent sends funds to the\n * address on whichever chain they prefer (via x402 or MPP), and Stripe auto-captures\n * the PI when funds land.\n *\n * This is the canonical path for the multi-chain x402 + Tempo flow.\n * Distinct from the Stripe SPT (Shared Payment Token) flow, which is handled via\n * `createMppxStripe` + the agent's own Stripe account or `link-cli`.\n */\nexport async function createMultichainPaymentIntent(\n input: CreateMultichainPaymentIntentInput,\n): Promise<MultichainPaymentIntentResult> {\n const pi = await input.stripe.paymentIntents.create(\n {\n amount: input.amount,\n currency: input.currency ?? 'usd',\n payment_method_types: ['crypto'],\n payment_method_data: { type: 'crypto' },\n payment_method_options: {\n crypto: {\n mode: 'deposit',\n deposit_options: { networks: input.networks ?? ['tempo', 'base', 'solana'] },\n },\n },\n confirm: true,\n ...(input.metadata ? { metadata: input.metadata } : {}),\n },\n input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : undefined,\n );\n\n const depositAddresses: Record<string, string> = {};\n const addrs = pi.next_action?.crypto_display_details?.deposit_addresses ?? {};\n for (const [network, info] of Object.entries(addrs)) {\n if (info?.address) depositAddresses[network] = info.address;\n }\n\n if (Object.keys(depositAddresses).length === 0) {\n throw new Error('No deposit addresses returned from Stripe PaymentIntent');\n }\n\n return { paymentIntentId: pi.id, depositAddresses };\n}\n\n/**\n * Convenience accessor: get the deposit address for a specific network from a\n * createMultichainPaymentIntent result. Returns undefined if Stripe didn't issue\n * an address for that network.\n */\nexport function getDepositAddress(\n result: MultichainPaymentIntentResult,\n network: string,\n): string | undefined {\n return result.depositAddresses[network];\n}\n","/**\n * Stripe's documented magic test_helpers transaction hash that resolves the\n * PaymentIntent to `succeeded` within 15 seconds. Same value across all networks —\n * Stripe normalizes the format internally. Anything else (including network-shaped\n * placeholder bytes) is rejected with \"not a valid testmode transaction hash\".\n *\n * See: https://docs.stripe.com/payments/deposit-mode-stablecoin-payments\n */\nexport const STRIPE_TEST_TX_HASH_SUCCESS =\n '0x00000000000000000000000000000000000000000000000000000testsuccess';\n\n/**\n * Stripe's documented magic test_helpers transaction hash that fails the charge\n * (PaymentIntent returns to `requires_payment_method` within 15 seconds).\n */\nexport const STRIPE_TEST_TX_HASH_FAILED =\n '0x000000000000000000000000000000000000000000000000000000testfailed';\n\nexport interface SimulateCryptoDepositInput {\n /** Stripe PaymentIntent id to simulate a deposit on. */\n paymentIntentId: string;\n /** Network the simulated deposit lands on. */\n network: 'tempo' | 'base' | 'solana';\n /** Optional simulated buyer wallet address. Defaults to a sensible placeholder per network. */\n buyerWallet?: string;\n /** Token currency (e.g., 'usdc'). Optional — passed as a form param if set. */\n tokenCurrency?: string;\n /** Simulated transaction hash. Optional — passed as a form param if set. */\n transactionHash?: string;\n /** Stripe secret key (for the test-helpers Authorization header). Must be a `sk_test_...` key. */\n stripeSecretKey: string;\n /** Stripe API version to request via the `Stripe-Version` header. Useful for preview APIs. */\n stripeVersion?: string;\n /** Override the Stripe API base URL. Default 'https://api.stripe.com'. */\n stripeApiBase?: string;\n /** Arbitrary additional form params to merge into the request body. */\n extra?: Record<string, string>;\n}\n\nconst DEFAULT_BUYER_WALLET: Record<string, string> = {\n base: '0x0000000000000000000000000000000000000001',\n tempo: '0x0000000000000000000000000000000000000001',\n solana: '11111111111111111111111111111111',\n};\n\n/**\n * Call Stripe's `test_helpers/payment_intents/{id}/simulate_crypto_deposit` endpoint. Used\n * in testnet/dev to simulate a deposit landing on a PaymentIntent so the integration\n * end-to-end can be exercised without on-chain transfers.\n *\n * Throws on non-2xx responses (returns Stripe's error body in the message).\n */\nexport async function simulateCryptoDeposit(input: SimulateCryptoDepositInput): Promise<void> {\n const url = `${input.stripeApiBase ?? 'https://api.stripe.com'}/v1/test_helpers/payment_intents/${input.paymentIntentId}/simulate_crypto_deposit`;\n const params = new URLSearchParams({\n network: input.network,\n buyer_wallet: input.buyerWallet ?? DEFAULT_BUYER_WALLET[input.network] ?? '',\n });\n if (input.tokenCurrency) params.set('token_currency', input.tokenCurrency);\n if (input.transactionHash) params.set('transaction_hash', input.transactionHash);\n for (const [k, v] of Object.entries(input.extra ?? {})) {\n params.set(k, v);\n }\n const headers: Record<string, string> = {\n Authorization: `Bearer ${input.stripeSecretKey}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n if (input.stripeVersion) headers['Stripe-Version'] = input.stripeVersion;\n const res = await fetch(url, { method: 'POST', headers, body: params.toString() });\n if (!res.ok) {\n throw new Error(`Stripe simulate_crypto_deposit failed: ${res.status} ${await res.text()}`);\n }\n}\n\nexport interface SimulateDepositIfTestModeInput {\n /** Stripe PaymentIntent id resolver — given a deposit address, return the PI id (or undefined\n * if the cache TTL expired between 402 emit and settlement). Typically `cache.getPaymentIntentId`. */\n getPaymentIntentId: (depositAddress: string) => string | undefined;\n /** The deposit address that was paid to (recipient). */\n depositAddress: string;\n /** Network the simulated deposit lands on. */\n network: 'tempo' | 'base' | 'solana';\n /** Optional simulated buyer wallet (defaults per network in `simulateCryptoDeposit`). */\n buyerWallet?: string;\n /** Token currency to pass through to Stripe (typically `'usdc'`). */\n tokenCurrency?: string;\n /** Stripe secret key. The wrapper checks this starts with `sk_test_` and skips otherwise. */\n stripeSecretKey: string;\n /** Stripe API version (e.g. `'2026-03-04.preview'` for the deposit-mode preview). */\n stripeVersion?: string;\n}\n\n/**\n * Higher-level wrapper around {@link simulateCryptoDeposit} for the testnet/dev path.\n * Bundles the three steps every Stripe-multichain merchant repeats:\n *\n * 1. Gate on `sk_test_` key prefix — production keys reject the test_helpers endpoint\n * with 400; live deposits reach Stripe's real crypto-deposit watcher instead.\n * 2. Resolve the PaymentIntent id from the deposit address (cache lookup).\n * 3. Call `simulate_crypto_deposit` with Stripe's documented success magic hash.\n *\n * Logs `[stripe] ✓ Simulated <network> deposit for PI <id>` on success and\n * `[stripe] ✗ Failed to simulate <network> deposit for PI <id>: <err>` on failure.\n * Errors are caught + logged (never thrown) so a sim hiccup doesn't fail the order.\n *\n * Use case is exclusively dev/testnet end-to-end — production servers (sk_live_) no-op.\n */\nexport async function simulateDepositIfTestMode(input: SimulateDepositIfTestModeInput): Promise<void> {\n if (!input.stripeSecretKey.startsWith('sk_test_')) return;\n const piId = input.getPaymentIntentId(input.depositAddress);\n if (!piId) {\n console.warn(\n `[stripe] Skipping deposit simulation — no PI cached for deposit address ${input.depositAddress.slice(0, 10)}… (network=${input.network}). The PI cache TTL may have expired between 402 emission and settlement.`,\n );\n return;\n }\n try {\n await simulateCryptoDeposit({\n paymentIntentId: piId,\n network: input.network,\n ...(input.buyerWallet !== undefined && { buyerWallet: input.buyerWallet }),\n tokenCurrency: input.tokenCurrency ?? 'usdc',\n transactionHash: STRIPE_TEST_TX_HASH_SUCCESS,\n stripeSecretKey: input.stripeSecretKey,\n ...(input.stripeVersion !== undefined && { stripeVersion: input.stripeVersion }),\n });\n console.warn(`[stripe] ✓ Simulated ${input.network} deposit for PI ${piId}`);\n } catch (err) {\n console.error(\n `[stripe] ✗ Failed to simulate ${input.network} deposit for PI ${piId}:`,\n err instanceof Error ? err.message : err,\n );\n }\n}\n","export interface CreateMppxStripeInput {\n /** Stripe profile_id / network_id (the value advertised in your `stripe/charge` accepted_methods entry). */\n profileId: string;\n /** Stripe secret key — mppx uses it to validate inbound SharedPaymentTokens. */\n secretKey: string;\n /** Payment method types this stripe rail accepts. Default ['card', 'link']. */\n paymentMethodTypes?: string[];\n}\n\n/**\n * Wraps the `mppStripe.charge(...)` boilerplate from `mppx/server`. Returns the value\n * vendors pass into `Mppx.create({ methods: [...] })`. mppx is an OPTIONAL peer dependency —\n * vendors who don't use Stripe SPT don't need to install it.\n *\n * Example:\n *\n * import { Mppx, tempo } from 'mppx/server';\n * import { createMppxStripe } from '@agent-score/commerce/stripe-multichain';\n *\n * const stripeMethod = await createMppxStripe({\n * profileId: process.env.STRIPE_PROFILE_ID!,\n * secretKey: process.env.STRIPE_SECRET_KEY!,\n * });\n *\n * const mppx = Mppx.create({\n * methods: [tempo.charge({...}), stripeMethod],\n * secretKey: process.env.MPP_SECRET_KEY!,\n * });\n *\n * Throws if mppx is not installed.\n */\nexport async function createMppxStripe(input: CreateMppxStripeInput): Promise<unknown> {\n const moduleName = 'mppx/server';\n const mppx = (await import(moduleName).catch(() => null)) as {\n stripe?: {\n charge: (config: {\n networkId: string;\n paymentMethodTypes?: string[];\n secretKey: string;\n }) => unknown;\n };\n } | null;\n /* v8 ignore start -- peer-dep-absence guard; mppx is installed in the test env so this branch can't be exercised without mocking the dynamic import */\n if (!mppx?.stripe?.charge) {\n throw new Error(\n 'mppx not installed — install with `npm install mppx` to use createMppxStripe.',\n );\n }\n /* v8 ignore stop */\n return mppx.stripe.charge({\n networkId: input.profileId,\n paymentMethodTypes: input.paymentMethodTypes ?? ['card', 'link'],\n secretKey: input.secretKey,\n });\n}\n","/**\n * Stripe PaymentIntent + deposit-address cache.\n *\n * Stripe-multichain merchants need three lookups during a request lifecycle:\n *\n * 1. **Is this on-chain `pay_to` address one we minted?** — when an MPP credential\n * arrives with a `recipient`, verify it matches a recently-minted Stripe deposit\n * address. Validates the credential's deposit address against the addresses the\n * merchant has actually minted.\n *\n * 2. **Which PaymentIntent owns this deposit address?** — when settling, the\n * `simulate_crypto_deposit` test_helpers call needs the PaymentIntent id for the\n * deposit address that was paid to.\n *\n * 3. **Which sibling deposit addresses belong to the same PaymentIntent?** — when\n * enriching a 402 with x402 entries, the merchant needs the Base + Solana addresses\n * Stripe minted alongside the original Tempo address (one PI carries up to three).\n *\n * All three are TTL-bounded (default 300s — long enough for an agent to retry, short\n * enough to bound memory). Backed by Redis when `redisUrl` is set, falls back to\n * in-process Map otherwise. Single-instance servers can use the in-memory cache;\n * multi-instance deployments need a shared cache (Redis) so a deposit lands on\n * whichever instance settles it.\n */\n\n// ioredis is an optional peer dep — typed structurally to avoid pulling its types into\n// the build for merchants that run in-process without Redis. The structural type covers\n// only the methods we call (set with EX/get/on); merchants using Redis install ioredis\n// themselves.\ninterface RedisLike {\n set: (key: string, value: string, mode: 'EX', ttl: number) => Promise<unknown>;\n get: (key: string) => Promise<string | null>;\n on: (event: 'error', cb: (err: Error) => void) => unknown;\n}\n\nexport interface PiCacheOptions {\n /** Redis connection URL (e.g. `rediss://…cache.amazonaws.com:6379`). When omitted,\n * the cache falls back to in-process Maps with the same API. */\n redisUrl?: string;\n /** TTL for cached entries in seconds. Default 300. */\n ttlSeconds?: number;\n /** Prefix for Redis keys. Default `'payto:'`. */\n keyPrefix?: string;\n}\n\nexport interface PiCache {\n /** Mark an on-chain address as one this merchant minted. Idempotent + TTL-bounded. */\n cacheAddress(address: string): Promise<void>;\n /** Return true when the address was minted by this merchant within TTL. */\n hasAddress(address: string): Promise<boolean>;\n /** Associate an on-chain deposit address with the Stripe PaymentIntent that minted it. */\n cachePaymentIntent(depositAddress: string, paymentIntentId: string): void;\n /** Get the Stripe PaymentIntent id for a previously-minted deposit address, or undefined. */\n getPaymentIntentId(depositAddress: string): string | undefined;\n /** Associate a PaymentIntent id with the full set of sibling deposit addresses (one per network). */\n cacheNetworkAddresses(paymentIntentId: string, addresses: Record<string, string>): void;\n /** Look up the deposit address Stripe minted on a specific network for a given PaymentIntent. */\n getNetworkDepositAddress(paymentIntentId: string, network: string): string | undefined;\n /** Stop the background TTL-eviction loop. Call from server shutdown handlers. */\n stop(): void;\n}\n\ninterface Entry<T> { value: T; expiresAt: number }\n\nexport function createPiCache(opts: PiCacheOptions = {}): PiCache {\n const ttlSeconds = opts.ttlSeconds ?? 300;\n const keyPrefix = opts.keyPrefix ?? 'payto:';\n\n let redis: RedisLike | null = null;\n const addrMemCache = new Map<string, number>();\n const piCache = new Map<string, Entry<string>>();\n const networkAddressCache = new Map<string, Entry<Record<string, string>>>();\n\n const evict = setInterval(() => {\n const now = Date.now();\n for (const [k, v] of piCache) { if (v.expiresAt < now) piCache.delete(k); }\n for (const [k, v] of networkAddressCache) { if (v.expiresAt < now) networkAddressCache.delete(k); }\n for (const [k, expires] of addrMemCache) { if (expires < now) addrMemCache.delete(k); }\n }, 60_000);\n // Don't keep the event loop alive on test shutdown / one-shot scripts.\n if (typeof evict.unref === 'function') evict.unref();\n\n async function getRedis(): Promise<RedisLike | null> {\n if (!opts.redisUrl) return null;\n if (redis) return redis;\n // Dynamic import keeps ioredis as an optional peer dep — merchants without\n // Redis don't pay the install cost.\n const mod = await import('ioredis' as string).catch(() => null) as\n | { default: new (url: string, opts: unknown) => RedisLike }\n | null;\n if (!mod) {\n console.error('[pi-cache] redisUrl set but `ioredis` is not installed. Run `npm install ioredis` or unset redisUrl.');\n return null;\n }\n redis = new mod.default(opts.redisUrl, {\n connectTimeout: 5000,\n maxRetriesPerRequest: 1,\n tls: opts.redisUrl.startsWith('rediss://') ? {} : undefined,\n });\n redis.on('error', (err: Error) => console.error('[pi-cache] Redis error:', err.message));\n return redis;\n }\n\n return {\n async cacheAddress(address) {\n const r = await getRedis();\n if (r) await r.set(`${keyPrefix}${address}`, '1', 'EX', ttlSeconds).catch(() => {});\n addrMemCache.set(address, Date.now() + ttlSeconds * 1000);\n },\n async hasAddress(address) {\n const r = await getRedis();\n if (r) {\n const val = await r.get(`${keyPrefix}${address}`).catch(() => null);\n if (val) return true;\n }\n const expiry = addrMemCache.get(address);\n return !!expiry && expiry > Date.now();\n },\n cachePaymentIntent(depositAddress, paymentIntentId) {\n piCache.set(depositAddress, { value: paymentIntentId, expiresAt: Date.now() + ttlSeconds * 1000 });\n },\n getPaymentIntentId(depositAddress) {\n const entry = piCache.get(depositAddress);\n if (!entry) return undefined;\n if (entry.expiresAt < Date.now()) { piCache.delete(depositAddress); return undefined; }\n return entry.value;\n },\n cacheNetworkAddresses(paymentIntentId, addresses) {\n networkAddressCache.set(paymentIntentId, { value: addresses, expiresAt: Date.now() + ttlSeconds * 1000 });\n },\n getNetworkDepositAddress(paymentIntentId, network) {\n const entry = networkAddressCache.get(paymentIntentId);\n if (!entry) return undefined;\n if (entry.expiresAt < Date.now()) { networkAddressCache.delete(paymentIntentId); return undefined; }\n return entry.value[network];\n },\n stop() {\n clearInterval(evict);\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/stripe-multichain/payment_intent.ts","../../src/stripe-multichain/simulate_deposit.ts","../../src/stripe-multichain/mppx_stripe.ts","../../src/stripe-multichain/pi-cache.ts"],"sourcesContent":["/**\n * Minimal Stripe client surface — only the methods we use. Vendors pass their actual\n * `Stripe` instance (peer dep on the `stripe` package); this interface keeps the SDK\n * decoupled from any specific Stripe version.\n */\nexport interface StripeClientLike {\n paymentIntents: {\n create(\n params: Record<string, unknown>,\n opts?: { idempotencyKey?: string },\n ): Promise<StripePaymentIntent>;\n };\n}\n\nexport interface StripePaymentIntent {\n id: string;\n next_action?: {\n crypto_display_details?: {\n deposit_addresses?: Record<string, { address?: string } | undefined>;\n };\n } | null;\n [key: string]: unknown;\n}\n\nexport interface MultichainPaymentIntentResult {\n /** Stripe PaymentIntent ID. */\n paymentIntentId: string;\n /** Map of network name → on-chain deposit address. e.g., { tempo: '0x...', base: '0x...', solana: '...' }. */\n depositAddresses: Record<string, string>;\n}\n\n/**\n * Create a Stripe PaymentIntent with `deposit_options.networks` set to multiple chains,\n * returning the PI id + deposit addresses per network. The agent sends funds to the\n * address on whichever chain they prefer (via x402 or MPP), and Stripe auto-captures\n * the PI when funds land.\n *\n * This is the canonical path for the multi-chain x402 + Tempo flow.\n * Distinct from the Stripe SPT (Shared Payment Token) flow, which is handled via\n * `createMppxStripe` + the agent's own Stripe account or `link-cli`.\n */\nexport async function createMultichainPaymentIntent({\n stripe,\n amount,\n currency = 'usd',\n networks,\n metadata,\n idempotencyKey,\n}: {\n /** A configured Stripe SDK instance. */\n stripe: StripeClientLike;\n /** Amount in cents (Stripe convention — $1.00 = 100). */\n amount: number;\n /** Currency code. Default 'usd'. */\n currency?: string;\n /** Networks to advertise to Stripe deposit_options. Default ['tempo', 'base', 'solana']. */\n networks?: string[];\n /** Metadata to attach to the PI (visible in Stripe dashboard). */\n metadata?: Record<string, string>;\n /** Idempotency key — agent retries of the same purchase won't create duplicate PIs. */\n idempotencyKey?: string;\n}): Promise<MultichainPaymentIntentResult> {\n const pi = await stripe.paymentIntents.create(\n {\n amount,\n currency,\n payment_method_types: ['crypto'],\n payment_method_data: { type: 'crypto' },\n payment_method_options: {\n crypto: {\n mode: 'deposit',\n deposit_options: { networks: networks ?? ['tempo', 'base', 'solana'] },\n },\n },\n confirm: true,\n ...(metadata ? { metadata } : {}),\n },\n idempotencyKey ? { idempotencyKey } : undefined,\n );\n\n const depositAddresses: Record<string, string> = {};\n const addrs = pi.next_action?.crypto_display_details?.deposit_addresses ?? {};\n for (const [network, info] of Object.entries(addrs)) {\n if (info?.address) depositAddresses[network] = info.address;\n }\n\n if (Object.keys(depositAddresses).length === 0) {\n throw new Error('No deposit addresses returned from Stripe PaymentIntent');\n }\n\n return { paymentIntentId: pi.id, depositAddresses };\n}\n","/**\n * Stripe's documented magic test_helpers transaction hash that resolves the\n * PaymentIntent to `succeeded` within 15 seconds. Same value across all networks —\n * Stripe normalizes the format internally. Anything else (including network-shaped\n * placeholder bytes) is rejected with \"not a valid testmode transaction hash\".\n *\n * See: https://docs.stripe.com/payments/deposit-mode-stablecoin-payments\n */\nexport const STRIPE_TEST_TX_HASH_SUCCESS =\n '0x00000000000000000000000000000000000000000000000000000testsuccess';\n\n/**\n * Stripe's documented magic test_helpers transaction hash that fails the charge\n * (PaymentIntent returns to `requires_payment_method` within 15 seconds).\n */\nexport const STRIPE_TEST_TX_HASH_FAILED =\n '0x000000000000000000000000000000000000000000000000000000testfailed';\n\nconst DEFAULT_BUYER_WALLET: Record<string, string> = {\n base: '0x0000000000000000000000000000000000000001',\n tempo: '0x0000000000000000000000000000000000000001',\n solana: '11111111111111111111111111111111',\n};\n\n/**\n * Call Stripe's `test_helpers/payment_intents/{id}/simulate_crypto_deposit` endpoint. Used\n * in testnet/dev to simulate a deposit landing on a PaymentIntent so the integration\n * end-to-end can be exercised without on-chain transfers.\n *\n * Throws on non-2xx responses (returns Stripe's error body in the message).\n */\nexport async function simulateCryptoDeposit({\n paymentIntentId,\n network,\n buyerWallet,\n tokenCurrency,\n transactionHash,\n stripeSecretKey,\n stripeVersion,\n stripeApiBase,\n extra,\n}: {\n /** Stripe PaymentIntent id to simulate a deposit on. */\n paymentIntentId: string;\n /** Network the simulated deposit lands on. */\n network: 'tempo' | 'base' | 'solana';\n /** Optional simulated buyer wallet address. Defaults to a sensible placeholder per network. */\n buyerWallet?: string;\n /** Token currency (e.g., 'usdc'). Optional — passed as a form param if set. */\n tokenCurrency?: string;\n /** Simulated transaction hash. Optional — passed as a form param if set. */\n transactionHash?: string;\n /** Stripe secret key (for the test-helpers Authorization header). Must be a `sk_test_...` key. */\n stripeSecretKey: string;\n /** Stripe API version to request via the `Stripe-Version` header. Useful for preview APIs. */\n stripeVersion?: string;\n /** Override the Stripe API base URL. Default 'https://api.stripe.com'. */\n stripeApiBase?: string;\n /** Arbitrary additional form params to merge into the request body. */\n extra?: Record<string, string>;\n}): Promise<void> {\n const url = `${stripeApiBase ?? 'https://api.stripe.com'}/v1/test_helpers/payment_intents/${paymentIntentId}/simulate_crypto_deposit`;\n const params = new URLSearchParams({\n network,\n buyer_wallet: buyerWallet ?? DEFAULT_BUYER_WALLET[network] ?? '',\n });\n if (tokenCurrency) params.set('token_currency', tokenCurrency);\n if (transactionHash) params.set('transaction_hash', transactionHash);\n for (const [k, v] of Object.entries(extra ?? {})) {\n params.set(k, v);\n }\n const headers: Record<string, string> = {\n Authorization: `Bearer ${stripeSecretKey}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n if (stripeVersion) headers['Stripe-Version'] = stripeVersion;\n const res = await fetch(url, { method: 'POST', headers, body: params.toString() });\n if (!res.ok) {\n throw new Error(`Stripe simulate_crypto_deposit failed: ${res.status} ${await res.text()}`);\n }\n}\n\n/**\n * Higher-level wrapper around {@link simulateCryptoDeposit} for the testnet/dev path.\n * Bundles the three steps every Stripe-multichain merchant repeats:\n *\n * 1. Gate on `sk_test_` key prefix — production keys reject the test_helpers endpoint\n * with 400; live deposits reach Stripe's real crypto-deposit watcher instead.\n * 2. Resolve the PaymentIntent id from the deposit address (cache lookup).\n * 3. Call `simulate_crypto_deposit` with Stripe's documented success magic hash.\n *\n * Logs `[stripe] ✓ Simulated <network> deposit for PI <id>` on success and\n * `[stripe] ✗ Failed to simulate <network> deposit for PI <id>: <err>` on failure.\n * Errors are caught + logged (never thrown) so a sim hiccup doesn't fail the order.\n *\n * Use case is exclusively dev/testnet end-to-end — production servers (sk_live_) no-op.\n */\nexport async function simulateDepositIfTestMode({\n getPaymentIntentId,\n depositAddress,\n network,\n buyerWallet,\n tokenCurrency,\n stripeSecretKey,\n stripeVersion,\n}: {\n /** Stripe PaymentIntent id resolver — given a deposit address, return the PI id (or undefined\n * if the cache TTL expired between 402 emit and settlement). Typically `cache.getPaymentIntentId`. */\n getPaymentIntentId: (depositAddress: string) => string | undefined;\n /** The deposit address that was paid to (recipient). */\n depositAddress: string;\n /** Network the simulated deposit lands on. */\n network: 'tempo' | 'base' | 'solana';\n /** Optional simulated buyer wallet (defaults per network in `simulateCryptoDeposit`). */\n buyerWallet?: string;\n /** Token currency to pass through to Stripe (typically `'usdc'`). */\n tokenCurrency?: string;\n /** Stripe secret key. The wrapper checks this starts with `sk_test_` and skips otherwise. */\n stripeSecretKey: string;\n /** Stripe API version (e.g. `'2026-03-04.preview'` for the deposit-mode preview). */\n stripeVersion?: string;\n}): Promise<void> {\n if (!stripeSecretKey.startsWith('sk_test_')) return;\n const piId = getPaymentIntentId(depositAddress);\n if (!piId) {\n console.warn(\n `[stripe] Skipping deposit simulation — no PI cached for deposit address ${depositAddress.slice(0, 10)}… (network=${network}). The PI cache TTL may have expired between 402 emission and settlement.`,\n );\n return;\n }\n try {\n await simulateCryptoDeposit({\n paymentIntentId: piId,\n network,\n ...(buyerWallet !== undefined && { buyerWallet }),\n tokenCurrency: tokenCurrency ?? 'usdc',\n transactionHash: STRIPE_TEST_TX_HASH_SUCCESS,\n stripeSecretKey,\n ...(stripeVersion !== undefined && { stripeVersion }),\n });\n console.warn(`[stripe] ✓ Simulated ${network} deposit for PI ${piId}`);\n } catch (err) {\n console.error(\n `[stripe] ✗ Failed to simulate ${network} deposit for PI ${piId}:`,\n err instanceof Error ? err.message : err,\n );\n }\n}\n","/**\n * Wraps the `mppStripe.charge(...)` boilerplate from `mppx/server`. Returns the value\n * vendors pass into `Mppx.create({ methods: [...] })`. mppx is an OPTIONAL peer dependency —\n * vendors who don't use Stripe SPT don't need to install it.\n *\n * Example:\n *\n * import { Mppx, tempo } from 'mppx/server';\n * import { createMppxStripe } from '@agent-score/commerce/stripe-multichain';\n *\n * const stripeMethod = await createMppxStripe({\n * profileId: process.env.STRIPE_PROFILE_ID!,\n * secretKey: process.env.STRIPE_SECRET_KEY!,\n * });\n *\n * const mppx = Mppx.create({\n * methods: [tempo.charge({...}), stripeMethod],\n * secretKey: process.env.MPP_SECRET_KEY!,\n * });\n *\n * Throws if mppx is not installed.\n */\nexport async function createMppxStripe({\n profileId,\n secretKey,\n paymentMethodTypes,\n}: {\n /** Stripe profile_id / network_id (the value advertised in your `stripe/charge` accepted_methods entry). */\n profileId: string;\n /** Stripe secret key — mppx uses it to validate inbound SharedPaymentTokens. */\n secretKey: string;\n /** Payment method types this stripe rail accepts. Default ['card', 'link']. */\n paymentMethodTypes?: string[];\n}): Promise<unknown> {\n const moduleName = 'mppx/server';\n const mppx = (await import(moduleName).catch(() => null)) as {\n stripe?: {\n charge: (config: {\n networkId: string;\n paymentMethodTypes?: string[];\n secretKey: string;\n }) => unknown;\n };\n } | null;\n /* v8 ignore start -- peer-dep-absence guard; mppx is installed in the test env so this branch can't be exercised without mocking the dynamic import */\n if (!mppx?.stripe?.charge) {\n throw new Error(\n 'mppx not installed — install with `npm install mppx` to use createMppxStripe.',\n );\n }\n /* v8 ignore stop */\n return mppx.stripe.charge({\n networkId: profileId,\n paymentMethodTypes: paymentMethodTypes ?? ['card', 'link'],\n secretKey,\n });\n}\n","/**\n * Stripe PaymentIntent + deposit-address cache.\n *\n * Stripe-multichain merchants need three lookups during a request lifecycle:\n *\n * 1. **Is this on-chain `pay_to` address one we minted?** — when an MPP credential\n * arrives with a `recipient`, verify it matches a recently-minted Stripe deposit\n * address. Validates the credential's deposit address against the addresses the\n * merchant has actually minted.\n *\n * 2. **Which PaymentIntent owns this deposit address?** — when settling, the\n * `simulate_crypto_deposit` test_helpers call needs the PaymentIntent id for the\n * deposit address that was paid to.\n *\n * 3. **Which sibling deposit addresses belong to the same PaymentIntent?** — when\n * enriching a 402 with x402 entries, the merchant needs the Base + Solana addresses\n * Stripe minted alongside the original Tempo address (one PI carries up to three).\n *\n * All three are TTL-bounded (default 300s — long enough for an agent to retry, short\n * enough to bound memory). Backed by Redis when `redisUrl` is set, falls back to\n * in-process Map otherwise. Single-instance servers can use the in-memory cache;\n * multi-instance deployments need a shared cache (Redis) so a deposit lands on\n * whichever instance settles it.\n */\n\n// ioredis is an optional peer dep — typed structurally to avoid pulling its types into\n// the build for merchants that run in-process without Redis. The structural type covers\n// only the methods we call (set with EX/get/on); merchants using Redis install ioredis\n// themselves.\ninterface RedisLike {\n set: (key: string, value: string, mode: 'EX', ttl: number) => Promise<unknown>;\n get: (key: string) => Promise<string | null>;\n on: (event: 'error', cb: (err: Error) => void) => unknown;\n}\n\n\nexport interface PiCache {\n /** Mark an on-chain address as one this merchant minted. Idempotent + TTL-bounded. */\n cacheAddress(address: string): Promise<void>;\n /** Return true when the address was minted by this merchant within TTL. */\n hasAddress(address: string): Promise<boolean>;\n /** Associate an on-chain deposit address with the Stripe PaymentIntent that minted it. */\n cachePaymentIntent(depositAddress: string, paymentIntentId: string): void;\n /** Get the Stripe PaymentIntent id for a previously-minted deposit address, or undefined. */\n getPaymentIntentId(depositAddress: string): string | undefined;\n /** Associate a PaymentIntent id with the full set of sibling deposit addresses (one per network). */\n cacheNetworkAddresses(paymentIntentId: string, addresses: Record<string, string>): void;\n /** Look up the deposit address Stripe minted on a specific network for a given PaymentIntent. */\n getNetworkDepositAddress(paymentIntentId: string, network: string): string | undefined;\n /** Stop the background TTL-eviction loop. Call from server shutdown handlers. */\n stop(): void;\n}\n\ninterface Entry<T> { value: T; expiresAt: number }\n\nexport function createPiCache({\n redisUrl,\n ttlSeconds = 300,\n keyPrefix = 'payto:',\n}: {\n /** Redis connection URL (e.g. `rediss://…cache.amazonaws.com:6379`). When omitted,\n * the cache falls back to in-process Maps with the same API. */\n redisUrl?: string;\n /** TTL for cached entries in seconds. Default 300. */\n ttlSeconds?: number;\n /** Prefix for Redis keys. Default `'payto:'`. */\n keyPrefix?: string;\n} = {}): PiCache {\n\n let redis: RedisLike | null = null;\n const addrMemCache = new Map<string, number>();\n const piCache = new Map<string, Entry<string>>();\n const networkAddressCache = new Map<string, Entry<Record<string, string>>>();\n\n const evict = setInterval(() => {\n const now = Date.now();\n for (const [k, v] of piCache) { if (v.expiresAt < now) piCache.delete(k); }\n for (const [k, v] of networkAddressCache) { if (v.expiresAt < now) networkAddressCache.delete(k); }\n for (const [k, expires] of addrMemCache) { if (expires < now) addrMemCache.delete(k); }\n }, 60_000);\n // Don't keep the event loop alive on test shutdown / one-shot scripts.\n if (typeof evict.unref === 'function') evict.unref();\n\n async function getRedis(): Promise<RedisLike | null> {\n if (!redisUrl) return null;\n if (redis) return redis;\n // Dynamic import keeps ioredis as an optional peer dep — merchants without\n // Redis don't pay the install cost.\n const mod = await import('ioredis' as string).catch(() => null) as\n | { default: new (url: string, opts: unknown) => RedisLike }\n | null;\n if (!mod) {\n console.error('[pi-cache] redisUrl set but `ioredis` is not installed. Run `npm install ioredis` or unset redisUrl.');\n return null;\n }\n redis = new mod.default(redisUrl, {\n connectTimeout: 5000,\n maxRetriesPerRequest: 1,\n tls: redisUrl.startsWith('rediss://') ? {} : undefined,\n });\n redis.on('error', (err: Error) => console.error('[pi-cache] Redis error:', err.message));\n return redis;\n }\n\n return {\n async cacheAddress(address) {\n const r = await getRedis();\n if (r) await r.set(`${keyPrefix}${address}`, '1', 'EX', ttlSeconds).catch(() => {});\n addrMemCache.set(address, Date.now() + ttlSeconds * 1000);\n },\n async hasAddress(address) {\n const r = await getRedis();\n if (r) {\n const val = await r.get(`${keyPrefix}${address}`).catch(() => null);\n if (val) return true;\n }\n const expiry = addrMemCache.get(address);\n return !!expiry && expiry > Date.now();\n },\n cachePaymentIntent(depositAddress, paymentIntentId) {\n piCache.set(depositAddress, { value: paymentIntentId, expiresAt: Date.now() + ttlSeconds * 1000 });\n },\n getPaymentIntentId(depositAddress) {\n const entry = piCache.get(depositAddress);\n if (!entry) return undefined;\n if (entry.expiresAt < Date.now()) { piCache.delete(depositAddress); return undefined; }\n return entry.value;\n },\n cacheNetworkAddresses(paymentIntentId, addresses) {\n networkAddressCache.set(paymentIntentId, { value: addresses, expiresAt: Date.now() + ttlSeconds * 1000 });\n },\n getNetworkDepositAddress(paymentIntentId, network) {\n const entry = networkAddressCache.get(paymentIntentId);\n if (!entry) return undefined;\n if (entry.expiresAt < Date.now()) { networkAddressCache.delete(paymentIntentId); return undefined; }\n return entry.value[network];\n },\n stop() {\n clearInterval(evict);\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,14 +3,6 @@
|
|
|
3
3
|
* Per RFC 7235, multiple challenges are comma-separated.
|
|
4
4
|
*/
|
|
5
5
|
declare function wwwAuthenticateHeader(directives: string[]): string;
|
|
6
|
-
interface PaymentRequiredHeaderInput {
|
|
7
|
-
x402Version: 1 | 2;
|
|
8
|
-
accepts: unknown[];
|
|
9
|
-
resource?: {
|
|
10
|
-
url: string;
|
|
11
|
-
mimeType?: string;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
6
|
/**
|
|
15
7
|
* Add the v1↔v2 amount-field alias to each accepts entry. Idempotent. Used by both
|
|
16
8
|
* `paymentRequiredHeader` (header emit) and `build402Body` (body emit) so every
|
|
@@ -32,6 +24,13 @@ declare function aliasAmountFields(accepts: unknown[]): unknown[];
|
|
|
32
24
|
* output) makes the match silently fail at settle time. Keep `accepts` shape
|
|
33
25
|
* identical to whatever `buildPaymentRequirements` produces server-side.
|
|
34
26
|
*/
|
|
35
|
-
declare function paymentRequiredHeader(
|
|
27
|
+
declare function paymentRequiredHeader({ x402Version, accepts, resource, }: {
|
|
28
|
+
x402Version: 1 | 2;
|
|
29
|
+
accepts: unknown[];
|
|
30
|
+
resource?: {
|
|
31
|
+
url: string;
|
|
32
|
+
mimeType?: string;
|
|
33
|
+
};
|
|
34
|
+
}): string;
|
|
36
35
|
|
|
37
|
-
export {
|
|
36
|
+
export { aliasAmountFields as a, paymentRequiredHeader as p, wwwAuthenticateHeader as w };
|
|
@@ -3,14 +3,6 @@
|
|
|
3
3
|
* Per RFC 7235, multiple challenges are comma-separated.
|
|
4
4
|
*/
|
|
5
5
|
declare function wwwAuthenticateHeader(directives: string[]): string;
|
|
6
|
-
interface PaymentRequiredHeaderInput {
|
|
7
|
-
x402Version: 1 | 2;
|
|
8
|
-
accepts: unknown[];
|
|
9
|
-
resource?: {
|
|
10
|
-
url: string;
|
|
11
|
-
mimeType?: string;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
6
|
/**
|
|
15
7
|
* Add the v1↔v2 amount-field alias to each accepts entry. Idempotent. Used by both
|
|
16
8
|
* `paymentRequiredHeader` (header emit) and `build402Body` (body emit) so every
|
|
@@ -32,6 +24,13 @@ declare function aliasAmountFields(accepts: unknown[]): unknown[];
|
|
|
32
24
|
* output) makes the match silently fail at settle time. Keep `accepts` shape
|
|
33
25
|
* identical to whatever `buildPaymentRequirements` produces server-side.
|
|
34
26
|
*/
|
|
35
|
-
declare function paymentRequiredHeader(
|
|
27
|
+
declare function paymentRequiredHeader({ x402Version, accepts, resource, }: {
|
|
28
|
+
x402Version: 1 | 2;
|
|
29
|
+
accepts: unknown[];
|
|
30
|
+
resource?: {
|
|
31
|
+
url: string;
|
|
32
|
+
mimeType?: string;
|
|
33
|
+
};
|
|
34
|
+
}): string;
|
|
36
35
|
|
|
37
|
-
export {
|
|
36
|
+
export { aliasAmountFields as a, paymentRequiredHeader as p, wwwAuthenticateHeader as w };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
type X402SymbolicRail = 'x402-base-mainnet' | 'x402-base-sepolia' | 'x402-base-mainnet-upto' | 'x402-base-sepolia-upto';
|
|
2
|
+
type X402FacilitatorChoice = 'coinbase' | 'http' | unknown;
|
|
3
|
+
interface CreateX402ServerOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Facilitator selection:
|
|
6
|
+
* - 'coinbase' → Coinbase CDP facilitator (requires `@coinbase/x402` installed)
|
|
7
|
+
* - 'http' → HTTP-only public testnet facilitator
|
|
8
|
+
* - any object → custom facilitator instance, used directly
|
|
9
|
+
* - omitted → defaults to 'http'
|
|
10
|
+
*/
|
|
11
|
+
facilitator?: X402FacilitatorChoice;
|
|
12
|
+
/**
|
|
13
|
+
* Symbolic rail names to register schemes for. Each gets v1+v2 dual-register applied.
|
|
14
|
+
* Requires `@x402/evm` peer dep installed.
|
|
15
|
+
*/
|
|
16
|
+
rails?: X402SymbolicRail[];
|
|
17
|
+
/** Advanced: register custom {network, scheme} pairs (in addition to or instead of `rails`). */
|
|
18
|
+
schemes?: {
|
|
19
|
+
network: string;
|
|
20
|
+
scheme: unknown;
|
|
21
|
+
}[];
|
|
22
|
+
/** Register the Bazaar discovery extension. Requires `@x402/extensions` installed. */
|
|
23
|
+
bazaar?: boolean;
|
|
24
|
+
/** Initialize the server immediately (calls facilitator). Default true. */
|
|
25
|
+
initialize?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Loose type for the x402 resource server. We name the methods commerce calls during
|
|
29
|
+
* setup; everything else (settlePayment, buildPaymentRequirements, processPaymentRequest,
|
|
30
|
+
* enrichExtensions, etc.) is callable via the index signature so vendor code can use the
|
|
31
|
+
* full @x402/core surface without us having to mirror every method signature.
|
|
32
|
+
*/
|
|
33
|
+
interface X402Server {
|
|
34
|
+
register(network: string, scheme: unknown): void;
|
|
35
|
+
registerV1?(network: string, scheme: unknown): void;
|
|
36
|
+
registerExtension(ext: unknown): void;
|
|
37
|
+
initialize(): Promise<void>;
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One-call x402 server setup. Resolves facilitator, constructs the server, registers
|
|
42
|
+
* schemes per network with v1+v2 dual-register, optionally adds the Bazaar extension,
|
|
43
|
+
* and initializes — replaces ~15 lines of boilerplate with a single config call.
|
|
44
|
+
*
|
|
45
|
+
* x402 packages are peer dependencies — vendors install only the schemes they use.
|
|
46
|
+
* Throws a guiding error if a required peer is missing.
|
|
47
|
+
*
|
|
48
|
+
* const server = await createX402Server({
|
|
49
|
+
* facilitator: 'coinbase',
|
|
50
|
+
* rails: ['x402-base-mainnet'],
|
|
51
|
+
* bazaar: true,
|
|
52
|
+
* });
|
|
53
|
+
*/
|
|
54
|
+
declare function createX402Server(opts?: CreateX402ServerOptions): Promise<X402Server>;
|
|
55
|
+
interface BuildX402AcceptsForOptions {
|
|
56
|
+
network: string;
|
|
57
|
+
price: string;
|
|
58
|
+
payTo: string;
|
|
59
|
+
scheme?: string;
|
|
60
|
+
maxTimeoutSeconds?: number;
|
|
61
|
+
extensions?: string[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build x402 `accepts[]` entries for a 402 challenge body.
|
|
65
|
+
*
|
|
66
|
+
* Wraps `server.buildPaymentRequirements(...)` so merchants don't have to:
|
|
67
|
+
*
|
|
68
|
+
* 1. Construct the resource-config object themselves
|
|
69
|
+
* 2. Remember to serialize each Pydantic-equivalent requirement back to a
|
|
70
|
+
* plain object before stitching it into the 402 body
|
|
71
|
+
* 3. Hardcode `extra` (which differs by the actual on-chain contract — base
|
|
72
|
+
* mainnet USDC has `name: "USD Coin"`, base sepolia USDC has `name: "USDC"`;
|
|
73
|
+
* EIP-712 domain hashes differ, so getting this wrong silently breaks every
|
|
74
|
+
* signature verify at the facilitator)
|
|
75
|
+
*
|
|
76
|
+
* Returns a list of plain objects in the shape that x402 expects on the wire —
|
|
77
|
+
* drop them straight into the `accepts` field of the 402 challenge body.
|
|
78
|
+
*/
|
|
79
|
+
declare function buildX402AcceptsFor402(server: X402Server, opts: BuildX402AcceptsForOptions): Promise<unknown[]>;
|
|
80
|
+
|
|
81
|
+
export { type BuildX402AcceptsForOptions as B, type CreateX402ServerOptions as C, type X402Server as X, type X402FacilitatorChoice as a, type X402SymbolicRail as b, buildX402AcceptsFor402 as c, createX402Server as d };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
type X402SymbolicRail = 'x402-base-mainnet' | 'x402-base-sepolia' | 'x402-base-mainnet-upto' | 'x402-base-sepolia-upto';
|
|
2
|
+
type X402FacilitatorChoice = 'coinbase' | 'http' | unknown;
|
|
3
|
+
interface CreateX402ServerOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Facilitator selection:
|
|
6
|
+
* - 'coinbase' → Coinbase CDP facilitator (requires `@coinbase/x402` installed)
|
|
7
|
+
* - 'http' → HTTP-only public testnet facilitator
|
|
8
|
+
* - any object → custom facilitator instance, used directly
|
|
9
|
+
* - omitted → defaults to 'http'
|
|
10
|
+
*/
|
|
11
|
+
facilitator?: X402FacilitatorChoice;
|
|
12
|
+
/**
|
|
13
|
+
* Symbolic rail names to register schemes for. Each gets v1+v2 dual-register applied.
|
|
14
|
+
* Requires `@x402/evm` peer dep installed.
|
|
15
|
+
*/
|
|
16
|
+
rails?: X402SymbolicRail[];
|
|
17
|
+
/** Advanced: register custom {network, scheme} pairs (in addition to or instead of `rails`). */
|
|
18
|
+
schemes?: {
|
|
19
|
+
network: string;
|
|
20
|
+
scheme: unknown;
|
|
21
|
+
}[];
|
|
22
|
+
/** Register the Bazaar discovery extension. Requires `@x402/extensions` installed. */
|
|
23
|
+
bazaar?: boolean;
|
|
24
|
+
/** Initialize the server immediately (calls facilitator). Default true. */
|
|
25
|
+
initialize?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Loose type for the x402 resource server. We name the methods commerce calls during
|
|
29
|
+
* setup; everything else (settlePayment, buildPaymentRequirements, processPaymentRequest,
|
|
30
|
+
* enrichExtensions, etc.) is callable via the index signature so vendor code can use the
|
|
31
|
+
* full @x402/core surface without us having to mirror every method signature.
|
|
32
|
+
*/
|
|
33
|
+
interface X402Server {
|
|
34
|
+
register(network: string, scheme: unknown): void;
|
|
35
|
+
registerV1?(network: string, scheme: unknown): void;
|
|
36
|
+
registerExtension(ext: unknown): void;
|
|
37
|
+
initialize(): Promise<void>;
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One-call x402 server setup. Resolves facilitator, constructs the server, registers
|
|
42
|
+
* schemes per network with v1+v2 dual-register, optionally adds the Bazaar extension,
|
|
43
|
+
* and initializes — replaces ~15 lines of boilerplate with a single config call.
|
|
44
|
+
*
|
|
45
|
+
* x402 packages are peer dependencies — vendors install only the schemes they use.
|
|
46
|
+
* Throws a guiding error if a required peer is missing.
|
|
47
|
+
*
|
|
48
|
+
* const server = await createX402Server({
|
|
49
|
+
* facilitator: 'coinbase',
|
|
50
|
+
* rails: ['x402-base-mainnet'],
|
|
51
|
+
* bazaar: true,
|
|
52
|
+
* });
|
|
53
|
+
*/
|
|
54
|
+
declare function createX402Server(opts?: CreateX402ServerOptions): Promise<X402Server>;
|
|
55
|
+
interface BuildX402AcceptsForOptions {
|
|
56
|
+
network: string;
|
|
57
|
+
price: string;
|
|
58
|
+
payTo: string;
|
|
59
|
+
scheme?: string;
|
|
60
|
+
maxTimeoutSeconds?: number;
|
|
61
|
+
extensions?: string[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build x402 `accepts[]` entries for a 402 challenge body.
|
|
65
|
+
*
|
|
66
|
+
* Wraps `server.buildPaymentRequirements(...)` so merchants don't have to:
|
|
67
|
+
*
|
|
68
|
+
* 1. Construct the resource-config object themselves
|
|
69
|
+
* 2. Remember to serialize each Pydantic-equivalent requirement back to a
|
|
70
|
+
* plain object before stitching it into the 402 body
|
|
71
|
+
* 3. Hardcode `extra` (which differs by the actual on-chain contract — base
|
|
72
|
+
* mainnet USDC has `name: "USD Coin"`, base sepolia USDC has `name: "USDC"`;
|
|
73
|
+
* EIP-712 domain hashes differ, so getting this wrong silently breaks every
|
|
74
|
+
* signature verify at the facilitator)
|
|
75
|
+
*
|
|
76
|
+
* Returns a list of plain objects in the shape that x402 expects on the wire —
|
|
77
|
+
* drop them straight into the `accepts` field of the 402 challenge body.
|
|
78
|
+
*/
|
|
79
|
+
declare function buildX402AcceptsFor402(server: X402Server, opts: BuildX402AcceptsForOptions): Promise<unknown[]>;
|
|
80
|
+
|
|
81
|
+
export { type BuildX402AcceptsForOptions as B, type CreateX402ServerOptions as C, type X402Server as X, type X402FacilitatorChoice as a, type X402SymbolicRail as b, buildX402AcceptsFor402 as c, createX402Server as d };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-score/commerce",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Agent commerce SDK — identity middleware (Hono, Express, Fastify, Next.js, Web Fetch) + payment helpers + 402 builders + discovery + Stripe multichain. The full merchant-side toolkit for AgentScore-powered agent commerce.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"build": "tsup",
|
|
80
80
|
"lint": "eslint .",
|
|
81
81
|
"lint:fix": "eslint . --fix",
|
|
82
|
+
"knip": "knip",
|
|
82
83
|
"test": "vitest run",
|
|
83
84
|
"typecheck": "tsc --noEmit",
|
|
84
85
|
"prepare": "lefthook install",
|
|
@@ -121,7 +122,7 @@
|
|
|
121
122
|
"bun": ">=1.3.0"
|
|
122
123
|
},
|
|
123
124
|
"dependencies": {
|
|
124
|
-
"@agent-score/sdk": "^2.3.
|
|
125
|
+
"@agent-score/sdk": "^2.3.2"
|
|
125
126
|
},
|
|
126
127
|
"overrides": {
|
|
127
128
|
"axios": "^1.15.0",
|
|
@@ -165,11 +166,11 @@
|
|
|
165
166
|
"@solana/kit": "^6.9.0",
|
|
166
167
|
"@solana/mpp": "^0.5.2",
|
|
167
168
|
"@types/express": "^5.0.6",
|
|
168
|
-
"@types/node": "^25.
|
|
169
|
+
"@types/node": "^25.8.0",
|
|
169
170
|
"@vitest/coverage-v8": "^4.1.6",
|
|
170
|
-
"@x402/core": "^2.
|
|
171
|
-
"@x402/evm": "^2.
|
|
172
|
-
"@x402/extensions": "^2.
|
|
171
|
+
"@x402/core": "^2.12.0",
|
|
172
|
+
"@x402/evm": "^2.12.0",
|
|
173
|
+
"@x402/extensions": "^2.12.0",
|
|
173
174
|
"dotenv": "^17.4.2",
|
|
174
175
|
"eslint": "^9.39.4",
|
|
175
176
|
"eslint-plugin-import": "^2.32.0",
|
|
@@ -178,8 +179,9 @@
|
|
|
178
179
|
"fastify": "^5.8.5",
|
|
179
180
|
"hono": "^4.12.18",
|
|
180
181
|
"jose": "^6.2.3",
|
|
182
|
+
"knip": "^6.13.1",
|
|
181
183
|
"lefthook": "^2.1.6",
|
|
182
|
-
"mppx": "^0.6.
|
|
184
|
+
"mppx": "^0.6.20",
|
|
183
185
|
"tsup": "^8.5.1",
|
|
184
186
|
"typescript": "^6.0.3",
|
|
185
187
|
"typescript-eslint": "^8.59.3",
|