@axonfi/sdk 0.3.7 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3030,14 +3030,371 @@ function generateUuid() {
3030
3030
  const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
3031
3031
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
3032
3032
  }
3033
+
3034
+ // src/x402.ts
3035
+ function parsePaymentRequired(headerValue) {
3036
+ let decoded;
3037
+ try {
3038
+ decoded = atob(headerValue);
3039
+ } catch {
3040
+ decoded = headerValue;
3041
+ }
3042
+ const parsed = JSON.parse(decoded);
3043
+ if (!parsed.accepts || !Array.isArray(parsed.accepts) || parsed.accepts.length === 0) {
3044
+ throw new Error("x402: no payment options in PAYMENT-REQUIRED header");
3045
+ }
3046
+ if (!parsed.resource) {
3047
+ throw new Error("x402: missing resource in PAYMENT-REQUIRED header");
3048
+ }
3049
+ return parsed;
3050
+ }
3051
+ function parseChainId(network) {
3052
+ const parts = network.split(":");
3053
+ if (parts.length !== 2 || parts[0] !== "eip155") {
3054
+ throw new Error(`x402: unsupported network format "${network}" (expected "eip155:<chainId>")`);
3055
+ }
3056
+ const chainId = parseInt(parts[1], 10);
3057
+ if (isNaN(chainId)) {
3058
+ throw new Error(`x402: invalid chain ID in network "${network}"`);
3059
+ }
3060
+ return chainId;
3061
+ }
3062
+ function findMatchingOption(accepts, chainId) {
3063
+ const matchingOptions = [];
3064
+ for (const option of accepts) {
3065
+ try {
3066
+ const optionChainId = parseChainId(option.network);
3067
+ if (optionChainId === chainId) {
3068
+ matchingOptions.push(option);
3069
+ }
3070
+ } catch {
3071
+ continue;
3072
+ }
3073
+ }
3074
+ if (matchingOptions.length === 0) return null;
3075
+ const usdcAddress = USDC[chainId]?.toLowerCase();
3076
+ if (usdcAddress) {
3077
+ const usdcOption = matchingOptions.find((opt) => opt.asset.toLowerCase() === usdcAddress);
3078
+ if (usdcOption) return usdcOption;
3079
+ }
3080
+ return matchingOptions[0] ?? null;
3081
+ }
3082
+ function extractX402Metadata(parsed, selectedOption) {
3083
+ const metadata = {};
3084
+ if (parsed.x402Version !== void 0) {
3085
+ metadata.x402_version = String(parsed.x402Version);
3086
+ }
3087
+ if (selectedOption.scheme) {
3088
+ metadata.x402_scheme = selectedOption.scheme;
3089
+ }
3090
+ if (parsed.resource.mimeType) {
3091
+ metadata.x402_mime_type = parsed.resource.mimeType;
3092
+ }
3093
+ if (selectedOption.payTo) {
3094
+ metadata.x402_merchant = selectedOption.payTo;
3095
+ }
3096
+ if (parsed.resource.description) {
3097
+ metadata.x402_resource_description = parsed.resource.description;
3098
+ }
3099
+ return {
3100
+ resourceUrl: parsed.resource.url,
3101
+ memo: parsed.resource.description ?? null,
3102
+ recipientLabel: selectedOption.payTo ? `${selectedOption.payTo.slice(0, 6)}...${selectedOption.payTo.slice(-4)}` : null,
3103
+ metadata
3104
+ };
3105
+ }
3106
+ function formatPaymentSignature(payload) {
3107
+ const json = JSON.stringify(payload);
3108
+ return btoa(json);
3109
+ }
3110
+ var USDC_EIP712_DOMAIN = {
3111
+ // Base mainnet
3112
+ 8453: { name: "USD Coin", version: "2" },
3113
+ // Base Sepolia
3114
+ 84532: { name: "USDC", version: "2" },
3115
+ // Arbitrum One
3116
+ 42161: { name: "USD Coin", version: "2" },
3117
+ // Arbitrum Sepolia (same as mainnet convention)
3118
+ 421614: { name: "USDC", version: "2" }
3119
+ };
3120
+ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
3121
+ TransferWithAuthorization: [
3122
+ { name: "from", type: "address" },
3123
+ { name: "to", type: "address" },
3124
+ { name: "value", type: "uint256" },
3125
+ { name: "validAfter", type: "uint256" },
3126
+ { name: "validBefore", type: "uint256" },
3127
+ { name: "nonce", type: "bytes32" }
3128
+ ]
3129
+ };
3130
+ function randomNonce() {
3131
+ const bytes = new Uint8Array(32);
3132
+ crypto.getRandomValues(bytes);
3133
+ return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
3134
+ }
3135
+ async function signTransferWithAuthorization(privateKey, chainId, auth) {
3136
+ const domainConfig = USDC_EIP712_DOMAIN[chainId];
3137
+ if (!domainConfig) {
3138
+ throw new Error(`EIP-3009 not configured for chain ${chainId}`);
3139
+ }
3140
+ const usdcAddress = USDC[chainId];
3141
+ if (!usdcAddress) {
3142
+ throw new Error(`USDC address not known for chain ${chainId}`);
3143
+ }
3144
+ const account = privateKeyToAccount(privateKey);
3145
+ return account.signTypedData({
3146
+ domain: {
3147
+ name: domainConfig.name,
3148
+ version: domainConfig.version,
3149
+ chainId,
3150
+ verifyingContract: usdcAddress
3151
+ },
3152
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
3153
+ primaryType: "TransferWithAuthorization",
3154
+ message: {
3155
+ from: auth.from,
3156
+ to: auth.to,
3157
+ value: auth.value,
3158
+ validAfter: auth.validAfter,
3159
+ validBefore: auth.validBefore,
3160
+ nonce: auth.nonce
3161
+ }
3162
+ });
3163
+ }
3164
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
3165
+ var X402_PROXY_ADDRESS = "0x4020CD856C882D5fb903D99CE35316A085Bb0001";
3166
+ var WITNESS_TYPE_STRING = "TransferDetails witness)TokenPermissions(address token,uint256 amount)TransferDetails(address to,uint256 requestedAmount)";
3167
+ var PERMIT_WITNESS_TRANSFER_FROM_TYPES = {
3168
+ PermitWitnessTransferFrom: [
3169
+ { name: "permitted", type: "TokenPermissions" },
3170
+ { name: "spender", type: "address" },
3171
+ { name: "nonce", type: "uint256" },
3172
+ { name: "deadline", type: "uint256" },
3173
+ { name: "witness", type: "TransferDetails" }
3174
+ ],
3175
+ TokenPermissions: [
3176
+ { name: "token", type: "address" },
3177
+ { name: "amount", type: "uint256" }
3178
+ ],
3179
+ TransferDetails: [
3180
+ { name: "to", type: "address" },
3181
+ { name: "requestedAmount", type: "uint256" }
3182
+ ]
3183
+ };
3184
+ function randomPermit2Nonce() {
3185
+ const bytes = new Uint8Array(32);
3186
+ crypto.getRandomValues(bytes);
3187
+ let n = 0n;
3188
+ for (const b of bytes) {
3189
+ n = n << 8n | BigInt(b);
3190
+ }
3191
+ return n;
3192
+ }
3193
+ async function signPermit2WitnessTransfer(privateKey, chainId, permit) {
3194
+ const account = privateKeyToAccount(privateKey);
3195
+ return account.signTypedData({
3196
+ domain: {
3197
+ name: "Permit2",
3198
+ chainId,
3199
+ verifyingContract: PERMIT2_ADDRESS
3200
+ },
3201
+ types: PERMIT_WITNESS_TRANSFER_FROM_TYPES,
3202
+ primaryType: "PermitWitnessTransferFrom",
3203
+ message: {
3204
+ permitted: {
3205
+ token: permit.token,
3206
+ amount: permit.amount
3207
+ },
3208
+ spender: permit.spender,
3209
+ nonce: permit.nonce,
3210
+ deadline: permit.deadline,
3211
+ witness: {
3212
+ to: permit.witnessTo,
3213
+ requestedAmount: permit.witnessRequestedAmount
3214
+ }
3215
+ }
3216
+ });
3217
+ }
3218
+
3219
+ // src/client.ts
3033
3220
  var AxonClient = class {
3034
3221
  constructor(config) {
3222
+ // ============================================================================
3223
+ // x402 — HTTP 402 Payment Required
3224
+ // ============================================================================
3225
+ /**
3226
+ * x402 utilities for handling HTTP 402 Payment Required responses.
3227
+ *
3228
+ * The x402 flow:
3229
+ * 1. Bot hits an API that returns HTTP 402 + PAYMENT-REQUIRED header
3230
+ * 2. SDK parses the header, finds a matching payment option
3231
+ * 3. SDK funds the bot's EOA from the vault (full Axon pipeline applies)
3232
+ * 4. Bot signs an EIP-3009 or Permit2 authorization
3233
+ * 5. SDK returns a PAYMENT-SIGNATURE header for the bot to retry with
3234
+ *
3235
+ * @example
3236
+ * ```ts
3237
+ * const response = await fetch('https://api.example.com/data');
3238
+ * if (response.status === 402) {
3239
+ * const result = await client.x402.handlePaymentRequired(response.headers);
3240
+ * const data = await fetch('https://api.example.com/data', {
3241
+ * headers: { 'PAYMENT-SIGNATURE': result.paymentSignature },
3242
+ * });
3243
+ * }
3244
+ * ```
3245
+ */
3246
+ this.x402 = {
3247
+ /**
3248
+ * Fund the bot's EOA from the vault for x402 settlement.
3249
+ *
3250
+ * This is a regular Axon payment (to = bot's own address) that goes through
3251
+ * the full pipeline: policy engine, AI scan, human review if needed.
3252
+ *
3253
+ * @param amount - Amount in token base units
3254
+ * @param token - Token address (defaults to USDC on this chain)
3255
+ * @param metadata - Optional metadata for the payment record
3256
+ */
3257
+ fund: async (amount, token, metadata) => {
3258
+ const tokenAddress = token ?? USDC[this.chainId];
3259
+ if (!tokenAddress) {
3260
+ throw new Error(`No default USDC address for chain ${this.chainId}`);
3261
+ }
3262
+ return this.pay({
3263
+ to: this.botAddress,
3264
+ token: tokenAddress,
3265
+ amount,
3266
+ x402Funding: true,
3267
+ ...metadata
3268
+ });
3269
+ },
3270
+ /**
3271
+ * Handle a full x402 flow: parse header, fund bot, sign authorization, return header.
3272
+ *
3273
+ * Supports both EIP-3009 (USDC) and Permit2 (any ERC-20) settlement.
3274
+ * The bot's EOA is funded from the vault first (full Axon pipeline applies).
3275
+ *
3276
+ * @param headers - Response headers from the 402 response (must contain PAYMENT-REQUIRED)
3277
+ * @param maxTimeoutMs - Maximum time to wait for pending_review resolution (default: 120s)
3278
+ * @param pollIntervalMs - Polling interval for pending_review (default: 5s)
3279
+ * @returns Payment signature header value + funding details
3280
+ */
3281
+ handlePaymentRequired: async (headers, maxTimeoutMs = 12e4, pollIntervalMs = 5e3) => {
3282
+ const headerValue = headers instanceof Headers ? headers.get("payment-required") ?? headers.get("PAYMENT-REQUIRED") : headers["payment-required"] ?? headers["PAYMENT-REQUIRED"];
3283
+ if (!headerValue) {
3284
+ throw new Error("x402: no PAYMENT-REQUIRED header found");
3285
+ }
3286
+ const parsed = parsePaymentRequired(headerValue);
3287
+ const option = findMatchingOption(parsed.accepts, this.chainId);
3288
+ if (!option) {
3289
+ throw new Error(
3290
+ `x402: no payment option matches chain ${this.chainId}. Available: ${parsed.accepts.map((a) => a.network).join(", ")}`
3291
+ );
3292
+ }
3293
+ const x402Meta = extractX402Metadata(parsed, option);
3294
+ const amount = BigInt(option.amount);
3295
+ const tokenAddress = option.asset;
3296
+ const payInput = {
3297
+ to: this.botAddress,
3298
+ token: tokenAddress,
3299
+ amount,
3300
+ x402Funding: true,
3301
+ resourceUrl: x402Meta.resourceUrl,
3302
+ metadata: x402Meta.metadata
3303
+ };
3304
+ if (x402Meta.memo) payInput.memo = x402Meta.memo;
3305
+ if (x402Meta.recipientLabel) payInput.recipientLabel = x402Meta.recipientLabel;
3306
+ let fundingResult = await this.pay(payInput);
3307
+ if (fundingResult.status === "pending_review") {
3308
+ const deadline = Date.now() + maxTimeoutMs;
3309
+ while (fundingResult.status === "pending_review" && Date.now() < deadline) {
3310
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
3311
+ fundingResult = await this.poll(fundingResult.requestId);
3312
+ }
3313
+ if (fundingResult.status === "pending_review") {
3314
+ throw new Error(`x402: funding timed out after ${maxTimeoutMs}ms (still pending_review)`);
3315
+ }
3316
+ }
3317
+ if (fundingResult.status === "rejected") {
3318
+ throw new Error(`x402: funding rejected \u2014 ${fundingResult.reason ?? "unknown reason"}`);
3319
+ }
3320
+ const botPrivateKey = this.botPrivateKey;
3321
+ const payTo = option.payTo;
3322
+ const usdcAddress = USDC[this.chainId]?.toLowerCase();
3323
+ const isUsdc = tokenAddress.toLowerCase() === usdcAddress;
3324
+ let signaturePayload;
3325
+ if (isUsdc && USDC_EIP712_DOMAIN[this.chainId]) {
3326
+ const nonce = randomNonce();
3327
+ const validAfter = 0n;
3328
+ const validBefore = BigInt(Math.floor(Date.now() / 1e3) + 300);
3329
+ const sig = await signTransferWithAuthorization(botPrivateKey, this.chainId, {
3330
+ from: this.botAddress,
3331
+ to: payTo,
3332
+ value: amount,
3333
+ validAfter,
3334
+ validBefore,
3335
+ nonce
3336
+ });
3337
+ signaturePayload = {
3338
+ scheme: "exact",
3339
+ signature: sig,
3340
+ authorization: {
3341
+ from: this.botAddress,
3342
+ to: payTo,
3343
+ value: amount.toString(),
3344
+ validAfter: validAfter.toString(),
3345
+ validBefore: validBefore.toString(),
3346
+ nonce
3347
+ }
3348
+ };
3349
+ } else {
3350
+ const nonce = randomPermit2Nonce();
3351
+ const deadline = BigInt(Math.floor(Date.now() / 1e3) + 300);
3352
+ const sig = await signPermit2WitnessTransfer(botPrivateKey, this.chainId, {
3353
+ token: tokenAddress,
3354
+ amount,
3355
+ spender: X402_PROXY_ADDRESS,
3356
+ nonce,
3357
+ deadline,
3358
+ witnessTo: payTo,
3359
+ witnessRequestedAmount: amount
3360
+ });
3361
+ signaturePayload = {
3362
+ scheme: "permit2",
3363
+ signature: sig,
3364
+ permit: {
3365
+ permitted: { token: tokenAddress, amount: amount.toString() },
3366
+ spender: X402_PROXY_ADDRESS,
3367
+ nonce: nonce.toString(),
3368
+ deadline: deadline.toString()
3369
+ },
3370
+ witness: {
3371
+ to: payTo,
3372
+ requestedAmount: amount.toString()
3373
+ }
3374
+ };
3375
+ }
3376
+ const paymentSignature = formatPaymentSignature(signaturePayload);
3377
+ const handleResult = {
3378
+ paymentSignature,
3379
+ selectedOption: option,
3380
+ fundingResult: {
3381
+ requestId: fundingResult.requestId,
3382
+ status: fundingResult.status
3383
+ }
3384
+ };
3385
+ if (fundingResult.txHash) {
3386
+ handleResult.fundingResult.txHash = fundingResult.txHash;
3387
+ }
3388
+ return handleResult;
3389
+ }
3390
+ };
3035
3391
  this.vaultAddress = config.vaultAddress;
3036
3392
  this.chainId = config.chainId;
3037
- this.relayerUrl = "https://relay.axonfi.xyz";
3393
+ this.relayerUrl = config.relayerUrl ?? "https://relay.axonfi.xyz";
3038
3394
  if (!config.botPrivateKey) {
3039
3395
  throw new Error("botPrivateKey is required in AxonClientConfig");
3040
3396
  }
3397
+ this.botPrivateKey = config.botPrivateKey;
3041
3398
  this.walletClient = createAxonWalletClient(config.botPrivateKey, config.chainId);
3042
3399
  }
3043
3400
  // ============================================================================
@@ -3340,7 +3697,8 @@ Timestamp: ${timestamp}`;
3340
3697
  ...input.invoiceId !== void 0 && { invoiceId: input.invoiceId },
3341
3698
  ...input.orderId !== void 0 && { orderId: input.orderId },
3342
3699
  ...input.recipientLabel !== void 0 && { recipientLabel: input.recipientLabel },
3343
- ...input.metadata !== void 0 && { metadata: input.metadata }
3700
+ ...input.metadata !== void 0 && { metadata: input.metadata },
3701
+ ...input.x402Funding !== void 0 && { x402Funding: input.x402Funding }
3344
3702
  };
3345
3703
  return this._post(RELAYER_API.PAYMENTS, idempotencyKey, body);
3346
3704
  }
@@ -3899,6 +4257,6 @@ var AxonRegistryAbi = [
3899
4257
  }
3900
4258
  ];
3901
4259
 
3902
- export { AxonClient, AxonRegistryAbi, AxonVaultAbi, AxonVaultFactoryAbi, CHAIN_NAMES, Chain, DEFAULT_DEADLINE_SECONDS, EIP712_DOMAIN_NAME, EIP712_DOMAIN_VERSION, EXECUTE_INTENT_TYPEHASH, EXPLORER_ADDR, EXPLORER_TX, KNOWN_TOKENS, NATIVE_ETH, PAYMENT_INTENT_TYPEHASH, PaymentErrorCode, RELAYER_API, SUPPORTED_CHAIN_IDS, SWAP_INTENT_TYPEHASH, Token, USDC, WINDOW, createAxonPublicClient, createAxonWalletClient, decryptKeystore, deployVault, encodeRef, encryptKeystore, getBotConfig, getChain, getDomainSeparator, getKnownTokensForChain, getOperatorCeilings, getRebalanceTokenCount, getTokenSymbolByAddress, getVaultOperator, getVaultOwner, getVaultVersion, isBotActive, isDestinationAllowed, isRebalanceTokenWhitelisted, isVaultPaused, operatorMaxDrainPerDay, parseAmount, resolveToken, resolveTokenDecimals, signExecuteIntent, signPayment, signSwapIntent };
4260
+ export { AxonClient, AxonRegistryAbi, AxonVaultAbi, AxonVaultFactoryAbi, CHAIN_NAMES, Chain, DEFAULT_DEADLINE_SECONDS, EIP712_DOMAIN_NAME, EIP712_DOMAIN_VERSION, EXECUTE_INTENT_TYPEHASH, EXPLORER_ADDR, EXPLORER_TX, KNOWN_TOKENS, NATIVE_ETH, PAYMENT_INTENT_TYPEHASH, PERMIT2_ADDRESS, PaymentErrorCode, RELAYER_API, SUPPORTED_CHAIN_IDS, SWAP_INTENT_TYPEHASH, Token, USDC, USDC_EIP712_DOMAIN, WINDOW, WITNESS_TYPE_STRING, X402_PROXY_ADDRESS, createAxonPublicClient, createAxonWalletClient, decryptKeystore, deployVault, encodeRef, encryptKeystore, extractX402Metadata, findMatchingOption, formatPaymentSignature, getBotConfig, getChain, getDomainSeparator, getKnownTokensForChain, getOperatorCeilings, getRebalanceTokenCount, getTokenSymbolByAddress, getVaultOperator, getVaultOwner, getVaultVersion, isBotActive, isDestinationAllowed, isRebalanceTokenWhitelisted, isVaultPaused, operatorMaxDrainPerDay, parseAmount, parseChainId, parsePaymentRequired, randomNonce, randomPermit2Nonce, resolveToken, resolveTokenDecimals, signExecuteIntent, signPayment, signPermit2WitnessTransfer, signSwapIntent, signTransferWithAuthorization };
3903
4261
  //# sourceMappingURL=index.js.map
3904
4262
  //# sourceMappingURL=index.js.map