moltspay 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +293 -274
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +294 -275
- 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 +435 -343
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +430 -338
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +25 -9
- package/dist/server/index.d.ts +25 -9
- package/dist/server/index.js +187 -210
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +187 -210
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/client/index.ts
|
|
@@ -97,6 +87,9 @@ function getChain(name) {
|
|
|
97
87
|
}
|
|
98
88
|
|
|
99
89
|
// src/client/index.ts
|
|
90
|
+
var X402_VERSION = 2;
|
|
91
|
+
var PAYMENT_REQUIRED_HEADER = "x-payment-required";
|
|
92
|
+
var PAYMENT_HEADER = "x-payment";
|
|
100
93
|
var DEFAULT_CONFIG = {
|
|
101
94
|
chain: "base",
|
|
102
95
|
limits: {
|
|
@@ -160,43 +153,112 @@ var MoltsPayClient = class {
|
|
|
160
153
|
return res.json();
|
|
161
154
|
}
|
|
162
155
|
/**
|
|
163
|
-
* Pay for a service and get the result
|
|
156
|
+
* Pay for a service and get the result (x402 protocol)
|
|
157
|
+
*
|
|
158
|
+
* This is GASLESS for the client - server pays gas to claim payment.
|
|
159
|
+
* This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.
|
|
164
160
|
*/
|
|
165
161
|
async pay(serverUrl, service, params) {
|
|
166
|
-
if (!this.wallet) {
|
|
162
|
+
if (!this.wallet || !this.walletData) {
|
|
167
163
|
throw new Error("Client not initialized. Run: npx moltspay init");
|
|
168
164
|
}
|
|
169
|
-
|
|
165
|
+
console.log(`[MoltsPay] Requesting service: ${service}`);
|
|
166
|
+
const initialRes = await fetch(`${serverUrl}/execute`, {
|
|
170
167
|
method: "POST",
|
|
171
168
|
headers: { "Content-Type": "application/json" },
|
|
172
169
|
body: JSON.stringify({ service, params })
|
|
173
170
|
});
|
|
174
|
-
if (
|
|
175
|
-
const
|
|
176
|
-
|
|
171
|
+
if (initialRes.status !== 402) {
|
|
172
|
+
const data = await initialRes.json();
|
|
173
|
+
if (initialRes.ok && data.result) {
|
|
174
|
+
return data.result;
|
|
175
|
+
}
|
|
176
|
+
throw new Error(data.error || "Unexpected response");
|
|
177
|
+
}
|
|
178
|
+
const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);
|
|
179
|
+
if (!paymentRequiredHeader) {
|
|
180
|
+
throw new Error("Missing x-payment-required header");
|
|
181
|
+
}
|
|
182
|
+
let requirements;
|
|
183
|
+
try {
|
|
184
|
+
const decoded = Buffer.from(paymentRequiredHeader, "base64").toString("utf-8");
|
|
185
|
+
requirements = JSON.parse(decoded);
|
|
186
|
+
if (!Array.isArray(requirements)) {
|
|
187
|
+
requirements = [requirements];
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
throw new Error("Invalid x-payment-required header");
|
|
177
191
|
}
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
192
|
+
const chain = getChain(this.config.chain);
|
|
193
|
+
const network = `eip155:${chain.chainId}`;
|
|
194
|
+
const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
|
|
195
|
+
if (!req) {
|
|
196
|
+
throw new Error(`No matching payment option for ${network}`);
|
|
197
|
+
}
|
|
198
|
+
const amount = Number(req.maxAmountRequired) / 1e6;
|
|
199
|
+
this.checkLimits(amount);
|
|
200
|
+
console.log(`[MoltsPay] Signing payment: $${amount} USDC (gasless)`);
|
|
201
|
+
const authorization = await this.signEIP3009(req.resource, amount, chain);
|
|
202
|
+
const payload = {
|
|
203
|
+
x402Version: X402_VERSION,
|
|
204
|
+
scheme: "exact",
|
|
205
|
+
network,
|
|
206
|
+
payload: authorization
|
|
207
|
+
};
|
|
208
|
+
const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
209
|
+
console.log(`[MoltsPay] Sending request with payment...`);
|
|
210
|
+
const paidRes = await fetch(`${serverUrl}/execute`, {
|
|
185
211
|
method: "POST",
|
|
186
|
-
headers: {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
})
|
|
212
|
+
headers: {
|
|
213
|
+
"Content-Type": "application/json",
|
|
214
|
+
[PAYMENT_HEADER]: paymentHeader
|
|
215
|
+
},
|
|
216
|
+
body: JSON.stringify({ service, params })
|
|
191
217
|
});
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
throw new Error(
|
|
218
|
+
const result = await paidRes.json();
|
|
219
|
+
if (!paidRes.ok) {
|
|
220
|
+
throw new Error(result.error || "Service execution failed");
|
|
195
221
|
}
|
|
196
|
-
|
|
197
|
-
|
|
222
|
+
this.recordSpending(amount);
|
|
223
|
+
console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
|
|
198
224
|
return result.result;
|
|
199
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Sign EIP-3009 transferWithAuthorization (GASLESS)
|
|
228
|
+
* This only signs - no on-chain transaction, no gas needed.
|
|
229
|
+
*/
|
|
230
|
+
async signEIP3009(to, amount, chain) {
|
|
231
|
+
const validAfter = 0;
|
|
232
|
+
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
233
|
+
const nonce = import_ethers.ethers.hexlify(import_ethers.ethers.randomBytes(32));
|
|
234
|
+
const value = BigInt(Math.floor(amount * 1e6)).toString();
|
|
235
|
+
const authorization = {
|
|
236
|
+
from: this.wallet.address,
|
|
237
|
+
to,
|
|
238
|
+
value,
|
|
239
|
+
validAfter: validAfter.toString(),
|
|
240
|
+
validBefore: validBefore.toString(),
|
|
241
|
+
nonce
|
|
242
|
+
};
|
|
243
|
+
const domain = {
|
|
244
|
+
name: "USD Coin",
|
|
245
|
+
version: "2",
|
|
246
|
+
chainId: chain.chainId,
|
|
247
|
+
verifyingContract: chain.usdc
|
|
248
|
+
};
|
|
249
|
+
const types = {
|
|
250
|
+
TransferWithAuthorization: [
|
|
251
|
+
{ name: "from", type: "address" },
|
|
252
|
+
{ name: "to", type: "address" },
|
|
253
|
+
{ name: "value", type: "uint256" },
|
|
254
|
+
{ name: "validAfter", type: "uint256" },
|
|
255
|
+
{ name: "validBefore", type: "uint256" },
|
|
256
|
+
{ name: "nonce", type: "bytes32" }
|
|
257
|
+
]
|
|
258
|
+
};
|
|
259
|
+
const signature = await this.wallet.signTypedData(domain, types, authorization);
|
|
260
|
+
return { authorization, signature };
|
|
261
|
+
}
|
|
200
262
|
/**
|
|
201
263
|
* Check spending limits
|
|
202
264
|
*/
|
|
@@ -223,36 +285,6 @@ var MoltsPayClient = class {
|
|
|
223
285
|
recordSpending(amount) {
|
|
224
286
|
this.todaySpending += amount;
|
|
225
287
|
}
|
|
226
|
-
/**
|
|
227
|
-
* Execute payment on-chain
|
|
228
|
-
*/
|
|
229
|
-
async executePayment(payment) {
|
|
230
|
-
let chain;
|
|
231
|
-
try {
|
|
232
|
-
chain = getChain(payment.chain);
|
|
233
|
-
} catch {
|
|
234
|
-
throw new Error(`Unknown chain: ${payment.chain}`);
|
|
235
|
-
}
|
|
236
|
-
const { ethers } = await import("ethers");
|
|
237
|
-
const provider = new ethers.JsonRpcProvider(chain.rpc);
|
|
238
|
-
const signer = new ethers.Wallet(this.walletData.privateKey, provider);
|
|
239
|
-
const usdcAddress = chain.usdc;
|
|
240
|
-
const usdcAbi = [
|
|
241
|
-
"function transfer(address to, uint256 amount) returns (bool)",
|
|
242
|
-
"function balanceOf(address account) view returns (uint256)"
|
|
243
|
-
];
|
|
244
|
-
const usdc = new ethers.Contract(usdcAddress, usdcAbi, signer);
|
|
245
|
-
const amountInUnits = ethers.parseUnits(payment.amount.toString(), 6);
|
|
246
|
-
const balance = await usdc.balanceOf(this.wallet.address);
|
|
247
|
-
if (balance < amountInUnits) {
|
|
248
|
-
throw new Error(
|
|
249
|
-
`Insufficient USDC balance: ${ethers.formatUnits(balance, 6)} < ${payment.amount}`
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
const tx = await usdc.transfer(payment.wallet, amountInUnits);
|
|
253
|
-
const receipt = await tx.wait();
|
|
254
|
-
return receipt.hash;
|
|
255
|
-
}
|
|
256
288
|
// --- Config & Wallet Management ---
|
|
257
289
|
loadConfig() {
|
|
258
290
|
const configPath = (0, import_path.join)(this.configDir, "config.json");
|
|
@@ -312,15 +344,14 @@ var MoltsPayClient = class {
|
|
|
312
344
|
} catch {
|
|
313
345
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
314
346
|
}
|
|
315
|
-
const
|
|
316
|
-
const provider = new ethers.JsonRpcProvider(chain.rpc);
|
|
347
|
+
const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
|
|
317
348
|
const nativeBalance = await provider.getBalance(this.wallet.address);
|
|
318
349
|
const usdcAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
319
|
-
const usdc = new ethers.Contract(chain.usdc, usdcAbi, provider);
|
|
350
|
+
const usdc = new import_ethers.ethers.Contract(chain.usdc, usdcAbi, provider);
|
|
320
351
|
const usdcBalance = await usdc.balanceOf(this.wallet.address);
|
|
321
352
|
return {
|
|
322
|
-
usdc: parseFloat(ethers.formatUnits(usdcBalance, 6)),
|
|
323
|
-
native: parseFloat(ethers.formatEther(nativeBalance))
|
|
353
|
+
usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, 6)),
|
|
354
|
+
native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
|
|
324
355
|
};
|
|
325
356
|
}
|
|
326
357
|
};
|
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 * 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 } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Wallet } from 'ethers';\nimport { getChain, type ChainName } from '../chains/index.js';\nimport {\n ClientConfig,\n WalletData,\n PaymentRequired,\n ServicesResponse,\n VerifyResponse,\n MoltsPayClientOptions,\n} from './types.js';\n\nexport * from './types.js';\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 \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\n */\n async pay(\n serverUrl: string,\n service: string,\n params: Record<string, any>\n ): Promise<Record<string, any>> {\n if (!this.wallet) {\n throw new Error('Client not initialized. Run: npx moltspay init');\n }\n\n // Step 1: Request payment info\n const payRes = await fetch(`${serverUrl}/pay`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ service, params }),\n });\n\n if (payRes.status !== 402) {\n const err = await payRes.json() as { error?: string };\n throw new Error(err.error || 'Unexpected response');\n }\n\n const paymentReq = await payRes.json() as PaymentRequired;\n const { payment } = paymentReq;\n\n // Step 2: Check limits\n this.checkLimits(payment.amount);\n\n // Step 3: Execute payment on-chain\n console.log(`[MoltsPay] Paying $${payment.amount} ${payment.currency} to ${payment.wallet}`);\n const txHash = await this.executePayment(payment);\n console.log(`[MoltsPay] Payment tx: ${txHash}`);\n\n // Step 4: Verify and get result\n const verifyRes = await fetch(`${serverUrl}/verify`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n chargeId: payment.chargeId,\n txHash,\n }),\n });\n\n if (!verifyRes.ok) {\n const err = await verifyRes.json() as { error?: string };\n throw new Error(err.error || 'Verification failed');\n }\n\n const result = await verifyRes.json() as VerifyResponse;\n \n // Update spending tracking\n this.recordSpending(payment.amount);\n \n return result.result;\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 }\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\n */\n private recordSpending(amount: number): void {\n this.todaySpending += amount;\n }\n\n /**\n * Execute payment on-chain\n */\n private async executePayment(payment: PaymentRequired['payment']): Promise<string> {\n let chain;\n try {\n chain = getChain(payment.chain as ChainName);\n } catch {\n throw new Error(`Unknown chain: ${payment.chain}`);\n }\n\n // For now, we'll use a simple USDC transfer\n // In production, this would connect to the actual chain\n const { ethers } = await import('ethers');\n \n const provider = new ethers.JsonRpcProvider(chain.rpc);\n const signer = new ethers.Wallet(this.walletData!.privateKey, provider);\n\n // USDC contract (Base mainnet)\n const usdcAddress = chain.usdc;\n const usdcAbi = [\n 'function transfer(address to, uint256 amount) returns (bool)',\n 'function balanceOf(address account) view returns (uint256)',\n ];\n \n const usdc = new ethers.Contract(usdcAddress, usdcAbi, signer);\n\n // Convert amount to USDC decimals (6)\n const amountInUnits = ethers.parseUnits(payment.amount.toString(), 6);\n\n // Check balance\n const balance = await usdc.balanceOf(this.wallet!.address);\n if (balance < amountInUnits) {\n throw new Error(\n `Insufficient USDC balance: ${ethers.formatUnits(balance, 6)} < ${payment.amount}`\n );\n }\n\n // Send transaction\n const tx = await usdc.transfer(payment.wallet, amountInUnits);\n const receipt = await tx.wait();\n\n return receipt.hash;\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 private loadWallet(): WalletData | null {\n const walletPath = join(this.configDir, 'wallet.json');\n if (existsSync(walletPath)) {\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\n const walletPath = join(configDir, 'wallet.json');\n writeFileSync(walletPath, JSON.stringify(walletData, null, 2));\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\n */\n async getBalance(): Promise<{ usdc: 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 { ethers } = await import('ethers');\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n\n // Get native balance\n const nativeBalance = await provider.getBalance(this.wallet.address);\n\n // Get USDC balance\n const usdcAbi = ['function balanceOf(address) view returns (uint256)'];\n const usdc = new ethers.Contract(chain.usdc, usdcAbi, provider);\n const usdcBalance = await usdc.balanceOf(this.wallet.address);\n\n return {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, 6)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\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/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 } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Wallet, ethers } from 'ethers';\nimport { getChain, type ChainName } from '../chains/index.js';\nimport {\n ClientConfig,\n WalletData,\n ServicesResponse,\n MoltsPayClientOptions,\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 X402PaymentRequirements {\n scheme: string;\n network: string;\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 \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 async pay(\n serverUrl: string,\n service: string,\n params: Record<string, any>\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 initialRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ service, params }),\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 requirements = JSON.parse(decoded);\n if (!Array.isArray(requirements)) {\n requirements = [requirements];\n }\n } catch {\n throw new Error('Invalid x-payment-required header');\n }\n\n // Find matching requirement for our chain\n const chain = getChain(this.config.chain as 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(`No matching payment option for ${network}`);\n }\n\n // Step 3: Check limits\n const amount = Number(req.maxAmountRequired) / 1e6;\n this.checkLimits(amount);\n\n console.log(`[MoltsPay] Signing payment: $${amount} USDC (gasless)`);\n\n // Step 4: Sign EIP-3009 authorization (GASLESS - just signing)\n const authorization = await this.signEIP3009(req.resource, amount, chain);\n\n // Step 5: Create x402 payment payload\n const payload = {\n x402Version: X402_VERSION,\n scheme: 'exact',\n network,\n payload: authorization,\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 paidRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [PAYMENT_HEADER]: paymentHeader,\n },\n body: JSON.stringify({ service, params }),\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 */\n private async signEIP3009(\n to: string,\n amount: number,\n chain: { chainId: number; usdc: string }\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 const value = BigInt(Math.floor(amount * 1e6)).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 for USDC\n const domain = {\n name: 'USD Coin',\n version: '2',\n chainId: chain.chainId,\n verifyingContract: chain.usdc,\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 }\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\n */\n private recordSpending(amount: number): void {\n this.todaySpending += amount;\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 private loadWallet(): WalletData | null {\n const walletPath = join(this.configDir, 'wallet.json');\n if (existsSync(walletPath)) {\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\n const walletPath = join(configDir, 'wallet.json');\n writeFileSync(walletPath, JSON.stringify(walletData, null, 2));\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\n */\n async getBalance(): Promise<{ usdc: 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\n // Get native balance\n const nativeBalance = await provider.getBalance(this.wallet.address);\n\n // Get USDC balance\n const usdcAbi = ['function balanceOf(address) view returns (uint256)'];\n const usdc = new ethers.Contract(chain.usdc, usdcAbi, provider);\n const usdcBalance = await usdc.balanceOf(this.wallet.address);\n\n return {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, 6)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\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/client/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import { join } from "path";
|
|
5
|
-
import { Wallet } from "ethers";
|
|
5
|
+
import { Wallet, ethers } from "ethers";
|
|
6
6
|
|
|
7
7
|
// src/chains/index.ts
|
|
8
8
|
var CHAINS = {
|
|
@@ -63,6 +63,9 @@ function getChain(name) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// src/client/index.ts
|
|
66
|
+
var X402_VERSION = 2;
|
|
67
|
+
var PAYMENT_REQUIRED_HEADER = "x-payment-required";
|
|
68
|
+
var PAYMENT_HEADER = "x-payment";
|
|
66
69
|
var DEFAULT_CONFIG = {
|
|
67
70
|
chain: "base",
|
|
68
71
|
limits: {
|
|
@@ -126,43 +129,112 @@ var MoltsPayClient = class {
|
|
|
126
129
|
return res.json();
|
|
127
130
|
}
|
|
128
131
|
/**
|
|
129
|
-
* Pay for a service and get the result
|
|
132
|
+
* Pay for a service and get the result (x402 protocol)
|
|
133
|
+
*
|
|
134
|
+
* This is GASLESS for the client - server pays gas to claim payment.
|
|
135
|
+
* This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.
|
|
130
136
|
*/
|
|
131
137
|
async pay(serverUrl, service, params) {
|
|
132
|
-
if (!this.wallet) {
|
|
138
|
+
if (!this.wallet || !this.walletData) {
|
|
133
139
|
throw new Error("Client not initialized. Run: npx moltspay init");
|
|
134
140
|
}
|
|
135
|
-
|
|
141
|
+
console.log(`[MoltsPay] Requesting service: ${service}`);
|
|
142
|
+
const initialRes = await fetch(`${serverUrl}/execute`, {
|
|
136
143
|
method: "POST",
|
|
137
144
|
headers: { "Content-Type": "application/json" },
|
|
138
145
|
body: JSON.stringify({ service, params })
|
|
139
146
|
});
|
|
140
|
-
if (
|
|
141
|
-
const
|
|
142
|
-
|
|
147
|
+
if (initialRes.status !== 402) {
|
|
148
|
+
const data = await initialRes.json();
|
|
149
|
+
if (initialRes.ok && data.result) {
|
|
150
|
+
return data.result;
|
|
151
|
+
}
|
|
152
|
+
throw new Error(data.error || "Unexpected response");
|
|
153
|
+
}
|
|
154
|
+
const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);
|
|
155
|
+
if (!paymentRequiredHeader) {
|
|
156
|
+
throw new Error("Missing x-payment-required header");
|
|
157
|
+
}
|
|
158
|
+
let requirements;
|
|
159
|
+
try {
|
|
160
|
+
const decoded = Buffer.from(paymentRequiredHeader, "base64").toString("utf-8");
|
|
161
|
+
requirements = JSON.parse(decoded);
|
|
162
|
+
if (!Array.isArray(requirements)) {
|
|
163
|
+
requirements = [requirements];
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
throw new Error("Invalid x-payment-required header");
|
|
143
167
|
}
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
168
|
+
const chain = getChain(this.config.chain);
|
|
169
|
+
const network = `eip155:${chain.chainId}`;
|
|
170
|
+
const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
|
|
171
|
+
if (!req) {
|
|
172
|
+
throw new Error(`No matching payment option for ${network}`);
|
|
173
|
+
}
|
|
174
|
+
const amount = Number(req.maxAmountRequired) / 1e6;
|
|
175
|
+
this.checkLimits(amount);
|
|
176
|
+
console.log(`[MoltsPay] Signing payment: $${amount} USDC (gasless)`);
|
|
177
|
+
const authorization = await this.signEIP3009(req.resource, amount, chain);
|
|
178
|
+
const payload = {
|
|
179
|
+
x402Version: X402_VERSION,
|
|
180
|
+
scheme: "exact",
|
|
181
|
+
network,
|
|
182
|
+
payload: authorization
|
|
183
|
+
};
|
|
184
|
+
const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
185
|
+
console.log(`[MoltsPay] Sending request with payment...`);
|
|
186
|
+
const paidRes = await fetch(`${serverUrl}/execute`, {
|
|
151
187
|
method: "POST",
|
|
152
|
-
headers: {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
})
|
|
188
|
+
headers: {
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
[PAYMENT_HEADER]: paymentHeader
|
|
191
|
+
},
|
|
192
|
+
body: JSON.stringify({ service, params })
|
|
157
193
|
});
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
throw new Error(
|
|
194
|
+
const result = await paidRes.json();
|
|
195
|
+
if (!paidRes.ok) {
|
|
196
|
+
throw new Error(result.error || "Service execution failed");
|
|
161
197
|
}
|
|
162
|
-
|
|
163
|
-
|
|
198
|
+
this.recordSpending(amount);
|
|
199
|
+
console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
|
|
164
200
|
return result.result;
|
|
165
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Sign EIP-3009 transferWithAuthorization (GASLESS)
|
|
204
|
+
* This only signs - no on-chain transaction, no gas needed.
|
|
205
|
+
*/
|
|
206
|
+
async signEIP3009(to, amount, chain) {
|
|
207
|
+
const validAfter = 0;
|
|
208
|
+
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
209
|
+
const nonce = ethers.hexlify(ethers.randomBytes(32));
|
|
210
|
+
const value = BigInt(Math.floor(amount * 1e6)).toString();
|
|
211
|
+
const authorization = {
|
|
212
|
+
from: this.wallet.address,
|
|
213
|
+
to,
|
|
214
|
+
value,
|
|
215
|
+
validAfter: validAfter.toString(),
|
|
216
|
+
validBefore: validBefore.toString(),
|
|
217
|
+
nonce
|
|
218
|
+
};
|
|
219
|
+
const domain = {
|
|
220
|
+
name: "USD Coin",
|
|
221
|
+
version: "2",
|
|
222
|
+
chainId: chain.chainId,
|
|
223
|
+
verifyingContract: chain.usdc
|
|
224
|
+
};
|
|
225
|
+
const types = {
|
|
226
|
+
TransferWithAuthorization: [
|
|
227
|
+
{ name: "from", type: "address" },
|
|
228
|
+
{ name: "to", type: "address" },
|
|
229
|
+
{ name: "value", type: "uint256" },
|
|
230
|
+
{ name: "validAfter", type: "uint256" },
|
|
231
|
+
{ name: "validBefore", type: "uint256" },
|
|
232
|
+
{ name: "nonce", type: "bytes32" }
|
|
233
|
+
]
|
|
234
|
+
};
|
|
235
|
+
const signature = await this.wallet.signTypedData(domain, types, authorization);
|
|
236
|
+
return { authorization, signature };
|
|
237
|
+
}
|
|
166
238
|
/**
|
|
167
239
|
* Check spending limits
|
|
168
240
|
*/
|
|
@@ -189,36 +261,6 @@ var MoltsPayClient = class {
|
|
|
189
261
|
recordSpending(amount) {
|
|
190
262
|
this.todaySpending += amount;
|
|
191
263
|
}
|
|
192
|
-
/**
|
|
193
|
-
* Execute payment on-chain
|
|
194
|
-
*/
|
|
195
|
-
async executePayment(payment) {
|
|
196
|
-
let chain;
|
|
197
|
-
try {
|
|
198
|
-
chain = getChain(payment.chain);
|
|
199
|
-
} catch {
|
|
200
|
-
throw new Error(`Unknown chain: ${payment.chain}`);
|
|
201
|
-
}
|
|
202
|
-
const { ethers } = await import("ethers");
|
|
203
|
-
const provider = new ethers.JsonRpcProvider(chain.rpc);
|
|
204
|
-
const signer = new ethers.Wallet(this.walletData.privateKey, provider);
|
|
205
|
-
const usdcAddress = chain.usdc;
|
|
206
|
-
const usdcAbi = [
|
|
207
|
-
"function transfer(address to, uint256 amount) returns (bool)",
|
|
208
|
-
"function balanceOf(address account) view returns (uint256)"
|
|
209
|
-
];
|
|
210
|
-
const usdc = new ethers.Contract(usdcAddress, usdcAbi, signer);
|
|
211
|
-
const amountInUnits = ethers.parseUnits(payment.amount.toString(), 6);
|
|
212
|
-
const balance = await usdc.balanceOf(this.wallet.address);
|
|
213
|
-
if (balance < amountInUnits) {
|
|
214
|
-
throw new Error(
|
|
215
|
-
`Insufficient USDC balance: ${ethers.formatUnits(balance, 6)} < ${payment.amount}`
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
const tx = await usdc.transfer(payment.wallet, amountInUnits);
|
|
219
|
-
const receipt = await tx.wait();
|
|
220
|
-
return receipt.hash;
|
|
221
|
-
}
|
|
222
264
|
// --- Config & Wallet Management ---
|
|
223
265
|
loadConfig() {
|
|
224
266
|
const configPath = join(this.configDir, "config.json");
|
|
@@ -278,7 +320,6 @@ var MoltsPayClient = class {
|
|
|
278
320
|
} catch {
|
|
279
321
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
280
322
|
}
|
|
281
|
-
const { ethers } = await import("ethers");
|
|
282
323
|
const provider = new ethers.JsonRpcProvider(chain.rpc);
|
|
283
324
|
const nativeBalance = await provider.getBalance(this.wallet.address);
|
|
284
325
|
const usdcAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
@@ -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 * 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 } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Wallet } from 'ethers';\nimport { getChain, type ChainName } from '../chains/index.js';\nimport {\n ClientConfig,\n WalletData,\n PaymentRequired,\n ServicesResponse,\n VerifyResponse,\n MoltsPayClientOptions,\n} from './types.js';\n\nexport * from './types.js';\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 \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\n */\n async pay(\n serverUrl: string,\n service: string,\n params: Record<string, any>\n ): Promise<Record<string, any>> {\n if (!this.wallet) {\n throw new Error('Client not initialized. Run: npx moltspay init');\n }\n\n // Step 1: Request payment info\n const payRes = await fetch(`${serverUrl}/pay`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ service, params }),\n });\n\n if (payRes.status !== 402) {\n const err = await payRes.json() as { error?: string };\n throw new Error(err.error || 'Unexpected response');\n }\n\n const paymentReq = await payRes.json() as PaymentRequired;\n const { payment } = paymentReq;\n\n // Step 2: Check limits\n this.checkLimits(payment.amount);\n\n // Step 3: Execute payment on-chain\n console.log(`[MoltsPay] Paying $${payment.amount} ${payment.currency} to ${payment.wallet}`);\n const txHash = await this.executePayment(payment);\n console.log(`[MoltsPay] Payment tx: ${txHash}`);\n\n // Step 4: Verify and get result\n const verifyRes = await fetch(`${serverUrl}/verify`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n chargeId: payment.chargeId,\n txHash,\n }),\n });\n\n if (!verifyRes.ok) {\n const err = await verifyRes.json() as { error?: string };\n throw new Error(err.error || 'Verification failed');\n }\n\n const result = await verifyRes.json() as VerifyResponse;\n \n // Update spending tracking\n this.recordSpending(payment.amount);\n \n return result.result;\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 }\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\n */\n private recordSpending(amount: number): void {\n this.todaySpending += amount;\n }\n\n /**\n * Execute payment on-chain\n */\n private async executePayment(payment: PaymentRequired['payment']): Promise<string> {\n let chain;\n try {\n chain = getChain(payment.chain as ChainName);\n } catch {\n throw new Error(`Unknown chain: ${payment.chain}`);\n }\n\n // For now, we'll use a simple USDC transfer\n // In production, this would connect to the actual chain\n const { ethers } = await import('ethers');\n \n const provider = new ethers.JsonRpcProvider(chain.rpc);\n const signer = new ethers.Wallet(this.walletData!.privateKey, provider);\n\n // USDC contract (Base mainnet)\n const usdcAddress = chain.usdc;\n const usdcAbi = [\n 'function transfer(address to, uint256 amount) returns (bool)',\n 'function balanceOf(address account) view returns (uint256)',\n ];\n \n const usdc = new ethers.Contract(usdcAddress, usdcAbi, signer);\n\n // Convert amount to USDC decimals (6)\n const amountInUnits = ethers.parseUnits(payment.amount.toString(), 6);\n\n // Check balance\n const balance = await usdc.balanceOf(this.wallet!.address);\n if (balance < amountInUnits) {\n throw new Error(\n `Insufficient USDC balance: ${ethers.formatUnits(balance, 6)} < ${payment.amount}`\n );\n }\n\n // Send transaction\n const tx = await usdc.transfer(payment.wallet, amountInUnits);\n const receipt = await tx.wait();\n\n return receipt.hash;\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 private loadWallet(): WalletData | null {\n const walletPath = join(this.configDir, 'wallet.json');\n if (existsSync(walletPath)) {\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\n const walletPath = join(configDir, 'wallet.json');\n writeFileSync(walletPath, JSON.stringify(walletData, null, 2));\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\n */\n async getBalance(): Promise<{ usdc: 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 { ethers } = await import('ethers');\n const provider = new ethers.JsonRpcProvider(chain.rpc);\n\n // Get native balance\n const nativeBalance = await provider.getBalance(this.wallet.address);\n\n // Get USDC balance\n const usdcAbi = ['function balanceOf(address) view returns (uint256)'];\n const usdc = new ethers.Contract(chain.usdc, usdcAbi, provider);\n const usdcBalance = await usdc.balanceOf(this.wallet.address);\n\n return {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, 6)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\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/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 } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Wallet, ethers } from 'ethers';\nimport { getChain, type ChainName } from '../chains/index.js';\nimport {\n ClientConfig,\n WalletData,\n ServicesResponse,\n MoltsPayClientOptions,\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 X402PaymentRequirements {\n scheme: string;\n network: string;\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 \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 async pay(\n serverUrl: string,\n service: string,\n params: Record<string, any>\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 initialRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ service, params }),\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 requirements = JSON.parse(decoded);\n if (!Array.isArray(requirements)) {\n requirements = [requirements];\n }\n } catch {\n throw new Error('Invalid x-payment-required header');\n }\n\n // Find matching requirement for our chain\n const chain = getChain(this.config.chain as 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(`No matching payment option for ${network}`);\n }\n\n // Step 3: Check limits\n const amount = Number(req.maxAmountRequired) / 1e6;\n this.checkLimits(amount);\n\n console.log(`[MoltsPay] Signing payment: $${amount} USDC (gasless)`);\n\n // Step 4: Sign EIP-3009 authorization (GASLESS - just signing)\n const authorization = await this.signEIP3009(req.resource, amount, chain);\n\n // Step 5: Create x402 payment payload\n const payload = {\n x402Version: X402_VERSION,\n scheme: 'exact',\n network,\n payload: authorization,\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 paidRes = await fetch(`${serverUrl}/execute`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [PAYMENT_HEADER]: paymentHeader,\n },\n body: JSON.stringify({ service, params }),\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 */\n private async signEIP3009(\n to: string,\n amount: number,\n chain: { chainId: number; usdc: string }\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 const value = BigInt(Math.floor(amount * 1e6)).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 for USDC\n const domain = {\n name: 'USD Coin',\n version: '2',\n chainId: chain.chainId,\n verifyingContract: chain.usdc,\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 }\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\n */\n private recordSpending(amount: number): void {\n this.todaySpending += amount;\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 private loadWallet(): WalletData | null {\n const walletPath = join(this.configDir, 'wallet.json');\n if (existsSync(walletPath)) {\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\n const walletPath = join(configDir, 'wallet.json');\n writeFileSync(walletPath, JSON.stringify(walletData, null, 2));\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\n */\n async getBalance(): Promise<{ usdc: 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\n // Get native balance\n const nativeBalance = await provider.getBalance(this.wallet.address);\n\n // Get USDC balance\n const usdcAbi = ['function balanceOf(address) view returns (uint256)'];\n const usdc = new ethers.Contract(chain.usdc, usdcAbi, provider);\n const usdcBalance = await usdc.balanceOf(this.wallet.address);\n\n return {\n usdc: parseFloat(ethers.formatUnits(usdcBalance, 6)),\n native: parseFloat(ethers.formatEther(nativeBalance)),\n };\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":[]}
|