Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 99 additions & 103 deletions bun.lock

Large diffs are not rendered by default.

37 changes: 18 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agent-score/commerce",
"version": "2.14.0",
"version": "2.15.0",
"description": "Agentic 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 agentic commerce.",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down Expand Up @@ -125,7 +125,6 @@
"fastify",
"middleware",
"trust",
"reputation",
"kyc",
"identity",
"stripe",
Expand All @@ -148,12 +147,12 @@
"bun": ">=1.3.0 <2"
},
"dependencies": {
"@agent-score/sdk": "^2.7.12"
"@agent-score/sdk": "^2.8.0"
},
"overrides": {
"axios": "^1.18.0",
"axios": "^1.20.0",
"esbuild": "^0.28.1",
"viem": "2.57.0"
"viem": "2.57.2"
},
"peerDependencies": {
"@solana/kit": ">=8.0.0 <9.0.0",
Expand Down Expand Up @@ -192,34 +191,34 @@
}
},
"devDependencies": {
"@a2a-js/sdk": "^1.1.0",
"@a2a-js/sdk": "^1.3.0",
"@coinbase/x402": "^2.1.0",
"@eslint/js": "^9.39.5",
"@solana/kit": "^8.2.0",
"@solana/kit": "^8.4.0",
"@solana/mpp": "^0.7.0",
"@types/express": "^5.0.6",
"@types/node": "^26.6.1",
"@vitest/coverage-v8": "^5.0.1",
"@x402/core": "2.27.0",
"@x402/evm": "2.27.0",
"@x402/extensions": "2.27.0",
"dotenv": "^18.0.1",
"@vitest/coverage-v8": "^5.0.3",
"@x402/core": "2.28.0",
"@x402/evm": "2.28.0",
"@x402/extensions": "2.28.0",
"dotenv": "^18.0.5",
"eslint": "^9.39.5",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-unused-imports": "^4.4.1",
"express": "^5.2.1",
"fastify": "^5.12.5",
"hono": "^4.13.8",
"hono": "^4.13.12",
"ioredis": "^6.0.0",
"jose": "^6.2.12",
"knip": "^6.36.0",
"lefthook": "^2.1.14",
"mppx": "0.11.0",
"knip": "^6.39.0",
"lefthook": "^2.1.16",
"mppx": "0.12.0",
"tsup": "^8.5.1",
"typescript": "^6.0.3",
"typescript-eslint": "^8.69.0",
"viem": "2.57.0",
"vitest": "^5.0.1"
"typescript-eslint": "^8.71.0",
"viem": "2.57.2",
"vitest": "^5.0.3"
},
"packageManager": "[email protected]"
}
2 changes: 1 addition & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* everything from the underlying `@agent-score/sdk` here. Don't add `@agent-score/sdk`
* as a separate dep; the two can drift versions and cause subtle type mismatches.
*
* Use this for: programmatic API calls (sessions, credentials, reputation) and the
* Use this for: programmatic API calls (assess, sessions, credentials) and the
* test-mode address fixtures for integration tests.
*/
export {
Expand Down
2 changes: 1 addition & 1 deletion src/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ export interface CheckoutGateConfig {
failOpen?: boolean;
/** How long to cache results in seconds. Default 300. */
cacheSeconds?: number;
/** Optional chain filter for scoring. */
/** Optional chain passed to the assess call. */
chain?: string;
/** Session-mint config for missing-identity bootstrap. Hooks receive the
* CheckoutContext so `getSessionOptions` and `onBeforeSession` can read
Expand Down
2 changes: 1 addition & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export interface AgentScoreCoreOptions {
cacheSeconds?: number;
/** AgentScore API base URL. Defaults to "https://api.agentscore.com". */
baseUrl?: string;
/** Optional chain to filter scoring to. */
/** Optional chain passed to the assess call. */
chain?: string;
/** Prepended to the default User-Agent as `"{userAgent} (@agent-score/commerce@{version})"`. Use to attribute API calls to your app. */
userAgent?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/stripe-multichain/payment_intent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ async function createMultichainPaymentIntentOnce({
{
amount,
currency,
payment_method_types: ['crypto'],
allowed_payment_method_types: ['crypto'],
payment_method_data: { type: 'crypto' },
payment_method_options: {
crypto: {
Expand Down
29 changes: 28 additions & 1 deletion tests/payment/mppx_server.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
import { describe, expect, it } from 'vitest';
import { createMppxServer } from '../../src/payment/mppx_server';
import { buildMppxComposeRails } from '../../src/payment/compose_rails';
import { composeMppxRequest, createMppxServer } from '../../src/payment/mppx_server';
import { networks } from '../../src/payment/networks';
import { USDC } from '../../src/payment/usdc';
import type {
SolanaMppRailSpec,
TempoRailSpec,
} from '../../src/payment/rail_spec';

function decodeChallengeRequests(header: string): Record<string, unknown>[] {
return [...header.matchAll(/request="([^"]+)"/g)].map(
([, encoded]) => JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8')) as Record<string, unknown>,
);
}

describe('createMppxServer', () => {
// mppx defaults an unconfigured Tempo offer to OUSD first; the rail must advertise only the USDC we settle in.
it('issues a single Tempo challenge priced in USDC', async () => {
const recipient = '0x0000000000000000000000000000000000000001';
const server = await createMppxServer({
rails: { tempo: { recipient } as TempoRailSpec },
secretKey: 'mpp_test_secret_key_padded_to_32_bytes',
});
const result = await composeMppxRequest(
server,
buildMppxComposeRails({ amountUsd: '1.00', tempoRecipient: recipient, includeStripe: false }),
new Request('https://merchant.example/buy', { method: 'POST' }),
);
expect(result.status).toBe(402);
const header = (result as { challenge: Response }).challenge.headers.get('www-authenticate') ?? '';
const requests = decodeChallengeRequests(header);
expect(requests).toHaveLength(1);
expect(requests[0]).toMatchObject({ amount: '1000000', currency: USDC.tempo.mainnet.address, recipient });
});

it('returns an mppx server with no rails when none configured', async () => {
const server = await createMppxServer({ secretKey: 'mpp_test_secret_key_padded_to_32_bytes' });
expect(server).toBeDefined();
Expand Down
2 changes: 1 addition & 1 deletion tests/payment/mppx_server_extras.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('createMppxServer — additional rail branches', () => {
rails: {
tempo: {
recipient: '0x0000000000000000000000000000000000000001',
token: '0xCustomCurrencyAddress',
token: '0x20c0000000000000000000000000000000000abc',
} as TempoRailSpec,
},
secretKey: 'mpp_test_secret_key_padded_to_32_bytes',
Expand Down
13 changes: 13 additions & 0 deletions tests/stripe-multichain/payment_intent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ describe('createMultichainPaymentIntent', () => {
expect(callArgs.payment_method_options.crypto.deposit_options.networks).toEqual(['tempo', 'base', 'solana']);
});

// Stripe's 2026-09-30.preview API rejects payment_method_types ("no longer supported");
// allowed_payment_method_types is accepted on that version and on the earlier previews.
it('restricts the intent to crypto with allowed_payment_method_types', async () => {
const stripe = fakeStripe({
id: 'pi_types',
next_action: { crypto_display_details: { deposit_addresses: { base: { address: '0xb' } } } },
});
await createMultichainPaymentIntent({ stripe, amount: 100 });
const params = stripe._create.mock.calls[0]![0] as Record<string, unknown>;
expect(params.allowed_payment_method_types).toEqual(['crypto']);
expect(params).not.toHaveProperty('payment_method_types');
});

it('passes idempotencyKey + metadata through', async () => {
const stripe = fakeStripe({
id: 'pi_x',
Expand Down
Loading