agentstore 1.0.0 → 1.0.2

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.
Files changed (3) hide show
  1. package/README.md +10 -18
  2. package/dist/index.js +320 -283
  3. 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
- ```bash
8
- npm install -g agentstore
9
- ```
7
+ **Native plugin** (recommended — no npm needed):
10
8
 
11
- Or use directly:
12
-
13
- ```bash
14
- npx agentstore browse
9
+ ```
10
+ /plugin marketplace add techgangboss/agentstore
11
+ /plugin install code-reviewer@agentstore
15
12
  ```
16
13
 
17
- ## Quick Start
14
+ **npm CLI** (adds wallet, payments, and publishing):
18
15
 
19
16
  ```bash
20
- # Browse available agents
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
@@ -49,12 +41,12 @@ Or skip the CLI entirely and use the API:
49
41
 
50
42
  ```bash
51
43
  # Register (no auth needed)
52
- curl -X POST https://api.agentstore.dev/api/publishers \
44
+ curl -X POST https://api.agentstore.tools/api/publishers \
53
45
  -H "Content-Type: application/json" \
54
46
  -d '{"name":"my-publisher","display_name":"My Publisher"}'
55
47
 
56
48
  # Publish a free agent (no auth needed)
57
- curl -X POST https://api.agentstore.dev/api/publishers/agents/simple \
49
+ curl -X POST https://api.agentstore.tools/api/publishers/agents/simple \
58
50
  -H "Content-Type: application/json" \
59
51
  -d '{"publisher_id":"my-publisher","name":"My Agent","description":"Does cool stuff","version":"1.0.0"}'
60
52
  ```
@@ -88,7 +80,7 @@ agentstore install publisher.paid-agent --pay
88
80
  ## Links
89
81
 
