From 48975df38b49ad9a2f4cab896104cda6c6af8096 Mon Sep 17 00:00:00 2001 From: Maksym Nebliienko Date: Wed, 17 Jun 2026 18:24:57 +0300 Subject: [PATCH] IX-832: Investigate Tempo --- .changeset/ix-832-tempo.md | 2 + examples/x402-buyer-fetch/src/index.ts | 46 +++++++++++++------ .../test/fixtures/config-response.ts | 23 +++++++++- .../test/unit/inflow-accepts.test.ts | 33 +++++++------ .../test/unit/scheme-registrations.test.ts | 6 ++- 5 files changed, 77 insertions(+), 33 deletions(-) create mode 100644 .changeset/ix-832-tempo.md diff --git a/.changeset/ix-832-tempo.md b/.changeset/ix-832-tempo.md new file mode 100644 index 0000000..a845151 --- /dev/null +++ b/.changeset/ix-832-tempo.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/examples/x402-buyer-fetch/src/index.ts b/examples/x402-buyer-fetch/src/index.ts index 9a4347f..9f23619 100644 --- a/examples/x402-buyer-fetch/src/index.ts +++ b/examples/x402-buyer-fetch/src/index.ts @@ -2,6 +2,7 @@ import 'dotenv/config'; import { createInflowClient } from '@inflowpayai/x402-buyer'; import { sellerProbe } from '@inflowpayai/x402-buyer/probe'; import { x402HTTPClient } from '@x402/core/client'; +import { decodePaymentResponseHeader } from '@x402/core/http'; const apiKey = process.env.INFLOW_API_KEY; if (apiKey === undefined || apiKey === '') { @@ -41,20 +42,35 @@ const paymentHeaders = http.encodePaymentSignatureHeader(paymentPayload); const paid = await fetch(target, { headers: paymentHeaders }); -// processResponse parses the response body once and returns a -// discriminated outcome. `'success'` carries the parsed body and the -// settle response; other branches surface the failure mode. -const result = await http.processResponse(paid); -switch (result.kind) { - case 'success': - console.log(` status: ${result.response.status.toString()}`); - console.log(` body: ${JSON.stringify(result.body)}`); - console.log(` paid via ${result.settleResponse.network}: ${result.settleResponse.transaction}`); - break; - case 'settle_failed': - console.error(` settle failed: ${result.settleResponse.errorReason ?? 'unknown'}`); - process.exit(1); - default: - console.error(` unexpected outcome: kind=${result.kind}`); +const body = await readResponseBody(paid); +console.log(` status: ${paid.status.toString()}`); +console.log(` body: ${JSON.stringify(body)}`); + +const paymentResponseHeader = paid.headers.get('payment-response') ?? paid.headers.get('x-payment-response'); +if (paymentResponseHeader !== null && paymentResponseHeader !== '') { + const settled = decodePaymentResponseHeader(paymentResponseHeader); + if (settled.success) { + console.log(` paid via ${settled.network}: ${settled.transaction}`); + } else { + console.error(` settle failed: ${settled.errorReason ?? settled.errorMessage ?? 'unknown'}`); process.exit(1); + } +} + +if (!paid.ok) { + process.exit(1); +} + +async function readResponseBody(response: Response): Promise { + const text = await response.text(); + if (text === '') { + return undefined; + } + + const contentType = response.headers.get('content-type') ?? ''; + if (contentType.includes('application/json')) { + return JSON.parse(text) as unknown; + } + + return text; } diff --git a/packages/x402-seller/test/fixtures/config-response.ts b/packages/x402-seller/test/fixtures/config-response.ts index 57f555c..0da961c 100644 --- a/packages/x402-seller/test/fixtures/config-response.ts +++ b/packages/x402-seller/test/fixtures/config-response.ts @@ -1,14 +1,15 @@ import type { X402ConfigResponse, X402FacilitatorSupportedResponse } from '@inflowpayai/x402'; /** - * Representative `X402ConfigResponse` shape. One EVM chain (Base) with USDC + USDT, one Solana chain with USDC, and an - * InFlow balance payment method. All addresses are deterministic fakes. + * Representative `X402ConfigResponse` shape. Base with USDC + USDT, Solana with USDC, Tempo with USDC, and an InFlow + * balance payment method. All addresses are deterministic fakes. */ export const SAMPLE_CONFIG: X402ConfigResponse = { sellerId: '00000000-0000-0000-0000-000000000001', supported: [ { network: 'eip155:8453', scheme: 'exact', x402Version: 2 }, { network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', scheme: 'exact', x402Version: 2 }, + { network: 'eip155:4217', scheme: 'exact', x402Version: 2 }, { network: 'inflow:1', scheme: 'balance', x402Version: 2 }, ], wallets: [ @@ -23,6 +24,11 @@ export const SAMPLE_CONFIG: X402ConfigResponse = { network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', feePayer: 'SoLaNaFeePayer0000000000000000000000000004', }, + { + address: '0x0000000000000000000000000000000000004217', + blockchain: 'TEMPO', + network: 'eip155:4217', + }, ], assets: [ { @@ -58,6 +64,18 @@ export const SAMPLE_CONFIG: X402ConfigResponse = { decimals: 6, network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', }, + { + assetTransferMethod: 'permit2', + assetId: '0x20c000000000000000000000b9537d11c60e8b50', + assetName: 'USDC', + blockchain: 'TEMPO', + currency: 'USDC', + decimals: 6, + network: 'eip155:4217', + permit2Proxy: '0x402085c248EeA27D92E8b30b2C58ed07f9E20001', + tokenName: 'USDC.e', + tokenVersion: '2', + }, ], paymentMethods: [ { @@ -74,6 +92,7 @@ export const SAMPLE_SUPPORTED: X402FacilitatorSupportedResponse = { extensions: ['payment-identifier'], signers: { 'eip155:8453': ['0xSigner1', '0xSigner2'], + 'eip155:4217': ['0xTempoSigner'], 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': ['SoLaNaSigner'], }, }; diff --git a/packages/x402-seller/test/unit/inflow-accepts.test.ts b/packages/x402-seller/test/unit/inflow-accepts.test.ts index 8ea265d..c2546d1 100644 --- a/packages/x402-seller/test/unit/inflow-accepts.test.ts +++ b/packages/x402-seller/test/unit/inflow-accepts.test.ts @@ -33,15 +33,14 @@ describe('inflowAccepts', () => { // SAMPLE_CONFIG (under USD wildcard): // On-chain: USDC/Base (1 entry), USDT/Base (1 entry), - // USDC/Solana (1 entry) = 3 exact entries. The SDK takes - // `assetTransferMethod` from the server verbatim — no - // implicit EIP-3009/Permit2 fanout. + // USDC/Solana (1 entry), USDC/Tempo (1 entry) = 4 exact entries. + // The SDK takes `assetTransferMethod` from the server verbatim. // Balance: 1 paymentMethod × 2 distinct asset currencies (USDC, // USDT) = 2 balance entries. - // Total: 3 + 2 = 5. - expect(out).toHaveLength(5); + // Total: 4 + 2 = 6. + expect(out).toHaveLength(6); const schemes = out.map((o) => o.scheme); - expect(schemes.filter((s) => s === 'exact')).toHaveLength(3); + expect(schemes.filter((s) => s === 'exact')).toHaveLength(4); expect(schemes.filter((s) => s === 'balance')).toHaveLength(2); }); @@ -112,6 +111,13 @@ describe('inflowAccepts', () => { expect(extra.permit2Proxy).toBe('0x402085c248EeA27D92E8b30b2C58ed07f9E20001'); }); + it('on-chain entries advertise the server assetName in extras', async () => { + const client = await makeClient(); + const out = await inflowAccepts(client, { price: '0.01 USDC', networks: ['eip155:4217'] }); + expect(out).toHaveLength(1); + expect((out[0]!.extra as { assetName: string }).assetName).toBe('USDC'); + }); + it('emits feePayer on Solana entries', async () => { const client = await makeClient(); const out = await inflowAccepts(client, { price: '$0.01' }); @@ -230,10 +236,8 @@ describe('inflowAccepts', () => { const client = await makeClient(); const out = await inflowAccepts(client, { price: '$0.01', schemes: ['exact'] }); expect(out.every((o) => o.scheme === 'exact')).toBe(true); - // SAMPLE_CONFIG has three on-chain assets: USDC/Base, USDT/Base, - // USDC/Solana. One entry per (wallet, asset) pair — no implicit - // EIP-3009/Permit2 fanout — so 3 exact entries total. - expect(out).toHaveLength(3); + // SAMPLE_CONFIG has four on-chain assets: USDC/Base, USDT/Base, USDC/Solana, and USDC/Tempo. + expect(out).toHaveLength(4); }); it('filters by schemes (balance only)', async () => { @@ -332,20 +336,21 @@ describe('inflowAccepts', () => { const client = await makeClient(); const out = await inflowAccepts(client, { price: '$0.01' }); const assets = new Set(out.filter((o) => o.scheme === 'exact').map((o) => (o.price as { asset: string }).asset)); - // Both USDC (Base + Solana) and USDT (Base) — three distinct assets. - expect(assets.size).toBe(3); + // USDC on Base, Solana, and Tempo plus USDT on Base. + expect(assets.size).toBe(4); }); it('ordering: on-chain entries in wallet declaration order, then payment methods', async () => { const client = await makeClient(); const out = await inflowAccepts(client, { price: '$0.01' }); - // Wallet declaration order: Base, Solana. One entry per (wallet, asset). + // Wallet declaration order: Base, Solana, Tempo. One entry per (wallet, asset). // Then payment methods (one per distinct currency under USD wildcard). expect(out[0]!.network).toBe('eip155:8453'); // USDC eip3009 expect(out[1]!.network).toBe('eip155:8453'); // USDT permit2 expect(out[2]!.network).toBe('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'); // USDC solana - expect(out[3]!.network).toBe('inflow:1'); + expect(out[3]!.network).toBe('eip155:4217'); // USDC Tempo permit2 expect(out[4]!.network).toBe('inflow:1'); + expect(out[5]!.network).toBe('inflow:1'); }); it('within an asset, the server-published assetTransferMethod is emitted verbatim', async () => { diff --git a/packages/x402-seller/test/unit/scheme-registrations.test.ts b/packages/x402-seller/test/unit/scheme-registrations.test.ts index 2c5b3ea..bb629d6 100644 --- a/packages/x402-seller/test/unit/scheme-registrations.test.ts +++ b/packages/x402-seller/test/unit/scheme-registrations.test.ts @@ -34,10 +34,12 @@ describe('inflowSchemeRegistrations', () => { // SAMPLE_CONFIG has: // - assets on eip155:8453 (USDC + USDT) → dedupes to one (exact, eip155:8453) // - asset on solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp → (exact, solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp) + // - asset on eip155:4217 → (exact, eip155:4217) // - paymentMethods: balance / inflow:1 → (balance, inflow:1) expect(pairs(registrations)).toEqual([ ['exact', 'eip155:8453'], ['exact', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], + ['exact', 'eip155:4217'], ['balance', 'inflow:1'], ]); }); @@ -71,11 +73,11 @@ describe('inflowSchemeRegistrations', () => { ], }); const registrations = await inflowSchemeRegistrations(client); - // Still exactly three: dedupe collapsed the duplicate. - expect(registrations).toHaveLength(3); + expect(registrations).toHaveLength(4); expect(pairs(registrations)).toEqual([ ['exact', 'eip155:8453'], ['exact', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], + ['exact', 'eip155:4217'], ['balance', 'inflow:1'], ]); });