agentstore 1.0.1 → 1.0.3
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/README.md +7 -15
- package/dist/index.js +375 -268
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,27 +4,19 @@ Open-source marketplace CLI for Claude Code plugins. Browse, install, publish, a
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
npm install -g agentstore
|
|
9
|
-
```
|
|
7
|
+
**Native plugin** (recommended — no npm needed):
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
npx agentstore browse
|
|
9
|
+
```
|
|
10
|
+
/plugin marketplace add techgangboss/agentstore
|
|
11
|
+
/plugin install code-reviewer@agentstore
|
|
15
12
|
```
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
**npm CLI** (adds wallet, payments, and publishing):
|
|
18
15
|
|
|
19
16
|
```bash
|
|
20
|
-
|
|
17
|
+
npm install -g agentstore
|
|
21
18
|
agentstore browse
|
|
22
|
-
|
|
23
|
-
# Install a free agent
|
|
24
|
-
agentstore install techgangboss.wallet-assistant
|
|
25
|
-
|
|
26
|
-
# Setup the MCP gateway (routes agent tools through Claude Code)
|
|
27
|
-
agentstore gateway-setup
|
|
19
|
+
agentstore install techgangboss.code-reviewer
|
|
28
20
|
```
|
|
29
21
|
|
|
30
22
|
## For Publishers
|
package/dist/index.js
CHANGED
|
@@ -7,15 +7,39 @@ import * as crypto from 'crypto';
|
|
|
7
7
|
import * as readline from 'readline';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { generatePrivateKey, privateKeyToAccount, } from 'viem/accounts';
|
|
10
|
-
import { createPublicClient,
|
|
10
|
+
import { createPublicClient, http, parseAbi, formatUnits, parseUnits, } from 'viem';
|
|
11
11
|
import { mainnet } from 'viem/chains';
|
|
12
12
|
import * as keytar from 'keytar';
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = path.dirname(__filename);
|
|
15
15
|
const API_BASE = 'https://api.agentstore.tools';
|
|
16
|
-
const
|
|
16
|
+
const ETHEREUM_RPC = 'https://ethereum-rpc.publicnode.com';
|
|
17
17
|
const KEYCHAIN_SERVICE = 'agentstore-wallet';
|
|
18
18
|
const KEYCHAIN_ACCOUNT = 'encryption-key';
|
|
19
|
+
// USDC contract on Ethereum mainnet
|
|
20
|
+
const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
|
|
21
|
+
const USDC_DECIMALS = 6;
|
|
22
|
+
const ERC20_BALANCE_ABI = parseAbi([
|
|
23
|
+
'function balanceOf(address owner) view returns (uint256)',
|
|
24
|
+
]);
|
|
25
|
+
// EIP-712 domain for USDC (EIP-3009 transferWithAuthorization)
|
|
26
|
+
const USDC_EIP712_DOMAIN = {
|
|
27
|
+
name: 'USD Coin',
|
|
28
|
+
version: '2',
|
|
29
|
+
chainId: 1,
|
|
30
|
+
verifyingContract: USDC_ADDRESS,
|
|
31
|
+
};
|
|
32
|
+
// EIP-712 types for TransferWithAuthorization
|
|
33
|
+
const TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
34
|
+
TransferWithAuthorization: [
|
|
35
|
+
{ name: 'from', type: 'address' },
|
|
36
|
+
{ name: 'to', type: 'address' },
|
|
37
|
+
{ name: 'value', type: 'uint256' },
|
|
38
|
+
{ name: 'validAfter', type: 'uint256' },
|
|
39
|
+
{ name: 'validBefore', type: 'uint256' },
|
|
40
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
41
|
+
],
|
|
42
|
+
};
|
|
19
43
|
// File paths
|
|
20
44
|
const HOME_DIR = os.homedir();
|
|
21
45
|
const AGENTSTORE_DIR = path.join(HOME_DIR, '.agentstore');
|
|
@@ -29,7 +53,7 @@ const TX_HISTORY_FILE = path.join(AGENTSTORE_DIR, 'tx_history.json');
|
|
|
29
53
|
// Create public client for reading blockchain state
|
|
30
54
|
const publicClient = createPublicClient({
|
|
31
55
|
chain: mainnet,
|
|
32
|
-
transport: http(
|
|
56
|
+
transport: http(ETHEREUM_RPC),
|
|
33
57
|
});
|
|
34
58
|
// Ensure directories exist
|
|
35
59
|
function ensureDirectories() {
|
|
@@ -190,7 +214,7 @@ async function createNewWallet() {
|
|
|
190
214
|
address: account.address,
|
|
191
215
|
createdAt: new Date().toISOString(),
|
|
192
216
|
network: 'mainnet',
|
|
193
|
-
rpcEndpoint:
|
|
217
|
+
rpcEndpoint: ETHEREUM_RPC,
|
|
194
218
|
spendLimits: {
|
|
195
219
|
perTransaction: 100,
|
|
196
220
|
daily: 500,
|
|
@@ -219,7 +243,7 @@ async function ensureWalletExists() {
|
|
|
219
243
|
address: account.address,
|
|
220
244
|
createdAt: new Date().toISOString(),
|
|
221
245
|
network: 'mainnet',
|
|
222
|
-
rpcEndpoint:
|
|
246
|
+
rpcEndpoint: ETHEREUM_RPC,
|
|
223
247
|
spendLimits: {
|
|
224
248
|
perTransaction: 100,
|
|
225
249
|
daily: 500,
|
|
@@ -234,41 +258,50 @@ async function ensureWalletExists() {
|
|
|
234
258
|
fs.writeFileSync(TX_HISTORY_FILE, JSON.stringify([]), { mode: 0o600 });
|
|
235
259
|
return { address: account.address, created: true };
|
|
236
260
|
}
|
|
237
|
-
//
|
|
238
|
-
async function
|
|
261
|
+
// Get USDC balance for an address
|
|
262
|
+
async function getUsdcBalance(address) {
|
|
263
|
+
return publicClient.readContract({
|
|
264
|
+
address: USDC_ADDRESS,
|
|
265
|
+
abi: ERC20_BALANCE_ABI,
|
|
266
|
+
functionName: 'balanceOf',
|
|
267
|
+
args: [address],
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// Format USDC from raw bigint (6 decimals) to human-readable string
|
|
271
|
+
function formatUsdc(raw) {
|
|
272
|
+
return formatUnits(raw, USDC_DECIMALS);
|
|
273
|
+
}
|
|
274
|
+
// Parse USDC from human-readable string to raw bigint
|
|
275
|
+
function parseUsdc(amount) {
|
|
276
|
+
const str = typeof amount === 'number' ? amount.toFixed(USDC_DECIMALS) : amount;
|
|
277
|
+
return parseUnits(str, USDC_DECIMALS);
|
|
278
|
+
}
|
|
279
|
+
// Trigger funding flow and wait for USDC funds
|
|
280
|
+
async function triggerFundingFlow(requiredUsdc) {
|
|
239
281
|
const config = loadWalletConfig();
|
|
240
282
|
if (!config)
|
|
241
283
|
return false;
|
|
242
|
-
console.log('\n💳
|
|
284
|
+
console.log('\n💳 Fund your wallet with USDC\n');
|
|
243
285
|
console.log(` Wallet: ${config.address}`);
|
|
244
|
-
console.log(` Required:
|
|
245
|
-
// Get initial balance
|
|
246
|
-
const initialBalance = await
|
|
247
|
-
|
|
248
|
-
});
|
|
249
|
-
// Get onramp URL from API
|
|
286
|
+
console.log(` Required: $${requiredUsdc.toFixed(2)} USDC\n`);
|
|
287
|
+
// Get initial USDC balance
|
|
288
|
+
const initialBalance = await getUsdcBalance(config.address);
|
|
289
|
+
// Try to get onramp URL from API
|
|
250
290
|
const response = await fetch(`${API_BASE}/api/onramp/session`, {
|
|
251
291
|
method: 'POST',
|
|
252
292
|
headers: { 'Content-Type': 'application/json' },
|
|
253
293
|
body: JSON.stringify({
|
|
254
294
|
wallet_address: config.address,
|
|
255
|
-
amount_usd: Math.ceil(
|
|
295
|
+
amount_usd: Math.ceil(requiredUsdc),
|
|
296
|
+
asset: 'USDC',
|
|
256
297
|
}),
|
|
257
298
|
});
|
|
258
299
|
const result = await response.json();
|
|
259
300
|
if (!response.ok || !result.success) {
|
|
260
|
-
|
|
261
|
-
console.log('⚠️ Coinbase Onramp not configured.\n');
|
|
262
|
-
console.log(' Manual funding instructions:');
|
|
263
|
-
console.log(` 1. ${result.manual_instructions.step1}`);
|
|
264
|
-
console.log(` 2. ${result.manual_instructions.step2}`);
|
|
265
|
-
console.log(` 3. ${result.manual_instructions.step3}`);
|
|
266
|
-
console.log(`\n Your wallet address: ${config.address}`);
|
|
267
|
-
console.log('\n After funding, run the install command again.');
|
|
268
|
-
}
|
|
301
|
+
showFundingOptions(config.address, requiredUsdc);
|
|
269
302
|
return false;
|
|
270
303
|
}
|
|
271
|
-
// Open browser
|
|
304
|
+
// Open browser for Coinbase onramp
|
|
272
305
|
const { exec } = await import('child_process');
|
|
273
306
|
const openCmd = process.platform === 'darwin'
|
|
274
307
|
? `open "${result.onramp_url}"`
|
|
@@ -277,47 +310,61 @@ async function triggerFundingFlow(requiredUsd) {
|
|
|
277
310
|
: `xdg-open "${result.onramp_url}"`;
|
|
278
311
|
exec(openCmd);
|
|
279
312
|
console.log('🌐 Coinbase opened in your browser.\n');
|
|
280
|
-
console.log(' Complete the purchase, then wait for funds to arrive.');
|
|
281
|
-
console.log('
|
|
282
|
-
|
|
313
|
+
console.log(' Complete the USDC purchase, then wait for funds to arrive.');
|
|
314
|
+
console.log(' Or use one of the other options below while waiting.\n');
|
|
315
|
+
showFundingOptionsShort(config.address);
|
|
316
|
+
console.log('\n⏳ Waiting for USDC (Ctrl+C to cancel)...\n');
|
|
317
|
+
// Poll for USDC balance
|
|
318
|
+
const requiredRaw = parseUsdc(requiredUsdc);
|
|
283
319
|
const startTime = Date.now();
|
|
284
320
|
const maxWaitTime = 10 * 60 * 1000; // 10 minutes
|
|
285
321
|
const pollInterval = 10 * 1000; // 10 seconds
|
|
286
322
|
while (Date.now() - startTime < maxWaitTime) {
|
|
287
323
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
288
324
|
try {
|
|
289
|
-
const currentBalance = await
|
|
290
|
-
|
|
291
|
-
});
|
|
292
|
-
if (currentBalance > initialBalance) {
|
|
325
|
+
const currentBalance = await getUsdcBalance(config.address);
|
|
326
|
+
if (currentBalance >= requiredRaw && currentBalance > initialBalance) {
|
|
293
327
|
const added = currentBalance - initialBalance;
|
|
294
|
-
console.log(`\n✅
|
|
328
|
+
console.log(`\n✅ USDC received! +$${formatUsdc(added)} USDC\n`);
|
|
295
329
|
return true;
|
|
296
330
|
}
|
|
297
331
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
298
|
-
process.stdout.write(`\r Checking balance... (${elapsed}s elapsed)`);
|
|
332
|
+
process.stdout.write(`\r Checking USDC balance... (${elapsed}s elapsed)`);
|
|
299
333
|
}
|
|
300
334
|
catch {
|
|
301
335
|
// Ignore poll errors
|
|
302
336
|
}
|
|
303
337
|
}
|
|
304
|
-
console.log('\n⚠️ Timeout waiting for
|
|
338
|
+
console.log('\n⚠️ Timeout waiting for USDC. Run install again after funding.');
|
|
305
339
|
return false;
|
|
306
340
|
}
|
|
307
|
-
//
|
|
341
|
+
// Show all funding options
|
|
342
|
+
function showFundingOptions(address, requiredUsdc) {
|
|
343
|
+
console.log(' Three ways to fund your wallet:\n');
|
|
344
|
+
console.log(' 1. Buy with card (Coinbase):');
|
|
345
|
+
console.log(' agentstore wallet fund --wait\n');
|
|
346
|
+
console.log(' 2. Send USDC directly (from any wallet/exchange):');
|
|
347
|
+
console.log(` Send $${requiredUsdc.toFixed(2)} USDC (Ethereum) to:`);
|
|
348
|
+
console.log(` ${address}\n`);
|
|
349
|
+
console.log(' 3. Import an existing wallet with USDC:');
|
|
350
|
+
console.log(' agentstore wallet import\n');
|
|
351
|
+
console.log(' After funding, run the install command again.');
|
|
352
|
+
}
|
|
353
|
+
// Short version for inline display
|
|
354
|
+
function showFundingOptionsShort(address) {
|
|
355
|
+
console.log(' Other ways to add USDC:');
|
|
356
|
+
console.log(` - Send USDC (Ethereum) to: ${address}`);
|
|
357
|
+
console.log(' - Import existing wallet: agentstore wallet import');
|
|
358
|
+
}
|
|
359
|
+
// Get wallet balance (USDC)
|
|
308
360
|
async function getWalletBalance() {
|
|
309
361
|
const config = loadWalletConfig();
|
|
310
362
|
if (!config)
|
|
311
363
|
throw new Error('Wallet not initialized');
|
|
312
|
-
const
|
|
313
|
-
address: config.address,
|
|
314
|
-
});
|
|
315
|
-
const ethBalance = formatEther(balanceWei);
|
|
316
|
-
const ethPrice = await getEthPrice();
|
|
317
|
-
const usdBalance = parseFloat(ethBalance) * ethPrice;
|
|
364
|
+
const raw = await getUsdcBalance(config.address);
|
|
318
365
|
return {
|
|
319
|
-
|
|
320
|
-
|
|
366
|
+
usdc: formatUsdc(raw),
|
|
367
|
+
usdcRaw: raw,
|
|
321
368
|
};
|
|
322
369
|
}
|
|
323
370
|
// Check spend limits
|
|
@@ -353,100 +400,6 @@ function checkSpendLimit(amountUsd, config, txHistory) {
|
|
|
353
400
|
}
|
|
354
401
|
return { allowed: true };
|
|
355
402
|
}
|
|
356
|
-
// Send payment for agent
|
|
357
|
-
async function sendAgentPayment(params) {
|
|
358
|
-
const config = loadWalletConfig();
|
|
359
|
-
if (!config)
|
|
360
|
-
throw new Error('Wallet not initialized');
|
|
361
|
-
const privateKey = await loadPrivateKey();
|
|
362
|
-
if (!privateKey)
|
|
363
|
-
throw new Error('Could not load wallet private key');
|
|
364
|
-
const txHistory = loadTxHistory();
|
|
365
|
-
// Check spend limits
|
|
366
|
-
const limitCheck = checkSpendLimit(params.amountUsd, config, txHistory);
|
|
367
|
-
if (!limitCheck.allowed) {
|
|
368
|
-
throw new Error(limitCheck.reason);
|
|
369
|
-
}
|
|
370
|
-
// Check publisher allowlist
|
|
371
|
-
if (config.allowedPublishers.length > 0 && !config.allowedPublishers.includes(params.to.toLowerCase())) {
|
|
372
|
-
throw new Error(`Publisher ${params.to} is not in your allowed publishers list`);
|
|
373
|
-
}
|
|
374
|
-
// Get current ETH price
|
|
375
|
-
const ethPrice = await getEthPrice();
|
|
376
|
-
const amountEth = params.amountUsd / ethPrice;
|
|
377
|
-
const amountWei = parseEther(amountEth.toFixed(18));
|
|
378
|
-
// Check balance
|
|
379
|
-
const balance = await getWalletBalance();
|
|
380
|
-
if (parseFloat(balance.eth) < amountEth) {
|
|
381
|
-
throw new Error(`Insufficient balance: have ${balance.eth} ETH, need ${amountEth.toFixed(6)} ETH`);
|
|
382
|
-
}
|
|
383
|
-
// Create wallet client for signing
|
|
384
|
-
const account = privateKeyToAccount(privateKey);
|
|
385
|
-
const walletClient = createWalletClient({
|
|
386
|
-
account,
|
|
387
|
-
chain: mainnet,
|
|
388
|
-
transport: http(MEV_COMMIT_RPC),
|
|
389
|
-
});
|
|
390
|
-
console.log('Sending transaction...');
|
|
391
|
-
// Send transaction
|
|
392
|
-
const txHash = await walletClient.sendTransaction({
|
|
393
|
-
to: params.to,
|
|
394
|
-
value: amountWei,
|
|
395
|
-
});
|
|
396
|
-
// Record transaction
|
|
397
|
-
const txRecord = {
|
|
398
|
-
txHash,
|
|
399
|
-
to: params.to,
|
|
400
|
-
amountEth: amountEth.toFixed(6),
|
|
401
|
-
amountUsd: params.amountUsd,
|
|
402
|
-
agentId: params.agentId,
|
|
403
|
-
timestamp: new Date().toISOString(),
|
|
404
|
-
status: 'pending',
|
|
405
|
-
};
|
|
406
|
-
txHistory.push(txRecord);
|
|
407
|
-
saveTxHistory(txHistory);
|
|
408
|
-
console.log(`Transaction sent: ${txHash}`);
|
|
409
|
-
// Wait for confirmation
|
|
410
|
-
try {
|
|
411
|
-
console.log('Waiting for confirmation...');
|
|
412
|
-
const receipt = await publicClient.waitForTransactionReceipt({
|
|
413
|
-
hash: txHash,
|
|
414
|
-
confirmations: 2,
|
|
415
|
-
timeout: 120_000,
|
|
416
|
-
});
|
|
417
|
-
const txIndex = txHistory.findIndex((tx) => tx.txHash === txHash);
|
|
418
|
-
const txRecord = txHistory[txIndex];
|
|
419
|
-
if (txIndex !== -1 && txRecord) {
|
|
420
|
-
txRecord.status = receipt.status === 'success' ? 'confirmed' : 'failed';
|
|
421
|
-
saveTxHistory(txHistory);
|
|
422
|
-
}
|
|
423
|
-
if (receipt.status !== 'success') {
|
|
424
|
-
throw new Error('Transaction failed on chain');
|
|
425
|
-
}
|
|
426
|
-
console.log('✓ Transaction confirmed!');
|
|
427
|
-
}
|
|
428
|
-
catch (error) {
|
|
429
|
-
const txIndex = txHistory.findIndex((tx) => tx.txHash === txHash);
|
|
430
|
-
const txRecord = txHistory[txIndex];
|
|
431
|
-
if (txIndex !== -1 && txRecord) {
|
|
432
|
-
txRecord.status = 'failed';
|
|
433
|
-
saveTxHistory(txHistory);
|
|
434
|
-
}
|
|
435
|
-
throw error;
|
|
436
|
-
}
|
|
437
|
-
return { txHash, amountEth: amountEth.toFixed(6) };
|
|
438
|
-
}
|
|
439
|
-
// Get ETH price from CoinGecko
|
|
440
|
-
async function getEthPrice() {
|
|
441
|
-
try {
|
|
442
|
-
const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
|
|
443
|
-
const data = (await response.json());
|
|
444
|
-
return data.ethereum?.usd || 2000;
|
|
445
|
-
}
|
|
446
|
-
catch {
|
|
447
|
-
return 2000;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
403
|
// Prompt user for confirmation
|
|
451
404
|
function prompt(question) {
|
|
452
405
|
const rl = readline.createInterface({
|
|
@@ -460,27 +413,138 @@ function prompt(question) {
|
|
|
460
413
|
});
|
|
461
414
|
});
|
|
462
415
|
}
|
|
463
|
-
//
|
|
464
|
-
|
|
416
|
+
// Prompt for secret input (masked)
|
|
417
|
+
function promptSecret(question) {
|
|
418
|
+
return new Promise((resolve) => {
|
|
419
|
+
process.stdout.write(question);
|
|
420
|
+
const stdin = process.stdin;
|
|
421
|
+
const wasRaw = stdin.isRaw;
|
|
422
|
+
stdin.setRawMode?.(true);
|
|
423
|
+
stdin.resume();
|
|
424
|
+
stdin.setEncoding('utf8');
|
|
425
|
+
let input = '';
|
|
426
|
+
const onData = (ch) => {
|
|
427
|
+
if (ch === '\n' || ch === '\r') {
|
|
428
|
+
stdin.setRawMode?.(wasRaw ?? false);
|
|
429
|
+
stdin.pause();
|
|
430
|
+
stdin.removeListener('data', onData);
|
|
431
|
+
process.stdout.write('\n');
|
|
432
|
+
resolve(input);
|
|
433
|
+
}
|
|
434
|
+
else if (ch === '\u0003') {
|
|
435
|
+
// Ctrl+C
|
|
436
|
+
process.exit(0);
|
|
437
|
+
}
|
|
438
|
+
else if (ch === '\u007f' || ch === '\b') {
|
|
439
|
+
if (input.length > 0) {
|
|
440
|
+
input = input.slice(0, -1);
|
|
441
|
+
process.stdout.write('\b \b');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
input += ch;
|
|
446
|
+
process.stdout.write('*');
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
stdin.on('data', onData);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
// Check agent access — returns entitlement if already purchased, or 402 payment params
|
|
453
|
+
async function getPaymentRequired(agentId, walletAddress) {
|
|
454
|
+
try {
|
|
455
|
+
const response = await fetch(`${API_BASE}/api/agents/${encodeURIComponent(agentId)}/access`, {
|
|
456
|
+
headers: { 'X-Wallet-Address': walletAddress },
|
|
457
|
+
});
|
|
458
|
+
if (response.status === 200) {
|
|
459
|
+
const data = await response.json();
|
|
460
|
+
return { status: 'granted', entitlement: data.entitlement, install: data.install };
|
|
461
|
+
}
|
|
462
|
+
if (response.status === 402) {
|
|
463
|
+
const data = await response.json();
|
|
464
|
+
return { status: 'payment_required', payment: data.payment };
|
|
465
|
+
}
|
|
466
|
+
const data = await response.json();
|
|
467
|
+
return { status: 'error', error: data.error || `HTTP ${response.status}` };
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
return { status: 'error', error: error instanceof Error ? error.message : String(error) };
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// Sign EIP-3009 TransferWithAuthorization typed data
|
|
474
|
+
async function signTransferAuthorization(payment, privateKey, walletAddress) {
|
|
475
|
+
const account = privateKeyToAccount(privateKey);
|
|
476
|
+
const value = parseUsdc(payment.amount);
|
|
477
|
+
const validBefore = BigInt(Math.floor(new Date(payment.expires_at).getTime() / 1000));
|
|
478
|
+
const authNonce = ('0x' + crypto.randomBytes(32).toString('hex'));
|
|
479
|
+
const signature = await account.signTypedData({
|
|
480
|
+
domain: USDC_EIP712_DOMAIN,
|
|
481
|
+
types: TRANSFER_WITH_AUTHORIZATION_TYPES,
|
|
482
|
+
primaryType: 'TransferWithAuthorization',
|
|
483
|
+
message: {
|
|
484
|
+
from: walletAddress,
|
|
485
|
+
to: payment.payTo,
|
|
486
|
+
value,
|
|
487
|
+
validAfter: 0n,
|
|
488
|
+
validBefore,
|
|
489
|
+
nonce: authNonce,
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
// Parse signature into v, r, s components
|
|
493
|
+
const r = ('0x' + signature.slice(2, 66));
|
|
494
|
+
const s = ('0x' + signature.slice(66, 130));
|
|
495
|
+
const v = parseInt(signature.slice(130, 132), 16);
|
|
496
|
+
return {
|
|
497
|
+
from: walletAddress,
|
|
498
|
+
to: payment.payTo,
|
|
499
|
+
value: value.toString(),
|
|
500
|
+
validAfter: '0',
|
|
501
|
+
validBefore: validBefore.toString(),
|
|
502
|
+
nonce: authNonce,
|
|
503
|
+
v,
|
|
504
|
+
r,
|
|
505
|
+
s,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
// Submit signed x402 payment to API
|
|
509
|
+
async function submitX402Payment(agentId, walletAddress, payment, authorization) {
|
|
465
510
|
try {
|
|
466
|
-
const response = await fetch(`${API_BASE}/api/
|
|
511
|
+
const response = await fetch(`${API_BASE}/api/payments/submit`, {
|
|
467
512
|
method: 'POST',
|
|
468
513
|
headers: { 'Content-Type': 'application/json' },
|
|
469
514
|
body: JSON.stringify({
|
|
470
515
|
agent_id: agentId,
|
|
471
516
|
wallet_address: walletAddress,
|
|
472
|
-
|
|
517
|
+
payment_required: {
|
|
518
|
+
amount: payment.amount,
|
|
519
|
+
currency: payment.currency,
|
|
520
|
+
payTo: payment.payTo,
|
|
521
|
+
nonce: payment.nonce,
|
|
522
|
+
expires_at: payment.expires_at,
|
|
523
|
+
},
|
|
524
|
+
authorization,
|
|
473
525
|
}),
|
|
474
526
|
});
|
|
475
527
|
if (!response.ok) {
|
|
476
|
-
const error =
|
|
477
|
-
|
|
528
|
+
const error = await response.json();
|
|
529
|
+
if (response.status === 409) {
|
|
530
|
+
// Already purchased — re-check /access for the entitlement
|
|
531
|
+
console.log('Agent already purchased. Retrieving entitlement...');
|
|
532
|
+
const access = await getPaymentRequired(agentId, walletAddress);
|
|
533
|
+
if (access.status === 'granted' && access.entitlement) {
|
|
534
|
+
return {
|
|
535
|
+
entitlement_token: access.entitlement.token,
|
|
536
|
+
install: access.install,
|
|
537
|
+
proof: null,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
console.error(`Payment failed: ${error.error || response.statusText}`);
|
|
478
542
|
return null;
|
|
479
543
|
}
|
|
480
|
-
return
|
|
544
|
+
return await response.json();
|
|
481
545
|
}
|
|
482
546
|
catch (error) {
|
|
483
|
-
console.error(`
|
|
547
|
+
console.error(`Payment error: ${error instanceof Error ? error.message : error}`);
|
|
484
548
|
return null;
|
|
485
549
|
}
|
|
486
550
|
}
|
|
@@ -559,6 +623,9 @@ This is a prompt-based agent. Reference it by asking Claude to follow the instru
|
|
|
559
623
|
}
|
|
560
624
|
// Install command
|
|
561
625
|
async function installAgent(agentId, options) {
|
|
626
|
+
// --pay is a true alias for --yes
|
|
627
|
+
if (options.pay)
|
|
628
|
+
options.yes = true;
|
|
562
629
|
ensureDirectories();
|
|
563
630
|
console.log(`Fetching agent: ${agentId}...`);
|
|
564
631
|
const agent = await fetchAgent(agentId);
|
|
@@ -592,57 +659,71 @@ async function installAgent(agentId, options) {
|
|
|
592
659
|
}
|
|
593
660
|
}
|
|
594
661
|
console.log('└─────────────────────────────────────────────────┘');
|
|
595
|
-
// For paid agents, handle payment flow
|
|
662
|
+
// For paid agents, handle x402 USDC payment flow
|
|
596
663
|
let entitlementToken = null;
|
|
597
664
|
let expiresAt = null;
|
|
598
665
|
if (agent.type === 'proprietary' && agent.pricing.model !== 'free') {
|
|
599
|
-
|
|
666
|
+
const priceUsd = getPriceUsd(agent.pricing);
|
|
667
|
+
// Lazy wallet creation
|
|
600
668
|
const { address: walletAddress, created: walletCreated } = await ensureWalletExists();
|
|
601
669
|
if (walletCreated) {
|
|
602
670
|
console.log('\n🔐 Wallet created automatically');
|
|
603
671
|
console.log(` Address: ${walletAddress}`);
|
|
604
672
|
}
|
|
605
|
-
const wallet = loadWalletConfig();
|
|
606
|
-
const ethPrice = await getEthPrice();
|
|
607
|
-
const priceEth = getPriceUsd(agent.pricing) / ethPrice;
|
|
608
673
|
console.log('\n💰 Payment Required:');
|
|
609
|
-
console.log(` Price: $${
|
|
610
|
-
console.log(` Your wallet: ${
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
if (existing) {
|
|
674
|
+
console.log(` Price: $${priceUsd.toFixed(2)} USDC`);
|
|
675
|
+
console.log(` Your wallet: ${walletAddress}`);
|
|
676
|
+
// Check local entitlements first (skip API call if already purchased)
|
|
677
|
+
const localEntitlements = loadEntitlements();
|
|
678
|
+
const localEntitlement = localEntitlements.find((e) => e.agentId === agent.agent_id);
|
|
679
|
+
if (localEntitlement) {
|
|
616
680
|
console.log('\n✓ Already purchased! Using existing entitlement.');
|
|
617
|
-
entitlementToken =
|
|
618
|
-
expiresAt =
|
|
681
|
+
entitlementToken = localEntitlement.token;
|
|
682
|
+
expiresAt = localEntitlement.expiresAt;
|
|
619
683
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
684
|
+
// If no local entitlement, check server
|
|
685
|
+
if (!entitlementToken) {
|
|
686
|
+
const accessResult = await getPaymentRequired(agent.agent_id, walletAddress);
|
|
687
|
+
if (accessResult.status === 'error') {
|
|
688
|
+
console.log(`\n❌ Access check failed: ${accessResult.error}`);
|
|
689
|
+
process.exit(1);
|
|
690
|
+
}
|
|
691
|
+
if (accessResult.status === 'granted' && accessResult.entitlement) {
|
|
692
|
+
console.log('\n✓ Already purchased! Using existing entitlement.');
|
|
693
|
+
entitlementToken = accessResult.entitlement.token;
|
|
694
|
+
expiresAt = accessResult.entitlement.expires_at;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (!entitlementToken) {
|
|
698
|
+
// Need to pay — fetch 402 details
|
|
699
|
+
const accessResult = await getPaymentRequired(agent.agent_id, walletAddress);
|
|
700
|
+
if (accessResult.status !== 'payment_required') {
|
|
701
|
+
if (accessResult.status === 'granted') {
|
|
702
|
+
console.log('\n✓ Access granted (no payment needed).');
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
console.log(`\n❌ Unexpected access status: ${accessResult.status}`);
|
|
628
706
|
process.exit(1);
|
|
629
707
|
}
|
|
630
|
-
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
const paymentRequired = accessResult.payment;
|
|
711
|
+
// Check USDC balance
|
|
631
712
|
try {
|
|
632
713
|
const balance = await getWalletBalance();
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if (balance.
|
|
636
|
-
console.log(`\n⚠️ Insufficient
|
|
637
|
-
const funded = await triggerFundingFlow(
|
|
714
|
+
const requiredRaw = parseUsdc(paymentRequired.amount);
|
|
715
|
+
console.log(` Your USDC balance: $${balance.usdc}`);
|
|
716
|
+
if (balance.usdcRaw < requiredRaw) {
|
|
717
|
+
console.log(`\n⚠️ Insufficient USDC. Need $${paymentRequired.amount}, have $${balance.usdc}`);
|
|
718
|
+
const funded = await triggerFundingFlow(priceUsd);
|
|
638
719
|
if (!funded) {
|
|
639
720
|
process.exit(1);
|
|
640
721
|
}
|
|
641
722
|
// Re-check balance after funding
|
|
642
723
|
const newBalance = await getWalletBalance();
|
|
643
|
-
console.log(` New balance:
|
|
644
|
-
if (newBalance.
|
|
645
|
-
console.log(`\n❌ Still insufficient. Need $${
|
|
724
|
+
console.log(` New USDC balance: $${newBalance.usdc}`);
|
|
725
|
+
if (newBalance.usdcRaw < requiredRaw) {
|
|
726
|
+
console.log(`\n❌ Still insufficient. Need $${paymentRequired.amount}, have $${newBalance.usdc}`);
|
|
646
727
|
process.exit(1);
|
|
647
728
|
}
|
|
648
729
|
}
|
|
@@ -651,47 +732,53 @@ async function installAgent(agentId, options) {
|
|
|
651
732
|
console.log(`\n❌ Could not check balance: ${error instanceof Error ? error.message : error}`);
|
|
652
733
|
process.exit(1);
|
|
653
734
|
}
|
|
654
|
-
//
|
|
735
|
+
// Check spend limits
|
|
736
|
+
const config = loadWalletConfig();
|
|
737
|
+
const txHistory = loadTxHistory();
|
|
738
|
+
const limitCheck = checkSpendLimit(priceUsd, config, txHistory);
|
|
739
|
+
if (!limitCheck.allowed) {
|
|
740
|
+
console.log(`\n❌ Spend limit exceeded: ${limitCheck.reason}`);
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
// Confirm payment (skip with --yes or --pay)
|
|
655
744
|
if (!options.yes) {
|
|
656
|
-
const confirm = await prompt(`\nPay $${
|
|
745
|
+
const confirm = await prompt(`\nPay $${paymentRequired.amount} USDC for "${agent.name}"? (y/n) `);
|
|
657
746
|
if (confirm !== 'y' && confirm !== 'yes') {
|
|
658
747
|
console.log('Payment cancelled.');
|
|
659
748
|
process.exit(0);
|
|
660
749
|
}
|
|
661
750
|
}
|
|
662
|
-
//
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
amountUsd: getPriceUsd(agent.pricing),
|
|
668
|
-
agentId: agent.agent_id,
|
|
669
|
-
});
|
|
670
|
-
txHash = payment.txHash;
|
|
671
|
-
console.log(`✓ Payment sent: ${txHash}`);
|
|
751
|
+
// Sign the EIP-3009 authorization
|
|
752
|
+
const privateKey = await loadPrivateKey();
|
|
753
|
+
if (!privateKey) {
|
|
754
|
+
console.log('\n❌ Could not load wallet private key');
|
|
755
|
+
process.exit(1);
|
|
672
756
|
}
|
|
673
|
-
|
|
674
|
-
|
|
757
|
+
console.log('\n🔐 Signing USDC authorization...');
|
|
758
|
+
const authorization = await signTransferAuthorization(paymentRequired, privateKey, walletAddress);
|
|
759
|
+
// Submit to API for facilitator relay
|
|
760
|
+
console.log('🔄 Processing payment via x402...');
|
|
761
|
+
const result = await submitX402Payment(agent.agent_id, walletAddress, paymentRequired, authorization);
|
|
762
|
+
if (!result) {
|
|
763
|
+
console.log('❌ Payment processing failed.');
|
|
675
764
|
process.exit(1);
|
|
676
765
|
}
|
|
766
|
+
entitlementToken = result.entitlement_token;
|
|
767
|
+
expiresAt = null;
|
|
768
|
+
console.log('✓ Payment confirmed! (gasless USDC via x402)');
|
|
769
|
+
// Record in local tx history
|
|
770
|
+
const txProof = result.proof;
|
|
771
|
+
txHistory.push({
|
|
772
|
+
txHash: txProof?.tx_hash || 'x402-' + Date.now(),
|
|
773
|
+
to: paymentRequired.payTo,
|
|
774
|
+
amountUsdc: paymentRequired.amount,
|
|
775
|
+
amountUsd: priceUsd,
|
|
776
|
+
agentId: agent.agent_id,
|
|
777
|
+
timestamp: new Date().toISOString(),
|
|
778
|
+
status: 'confirmed',
|
|
779
|
+
});
|
|
780
|
+
saveTxHistory(txHistory);
|
|
677
781
|
}
|
|
678
|
-
// Verify payment with API
|
|
679
|
-
if (!txHash) {
|
|
680
|
-
console.log('\n💳 Payment options:');
|
|
681
|
-
console.log(' 1. Auto-pay: agentstore install ' + agent.agent_id + ' --pay');
|
|
682
|
-
console.log(' 2. Manual: Send ' + priceEth.toFixed(6) + ' ETH to ' + (agent.publisher.payout_address || '[publisher]'));
|
|
683
|
-
console.log(' Then: agentstore install ' + agent.agent_id + ' --tx-hash 0x...');
|
|
684
|
-
process.exit(1);
|
|
685
|
-
}
|
|
686
|
-
console.log('\nVerifying payment with marketplace...');
|
|
687
|
-
const purchase = await purchaseAgent(agent.agent_id, wallet.address, txHash);
|
|
688
|
-
if (!purchase) {
|
|
689
|
-
console.log('❌ Payment verification failed.');
|
|
690
|
-
process.exit(1);
|
|
691
|
-
}
|
|
692
|
-
entitlementToken = purchase.entitlement_token;
|
|
693
|
-
expiresAt = purchase.expires_at;
|
|
694
|
-
console.log('✓ Payment verified!');
|
|
695
782
|
}
|
|
696
783
|
}
|
|
697
784
|
console.log('\nInstalling...');
|
|
@@ -890,9 +977,8 @@ program
|
|
|
890
977
|
program
|
|
891
978
|
.command('install <agent_id>')
|
|
892
979
|
.description('Install an agent from the marketplace')
|
|
893
|
-
.option('-y, --yes', 'Skip confirmation /
|
|
894
|
-
.option('--pay', '
|
|
895
|
-
.option('--tx-hash <hash>', 'Transaction hash for manual payment verification')
|
|
980
|
+
.option('-y, --yes', 'Skip confirmation / auto-confirm payment')
|
|
981
|
+
.option('--pay', 'Auto-confirm payment (alias for --yes)')
|
|
896
982
|
.action(installAgent);
|
|
897
983
|
program
|
|
898
984
|
.command('list')
|
|
@@ -952,7 +1038,7 @@ program
|
|
|
952
1038
|
console.log();
|
|
953
1039
|
}
|
|
954
1040
|
console.log('Install with: agentstore install <agent_id>');
|
|
955
|
-
console.log('
|
|
1041
|
+
console.log('Paid agents prompt for USDC payment automatically.');
|
|
956
1042
|
}
|
|
957
1043
|
catch (error) {
|
|
958
1044
|
console.error(`Error: ${error instanceof Error ? error.message : error}`);
|
|
@@ -978,8 +1064,11 @@ walletCmd
|
|
|
978
1064
|
const { address } = await createNewWallet();
|
|
979
1065
|
console.log('\n✅ Wallet created!');
|
|
980
1066
|
console.log(`\nAddress: ${address}`);
|
|
981
|
-
console.log('\n⚠️ Fund this address with
|
|
982
|
-
console.log('
|
|
1067
|
+
console.log('\n⚠️ Fund this address with USDC to purchase paid agents.');
|
|
1068
|
+
console.log(' Options:');
|
|
1069
|
+
console.log(' 1. Buy with card: agentstore wallet fund');
|
|
1070
|
+
console.log(` 2. Send USDC (Ethereum) from any wallet to: ${address}`);
|
|
1071
|
+
console.log(' 3. Import existing wallet: agentstore wallet import');
|
|
983
1072
|
}
|
|
984
1073
|
catch (error) {
|
|
985
1074
|
console.error(`Error: ${error instanceof Error ? error.message : error}`);
|
|
@@ -988,7 +1077,7 @@ walletCmd
|
|
|
988
1077
|
});
|
|
989
1078
|
walletCmd
|
|
990
1079
|
.command('balance')
|
|
991
|
-
.description('Show wallet balance')
|
|
1080
|
+
.description('Show wallet USDC balance')
|
|
992
1081
|
.action(async () => {
|
|
993
1082
|
try {
|
|
994
1083
|
if (!walletExists()) {
|
|
@@ -996,10 +1085,11 @@ walletCmd
|
|
|
996
1085
|
process.exit(1);
|
|
997
1086
|
}
|
|
998
1087
|
const config = loadWalletConfig();
|
|
999
|
-
console.log(`\
|
|
1000
|
-
console.log('Fetching balance...');
|
|
1088
|
+
console.log(`\nWallet: ${config?.address}`);
|
|
1089
|
+
console.log('Fetching USDC balance...');
|
|
1001
1090
|
const balance = await getWalletBalance();
|
|
1002
|
-
console.log(
|
|
1091
|
+
console.log(`USDC Balance: $${balance.usdc}`);
|
|
1092
|
+
console.log('Network: Ethereum Mainnet');
|
|
1003
1093
|
// Show spending stats
|
|
1004
1094
|
const txHistory = loadTxHistory();
|
|
1005
1095
|
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;
|
|
@@ -1041,7 +1131,7 @@ walletCmd
|
|
|
1041
1131
|
const date = new Date(tx.timestamp).toLocaleDateString();
|
|
1042
1132
|
const statusIcon = tx.status === 'confirmed' ? '✓' : tx.status === 'pending' ? '⏳' : '✗';
|
|
1043
1133
|
console.log(` ${statusIcon} ${date} | ${tx.agentId}`);
|
|
1044
|
-
console.log(`
|
|
1134
|
+
console.log(` $${tx.amountUsdc || tx.amountUsd} USDC → ${tx.to.slice(0, 10)}...`);
|
|
1045
1135
|
console.log(` ${tx.txHash.slice(0, 20)}...`);
|
|
1046
1136
|
console.log();
|
|
1047
1137
|
}
|
|
@@ -1068,12 +1158,52 @@ walletCmd
|
|
|
1068
1158
|
process.exit(1);
|
|
1069
1159
|
}
|
|
1070
1160
|
});
|
|
1161
|
+
walletCmd
|
|
1162
|
+
.command('import')
|
|
1163
|
+
.description('Import an existing wallet by private key')
|
|
1164
|
+
.action(async () => {
|
|
1165
|
+
try {
|
|
1166
|
+
if (walletExists()) {
|
|
1167
|
+
console.log('Wallet already exists. Delete ~/.agentstore/wallet.* files to import a new one.');
|
|
1168
|
+
process.exit(1);
|
|
1169
|
+
}
|
|
1170
|
+
const key = await promptSecret('Enter private key (0x...): ');
|
|
1171
|
+
if (!key.startsWith('0x') || key.length !== 66) {
|
|
1172
|
+
console.log('Invalid private key format. Must be a 0x-prefixed 64-character hex string.');
|
|
1173
|
+
process.exit(1);
|
|
1174
|
+
}
|
|
1175
|
+
ensureDirectories();
|
|
1176
|
+
const account = privateKeyToAccount(key);
|
|
1177
|
+
const config = {
|
|
1178
|
+
address: account.address,
|
|
1179
|
+
createdAt: new Date().toISOString(),
|
|
1180
|
+
network: 'mainnet',
|
|
1181
|
+
rpcEndpoint: ETHEREUM_RPC,
|
|
1182
|
+
spendLimits: { perTransaction: 100, daily: 500, weekly: 2000 },
|
|
1183
|
+
allowedPublishers: [],
|
|
1184
|
+
};
|
|
1185
|
+
const password = await getOrCreatePassword();
|
|
1186
|
+
const encrypted = encryptKey(key, password);
|
|
1187
|
+
fs.writeFileSync(KEYSTORE_FILE, JSON.stringify(encrypted), { mode: 0o600 });
|
|
1188
|
+
fs.writeFileSync(WALLET_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
1189
|
+
fs.writeFileSync(TX_HISTORY_FILE, JSON.stringify([]), { mode: 0o600 });
|
|
1190
|
+
// Show USDC balance
|
|
1191
|
+
const usdcRaw = await getUsdcBalance(account.address);
|
|
1192
|
+
console.log(`\n✅ Wallet imported!`);
|
|
1193
|
+
console.log(` Address: ${account.address}`);
|
|
1194
|
+
console.log(` USDC Balance: $${formatUsdc(usdcRaw)}`);
|
|
1195
|
+
}
|
|
1196
|
+
catch (error) {
|
|
1197
|
+
console.error(`Error: ${error instanceof Error ? error.message : error}`);
|
|
1198
|
+
process.exit(1);
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1071
1201
|
walletCmd
|
|
1072
1202
|
.command('fund')
|
|
1073
|
-
.description('Fund your wallet with
|
|
1203
|
+
.description('Fund your wallet with USDC')
|
|
1074
1204
|
.option('-a, --amount <usd>', 'Amount in USD to purchase', parseFloat)
|
|
1075
1205
|
.option('--no-open', 'Print URL instead of opening browser')
|
|
1076
|
-
.option('--wait', 'Wait and poll for
|
|
1206
|
+
.option('--wait', 'Wait and poll for USDC to arrive')
|
|
1077
1207
|
.action(async (options) => {
|
|
1078
1208
|
try {
|
|
1079
1209
|
if (!walletExists()) {
|
|
@@ -1085,23 +1215,19 @@ walletCmd
|
|
|
1085
1215
|
console.log('Failed to load wallet config');
|
|
1086
1216
|
process.exit(1);
|
|
1087
1217
|
}
|
|
1088
|
-
console.log('\n💳
|
|
1218
|
+
console.log('\n💳 Fund Your Wallet with USDC\n');
|
|
1089
1219
|
console.log(` Wallet: ${config.address}`);
|
|
1090
|
-
// Get initial balance for comparison
|
|
1220
|
+
// Get initial USDC balance for comparison
|
|
1091
1221
|
let initialBalance;
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
}
|
|
1099
|
-
catch {
|
|
1100
|
-
// Ignore balance fetch errors
|
|
1101
|
-
}
|
|
1222
|
+
try {
|
|
1223
|
+
initialBalance = await getUsdcBalance(config.address);
|
|
1224
|
+
console.log(` Current USDC: $${formatUsdc(initialBalance)}`);
|
|
1225
|
+
}
|
|
1226
|
+
catch {
|
|
1227
|
+
// Ignore balance fetch errors
|
|
1102
1228
|
}
|
|
1103
1229
|
if (options.amount) {
|
|
1104
|
-
console.log(` Amount: $${options.amount}
|
|
1230
|
+
console.log(` Amount: $${options.amount} USDC`);
|
|
1105
1231
|
}
|
|
1106
1232
|
console.log('\n🔄 Generating secure onramp session...');
|
|
1107
1233
|
// Call API to get onramp URL
|
|
@@ -1111,38 +1237,22 @@ walletCmd
|
|
|
1111
1237
|
body: JSON.stringify({
|
|
1112
1238
|
wallet_address: config.address,
|
|
1113
1239
|
amount_usd: options.amount,
|
|
1240
|
+
asset: 'USDC',
|
|
1114
1241
|
}),
|
|
1115
1242
|
});
|
|
1116
1243
|
const result = await response.json();
|
|
1117
1244
|
if (!response.ok || !result.success) {
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
console.log('\n⚠️ Coinbase Onramp not configured on server.\n');
|
|
1121
|
-
console.log(' Manual funding instructions:');
|
|
1122
|
-
console.log(` 1. ${result.manual_instructions.step1}`);
|
|
1123
|
-
console.log(` 2. ${result.manual_instructions.step2}`);
|
|
1124
|
-
console.log(` 3. ${result.manual_instructions.step3}`);
|
|
1125
|
-
console.log(`\n Your wallet address: ${config.address}`);
|
|
1126
|
-
}
|
|
1127
|
-
else {
|
|
1128
|
-
console.log(`\n❌ Error: ${result.error || result.message || 'Unknown error'}`);
|
|
1129
|
-
if (result.fallback) {
|
|
1130
|
-
console.log(`\n ${result.fallback.message}`);
|
|
1131
|
-
console.log(` 1. ${result.fallback.step1}`);
|
|
1132
|
-
console.log(` 2. ${result.fallback.step2}`);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1245
|
+
console.log('\n⚠️ Coinbase Onramp unavailable.\n');
|
|
1246
|
+
showFundingOptions(config.address, options.amount || 10);
|
|
1135
1247
|
process.exit(1);
|
|
1136
1248
|
}
|
|
1137
1249
|
const onrampUrl = result.onramp_url;
|
|
1138
1250
|
if (options.open === false) {
|
|
1139
|
-
// Just print the URL
|
|
1140
1251
|
console.log('\n✅ Onramp URL generated:\n');
|
|
1141
1252
|
console.log(` ${onrampUrl}\n`);
|
|
1142
|
-
console.log(' Open this URL in your browser to
|
|
1253
|
+
console.log(' Open this URL in your browser to purchase USDC.');
|
|
1143
1254
|
}
|
|
1144
1255
|
else {
|
|
1145
|
-
// Open in default browser
|
|
1146
1256
|
console.log('\n🌐 Opening Coinbase in your browser...\n');
|
|
1147
1257
|
const { exec } = await import('child_process');
|
|
1148
1258
|
const openCmd = process.platform === 'darwin'
|
|
@@ -1156,38 +1266,35 @@ walletCmd
|
|
|
1156
1266
|
console.log(` ${onrampUrl}\n`);
|
|
1157
1267
|
}
|
|
1158
1268
|
});
|
|
1159
|
-
console.log(' Complete the purchase in your browser.');
|
|
1160
|
-
console.log('
|
|
1269
|
+
console.log(' Complete the USDC purchase in your browser.');
|
|
1270
|
+
console.log(' USDC will arrive in your wallet within a few minutes.\n');
|
|
1271
|
+
showFundingOptionsShort(config.address);
|
|
1161
1272
|
}
|
|
1162
|
-
// Poll for balance changes if --wait flag is set
|
|
1273
|
+
// Poll for USDC balance changes if --wait flag is set
|
|
1163
1274
|
if (options.wait && initialBalance !== undefined) {
|
|
1164
|
-
console.log('⏳ Waiting for
|
|
1275
|
+
console.log('\n⏳ Waiting for USDC to arrive (Ctrl+C to cancel)...\n');
|
|
1165
1276
|
const startTime = Date.now();
|
|
1166
1277
|
const maxWaitTime = 10 * 60 * 1000; // 10 minutes
|
|
1167
1278
|
const pollInterval = 15 * 1000; // 15 seconds
|
|
1168
1279
|
while (Date.now() - startTime < maxWaitTime) {
|
|
1169
1280
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1170
1281
|
try {
|
|
1171
|
-
const currentBalance = await
|
|
1172
|
-
address: config.address,
|
|
1173
|
-
});
|
|
1282
|
+
const currentBalance = await getUsdcBalance(config.address);
|
|
1174
1283
|
if (currentBalance > initialBalance) {
|
|
1175
1284
|
const added = currentBalance - initialBalance;
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
console.log(
|
|
1179
|
-
console.log(` Added: ${formatEther(added)} ETH (~$${addedUsd.toFixed(2)})`);
|
|
1180
|
-
console.log(` New balance: ${formatEther(currentBalance)} ETH`);
|
|
1285
|
+
console.log('\n✅ USDC received!\n');
|
|
1286
|
+
console.log(` Added: $${formatUsdc(added)} USDC`);
|
|
1287
|
+
console.log(` New balance: $${formatUsdc(currentBalance)} USDC`);
|
|
1181
1288
|
process.exit(0);
|
|
1182
1289
|
}
|
|
1183
1290
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
1184
|
-
process.stdout.write(`\r Checking... (${elapsed}s elapsed)`);
|
|
1291
|
+
process.stdout.write(`\r Checking USDC... (${elapsed}s elapsed)`);
|
|
1185
1292
|
}
|
|
1186
1293
|
catch {
|
|
1187
1294
|
// Ignore individual poll errors
|
|
1188
1295
|
}
|
|
1189
1296
|
}
|
|
1190
|
-
console.log('\n\n⚠️ Timed out waiting for
|
|
1297
|
+
console.log('\n\n⚠️ Timed out waiting for USDC.');
|
|
1191
1298
|
console.log(' Funds may still arrive - check your balance later with:');
|
|
1192
1299
|
console.log(' agentstore wallet balance\n');
|
|
1193
1300
|
}
|