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.
Files changed (3) hide show
  1. package/README.md +7 -15
  2. package/dist/index.js +375 -268
  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
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
15
  const API_BASE = 'https://api.agentstore.tools';
16
- const MEV_COMMIT_RPC = 'https://fastrpc.mev-commit.xyz';
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(MEV_COMMIT_RPC),
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: MEV_COMMIT_RPC,
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: MEV_COMMIT_RPC,
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
- // 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,138 @@ function prompt(question) {
460
413
  });
461
414
  });
462
415
  }
463
- // Purchase agent via API
464
- async function purchaseAgent(agentId, walletAddress, txHash) {
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/purchase`, {
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
- tx_hash: txHash,
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 = (await response.json());
477
- console.error(`Purchase failed: ${error.error || response.statusText}`);
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 (await response.json());
544
+ return await response.json();
481
545
  }
482
546
  catch (error) {
483
- console.error(`Purchase error: ${error instanceof Error ? error.message : 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
- // Lazy wallet creation - create if doesn't exist
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: $${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) {
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 = existing.token;
618
- expiresAt = existing.expiresAt;
681
+ entitlementToken = localEntitlement.token;
682
+ expiresAt = localEntitlement.expiresAt;
619
683
  }
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.');
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
- // Check balance and trigger funding if needed
708
+ }
709
+ else {
710
+ const paymentRequired = accessResult.payment;
711
+ // Check USDC balance
631
712
  try {
632
713
  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));
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: ${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}`);
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
- // Confirm payment
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 $${getPriceUsd(agent.pricing)} (~${priceEth.toFixed(6)} ETH) to ${payoutAddress.slice(0, 10)}...? (y/n) `);
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
- // 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}`);
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
- catch (error) {
674
- console.log(`\n❌ Payment failed: ${error instanceof Error ? error.message : error}`);
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 / force reinstall')
894
- .option('--pay', 'Pay for agent directly from wallet')
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('For paid agents: agentstore install <agent_id> --pay');
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 ETH to purchase paid agents.');
982
- console.log(' Use any exchange or wallet to send ETH to this address.');
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(`\nAddress: ${config?.address}`);
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(`\n💰 Balance: ${balance.eth} ETH (~$${balance.usd})`);
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(` ${tx.amountEth} ETH ($${tx.amountUsd}) → ${tx.to.slice(0, 10)}...`);
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 a credit card via Coinbase')
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 funds to arrive')
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💳 Coinbase Onramp - Fund Your Wallet\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
- 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
- }
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} USD`);
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
- // 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
- }
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 complete the purchase.');
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(' ETH will be sent to your wallet within a few minutes.\n');
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 funds to arrive (Ctrl+C to cancel)...\n');
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 publicClient.getBalance({
1172
- address: config.address,
1173
- });
1282
+ const currentBalance = await getUsdcBalance(config.address);
1174
1283
  if (currentBalance > initialBalance) {
1175
1284
  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`);
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 funds.');
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentstore",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "AgentStore CLI - Browse, install, and pay for Claude Code agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",