90
82
  - Website: [agentstore.tools](https://agentstore.tools)
91
- - API Docs: [api.agentstore.dev/api](https://api.agentstore.dev/api)
83
+ - API Docs: [api.agentstore.tools/api](https://api.agentstore.tools/api)
92
84
  - GitHub: [github.com/techgangboss/agentstore](https://github.com/techgangboss/agentstore)
93
85
 
94
86
  ## License
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, createWalletClient, http, formatEther, parseEther, } from 'viem';
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
- const API_BASE = 'https://api.agentstore.dev';
15
+ const API_BASE = 'https://api.agentstore.tools';
16
16
  const MEV_COMMIT_RPC = 'https://fastrpc.mev-commit.xyz';
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');
@@ -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
- // Trigger funding flow and wait for funds
238
- async function triggerFundingFlow(requiredUsd) {
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💳 Opening Coinbase to fund your wallet...\n');
284
+ console.log('\n💳 Fund your wallet with USDC\n');
243
285
  console.log(` Wallet: ${config.address}`);
244
- console.log(` Required: ~$${requiredUsd} USD\n`);
245
- // Get initial balance
246
- const initialBalance = await publicClient.getBalance({
247
- address: config.address,
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(requiredUsd * 1.1), // Add 10% buffer for gas
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
- if (result.manual_instructions) {
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(' Waiting for funds (Ctrl+C to cancel)...\n');
282
- // Poll for balance
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 publicClient.getBalance({
290
- address: config.address,
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✅ Funds received! +${formatEther(added)} ETH\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 funds. Run install again after funding.');
338
+ console.log('\n⚠️ Timeout waiting for USDC. Run install again after funding.');
305
339
  return false;
306
340
  }
307
- // Get wallet balance
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 balanceWei = await publicClient.getBalance({
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
- eth: ethBalance,
320
- usd: Math.round(usdBalance * 100) / 100,
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,90 @@ function prompt(question) {
460
413
  });
461
414
  });
462
415
  }
463
- // Purchase agent via API
464
- async function purchaseAgent(agentId, walletAddress, txHash) {
416
+ // Check agent access — returns entitlement if already purchased, or 402 payment params
417
+ async function getPaymentRequired(agentId, walletAddress) {
418
+ try {
419
+ const response = await fetch(`${API_BASE}/api/agents/${encodeURIComponent(agentId)}/access`, {
420
+ headers: { 'X-Wallet-Address': walletAddress },
421
+ });
422
+ if (response.status === 200) {
423
+ const data = await response.json();
424
+ return { status: 'granted', entitlement: data.entitlement, install: data.install };
425
+ }
426
+ if (response.status === 402) {
427
+ const data = await response.json();
428
+ return { status: 'payment_required', payment: data.payment };
429
+ }
430
+ const data = await response.json();
431
+ return { status: 'error', error: data.error || `HTTP ${response.status}` };
432
+ }
433
+ catch (error) {
434
+ return { status: 'error', error: error instanceof Error ? error.message : String(error) };
435
+ }
436
+ }
437
+ // Sign EIP-3009 TransferWithAuthorization typed data
438
+ async function signTransferAuthorization(payment, privateKey, walletAddress) {
439
+ const account = privateKeyToAccount(privateKey);
440
+ const value = parseUsdc(payment.amount);
441
+ const validBefore = BigInt(Math.floor(new Date(payment.expires_at).getTime() / 1000));
442
+ const authNonce = ('0x' + crypto.randomBytes(32).toString('hex'));
443
+ const signature = await account.signTypedData({
444
+ domain: USDC_EIP712_DOMAIN,
445
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
446
+ primaryType: 'TransferWithAuthorization',
447
+ message: {
448
+ from: walletAddress,
449
+ to: payment.payTo,
450
+ value,
451
+ validAfter: 0n,
452
+ validBefore,
453
+ nonce: authNonce,
454
+ },
455
+ });
456
+ // Parse signature into v, r, s components
457
+ const r = ('0x' + signature.slice(2, 66));
458
+ const s = ('0x' + signature.slice(66, 130));
459
+ const v = parseInt(signature.slice(130, 132), 16);
460
+ return {
461
+ from: walletAddress,
462
+ to: payment.payTo,
463
+ value: value.toString(),
464
+ validAfter: '0',
465
+ validBefore: validBefore.toString(),
466
+ nonce: authNonce,
467
+ v,
468
+ r,
469
+ s,
470
+ };
471
+ }
472
+ // Submit signed x402 payment to API
473
+ async function submitX402Payment(agentId, walletAddress, payment, authorization) {
465
474
  try {
466
- const response = await fetch(`${API_BASE}/api/purchase`, {
475
+ const response = await fetch(`${API_BASE}/api/payments/submit`, {
467
476
  method: 'POST',
468
477
  headers: { 'Content-Type': 'application/json' },
469
478
  body: JSON.stringify({
470
479
  agent_id: agentId,
471
480
  wallet_address: walletAddress,
472
- tx_hash: txHash,
481
+ payment_required: {
482
+ amount: payment.amount,
483
+ currency: payment.currency,
484
+ payTo: payment.payTo,
485
+ nonce: payment.nonce,
486
+ expires_at: payment.expires_at,
487
+ },
488
+ authorization,
473
489
  }),
474
490
  });
475
491
  if (!response.ok) {
476
- const error = (await response.json());
477
- console.error(`Purchase failed: ${error.error || response.statusText}`);
492
+ const error = await response.json();
493
+ console.error(`Payment failed: ${error.error || response.statusText}`);
478
494
  return null;
479
495
  }
480
- return (await response.json());
496
+ return await response.json();
481
497
  }
482
498
  catch (error) {
483
- console.error(`Purchase error: ${error instanceof Error ? error.message : error}`);
499
+ console.error(`Payment error: ${error instanceof Error ? error.message : error}`);
484
500
  return null;
485
501
  }
486
502
  }
@@ -592,106 +608,107 @@ async function installAgent(agentId, options) {
592
608
  }
593
609
  }
594
610
  console.log('└─────────────────────────────────────────────────┘');
595
- // For paid agents, handle payment flow
611
+ // For paid agents, handle x402 USDC payment flow
596
612
  let entitlementToken = null;
597
613
  let expiresAt = null;
598
614
  if (agent.type === 'proprietary' && agent.pricing.model !== 'free') {
599
- // Lazy wallet creation - create if doesn't exist
615
+ const priceUsd = getPriceUsd(agent.pricing);
616
+ // Lazy wallet creation
600
617
  const { address: walletAddress, created: walletCreated } = await ensureWalletExists();
601
618
  if (walletCreated) {
602
619
  console.log('\n🔐 Wallet created automatically');
603
620
  console.log(` Address: ${walletAddress}`);
604
621
  }
605
- const wallet = loadWalletConfig();
606
- const ethPrice = await getEthPrice();
607
- const priceEth = getPriceUsd(agent.pricing) / ethPrice;
608
622
  console.log('\n💰 Payment Required:');
609
- console.log(` Price: $${getPriceUsd(agent.pricing)} (~${priceEth.toFixed(6)} ETH)`);
610
- console.log(` Your wallet: ${wallet.address}`);
611
- console.log(` ETH Price: $${ethPrice}`);
612
- // Check if already purchased
613
- const entitlements = loadEntitlements();
614
- const existing = entitlements.find((e) => e.agentId === agent.agent_id);
615
- if (existing) {
623
+ console.log(` Price: $${priceUsd.toFixed(2)} USDC`);
624
+ console.log(` Your wallet: ${walletAddress}`);
625
+ // Check access status (entitlement or 402)
626
+ const accessResult = await getPaymentRequired(agent.agent_id, walletAddress);
627
+ if (accessResult.status === 'error') {
628
+ console.log(`\n❌ Access check failed: ${accessResult.error}`);
629
+ process.exit(1);
630
+ }
631
+ if (accessResult.status === 'granted' && accessResult.entitlement) {
616
632
  console.log('\n✓ Already purchased! Using existing entitlement.');
617
- entitlementToken = existing.token;
618
- expiresAt = existing.expiresAt;
633
+ entitlementToken = accessResult.entitlement.token;
634
+ expiresAt = accessResult.entitlement.expires_at;
619
635
  }
620
- else {
621
- let txHash = options.txHash;
622
- // Direct payment with --pay flag
623
- if (options.pay && !txHash) {
624
- const payoutAddress = agent.publisher.payout_address;
625
- if (!payoutAddress) {
626
- console.log('\n❌ Publisher has no payout address configured.');
627
- console.log(' Contact the publisher or use --tx-hash with manual payment.');
628
- process.exit(1);
629
- }
630
- // Check balance and trigger funding if needed
631
- try {
632
- const balance = await getWalletBalance();
633
- console.log(`\n Your balance: ${balance.eth} ETH ($${balance.usd})`);
634
- // Auto-trigger funding flow if insufficient balance
635
- if (balance.usd < getPriceUsd(agent.pricing)) {
636
- console.log(`\n⚠️ Insufficient balance. Need $${getPriceUsd(agent.pricing)}, have $${balance.usd}`);
637
- const funded = await triggerFundingFlow(getPriceUsd(agent.pricing));
638
- if (!funded) {
639
- process.exit(1);
640
- }
641
- // Re-check balance after funding
642
- const newBalance = await getWalletBalance();
643
- console.log(` New balance: ${newBalance.eth} ETH ($${newBalance.usd})`);
644
- if (newBalance.usd < getPriceUsd(agent.pricing)) {
645
- console.log(`\n❌ Still insufficient. Need $${getPriceUsd(agent.pricing)}, have $${newBalance.usd}`);
646
- process.exit(1);
647
- }
636
+ else if (accessResult.status === 'payment_required') {
637
+ const paymentRequired = accessResult.payment;
638
+ // Check USDC balance
639
+ try {
640
+ const balance = await getWalletBalance();
641
+ const requiredRaw = parseUsdc(paymentRequired.amount);
642
+ console.log(` Your USDC balance: $${balance.usdc}`);
643
+ if (balance.usdcRaw < requiredRaw) {
644
+ console.log(`\n⚠️ Insufficient USDC. Need $${paymentRequired.amount}, have $${balance.usdc}`);
645
+ const funded = await triggerFundingFlow(priceUsd);
646
+ if (!funded) {
647
+ process.exit(1);
648
648
  }
649
- }
650
- catch (error) {
651
- console.log(`\n❌ Could not check balance: ${error instanceof Error ? error.message : error}`);
652
- process.exit(1);
653
- }
654
- // Confirm payment
655
- if (!options.yes) {
656
- const confirm = await prompt(`\nPay $${getPriceUsd(agent.pricing)} (~${priceEth.toFixed(6)} ETH) to ${payoutAddress.slice(0, 10)}...? (y/n) `);
657
- if (confirm !== 'y' && confirm !== 'yes') {
658
- console.log('Payment cancelled.');
659
- process.exit(0);
649
+ // Re-check balance after funding
650
+ const newBalance = await getWalletBalance();
651
+ console.log(` New USDC balance: $${newBalance.usdc}`);
652
+ if (newBalance.usdcRaw < requiredRaw) {
653
+ console.log(`\n❌ Still insufficient. Need $${paymentRequired.amount}, have $${newBalance.usdc}`);
654
+ process.exit(1);
660
655
  }
661
656
  }
662
- // Send payment
663
- try {
664
- console.log('\n🔄 Processing payment...');
665
- const payment = await sendAgentPayment({
666
- to: payoutAddress,
667
- amountUsd: getPriceUsd(agent.pricing),
668
- agentId: agent.agent_id,
669
- });
670
- txHash = payment.txHash;
671
- console.log(`✓ Payment sent: ${txHash}`);
672
- }
673
- catch (error) {
674
- console.log(`\n❌ Payment failed: ${error instanceof Error ? error.message : error}`);
675
- process.exit(1);
657
+ }
658
+ catch (error) {
659
+ console.log(`\n Could not check balance: ${error instanceof Error ? error.message : error}`);
660
+ process.exit(1);
661
+ }
662
+ // Check spend limits
663
+ const config = loadWalletConfig();
664
+ const txHistory = loadTxHistory();
665
+ const limitCheck = checkSpendLimit(priceUsd, config, txHistory);
666
+ if (!limitCheck.allowed) {
667
+ console.log(`\n❌ Spend limit exceeded: ${limitCheck.reason}`);
668
+ process.exit(1);
669
+ }
670
+ // Confirm payment (skip with --yes or --pay)
671
+ if (!options.yes && !options.pay) {
672
+ const confirm = await prompt(`\nPay $${paymentRequired.amount} USDC for "${agent.name}"? (y/n) `);
673
+ if (confirm !== 'y' && confirm !== 'yes') {
674
+ console.log('Payment cancelled.');
675
+ process.exit(0);
676
676
  }
677
677
  }
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...');
678
+ // Sign the EIP-3009 authorization
679
+ const privateKey = await loadPrivateKey();
680
+ if (!privateKey) {
681
+ console.log('\n❌ Could not load wallet private key');
684
682
  process.exit(1);
685
683
  }
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.');
684
+ console.log('\n🔐 Signing USDC authorization...');
685
+ const authorization = await signTransferAuthorization(paymentRequired, privateKey, walletAddress);
686
+ // Submit to API for facilitator relay
687
+ console.log('🔄 Processing payment via x402...');
688
+ const result = await submitX402Payment(agent.agent_id, walletAddress, paymentRequired, authorization);
689
+ if (!result) {
690
+ console.log('❌ Payment processing failed.');
690
691
  process.exit(1);
691
692
  }
692
- entitlementToken = purchase.entitlement_token;
693
- expiresAt = purchase.expires_at;
694
- console.log('✓ Payment verified!');
693
+ entitlementToken = result.entitlement_token;
694
+ expiresAt = null;
695
+ console.log('✓ Payment confirmed! (gasless USDC via x402)');
696
+ // Record in local tx history
697
+ const txProof = result.proof;
698
+ txHistory.push({
699
+ txHash: txProof?.tx_hash || 'x402-' + Date.now(),
700
+ to: paymentRequired.payTo,
701
+ amountUsdc: paymentRequired.amount,
702
+ amountUsd: priceUsd,
703
+ agentId: agent.agent_id,
704
+ timestamp: new Date().toISOString(),
705
+ status: 'confirmed',
706
+ });
707
+ saveTxHistory(txHistory);
708
+ }
709
+ else if (accessResult.status === 'granted') {
710
+ // Free agent via access route (model says free)
711
+ console.log('\n✓ Access granted (no payment needed).');
695
712
  }
696
713
  }
697
714
  console.log('\nInstalling...');
@@ -890,9 +907,8 @@ program
890
907
  program
891
908
  .command('install <agent_id>')
892
909
  .description('Install an agent from the marketplace')
893
- .option('-y, --yes', 'Skip confirmation / force reinstall')
894
- .option('--pay', 'Pay for agent directly from wallet')
895
- .option('--tx-hash <hash>', 'Transaction hash for manual payment verification')
910
+ .option('-y, --yes', 'Skip confirmation / auto-confirm payment')
911
+ .option('--pay', 'Auto-confirm payment (alias for --yes)')
896
912
  .action(installAgent);
897
913
  program
898
914
  .command('list')
@@ -952,7 +968,7 @@ program
952
968
  console.log();
953
969
  }
954
970
  console.log('Install with: agentstore install <agent_id>');
955
- console.log('For paid agents: agentstore install <agent_id> --pay');
971
+ console.log('Paid agents prompt for USDC payment automatically.');
956
972
  }
957
973
  catch (error) {
958
974
  console.error(`Error: ${error instanceof Error ? error.message : error}`);
@@ -978,8 +994,11 @@ walletCmd
978
994
  const { address } = await createNewWallet();
979
995
  console.log('\n✅ Wallet created!');
980
996
  console.log(`\nAddress: ${address}`);
981
- console.log('\n⚠️ Fund this address with ETH to purchase paid agents.');
982
- console.log(' Use any exchange or wallet to send ETH to this address.');
997
+ console.log('\n⚠️ Fund this address with USDC to purchase paid agents.');
998
+ console.log(' Options:');
999
+ console.log(' 1. Buy with card: agentstore wallet fund');
1000
+ console.log(` 2. Send USDC (Ethereum) from any wallet to: ${address}`);
1001
+ console.log(' 3. Import existing wallet: agentstore wallet import');
983
1002
  }
984
1003
  catch (error) {
985
1004
  console.error(`Error: ${error instanceof Error ? error.message : error}`);
@@ -988,7 +1007,7 @@ walletCmd
988
1007
  });
989
1008
  walletCmd
990
1009
  .command('balance')
991
- .description('Show wallet balance')
1010
+ .description('Show wallet USDC balance')
992
1011
  .action(async () => {
993
1012
  try {
994
1013
  if (!walletExists()) {
@@ -996,10 +1015,11 @@ walletCmd
996
1015
  process.exit(1);
997
1016
  }
998
1017
  const config = loadWalletConfig();
999
- console.log(`\nAddress: ${config?.address}`);
1000
- console.log('Fetching balance...');
1018
+ console.log(`\nWallet: ${config?.address}`);
1019
+ console.log('Fetching USDC balance...');
1001
1020
  const balance = await getWalletBalance();
1002
- console.log(`\n💰 Balance: ${balance.eth} ETH (~$${balance.usd})`);
1021
+ console.log(`USDC Balance: $${balance.usdc}`);
1022
+ console.log('Network: Ethereum Mainnet');
1003
1023
  // Show spending stats
1004
1024
  const txHistory = loadTxHistory();
1005
1025
  const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;
@@ -1041,7 +1061,7 @@ walletCmd
1041
1061
  const date = new Date(tx.timestamp).toLocaleDateString();
1042
1062
  const statusIcon = tx.status === 'confirmed' ? '✓' : tx.status === 'pending' ? '⏳' : '✗';
1043
1063
  console.log(` ${statusIcon} ${date} | ${tx.agentId}`);
1044
- console.log(` ${tx.amountEth} ETH ($${tx.amountUsd}) → ${tx.to.slice(0, 10)}...`);
1064
+ console.log(` $${tx.amountUsdc || tx.amountUsd} USDC → ${tx.to.slice(0, 10)}...`);
1045
1065
  console.log(` ${tx.txHash.slice(0, 20)}...`);
1046
1066
  console.log();
1047
1067
  }
@@ -1068,12 +1088,52 @@ walletCmd
1068
1088
  process.exit(1);
1069
1089
  }
1070
1090
  });
1091
+ walletCmd
1092
+ .command('import')
1093
+ .description('Import an existing wallet by private key')
1094
+ .action(async () => {
1095
+ try {
1096
+ if (walletExists()) {
1097
+ console.log('Wallet already exists. Delete ~/.agentstore/wallet.* files to import a new one.');
1098
+ process.exit(1);
1099
+ }
1100
+ const key = await prompt('Enter private key (0x...): ');
1101
+ if (!key.startsWith('0x') || key.length !== 66) {
1102
+ console.log('Invalid private key format. Must be a 0x-prefixed 64-character hex string.');
1103
+ process.exit(1);
1104
+ }
1105
+ ensureDirectories();
1106
+ const account = privateKeyToAccount(key);
1107
+ const config = {
1108
+ address: account.address,
1109
+ createdAt: new Date().toISOString(),
1110
+ network: 'mainnet',
1111
+ rpcEndpoint: MEV_COMMIT_RPC,
1112
+ spendLimits: { perTransaction: 100, daily: 500, weekly: 2000 },
1113
+ allowedPublishers: [],
1114
+ };
1115
+ const password = await getOrCreatePassword();
1116
+ const encrypted = encryptKey(key, password);
1117
+ fs.writeFileSync(KEYSTORE_FILE, JSON.stringify(encrypted), { mode: 0o600 });
1118
+ fs.writeFileSync(WALLET_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
1119
+ fs.writeFileSync(TX_HISTORY_FILE, JSON.stringify([]), { mode: 0o600 });
1120
+ // Show USDC balance
1121
+ const usdcRaw = await getUsdcBalance(account.address);
1122
+ console.log(`\n✅ Wallet imported!`);
1123
+ console.log(` Address: ${account.address}`);
1124
+ console.log(` USDC Balance: $${formatUsdc(usdcRaw)}`);
1125
+ }
1126
+ catch (error) {
1127
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
1128
+ process.exit(1);
1129
+ }
1130
+ });
1071
1131
  walletCmd
1072
1132
  .command('fund')
1073
- .description('Fund your wallet with a credit card via Coinbase')
1133
+ .description('Fund your wallet with USDC')
1074
1134
  .option('-a, --amount <usd>', 'Amount in USD to purchase', parseFloat)
1075
1135
  .option('--no-open', 'Print URL instead of opening browser')
1076
- .option('--wait', 'Wait and poll for funds to arrive')
1136
+ .option('--wait', 'Wait and poll for USDC to arrive')
1077
1137
  .action(async (options) => {
1078
1138
  try {
1079
1139
  if (!walletExists()) {
@@ -1085,23 +1145,19 @@ walletCmd
1085
1145
  console.log('Failed to load wallet config');
1086
1146
  process.exit(1);
1087
1147
  }
1088
- console.log('\n💳 Coinbase Onramp - Fund Your Wallet\n');
1148
+ console.log('\n💳 Fund Your Wallet with USDC\n');
1089
1149
  console.log(` Wallet: ${config.address}`);
1090
- // Get initial balance for comparison
1150
+ // Get initial USDC balance for comparison
1091
1151
  let initialBalance;
1092
- if (options.wait) {
1093
- try {
1094
- initialBalance = await publicClient.getBalance({
1095
- address: config.address,
1096
- });
1097
- console.log(` Current balance: ${formatEther(initialBalance)} ETH`);
1098
- }
1099
- catch {
1100
- // Ignore balance fetch errors
1101
- }
1152
+ try {
1153
+ initialBalance = await getUsdcBalance(config.address);
1154
+ console.log(` Current USDC: $${formatUsdc(initialBalance)}`);
1155
+ }
1156
+ catch {
1157
+ // Ignore balance fetch errors
1102
1158
  }
1103
1159
  if (options.amount) {
1104
- console.log(` Amount: $${options.amount} USD`);
1160
+ console.log(` Amount: $${options.amount} USDC`);
1105
1161
  }
1106
1162
  console.log('\n🔄 Generating secure onramp session...');
1107
1163
  // Call API to get onramp URL
@@ -1111,38 +1167,22 @@ walletCmd
1111
1167
  body: JSON.stringify({
1112
1168
  wallet_address: config.address,
1113
1169
  amount_usd: options.amount,
1170
+ asset: 'USDC',
1114
1171
  }),
1115
1172
  });
1116
1173
  const result = await response.json();
1117
1174
  if (!response.ok || !result.success) {
1118
- // Handle fallback for when CDP credentials aren't configured
1119
- if (result.manual_instructions) {
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
- }
1175
+ console.log('\n⚠️ Coinbase Onramp unavailable.\n');
1176
+ showFundingOptions(config.address, options.amount || 10);
1135
1177
  process.exit(1);
1136
1178
  }
1137
1179
  const onrampUrl = result.onramp_url;
1138
1180
  if (options.open === false) {
1139
- // Just print the URL
1140
1181
  console.log('\n✅ Onramp URL generated:\n');
1141
1182
  console.log(` ${onrampUrl}\n`);
1142
- console.log(' Open this URL in your browser to complete the purchase.');
1183
+ console.log(' Open this URL in your browser to purchase USDC.');
1143
1184
  }
1144
1185
  else {
1145
- // Open in default browser
1146
1186
  console.log('\n🌐 Opening Coinbase in your browser...\n');
1147
1187
  const { exec } = await import('child_process');
1148
1188
  const openCmd = process.platform === 'darwin'
@@ -1156,38 +1196,35 @@ walletCmd
1156
1196
  console.log(` ${onrampUrl}\n`);
1157
1197
  }
1158
1198
  });
1159
- console.log(' Complete the purchase in your browser.');
1160
- console.log(' ETH will be sent to your wallet within a few minutes.\n');
1199
+ console.log(' Complete the USDC purchase in your browser.');
1200
+ console.log(' USDC will arrive in your wallet within a few minutes.\n');
1201
+ showFundingOptionsShort(config.address);
1161
1202
  }
1162
- // Poll for balance changes if --wait flag is set
1203
+ // Poll for USDC balance changes if --wait flag is set
1163
1204
  if (options.wait && initialBalance !== undefined) {
1164
- console.log('⏳ Waiting for funds to arrive (Ctrl+C to cancel)...\n');
1205
+ console.log('\n⏳ Waiting for USDC to arrive (Ctrl+C to cancel)...\n');
1165
1206
  const startTime = Date.now();
1166
1207
  const maxWaitTime = 10 * 60 * 1000; // 10 minutes
1167
1208
  const pollInterval = 15 * 1000; // 15 seconds
1168
1209
  while (Date.now() - startTime < maxWaitTime) {
1169
1210
  await new Promise((resolve) => setTimeout(resolve, pollInterval));
1170
1211
  try {
1171
- const currentBalance = await publicClient.getBalance({
1172
- address: config.address,
1173
- });
1212
+ const currentBalance = await getUsdcBalance(config.address);
1174
1213
  if (currentBalance > initialBalance) {
1175
1214
  const added = currentBalance - initialBalance;
1176
- const ethPrice = await getEthPrice();
1177
- const addedUsd = parseFloat(formatEther(added)) * ethPrice;
1178
- console.log('✅ Funds received!\n');
1179
- console.log(` Added: ${formatEther(added)} ETH (~$${addedUsd.toFixed(2)})`);
1180
- console.log(` New balance: ${formatEther(currentBalance)} ETH`);
1215
+ console.log('\n✅ USDC received!\n');
1216
+ console.log(` Added: $${formatUsdc(added)} USDC`);
1217
+ console.log(` New balance: $${formatUsdc(currentBalance)} USDC`);
1181
1218
  process.exit(0);
1182
1219
  }
1183
1220
  const elapsed = Math.floor((Date.now() - startTime) / 1000);
1184
- process.stdout.write(`\r Checking... (${elapsed}s elapsed)`);
1221
+ process.stdout.write(`\r Checking USDC... (${elapsed}s elapsed)`);
1185
1222
  }
1186
1223
  catch {
1187
1224
  // Ignore individual poll errors
1188
1225
  }
1189
1226
  }
1190
- console.log('\n\n⚠️ Timed out waiting for funds.');
1227
+ console.log('\n\n⚠️ Timed out waiting for USDC.');
1191
1228
  console.log(' Funds may still arrive - check your balance later with:');
1192
1229
  console.log(' agentstore wallet balance\n');
1193
1230
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentstore",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "AgentStore CLI - Browse, install, and pay for Claude Code agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",