moltspay 0.7.2 → 0.8.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 +253 -259
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +255 -261
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +11 -5
- package/dist/client/index.d.ts +11 -5
- package/dist/client/index.js +99 -68
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +96 -55
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.js +387 -321
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +391 -325
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +17 -8
- package/dist/server/index.d.ts +17 -8
- package/dist/server/index.js +145 -194
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +145 -194
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/index.ts","../../src/verify/index.ts","../../src/chains/index.ts"],"sourcesContent":["/**\n * MoltsPay Server - Payment infrastructure for AI Agents\n * \n * Usage:\n * const server = new MoltsPayServer('./moltspay.services.json');\n * server.skill('text-to-video', async (params) => { ... });\n * server.listen(3000);\n */\n\nimport { readFileSync } from 'fs';\nimport { createServer, IncomingMessage, ServerResponse } from 'http';\nimport { verifyPayment } from '../verify/index.js';\nimport {\n ServicesManifest,\n ServiceConfig,\n SkillFunction,\n RegisteredSkill,\n Charge,\n ChargeStatus,\n PaymentRequest,\n MoltsPayServerOptions,\n} from './types.js';\n\nexport * from './types.js';\n\nfunction generateChargeId(): string {\n return 'ch_' + Math.random().toString(36).substring(2, 15);\n}\n\nexport class MoltsPayServer {\n private manifest: ServicesManifest;\n private skills: Map<string, RegisteredSkill> = new Map();\n private charges: Map<string, Charge> = new Map();\n private options: MoltsPayServerOptions;\n\n constructor(servicesPath: string, options: MoltsPayServerOptions = {}) {\n // Load services manifest\n const content = readFileSync(servicesPath, 'utf-8');\n this.manifest = JSON.parse(content) as ServicesManifest;\n \n this.options = {\n port: options.port || 3000,\n host: options.host || '0.0.0.0',\n chargeExpirySecs: options.chargeExpirySecs || 300, // 5 minutes\n };\n\n console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);\n console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);\n console.log(`[MoltsPay] Wallet: ${this.manifest.provider.wallet}`);\n }\n\n /**\n * Register a skill handler for a service\n */\n skill(serviceId: string, handler: SkillFunction): this {\n const config = this.manifest.services.find(s => s.id === serviceId);\n if (!config) {\n throw new Error(`Service '${serviceId}' not found in manifest`);\n }\n\n this.skills.set(serviceId, {\n id: serviceId,\n config,\n handler,\n });\n\n console.log(`[MoltsPay] Registered skill: ${serviceId} ($${config.price} ${config.currency})`);\n return this;\n }\n\n /**\n * Start the server\n */\n listen(port?: number): void {\n const p = port || this.options.port!;\n \n const server = createServer((req, res) => this.handleRequest(req, res));\n \n server.listen(p, this.options.host, () => {\n console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);\n console.log(`[MoltsPay] Endpoints:`);\n console.log(` GET /services - List available services`);\n console.log(` POST /pay - Create payment & execute service`);\n console.log(` POST /verify - Verify payment & get result`);\n console.log(` GET /status/:id - Check charge status`);\n });\n }\n\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = new URL(req.url || '/', `http://${req.headers.host}`);\n const path = url.pathname;\n const method = req.method || 'GET';\n\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n try {\n if (method === 'GET' && path === '/services') {\n return this.handleGetServices(res);\n }\n\n if (method === 'POST' && path === '/pay') {\n const body = await this.readBody(req);\n return this.handlePay(body, res);\n }\n\n if (method === 'POST' && path === '/verify') {\n const body = await this.readBody(req);\n return this.handleVerify(body, res);\n }\n\n if (method === 'GET' && path.startsWith('/status/')) {\n const chargeId = path.replace('/status/', '');\n return this.handleStatus(chargeId, res);\n }\n\n // Not found\n this.sendJson(res, 404, { error: 'Not found' });\n } catch (err: any) {\n console.error('[MoltsPay] Error:', err);\n this.sendJson(res, 500, { error: err.message || 'Internal error' });\n }\n }\n\n /**\n * GET /services - List available services\n */\n private handleGetServices(res: ServerResponse): void {\n const services = this.manifest.services.map(s => ({\n id: s.id,\n name: s.name,\n description: s.description,\n price: s.price,\n currency: s.currency,\n input: s.input,\n output: s.output,\n available: this.skills.has(s.id),\n }));\n\n this.sendJson(res, 200, {\n provider: this.manifest.provider,\n services,\n });\n }\n\n /**\n * POST /pay - Create payment request\n * Body: { service: string, params: object }\n */\n private handlePay(body: any, res: ServerResponse): void {\n const { service, params } = body;\n\n if (!service) {\n return this.sendJson(res, 400, { error: 'Missing service' });\n }\n\n const skill = this.skills.get(service);\n if (!skill) {\n return this.sendJson(res, 404, { error: `Service '${service}' not found or not registered` });\n }\n\n // Validate required params\n for (const [key, field] of Object.entries(skill.config.input)) {\n if (field.required && (!params || params[key] === undefined)) {\n return this.sendJson(res, 400, { error: `Missing required param: ${key}` });\n }\n }\n\n // Create charge\n const chargeId = generateChargeId();\n const now = Date.now();\n const charge: Charge = {\n id: chargeId,\n service,\n params: params || {},\n amount: skill.config.price,\n currency: skill.config.currency,\n status: 'pending',\n createdAt: now,\n expiresAt: now + (this.options.chargeExpirySecs! * 1000),\n };\n\n this.charges.set(chargeId, charge);\n\n // Return payment request\n const paymentRequest: PaymentRequest = {\n chargeId,\n service,\n amount: charge.amount,\n currency: charge.currency,\n wallet: this.manifest.provider.wallet,\n chain: this.manifest.provider.chain,\n expiresAt: charge.expiresAt,\n };\n\n this.sendJson(res, 402, {\n message: 'Payment required',\n payment: paymentRequest,\n });\n }\n\n /**\n * POST /verify - Verify payment and execute skill\n * Body: { chargeId: string, txHash: string }\n */\n private async handleVerify(body: any, res: ServerResponse): Promise<void> {\n const { chargeId, txHash } = body;\n\n if (!chargeId || !txHash) {\n return this.sendJson(res, 400, { error: 'Missing chargeId or txHash' });\n }\n\n const charge = this.charges.get(chargeId);\n if (!charge) {\n return this.sendJson(res, 404, { error: 'Charge not found' });\n }\n\n // Check expiry\n if (Date.now() > charge.expiresAt) {\n charge.status = 'expired';\n return this.sendJson(res, 400, { error: 'Charge expired' });\n }\n\n // Check if already paid\n if (charge.status === 'completed') {\n return this.sendJson(res, 200, {\n status: 'completed',\n result: charge.result,\n });\n }\n\n try {\n const verification = await verifyPayment({\n txHash,\n expectedTo: this.manifest.provider.wallet,\n expectedAmount: charge.amount,\n chain: this.manifest.provider.chain,\n });\n\n if (!verification.verified) {\n charge.status = 'failed';\n return this.sendJson(res, 400, {\n error: 'Payment verification failed',\n reason: verification.error,\n });\n }\n\n // Payment verified - update charge\n charge.status = 'paid';\n charge.txHash = txHash;\n charge.paidAt = Date.now();\n\n // Execute skill\n const skill = this.skills.get(charge.service)!;\n console.log(`[MoltsPay] Executing skill: ${charge.service}`);\n \n const result = await skill.handler(charge.params);\n \n charge.status = 'completed';\n charge.result = result;\n charge.completedAt = Date.now();\n\n this.sendJson(res, 200, {\n status: 'completed',\n chargeId,\n txHash,\n result,\n });\n } catch (err: any) {\n console.error('[MoltsPay] Skill execution error:', err);\n charge.status = 'failed';\n this.sendJson(res, 500, {\n error: 'Skill execution failed',\n message: err.message,\n });\n }\n }\n\n /**\n * GET /status/:chargeId - Check charge status\n */\n private handleStatus(chargeId: string, res: ServerResponse): void {\n const charge = this.charges.get(chargeId);\n if (!charge) {\n return this.sendJson(res, 404, { error: 'Charge not found' });\n }\n\n this.sendJson(res, 200, {\n chargeId: charge.id,\n service: charge.service,\n amount: charge.amount,\n currency: charge.currency,\n status: charge.status,\n txHash: charge.txHash,\n result: charge.status === 'completed' ? charge.result : undefined,\n createdAt: charge.createdAt,\n expiresAt: charge.expiresAt,\n });\n }\n\n private async readBody(req: IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', chunk => body += chunk);\n req.on('end', () => {\n try {\n resolve(body ? JSON.parse(body) : {});\n } catch {\n reject(new Error('Invalid JSON'));\n }\n });\n req.on('error', reject);\n });\n }\n\n private sendJson(res: ServerResponse, status: number, data: any): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data, null, 2));\n }\n}\n","/**\n * On-chain Payment Verification Module\n */\n\nimport { ethers } from 'ethers';\nimport { getChain, getChainById, type ChainConfig, type ChainName } from '../chains';\n\n// ERC20 Transfer event signature\nconst TRANSFER_EVENT_TOPIC = ethers.id('Transfer(address,address,uint256)');\n\nexport interface VerifyPaymentParams {\n txHash: string;\n expectedAmount: number;\n expectedTo?: string;\n chain?: string | number;\n}\n\nexport interface VerifyPaymentResult {\n verified: boolean;\n amount?: number;\n from?: string;\n to?: string;\n txHash?: string;\n blockNumber?: number;\n error?: string;\n}\n\n/**\n * Verify on-chain payment\n */\nexport async function verifyPayment(params: VerifyPaymentParams): Promise<VerifyPaymentResult> {\n const { txHash, expectedAmount, expectedTo } = params;\n \n // Get chain config\n let chain: ChainConfig | undefined;\n try {\n if (typeof params.chain === 'number') {\n chain = getChainById(params.chain);\n } else {\n chain = getChain((params.chain || 'base') as ChainName);\n }\n if (!chain) {\n return { verified: false, error: `Unsupported chain: ${params.chain}` };\n }\n } catch (e) {\n return { verified: false, error: `Unsupported chain: ${params.chain}` };\n }\n\n try {\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n \n // Get transaction receipt\n const receipt = await provider.getTransactionReceipt(txHash);\n \n if (!receipt) {\n return { verified: false, error: 'Transaction not found or not confirmed' };\n }\n\n if (receipt.status !== 1) {\n return { verified: false, error: 'Transaction failed' };\n }\n\n // Parse Transfer event\n const usdcAddress = chain.usdc?.toLowerCase();\n if (!usdcAddress) {\n return { verified: false, error: `Chain ${chain.name} USDC address not configured` };\n }\n\n for (const log of receipt.logs) {\n // Check if USDC contract\n if (log.address.toLowerCase() !== usdcAddress) {\n continue;\n }\n\n // Check if Transfer event\n if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {\n continue;\n }\n\n // Parse Transfer event params\n const from = '0x' + log.topics[1].slice(-40);\n const to = '0x' + log.topics[2].slice(-40);\n const amountRaw = BigInt(log.data);\n const amount = Number(amountRaw) / 1e6; // USDC 6 decimals\n\n // Verify recipient address\n if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {\n continue;\n }\n\n // Verify amount\n if (amount < expectedAmount) {\n return {\n verified: false,\n error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,\n amount,\n from,\n to,\n txHash,\n blockNumber: receipt.blockNumber,\n };\n }\n\n // Verification successful\n return {\n verified: true,\n amount,\n from,\n to,\n txHash,\n blockNumber: receipt.blockNumber,\n };\n }\n\n return { verified: false, error: 'No USDC transfer found' };\n\n } catch (e: any) {\n return { verified: false, error: e.message || String(e) };\n }\n}\n\n/**\n * Get transaction status\n */\nexport async function getTransactionStatus(\n txHash: string,\n chain: string | number = 'base'\n): Promise<{\n status: 'pending' | 'confirmed' | 'failed' | 'not_found';\n blockNumber?: number;\n confirmations?: number;\n}> {\n let chainConfig: ChainConfig | undefined;\n try {\n chainConfig = typeof chain === 'number' ? getChainById(chain) : getChain(chain as ChainName);\n if (!chainConfig) return { status: 'not_found' };\n } catch {\n return { status: 'not_found' };\n }\n\n try {\n const provider = new ethers.JsonRpcProvider(chainConfig.rpc);\n const receipt = await provider.getTransactionReceipt(txHash);\n\n if (!receipt) {\n // Check if in pending pool\n const tx = await provider.getTransaction(txHash);\n if (tx) {\n return { status: 'pending' };\n }\n return { status: 'not_found' };\n }\n\n const currentBlock = await provider.getBlockNumber();\n const confirmations = currentBlock - receipt.blockNumber;\n\n if (receipt.status === 1) {\n return {\n status: 'confirmed',\n blockNumber: receipt.blockNumber,\n confirmations,\n };\n } else {\n return {\n status: 'failed',\n blockNumber: receipt.blockNumber,\n };\n }\n } catch {\n return { status: 'not_found' };\n }\n}\n\n/**\n * Wait for transaction confirmation\n */\nexport async function waitForTransaction(\n txHash: string,\n chain: string | number = 'base',\n confirmations = 1,\n timeoutMs = 60000\n): Promise<VerifyPaymentResult & { confirmed: boolean }> {\n let chainConfig: ChainConfig | undefined;\n try {\n chainConfig = typeof chain === 'number' ? getChainById(chain) : getChain(chain as ChainName);\n if (!chainConfig) {\n return { verified: false, confirmed: false, error: `Unsupported chain: ${chain}` };\n }\n } catch (e) {\n return { verified: false, confirmed: false, error: `Unsupported chain: ${chain}` };\n }\n\n const provider = new ethers.JsonRpcProvider(chainConfig.rpc);\n \n try {\n const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);\n \n if (!receipt) {\n return { verified: false, confirmed: false, error: 'Timeout waiting' };\n }\n\n if (receipt.status !== 1) {\n return { verified: false, confirmed: true, error: 'Transaction failed' };\n }\n\n return {\n verified: true,\n confirmed: true,\n txHash,\n blockNumber: receipt.blockNumber,\n };\n } catch (e: any) {\n return { verified: false, confirmed: false, error: e.message || String(e) };\n }\n}\n","/**\n * Blockchain Configuration\n */\n\nimport type { ChainConfig, ChainName } 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 usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\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-rpc.com',\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 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 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 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 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 };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/index.ts","../../src/chains/index.ts"],"sourcesContent":["/**\n * MoltsPay Server - Payment infrastructure for AI Agents\n * \n * Uses x402 protocol for gasless, pay-for-success payments.\n * \n * Usage:\n * const server = new MoltsPayServer('./moltspay.services.json');\n * server.skill('text-to-video', async (params) => { ... });\n * server.listen(3000);\n */\n\nimport { readFileSync } from 'fs';\nimport { createServer, IncomingMessage, ServerResponse } from 'http';\nimport { ethers } from 'ethers';\nimport { getChain } from '../chains/index.js';\nimport type { ChainName } from '../chains/index.js';\nimport {\n ServicesManifest,\n ServiceConfig,\n SkillFunction,\n RegisteredSkill,\n MoltsPayServerOptions,\n} from './types.js';\n\nexport * from './types.js';\n\n// x402 constants\nconst X402_VERSION = 2;\nconst PAYMENT_REQUIRED_HEADER = 'x-payment-required';\nconst PAYMENT_HEADER = 'x-payment';\n\ninterface EIP3009Authorization {\n from: string;\n to: string;\n value: string;\n validAfter: string;\n validBefore: string;\n nonce: string;\n}\n\ninterface X402PaymentPayload {\n x402Version: number;\n scheme: string;\n network: string;\n payload: {\n signature: string;\n authorization: EIP3009Authorization;\n };\n}\n\nexport class MoltsPayServer {\n private manifest: ServicesManifest;\n private skills: Map<string, RegisteredSkill> = new Map();\n private options: MoltsPayServerOptions;\n private provider: ethers.JsonRpcProvider | null = null;\n private wallet: ethers.Wallet | null = null;\n\n constructor(servicesPath: string, options: MoltsPayServerOptions = {}) {\n // Load services manifest\n const content = readFileSync(servicesPath, 'utf-8');\n this.manifest = JSON.parse(content) as ServicesManifest;\n \n this.options = {\n port: options.port || 3000,\n host: options.host || '0.0.0.0',\n privateKey: options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY,\n };\n\n // Initialize provider and wallet for claiming payments\n if (this.options.privateKey) {\n try {\n const chain = getChain(this.manifest.provider.chain as ChainName);\n this.provider = new ethers.JsonRpcProvider(chain.rpc);\n this.wallet = new ethers.Wallet(this.options.privateKey, this.provider);\n console.log(`[MoltsPay] Payment wallet: ${this.wallet.address}`);\n } catch (err) {\n console.warn('[MoltsPay] Warning: Could not initialize wallet for payment claims');\n }\n }\n\n console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);\n console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);\n console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);\n console.log(`[MoltsPay] Protocol: x402 (gasless, pay-for-success)`);\n }\n\n /**\n * Register a skill handler for a service\n */\n skill(serviceId: string, handler: SkillFunction): this {\n const config = this.manifest.services.find(s => s.id === serviceId);\n if (!config) {\n throw new Error(`Service '${serviceId}' not found in manifest`);\n }\n\n this.skills.set(serviceId, {\n id: serviceId,\n config,\n handler,\n });\n\n console.log(`[MoltsPay] Registered skill: ${serviceId} ($${config.price} ${config.currency})`);\n return this;\n }\n\n /**\n * Start the server\n */\n listen(port?: number): void {\n const p = port || this.options.port!;\n \n const server = createServer((req, res) => this.handleRequest(req, res));\n \n server.listen(p, this.options.host, () => {\n console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);\n console.log(`[MoltsPay] Endpoints:`);\n console.log(` GET /services - List available services`);\n console.log(` POST /execute - Execute service (x402 payment)`);\n });\n }\n\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = new URL(req.url || '/', `http://${req.headers.host}`);\n const path = url.pathname;\n const method = req.method || 'GET';\n\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Payment');\n res.setHeader('Access-Control-Expose-Headers', 'X-Payment-Required, X-Payment-Response');\n\n if (method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n try {\n if (method === 'GET' && path === '/services') {\n return this.handleGetServices(res);\n }\n\n if (method === 'POST' && path === '/execute') {\n const body = await this.readBody(req);\n const paymentHeader = req.headers[PAYMENT_HEADER] as string | undefined;\n return this.handleExecute(body, paymentHeader, res);\n }\n\n // Not found\n this.sendJson(res, 404, { error: 'Not found' });\n } catch (err: any) {\n console.error('[MoltsPay] Error:', err);\n this.sendJson(res, 500, { error: err.message || 'Internal error' });\n }\n }\n\n /**\n * GET /services - List available services\n */\n private handleGetServices(res: ServerResponse): void {\n const chain = getChain(this.manifest.provider.chain as ChainName);\n \n const services = this.manifest.services.map(s => ({\n id: s.id,\n name: s.name,\n description: s.description,\n price: s.price,\n currency: s.currency,\n input: s.input,\n output: s.output,\n available: this.skills.has(s.id),\n }));\n\n this.sendJson(res, 200, {\n provider: this.manifest.provider,\n services,\n x402: {\n version: X402_VERSION,\n network: `eip155:${chain.chainId}`,\n schemes: ['exact'],\n },\n });\n }\n\n /**\n * POST /execute - Execute service with x402 payment\n * Body: { service: string, params: object }\n * Header: X-Payment (optional - if missing, returns 402)\n */\n private async handleExecute(\n body: any,\n paymentHeader: string | undefined,\n res: ServerResponse\n ): Promise<void> {\n const { service, params } = body;\n\n if (!service) {\n return this.sendJson(res, 400, { error: 'Missing service' });\n }\n\n const skill = this.skills.get(service);\n if (!skill) {\n return this.sendJson(res, 404, { error: `Service '${service}' not found or not registered` });\n }\n\n // Validate required params\n for (const [key, field] of Object.entries(skill.config.input)) {\n if (field.required && (!params || params[key] === undefined)) {\n return this.sendJson(res, 400, { error: `Missing required param: ${key}` });\n }\n }\n\n // If no payment header, return 402 with payment requirements\n if (!paymentHeader) {\n return this.sendPaymentRequired(skill.config, res);\n }\n\n // Verify payment\n let payment: X402PaymentPayload;\n try {\n const decoded = Buffer.from(paymentHeader, 'base64').toString('utf-8');\n payment = JSON.parse(decoded);\n } catch {\n return this.sendJson(res, 400, { error: 'Invalid X-Payment header' });\n }\n\n // Validate payment\n const validation = this.validatePayment(payment, skill.config);\n if (!validation.valid) {\n return this.sendJson(res, 402, { error: validation.error });\n }\n\n // Execute skill FIRST (pay-for-success)\n console.log(`[MoltsPay] Executing skill: ${service}`);\n let result: any;\n try {\n result = await skill.handler(params || {});\n } catch (err: any) {\n console.error('[MoltsPay] Skill execution failed:', err.message);\n // Don't claim payment if skill fails\n return this.sendJson(res, 500, {\n error: 'Service execution failed',\n message: err.message,\n });\n }\n\n // Skill succeeded - now claim payment\n console.log(`[MoltsPay] Skill succeeded, claiming payment...`);\n let txHash: string | null = null;\n try {\n txHash = await this.claimPayment(payment);\n console.log(`[MoltsPay] Payment claimed: ${txHash}`);\n } catch (err: any) {\n console.error('[MoltsPay] Payment claim failed:', err.message);\n // Still return result even if payment claim fails\n // (we can retry claiming later)\n }\n\n this.sendJson(res, 200, {\n success: true,\n result,\n payment: txHash ? { txHash, status: 'claimed' } : { status: 'pending' },\n });\n }\n\n /**\n * Return 402 with x402 payment requirements\n */\n private sendPaymentRequired(config: ServiceConfig, res: ServerResponse): void {\n const chain = getChain(this.manifest.provider.chain as ChainName);\n const amountInUnits = Math.floor(config.price * 1e6).toString();\n\n const requirements = [{\n scheme: 'exact',\n network: `eip155:${chain.chainId}`,\n maxAmountRequired: amountInUnits,\n resource: this.manifest.provider.wallet,\n description: `${config.name} - $${config.price} ${config.currency}`,\n }];\n\n const encoded = Buffer.from(JSON.stringify(requirements)).toString('base64');\n\n res.writeHead(402, {\n 'Content-Type': 'application/json',\n [PAYMENT_REQUIRED_HEADER]: encoded,\n });\n res.end(JSON.stringify({\n error: 'Payment required',\n message: `Service requires $${config.price} ${config.currency}`,\n x402: requirements[0],\n }, null, 2));\n }\n\n /**\n * Validate x402 payment payload\n */\n private validatePayment(\n payment: X402PaymentPayload,\n config: ServiceConfig\n ): { valid: boolean; error?: string } {\n if (payment.x402Version !== X402_VERSION) {\n return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };\n }\n\n if (payment.scheme !== 'exact') {\n return { valid: false, error: `Unsupported scheme: ${payment.scheme}` };\n }\n\n const chain = getChain(this.manifest.provider.chain as ChainName);\n const expectedNetwork = `eip155:${chain.chainId}`;\n if (payment.network !== expectedNetwork) {\n return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };\n }\n\n const auth = payment.payload.authorization;\n \n // Check recipient\n if (auth.to.toLowerCase() !== this.manifest.provider.wallet.toLowerCase()) {\n return { valid: false, error: 'Payment recipient mismatch' };\n }\n\n // Check amount\n const amount = Number(auth.value) / 1e6;\n if (amount < config.price) {\n return { valid: false, error: `Insufficient amount: $${amount} < $${config.price}` };\n }\n\n // Check expiry\n const now = Math.floor(Date.now() / 1000);\n if (Number(auth.validBefore) < now) {\n return { valid: false, error: 'Payment authorization expired' };\n }\n\n if (Number(auth.validAfter) > now) {\n return { valid: false, error: 'Payment authorization not yet valid' };\n }\n\n return { valid: true };\n }\n\n /**\n * Claim payment using transferWithAuthorization\n */\n private async claimPayment(payment: X402PaymentPayload): Promise<string> {\n if (!this.wallet || !this.provider) {\n throw new Error('Wallet not configured for payment claims');\n }\n\n const chain = getChain(this.manifest.provider.chain as ChainName);\n const auth = payment.payload.authorization;\n const sig = payment.payload.signature;\n\n // Parse signature\n const { r, s, v } = ethers.Signature.from(sig);\n\n // USDC transferWithAuthorization ABI\n const usdcAbi = [\n 'function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)'\n ];\n\n const usdc = new ethers.Contract(chain.usdc, usdcAbi, this.wallet);\n\n const tx = await usdc.transferWithAuthorization(\n auth.from,\n auth.to,\n auth.value,\n auth.validAfter,\n auth.validBefore,\n auth.nonce,\n v,\n r,\n s\n );\n\n const receipt = await tx.wait();\n return receipt.hash;\n }\n\n private async readBody(req: IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', chunk => body += chunk);\n req.on('end', () => {\n try {\n resolve(body ? JSON.parse(body) : {});\n } catch {\n reject(new Error('Invalid JSON'));\n }\n });\n req.on('error', reject);\n });\n }\n\n private sendJson(res: ServerResponse, status: number, data: any): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data, null, 2));\n }\n}\n","/**\n * Blockchain Configuration\n */\n\nimport type { ChainConfig, ChainName } 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 usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\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-rpc.com',\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 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 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 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 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 };\n"],"mappings":"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","names":[]}
|
package/dist/server/index.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// src/server/index.ts
|
|
2
2
|
import { readFileSync } from "fs";
|
|
3
3
|
import { createServer } from "http";
|
|
4
|
-
|
|
5
|
-
// src/verify/index.ts
|
|
6
4
|
import { ethers } from "ethers";
|
|
7
5
|
|
|
8
6
|
// src/chains/index.ts
|
|
@@ -62,101 +60,39 @@ function getChain(name) {
|
|
|
62
60
|
}
|
|
63
61
|
return config;
|
|
64
62
|
}
|
|
65
|
-
function getChainById(chainId) {
|
|
66
|
-
return Object.values(CHAINS).find((c) => c.chainId === chainId);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// src/verify/index.ts
|
|
70
|
-
var TRANSFER_EVENT_TOPIC = ethers.id("Transfer(address,address,uint256)");
|
|
71
|
-
async function verifyPayment(params) {
|
|
72
|
-
const { txHash, expectedAmount, expectedTo } = params;
|
|
73
|
-
let chain;
|
|
74
|
-
try {
|
|
75
|
-
if (typeof params.chain === "number") {
|
|
76
|
-
chain = getChainById(params.chain);
|
|
77
|
-
} else {
|
|
78
|
-
chain = getChain(params.chain || "base");
|
|
79
|
-
}
|
|
80
|
-
if (!chain) {
|
|
81
|
-
return { verified: false, error: `Unsupported chain: ${params.chain}` };
|
|
82
|
-
}
|
|
83
|
-
} catch (e) {
|
|
84
|
-
return { verified: false, error: `Unsupported chain: ${params.chain}` };
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
const provider = new ethers.JsonRpcProvider(chain.rpc);
|
|
88
|
-
const receipt = await provider.getTransactionReceipt(txHash);
|
|
89
|
-
if (!receipt) {
|
|
90
|
-
return { verified: false, error: "Transaction not found or not confirmed" };
|
|
91
|
-
}
|
|
92
|
-
if (receipt.status !== 1) {
|
|
93
|
-
return { verified: false, error: "Transaction failed" };
|
|
94
|
-
}
|
|
95
|
-
const usdcAddress = chain.usdc?.toLowerCase();
|
|
96
|
-
if (!usdcAddress) {
|
|
97
|
-
return { verified: false, error: `Chain ${chain.name} USDC address not configured` };
|
|
98
|
-
}
|
|
99
|
-
for (const log of receipt.logs) {
|
|
100
|
-
if (log.address.toLowerCase() !== usdcAddress) {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
const from = "0x" + log.topics[1].slice(-40);
|
|
107
|
-
const to = "0x" + log.topics[2].slice(-40);
|
|
108
|
-
const amountRaw = BigInt(log.data);
|
|
109
|
-
const amount = Number(amountRaw) / 1e6;
|
|
110
|
-
if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (amount < expectedAmount) {
|
|
114
|
-
return {
|
|
115
|
-
verified: false,
|
|
116
|
-
error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,
|
|
117
|
-
amount,
|
|
118
|
-
from,
|
|
119
|
-
to,
|
|
120
|
-
txHash,
|
|
121
|
-
blockNumber: receipt.blockNumber
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
return {
|
|
125
|
-
verified: true,
|
|
126
|
-
amount,
|
|
127
|
-
from,
|
|
128
|
-
to,
|
|
129
|
-
txHash,
|
|
130
|
-
blockNumber: receipt.blockNumber
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
return { verified: false, error: "No USDC transfer found" };
|
|
134
|
-
} catch (e) {
|
|
135
|
-
return { verified: false, error: e.message || String(e) };
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
63
|
|
|
139
64
|
// src/server/index.ts
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
65
|
+
var X402_VERSION = 2;
|
|
66
|
+
var PAYMENT_REQUIRED_HEADER = "x-payment-required";
|
|
67
|
+
var PAYMENT_HEADER = "x-payment";
|
|
143
68
|
var MoltsPayServer = class {
|
|
144
69
|
manifest;
|
|
145
70
|
skills = /* @__PURE__ */ new Map();
|
|
146
|
-
charges = /* @__PURE__ */ new Map();
|
|
147
71
|
options;
|
|
72
|
+
provider = null;
|
|
73
|
+
wallet = null;
|
|
148
74
|
constructor(servicesPath, options = {}) {
|
|
149
75
|
const content = readFileSync(servicesPath, "utf-8");
|
|
150
76
|
this.manifest = JSON.parse(content);
|
|
151
77
|
this.options = {
|
|
152
78
|
port: options.port || 3e3,
|
|
153
79
|
host: options.host || "0.0.0.0",
|
|
154
|
-
|
|
155
|
-
// 5 minutes
|
|
80
|
+
privateKey: options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY
|
|
156
81
|
};
|
|
82
|
+
if (this.options.privateKey) {
|
|
83
|
+
try {
|
|
84
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
85
|
+
this.provider = new ethers.JsonRpcProvider(chain.rpc);
|
|
86
|
+
this.wallet = new ethers.Wallet(this.options.privateKey, this.provider);
|
|
87
|
+
console.log(`[MoltsPay] Payment wallet: ${this.wallet.address}`);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.warn("[MoltsPay] Warning: Could not initialize wallet for payment claims");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
157
92
|
console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
|
|
158
93
|
console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
|
|
159
|
-
console.log(`[MoltsPay]
|
|
94
|
+
console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
|
|
95
|
+
console.log(`[MoltsPay] Protocol: x402 (gasless, pay-for-success)`);
|
|
160
96
|
}
|
|
161
97
|
/**
|
|
162
98
|
* Register a skill handler for a service
|
|
@@ -183,10 +119,8 @@ var MoltsPayServer = class {
|
|
|
183
119
|
server.listen(p, this.options.host, () => {
|
|
184
120
|
console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);
|
|
185
121
|
console.log(`[MoltsPay] Endpoints:`);
|
|
186
|
-
console.log(` GET /services
|
|
187
|
-
console.log(` POST /
|
|
188
|
-
console.log(` POST /verify - Verify payment & get result`);
|
|
189
|
-
console.log(` GET /status/:id - Check charge status`);
|
|
122
|
+
console.log(` GET /services - List available services`);
|
|
123
|
+
console.log(` POST /execute - Execute service (x402 payment)`);
|
|
190
124
|
});
|
|
191
125
|
}
|
|
192
126
|
async handleRequest(req, res) {
|
|
@@ -195,7 +129,8 @@ var MoltsPayServer = class {
|
|
|
195
129
|
const method = req.method || "GET";
|
|
196
130
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
197
131
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
198
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
132
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
|
|
133
|
+
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
|
|
199
134
|
if (method === "OPTIONS") {
|
|
200
135
|
res.writeHead(204);
|
|
201
136
|
res.end();
|
|
@@ -205,17 +140,10 @@ var MoltsPayServer = class {
|
|
|
205
140
|
if (method === "GET" && path === "/services") {
|
|
206
141
|
return this.handleGetServices(res);
|
|
207
142
|
}
|
|
208
|
-
if (method === "POST" && path === "/
|
|
209
|
-
const body = await this.readBody(req);
|
|
210
|
-
return this.handlePay(body, res);
|
|
211
|
-
}
|
|
212
|
-
if (method === "POST" && path === "/verify") {
|
|
143
|
+
if (method === "POST" && path === "/execute") {
|
|
213
144
|
const body = await this.readBody(req);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (method === "GET" && path.startsWith("/status/")) {
|
|
217
|
-
const chargeId = path.replace("/status/", "");
|
|
218
|
-
return this.handleStatus(chargeId, res);
|
|
145
|
+
const paymentHeader = req.headers[PAYMENT_HEADER];
|
|
146
|
+
return this.handleExecute(body, paymentHeader, res);
|
|
219
147
|
}
|
|
220
148
|
this.sendJson(res, 404, { error: "Not found" });
|
|
221
149
|
} catch (err) {
|
|
@@ -227,6 +155,7 @@ var MoltsPayServer = class {
|
|
|
227
155
|
* GET /services - List available services
|
|
228
156
|
*/
|
|
229
157
|
handleGetServices(res) {
|
|
158
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
230
159
|
const services = this.manifest.services.map((s) => ({
|
|
231
160
|
id: s.id,
|
|
232
161
|
name: s.name,
|
|
@@ -239,14 +168,20 @@ var MoltsPayServer = class {
|
|
|
239
168
|
}));
|
|
240
169
|
this.sendJson(res, 200, {
|
|
241
170
|
provider: this.manifest.provider,
|
|
242
|
-
services
|
|
171
|
+
services,
|
|
172
|
+
x402: {
|
|
173
|
+
version: X402_VERSION,
|
|
174
|
+
network: `eip155:${chain.chainId}`,
|
|
175
|
+
schemes: ["exact"]
|
|
176
|
+
}
|
|
243
177
|
});
|
|
244
178
|
}
|
|
245
179
|
/**
|
|
246
|
-
* POST /
|
|
180
|
+
* POST /execute - Execute service with x402 payment
|
|
247
181
|
* Body: { service: string, params: object }
|
|
182
|
+
* Header: X-Payment (optional - if missing, returns 402)
|
|
248
183
|
*/
|
|
249
|
-
|
|
184
|
+
async handleExecute(body, paymentHeader, res) {
|
|
250
185
|
const { service, params } = body;
|
|
251
186
|
if (!service) {
|
|
252
187
|
return this.sendJson(res, 400, { error: "Missing service" });
|
|
@@ -260,113 +195,129 @@ var MoltsPayServer = class {
|
|
|
260
195
|
return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
|
|
261
196
|
}
|
|
262
197
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
198
|
+
if (!paymentHeader) {
|
|
199
|
+
return this.sendPaymentRequired(skill.config, res);
|
|
200
|
+
}
|
|
201
|
+
let payment;
|
|
202
|
+
try {
|
|
203
|
+
const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
|
|
204
|
+
payment = JSON.parse(decoded);
|
|
205
|
+
} catch {
|
|
206
|
+
return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
|
|
207
|
+
}
|
|
208
|
+
const validation = this.validatePayment(payment, skill.config);
|
|
209
|
+
if (!validation.valid) {
|
|
210
|
+
return this.sendJson(res, 402, { error: validation.error });
|
|
211
|
+
}
|
|
212
|
+
console.log(`[MoltsPay] Executing skill: ${service}`);
|
|
213
|
+
let result;
|
|
214
|
+
try {
|
|
215
|
+
result = await skill.handler(params || {});
|
|
216
|
+
} catch (err) {
|
|
217
|
+
console.error("[MoltsPay] Skill execution failed:", err.message);
|
|
218
|
+
return this.sendJson(res, 500, {
|
|
219
|
+
error: "Service execution failed",
|
|
220
|
+
message: err.message
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
console.log(`[MoltsPay] Skill succeeded, claiming payment...`);
|
|
224
|
+
let txHash = null;
|
|
225
|
+
try {
|
|
226
|
+
txHash = await this.claimPayment(payment);
|
|
227
|
+
console.log(`[MoltsPay] Payment claimed: ${txHash}`);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
console.error("[MoltsPay] Payment claim failed:", err.message);
|
|
230
|
+
}
|
|
231
|
+
this.sendJson(res, 200, {
|
|
232
|
+
success: true,
|
|
233
|
+
result,
|
|
234
|
+
payment: txHash ? { txHash, status: "claimed" } : { status: "pending" }
|
|
288
235
|
});
|
|
289
236
|
}
|
|
290
237
|
/**
|
|
291
|
-
*
|
|
292
|
-
* Body: { chargeId: string, txHash: string }
|
|
238
|
+
* Return 402 with x402 payment requirements
|
|
293
239
|
*/
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
240
|
+
sendPaymentRequired(config, res) {
|
|
241
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
242
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
243
|
+
const requirements = [{
|
|
244
|
+
scheme: "exact",
|
|
245
|
+
network: `eip155:${chain.chainId}`,
|
|
246
|
+
maxAmountRequired: amountInUnits,
|
|
247
|
+
resource: this.manifest.provider.wallet,
|
|
248
|
+
description: `${config.name} - $${config.price} ${config.currency}`
|
|
249
|
+
}];
|
|
250
|
+
const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
|
|
251
|
+
res.writeHead(402, {
|
|
252
|
+
"Content-Type": "application/json",
|
|
253
|
+
[PAYMENT_REQUIRED_HEADER]: encoded
|
|
254
|
+
});
|
|
255
|
+
res.end(JSON.stringify({
|
|
256
|
+
error: "Payment required",
|
|
257
|
+
message: `Service requires $${config.price} ${config.currency}`,
|
|
258
|
+
x402: requirements[0]
|
|
259
|
+
}, null, 2));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Validate x402 payment payload
|
|
263
|
+
*/
|
|
264
|
+
validatePayment(payment, config) {
|
|
265
|
+
if (payment.x402Version !== X402_VERSION) {
|
|
266
|
+
return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
|
|
298
267
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
return this.sendJson(res, 404, { error: "Charge not found" });
|
|
268
|
+
if (payment.scheme !== "exact") {
|
|
269
|
+
return { valid: false, error: `Unsupported scheme: ${payment.scheme}` };
|
|
302
270
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
271
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
272
|
+
const expectedNetwork = `eip155:${chain.chainId}`;
|
|
273
|
+
if (payment.network !== expectedNetwork) {
|
|
274
|
+
return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
|
|
306
275
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
result: charge.result
|
|
311
|
-
});
|
|
276
|
+
const auth = payment.payload.authorization;
|
|
277
|
+
if (auth.to.toLowerCase() !== this.manifest.provider.wallet.toLowerCase()) {
|
|
278
|
+
return { valid: false, error: "Payment recipient mismatch" };
|
|
312
279
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
if (!verification.verified) {
|
|
321
|
-
charge.status = "failed";
|
|
322
|
-
return this.sendJson(res, 400, {
|
|
323
|
-
error: "Payment verification failed",
|
|
324
|
-
reason: verification.error
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
charge.status = "paid";
|
|
328
|
-
charge.txHash = txHash;
|
|
329
|
-
charge.paidAt = Date.now();
|
|
330
|
-
const skill = this.skills.get(charge.service);
|
|
331
|
-
console.log(`[MoltsPay] Executing skill: ${charge.service}`);
|
|
332
|
-
const result = await skill.handler(charge.params);
|
|
333
|
-
charge.status = "completed";
|
|
334
|
-
charge.result = result;
|
|
335
|
-
charge.completedAt = Date.now();
|
|
336
|
-
this.sendJson(res, 200, {
|
|
337
|
-
status: "completed",
|
|
338
|
-
chargeId,
|
|
339
|
-
txHash,
|
|
340
|
-
result
|
|
341
|
-
});
|
|
342
|
-
} catch (err) {
|
|
343
|
-
console.error("[MoltsPay] Skill execution error:", err);
|
|
344
|
-
charge.status = "failed";
|
|
345
|
-
this.sendJson(res, 500, {
|
|
346
|
-
error: "Skill execution failed",
|
|
347
|
-
message: err.message
|
|
348
|
-
});
|
|
280
|
+
const amount = Number(auth.value) / 1e6;
|
|
281
|
+
if (amount < config.price) {
|
|
282
|
+
return { valid: false, error: `Insufficient amount: $${amount} < $${config.price}` };
|
|
283
|
+
}
|
|
284
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
285
|
+
if (Number(auth.validBefore) < now) {
|
|
286
|
+
return { valid: false, error: "Payment authorization expired" };
|
|
349
287
|
}
|
|
288
|
+
if (Number(auth.validAfter) > now) {
|
|
289
|
+
return { valid: false, error: "Payment authorization not yet valid" };
|
|
290
|
+
}
|
|
291
|
+
return { valid: true };
|
|
350
292
|
}
|
|
351
293
|
/**
|
|
352
|
-
*
|
|
294
|
+
* Claim payment using transferWithAuthorization
|
|
353
295
|
*/
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return this.sendJson(res, 404, { error: "Charge not found" });
|
|
296
|
+
async claimPayment(payment) {
|
|
297
|
+
if (!this.wallet || !this.provider) {
|
|
298
|
+
throw new Error("Wallet not configured for payment claims");
|
|
358
299
|
}
|
|
359
|
-
this.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
300
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
301
|
+
const auth = payment.payload.authorization;
|
|
302
|
+
const sig = payment.payload.signature;
|
|
303
|
+
const { r, s, v } = ethers.Signature.from(sig);
|
|
304
|
+
const usdcAbi = [
|
|
305
|
+
"function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)"
|
|
306
|
+
];
|
|
307
|
+
const usdc = new ethers.Contract(chain.usdc, usdcAbi, this.wallet);
|
|
308
|
+
const tx = await usdc.transferWithAuthorization(
|
|
309
|
+
auth.from,
|
|
310
|
+
auth.to,
|
|
311
|
+
auth.value,
|
|
312
|
+
auth.validAfter,
|
|
313
|
+
auth.validBefore,
|
|
314
|
+
auth.nonce,
|
|
315
|
+
v,
|
|
316
|
+
r,
|
|
317
|
+
s
|
|
318
|
+
);
|
|
319
|
+
const receipt = await tx.wait();
|
|
320
|
+
return receipt.hash;
|
|
370
321
|
}
|
|
371
322
|
async readBody(req) {
|
|
372
323
|
return new Promise((resolve, reject) => {
|