From e63626ddbc65d8a16c8fbb862d9906ee15252f44 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 10:45:42 -0700 Subject: [PATCH 01/19] feat(x402-settle): wrap verify-stage facilitator throws as 'facilitator_error' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Today processX402Settle only wraps settlePayment in try/catch. The three earlier facilitator-touching calls — buildPaymentRequirements, enrichExtensions, processPaymentRequest — bubble exceptions unchecked, so a facilitator that throws on a misconfigured network (e.g. Coinbase CDP rejecting Solana devnet) lands in the merchant's outermost catch as an opaque 500. Add a new 'facilitator_error' tagged variant to ProcessX402SettleResult with a 'step' discriminator (build_requirements | enrich_extensions | process_payment_request) and the original error for diagnostics. Wrap all three calls. Merchants get structured 503-with-recovery instead of 500. Stays facilitator-agnostic — no vendor-name validation, no auto-select. Merchants pick whichever facilitator they want (Coinbase CDP, public x402.org, Nevermined, self-hosted); the SDK now just surfaces facilitator failures cleanly regardless of which one was chosen. Test plan: - 8 new tests in tests/payment/x402_settle.test.ts covering all success + sad paths - Full suite: 638 passed Bumps to 1.3.0 (minor: additive variant on the result union, no breaking change to the success path or existing failure phases). Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 2 +- src/payment/x402_settle.ts | 50 +++++++++++--- tests/payment/x402_settle.test.ts | 111 ++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 tests/payment/x402_settle.test.ts diff --git a/package.json b/package.json index 02340e6..2a6dc2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agent-score/commerce", - "version": "1.2.1", + "version": "1.3.0", "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.", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/src/payment/x402_settle.ts b/src/payment/x402_settle.ts index f471739..2bf7537 100644 --- a/src/payment/x402_settle.ts +++ b/src/payment/x402_settle.ts @@ -50,7 +50,20 @@ export type ProcessX402SettleResult = } | { success: false; phase: 'no_requirements'; reason: string } | { success: false; phase: 'verify_failed'; verifyResult: unknown } - | { success: false; phase: 'settle_failed'; error: unknown; matchedRequirement: unknown }; + | { success: false; phase: 'settle_failed'; error: unknown; matchedRequirement: unknown } + | { + success: false; + /** Facilitator threw an unexpected error during one of the verify-stage calls + * (build requirements, extension enrich, or processPaymentRequest). Most common + * cause: the facilitator client rejects the configured network — for example, + * Coinbase's CDP facilitator throws on Solana devnet because it only supports + * mainnet networks. The merchant should emit a 503 with diagnostic info so the + * agent can pick a different rail or the operator can fix the deploy config. */ + phase: 'facilitator_error'; + /** Which verify-stage step threw. */ + step: 'build_requirements' | 'enrich_extensions' | 'process_payment_request'; + error: unknown; + }; export async function processX402Settle(input: ProcessX402SettleInput): Promise { const server = input.x402Server as unknown as { @@ -65,7 +78,12 @@ export async function processX402Settle(input: ProcessX402SettleInput): Promise< settlePayment: (payload: unknown, requirement: unknown) => Promise; }; - const builtRequirements = await server.buildPaymentRequirements(input.resourceConfig); + let builtRequirements: unknown[]; + try { + builtRequirements = await server.buildPaymentRequirements(input.resourceConfig); + } catch (err) { + return { success: false, phase: 'facilitator_error', step: 'build_requirements', error: err }; + } const matchedRequirement = builtRequirements[0]; if (!matchedRequirement) { return { success: false, phase: 'no_requirements', reason: 'x402Server.buildPaymentRequirements returned empty' }; @@ -76,16 +94,26 @@ export async function processX402Settle(input: ProcessX402SettleInput): Promise< return { method: 'POST', adapter: { getPath: () => path }, routePattern: path }; })(); - const enrichedExt = input.extension !== undefined - ? server.enrichExtensions(input.extension, transportContext) - : undefined; + let enrichedExt: unknown; + try { + enrichedExt = input.extension !== undefined + ? server.enrichExtensions(input.extension, transportContext) + : undefined; + } catch (err) { + return { success: false, phase: 'facilitator_error', step: 'enrich_extensions', error: err }; + } - const verifyResult = await server.processPaymentRequest( - input.payload, - input.resourceConfig, - input.resourceMeta, - enrichedExt, - ); + let verifyResult: { success: boolean; [key: string]: unknown }; + try { + verifyResult = await server.processPaymentRequest( + input.payload, + input.resourceConfig, + input.resourceMeta, + enrichedExt, + ); + } catch (err) { + return { success: false, phase: 'facilitator_error', step: 'process_payment_request', error: err }; + } if (!verifyResult.success) { return { success: false, phase: 'verify_failed', verifyResult }; diff --git a/tests/payment/x402_settle.test.ts b/tests/payment/x402_settle.test.ts new file mode 100644 index 0000000..3827ada --- /dev/null +++ b/tests/payment/x402_settle.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, it, vi } from 'vitest'; +import { processX402Settle } from '../../src/payment/x402_settle'; +import type { X402Server } from '../../src/payment/x402_server'; + +const baseInput = { + payload: { accepted: { network: 'eip155:8453', payTo: '0xfeed' } }, + resourceConfig: { scheme: 'exact', network: 'eip155:8453', price: '$0.10', payTo: '0xfeed' }, + resourceMeta: { url: 'https://example.com/x', description: 'demo', mimeType: 'application/json' }, +}; + +function makeServer(overrides: Partial<{ + buildPaymentRequirements: ReturnType; + enrichExtensions: ReturnType; + processPaymentRequest: ReturnType; + settlePayment: ReturnType; +}>): X402Server { + return { + buildPaymentRequirements: overrides.buildPaymentRequirements ?? vi.fn().mockResolvedValue([{ matched: true }]), + enrichExtensions: overrides.enrichExtensions ?? vi.fn().mockReturnValue(undefined), + processPaymentRequest: overrides.processPaymentRequest ?? vi.fn().mockResolvedValue({ success: true }), + settlePayment: overrides.settlePayment ?? vi.fn().mockResolvedValue({ tx: '0xabc' }), + } as unknown as X402Server; +} + +describe('processX402Settle', () => { + it('returns success on the happy path with paymentResponseHeader as base64 of the settle result', async () => { + const server = makeServer({}); + const result = await processX402Settle({ x402Server: server, ...baseInput }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.matchedRequirement).toEqual({ matched: true }); + expect(result.settleResult).toEqual({ tx: '0xabc' }); + expect(result.paymentResponseHeader).toBe(Buffer.from(JSON.stringify({ tx: '0xabc' })).toString('base64')); + } + }); + + it('returns no_requirements when buildPaymentRequirements yields an empty array', async () => { + const server = makeServer({ buildPaymentRequirements: vi.fn().mockResolvedValue([]) }); + const result = await processX402Settle({ x402Server: server, ...baseInput }); + expect(result).toEqual({ + success: false, + phase: 'no_requirements', + reason: 'x402Server.buildPaymentRequirements returned empty', + }); + }); + + it('returns verify_failed when processPaymentRequest yields { success: false }', async () => { + const server = makeServer({ processPaymentRequest: vi.fn().mockResolvedValue({ success: false, reason: 'invalid_credential' }) }); + const result = await processX402Settle({ x402Server: server, ...baseInput }); + expect(result.success).toBe(false); + if (!result.success && result.phase === 'verify_failed') { + expect((result.verifyResult as { reason: string }).reason).toBe('invalid_credential'); + } + }); + + it('returns settle_failed when settlePayment throws', async () => { + const server = makeServer({ settlePayment: vi.fn().mockRejectedValue(new Error('chain rejected tx')) }); + const result = await processX402Settle({ x402Server: server, ...baseInput }); + expect(result.success).toBe(false); + if (!result.success && result.phase === 'settle_failed') { + expect((result.error as Error).message).toBe('chain rejected tx'); + } + }); + + describe('facilitator_error wrap', () => { + it('wraps buildPaymentRequirements throws as facilitator_error step=build_requirements', async () => { + const server = makeServer({ + buildPaymentRequirements: vi.fn().mockRejectedValue(new Error('facilitator: network not supported')), + }); + const result = await processX402Settle({ x402Server: server, ...baseInput }); + expect(result.success).toBe(false); + if (!result.success && result.phase === 'facilitator_error') { + expect(result.step).toBe('build_requirements'); + expect((result.error as Error).message).toBe('facilitator: network not supported'); + } + }); + + it('wraps enrichExtensions throws as facilitator_error step=enrich_extensions', async () => { + const server = makeServer({ + enrichExtensions: vi.fn().mockImplementation(() => { throw new Error('extension barfed'); }), + }); + const result = await processX402Settle({ x402Server: server, ...baseInput, extension: { kind: 'bazaar' } }); + expect(result.success).toBe(false); + if (!result.success && result.phase === 'facilitator_error') { + expect(result.step).toBe('enrich_extensions'); + expect((result.error as Error).message).toBe('extension barfed'); + } + }); + + it('wraps processPaymentRequest throws as facilitator_error step=process_payment_request', async () => { + const server = makeServer({ + processPaymentRequest: vi.fn().mockRejectedValue(new Error('CDP facilitator: solana:devnet not supported')), + }); + const result = await processX402Settle({ x402Server: server, ...baseInput }); + expect(result.success).toBe(false); + if (!result.success && result.phase === 'facilitator_error') { + expect(result.step).toBe('process_payment_request'); + expect((result.error as Error).message).toBe('CDP facilitator: solana:devnet not supported'); + } + }); + + it('does NOT swallow settle errors as facilitator_error — settle_failed stays its own phase', async () => { + const server = makeServer({ settlePayment: vi.fn().mockRejectedValue(new Error('on-chain rejection')) }); + const result = await processX402Settle({ x402Server: server, ...baseInput }); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.phase).toBe('settle_failed'); + } + }); + }); +}); From d39814d643ed692e107b86d4eade4cd9a38d7a78 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 11:01:34 -0700 Subject: [PATCH 02/19] feat(x402-settle): add classifyX402SettleResult helper Maps the discriminated union from processX402Settle to a recommended {status, code, message, nextSteps} envelope so merchants stop sniffing facilitator error message strings to decide between 400 / 503 / 500. Phase mapping: - verify_failed -> 400 payment_proof_invalid / regenerate_payment_credential - facilitator_error -> 503 payment_provider_unavailable / try_different_rail - settle_failed -> 503 payment_provider_unavailable / retry_or_swap_method (retry_after_seconds: 10) - no_requirements -> 500 payment_internal_error / contact_support Returned object is intentionally facilitator-agnostic and never carries the raw error; merchants log result server-side and return the classified envelope to the consumer. Updated JSDoc on each non-success variant of ProcessX402SettleResult to spell out the log-raw / return-controlled posture and point at the helper. Replaced the brittle 'phase' field leak in the README example with the helper. Mirrored the helper mention into the package-table docs. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 11 ++- src/payment/x402_settle.ts | 130 +++++++++++++++++++++++++++--- tests/payment/x402_settle.test.ts | 81 ++++++++++++++++++- 3 files changed, 209 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1c7386a..316eb32 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ npm install hono mppx @x402/core @x402/evm @x402/svm stripe # whatever your st | Subpath | What it provides | |---|---| | `/identity/{hono,express,fastify,nextjs,web}` | Trust gate middleware: KYC, sanctions, age, jurisdiction. `agentscoreGate(...)`, `getAgentScoreData(c)`, `captureWallet(...)`, `verifyWalletSignerMatch(...)`. Plus shared denial helpers: `denialReasonStatus`, `denialReasonToBody`, `buildSignerMismatchBody`, `buildContactSupportNextSteps`, `verificationAgentInstructions`, `isFixableDenial`, `FIXABLE_DENIAL_REASONS`. | -| `/payment` | `networks`, `USDC`, `rails` registries; `paymentDirective`, `buildPaymentDirective`, `wwwAuthenticateHeader`, `paymentRequiredHeader`, `aliasAmountFields` (v1↔v2 amount field shim — emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlementOverrideHeader`, `dispatchSettlementByNetwork`, `extractPaymentSigner` (returns `{address, network}`); `createX402Server`, `createMppxServer`; drop-in x402 helpers: `validateX402NetworkConfig` (boot-time guard), `verifyX402Request` (parse + validate inbound X-Payment), `processX402Settle` (verify-then-settle with one call). | +| `/payment` | `networks`, `USDC`, `rails` registries; `paymentDirective`, `buildPaymentDirective`, `wwwAuthenticateHeader`, `paymentRequiredHeader`, `aliasAmountFields` (v1↔v2 amount field shim — emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlementOverrideHeader`, `dispatchSettlementByNetwork`, `extractPaymentSigner` (returns `{address, network}`); `createX402Server`, `createMppxServer`; drop-in x402 helpers: `validateX402NetworkConfig` (boot-time guard), `verifyX402Request` (parse + validate inbound X-Payment), `processX402Settle` (verify-then-settle with one call), `classifyX402SettleResult` (maps the tagged settle result to a recommended HTTP status / code / nextSteps so merchants stop string-sniffing facilitator error messages). | | `/discovery` | `isDiscoveryProbeRequest`, `buildDiscoveryProbeResponse` (with optional `x402Sample` for x402-aware crawlers — `awal x402 details` etc.), `sampleX402AcceptForNetwork` (USDC sample-accept builder for known CAIP-2 networks), `buildWellKnownMpp`, `buildLlmsTxt` + `llmsTxtIdentitySection` + `llmsTxtPaymentSection` (compact + verbose modes), `buildSkillMd` (Claude-Skill-compatible `/skill.md` agent-discovery manifest — strictly agent-facing data only, no internal posture), `agentscoreOpenApiSnippets`, `createBazaarDiscovery`, `noindexNonDiscoveryPaths` (Hono middleware that emits `X-Robots-Tag: noindex` on every path except the agent-discovery surfaces — defaults cover `/openapi.json`, `/llms.txt`, `/skill.md`, `/.well-known/{mpp.json,agent-card.json,ucp}`, `/favicon.{png,ico}`; pure helpers `isDiscoveryPath` + `defaultDiscoveryPaths` for non-Hono frameworks). | | `/challenge` | `build402Body`, `buildAcceptedMethods`, `buildIdentityMetadata`, `buildHowToPay`, `buildAgentInstructions` (auto-emits per-rail `compatible_clients` — smoke-verified CLIs the agent should use; vendor override supported), `buildPricingBlock`, `firstEncounterAgentMemory`, `OrderReceipt`; `respond402` — drop-in 402 emit that preserves mppx's `WWW-Authenticate` and layers x402's `PAYMENT-REQUIRED`. `buildValidationError` — structured 4xx body builder (`{error: {code, message}, required_fields?, example_body?, next_steps?, ...extra}`) so vendors compose body shapes by name instead of inlining at every validation site. | | `/stripe-multichain` | `createMultichainPaymentIntent`, `getDepositAddress`, `simulateCryptoDeposit`, `createMppxStripe`; `createPiCache` (TTL'd PI / deposit-address cache, Redis-backed when `redisUrl` set, in-memory otherwise), `simulateDepositIfTestMode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. | @@ -236,6 +236,7 @@ await simulateDepositIfTestMode({ ```typescript import { + classifyX402SettleResult, processX402Settle, validateX402NetworkConfig, verifyX402Request, @@ -261,7 +262,13 @@ app.post("/purchase", async (c) => { resourceConfig: { scheme: "exact", network: verified.signedNetwork, price: `$${total}`, payTo: verified.signedPayTo, maxTimeoutSeconds: 300 }, resourceMeta: { url: c.req.url, mimeType: "application/json" }, }); - if (!settle.success) return c.json({ error: { code: "payment_proof_invalid", phase: settle.phase } }, 400); + const classified = classifyX402SettleResult(settle); + if (classified) { + // Log raw `settle` server-side; return controlled phase-based response to the agent. + console.error("[x402-settle]", { phase: settle.success ? null : settle.phase, raw: settle }); + return c.json({ error: { code: classified.code, message: classified.message }, next_steps: classified.nextSteps }, classified.status); + } + if (!settle.success) throw new Error("unreachable: classified covers every non-success phase"); const headers: Record = {}; if (settle.paymentResponseHeader) headers["payment-response"] = settle.paymentResponseHeader; diff --git a/src/payment/x402_settle.ts b/src/payment/x402_settle.ts index 2bf7537..edb0ccf 100644 --- a/src/payment/x402_settle.ts +++ b/src/payment/x402_settle.ts @@ -1,17 +1,18 @@ /** - * `processX402Settle` — single-call x402 verify+settle for merchants. + * `processX402Settle`: single-call x402 verify+settle for merchants. * * Wraps the four x402-server steps every x402-accepting merchant repeats: - * 1. `buildPaymentRequirements(resourceConfig)` — builds the requirement entries the + * 1. `buildPaymentRequirements(resourceConfig)`: builds the requirement entries the * facilitator validates against - * 2. `enrichExtensions(extension, transportContext)` — folds in Bazaar (or other) + * 2. `enrichExtensions(extension, transportContext)`: folds in Bazaar (or other) * extensions for the verify step - * 3. `processPaymentRequest(payload, resourceConfig, resourceMeta, extensions)` — + * 3. `processPaymentRequest(payload, resourceConfig, resourceMeta, extensions)`: * runs verify against the facilitator - * 4. `settlePayment(payload, matchedRequirement)` — settles on-chain + * 4. `settlePayment(payload, matchedRequirement)`: settles on-chain * * Returns a tagged result so the caller can map errors to merchant-shaped responses - * without owning the orchestration boilerplate. + * without owning the orchestration boilerplate. Use `classifyX402SettleResult` to + * map the tagged result to a recommended HTTP response. */ import type { X402Server } from './x402_server'; @@ -48,23 +49,132 @@ export type ProcessX402SettleResult = /** The x402 server's `processPaymentRequest` verify result. */ verifyResult: { success: true; [key: string]: unknown }; } + /** No-requirements branch: `buildPaymentRequirements` returned an empty array, so + * there is nothing to verify against. Indicates a merchant-side misconfiguration + * (resource config doesn't match any registered scheme/network). + * Recommended response: log `reason` server-side; map to a controlled 500 to the + * consumer via `classifyX402SettleResult`. */ | { success: false; phase: 'no_requirements'; reason: string } + /** Verify-failed branch: the facilitator's verify step ran and returned + * `{ success: false, ... }`. Payload is structurally invalid, expired, signed by + * the wrong wallet, or otherwise rejected by facilitator policy. + * Recommended response: log `verifyResult` server-side; map to a controlled 400 + * with `payment_proof_invalid` to the consumer via `classifyX402SettleResult`. */ | { success: false; phase: 'verify_failed'; verifyResult: unknown } + /** Settle-failed branch: verify succeeded but `settlePayment` threw (on-chain + * rejection, RPC outage, facilitator broadcast failure, etc.). The agent's + * credential was valid; funds did not move. + * Recommended response: log raw `error` server-side; map to a controlled 503 with + * `payment_provider_unavailable` to the consumer via `classifyX402SettleResult`. */ | { success: false; phase: 'settle_failed'; error: unknown; matchedRequirement: unknown } | { success: false; /** Facilitator threw an unexpected error during one of the verify-stage calls * (build requirements, extension enrich, or processPaymentRequest). Most common - * cause: the facilitator client rejects the configured network — for example, - * Coinbase's CDP facilitator throws on Solana devnet because it only supports - * mainnet networks. The merchant should emit a 503 with diagnostic info so the - * agent can pick a different rail or the operator can fix the deploy config. */ + * cause: the facilitator client rejects the configured network. Coinbase's CDP + * facilitator throws on Solana devnet because it only supports mainnet networks; + * Stripe's SPT facilitator throws on EVM networks; etc. + * Recommended response: log raw `error` server-side; map to a controlled 503 + * with `payment_provider_unavailable` to the consumer via `classifyX402SettleResult` + * so the agent can pick a different rail. */ phase: 'facilitator_error'; /** Which verify-stage step threw. */ step: 'build_requirements' | 'enrich_extensions' | 'process_payment_request'; error: unknown; }; +/** + * The merchant-shaped response for a non-success `ProcessX402SettleResult`. + * + * `status` / `code` / `message` are safe to send back to the consumer. `nextSteps` + * is the agent-instructions block describing what the agent should do next. Raw + * facilitator errors stay server-side: do NOT serialize the original `error` / + * `verifyResult` / `reason` to the consumer; log them yourself. + */ +export interface ClassifiedX402Error { + status: 400 | 500 | 503; + code: + | 'payment_proof_invalid' + | 'payment_provider_unavailable' + | 'payment_internal_error'; + message: string; + nextSteps: { + action: string; + user_message: string; + retry_after_seconds?: number; + }; +} + +/** + * Map a `ProcessX402SettleResult` to the recommended merchant response. + * + * Returns `null` for `success: true`. For each error phase, returns a controlled + * status / code / message / nextSteps tuple. Replaces error-message string-sniffing + * with a phase-based dispatch so merchants stop coupling to facilitator-specific + * error text. + * + * Phase mapping: + * - `verify_failed` → 400 `payment_proof_invalid` / `regenerate_payment_credential` + * - `facilitator_error` → 503 `payment_provider_unavailable` / `try_different_rail` + * - `settle_failed` → 503 `payment_provider_unavailable` / `retry_or_swap_method` + * - `no_requirements` → 500 `payment_internal_error` / `contact_support` + * + * Always log the raw `result` server-side before responding; the returned object + * is intentionally facilitator-agnostic and never carries raw error detail. + */ +export function classifyX402SettleResult( + result: ProcessX402SettleResult, +): ClassifiedX402Error | null { + if (result.success) return null; + switch (result.phase) { + case 'no_requirements': + return { + status: 500, + code: 'payment_internal_error', + message: 'Failed to build x402 payment requirements for this configuration', + nextSteps: { + action: 'contact_support', + user_message: + 'The merchant could not produce a payment challenge for this request. Try again later or contact support.', + }, + }; + case 'verify_failed': + return { + status: 400, + code: 'payment_proof_invalid', + message: 'Payment credential failed verification; regenerate from a fresh 402 challenge', + nextSteps: { + action: 'regenerate_payment_credential', + user_message: + 'The payment credential was rejected at verify time. Discard it, fetch a fresh 402 challenge, and re-sign.', + }, + }; + case 'facilitator_error': + return { + status: 503, + code: 'payment_provider_unavailable', + message: 'Payment provider could not process this network configuration', + nextSteps: { + action: 'try_different_rail', + user_message: + 'This rail is currently unavailable. Pick a different rail from the 402 challenge and retry.', + }, + }; + case 'settle_failed': + return { + status: 503, + code: 'payment_provider_unavailable', + message: 'Payment credential verified but on-chain settlement failed', + nextSteps: { + action: 'retry_or_swap_method', + retry_after_seconds: 10, + user_message: + 'Transient settlement error. Retry in a few seconds, or pick a different rail from the 402 challenge.', + }, + }; + } +} + export async function processX402Settle(input: ProcessX402SettleInput): Promise { const server = input.x402Server as unknown as { buildPaymentRequirements: (cfg: unknown) => Promise; diff --git a/tests/payment/x402_settle.test.ts b/tests/payment/x402_settle.test.ts index 3827ada..d7d6dfc 100644 --- a/tests/payment/x402_settle.test.ts +++ b/tests/payment/x402_settle.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; -import { processX402Settle } from '../../src/payment/x402_settle'; +import { classifyX402SettleResult, processX402Settle } from '../../src/payment/x402_settle'; import type { X402Server } from '../../src/payment/x402_server'; +import type { ProcessX402SettleResult } from '../../src/payment/x402_settle'; const baseInput = { payload: { accepted: { network: 'eip155:8453', payTo: '0xfeed' } }, @@ -109,3 +110,81 @@ describe('processX402Settle', () => { }); }); }); + +describe('classifyX402SettleResult', () => { + it('returns null on success', async () => { + const successResult: ProcessX402SettleResult = { + success: true, + matchedRequirement: { matched: true }, + settleResult: { tx: '0xabc' }, + paymentResponseHeader: 'base64', + verifyResult: { success: true }, + }; + expect(classifyX402SettleResult(successResult)).toBeNull(); + }); + + it('maps no_requirements to 500 payment_internal_error / contact_support', () => { + const classified = classifyX402SettleResult({ + success: false, + phase: 'no_requirements', + reason: 'empty', + }); + expect(classified).not.toBeNull(); + expect(classified!.status).toBe(500); + expect(classified!.code).toBe('payment_internal_error'); + expect(classified!.nextSteps.action).toBe('contact_support'); + }); + + it('maps verify_failed to 400 payment_proof_invalid / regenerate_payment_credential', () => { + const classified = classifyX402SettleResult({ + success: false, + phase: 'verify_failed', + verifyResult: { success: false, reason: 'expired' }, + }); + expect(classified).not.toBeNull(); + expect(classified!.status).toBe(400); + expect(classified!.code).toBe('payment_proof_invalid'); + expect(classified!.nextSteps.action).toBe('regenerate_payment_credential'); + }); + + it('maps facilitator_error to 503 payment_provider_unavailable / try_different_rail', () => { + const classified = classifyX402SettleResult({ + success: false, + phase: 'facilitator_error', + step: 'process_payment_request', + error: new Error('CDP rejects solana:devnet'), + }); + expect(classified).not.toBeNull(); + expect(classified!.status).toBe(503); + expect(classified!.code).toBe('payment_provider_unavailable'); + expect(classified!.nextSteps.action).toBe('try_different_rail'); + }); + + it('maps settle_failed to 503 payment_provider_unavailable / retry_or_swap_method with retry_after_seconds', () => { + const classified = classifyX402SettleResult({ + success: false, + phase: 'settle_failed', + error: new Error('on-chain rejection'), + matchedRequirement: { matched: true }, + }); + expect(classified).not.toBeNull(); + expect(classified!.status).toBe(503); + expect(classified!.code).toBe('payment_provider_unavailable'); + expect(classified!.nextSteps.action).toBe('retry_or_swap_method'); + expect(classified!.nextSteps.retry_after_seconds).toBe(10); + }); + + it('classified message and nextSteps.user_message do NOT leak raw error details', () => { + const sensitiveError = new Error('CDP-INTERNAL-TRACE-ID-12345 secret-key-in-stack'); + const classified = classifyX402SettleResult({ + success: false, + phase: 'facilitator_error', + step: 'process_payment_request', + error: sensitiveError, + }); + expect(classified).not.toBeNull(); + expect(classified!.message).not.toContain('CDP-INTERNAL-TRACE-ID-12345'); + expect(classified!.message).not.toContain('secret-key-in-stack'); + expect(classified!.nextSteps.user_message).not.toContain('CDP-INTERNAL-TRACE-ID-12345'); + }); +}); From 20e15ba02f242f429fe07187a9d59175ed534988 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 11:27:28 -0700 Subject: [PATCH 03/19] docs(x402-settle): drop 'string-sniffing' phrasing from /payment table Describe the helper neutrally as a controlled-envelope mapper that decouples merchants from facilitator-specific error text, instead of naming the prior antipattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 316eb32..163b239 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ npm install hono mppx @x402/core @x402/evm @x402/svm stripe # whatever your st | Subpath | What it provides | |---|---| | `/identity/{hono,express,fastify,nextjs,web}` | Trust gate middleware: KYC, sanctions, age, jurisdiction. `agentscoreGate(...)`, `getAgentScoreData(c)`, `captureWallet(...)`, `verifyWalletSignerMatch(...)`. Plus shared denial helpers: `denialReasonStatus`, `denialReasonToBody`, `buildSignerMismatchBody`, `buildContactSupportNextSteps`, `verificationAgentInstructions`, `isFixableDenial`, `FIXABLE_DENIAL_REASONS`. | -| `/payment` | `networks`, `USDC`, `rails` registries; `paymentDirective`, `buildPaymentDirective`, `wwwAuthenticateHeader`, `paymentRequiredHeader`, `aliasAmountFields` (v1↔v2 amount field shim — emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlementOverrideHeader`, `dispatchSettlementByNetwork`, `extractPaymentSigner` (returns `{address, network}`); `createX402Server`, `createMppxServer`; drop-in x402 helpers: `validateX402NetworkConfig` (boot-time guard), `verifyX402Request` (parse + validate inbound X-Payment), `processX402Settle` (verify-then-settle with one call), `classifyX402SettleResult` (maps the tagged settle result to a recommended HTTP status / code / nextSteps so merchants stop string-sniffing facilitator error messages). | +| `/payment` | `networks`, `USDC`, `rails` registries; `paymentDirective`, `buildPaymentDirective`, `wwwAuthenticateHeader`, `paymentRequiredHeader`, `aliasAmountFields` (v1↔v2 amount field shim — emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlementOverrideHeader`, `dispatchSettlementByNetwork`, `extractPaymentSigner` (returns `{address, network}`); `createX402Server`, `createMppxServer`; drop-in x402 helpers: `validateX402NetworkConfig` (boot-time guard), `verifyX402Request` (parse + validate inbound X-Payment), `processX402Settle` (verify-then-settle with one call), `classifyX402SettleResult` (maps the tagged settle result to a recommended HTTP status / code / nextSteps so merchants get a controlled envelope without coupling to facilitator-specific error text). | | `/discovery` | `isDiscoveryProbeRequest`, `buildDiscoveryProbeResponse` (with optional `x402Sample` for x402-aware crawlers — `awal x402 details` etc.), `sampleX402AcceptForNetwork` (USDC sample-accept builder for known CAIP-2 networks), `buildWellKnownMpp`, `buildLlmsTxt` + `llmsTxtIdentitySection` + `llmsTxtPaymentSection` (compact + verbose modes), `buildSkillMd` (Claude-Skill-compatible `/skill.md` agent-discovery manifest — strictly agent-facing data only, no internal posture), `agentscoreOpenApiSnippets`, `createBazaarDiscovery`, `noindexNonDiscoveryPaths` (Hono middleware that emits `X-Robots-Tag: noindex` on every path except the agent-discovery surfaces — defaults cover `/openapi.json`, `/llms.txt`, `/skill.md`, `/.well-known/{mpp.json,agent-card.json,ucp}`, `/favicon.{png,ico}`; pure helpers `isDiscoveryPath` + `defaultDiscoveryPaths` for non-Hono frameworks). | | `/challenge` | `build402Body`, `buildAcceptedMethods`, `buildIdentityMetadata`, `buildHowToPay`, `buildAgentInstructions` (auto-emits per-rail `compatible_clients` — smoke-verified CLIs the agent should use; vendor override supported), `buildPricingBlock`, `firstEncounterAgentMemory`, `OrderReceipt`; `respond402` — drop-in 402 emit that preserves mppx's `WWW-Authenticate` and layers x402's `PAYMENT-REQUIRED`. `buildValidationError` — structured 4xx body builder (`{error: {code, message}, required_fields?, example_body?, next_steps?, ...extra}`) so vendors compose body shapes by name instead of inlining at every validation site. | | `/stripe-multichain` | `createMultichainPaymentIntent`, `getDepositAddress`, `simulateCryptoDeposit`, `createMppxStripe`; `createPiCache` (TTL'd PI / deposit-address cache, Redis-backed when `redisUrl` set, in-memory otherwise), `simulateDepositIfTestMode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. | From 01010df8f9d4ca68511ad878966335f0f6100801 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 12:20:24 -0700 Subject: [PATCH 04/19] feat(discovery): x402scan Layer 1 builders (well-known/x402, x-payment-info, x-guidance, siwx) Closes the discovery-substrate gap with x402scan: every commerce-SDK merchant now emits everything x402scan's strict parser needs, no per-merchant boilerplate. - buildWellKnownX402(): emits {version: 1, resources: ["METHOD /path"]} for /.well-known/x402 - xPaymentInfoExtension(): per-operation x-payment-info with fixed/dynamic price + protocols (x402, mpp) - xGuidanceExtension(): info.x-guidance prose blob for OpenAPI top-level - siwxSecurityScheme(): http bearer SIWX entry for components.securitySchemes - agentscoreSecuritySchemes() now includes siwx alongside OperatorToken + WalletAddress - /.well-known/x402 added to defaultDiscoveryPaths so the noindex middleware lets it through Smoke verified end-to-end: spun up an http server emitting all the new docs and ran @agentcash/discovery against it. Output: Source: openapi Routes: 3 POST /purchase paid 0.10 USD [x402, mpp] POST /quote paid 0.01-5.00 USD [x402] GET /whoami siwx Guidance: 29 tokens x402scan classifies fixed-price, dynamic-price, multi-protocol, and SIWX-only routes correctly. Only warning is missing favicon (cosmetic). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/discovery/index.ts | 1 + src/discovery/openapi.ts | 104 ++++++++++++++++++++++++ src/discovery/robots_tag.ts | 1 + src/discovery/well_known_x402.ts | 42 ++++++++++ tests/discovery/openapi.test.ts | 46 +++++++++++ tests/discovery/robots_tag.test.ts | 1 + tests/discovery/well_known_x402.test.ts | 37 +++++++++ 7 files changed, 232 insertions(+) create mode 100644 src/discovery/well_known_x402.ts create mode 100644 tests/discovery/well_known_x402.test.ts diff --git a/src/discovery/index.ts b/src/discovery/index.ts index 050b3c6..df12897 100644 --- a/src/discovery/index.ts +++ b/src/discovery/index.ts @@ -1,6 +1,7 @@ export * from './probe'; export * from './bazaar'; export * from './well_known_mpp'; +export * from './well_known_x402'; export * from './llms_txt'; export * from './openapi'; export * from './robots_tag'; diff --git a/src/discovery/openapi.ts b/src/discovery/openapi.ts index 4f3e662..cdfefee 100644 --- a/src/discovery/openapi.ts +++ b/src/discovery/openapi.ts @@ -9,6 +9,10 @@ /** * Standard AgentScore identity security schemes. Plug into `components.securitySchemes`. + * + * Includes `siwx` (Sign-In With X) per the x402scan discovery spec so identity-gated + * operations can declare `security: [{ siwx: [] }]` and stay classified as identity-only, + * not paid. */ export function agentscoreSecuritySchemes(): Record { return { @@ -26,6 +30,24 @@ export function agentscoreSecuritySchemes(): Record { description: 'Wallet-path identity (0x... or base58). Only works on rails that carry a wallet signature (Tempo MPP, x402 EIP-3009, x402 SPL Token). The wallet you claim MUST sign the payment.', }, + siwx: siwxSecurityScheme(), + }; +} + +/** + * Sign-In With X security scheme entry, per the x402scan discovery spec. + * + * Reference it on identity-gated (but free) operations as + * `security: [{ siwx: [] }]`. Do NOT also attach `x-payment-info` to those routes, + * x402scan will misclassify them as paid. + */ +export function siwxSecurityScheme(): Record { + return { + type: 'http', + scheme: 'bearer', + bearerFormat: 'SIWX', + description: + 'Sign-In With X wallet authentication. Agent signs a challenge with their wallet (any supported chain) and presents the proof in the Authorization header. Used for identity-gated free endpoints; payment-required endpoints declare x-payment-info instead.', }; } @@ -112,6 +134,88 @@ export function agentscorePaymentRequiredSchema(): Record { }; } +/** + * Per-operation `x-payment-info` extension, per the x402scan discovery spec. + * + * Every payment-required OpenAPI operation should carry this block alongside a + * 402 response. Tells discovery crawlers (x402scan, agent CLIs) the static price + * and which protocols the route accepts. Runtime 402 behavior is authoritative + * over this static metadata; the static side is for indexability. + * + * @example fixed price across x402 + MPP Tempo + * ```ts + * Object.assign(operation, { + * ...xPaymentInfoExtension({ + * price: { mode: 'fixed', currency: 'USD', amount: '0.10' }, + * protocols: [ + * { x402: {} }, + * { mpp: { method: 'tempo/charge', intent: 'pay', currency: 'USD' } }, + * ], + * }), + * responses: { + * '200': {...}, + * '402': { description: 'Payment Required' }, + * }, + * }); + * ``` + */ +export interface XPaymentInfoFixedPrice { + mode: 'fixed'; + currency: string; + amount: string; +} + +export interface XPaymentInfoDynamicPrice { + mode: 'dynamic'; + currency: string; + min: string; + max: string; +} + +export type XPaymentInfoPrice = XPaymentInfoFixedPrice | XPaymentInfoDynamicPrice; + +export interface XPaymentInfoX402Protocol { + x402: Record; +} + +export interface XPaymentInfoMppProtocol { + mpp: { method: string; intent: string; currency: string }; +} + +export type XPaymentInfoProtocol = XPaymentInfoX402Protocol | XPaymentInfoMppProtocol; + +export interface XPaymentInfoInput { + price: XPaymentInfoPrice; + protocols: XPaymentInfoProtocol[]; +} + +export function xPaymentInfoExtension( + input: XPaymentInfoInput, +): { 'x-payment-info': { price: XPaymentInfoPrice; protocols: XPaymentInfoProtocol[] } } { + return { 'x-payment-info': { price: input.price, protocols: input.protocols } }; +} + +/** + * `info.x-guidance` extension, per the x402scan discovery spec. Spread into your + * OpenAPI document's `info` block to give agents a high-level prose description + * of how to use the API. Discovery crawlers surface this on the listing page. + * + * @example + * ```ts + * const spec = { + * openapi: '3.1.0', + * info: { + * title: 'My Merchant API', + * version: '1.0', + * ...xGuidanceExtension('Wine merchant. POST /purchase with a verified operator token...'), + * }, + * }; + * ``` + */ +export function xGuidanceExtension(text: string): { 'x-guidance': string } { + return { 'x-guidance': text }; +} + export interface BuildAgentScoreOpenApiSnippetsInput { /** Include security schemes in the snippet. Default true. */ security?: boolean; diff --git a/src/discovery/robots_tag.ts b/src/discovery/robots_tag.ts index 82c5344..02045ed 100644 --- a/src/discovery/robots_tag.ts +++ b/src/discovery/robots_tag.ts @@ -15,6 +15,7 @@ export const defaultDiscoveryPaths: ReadonlySet = new Set([ '/skill.md', '/SKILL.md', '/.well-known/mpp.json', + '/.well-known/x402', '/.well-known/agent-card.json', '/.well-known/ucp', '/favicon.png', diff --git a/src/discovery/well_known_x402.ts b/src/discovery/well_known_x402.ts new file mode 100644 index 0000000..e634ea5 --- /dev/null +++ b/src/discovery/well_known_x402.ts @@ -0,0 +1,42 @@ +/** + * `buildWellKnownX402`: emits the x402scan v1 `/.well-known/x402` discovery shape. + * + * x402scan accepts three discovery strategies (OpenAPI > `/.well-known/x402` > endpoint + * probe). Most AgentScore merchants already publish a richer `/.well-known/mpp.json`, + * but x402scan's strict parser only reads the v1 shape, so we emit both. The two + * coexist on different paths. + * + * Spec (verbatim, x402scan): + * + * { + * "version": 1, + * "resources": ["POST /api/route", ...] + * } + * + * Resource entries are `"METHOD /path"` strings, not objects. Runtime 402 behavior + * is authoritative over this static metadata. + */ + +export interface WellKnownX402Resource { + /** HTTP method, uppercase: `'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'`. */ + method: string; + /** Path, leading slash: `'/purchase'`. */ + path: string; +} + +export interface BuildWellKnownX402Input { + /** Invocable, payment-required routes. Each entry becomes `"METHOD /path"`. */ + resources: WellKnownX402Resource[]; +} + +export interface WellKnownX402Document { + version: 1; + resources: string[]; +} + +export function buildWellKnownX402(input: BuildWellKnownX402Input): WellKnownX402Document { + return { + version: 1, + resources: input.resources.map((r) => `${r.method.toUpperCase()} ${r.path}`), + }; +} diff --git a/tests/discovery/openapi.test.ts b/tests/discovery/openapi.test.ts index 8321f82..f9a1296 100644 --- a/tests/discovery/openapi.test.ts +++ b/tests/discovery/openapi.test.ts @@ -4,6 +4,9 @@ import { agentscoreDenialSchemas, agentscorePaymentRequiredSchema, agentscoreOpenApiSnippets, + siwxSecurityScheme, + xPaymentInfoExtension, + xGuidanceExtension, } from '../../src/discovery/openapi'; describe('agentscoreSecuritySchemes', () => { @@ -12,6 +15,49 @@ describe('agentscoreSecuritySchemes', () => { expect(schemes.OperatorToken).toMatchObject({ type: 'apiKey', in: 'header', name: 'X-Operator-Token' }); expect(schemes.WalletAddress).toMatchObject({ type: 'apiKey', in: 'header', name: 'X-Wallet-Address' }); }); + + it('includes the x402scan-spec siwx scheme', () => { + const schemes = agentscoreSecuritySchemes(); + expect(schemes.siwx).toMatchObject({ type: 'http', scheme: 'bearer', bearerFormat: 'SIWX' }); + }); +}); + +describe('siwxSecurityScheme', () => { + it('returns an http bearer scheme with bearerFormat=SIWX', () => { + const scheme = siwxSecurityScheme(); + expect(scheme).toMatchObject({ type: 'http', scheme: 'bearer', bearerFormat: 'SIWX' }); + }); +}); + +describe('xPaymentInfoExtension', () => { + it('emits fixed-mode price wrapped under x-payment-info', () => { + const ext = xPaymentInfoExtension({ + price: { mode: 'fixed', currency: 'USD', amount: '0.10' }, + protocols: [{ x402: {} }], + }); + expect(ext['x-payment-info'].price).toEqual({ mode: 'fixed', currency: 'USD', amount: '0.10' }); + expect(ext['x-payment-info'].protocols).toEqual([{ x402: {} }]); + }); + + it('emits dynamic-mode price + multi-protocol entries', () => { + const ext = xPaymentInfoExtension({ + price: { mode: 'dynamic', currency: 'USD', min: '0.01', max: '5.00' }, + protocols: [ + { x402: {} }, + { mpp: { method: 'tempo/charge', intent: 'pay', currency: 'USD' } }, + ], + }); + expect(ext['x-payment-info'].price).toMatchObject({ mode: 'dynamic', min: '0.01', max: '5.00' }); + expect(ext['x-payment-info'].protocols).toHaveLength(2); + }); +}); + +describe('xGuidanceExtension', () => { + it('wraps a string under x-guidance', () => { + expect(xGuidanceExtension('Use POST /purchase with X-Operator-Token...')).toEqual({ + 'x-guidance': 'Use POST /purchase with X-Operator-Token...', + }); + }); }); describe('agentscoreDenialSchemas', () => { diff --git a/tests/discovery/robots_tag.test.ts b/tests/discovery/robots_tag.test.ts index 3bc3557..2bf7cf8 100644 --- a/tests/discovery/robots_tag.test.ts +++ b/tests/discovery/robots_tag.test.ts @@ -17,6 +17,7 @@ describe('defaultDiscoveryPaths', () => { '/skill.md', '/SKILL.md', '/.well-known/mpp.json', + '/.well-known/x402', '/.well-known/agent-card.json', '/.well-known/ucp', '/favicon.png', diff --git a/tests/discovery/well_known_x402.test.ts b/tests/discovery/well_known_x402.test.ts new file mode 100644 index 0000000..143391e --- /dev/null +++ b/tests/discovery/well_known_x402.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { buildWellKnownX402 } from '../../src/discovery/well_known_x402'; + +describe('buildWellKnownX402', () => { + it('emits {version: 1, resources: ["METHOD /path"]} per the x402scan v1 spec', () => { + const doc = buildWellKnownX402({ + resources: [ + { method: 'POST', path: '/purchase' }, + { method: 'GET', path: '/catalog' }, + ], + }); + expect(doc).toEqual({ + version: 1, + resources: ['POST /purchase', 'GET /catalog'], + }); + }); + + it('uppercases the method even when caller passes lowercase', () => { + const doc = buildWellKnownX402({ resources: [{ method: 'post', path: '/purchase' }] }); + expect(doc.resources).toEqual(['POST /purchase']); + }); + + it('preserves resource ordering', () => { + const doc = buildWellKnownX402({ + resources: [ + { method: 'POST', path: '/a' }, + { method: 'POST', path: '/b' }, + { method: 'POST', path: '/c' }, + ], + }); + expect(doc.resources).toEqual(['POST /a', 'POST /b', 'POST /c']); + }); + + it('emits an empty resources array when input is empty', () => { + expect(buildWellKnownX402({ resources: [] })).toEqual({ version: 1, resources: [] }); + }); +}); From 32ea66392d7e7b0d4d3f59ee1e16fae6a54ec1b9 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 14:12:29 -0700 Subject: [PATCH 05/19] feat(solana)!: kill x402 Solana, add MPP solana/charge as the canonical Solana path x402 Solana was structurally broken against any payTo whose USDC ATA wasn't pre-warmed. The upstream `@x402/svm` `ExactSvmScheme` tx-builder doesn't include a `createAssociatedTokenIdempotent` instruction, so SPL transfers fail simulation with `InvalidAccountData` whenever the destination ATA doesn't exist (Stripe- multichain rotating addresses, fresh wallets, etc.). Upstream's stance per x402-foundation/x402#1020 is that ATA pre-creation is the seller's responsibility by design (rent-drain attack vector); they will not patch. The cleaner architectural answer is MPP `solana/charge`. The buyer's tx-builder includes idempotent ATA-create before TransferChecked, so payments work against any payTo, and the spec is what Stripe officially routes Solana through per docs.stripe.com/payments/machine. Changes (BREAKING): - Drop `x402-solana-mainnet` / `x402-solana-devnet` rails from the rail registry - Drop SVM from `validateX402NetworkConfig` (now base-only) and `verifyX402Request` (single `acceptedNetwork`, not a `{base, svm}` object) - Drop SVM from `createX402Server` rail union - Drop the SVM extraction branch in `extractPaymentSigner` - Add `mpp-solana-mainnet` / `mpp-solana-devnet` rails (method='solana') - Add `solana` rail to `createMppxServer` wiring `@solana/mpp/server` charge - Rename `x402_solana` -> `solana_mpp` field across challenge builders and discovery (accepted_methods emits `solana/charge`, agent_instructions/skill_md/llms_txt surface MPP Solana terminology) - New `SolanaMppMethodEntry` type with optional `fee_payer_key` - Drop `@x402/svm` from devDependencies; add `@solana/mpp` + `@solana/kit` as optional peer deps - Trim implementation noise (ATA-create mechanics, settle internals) from buyer-facing surfaces (skill.md, llms.txt, agent_instructions, how_to_pay) per feedback that those are agent-actionable, not protocol explainers - Bump 1.3.0 -> 1.4.0 - Update README + examples + CLAUDE.md 655 tests pass, lint + typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 2 +- README.md | 19 +- bun.lock | 291 +++++++++++++++++---- examples/api-provider.ts | 29 +- examples/multi-rail-merchant.ts | 43 ++- package.json | 13 +- src/challenge/accepted_methods.ts | 34 ++- src/challenge/agent_instructions.ts | 25 +- src/challenge/how_to_pay.ts | 20 +- src/discovery/llms_txt.ts | 84 +++--- src/discovery/skill_md.ts | 4 +- src/identity/ucp.ts | 2 +- src/payment/mppx_server.ts | 74 +++++- src/payment/rails.ts | 8 +- src/payment/x402_server.ts | 30 +-- src/payment/x402_validation.ts | 73 ++---- src/signer.ts | 50 +--- tests/challenge/accepted_methods.test.ts | 11 +- tests/challenge/agent_instructions.test.ts | 16 +- tests/challenge/how_to_pay.test.ts | 6 +- tests/discovery/llms_txt.test.ts | 40 +-- tests/discovery/skill_md.test.ts | 6 +- tests/payment/headers.test.ts | 4 +- tests/payment/mppx_server.test.ts | 35 +++ tests/payment/x402_server.test.ts | 9 +- tests/payment/x402_server_upto.test.ts | 6 - tests/payment/x402_validation.test.ts | 67 ++--- tests/signer.test.ts | 80 +----- 28 files changed, 611 insertions(+), 470 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 008bb6f..a9e3121 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,7 @@ Single TypeScript package, tsup-built CJS + ESM with subpath exports. Per-framew | `examples/` | Runnable single-file Hono apps for each common scenario | | `tests/` | Vitest, one file per surface, ~360+ tests | -Peer-dep pattern: payment/x402/mppx/stripe modules `dynamic import` at runtime — vendors install only what they use (`@x402/core`, `@x402/evm`, `@x402/svm`, `@coinbase/x402`, `mppx`, `stripe`). Missing peer dep throws a guiding error with the install command. +Peer-dep pattern: payment/x402/mppx/stripe modules `dynamic import` at runtime — vendors install only what they use (`@x402/core`, `@x402/evm`, `@coinbase/x402`, `mppx`, `@solana/mpp`, `@solana/kit`, `stripe`). Missing peer dep throws a guiding error with the install command. x402 in this SDK is EVM-only; Solana SPL payments go through MPP `solana/charge` (`@solana/mpp/server`). ## Examples diff --git a/README.md b/README.md index 163b239..6ef2888 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ bun add @agent-score/commerce Framework + protocol packages are optional peer deps — install only what you use: ```bash -npm install hono mppx @x402/core @x402/evm @x402/svm stripe # whatever your stack needs +npm install hono mppx @x402/core @x402/evm @solana/mpp @solana/kit stripe # whatever your stack needs ``` ## What's in the package @@ -89,9 +89,9 @@ import { // Build paymentauth.org directives by symbolic rail name (decimals + currency from registry) const directives = [ - buildPaymentDirective({ rail: "tempo-mainnet", id: "chg_t", realm: "ex.com", recipient: TEMPO_ADDR, amountUsd: 0.01 }), - buildPaymentDirective({ rail: "x402-base-mainnet", id: "chg_b", realm: "ex.com", recipient: BASE_ADDR, amountUsd: 0.01 }), - buildPaymentDirective({ rail: "x402-solana-mainnet", id: "chg_s", realm: "ex.com", recipient: SOL_ADDR, amountUsd: 0.01 }), + buildPaymentDirective({ rail: "tempo-mainnet", id: "chg_t", realm: "ex.com", recipient: TEMPO_ADDR, amountUsd: 0.01 }), + buildPaymentDirective({ rail: "x402-base-mainnet", id: "chg_b", realm: "ex.com", recipient: BASE_ADDR, amountUsd: 0.01 }), + buildPaymentDirective({ rail: "mpp-solana-mainnet", id: "chg_s", realm: "ex.com", recipient: SOL_ADDR, amountUsd: 0.01 }), ]; const wwwAuth = wwwAuthenticateHeader(directives); @@ -106,12 +106,17 @@ import { createX402Server, createMppxServer } from "@agent-score/commerce/paymen const x402 = await createX402Server({ facilitator: "coinbase", // or "http", or pass a custom facilitator instance - rails: ["x402-base-mainnet", "x402-solana-mainnet", "x402-base-mainnet-upto"], + rails: ["x402-base-mainnet", "x402-base-mainnet-upto"], }); const mppx = await createMppxServer({ rails: { tempo: { recipient: process.env.TEMPO_RECIPIENT! }, + solana: { + recipient: process.env.SOLANA_RECIPIENT!, + // Optional fee sponsor — pass any `TransactionPartialSigner` from `@solana/kit`. + // signer: solanaFeePayerSigner, + }, stripe: { profileId: process.env.STRIPE_PROFILE_ID!, secretKey: process.env.STRIPE_SECRET_KEY! }, }, secretKey: process.env.MPP_SECRET_KEY!, @@ -132,7 +137,7 @@ import { const acceptedMethods = buildAcceptedMethods({ tempo: { recipient: TEMPO_ADDR }, x402_base: { recipient: BASE_ADDR }, - x402_solana: { recipient: SOL_ADDR }, + solana_mpp: { recipient: SOL_ADDR, feePayerKey: SOLANA_FEE_PAYER }, stripe: { profileId: STRIPE_PROFILE_ID }, }); @@ -244,7 +249,7 @@ import { import { respond402 } from "@agent-score/commerce/challenge"; // Boot-time guard — raises if a configured network isn't supported. -validateX402NetworkConfig({ baseNetwork: X402_BASE, svmNetwork: X402_SVM }); +validateX402NetworkConfig({ baseNetwork: X402_BASE }); app.post("/purchase", async (c) => { // Path A — agent presented an x402 X-Payment header diff --git a/bun.lock b/bun.lock index 1176a83..df0706e 100644 --- a/bun.lock +++ b/bun.lock @@ -10,13 +10,14 @@ "devDependencies": { "@coinbase/x402": "^2.1.0", "@eslint/js": "^9.39.4", + "@solana/kit": "^6.8.0", + "@solana/mpp": "^0.5.2", "@types/express": "^5.0.6", "@types/node": "^25.6.0", "@vitest/coverage-v8": "^4.1.5", "@x402/core": "^2.11.0", "@x402/evm": "^2.11.0", "@x402/extensions": "^2.11.0", - "@x402/svm": "^2.11.0", "dotenv": "^17.4.2", "eslint": "^9.39.4", "eslint-plugin-import": "^2.32.0", @@ -32,12 +33,16 @@ "vitest": "^4.1.5", }, "peerDependencies": { + "@solana/kit": ">=6.5.0", + "@solana/mpp": ">=0.5.0", "express": ">=4.0.0", "fastify": ">=4.0.0", "hono": ">=4.0.0", "stripe": ">=17.0.0", }, "optionalPeers": [ + "@solana/kit", + "@solana/mpp", "express", "fastify", "hono", @@ -283,91 +288,95 @@ "@signinwithethereum/siwe-parser": ["@signinwithethereum/siwe-parser@4.2.0", "", { "dependencies": { "@noble/hashes": "^1.7.0", "apg-js": "^4.4.0" } }, "sha512-e3edh8XpZrEjbzVYc0BZ4ySFOa8RKTZOQTafSf1E6ejCB5XcBH93jEpYmjzry1EEocgMNq4KlB3YunsFNCXakQ=="], - "@solana-program/compute-budget": ["@solana-program/compute-budget@0.11.0", "", { "peerDependencies": { "@solana/kit": "^5.0" } }, "sha512-7f1ePqB/eURkTwTOO9TNIdUXZcyrZoX3Uy2hNo7cXMfNhPFWp9AVgIyRNBc2jf15sdUa9gNpW+PfP2iV8AYAaw=="], + "@solana-program/compute-budget": ["@solana-program/compute-budget@0.15.0", "", { "peerDependencies": { "@solana/kit": "^6.3.0" } }, "sha512-toejNdIkzpUTqLSIzP0Nofr/EFel8QpPWuTtIKzfCcjn+mXpkThHxPJaNesk251rSTiWaxDZ3WxG7RxYwTWTqA=="], - "@solana-program/system": ["@solana-program/system@0.10.0", "", { "peerDependencies": { "@solana/kit": "^5.0" } }, "sha512-Go+LOEZmqmNlfr+Gjy5ZWAdY5HbYzk2RBewD9QinEU/bBSzpFfzqDRT55JjFRBGJUvMgf3C2vfXEGT4i8DSI4g=="], + "@solana-program/system": ["@solana-program/system@0.12.0", "", { "peerDependencies": { "@solana/kit": "^6.1.0" } }, "sha512-ZnAAWeGVMWNtJhw3GdifI2HnhZ0A0H0qs8tBkcFvxp/8wIavvO+GOM4Jd0N22u2+Lni2zcwvcrxrsxj6Mjphng=="], - "@solana-program/token": ["@solana-program/token@0.9.0", "", { "peerDependencies": { "@solana/kit": "^5.0" } }, "sha512-vnZxndd4ED4Fc56sw93cWZ2djEeeOFxtaPS8SPf5+a+JZjKA/EnKqzbE1y04FuMhIVrLERQ8uR8H2h72eZzlsA=="], + "@solana-program/token": ["@solana-program/token@0.11.0", "", { "dependencies": { "@solana-program/system": "^0.12.0" }, "peerDependencies": { "@solana/kit": "^6.1.0" } }, "sha512-/lB72Pcmkn5kjUfP1Vd8F86HBlhN9RN4hJYgUG5csc23w9LCdYom2CiRdGsK25KkR9UFf6ndCCAMh83hMo+ftw=="], - "@solana-program/token-2022": ["@solana-program/token-2022@0.6.1", "", { "peerDependencies": { "@solana/kit": "^5.0", "@solana/sysvars": "^5.0" } }, "sha512-Ex02cruDMGfBMvZZCrggVR45vdQQSI/unHVpt/7HPt/IwFYB4eTlXtO8otYZyqV/ce5GqZ8S6uwyRf0zy6fdbA=="], + "@solana/accounts": ["@solana/accounts@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rXjFYVopaEw1H2PTBQbRjKr+0i4EFuBEhRT5E0dI4cMaabSb4KKypC2gaf47+6cjU3hMlM1AcsyIs72/MqAVBw=="], - "@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@solana/addresses": ["@solana/addresses@6.8.0", "", { "dependencies": { "@solana/assertions": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/nominal-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-xVlA0DNX1LVfTueVsbhxDDoqr1VxeXvgJEh2GcIN/vcJPhY3GE3AYtjTbJJmTDgPrzOccI0t6ElVb1gelJH/PQ=="], - "@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + "@solana/assertions": ["@solana/assertions@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-OU6prCq39fSvGL8xY1C/9vhghasvAkMiRlituzJxzJpZRfpVRrwhzLd6P5NPAPoQ28qKcenA50kFdw9+ZyneJQ=="], - "@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana/codecs": ["@solana/codecs@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/options": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-qCSAaw1qszeQflavkIM7c21qJ3BHReP/qgDelZbhsEXpZc852CCZM00FOIWuxePr6X+JjSNqJquxwdDSoZe7Bw=="], - "@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + "@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/codecs-data-structures": ["@solana/codecs-data-structures@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-97bJWGyUY9WvBz3mX1UV3YPWGDTez6btCfD0ip3UVEXJbItVuUiOkzcO5iFDUtQT5riKT6xC+Mzl+0nO76gd0w=="], + "@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + "@solana/errors": ["@solana/errors@6.8.0", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.3" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-HRTrLgTn0c99GKz4v4IKgz2+6soaRY1mh2tLW4sk1Fe4Zzv85Q6ZLK1mXrVGL73z1apyHDrr9/Sd/9ZhUsUvpA=="], - "@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lZa3Qnsn+9ew6rHTXkPc+uqSa3i+AWqSBhV6oYxxBc+smvuxovItU4TPIs30cTfA7lAP+j+oYAQtUDu2dLy0hA=="], - "@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], + "@solana/functional": ["@solana/functional@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-oMSAD/8w9ujx7OplvwRWwHHFnaaxi/Xrji1XH3xAB+gzxupUpBbOmgxQ+e84x+9VN8QWk5aU3L7gmCqdTAR6OA=="], - "@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], + "@solana/instruction-plans": ["@solana/instruction-plans@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/promises": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-osAsY8ozqohrcTcHlG1EmO3i9flc0eESMIy9akTHyVvqk915gZgkaTmt4IjcYSwBGt7i+Rh8TmLj27RrTpCKvg=="], - "@solana/instruction-plans": ["@solana/instruction-plans@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/promises": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-7z3CB7YMcFKuVvgcnNY8bY6IsZ8LG61Iytbz7HpNVGX2u1RthOs1tRW8luTzSG1MPL0Ox7afyAVMYeFqSPHnaQ=="], + "@solana/instructions": ["@solana/instructions@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-dTtykhS9IeN3npCfnd7wSS6KmKAh54+g90JRtLYy5/31L2Zvunf3AJz2QUk58vgsAGZ5fuoiMyhCxRJm4rHUBQ=="], - "@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana/keys": ["@solana/keys@6.8.0", "", { "dependencies": { "@solana/assertions": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/promises": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wo8CnbrVfCP1Jbsb3ElMej/3dmMrl4ArPhI1mDcqIIz/O4j4HmxZYbn2BCWtnV9V/LPM638EMO2r1x6GzDNrPA=="], - "@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana/kit": ["@solana/kit@6.8.0", "", { "dependencies": { "@solana/accounts": "6.8.0", "@solana/addresses": "6.8.0", "@solana/codecs": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instruction-plans": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/offchain-messages": "6.8.0", "@solana/plugin-core": "6.8.0", "@solana/plugin-interfaces": "6.8.0", "@solana/program-client-core": "6.8.0", "@solana/programs": "6.8.0", "@solana/rpc": "6.8.0", "@solana/rpc-api": "6.8.0", "@solana/rpc-parsed-types": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/rpc-subscriptions": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/signers": "6.8.0", "@solana/subscribable": "6.8.0", "@solana/sysvars": "6.8.0", "@solana/transaction-confirmation": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-+McC1aCgcUBdM7Cd7U6k2ZHJ9OKCy5mzpb0XWrhkrgsFxT0QoRr0AcWJc85o6tIDfG6Jz7vVhbS3l8ugYz2Vzw=="], - "@solana/kit": ["@solana/kit@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instruction-plans": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/plugin-core": "5.5.1", "@solana/programs": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-parsed-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/signers": "5.5.1", "@solana/sysvars": "5.5.1", "@solana/transaction-confirmation": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ=="], + "@solana/mpp": ["@solana/mpp@0.5.2", "", { "dependencies": { "@solana-program/compute-budget": "^0.15.0", "@solana-program/system": "^0.12.0", "@solana-program/token": "^0.11.0" }, "peerDependencies": { "@solana/kit": ">=6.5.0", "mppx": ">=0.5.5" } }, "sha512-yCBcDZVJ8BIFGF7/ybPcF+cMJuTmBrCFdNEbgDPfKJau5MMoJ+eHUVatYN5f809xqFmQ4LQv7yZvcy9udCsx+Q=="], - "@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + "@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], + "@solana/offchain-messages": ["@solana/offchain-messages@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HoniTs2uoCHGicD0dTTJ3YBhLZC9URxdXXUf0CHalLFwAidF9iNuB8dsuKk16Euu68L4/ERKKGfyC0QobBvahw=="], - "@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana/options": ["@solana/options@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-T5441HHeucFaLtaMAJQJl79T7mX007oAFPunpPebBphRvCXGv+qQwQvqa4HkYct6Jf2O0aKLBL9GSe/kfdCk9A=="], - "@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], + "@solana/plugin-core": ["@solana/plugin-core@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kdqFIhQvJP2BDUsMOIbor35esj8u78SO33Xv0Wmo+uTRg6yKONKVK53ghw235pWrinOT4f0VnVe6MN6ciYiQVA=="], - "@solana/programs": ["@solana/programs@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-7U9kn0Jsx1NuBLn5HRTFYh78MV4XN145Yc3WP/q5BlqAVNlMoU9coG5IUTJIG847TUqC1lRto3Dnpwm6T4YRpA=="], + "@solana/plugin-interfaces": ["@solana/plugin-interfaces@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/instruction-plans": "6.8.0", "@solana/keys": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-subscriptions-spec": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/signers": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-4olaMKGUVA7wG6BBWM5A31bQsUWBlfcL1pjhq6ZTqVEJ7vshHXGwHVlWYXYyYn9ixozGDpGSl553yaRY9jQwWw=="], - "@solana/promises": ["@solana/promises@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-T9lfuUYkGykJmppEcssNiCf6yiYQxJkhiLPP+pyAc2z84/7r3UVIb2tNJk4A9sucS66pzJnVHZKcZVGUUp6wzA=="], + "@solana/program-client-core": ["@solana/program-client-core@6.8.0", "", { "dependencies": { "@solana/accounts": "6.8.0", "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0", "@solana/instruction-plans": "6.8.0", "@solana/instructions": "6.8.0", "@solana/plugin-interfaces": "6.8.0", "@solana/rpc-api": "6.8.0", "@solana/signers": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eOZtEnwl+vdiy9x/rFF89NDtnvt+Q3H04A/0u4GoHnt+fFkQG3JS+ChWG9c77izmpmRuz5C1GptOPDGNDnIUgQ=="], - "@solana/rpc": ["@solana/rpc@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/fast-stable-stringify": "5.5.1", "@solana/functional": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-transport-http": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ku8zTUMrkCWci66PRIBC+1mXepEnZH/q1f3ck0kJZ95a06bOTl5KU7HeXWtskkyefzARJ5zvCs54AD5nxjQJ+A=="], + "@solana/programs": ["@solana/programs@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-8hSKGfPTLX9Sm7KGV/UtiGCeSzptT/9vcjbodE+ZGHKFefo5vES4UAW+qD01LjL7IumGtMJvnfhCWt81qT/jbQ=="], - "@solana/rpc-api": ["@solana/rpc-api@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/rpc-parsed-types": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-XWOQQPhKl06Vj0xi3RYHAc6oEQd8B82okYJ04K7N0Vvy3J4PN2cxeK7klwkjgavdcN9EVkYCChm2ADAtnztKnA=="], + "@solana/promises": ["@solana/promises@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kIypZG83ZbADbrAq9/LS7LuWlVxlgJSzIpic75+9IuAfC3k5/KSus8LrvggBkCzfAyIslrUh70iz4JcnzUZrOw=="], - "@solana/rpc-parsed-types": ["@solana/rpc-parsed-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HEi3G2nZqGEsa3vX6U0FrXLaqnUCg4SKIUrOe8CezD+cSFbRTOn3rCLrUmJrhVyXlHoQVaRO9mmeovk31jWxJg=="], + "@solana/rpc": ["@solana/rpc@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/fast-stable-stringify": "6.8.0", "@solana/functional": "6.8.0", "@solana/rpc-api": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/rpc-transformers": "6.8.0", "@solana/rpc-transport-http": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-+jW4n9TDmBttY3bO3PdUo54GAnwFrd7UJsyfXoMgl/lWGQq5uddYDgnzQLtHOBP5zKslkR8h0RKkic0GZhMZrQ=="], - "@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@solana/rpc-api": ["@solana/rpc-api@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/keys": "6.8.0", "@solana/rpc-parsed-types": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-transformers": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-v8ZKWgPtKbF6HeJcfC4ciwI8mwDCizBtRLYYjjHOu+9S9IJYyefQzsQxL5P8OjJPpI4gFauT6gsjQLo76BoojA=="], - "@solana/rpc-spec-types": ["@solana/rpc-spec-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-6OFKtRpIEJQs8Jb2C4OO8KyP2h2Hy1MFhatMAoXA+0Ik8S3H+CicIuMZvGZ91mIu/tXicuOOsNNLu3HAkrakrw=="], + "@solana/rpc-parsed-types": ["@solana/rpc-parsed-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-jYddZviBSUYbuUKqvNthet7KbJVI7me6xfRH2znv1SjIpmvhSPJcGN5QrlHVOasHdzEWSpvZa5VYDfnqH3aYvA=="], - "@solana/rpc-subscriptions": ["@solana/rpc-subscriptions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/fast-stable-stringify": "5.5.1", "@solana/functional": "5.5.1", "@solana/promises": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions-api": "5.5.1", "@solana/rpc-subscriptions-channel-websocket": "5.5.1", "@solana/rpc-subscriptions-spec": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/subscribable": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CTMy5bt/6mDh4tc6vUJms9EcuZj3xvK0/xq8IQ90rhkpYvate91RjBP+egvjgSayUg9yucU9vNuUpEjz4spM7w=="], + "@solana/rpc-spec": ["@solana/rpc-spec@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/rpc-spec-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kE5uOspxCVFJKNUu73hlebGiAFosjfYXbbTXAbGKfksPzy84u1oJFC2IVIobLRnqUCw1x7oJcvfnX00Zs0Itpg=="], - "@solana/rpc-subscriptions-api": ["@solana/rpc-subscriptions-api@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/keys": "5.5.1", "@solana/rpc-subscriptions-spec": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5Oi7k+GdeS8xR2ly1iuSFkAv6CZqwG0Z6b1QZKbEgxadE1XGSDrhM2cn59l+bqCozUWCqh4c/A2znU/qQjROlw=="], + "@solana/rpc-spec-types": ["@solana/rpc-spec-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebCWgiQbIgFOehU7PdRFmYCzda3Azc/qa2Y3P8gexSHSsDAO27VwS4E05XSY+a7cIL5MYmvUa1vpDynl1Rkakw=="], - "@solana/rpc-subscriptions-channel-websocket": ["@solana/rpc-subscriptions-channel-websocket@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/rpc-subscriptions-spec": "5.5.1", "@solana/subscribable": "5.5.1", "ws": "^8.19.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-7tGfBBrYY8TrngOyxSHoCU5shy86iA9SRMRrPSyBhEaZRAk6dnbdpmUTez7gtdVo0BCvh9nzQtUycKWSS7PnFQ=="], + "@solana/rpc-subscriptions": ["@solana/rpc-subscriptions@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/fast-stable-stringify": "6.8.0", "@solana/functional": "6.8.0", "@solana/promises": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/rpc-subscriptions-api": "6.8.0", "@solana/rpc-subscriptions-channel-websocket": "6.8.0", "@solana/rpc-subscriptions-spec": "6.8.0", "@solana/rpc-transformers": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/subscribable": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-9CotreNZmKAP2z07FY1I7TPPvylKLFF5p4mujB5ZFMHQPp5JVQFVCmMIhSj5voZHAeYx7jdwJ2Kf0RDeClqJzA=="], - "@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/promises": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/subscribable": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iq+rGq5fMKP3/mKHPNB6MC8IbVW41KGZg83Us/+LE3AWOTWV1WT20KT2iH1F1ik9roi42COv/TpoZZvhKj45XQ=="], + "@solana/rpc-subscriptions-api": ["@solana/rpc-subscriptions-api@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/keys": "6.8.0", "@solana/rpc-subscriptions-spec": "6.8.0", "@solana/rpc-transformers": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-cPJOsydyoqkztW3msEH09wPDYqxJcMvO6DBlvrboq6wGu1UjeP66w2eApzQ8POoQHxhyw+CfEXl1Gbu6kKwuMQ=="], - "@solana/rpc-transformers": ["@solana/rpc-transformers@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-OsWqLCQdcrRJKvHiMmwFhp9noNZ4FARuMkHT5us3ustDLXaxOjF0gfqZLnMkulSLcKt7TGXqMhBV+HCo7z5M8Q=="], + "@solana/rpc-subscriptions-channel-websocket": ["@solana/rpc-subscriptions-channel-websocket@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/rpc-subscriptions-spec": "6.8.0", "@solana/subscribable": "6.8.0", "ws": "^8.19.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-c3PpkorYwhAz1iuUfM5sLpZQi8xtZFGbaPbaPRELVeDjFSRzoa12KFnuQs4i9fbVbLy5Cnt1t23tf0bL2snZCQ=="], - "@solana/rpc-transport-http": ["@solana/rpc-transport-http@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "undici-types": "^7.19.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-yv8GoVSHqEV0kUJEIhkdOVkR2SvJ6yoWC51cJn2rSV7plr6huLGe0JgujCmB7uZhhaLbcbP3zxXxu9sOjsi7Fg=="], + "@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/promises": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/subscribable": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-+t4L5q9qE6IVfunW3n1amA/3EswJr64pVqRF7234vCUuVUz4PgYfbqtEBV3KkA1o0NwEHHM3pXuofT63nBb8Bg=="], - "@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@solana/rpc-transformers": ["@solana/rpc-transformers@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-GzcFkllym7eXbw7grdE41MCb15CjkibrXtr7EFsf4d6LD9DRvzFj2ZRYywS2FB2ibVP0LUXXGk3vmtkZJjfajA=="], - "@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + "@solana/rpc-transport-http": ["@solana/rpc-transport-http@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "undici-types": "^8.0.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-jw/L0q2motGcx7yo6KvkKJd2HGVg9gvViXatFloLl1XmHbkwE7+97YYmG17WRuM5xauzI/UGYOXNW7cEB+Uaxw=="], - "@solana/subscribable": ["@solana/subscribable@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-9K0PsynFq0CsmK1CDi5Y2vUIJpCqkgSS5yfDN0eKPgHqEptLEaia09Kaxc90cSZDZU5mKY/zv1NBmB6Aro9zQQ=="], + "@solana/rpc-types": ["@solana/rpc-types@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/nominal-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-vACMV9VR2JsZGDcgaMOFN/dwLK57CsE+erassxxtF12sSPXJooz+Vu1vyY2Yp2EkCc7mDf7BNkTKvSXajbt+Qw=="], - "@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], + "@solana/signers": ["@solana/signers@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/offchain-messages": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-7E1cAXBLOcz9kmHhzWdu5m3UJlJzxfwOl8irOMLJI6NnKB2EmU0B0h4I+Mlfs9w8Bfj0WQpUei21ammbNBq39g=="], - "@solana/transaction-confirmation": ["@solana/transaction-confirmation@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/promises": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-j4mKlYPHEyu+OD7MBt3jRoX4ScFgkhZC6H65on4Fux6LMScgivPJlwnKoZMnsgxFgWds0pl+BYzSiALDsXlYtw=="], + "@solana/subscribable": ["@solana/subscribable@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-yj41Q97MiWrOmLj1iRFobvTdtU6H5wz5BlH5FHJg9lyapy1YQyaYF37MZx4LiUj4Ww0V3ReluIZTWWDBOJ53Jg=="], - "@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@solana/sysvars": ["@solana/sysvars@6.8.0", "", { "dependencies": { "@solana/accounts": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-pwfMpMNL6MSmm07eHQYdTdRdzmPOd+EuVCCaNLSYdWGpYcocVJiaLiNWRV3cXA5wPj/ZFkoUGtc1bo0v7H50lw=="], - "@solana/transactions": ["@solana/transactions@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-8hHtDxtqalZ157pnx6p8k10D7J/KY/biLzfgh9R09VNLLY3Fqi7kJvJCr7M2ik3oRll56pxhraAGCC9yIT6eOA=="], + "@solana/transaction-confirmation": ["@solana/transaction-confirmation@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/keys": "6.8.0", "@solana/promises": "6.8.0", "@solana/rpc": "6.8.0", "@solana/rpc-subscriptions": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-R6rj8y/+kZqYJr8FR/fWxgi3Pw3eCiacUyjCPTVtdVe6i+hIiBApTGLzXrSRJmAMdpZrjYBZU1cG8C6oAb+B2A=="], + + "@solana/transaction-messages": ["@solana/transaction-messages@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instructions": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-jsJu9mAcN1x7onKOeC4WEvYP04UVcnkOYu/9bMe+S9jqjL+3DMy9kFZpV5FBl+TPuTNJrtOqc6Gc28hUWyyp1A=="], + + "@solana/transactions": ["@solana/transactions@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Q46m+o3C1yL2EIZBAP5B8ou2VZwHN9wTi+muIS6/giCKO3jwUtnTEbWcZEDMj2vxUb7P2WfwTluZb/VAWxlx7Q=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], @@ -447,8 +456,6 @@ "@x402/extensions": ["@x402/extensions@2.11.0", "", { "dependencies": { "@noble/curves": "^1.9.0", "@scure/base": "^1.2.6", "@signinwithethereum/siwe": "^4.1.0", "@x402/core": "~2.11.0", "ajv": "^8.17.1", "jose": "^5.9.6", "tweetnacl": "^1.0.3", "viem": "^2.43.5", "zod": "^3.24.2" } }, "sha512-S0SOoUsx4WtWqiWZuqslSzhopW/JcrEbnEC5l2sIQH/TABWzR0DgJLy36C43tD6TOJc0tEPN9bHuD+V8DYWomA=="], - "@x402/svm": ["@x402/svm@2.11.0", "", { "dependencies": { "@solana-program/compute-budget": "^0.11.0", "@solana-program/token": "^0.9.0", "@solana-program/token-2022": "^0.6.1", "@x402/core": "~2.11.0" }, "peerDependencies": { "@solana/kit": ">=5.1.0" } }, "sha512-MmhLlEeb3FtgTxO4n3RkZszKEBBnYLfNnX8P3TvqVYP1u9gY2SgbYW4K3TsPAv00edCxoCOqYixI2JurYjW4Sw=="], - "abitype": ["abitype@1.0.6", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A=="], "abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="], @@ -535,7 +542,7 @@ "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], @@ -1215,6 +1222,12 @@ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@coinbase/cdp-sdk/@solana-program/system": ["@solana-program/system@0.10.0", "", { "peerDependencies": { "@solana/kit": "^5.0" } }, "sha512-Go+LOEZmqmNlfr+Gjy5ZWAdY5HbYzk2RBewD9QinEU/bBSzpFfzqDRT55JjFRBGJUvMgf3C2vfXEGT4i8DSI4g=="], + + "@coinbase/cdp-sdk/@solana-program/token": ["@solana-program/token@0.9.0", "", { "peerDependencies": { "@solana/kit": "^5.0" } }, "sha512-vnZxndd4ED4Fc56sw93cWZ2djEeeOFxtaPS8SPf5+a+JZjKA/EnKqzbE1y04FuMhIVrLERQ8uR8H2h72eZzlsA=="], + + "@coinbase/cdp-sdk/@solana/kit": ["@solana/kit@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instruction-plans": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/plugin-core": "5.5.1", "@solana/programs": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-parsed-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/signers": "5.5.1", "@solana/sysvars": "5.5.1", "@solana/transaction-confirmation": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ=="], + "@coinbase/cdp-sdk/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], "@coinbase/x402/@x402/core": ["@x402/core@2.10.0", "", { "dependencies": { "zod": "^3.24.2" } }, "sha512-n9Exnt1HN4LFaINaPYhk6Cy3ICBt0e46XN1Uo5i6efIZfIoqP6pY8ONSX/M9bU4F1fpvMj0JZ3xdcBZCiGInfw=="], @@ -1229,10 +1242,10 @@ "@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/rpc-subscriptions-channel-websocket/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "@solana/rpc-transport-http/undici-types": ["undici-types@8.2.0", "", {}, "sha512-uciYZ5yCmf+QJb18kJw10HjquzM7K0z992vWcI+84KeBpTfXT4hfgfGJ5DQbf/mCBPACofkrjvqgcjZfuujjFA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], @@ -1287,6 +1300,8 @@ "ox/abitype": ["abitype@1.2.4", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-dpKH+N27vRjarMVTFFkeY445VTKftzGWpL0FiT7xmVmzQRKazZexzC5uHG0f6XKsVLAuUlndnbGau6lRejClxg=="], + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/type-utils": "8.59.1", "@typescript-eslint/utils": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag=="], "viem/abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="], @@ -1295,6 +1310,50 @@ "vitest/tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], + "@coinbase/cdp-sdk/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/instruction-plans": ["@solana/instruction-plans@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/promises": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-7z3CB7YMcFKuVvgcnNY8bY6IsZ8LG61Iytbz7HpNVGX2u1RthOs1tRW8luTzSG1MPL0Ox7afyAVMYeFqSPHnaQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/programs": ["@solana/programs@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-7U9kn0Jsx1NuBLn5HRTFYh78MV4XN145Yc3WP/q5BlqAVNlMoU9coG5IUTJIG847TUqC1lRto3Dnpwm6T4YRpA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc": ["@solana/rpc@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/fast-stable-stringify": "5.5.1", "@solana/functional": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-transport-http": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ku8zTUMrkCWci66PRIBC+1mXepEnZH/q1f3ck0kJZ95a06bOTl5KU7HeXWtskkyefzARJ5zvCs54AD5nxjQJ+A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-api": ["@solana/rpc-api@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/rpc-parsed-types": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-XWOQQPhKl06Vj0xi3RYHAc6oEQd8B82okYJ04K7N0Vvy3J4PN2cxeK7klwkjgavdcN9EVkYCChm2ADAtnztKnA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-parsed-types": ["@solana/rpc-parsed-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HEi3G2nZqGEsa3vX6U0FrXLaqnUCg4SKIUrOe8CezD+cSFbRTOn3rCLrUmJrhVyXlHoQVaRO9mmeovk31jWxJg=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-spec-types": ["@solana/rpc-spec-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-6OFKtRpIEJQs8Jb2C4OO8KyP2h2Hy1MFhatMAoXA+0Ik8S3H+CicIuMZvGZ91mIu/tXicuOOsNNLu3HAkrakrw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions": ["@solana/rpc-subscriptions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/fast-stable-stringify": "5.5.1", "@solana/functional": "5.5.1", "@solana/promises": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions-api": "5.5.1", "@solana/rpc-subscriptions-channel-websocket": "5.5.1", "@solana/rpc-subscriptions-spec": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/subscribable": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CTMy5bt/6mDh4tc6vUJms9EcuZj3xvK0/xq8IQ90rhkpYvate91RjBP+egvjgSayUg9yucU9vNuUpEjz4spM7w=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-confirmation": ["@solana/transaction-confirmation@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/promises": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-j4mKlYPHEyu+OD7MBt3jRoX4ScFgkhZC6H65on4Fux6LMScgivPJlwnKoZMnsgxFgWds0pl+BYzSiALDsXlYtw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transactions": ["@solana/transactions@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-8hHtDxtqalZ157pnx6p8k10D7J/KY/biLzfgh9R09VNLLY3Fqi7kJvJCr7M2ik3oRll56pxhraAGCC9yIT6eOA=="], + "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], @@ -1331,6 +1390,120 @@ "viem/ox/abitype": ["abitype@1.2.4", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-dpKH+N27vRjarMVTFFkeY445VTKftzGWpL0FiT7xmVmzQRKazZexzC5uHG0f6XKsVLAuUlndnbGau6lRejClxg=="], + "@coinbase/cdp-sdk/@solana/kit/@solana/accounts/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/accounts/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/addresses/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/codecs/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/codecs/@solana/codecs-data-structures": ["@solana/codecs-data-structures@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-97bJWGyUY9WvBz3mX1UV3YPWGDTez6btCfD0ip3UVEXJbItVuUiOkzcO5iFDUtQT5riKT6xC+Mzl+0nO76gd0w=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/codecs/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/instruction-plans/@solana/promises": ["@solana/promises@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-T9lfuUYkGykJmppEcssNiCf6yiYQxJkhiLPP+pyAc2z84/7r3UVIb2tNJk4A9sucS66pzJnVHZKcZVGUUp6wzA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/instructions/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/keys/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/keys/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/keys/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/offchain-messages/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/offchain-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-97bJWGyUY9WvBz3mX1UV3YPWGDTez6btCfD0ip3UVEXJbItVuUiOkzcO5iFDUtQT5riKT6xC+Mzl+0nO76gd0w=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/offchain-messages/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/offchain-messages/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc/@solana/rpc-transformers": ["@solana/rpc-transformers@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-OsWqLCQdcrRJKvHiMmwFhp9noNZ4FARuMkHT5us3ustDLXaxOjF0gfqZLnMkulSLcKt7TGXqMhBV+HCo7z5M8Q=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc/@solana/rpc-transport-http": ["@solana/rpc-transport-http@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "undici-types": "^7.19.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-yv8GoVSHqEV0kUJEIhkdOVkR2SvJ6yoWC51cJn2rSV7plr6huLGe0JgujCmB7uZhhaLbcbP3zxXxu9sOjsi7Fg=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-api/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-api/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-api/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-api/@solana/rpc-transformers": ["@solana/rpc-transformers@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-OsWqLCQdcrRJKvHiMmwFhp9noNZ4FARuMkHT5us3ustDLXaxOjF0gfqZLnMkulSLcKt7TGXqMhBV+HCo7z5M8Q=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions/@solana/promises": ["@solana/promises@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-T9lfuUYkGykJmppEcssNiCf6yiYQxJkhiLPP+pyAc2z84/7r3UVIb2tNJk4A9sucS66pzJnVHZKcZVGUUp6wzA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-api": ["@solana/rpc-subscriptions-api@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/keys": "5.5.1", "@solana/rpc-subscriptions-spec": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5Oi7k+GdeS8xR2ly1iuSFkAv6CZqwG0Z6b1QZKbEgxadE1XGSDrhM2cn59l+bqCozUWCqh4c/A2znU/qQjROlw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-channel-websocket": ["@solana/rpc-subscriptions-channel-websocket@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/rpc-subscriptions-spec": "5.5.1", "@solana/subscribable": "5.5.1", "ws": "^8.19.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-7tGfBBrYY8TrngOyxSHoCU5shy86iA9SRMRrPSyBhEaZRAk6dnbdpmUTez7gtdVo0BCvh9nzQtUycKWSS7PnFQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/promises": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/subscribable": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iq+rGq5fMKP3/mKHPNB6MC8IbVW41KGZg83Us/+LE3AWOTWV1WT20KT2iH1F1ik9roi42COv/TpoZZvhKj45XQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-transformers": ["@solana/rpc-transformers@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-OsWqLCQdcrRJKvHiMmwFhp9noNZ4FARuMkHT5us3ustDLXaxOjF0gfqZLnMkulSLcKt7TGXqMhBV+HCo7z5M8Q=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions/@solana/subscribable": ["@solana/subscribable@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-9K0PsynFq0CsmK1CDi5Y2vUIJpCqkgSS5yfDN0eKPgHqEptLEaia09Kaxc90cSZDZU5mKY/zv1NBmB6Aro9zQQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-types/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-types/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-types/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/signers/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/signers/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-confirmation/@solana/promises": ["@solana/promises@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-T9lfuUYkGykJmppEcssNiCf6yiYQxJkhiLPP+pyAc2z84/7r3UVIb2tNJk4A9sucS66pzJnVHZKcZVGUUp6wzA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-messages/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-97bJWGyUY9WvBz3mX1UV3YPWGDTez6btCfD0ip3UVEXJbItVuUiOkzcO5iFDUtQT5riKT6xC+Mzl+0nO76gd0w=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-messages/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transactions/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-97bJWGyUY9WvBz3mX1UV3YPWGDTez6btCfD0ip3UVEXJbItVuUiOkzcO5iFDUtQT5riKT6xC+Mzl+0nO76gd0w=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.0", "@typescript-eslint/types": "^8.59.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg=="], @@ -1347,6 +1520,26 @@ "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "@coinbase/cdp-sdk/@solana/kit/@solana/accounts/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/addresses/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/keys/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-api/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-api/@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-channel-websocket/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/rpc/@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], + + "@coinbase/cdp-sdk/@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/examples/api-provider.ts b/examples/api-provider.ts index a332f57..68e812a 100644 --- a/examples/api-provider.ts +++ b/examples/api-provider.ts @@ -9,19 +9,19 @@ * Rails advertised: * - **Tempo MPP** (`tempo/charge` intent) * - **x402 USDC on Base** (EIP-3009) - * - **x402 USDC on Solana** (SPL Token) + * - **MPP USDC on Solana** (`solana/charge` intent) * * The 402 lists all rails neutrally — the agent picks based on what their wallet supports. * * Peer deps to install: - * bun add @agent-score/commerce hono mppx @x402/core @x402/evm @x402/svm + * bun add @agent-score/commerce hono mppx @x402/core @x402/evm @solana/mpp @solana/kit * # @coinbase/x402 optional — only if you want the Coinbase CDP facilitator instead of HTTP * * Env vars: - * MPP_SECRET_KEY — random base64 (mppx merchant secret) - * TEMPO_RECIPIENT — your Tempo wallet for receiving USDC.e - * X402_BASE_RECIPIENT — your Base wallet for receiving USDC - * X402_SOLANA_RECIPIENT — your Solana wallet for receiving USDC + * MPP_SECRET_KEY — random base64 (mppx merchant secret) + * TEMPO_RECIPIENT — your Tempo wallet for receiving USDC.e + * X402_BASE_RECIPIENT — your Base wallet for receiving USDC + * SOLANA_RECIPIENT — your Solana wallet for receiving USDC (MPP solana/charge) * * Run: bun run examples/api-provider.ts */ @@ -46,13 +46,16 @@ const REALM = 'api.example.com'; // ── Boot both rails — Tempo MPP + x402 ────────────────────────────────────── // One-call setup for each. Vendors who only support one rail can drop the other. await createMppxServer({ - rails: { tempo: { recipient: process.env.TEMPO_RECIPIENT! } }, + rails: { + tempo: { recipient: process.env.TEMPO_RECIPIENT! }, + solana: { recipient: process.env.SOLANA_RECIPIENT! }, + }, secretKey: process.env.MPP_SECRET_KEY!, }); await createX402Server({ facilitator: 'http', // 'coinbase' if you have @coinbase/x402 installed - rails: ['x402-base-mainnet', 'x402-solana-mainnet'], + rails: ['x402-base-mainnet'], }); const app = new Hono(); @@ -106,7 +109,7 @@ app.post('/search', async (c) => { request: '', }), paymentDirective({ - rail: 'x402-solana-mainnet', + rail: 'mpp-solana-mainnet', id: `${challengeId}_solana`, realm: REALM, request: '', @@ -121,14 +124,6 @@ app.post('/search', async (c) => { payTo: process.env.X402_BASE_RECIPIENT, extra: { decimals: 6 }, }, - { - scheme: 'exact', - network: networks.solana.mainnet.caip2, - amount: String(PRICE_USDC * 1_000_000), - asset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', - payTo: process.env.X402_SOLANA_RECIPIENT, - extra: { decimals: 6 }, - }, ]; return new Response( JSON.stringify({ payment_required: true, x402Version: 2, accepts }), diff --git a/examples/multi-rail-merchant.ts b/examples/multi-rail-merchant.ts index 947932b..2ead588 100644 --- a/examples/multi-rail-merchant.ts +++ b/examples/multi-rail-merchant.ts @@ -2,7 +2,7 @@ * Example: full multi-rail agent commerce merchant * * Scenario: you want to accept agent payments via every rail — Tempo MPP, x402 on - * Base + Solana, AND Stripe SPT. Identity-gated for compliance. + * Base, MPP solana/charge on Solana, AND Stripe SPT. Identity-gated for compliance. * * The flow on each /purchase POST: * 1. Identity gate (agentscoreGate): KYC + age + jurisdiction + sanctions @@ -16,7 +16,7 @@ * * Peer deps to install: * bun add @agent-score/commerce hono mppx stripe \\ - * @x402/core @x402/evm @x402/svm @x402/extensions + * @x402/core @x402/evm @x402/extensions @solana/mpp @solana/kit * * Env vars: * AGENTSCORE_API_KEY — your AgentScore API key @@ -26,7 +26,7 @@ * STRIPE_PROFILE_ID — your Stripe Connect profile id (for shared payment tokens) * TEMPO_USDC_ADDRESS — USDC token address on Tempo (mainnet or testnet) * X402_BASE_NETWORK — CAIP-2 (eip155:8453 mainnet, eip155:84532 sepolia) - * X402_SVM_NETWORK — CAIP-2 (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp mainnet, solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 devnet) + * SOLANA_NETWORK — 'mainnet-beta' | 'devnet' (default 'mainnet-beta') * REDIS_URL — optional; in-memory PI cache otherwise * * Run: bun run examples/multi-rail-merchant.ts @@ -62,11 +62,15 @@ import Stripe from 'stripe'; const APP_URL = process.env.APP_URL!; const X402_BASE_NETWORK = process.env.X402_BASE_NETWORK ?? networks.base.mainnet.caip2; -const X402_SVM_NETWORK = process.env.X402_SVM_NETWORK ?? networks.solana.mainnet.caip2; +const SOLANA_NETWORK = (process.env.SOLANA_NETWORK ?? 'mainnet-beta') as + | 'mainnet-beta' + | 'devnet' + | 'localnet'; +const SOLANA_CAIP2 = + SOLANA_NETWORK === 'devnet' ? networks.solana.devnet.caip2 : networks.solana.mainnet.caip2; -// Boot-time guard: validate the configured x402 networks are in the supported set. -// Throws on misconfigured deploys before the first request. -validateX402NetworkConfig({ baseNetwork: X402_BASE_NETWORK, svmNetwork: X402_SVM_NETWORK }); +// Boot-time guard: validate the configured x402 base network is in the supported set. +validateX402NetworkConfig({ baseNetwork: X402_BASE_NETWORK }); const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2024-11-20.acacia' as never }); @@ -75,11 +79,10 @@ const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2 // falls back to in-process Map for single-instance dev. const piCache = createPiCache({ redisUrl: process.env.REDIS_URL }); -// ── Boot: x402 server with both EVM + SVM rails ───────────────────────────── +// ── Boot: x402 server with Base rails ─────────────────────────────────────── const x402Server = await createX402Server({ facilitator: 'coinbase', - rails: [X402_BASE_NETWORK.includes('84532') ? 'x402-base-sepolia' : 'x402-base-mainnet', - X402_SVM_NETWORK.includes('EtWTRAB') ? 'x402-solana-devnet' : 'x402-solana-mainnet'], + rails: [X402_BASE_NETWORK.includes('84532') ? 'x402-base-sepolia' : 'x402-base-mainnet'], bazaar: true, // register the @x402/extensions bazaar discovery extension }); @@ -121,7 +124,7 @@ app.post('/purchase', async (c) => { const verified = await verifyX402Request({ request: c.req.raw, isCachedAddress: piCache.hasAddress, - acceptedNetworks: { base: X402_BASE_NETWORK, svm: X402_SVM_NETWORK }, + acceptedNetwork: X402_BASE_NETWORK, }); if (!verified.ok) return c.json(verified.body, verified.status); @@ -154,7 +157,7 @@ app.post('/purchase', async (c) => { await simulateDepositIfTestMode({ getPaymentIntentId: piCache.getPaymentIntentId, depositAddress: verified.signedPayTo, - network: verified.isSolana ? 'solana' : 'base', + network: 'base', stripeSecretKey: process.env.STRIPE_SECRET_KEY!, }); @@ -184,6 +187,10 @@ app.post('/purchase', async (c) => { currency: process.env.TEMPO_USDC_ADDRESS!, testnet: process.env.TEMPO_USDC_ADDRESS === '0x20c0000000000000000000000000000000000000', }, + solana: { + recipient: depositAddresses.solana, + network: SOLANA_NETWORK, + }, stripe: { profileId: process.env.STRIPE_PROFILE_ID!, secretKey: process.env.STRIPE_SECRET_KEY!, @@ -209,7 +216,7 @@ app.post('/purchase', async (c) => { const acceptedMethods = buildAcceptedMethods({ tempo: { recipient: depositAddresses.tempo, network: 'tempo-mainnet', chainId: networks.tempo.mainnet.chainId }, x402_base: { recipient: depositAddresses.base, network: X402_BASE_NETWORK }, - x402_solana: { recipient: depositAddresses.solana, network: X402_SVM_NETWORK }, + solana_mpp: { recipient: depositAddresses.solana, network: SOLANA_CAIP2 }, ...(isWalletAuth ? {} : { stripe: { profileId: process.env.STRIPE_PROFILE_ID! } }), }); @@ -220,7 +227,7 @@ app.post('/purchase', async (c) => { rails: { tempo: { recipient: depositAddresses.tempo }, x402_base: { recipient: depositAddresses.base }, - x402_solana: { recipient: depositAddresses.solana }, + solana_mpp: { recipient: depositAddresses.solana }, stripe: { profileId: process.env.STRIPE_PROFILE_ID! }, }, }); @@ -253,14 +260,6 @@ app.post('/purchase', async (c) => { payTo: depositAddresses.base, maxTimeoutSeconds: 300, }, - { - scheme: 'exact', - network: X402_SVM_NETWORK, - amount: String(Math.round(Number(totalUsd) * 1_000_000)), - asset: USDC.solana.mainnet.mint, - payTo: depositAddresses.solana, - maxTimeoutSeconds: 300, - }, ], resource: { url: c.req.url, mimeType: 'application/json' }, }, diff --git a/package.json b/package.json index 2a6dc2a..6789803 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agent-score/commerce", - "version": "1.3.0", + "version": "1.4.0", "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.", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -126,12 +126,20 @@ "axios": "^1.15.0" }, "peerDependencies": { + "@solana/kit": ">=6.5.0", + "@solana/mpp": ">=0.5.0", "express": ">=4.0.0", "fastify": ">=4.0.0", "hono": ">=4.0.0", "stripe": ">=17.0.0" }, "peerDependenciesMeta": { + "@solana/kit": { + "optional": true + }, + "@solana/mpp": { + "optional": true + }, "express": { "optional": true }, @@ -148,13 +156,14 @@ "devDependencies": { "@coinbase/x402": "^2.1.0", "@eslint/js": "^9.39.4", + "@solana/kit": "^6.8.0", + "@solana/mpp": "^0.5.2", "@types/express": "^5.0.6", "@types/node": "^25.6.0", "@vitest/coverage-v8": "^4.1.5", "@x402/core": "^2.11.0", "@x402/evm": "^2.11.0", "@x402/extensions": "^2.11.0", - "@x402/svm": "^2.11.0", "dotenv": "^17.4.2", "eslint": "^9.39.4", "eslint-plugin-import": "^2.32.0", diff --git a/src/challenge/accepted_methods.ts b/src/challenge/accepted_methods.ts index e41eede..75ac96b 100644 --- a/src/challenge/accepted_methods.ts +++ b/src/challenge/accepted_methods.ts @@ -18,13 +18,27 @@ export interface X402MethodEntry { pay_to: string; } +export interface SolanaMppMethodEntry { + method: 'solana/charge'; + network: string; + token: string; + symbol: string; + decimals: number; + pay_to: string; + fee_payer_key?: string; +} + export interface StripeMethodEntry { method: 'stripe/charge'; rails: ('card' | 'link' | 'shared_payment_token')[]; profile_id: string | null; } -export type AcceptedMethodEntry = TempoMethodEntry | X402MethodEntry | StripeMethodEntry; +export type AcceptedMethodEntry = + | TempoMethodEntry + | X402MethodEntry + | SolanaMppMethodEntry + | StripeMethodEntry; export interface BuildAcceptedMethodsInput { tempo?: { @@ -43,12 +57,13 @@ export interface BuildAcceptedMethodsInput { symbol?: string; decimals?: number; }; - x402_solana?: { + solana_mpp?: { recipient: string; network?: string; token?: string; symbol?: string; decimals?: number; + feePayerKey?: string; }; stripe?: { profileId?: string | null; @@ -88,14 +103,15 @@ export function buildAcceptedMethods(input: BuildAcceptedMethodsInput): Accepted }); } - if (input.x402_solana) { + if (input.solana_mpp) { out.push({ - method: 'x402/exact', - network: input.x402_solana.network ?? 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', - token: input.x402_solana.token ?? 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', - symbol: input.x402_solana.symbol ?? 'USDC', - decimals: input.x402_solana.decimals ?? 6, - pay_to: input.x402_solana.recipient, + method: 'solana/charge', + network: input.solana_mpp.network ?? 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + token: input.solana_mpp.token ?? 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + symbol: input.solana_mpp.symbol ?? 'USDC', + decimals: input.solana_mpp.decimals ?? 6, + pay_to: input.solana_mpp.recipient, + ...(input.solana_mpp.feePayerKey ? { fee_payer_key: input.solana_mpp.feePayerKey } : {}), }); } diff --git a/src/challenge/agent_instructions.ts b/src/challenge/agent_instructions.ts index 0c5a5bf..0a041f7 100644 --- a/src/challenge/agent_instructions.ts +++ b/src/challenge/agent_instructions.ts @@ -39,29 +39,28 @@ export interface AgentInstructions { } const TEMPO_WARNING = - 'Do NOT use `tempo wallet transfer` to pay to the address above. That moves USDC on-chain but does not notify this server, leaving your order in pending_identity state. Use `tempo request` instead — it performs the full MPP handshake (signs, submits Authorization: Payment, waits for server confirmation).'; + 'Do NOT use `tempo wallet transfer`. That moves USDC on-chain without completing the protocol handshake; your order stays in pending_identity. Use `tempo request` instead.'; const X402_WARNING = - 'Do NOT send USDC manually to the x402 deposit addresses (e.g. via a bare wallet `transfer`). Use `agentscore-pay pay` so the X-Payment credential is signed and submitted; otherwise the order stays in pending_identity even though the deposit lands.'; + 'Do NOT send USDC manually to the deposit addresses. Use `agentscore-pay pay` so the credential is signed and submitted; otherwise the order stays in pending_identity even though the deposit lands.'; -const TEMPO_TOOL = '`tempo request` for Tempo USDC (installs via `tempo add request`)'; -const AGENTSCORE_PAY_TOOL = - '`agentscore-pay` (npm: `@agent-score/pay`) — single CLI for x402 on Base + Solana, also speaks tempo MPP via `--chain tempo`'; +const TEMPO_TOOL = '`tempo request` for Tempo USDC'; +const AGENTSCORE_PAY_TOOL = '`agentscore-pay` — Base + Solana + Tempo from one CLI'; const DEFAULT_WALLET_COMPATIBILITY = - 'No specific wallet stack required. The 402 challenge is rail-neutral: any client that can produce a valid MPP credential (Authorization: Payment) or x402 X-Payment header is accepted. The CLI commands above are the easiest path; sign-it-yourself is fine too.'; + 'Any client that can produce a valid MPP credential (Authorization: Payment) or x402 X-Payment header. Use the CLI commands above; sign-it-yourself is also fine.'; function defaultRecommendedTools(howToPay: HowToPayBlock): string[] { const tools: string[] = []; if (howToPay.tempo) tools.push(TEMPO_TOOL); - if (howToPay.tempo || howToPay.x402_base || howToPay.x402_solana) tools.push(AGENTSCORE_PAY_TOOL); + if (howToPay.tempo || howToPay.x402_base || howToPay.solana_mpp) tools.push(AGENTSCORE_PAY_TOOL); return tools; } function defaultWarnings(howToPay: HowToPayBlock): string[] { const w: string[] = []; if (howToPay.tempo) w.push(TEMPO_WARNING); - if (howToPay.x402_base || howToPay.x402_solana) w.push(X402_WARNING); + if (howToPay.x402_base) w.push(X402_WARNING); return w; } @@ -78,12 +77,12 @@ function defaultWarnings(howToPay: HowToPayBlock): string[] { */ /** Symbolic rail keys agent-facing surfaces use to talk about a rail without spelling out * network/scheme details. Same keys as `CompatibleClients` map keys. */ -export type RailKey = 'tempo_mpp' | 'x402_base' | 'x402_solana' | 'stripe'; +export type RailKey = 'tempo_mpp' | 'x402_base' | 'solana_mpp' | 'stripe'; const RAIL_CLIENTS: Record = { tempo_mpp: ['agentscore-pay', 'tempo request', 'x402-proxy'], x402_base: ['agentscore-pay', 'x402-proxy', 'purl (omit --network flag)'], - x402_solana: ['agentscore-pay'], + solana_mpp: ['agentscore-pay'], stripe: ['link-cli'], }; @@ -101,7 +100,7 @@ function defaultCompatibleClients(howToPay: HowToPayBlock): CompatibleClients | const rails: RailKey[] = []; if (howToPay.tempo) rails.push('tempo_mpp'); if (howToPay.x402_base) rails.push('x402_base'); - if (howToPay.x402_solana) rails.push('x402_solana'); + if (howToPay.solana_mpp) rails.push('solana_mpp'); if (howToPay.stripe) rails.push('stripe'); return compatibleClientsByRails(rails); } @@ -111,8 +110,8 @@ function defaultCompatibleClients(howToPay: HowToPayBlock): CompatibleClients | * recommended tools, warnings, wallet-compatibility note, and timeout. * * Defaults adapt to the rails declared in `howToPay`: only tempo-relevant warnings/tools - * appear if `howToPay.tempo` is set, only x402-relevant ones if `x402_base`/`x402_solana` - * are set. Stripe-only merchants get neither rail-specific warning. Vendors override + * appear if `howToPay.tempo` is set, only x402-relevant ones if `x402_base` is set. + * Stripe-only merchants get neither rail-specific warning. Vendors override * `warnings`/`recommendedTools` for full control. */ export function buildAgentInstructions(input: BuildAgentInstructionsInput): AgentInstructions { diff --git a/src/challenge/how_to_pay.ts b/src/challenge/how_to_pay.ts index be808d2..b5619d7 100644 --- a/src/challenge/how_to_pay.ts +++ b/src/challenge/how_to_pay.ts @@ -18,7 +18,7 @@ export interface HowToPayStripeEntry { export interface HowToPayBlock { tempo?: HowToPayRailEntry; x402_base?: HowToPayRailEntry; - x402_solana?: HowToPayRailEntry; + solana_mpp?: HowToPayRailEntry; stripe?: HowToPayStripeEntry; } @@ -33,7 +33,7 @@ export interface BuildHowToPayInput { rails: { tempo?: { recipient: string; networkName?: string; chainId?: number; recommend?: 'tempo' | 'agentscore-pay' | 'both' }; x402_base?: { recipient: string; network?: string }; - x402_solana?: { recipient: string; network?: string }; + solana_mpp?: { recipient: string; network?: string }; stripe?: { profileId?: string | null; productName?: string }; }; /** Placeholder text for the operator token in commands. Defaults to ''. */ @@ -89,7 +89,7 @@ export function buildHowToPay(input: BuildHowToPayInput): HowToPayBlock { : recommend === 'agentscore-pay' ? { alternative_command: tempoCommand } : {}), - what_it_does: `Hits this endpoint, receives this same 402, signs the MPP challenge on ${networkName}, and submits the credential back via Authorization: Payment. Either client (tempo request or agentscore-pay pay --chain tempo) works — both run the full MPP handshake.`, + what_it_does: `Pays via Tempo USDC on ${networkName}.`, }; } @@ -99,19 +99,17 @@ export function buildHowToPay(input: BuildHowToPayInput): HowToPayBlock { setup: PAY_SETUP_BASE, prerequisite: `Run \`agentscore-pay balance --chain base\` and confirm USDC balance on Base (${network}) is at least $${maxSpend}. If the CLI is not installed, run the setup commands above first.`, command: `agentscore-pay pay POST ${input.url} --chain base -H 'X-Operator-Token: ${opToken}' -H 'Content-Type: application/json' -d '${input.retryBodyJson}' --max-spend ${maxSpend}`, - what_it_does: - 'Hits this endpoint, receives this same 402, signs an EIP-3009 USDC TransferWithAuthorization on Base, submits via X-Payment header. Server verifies + settles via the Coinbase facilitator + returns 200 with the completed order.', + what_it_does: 'Pays via USDC on Base.', }; } - if (input.rails.x402_solana) { - const network = input.rails.x402_solana.network ?? 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'; - block.x402_solana = { + if (input.rails.solana_mpp) { + const network = input.rails.solana_mpp.network ?? 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'; + block.solana_mpp = { setup: PAY_SETUP_SOLANA, prerequisite: `Run \`agentscore-pay balance --chain solana\` and confirm USDC balance on Solana (${network}) is at least $${maxSpend}. If the CLI is not installed, run the setup commands above first.`, command: `agentscore-pay pay POST ${input.url} --chain solana -H 'X-Operator-Token: ${opToken}' -H 'Content-Type: application/json' -d '${input.retryBodyJson}' --max-spend ${maxSpend}`, - what_it_does: - 'Hits this endpoint, receives this same 402, signs an SPL Token TransferChecked transaction on Solana, submits via X-Payment header. Server verifies + settles via the Coinbase facilitator + returns 200 with the completed order.', + what_it_does: 'Pays via USDC on Solana.', }; } @@ -138,7 +136,7 @@ export function buildHowToPay(input: BuildHowToPayInput): HowToPayBlock { `link-cli mpp pay ${input.url} --spend-request-id $SPEND_ID --method POST --data '${input.retryBodyJson}' --header 'X-Operator-Token: ${opToken}' --output-json`, ]; stripe.what_it_does_link_cli = - 'For users who have a Stripe Link wallet: step 1 mints a one-time-use SharedPaymentToken scoped to this purchase and pushes a notification to the user for approval (blocks until approved); step 2 submits the SPT via the MPP handshake along with your AgentScore operator credential.'; + 'Mints a one-time-use SharedPaymentToken scoped to this purchase (user approves in Link wallet), then submits it as the payment credential.'; } else if (linkCliBlocked) { stripe.note = `link-cli SPT path not available for this purchase — Stripe link-cli caps spend requests at $500.00 ($50000 cents); your total is $${totalNum}. Use your own Stripe account with the SharedPaymentToken API instead.`; } diff --git a/src/discovery/llms_txt.ts b/src/discovery/llms_txt.ts index de44772..bf0e1c3 100644 --- a/src/discovery/llms_txt.ts +++ b/src/discovery/llms_txt.ts @@ -32,24 +32,18 @@ export function llmsTxtIdentitySection(input: LlmsTxtIdentitySectionInput = {}): .filter(Boolean) .join(', ')}.` : ''; - return `## Choose your identity header + return `## Identity -AgentScore provides cross-merchant identity — one KYC works for every AgentScore-gated endpoint, no re-verification per site. Pick the right header based on how you plan to pay: +AgentScore identity is reusable across every AgentScore-gated merchant — one KYC, no re-verification per site. Pick a header: -- **Wallet-auth** (preferred for agents with a stable crypto wallet): send \`X-Wallet-Address: 0x...\`. Only works on rails that carry a wallet signature — Tempo MPP and x402 EIP-3009. The wallet you claim MUST sign the payment; we verify that the payment signer resolves to the same operator as the claimed wallet. No TTL, no rotation. -- **Operator-token** (works for every rail): send \`X-Operator-Token: opc_...\`. Required when paying with Stripe SPT or card (those rails have no wallet signer). Reusable across every AgentScore merchant until the token expires. -- **Neither**: you'll get 403 with \`verify_url\` + \`poll_secret\` — complete the session flow once, then reuse the credential (or any wallet that pays under it) forever. - -If you reach another AgentScore-gated endpoint in the future, the same credential and/or any linked wallet already works — skip the session flow. - -## Memory contract - -First-encounter 403 responses (and the POST /v1/sessions success body) include an \`agent_memory\` block describing how AgentScore identity works across merchants. LLM-hosted agents should persist its contents (the pattern, never secrets) so subsequent AgentScore-gated endpoints become returning-customer encounters.${complianceNote}`; +- **\`X-Wallet-Address: 0x...\` or base58** — works on signing rails (Tempo, x402, Solana MPP). The wallet you claim must sign the payment. +- **\`X-Operator-Token: opc_...\`** — works on every rail, including Stripe SPT. Reusable across AgentScore merchants until expiry. +- **Neither** — you get a 403 with \`verify_url\`. Complete the session flow once and reuse the resulting \`opc_...\` everywhere.${complianceNote}`; } export interface LlmsTxtPaymentSectionInput { /** Symbolic rail names supported. */ - rails: ('tempo-mainnet' | 'tempo-testnet' | 'x402-base-mainnet' | 'x402-base-sepolia' | 'x402-solana-mainnet' | 'x402-solana-devnet' | 'stripe-spt' | string)[]; + rails: ('tempo-mainnet' | 'tempo-testnet' | 'x402-base-mainnet' | 'x402-base-sepolia' | 'mpp-solana-mainnet' | 'mpp-solana-devnet' | 'stripe-spt' | string)[]; /** Merchant URL — used in the example commands. */ appUrl: string; /** @@ -93,8 +87,8 @@ function llmsTxtPaymentSectionCompact(input: LlmsTxtPaymentSectionInput): string if (hasRailFamily(rails, 'x402-base-')) { lines.push('- **x402 USDC on Base** (EIP-3009) — `agentscore-pay pay POST ' + input.appUrl + ' --chain base -H "X-Operator-Token: opc_..." -d \'{...}\'`'); } - if (hasRailFamily(rails, 'x402-solana-')) { - lines.push('- **x402 USDC on Solana** (SPL Token) — `agentscore-pay pay POST ' + input.appUrl + ' --chain solana -H "X-Operator-Token: opc_..." -d \'{...}\'`'); + if (hasRailFamily(rails, 'mpp-solana-')) { + lines.push('- **USDC on Solana** — `agentscore-pay pay POST ' + input.appUrl + ' --chain solana -H "X-Operator-Token: opc_..." -d \'{...}\'`'); } if (rails.includes('stripe-spt')) { lines.push('- **Stripe Shared Payment Token** — agent mints SPT (own Stripe account scoped to networkId, OR `link-cli spend-request create --credential-type shared_payment_token --network-id ...`)'); @@ -111,77 +105,67 @@ function llmsTxtPaymentSectionVerbose(input: LlmsTxtPaymentSectionInput): string const tempoChain = input.tempoChainId ?? 4217; const hasTempo = hasRailFamily(rails, 'tempo-'); const hasBase = hasRailFamily(rails, 'x402-base-'); - const hasSolana = hasRailFamily(rails, 'x402-solana-'); + const hasSolana = hasRailFamily(rails, 'mpp-solana-'); const hasStripe = rails.includes('stripe-spt'); const baseNetworkName = isTestnetRail(rails, 'x402-base-') ? 'Base Sepolia' : 'Base'; - const solanaNetworkName = isTestnetRail(rails, 'x402-solana-') ? 'Solana devnet' : 'Solana'; + const solanaNetworkName = isTestnetRail(rails, 'mpp-solana-') ? 'Solana devnet' : 'Solana'; const lines: string[] = ['## Payment', '']; - lines.push('This is an agent-first API. All payments are initiated and completed by agents. The 402 challenge advertises:'); + lines.push('Accepted rails:'); lines.push(''); - if (hasTempo) lines.push('- **Tempo USDC via MPP** (on-chain stablecoin)'); - if (hasBase || hasSolana) { - const chains = [hasBase && `${baseNetworkName} (EIP-3009)`, hasSolana && `${solanaNetworkName} (SPL Token)`].filter(Boolean).join(' and '); - lines.push(`- **x402 USDC** on ${chains}, via the Coinbase facilitator`); - } - if (hasStripe) lines.push('- **Stripe Shared Payment Token** (agent mints SPT on their Stripe account scoped to our networkId in the challenge, submits it in the credential)'); + if (hasTempo) lines.push('- **USDC on Tempo**'); + if (hasBase) lines.push(`- **USDC on ${baseNetworkName}**`); + if (hasSolana) lines.push(`- **USDC on ${solanaNetworkName}**`); + if (hasStripe) lines.push('- **Stripe Shared Payment Token**'); lines.push(''); if (hasTempo) { - lines.push('### How to pay with Tempo'); + lines.push('### Pay with Tempo'); lines.push(''); - lines.push('1. Install the Tempo CLI: curl -fsSL https://tempo.xyz/install | bash'); - lines.push('2. Log in to your Tempo Wallet: tempo wallet login (passkey auth in browser)'); - lines.push(`3. Confirm your balance: tempo wallet whoami (need USDC.e on ${tempoNetwork}, chain ${tempoChain})`); - lines.push('4. If balance is zero, fund it: tempo wallet fund'); - lines.push(''); - lines.push('Then use `tempo request` to make the paid purchase:'); + lines.push('```bash'); + lines.push('curl -fsSL https://tempo.xyz/install | bash'); + lines.push('tempo wallet login'); + lines.push(`tempo wallet whoami # need USDC.e on ${tempoNetwork} (chain ${tempoChain})`); + lines.push('tempo wallet fund # if zero'); lines.push(''); lines.push('tempo request -X POST \\'); - lines.push(' -H "X-Operator-Token: opc_your_credential" \\'); - lines.push(' -H "Content-Type: application/json" \\'); + lines.push(' -H "X-Operator-Token: opc_..." \\'); lines.push(" --json '{...}' \\"); lines.push(' --max-spend N \\'); lines.push(` ${input.appUrl}`); - lines.push(''); - lines.push(`\`tempo request\` handles the full MPP handshake: sends the POST, receives the 402 challenge, signs the payment on ${tempoNetwork}, submits the credential, and returns the completed order.`); + lines.push('```'); lines.push(''); } if (hasBase || hasSolana) { const chainsLabel = [hasBase && baseNetworkName, hasSolana && solanaNetworkName].filter(Boolean).join(' or '); const flags = [hasBase && '`--chain base`', hasSolana && '`--chain solana`'].filter(Boolean).join(' or '); - lines.push(`### How to pay with x402 (${chainsLabel})`); + lines.push(`### Pay with ${chainsLabel}`); lines.push(''); - lines.push('1. Install the agentscore-pay CLI: npm install -g @agent-score/pay (or: brew install agentscore/tap/agentscore-pay)'); - lines.push(`2. Create a wallet on your chain of choice: agentscore-pay wallet create ${flags}`); - lines.push(`3. Fund the printed address with USDC on ${chainsLabel}`); - lines.push(`4. Confirm balance: agentscore-pay balance ${flags}`); - lines.push(''); - lines.push('Then submit the paid purchase:'); + lines.push('```bash'); + lines.push('npm install -g @agent-score/pay'); + lines.push(`agentscore-pay wallet create ${flags}`); + lines.push(`agentscore-pay balance ${flags} # fund the printed address with USDC`); lines.push(''); lines.push(`agentscore-pay pay POST ${input.appUrl} \\`); lines.push(` ${hasBase ? '--chain base' : '--chain solana'} \\`); - lines.push(' -H "X-Operator-Token: opc_your_credential" \\'); - lines.push(' -H "Content-Type: application/json" \\'); + lines.push(' -H "X-Operator-Token: opc_..." \\'); lines.push(" -d '{...}' \\"); lines.push(' --max-spend N'); - lines.push(''); - const handshakeChains = [hasBase && 'EIP-3009 (Base)', hasSolana && 'SPL Token (Solana)'].filter(Boolean).join(' or '); - lines.push(`The CLI handles the full x402 handshake: hits the URL, parses the 402 challenge, signs the ${handshakeChains} transaction, submits via X-Payment header, and returns the completed order.`); + lines.push('```'); lines.push(''); } if (hasStripe) { - lines.push('### How to pay with Stripe SPT'); + lines.push('### Pay with Stripe SPT'); lines.push(''); - lines.push('Mint a SharedPaymentToken scoped to the profile_id advertised in `accepted_methods.stripe.profile_id`, then submit via `Authorization: Payment` MPP header with `method=stripe/charge`. Either bring your own Stripe account or use `link-cli spend-request create --credential-type shared_payment_token --network-id ...` for users with Stripe Link wallets.'); + lines.push('Mint a SharedPaymentToken scoped to the `profile_id` from the 402 body, then submit via `Authorization: Payment` with `method=stripe/charge`. Either your own Stripe account or `link-cli spend-request create --credential-type shared_payment_token --network-id ...` for Stripe Link wallets.'); lines.push(''); } - lines.push('IMPORTANT: Do NOT use `tempo wallet transfer` or send USDC manually to the x402 deposit addresses — those bypass the payment handshake and your order will stay in pending_identity.'); + lines.push('IMPORTANT: Use the CLIs above. Raw on-chain transfers (e.g. `tempo wallet transfer`, sending USDC manually to deposit addresses) bypass the protocol handshake and the order stays in pending_identity.'); if (hasBase || hasSolana) { - lines.push('IMPORTANT: x402 payments must be the exact amount specified in the 402 challenge. Overpayments and underpayments cannot be matched and funds may be unrecoverable.'); + lines.push('IMPORTANT: Pay the exact amount in the 402 challenge. Overpayments and underpayments cannot be matched.'); } lines.push(''); return lines.join('\n'); diff --git a/src/discovery/skill_md.ts b/src/discovery/skill_md.ts index 30228ea..bf70bd8 100644 --- a/src/discovery/skill_md.ts +++ b/src/discovery/skill_md.ts @@ -116,14 +116,14 @@ export interface BuildSkillMdInput { const RAIL_LABELS: Record = { tempo_mpp: 'MPP on Tempo', x402_base: 'x402 on Base', - x402_solana: 'x402 on Solana', + solana_mpp: 'MPP on Solana', stripe: 'Stripe Shared Payment Token', }; const RAIL_NOTES: Record = { tempo_mpp: 'USDC. Use `agentscore-pay --chain tempo` (or `tempo request`); MPP credential goes in `Authorization: Payment`.', x402_base: 'USDC (EIP-3009). Use `agentscore-pay`; X-Payment header carries the signed credential.', - x402_solana: 'USDC (SPL). Use `agentscore-pay`; X-Payment header carries the signed credential.', + solana_mpp: 'USDC (SPL). Use `agentscore-pay --chain solana`; MPP credential goes in `Authorization: Payment`.', stripe: 'Card via Link wallet. Use `@stripe/link-cli` — `agentscore-pay` emits the handoff hint when this rail is picked.', }; diff --git a/src/identity/ucp.ts b/src/identity/ucp.ts index 25c5101..a4fdde4 100644 --- a/src/identity/ucp.ts +++ b/src/identity/ucp.ts @@ -60,7 +60,7 @@ export interface UCPCapability { } export interface UCPPaymentHandler { - /** Handler name — `stripe`, `tempo`, `x402-base`, `x402-solana`, etc. */ + /** Handler name — `stripe`, `tempo`, `x402-base`, `solana`, etc. */ name: string; /** Handler config — recipient address, profile id, etc. */ config?: Record; diff --git a/src/payment/mppx_server.ts b/src/payment/mppx_server.ts index 5b35884..e227f78 100644 --- a/src/payment/mppx_server.ts +++ b/src/payment/mppx_server.ts @@ -1,8 +1,10 @@ import { createMppxStripe } from '../stripe-multichain/mppx_stripe'; import { USDC } from './usdc'; +export type SolanaMppNetwork = 'mainnet-beta' | 'devnet' | 'localnet'; + export interface CreateMppxServerOptions { - /** Symbolic rail config — commerce wires the boilerplate (tempo.charge, mppStripe.charge, etc.). */ + /** Symbolic rail config — commerce wires the boilerplate (tempo.charge, mppStripe.charge, solana.charge, etc.). */ rails?: { /** One-shot Tempo USDC charge (intent: 'charge'). */ tempo?: { @@ -12,6 +14,38 @@ export interface CreateMppxServerOptions { /** Use Tempo testnet (Moderato). Default false. */ testnet?: boolean; }; + /** + * Solana SPL charge (intent: 'charge'). Bakes createAssociatedTokenIdempotent + * into the buyer's tx so payments work against any payTo, fresh or warmed. + * + * Requires `@solana/mpp` + `@solana/kit` peer deps. + * Underlying spec: paymentauth.org/draft-solana-charge-00. + */ + solana?: { + /** Base58-encoded Solana recipient public key. */ + recipient: string; + /** SPL token mint (base58). Default: USDC for the selected network. */ + currency?: string; + /** Token decimals. Default 6 (USDC). */ + decimals?: number; + /** Solana network. Default 'mainnet-beta'. */ + network?: SolanaMppNetwork; + /** Custom RPC URL. Default: public RPC for the network. */ + rpcUrl?: string; + /** + * Optional fee-payer signer for server-side fee sponsorship. When provided, + * the server's pubkey is advertised as `feePayerKey` in the 402 challenge and + * the server co-signs settle txs as fee payer (so buyers don't need SOL, and + * ATA-creation rent is server-funded). Construct via + * `@solana/kit`'s `createKeyPairSignerFromBytes` or equivalent. + * + * Typed as `unknown` to avoid a hard dep on @solana/kit at this layer; pass any + * `TransactionPartialSigner` from `@solana/kit`. + */ + signer?: unknown; + /** SPL token program hint (TOKEN_PROGRAM or TOKEN_2022_PROGRAM). Auto-detected when omitted. */ + tokenProgram?: string; + }; /** * Tempo session (intent: 'session') — pay-as-you-go channel for repeated calls or * SSE-streamed responses. Vendor brings their own ChannelStore (DB-backed implementation @@ -63,6 +97,18 @@ interface MppxModule { }; } +interface SolanaMppModule { + charge?: (opts: { + recipient: string; + currency?: string; + decimals?: number; + network?: string; + rpcUrl?: string; + signer?: unknown; + tokenProgram?: string; + }) => unknown; +} + /** * One-call mppx server setup. Wires `tempo.charge(...)`, `tempo.session(...)`, and Stripe SPT * (via createMppxStripe) from symbolic rail config, replacing the boilerplate of constructing @@ -132,6 +178,32 @@ export async function createMppxServer(opts: CreateMppxServerOptions): Promise('@solana/mpp/server'); + if (!solanaMpp?.charge) { + throw new Error( + '@solana/mpp not installed — `npm install @solana/mpp @solana/kit` to use the solana rail.', + ); + } + const s = opts.rails.solana; + const network: SolanaMppNetwork = s.network ?? 'mainnet-beta'; + const defaultMint = + network === 'mainnet-beta' ? USDC.solana.mainnet.mint : USDC.solana.devnet.mint; + const defaultDecimals = + network === 'mainnet-beta' ? USDC.solana.mainnet.decimals : USDC.solana.devnet.decimals; + methods.push( + solanaMpp.charge({ + recipient: s.recipient, + currency: s.currency ?? defaultMint, + decimals: s.decimals ?? defaultDecimals, + network, + ...(s.rpcUrl ? { rpcUrl: s.rpcUrl } : {}), + ...(s.signer ? { signer: s.signer } : {}), + ...(s.tokenProgram ? { tokenProgram: s.tokenProgram } : {}), + }), + ); + } + if (opts.rails?.stripe) { const stripeMethod = await createMppxStripe(opts.rails.stripe); methods.push(stripeMethod); diff --git a/src/payment/rails.ts b/src/payment/rails.ts index 282cd75..3cbbe92 100644 --- a/src/payment/rails.ts +++ b/src/payment/rails.ts @@ -59,15 +59,15 @@ export const rails = { decimals: USDC.base.sepolia.decimals, asset: USDC.base.sepolia.address, }, - 'x402-solana-mainnet': { - method: 'x402', + 'mpp-solana-mainnet': { + method: 'solana', network: networks.solana.mainnet.caip2, currency: USDC.solana.mainnet.mint, decimals: USDC.solana.mainnet.decimals, asset: USDC.solana.mainnet.mint, }, - 'x402-solana-devnet': { - method: 'x402', + 'mpp-solana-devnet': { + method: 'solana', network: networks.solana.devnet.caip2, currency: USDC.solana.devnet.mint, decimals: USDC.solana.devnet.decimals, diff --git a/src/payment/x402_server.ts b/src/payment/x402_server.ts index 17ba3ad..26bd6ea 100644 --- a/src/payment/x402_server.ts +++ b/src/payment/x402_server.ts @@ -4,11 +4,6 @@ import { registerX402SchemesV1V2 } from './x402'; export type X402SymbolicRail = | 'x402-base-mainnet' | 'x402-base-sepolia' - | 'x402-solana-mainnet' - | 'x402-solana-devnet' - // Upto rails — pay UP TO a max amount via Permit2 (vs EIP-3009 fixed-amount). Use for - // variable-cost APIs (LLM tokens, bandwidth, etc.). Solana svm doesn't ship an upto - // scheme yet — only EVM rails support it. | 'x402-base-mainnet-upto' | 'x402-base-sepolia-upto'; @@ -25,7 +20,7 @@ export interface CreateX402ServerOptions { facilitator?: X402FacilitatorChoice; /** * Symbolic rail names to register schemes for. Each gets v1+v2 dual-register applied. - * Requires the corresponding peer dep installed (`@x402/evm` for base, `@x402/svm` for solana). + * Requires `@x402/evm` peer dep installed. */ rails?: X402SymbolicRail[]; /** Advanced: register custom {network, scheme} pairs (in addition to or instead of `rails`). */ @@ -58,7 +53,6 @@ interface X402CoreModule { interface SchemeModule { ExactEvmScheme?: new () => unknown; - ExactSvmScheme?: new () => unknown; UptoEvmScheme?: new () => unknown; } @@ -80,18 +74,11 @@ interface BazaarModule { * * const server = await createX402Server({ * facilitator: 'coinbase', - * rails: ['x402-base-mainnet', 'x402-solana-mainnet'], + * rails: ['x402-base-mainnet'], * bazaar: true, * }); */ export async function createX402Server(opts: CreateX402ServerOptions = {}): Promise { - // Eager validation — surface bad rail combinations before paying for peer-dep resolution. - for (const rail of opts.rails ?? []) { - if (rail.startsWith('x402-solana') && rail.endsWith('-upto')) { - throw new Error(`Rail "${rail}" not supported — @x402/svm does not ship an upto scheme yet (EVM-only).`); - } - } - const x402Core = (await dynamicImport('@x402/core/server')) ?? null; /* v8 ignore start -- peer-dep-absence guard; @x402/core is installed in test env */ if (!x402Core) { @@ -122,7 +109,6 @@ export async function createX402Server(opts: CreateX402ServerOptions = {}): Prom let evmExactModule: SchemeModule | null = null; let evmUptoModule: SchemeModule | null = null; - let svmModule: SchemeModule | null = null; for (const rail of opts.rails ?? []) { const isUpto = rail.endsWith('-upto'); if (rail.startsWith('x402-base')) { @@ -146,18 +132,6 @@ export async function createX402Server(opts: CreateX402ServerOptions = {}): Prom /* v8 ignore stop */ registerX402SchemesV1V2(server, network, new evmExactModule.ExactEvmScheme()); } - } else if (rail.startsWith('x402-solana')) { - svmModule ??= await dynamicImport('@x402/svm/exact/server'); - /* v8 ignore start -- peer-dep-absence guard; @x402/svm is installed in test env */ - if (!svmModule?.ExactSvmScheme) { - throw new Error('@x402/svm not installed — `npm install @x402/svm` for x402 solana rails.'); - } - /* v8 ignore stop */ - const network = - rail === 'x402-solana-mainnet' - ? networks.solana.mainnet.caip2 - : networks.solana.devnet.caip2; - registerX402SchemesV1V2(server, network, new svmModule.ExactSvmScheme()); } } diff --git a/src/payment/x402_validation.ts b/src/payment/x402_validation.ts index 30719e9..8f4c97b 100644 --- a/src/payment/x402_validation.ts +++ b/src/payment/x402_validation.ts @@ -3,20 +3,18 @@ * * Two layers of validation every x402-accepting merchant repeats: * - * - **Boot-time**: validate the configured `X402_BASE_NETWORK` + `X402_SVM_NETWORK` - * env vars are in the supported set, and aren't pointing at the same network - * family. Failing loud at boot is much better than per-request "unsupported + * - **Boot-time**: validate the configured `X402_BASE_NETWORK` env var is in the + * supported set. Failing loud at boot is much better than per-request "unsupported * network" errors after a misconfigured deploy. * - * - **Per-request**: when an x402 X-Payment header arrives, parse the base64 - * payload, extract the signed network + payTo, validate against the merchant's - * accepted networks, validate the payTo address shape per network family, and - * check that the payTo was minted by THIS merchant (cache hit). Each step has - * its own denial code and `next_steps` shape — getting the message right by - * hand across 4 conditions is fiddly. + * - **Per-request**: when an x402 X-Payment header arrives, parse the base64 payload, + * extract the signed network + payTo, validate against the merchant's accepted + * network, validate the payTo address shape, and check that the payTo was minted by + * THIS merchant (cache hit). Each step has its own denial code and `next_steps` + * shape — getting the message right by hand across 4 conditions is fiddly. */ -import { networks, networkFamily } from './networks'; +import { networks } from './networks'; /** CAIP-2 networks the commerce SDK supports for x402 Base (EVM USDC). */ export const X402_SUPPORTED_BASE_NETWORKS = new Set([ @@ -24,23 +22,14 @@ export const X402_SUPPORTED_BASE_NETWORKS = new Set([ networks.base.sepolia.caip2, ]); -/** CAIP-2 networks the commerce SDK supports for x402 Solana (SPL Token USDC). */ -export const X402_SUPPORTED_SVM_NETWORKS = new Set([ - networks.solana.mainnet.caip2, - networks.solana.devnet.caip2, -]); - export interface ValidateX402NetworkConfigInput { /** CAIP-2 base network string (e.g. `'eip155:8453'`). */ baseNetwork: string; - /** CAIP-2 SVM network string (e.g. `'solana:5eykt…'`). */ - svmNetwork: string; } /** - * Boot-time guard: throws if either network isn't supported, or if both point at the - * same family (which would silently route Solana payments to the Base path or vice - * versa). Call once at module init / server boot. + * Boot-time guard: throws if the base network isn't supported. Call once at module + * init / server boot. * * Throws `Error` with a message that names the unsupported value AND lists the valid * options — agents tracking down a misconfigured deploy don't need to grep for the @@ -52,20 +41,9 @@ export function validateX402NetworkConfig(input: ValidateX402NetworkConfigInput) `X402_BASE_NETWORK=${input.baseNetwork} is not supported. Use one of: ${[...X402_SUPPORTED_BASE_NETWORKS].join(', ')}`, ); } - if (!X402_SUPPORTED_SVM_NETWORKS.has(input.svmNetwork)) { - throw new Error( - `X402_SVM_NETWORK=${input.svmNetwork} is not supported. Use one of: ${[...X402_SUPPORTED_SVM_NETWORKS].join(', ')}`, - ); - } - if (input.baseNetwork === input.svmNetwork) { - throw new Error( - `X402_BASE_NETWORK and X402_SVM_NETWORK must be different (both set to ${input.baseNetwork}).`, - ); - } } const EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/; -const SOLANA_ADDRESS_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/; export interface VerifyX402RequestInput { /** The incoming Request — `verifyX402Request` reads the X-Payment / payment-signature header. */ @@ -74,14 +52,8 @@ export interface VerifyX402RequestInput { * (typically `piCache.hasAddress`). The cache check defends against agents replaying * credentials against attacker-controlled deposit addresses. */ isCachedAddress: (address: string) => Promise; - /** The merchant's accepted networks per family. Both required — pass the same env - * values you fed to `validateX402NetworkConfig`. */ - acceptedNetworks: { - /** CAIP-2 base network — e.g. `'eip155:8453'`. */ - base: string; - /** CAIP-2 SVM network — e.g. `'solana:5eykt…'`. */ - svm: string; - }; + /** The merchant's accepted Base network. CAIP-2, e.g. `'eip155:8453'`. */ + acceptedNetwork: string; } export type VerifyX402RequestResult = @@ -93,8 +65,6 @@ export type VerifyX402RequestResult = signedNetwork: string; /** The on-chain pay-to address the agent signed for (already validated). */ signedPayTo: string; - /** True when the signed network is Solana — useful for routing settlement. */ - isSolana: boolean; } | { ok: false; @@ -115,7 +85,7 @@ export type VerifyX402RequestResult = }; const REGENERATE_WARNING = - "If you're trying to pay with Tempo USDC, use `tempo request` (sends Authorization: Payment), not a manual X-Payment header. Do NOT use `tempo wallet transfer` — that sends USDC on-chain but will not complete the MPP handshake. For x402 on Base/Solana, use `agentscore-pay pay` so the X-Payment credential is signed and submitted; bare wallet transfers do not complete the handshake."; + 'Use `agentscore-pay pay --chain base` (or `tempo request` for Tempo USDC) so the credential is signed and submitted via the protocol handshake. Do NOT use `tempo wallet transfer` — that sends USDC on-chain but does not complete the handshake.'; function regenerateBody(message: string, userMessage: string) { return { @@ -133,8 +103,8 @@ function regenerateBody(message: string, userMessage: string) { * confirm the address was minted by this merchant. One call replaces ~45 lines of * inline header decode + regex validation + cache lookup. * - * Returns `{ok: true, payload, signedNetwork, signedPayTo, isSolana}` when valid; the - * caller passes `payload` straight into `processX402Settle`. + * Returns `{ok: true, payload, signedNetwork, signedPayTo}` when valid; the caller + * passes `payload` straight into `processX402Settle`. * * Returns `{ok: false, body, status}` when invalid — the merchant just does * `return c.json(body, status)` (or framework equivalent). @@ -174,21 +144,18 @@ export async function verifyX402Request(input: VerifyX402RequestInput): Promise< const signedNetwork = payload.accepted?.network; const signedPayTo = payload.accepted?.payTo; - if (!signedNetwork || (signedNetwork !== input.acceptedNetworks.base && signedNetwork !== input.acceptedNetworks.svm)) { + if (!signedNetwork || signedNetwork !== input.acceptedNetwork) { return { ok: false, status: 400, body: regenerateBody( - `Unsupported x402 network ${signedNetwork ?? ''}; this server accepts ${input.acceptedNetworks.base} (Base) and ${input.acceptedNetworks.svm} (Solana)`, - 'The credential signed for an unsupported network. Pick one of the accepted networks from the 402 challenge and re-sign.', + `Unsupported x402 network ${signedNetwork ?? ''}; this server accepts ${input.acceptedNetwork}.`, + 'The credential signed for an unsupported network. Pick the accepted network from the 402 challenge and re-sign.', ), }; } - const isSolana = networkFamily(signedNetwork) === 'solana'; - const addressShapeOk = isSolana - ? typeof signedPayTo === 'string' && SOLANA_ADDRESS_RE.test(signedPayTo) - : typeof signedPayTo === 'string' && EVM_ADDRESS_RE.test(signedPayTo); + const addressShapeOk = typeof signedPayTo === 'string' && EVM_ADDRESS_RE.test(signedPayTo); if (!signedPayTo || !addressShapeOk) { return { @@ -212,5 +179,5 @@ export async function verifyX402Request(input: VerifyX402RequestInput): Promise< }; } - return { ok: true, payload, signedNetwork, signedPayTo, isSolana }; + return { ok: true, payload, signedNetwork, signedPayTo }; } diff --git a/src/signer.ts b/src/signer.ts index 7384d2b..a9fa779 100644 --- a/src/signer.ts +++ b/src/signer.ts @@ -2,20 +2,16 @@ * Payment-signer extraction. * * Shared between merchants and the gate — both need to recover the on-chain signer from - * a payment credential without duplicating code. Three rails carry a wallet signer: + * a payment credential without duplicating code. Two paths carry a recoverable wallet + * signer here: * * - **Tempo MPP** — `Authorization: Payment ` header; credential `source` is a DID * of the form `did:pkh:eip155::
`. * - **x402 EIP-3009** (EVM, e.g. Base/Sepolia) — `payment-signature` / `x-payment` header; * decoded payload carries `payload.authorization.from`. - * - **x402 SVM** (Solana) — same headers, but `payload.transaction` is a base64-encoded - * Solana transaction; the SPL Token TransferChecked instruction's source-account owner - * is the signer. Recovered via `@x402/svm`'s `decodeTransactionFromPayload` + - * `getTokenPayerFromTransaction`. * - * `mppx` and `@x402/svm` are optional peer dependencies — we import them dynamically so - * merchants who don't use those rails don't need to install them. The EVM x402 path is pure - * JSON parsing with no external dep. + * `mppx` is an optional peer dependency — we import it dynamically so merchants who don't + * use MPP don't need to install it. The EVM x402 path is pure JSON parsing with no external dep. */ export type SignerNetwork = 'evm' | 'solana'; @@ -62,44 +58,16 @@ export async function extractPaymentSigner( } } - // x402 — base64 JSON. Network identifier on `accepted.network` selects the extraction - // path: `eip155:*` → EIP-3009 `payload.authorization.from`; `solana:*` → SPL Token - // payer recovered from the encoded transaction. + // x402 — base64 JSON, EIP-3009 only. EVM `payload.authorization.from` is the signer. if (x402PaymentHeader) { try { const decoded = atob(x402PaymentHeader); const parsed = JSON.parse(decoded) as { - accepted?: { network?: string }; - payload?: { authorization?: { from?: string }; transaction?: string }; + payload?: { authorization?: { from?: string } }; }; - const network = parsed?.accepted?.network ?? ''; - - if (network.startsWith('eip155:')) { - const from = parsed?.payload?.authorization?.from; - if (typeof from === 'string' && /^0x[0-9a-fA-F]{40}$/.test(from)) { - return { address: from.toLowerCase(), network: 'evm' }; - } - } else if (network.startsWith('solana:')) { - const transaction = parsed?.payload?.transaction; - if (typeof transaction === 'string') { - const moduleName = '@x402/svm'; - const svm = (await import(moduleName).catch(() => null)) as { - decodeTransactionFromPayload?: (p: { transaction: string }) => unknown; - getTokenPayerFromTransaction?: (tx: unknown) => string | undefined; - } | null; - if (svm?.decodeTransactionFromPayload && svm.getTokenPayerFromTransaction) { - const tx = svm.decodeTransactionFromPayload({ transaction }); - const payer = svm.getTokenPayerFromTransaction(tx); - if (typeof payer === 'string' && payer.length > 0) return { address: payer, network: 'solana' }; - } - } - } else { - // Back-compat: a payload without an `accepted.network` field still uses EIP-3009 - // shape if `payload.authorization.from` looks EVM. Older x402 clients emitted these. - const from = parsed?.payload?.authorization?.from; - if (typeof from === 'string' && /^0x[0-9a-fA-F]{40}$/.test(from)) { - return { address: from.toLowerCase(), network: 'evm' }; - } + const from = parsed?.payload?.authorization?.from; + if (typeof from === 'string' && /^0x[0-9a-fA-F]{40}$/.test(from)) { + return { address: from.toLowerCase(), network: 'evm' }; } } catch (err) { console.warn('[gate] x402 signer extraction failed:', err instanceof Error ? err.message : err); diff --git a/tests/challenge/accepted_methods.test.ts b/tests/challenge/accepted_methods.test.ts index c59375f..235a968 100644 --- a/tests/challenge/accepted_methods.test.ts +++ b/tests/challenge/accepted_methods.test.ts @@ -12,15 +12,22 @@ describe('buildAcceptedMethods', () => { const methods = buildAcceptedMethods({ tempo: { recipient: '0xt' }, x402_base: { recipient: '0xb' }, - x402_solana: { recipient: 'sol1' }, + solana_mpp: { recipient: 'sol1', feePayerKey: 'sponsorPubkey' }, stripe: { profileId: 'acct_test' }, }); expect(methods.map((m) => m.method)).toEqual([ 'tempo/charge', 'x402/exact', - 'x402/exact', + 'solana/charge', 'stripe/charge', ]); + expect(methods[2]).toMatchObject({ pay_to: 'sol1', fee_payer_key: 'sponsorPubkey' }); + }); + + it('emits solana/charge without fee_payer_key when feePayerKey is omitted', () => { + const methods = buildAcceptedMethods({ solana_mpp: { recipient: 'sol1' } }); + expect(methods[0]).toMatchObject({ method: 'solana/charge', pay_to: 'sol1' }); + expect((methods[0] as { fee_payer_key?: string }).fee_payer_key).toBeUndefined(); }); it('omits rails the vendor did not pass', () => { diff --git a/tests/challenge/agent_instructions.test.ts b/tests/challenge/agent_instructions.test.ts index ef27571..e4f2a9d 100644 --- a/tests/challenge/agent_instructions.test.ts +++ b/tests/challenge/agent_instructions.test.ts @@ -5,29 +5,33 @@ describe('buildAgentInstructions', () => { it('wraps howToPay with sensible defaults for tools/wallet/warnings/timeout', () => { const instructions = buildAgentInstructions({ howToPay: { tempo: { command: 'x', what_it_does: 'y' } } }); expect(instructions.how_to_pay.tempo).toBeDefined(); - expect(instructions.recommended_tools).toContain('`tempo request` for Tempo USDC (installs via `tempo add request`)'); + expect(instructions.recommended_tools).toContain('`tempo request` for Tempo USDC'); expect(instructions.timeout_seconds).toBe(300); expect(instructions.warnings.length).toBeGreaterThan(0); expect(instructions.warnings[0]).toContain('tempo wallet transfer'); }); it('warnings + tools default to ONLY the rails actually present in howToPay', () => { - // x402-only merchant: no tempo warning/tool const x402Only = buildAgentInstructions({ howToPay: { x402_base: { command: 'x', what_it_does: 'y' } }, }); expect(x402Only.warnings.some((w) => w.includes('tempo wallet transfer'))).toBe(false); - expect(x402Only.warnings.some((w) => w.includes('x402 deposit addresses'))).toBe(true); + expect(x402Only.warnings.some((w) => w.includes('deposit addresses'))).toBe(true); expect(x402Only.recommended_tools.some((t) => t.includes('tempo request'))).toBe(false); expect(x402Only.recommended_tools.some((t) => t.includes('agentscore-pay'))).toBe(true); - // tempo-only merchant: no x402 warning const tempoOnly = buildAgentInstructions({ howToPay: { tempo: { command: 'x', what_it_does: 'y' } }, }); - expect(tempoOnly.warnings.some((w) => w.includes('x402 deposit addresses'))).toBe(false); + expect(tempoOnly.warnings.some((w) => w.includes('deposit addresses'))).toBe(false); + + const solanaOnly = buildAgentInstructions({ + howToPay: { solana_mpp: { command: 'x', what_it_does: 'y' } }, + }); + expect(solanaOnly.warnings.some((w) => w.includes('tempo wallet transfer'))).toBe(false); + expect(solanaOnly.warnings.some((w) => w.includes('deposit addresses'))).toBe(false); + expect(solanaOnly.recommended_tools.some((t) => t.includes('agentscore-pay'))).toBe(true); - // stripe-only merchant: no rail-specific warnings/tools const stripeOnly = buildAgentInstructions({ howToPay: { stripe: { prerequisite: 'x', instructions: 'y' } }, }); diff --git a/tests/challenge/how_to_pay.test.ts b/tests/challenge/how_to_pay.test.ts index b5cd37d..ee06702 100644 --- a/tests/challenge/how_to_pay.test.ts +++ b/tests/challenge/how_to_pay.test.ts @@ -30,13 +30,13 @@ describe('buildHowToPay', () => { expect(block.tempo!.alternative_command).toContain('tempo request'); }); - it('builds x402_base + x402_solana entries with --chain flags', () => { + it('builds x402_base + solana_mpp entries with --chain flags', () => { const block = buildHowToPay({ ...baseInput, - rails: { x402_base: { recipient: '0xb' }, x402_solana: { recipient: 'sol1' } }, + rails: { x402_base: { recipient: '0xb' }, solana_mpp: { recipient: 'sol1' } }, }); expect(block.x402_base!.command).toContain('--chain base'); - expect(block.x402_solana!.command).toContain('--chain solana'); + expect(block.solana_mpp!.command).toContain('--chain solana'); }); it('builds stripe entry with link-cli commands when profileId set + amount under cap', () => { diff --git a/tests/discovery/llms_txt.test.ts b/tests/discovery/llms_txt.test.ts index 4736d29..68fb185 100644 --- a/tests/discovery/llms_txt.test.ts +++ b/tests/discovery/llms_txt.test.ts @@ -9,10 +9,10 @@ describe('llmsTxtIdentitySection', () => { it('returns the standard wallet vs operator-token explanation when enabled', () => { const section = llmsTxtIdentitySection({ agentscore: true }); - expect(section).toContain('## Choose your identity header'); + expect(section).toContain('## Identity'); expect(section).toContain('X-Wallet-Address'); expect(section).toContain('X-Operator-Token'); - expect(section).toContain('cross-merchant'); + expect(section).toContain('AgentScore'); }); it('appends compliance summary when provided', () => { @@ -35,10 +35,19 @@ describe('llmsTxtPaymentSection', () => { }); expect(section).toContain('Tempo USDC'); expect(section).toContain('x402 USDC on Base'); - expect(section).not.toContain('x402 USDC on Solana'); + expect(section).not.toContain('USDC on Solana'); expect(section).not.toContain('Stripe Shared Payment Token'); }); + it('emits the Solana MPP rail line when mpp-solana-mainnet is configured', () => { + const section = llmsTxtPaymentSection({ + rails: ['mpp-solana-mainnet'], + appUrl: 'https://merchant.example', + }); + expect(section).toContain('USDC on Solana'); + expect(section).toContain('--chain solana'); + }); + it('includes Stripe + link-cli when stripe-spt is configured', () => { const section = llmsTxtPaymentSection({ rails: ['stripe-spt'], appUrl: 'https://x' }); expect(section).toContain('Stripe Shared Payment Token'); @@ -59,13 +68,13 @@ describe('llmsTxtPaymentSection', () => { tempoNetworkName: 'tempo-mainnet', tempoChainId: 4217, }); - expect(section).toContain('### How to pay with Tempo'); + expect(section).toContain('### Pay with Tempo'); expect(section).toContain('curl -fsSL https://tempo.xyz/install'); expect(section).toContain('tempo wallet login'); expect(section).toContain('tempo wallet whoami'); - expect(section).toContain('USDC.e on tempo-mainnet, chain 4217'); + expect(section).toContain('USDC.e on tempo-mainnet (chain 4217)'); expect(section).toContain('tempo request -X POST'); - expect(section).toContain('### How to pay with x402'); + expect(section).toContain('### Pay with Base'); expect(section).toContain('npm install -g @agent-score/pay'); expect(section).toContain('agentscore-pay wallet create'); expect(section).toContain('https://my.merchant'); @@ -77,9 +86,10 @@ describe('llmsTxtPaymentSection', () => { appUrl: 'https://x', verbose: true, }); - expect(section).toContain('Tempo USDC'); - expect(section).not.toContain('### How to pay with x402'); - expect(section).not.toContain('### How to pay with Stripe'); + expect(section).toContain('USDC on Tempo'); + expect(section).not.toContain('### Pay with Base'); + expect(section).not.toContain('### Pay with Solana'); + expect(section).not.toContain('### Pay with Stripe'); }); it('emits the exact-amount warning when x402 rails are configured', () => { @@ -88,7 +98,7 @@ describe('llmsTxtPaymentSection', () => { appUrl: 'https://x', verbose: true, }); - expect(section).toContain('exact amount specified in the 402 challenge'); + expect(section).toContain('exact amount in the 402 challenge'); }); it('emits the Stripe SPT block when stripe-spt is configured', () => { @@ -97,17 +107,17 @@ describe('llmsTxtPaymentSection', () => { appUrl: 'https://x', verbose: true, }); - expect(section).toContain('### How to pay with Stripe SPT'); + expect(section).toContain('### Pay with Stripe SPT'); expect(section).toContain('SharedPaymentToken'); }); - it('handles solana-only without base', () => { + it('handles solana-only via mpp-solana-mainnet', () => { const section = llmsTxtPaymentSection({ - rails: ['x402-solana-mainnet'], + rails: ['mpp-solana-mainnet'], appUrl: 'https://x', verbose: true, }); - expect(section).toContain('### How to pay with x402 (Solana)'); + expect(section).toContain('### Pay with Solana'); expect(section).toContain('--chain solana'); expect(section).not.toContain('--chain base'); }); @@ -134,7 +144,7 @@ describe('buildLlmsTxt', () => { agentscoreIdentity: { agentscore: true }, payment: { rails: ['tempo-mainnet'], appUrl: 'https://acme.example' }, }); - expect(doc).toContain('## Choose your identity header'); + expect(doc).toContain('## Identity'); expect(doc).toContain('## Payment'); }); }); diff --git a/tests/discovery/skill_md.test.ts b/tests/discovery/skill_md.test.ts index fbb474b..baaf0d7 100644 --- a/tests/discovery/skill_md.test.ts +++ b/tests/discovery/skill_md.test.ts @@ -7,7 +7,7 @@ describe('buildSkillMd', () => { description: 'Buy wine from Martin Estate via an AI agent', homepage: 'https://martin-estate.com', merchantName: 'Martin Estate', - acceptedRails: ['tempo_mpp', 'x402_base', 'x402_solana', 'stripe'] as const, + acceptedRails: ['tempo_mpp', 'x402_base', 'solana_mpp', 'stripe'] as const, endpoints: [ { method: 'GET' as const, path: '/api/v1/wines', authRequired: false, description: 'Wine catalog' }, { method: 'POST' as const, path: '/api/v1/orders', authRequired: true, description: 'Place order' }, @@ -200,7 +200,7 @@ describe('buildSkillMd', () => { expect(out).toContain('agentscore-pay, tempo request, x402-proxy'); expect(out).toContain('**x402 on Base**'); expect(out).toContain('agentscore-pay, x402-proxy, purl (omit --network flag)'); - expect(out).toContain('**x402 on Solana**'); + expect(out).toContain('**MPP on Solana**'); expect(out).toContain('**Stripe Shared Payment Token**'); expect(out).toContain('link-cli'); }); @@ -208,7 +208,7 @@ describe('buildSkillMd', () => { it('omits rails not declared in acceptedRails', () => { const out = buildSkillMd({ ...baseInput, - acceptedRails: ['tempo_mpp', 'x402_base', 'x402_solana'], + acceptedRails: ['tempo_mpp', 'x402_base', 'solana_mpp'], }); expect(out).toContain('**MPP on Tempo**'); expect(out).not.toContain('**Stripe Shared Payment Token**'); diff --git a/tests/payment/headers.test.ts b/tests/payment/headers.test.ts index d52c16c..31f6bdd 100644 --- a/tests/payment/headers.test.ts +++ b/tests/payment/headers.test.ts @@ -34,11 +34,11 @@ describe('buildPaymentHeaders', () => { realm: 'a.example', rails: [ { rail: 'tempo-mainnet', amountUsd: 1, recipient: '0xa' }, - { rail: 'x402-solana-mainnet', amountUsd: 1, recipient: '0xb' }, + { rail: 'mpp-solana-mainnet', amountUsd: 1, recipient: 'GEQg2TM4VL315Bd4LLkGrhBjdNfoatKjCJYHBDPM3D74' }, ], }); expect(result['www-authenticate']).toContain('id="ord_3-tempo-mainnet"'); - expect(result['www-authenticate']).toContain('id="ord_3-x402-solana-mainnet"'); + expect(result['www-authenticate']).toContain('id="ord_3-mpp-solana-mainnet"'); }); it('emits PAYMENT-REQUIRED header when x402.accepts is provided', () => { diff --git a/tests/payment/mppx_server.test.ts b/tests/payment/mppx_server.test.ts index f48ec1a..de6961c 100644 --- a/tests/payment/mppx_server.test.ts +++ b/tests/payment/mppx_server.test.ts @@ -31,4 +31,39 @@ describe('createMppxServer', () => { }); expect(server).toBeDefined(); }); + + it('registers the solana mpp charge method (mainnet default, no signer)', async () => { + const server = await createMppxServer({ + rails: { solana: { recipient: 'GEQg2TM4VL315Bd4LLkGrhBjdNfoatKjCJYHBDPM3D74' } }, + secretKey: 'mpp_secret_xxx', + }); + expect(server).toBeDefined(); + }); + + it('registers the solana devnet mpp charge method', async () => { + const server = await createMppxServer({ + rails: { + solana: { + recipient: 'GEQg2TM4VL315Bd4LLkGrhBjdNfoatKjCJYHBDPM3D74', + network: 'devnet', + }, + }, + secretKey: 'mpp_secret_xxx', + }); + expect(server).toBeDefined(); + }); + + it('forwards optional rpcUrl and tokenProgram fields when provided', async () => { + const server = await createMppxServer({ + rails: { + solana: { + recipient: 'GEQg2TM4VL315Bd4LLkGrhBjdNfoatKjCJYHBDPM3D74', + rpcUrl: 'https://api.mainnet-beta.solana.com', + tokenProgram: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', + }, + }, + secretKey: 'mpp_secret_xxx', + }); + expect(server).toBeDefined(); + }); }); diff --git a/tests/payment/x402_server.test.ts b/tests/payment/x402_server.test.ts index e414702..17789cd 100644 --- a/tests/payment/x402_server.test.ts +++ b/tests/payment/x402_server.test.ts @@ -14,11 +14,6 @@ describe('createX402Server', () => { expect(server).toBeDefined(); }); - it('registers ExactSvmScheme for x402 solana rails', async () => { - const server = await createX402Server({ rails: ['x402-solana-mainnet'], initialize: false }); - expect(server).toBeDefined(); - }); - it('registers UptoEvmScheme for upto rails', async () => { const server = await createX402Server({ rails: ['x402-base-mainnet-upto'], initialize: false }); expect(server).toBeDefined(); @@ -48,9 +43,9 @@ describe('createX402Server', () => { expect(server).toBeDefined(); }); - it('registers multiple rails in one call (base + solana)', async () => { + it('registers multiple Base rails in one call (mainnet + sepolia)', async () => { const server = await createX402Server({ - rails: ['x402-base-mainnet', 'x402-base-sepolia', 'x402-solana-mainnet', 'x402-solana-devnet'], + rails: ['x402-base-mainnet', 'x402-base-sepolia'], initialize: false, }); expect(server).toBeDefined(); diff --git a/tests/payment/x402_server_upto.test.ts b/tests/payment/x402_server_upto.test.ts index bcaec58..041e571 100644 --- a/tests/payment/x402_server_upto.test.ts +++ b/tests/payment/x402_server_upto.test.ts @@ -2,12 +2,6 @@ import { describe, expect, it } from 'vitest'; import { createX402Server } from '../../src/payment/x402_server'; describe('createX402Server — upto scheme support', () => { - it('throws a clear error when upto is requested on Solana (svm has no upto)', async () => { - await expect( - createX402Server({ rails: ['x402-solana-mainnet-upto' as never] }), - ).rejects.toThrow(/upto scheme yet/); - }); - it('successfully registers an UptoEvmScheme on x402 base rails', async () => { const server = await createX402Server({ rails: ['x402-base-mainnet-upto'], initialize: false }); expect(server).toBeDefined(); diff --git a/tests/payment/x402_validation.test.ts b/tests/payment/x402_validation.test.ts index 2394701..c7a30be 100644 --- a/tests/payment/x402_validation.test.ts +++ b/tests/payment/x402_validation.test.ts @@ -2,10 +2,7 @@ import { describe, expect, it } from 'vitest'; import { networks } from '../../src/payment/networks'; import { validateX402NetworkConfig, verifyX402Request } from '../../src/payment/x402_validation'; -const accepted = { - base: networks.base.sepolia.caip2, - svm: networks.solana.devnet.caip2, -}; +const acceptedNetwork = networks.base.sepolia.caip2; const evm = (header: string) => new Request('https://m.example/x', { headers: { 'x-payment': header } }); @@ -15,20 +12,12 @@ const sig = (header: string) => describe('validateX402NetworkConfig', () => { it('throws when baseNetwork is unsupported and includes the supported list', () => { expect(() => - validateX402NetworkConfig({ baseNetwork: 'eip155:99999', svmNetwork: accepted.svm }), + validateX402NetworkConfig({ baseNetwork: 'eip155:99999' }), ).toThrow(/X402_BASE_NETWORK=eip155:99999.*not supported.*eip155:8453/); }); - it('throws when svmNetwork is unsupported', () => { - expect(() => - validateX402NetworkConfig({ baseNetwork: accepted.base, svmNetwork: 'solana:fake' }), - ).toThrow(/X402_SVM_NETWORK=solana:fake.*not supported/); - }); - - it('returns silently for a valid pair', () => { - expect(() => - validateX402NetworkConfig({ baseNetwork: accepted.base, svmNetwork: accepted.svm }), - ).not.toThrow(); + it('returns silently for a valid base network', () => { + expect(() => validateX402NetworkConfig({ baseNetwork: acceptedNetwork })).not.toThrow(); }); }); @@ -37,7 +26,7 @@ describe('verifyX402Request', () => { const res = await verifyX402Request({ request: new Request('https://m.example/x'), isCachedAddress: async () => true, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(false); if (!res.ok) { @@ -50,31 +39,29 @@ describe('verifyX402Request', () => { it('returns ok:true for a valid evm payload', async () => { const payTo = '0x' + 'a'.repeat(40); - const headerValue = Buffer.from(JSON.stringify({ accepted: { network: accepted.base, payTo } })).toString('base64'); + const headerValue = Buffer.from(JSON.stringify({ accepted: { network: acceptedNetwork, payTo } })).toString('base64'); const res = await verifyX402Request({ request: evm(headerValue), isCachedAddress: async () => true, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(true); if (res.ok) { expect(res.signedPayTo).toBe(payTo); - expect(res.isSolana).toBe(false); } }); - it('returns ok:true for a valid solana payload via payment-signature header', async () => { - const payTo = 'GEQg2TM4VL315Bd4LLkGrhBjdNfoatKjCJYHBDPM3D74'; - const headerValue = Buffer.from(JSON.stringify({ accepted: { network: accepted.svm, payTo } })).toString('base64'); + it('returns ok:true for a valid evm payload via payment-signature header', async () => { + const payTo = '0x' + 'c'.repeat(40); + const headerValue = Buffer.from(JSON.stringify({ accepted: { network: acceptedNetwork, payTo } })).toString('base64'); const res = await verifyX402Request({ request: sig(headerValue), isCachedAddress: async () => true, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(true); if (res.ok) { - expect(res.isSolana).toBe(true); - expect(res.signedNetwork).toBe(accepted.svm); + expect(res.signedNetwork).toBe(acceptedNetwork); } }); @@ -82,7 +69,7 @@ describe('verifyX402Request', () => { const res = await verifyX402Request({ request: evm('not-base64-json'), isCachedAddress: async () => true, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(false); if (!res.ok) expect(res.body.error.message).toMatch(/not valid base64 JSON/); @@ -93,46 +80,46 @@ describe('verifyX402Request', () => { const res = await verifyX402Request({ request: evm(headerValue), isCachedAddress: async () => true, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(false); if (!res.ok) expect(res.body.error.message).toContain('Unsupported x402 network'); }); - it('returns ok:false when network is not in the merchant accepted set', async () => { + it('returns ok:false when network is not the merchant accepted Base network', async () => { const headerValue = Buffer.from( JSON.stringify({ accepted: { network: 'eip155:1', payTo: '0x' + 'a'.repeat(40) } }), ).toString('base64'); const res = await verifyX402Request({ request: evm(headerValue), isCachedAddress: async () => true, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(false); if (!res.ok) expect(res.body.error.message).toContain('eip155:1'); }); - it('returns ok:false when payTo is malformed for EVM (wrong length)', async () => { + it('returns ok:false for any solana payload (Solana is no longer supported under x402; route via MPP)', async () => { const headerValue = Buffer.from( - JSON.stringify({ accepted: { network: accepted.base, payTo: '0xabc' } }), + JSON.stringify({ accepted: { network: networks.solana.mainnet.caip2, payTo: 'GEQg2TM4VL315Bd4LLkGrhBjdNfoatKjCJYHBDPM3D74' } }), ).toString('base64'); const res = await verifyX402Request({ request: evm(headerValue), isCachedAddress: async () => true, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(false); - if (!res.ok) expect(res.body.error.message).toMatch(/missing or malformed accepted.payTo/); + if (!res.ok) expect(res.body.error.message).toContain('Unsupported x402 network'); }); - it('returns ok:false when payTo is malformed for Solana (wrong charset)', async () => { + it('returns ok:false when payTo is malformed for EVM (wrong length)', async () => { const headerValue = Buffer.from( - JSON.stringify({ accepted: { network: accepted.svm, payTo: '0xnotbase58' } }), + JSON.stringify({ accepted: { network: acceptedNetwork, payTo: '0xabc' } }), ).toString('base64'); const res = await verifyX402Request({ request: evm(headerValue), isCachedAddress: async () => true, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(false); if (!res.ok) expect(res.body.error.message).toMatch(/missing or malformed accepted.payTo/); @@ -140,23 +127,23 @@ describe('verifyX402Request', () => { it('returns ok:false when payTo is missing entirely', async () => { const headerValue = Buffer.from( - JSON.stringify({ accepted: { network: accepted.base } }), + JSON.stringify({ accepted: { network: acceptedNetwork } }), ).toString('base64'); const res = await verifyX402Request({ request: evm(headerValue), isCachedAddress: async () => true, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(false); }); it('returns ok:false when isCachedAddress returns false', async () => { const payTo = '0x' + 'b'.repeat(40); - const headerValue = Buffer.from(JSON.stringify({ accepted: { network: accepted.base, payTo } })).toString('base64'); + const headerValue = Buffer.from(JSON.stringify({ accepted: { network: acceptedNetwork, payTo } })).toString('base64'); const res = await verifyX402Request({ request: evm(headerValue), isCachedAddress: async () => false, - acceptedNetworks: accepted, + acceptedNetwork, }); expect(res.ok).toBe(false); if (!res.ok) expect(res.body.error.message).toMatch(/payTo address not found in cache/); diff --git a/tests/signer.test.ts b/tests/signer.test.ts index c08877d..439b939 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -141,86 +141,16 @@ describe('extractPaymentSignerAddress — MPP path', () => { }); }); -describe('extractPaymentSignerAddress — Solana x402 path', () => { - // The Solana branch is selected when `accepted.network` starts with `solana:`. It - // dynamic-imports `@x402/svm` (optional peer dep) and recovers the SPL Token payer - // from the encoded transaction. Critically: payer is base58 and case-sensitive — we - // return it verbatim, never lowercase. - const SOL_PAYER = 'G2ajX7CrLGoaL8ncaDYNCQoV9b7XhwGF1RzAyKDEZgNZ'; - - it('extracts the case-preserved Solana payer when @x402/svm is available', async () => { - vi.doMock('@x402/svm', () => ({ - decodeTransactionFromPayload: vi.fn(() => ({})), - getTokenPayerFromTransaction: vi.fn(() => SOL_PAYER), - })); - const { extractPaymentSignerAddress: freshExtract } = await import( - `../src/signer?svm=${freshImportKey()}` - ); - const header = encodeX402({ - accepted: { network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1' }, - payload: { transaction: Buffer.from('not-a-real-tx').toString('base64') }, - }); - const result = await freshExtract(makeRequest(), header); - expect(result).toBe(SOL_PAYER); - // Verbatim case — Solana would silently break if we lowercased here. - expect(result).not.toBe(SOL_PAYER.toLowerCase()); - vi.doUnmock('@x402/svm'); - }); - - it('returns null on a Solana payload when @x402/svm is unavailable (graceful when peer dep absent)', async () => { +describe('extractPaymentSignerAddress — Solana credentials are no longer extracted', () => { + it('returns null for a credential carrying a Solana network (Solana goes through MPP solana/charge; gate signer-extraction skipped)', async () => { const header = encodeX402({ - accepted: { network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1' }, + accepted: { network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' }, payload: { transaction: Buffer.from('any-tx').toString('base64') }, }); - // No vi.doMock for @x402/svm — the dynamic import resolves to null in the test env. - const result = await extractPaymentSignerAddress(makeRequest(), header); - expect(result).toBeNull(); - }); - - it('returns null on a Solana payload missing the transaction field', async () => { - vi.doMock('@x402/svm', () => ({ - decodeTransactionFromPayload: vi.fn(), - getTokenPayerFromTransaction: vi.fn(), - })); - const { extractPaymentSignerAddress: freshExtract } = await import( - `../src/signer?svm-missing-tx=${freshImportKey()}` - ); - const header = encodeX402({ - accepted: { network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1' }, - payload: {}, - }); - expect(await freshExtract(makeRequest(), header)).toBeNull(); - vi.doUnmock('@x402/svm'); - }); - - it('returns null when @x402/svm returns no payer (malformed Solana transaction)', async () => { - vi.doMock('@x402/svm', () => ({ - decodeTransactionFromPayload: vi.fn(() => ({})), - getTokenPayerFromTransaction: vi.fn(() => undefined), - })); - const { extractPaymentSignerAddress: freshExtract } = await import( - `../src/signer?svm-no-payer=${freshImportKey()}` - ); - const header = encodeX402({ - accepted: { network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1' }, - payload: { transaction: Buffer.from('tx').toString('base64') }, - }); - expect(await freshExtract(makeRequest(), header)).toBeNull(); - vi.doUnmock('@x402/svm'); - }); - - it('takes the EIP-3009 branch (not Solana) when network is eip155:*', async () => { - const header = encodeX402({ - accepted: { network: 'eip155:84532' }, - payload: { authorization: { from: SIGNER_MIXED } }, - }); - expect(await extractPaymentSignerAddress(makeRequest(), header)).toBe(SIGNER_LOWER); + expect(await extractPaymentSignerAddress(makeRequest(), header)).toBeNull(); }); - it('falls back to legacy EIP-3009 extraction when accepted.network is missing (back-compat)', async () => { - // Older x402 clients (pre-multi-network) didn't emit `accepted.network`. We still - // extract `payload.authorization.from` if it looks EVM — preserves wallet-auth on - // those clients without forcing them to upgrade. + it('still extracts EIP-3009 EVM signer when accepted.network is missing (back-compat)', async () => { const header = encodeX402({ payload: { authorization: { from: SIGNER_MIXED } } }); expect(await extractPaymentSignerAddress(makeRequest(), header)).toBe(SIGNER_LOWER); }); From 7df77db13f7e70e3e4a9c34bb26915dccf69ac0c Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 17:34:51 -0700 Subject: [PATCH 06/19] docs(commerce): clean up x402-Solana remnants in README + example metadata Tightens the audit findings post-1.4.0 release: - README quick-start sample updated to the new single `acceptedNetwork` signature (was the dropped `acceptedNetworks: { base, svm }` object) - examples/README + CLAUDE.md per-example descriptions advertise Solana via MPP `solana/charge`, not x402 SVM - api-provider.ts inline comment clarifies which header carries which protocol now that x402 is base-only - well_known_mpp test passes through `x402.networks: ['base']` instead of `['base', 'solana']`; merchant builders should advertise base only --- CLAUDE.md | 2 +- README.md | 2 +- examples/README.md | 4 ++-- examples/api-provider.ts | 6 +++--- tests/discovery/well_known_mpp.test.ts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a9e3121..32fdbd8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,7 +40,7 @@ Peer-dep pattern: payment/x402/mppx/stripe modules `dynamic import` at runtime | Example | Scenario | |---|---| -| `api-provider.ts` | Per-call API billing on multiple rails: Tempo MPP + x402 (Base + Solana); no compliance gate | +| `api-provider.ts` | Per-call API billing on multiple rails: Tempo MPP + x402 Base + Solana MPP; no compliance gate | | `identity-only.ts` | Compliance gate without payment (vendor handles their own) | | `multi-rail-merchant.ts` | Full agent-commerce: identity + Tempo MPP + x402 + Stripe SPT | | `stripe-multichain-merchant.ts` | Stripe-anchored multichain (PaymentIntent → tempo/base/solana deposit addresses) | diff --git a/README.md b/README.md index 6ef2888..2e43b0f 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ app.post("/purchase", async (c) => { const verified = await verifyX402Request({ request: c.req.raw, isCachedAddress: piCache.hasAddress, - acceptedNetworks: { base: X402_BASE, svm: X402_SVM }, + acceptedNetwork: X402_BASE, }); if (!verified.ok) return c.json(verified.body, verified.status); diff --git a/examples/README.md b/examples/README.md index c941caf..0c9b71f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,9 +4,9 @@ Runnable, copy-pasteable example integrations covering the most common merchant | Example | Scenario | What it shows | |---|---|---| -| [`api-provider.ts`](./api-provider.ts) | API provider (Exa-style) | Per-call billing on multiple rails: Tempo MPP + x402 (Base + Solana). No identity gate, no compliance — pay-or-fail. | +| [`api-provider.ts`](./api-provider.ts) | API provider (Exa-style) | Per-call billing on multiple rails: Tempo MPP + x402 Base + Solana MPP. No identity gate, no compliance; pay-or-fail. | | [`identity-only.ts`](./identity-only.ts) | Compliance gate without payment | Wraps any endpoint with KYC + age + jurisdiction checks; vendor handles their own payment | -| [`multi-rail-merchant.ts`](./multi-rail-merchant.ts) | Full agent-commerce merchant | Identity gate + Tempo MPP + x402 Base + x402 Solana + Stripe SPT, all rails accepted, full 402 builder | +| [`multi-rail-merchant.ts`](./multi-rail-merchant.ts) | Full agent-commerce merchant | Identity gate + Tempo MPP + x402 Base + Solana MPP + Stripe SPT, all rails accepted, full 402 builder | | [`stripe-multichain-merchant.ts`](./stripe-multichain-merchant.ts) | Stripe-anchored multi-chain | Stripe PaymentIntent with deposit_options for tempo/base/solana; crypto deposits flow through Stripe | | [`variable-cost-merchant.ts`](./variable-cost-merchant.ts) | Pay-per-actual-usage (LLM, transcode, etc.) | Same use case on **two protocols**: x402 upto (Permit2 authorize-max → Settlement-Overrides settle-actual) AND MPP tempo session (channel + SSE + mid-stream vouchers). Vendor offers both so agents pick whichever their wallet supports. | | [`compliance-merchant.ts`](./compliance-merchant.ts) | Regulated-goods merchant (wine, cannabis, etc.) | Full compliance gate (KYC + sanctions + age + jurisdiction) + custom `onDenied` composing commerce helpers: `verificationAgentInstructions`, `isFixableDenial`, `buildContactSupportNextSteps`, `denialReasonToBody`/`denialReasonStatus`, `buildSignerMismatchBody`. Shows how vendors write only the business-specific branches and let commerce handle the rest. | diff --git a/examples/api-provider.ts b/examples/api-provider.ts index 68e812a..f725107 100644 --- a/examples/api-provider.ts +++ b/examples/api-provider.ts @@ -138,9 +138,9 @@ app.post('/search', async (c) => { ); } - // Payment present — validate via the right server based on which header arrived: - // Authorization: Payment ... → MPP (tempo) — call mppx.compose() to verify + settle - // payment-signature / x-payment → x402 (base or solana) — call verifyX402Request + // Payment present; validate via the right server based on which header arrived: + // Authorization: Payment ... → MPP (tempo or solana); call mppx.compose() to verify + settle + // payment-signature / x-payment → x402 base; call verifyX402Request // then processX402Settle from `@agent-score/commerce/payment` for a single-call // verify + settle (returns base64 paymentResponseHeader for the success response). // See multi-rail-merchant.ts for the full drop-in pattern. diff --git a/tests/discovery/well_known_mpp.test.ts b/tests/discovery/well_known_mpp.test.ts index e0eee62..2c9dffc 100644 --- a/tests/discovery/well_known_mpp.test.ts +++ b/tests/discovery/well_known_mpp.test.ts @@ -33,7 +33,7 @@ describe('buildWellKnownMpp', () => { endpoints: {}, purchase: { methods: ['tempo', 'x402', 'stripe'], - x402: { networks: ['base', 'solana'], scheme: 'exact', asset: 'USDC' }, + x402: { networks: ['base'], scheme: 'exact', asset: 'USDC' }, identity_paths: { wallet: { header: 'X-Wallet-Address', applies_to_rails: ['tempo', 'x402'] }, operator_token: { header: 'X-Operator-Token', applies_to_rails: ['tempo', 'x402', 'stripe'] }, @@ -42,7 +42,7 @@ describe('buildWellKnownMpp', () => { }, }); const purchase = manifest.purchase as Record; - expect(purchase.x402).toEqual({ networks: ['base', 'solana'], scheme: 'exact', asset: 'USDC' }); + expect(purchase.x402).toEqual({ networks: ['base'], scheme: 'exact', asset: 'USDC' }); expect(purchase.identity_paths).toBeDefined(); expect(purchase.compliance).toEqual({ require_kyc: true, min_age: 21, allowed_jurisdictions: ['US'] }); }); From 4055a6ea6b4e55e513ff383bdf5795103187d285 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 17:47:36 -0700 Subject: [PATCH 07/19] feat(commerce): targeted Solana-network rejection hint + extraWarnings hook verifyX402Request gets a dedicated branch for incoming x402 credentials on a solana:* network. Instead of the generic "Unsupported x402 network" message, the response points the client at the `solana/charge` rail in the 402 challenge so an agent on a stale x402 SVM client can recover with one re-sign. Both the error message and next_steps stay behavior-only: no internal terminology, no CLI vendor references, no protocol-handshake mechanics, no infra disclosure. buildAgentInstructions gets an `extraWarnings` field so merchants can append per-order rail-availability notes on top of the SDK's protocol-footgun defaults (without overriding them). Set `warnings` directly to override entirely; `extraWarnings` is ignored in that case. - src/payment/x402_validation.ts: new solana:* branch in the network-mismatch check - src/challenge/agent_instructions.ts: BuildAgentInstructionsInput gets `extraWarnings`; default-warnings concat covers both rails + extras - tests/payment/x402_validation.test.ts: assertion updated for the new Solana hint - tests/challenge/agent_instructions.test.ts: two new tests for extraWarnings + the override case --- src/challenge/agent_instructions.ts | 6 +++++- src/payment/x402_validation.ts | 10 ++++++++++ tests/challenge/agent_instructions.test.ts | 19 +++++++++++++++++++ tests/payment/x402_validation.test.ts | 8 +++++++- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/challenge/agent_instructions.ts b/src/challenge/agent_instructions.ts index 0a041f7..0cb829b 100644 --- a/src/challenge/agent_instructions.ts +++ b/src/challenge/agent_instructions.ts @@ -17,6 +17,10 @@ export interface BuildAgentInstructionsInput { timeoutSeconds?: number; /** Warnings about common footguns. Defaults include tempo wallet transfer + raw on-chain x402 deposits. */ warnings?: string[]; + /** Additional warnings appended to the default protocol-footgun set. Use this when you want + * to keep the SDK's protocol warnings AND add merchant-specific notes (e.g., a per-order + * rail-availability message). Ignored when `warnings` is set explicitly. */ + extraWarnings?: string[]; /** Recommended rail (e.g., 'tempo', 'x402_base'). Surfaced for agents to default to. */ recommended?: string; /** Per-rail list of client names the merchant has verified work end-to-end. Vendors set @@ -121,7 +125,7 @@ export function buildAgentInstructions(input: BuildAgentInstructionsInput): Agen recommended_tools: input.recommendedTools ?? defaultRecommendedTools(input.howToPay), wallet_compatibility: input.walletCompatibility ?? DEFAULT_WALLET_COMPATIBILITY, timeout_seconds: input.timeoutSeconds ?? 300, - warnings: input.warnings ?? defaultWarnings(input.howToPay), + warnings: input.warnings ?? [...defaultWarnings(input.howToPay), ...(input.extraWarnings ?? [])], ...(input.recommended ? { recommended: input.recommended } : {}), ...(compatibleClients ? { compatible_clients: compatibleClients } : {}), ...(input.extra ?? {}), diff --git a/src/payment/x402_validation.ts b/src/payment/x402_validation.ts index 8f4c97b..f01881c 100644 --- a/src/payment/x402_validation.ts +++ b/src/payment/x402_validation.ts @@ -145,6 +145,16 @@ export async function verifyX402Request(input: VerifyX402RequestInput): Promise< const signedPayTo = payload.accepted?.payTo; if (!signedNetwork || signedNetwork !== input.acceptedNetwork) { + if (signedNetwork && signedNetwork.startsWith('solana:')) { + return { + ok: false, + status: 400, + body: regenerateBody( + `x402 on ${signedNetwork} is not accepted; Solana payments must use the \`solana/charge\` rail advertised in the 402 challenge. This server accepts x402 on ${input.acceptedNetwork} only.`, + 'Solana payments are not accepted over x402 at this merchant. Pick the `solana/charge` rail from the 402 challenge and re-sign.', + ), + }; + } return { ok: false, status: 400, diff --git a/tests/challenge/agent_instructions.test.ts b/tests/challenge/agent_instructions.test.ts index e4f2a9d..30a7d07 100644 --- a/tests/challenge/agent_instructions.test.ts +++ b/tests/challenge/agent_instructions.test.ts @@ -39,6 +39,25 @@ describe('buildAgentInstructions', () => { expect(stripeOnly.recommended_tools).toEqual([]); }); + it('appends extraWarnings to defaults', () => { + const instructions = buildAgentInstructions({ + howToPay: { tempo: { prerequisite: 'x', instructions: 'y' }, x402_base: { prerequisite: 'x', instructions: 'y' } }, + extraWarnings: ['Solana unavailable for this order; use base or tempo.'], + }); + expect(instructions.warnings.length).toBe(3); + expect(instructions.warnings[0]).toContain('tempo wallet transfer'); + expect(instructions.warnings[2]).toContain('Solana unavailable'); + }); + + it('extraWarnings is ignored when warnings is set explicitly', () => { + const instructions = buildAgentInstructions({ + howToPay: { tempo: { prerequisite: 'x', instructions: 'y' } }, + warnings: ['custom only'], + extraWarnings: ['ignored'], + }); + expect(instructions.warnings).toEqual(['custom only']); + }); + it('overrides defaults when vendor passes them', () => { const instructions = buildAgentInstructions({ howToPay: {}, diff --git a/tests/payment/x402_validation.test.ts b/tests/payment/x402_validation.test.ts index c7a30be..e635c0c 100644 --- a/tests/payment/x402_validation.test.ts +++ b/tests/payment/x402_validation.test.ts @@ -109,7 +109,13 @@ describe('verifyX402Request', () => { acceptedNetwork, }); expect(res.ok).toBe(false); - if (!res.ok) expect(res.body.error.message).toContain('Unsupported x402 network'); + // Solana credentials get a behavior-only recovery hint pointing at the + // `solana/charge` rail. No internal terminology, no CLI vendor references. + if (!res.ok) { + expect(res.body.error.message).toContain('Solana'); + expect(res.body.error.message).toContain('`solana/charge`'); + expect(res.body.next_steps.user_message).toContain('solana/charge'); + } }); it('returns ok:false when payTo is malformed for EVM (wrong length)', async () => { From 3e4924d7c97051e2700d715d057a8c544e31aa42 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 18:05:30 -0700 Subject: [PATCH 08/19] chore(commerce): scrub internal disclosures from public-package source Public-package source describes behavior, not internal state names or named-customer business attributions. Sweep across docstring examples and agent-visible warnings: - Removed "pending_identity" (internal order-state column value) from the default warnings emitted by buildAgentInstructions + buildLlmsTxt; agents see "the order will not complete" instead of an internal state name - Genericized docstring examples in identity/a2a.ts, identity/ucp.ts, discovery/skill_md.ts, challenge/order_receipt.ts: "Martin Estate Wine Concierge" + "agents.martinestate.com" + "wine-purchase" placeholders replaced with "Example Merchant" / "agents.example.com" / generic skills - Removed the "(Martin Estate today; Commerce7 / WooCommerce / Shopify plugins tomorrow)" forward-looking rollout note from order_receipt.ts Mintlify worked-example pages and the public marketing site keep referencing Martin Estate by name; that's allowed at the marketing/URL level per the disclosure-posture rules. --- src/challenge/agent_instructions.ts | 4 ++-- src/challenge/order_receipt.ts | 7 +++---- src/discovery/llms_txt.ts | 2 +- src/discovery/skill_md.ts | 4 ++-- src/identity/a2a.ts | 12 ++++++------ src/identity/ucp.ts | 6 +++--- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/challenge/agent_instructions.ts b/src/challenge/agent_instructions.ts index 0cb829b..56f270f 100644 --- a/src/challenge/agent_instructions.ts +++ b/src/challenge/agent_instructions.ts @@ -43,10 +43,10 @@ export interface AgentInstructions { } const TEMPO_WARNING = - 'Do NOT use `tempo wallet transfer`. That moves USDC on-chain without completing the protocol handshake; your order stays in pending_identity. Use `tempo request` instead.'; + 'Do NOT use `tempo wallet transfer`. That moves USDC on-chain without completing the protocol handshake, so the order will not complete. Use `tempo request` instead.'; const X402_WARNING = - 'Do NOT send USDC manually to the deposit addresses. Use `agentscore-pay pay` so the credential is signed and submitted; otherwise the order stays in pending_identity even though the deposit lands.'; + 'Do NOT send USDC manually to the deposit addresses. Use `agentscore-pay pay` so the credential is signed and submitted; otherwise the order will not complete even though the deposit lands.'; const TEMPO_TOOL = '`tempo request` for Tempo USDC'; const AGENTSCORE_PAY_TOOL = '`agentscore-pay` — Base + Solana + Tempo from one CLI'; diff --git a/src/challenge/order_receipt.ts b/src/challenge/order_receipt.ts index 687d9d9..2dd5f53 100644 --- a/src/challenge/order_receipt.ts +++ b/src/challenge/order_receipt.ts @@ -1,10 +1,9 @@ /** * Canonical order-receipt shape returned to agents on the 200 after a successful settlement. * - * Merchants own their order schema, but converging on this shape across every AgentScore-gated - * merchant (Martin Estate today; Commerce7 / WooCommerce / Shopify plugins tomorrow) means - * agents can render and post-process orders consistently. Lift this type, fill the fields you - * care about, and ignore (or extend via `extras`) what you don't. + * Merchants own their order schema, but converging on this shape across AgentScore-gated + * merchants means agents can render and post-process orders consistently. Lift this type, + * fill the fields you care about, and ignore (or extend via `extras`) what you don't. * * All money fields are dollar-strings (e.g. `"250.00"`). Use `buildPricingBlock` from * `@agent-score/commerce/challenge` to compose the pricing fields from cents. diff --git a/src/discovery/llms_txt.ts b/src/discovery/llms_txt.ts index bf0e1c3..367b4b3 100644 --- a/src/discovery/llms_txt.ts +++ b/src/discovery/llms_txt.ts @@ -163,7 +163,7 @@ function llmsTxtPaymentSectionVerbose(input: LlmsTxtPaymentSectionInput): string lines.push(''); } - lines.push('IMPORTANT: Use the CLIs above. Raw on-chain transfers (e.g. `tempo wallet transfer`, sending USDC manually to deposit addresses) bypass the protocol handshake and the order stays in pending_identity.'); + lines.push('IMPORTANT: Use the CLIs above. Raw on-chain transfers (e.g. `tempo wallet transfer`, sending USDC manually to deposit addresses) bypass the protocol handshake and the order will not complete.'); if (hasBase || hasSolana) { lines.push('IMPORTANT: Pay the exact amount in the 402 challenge. Overpayments and underpayments cannot be matched.'); } diff --git a/src/discovery/skill_md.ts b/src/discovery/skill_md.ts index bf70bd8..765a09f 100644 --- a/src/discovery/skill_md.ts +++ b/src/discovery/skill_md.ts @@ -37,7 +37,7 @@ export interface SkillMdLink { export interface BuildSkillMdInput { /** Skill manifest identifier — kebab-case per agentskills.io spec: 1-64 chars, lowercase * alphanumeric + hyphens, no leading/trailing/consecutive hyphens. Validated at build - * time; invalid names throw. e.g. 'martin-estate-wine-commerce'. */ + * time; invalid names throw. e.g. 'example-merchant-commerce'. */ name: string; /** Skill description — agentskills.io spec: 1-1024 chars, non-empty. Should describe both * what the skill does AND when to use it; imperative phrasing recommended ("Use when…"). @@ -63,7 +63,7 @@ export interface BuildSkillMdInput { * `metadata:`. Spec requires string values. */ metadata?: Record; - /** Human display name (e.g. "Martin Estate Winery"). */ + /** Human display name (e.g. "Example Merchant"). */ merchantName: string; /** Optional one-line tagline appearing under the title. */ tagline?: string; diff --git a/src/identity/a2a.ts b/src/identity/a2a.ts index b4d1447..9d126e4 100644 --- a/src/identity/a2a.ts +++ b/src/identity/a2a.ts @@ -19,7 +19,7 @@ import type { AgentScoreData } from '../core'; export interface A2AAgentCardCapabilities { /** Endpoints the agent exposes — `[{ name: "purchase", path: "/purchase", method: "POST" }, ...]`. */ endpoints?: { name: string; path?: string; method?: string }[]; - /** Free-form skill tags — `["wine-purchase", "regulated-commerce", ...]`. */ + /** Free-form skill tags — `["product-purchase", "regulated-commerce", ...]`. */ skills?: string[]; } @@ -62,7 +62,7 @@ export interface A2AAgentCard { } export interface BuildA2AAgentCardInput { - /** Display name for the agent — `"Martin Estate Wine Concierge"`, etc. */ + /** Display name for the agent — e.g. a merchant brand or service name. */ name: string; /** Optional one-line description. */ description?: string; @@ -100,12 +100,12 @@ const CARD_VERSION = 1; * app.get('/.well-known/agent-card', async (c) => { * const data = getAgentScoreData(c); * const card = buildA2AAgentCard({ - * name: 'Martin Estate Wine Concierge', - * description: 'Buy regulated wines from Martin Estate via agent payments.', - * url: 'https://agents.martinestate.com', + * name: 'Example Merchant Concierge', + * description: 'Buy regulated goods via agent payments.', + * url: 'https://agents.example.com', * capabilities: { * endpoints: [{ name: 'purchase', path: '/purchase', method: 'POST' }], - * skills: ['wine-purchase', 'regulated-commerce'], + * skills: ['product-purchase', 'regulated-commerce'], * }, * data, * }); diff --git a/src/identity/ucp.ts b/src/identity/ucp.ts index a4fdde4..bef7ab1 100644 --- a/src/identity/ucp.ts +++ b/src/identity/ucp.ts @@ -128,13 +128,13 @@ const AGENTSCORE_CAPABILITY_VERSION = '1'; * app.get('/.well-known/ucp', async (c) => { * const data = getAgentScoreData(c); * return c.json(buildUCPProfile({ - * name: 'Martin Estate', - * services: [{ type: 'rest', url: 'https://agents.martinestate.com' }], + * name: 'Example Merchant', + * services: [{ type: 'rest', url: 'https://agents.example.com' }], * payment_handlers: [ * { name: 'tempo', config: { recipient: TEMPO_ADDR } }, * { name: 'stripe', config: { profile_id: STRIPE_PROFILE_ID } }, * ], - * signing_keys: [{ kid: 'me-2026-04', kty: 'EC', alg: 'ES256', crv: 'P-256', x: '...', y: '...' }], + * signing_keys: [{ kid: 'merchant-2026-04', kty: 'EC', alg: 'ES256', crv: 'P-256', x: '...', y: '...' }], * data, * })); * }); From 1a697c9f4ba5355425260d7dbf8717b06ef42207 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 18:08:31 -0700 Subject: [PATCH 09/19] chore(commerce): scrub remaining customer-domain example in _denial docstring buildContactSupportNextSteps usage example used a real customer's support email address. Genericize to support@example.com per the disclosure-posture rules; merchants supply their own address at the call site. --- src/_denial.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_denial.ts b/src/_denial.ts index ee32ed4..abc5ffd 100644 --- a/src/_denial.ts +++ b/src/_denial.ts @@ -138,7 +138,7 @@ export function buildSignerMismatchBody(input: SignerMismatchBodyInput): Record< * return c.json({ * error: { code: 'compliance_denied', message: '...' }, * reasons, - * next_steps: buildContactSupportNextSteps('support@martinestate.com'), + * next_steps: buildContactSupportNextSteps('support@example.com'), * }, 403); */ export function buildContactSupportNextSteps( From df16fb58be56dbce8258968f6754650ed582bfd8 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 18:11:01 -0700 Subject: [PATCH 10/19] chore(commerce): genericize Martin Estate references in tests Public-package tests are part of the public surface (visible on the npm tarball + GitHub source). The hono + a2a tests were using "Martin Estate" / "https://agents.martinestate.com" / "wine-purchase" as fixture values; that bakes a public reference customer's domain into the SDK source. Swap to "Example Merchant" / "https://agents.example.com" / "product-purchase". --- tests/hono.test.ts | 12 ++++++------ tests/identity/a2a.test.ts | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/hono.test.ts b/tests/hono.test.ts index d4a5066..7c1c8b0 100644 --- a/tests/hono.test.ts +++ b/tests/hono.test.ts @@ -248,7 +248,7 @@ describe('Hono adapter — createSessionOnMissing', () => { const app = new Hono(); app.use('*', agentscoreGate({ apiKey: API_KEY, - createSessionOnMissing: { apiKey: API_KEY, context: 'wine-purchase' }, + createSessionOnMissing: { apiKey: API_KEY, context: 'product-purchase' }, })); app.get('/test', (c) => c.text('reached')); @@ -269,7 +269,7 @@ describe('Hono adapter — createSessionOnMissing', () => { const fetchCall = (global.fetch as ReturnType).mock.calls[0]; expect(fetchCall[0]).toContain('/v1/sessions'); const postBody = JSON.parse(fetchCall[1].body as string); - expect(postBody.context).toBe('wine-purchase'); + expect(postBody.context).toBe('product-purchase'); }); it('fixable wallet denial (kyc_required) bootstraps a session like missing_identity', async () => { @@ -291,7 +291,7 @@ describe('Hono adapter — createSessionOnMissing', () => { app.use('*', agentscoreGate({ apiKey: API_KEY, requireKyc: true, - createSessionOnMissing: { apiKey: API_KEY, context: 'wine-purchase' }, + createSessionOnMissing: { apiKey: API_KEY, context: 'product-purchase' }, })); app.get('/test', (c) => c.text('reached')); @@ -318,7 +318,7 @@ describe('Hono adapter — createSessionOnMissing', () => { app.use('*', agentscoreGate({ apiKey: API_KEY, requireKyc: true, - createSessionOnMissing: { apiKey: API_KEY, context: 'wine-purchase' }, + createSessionOnMissing: { apiKey: API_KEY, context: 'product-purchase' }, })); app.get('/test', (c) => c.text('reached')); @@ -346,7 +346,7 @@ describe('Hono adapter — createSessionOnMissing', () => { apiKey: API_KEY, createSessionOnMissing: { apiKey: API_KEY, - context: 'wine-purchase', + context: 'product-purchase', productName: 'static fallback', getSessionOptions: (c) => ({ productName: c.get('productName' as never) as string }), }, @@ -357,7 +357,7 @@ describe('Hono adapter — createSessionOnMissing', () => { const fetchCall = (global.fetch as ReturnType).mock.calls[0]; const postBody = JSON.parse(fetchCall[1].body as string); expect(postBody.product_name).toBe('dynamic Cabernet'); - expect(postBody.context).toBe('wine-purchase'); + expect(postBody.context).toBe('product-purchase'); }); it('getSessionOptions may be async', async () => { diff --git a/tests/identity/a2a.test.ts b/tests/identity/a2a.test.ts index 7e10e78..1d1a86a 100644 --- a/tests/identity/a2a.test.ts +++ b/tests/identity/a2a.test.ts @@ -20,14 +20,14 @@ const fullData: AgentScoreData = { describe('buildA2AAgentCard', () => { it('emits a card with identity claims when data is provided', () => { const card = buildA2AAgentCard({ - name: 'Martin Estate', - url: 'https://agents.martinestate.com', + name: 'Example Merchant', + url: 'https://agents.example.com', data: fullData, }); expect(card.protocol_version).toBe('1.0'); expect(card.card_version).toBe(1); - expect(card.name).toBe('Martin Estate'); - expect(card.url).toBe('https://agents.martinestate.com'); + expect(card.name).toBe('Example Merchant'); + expect(card.url).toBe('https://agents.example.com'); expect(card.identity).not.toBeNull(); expect(card.identity?.operator_id).toBe('op_abc'); expect(card.identity?.kyc_level).toBe('enhanced'); From bf562d4903b30b693dd14ba7822ba13a6adaa8cf Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 18:13:51 -0700 Subject: [PATCH 11/19] chore(commerce): final scrub of customer + threat-model leaks Public-package source + tests describe behavior, not threat model. - src/core.ts: drop "malicious merchant phishing agents into attacker- controlled endpoints" rationale from the CANONICAL_AGENTSCORE_API comment; the constant is hardcoded; the why doesn't need to ship - src/stripe-multichain/pi-cache.ts: replace "prevents agents from sending payment to an attacker-controlled address" with "validates the credential's deposit address against the addresses the merchant has minted" - src/payment/x402_validation.ts: replace "defends against agents replaying credentials against attacker-controlled deposit addresses" with the behavior-only equivalent Tests: genericize remaining customer references in tests/_response.test.ts, tests/identity/ucp.test.ts, tests/payment/directive.test.ts, and the entire tests/discovery/skill_md.test.ts fixture set. Swap "Martin Estate" / "martinestate.com" / "wine-purchase" / "martin-estate-wine-commerce" for "Example Merchant" / "example.com" / "product-purchase" / "example-merchant-commerce". --- src/core.ts | 2 -- src/payment/x402_validation.ts | 4 ++-- src/stripe-multichain/pi-cache.ts | 4 ++-- tests/_response.test.ts | 4 ++-- tests/discovery/skill_md.test.ts | 24 ++++++++++++------------ tests/identity/ucp.test.ts | 4 ++-- tests/payment/directive.test.ts | 2 +- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/core.ts b/src/core.ts index edc1fd5..bd2820f 100644 --- a/src/core.ts +++ b/src/core.ts @@ -347,8 +347,6 @@ interface AssessResult { */ // Canonical production AgentScore API — used as the authoritative source for endpoint pointers // emitted to agent memory regardless of how a given merchant configured their gate's baseUrl. -// Hardcoded so a malicious merchant can't set `baseUrl: "evil.com"` and phish agents into -// sending their credentials to attacker-controlled endpoints. const CANONICAL_AGENTSCORE_API = 'https://api.agentscore.sh'; // JSON-encoded action copy emitted on wallet-signer-match denials. Spread into 403 bodies diff --git a/src/payment/x402_validation.ts b/src/payment/x402_validation.ts index f01881c..7f2b242 100644 --- a/src/payment/x402_validation.ts +++ b/src/payment/x402_validation.ts @@ -49,8 +49,8 @@ export interface VerifyX402RequestInput { /** The incoming Request — `verifyX402Request` reads the X-Payment / payment-signature header. */ request: Request; /** Async lookup that returns true when the address was minted by this merchant - * (typically `piCache.hasAddress`). The cache check defends against agents replaying - * credentials against attacker-controlled deposit addresses. */ + * (typically `piCache.hasAddress`). The check validates that the credential's + * deposit address matches one the merchant actually minted. */ isCachedAddress: (address: string) => Promise; /** The merchant's accepted Base network. CAIP-2, e.g. `'eip155:8453'`. */ acceptedNetwork: string; diff --git a/src/stripe-multichain/pi-cache.ts b/src/stripe-multichain/pi-cache.ts index fc077bd..1478d1e 100644 --- a/src/stripe-multichain/pi-cache.ts +++ b/src/stripe-multichain/pi-cache.ts @@ -5,8 +5,8 @@ * * 1. **Is this on-chain `pay_to` address one we minted?** — when an MPP credential * arrives with a `recipient`, verify it matches a recently-minted Stripe deposit - * address. Prevents agents from sending payment to an attacker-controlled address - * and replaying the credential against the merchant's endpoint. + * address. Validates the credential's deposit address against the addresses the + * merchant has actually minted. * * 2. **Which PaymentIntent owns this deposit address?** — when settling, the * `simulate_crypto_deposit` test_helpers call needs the PaymentIntent id for the diff --git a/tests/_response.test.ts b/tests/_response.test.ts index e3b495d..cfe1035 100644 --- a/tests/_response.test.ts +++ b/tests/_response.test.ts @@ -110,10 +110,10 @@ describe('denialReasonToBody', () => { it('merges extra fields onto the body (createSessionOnMissing.onBeforeSession hook)', () => { const body = denialReasonToBody(reason({ code: 'identity_verification_required', - extra: { order_id: 'ord_123', merchant_context: 'wine-purchase' }, + extra: { order_id: 'ord_123', merchant_context: 'product-purchase' }, })); expect(body.order_id).toBe('ord_123'); - expect(body.merchant_context).toBe('wine-purchase'); + expect(body.merchant_context).toBe('product-purchase'); }); it('injects canonical wallet_not_trusted agent_instructions when reason has none', () => { diff --git a/tests/discovery/skill_md.test.ts b/tests/discovery/skill_md.test.ts index baaf0d7..833cff8 100644 --- a/tests/discovery/skill_md.test.ts +++ b/tests/discovery/skill_md.test.ts @@ -3,24 +3,24 @@ import { buildSkillMd } from '../../src/discovery/skill_md'; describe('buildSkillMd', () => { const baseInput = { - name: 'martin-estate-wine-commerce', - description: 'Buy wine from Martin Estate via an AI agent', + name: 'example-merchant-commerce', + description: 'Buy from Example Merchant via an AI agent', homepage: 'https://martin-estate.com', - merchantName: 'Martin Estate', + merchantName: 'Example Merchant', acceptedRails: ['tempo_mpp', 'x402_base', 'solana_mpp', 'stripe'] as const, endpoints: [ { method: 'GET' as const, path: '/api/v1/wines', authRequired: false, description: 'Wine catalog' }, { method: 'POST' as const, path: '/api/v1/orders', authRequired: true, description: 'Place order' }, ], - triggers: ['User wants to buy wine from Martin Estate'], + triggers: ['User wants to buy from Example Merchant'], }; describe('frontmatter (agentskills.io spec)', () => { it('emits valid YAML frontmatter with name + quoted description + metadata', () => { const out = buildSkillMd(baseInput); expect(out.startsWith('---\n')).toBe(true); - expect(out).toContain('name: martin-estate-wine-commerce'); - expect(out).toContain('description: "Buy wine from Martin Estate via an AI agent"'); + expect(out).toContain('name: example-merchant-commerce'); + expect(out).toContain('description: "Buy from Example Merchant via an AI agent"'); expect(out).toContain('metadata:'); expect(out).toContain(' version: "1"'); expect(out).toContain(' homepage: "https://martin-estate.com"'); @@ -126,7 +126,7 @@ describe('buildSkillMd', () => { describe('title block', () => { it('renders merchant name as h1', () => { const out = buildSkillMd(baseInput); - expect(out).toContain('\n# Martin Estate\n'); + expect(out).toContain('\n# Example Merchant\n'); }); it('renders title + tagline + intro with single blank line between each', () => { @@ -135,17 +135,17 @@ describe('buildSkillMd', () => { tagline: 'A classic is forever', intro: 'Napa Valley winery, family-run.', }); - expect(out).toContain('# Martin Estate\n\n_A classic is forever_\n\nNapa Valley winery, family-run.'); + expect(out).toContain('# Example Merchant\n\n_A classic is forever_\n\nNapa Valley winery, family-run.'); }); it('renders tagline only when provided', () => { const out = buildSkillMd({ ...baseInput, tagline: 'A classic is forever' }); - expect(out).toContain('# Martin Estate\n\n_A classic is forever_'); + expect(out).toContain('# Example Merchant\n\n_A classic is forever_'); }); it('renders intro only when provided', () => { const out = buildSkillMd({ ...baseInput, intro: 'Napa Valley winery.' }); - expect(out).toContain('# Martin Estate\n\nNapa Valley winery.'); + expect(out).toContain('# Example Merchant\n\nNapa Valley winery.'); }); }); @@ -362,10 +362,10 @@ describe('buildSkillMd', () => { it('emits each trigger as a bullet', () => { const out = buildSkillMd({ ...baseInput, - triggers: ['Buy wine from Martin Estate', 'Check order status'], + triggers: ['Buy from Example Merchant', 'Check order status'], }); expect(out).toContain('## Triggers'); - expect(out).toContain('- Buy wine from Martin Estate'); + expect(out).toContain('- Buy from Example Merchant'); expect(out).toContain('- Check order status'); }); diff --git a/tests/identity/ucp.test.ts b/tests/identity/ucp.test.ts index 167fd7f..e759f05 100644 --- a/tests/identity/ucp.test.ts +++ b/tests/identity/ucp.test.ts @@ -69,14 +69,14 @@ describe('buildUCPProfile', () => { it('passes through name + payment_handlers + extras', () => { const profile = buildUCPProfile({ ...baseInput, - name: 'Martin Estate', + name: 'Example Merchant', payment_handlers: [ { name: 'tempo', config: { recipient: '0xtempo' } }, { name: 'stripe', config: { profile_id: 'prof_x' } }, ], extras: { custom_field: 'custom_value' }, }); - expect(profile.name).toBe('Martin Estate'); + expect(profile.name).toBe('Example Merchant'); expect(profile.payment_handlers).toHaveLength(2); expect((profile as Record).custom_field).toBe('custom_value'); }); diff --git a/tests/payment/directive.test.ts b/tests/payment/directive.test.ts index 31bbb82..1f57dd9 100644 --- a/tests/payment/directive.test.ts +++ b/tests/payment/directive.test.ts @@ -104,7 +104,7 @@ describe('buildPaymentDirective (convenience)', () => { const directive = buildPaymentDirective({ rail: 'tempo-mainnet', id: 'chg_001', - realm: 'martinestate.com', + realm: 'example.com', amountUsd: 2, recipient: '0xrecipient', expires: '2026-04-26T00:00:00.000Z', From ff50bdda2ccd31ef8a9d770ca77a8bc4e5547941 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 18:16:11 -0700 Subject: [PATCH 12/19] chore(commerce): drop "malicious merchant" rationale from tests + scrub remaining wine ref - tests/signer-match.test.ts: rephrase the buildAgentMemoryHint test name + comment from "prevent cross-merchant phishing / malicious merchant" to "emits the canonical AgentScore API regardless of merchant baseUrl". Behavior is the same; the rationale narration goes away. - tests/express.test.ts: replace remaining 'wine purchase' context fixture with 'product purchase' --- tests/express.test.ts | 4 ++-- tests/signer-match.test.ts | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/express.test.ts b/tests/express.test.ts index 3aa3f4a..4f0f93a 100644 --- a/tests/express.test.ts +++ b/tests/express.test.ts @@ -1184,7 +1184,7 @@ describe('agentscoreGate middleware — createSessionOnMissing', () => { apiKey: API_KEY, createSessionOnMissing: { apiKey: 'ask_session_key', - context: 'wine purchase', + context: 'product purchase', productName: 'Cabernet Reserve 2021', }, }); @@ -1197,7 +1197,7 @@ describe('agentscoreGate middleware — createSessionOnMissing', () => { const fetchCall = (global.fetch as ReturnType).mock.calls[0]; const body = JSON.parse(fetchCall[1].body as string); expect(body).toEqual({ - context: 'wine purchase', + context: 'product purchase', product_name: 'Cabernet Reserve 2021', }); }); diff --git a/tests/signer-match.test.ts b/tests/signer-match.test.ts index ae8b44f..0994470 100644 --- a/tests/signer-match.test.ts +++ b/tests/signer-match.test.ts @@ -395,12 +395,11 @@ describe('AgentScoreCore.verifyWalletSignerMatch — Section IV (both headers)', }); describe('buildAgentMemoryHint — hardcoded canonical URLs', () => { - it('ignores merchant baseUrl to prevent cross-merchant phishing', async () => { + it('emits the canonical AgentScore API regardless of merchant baseUrl', async () => { const { buildAgentMemoryHint } = await import('../src/core'); - // Even if a malicious merchant configured their gate with baseUrl pointing at their own - // evil endpoint, the agent memory must always advertise the canonical AgentScore API so - // an agent following the memory hint doesn't leak credentials to a rogue merchant. - const hint = buildAgentMemoryHint('https://evil.example.com'); + // Agent memory always advertises the canonical AgentScore API; the merchant's + // configured gate baseUrl does not flow through to the emitted memory pointer. + const hint = buildAgentMemoryHint('https://other.example.com'); expect(hint.identity_check_endpoint).toBe('https://api.agentscore.sh/v1/credentials'); // list_wallets_endpoint is reserved for a future GET endpoint — not emitted today. expect(hint.list_wallets_endpoint).toBeUndefined(); From e8329e3e736da01c13bacb5f42f9b1c025c6838d Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 21:13:54 -0700 Subject: [PATCH 13/19] chore: bump version to 1.3.0 Lands the cycle's commerce SDK changes (kill x402 Solana + add MPP solana/charge + classify+extraWarnings + x402scan discovery + internal- disclosure scrubs) as the next minor release after 1.2.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6789803..b7fe2b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agent-score/commerce", - "version": "1.4.0", + "version": "1.3.0", "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.", "main": "./dist/index.js", "module": "./dist/index.mjs", From c91e9ad17ee9d1ddb6a394e9bdb530b39dc672c1 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 21:24:30 -0700 Subject: [PATCH 14/19] fix(commerce): case-insensitive Solana CAIP-2 detection in verifyX402Request A misformatted credential carrying `SOLANA:5eyk...` or `Solana:5eyk...` would fall through to the generic 'Unsupported x402 network' message instead of the targeted Solana hint that points at the `solana/charge` rail. CAIP-2 spec is lowercase by convention, but defensive code costs one `.toLowerCase()` and rescues malformed inputs. --- src/payment/x402_validation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/payment/x402_validation.ts b/src/payment/x402_validation.ts index 7f2b242..8cbf8a2 100644 --- a/src/payment/x402_validation.ts +++ b/src/payment/x402_validation.ts @@ -145,7 +145,7 @@ export async function verifyX402Request(input: VerifyX402RequestInput): Promise< const signedPayTo = payload.accepted?.payTo; if (!signedNetwork || signedNetwork !== input.acceptedNetwork) { - if (signedNetwork && signedNetwork.startsWith('solana:')) { + if (signedNetwork && signedNetwork.toLowerCase().startsWith('solana:')) { return { ok: false, status: 400, From d12db626802887eaac73917847d973cb4596c35c Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 4 May 2026 21:34:54 -0700 Subject: [PATCH 15/19] feat(signer): match Solana DIDs (did:pkh:solana:...) in extractPaymentSigner Anticipatory: when an MPP credential's `source` field is a Solana CAIP-10 DID (`did:pkh:solana::`), extract the base58 address with network='solana'. Mirrors the existing EVM branch. Why anticipatory: `@solana/mpp/client` does not currently set `Credential.source` when serializing a `solana/charge` credential, even though the mppx Tempo plugin does set `did:pkh:eip155:...` for tempo. So this matcher is dead code today; it activates as soon as upstream sets source, or as soon as a merchant/client adds source themselves. Without this matcher, Solana MPP settles produce `capturedSigner=null`, which silently skips `captureWallet` (no operator-wallet link forms on Solana) and weakens wallet-auth signer-match enforcement vs the EVM rails. Test: tests/signer.test.ts now covers the Solana DID happy path; existing EVM-DID and non-DID null cases stand. --- src/signer.ts | 7 +++++-- tests/signer.test.ts | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/signer.ts b/src/signer.ts index a9fa779..45f5b31 100644 --- a/src/signer.ts +++ b/src/signer.ts @@ -50,8 +50,11 @@ export async function extractPaymentSigner( if (mppx?.Credential?.extractPaymentScheme(authHeader)) { const credential = mppx.Credential.fromRequest(request); const source = (credential as { source?: string }).source; - const match = source?.match(/^did:pkh:eip155:\d+:(0x[0-9a-fA-F]{40})$/); - if (match) return { address: match[1]!.toLowerCase(), network: 'evm' }; + const evmMatch = source?.match(/^did:pkh:eip155:\d+:(0x[0-9a-fA-F]{40})$/); + if (evmMatch) return { address: evmMatch[1]!.toLowerCase(), network: 'evm' }; + // Solana CAIP-10: did:pkh:solana:: + const solMatch = source?.match(/^did:pkh:solana:[1-9A-HJ-NP-Za-km-z]{32,44}:([1-9A-HJ-NP-Za-km-z]{32,44})$/); + if (solMatch) return { address: solMatch[1]!, network: 'solana' }; } } catch (err) { console.warn('[gate] MPP signer extraction failed:', err instanceof Error ? err.message : err); diff --git a/tests/signer.test.ts b/tests/signer.test.ts index 439b939..8f00801 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -1,6 +1,9 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { extractPaymentSignerAddress, readX402PaymentHeader } from '../src/signer'; +const SOLANA_GENESIS_MAINNET = '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'; +const SOLANA_SIGNER = 'GEQg2TM4VL315Bd4LLkGrhBjdNfoatKjCJYHBDPM3D74'; + const SIGNER_LOWER = '0xabcdef0123456789abcdef0123456789abcdef01'; const SIGNER_MIXED = '0xABCDEF0123456789ABCDEF0123456789ABCDEF01'; @@ -108,6 +111,22 @@ describe('extractPaymentSignerAddress — MPP path', () => { vi.doUnmock('mppx'); }); + it('extracts the base58 address from an MPP DID (did:pkh:solana:...) with network=solana', async () => { + vi.doMock('mppx', () => ({ + Credential: { + extractPaymentScheme: () => true, + fromRequest: () => ({ source: `did:pkh:solana:${SOLANA_GENESIS_MAINNET}:${SOLANA_SIGNER}` }), + }, + })); + const { extractPaymentSigner: freshExtract } = await import( + `../src/signer?mpp-solana=${freshImportKey()}` + ); + const req = makeRequest({ authorization: 'Payment mpp-cred' }); + const result = await freshExtract(req); + expect(result).toEqual({ address: SOLANA_SIGNER, network: 'solana' }); + vi.doUnmock('mppx'); + }); + it('returns null when the MPP credential source is not a did:pkh:eip155 shape', async () => { vi.doMock('mppx', () => ({ Credential: { From a0c6e8a962331fb138527012f488f87ec2a9d04b Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 5 May 2026 00:08:59 -0700 Subject: [PATCH 16/19] feat(signer): decode Solana credential payload to recover signer (pull mode) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the wallet-signer-match gap on Solana MPP `solana/charge`. When the mppx credential's `source` field is unset (current `@solana/mpp/client` behavior), fall back to decoding the credential's signed-tx payload and read the SPL `TransferChecked` authority field. The authority is the source-ATA owner, which is the buyer's wallet. Pull mode only (`payload.type === 'transaction'`). Push mode (`payload.type === 'signature'`) returns null because recovery would require an RPC fetch by signature; pay uses pull mode by default and no consumer in the stack opts into push today. Why this matters: without signer recovery, the gate's wallet-auth check on Solana could not enforce that the claimed `X-Wallet-Address` matches who actually paid. An agent claiming a KYC'd wallet could pay with an unverified one. Now the recovered Solana base58 address feeds `verifyWalletSignerMatch` the same way the EVM EIP-3009 `from` does, so operator-equivalence enforcement is uniform across rails. Implementation: - `@solana/kit` is dynamic-imported, matching the existing `mppx` pattern; optional peer dep already declared in package.json (>=6.5.0). - Walks instructions for SPL Token Program or Token-2022 with the `TransferChecked` discriminator (12), then reads `accountIndices[3]` (authority slot) and looks it up in `staticAccounts`. - Failure paths (missing peer dep, malformed tx, no TransferChecked, push-mode payload) all return null cleanly so the existing fall-through to x402 EVM still works. Tests: 660 passing (up from 658). New cases: - pull-mode Solana credential with TransferChecked → returns { address: , network: 'solana' } - push-mode Solana credential (`type: 'signature'`) → returns null --- src/signer.ts | 82 +++++++++++++++++++++++++++++++++++++++----- tests/signer.test.ts | 63 ++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 8 deletions(-) diff --git a/src/signer.ts b/src/signer.ts index 45f5b31..26eb8b8 100644 --- a/src/signer.ts +++ b/src/signer.ts @@ -1,21 +1,82 @@ /** * Payment-signer extraction. * - * Shared between merchants and the gate — both need to recover the on-chain signer from - * a payment credential without duplicating code. Two paths carry a recoverable wallet - * signer here: + * Shared between merchants and the gate. Three paths recover a wallet signer: * - * - **Tempo MPP** — `Authorization: Payment ` header; credential `source` is a DID - * of the form `did:pkh:eip155::
`. - * - **x402 EIP-3009** (EVM, e.g. Base/Sepolia) — `payment-signature` / `x-payment` header; + * - **Tempo MPP** — `Authorization: Payment `; credential `source` is a DID of the + * form `did:pkh:eip155::
`. + * - **Solana MPP `solana/charge`** — `Authorization: Payment `; recovery via either + * a `did:pkh:solana::
` source (when set by the client) or by decoding + * the credential's signed-tx payload and reading the SPL `TransferChecked` authority + * (pull mode only — `payload.type === 'transaction'`). + * - **x402 EIP-3009 (EVM, e.g. Base/Sepolia)** — `payment-signature` / `x-payment`; * decoded payload carries `payload.authorization.from`. * - * `mppx` is an optional peer dependency — we import it dynamically so merchants who don't - * use MPP don't need to install it. The EVM x402 path is pure JSON parsing with no external dep. + * Optional peer deps: `mppx` for MPP credentials, `@solana/kit` for the Solana tx-decode + * fallback. Both dynamic-imported; merchants who don't accept that rail don't need them. */ export type SignerNetwork = 'evm' | 'solana'; +const TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; +const TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'; +const TRANSFER_CHECKED_DISCRIMINATOR = 12; + +interface SolanaKitMinimal { + getBase64Codec: () => { encode: (s: string) => Uint8Array }; + getTransactionDecoder: () => { decode: (b: Uint8Array) => { messageBytes: Uint8Array } }; + getCompiledTransactionMessageDecoder: () => { + decode: (b: Uint8Array) => { + staticAccounts: ReadonlyArray; + instructions: ReadonlyArray<{ + programAddressIndex: number; + accountIndices?: number[]; + data?: Uint8Array; + }>; + }; + }; +} + +/** + * Decode a Solana MPP `solana/charge` credential's `payload.transaction` (base64-encoded + * signed Solana tx) and return the SPL `TransferChecked` authority — the source-ATA owner, + * which is the buyer's wallet. Pull mode only (`payload.type === 'transaction'`); push mode + * (`payload.type === 'signature'`) returns null because recovery would require an RPC fetch. + */ +async function extractSolanaSignerFromCredential(credential: unknown): Promise { + const payload = (credential as { payload?: { transaction?: string; type?: string } }).payload; + if (!payload?.transaction || payload.type !== 'transaction') return null; + + const moduleName = '@solana/kit'; + const kit = (await import(moduleName).catch(() => null)) as SolanaKitMinimal | null; + if (!kit?.getBase64Codec || !kit.getTransactionDecoder || !kit.getCompiledTransactionMessageDecoder) { + return null; + } + + try { + const txBytes = kit.getBase64Codec().encode(payload.transaction); + const decoded = kit.getTransactionDecoder().decode(txBytes); + const message = kit.getCompiledTransactionMessageDecoder().decode(decoded.messageBytes); + + for (const ix of message.instructions) { + const programId = message.staticAccounts[ix.programAddressIndex]; + if (programId !== TOKEN_PROGRAM && programId !== TOKEN_2022_PROGRAM) continue; + const data = ix.data; + if (!data || data.length === 0 || data[0] !== TRANSFER_CHECKED_DISCRIMINATOR) continue; + // SPL TransferChecked accounts: [source ATA, mint, destination ATA, authority, ...signers] + const accountIndices = ix.accountIndices ?? []; + const authorityIndex = accountIndices[3]; + if (authorityIndex === undefined) continue; + const authority = message.staticAccounts[authorityIndex]; + if (authority) return authority; + } + return null; + } catch (err) { + console.warn('[gate] Solana credential decode failed:', err instanceof Error ? err.message : err); + return null; + } +} + export interface PaymentSigner { /** Recovered wallet address (EVM lowercased; Solana base58 preserved verbatim). */ address: string; @@ -55,6 +116,11 @@ export async function extractPaymentSigner( // Solana CAIP-10: did:pkh:solana:: const solMatch = source?.match(/^did:pkh:solana:[1-9A-HJ-NP-Za-km-z]{32,44}:([1-9A-HJ-NP-Za-km-z]{32,44})$/); if (solMatch) return { address: solMatch[1]!, network: 'solana' }; + // Fallback: source not set by upstream client. Decode the credential's signed-tx + // payload to find the SPL TransferChecked authority (= source-ATA owner = buyer + // wallet). Pull mode only. + const solanaFromTx = await extractSolanaSignerFromCredential(credential); + if (solanaFromTx) return { address: solanaFromTx, network: 'solana' }; } } catch (err) { console.warn('[gate] MPP signer extraction failed:', err instanceof Error ? err.message : err); diff --git a/tests/signer.test.ts b/tests/signer.test.ts index 8f00801..c477ac4 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -127,6 +127,69 @@ describe('extractPaymentSignerAddress — MPP path', () => { vi.doUnmock('mppx'); }); + it('falls back to decoding payload.transaction when source is unset (Solana pull mode)', async () => { + // Stand-in for @solana/kit. The extract path passes payload bytes through + // getBase64Codec → getTransactionDecoder → getCompiledTransactionMessageDecoder + // and then walks instructions for SPL TransferChecked. We hand it a fake + // compiled message whose 0th instruction is a TokenProgram TransferChecked + // with the authority at account index 3. + const TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; + const fakeMessage = { + staticAccounts: [ + TOKEN_PROGRAM, // 0: program (referenced by programAddressIndex below) + 'SrcATAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + 'MintXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + 'DstATAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + SOLANA_SIGNER, // 4: authority + ], + instructions: [ + { + programAddressIndex: 0, + accountIndices: [1, 2, 3, 4], + data: new Uint8Array([12, 0, 0, 0, 0, 0, 0, 0, 0, 6]), + }, + ], + }; + vi.doMock('mppx', () => ({ + Credential: { + extractPaymentScheme: () => true, + fromRequest: () => ({ + payload: { transaction: 'AAAA', type: 'transaction' }, + }), + }, + })); + vi.doMock('@solana/kit', () => ({ + getBase64Codec: () => ({ encode: () => new Uint8Array([0]) }), + getTransactionDecoder: () => ({ decode: () => ({ messageBytes: new Uint8Array([0]) }) }), + getCompiledTransactionMessageDecoder: () => ({ decode: () => fakeMessage }), + })); + const { extractPaymentSigner: freshExtract } = await import( + `../src/signer?solana-tx=${freshImportKey()}` + ); + const req = makeRequest({ authorization: 'Payment mpp-cred' }); + const result = await freshExtract(req); + expect(result).toEqual({ address: SOLANA_SIGNER, network: 'solana' }); + vi.doUnmock('mppx'); + vi.doUnmock('@solana/kit'); + }); + + it('returns null on push-mode Solana credentials (payload.type=signature, no tx to decode)', async () => { + vi.doMock('mppx', () => ({ + Credential: { + extractPaymentScheme: () => true, + fromRequest: () => ({ + payload: { signature: 'sigBytes', type: 'signature' }, + }), + }, + })); + const { extractPaymentSigner: freshExtract } = await import( + `../src/signer?solana-push=${freshImportKey()}` + ); + const req = makeRequest({ authorization: 'Payment mpp-cred' }); + expect(await freshExtract(req)).toBeNull(); + vi.doUnmock('mppx'); + }); + it('returns null when the MPP credential source is not a did:pkh:eip155 shape', async () => { vi.doMock('mppx', () => ({ Credential: { From 76364a0518bb91ecfe2088ff968212925fc5418a Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 5 May 2026 00:15:33 -0700 Subject: [PATCH 17/19] fix(signer): defensive bounds-check on Solana lookup-table indices + doc updates Two issues from the round-5 review: 1. Account-index overflow on v0 transactions with address lookup tables. Solana v0 txs can carry instruction account indices that resolve via lookup tables; staticAccounts only holds the static set. If `accountIndices[3]` (TransferChecked authority) points outside staticAccounts, the previous code returned the wrong address (or `undefined`) silently. Now: bounds-check, log a warning, skip cleanly. The `@solana/mpp/client` builder uses static accounts only for fresh SPL transfers, so this is defensive against future tx shapes rather than a current-day bug. 2. Multi-recipient `splits` clarification. The loop returns the FIRST matched TransferChecked authority. For splits, the buyer signs ONE tx with N TransferChecked instructions all sharing the same authority (their wallet), so first-match is correct. Added a comment to make the assumption explicit so a future maintainer doesn't read the loop as buggy. Doc updates pulling the Solana signer-recovery work into the canonical descriptions: - README.md `extractPaymentSigner` snippet now mentions x402 EVM, Tempo MPP, and Solana MPP (DID-or-tx-decode) coverage. - CLAUDE.md wallet-signer-match paragraph spells out the three recovery paths and the optional `@solana/kit` peer dep. --- CLAUDE.md | 2 +- README.md | 5 ++++- src/signer.ts | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 32fdbd8..499bf39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,7 +58,7 @@ Denial reason codes: `missing_identity`, `identity_verification_required`, `toke Captured wallets: `captureWallet(ctx, { walletAddress, network, idempotencyKey })` is fire-and-forget — reads `operator_token` stashed during gating and POSTs to `/v1/credentials/wallets`. No-ops for wallet-authenticated requests. -Wallet-signer-match: `verifyWalletSignerMatch(ctx, { signer, network })` makes a single `/v1/assess` call with `resolve_signer` set; the API resolves both wallets and emits a `signer_match` verdict in the same response — collapses the legacy 2 follow-up assess calls into one round trip. Repeat lookups for the same `(claimed, signer)` pair hit a per-cache-entry `signerMatchBySigner` sub-map and skip the API entirely. Falls back to a 2-resolve path when the API doesn't emit `signer_match` (canary rollout safety). +Wallet-signer-match: `verifyWalletSignerMatch(ctx, { signer, network })` makes a single `/v1/assess` call with `resolve_signer` set; the API resolves both wallets and emits a `signer_match` verdict in the same response — collapses the legacy 2 follow-up assess calls into one round trip. Repeat lookups for the same `(claimed, signer)` pair hit a per-cache-entry `signerMatchBySigner` sub-map and skip the API entirely. Falls back to a 2-resolve path when the API doesn't emit `signer_match` (canary rollout safety). Signer recovery covers x402 EIP-3009 (EVM `from` address), Tempo MPP (`did:pkh:eip155` source), and Solana MPP `solana/charge` (via `did:pkh:solana` source when set, otherwise by decoding the credential's signed-tx payload to read the SPL `TransferChecked` authority — pull mode only, requires the `@solana/kit` optional peer). ### Fail-open (opt-in) diff --git a/README.md b/README.md index 2e43b0f..8a5e5fc 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,10 @@ const directives = [ ]; const wwwAuth = wwwAuthenticateHeader(directives); -// Recover the on-chain signer from the inbound credential — returns {address, network} +// Recover the on-chain signer from the inbound credential — returns {address, network}. +// Covers x402 EIP-3009 (EVM `from` address), Tempo MPP (`did:pkh:eip155` source), +// and Solana MPP `solana/charge` (via `did:pkh:solana` source when set, else by +// decoding the credential's signed-tx payload — `@solana/kit` optional peer). const signer = await extractPaymentSigner(req, req.headers.get("x-payment") ?? undefined); ``` diff --git a/src/signer.ts b/src/signer.ts index 26eb8b8..7b21bd6 100644 --- a/src/signer.ts +++ b/src/signer.ts @@ -58,15 +58,30 @@ async function extractSolanaSignerFromCredential(credential: unknown): Promise= message.staticAccounts.length) { + console.warn( + '[gate] Solana TransferChecked authority resolves through an address lookup table; ' + + 'signer-match recovery requires the static-account form. Skipping.', + ); + continue; + } const authority = message.staticAccounts[authorityIndex]; if (authority) return authority; } From d433b5bf8ce5c7f996d3944aab5ca7aca2144017 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 5 May 2026 01:35:41 -0700 Subject: [PATCH 18/19] test(signer): cover decode-fallback edge cases (kit-missing, lookup-table, throw) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three new tests for `extractSolanaSignerFromCredential`: 1. `@solana/kit` peer dep stub missing required exports → getBase64Codec/getTransactionDecoder/getCompiledTransactionMessageDecoder undefined; returns null without throwing. 2. SPL TransferChecked authority resolves through an address lookup table (accountIndices[3] >= staticAccounts.length); skips with a warning log instead of returning a wrong address. 3. @solana/kit decoder throws on malformed tx bytes; outer try/catch catches and logs, returns null cleanly. Closes the branch-coverage gap that was blocking PR CI (89.95% → 90.1%, above the 90% threshold). --- tests/signer.test.ts | 94 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/tests/signer.test.ts b/tests/signer.test.ts index c477ac4..a425b5c 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -173,6 +173,100 @@ describe('extractPaymentSignerAddress — MPP path', () => { vi.doUnmock('@solana/kit'); }); + it('returns null when @solana/kit is unavailable (decode fallback skipped cleanly)', async () => { + vi.doMock('mppx', () => ({ + Credential: { + extractPaymentScheme: () => true, + fromRequest: () => ({ + payload: { transaction: 'AAAA', type: 'transaction' }, + }), + }, + })); + vi.doMock('@solana/kit', () => ({ + // Stub missing the required exports → decode fallback returns null without throwing + getBase58Codec: () => ({ encode: () => new Uint8Array([0]) }), + })); + const { extractPaymentSigner: freshExtract } = await import( + `../src/signer?solana-no-kit=${freshImportKey()}` + ); + const req = makeRequest({ authorization: 'Payment mpp-cred' }); + expect(await freshExtract(req)).toBeNull(); + vi.doUnmock('mppx'); + vi.doUnmock('@solana/kit'); + }); + + it('skips and warns when TransferChecked authority resolves through an address lookup table', async () => { + // staticAccounts has only 4 entries; instruction asks for index 99 (i.e. lookup-table-loaded). + // Path: bounds-check trips, console.warn, continue, no further match → null. + const TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; + const fakeMessage = { + staticAccounts: [TOKEN_PROGRAM, 'a', 'b', 'c'], + instructions: [ + { + programAddressIndex: 0, + accountIndices: [1, 2, 3, 99], // authority idx 99 > staticAccounts.length + data: new Uint8Array([12, 0, 0, 0]), + }, + ], + }; + vi.doMock('mppx', () => ({ + Credential: { + extractPaymentScheme: () => true, + fromRequest: () => ({ + payload: { transaction: 'AAAA', type: 'transaction' }, + }), + }, + })); + vi.doMock('@solana/kit', () => ({ + getBase64Codec: () => ({ encode: () => new Uint8Array([0]) }), + getTransactionDecoder: () => ({ decode: () => ({ messageBytes: new Uint8Array([0]) }) }), + getCompiledTransactionMessageDecoder: () => ({ decode: () => fakeMessage }), + })); + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const { extractPaymentSigner: freshExtract } = await import( + `../src/signer?solana-lut=${freshImportKey()}` + ); + const req = makeRequest({ authorization: 'Payment mpp-cred' }); + expect(await freshExtract(req)).toBeNull(); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('address lookup table')); + warn.mockRestore(); + vi.doUnmock('mppx'); + vi.doUnmock('@solana/kit'); + }); + + it('returns null and logs when @solana/kit decoder throws', async () => { + vi.doMock('mppx', () => ({ + Credential: { + extractPaymentScheme: () => true, + fromRequest: () => ({ + payload: { transaction: 'AAAA', type: 'transaction' }, + }), + }, + })); + vi.doMock('@solana/kit', () => ({ + getBase64Codec: () => ({ encode: () => new Uint8Array([0]) }), + getTransactionDecoder: () => ({ + decode: () => { + throw new Error('malformed solana tx bytes'); + }, + }), + getCompiledTransactionMessageDecoder: () => ({ decode: () => ({}) }), + })); + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const { extractPaymentSigner: freshExtract } = await import( + `../src/signer?solana-throw=${freshImportKey()}` + ); + const req = makeRequest({ authorization: 'Payment mpp-cred' }); + expect(await freshExtract(req)).toBeNull(); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('Solana credential decode failed'), + expect.anything(), + ); + warn.mockRestore(); + vi.doUnmock('mppx'); + vi.doUnmock('@solana/kit'); + }); + it('returns null on push-mode Solana credentials (payload.type=signature, no tx to decode)', async () => { vi.doMock('mppx', () => ({ Credential: { From 2f93357a67ed771fe05aa12bb673f76d80fbcdf9 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 5 May 2026 01:38:40 -0700 Subject: [PATCH 19/19] =?UTF-8?q?chore(deps):=20bun=20update=20=E2=80=94?= =?UTF-8?q?=20mppx=200.6.14=20=E2=86=92=200.6.15,=20typescript-eslint=208.?= =?UTF-8?q?59.1=20=E2=86=92=208.59.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Within-semver patch bumps. eslint 9 → 10 + @eslint/js 9 → 10 are available but held back as a separate major-bump decision (eslint 10 removed several deprecated APIs and changed the rule meta-format; needs its own validation pass). Tests: 663 vitest passing, lint + typecheck clean. --- bun.lock | 46 +++++++++++++++++++++------------------------- package.json | 4 ++-- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/bun.lock b/bun.lock index df0706e..dd2c9c1 100644 --- a/bun.lock +++ b/bun.lock @@ -26,10 +26,10 @@ "fastify": "^5.8.5", "hono": "^4.12.16", "lefthook": "^2.1.6", - "mppx": "^0.6.14", + "mppx": "^0.6.15", "tsup": "^8.5.1", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.1", + "typescript-eslint": "^8.59.2", "vitest": "^4.1.5", }, "peerDependencies": { @@ -416,21 +416,21 @@ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/type-utils": "8.59.0", "@typescript-eslint/utils": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.2", "@typescript-eslint/types": "8.59.2", "@typescript-eslint/typescript-estree": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.1", "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.2", "@typescript-eslint/types": "^8.59.2", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0" } }, "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw=="], "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/utils": "8.59.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.59.1", "", {}, "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.59.2", "", {}, "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.1", "@typescript-eslint/tsconfig-utils": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.2", "@typescript-eslint/tsconfig-utils": "8.59.2", "@typescript-eslint/types": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.2", "@typescript-eslint/types": "8.59.2", "@typescript-eslint/typescript-estree": "8.59.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q=="], "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q=="], @@ -748,7 +748,7 @@ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - "incur": ["incur@0.3.25", "", { "dependencies": { "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/server": "^2.0.0-alpha.2", "@toon-format/toon": "^2.1.0", "tokenx": "^1.3.0", "yaml": "^2.8.2", "zod": "^4.3.6" }, "bin": { "incur": "dist/bin.js", "incur.src": "src/bin.ts" } }, "sha512-jrSkzauM42ilbQJ6THVkAY6dTulkyVW0sZpVHdA8gfiBwrLrLnLUf8U3bAOegAKBIMSOFgk1idchgu9xm9HMng=="], + "incur": ["incur@0.4.5", "", { "dependencies": { "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/server": "^2.0.0-alpha.2", "@toon-format/toon": "^2.1.0", "tokenx": "^1.3.0", "yaml": "^2.8.2", "zod": "^4.3.6" }, "bin": { "incur": "dist/bin.js", "incur.src": "src/bin.ts" } }, "sha512-4WFlqm5e+IKNoX+lDIjjpebO8ResPx3vPUBChxNfnNo3oGp0ooo6piiiTspOMub2dq2mcG/AmlUq0ejuGfKobg=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -926,7 +926,7 @@ "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], - "mppx": ["mppx@0.6.14", "", { "dependencies": { "incur": "^0.3.25", "ox": "0.14.18", "zod": "^4.3.6" }, "peerDependencies": { "@modelcontextprotocol/sdk": ">=1.25.0", "elysia": ">=1", "express": ">=5", "hono": ">=4.12.14", "viem": ">=2.47.5" }, "optionalPeers": ["@modelcontextprotocol/sdk", "elysia", "express", "hono"], "bin": { "mppx": "dist/bin.js", "mppx.src": "src/bin.ts" } }, "sha512-sux4amv+pIPR/Wf2znvgnh76DG/gWGAplzLuCvEbaYfGV9aycRrZc3u6vttws/prJmYs7+9qkx+/I4gonNqR8w=="], + "mppx": ["mppx@0.6.15", "", { "dependencies": { "incur": "^0.4.5", "ox": "0.14.20", "zod": "^4.3.6" }, "peerDependencies": { "@modelcontextprotocol/sdk": ">=1.25.0", "elysia": ">=1", "express": ">=5", "hono": ">=4.12.14", "viem": ">=2.47.5" }, "optionalPeers": ["@modelcontextprotocol/sdk", "elysia", "express", "hono"], "bin": { "mppx": "dist/bin.js", "mppx.src": "src/bin.ts" } }, "sha512-5p0XtrkKGW158rPAMGIuS8Cmowk4IJVoWEiHdxism7iV+8YjJdxNJG5etMBeEnh52wPRcrs9f18ENDGbwy9/EA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -968,7 +968,7 @@ "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], - "ox": ["ox@0.14.18", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-1Irk/tvMsw7xJDuCTT/u9azSjz0YX9hrYFgJOacIuFwibaW2zZBXAMrpzegndYb5o8GLpxB6/0qro4/c40q6VQ=="], + "ox": ["ox@0.14.20", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -1176,7 +1176,7 @@ "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - "typescript-eslint": ["typescript-eslint@8.59.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.1", "@typescript-eslint/parser": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/utils": "8.59.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ=="], + "typescript-eslint": ["typescript-eslint@8.59.2", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.2", "@typescript-eslint/parser": "8.59.2", "@typescript-eslint/typescript-estree": "8.59.2", "@typescript-eslint/utils": "8.59.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ=="], "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], @@ -1250,9 +1250,9 @@ "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1" } }, "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg=="], + "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2" } }, "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg=="], - "@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg=="], + "@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA=="], "@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], @@ -1262,13 +1262,13 @@ "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g=="], - "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg=="], + "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1" } }, "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg=="], + "@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2" } }, "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg=="], "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], @@ -1302,12 +1302,10 @@ "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - "typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/type-utils": "8.59.1", "@typescript-eslint/utils": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag=="], + "typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.2", "@typescript-eslint/type-utils": "8.59.2", "@typescript-eslint/utils": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ=="], "viem/abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="], - "viem/ox": ["ox@0.14.20", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw=="], - "vitest/tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], "@coinbase/cdp-sdk/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], @@ -1374,22 +1372,20 @@ "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], - "@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg=="], + "@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA=="], "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1" } }, "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg=="], + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2" } }, "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg=="], - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w=="], + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "@typescript-eslint/typescript-estree": "8.59.2", "@typescript-eslint/utils": "8.59.2", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ=="], - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg=="], + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA=="], "typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "viem/ox/abitype": ["abitype@1.2.4", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-dpKH+N27vRjarMVTFFkeY445VTKftzGWpL0FiT7xmVmzQRKazZexzC5uHG0f6XKsVLAuUlndnbGau6lRejClxg=="], - "@coinbase/cdp-sdk/@solana/kit/@solana/accounts/@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], "@coinbase/cdp-sdk/@solana/kit/@solana/accounts/@solana/codecs-strings": ["@solana/codecs-strings@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A=="], diff --git a/package.json b/package.json index b7fe2b9..57a685a 100644 --- a/package.json +++ b/package.json @@ -172,10 +172,10 @@ "fastify": "^5.8.5", "hono": "^4.12.16", "lefthook": "^2.1.6", - "mppx": "^0.6.14", + "mppx": "^0.6.15", "tsup": "^8.5.1", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.1", + "typescript-eslint": "^8.59.2", "vitest": "^4.1.5" } }