@relai-fi/x402 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.cjs +18 -6
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +18 -6
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +27 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -9
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +26 -8
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +26 -8
- package/dist/react/index.js.map +1 -1
- package/dist/server.cjs +8 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +8 -2
- package/dist/server.js.map +1 -1
- package/dist/{types-Bw8MIl3i.d.cts → types-C9LlAJj8.d.cts} +1 -1
- package/dist/{types-Bw8MIl3i.d.ts → types-C9LlAJj8.d.ts} +1 -1
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -2
package/dist/react/index.js
CHANGED
|
@@ -22,7 +22,10 @@ var NETWORK_CAIP2 = {
|
|
|
22
22
|
"solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
23
23
|
"base": "eip155:8453",
|
|
24
24
|
"avalanche": "eip155:43114",
|
|
25
|
-
"skale-base": "eip155:1187947933"
|
|
25
|
+
"skale-base": "eip155:1187947933",
|
|
26
|
+
"skale-bite": "eip155:103698795",
|
|
27
|
+
"polygon": "eip155:137",
|
|
28
|
+
"ethereum": "eip155:1"
|
|
26
29
|
};
|
|
27
30
|
var CAIP2_TO_NETWORK = Object.fromEntries(
|
|
28
31
|
Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k])
|
|
@@ -30,36 +33,48 @@ var CAIP2_TO_NETWORK = Object.fromEntries(
|
|
|
30
33
|
var CHAIN_IDS = {
|
|
31
34
|
"base": 8453,
|
|
32
35
|
"avalanche": 43114,
|
|
33
|
-
"skale-base": 1187947933
|
|
36
|
+
"skale-base": 1187947933,
|
|
37
|
+
"skale-bite": 103698795,
|
|
38
|
+
"polygon": 137,
|
|
39
|
+
"ethereum": 1
|
|
34
40
|
};
|
|
35
41
|
var USDC_ADDRESSES = {
|
|
36
42
|
"solana": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
37
43
|
"base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
38
44
|
"avalanche": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
39
|
-
"skale-base": "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20"
|
|
45
|
+
"skale-base": "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
46
|
+
"skale-bite": "0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8",
|
|
47
|
+
"polygon": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
48
|
+
"ethereum": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
40
49
|
};
|
|
41
50
|
var EXPLORER_TX_URL = {
|
|
42
51
|
"solana": (tx) => `https://solscan.io/tx/${tx}`,
|
|
43
52
|
"base": (tx) => `https://basescan.org/tx/${tx}`,
|
|
44
53
|
"avalanche": (tx) => `https://snowtrace.io/tx/${tx}`,
|
|
45
|
-
"skale-base": (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}
|
|
54
|
+
"skale-base": (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}`,
|
|
55
|
+
"skale-bite": (tx) => `https://base-sepolia-testnet.explorer.skalenodes.com/tx/${tx}`,
|
|
56
|
+
"polygon": (tx) => `https://polygonscan.com/tx/${tx}`,
|
|
57
|
+
"ethereum": (tx) => `https://etherscan.io/tx/${tx}`
|
|
46
58
|
};
|
|
47
59
|
var NETWORK_LABELS = {
|
|
48
60
|
"solana": "Solana",
|
|
49
61
|
"base": "Base",
|
|
50
62
|
"avalanche": "Avalanche",
|
|
51
|
-
"skale-base": "SKALE Base"
|
|
63
|
+
"skale-base": "SKALE Base",
|
|
64
|
+
"skale-bite": "SKALE BITE V2",
|
|
65
|
+
"polygon": "Polygon",
|
|
66
|
+
"ethereum": "Ethereum"
|
|
52
67
|
};
|
|
53
68
|
var SOLANA_MAINNET_NETWORK = NETWORK_CAIP2["solana"];
|
|
54
69
|
var BASE_MAINNET_NETWORK = NETWORK_CAIP2["base"];
|
|
55
70
|
var USDC_SOLANA = USDC_ADDRESSES["solana"];
|
|
56
71
|
var USDC_BASE = USDC_ADDRESSES["base"];
|
|
57
|
-
var RELAI_NETWORKS = ["solana", "base", "avalanche", "skale-base"];
|
|
72
|
+
var RELAI_NETWORKS = ["solana", "base", "avalanche", "skale-base", "skale-bite", "polygon", "ethereum"];
|
|
58
73
|
function isSolana(network) {
|
|
59
74
|
return network === "solana" || network.startsWith("solana:");
|
|
60
75
|
}
|
|
61
76
|
function isEvm(network) {
|
|
62
|
-
return ["base", "avalanche", "skale-base"].includes(network) || network.startsWith("eip155:");
|
|
77
|
+
return ["base", "avalanche", "skale-base", "skale-bite", "polygon", "ethereum"].includes(network) || network.startsWith("eip155:");
|
|
63
78
|
}
|
|
64
79
|
function normalizeNetwork(network) {
|
|
65
80
|
if (RELAI_NETWORKS.includes(network)) return network;
|
|
@@ -78,8 +93,11 @@ function normalizeNetwork(network) {
|
|
|
78
93
|
var PERMIT_NETWORKS = /* @__PURE__ */ new Set([]);
|
|
79
94
|
var DEFAULT_EVM_RPC_URLS = {
|
|
80
95
|
"skale-base": "https://skale-base.skalenodes.com/v1/base",
|
|
96
|
+
"skale-bite": "https://base-sepolia-testnet.skalenodes.com/v1/bite-v2-sandbox",
|
|
81
97
|
"base": "https://mainnet.base.org",
|
|
82
|
-
"avalanche": "https://api.avax.network/ext/bc/C/rpc"
|
|
98
|
+
"avalanche": "https://api.avax.network/ext/bc/C/rpc",
|
|
99
|
+
"polygon": "https://polygon-rpc.com",
|
|
100
|
+
"ethereum": "https://ethereum-rpc.publicnode.com"
|
|
83
101
|
};
|
|
84
102
|
function createX402Client(config) {
|
|
85
103
|
const {
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/useRelaiPayment.ts","../../src/client.ts","../../src/types.ts"],"sourcesContent":["/**\n * React Hook for RelAI x402 Payments\n *\n * Single hook that auto-detects the network from the 402 response and uses\n * the correct signing method. Supports all RelAI facilitator networks:\n * Solana, Base, Avalanche, SKALE Base.\n *\n * @example\n * ```tsx\n * import { useRelaiPayment } from '@relai-fi/x402/react';\n *\n * function PayButton() {\n * const {\n * fetch,\n * isLoading,\n * status,\n * error,\n * transactionUrl,\n * connectedChains,\n * } = useRelaiPayment({\n * wallets: { solana: solanaWallet, evm: evmWalletClient },\n * });\n *\n * return (\n * <button onClick={() => fetch(url)} disabled={isLoading}>\n * {isLoading ? 'Paying...' : 'Access'}\n * </button>\n * );\n * }\n * ```\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport { createX402Client, type X402ClientConfig } from '../client';\nimport type { SolanaWallet, EvmWallet, WalletSet } from '../types';\nimport {\n EXPLORER_TX_URL,\n NETWORK_LABELS,\n normalizeNetwork,\n type RelaiNetwork,\n} from '../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type PaymentStatus = 'idle' | 'pending' | 'success' | 'error';\n\nexport interface ConnectedChains {\n solana: boolean;\n evm: boolean;\n}\n\nexport interface UseRelaiPaymentConfig {\n /** Wallets — pass both Solana + EVM and the hook picks automatically */\n wallets?: WalletSet;\n /** Preferred network when multiple options available */\n preferredNetwork?: RelaiNetwork;\n /** Custom Solana RPC URL */\n solanaRpcUrl?: string;\n /** Custom facilitator URL (default: RelAI) */\n facilitatorUrl?: string;\n /** Maximum payment in atomic units (safety guard) */\n maxAmountAtomic?: string;\n /** Verbose console logging */\n verbose?: boolean;\n}\n\nexport interface UseRelaiPaymentReturn {\n /** Fetch with automatic x402 payment handling */\n fetch: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\n /** Payment in progress */\n isLoading: boolean;\n /** Current status */\n status: PaymentStatus;\n /** Error on failure */\n error: Error | null;\n /** Transaction hash/signature on success */\n transactionId: string | null;\n /** Network the payment was made on */\n transactionNetwork: RelaiNetwork | null;\n /** Human-readable network label */\n transactionNetworkLabel: string | null;\n /** Block explorer URL for the transaction */\n transactionUrl: string | null;\n /** Which chain types have connected wallets */\n connectedChains: ConnectedChains;\n /** Whether any wallet is connected */\n isConnected: boolean;\n /** Reset hook state to idle */\n reset: () => void;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction isSolanaWalletConnected(wallet?: SolanaWallet): boolean {\n return !!(wallet?.publicKey && wallet?.signTransaction);\n}\n\nfunction isEvmWalletConnected(wallet?: EvmWallet): boolean {\n return !!(wallet?.address && wallet?.signTypedData);\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useRelaiPayment(config: UseRelaiPaymentConfig = {}): UseRelaiPaymentReturn {\n const {\n wallets = {},\n preferredNetwork,\n solanaRpcUrl,\n facilitatorUrl,\n maxAmountAtomic,\n verbose = false,\n } = config;\n\n // State\n const [isLoading, setIsLoading] = useState(false);\n const [status, setStatus] = useState<PaymentStatus>('idle');\n const [error, setError] = useState<Error | null>(null);\n const [transactionId, setTransactionId] = useState<string | null>(null);\n const [transactionNetwork, setTransactionNetwork] = useState<RelaiNetwork | null>(null);\n\n const log = useCallback((...args: unknown[]) => {\n if (verbose) console.log('[useRelaiPayment]', ...args);\n }, [verbose]);\n\n // Connected chains derived from wallets\n const connectedChains = useMemo((): ConnectedChains => ({\n solana: isSolanaWalletConnected(wallets.solana),\n evm: isEvmWalletConnected(wallets.evm),\n }), [wallets]);\n\n const isConnected = connectedChains.solana || connectedChains.evm;\n\n // Build x402 client (re-created when wallets change)\n const client = useMemo(() => {\n const cfg: X402ClientConfig = {\n wallets,\n preferredNetwork,\n maxAmountAtomic,\n verbose,\n };\n if (solanaRpcUrl) cfg.solanaRpcUrl = solanaRpcUrl;\n if (facilitatorUrl) cfg.facilitatorUrl = facilitatorUrl;\n return createX402Client(cfg);\n }, [wallets, preferredNetwork, solanaRpcUrl, facilitatorUrl, maxAmountAtomic, verbose]);\n\n // Reset\n const reset = useCallback(() => {\n setIsLoading(false);\n setStatus('idle');\n setError(null);\n setTransactionId(null);\n setTransactionNetwork(null);\n }, []);\n\n // Main fetch\n const fetchWithPayment = useCallback(async (\n input: string | URL | Request,\n init?: RequestInit,\n ): Promise<Response> => {\n setIsLoading(true);\n setStatus('pending');\n setError(null);\n setTransactionId(null);\n setTransactionNetwork(null);\n\n if (!isConnected) {\n const err = new Error('No wallet connected. Connect a Solana or EVM wallet.');\n setError(err);\n setStatus('error');\n setIsLoading(false);\n throw err;\n }\n\n try {\n log('Fetching with x402 client:', input);\n const response = await client.fetch(input, init);\n\n // Extract transaction info from PAYMENT-RESPONSE header\n const paymentResponse =\n response.headers.get('PAYMENT-RESPONSE') ||\n response.headers.get('payment-response');\n\n if (paymentResponse) {\n try {\n const decoded = JSON.parse(atob(paymentResponse));\n if (decoded.transaction) setTransactionId(decoded.transaction);\n if (decoded.network) {\n const net = normalizeNetwork(decoded.network);\n if (net) setTransactionNetwork(net);\n }\n } catch {\n log('Could not parse PAYMENT-RESPONSE header');\n }\n }\n\n setStatus('success');\n return response;\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n setStatus('error');\n throw err;\n } finally {\n setIsLoading(false);\n }\n }, [client, isConnected, log]);\n\n // Derived: explorer URL\n const transactionUrl = useMemo(() => {\n if (!transactionId || !transactionNetwork) return null;\n const fn = EXPLORER_TX_URL[transactionNetwork as RelaiNetwork];\n return fn ? fn(transactionId) : null;\n }, [transactionId, transactionNetwork]);\n\n // Derived: network label\n const transactionNetworkLabel = useMemo(() => {\n if (!transactionNetwork) return null;\n return NETWORK_LABELS[transactionNetwork as RelaiNetwork] || transactionNetwork;\n }, [transactionNetwork]);\n\n return {\n fetch: fetchWithPayment,\n isLoading,\n status,\n error,\n transactionId,\n transactionNetwork,\n transactionNetworkLabel,\n transactionUrl,\n connectedChains,\n isConnected,\n reset,\n };\n}\n","// src/client.ts\nimport {\n Connection,\n PublicKey,\n TransactionMessage,\n VersionedTransaction,\n} from '@solana/web3.js';\nimport {\n getAssociatedTokenAddress,\n createTransferCheckedInstruction,\n getMint,\n TOKEN_PROGRAM_ID,\n TOKEN_2022_PROGRAM_ID,\n} from '@solana/spl-token';\nimport type { SolanaWallet, EvmWallet, WalletSet } from './types';\nimport {\n RELAI_FACILITATOR_URL,\n NETWORK_CAIP2,\n CHAIN_IDS,\n isSolana,\n isEvm,\n normalizeNetwork,\n type RelaiNetwork,\n} from './types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface X402ClientConfig {\n /** Multi-chain wallets (Solana + EVM) */\n wallets?: WalletSet;\n /** Single Solana wallet (legacy shortcut) */\n wallet?: SolanaWallet;\n /** Custom facilitator URL, default: RelAI facilitator */\n facilitatorUrl?: string;\n /** Preferred network when multiple options available */\n preferredNetwork?: RelaiNetwork;\n /** Custom Solana RPC URL */\n solanaRpcUrl?: string;\n /** Custom EVM RPC URLs per network (e.g. { 'skale-base': 'https://...' }) */\n evmRpcUrls?: Record<string, string>;\n /** Maximum payment amount in atomic units */\n maxAmountAtomic?: string;\n /** Enable verbose logging */\n verbose?: boolean;\n}\n\nexport interface X402Client {\n /** Fetch with automatic x402 payment handling */\n fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;\n}\n\n/**\n * Create an x402 client for automatic payment handling.\n * Supports all RelAI facilitator networks: Solana, Base, Avalanche, SKALE Base.\n * Auto-detects the correct chain from the 402 response and picks the right\n * signing method (Solana SPL transfer, EVM EIP-3009 transferWithAuthorization).\n *\n * @example\n * ```typescript\n * import { createX402Client } from '@relai-fi/x402';\n *\n * const client = createX402Client({\n * wallets: { solana: solanaWallet, evm: evmWalletClient },\n * });\n *\n * // Automatically handles 402 on any RelAI-supported network\n * const response = await client.fetch('https://api.example.com/protected');\n * ```\n */\n// Networks that use EIP-2612 permit instead of EIP-3009 transferWithAuthorization (currently none)\nconst PERMIT_NETWORKS = new Set<string>([]);\n\n// Default EVM RPC URLs\nconst DEFAULT_EVM_RPC_URLS: Record<string, string> = {\n 'skale-base': 'https://skale-base.skalenodes.com/v1/base',\n 'base': 'https://mainnet.base.org',\n 'avalanche': 'https://api.avax.network/ext/bc/C/rpc',\n};\n\nexport function createX402Client(config: X402ClientConfig): X402Client {\n const {\n wallets = {},\n wallet: legacyWallet,\n facilitatorUrl = RELAI_FACILITATOR_URL,\n preferredNetwork,\n solanaRpcUrl = 'https://api.mainnet-beta.solana.com',\n evmRpcUrls = {},\n maxAmountAtomic,\n verbose = false,\n } = config;\n\n const log = verbose ? console.log.bind(console, '[relai-x402]') : () => {};\n\n // Merge legacy wallet into wallet set\n const effectiveWallets: WalletSet = { ...wallets };\n if (legacyWallet && !effectiveWallets.solana) {\n effectiveWallets.solana = legacyWallet;\n }\n\n const hasSolanaWallet = Boolean(\n effectiveWallets.solana?.publicKey && effectiveWallets.solana?.signTransaction\n );\n if (hasSolanaWallet) log('Solana wallet ready');\n\n // -----------------------------------------------------------------------\n // Select a payment option from the 402 response's `accepts` array\n // -----------------------------------------------------------------------\n function selectAccept(accepts: any[]): { accept: any; chain: 'solana' | 'evm' } | null {\n // 1) Preferred network first\n if (preferredNetwork) {\n const caip2 = NETWORK_CAIP2[preferredNetwork];\n for (const a of accepts) {\n const net = a.network || '';\n if (net === preferredNetwork || net === caip2) {\n const chain = isSolana(net) ? 'solana' as const : 'evm' as const;\n if ((chain === 'solana' && hasSolanaWallet) || (chain === 'evm' && effectiveWallets.evm)) {\n return { accept: a, chain };\n }\n }\n }\n }\n\n // 2) First option we have a wallet for\n for (const a of accepts) {\n const net = a.network || '';\n if (isSolana(net) && hasSolanaWallet) return { accept: a, chain: 'solana' };\n if (isEvm(net) && effectiveWallets.evm) return { accept: a, chain: 'evm' };\n }\n\n return null;\n }\n\n // -----------------------------------------------------------------------\n // JSON-RPC helper (for reading EVM contract state without ethers)\n // -----------------------------------------------------------------------\n async function evmRpcCall(rpcUrl: string, to: string, data: string): Promise<string> {\n const res = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n method: 'eth_call',\n params: [{ to, data }, 'latest'],\n id: 1,\n }),\n });\n const json = await res.json();\n if (json.error) throw new Error(`RPC error: ${json.error.message}`);\n return json.result;\n }\n\n function getEvmRpcUrl(network: string): string {\n return evmRpcUrls[network] || DEFAULT_EVM_RPC_URLS[network] || '';\n }\n\n // -----------------------------------------------------------------------\n // Build EVM payment — EIP-2612 permit (for SKALE Base)\n // -----------------------------------------------------------------------\n async function buildEvmPermitPayment(\n accept: any,\n requirements: any,\n url: string,\n ): Promise<string> {\n const evmWallet = effectiveWallets.evm!;\n const extra = accept.extra || {};\n\n const rawNetwork = accept.network || '';\n const network = normalizeNetwork(rawNetwork);\n const chainId = network ? CHAIN_IDS[network] : parseInt(rawNetwork.split(':')[1] || '8453');\n const paymentAmount = accept.amount || accept.maxAmountRequired;\n const spender = extra.feePayer || accept.payTo;\n const usdcAddress = accept.asset;\n\n const rpcUrl = getEvmRpcUrl(network || rawNetwork);\n if (!rpcUrl) throw new Error(`[relai-x402] No EVM RPC URL for network ${network || rawNetwork}`);\n\n log('Building EIP-2612 permit on chain', chainId);\n\n // Read nonce from USDC contract: nonces(address) = 0x7ecebe00\n const paddedAddress = evmWallet.address.toLowerCase().replace('0x', '').padStart(64, '0');\n const nonceHex = await evmRpcCall(rpcUrl, usdcAddress, '0x7ecebe00' + paddedAddress);\n const nonce = nonceHex ? parseInt(nonceHex, 16) : 0;\n if (isNaN(nonce)) throw new Error(`[relai-x402] Failed to read permit nonce from ${usdcAddress} on ${rpcUrl}`);\n log(' Permit nonce:', nonce);\n\n // Read token name: name() = 0x06fdde03\n const nameHex = await evmRpcCall(rpcUrl, usdcAddress, '0x06fdde03');\n // Decode ABI-encoded string\n let tokenName = 'USD Coin';\n try {\n const offset = parseInt(nameHex.slice(2, 66), 16) * 2;\n const length = parseInt(nameHex.slice(2 + offset, 2 + offset + 64), 16);\n const hex = nameHex.slice(2 + offset + 64, 2 + offset + 64 + length * 2);\n tokenName = decodeURIComponent(hex.replace(/[0-9a-f]{2}/g, '%$&'));\n } catch {\n tokenName = extra.name || 'USD Coin';\n }\n log(' Token name:', tokenName);\n\n const deadline = Math.floor(Date.now() / 1000) + 600; // 10 min\n\n const domain = {\n name: tokenName,\n version: extra.version || '2',\n chainId,\n verifyingContract: usdcAddress,\n };\n\n const types = {\n Permit: [\n { name: 'owner', type: 'address' },\n { name: 'spender', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'nonce', type: 'uint256' },\n { name: 'deadline', type: 'uint256' },\n ],\n };\n\n const message = {\n owner: evmWallet.address,\n spender,\n value: paymentAmount,\n nonce: String(nonce),\n deadline: String(deadline),\n };\n\n log('Signing EIP-2612 permit:', message);\n\n const signature = await evmWallet.signTypedData({\n domain,\n types,\n message,\n primaryType: 'Permit',\n });\n\n // Split signature into v, r, s\n const sigHex = (signature as string).replace('0x', '');\n const r = '0x' + sigHex.slice(0, 64);\n const s = '0x' + sigHex.slice(64, 128);\n const v = parseInt(sigHex.slice(128, 130), 16);\n\n log(' Permit signed: v=%d r=%s s=%s', v, r, s);\n\n // Build x402 v2 payment payload (SKALE format)\n const paymentPayload = {\n x402Version: 2,\n scheme: 'exact',\n network: network || rawNetwork,\n payload: {\n userAddress: evmWallet.address,\n permit: { deadline: String(deadline), v, r, s },\n amount: paymentAmount,\n },\n };\n\n return btoa(JSON.stringify(paymentPayload));\n }\n\n // -----------------------------------------------------------------------\n // Build EVM payment (EIP-3009 transferWithAuthorization)\n // -----------------------------------------------------------------------\n async function buildEvmPayment(\n accept: any,\n requirements: any,\n url: string,\n ): Promise<string> {\n const evmWallet = effectiveWallets.evm!;\n const extra = accept.extra || {};\n\n const rawNetwork = accept.network || '';\n const network = normalizeNetwork(rawNetwork);\n const chainId = network ? CHAIN_IDS[network] : parseInt(rawNetwork.split(':')[1] || '8453');\n\n const paymentAmount = accept.amount || accept.maxAmountRequired;\n\n // EIP-3009 transferWithAuthorization typed data\n const domain = {\n name: extra.name || 'USD Coin',\n version: extra.version || '2',\n chainId,\n verifyingContract: accept.asset,\n };\n\n const validAfter = 0;\n const validBefore = Math.floor(Date.now() / 1000) + 3600;\n const nonce = '0x' + [...crypto.getRandomValues(new Uint8Array(32))]\n .map(b => b.toString(16).padStart(2, '0')).join('');\n\n const types = {\n TransferWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n };\n\n const spender = extra.feePayer || accept.payTo;\n\n const message = {\n from: evmWallet.address,\n to: spender,\n value: paymentAmount,\n validAfter: String(validAfter),\n validBefore: String(validBefore),\n nonce,\n };\n\n log('Signing EIP-3009 transferWithAuthorization on chain', chainId);\n\n const signature = await evmWallet.signTypedData({\n domain,\n types,\n message,\n primaryType: 'TransferWithAuthorization',\n });\n\n // Build x402 v2 payment payload\n const paymentPayload = {\n x402Version: 2,\n resource: requirements.resource || { url },\n accepted: accept,\n payload: {\n authorization: message,\n signature,\n },\n facilitatorUrl,\n };\n\n return btoa(JSON.stringify(paymentPayload));\n }\n\n // -----------------------------------------------------------------------\n // Build Solana payment (SPL transfer with fee payer sponsorship)\n // -----------------------------------------------------------------------\n async function buildSolanaPayment(\n accept: any,\n requirements: any,\n url: string,\n ): Promise<string> {\n const solWallet = effectiveWallets.solana!;\n const extra = accept.extra || {};\n\n if (!extra.feePayer) {\n throw new Error('[relai-x402] Missing feePayer in Solana payment requirements');\n }\n\n const connection = new Connection(solanaRpcUrl, 'confirmed');\n const userPubkey = new PublicKey(solWallet.publicKey!.toString());\n const merchantPubkey = new PublicKey(accept.payTo);\n const feePayerPubkey = new PublicKey(extra.feePayer);\n const mintPubkey = new PublicKey(accept.asset);\n const paymentAmount = BigInt(accept.amount || accept.maxAmountRequired);\n\n log('Building Solana SPL transfer');\n log(' User:', userPubkey.toBase58());\n log(' Merchant:', merchantPubkey.toBase58());\n log(' FeePayer:', feePayerPubkey.toBase58());\n log(' Mint:', mintPubkey.toBase58());\n log(' Amount:', paymentAmount.toString());\n\n // Determine token program (TOKEN_PROGRAM_ID vs TOKEN_2022)\n const mintInfo = await getMint(connection, mintPubkey);\n const programId = mintInfo.address.equals(mintPubkey)\n ? (mintInfo as any).owner?.toBase58?.() === TOKEN_2022_PROGRAM_ID.toBase58()\n ? TOKEN_2022_PROGRAM_ID\n : TOKEN_PROGRAM_ID\n : TOKEN_PROGRAM_ID;\n\n // Get ATAs\n const sourceAta = await getAssociatedTokenAddress(\n mintPubkey, userPubkey, false, programId,\n );\n const destinationAta = await getAssociatedTokenAddress(\n mintPubkey, merchantPubkey, true, programId,\n );\n\n log(' Source ATA:', sourceAta.toBase58());\n log(' Dest ATA:', destinationAta.toBase58());\n\n // Build transfer instruction\n const transferIx = createTransferCheckedInstruction(\n sourceAta,\n mintPubkey,\n destinationAta,\n userPubkey,\n paymentAmount,\n mintInfo.decimals,\n [],\n programId,\n );\n\n // Build versioned transaction with feePayer\n const { blockhash } = await connection.getLatestBlockhash('confirmed');\n const message = new TransactionMessage({\n payerKey: feePayerPubkey,\n recentBlockhash: blockhash,\n instructions: [transferIx],\n }).compileToV0Message();\n\n const transaction = new VersionedTransaction(message);\n\n // User signs (feePayer signs on backend/facilitator side)\n const signedTx = await solWallet.signTransaction!(transaction) as VersionedTransaction;\n log('Transaction signed by user');\n\n // Serialize to base64\n const serializedTx = Buffer.from(signedTx.serialize()).toString('base64');\n\n // Build x402 v2 payment payload\n const paymentPayload = {\n x402Version: 2,\n resource: requirements.resource || { url },\n accepted: accept,\n payload: {\n transaction: serializedTx,\n },\n };\n\n return btoa(JSON.stringify(paymentPayload));\n }\n\n // -----------------------------------------------------------------------\n // Main fetch\n // -----------------------------------------------------------------------\n async function x402Fetch(\n input: string | URL | Request,\n init?: RequestInit,\n ): Promise<Response> {\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;\n log('Request:', url);\n\n const response = await fetch(input, init);\n if (response.status !== 402) return response;\n\n log('Got 402 Payment Required');\n\n let requirements: any;\n try {\n requirements = await response.clone().json();\n } catch {\n throw new Error('[relai-x402] Failed to parse 402 response body');\n }\n\n const accepts = requirements.accepts || [];\n if (!accepts.length) throw new Error('[relai-x402] No payment options in 402 response');\n\n const selected = selectAccept(accepts);\n if (!selected) {\n const networks = accepts.map((a: any) => a.network).join(', ');\n throw new Error(`[relai-x402] No wallet available for networks: ${networks}`);\n }\n\n const { accept, chain } = selected;\n const amount = accept.amount || accept.maxAmountRequired;\n log(`Selected: ${chain} / ${accept.network} / amount=${amount}`);\n\n // Amount guard\n if (maxAmountAtomic && BigInt(amount) > BigInt(maxAmountAtomic)) {\n throw new Error(`[relai-x402] Amount ${amount} exceeds max ${maxAmountAtomic}`);\n }\n\n // Solana — build SPL transfer natively (no x402-solana dependency)\n if (chain === 'solana' && hasSolanaWallet) {\n const paymentHeader = await buildSolanaPayment(accept, requirements, url);\n log('Retrying with X-PAYMENT header (Solana)');\n return fetch(input, {\n ...init,\n headers: {\n ...(init?.headers || {}),\n 'X-PAYMENT': paymentHeader,\n },\n });\n }\n\n // EVM — build payment header and retry\n if (chain === 'evm') {\n const evmNetwork = normalizeNetwork(accept.network || '');\n const usePermit = evmNetwork && PERMIT_NETWORKS.has(evmNetwork);\n const paymentHeader = usePermit\n ? await buildEvmPermitPayment(accept, requirements, url)\n : await buildEvmPayment(accept, requirements, url);\n log('Retrying with X-PAYMENT header');\n return fetch(input, {\n ...init,\n headers: {\n ...(init?.headers || {}),\n 'X-PAYMENT': paymentHeader,\n },\n });\n }\n\n throw new Error('[relai-x402] Unexpected state — no payment handler matched');\n }\n\n return { fetch: x402Fetch };\n}\n\nexport default createX402Client;\n","// src/types.ts\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** RelAI Facilitator URL */\nexport const RELAI_FACILITATOR_URL = 'https://facilitator.x402.fi';\n\n// ============================================================================\n// Supported Networks\n// ============================================================================\n\n/** All networks supported by RelAI facilitator */\nexport type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base';\n\n/** CAIP-2 network identifiers */\nexport const NETWORK_CAIP2: Record<RelaiNetwork, string> = {\n 'solana': 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n 'base': 'eip155:8453',\n 'avalanche': 'eip155:43114',\n 'skale-base': 'eip155:1187947933',\n};\n\n/** Reverse lookup: CAIP-2 → simple network name */\nexport const CAIP2_TO_NETWORK: Record<string, RelaiNetwork> = Object.fromEntries(\n Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k as RelaiNetwork])\n) as Record<string, RelaiNetwork>;\n\n/** Chain IDs for EVM networks */\nexport const CHAIN_IDS: Record<string, number> = {\n 'base': 8453,\n 'avalanche': 43114,\n 'skale-base': 1187947933,\n};\n\n/** USDC contract addresses per network */\nexport const USDC_ADDRESSES: Record<RelaiNetwork, string> = {\n 'solana': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n 'base': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'avalanche': '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',\n 'skale-base': '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20',\n};\n\n/** Explorer URLs per network */\nexport const EXPLORER_TX_URL: Record<RelaiNetwork, (tx: string) => string> = {\n 'solana': (tx) => `https://solscan.io/tx/${tx}`,\n 'base': (tx) => `https://basescan.org/tx/${tx}`,\n 'avalanche': (tx) => `https://snowtrace.io/tx/${tx}`,\n 'skale-base': (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}`,\n};\n\n/** Human-readable network labels */\nexport const NETWORK_LABELS: Record<RelaiNetwork, string> = {\n 'solana': 'Solana',\n 'base': 'Base',\n 'avalanche': 'Avalanche',\n 'skale-base': 'SKALE Base',\n};\n\n/** Legacy CAIP-2 exports for backward compatibility */\nexport const SOLANA_MAINNET_NETWORK = NETWORK_CAIP2['solana'];\nexport const BASE_MAINNET_NETWORK = NETWORK_CAIP2['base'];\n\n/** Legacy USDC exports for backward compatibility */\nexport const USDC_SOLANA = USDC_ADDRESSES['solana'];\nexport const USDC_BASE = USDC_ADDRESSES['base'];\n\n/** All supported RelAI networks list */\nexport const RELAI_NETWORKS: RelaiNetwork[] = ['solana', 'base', 'avalanche', 'skale-base'];\n\n/** Check if a network is Solana-based */\nexport function isSolana(network: string): boolean {\n return network === 'solana' || network.startsWith('solana:');\n}\n\n/** Check if a network is EVM-based */\nexport function isEvm(network: string): boolean {\n return ['base', 'avalanche', 'skale-base'].includes(network) || network.startsWith('eip155:');\n}\n\n/** Normalize CAIP-2 or simple name to RelaiNetwork */\nexport function normalizeNetwork(network: string): RelaiNetwork | null {\n if (RELAI_NETWORKS.includes(network as RelaiNetwork)) return network as RelaiNetwork;\n const fromCaip2 = CAIP2_TO_NETWORK[network];\n if (fromCaip2) return fromCaip2;\n // Partial match\n if (network.startsWith('solana:')) return 'solana';\n if (network.startsWith('eip155:')) {\n const chainId = parseInt(network.split(':')[1]);\n const entry = Object.entries(CHAIN_IDS).find(([, id]) => id === chainId);\n if (entry) return entry[0] as RelaiNetwork;\n }\n return null;\n}\n\n// ============================================================================\n// Wallet Types\n// ============================================================================\n\n/** Solana wallet interface */\nexport interface SolanaWallet {\n publicKey: { toString(): string } | null;\n signTransaction: ((tx: unknown) => Promise<unknown>) | null;\n signAllTransactions?: ((txs: unknown[]) => Promise<unknown[]>) | null;\n}\n\n/** EVM wallet interface (viem-compatible) */\nexport interface EvmWallet {\n address: string;\n signTypedData: (params: unknown) => Promise<string>;\n chain?: { id: number };\n}\n\n/** Wallet set for multi-chain support */\nexport interface WalletSet {\n solana?: SolanaWallet;\n evm?: EvmWallet;\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/** Extra fields in payment requirements */\nexport interface AcceptsExtra {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n}\n\n/** A single payment option */\nexport interface PaymentAccept {\n x402Version?: 1 | 2;\n scheme: string;\n network: string;\n maxAmountRequired?: string;\n amount?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: AcceptsExtra;\n resource?: string;\n description?: string;\n mimeType?: string;\n outputSchema?: unknown;\n}\n\n/** Resource info for v2 */\nexport interface ResourceInfo {\n url: string;\n description?: string;\n mimeType?: string;\n}\n\n/** Payment requirements (402 response) */\nexport interface PaymentRequired {\n x402Version: 1 | 2;\n error?: string;\n accepts: PaymentAccept[];\n resource?: ResourceInfo;\n extensions?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Config Types (server-specific types are in server.ts)\n// ============================================================================\n"],"mappings":";AAgCA,SAAS,UAAU,aAAa,eAAe;;;AC/B/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACNA,IAAM,wBAAwB;AAU9B,IAAM,gBAA8C;AAAA,EACzD,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,cAAc;AAChB;AAGO,IAAM,mBAAiD,OAAO;AAAA,EACnE,OAAO,QAAQ,aAAa,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAiB,CAAC;AACtE;AAGO,IAAM,YAAoC;AAAA,EAC/C,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,cAAc;AAChB;AAGO,IAAM,iBAA+C;AAAA,EAC1D,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,cAAc;AAChB;AAGO,IAAM,kBAAgE;AAAA,EAC3E,UAAU,CAAC,OAAO,yBAAyB,EAAE;AAAA,EAC7C,QAAQ,CAAC,OAAO,2BAA2B,EAAE;AAAA,EAC7C,aAAa,CAAC,OAAO,2BAA2B,EAAE;AAAA,EAClD,cAAc,CAAC,OAAO,iDAAiD,EAAE;AAC3E;AAGO,IAAM,iBAA+C;AAAA,EAC1D,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,cAAc;AAChB;AAGO,IAAM,yBAAyB,cAAc,QAAQ;AACrD,IAAM,uBAAuB,cAAc,MAAM;AAGjD,IAAM,cAAc,eAAe,QAAQ;AAC3C,IAAM,YAAY,eAAe,MAAM;AAGvC,IAAM,iBAAiC,CAAC,UAAU,QAAQ,aAAa,YAAY;AAGnF,SAAS,SAAS,SAA0B;AACjD,SAAO,YAAY,YAAY,QAAQ,WAAW,SAAS;AAC7D;AAGO,SAAS,MAAM,SAA0B;AAC9C,SAAO,CAAC,QAAQ,aAAa,YAAY,EAAE,SAAS,OAAO,KAAK,QAAQ,WAAW,SAAS;AAC9F;AAGO,SAAS,iBAAiB,SAAsC;AACrE,MAAI,eAAe,SAAS,OAAuB,EAAG,QAAO;AAC7D,QAAM,YAAY,iBAAiB,OAAO;AAC1C,MAAI,UAAW,QAAO;AAEtB,MAAI,QAAQ,WAAW,SAAS,EAAG,QAAO;AAC1C,MAAI,QAAQ,WAAW,SAAS,GAAG;AACjC,UAAM,UAAU,SAAS,QAAQ,MAAM,GAAG,EAAE,CAAC,CAAC;AAC9C,UAAM,QAAQ,OAAO,QAAQ,SAAS,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,MAAM,OAAO,OAAO;AACvE,QAAI,MAAO,QAAO,MAAM,CAAC;AAAA,EAC3B;AACA,SAAO;AACT;;;ADtBA,IAAM,kBAAkB,oBAAI,IAAY,CAAC,CAAC;AAG1C,IAAM,uBAA+C;AAAA,EACnD,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,aAAa;AACf;AAEO,SAAS,iBAAiB,QAAsC;AACrE,QAAM;AAAA,IACJ,UAAU,CAAC;AAAA,IACX,QAAQ;AAAA,IACR,iBAAiB;AAAA,IACjB;AAAA,IACA,eAAe;AAAA,IACf,aAAa,CAAC;AAAA,IACd;AAAA,IACA,UAAU;AAAA,EACZ,IAAI;AAEJ,QAAM,MAAM,UAAU,QAAQ,IAAI,KAAK,SAAS,cAAc,IAAI,MAAM;AAAA,EAAC;AAGzE,QAAM,mBAA8B,EAAE,GAAG,QAAQ;AACjD,MAAI,gBAAgB,CAAC,iBAAiB,QAAQ;AAC5C,qBAAiB,SAAS;AAAA,EAC5B;AAEA,QAAM,kBAAkB;AAAA,IACtB,iBAAiB,QAAQ,aAAa,iBAAiB,QAAQ;AAAA,EACjE;AACA,MAAI,gBAAiB,KAAI,qBAAqB;AAK9C,WAAS,aAAa,SAAiE;AAErF,QAAI,kBAAkB;AACpB,YAAM,QAAQ,cAAc,gBAAgB;AAC5C,iBAAW,KAAK,SAAS;AACvB,cAAM,MAAM,EAAE,WAAW;AACzB,YAAI,QAAQ,oBAAoB,QAAQ,OAAO;AAC7C,gBAAM,QAAQ,SAAS,GAAG,IAAI,WAAoB;AAClD,cAAK,UAAU,YAAY,mBAAqB,UAAU,SAAS,iBAAiB,KAAM;AACxF,mBAAO,EAAE,QAAQ,GAAG,MAAM;AAAA,UAC5B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,eAAW,KAAK,SAAS;AACvB,YAAM,MAAM,EAAE,WAAW;AACzB,UAAI,SAAS,GAAG,KAAK,gBAAiB,QAAO,EAAE,QAAQ,GAAG,OAAO,SAAS;AAC1E,UAAI,MAAM,GAAG,KAAK,iBAAiB,IAAK,QAAO,EAAE,QAAQ,GAAG,OAAO,MAAM;AAAA,IAC3E;AAEA,WAAO;AAAA,EACT;AAKA,iBAAe,WAAW,QAAgB,IAAY,MAA+B;AACnF,UAAM,MAAM,MAAM,MAAM,QAAQ;AAAA,MAC9B,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,QAAQ,CAAC,EAAE,IAAI,KAAK,GAAG,QAAQ;AAAA,QAC/B,IAAI;AAAA,MACN,CAAC;AAAA,IACH,CAAC;AACD,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,KAAK,MAAO,OAAM,IAAI,MAAM,cAAc,KAAK,MAAM,OAAO,EAAE;AAClE,WAAO,KAAK;AAAA,EACd;AAEA,WAAS,aAAa,SAAyB;AAC7C,WAAO,WAAW,OAAO,KAAK,qBAAqB,OAAO,KAAK;AAAA,EACjE;AAKA,iBAAe,sBACb,QACA,cACA,KACiB;AACjB,UAAM,YAAY,iBAAiB;AACnC,UAAM,QAAQ,OAAO,SAAS,CAAC;AAE/B,UAAM,aAAa,OAAO,WAAW;AACrC,UAAM,UAAU,iBAAiB,UAAU;AAC3C,UAAM,UAAU,UAAU,UAAU,OAAO,IAAI,SAAS,WAAW,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM;AAC1F,UAAM,gBAAgB,OAAO,UAAU,OAAO;AAC9C,UAAM,UAAU,MAAM,YAAY,OAAO;AACzC,UAAM,cAAc,OAAO;AAE3B,UAAM,SAAS,aAAa,WAAW,UAAU;AACjD,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,2CAA2C,WAAW,UAAU,EAAE;AAE/F,QAAI,qCAAqC,OAAO;AAGhD,UAAM,gBAAgB,UAAU,QAAQ,YAAY,EAAE,QAAQ,MAAM,EAAE,EAAE,SAAS,IAAI,GAAG;AACxF,UAAM,WAAW,MAAM,WAAW,QAAQ,aAAa,eAAe,aAAa;AACnF,UAAM,QAAQ,WAAW,SAAS,UAAU,EAAE,IAAI;AAClD,QAAI,MAAM,KAAK,EAAG,OAAM,IAAI,MAAM,iDAAiD,WAAW,OAAO,MAAM,EAAE;AAC7G,QAAI,mBAAmB,KAAK;AAG5B,UAAM,UAAU,MAAM,WAAW,QAAQ,aAAa,YAAY;AAElE,QAAI,YAAY;AAChB,QAAI;AACF,YAAM,SAAS,SAAS,QAAQ,MAAM,GAAG,EAAE,GAAG,EAAE,IAAI;AACpD,YAAM,SAAS,SAAS,QAAQ,MAAM,IAAI,QAAQ,IAAI,SAAS,EAAE,GAAG,EAAE;AACtE,YAAM,MAAM,QAAQ,MAAM,IAAI,SAAS,IAAI,IAAI,SAAS,KAAK,SAAS,CAAC;AACvE,kBAAY,mBAAmB,IAAI,QAAQ,gBAAgB,KAAK,CAAC;AAAA,IACnE,QAAQ;AACN,kBAAY,MAAM,QAAQ;AAAA,IAC5B;AACA,QAAI,iBAAiB,SAAS;AAE9B,UAAM,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI;AAEjD,UAAM,SAAS;AAAA,MACb,MAAM;AAAA,MACN,SAAS,MAAM,WAAW;AAAA,MAC1B;AAAA,MACA,mBAAmB;AAAA,IACrB;AAEA,UAAM,QAAQ;AAAA,MACZ,QAAQ;AAAA,QACN,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,QACjC,EAAE,MAAM,WAAW,MAAM,UAAU;AAAA,QACnC,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,QACjC,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,QACjC,EAAE,MAAM,YAAY,MAAM,UAAU;AAAA,MACtC;AAAA,IACF;AAEA,UAAM,UAAU;AAAA,MACd,OAAO,UAAU;AAAA,MACjB;AAAA,MACA,OAAO;AAAA,MACP,OAAO,OAAO,KAAK;AAAA,MACnB,UAAU,OAAO,QAAQ;AAAA,IAC3B;AAEA,QAAI,4BAA4B,OAAO;AAEvC,UAAM,YAAY,MAAM,UAAU,cAAc;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa;AAAA,IACf,CAAC;AAGD,UAAM,SAAU,UAAqB,QAAQ,MAAM,EAAE;AACrD,UAAM,IAAI,OAAO,OAAO,MAAM,GAAG,EAAE;AACnC,UAAM,IAAI,OAAO,OAAO,MAAM,IAAI,GAAG;AACrC,UAAM,IAAI,SAAS,OAAO,MAAM,KAAK,GAAG,GAAG,EAAE;AAE7C,QAAI,mCAAmC,GAAG,GAAG,CAAC;AAG9C,UAAM,iBAAiB;AAAA,MACrB,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS,WAAW;AAAA,MACpB,SAAS;AAAA,QACP,aAAa,UAAU;AAAA,QACvB,QAAQ,EAAE,UAAU,OAAO,QAAQ,GAAG,GAAG,GAAG,EAAE;AAAA,QAC9C,QAAQ;AAAA,MACV;AAAA,IACF;AAEA,WAAO,KAAK,KAAK,UAAU,cAAc,CAAC;AAAA,EAC5C;AAKA,iBAAe,gBACb,QACA,cACA,KACiB;AACjB,UAAM,YAAY,iBAAiB;AACnC,UAAM,QAAQ,OAAO,SAAS,CAAC;AAE/B,UAAM,aAAa,OAAO,WAAW;AACrC,UAAM,UAAU,iBAAiB,UAAU;AAC3C,UAAM,UAAU,UAAU,UAAU,OAAO,IAAI,SAAS,WAAW,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM;AAE1F,UAAM,gBAAgB,OAAO,UAAU,OAAO;AAG9C,UAAM,SAAS;AAAA,MACb,MAAM,MAAM,QAAQ;AAAA,MACpB,SAAS,MAAM,WAAW;AAAA,MAC1B;AAAA,MACA,mBAAmB,OAAO;AAAA,IAC5B;AAEA,UAAM,aAAa;AACnB,UAAM,cAAc,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI;AACpD,UAAM,QAAQ,OAAO,CAAC,GAAG,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,CAAC,EAChE,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAEpD,UAAM,QAAQ;AAAA,MACZ,2BAA2B;AAAA,QACzB,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,QAChC,EAAE,MAAM,MAAM,MAAM,UAAU;AAAA,QAC9B,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,QACjC,EAAE,MAAM,cAAc,MAAM,UAAU;AAAA,QACtC,EAAE,MAAM,eAAe,MAAM,UAAU;AAAA,QACvC,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,MACnC;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,YAAY,OAAO;AAEzC,UAAM,UAAU;AAAA,MACd,MAAM,UAAU;AAAA,MAChB,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,YAAY,OAAO,UAAU;AAAA,MAC7B,aAAa,OAAO,WAAW;AAAA,MAC/B;AAAA,IACF;AAEA,QAAI,uDAAuD,OAAO;AAElE,UAAM,YAAY,MAAM,UAAU,cAAc;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa;AAAA,IACf,CAAC;AAGD,UAAM,iBAAiB;AAAA,MACrB,aAAa;AAAA,MACb,UAAU,aAAa,YAAY,EAAE,IAAI;AAAA,MACzC,UAAU;AAAA,MACV,SAAS;AAAA,QACP,eAAe;AAAA,QACf;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAEA,WAAO,KAAK,KAAK,UAAU,cAAc,CAAC;AAAA,EAC5C;AAKA,iBAAe,mBACb,QACA,cACA,KACiB;AACjB,UAAM,YAAY,iBAAiB;AACnC,UAAM,QAAQ,OAAO,SAAS,CAAC;AAE/B,QAAI,CAAC,MAAM,UAAU;AACnB,YAAM,IAAI,MAAM,8DAA8D;AAAA,IAChF;AAEA,UAAM,aAAa,IAAI,WAAW,cAAc,WAAW;AAC3D,UAAM,aAAa,IAAI,UAAU,UAAU,UAAW,SAAS,CAAC;AAChE,UAAM,iBAAiB,IAAI,UAAU,OAAO,KAAK;AACjD,UAAM,iBAAiB,IAAI,UAAU,MAAM,QAAQ;AACnD,UAAM,aAAa,IAAI,UAAU,OAAO,KAAK;AAC7C,UAAM,gBAAgB,OAAO,OAAO,UAAU,OAAO,iBAAiB;AAEtE,QAAI,8BAA8B;AAClC,QAAI,WAAW,WAAW,SAAS,CAAC;AACpC,QAAI,eAAe,eAAe,SAAS,CAAC;AAC5C,QAAI,eAAe,eAAe,SAAS,CAAC;AAC5C,QAAI,WAAW,WAAW,SAAS,CAAC;AACpC,QAAI,aAAa,cAAc,SAAS,CAAC;AAGzC,UAAM,WAAW,MAAM,QAAQ,YAAY,UAAU;AACrD,UAAM,YAAY,SAAS,QAAQ,OAAO,UAAU,IAC/C,SAAiB,OAAO,WAAW,MAAM,sBAAsB,SAAS,IACvE,wBACA,mBACF;AAGJ,UAAM,YAAY,MAAM;AAAA,MACtB;AAAA,MAAY;AAAA,MAAY;AAAA,MAAO;AAAA,IACjC;AACA,UAAM,iBAAiB,MAAM;AAAA,MAC3B;AAAA,MAAY;AAAA,MAAgB;AAAA,MAAM;AAAA,IACpC;AAEA,QAAI,iBAAiB,UAAU,SAAS,CAAC;AACzC,QAAI,eAAe,eAAe,SAAS,CAAC;AAG5C,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,CAAC;AAAA,MACD;AAAA,IACF;AAGA,UAAM,EAAE,UAAU,IAAI,MAAM,WAAW,mBAAmB,WAAW;AACrE,UAAM,UAAU,IAAI,mBAAmB;AAAA,MACrC,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,cAAc,CAAC,UAAU;AAAA,IAC3B,CAAC,EAAE,mBAAmB;AAEtB,UAAM,cAAc,IAAI,qBAAqB,OAAO;AAGpD,UAAM,WAAW,MAAM,UAAU,gBAAiB,WAAW;AAC7D,QAAI,4BAA4B;AAGhC,UAAM,eAAe,OAAO,KAAK,SAAS,UAAU,CAAC,EAAE,SAAS,QAAQ;AAGxE,UAAM,iBAAiB;AAAA,MACrB,aAAa;AAAA,MACb,UAAU,aAAa,YAAY,EAAE,IAAI;AAAA,MACzC,UAAU;AAAA,MACV,SAAS;AAAA,QACP,aAAa;AAAA,MACf;AAAA,IACF;AAEA,WAAO,KAAK,KAAK,UAAU,cAAc,CAAC;AAAA,EAC5C;AAKA,iBAAe,UACb,OACA,MACmB;AACnB,UAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,iBAAiB,MAAM,MAAM,OAAO,MAAM;AAC1F,QAAI,YAAY,GAAG;AAEnB,UAAM,WAAW,MAAM,MAAM,OAAO,IAAI;AACxC,QAAI,SAAS,WAAW,IAAK,QAAO;AAEpC,QAAI,0BAA0B;AAE9B,QAAI;AACJ,QAAI;AACF,qBAAe,MAAM,SAAS,MAAM,EAAE,KAAK;AAAA,IAC7C,QAAQ;AACN,YAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AAEA,UAAM,UAAU,aAAa,WAAW,CAAC;AACzC,QAAI,CAAC,QAAQ,OAAQ,OAAM,IAAI,MAAM,iDAAiD;AAEtF,UAAM,WAAW,aAAa,OAAO;AACrC,QAAI,CAAC,UAAU;AACb,YAAM,WAAW,QAAQ,IAAI,CAAC,MAAW,EAAE,OAAO,EAAE,KAAK,IAAI;AAC7D,YAAM,IAAI,MAAM,kDAAkD,QAAQ,EAAE;AAAA,IAC9E;AAEA,UAAM,EAAE,QAAQ,MAAM,IAAI;AAC1B,UAAM,SAAS,OAAO,UAAU,OAAO;AACvC,QAAI,aAAa,KAAK,MAAM,OAAO,OAAO,aAAa,MAAM,EAAE;AAG/D,QAAI,mBAAmB,OAAO,MAAM,IAAI,OAAO,eAAe,GAAG;AAC/D,YAAM,IAAI,MAAM,uBAAuB,MAAM,gBAAgB,eAAe,EAAE;AAAA,IAChF;AAGA,QAAI,UAAU,YAAY,iBAAiB;AACzC,YAAM,gBAAgB,MAAM,mBAAmB,QAAQ,cAAc,GAAG;AACxE,UAAI,yCAAyC;AAC7C,aAAO,MAAM,OAAO;AAAA,QAClB,GAAG;AAAA,QACH,SAAS;AAAA,UACP,GAAI,MAAM,WAAW,CAAC;AAAA,UACtB,aAAa;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH;AAGA,QAAI,UAAU,OAAO;AACnB,YAAM,aAAa,iBAAiB,OAAO,WAAW,EAAE;AACxD,YAAM,YAAY,cAAc,gBAAgB,IAAI,UAAU;AAC9D,YAAM,gBAAgB,YAClB,MAAM,sBAAsB,QAAQ,cAAc,GAAG,IACrD,MAAM,gBAAgB,QAAQ,cAAc,GAAG;AACnD,UAAI,gCAAgC;AACpC,aAAO,MAAM,OAAO;AAAA,QAClB,GAAG;AAAA,QACH,SAAS;AAAA,UACP,GAAI,MAAM,WAAW,CAAC;AAAA,UACtB,aAAa;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,IAAI,MAAM,iEAA4D;AAAA,EAC9E;AAEA,SAAO,EAAE,OAAO,UAAU;AAC5B;;;ADnZA,SAAS,wBAAwB,QAAgC;AAC/D,SAAO,CAAC,EAAE,QAAQ,aAAa,QAAQ;AACzC;AAEA,SAAS,qBAAqB,QAA6B;AACzD,SAAO,CAAC,EAAE,QAAQ,WAAW,QAAQ;AACvC;AAMO,SAAS,gBAAgB,SAAgC,CAAC,GAA0B;AACzF,QAAM;AAAA,IACJ,UAAU,CAAC;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,EACZ,IAAI;AAGJ,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,MAAM;AAC1D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AACrD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAwB,IAAI;AACtE,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,SAA8B,IAAI;AAEtF,QAAM,MAAM,YAAY,IAAI,SAAoB;AAC9C,QAAI,QAAS,SAAQ,IAAI,qBAAqB,GAAG,IAAI;AAAA,EACvD,GAAG,CAAC,OAAO,CAAC;AAGZ,QAAM,kBAAkB,QAAQ,OAAwB;AAAA,IACtD,QAAQ,wBAAwB,QAAQ,MAAM;AAAA,IAC9C,KAAK,qBAAqB,QAAQ,GAAG;AAAA,EACvC,IAAI,CAAC,OAAO,CAAC;AAEb,QAAM,cAAc,gBAAgB,UAAU,gBAAgB;AAG9D,QAAM,SAAS,QAAQ,MAAM;AAC3B,UAAM,MAAwB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,aAAc,KAAI,eAAe;AACrC,QAAI,eAAgB,KAAI,iBAAiB;AACzC,WAAO,iBAAiB,GAAG;AAAA,EAC7B,GAAG,CAAC,SAAS,kBAAkB,cAAc,gBAAgB,iBAAiB,OAAO,CAAC;AAGtF,QAAM,QAAQ,YAAY,MAAM;AAC9B,iBAAa,KAAK;AAClB,cAAU,MAAM;AAChB,aAAS,IAAI;AACb,qBAAiB,IAAI;AACrB,0BAAsB,IAAI;AAAA,EAC5B,GAAG,CAAC,CAAC;AAGL,QAAM,mBAAmB,YAAY,OACnC,OACA,SACsB;AACtB,iBAAa,IAAI;AACjB,cAAU,SAAS;AACnB,aAAS,IAAI;AACb,qBAAiB,IAAI;AACrB,0BAAsB,IAAI;AAE1B,QAAI,CAAC,aAAa;AAChB,YAAM,MAAM,IAAI,MAAM,sDAAsD;AAC5E,eAAS,GAAG;AACZ,gBAAU,OAAO;AACjB,mBAAa,KAAK;AAClB,YAAM;AAAA,IACR;AAEA,QAAI;AACF,UAAI,8BAA8B,KAAK;AACvC,YAAM,WAAW,MAAM,OAAO,MAAM,OAAO,IAAI;AAG/C,YAAM,kBACJ,SAAS,QAAQ,IAAI,kBAAkB,KACvC,SAAS,QAAQ,IAAI,kBAAkB;AAEzC,UAAI,iBAAiB;AACnB,YAAI;AACF,gBAAM,UAAU,KAAK,MAAM,KAAK,eAAe,CAAC;AAChD,cAAI,QAAQ,YAAa,kBAAiB,QAAQ,WAAW;AAC7D,cAAI,QAAQ,SAAS;AACnB,kBAAM,MAAM,iBAAiB,QAAQ,OAAO;AAC5C,gBAAI,IAAK,uBAAsB,GAAG;AAAA,UACpC;AAAA,QACF,QAAQ;AACN,cAAI,yCAAyC;AAAA,QAC/C;AAAA,MACF;AAEA,gBAAU,SAAS;AACnB,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC5D,eAAS,CAAC;AACV,gBAAU,OAAO;AACjB,YAAM;AAAA,IACR,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,aAAa,GAAG,CAAC;AAG7B,QAAM,iBAAiB,QAAQ,MAAM;AACnC,QAAI,CAAC,iBAAiB,CAAC,mBAAoB,QAAO;AAClD,UAAM,KAAK,gBAAgB,kBAAkC;AAC7D,WAAO,KAAK,GAAG,aAAa,IAAI;AAAA,EAClC,GAAG,CAAC,eAAe,kBAAkB,CAAC;AAGtC,QAAM,0BAA0B,QAAQ,MAAM;AAC5C,QAAI,CAAC,mBAAoB,QAAO;AAChC,WAAO,eAAe,kBAAkC,KAAK;AAAA,EAC/D,GAAG,CAAC,kBAAkB,CAAC;AAEvB,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/useRelaiPayment.ts","../../src/client.ts","../../src/types.ts"],"sourcesContent":["/**\n * React Hook for RelAI x402 Payments\n *\n * Single hook that auto-detects the network from the 402 response and uses\n * the correct signing method. Supports all RelAI facilitator networks:\n * Solana, Base, Avalanche, SKALE Base.\n *\n * @example\n * ```tsx\n * import { useRelaiPayment } from '@relai-fi/x402/react';\n *\n * function PayButton() {\n * const {\n * fetch,\n * isLoading,\n * status,\n * error,\n * transactionUrl,\n * connectedChains,\n * } = useRelaiPayment({\n * wallets: { solana: solanaWallet, evm: evmWalletClient },\n * });\n *\n * return (\n * <button onClick={() => fetch(url)} disabled={isLoading}>\n * {isLoading ? 'Paying...' : 'Access'}\n * </button>\n * );\n * }\n * ```\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport { createX402Client, type X402ClientConfig } from '../client';\nimport type { SolanaWallet, EvmWallet, WalletSet } from '../types';\nimport {\n EXPLORER_TX_URL,\n NETWORK_LABELS,\n normalizeNetwork,\n type RelaiNetwork,\n} from '../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type PaymentStatus = 'idle' | 'pending' | 'success' | 'error';\n\nexport interface ConnectedChains {\n solana: boolean;\n evm: boolean;\n}\n\nexport interface UseRelaiPaymentConfig {\n /** Wallets — pass both Solana + EVM and the hook picks automatically */\n wallets?: WalletSet;\n /** Preferred network when multiple options available */\n preferredNetwork?: RelaiNetwork;\n /** Custom Solana RPC URL */\n solanaRpcUrl?: string;\n /** Custom facilitator URL (default: RelAI) */\n facilitatorUrl?: string;\n /** Maximum payment in atomic units (safety guard) */\n maxAmountAtomic?: string;\n /** Verbose console logging */\n verbose?: boolean;\n}\n\nexport interface UseRelaiPaymentReturn {\n /** Fetch with automatic x402 payment handling */\n fetch: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\n /** Payment in progress */\n isLoading: boolean;\n /** Current status */\n status: PaymentStatus;\n /** Error on failure */\n error: Error | null;\n /** Transaction hash/signature on success */\n transactionId: string | null;\n /** Network the payment was made on */\n transactionNetwork: RelaiNetwork | null;\n /** Human-readable network label */\n transactionNetworkLabel: string | null;\n /** Block explorer URL for the transaction */\n transactionUrl: string | null;\n /** Which chain types have connected wallets */\n connectedChains: ConnectedChains;\n /** Whether any wallet is connected */\n isConnected: boolean;\n /** Reset hook state to idle */\n reset: () => void;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction isSolanaWalletConnected(wallet?: SolanaWallet): boolean {\n return !!(wallet?.publicKey && wallet?.signTransaction);\n}\n\nfunction isEvmWalletConnected(wallet?: EvmWallet): boolean {\n return !!(wallet?.address && wallet?.signTypedData);\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useRelaiPayment(config: UseRelaiPaymentConfig = {}): UseRelaiPaymentReturn {\n const {\n wallets = {},\n preferredNetwork,\n solanaRpcUrl,\n facilitatorUrl,\n maxAmountAtomic,\n verbose = false,\n } = config;\n\n // State\n const [isLoading, setIsLoading] = useState(false);\n const [status, setStatus] = useState<PaymentStatus>('idle');\n const [error, setError] = useState<Error | null>(null);\n const [transactionId, setTransactionId] = useState<string | null>(null);\n const [transactionNetwork, setTransactionNetwork] = useState<RelaiNetwork | null>(null);\n\n const log = useCallback((...args: unknown[]) => {\n if (verbose) console.log('[useRelaiPayment]', ...args);\n }, [verbose]);\n\n // Connected chains derived from wallets\n const connectedChains = useMemo((): ConnectedChains => ({\n solana: isSolanaWalletConnected(wallets.solana),\n evm: isEvmWalletConnected(wallets.evm),\n }), [wallets]);\n\n const isConnected = connectedChains.solana || connectedChains.evm;\n\n // Build x402 client (re-created when wallets change)\n const client = useMemo(() => {\n const cfg: X402ClientConfig = {\n wallets,\n preferredNetwork,\n maxAmountAtomic,\n verbose,\n };\n if (solanaRpcUrl) cfg.solanaRpcUrl = solanaRpcUrl;\n if (facilitatorUrl) cfg.facilitatorUrl = facilitatorUrl;\n return createX402Client(cfg);\n }, [wallets, preferredNetwork, solanaRpcUrl, facilitatorUrl, maxAmountAtomic, verbose]);\n\n // Reset\n const reset = useCallback(() => {\n setIsLoading(false);\n setStatus('idle');\n setError(null);\n setTransactionId(null);\n setTransactionNetwork(null);\n }, []);\n\n // Main fetch\n const fetchWithPayment = useCallback(async (\n input: string | URL | Request,\n init?: RequestInit,\n ): Promise<Response> => {\n setIsLoading(true);\n setStatus('pending');\n setError(null);\n setTransactionId(null);\n setTransactionNetwork(null);\n\n if (!isConnected) {\n const err = new Error('No wallet connected. Connect a Solana or EVM wallet.');\n setError(err);\n setStatus('error');\n setIsLoading(false);\n throw err;\n }\n\n try {\n log('Fetching with x402 client:', input);\n const response = await client.fetch(input, init);\n\n // Extract transaction info from PAYMENT-RESPONSE header\n const paymentResponse =\n response.headers.get('PAYMENT-RESPONSE') ||\n response.headers.get('payment-response');\n\n if (paymentResponse) {\n try {\n const decoded = JSON.parse(atob(paymentResponse));\n if (decoded.transaction) setTransactionId(decoded.transaction);\n if (decoded.network) {\n const net = normalizeNetwork(decoded.network);\n if (net) setTransactionNetwork(net);\n }\n } catch {\n log('Could not parse PAYMENT-RESPONSE header');\n }\n }\n\n setStatus('success');\n return response;\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n setStatus('error');\n throw err;\n } finally {\n setIsLoading(false);\n }\n }, [client, isConnected, log]);\n\n // Derived: explorer URL\n const transactionUrl = useMemo(() => {\n if (!transactionId || !transactionNetwork) return null;\n const fn = EXPLORER_TX_URL[transactionNetwork as RelaiNetwork];\n return fn ? fn(transactionId) : null;\n }, [transactionId, transactionNetwork]);\n\n // Derived: network label\n const transactionNetworkLabel = useMemo(() => {\n if (!transactionNetwork) return null;\n return NETWORK_LABELS[transactionNetwork as RelaiNetwork] || transactionNetwork;\n }, [transactionNetwork]);\n\n return {\n fetch: fetchWithPayment,\n isLoading,\n status,\n error,\n transactionId,\n transactionNetwork,\n transactionNetworkLabel,\n transactionUrl,\n connectedChains,\n isConnected,\n reset,\n };\n}\n","// src/client.ts\nimport {\n Connection,\n PublicKey,\n TransactionMessage,\n VersionedTransaction,\n} from '@solana/web3.js';\nimport {\n getAssociatedTokenAddress,\n createTransferCheckedInstruction,\n getMint,\n TOKEN_PROGRAM_ID,\n TOKEN_2022_PROGRAM_ID,\n} from '@solana/spl-token';\nimport type { SolanaWallet, EvmWallet, WalletSet } from './types';\nimport {\n RELAI_FACILITATOR_URL,\n NETWORK_CAIP2,\n CHAIN_IDS,\n isSolana,\n isEvm,\n normalizeNetwork,\n type RelaiNetwork,\n} from './types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface X402ClientConfig {\n /** Multi-chain wallets (Solana + EVM) */\n wallets?: WalletSet;\n /** Single Solana wallet (legacy shortcut) */\n wallet?: SolanaWallet;\n /** Custom facilitator URL, default: RelAI facilitator */\n facilitatorUrl?: string;\n /** Preferred network when multiple options available */\n preferredNetwork?: RelaiNetwork;\n /** Custom Solana RPC URL */\n solanaRpcUrl?: string;\n /** Custom EVM RPC URLs per network (e.g. { 'skale-base': 'https://...' }) */\n evmRpcUrls?: Record<string, string>;\n /** Maximum payment amount in atomic units */\n maxAmountAtomic?: string;\n /** Enable verbose logging */\n verbose?: boolean;\n}\n\nexport interface X402Client {\n /** Fetch with automatic x402 payment handling */\n fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;\n}\n\n/**\n * Create an x402 client for automatic payment handling.\n * Supports all RelAI facilitator networks: Solana, Base, Avalanche, SKALE Base.\n * Auto-detects the correct chain from the 402 response and picks the right\n * signing method (Solana SPL transfer, EVM EIP-3009 transferWithAuthorization).\n *\n * @example\n * ```typescript\n * import { createX402Client } from '@relai-fi/x402';\n *\n * const client = createX402Client({\n * wallets: { solana: solanaWallet, evm: evmWalletClient },\n * });\n *\n * // Automatically handles 402 on any RelAI-supported network\n * const response = await client.fetch('https://api.example.com/protected');\n * ```\n */\n// Networks that use EIP-2612 permit instead of EIP-3009 transferWithAuthorization (currently none)\nconst PERMIT_NETWORKS = new Set<string>([]);\n\n// Default EVM RPC URLs\nconst DEFAULT_EVM_RPC_URLS: Record<string, string> = {\n 'skale-base': 'https://skale-base.skalenodes.com/v1/base',\n 'skale-bite': 'https://base-sepolia-testnet.skalenodes.com/v1/bite-v2-sandbox',\n 'base': 'https://mainnet.base.org',\n 'avalanche': 'https://api.avax.network/ext/bc/C/rpc',\n 'polygon': 'https://polygon-rpc.com',\n 'ethereum': 'https://ethereum-rpc.publicnode.com',\n};\n\nexport function createX402Client(config: X402ClientConfig): X402Client {\n const {\n wallets = {},\n wallet: legacyWallet,\n facilitatorUrl = RELAI_FACILITATOR_URL,\n preferredNetwork,\n solanaRpcUrl = 'https://api.mainnet-beta.solana.com',\n evmRpcUrls = {},\n maxAmountAtomic,\n verbose = false,\n } = config;\n\n const log = verbose ? console.log.bind(console, '[relai-x402]') : () => {};\n\n // Merge legacy wallet into wallet set\n const effectiveWallets: WalletSet = { ...wallets };\n if (legacyWallet && !effectiveWallets.solana) {\n effectiveWallets.solana = legacyWallet;\n }\n\n const hasSolanaWallet = Boolean(\n effectiveWallets.solana?.publicKey && effectiveWallets.solana?.signTransaction\n );\n if (hasSolanaWallet) log('Solana wallet ready');\n\n // -----------------------------------------------------------------------\n // Select a payment option from the 402 response's `accepts` array\n // -----------------------------------------------------------------------\n function selectAccept(accepts: any[]): { accept: any; chain: 'solana' | 'evm' } | null {\n // 1) Preferred network first\n if (preferredNetwork) {\n const caip2 = NETWORK_CAIP2[preferredNetwork];\n for (const a of accepts) {\n const net = a.network || '';\n if (net === preferredNetwork || net === caip2) {\n const chain = isSolana(net) ? 'solana' as const : 'evm' as const;\n if ((chain === 'solana' && hasSolanaWallet) || (chain === 'evm' && effectiveWallets.evm)) {\n return { accept: a, chain };\n }\n }\n }\n }\n\n // 2) First option we have a wallet for\n for (const a of accepts) {\n const net = a.network || '';\n if (isSolana(net) && hasSolanaWallet) return { accept: a, chain: 'solana' };\n if (isEvm(net) && effectiveWallets.evm) return { accept: a, chain: 'evm' };\n }\n\n return null;\n }\n\n // -----------------------------------------------------------------------\n // JSON-RPC helper (for reading EVM contract state without ethers)\n // -----------------------------------------------------------------------\n async function evmRpcCall(rpcUrl: string, to: string, data: string): Promise<string> {\n const res = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n method: 'eth_call',\n params: [{ to, data }, 'latest'],\n id: 1,\n }),\n });\n const json = await res.json();\n if (json.error) throw new Error(`RPC error: ${json.error.message}`);\n return json.result;\n }\n\n function getEvmRpcUrl(network: string): string {\n return evmRpcUrls[network] || DEFAULT_EVM_RPC_URLS[network] || '';\n }\n\n // -----------------------------------------------------------------------\n // Build EVM payment — EIP-2612 permit (for SKALE Base)\n // -----------------------------------------------------------------------\n async function buildEvmPermitPayment(\n accept: any,\n requirements: any,\n url: string,\n ): Promise<string> {\n const evmWallet = effectiveWallets.evm!;\n const extra = accept.extra || {};\n\n const rawNetwork = accept.network || '';\n const network = normalizeNetwork(rawNetwork);\n const chainId = network ? CHAIN_IDS[network] : parseInt(rawNetwork.split(':')[1] || '8453');\n const paymentAmount = accept.amount || accept.maxAmountRequired;\n const spender = extra.feePayer || accept.payTo;\n const usdcAddress = accept.asset;\n\n const rpcUrl = getEvmRpcUrl(network || rawNetwork);\n if (!rpcUrl) throw new Error(`[relai-x402] No EVM RPC URL for network ${network || rawNetwork}`);\n\n log('Building EIP-2612 permit on chain', chainId);\n\n // Read nonce from USDC contract: nonces(address) = 0x7ecebe00\n const paddedAddress = evmWallet.address.toLowerCase().replace('0x', '').padStart(64, '0');\n const nonceHex = await evmRpcCall(rpcUrl, usdcAddress, '0x7ecebe00' + paddedAddress);\n const nonce = nonceHex ? parseInt(nonceHex, 16) : 0;\n if (isNaN(nonce)) throw new Error(`[relai-x402] Failed to read permit nonce from ${usdcAddress} on ${rpcUrl}`);\n log(' Permit nonce:', nonce);\n\n // Read token name: name() = 0x06fdde03\n const nameHex = await evmRpcCall(rpcUrl, usdcAddress, '0x06fdde03');\n // Decode ABI-encoded string\n let tokenName = 'USD Coin';\n try {\n const offset = parseInt(nameHex.slice(2, 66), 16) * 2;\n const length = parseInt(nameHex.slice(2 + offset, 2 + offset + 64), 16);\n const hex = nameHex.slice(2 + offset + 64, 2 + offset + 64 + length * 2);\n tokenName = decodeURIComponent(hex.replace(/[0-9a-f]{2}/g, '%$&'));\n } catch {\n tokenName = extra.name || 'USD Coin';\n }\n log(' Token name:', tokenName);\n\n const deadline = Math.floor(Date.now() / 1000) + 600; // 10 min\n\n const domain = {\n name: tokenName,\n version: extra.version || '2',\n chainId,\n verifyingContract: usdcAddress,\n };\n\n const types = {\n Permit: [\n { name: 'owner', type: 'address' },\n { name: 'spender', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'nonce', type: 'uint256' },\n { name: 'deadline', type: 'uint256' },\n ],\n };\n\n const message = {\n owner: evmWallet.address,\n spender,\n value: paymentAmount,\n nonce: String(nonce),\n deadline: String(deadline),\n };\n\n log('Signing EIP-2612 permit:', message);\n\n const signature = await evmWallet.signTypedData({\n domain,\n types,\n message,\n primaryType: 'Permit',\n });\n\n // Split signature into v, r, s\n const sigHex = (signature as string).replace('0x', '');\n const r = '0x' + sigHex.slice(0, 64);\n const s = '0x' + sigHex.slice(64, 128);\n const v = parseInt(sigHex.slice(128, 130), 16);\n\n log(' Permit signed: v=%d r=%s s=%s', v, r, s);\n\n // Build x402 v2 payment payload (SKALE format)\n const paymentPayload = {\n x402Version: 2,\n scheme: 'exact',\n network: network || rawNetwork,\n payload: {\n userAddress: evmWallet.address,\n permit: { deadline: String(deadline), v, r, s },\n amount: paymentAmount,\n },\n };\n\n return btoa(JSON.stringify(paymentPayload));\n }\n\n // -----------------------------------------------------------------------\n // Build EVM payment (EIP-3009 transferWithAuthorization)\n // -----------------------------------------------------------------------\n async function buildEvmPayment(\n accept: any,\n requirements: any,\n url: string,\n ): Promise<string> {\n const evmWallet = effectiveWallets.evm!;\n const extra = accept.extra || {};\n\n const rawNetwork = accept.network || '';\n const network = normalizeNetwork(rawNetwork);\n const chainId = network ? CHAIN_IDS[network] : parseInt(rawNetwork.split(':')[1] || '8453');\n\n const paymentAmount = accept.amount || accept.maxAmountRequired;\n\n // EIP-3009 transferWithAuthorization typed data\n const domain = {\n name: extra.name || 'USD Coin',\n version: extra.version || '2',\n chainId,\n verifyingContract: accept.asset,\n };\n\n const validAfter = 0;\n const validBefore = Math.floor(Date.now() / 1000) + 3600;\n const nonce = '0x' + [...crypto.getRandomValues(new Uint8Array(32))]\n .map(b => b.toString(16).padStart(2, '0')).join('');\n\n const types = {\n TransferWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n };\n\n const spender = extra.feePayer || accept.payTo;\n\n const message = {\n from: evmWallet.address,\n to: spender,\n value: paymentAmount,\n validAfter: String(validAfter),\n validBefore: String(validBefore),\n nonce,\n };\n\n log('Signing EIP-3009 transferWithAuthorization on chain', chainId);\n\n const signature = await evmWallet.signTypedData({\n domain,\n types,\n message,\n primaryType: 'TransferWithAuthorization',\n });\n\n // Build x402 v2 payment payload\n const paymentPayload = {\n x402Version: 2,\n resource: requirements.resource || { url },\n accepted: accept,\n payload: {\n authorization: message,\n signature,\n },\n facilitatorUrl,\n };\n\n return btoa(JSON.stringify(paymentPayload));\n }\n\n // -----------------------------------------------------------------------\n // Build Solana payment (SPL transfer with fee payer sponsorship)\n // -----------------------------------------------------------------------\n async function buildSolanaPayment(\n accept: any,\n requirements: any,\n url: string,\n ): Promise<string> {\n const solWallet = effectiveWallets.solana!;\n const extra = accept.extra || {};\n\n if (!extra.feePayer) {\n throw new Error('[relai-x402] Missing feePayer in Solana payment requirements');\n }\n\n const connection = new Connection(solanaRpcUrl, 'confirmed');\n const userPubkey = new PublicKey(solWallet.publicKey!.toString());\n const merchantPubkey = new PublicKey(accept.payTo);\n const feePayerPubkey = new PublicKey(extra.feePayer);\n const mintPubkey = new PublicKey(accept.asset);\n const paymentAmount = BigInt(accept.amount || accept.maxAmountRequired);\n\n log('Building Solana SPL transfer');\n log(' User:', userPubkey.toBase58());\n log(' Merchant:', merchantPubkey.toBase58());\n log(' FeePayer:', feePayerPubkey.toBase58());\n log(' Mint:', mintPubkey.toBase58());\n log(' Amount:', paymentAmount.toString());\n\n // Determine token program (TOKEN_PROGRAM_ID vs TOKEN_2022)\n const mintInfo = await getMint(connection, mintPubkey);\n const programId = mintInfo.address.equals(mintPubkey)\n ? (mintInfo as any).owner?.toBase58?.() === TOKEN_2022_PROGRAM_ID.toBase58()\n ? TOKEN_2022_PROGRAM_ID\n : TOKEN_PROGRAM_ID\n : TOKEN_PROGRAM_ID;\n\n // Get ATAs\n const sourceAta = await getAssociatedTokenAddress(\n mintPubkey, userPubkey, false, programId,\n );\n const destinationAta = await getAssociatedTokenAddress(\n mintPubkey, merchantPubkey, true, programId,\n );\n\n log(' Source ATA:', sourceAta.toBase58());\n log(' Dest ATA:', destinationAta.toBase58());\n\n // Build transfer instruction\n const transferIx = createTransferCheckedInstruction(\n sourceAta,\n mintPubkey,\n destinationAta,\n userPubkey,\n paymentAmount,\n mintInfo.decimals,\n [],\n programId,\n );\n\n // Build versioned transaction with feePayer\n const { blockhash } = await connection.getLatestBlockhash('confirmed');\n const message = new TransactionMessage({\n payerKey: feePayerPubkey,\n recentBlockhash: blockhash,\n instructions: [transferIx],\n }).compileToV0Message();\n\n const transaction = new VersionedTransaction(message);\n\n // User signs (feePayer signs on backend/facilitator side)\n const signedTx = await solWallet.signTransaction!(transaction) as VersionedTransaction;\n log('Transaction signed by user');\n\n // Serialize to base64\n const serializedTx = Buffer.from(signedTx.serialize()).toString('base64');\n\n // Build x402 v2 payment payload\n const paymentPayload = {\n x402Version: 2,\n resource: requirements.resource || { url },\n accepted: accept,\n payload: {\n transaction: serializedTx,\n },\n };\n\n return btoa(JSON.stringify(paymentPayload));\n }\n\n // -----------------------------------------------------------------------\n // Main fetch\n // -----------------------------------------------------------------------\n async function x402Fetch(\n input: string | URL | Request,\n init?: RequestInit,\n ): Promise<Response> {\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;\n log('Request:', url);\n\n const response = await fetch(input, init);\n if (response.status !== 402) return response;\n\n log('Got 402 Payment Required');\n\n let requirements: any;\n try {\n requirements = await response.clone().json();\n } catch {\n throw new Error('[relai-x402] Failed to parse 402 response body');\n }\n\n const accepts = requirements.accepts || [];\n if (!accepts.length) throw new Error('[relai-x402] No payment options in 402 response');\n\n const selected = selectAccept(accepts);\n if (!selected) {\n const networks = accepts.map((a: any) => a.network).join(', ');\n throw new Error(`[relai-x402] No wallet available for networks: ${networks}`);\n }\n\n const { accept, chain } = selected;\n const amount = accept.amount || accept.maxAmountRequired;\n log(`Selected: ${chain} / ${accept.network} / amount=${amount}`);\n\n // Amount guard\n if (maxAmountAtomic && BigInt(amount) > BigInt(maxAmountAtomic)) {\n throw new Error(`[relai-x402] Amount ${amount} exceeds max ${maxAmountAtomic}`);\n }\n\n // Solana — build SPL transfer natively (no x402-solana dependency)\n if (chain === 'solana' && hasSolanaWallet) {\n const paymentHeader = await buildSolanaPayment(accept, requirements, url);\n log('Retrying with X-PAYMENT header (Solana)');\n return fetch(input, {\n ...init,\n headers: {\n ...(init?.headers || {}),\n 'X-PAYMENT': paymentHeader,\n },\n });\n }\n\n // EVM — build payment header and retry\n if (chain === 'evm') {\n const evmNetwork = normalizeNetwork(accept.network || '');\n const usePermit = evmNetwork && PERMIT_NETWORKS.has(evmNetwork);\n const paymentHeader = usePermit\n ? await buildEvmPermitPayment(accept, requirements, url)\n : await buildEvmPayment(accept, requirements, url);\n log('Retrying with X-PAYMENT header');\n return fetch(input, {\n ...init,\n headers: {\n ...(init?.headers || {}),\n 'X-PAYMENT': paymentHeader,\n },\n });\n }\n\n throw new Error('[relai-x402] Unexpected state — no payment handler matched');\n }\n\n return { fetch: x402Fetch };\n}\n\nexport default createX402Client;\n","// src/types.ts\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** RelAI Facilitator URL */\nexport const RELAI_FACILITATOR_URL = 'https://facilitator.x402.fi';\n\n// ============================================================================\n// Supported Networks\n// ============================================================================\n\n/** All networks supported by RelAI facilitator */\nexport type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base' | 'skale-bite' | 'polygon' | 'ethereum';\n\n/** CAIP-2 network identifiers */\nexport const NETWORK_CAIP2: Record<RelaiNetwork, string> = {\n 'solana': 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n 'base': 'eip155:8453',\n 'avalanche': 'eip155:43114',\n 'skale-base': 'eip155:1187947933',\n 'skale-bite': 'eip155:103698795',\n 'polygon': 'eip155:137',\n 'ethereum': 'eip155:1',\n};\n\n/** Reverse lookup: CAIP-2 → simple network name */\nexport const CAIP2_TO_NETWORK: Record<string, RelaiNetwork> = Object.fromEntries(\n Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k as RelaiNetwork])\n) as Record<string, RelaiNetwork>;\n\n/** Chain IDs for EVM networks */\nexport const CHAIN_IDS: Record<string, number> = {\n 'base': 8453,\n 'avalanche': 43114,\n 'skale-base': 1187947933,\n 'skale-bite': 103698795,\n 'polygon': 137,\n 'ethereum': 1,\n};\n\n/** USDC contract addresses per network */\nexport const USDC_ADDRESSES: Record<RelaiNetwork, string> = {\n 'solana': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n 'base': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'avalanche': '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',\n 'skale-base': '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20',\n 'skale-bite': '0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8',\n 'polygon': '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n 'ethereum': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n};\n\n/** Explorer URLs per network */\nexport const EXPLORER_TX_URL: Record<RelaiNetwork, (tx: string) => string> = {\n 'solana': (tx) => `https://solscan.io/tx/${tx}`,\n 'base': (tx) => `https://basescan.org/tx/${tx}`,\n 'avalanche': (tx) => `https://snowtrace.io/tx/${tx}`,\n 'skale-base': (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}`,\n 'skale-bite': (tx) => `https://base-sepolia-testnet.explorer.skalenodes.com/tx/${tx}`,\n 'polygon': (tx) => `https://polygonscan.com/tx/${tx}`,\n 'ethereum': (tx) => `https://etherscan.io/tx/${tx}`,\n};\n\n/** Human-readable network labels */\nexport const NETWORK_LABELS: Record<RelaiNetwork, string> = {\n 'solana': 'Solana',\n 'base': 'Base',\n 'avalanche': 'Avalanche',\n 'skale-base': 'SKALE Base',\n 'skale-bite': 'SKALE BITE V2',\n 'polygon': 'Polygon',\n 'ethereum': 'Ethereum',\n};\n\n/** Legacy CAIP-2 exports for backward compatibility */\nexport const SOLANA_MAINNET_NETWORK = NETWORK_CAIP2['solana'];\nexport const BASE_MAINNET_NETWORK = NETWORK_CAIP2['base'];\n\n/** Legacy USDC exports for backward compatibility */\nexport const USDC_SOLANA = USDC_ADDRESSES['solana'];\nexport const USDC_BASE = USDC_ADDRESSES['base'];\n\n/** All supported RelAI networks list */\nexport const RELAI_NETWORKS: RelaiNetwork[] = ['solana', 'base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'];\n\n/** Check if a network is Solana-based */\nexport function isSolana(network: string): boolean {\n return network === 'solana' || network.startsWith('solana:');\n}\n\n/** Check if a network is EVM-based */\nexport function isEvm(network: string): boolean {\n return ['base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'].includes(network) || network.startsWith('eip155:');\n}\n\n/** Normalize CAIP-2 or simple name to RelaiNetwork */\nexport function normalizeNetwork(network: string): RelaiNetwork | null {\n if (RELAI_NETWORKS.includes(network as RelaiNetwork)) return network as RelaiNetwork;\n const fromCaip2 = CAIP2_TO_NETWORK[network];\n if (fromCaip2) return fromCaip2;\n // Partial match\n if (network.startsWith('solana:')) return 'solana';\n if (network.startsWith('eip155:')) {\n const chainId = parseInt(network.split(':')[1]);\n const entry = Object.entries(CHAIN_IDS).find(([, id]) => id === chainId);\n if (entry) return entry[0] as RelaiNetwork;\n }\n return null;\n}\n\n// ============================================================================\n// Wallet Types\n// ============================================================================\n\n/** Solana wallet interface */\nexport interface SolanaWallet {\n publicKey: { toString(): string } | null;\n signTransaction: ((tx: unknown) => Promise<unknown>) | null;\n signAllTransactions?: ((txs: unknown[]) => Promise<unknown[]>) | null;\n}\n\n/** EVM wallet interface (viem-compatible) */\nexport interface EvmWallet {\n address: string;\n signTypedData: (params: unknown) => Promise<string>;\n chain?: { id: number };\n}\n\n/** Wallet set for multi-chain support */\nexport interface WalletSet {\n solana?: SolanaWallet;\n evm?: EvmWallet;\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/** Extra fields in payment requirements */\nexport interface AcceptsExtra {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n}\n\n/** A single payment option */\nexport interface PaymentAccept {\n x402Version?: 1 | 2;\n scheme: string;\n network: string;\n maxAmountRequired?: string;\n amount?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: AcceptsExtra;\n resource?: string;\n description?: string;\n mimeType?: string;\n outputSchema?: unknown;\n}\n\n/** Resource info for v2 */\nexport interface ResourceInfo {\n url: string;\n description?: string;\n mimeType?: string;\n}\n\n/** Payment requirements (402 response) */\nexport interface PaymentRequired {\n x402Version: 1 | 2;\n error?: string;\n accepts: PaymentAccept[];\n resource?: ResourceInfo;\n extensions?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Config Types (server-specific types are in server.ts)\n// ============================================================================\n"],"mappings":"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","names":[]}
|
package/dist/server.cjs
CHANGED
|
@@ -31,7 +31,10 @@ var NETWORK_CAIP2 = {
|
|
|
31
31
|
"solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
32
32
|
"base": "eip155:8453",
|
|
33
33
|
"avalanche": "eip155:43114",
|
|
34
|
-
"skale-base": "eip155:1187947933"
|
|
34
|
+
"skale-base": "eip155:1187947933",
|
|
35
|
+
"skale-bite": "eip155:103698795",
|
|
36
|
+
"polygon": "eip155:137",
|
|
37
|
+
"ethereum": "eip155:1"
|
|
35
38
|
};
|
|
36
39
|
var CAIP2_TO_NETWORK = Object.fromEntries(
|
|
37
40
|
Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k])
|
|
@@ -40,7 +43,10 @@ var USDC_ADDRESSES = {
|
|
|
40
43
|
"solana": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
41
44
|
"base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
42
45
|
"avalanche": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
43
|
-
"skale-base": "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20"
|
|
46
|
+
"skale-base": "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
47
|
+
"skale-bite": "0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8",
|
|
48
|
+
"polygon": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
49
|
+
"ethereum": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
44
50
|
};
|
|
45
51
|
var SOLANA_MAINNET_NETWORK = NETWORK_CAIP2["solana"];
|
|
46
52
|
var BASE_MAINNET_NETWORK = NETWORK_CAIP2["base"];
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/types.ts"],"sourcesContent":["// src/server.ts\nimport {\n NETWORK_CAIP2,\n USDC_ADDRESSES,\n RELAI_FACILITATOR_URL,\n type RelaiNetwork,\n} from './types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RelaiServerConfig {\n /** Network to accept payments on */\n network: RelaiNetwork;\n /** RelAI facilitator URL (default: https://facilitator.x402.fi) */\n facilitatorUrl?: string;\n}\n\nexport type DynamicPrice = number | ((req: any) => number | Promise<number>);\n\nexport interface ProtectOptions {\n /** Price in USD (e.g., 0.01 for 1 cent) */\n price: DynamicPrice;\n /** Wallet address to receive payments */\n payTo: string;\n /** Description shown to payer */\n description?: string;\n /** MIME type of the response (default: application/json) */\n mimeType?: string;\n /** Maximum timeout in seconds (default: 60) */\n maxTimeoutSeconds?: number;\n /** Override network for this endpoint */\n network?: RelaiNetwork;\n /** Custom validation after payment is settled */\n customRules?: (req: any) => boolean | Promise<boolean>;\n /** Callback when 402 is returned (no payment provided) */\n onPaymentRequired?: (req: any, info: { price: number; network: RelaiNetwork }) => void;\n /** Callback when payment is settled successfully */\n onPaymentSettled?: (req: any, result: SettleResult) => void;\n /** Callback on error */\n onError?: (req: any, error: unknown) => void;\n}\n\nexport interface SettleResult {\n success: boolean;\n transaction?: string;\n payer?: string;\n network?: string;\n error?: string;\n errorReason?: string;\n}\n\nexport interface PaymentInfo {\n verified: boolean;\n transactionId?: string;\n payer?: string;\n network: RelaiNetwork;\n amount: number;\n}\n\n// ============================================================================\n// Relai Server SDK\n// ============================================================================\n\n/**\n * Server-side SDK for protecting Express endpoints with x402 micropayments.\n * Settles payments through the RelAI facilitator (zero gas fees for users).\n *\n * Supports: Solana, Base, Avalanche, SKALE Base.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n *\n * const relai = new Relai({ network: 'base' });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01, // $0.01 USDC\n * }), (req, res) => {\n * res.json({ data: 'Protected content', payment: req.payment });\n * });\n * ```\n */\nexport class Relai {\n private network: RelaiNetwork;\n private facilitatorUrl: string;\n\n constructor(config: RelaiServerConfig) {\n this.network = config.network;\n this.facilitatorUrl = config.facilitatorUrl || RELAI_FACILITATOR_URL;\n }\n\n /**\n * Express middleware to protect an endpoint with x402 micropayments.\n *\n * Flow:\n * 1. No payment header → returns 402 with payment requirements\n * 2. Payment header present → calls RelAI facilitator `/settle`\n * 3. Settlement success → sets `PAYMENT-RESPONSE` header, attaches `req.payment`, calls `next()`\n */\n protect(options: ProtectOptions) {\n const self = this;\n\n return async (req: any, res: any, next: any) => {\n try {\n // Resolve dynamic price\n const resolvedPrice = typeof options.price === 'function'\n ? await options.price(req)\n : options.price;\n\n if (typeof resolvedPrice !== 'number' || !isFinite(resolvedPrice) || resolvedPrice <= 0) {\n return res.status(400).json({ error: 'Invalid price configuration' });\n }\n\n const network = options.network || self.network;\n const caip2 = NETWORK_CAIP2[network];\n const asset = USDC_ADDRESSES[network];\n const amount = String(Math.floor(resolvedPrice * 1_000_000)); // USD → USDC atomic units (6 decimals)\n\n // Check for payment header (base64-encoded JSON)\n const paymentHeader =\n req.headers['x-payment'] ||\n req.headers['payment-signature'] ||\n req.headers['x-payment-signature'];\n\n // -----------------------------------------------------------\n // No payment → return 402 Payment Required\n // -----------------------------------------------------------\n if (!paymentHeader) {\n options.onPaymentRequired?.(req, { price: resolvedPrice, network });\n\n return res.status(402).json({\n x402Version: 2,\n error: 'Payment required',\n resource: {\n url: `${req.protocol}://${req.get('host')}${req.originalUrl}`,\n description: options.description || 'API access',\n mimeType: options.mimeType || 'application/json',\n },\n accepts: [{\n scheme: 'exact',\n network: caip2,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n extra: {\n name: 'USD Coin',\n version: '2',\n decimals: 6,\n },\n }],\n });\n }\n\n // -----------------------------------------------------------\n // Payment header present → parse and settle via facilitator\n // -----------------------------------------------------------\n let paymentProof: any;\n try {\n // Try base64 first (standard x402 format)\n const decoded = Buffer.from(paymentHeader, 'base64').toString('utf-8');\n paymentProof = JSON.parse(decoded);\n } catch {\n try {\n // Fallback: raw JSON string\n paymentProof = JSON.parse(paymentHeader);\n } catch {\n return res.status(400).json({\n x402Version: 2,\n error: 'Invalid payment header — expected base64-encoded JSON',\n });\n }\n }\n\n // Build payment requirements for facilitator\n const paymentRequirements = {\n scheme: 'exact',\n network,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n };\n\n // Call facilitator /settle\n const settleUrl = `${self.facilitatorUrl}/settle`;\n const settleRes = await fetch(settleUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n paymentPayload: paymentProof,\n paymentRequirements,\n }),\n });\n\n const result: SettleResult = await settleRes.json() as SettleResult;\n\n if (!result.success) {\n return res.status(402).json({\n x402Version: 2,\n error: result.errorReason || result.error || 'Payment settlement failed',\n });\n }\n\n // Attach payment info to request\n const paymentInfo: PaymentInfo = {\n verified: true,\n transactionId: result.transaction,\n payer: result.payer,\n network,\n amount: resolvedPrice,\n };\n req.payment = paymentInfo;\n req.x402Payer = result.payer;\n req.x402Paid = true;\n req.x402Transaction = result.transaction;\n req.x402Network = network;\n\n // Set x402 v2 PAYMENT-RESPONSE header (base64 JSON)\n const paymentResponse = {\n x402Version: 2,\n scheme: 'exact',\n network: caip2,\n transaction: result.transaction,\n payer: result.payer,\n amount,\n asset,\n };\n res.setHeader(\n 'PAYMENT-RESPONSE',\n Buffer.from(JSON.stringify(paymentResponse)).toString('base64'),\n );\n\n options.onPaymentSettled?.(req, result);\n\n // Custom validation after payment\n if (options.customRules) {\n const valid = await options.customRules(req);\n if (!valid) {\n return res.status(403).json({ error: 'Custom validation failed' });\n }\n }\n\n next();\n } catch (error) {\n options.onError?.(req, error);\n console.error('[Relai] Protection error:', error);\n res.status(500).json({ error: 'Internal server error' });\n }\n };\n }\n}\n\nexport default Relai;\n","// src/types.ts\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** RelAI Facilitator URL */\nexport const RELAI_FACILITATOR_URL = 'https://facilitator.x402.fi';\n\n// ============================================================================\n// Supported Networks\n// ============================================================================\n\n/** All networks supported by RelAI facilitator */\nexport type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base';\n\n/** CAIP-2 network identifiers */\nexport const NETWORK_CAIP2: Record<RelaiNetwork, string> = {\n 'solana': 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n 'base': 'eip155:8453',\n 'avalanche': 'eip155:43114',\n 'skale-base': 'eip155:1187947933',\n};\n\n/** Reverse lookup: CAIP-2 → simple network name */\nexport const CAIP2_TO_NETWORK: Record<string, RelaiNetwork> = Object.fromEntries(\n Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k as RelaiNetwork])\n) as Record<string, RelaiNetwork>;\n\n/** Chain IDs for EVM networks */\nexport const CHAIN_IDS: Record<string, number> = {\n 'base': 8453,\n 'avalanche': 43114,\n 'skale-base': 1187947933,\n};\n\n/** USDC contract addresses per network */\nexport const USDC_ADDRESSES: Record<RelaiNetwork, string> = {\n 'solana': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n 'base': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'avalanche': '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',\n 'skale-base': '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20',\n};\n\n/** Explorer URLs per network */\nexport const EXPLORER_TX_URL: Record<RelaiNetwork, (tx: string) => string> = {\n 'solana': (tx) => `https://solscan.io/tx/${tx}`,\n 'base': (tx) => `https://basescan.org/tx/${tx}`,\n 'avalanche': (tx) => `https://snowtrace.io/tx/${tx}`,\n 'skale-base': (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}`,\n};\n\n/** Human-readable network labels */\nexport const NETWORK_LABELS: Record<RelaiNetwork, string> = {\n 'solana': 'Solana',\n 'base': 'Base',\n 'avalanche': 'Avalanche',\n 'skale-base': 'SKALE Base',\n};\n\n/** Legacy CAIP-2 exports for backward compatibility */\nexport const SOLANA_MAINNET_NETWORK = NETWORK_CAIP2['solana'];\nexport const BASE_MAINNET_NETWORK = NETWORK_CAIP2['base'];\n\n/** Legacy USDC exports for backward compatibility */\nexport const USDC_SOLANA = USDC_ADDRESSES['solana'];\nexport const USDC_BASE = USDC_ADDRESSES['base'];\n\n/** All supported RelAI networks list */\nexport const RELAI_NETWORKS: RelaiNetwork[] = ['solana', 'base', 'avalanche', 'skale-base'];\n\n/** Check if a network is Solana-based */\nexport function isSolana(network: string): boolean {\n return network === 'solana' || network.startsWith('solana:');\n}\n\n/** Check if a network is EVM-based */\nexport function isEvm(network: string): boolean {\n return ['base', 'avalanche', 'skale-base'].includes(network) || network.startsWith('eip155:');\n}\n\n/** Normalize CAIP-2 or simple name to RelaiNetwork */\nexport function normalizeNetwork(network: string): RelaiNetwork | null {\n if (RELAI_NETWORKS.includes(network as RelaiNetwork)) return network as RelaiNetwork;\n const fromCaip2 = CAIP2_TO_NETWORK[network];\n if (fromCaip2) return fromCaip2;\n // Partial match\n if (network.startsWith('solana:')) return 'solana';\n if (network.startsWith('eip155:')) {\n const chainId = parseInt(network.split(':')[1]);\n const entry = Object.entries(CHAIN_IDS).find(([, id]) => id === chainId);\n if (entry) return entry[0] as RelaiNetwork;\n }\n return null;\n}\n\n// ============================================================================\n// Wallet Types\n// ============================================================================\n\n/** Solana wallet interface */\nexport interface SolanaWallet {\n publicKey: { toString(): string } | null;\n signTransaction: ((tx: unknown) => Promise<unknown>) | null;\n signAllTransactions?: ((txs: unknown[]) => Promise<unknown[]>) | null;\n}\n\n/** EVM wallet interface (viem-compatible) */\nexport interface EvmWallet {\n address: string;\n signTypedData: (params: unknown) => Promise<string>;\n chain?: { id: number };\n}\n\n/** Wallet set for multi-chain support */\nexport interface WalletSet {\n solana?: SolanaWallet;\n evm?: EvmWallet;\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/** Extra fields in payment requirements */\nexport interface AcceptsExtra {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n}\n\n/** A single payment option */\nexport interface PaymentAccept {\n x402Version?: 1 | 2;\n scheme: string;\n network: string;\n maxAmountRequired?: string;\n amount?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: AcceptsExtra;\n resource?: string;\n description?: string;\n mimeType?: string;\n outputSchema?: unknown;\n}\n\n/** Resource info for v2 */\nexport interface ResourceInfo {\n url: string;\n description?: string;\n mimeType?: string;\n}\n\n/** Payment requirements (402 response) */\nexport interface PaymentRequired {\n x402Version: 1 | 2;\n error?: string;\n accepts: PaymentAccept[];\n resource?: ResourceInfo;\n extensions?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Config Types (server-specific types are in server.ts)\n// ============================================================================\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/types.ts"],"sourcesContent":["// src/server.ts\nimport {\n NETWORK_CAIP2,\n USDC_ADDRESSES,\n RELAI_FACILITATOR_URL,\n type RelaiNetwork,\n} from './types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RelaiServerConfig {\n /** Network to accept payments on */\n network: RelaiNetwork;\n /** RelAI facilitator URL (default: https://facilitator.x402.fi) */\n facilitatorUrl?: string;\n}\n\nexport type DynamicPrice = number | ((req: any) => number | Promise<number>);\n\nexport interface ProtectOptions {\n /** Price in USD (e.g., 0.01 for 1 cent) */\n price: DynamicPrice;\n /** Wallet address to receive payments */\n payTo: string;\n /** Description shown to payer */\n description?: string;\n /** MIME type of the response (default: application/json) */\n mimeType?: string;\n /** Maximum timeout in seconds (default: 60) */\n maxTimeoutSeconds?: number;\n /** Override network for this endpoint */\n network?: RelaiNetwork;\n /** Custom validation after payment is settled */\n customRules?: (req: any) => boolean | Promise<boolean>;\n /** Callback when 402 is returned (no payment provided) */\n onPaymentRequired?: (req: any, info: { price: number; network: RelaiNetwork }) => void;\n /** Callback when payment is settled successfully */\n onPaymentSettled?: (req: any, result: SettleResult) => void;\n /** Callback on error */\n onError?: (req: any, error: unknown) => void;\n}\n\nexport interface SettleResult {\n success: boolean;\n transaction?: string;\n payer?: string;\n network?: string;\n error?: string;\n errorReason?: string;\n}\n\nexport interface PaymentInfo {\n verified: boolean;\n transactionId?: string;\n payer?: string;\n network: RelaiNetwork;\n amount: number;\n}\n\n// ============================================================================\n// Relai Server SDK\n// ============================================================================\n\n/**\n * Server-side SDK for protecting Express endpoints with x402 micropayments.\n * Settles payments through the RelAI facilitator (zero gas fees for users).\n *\n * Supports: Solana, Base, Avalanche, SKALE Base.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n *\n * const relai = new Relai({ network: 'base' });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01, // $0.01 USDC\n * }), (req, res) => {\n * res.json({ data: 'Protected content', payment: req.payment });\n * });\n * ```\n */\nexport class Relai {\n private network: RelaiNetwork;\n private facilitatorUrl: string;\n\n constructor(config: RelaiServerConfig) {\n this.network = config.network;\n this.facilitatorUrl = config.facilitatorUrl || RELAI_FACILITATOR_URL;\n }\n\n /**\n * Express middleware to protect an endpoint with x402 micropayments.\n *\n * Flow:\n * 1. No payment header → returns 402 with payment requirements\n * 2. Payment header present → calls RelAI facilitator `/settle`\n * 3. Settlement success → sets `PAYMENT-RESPONSE` header, attaches `req.payment`, calls `next()`\n */\n protect(options: ProtectOptions) {\n const self = this;\n\n return async (req: any, res: any, next: any) => {\n try {\n // Resolve dynamic price\n const resolvedPrice = typeof options.price === 'function'\n ? await options.price(req)\n : options.price;\n\n if (typeof resolvedPrice !== 'number' || !isFinite(resolvedPrice) || resolvedPrice <= 0) {\n return res.status(400).json({ error: 'Invalid price configuration' });\n }\n\n const network = options.network || self.network;\n const caip2 = NETWORK_CAIP2[network];\n const asset = USDC_ADDRESSES[network];\n const amount = String(Math.floor(resolvedPrice * 1_000_000)); // USD → USDC atomic units (6 decimals)\n\n // Check for payment header (base64-encoded JSON)\n const paymentHeader =\n req.headers['x-payment'] ||\n req.headers['payment-signature'] ||\n req.headers['x-payment-signature'];\n\n // -----------------------------------------------------------\n // No payment → return 402 Payment Required\n // -----------------------------------------------------------\n if (!paymentHeader) {\n options.onPaymentRequired?.(req, { price: resolvedPrice, network });\n\n return res.status(402).json({\n x402Version: 2,\n error: 'Payment required',\n resource: {\n url: `${req.protocol}://${req.get('host')}${req.originalUrl}`,\n description: options.description || 'API access',\n mimeType: options.mimeType || 'application/json',\n },\n accepts: [{\n scheme: 'exact',\n network: caip2,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n extra: {\n name: 'USD Coin',\n version: '2',\n decimals: 6,\n },\n }],\n });\n }\n\n // -----------------------------------------------------------\n // Payment header present → parse and settle via facilitator\n // -----------------------------------------------------------\n let paymentProof: any;\n try {\n // Try base64 first (standard x402 format)\n const decoded = Buffer.from(paymentHeader, 'base64').toString('utf-8');\n paymentProof = JSON.parse(decoded);\n } catch {\n try {\n // Fallback: raw JSON string\n paymentProof = JSON.parse(paymentHeader);\n } catch {\n return res.status(400).json({\n x402Version: 2,\n error: 'Invalid payment header — expected base64-encoded JSON',\n });\n }\n }\n\n // Build payment requirements for facilitator\n const paymentRequirements = {\n scheme: 'exact',\n network,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n };\n\n // Call facilitator /settle\n const settleUrl = `${self.facilitatorUrl}/settle`;\n const settleRes = await fetch(settleUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n paymentPayload: paymentProof,\n paymentRequirements,\n }),\n });\n\n const result: SettleResult = await settleRes.json() as SettleResult;\n\n if (!result.success) {\n return res.status(402).json({\n x402Version: 2,\n error: result.errorReason || result.error || 'Payment settlement failed',\n });\n }\n\n // Attach payment info to request\n const paymentInfo: PaymentInfo = {\n verified: true,\n transactionId: result.transaction,\n payer: result.payer,\n network,\n amount: resolvedPrice,\n };\n req.payment = paymentInfo;\n req.x402Payer = result.payer;\n req.x402Paid = true;\n req.x402Transaction = result.transaction;\n req.x402Network = network;\n\n // Set x402 v2 PAYMENT-RESPONSE header (base64 JSON)\n const paymentResponse = {\n x402Version: 2,\n scheme: 'exact',\n network: caip2,\n transaction: result.transaction,\n payer: result.payer,\n amount,\n asset,\n };\n res.setHeader(\n 'PAYMENT-RESPONSE',\n Buffer.from(JSON.stringify(paymentResponse)).toString('base64'),\n );\n\n options.onPaymentSettled?.(req, result);\n\n // Custom validation after payment\n if (options.customRules) {\n const valid = await options.customRules(req);\n if (!valid) {\n return res.status(403).json({ error: 'Custom validation failed' });\n }\n }\n\n next();\n } catch (error) {\n options.onError?.(req, error);\n console.error('[Relai] Protection error:', error);\n res.status(500).json({ error: 'Internal server error' });\n }\n };\n }\n}\n\nexport default Relai;\n","// src/types.ts\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** RelAI Facilitator URL */\nexport const RELAI_FACILITATOR_URL = 'https://facilitator.x402.fi';\n\n// ============================================================================\n// Supported Networks\n// ============================================================================\n\n/** All networks supported by RelAI facilitator */\nexport type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base' | 'skale-bite' | 'polygon' | 'ethereum';\n\n/** CAIP-2 network identifiers */\nexport const NETWORK_CAIP2: Record<RelaiNetwork, string> = {\n 'solana': 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n 'base': 'eip155:8453',\n 'avalanche': 'eip155:43114',\n 'skale-base': 'eip155:1187947933',\n 'skale-bite': 'eip155:103698795',\n 'polygon': 'eip155:137',\n 'ethereum': 'eip155:1',\n};\n\n/** Reverse lookup: CAIP-2 → simple network name */\nexport const CAIP2_TO_NETWORK: Record<string, RelaiNetwork> = Object.fromEntries(\n Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k as RelaiNetwork])\n) as Record<string, RelaiNetwork>;\n\n/** Chain IDs for EVM networks */\nexport const CHAIN_IDS: Record<string, number> = {\n 'base': 8453,\n 'avalanche': 43114,\n 'skale-base': 1187947933,\n 'skale-bite': 103698795,\n 'polygon': 137,\n 'ethereum': 1,\n};\n\n/** USDC contract addresses per network */\nexport const USDC_ADDRESSES: Record<RelaiNetwork, string> = {\n 'solana': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n 'base': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'avalanche': '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',\n 'skale-base': '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20',\n 'skale-bite': '0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8',\n 'polygon': '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n 'ethereum': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n};\n\n/** Explorer URLs per network */\nexport const EXPLORER_TX_URL: Record<RelaiNetwork, (tx: string) => string> = {\n 'solana': (tx) => `https://solscan.io/tx/${tx}`,\n 'base': (tx) => `https://basescan.org/tx/${tx}`,\n 'avalanche': (tx) => `https://snowtrace.io/tx/${tx}`,\n 'skale-base': (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}`,\n 'skale-bite': (tx) => `https://base-sepolia-testnet.explorer.skalenodes.com/tx/${tx}`,\n 'polygon': (tx) => `https://polygonscan.com/tx/${tx}`,\n 'ethereum': (tx) => `https://etherscan.io/tx/${tx}`,\n};\n\n/** Human-readable network labels */\nexport const NETWORK_LABELS: Record<RelaiNetwork, string> = {\n 'solana': 'Solana',\n 'base': 'Base',\n 'avalanche': 'Avalanche',\n 'skale-base': 'SKALE Base',\n 'skale-bite': 'SKALE BITE V2',\n 'polygon': 'Polygon',\n 'ethereum': 'Ethereum',\n};\n\n/** Legacy CAIP-2 exports for backward compatibility */\nexport const SOLANA_MAINNET_NETWORK = NETWORK_CAIP2['solana'];\nexport const BASE_MAINNET_NETWORK = NETWORK_CAIP2['base'];\n\n/** Legacy USDC exports for backward compatibility */\nexport const USDC_SOLANA = USDC_ADDRESSES['solana'];\nexport const USDC_BASE = USDC_ADDRESSES['base'];\n\n/** All supported RelAI networks list */\nexport const RELAI_NETWORKS: RelaiNetwork[] = ['solana', 'base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'];\n\n/** Check if a network is Solana-based */\nexport function isSolana(network: string): boolean {\n return network === 'solana' || network.startsWith('solana:');\n}\n\n/** Check if a network is EVM-based */\nexport function isEvm(network: string): boolean {\n return ['base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'].includes(network) || network.startsWith('eip155:');\n}\n\n/** Normalize CAIP-2 or simple name to RelaiNetwork */\nexport function normalizeNetwork(network: string): RelaiNetwork | null {\n if (RELAI_NETWORKS.includes(network as RelaiNetwork)) return network as RelaiNetwork;\n const fromCaip2 = CAIP2_TO_NETWORK[network];\n if (fromCaip2) return fromCaip2;\n // Partial match\n if (network.startsWith('solana:')) return 'solana';\n if (network.startsWith('eip155:')) {\n const chainId = parseInt(network.split(':')[1]);\n const entry = Object.entries(CHAIN_IDS).find(([, id]) => id === chainId);\n if (entry) return entry[0] as RelaiNetwork;\n }\n return null;\n}\n\n// ============================================================================\n// Wallet Types\n// ============================================================================\n\n/** Solana wallet interface */\nexport interface SolanaWallet {\n publicKey: { toString(): string } | null;\n signTransaction: ((tx: unknown) => Promise<unknown>) | null;\n signAllTransactions?: ((txs: unknown[]) => Promise<unknown[]>) | null;\n}\n\n/** EVM wallet interface (viem-compatible) */\nexport interface EvmWallet {\n address: string;\n signTypedData: (params: unknown) => Promise<string>;\n chain?: { id: number };\n}\n\n/** Wallet set for multi-chain support */\nexport interface WalletSet {\n solana?: SolanaWallet;\n evm?: EvmWallet;\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/** Extra fields in payment requirements */\nexport interface AcceptsExtra {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n}\n\n/** A single payment option */\nexport interface PaymentAccept {\n x402Version?: 1 | 2;\n scheme: string;\n network: string;\n maxAmountRequired?: string;\n amount?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: AcceptsExtra;\n resource?: string;\n description?: string;\n mimeType?: string;\n outputSchema?: unknown;\n}\n\n/** Resource info for v2 */\nexport interface ResourceInfo {\n url: string;\n description?: string;\n mimeType?: string;\n}\n\n/** Payment requirements (402 response) */\nexport interface PaymentRequired {\n x402Version: 1 | 2;\n error?: string;\n accepts: PaymentAccept[];\n resource?: ResourceInfo;\n extensions?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Config Types (server-specific types are in server.ts)\n// ============================================================================\n"],"mappings":"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","names":[]}
|
package/dist/server.d.cts
CHANGED
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -4,7 +4,10 @@ var NETWORK_CAIP2 = {
|
|
|
4
4
|
"solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
5
5
|
"base": "eip155:8453",
|
|
6
6
|
"avalanche": "eip155:43114",
|
|
7
|
-
"skale-base": "eip155:1187947933"
|
|
7
|
+
"skale-base": "eip155:1187947933",
|
|
8
|
+
"skale-bite": "eip155:103698795",
|
|
9
|
+
"polygon": "eip155:137",
|
|
10
|
+
"ethereum": "eip155:1"
|
|
8
11
|
};
|
|
9
12
|
var CAIP2_TO_NETWORK = Object.fromEntries(
|
|
10
13
|
Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k])
|
|
@@ -13,7 +16,10 @@ var USDC_ADDRESSES = {
|
|
|
13
16
|
"solana": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
14
17
|
"base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
15
18
|
"avalanche": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
16
|
-
"skale-base": "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20"
|
|
19
|
+
"skale-base": "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
20
|
+
"skale-bite": "0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8",
|
|
21
|
+
"polygon": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
22
|
+
"ethereum": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
17
23
|
};
|
|
18
24
|
var SOLANA_MAINNET_NETWORK = NETWORK_CAIP2["solana"];
|
|
19
25
|
var BASE_MAINNET_NETWORK = NETWORK_CAIP2["base"];
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/server.ts"],"sourcesContent":["// src/types.ts\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** RelAI Facilitator URL */\nexport const RELAI_FACILITATOR_URL = 'https://facilitator.x402.fi';\n\n// ============================================================================\n// Supported Networks\n// ============================================================================\n\n/** All networks supported by RelAI facilitator */\nexport type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base';\n\n/** CAIP-2 network identifiers */\nexport const NETWORK_CAIP2: Record<RelaiNetwork, string> = {\n 'solana': 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n 'base': 'eip155:8453',\n 'avalanche': 'eip155:43114',\n 'skale-base': 'eip155:1187947933',\n};\n\n/** Reverse lookup: CAIP-2 → simple network name */\nexport const CAIP2_TO_NETWORK: Record<string, RelaiNetwork> = Object.fromEntries(\n Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k as RelaiNetwork])\n) as Record<string, RelaiNetwork>;\n\n/** Chain IDs for EVM networks */\nexport const CHAIN_IDS: Record<string, number> = {\n 'base': 8453,\n 'avalanche': 43114,\n 'skale-base': 1187947933,\n};\n\n/** USDC contract addresses per network */\nexport const USDC_ADDRESSES: Record<RelaiNetwork, string> = {\n 'solana': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n 'base': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'avalanche': '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',\n 'skale-base': '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20',\n};\n\n/** Explorer URLs per network */\nexport const EXPLORER_TX_URL: Record<RelaiNetwork, (tx: string) => string> = {\n 'solana': (tx) => `https://solscan.io/tx/${tx}`,\n 'base': (tx) => `https://basescan.org/tx/${tx}`,\n 'avalanche': (tx) => `https://snowtrace.io/tx/${tx}`,\n 'skale-base': (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}`,\n};\n\n/** Human-readable network labels */\nexport const NETWORK_LABELS: Record<RelaiNetwork, string> = {\n 'solana': 'Solana',\n 'base': 'Base',\n 'avalanche': 'Avalanche',\n 'skale-base': 'SKALE Base',\n};\n\n/** Legacy CAIP-2 exports for backward compatibility */\nexport const SOLANA_MAINNET_NETWORK = NETWORK_CAIP2['solana'];\nexport const BASE_MAINNET_NETWORK = NETWORK_CAIP2['base'];\n\n/** Legacy USDC exports for backward compatibility */\nexport const USDC_SOLANA = USDC_ADDRESSES['solana'];\nexport const USDC_BASE = USDC_ADDRESSES['base'];\n\n/** All supported RelAI networks list */\nexport const RELAI_NETWORKS: RelaiNetwork[] = ['solana', 'base', 'avalanche', 'skale-base'];\n\n/** Check if a network is Solana-based */\nexport function isSolana(network: string): boolean {\n return network === 'solana' || network.startsWith('solana:');\n}\n\n/** Check if a network is EVM-based */\nexport function isEvm(network: string): boolean {\n return ['base', 'avalanche', 'skale-base'].includes(network) || network.startsWith('eip155:');\n}\n\n/** Normalize CAIP-2 or simple name to RelaiNetwork */\nexport function normalizeNetwork(network: string): RelaiNetwork | null {\n if (RELAI_NETWORKS.includes(network as RelaiNetwork)) return network as RelaiNetwork;\n const fromCaip2 = CAIP2_TO_NETWORK[network];\n if (fromCaip2) return fromCaip2;\n // Partial match\n if (network.startsWith('solana:')) return 'solana';\n if (network.startsWith('eip155:')) {\n const chainId = parseInt(network.split(':')[1]);\n const entry = Object.entries(CHAIN_IDS).find(([, id]) => id === chainId);\n if (entry) return entry[0] as RelaiNetwork;\n }\n return null;\n}\n\n// ============================================================================\n// Wallet Types\n// ============================================================================\n\n/** Solana wallet interface */\nexport interface SolanaWallet {\n publicKey: { toString(): string } | null;\n signTransaction: ((tx: unknown) => Promise<unknown>) | null;\n signAllTransactions?: ((txs: unknown[]) => Promise<unknown[]>) | null;\n}\n\n/** EVM wallet interface (viem-compatible) */\nexport interface EvmWallet {\n address: string;\n signTypedData: (params: unknown) => Promise<string>;\n chain?: { id: number };\n}\n\n/** Wallet set for multi-chain support */\nexport interface WalletSet {\n solana?: SolanaWallet;\n evm?: EvmWallet;\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/** Extra fields in payment requirements */\nexport interface AcceptsExtra {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n}\n\n/** A single payment option */\nexport interface PaymentAccept {\n x402Version?: 1 | 2;\n scheme: string;\n network: string;\n maxAmountRequired?: string;\n amount?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: AcceptsExtra;\n resource?: string;\n description?: string;\n mimeType?: string;\n outputSchema?: unknown;\n}\n\n/** Resource info for v2 */\nexport interface ResourceInfo {\n url: string;\n description?: string;\n mimeType?: string;\n}\n\n/** Payment requirements (402 response) */\nexport interface PaymentRequired {\n x402Version: 1 | 2;\n error?: string;\n accepts: PaymentAccept[];\n resource?: ResourceInfo;\n extensions?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Config Types (server-specific types are in server.ts)\n// ============================================================================\n","// src/server.ts\nimport {\n NETWORK_CAIP2,\n USDC_ADDRESSES,\n RELAI_FACILITATOR_URL,\n type RelaiNetwork,\n} from './types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RelaiServerConfig {\n /** Network to accept payments on */\n network: RelaiNetwork;\n /** RelAI facilitator URL (default: https://facilitator.x402.fi) */\n facilitatorUrl?: string;\n}\n\nexport type DynamicPrice = number | ((req: any) => number | Promise<number>);\n\nexport interface ProtectOptions {\n /** Price in USD (e.g., 0.01 for 1 cent) */\n price: DynamicPrice;\n /** Wallet address to receive payments */\n payTo: string;\n /** Description shown to payer */\n description?: string;\n /** MIME type of the response (default: application/json) */\n mimeType?: string;\n /** Maximum timeout in seconds (default: 60) */\n maxTimeoutSeconds?: number;\n /** Override network for this endpoint */\n network?: RelaiNetwork;\n /** Custom validation after payment is settled */\n customRules?: (req: any) => boolean | Promise<boolean>;\n /** Callback when 402 is returned (no payment provided) */\n onPaymentRequired?: (req: any, info: { price: number; network: RelaiNetwork }) => void;\n /** Callback when payment is settled successfully */\n onPaymentSettled?: (req: any, result: SettleResult) => void;\n /** Callback on error */\n onError?: (req: any, error: unknown) => void;\n}\n\nexport interface SettleResult {\n success: boolean;\n transaction?: string;\n payer?: string;\n network?: string;\n error?: string;\n errorReason?: string;\n}\n\nexport interface PaymentInfo {\n verified: boolean;\n transactionId?: string;\n payer?: string;\n network: RelaiNetwork;\n amount: number;\n}\n\n// ============================================================================\n// Relai Server SDK\n// ============================================================================\n\n/**\n * Server-side SDK for protecting Express endpoints with x402 micropayments.\n * Settles payments through the RelAI facilitator (zero gas fees for users).\n *\n * Supports: Solana, Base, Avalanche, SKALE Base.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n *\n * const relai = new Relai({ network: 'base' });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01, // $0.01 USDC\n * }), (req, res) => {\n * res.json({ data: 'Protected content', payment: req.payment });\n * });\n * ```\n */\nexport class Relai {\n private network: RelaiNetwork;\n private facilitatorUrl: string;\n\n constructor(config: RelaiServerConfig) {\n this.network = config.network;\n this.facilitatorUrl = config.facilitatorUrl || RELAI_FACILITATOR_URL;\n }\n\n /**\n * Express middleware to protect an endpoint with x402 micropayments.\n *\n * Flow:\n * 1. No payment header → returns 402 with payment requirements\n * 2. Payment header present → calls RelAI facilitator `/settle`\n * 3. Settlement success → sets `PAYMENT-RESPONSE` header, attaches `req.payment`, calls `next()`\n */\n protect(options: ProtectOptions) {\n const self = this;\n\n return async (req: any, res: any, next: any) => {\n try {\n // Resolve dynamic price\n const resolvedPrice = typeof options.price === 'function'\n ? await options.price(req)\n : options.price;\n\n if (typeof resolvedPrice !== 'number' || !isFinite(resolvedPrice) || resolvedPrice <= 0) {\n return res.status(400).json({ error: 'Invalid price configuration' });\n }\n\n const network = options.network || self.network;\n const caip2 = NETWORK_CAIP2[network];\n const asset = USDC_ADDRESSES[network];\n const amount = String(Math.floor(resolvedPrice * 1_000_000)); // USD → USDC atomic units (6 decimals)\n\n // Check for payment header (base64-encoded JSON)\n const paymentHeader =\n req.headers['x-payment'] ||\n req.headers['payment-signature'] ||\n req.headers['x-payment-signature'];\n\n // -----------------------------------------------------------\n // No payment → return 402 Payment Required\n // -----------------------------------------------------------\n if (!paymentHeader) {\n options.onPaymentRequired?.(req, { price: resolvedPrice, network });\n\n return res.status(402).json({\n x402Version: 2,\n error: 'Payment required',\n resource: {\n url: `${req.protocol}://${req.get('host')}${req.originalUrl}`,\n description: options.description || 'API access',\n mimeType: options.mimeType || 'application/json',\n },\n accepts: [{\n scheme: 'exact',\n network: caip2,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n extra: {\n name: 'USD Coin',\n version: '2',\n decimals: 6,\n },\n }],\n });\n }\n\n // -----------------------------------------------------------\n // Payment header present → parse and settle via facilitator\n // -----------------------------------------------------------\n let paymentProof: any;\n try {\n // Try base64 first (standard x402 format)\n const decoded = Buffer.from(paymentHeader, 'base64').toString('utf-8');\n paymentProof = JSON.parse(decoded);\n } catch {\n try {\n // Fallback: raw JSON string\n paymentProof = JSON.parse(paymentHeader);\n } catch {\n return res.status(400).json({\n x402Version: 2,\n error: 'Invalid payment header — expected base64-encoded JSON',\n });\n }\n }\n\n // Build payment requirements for facilitator\n const paymentRequirements = {\n scheme: 'exact',\n network,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n };\n\n // Call facilitator /settle\n const settleUrl = `${self.facilitatorUrl}/settle`;\n const settleRes = await fetch(settleUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n paymentPayload: paymentProof,\n paymentRequirements,\n }),\n });\n\n const result: SettleResult = await settleRes.json() as SettleResult;\n\n if (!result.success) {\n return res.status(402).json({\n x402Version: 2,\n error: result.errorReason || result.error || 'Payment settlement failed',\n });\n }\n\n // Attach payment info to request\n const paymentInfo: PaymentInfo = {\n verified: true,\n transactionId: result.transaction,\n payer: result.payer,\n network,\n amount: resolvedPrice,\n };\n req.payment = paymentInfo;\n req.x402Payer = result.payer;\n req.x402Paid = true;\n req.x402Transaction = result.transaction;\n req.x402Network = network;\n\n // Set x402 v2 PAYMENT-RESPONSE header (base64 JSON)\n const paymentResponse = {\n x402Version: 2,\n scheme: 'exact',\n network: caip2,\n transaction: result.transaction,\n payer: result.payer,\n amount,\n asset,\n };\n res.setHeader(\n 'PAYMENT-RESPONSE',\n Buffer.from(JSON.stringify(paymentResponse)).toString('base64'),\n );\n\n options.onPaymentSettled?.(req, result);\n\n // Custom validation after payment\n if (options.customRules) {\n const valid = await options.customRules(req);\n if (!valid) {\n return res.status(403).json({ error: 'Custom validation failed' });\n }\n }\n\n next();\n } catch (error) {\n options.onError?.(req, error);\n console.error('[Relai] Protection error:', error);\n res.status(500).json({ error: 'Internal server error' });\n }\n };\n }\n}\n\nexport default Relai;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/server.ts"],"sourcesContent":["// src/types.ts\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** RelAI Facilitator URL */\nexport const RELAI_FACILITATOR_URL = 'https://facilitator.x402.fi';\n\n// ============================================================================\n// Supported Networks\n// ============================================================================\n\n/** All networks supported by RelAI facilitator */\nexport type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base' | 'skale-bite' | 'polygon' | 'ethereum';\n\n/** CAIP-2 network identifiers */\nexport const NETWORK_CAIP2: Record<RelaiNetwork, string> = {\n 'solana': 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n 'base': 'eip155:8453',\n 'avalanche': 'eip155:43114',\n 'skale-base': 'eip155:1187947933',\n 'skale-bite': 'eip155:103698795',\n 'polygon': 'eip155:137',\n 'ethereum': 'eip155:1',\n};\n\n/** Reverse lookup: CAIP-2 → simple network name */\nexport const CAIP2_TO_NETWORK: Record<string, RelaiNetwork> = Object.fromEntries(\n Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k as RelaiNetwork])\n) as Record<string, RelaiNetwork>;\n\n/** Chain IDs for EVM networks */\nexport const CHAIN_IDS: Record<string, number> = {\n 'base': 8453,\n 'avalanche': 43114,\n 'skale-base': 1187947933,\n 'skale-bite': 103698795,\n 'polygon': 137,\n 'ethereum': 1,\n};\n\n/** USDC contract addresses per network */\nexport const USDC_ADDRESSES: Record<RelaiNetwork, string> = {\n 'solana': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n 'base': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'avalanche': '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',\n 'skale-base': '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20',\n 'skale-bite': '0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8',\n 'polygon': '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n 'ethereum': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n};\n\n/** Explorer URLs per network */\nexport const EXPLORER_TX_URL: Record<RelaiNetwork, (tx: string) => string> = {\n 'solana': (tx) => `https://solscan.io/tx/${tx}`,\n 'base': (tx) => `https://basescan.org/tx/${tx}`,\n 'avalanche': (tx) => `https://snowtrace.io/tx/${tx}`,\n 'skale-base': (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}`,\n 'skale-bite': (tx) => `https://base-sepolia-testnet.explorer.skalenodes.com/tx/${tx}`,\n 'polygon': (tx) => `https://polygonscan.com/tx/${tx}`,\n 'ethereum': (tx) => `https://etherscan.io/tx/${tx}`,\n};\n\n/** Human-readable network labels */\nexport const NETWORK_LABELS: Record<RelaiNetwork, string> = {\n 'solana': 'Solana',\n 'base': 'Base',\n 'avalanche': 'Avalanche',\n 'skale-base': 'SKALE Base',\n 'skale-bite': 'SKALE BITE V2',\n 'polygon': 'Polygon',\n 'ethereum': 'Ethereum',\n};\n\n/** Legacy CAIP-2 exports for backward compatibility */\nexport const SOLANA_MAINNET_NETWORK = NETWORK_CAIP2['solana'];\nexport const BASE_MAINNET_NETWORK = NETWORK_CAIP2['base'];\n\n/** Legacy USDC exports for backward compatibility */\nexport const USDC_SOLANA = USDC_ADDRESSES['solana'];\nexport const USDC_BASE = USDC_ADDRESSES['base'];\n\n/** All supported RelAI networks list */\nexport const RELAI_NETWORKS: RelaiNetwork[] = ['solana', 'base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'];\n\n/** Check if a network is Solana-based */\nexport function isSolana(network: string): boolean {\n return network === 'solana' || network.startsWith('solana:');\n}\n\n/** Check if a network is EVM-based */\nexport function isEvm(network: string): boolean {\n return ['base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'].includes(network) || network.startsWith('eip155:');\n}\n\n/** Normalize CAIP-2 or simple name to RelaiNetwork */\nexport function normalizeNetwork(network: string): RelaiNetwork | null {\n if (RELAI_NETWORKS.includes(network as RelaiNetwork)) return network as RelaiNetwork;\n const fromCaip2 = CAIP2_TO_NETWORK[network];\n if (fromCaip2) return fromCaip2;\n // Partial match\n if (network.startsWith('solana:')) return 'solana';\n if (network.startsWith('eip155:')) {\n const chainId = parseInt(network.split(':')[1]);\n const entry = Object.entries(CHAIN_IDS).find(([, id]) => id === chainId);\n if (entry) return entry[0] as RelaiNetwork;\n }\n return null;\n}\n\n// ============================================================================\n// Wallet Types\n// ============================================================================\n\n/** Solana wallet interface */\nexport interface SolanaWallet {\n publicKey: { toString(): string } | null;\n signTransaction: ((tx: unknown) => Promise<unknown>) | null;\n signAllTransactions?: ((txs: unknown[]) => Promise<unknown[]>) | null;\n}\n\n/** EVM wallet interface (viem-compatible) */\nexport interface EvmWallet {\n address: string;\n signTypedData: (params: unknown) => Promise<string>;\n chain?: { id: number };\n}\n\n/** Wallet set for multi-chain support */\nexport interface WalletSet {\n solana?: SolanaWallet;\n evm?: EvmWallet;\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/** Extra fields in payment requirements */\nexport interface AcceptsExtra {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n}\n\n/** A single payment option */\nexport interface PaymentAccept {\n x402Version?: 1 | 2;\n scheme: string;\n network: string;\n maxAmountRequired?: string;\n amount?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: AcceptsExtra;\n resource?: string;\n description?: string;\n mimeType?: string;\n outputSchema?: unknown;\n}\n\n/** Resource info for v2 */\nexport interface ResourceInfo {\n url: string;\n description?: string;\n mimeType?: string;\n}\n\n/** Payment requirements (402 response) */\nexport interface PaymentRequired {\n x402Version: 1 | 2;\n error?: string;\n accepts: PaymentAccept[];\n resource?: ResourceInfo;\n extensions?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Config Types (server-specific types are in server.ts)\n// ============================================================================\n","// src/server.ts\nimport {\n NETWORK_CAIP2,\n USDC_ADDRESSES,\n RELAI_FACILITATOR_URL,\n type RelaiNetwork,\n} from './types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RelaiServerConfig {\n /** Network to accept payments on */\n network: RelaiNetwork;\n /** RelAI facilitator URL (default: https://facilitator.x402.fi) */\n facilitatorUrl?: string;\n}\n\nexport type DynamicPrice = number | ((req: any) => number | Promise<number>);\n\nexport interface ProtectOptions {\n /** Price in USD (e.g., 0.01 for 1 cent) */\n price: DynamicPrice;\n /** Wallet address to receive payments */\n payTo: string;\n /** Description shown to payer */\n description?: string;\n /** MIME type of the response (default: application/json) */\n mimeType?: string;\n /** Maximum timeout in seconds (default: 60) */\n maxTimeoutSeconds?: number;\n /** Override network for this endpoint */\n network?: RelaiNetwork;\n /** Custom validation after payment is settled */\n customRules?: (req: any) => boolean | Promise<boolean>;\n /** Callback when 402 is returned (no payment provided) */\n onPaymentRequired?: (req: any, info: { price: number; network: RelaiNetwork }) => void;\n /** Callback when payment is settled successfully */\n onPaymentSettled?: (req: any, result: SettleResult) => void;\n /** Callback on error */\n onError?: (req: any, error: unknown) => void;\n}\n\nexport interface SettleResult {\n success: boolean;\n transaction?: string;\n payer?: string;\n network?: string;\n error?: string;\n errorReason?: string;\n}\n\nexport interface PaymentInfo {\n verified: boolean;\n transactionId?: string;\n payer?: string;\n network: RelaiNetwork;\n amount: number;\n}\n\n// ============================================================================\n// Relai Server SDK\n// ============================================================================\n\n/**\n * Server-side SDK for protecting Express endpoints with x402 micropayments.\n * Settles payments through the RelAI facilitator (zero gas fees for users).\n *\n * Supports: Solana, Base, Avalanche, SKALE Base.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n *\n * const relai = new Relai({ network: 'base' });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01, // $0.01 USDC\n * }), (req, res) => {\n * res.json({ data: 'Protected content', payment: req.payment });\n * });\n * ```\n */\nexport class Relai {\n private network: RelaiNetwork;\n private facilitatorUrl: string;\n\n constructor(config: RelaiServerConfig) {\n this.network = config.network;\n this.facilitatorUrl = config.facilitatorUrl || RELAI_FACILITATOR_URL;\n }\n\n /**\n * Express middleware to protect an endpoint with x402 micropayments.\n *\n * Flow:\n * 1. No payment header → returns 402 with payment requirements\n * 2. Payment header present → calls RelAI facilitator `/settle`\n * 3. Settlement success → sets `PAYMENT-RESPONSE` header, attaches `req.payment`, calls `next()`\n */\n protect(options: ProtectOptions) {\n const self = this;\n\n return async (req: any, res: any, next: any) => {\n try {\n // Resolve dynamic price\n const resolvedPrice = typeof options.price === 'function'\n ? await options.price(req)\n : options.price;\n\n if (typeof resolvedPrice !== 'number' || !isFinite(resolvedPrice) || resolvedPrice <= 0) {\n return res.status(400).json({ error: 'Invalid price configuration' });\n }\n\n const network = options.network || self.network;\n const caip2 = NETWORK_CAIP2[network];\n const asset = USDC_ADDRESSES[network];\n const amount = String(Math.floor(resolvedPrice * 1_000_000)); // USD → USDC atomic units (6 decimals)\n\n // Check for payment header (base64-encoded JSON)\n const paymentHeader =\n req.headers['x-payment'] ||\n req.headers['payment-signature'] ||\n req.headers['x-payment-signature'];\n\n // -----------------------------------------------------------\n // No payment → return 402 Payment Required\n // -----------------------------------------------------------\n if (!paymentHeader) {\n options.onPaymentRequired?.(req, { price: resolvedPrice, network });\n\n return res.status(402).json({\n x402Version: 2,\n error: 'Payment required',\n resource: {\n url: `${req.protocol}://${req.get('host')}${req.originalUrl}`,\n description: options.description || 'API access',\n mimeType: options.mimeType || 'application/json',\n },\n accepts: [{\n scheme: 'exact',\n network: caip2,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n extra: {\n name: 'USD Coin',\n version: '2',\n decimals: 6,\n },\n }],\n });\n }\n\n // -----------------------------------------------------------\n // Payment header present → parse and settle via facilitator\n // -----------------------------------------------------------\n let paymentProof: any;\n try {\n // Try base64 first (standard x402 format)\n const decoded = Buffer.from(paymentHeader, 'base64').toString('utf-8');\n paymentProof = JSON.parse(decoded);\n } catch {\n try {\n // Fallback: raw JSON string\n paymentProof = JSON.parse(paymentHeader);\n } catch {\n return res.status(400).json({\n x402Version: 2,\n error: 'Invalid payment header — expected base64-encoded JSON',\n });\n }\n }\n\n // Build payment requirements for facilitator\n const paymentRequirements = {\n scheme: 'exact',\n network,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n };\n\n // Call facilitator /settle\n const settleUrl = `${self.facilitatorUrl}/settle`;\n const settleRes = await fetch(settleUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n paymentPayload: paymentProof,\n paymentRequirements,\n }),\n });\n\n const result: SettleResult = await settleRes.json() as SettleResult;\n\n if (!result.success) {\n return res.status(402).json({\n x402Version: 2,\n error: result.errorReason || result.error || 'Payment settlement failed',\n });\n }\n\n // Attach payment info to request\n const paymentInfo: PaymentInfo = {\n verified: true,\n transactionId: result.transaction,\n payer: result.payer,\n network,\n amount: resolvedPrice,\n };\n req.payment = paymentInfo;\n req.x402Payer = result.payer;\n req.x402Paid = true;\n req.x402Transaction = result.transaction;\n req.x402Network = network;\n\n // Set x402 v2 PAYMENT-RESPONSE header (base64 JSON)\n const paymentResponse = {\n x402Version: 2,\n scheme: 'exact',\n network: caip2,\n transaction: result.transaction,\n payer: result.payer,\n amount,\n asset,\n };\n res.setHeader(\n 'PAYMENT-RESPONSE',\n Buffer.from(JSON.stringify(paymentResponse)).toString('base64'),\n );\n\n options.onPaymentSettled?.(req, result);\n\n // Custom validation after payment\n if (options.customRules) {\n const valid = await options.customRules(req);\n if (!valid) {\n return res.status(403).json({ error: 'Custom validation failed' });\n }\n }\n\n next();\n } catch (error) {\n options.onError?.(req, error);\n console.error('[Relai] Protection error:', error);\n res.status(500).json({ error: 'Internal server error' });\n }\n };\n }\n}\n\nexport default Relai;\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** RelAI Facilitator URL */
|
|
2
2
|
declare const RELAI_FACILITATOR_URL = "https://facilitator.x402.fi";
|
|
3
3
|
/** All networks supported by RelAI facilitator */
|
|
4
|
-
type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base';
|
|
4
|
+
type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base' | 'skale-bite' | 'polygon' | 'ethereum';
|
|
5
5
|
/** CAIP-2 network identifiers */
|
|
6
6
|
declare const NETWORK_CAIP2: Record<RelaiNetwork, string>;
|
|
7
7
|
/** Reverse lookup: CAIP-2 → simple network name */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** RelAI Facilitator URL */
|
|
2
2
|
declare const RELAI_FACILITATOR_URL = "https://facilitator.x402.fi";
|
|
3
3
|
/** All networks supported by RelAI facilitator */
|
|
4
|
-
type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base';
|
|
4
|
+
type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base' | 'skale-bite' | 'polygon' | 'ethereum';
|
|
5
5
|
/** CAIP-2 network identifiers */
|
|
6
6
|
declare const NETWORK_CAIP2: Record<RelaiNetwork, string>;
|
|
7
7
|
/** Reverse lookup: CAIP-2 → simple network name */
|
package/dist/utils/index.cjs
CHANGED
|
@@ -158,7 +158,7 @@ function isSolanaNetwork(network) {
|
|
|
158
158
|
return network === "solana" || network === "solana-devnet" || network.startsWith("solana:");
|
|
159
159
|
}
|
|
160
160
|
function isEvmNetwork(network) {
|
|
161
|
-
const evmNetworks = ["base", "base-sepolia", "ethereum", "polygon", "avalanche", "skale-base", "peaq", "sei"];
|
|
161
|
+
const evmNetworks = ["base", "base-sepolia", "ethereum", "polygon", "avalanche", "skale-base", "skale-bite", "peaq", "sei"];
|
|
162
162
|
return evmNetworks.includes(network) || network.startsWith("eip155:");
|
|
163
163
|
}
|
|
164
164
|
function toAtomicUnits(usd, decimals = 6) {
|