moltspay 1.0.0 → 1.2.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/cli/index.js +192 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +201 -16
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +7 -2
- package/dist/client/index.d.ts +7 -2
- package/dist/client/index.js +13 -4
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +13 -4
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/client/index.d.mts
CHANGED
|
@@ -36,15 +36,20 @@ interface ServiceInfo {
|
|
|
36
36
|
input: Record<string, any>;
|
|
37
37
|
output: Record<string, any>;
|
|
38
38
|
available: boolean;
|
|
39
|
+
provider?: ProviderInfo;
|
|
39
40
|
}
|
|
40
41
|
interface ProviderInfo {
|
|
41
42
|
name: string;
|
|
43
|
+
username?: string;
|
|
42
44
|
description?: string;
|
|
43
45
|
wallet: string;
|
|
44
|
-
chain
|
|
46
|
+
chain?: string;
|
|
47
|
+
chains?: string[] | {
|
|
48
|
+
chain: string;
|
|
49
|
+
}[];
|
|
45
50
|
}
|
|
46
51
|
interface ServicesResponse {
|
|
47
|
-
provider
|
|
52
|
+
provider?: ProviderInfo;
|
|
48
53
|
services: ServiceInfo[];
|
|
49
54
|
}
|
|
50
55
|
interface VerifyResponse {
|
package/dist/client/index.d.ts
CHANGED
|
@@ -36,15 +36,20 @@ interface ServiceInfo {
|
|
|
36
36
|
input: Record<string, any>;
|
|
37
37
|
output: Record<string, any>;
|
|
38
38
|
available: boolean;
|
|
39
|
+
provider?: ProviderInfo;
|
|
39
40
|
}
|
|
40
41
|
interface ProviderInfo {
|
|
41
42
|
name: string;
|
|
43
|
+
username?: string;
|
|
42
44
|
description?: string;
|
|
43
45
|
wallet: string;
|
|
44
|
-
chain
|
|
46
|
+
chain?: string;
|
|
47
|
+
chains?: string[] | {
|
|
48
|
+
chain: string;
|
|
49
|
+
}[];
|
|
45
50
|
}
|
|
46
51
|
interface ServicesResponse {
|
|
47
|
-
provider
|
|
52
|
+
provider?: ProviderInfo;
|
|
48
53
|
services: ServiceInfo[];
|
|
49
54
|
}
|
|
50
55
|
interface VerifyResponse {
|
package/dist/client/index.js
CHANGED
|
@@ -210,11 +210,20 @@ var MoltsPayClient = class {
|
|
|
210
210
|
* Get services from a provider
|
|
211
211
|
*/
|
|
212
212
|
async getServices(serverUrl) {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
const normalizedUrl = serverUrl.replace(/\/(services|api\/services|registry\/services)\/?$/, "");
|
|
214
|
+
const endpoints = ["/services", "/api/services", "/registry/services"];
|
|
215
|
+
for (const endpoint of endpoints) {
|
|
216
|
+
try {
|
|
217
|
+
const res = await fetch(`${normalizedUrl}${endpoint}`);
|
|
218
|
+
if (!res.ok) continue;
|
|
219
|
+
const contentType = res.headers.get("content-type") || "";
|
|
220
|
+
if (!contentType.includes("application/json")) continue;
|
|
221
|
+
return await res.json();
|
|
222
|
+
} catch {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
216
225
|
}
|
|
217
|
-
|
|
226
|
+
throw new Error(`Failed to get services: no valid endpoint found at ${normalizedUrl}`);
|
|
218
227
|
}
|
|
219
228
|
/**
|
|
220
229
|
* Pay for a service and get the result (x402 protocol)
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/index.ts","../../src/chains/index.ts"],"sourcesContent":["/**\n * MoltsPay Client - Pay for AI Agent services\n * \n * Uses x402 protocol for gasless, pay-for-success payments.\n * \n * Usage:\n * const client = new MoltsPayClient(); // Loads from ~/.moltspay/\n * const services = await client.getServices('http://provider:3000');\n * const result = await client.pay('http://provider:3000', 'text-to-video', { prompt: '...' });\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, chmodSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Wallet, ethers } from 'ethers';\nimport { getChain, type ChainName, type TokenSymbol } from '../chains/index.js';\nimport {\n ClientConfig,\n WalletData,\n ServicesResponse,\n MoltsPayClientOptions,\n} from './types.js';\n\nexport * from './types.js';\n\nexport interface PayOptions {\n /** Token to pay with (default: USDC, or auto-select based on balance) */\n token?: TokenSymbol;\n /** Auto-select token based on balance (default: false) */\n autoSelect?: boolean;\n /** Chain to pay on (base or polygon, default: base) */\n chain?: 'base' | 'polygon';\n}\n\n// x402 constants\nconst X402_VERSION = 2;\nconst PAYMENT_REQUIRED_HEADER = 'x-payment-required';\nconst PAYMENT_HEADER = 'x-payment';\n\ninterface X402PaymentRequirements {\n scheme: string;\n network: string;\n // v2 fields\n amount?: string;\n asset?: string;\n payTo?: string;\n maxTimeoutSeconds?: number;\n extra?: Record<string, unknown>;\n // v1 fields (legacy)\n maxAmountRequired?: string;\n resource?: string;\n description?: string;\n}\n\ninterface EIP3009Authorization {\n from: string;\n to: string;\n value: string;\n validAfter: string;\n validBefore: string;\n nonce: string;\n}\n\nconst DEFAULT_CONFIG: ClientConfig = {\n chain: 'base',\n limits: {\n maxPerTx: 100,\n maxPerDay: 1000,\n },\n};\n\nexport class MoltsPayClient {\n private configDir: string;\n private config: ClientConfig;\n private walletData: WalletData | null = null;\n private wallet: Wallet | null = null;\n private todaySpending: number = 0;\n private lastSpendingReset: number = 0;\n\n constructor(options: MoltsPayClientOptions = {}) {\n this.configDir = options.configDir || join(homedir(), '.moltspay');\n this.config = this.loadConfig();\n this.walletData = this.loadWallet();\n this.loadSpending(); // Load persisted spending data\n \n if (this.walletData) {\n this.wallet = new Wallet(this.walletData.privateKey);\n }\n }\n\n /**\n * Check if client is initialized (has wallet)\n */\n get isInitialized(): boolean {\n return this.wallet !== null;\n }\n\n /**\n * Get wallet address\n */\n get address(): string | null {\n return this.wallet?.address || null;\n }\n\n /**\n * Get current config\n */\n getConfig(): ClientConfig {\n return { ...this.config };\n }\n\n /**\n * Update config\n */\n updateConfig(updates: Partial<ClientConfig['limits']>): void {\n if (updates.maxPerTx !== undefined) {\n this.config.limits.maxPerTx = updates.maxPerTx;\n }\n if (updates.maxPerDay !== undefined) {\n this.config.limits.maxPerDay = updates.maxPerDay;\n }\n this.saveConfig();\n }\n\n /**\n * Get services from a provider\n */\n async getServices(serverUrl: string): Promise<ServicesResponse> {\n const res = await fetch(`${serverUrl}/services`);\n if (!res.ok) {\n throw new Error(`Failed to get services: ${res.statusText}`);\n }\n return res.json() as Promise<ServicesResponse>;\n }\n\n /**\n * Pay for a service and get the result (x402 protocol)\n * \n * This is GASLESS for the client - server pays gas to claim payment.\n * This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.\n * \n * @param serverUrl - Server URL\n * @param service - Service ID\n * @param params - Service parameters\n * @param options - Payment options (token selection)\n */\n async pay(\n serverUrl: string,\n service: string,\n params: Record<string, any>,\n options: PayOptions = {}\n ): Promise<Record<string, any>> {\n if (!this.wallet || !this.walletData) {\n throw new Error('Client not initialized. Run: npx moltspay init');\n }\n\n // Step 1: Make initial request without payment\n console.log(`[MoltsPay] Requesting service: ${service}`);\n const requestBody: any = { service, params };\n if (options.chain) {\n requestBody.chain = options.chain;\n }\n const initialRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(requestBody),\n });\n\n // If not 402, check for success or error\n if (initialRes.status !== 402) {\n const data = await initialRes.json() as any;\n if (initialRes.ok && data.result) {\n return data.result;\n }\n throw new Error(data.error || 'Unexpected response');\n }\n\n // Step 2: Parse payment requirements from 402 response\n const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);\n if (!paymentRequiredHeader) {\n throw new Error('Missing x-payment-required header');\n }\n\n let requirements: X402PaymentRequirements[];\n try {\n const decoded = Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8');\n const parsed = JSON.parse(decoded);\n \n // Handle both v1 (array) and v2 (object with accepts) formats\n if (Array.isArray(parsed)) {\n // v1 format: direct array of requirements\n requirements = parsed;\n } else if (parsed.accepts && Array.isArray(parsed.accepts)) {\n // v2 format: { x402Version: 2, accepts: [...] }\n requirements = parsed.accepts;\n } else {\n // Single requirement object\n requirements = [parsed];\n }\n } catch {\n throw new Error('Invalid x-payment-required header');\n }\n\n // Helper to convert network ID to chain name\n const networkToChainName = (network: string): string | null => {\n const match = network.match(/^eip155:(\\d+)$/);\n if (!match) return null;\n const chainId = parseInt(match[1]);\n if (chainId === 8453) return 'base';\n if (chainId === 137) return 'polygon';\n if (chainId === 84532) return 'base_sepolia';\n return null;\n };\n\n // Get server's accepted chains\n const serverChains = requirements\n .map(r => networkToChainName(r.network))\n .filter((c): c is string => c !== null);\n\n // Determine which chain to use\n let chainName: ChainName;\n const userSpecifiedChain = options.chain;\n\n if (userSpecifiedChain) {\n // User specified --chain, validate it's accepted by server\n if (!serverChains.includes(userSpecifiedChain)) {\n throw new Error(\n `Server doesn't accept '${userSpecifiedChain}'.\\n` +\n `Server accepts: ${serverChains.join(', ')}`\n );\n }\n chainName = userSpecifiedChain as ChainName;\n } else {\n // No --chain provided\n if (serverChains.length === 1 && serverChains[0] === 'base') {\n // Only default to base if server ONLY accepts base\n chainName = 'base';\n } else {\n throw new Error(\n `Server accepts: ${serverChains.join(', ')}\\n` +\n `Please specify: --chain base or --chain polygon`\n );\n }\n }\n\n const chain = getChain(chainName);\n const network = `eip155:${chain.chainId}`;\n const req = requirements.find(r => r.scheme === 'exact' && r.network === network);\n\n if (!req) {\n throw new Error(`Failed to find payment requirement for ${chainName}`);\n }\n\n // Step 3: Check limits\n // v2 uses 'amount', v1 uses 'maxAmountRequired'\n const amountRaw = req.amount || req.maxAmountRequired;\n if (!amountRaw) {\n throw new Error('Missing amount in payment requirements');\n }\n const amount = Number(amountRaw) / 1e6;\n this.checkLimits(amount);\n\n // Determine which token to use\n let token: TokenSymbol = options.token || 'USDC';\n \n // Auto-select token based on balance if requested\n if (options.autoSelect) {\n const balances = await this.getBalance();\n if (balances.usdc >= amount) {\n token = 'USDC';\n } else if (balances.usdt >= amount) {\n token = 'USDT';\n } else {\n throw new Error(`Insufficient balance: need $${amount}, have ${balances.usdc} USDC / ${balances.usdt} USDT`);\n }\n }\n\n // USDT does not support gasless transfers (no EIP-2612 permit)\n // It requires on-chain approve + transfer, meaning the user pays gas\n if (token === 'USDT') {\n const balances = await this.getBalance();\n if (balances.native < 0.0001) {\n throw new Error(\n `USDT requires ETH for gas (~$0.01 on Base). ` +\n `Your ETH balance: ${balances.native.toFixed(6)} ETH. ` +\n `Please add a small amount of ETH to your wallet, or use USDC (gasless).`\n );\n }\n console.log(`[MoltsPay] ⚠️ USDT requires gas (~$0.01). Proceeding with payment...`);\n } else {\n console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);\n }\n\n // Step 4: Sign EIP-3009 authorization (GASLESS - just signing)\n // payTo is the recipient address (v2 format)\n const payTo = req.payTo || req.resource; // fallback for v1 compatibility\n if (!payTo) {\n throw new Error('Missing payTo address in payment requirements');\n }\n const authorization = await this.signEIP3009(payTo, amount, chain, token);\n\n // Get token-specific info\n const tokenConfig = chain.tokens[token];\n const tokenName = token === 'USDC' ? 'USD Coin' : 'Tether USD';\n\n // Step 5: Create x402 payment payload (v2 format requires 'accepted')\n const payload = {\n x402Version: X402_VERSION,\n payload: authorization,\n // v2 requires 'accepted' field with the requirements being fulfilled\n accepted: {\n scheme: 'exact',\n network,\n asset: tokenConfig.address,\n amount: amountRaw,\n payTo,\n maxTimeoutSeconds: req.maxTimeoutSeconds || 300,\n extra: req.extra || { name: tokenName, version: '2' },\n },\n };\n const paymentHeader = Buffer.from(JSON.stringify(payload)).toString('base64');\n\n // Step 6: Retry with payment header\n console.log(`[MoltsPay] Sending request with payment...`);\n const paidRequestBody: any = { service, params };\n if (options.chain) {\n paidRequestBody.chain = options.chain;\n }\n const paidRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [PAYMENT_HEADER]: paymentHeader,\n },\n body: JSON.stringify(paidRequestBody),\n });\n\n const result = await paidRes.json() as any;\n\n if (!paidRes.ok) {\n throw new Error(result.error || 'Service execution failed');\n }\n\n // Update spending tracking\n this.recordSpending(amount);\n\n console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || 'claimed'}`);\n \n return result.result;\n }\n\n /**\n * Sign EIP-3009 transferWithAuthorization (GASLESS)\n * This only signs - no on-chain transaction, no gas needed.\n * Supports both USDC and USDT.\n */\n private async signEIP3009(\n to: string,\n amount: number,\n chain: { chainId: number; tokens: Record<TokenSymbol, { address: string; decimals: number }> },\n token: TokenSymbol = 'USDC'\n ): Promise<{ authorization: EIP3009Authorization; signature: string }> {\n const validAfter = 0;\n const validBefore = Math.floor(Date.now() / 1000) + 3600; // 1 hour\n const nonce = ethers.hexlify(ethers.randomBytes(32));\n \n const tokenConfig = chain.tokens[token];\n const value = BigInt(Math.floor(amount * (10 ** tokenConfig.decimals))).toString();\n\n const authorization: EIP3009Authorization = {\n from: this.wallet!.address,\n to,\n value,\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n };\n\n // EIP-712 domain - token specific\n const tokenName = token === 'USDC' ? 'USD Coin' : 'Tether USD';\n const domain = {\n name: tokenName,\n version: '2',\n chainId: chain.chainId,\n verifyingContract: tokenConfig.address,\n };\n\n // EIP-3009 types\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 signature = await this.wallet!.signTypedData(domain, types, authorization);\n\n return { authorization, signature };\n }\n\n /**\n * Check spending limits\n */\n private checkLimits(amount: number): void {\n // Check per-tx limit\n if (amount > this.config.limits.maxPerTx) {\n throw new Error(\n `Amount $${amount} exceeds max per transaction ($${this.config.limits.maxPerTx})`\n );\n }\n\n // Reset daily spending if new day\n const today = new Date().setHours(0, 0, 0, 0);\n if (today > this.lastSpendingReset) {\n this.todaySpending = 0;\n this.lastSpendingReset = today;\n this.saveSpending(); // Persist reset\n }\n\n // Check daily limit\n if (this.todaySpending + amount > this.config.limits.maxPerDay) {\n throw new Error(\n `Would exceed daily limit ($${this.todaySpending} + $${amount} > $${this.config.limits.maxPerDay})`\n );\n }\n }\n\n /**\n * Record spending and persist to disk\n */\n private recordSpending(amount: number): void {\n this.todaySpending += amount;\n this.saveSpending();\n }\n\n // --- Config & Wallet Management ---\n\n private loadConfig(): ClientConfig {\n const configPath = join(this.configDir, 'config.json');\n if (existsSync(configPath)) {\n const content = readFileSync(configPath, 'utf-8');\n return { ...DEFAULT_CONFIG, ...JSON.parse(content) };\n }\n return { ...DEFAULT_CONFIG };\n }\n\n private saveConfig(): void {\n mkdirSync(this.configDir, { recursive: true });\n const configPath = join(this.configDir, 'config.json');\n writeFileSync(configPath, JSON.stringify(this.config, null, 2));\n }\n\n /**\n * Load spending data from disk\n */\n private loadSpending(): void {\n const spendingPath = join(this.configDir, 'spending.json');\n if (existsSync(spendingPath)) {\n try {\n const data = JSON.parse(readFileSync(spendingPath, 'utf-8'));\n const today = new Date().setHours(0, 0, 0, 0);\n \n // Only load if it's from today\n if (data.date && data.date === today) {\n this.todaySpending = data.amount || 0;\n this.lastSpendingReset = data.date;\n } else {\n // Data is from a previous day, reset\n this.todaySpending = 0;\n this.lastSpendingReset = today;\n }\n } catch {\n // Ignore parse errors, start fresh\n this.todaySpending = 0;\n this.lastSpendingReset = new Date().setHours(0, 0, 0, 0);\n }\n }\n }\n\n /**\n * Save spending data to disk\n */\n private saveSpending(): void {\n mkdirSync(this.configDir, { recursive: true });\n const spendingPath = join(this.configDir, 'spending.json');\n const data = {\n date: this.lastSpendingReset || new Date().setHours(0, 0, 0, 0),\n amount: this.todaySpending,\n updatedAt: Date.now(),\n };\n writeFileSync(spendingPath, JSON.stringify(data, null, 2));\n }\n\n private loadWallet(): WalletData | null {\n const walletPath = join(this.configDir, 'wallet.json');\n if (existsSync(walletPath)) {\n // Security check: warn and fix if permissions are too open\n try {\n const stats = statSync(walletPath);\n const mode = stats.mode & 0o777;\n if (mode !== 0o600) {\n console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);\n console.warn(`[MoltsPay] Fixing permissions to 0600...`);\n chmodSync(walletPath, 0o600);\n }\n } catch (err) {\n // Ignore permission check errors on Windows\n }\n \n const content = readFileSync(walletPath, 'utf-8');\n return JSON.parse(content);\n }\n return null;\n }\n\n /**\n * Initialize a new wallet (called by CLI)\n */\n static init(\n configDir: string,\n options: { chain: string; maxPerTx: number; maxPerDay: number }\n ): { address: string; configDir: string } {\n mkdirSync(configDir, { recursive: true });\n\n // Create wallet\n const wallet = Wallet.createRandom();\n const walletData: WalletData = {\n address: wallet.address,\n privateKey: wallet.privateKey,\n createdAt: Date.now(),\n };\n\n // Save wallet with secure permissions (0o600 = owner read/write only)\n const walletPath = join(configDir, 'wallet.json');\n writeFileSync(walletPath, JSON.stringify(walletData, null, 2), { mode: 0o600 });\n\n // Save config\n const config: ClientConfig = {\n chain: options.chain,\n limits: {\n maxPerTx: options.maxPerTx,\n maxPerDay: options.maxPerDay,\n },\n };\n const configPath = join(configDir, 'config.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2));\n\n return { address: wallet.address, configDir };\n }\n\n /**\n * Get wallet balance (USDC, USDT, and native token) on default chain\n */\n async getBalance(): Promise<{ usdc: number; usdt: number; native: number }> {\n if (!this.wallet) {\n throw new Error('Client not initialized');\n }\n\n let chain;\n try {\n chain = getChain(this.config.chain as ChainName);\n } catch {\n throw new Error(`Unknown chain: ${this.config.chain}`);\n }\n\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n const tokenAbi = ['function balanceOf(address) view returns (uint256)'];\n\n // Get all balances in parallel\n const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([\n provider.getBalance(this.wallet.address),\n new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),\n new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address),\n ]);\n\n return {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),\n usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\n }\n\n /**\n * Get wallet balances on all supported chains (Base + Polygon)\n */\n async getAllBalances(): Promise<Record<string, { usdc: number; usdt: number; native: number }>> {\n if (!this.wallet) {\n throw new Error('Client not initialized');\n }\n\n const supportedChains: ChainName[] = ['base', 'polygon'];\n const tokenAbi = ['function balanceOf(address) view returns (uint256)'];\n const results: Record<string, { usdc: number; usdt: number; native: number }> = {};\n\n // Query all chains in parallel\n await Promise.all(\n supportedChains.map(async (chainName) => {\n try {\n const chain = getChain(chainName);\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n\n const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([\n provider.getBalance(this.wallet!.address),\n new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet!.address),\n new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet!.address),\n ]);\n\n results[chainName] = {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),\n usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\n } catch (err) {\n // If chain query fails, show zeros\n results[chainName] = { usdc: 0, usdt: 0, native: 0 };\n }\n })\n );\n\n return results;\n }\n}\n","/**\n * Blockchain Configuration\n */\n\nimport type { ChainConfig, ChainName, TokenSymbol } from '../types/index.js';\n\nexport const CHAINS: Record<ChainName, ChainConfig> = {\n // ============ Mainnet ============\n base: {\n name: 'Base',\n chainId: 8453,\n rpc: 'https://mainnet.base.org',\n tokens: {\n USDC: {\n address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // deprecated, for backward compat\n explorer: 'https://basescan.org/address/',\n explorerTx: 'https://basescan.org/tx/',\n avgBlockTime: 2,\n },\n polygon: {\n name: 'Polygon',\n chainId: 137,\n rpc: 'https://polygon-bor-rpc.publicnode.com',\n tokens: {\n USDC: {\n address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n explorer: 'https://polygonscan.com/address/',\n explorerTx: 'https://polygonscan.com/tx/',\n avgBlockTime: 2,\n },\n ethereum: {\n name: 'Ethereum',\n chainId: 1,\n rpc: 'https://eth.llamarpc.com',\n tokens: {\n USDC: {\n address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n explorer: 'https://etherscan.io/address/',\n explorerTx: 'https://etherscan.io/tx/',\n avgBlockTime: 12,\n },\n\n // ============ Testnet ============\n base_sepolia: {\n name: 'Base Sepolia',\n chainId: 84532,\n rpc: 'https://sepolia.base.org',\n tokens: {\n USDC: {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // Same as USDC on testnet (no official USDT)\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n explorer: 'https://sepolia.basescan.org/address/',\n explorerTx: 'https://sepolia.basescan.org/tx/',\n avgBlockTime: 2,\n },\n sepolia: {\n name: 'Sepolia',\n chainId: 11155111,\n rpc: 'https://rpc.sepolia.org',\n tokens: {\n USDC: {\n address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', // Same as USDC on testnet\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',\n explorer: 'https://sepolia.etherscan.io/address/',\n explorerTx: 'https://sepolia.etherscan.io/tx/',\n avgBlockTime: 12,\n },\n};\n\n/**\n * Get token address for a chain\n */\nexport function getTokenAddress(chainName: ChainName, token: TokenSymbol): string {\n const chain = CHAINS[chainName];\n if (!chain) {\n throw new Error(`Unsupported chain: ${chainName}`);\n }\n const tokenConfig = chain.tokens[token];\n if (!tokenConfig) {\n throw new Error(`Token ${token} not supported on ${chainName}`);\n }\n return tokenConfig.address;\n}\n\n/**\n * Get token config for a chain\n */\nexport function getTokenConfig(chainName: ChainName, token: TokenSymbol) {\n const chain = CHAINS[chainName];\n if (!chain) {\n throw new Error(`Unsupported chain: ${chainName}`);\n }\n return chain.tokens[token];\n}\n\n/**\n * Get chain configuration\n */\nexport function getChain(name: ChainName): ChainConfig {\n const config = CHAINS[name];\n if (!config) {\n throw new Error(`Unsupported chain: ${name}. Supported: ${Object.keys(CHAINS).join(', ')}`);\n }\n return config;\n}\n\n/**\n * List all supported chains\n */\nexport function listChains(): ChainName[] {\n return Object.keys(CHAINS) as ChainName[];\n}\n\n/**\n * Get chain config by chainId\n */\nexport function getChainById(chainId: number): ChainConfig | undefined {\n return Object.values(CHAINS).find(c => c.chainId === chainId);\n}\n\n/**\n * ERC20 ABI (minimal, only required methods)\n */\nexport const ERC20_ABI = [\n 'function balanceOf(address owner) view returns (uint256)',\n 'function transfer(address to, uint256 amount) returns (bool)',\n 'function approve(address spender, uint256 amount) returns (bool)',\n 'function allowance(address owner, address spender) view returns (uint256)',\n 'function decimals() view returns (uint8)',\n 'function symbol() view returns (string)',\n 'function name() view returns (string)',\n 'function nonces(address owner) view returns (uint256)',\n 'function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)',\n 'event Transfer(address indexed from, address indexed to, uint256 value)',\n 'event Approval(address indexed owner, address indexed spender, uint256 value)',\n];\n\nexport type { ChainConfig, ChainName, TokenSymbol };\n"],"mappings":"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","names":["network"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts","../../src/chains/index.ts"],"sourcesContent":["/**\n * MoltsPay Client - Pay for AI Agent services\n * \n * Uses x402 protocol for gasless, pay-for-success payments.\n * \n * Usage:\n * const client = new MoltsPayClient(); // Loads from ~/.moltspay/\n * const services = await client.getServices('http://provider:3000');\n * const result = await client.pay('http://provider:3000', 'text-to-video', { prompt: '...' });\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, chmodSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Wallet, ethers } from 'ethers';\nimport { getChain, type ChainName, type TokenSymbol } from '../chains/index.js';\nimport {\n ClientConfig,\n WalletData,\n ServicesResponse,\n MoltsPayClientOptions,\n} from './types.js';\n\nexport * from './types.js';\n\nexport interface PayOptions {\n /** Token to pay with (default: USDC, or auto-select based on balance) */\n token?: TokenSymbol;\n /** Auto-select token based on balance (default: false) */\n autoSelect?: boolean;\n /** Chain to pay on (base or polygon, default: base) */\n chain?: 'base' | 'polygon';\n}\n\n// x402 constants\nconst X402_VERSION = 2;\nconst PAYMENT_REQUIRED_HEADER = 'x-payment-required';\nconst PAYMENT_HEADER = 'x-payment';\n\ninterface X402PaymentRequirements {\n scheme: string;\n network: string;\n // v2 fields\n amount?: string;\n asset?: string;\n payTo?: string;\n maxTimeoutSeconds?: number;\n extra?: Record<string, unknown>;\n // v1 fields (legacy)\n maxAmountRequired?: string;\n resource?: string;\n description?: string;\n}\n\ninterface EIP3009Authorization {\n from: string;\n to: string;\n value: string;\n validAfter: string;\n validBefore: string;\n nonce: string;\n}\n\nconst DEFAULT_CONFIG: ClientConfig = {\n chain: 'base',\n limits: {\n maxPerTx: 100,\n maxPerDay: 1000,\n },\n};\n\nexport class MoltsPayClient {\n private configDir: string;\n private config: ClientConfig;\n private walletData: WalletData | null = null;\n private wallet: Wallet | null = null;\n private todaySpending: number = 0;\n private lastSpendingReset: number = 0;\n\n constructor(options: MoltsPayClientOptions = {}) {\n this.configDir = options.configDir || join(homedir(), '.moltspay');\n this.config = this.loadConfig();\n this.walletData = this.loadWallet();\n this.loadSpending(); // Load persisted spending data\n \n if (this.walletData) {\n this.wallet = new Wallet(this.walletData.privateKey);\n }\n }\n\n /**\n * Check if client is initialized (has wallet)\n */\n get isInitialized(): boolean {\n return this.wallet !== null;\n }\n\n /**\n * Get wallet address\n */\n get address(): string | null {\n return this.wallet?.address || null;\n }\n\n /**\n * Get current config\n */\n getConfig(): ClientConfig {\n return { ...this.config };\n }\n\n /**\n * Update config\n */\n updateConfig(updates: Partial<ClientConfig['limits']>): void {\n if (updates.maxPerTx !== undefined) {\n this.config.limits.maxPerTx = updates.maxPerTx;\n }\n if (updates.maxPerDay !== undefined) {\n this.config.limits.maxPerDay = updates.maxPerDay;\n }\n this.saveConfig();\n }\n\n /**\n * Get services from a provider\n */\n async getServices(serverUrl: string): Promise<ServicesResponse> {\n // Normalize URL - don't append /services if already present\n const normalizedUrl = serverUrl.replace(/\\/(services|api\\/services|registry\\/services)\\/?$/, '');\n \n // Try /services first (standard provider endpoint)\n const endpoints = ['/services', '/api/services', '/registry/services'];\n \n for (const endpoint of endpoints) {\n try {\n const res = await fetch(`${normalizedUrl}${endpoint}`);\n if (!res.ok) continue;\n \n const contentType = res.headers.get('content-type') || '';\n if (!contentType.includes('application/json')) continue;\n \n return await res.json() as ServicesResponse;\n } catch {\n continue;\n }\n }\n \n throw new Error(`Failed to get services: no valid endpoint found at ${normalizedUrl}`);\n }\n\n /**\n * Pay for a service and get the result (x402 protocol)\n * \n * This is GASLESS for the client - server pays gas to claim payment.\n * This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.\n * \n * @param serverUrl - Server URL\n * @param service - Service ID\n * @param params - Service parameters\n * @param options - Payment options (token selection)\n */\n async pay(\n serverUrl: string,\n service: string,\n params: Record<string, any>,\n options: PayOptions = {}\n ): Promise<Record<string, any>> {\n if (!this.wallet || !this.walletData) {\n throw new Error('Client not initialized. Run: npx moltspay init');\n }\n\n // Step 1: Make initial request without payment\n console.log(`[MoltsPay] Requesting service: ${service}`);\n const requestBody: any = { service, params };\n if (options.chain) {\n requestBody.chain = options.chain;\n }\n const initialRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(requestBody),\n });\n\n // If not 402, check for success or error\n if (initialRes.status !== 402) {\n const data = await initialRes.json() as any;\n if (initialRes.ok && data.result) {\n return data.result;\n }\n throw new Error(data.error || 'Unexpected response');\n }\n\n // Step 2: Parse payment requirements from 402 response\n const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);\n if (!paymentRequiredHeader) {\n throw new Error('Missing x-payment-required header');\n }\n\n let requirements: X402PaymentRequirements[];\n try {\n const decoded = Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8');\n const parsed = JSON.parse(decoded);\n \n // Handle both v1 (array) and v2 (object with accepts) formats\n if (Array.isArray(parsed)) {\n // v1 format: direct array of requirements\n requirements = parsed;\n } else if (parsed.accepts && Array.isArray(parsed.accepts)) {\n // v2 format: { x402Version: 2, accepts: [...] }\n requirements = parsed.accepts;\n } else {\n // Single requirement object\n requirements = [parsed];\n }\n } catch {\n throw new Error('Invalid x-payment-required header');\n }\n\n // Helper to convert network ID to chain name\n const networkToChainName = (network: string): string | null => {\n const match = network.match(/^eip155:(\\d+)$/);\n if (!match) return null;\n const chainId = parseInt(match[1]);\n if (chainId === 8453) return 'base';\n if (chainId === 137) return 'polygon';\n if (chainId === 84532) return 'base_sepolia';\n return null;\n };\n\n // Get server's accepted chains\n const serverChains = requirements\n .map(r => networkToChainName(r.network))\n .filter((c): c is string => c !== null);\n\n // Determine which chain to use\n let chainName: ChainName;\n const userSpecifiedChain = options.chain;\n\n if (userSpecifiedChain) {\n // User specified --chain, validate it's accepted by server\n if (!serverChains.includes(userSpecifiedChain)) {\n throw new Error(\n `Server doesn't accept '${userSpecifiedChain}'.\\n` +\n `Server accepts: ${serverChains.join(', ')}`\n );\n }\n chainName = userSpecifiedChain as ChainName;\n } else {\n // No --chain provided\n if (serverChains.length === 1 && serverChains[0] === 'base') {\n // Only default to base if server ONLY accepts base\n chainName = 'base';\n } else {\n throw new Error(\n `Server accepts: ${serverChains.join(', ')}\\n` +\n `Please specify: --chain base or --chain polygon`\n );\n }\n }\n\n const chain = getChain(chainName);\n const network = `eip155:${chain.chainId}`;\n const req = requirements.find(r => r.scheme === 'exact' && r.network === network);\n\n if (!req) {\n throw new Error(`Failed to find payment requirement for ${chainName}`);\n }\n\n // Step 3: Check limits\n // v2 uses 'amount', v1 uses 'maxAmountRequired'\n const amountRaw = req.amount || req.maxAmountRequired;\n if (!amountRaw) {\n throw new Error('Missing amount in payment requirements');\n }\n const amount = Number(amountRaw) / 1e6;\n this.checkLimits(amount);\n\n // Determine which token to use\n let token: TokenSymbol = options.token || 'USDC';\n \n // Auto-select token based on balance if requested\n if (options.autoSelect) {\n const balances = await this.getBalance();\n if (balances.usdc >= amount) {\n token = 'USDC';\n } else if (balances.usdt >= amount) {\n token = 'USDT';\n } else {\n throw new Error(`Insufficient balance: need $${amount}, have ${balances.usdc} USDC / ${balances.usdt} USDT`);\n }\n }\n\n // USDT does not support gasless transfers (no EIP-2612 permit)\n // It requires on-chain approve + transfer, meaning the user pays gas\n if (token === 'USDT') {\n const balances = await this.getBalance();\n if (balances.native < 0.0001) {\n throw new Error(\n `USDT requires ETH for gas (~$0.01 on Base). ` +\n `Your ETH balance: ${balances.native.toFixed(6)} ETH. ` +\n `Please add a small amount of ETH to your wallet, or use USDC (gasless).`\n );\n }\n console.log(`[MoltsPay] ⚠️ USDT requires gas (~$0.01). Proceeding with payment...`);\n } else {\n console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);\n }\n\n // Step 4: Sign EIP-3009 authorization (GASLESS - just signing)\n // payTo is the recipient address (v2 format)\n const payTo = req.payTo || req.resource; // fallback for v1 compatibility\n if (!payTo) {\n throw new Error('Missing payTo address in payment requirements');\n }\n const authorization = await this.signEIP3009(payTo, amount, chain, token);\n\n // Get token-specific info\n const tokenConfig = chain.tokens[token];\n const tokenName = token === 'USDC' ? 'USD Coin' : 'Tether USD';\n\n // Step 5: Create x402 payment payload (v2 format requires 'accepted')\n const payload = {\n x402Version: X402_VERSION,\n payload: authorization,\n // v2 requires 'accepted' field with the requirements being fulfilled\n accepted: {\n scheme: 'exact',\n network,\n asset: tokenConfig.address,\n amount: amountRaw,\n payTo,\n maxTimeoutSeconds: req.maxTimeoutSeconds || 300,\n extra: req.extra || { name: tokenName, version: '2' },\n },\n };\n const paymentHeader = Buffer.from(JSON.stringify(payload)).toString('base64');\n\n // Step 6: Retry with payment header\n console.log(`[MoltsPay] Sending request with payment...`);\n const paidRequestBody: any = { service, params };\n if (options.chain) {\n paidRequestBody.chain = options.chain;\n }\n const paidRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [PAYMENT_HEADER]: paymentHeader,\n },\n body: JSON.stringify(paidRequestBody),\n });\n\n const result = await paidRes.json() as any;\n\n if (!paidRes.ok) {\n throw new Error(result.error || 'Service execution failed');\n }\n\n // Update spending tracking\n this.recordSpending(amount);\n\n console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || 'claimed'}`);\n \n return result.result;\n }\n\n /**\n * Sign EIP-3009 transferWithAuthorization (GASLESS)\n * This only signs - no on-chain transaction, no gas needed.\n * Supports both USDC and USDT.\n */\n private async signEIP3009(\n to: string,\n amount: number,\n chain: { chainId: number; tokens: Record<TokenSymbol, { address: string; decimals: number }> },\n token: TokenSymbol = 'USDC'\n ): Promise<{ authorization: EIP3009Authorization; signature: string }> {\n const validAfter = 0;\n const validBefore = Math.floor(Date.now() / 1000) + 3600; // 1 hour\n const nonce = ethers.hexlify(ethers.randomBytes(32));\n \n const tokenConfig = chain.tokens[token];\n const value = BigInt(Math.floor(amount * (10 ** tokenConfig.decimals))).toString();\n\n const authorization: EIP3009Authorization = {\n from: this.wallet!.address,\n to,\n value,\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n };\n\n // EIP-712 domain - token specific\n const tokenName = token === 'USDC' ? 'USD Coin' : 'Tether USD';\n const domain = {\n name: tokenName,\n version: '2',\n chainId: chain.chainId,\n verifyingContract: tokenConfig.address,\n };\n\n // EIP-3009 types\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 signature = await this.wallet!.signTypedData(domain, types, authorization);\n\n return { authorization, signature };\n }\n\n /**\n * Check spending limits\n */\n private checkLimits(amount: number): void {\n // Check per-tx limit\n if (amount > this.config.limits.maxPerTx) {\n throw new Error(\n `Amount $${amount} exceeds max per transaction ($${this.config.limits.maxPerTx})`\n );\n }\n\n // Reset daily spending if new day\n const today = new Date().setHours(0, 0, 0, 0);\n if (today > this.lastSpendingReset) {\n this.todaySpending = 0;\n this.lastSpendingReset = today;\n this.saveSpending(); // Persist reset\n }\n\n // Check daily limit\n if (this.todaySpending + amount > this.config.limits.maxPerDay) {\n throw new Error(\n `Would exceed daily limit ($${this.todaySpending} + $${amount} > $${this.config.limits.maxPerDay})`\n );\n }\n }\n\n /**\n * Record spending and persist to disk\n */\n private recordSpending(amount: number): void {\n this.todaySpending += amount;\n this.saveSpending();\n }\n\n // --- Config & Wallet Management ---\n\n private loadConfig(): ClientConfig {\n const configPath = join(this.configDir, 'config.json');\n if (existsSync(configPath)) {\n const content = readFileSync(configPath, 'utf-8');\n return { ...DEFAULT_CONFIG, ...JSON.parse(content) };\n }\n return { ...DEFAULT_CONFIG };\n }\n\n private saveConfig(): void {\n mkdirSync(this.configDir, { recursive: true });\n const configPath = join(this.configDir, 'config.json');\n writeFileSync(configPath, JSON.stringify(this.config, null, 2));\n }\n\n /**\n * Load spending data from disk\n */\n private loadSpending(): void {\n const spendingPath = join(this.configDir, 'spending.json');\n if (existsSync(spendingPath)) {\n try {\n const data = JSON.parse(readFileSync(spendingPath, 'utf-8'));\n const today = new Date().setHours(0, 0, 0, 0);\n \n // Only load if it's from today\n if (data.date && data.date === today) {\n this.todaySpending = data.amount || 0;\n this.lastSpendingReset = data.date;\n } else {\n // Data is from a previous day, reset\n this.todaySpending = 0;\n this.lastSpendingReset = today;\n }\n } catch {\n // Ignore parse errors, start fresh\n this.todaySpending = 0;\n this.lastSpendingReset = new Date().setHours(0, 0, 0, 0);\n }\n }\n }\n\n /**\n * Save spending data to disk\n */\n private saveSpending(): void {\n mkdirSync(this.configDir, { recursive: true });\n const spendingPath = join(this.configDir, 'spending.json');\n const data = {\n date: this.lastSpendingReset || new Date().setHours(0, 0, 0, 0),\n amount: this.todaySpending,\n updatedAt: Date.now(),\n };\n writeFileSync(spendingPath, JSON.stringify(data, null, 2));\n }\n\n private loadWallet(): WalletData | null {\n const walletPath = join(this.configDir, 'wallet.json');\n if (existsSync(walletPath)) {\n // Security check: warn and fix if permissions are too open\n try {\n const stats = statSync(walletPath);\n const mode = stats.mode & 0o777;\n if (mode !== 0o600) {\n console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);\n console.warn(`[MoltsPay] Fixing permissions to 0600...`);\n chmodSync(walletPath, 0o600);\n }\n } catch (err) {\n // Ignore permission check errors on Windows\n }\n \n const content = readFileSync(walletPath, 'utf-8');\n return JSON.parse(content);\n }\n return null;\n }\n\n /**\n * Initialize a new wallet (called by CLI)\n */\n static init(\n configDir: string,\n options: { chain: string; maxPerTx: number; maxPerDay: number }\n ): { address: string; configDir: string } {\n mkdirSync(configDir, { recursive: true });\n\n // Create wallet\n const wallet = Wallet.createRandom();\n const walletData: WalletData = {\n address: wallet.address,\n privateKey: wallet.privateKey,\n createdAt: Date.now(),\n };\n\n // Save wallet with secure permissions (0o600 = owner read/write only)\n const walletPath = join(configDir, 'wallet.json');\n writeFileSync(walletPath, JSON.stringify(walletData, null, 2), { mode: 0o600 });\n\n // Save config\n const config: ClientConfig = {\n chain: options.chain,\n limits: {\n maxPerTx: options.maxPerTx,\n maxPerDay: options.maxPerDay,\n },\n };\n const configPath = join(configDir, 'config.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2));\n\n return { address: wallet.address, configDir };\n }\n\n /**\n * Get wallet balance (USDC, USDT, and native token) on default chain\n */\n async getBalance(): Promise<{ usdc: number; usdt: number; native: number }> {\n if (!this.wallet) {\n throw new Error('Client not initialized');\n }\n\n let chain;\n try {\n chain = getChain(this.config.chain as ChainName);\n } catch {\n throw new Error(`Unknown chain: ${this.config.chain}`);\n }\n\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n const tokenAbi = ['function balanceOf(address) view returns (uint256)'];\n\n // Get all balances in parallel\n const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([\n provider.getBalance(this.wallet.address),\n new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),\n new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address),\n ]);\n\n return {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),\n usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\n }\n\n /**\n * Get wallet balances on all supported chains (Base + Polygon)\n */\n async getAllBalances(): Promise<Record<string, { usdc: number; usdt: number; native: number }>> {\n if (!this.wallet) {\n throw new Error('Client not initialized');\n }\n\n const supportedChains: ChainName[] = ['base', 'polygon'];\n const tokenAbi = ['function balanceOf(address) view returns (uint256)'];\n const results: Record<string, { usdc: number; usdt: number; native: number }> = {};\n\n // Query all chains in parallel\n await Promise.all(\n supportedChains.map(async (chainName) => {\n try {\n const chain = getChain(chainName);\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n\n const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([\n provider.getBalance(this.wallet!.address),\n new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet!.address),\n new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet!.address),\n ]);\n\n results[chainName] = {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),\n usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\n } catch (err) {\n // If chain query fails, show zeros\n results[chainName] = { usdc: 0, usdt: 0, native: 0 };\n }\n })\n );\n\n return results;\n }\n}\n","/**\n * Blockchain Configuration\n */\n\nimport type { ChainConfig, ChainName, TokenSymbol } from '../types/index.js';\n\nexport const CHAINS: Record<ChainName, ChainConfig> = {\n // ============ Mainnet ============\n base: {\n name: 'Base',\n chainId: 8453,\n rpc: 'https://mainnet.base.org',\n tokens: {\n USDC: {\n address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // deprecated, for backward compat\n explorer: 'https://basescan.org/address/',\n explorerTx: 'https://basescan.org/tx/',\n avgBlockTime: 2,\n },\n polygon: {\n name: 'Polygon',\n chainId: 137,\n rpc: 'https://polygon-bor-rpc.publicnode.com',\n tokens: {\n USDC: {\n address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n explorer: 'https://polygonscan.com/address/',\n explorerTx: 'https://polygonscan.com/tx/',\n avgBlockTime: 2,\n },\n ethereum: {\n name: 'Ethereum',\n chainId: 1,\n rpc: 'https://eth.llamarpc.com',\n tokens: {\n USDC: {\n address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n explorer: 'https://etherscan.io/address/',\n explorerTx: 'https://etherscan.io/tx/',\n avgBlockTime: 12,\n },\n\n // ============ Testnet ============\n base_sepolia: {\n name: 'Base Sepolia',\n chainId: 84532,\n rpc: 'https://sepolia.base.org',\n tokens: {\n USDC: {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // Same as USDC on testnet (no official USDT)\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n explorer: 'https://sepolia.basescan.org/address/',\n explorerTx: 'https://sepolia.basescan.org/tx/',\n avgBlockTime: 2,\n },\n sepolia: {\n name: 'Sepolia',\n chainId: 11155111,\n rpc: 'https://rpc.sepolia.org',\n tokens: {\n USDC: {\n address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', // Same as USDC on testnet\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',\n explorer: 'https://sepolia.etherscan.io/address/',\n explorerTx: 'https://sepolia.etherscan.io/tx/',\n avgBlockTime: 12,\n },\n};\n\n/**\n * Get token address for a chain\n */\nexport function getTokenAddress(chainName: ChainName, token: TokenSymbol): string {\n const chain = CHAINS[chainName];\n if (!chain) {\n throw new Error(`Unsupported chain: ${chainName}`);\n }\n const tokenConfig = chain.tokens[token];\n if (!tokenConfig) {\n throw new Error(`Token ${token} not supported on ${chainName}`);\n }\n return tokenConfig.address;\n}\n\n/**\n * Get token config for a chain\n */\nexport function getTokenConfig(chainName: ChainName, token: TokenSymbol) {\n const chain = CHAINS[chainName];\n if (!chain) {\n throw new Error(`Unsupported chain: ${chainName}`);\n }\n return chain.tokens[token];\n}\n\n/**\n * Get chain configuration\n */\nexport function getChain(name: ChainName): ChainConfig {\n const config = CHAINS[name];\n if (!config) {\n throw new Error(`Unsupported chain: ${name}. Supported: ${Object.keys(CHAINS).join(', ')}`);\n }\n return config;\n}\n\n/**\n * List all supported chains\n */\nexport function listChains(): ChainName[] {\n return Object.keys(CHAINS) as ChainName[];\n}\n\n/**\n * Get chain config by chainId\n */\nexport function getChainById(chainId: number): ChainConfig | undefined {\n return Object.values(CHAINS).find(c => c.chainId === chainId);\n}\n\n/**\n * ERC20 ABI (minimal, only required methods)\n */\nexport const ERC20_ABI = [\n 'function balanceOf(address owner) view returns (uint256)',\n 'function transfer(address to, uint256 amount) returns (bool)',\n 'function approve(address spender, uint256 amount) returns (bool)',\n 'function allowance(address owner, address spender) view returns (uint256)',\n 'function decimals() view returns (uint8)',\n 'function symbol() view returns (string)',\n 'function name() view returns (string)',\n 'function nonces(address owner) view returns (uint256)',\n 'function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)',\n 'event Transfer(address indexed from, address indexed to, uint256 value)',\n 'event Approval(address indexed owner, address indexed spender, uint256 value)',\n];\n\nexport type { ChainConfig, ChainName, TokenSymbol };\n"],"mappings":"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","names":["network"]}
|
package/dist/client/index.mjs
CHANGED
|
@@ -186,11 +186,20 @@ var MoltsPayClient = class {
|
|
|
186
186
|
* Get services from a provider
|
|
187
187
|
*/
|
|
188
188
|
async getServices(serverUrl) {
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
const normalizedUrl = serverUrl.replace(/\/(services|api\/services|registry\/services)\/?$/, "");
|
|
190
|
+
const endpoints = ["/services", "/api/services", "/registry/services"];
|
|
191
|
+
for (const endpoint of endpoints) {
|
|
192
|
+
try {
|
|
193
|
+
const res = await fetch(`${normalizedUrl}${endpoint}`);
|
|
194
|
+
if (!res.ok) continue;
|
|
195
|
+
const contentType = res.headers.get("content-type") || "";
|
|
196
|
+
if (!contentType.includes("application/json")) continue;
|
|
197
|
+
return await res.json();
|
|
198
|
+
} catch {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
192
201
|
}
|
|
193
|
-
|
|
202
|
+
throw new Error(`Failed to get services: no valid endpoint found at ${normalizedUrl}`);
|
|
194
203
|
}
|
|
195
204
|
/**
|
|
196
205
|
* Pay for a service and get the result (x402 protocol)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/index.ts","../../src/chains/index.ts"],"sourcesContent":["/**\n * MoltsPay Client - Pay for AI Agent services\n * \n * Uses x402 protocol for gasless, pay-for-success payments.\n * \n * Usage:\n * const client = new MoltsPayClient(); // Loads from ~/.moltspay/\n * const services = await client.getServices('http://provider:3000');\n * const result = await client.pay('http://provider:3000', 'text-to-video', { prompt: '...' });\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, chmodSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Wallet, ethers } from 'ethers';\nimport { getChain, type ChainName, type TokenSymbol } from '../chains/index.js';\nimport {\n ClientConfig,\n WalletData,\n ServicesResponse,\n MoltsPayClientOptions,\n} from './types.js';\n\nexport * from './types.js';\n\nexport interface PayOptions {\n /** Token to pay with (default: USDC, or auto-select based on balance) */\n token?: TokenSymbol;\n /** Auto-select token based on balance (default: false) */\n autoSelect?: boolean;\n /** Chain to pay on (base or polygon, default: base) */\n chain?: 'base' | 'polygon';\n}\n\n// x402 constants\nconst X402_VERSION = 2;\nconst PAYMENT_REQUIRED_HEADER = 'x-payment-required';\nconst PAYMENT_HEADER = 'x-payment';\n\ninterface X402PaymentRequirements {\n scheme: string;\n network: string;\n // v2 fields\n amount?: string;\n asset?: string;\n payTo?: string;\n maxTimeoutSeconds?: number;\n extra?: Record<string, unknown>;\n // v1 fields (legacy)\n maxAmountRequired?: string;\n resource?: string;\n description?: string;\n}\n\ninterface EIP3009Authorization {\n from: string;\n to: string;\n value: string;\n validAfter: string;\n validBefore: string;\n nonce: string;\n}\n\nconst DEFAULT_CONFIG: ClientConfig = {\n chain: 'base',\n limits: {\n maxPerTx: 100,\n maxPerDay: 1000,\n },\n};\n\nexport class MoltsPayClient {\n private configDir: string;\n private config: ClientConfig;\n private walletData: WalletData | null = null;\n private wallet: Wallet | null = null;\n private todaySpending: number = 0;\n private lastSpendingReset: number = 0;\n\n constructor(options: MoltsPayClientOptions = {}) {\n this.configDir = options.configDir || join(homedir(), '.moltspay');\n this.config = this.loadConfig();\n this.walletData = this.loadWallet();\n this.loadSpending(); // Load persisted spending data\n \n if (this.walletData) {\n this.wallet = new Wallet(this.walletData.privateKey);\n }\n }\n\n /**\n * Check if client is initialized (has wallet)\n */\n get isInitialized(): boolean {\n return this.wallet !== null;\n }\n\n /**\n * Get wallet address\n */\n get address(): string | null {\n return this.wallet?.address || null;\n }\n\n /**\n * Get current config\n */\n getConfig(): ClientConfig {\n return { ...this.config };\n }\n\n /**\n * Update config\n */\n updateConfig(updates: Partial<ClientConfig['limits']>): void {\n if (updates.maxPerTx !== undefined) {\n this.config.limits.maxPerTx = updates.maxPerTx;\n }\n if (updates.maxPerDay !== undefined) {\n this.config.limits.maxPerDay = updates.maxPerDay;\n }\n this.saveConfig();\n }\n\n /**\n * Get services from a provider\n */\n async getServices(serverUrl: string): Promise<ServicesResponse> {\n const res = await fetch(`${serverUrl}/services`);\n if (!res.ok) {\n throw new Error(`Failed to get services: ${res.statusText}`);\n }\n return res.json() as Promise<ServicesResponse>;\n }\n\n /**\n * Pay for a service and get the result (x402 protocol)\n * \n * This is GASLESS for the client - server pays gas to claim payment.\n * This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.\n * \n * @param serverUrl - Server URL\n * @param service - Service ID\n * @param params - Service parameters\n * @param options - Payment options (token selection)\n */\n async pay(\n serverUrl: string,\n service: string,\n params: Record<string, any>,\n options: PayOptions = {}\n ): Promise<Record<string, any>> {\n if (!this.wallet || !this.walletData) {\n throw new Error('Client not initialized. Run: npx moltspay init');\n }\n\n // Step 1: Make initial request without payment\n console.log(`[MoltsPay] Requesting service: ${service}`);\n const requestBody: any = { service, params };\n if (options.chain) {\n requestBody.chain = options.chain;\n }\n const initialRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(requestBody),\n });\n\n // If not 402, check for success or error\n if (initialRes.status !== 402) {\n const data = await initialRes.json() as any;\n if (initialRes.ok && data.result) {\n return data.result;\n }\n throw new Error(data.error || 'Unexpected response');\n }\n\n // Step 2: Parse payment requirements from 402 response\n const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);\n if (!paymentRequiredHeader) {\n throw new Error('Missing x-payment-required header');\n }\n\n let requirements: X402PaymentRequirements[];\n try {\n const decoded = Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8');\n const parsed = JSON.parse(decoded);\n \n // Handle both v1 (array) and v2 (object with accepts) formats\n if (Array.isArray(parsed)) {\n // v1 format: direct array of requirements\n requirements = parsed;\n } else if (parsed.accepts && Array.isArray(parsed.accepts)) {\n // v2 format: { x402Version: 2, accepts: [...] }\n requirements = parsed.accepts;\n } else {\n // Single requirement object\n requirements = [parsed];\n }\n } catch {\n throw new Error('Invalid x-payment-required header');\n }\n\n // Helper to convert network ID to chain name\n const networkToChainName = (network: string): string | null => {\n const match = network.match(/^eip155:(\\d+)$/);\n if (!match) return null;\n const chainId = parseInt(match[1]);\n if (chainId === 8453) return 'base';\n if (chainId === 137) return 'polygon';\n if (chainId === 84532) return 'base_sepolia';\n return null;\n };\n\n // Get server's accepted chains\n const serverChains = requirements\n .map(r => networkToChainName(r.network))\n .filter((c): c is string => c !== null);\n\n // Determine which chain to use\n let chainName: ChainName;\n const userSpecifiedChain = options.chain;\n\n if (userSpecifiedChain) {\n // User specified --chain, validate it's accepted by server\n if (!serverChains.includes(userSpecifiedChain)) {\n throw new Error(\n `Server doesn't accept '${userSpecifiedChain}'.\\n` +\n `Server accepts: ${serverChains.join(', ')}`\n );\n }\n chainName = userSpecifiedChain as ChainName;\n } else {\n // No --chain provided\n if (serverChains.length === 1 && serverChains[0] === 'base') {\n // Only default to base if server ONLY accepts base\n chainName = 'base';\n } else {\n throw new Error(\n `Server accepts: ${serverChains.join(', ')}\\n` +\n `Please specify: --chain base or --chain polygon`\n );\n }\n }\n\n const chain = getChain(chainName);\n const network = `eip155:${chain.chainId}`;\n const req = requirements.find(r => r.scheme === 'exact' && r.network === network);\n\n if (!req) {\n throw new Error(`Failed to find payment requirement for ${chainName}`);\n }\n\n // Step 3: Check limits\n // v2 uses 'amount', v1 uses 'maxAmountRequired'\n const amountRaw = req.amount || req.maxAmountRequired;\n if (!amountRaw) {\n throw new Error('Missing amount in payment requirements');\n }\n const amount = Number(amountRaw) / 1e6;\n this.checkLimits(amount);\n\n // Determine which token to use\n let token: TokenSymbol = options.token || 'USDC';\n \n // Auto-select token based on balance if requested\n if (options.autoSelect) {\n const balances = await this.getBalance();\n if (balances.usdc >= amount) {\n token = 'USDC';\n } else if (balances.usdt >= amount) {\n token = 'USDT';\n } else {\n throw new Error(`Insufficient balance: need $${amount}, have ${balances.usdc} USDC / ${balances.usdt} USDT`);\n }\n }\n\n // USDT does not support gasless transfers (no EIP-2612 permit)\n // It requires on-chain approve + transfer, meaning the user pays gas\n if (token === 'USDT') {\n const balances = await this.getBalance();\n if (balances.native < 0.0001) {\n throw new Error(\n `USDT requires ETH for gas (~$0.01 on Base). ` +\n `Your ETH balance: ${balances.native.toFixed(6)} ETH. ` +\n `Please add a small amount of ETH to your wallet, or use USDC (gasless).`\n );\n }\n console.log(`[MoltsPay] ⚠️ USDT requires gas (~$0.01). Proceeding with payment...`);\n } else {\n console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);\n }\n\n // Step 4: Sign EIP-3009 authorization (GASLESS - just signing)\n // payTo is the recipient address (v2 format)\n const payTo = req.payTo || req.resource; // fallback for v1 compatibility\n if (!payTo) {\n throw new Error('Missing payTo address in payment requirements');\n }\n const authorization = await this.signEIP3009(payTo, amount, chain, token);\n\n // Get token-specific info\n const tokenConfig = chain.tokens[token];\n const tokenName = token === 'USDC' ? 'USD Coin' : 'Tether USD';\n\n // Step 5: Create x402 payment payload (v2 format requires 'accepted')\n const payload = {\n x402Version: X402_VERSION,\n payload: authorization,\n // v2 requires 'accepted' field with the requirements being fulfilled\n accepted: {\n scheme: 'exact',\n network,\n asset: tokenConfig.address,\n amount: amountRaw,\n payTo,\n maxTimeoutSeconds: req.maxTimeoutSeconds || 300,\n extra: req.extra || { name: tokenName, version: '2' },\n },\n };\n const paymentHeader = Buffer.from(JSON.stringify(payload)).toString('base64');\n\n // Step 6: Retry with payment header\n console.log(`[MoltsPay] Sending request with payment...`);\n const paidRequestBody: any = { service, params };\n if (options.chain) {\n paidRequestBody.chain = options.chain;\n }\n const paidRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [PAYMENT_HEADER]: paymentHeader,\n },\n body: JSON.stringify(paidRequestBody),\n });\n\n const result = await paidRes.json() as any;\n\n if (!paidRes.ok) {\n throw new Error(result.error || 'Service execution failed');\n }\n\n // Update spending tracking\n this.recordSpending(amount);\n\n console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || 'claimed'}`);\n \n return result.result;\n }\n\n /**\n * Sign EIP-3009 transferWithAuthorization (GASLESS)\n * This only signs - no on-chain transaction, no gas needed.\n * Supports both USDC and USDT.\n */\n private async signEIP3009(\n to: string,\n amount: number,\n chain: { chainId: number; tokens: Record<TokenSymbol, { address: string; decimals: number }> },\n token: TokenSymbol = 'USDC'\n ): Promise<{ authorization: EIP3009Authorization; signature: string }> {\n const validAfter = 0;\n const validBefore = Math.floor(Date.now() / 1000) + 3600; // 1 hour\n const nonce = ethers.hexlify(ethers.randomBytes(32));\n \n const tokenConfig = chain.tokens[token];\n const value = BigInt(Math.floor(amount * (10 ** tokenConfig.decimals))).toString();\n\n const authorization: EIP3009Authorization = {\n from: this.wallet!.address,\n to,\n value,\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n };\n\n // EIP-712 domain - token specific\n const tokenName = token === 'USDC' ? 'USD Coin' : 'Tether USD';\n const domain = {\n name: tokenName,\n version: '2',\n chainId: chain.chainId,\n verifyingContract: tokenConfig.address,\n };\n\n // EIP-3009 types\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 signature = await this.wallet!.signTypedData(domain, types, authorization);\n\n return { authorization, signature };\n }\n\n /**\n * Check spending limits\n */\n private checkLimits(amount: number): void {\n // Check per-tx limit\n if (amount > this.config.limits.maxPerTx) {\n throw new Error(\n `Amount $${amount} exceeds max per transaction ($${this.config.limits.maxPerTx})`\n );\n }\n\n // Reset daily spending if new day\n const today = new Date().setHours(0, 0, 0, 0);\n if (today > this.lastSpendingReset) {\n this.todaySpending = 0;\n this.lastSpendingReset = today;\n this.saveSpending(); // Persist reset\n }\n\n // Check daily limit\n if (this.todaySpending + amount > this.config.limits.maxPerDay) {\n throw new Error(\n `Would exceed daily limit ($${this.todaySpending} + $${amount} > $${this.config.limits.maxPerDay})`\n );\n }\n }\n\n /**\n * Record spending and persist to disk\n */\n private recordSpending(amount: number): void {\n this.todaySpending += amount;\n this.saveSpending();\n }\n\n // --- Config & Wallet Management ---\n\n private loadConfig(): ClientConfig {\n const configPath = join(this.configDir, 'config.json');\n if (existsSync(configPath)) {\n const content = readFileSync(configPath, 'utf-8');\n return { ...DEFAULT_CONFIG, ...JSON.parse(content) };\n }\n return { ...DEFAULT_CONFIG };\n }\n\n private saveConfig(): void {\n mkdirSync(this.configDir, { recursive: true });\n const configPath = join(this.configDir, 'config.json');\n writeFileSync(configPath, JSON.stringify(this.config, null, 2));\n }\n\n /**\n * Load spending data from disk\n */\n private loadSpending(): void {\n const spendingPath = join(this.configDir, 'spending.json');\n if (existsSync(spendingPath)) {\n try {\n const data = JSON.parse(readFileSync(spendingPath, 'utf-8'));\n const today = new Date().setHours(0, 0, 0, 0);\n \n // Only load if it's from today\n if (data.date && data.date === today) {\n this.todaySpending = data.amount || 0;\n this.lastSpendingReset = data.date;\n } else {\n // Data is from a previous day, reset\n this.todaySpending = 0;\n this.lastSpendingReset = today;\n }\n } catch {\n // Ignore parse errors, start fresh\n this.todaySpending = 0;\n this.lastSpendingReset = new Date().setHours(0, 0, 0, 0);\n }\n }\n }\n\n /**\n * Save spending data to disk\n */\n private saveSpending(): void {\n mkdirSync(this.configDir, { recursive: true });\n const spendingPath = join(this.configDir, 'spending.json');\n const data = {\n date: this.lastSpendingReset || new Date().setHours(0, 0, 0, 0),\n amount: this.todaySpending,\n updatedAt: Date.now(),\n };\n writeFileSync(spendingPath, JSON.stringify(data, null, 2));\n }\n\n private loadWallet(): WalletData | null {\n const walletPath = join(this.configDir, 'wallet.json');\n if (existsSync(walletPath)) {\n // Security check: warn and fix if permissions are too open\n try {\n const stats = statSync(walletPath);\n const mode = stats.mode & 0o777;\n if (mode !== 0o600) {\n console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);\n console.warn(`[MoltsPay] Fixing permissions to 0600...`);\n chmodSync(walletPath, 0o600);\n }\n } catch (err) {\n // Ignore permission check errors on Windows\n }\n \n const content = readFileSync(walletPath, 'utf-8');\n return JSON.parse(content);\n }\n return null;\n }\n\n /**\n * Initialize a new wallet (called by CLI)\n */\n static init(\n configDir: string,\n options: { chain: string; maxPerTx: number; maxPerDay: number }\n ): { address: string; configDir: string } {\n mkdirSync(configDir, { recursive: true });\n\n // Create wallet\n const wallet = Wallet.createRandom();\n const walletData: WalletData = {\n address: wallet.address,\n privateKey: wallet.privateKey,\n createdAt: Date.now(),\n };\n\n // Save wallet with secure permissions (0o600 = owner read/write only)\n const walletPath = join(configDir, 'wallet.json');\n writeFileSync(walletPath, JSON.stringify(walletData, null, 2), { mode: 0o600 });\n\n // Save config\n const config: ClientConfig = {\n chain: options.chain,\n limits: {\n maxPerTx: options.maxPerTx,\n maxPerDay: options.maxPerDay,\n },\n };\n const configPath = join(configDir, 'config.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2));\n\n return { address: wallet.address, configDir };\n }\n\n /**\n * Get wallet balance (USDC, USDT, and native token) on default chain\n */\n async getBalance(): Promise<{ usdc: number; usdt: number; native: number }> {\n if (!this.wallet) {\n throw new Error('Client not initialized');\n }\n\n let chain;\n try {\n chain = getChain(this.config.chain as ChainName);\n } catch {\n throw new Error(`Unknown chain: ${this.config.chain}`);\n }\n\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n const tokenAbi = ['function balanceOf(address) view returns (uint256)'];\n\n // Get all balances in parallel\n const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([\n provider.getBalance(this.wallet.address),\n new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),\n new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address),\n ]);\n\n return {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),\n usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\n }\n\n /**\n * Get wallet balances on all supported chains (Base + Polygon)\n */\n async getAllBalances(): Promise<Record<string, { usdc: number; usdt: number; native: number }>> {\n if (!this.wallet) {\n throw new Error('Client not initialized');\n }\n\n const supportedChains: ChainName[] = ['base', 'polygon'];\n const tokenAbi = ['function balanceOf(address) view returns (uint256)'];\n const results: Record<string, { usdc: number; usdt: number; native: number }> = {};\n\n // Query all chains in parallel\n await Promise.all(\n supportedChains.map(async (chainName) => {\n try {\n const chain = getChain(chainName);\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n\n const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([\n provider.getBalance(this.wallet!.address),\n new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet!.address),\n new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet!.address),\n ]);\n\n results[chainName] = {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),\n usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\n } catch (err) {\n // If chain query fails, show zeros\n results[chainName] = { usdc: 0, usdt: 0, native: 0 };\n }\n })\n );\n\n return results;\n }\n}\n","/**\n * Blockchain Configuration\n */\n\nimport type { ChainConfig, ChainName, TokenSymbol } from '../types/index.js';\n\nexport const CHAINS: Record<ChainName, ChainConfig> = {\n // ============ Mainnet ============\n base: {\n name: 'Base',\n chainId: 8453,\n rpc: 'https://mainnet.base.org',\n tokens: {\n USDC: {\n address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // deprecated, for backward compat\n explorer: 'https://basescan.org/address/',\n explorerTx: 'https://basescan.org/tx/',\n avgBlockTime: 2,\n },\n polygon: {\n name: 'Polygon',\n chainId: 137,\n rpc: 'https://polygon-bor-rpc.publicnode.com',\n tokens: {\n USDC: {\n address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n explorer: 'https://polygonscan.com/address/',\n explorerTx: 'https://polygonscan.com/tx/',\n avgBlockTime: 2,\n },\n ethereum: {\n name: 'Ethereum',\n chainId: 1,\n rpc: 'https://eth.llamarpc.com',\n tokens: {\n USDC: {\n address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n explorer: 'https://etherscan.io/address/',\n explorerTx: 'https://etherscan.io/tx/',\n avgBlockTime: 12,\n },\n\n // ============ Testnet ============\n base_sepolia: {\n name: 'Base Sepolia',\n chainId: 84532,\n rpc: 'https://sepolia.base.org',\n tokens: {\n USDC: {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // Same as USDC on testnet (no official USDT)\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n explorer: 'https://sepolia.basescan.org/address/',\n explorerTx: 'https://sepolia.basescan.org/tx/',\n avgBlockTime: 2,\n },\n sepolia: {\n name: 'Sepolia',\n chainId: 11155111,\n rpc: 'https://rpc.sepolia.org',\n tokens: {\n USDC: {\n address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', // Same as USDC on testnet\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',\n explorer: 'https://sepolia.etherscan.io/address/',\n explorerTx: 'https://sepolia.etherscan.io/tx/',\n avgBlockTime: 12,\n },\n};\n\n/**\n * Get token address for a chain\n */\nexport function getTokenAddress(chainName: ChainName, token: TokenSymbol): string {\n const chain = CHAINS[chainName];\n if (!chain) {\n throw new Error(`Unsupported chain: ${chainName}`);\n }\n const tokenConfig = chain.tokens[token];\n if (!tokenConfig) {\n throw new Error(`Token ${token} not supported on ${chainName}`);\n }\n return tokenConfig.address;\n}\n\n/**\n * Get token config for a chain\n */\nexport function getTokenConfig(chainName: ChainName, token: TokenSymbol) {\n const chain = CHAINS[chainName];\n if (!chain) {\n throw new Error(`Unsupported chain: ${chainName}`);\n }\n return chain.tokens[token];\n}\n\n/**\n * Get chain configuration\n */\nexport function getChain(name: ChainName): ChainConfig {\n const config = CHAINS[name];\n if (!config) {\n throw new Error(`Unsupported chain: ${name}. Supported: ${Object.keys(CHAINS).join(', ')}`);\n }\n return config;\n}\n\n/**\n * List all supported chains\n */\nexport function listChains(): ChainName[] {\n return Object.keys(CHAINS) as ChainName[];\n}\n\n/**\n * Get chain config by chainId\n */\nexport function getChainById(chainId: number): ChainConfig | undefined {\n return Object.values(CHAINS).find(c => c.chainId === chainId);\n}\n\n/**\n * ERC20 ABI (minimal, only required methods)\n */\nexport const ERC20_ABI = [\n 'function balanceOf(address owner) view returns (uint256)',\n 'function transfer(address to, uint256 amount) returns (bool)',\n 'function approve(address spender, uint256 amount) returns (bool)',\n 'function allowance(address owner, address spender) view returns (uint256)',\n 'function decimals() view returns (uint8)',\n 'function symbol() view returns (string)',\n 'function name() view returns (string)',\n 'function nonces(address owner) view returns (uint256)',\n 'function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)',\n 'event Transfer(address indexed from, address indexed to, uint256 value)',\n 'event Approval(address indexed owner, address indexed spender, uint256 value)',\n];\n\nexport type { ChainConfig, ChainName, TokenSymbol };\n"],"mappings":"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","names":["network"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts","../../src/chains/index.ts"],"sourcesContent":["/**\n * MoltsPay Client - Pay for AI Agent services\n * \n * Uses x402 protocol for gasless, pay-for-success payments.\n * \n * Usage:\n * const client = new MoltsPayClient(); // Loads from ~/.moltspay/\n * const services = await client.getServices('http://provider:3000');\n * const result = await client.pay('http://provider:3000', 'text-to-video', { prompt: '...' });\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, chmodSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Wallet, ethers } from 'ethers';\nimport { getChain, type ChainName, type TokenSymbol } from '../chains/index.js';\nimport {\n ClientConfig,\n WalletData,\n ServicesResponse,\n MoltsPayClientOptions,\n} from './types.js';\n\nexport * from './types.js';\n\nexport interface PayOptions {\n /** Token to pay with (default: USDC, or auto-select based on balance) */\n token?: TokenSymbol;\n /** Auto-select token based on balance (default: false) */\n autoSelect?: boolean;\n /** Chain to pay on (base or polygon, default: base) */\n chain?: 'base' | 'polygon';\n}\n\n// x402 constants\nconst X402_VERSION = 2;\nconst PAYMENT_REQUIRED_HEADER = 'x-payment-required';\nconst PAYMENT_HEADER = 'x-payment';\n\ninterface X402PaymentRequirements {\n scheme: string;\n network: string;\n // v2 fields\n amount?: string;\n asset?: string;\n payTo?: string;\n maxTimeoutSeconds?: number;\n extra?: Record<string, unknown>;\n // v1 fields (legacy)\n maxAmountRequired?: string;\n resource?: string;\n description?: string;\n}\n\ninterface EIP3009Authorization {\n from: string;\n to: string;\n value: string;\n validAfter: string;\n validBefore: string;\n nonce: string;\n}\n\nconst DEFAULT_CONFIG: ClientConfig = {\n chain: 'base',\n limits: {\n maxPerTx: 100,\n maxPerDay: 1000,\n },\n};\n\nexport class MoltsPayClient {\n private configDir: string;\n private config: ClientConfig;\n private walletData: WalletData | null = null;\n private wallet: Wallet | null = null;\n private todaySpending: number = 0;\n private lastSpendingReset: number = 0;\n\n constructor(options: MoltsPayClientOptions = {}) {\n this.configDir = options.configDir || join(homedir(), '.moltspay');\n this.config = this.loadConfig();\n this.walletData = this.loadWallet();\n this.loadSpending(); // Load persisted spending data\n \n if (this.walletData) {\n this.wallet = new Wallet(this.walletData.privateKey);\n }\n }\n\n /**\n * Check if client is initialized (has wallet)\n */\n get isInitialized(): boolean {\n return this.wallet !== null;\n }\n\n /**\n * Get wallet address\n */\n get address(): string | null {\n return this.wallet?.address || null;\n }\n\n /**\n * Get current config\n */\n getConfig(): ClientConfig {\n return { ...this.config };\n }\n\n /**\n * Update config\n */\n updateConfig(updates: Partial<ClientConfig['limits']>): void {\n if (updates.maxPerTx !== undefined) {\n this.config.limits.maxPerTx = updates.maxPerTx;\n }\n if (updates.maxPerDay !== undefined) {\n this.config.limits.maxPerDay = updates.maxPerDay;\n }\n this.saveConfig();\n }\n\n /**\n * Get services from a provider\n */\n async getServices(serverUrl: string): Promise<ServicesResponse> {\n // Normalize URL - don't append /services if already present\n const normalizedUrl = serverUrl.replace(/\\/(services|api\\/services|registry\\/services)\\/?$/, '');\n \n // Try /services first (standard provider endpoint)\n const endpoints = ['/services', '/api/services', '/registry/services'];\n \n for (const endpoint of endpoints) {\n try {\n const res = await fetch(`${normalizedUrl}${endpoint}`);\n if (!res.ok) continue;\n \n const contentType = res.headers.get('content-type') || '';\n if (!contentType.includes('application/json')) continue;\n \n return await res.json() as ServicesResponse;\n } catch {\n continue;\n }\n }\n \n throw new Error(`Failed to get services: no valid endpoint found at ${normalizedUrl}`);\n }\n\n /**\n * Pay for a service and get the result (x402 protocol)\n * \n * This is GASLESS for the client - server pays gas to claim payment.\n * This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.\n * \n * @param serverUrl - Server URL\n * @param service - Service ID\n * @param params - Service parameters\n * @param options - Payment options (token selection)\n */\n async pay(\n serverUrl: string,\n service: string,\n params: Record<string, any>,\n options: PayOptions = {}\n ): Promise<Record<string, any>> {\n if (!this.wallet || !this.walletData) {\n throw new Error('Client not initialized. Run: npx moltspay init');\n }\n\n // Step 1: Make initial request without payment\n console.log(`[MoltsPay] Requesting service: ${service}`);\n const requestBody: any = { service, params };\n if (options.chain) {\n requestBody.chain = options.chain;\n }\n const initialRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(requestBody),\n });\n\n // If not 402, check for success or error\n if (initialRes.status !== 402) {\n const data = await initialRes.json() as any;\n if (initialRes.ok && data.result) {\n return data.result;\n }\n throw new Error(data.error || 'Unexpected response');\n }\n\n // Step 2: Parse payment requirements from 402 response\n const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);\n if (!paymentRequiredHeader) {\n throw new Error('Missing x-payment-required header');\n }\n\n let requirements: X402PaymentRequirements[];\n try {\n const decoded = Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8');\n const parsed = JSON.parse(decoded);\n \n // Handle both v1 (array) and v2 (object with accepts) formats\n if (Array.isArray(parsed)) {\n // v1 format: direct array of requirements\n requirements = parsed;\n } else if (parsed.accepts && Array.isArray(parsed.accepts)) {\n // v2 format: { x402Version: 2, accepts: [...] }\n requirements = parsed.accepts;\n } else {\n // Single requirement object\n requirements = [parsed];\n }\n } catch {\n throw new Error('Invalid x-payment-required header');\n }\n\n // Helper to convert network ID to chain name\n const networkToChainName = (network: string): string | null => {\n const match = network.match(/^eip155:(\\d+)$/);\n if (!match) return null;\n const chainId = parseInt(match[1]);\n if (chainId === 8453) return 'base';\n if (chainId === 137) return 'polygon';\n if (chainId === 84532) return 'base_sepolia';\n return null;\n };\n\n // Get server's accepted chains\n const serverChains = requirements\n .map(r => networkToChainName(r.network))\n .filter((c): c is string => c !== null);\n\n // Determine which chain to use\n let chainName: ChainName;\n const userSpecifiedChain = options.chain;\n\n if (userSpecifiedChain) {\n // User specified --chain, validate it's accepted by server\n if (!serverChains.includes(userSpecifiedChain)) {\n throw new Error(\n `Server doesn't accept '${userSpecifiedChain}'.\\n` +\n `Server accepts: ${serverChains.join(', ')}`\n );\n }\n chainName = userSpecifiedChain as ChainName;\n } else {\n // No --chain provided\n if (serverChains.length === 1 && serverChains[0] === 'base') {\n // Only default to base if server ONLY accepts base\n chainName = 'base';\n } else {\n throw new Error(\n `Server accepts: ${serverChains.join(', ')}\\n` +\n `Please specify: --chain base or --chain polygon`\n );\n }\n }\n\n const chain = getChain(chainName);\n const network = `eip155:${chain.chainId}`;\n const req = requirements.find(r => r.scheme === 'exact' && r.network === network);\n\n if (!req) {\n throw new Error(`Failed to find payment requirement for ${chainName}`);\n }\n\n // Step 3: Check limits\n // v2 uses 'amount', v1 uses 'maxAmountRequired'\n const amountRaw = req.amount || req.maxAmountRequired;\n if (!amountRaw) {\n throw new Error('Missing amount in payment requirements');\n }\n const amount = Number(amountRaw) / 1e6;\n this.checkLimits(amount);\n\n // Determine which token to use\n let token: TokenSymbol = options.token || 'USDC';\n \n // Auto-select token based on balance if requested\n if (options.autoSelect) {\n const balances = await this.getBalance();\n if (balances.usdc >= amount) {\n token = 'USDC';\n } else if (balances.usdt >= amount) {\n token = 'USDT';\n } else {\n throw new Error(`Insufficient balance: need $${amount}, have ${balances.usdc} USDC / ${balances.usdt} USDT`);\n }\n }\n\n // USDT does not support gasless transfers (no EIP-2612 permit)\n // It requires on-chain approve + transfer, meaning the user pays gas\n if (token === 'USDT') {\n const balances = await this.getBalance();\n if (balances.native < 0.0001) {\n throw new Error(\n `USDT requires ETH for gas (~$0.01 on Base). ` +\n `Your ETH balance: ${balances.native.toFixed(6)} ETH. ` +\n `Please add a small amount of ETH to your wallet, or use USDC (gasless).`\n );\n }\n console.log(`[MoltsPay] ⚠️ USDT requires gas (~$0.01). Proceeding with payment...`);\n } else {\n console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);\n }\n\n // Step 4: Sign EIP-3009 authorization (GASLESS - just signing)\n // payTo is the recipient address (v2 format)\n const payTo = req.payTo || req.resource; // fallback for v1 compatibility\n if (!payTo) {\n throw new Error('Missing payTo address in payment requirements');\n }\n const authorization = await this.signEIP3009(payTo, amount, chain, token);\n\n // Get token-specific info\n const tokenConfig = chain.tokens[token];\n const tokenName = token === 'USDC' ? 'USD Coin' : 'Tether USD';\n\n // Step 5: Create x402 payment payload (v2 format requires 'accepted')\n const payload = {\n x402Version: X402_VERSION,\n payload: authorization,\n // v2 requires 'accepted' field with the requirements being fulfilled\n accepted: {\n scheme: 'exact',\n network,\n asset: tokenConfig.address,\n amount: amountRaw,\n payTo,\n maxTimeoutSeconds: req.maxTimeoutSeconds || 300,\n extra: req.extra || { name: tokenName, version: '2' },\n },\n };\n const paymentHeader = Buffer.from(JSON.stringify(payload)).toString('base64');\n\n // Step 6: Retry with payment header\n console.log(`[MoltsPay] Sending request with payment...`);\n const paidRequestBody: any = { service, params };\n if (options.chain) {\n paidRequestBody.chain = options.chain;\n }\n const paidRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [PAYMENT_HEADER]: paymentHeader,\n },\n body: JSON.stringify(paidRequestBody),\n });\n\n const result = await paidRes.json() as any;\n\n if (!paidRes.ok) {\n throw new Error(result.error || 'Service execution failed');\n }\n\n // Update spending tracking\n this.recordSpending(amount);\n\n console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || 'claimed'}`);\n \n return result.result;\n }\n\n /**\n * Sign EIP-3009 transferWithAuthorization (GASLESS)\n * This only signs - no on-chain transaction, no gas needed.\n * Supports both USDC and USDT.\n */\n private async signEIP3009(\n to: string,\n amount: number,\n chain: { chainId: number; tokens: Record<TokenSymbol, { address: string; decimals: number }> },\n token: TokenSymbol = 'USDC'\n ): Promise<{ authorization: EIP3009Authorization; signature: string }> {\n const validAfter = 0;\n const validBefore = Math.floor(Date.now() / 1000) + 3600; // 1 hour\n const nonce = ethers.hexlify(ethers.randomBytes(32));\n \n const tokenConfig = chain.tokens[token];\n const value = BigInt(Math.floor(amount * (10 ** tokenConfig.decimals))).toString();\n\n const authorization: EIP3009Authorization = {\n from: this.wallet!.address,\n to,\n value,\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n };\n\n // EIP-712 domain - token specific\n const tokenName = token === 'USDC' ? 'USD Coin' : 'Tether USD';\n const domain = {\n name: tokenName,\n version: '2',\n chainId: chain.chainId,\n verifyingContract: tokenConfig.address,\n };\n\n // EIP-3009 types\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 signature = await this.wallet!.signTypedData(domain, types, authorization);\n\n return { authorization, signature };\n }\n\n /**\n * Check spending limits\n */\n private checkLimits(amount: number): void {\n // Check per-tx limit\n if (amount > this.config.limits.maxPerTx) {\n throw new Error(\n `Amount $${amount} exceeds max per transaction ($${this.config.limits.maxPerTx})`\n );\n }\n\n // Reset daily spending if new day\n const today = new Date().setHours(0, 0, 0, 0);\n if (today > this.lastSpendingReset) {\n this.todaySpending = 0;\n this.lastSpendingReset = today;\n this.saveSpending(); // Persist reset\n }\n\n // Check daily limit\n if (this.todaySpending + amount > this.config.limits.maxPerDay) {\n throw new Error(\n `Would exceed daily limit ($${this.todaySpending} + $${amount} > $${this.config.limits.maxPerDay})`\n );\n }\n }\n\n /**\n * Record spending and persist to disk\n */\n private recordSpending(amount: number): void {\n this.todaySpending += amount;\n this.saveSpending();\n }\n\n // --- Config & Wallet Management ---\n\n private loadConfig(): ClientConfig {\n const configPath = join(this.configDir, 'config.json');\n if (existsSync(configPath)) {\n const content = readFileSync(configPath, 'utf-8');\n return { ...DEFAULT_CONFIG, ...JSON.parse(content) };\n }\n return { ...DEFAULT_CONFIG };\n }\n\n private saveConfig(): void {\n mkdirSync(this.configDir, { recursive: true });\n const configPath = join(this.configDir, 'config.json');\n writeFileSync(configPath, JSON.stringify(this.config, null, 2));\n }\n\n /**\n * Load spending data from disk\n */\n private loadSpending(): void {\n const spendingPath = join(this.configDir, 'spending.json');\n if (existsSync(spendingPath)) {\n try {\n const data = JSON.parse(readFileSync(spendingPath, 'utf-8'));\n const today = new Date().setHours(0, 0, 0, 0);\n \n // Only load if it's from today\n if (data.date && data.date === today) {\n this.todaySpending = data.amount || 0;\n this.lastSpendingReset = data.date;\n } else {\n // Data is from a previous day, reset\n this.todaySpending = 0;\n this.lastSpendingReset = today;\n }\n } catch {\n // Ignore parse errors, start fresh\n this.todaySpending = 0;\n this.lastSpendingReset = new Date().setHours(0, 0, 0, 0);\n }\n }\n }\n\n /**\n * Save spending data to disk\n */\n private saveSpending(): void {\n mkdirSync(this.configDir, { recursive: true });\n const spendingPath = join(this.configDir, 'spending.json');\n const data = {\n date: this.lastSpendingReset || new Date().setHours(0, 0, 0, 0),\n amount: this.todaySpending,\n updatedAt: Date.now(),\n };\n writeFileSync(spendingPath, JSON.stringify(data, null, 2));\n }\n\n private loadWallet(): WalletData | null {\n const walletPath = join(this.configDir, 'wallet.json');\n if (existsSync(walletPath)) {\n // Security check: warn and fix if permissions are too open\n try {\n const stats = statSync(walletPath);\n const mode = stats.mode & 0o777;\n if (mode !== 0o600) {\n console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);\n console.warn(`[MoltsPay] Fixing permissions to 0600...`);\n chmodSync(walletPath, 0o600);\n }\n } catch (err) {\n // Ignore permission check errors on Windows\n }\n \n const content = readFileSync(walletPath, 'utf-8');\n return JSON.parse(content);\n }\n return null;\n }\n\n /**\n * Initialize a new wallet (called by CLI)\n */\n static init(\n configDir: string,\n options: { chain: string; maxPerTx: number; maxPerDay: number }\n ): { address: string; configDir: string } {\n mkdirSync(configDir, { recursive: true });\n\n // Create wallet\n const wallet = Wallet.createRandom();\n const walletData: WalletData = {\n address: wallet.address,\n privateKey: wallet.privateKey,\n createdAt: Date.now(),\n };\n\n // Save wallet with secure permissions (0o600 = owner read/write only)\n const walletPath = join(configDir, 'wallet.json');\n writeFileSync(walletPath, JSON.stringify(walletData, null, 2), { mode: 0o600 });\n\n // Save config\n const config: ClientConfig = {\n chain: options.chain,\n limits: {\n maxPerTx: options.maxPerTx,\n maxPerDay: options.maxPerDay,\n },\n };\n const configPath = join(configDir, 'config.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2));\n\n return { address: wallet.address, configDir };\n }\n\n /**\n * Get wallet balance (USDC, USDT, and native token) on default chain\n */\n async getBalance(): Promise<{ usdc: number; usdt: number; native: number }> {\n if (!this.wallet) {\n throw new Error('Client not initialized');\n }\n\n let chain;\n try {\n chain = getChain(this.config.chain as ChainName);\n } catch {\n throw new Error(`Unknown chain: ${this.config.chain}`);\n }\n\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n const tokenAbi = ['function balanceOf(address) view returns (uint256)'];\n\n // Get all balances in parallel\n const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([\n provider.getBalance(this.wallet.address),\n new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),\n new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address),\n ]);\n\n return {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),\n usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\n }\n\n /**\n * Get wallet balances on all supported chains (Base + Polygon)\n */\n async getAllBalances(): Promise<Record<string, { usdc: number; usdt: number; native: number }>> {\n if (!this.wallet) {\n throw new Error('Client not initialized');\n }\n\n const supportedChains: ChainName[] = ['base', 'polygon'];\n const tokenAbi = ['function balanceOf(address) view returns (uint256)'];\n const results: Record<string, { usdc: number; usdt: number; native: number }> = {};\n\n // Query all chains in parallel\n await Promise.all(\n supportedChains.map(async (chainName) => {\n try {\n const chain = getChain(chainName);\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n\n const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([\n provider.getBalance(this.wallet!.address),\n new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet!.address),\n new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet!.address),\n ]);\n\n results[chainName] = {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),\n usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\n } catch (err) {\n // If chain query fails, show zeros\n results[chainName] = { usdc: 0, usdt: 0, native: 0 };\n }\n })\n );\n\n return results;\n }\n}\n","/**\n * Blockchain Configuration\n */\n\nimport type { ChainConfig, ChainName, TokenSymbol } from '../types/index.js';\n\nexport const CHAINS: Record<ChainName, ChainConfig> = {\n // ============ Mainnet ============\n base: {\n name: 'Base',\n chainId: 8453,\n rpc: 'https://mainnet.base.org',\n tokens: {\n USDC: {\n address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // deprecated, for backward compat\n explorer: 'https://basescan.org/address/',\n explorerTx: 'https://basescan.org/tx/',\n avgBlockTime: 2,\n },\n polygon: {\n name: 'Polygon',\n chainId: 137,\n rpc: 'https://polygon-bor-rpc.publicnode.com',\n tokens: {\n USDC: {\n address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n explorer: 'https://polygonscan.com/address/',\n explorerTx: 'https://polygonscan.com/tx/',\n avgBlockTime: 2,\n },\n ethereum: {\n name: 'Ethereum',\n chainId: 1,\n rpc: 'https://eth.llamarpc.com',\n tokens: {\n USDC: {\n address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n explorer: 'https://etherscan.io/address/',\n explorerTx: 'https://etherscan.io/tx/',\n avgBlockTime: 12,\n },\n\n // ============ Testnet ============\n base_sepolia: {\n name: 'Base Sepolia',\n chainId: 84532,\n rpc: 'https://sepolia.base.org',\n tokens: {\n USDC: {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // Same as USDC on testnet (no official USDT)\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n explorer: 'https://sepolia.basescan.org/address/',\n explorerTx: 'https://sepolia.basescan.org/tx/',\n avgBlockTime: 2,\n },\n sepolia: {\n name: 'Sepolia',\n chainId: 11155111,\n rpc: 'https://rpc.sepolia.org',\n tokens: {\n USDC: {\n address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',\n decimals: 6,\n symbol: 'USDC',\n },\n USDT: {\n address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', // Same as USDC on testnet\n decimals: 6,\n symbol: 'USDT',\n },\n },\n usdc: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',\n explorer: 'https://sepolia.etherscan.io/address/',\n explorerTx: 'https://sepolia.etherscan.io/tx/',\n avgBlockTime: 12,\n },\n};\n\n/**\n * Get token address for a chain\n */\nexport function getTokenAddress(chainName: ChainName, token: TokenSymbol): string {\n const chain = CHAINS[chainName];\n if (!chain) {\n throw new Error(`Unsupported chain: ${chainName}`);\n }\n const tokenConfig = chain.tokens[token];\n if (!tokenConfig) {\n throw new Error(`Token ${token} not supported on ${chainName}`);\n }\n return tokenConfig.address;\n}\n\n/**\n * Get token config for a chain\n */\nexport function getTokenConfig(chainName: ChainName, token: TokenSymbol) {\n const chain = CHAINS[chainName];\n if (!chain) {\n throw new Error(`Unsupported chain: ${chainName}`);\n }\n return chain.tokens[token];\n}\n\n/**\n * Get chain configuration\n */\nexport function getChain(name: ChainName): ChainConfig {\n const config = CHAINS[name];\n if (!config) {\n throw new Error(`Unsupported chain: ${name}. Supported: ${Object.keys(CHAINS).join(', ')}`);\n }\n return config;\n}\n\n/**\n * List all supported chains\n */\nexport function listChains(): ChainName[] {\n return Object.keys(CHAINS) as ChainName[];\n}\n\n/**\n * Get chain config by chainId\n */\nexport function getChainById(chainId: number): ChainConfig | undefined {\n return Object.values(CHAINS).find(c => c.chainId === chainId);\n}\n\n/**\n * ERC20 ABI (minimal, only required methods)\n */\nexport const ERC20_ABI = [\n 'function balanceOf(address owner) view returns (uint256)',\n 'function transfer(address to, uint256 amount) returns (bool)',\n 'function approve(address spender, uint256 amount) returns (bool)',\n 'function allowance(address owner, address spender) view returns (uint256)',\n 'function decimals() view returns (uint8)',\n 'function symbol() view returns (string)',\n 'function name() view returns (string)',\n 'function nonces(address owner) view returns (uint256)',\n 'function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)',\n 'event Transfer(address indexed from, address indexed to, uint256 value)',\n 'event Approval(address indexed owner, address indexed spender, uint256 value)',\n];\n\nexport type { ChainConfig, ChainName, TokenSymbol };\n"],"mappings":";AAWA,SAAS,YAAY,cAAc,eAAe,WAAW,UAAU,iBAAiB;AACxF,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,QAAQ,cAAc;;;ACRxB,IAAM,SAAyC;AAAA;AAAA,EAEpD,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,KAAK;AAAA,IACL,QAAQ;AAAA,MACN,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,MAAM;AAAA;AAAA,IACN,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,SAAS;AAAA,IACT,KAAK;AAAA,IACL,QAAQ;AAAA,MACN,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,MAAM;AAAA,IACN,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB;AAAA,EACA,UAAU;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,IACT,KAAK;AAAA,IACL,QAAQ;AAAA,MACN,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,MAAM;AAAA,IACN,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB;AAAA;AAAA,EAGA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,KAAK;AAAA,IACL,QAAQ;AAAA,MACN,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,MAAM;AAAA,IACN,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,SAAS;AAAA,IACT,KAAK;AAAA,IACL,QAAQ;AAAA,MACN,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,MAAM;AAAA,IACN,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB;AACF;AA+BO,SAAS,SAAS,MAA8B;AACrD,QAAM,SAAS,OAAO,IAAI;AAC1B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,sBAAsB,IAAI,gBAAgB,OAAO,KAAK,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EAC5F;AACA,SAAO;AACT;;;ADrHA,IAAM,eAAe;AACrB,IAAM,0BAA0B;AAChC,IAAM,iBAAiB;AA0BvB,IAAM,iBAA+B;AAAA,EACnC,OAAO;AAAA,EACP,QAAQ;AAAA,IACN,UAAU;AAAA,IACV,WAAW;AAAA,EACb;AACF;AAEO,IAAM,iBAAN,MAAqB;AAAA,EAClB;AAAA,EACA;AAAA,EACA,aAAgC;AAAA,EAChC,SAAwB;AAAA,EACxB,gBAAwB;AAAA,EACxB,oBAA4B;AAAA,EAEpC,YAAY,UAAiC,CAAC,GAAG;AAC/C,SAAK,YAAY,QAAQ,aAAa,KAAK,QAAQ,GAAG,WAAW;AACjE,SAAK,SAAS,KAAK,WAAW;AAC9B,SAAK,aAAa,KAAK,WAAW;AAClC,SAAK,aAAa;AAElB,QAAI,KAAK,YAAY;AACnB,WAAK,SAAS,IAAI,OAAO,KAAK,WAAW,UAAU;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,gBAAyB;AAC3B,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAyB;AAC3B,WAAO,KAAK,QAAQ,WAAW;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,YAA0B;AACxB,WAAO,EAAE,GAAG,KAAK,OAAO;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,SAAgD;AAC3D,QAAI,QAAQ,aAAa,QAAW;AAClC,WAAK,OAAO,OAAO,WAAW,QAAQ;AAAA,IACxC;AACA,QAAI,QAAQ,cAAc,QAAW;AACnC,WAAK,OAAO,OAAO,YAAY,QAAQ;AAAA,IACzC;AACA,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,WAA8C;AAE9D,UAAM,gBAAgB,UAAU,QAAQ,qDAAqD,EAAE;AAG/F,UAAM,YAAY,CAAC,aAAa,iBAAiB,oBAAoB;AAErE,eAAW,YAAY,WAAW;AAChC,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG,aAAa,GAAG,QAAQ,EAAE;AACrD,YAAI,CAAC,IAAI,GAAI;AAEb,cAAM,cAAc,IAAI,QAAQ,IAAI,cAAc,KAAK;AACvD,YAAI,CAAC,YAAY,SAAS,kBAAkB,EAAG;AAE/C,eAAO,MAAM,IAAI,KAAK;AAAA,MACxB,QAAQ;AACN;AAAA,MACF;AAAA,IACF;AAEA,UAAM,IAAI,MAAM,sDAAsD,aAAa,EAAE;AAAA,EACvF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,IACJ,WACA,SACA,QACA,UAAsB,CAAC,GACO;AAC9B,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,YAAY;AACpC,YAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AAGA,YAAQ,IAAI,kCAAkC,OAAO,EAAE;AACvD,UAAM,cAAmB,EAAE,SAAS,OAAO;AAC3C,QAAI,QAAQ,OAAO;AACjB,kBAAY,QAAQ,QAAQ;AAAA,IAC9B;AACA,UAAM,aAAa,MAAM,MAAM,GAAG,SAAS,YAAY;AAAA,MACrD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,WAAW;AAAA,IAClC,CAAC;AAGD,QAAI,WAAW,WAAW,KAAK;AAC7B,YAAM,OAAO,MAAM,WAAW,KAAK;AACnC,UAAI,WAAW,MAAM,KAAK,QAAQ;AAChC,eAAO,KAAK;AAAA,MACd;AACA,YAAM,IAAI,MAAM,KAAK,SAAS,qBAAqB;AAAA,IACrD;AAGA,UAAM,wBAAwB,WAAW,QAAQ,IAAI,uBAAuB;AAC5E,QAAI,CAAC,uBAAuB;AAC1B,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,UAAU,OAAO,KAAK,uBAAuB,QAAQ,EAAE,SAAS,OAAO;AAC7E,YAAM,SAAS,KAAK,MAAM,OAAO;AAGjC,UAAI,MAAM,QAAQ,MAAM,GAAG;AAEzB,uBAAe;AAAA,MACjB,WAAW,OAAO,WAAW,MAAM,QAAQ,OAAO,OAAO,GAAG;AAE1D,uBAAe,OAAO;AAAA,MACxB,OAAO;AAEL,uBAAe,CAAC,MAAM;AAAA,MACxB;AAAA,IACF,QAAQ;AACN,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAGA,UAAM,qBAAqB,CAACA,aAAmC;AAC7D,YAAM,QAAQA,SAAQ,MAAM,gBAAgB;AAC5C,UAAI,CAAC,MAAO,QAAO;AACnB,YAAM,UAAU,SAAS,MAAM,CAAC,CAAC;AACjC,UAAI,YAAY,KAAM,QAAO;AAC7B,UAAI,YAAY,IAAK,QAAO;AAC5B,UAAI,YAAY,MAAO,QAAO;AAC9B,aAAO;AAAA,IACT;AAGA,UAAM,eAAe,aAClB,IAAI,OAAK,mBAAmB,EAAE,OAAO,CAAC,EACtC,OAAO,CAAC,MAAmB,MAAM,IAAI;AAGxC,QAAI;AACJ,UAAM,qBAAqB,QAAQ;AAEnC,QAAI,oBAAoB;AAEtB,UAAI,CAAC,aAAa,SAAS,kBAAkB,GAAG;AAC9C,cAAM,IAAI;AAAA,UACR,0BAA0B,kBAAkB;AAAA,kBACzB,aAAa,KAAK,IAAI,CAAC;AAAA,QAC5C;AAAA,MACF;AACA,kBAAY;AAAA,IACd,OAAO;AAEL,UAAI,aAAa,WAAW,KAAK,aAAa,CAAC,MAAM,QAAQ;AAE3D,oBAAY;AAAA,MACd,OAAO;AACL,cAAM,IAAI;AAAA,UACR,mBAAmB,aAAa,KAAK,IAAI,CAAC;AAAA;AAAA,QAE5C;AAAA,MACF;AAAA,IACF;AAEA,UAAM,QAAQ,SAAS,SAAS;AAChC,UAAM,UAAU,UAAU,MAAM,OAAO;AACvC,UAAM,MAAM,aAAa,KAAK,OAAK,EAAE,WAAW,WAAW,EAAE,YAAY,OAAO;AAEhF,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,0CAA0C,SAAS,EAAE;AAAA,IACvE;AAIA,UAAM,YAAY,IAAI,UAAU,IAAI;AACpC,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AACA,UAAM,SAAS,OAAO,SAAS,IAAI;AACnC,SAAK,YAAY,MAAM;AAGvB,QAAI,QAAqB,QAAQ,SAAS;AAG1C,QAAI,QAAQ,YAAY;AACtB,YAAM,WAAW,MAAM,KAAK,WAAW;AACvC,UAAI,SAAS,QAAQ,QAAQ;AAC3B,gBAAQ;AAAA,MACV,WAAW,SAAS,QAAQ,QAAQ;AAClC,gBAAQ;AAAA,MACV,OAAO;AACL,cAAM,IAAI,MAAM,+BAA+B,MAAM,UAAU,SAAS,IAAI,WAAW,SAAS,IAAI,OAAO;AAAA,MAC7G;AAAA,IACF;AAIA,QAAI,UAAU,QAAQ;AACpB,YAAM,WAAW,MAAM,KAAK,WAAW;AACvC,UAAI,SAAS,SAAS,MAAQ;AAC5B,cAAM,IAAI;AAAA,UACR,iEACqB,SAAS,OAAO,QAAQ,CAAC,CAAC;AAAA,QAEjD;AAAA,MACF;AACA,cAAQ,IAAI,iFAAuE;AAAA,IACrF,OAAO;AACL,cAAQ,IAAI,gCAAgC,MAAM,IAAI,KAAK,YAAY;AAAA,IACzE;AAIA,UAAM,QAAQ,IAAI,SAAS,IAAI;AAC/B,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AACA,UAAM,gBAAgB,MAAM,KAAK,YAAY,OAAO,QAAQ,OAAO,KAAK;AAGxE,UAAM,cAAc,MAAM,OAAO,KAAK;AACtC,UAAM,YAAY,UAAU,SAAS,aAAa;AAGlD,UAAM,UAAU;AAAA,MACd,aAAa;AAAA,MACb,SAAS;AAAA;AAAA,MAET,UAAU;AAAA,QACR,QAAQ;AAAA,QACR;AAAA,QACA,OAAO,YAAY;AAAA,QACnB,QAAQ;AAAA,QACR;AAAA,QACA,mBAAmB,IAAI,qBAAqB;AAAA,QAC5C,OAAO,IAAI,SAAS,EAAE,MAAM,WAAW,SAAS,IAAI;AAAA,MACtD;AAAA,IACF;AACA,UAAM,gBAAgB,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC,EAAE,SAAS,QAAQ;AAG5E,YAAQ,IAAI,4CAA4C;AACxD,UAAM,kBAAuB,EAAE,SAAS,OAAO;AAC/C,QAAI,QAAQ,OAAO;AACjB,sBAAgB,QAAQ,QAAQ;AAAA,IAClC;AACA,UAAM,UAAU,MAAM,MAAM,GAAG,SAAS,YAAY;AAAA,MAClD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,CAAC,cAAc,GAAG;AAAA,MACpB;AAAA,MACA,MAAM,KAAK,UAAU,eAAe;AAAA,IACtC,CAAC;AAED,UAAM,SAAS,MAAM,QAAQ,KAAK;AAElC,QAAI,CAAC,QAAQ,IAAI;AACf,YAAM,IAAI,MAAM,OAAO,SAAS,0BAA0B;AAAA,IAC5D;AAGA,SAAK,eAAe,MAAM;AAE1B,YAAQ,IAAI,gCAAgC,OAAO,SAAS,UAAU,SAAS,EAAE;AAEjF,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YACZ,IACA,QACA,OACA,QAAqB,QACgD;AACrE,UAAM,aAAa;AACnB,UAAM,cAAc,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI;AACpD,UAAM,QAAQ,OAAO,QAAQ,OAAO,YAAY,EAAE,CAAC;AAEnD,UAAM,cAAc,MAAM,OAAO,KAAK;AACtC,UAAM,QAAQ,OAAO,KAAK,MAAM,SAAU,MAAM,YAAY,QAAS,CAAC,EAAE,SAAS;AAEjF,UAAM,gBAAsC;AAAA,MAC1C,MAAM,KAAK,OAAQ;AAAA,MACnB;AAAA,MACA;AAAA,MACA,YAAY,WAAW,SAAS;AAAA,MAChC,aAAa,YAAY,SAAS;AAAA,MAClC;AAAA,IACF;AAGA,UAAM,YAAY,UAAU,SAAS,aAAa;AAClD,UAAM,SAAS;AAAA,MACb,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS,MAAM;AAAA,MACf,mBAAmB,YAAY;AAAA,IACjC;AAGA,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,YAAY,MAAM,KAAK,OAAQ,cAAc,QAAQ,OAAO,aAAa;AAE/E,WAAO,EAAE,eAAe,UAAU;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,QAAsB;AAExC,QAAI,SAAS,KAAK,OAAO,OAAO,UAAU;AACxC,YAAM,IAAI;AAAA,QACR,WAAW,MAAM,kCAAkC,KAAK,OAAO,OAAO,QAAQ;AAAA,MAChF;AAAA,IACF;AAGA,UAAM,SAAQ,oBAAI,KAAK,GAAE,SAAS,GAAG,GAAG,GAAG,CAAC;AAC5C,QAAI,QAAQ,KAAK,mBAAmB;AAClC,WAAK,gBAAgB;AACrB,WAAK,oBAAoB;AACzB,WAAK,aAAa;AAAA,IACpB;AAGA,QAAI,KAAK,gBAAgB,SAAS,KAAK,OAAO,OAAO,WAAW;AAC9D,YAAM,IAAI;AAAA,QACR,8BAA8B,KAAK,aAAa,OAAO,MAAM,OAAO,KAAK,OAAO,OAAO,SAAS;AAAA,MAClG;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAAsB;AAC3C,SAAK,iBAAiB;AACtB,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA,EAIQ,aAA2B;AACjC,UAAM,aAAa,KAAK,KAAK,WAAW,aAAa;AACrD,QAAI,WAAW,UAAU,GAAG;AAC1B,YAAM,UAAU,aAAa,YAAY,OAAO;AAChD,aAAO,EAAE,GAAG,gBAAgB,GAAG,KAAK,MAAM,OAAO,EAAE;AAAA,IACrD;AACA,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AAAA,EAEQ,aAAmB;AACzB,cAAU,KAAK,WAAW,EAAE,WAAW,KAAK,CAAC;AAC7C,UAAM,aAAa,KAAK,KAAK,WAAW,aAAa;AACrD,kBAAc,YAAY,KAAK,UAAU,KAAK,QAAQ,MAAM,CAAC,CAAC;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAqB;AAC3B,UAAM,eAAe,KAAK,KAAK,WAAW,eAAe;AACzD,QAAI,WAAW,YAAY,GAAG;AAC5B,UAAI;AACF,cAAM,OAAO,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAC3D,cAAM,SAAQ,oBAAI,KAAK,GAAE,SAAS,GAAG,GAAG,GAAG,CAAC;AAG5C,YAAI,KAAK,QAAQ,KAAK,SAAS,OAAO;AACpC,eAAK,gBAAgB,KAAK,UAAU;AACpC,eAAK,oBAAoB,KAAK;AAAA,QAChC,OAAO;AAEL,eAAK,gBAAgB;AACrB,eAAK,oBAAoB;AAAA,QAC3B;AAAA,MACF,QAAQ;AAEN,aAAK,gBAAgB;AACrB,aAAK,qBAAoB,oBAAI,KAAK,GAAE,SAAS,GAAG,GAAG,GAAG,CAAC;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAqB;AAC3B,cAAU,KAAK,WAAW,EAAE,WAAW,KAAK,CAAC;AAC7C,UAAM,eAAe,KAAK,KAAK,WAAW,eAAe;AACzD,UAAM,OAAO;AAAA,MACX,MAAM,KAAK,sBAAqB,oBAAI,KAAK,GAAE,SAAS,GAAG,GAAG,GAAG,CAAC;AAAA,MAC9D,QAAQ,KAAK;AAAA,MACb,WAAW,KAAK,IAAI;AAAA,IACtB;AACA,kBAAc,cAAc,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EAC3D;AAAA,EAEQ,aAAgC;AACtC,UAAM,aAAa,KAAK,KAAK,WAAW,aAAa;AACrD,QAAI,WAAW,UAAU,GAAG;AAE1B,UAAI;AACF,cAAM,QAAQ,SAAS,UAAU;AACjC,cAAM,OAAO,MAAM,OAAO;AAC1B,YAAI,SAAS,KAAO;AAClB,kBAAQ,KAAK,6DAA6D,KAAK,SAAS,CAAC,CAAC,GAAG;AAC7F,kBAAQ,KAAK,0CAA0C;AACvD,oBAAU,YAAY,GAAK;AAAA,QAC7B;AAAA,MACF,SAAS,KAAK;AAAA,MAEd;AAEA,YAAM,UAAU,aAAa,YAAY,OAAO;AAChD,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,KACL,WACA,SACwC;AACxC,cAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAGxC,UAAM,SAAS,OAAO,aAAa;AACnC,UAAM,aAAyB;AAAA,MAC7B,SAAS,OAAO;AAAA,MAChB,YAAY,OAAO;AAAA,MACnB,WAAW,KAAK,IAAI;AAAA,IACtB;AAGA,UAAM,aAAa,KAAK,WAAW,aAAa;AAChD,kBAAc,YAAY,KAAK,UAAU,YAAY,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAG9E,UAAM,SAAuB;AAAA,MAC3B,OAAO,QAAQ;AAAA,MACf,QAAQ;AAAA,QACN,UAAU,QAAQ;AAAA,QAClB,WAAW,QAAQ;AAAA,MACrB;AAAA,IACF;AACA,UAAM,aAAa,KAAK,WAAW,aAAa;AAChD,kBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAEzD,WAAO,EAAE,SAAS,OAAO,SAAS,UAAU;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAsE;AAC1E,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,wBAAwB;AAAA,IAC1C;AAEA,QAAI;AACJ,QAAI;AACF,cAAQ,SAAS,KAAK,OAAO,KAAkB;AAAA,IACjD,QAAQ;AACN,YAAM,IAAI,MAAM,kBAAkB,KAAK,OAAO,KAAK,EAAE;AAAA,IACvD;AAEA,UAAM,WAAW,IAAI,OAAO,gBAAgB,MAAM,GAAG;AACrD,UAAM,WAAW,CAAC,oDAAoD;AAGtE,UAAM,CAAC,eAAe,aAAa,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,MAClE,SAAS,WAAW,KAAK,OAAO,OAAO;AAAA,MACvC,IAAI,OAAO,SAAS,MAAM,OAAO,KAAK,SAAS,UAAU,QAAQ,EAAE,UAAU,KAAK,OAAO,OAAO;AAAA,MAChG,IAAI,OAAO,SAAS,MAAM,OAAO,KAAK,SAAS,UAAU,QAAQ,EAAE,UAAU,KAAK,OAAO,OAAO;AAAA,IAClG,CAAC;AAED,WAAO;AAAA,MACL,MAAM,WAAW,OAAO,YAAY,aAAa,MAAM,OAAO,KAAK,QAAQ,CAAC;AAAA,MAC5E,MAAM,WAAW,OAAO,YAAY,aAAa,MAAM,OAAO,KAAK,QAAQ,CAAC;AAAA,MAC5E,QAAQ,WAAW,OAAO,YAAY,aAAa,CAAC;AAAA,IACtD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAA0F;AAC9F,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,wBAAwB;AAAA,IAC1C;AAEA,UAAM,kBAA+B,CAAC,QAAQ,SAAS;AACvD,UAAM,WAAW,CAAC,oDAAoD;AACtE,UAAM,UAA0E,CAAC;AAGjF,UAAM,QAAQ;AAAA,MACZ,gBAAgB,IAAI,OAAO,cAAc;AACvC,YAAI;AACF,gBAAM,QAAQ,SAAS,SAAS;AAChC,gBAAM,WAAW,IAAI,OAAO,gBAAgB,MAAM,GAAG;AAErD,gBAAM,CAAC,eAAe,aAAa,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,YAClE,SAAS,WAAW,KAAK,OAAQ,OAAO;AAAA,YACxC,IAAI,OAAO,SAAS,MAAM,OAAO,KAAK,SAAS,UAAU,QAAQ,EAAE,UAAU,KAAK,OAAQ,OAAO;AAAA,YACjG,IAAI,OAAO,SAAS,MAAM,OAAO,KAAK,SAAS,UAAU,QAAQ,EAAE,UAAU,KAAK,OAAQ,OAAO;AAAA,UACnG,CAAC;AAED,kBAAQ,SAAS,IAAI;AAAA,YACnB,MAAM,WAAW,OAAO,YAAY,aAAa,MAAM,OAAO,KAAK,QAAQ,CAAC;AAAA,YAC5E,MAAM,WAAW,OAAO,YAAY,aAAa,MAAM,OAAO,KAAK,QAAQ,CAAC;AAAA,YAC5E,QAAQ,WAAW,OAAO,YAAY,aAAa,CAAC;AAAA,UACtD;AAAA,QACF,SAAS,KAAK;AAEZ,kBAAQ,SAAS,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,EAAE;AAAA,QACrD;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;","names":["network"]}
|
package/dist/index.js
CHANGED
|
@@ -31798,11 +31798,20 @@ var MoltsPayClient = class {
|
|
|
31798
31798
|
* Get services from a provider
|
|
31799
31799
|
*/
|
|
31800
31800
|
async getServices(serverUrl) {
|
|
31801
|
-
const
|
|
31802
|
-
|
|
31803
|
-
|
|
31801
|
+
const normalizedUrl = serverUrl.replace(/\/(services|api\/services|registry\/services)\/?$/, "");
|
|
31802
|
+
const endpoints = ["/services", "/api/services", "/registry/services"];
|
|
31803
|
+
for (const endpoint of endpoints) {
|
|
31804
|
+
try {
|
|
31805
|
+
const res = await fetch(`${normalizedUrl}${endpoint}`);
|
|
31806
|
+
if (!res.ok) continue;
|
|
31807
|
+
const contentType = res.headers.get("content-type") || "";
|
|
31808
|
+
if (!contentType.includes("application/json")) continue;
|
|
31809
|
+
return await res.json();
|
|
31810
|
+
} catch {
|
|
31811
|
+
continue;
|
|
31812
|
+
}
|
|
31804
31813
|
}
|
|
31805
|
-
|
|
31814
|
+
throw new Error(`Failed to get services: no valid endpoint found at ${normalizedUrl}`);
|
|
31806
31815
|
}
|
|
31807
31816
|
/**
|
|
31808
31817
|
* Pay for a service and get the result (x402 protocol)
|