@blockrun/cc 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  Hitting usage limits? Account disabled? Can't verify your phone? brcc fixes all of that.
6
6
 
7
7
  ```bash
8
- npm install -g brcc
8
+ npm install -g @blockrun/cc
9
9
  brcc start
10
10
  ```
11
11
 
@@ -35,7 +35,7 @@ Claude Code --> brcc (local proxy) --> BlockRun API --> Any model
35
35
 
36
36
  ```bash
37
37
  # Install
38
- npm install -g brcc
38
+ npm install -g @blockrun/cc
39
39
 
40
40
  # Create a wallet (one time)
41
41
  brcc setup
@@ -1,14 +1,30 @@
1
1
  import chalk from 'chalk';
2
- import { setupAgentWallet } from '@blockrun/llm';
2
+ import { setupAgentWallet, setupAgentSolanaWallet } from '@blockrun/llm';
3
+ import { loadChain } from '../config.js';
3
4
  export async function balanceCommand() {
5
+ const chain = loadChain();
4
6
  try {
5
- const client = setupAgentWallet({ silent: true });
6
- const address = client.getWalletAddress();
7
- const balance = await client.getBalance();
8
- console.log(`Wallet: ${chalk.cyan(address)}`);
9
- console.log(`USDC Balance: ${chalk.green(`$${balance.toFixed(2)}`)}`);
10
- if (balance === 0) {
11
- console.log(chalk.dim(`\nSend USDC on Base to ${address} to get started.`));
7
+ if (chain === 'solana') {
8
+ const client = await setupAgentSolanaWallet({ silent: true });
9
+ const address = await client.getWalletAddress();
10
+ const balance = await client.getBalance();
11
+ console.log(`Chain: ${chalk.magenta('solana')}`);
12
+ console.log(`Wallet: ${chalk.cyan(address)}`);
13
+ console.log(`USDC Balance: ${chalk.green(`$${balance.toFixed(2)}`)}`);
14
+ if (balance === 0) {
15
+ console.log(chalk.dim(`\nSend USDC on Solana to ${address} to get started.`));
16
+ }
17
+ }
18
+ else {
19
+ const client = setupAgentWallet({ silent: true });
20
+ const address = client.getWalletAddress();
21
+ const balance = await client.getBalance();
22
+ console.log(`Chain: ${chalk.magenta('base')}`);
23
+ console.log(`Wallet: ${chalk.cyan(address)}`);
24
+ console.log(`USDC Balance: ${chalk.green(`$${balance.toFixed(2)}`)}`);
25
+ if (balance === 0) {
26
+ console.log(chalk.dim(`\nSend USDC on Base to ${address} to get started.`));
27
+ }
12
28
  }
13
29
  }
14
30
  catch {
@@ -1 +1 @@
1
- export declare function setupCommand(): Promise<void>;
1
+ export declare function setupCommand(chainArg?: string): Promise<void>;
@@ -1,19 +1,41 @@
1
1
  import chalk from 'chalk';
2
- import { getOrCreateWallet, scanWallets } from '@blockrun/llm';
3
- export async function setupCommand() {
4
- const wallets = scanWallets();
5
- if (wallets.length > 0) {
6
- console.log(chalk.yellow('Wallet already exists.'));
7
- console.log(`Address: ${chalk.cyan(wallets[0].address)}`);
8
- return;
2
+ import { getOrCreateWallet, scanWallets, getOrCreateSolanaWallet, scanSolanaWallets, } from '@blockrun/llm';
3
+ import { saveChain } from '../config.js';
4
+ export async function setupCommand(chainArg) {
5
+ const chain = chainArg === 'solana' ? 'solana' : 'base';
6
+ if (chain === 'solana') {
7
+ const wallets = scanSolanaWallets();
8
+ if (wallets.length > 0) {
9
+ console.log(chalk.yellow('Solana wallet already exists.'));
10
+ console.log(`Address: ${chalk.cyan(wallets[0].publicKey)}`);
11
+ saveChain('solana');
12
+ return;
13
+ }
14
+ console.log('Creating new Solana wallet...\n');
15
+ const { address, isNew } = await getOrCreateSolanaWallet();
16
+ if (isNew) {
17
+ console.log(chalk.green('Solana wallet created!\n'));
18
+ }
19
+ console.log(`Address: ${chalk.cyan(address)}`);
20
+ console.log(`\nSend USDC on Solana to this address to fund your account.`);
9
21
  }
10
- console.log('Creating new wallet...\n');
11
- const { address, isNew } = getOrCreateWallet();
12
- if (isNew) {
13
- console.log(chalk.green('Wallet created!\n'));
22
+ else {
23
+ const wallets = scanWallets();
24
+ if (wallets.length > 0) {
25
+ console.log(chalk.yellow('Wallet already exists.'));
26
+ console.log(`Address: ${chalk.cyan(wallets[0].address)}`);
27
+ saveChain('base');
28
+ return;
29
+ }
30
+ console.log('Creating new wallet...\n');
31
+ const { address, isNew } = getOrCreateWallet();
32
+ if (isNew) {
33
+ console.log(chalk.green('Wallet created!\n'));
34
+ }
35
+ console.log(`Address: ${chalk.cyan(address)}`);
36
+ console.log(`\nSend USDC on Base to this address to fund your account.`);
14
37
  }
15
- console.log(`Address: ${chalk.cyan(address)}`);
16
- console.log(`\nSend USDC on Base to this address to fund your account.`);
38
+ saveChain(chain);
17
39
  console.log(`Then run ${chalk.bold('brcc start')} to launch Claude Code.\n`);
18
- console.log(chalk.dim('Wallet saved to ~/.blockrun/'));
40
+ console.log(chalk.dim(`Chain: ${chain} — saved to ~/.blockrun/`));
19
41
  }
@@ -1,23 +1,49 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import chalk from 'chalk';
3
- import { getOrCreateWallet } from '@blockrun/llm';
3
+ import { getOrCreateWallet, getOrCreateSolanaWallet } from '@blockrun/llm';
4
4
  import { createProxy } from '../proxy/server.js';
5
- import { DEFAULT_API_URL, DEFAULT_PROXY_PORT } from '../config.js';
5
+ import { loadChain, API_URLS, DEFAULT_PROXY_PORT } from '../config.js';
6
6
  export async function startCommand(options) {
7
- const wallet = getOrCreateWallet();
8
- if (wallet.isNew) {
9
- console.log(chalk.yellow('No wallet found — created a new one.'));
10
- console.log(`Address: ${chalk.cyan(wallet.address)}`);
11
- console.log(`\nSend USDC on Base to this address, then run ${chalk.bold('brcc start')} again.\n`);
12
- return;
7
+ const chain = loadChain();
8
+ const apiUrl = API_URLS[chain];
9
+ if (chain === 'solana') {
10
+ const wallet = await getOrCreateSolanaWallet();
11
+ if (wallet.isNew) {
12
+ console.log(chalk.yellow('No Solana wallet found — created a new one.'));
13
+ console.log(`Address: ${chalk.cyan(wallet.address)}`);
14
+ console.log(`\nSend USDC on Solana to this address, then run ${chalk.bold('brcc start')} again.\n`);
15
+ return;
16
+ }
17
+ const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
18
+ const shouldLaunch = options.launch !== false;
19
+ console.log(chalk.bold('brcc — BlockRun Claude Code\n'));
20
+ console.log(`Chain: ${chalk.magenta('solana')}`);
21
+ console.log(`Wallet: ${chalk.cyan(wallet.address)}`);
22
+ console.log(`Proxy: ${chalk.cyan(`http://localhost:${port}`)}`);
23
+ console.log(`Backend: ${chalk.dim(apiUrl)}\n`);
24
+ const server = createProxy({ port, apiUrl, chain: 'solana' });
25
+ launchServer(server, port, shouldLaunch);
26
+ }
27
+ else {
28
+ const wallet = getOrCreateWallet();
29
+ if (wallet.isNew) {
30
+ console.log(chalk.yellow('No wallet found — created a new one.'));
31
+ console.log(`Address: ${chalk.cyan(wallet.address)}`);
32
+ console.log(`\nSend USDC on Base to this address, then run ${chalk.bold('brcc start')} again.\n`);
33
+ return;
34
+ }
35
+ const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
36
+ const shouldLaunch = options.launch !== false;
37
+ console.log(chalk.bold('brcc — BlockRun Claude Code\n'));
38
+ console.log(`Chain: ${chalk.magenta('base')}`);
39
+ console.log(`Wallet: ${chalk.cyan(wallet.address)}`);
40
+ console.log(`Proxy: ${chalk.cyan(`http://localhost:${port}`)}`);
41
+ console.log(`Backend: ${chalk.dim(apiUrl)}\n`);
42
+ const server = createProxy({ port, apiUrl, chain: 'base' });
43
+ launchServer(server, port, shouldLaunch);
13
44
  }
14
- const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
15
- const shouldLaunch = options.launch !== false;
16
- console.log(chalk.bold('brcc — BlockRun Claude Code\n'));
17
- console.log(`Wallet: ${chalk.cyan(wallet.address)}`);
18
- console.log(`Proxy: ${chalk.cyan(`http://localhost:${port}`)}`);
19
- console.log(`Backend: ${chalk.dim(DEFAULT_API_URL)}\n`);
20
- const server = createProxy({ port, apiUrl: DEFAULT_API_URL });
45
+ }
46
+ function launchServer(server, port, shouldLaunch) {
21
47
  server.listen(port, () => {
22
48
  console.log(chalk.green(`Proxy running on port ${port}\n`));
23
49
  if (shouldLaunch) {
package/dist/config.d.ts CHANGED
@@ -1,2 +1,7 @@
1
- export declare const DEFAULT_API_URL = "https://blockrun.ai/api";
1
+ export type Chain = 'base' | 'solana';
2
+ export declare const BLOCKRUN_DIR: string;
3
+ export declare const CHAIN_FILE: string;
4
+ export declare const API_URLS: Record<Chain, string>;
2
5
  export declare const DEFAULT_PROXY_PORT = 8402;
6
+ export declare function saveChain(chain: Chain): void;
7
+ export declare function loadChain(): Chain;
package/dist/config.js CHANGED
@@ -1,2 +1,30 @@
1
- export const DEFAULT_API_URL = 'https://blockrun.ai/api';
1
+ import path from 'node:path';
2
+ import os from 'node:os';
3
+ import fs from 'node:fs';
4
+ export const BLOCKRUN_DIR = path.join(os.homedir(), '.blockrun');
5
+ export const CHAIN_FILE = path.join(BLOCKRUN_DIR, 'payment-chain');
6
+ export const API_URLS = {
7
+ base: 'https://blockrun.ai/api',
8
+ solana: 'https://sol.blockrun.ai/api',
9
+ };
2
10
  export const DEFAULT_PROXY_PORT = 8402;
11
+ export function saveChain(chain) {
12
+ fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
13
+ fs.writeFileSync(CHAIN_FILE, chain + '\n', { mode: 0o600 });
14
+ }
15
+ export function loadChain() {
16
+ const envChain = process.env.BRCC_CHAIN;
17
+ if (envChain === 'solana')
18
+ return 'solana';
19
+ if (envChain === 'base')
20
+ return 'base';
21
+ try {
22
+ const content = fs.readFileSync(CHAIN_FILE, 'utf-8').trim();
23
+ if (content === 'solana')
24
+ return 'solana';
25
+ return 'base';
26
+ }
27
+ catch {
28
+ return 'base';
29
+ }
30
+ }
package/dist/index.js CHANGED
@@ -9,9 +9,9 @@ program
9
9
  .description('BlockRun Claude Code — run Claude Code with any model, pay with USDC')
10
10
  .version('0.1.0');
11
11
  program
12
- .command('setup')
13
- .description('Create a new wallet for payments')
14
- .action(setupCommand);
12
+ .command('setup [chain]')
13
+ .description('Create a new wallet for payments (base or solana)')
14
+ .action((chain) => setupCommand(chain));
15
15
  program
16
16
  .command('start')
17
17
  .description('Start proxy and launch Claude Code')
@@ -1,6 +1,8 @@
1
1
  import http from 'node:http';
2
+ import type { Chain } from '../config.js';
2
3
  export interface ProxyOptions {
3
4
  port: number;
4
5
  apiUrl: string;
6
+ chain?: Chain;
5
7
  }
6
8
  export declare function createProxy(options: ProxyOptions): http.Server;
@@ -1,15 +1,26 @@
1
1
  import http from 'node:http';
2
- import { getOrCreateWallet, createPaymentPayload, parsePaymentRequired, extractPaymentDetails, } from '@blockrun/llm';
2
+ import { getOrCreateWallet, getOrCreateSolanaWallet, createPaymentPayload, createSolanaPaymentPayload, parsePaymentRequired, extractPaymentDetails, solanaKeyToBytes, SOLANA_NETWORK, } from '@blockrun/llm';
3
3
  export function createProxy(options) {
4
- const wallet = getOrCreateWallet();
5
- const privateKey = wallet.privateKey;
6
- const fromAddress = wallet.address;
4
+ const chain = options.chain || 'base';
5
+ let baseWallet = null;
6
+ let solanaWallet = null;
7
+ if (chain === 'base') {
8
+ const w = getOrCreateWallet();
9
+ baseWallet = { privateKey: w.privateKey, address: w.address };
10
+ }
11
+ const initSolana = async () => {
12
+ if (chain === 'solana' && !solanaWallet) {
13
+ const w = await getOrCreateSolanaWallet();
14
+ solanaWallet = { privateKey: w.privateKey, address: w.address };
15
+ }
16
+ };
7
17
  const server = http.createServer(async (req, res) => {
8
18
  if (req.method === 'OPTIONS') {
9
19
  res.writeHead(200);
10
20
  res.end();
11
21
  return;
12
22
  }
23
+ await initSolana();
13
24
  const path = req.url?.replace(/^\/api/, '') || '';
14
25
  const targetUrl = `${options.apiUrl}${path}`;
15
26
  let body = '';
@@ -34,7 +45,12 @@ export function createProxy(options) {
34
45
  body: body || undefined,
35
46
  });
36
47
  if (response.status === 402) {
37
- response = await handlePayment(response, targetUrl, req.method || 'POST', headers, body, privateKey, fromAddress);
48
+ if (chain === 'solana' && solanaWallet) {
49
+ response = await handleSolanaPayment(response, targetUrl, req.method || 'POST', headers, body, solanaWallet.privateKey, solanaWallet.address);
50
+ }
51
+ else if (baseWallet) {
52
+ response = await handleBasePayment(response, targetUrl, req.method || 'POST', headers, body, baseWallet.privateKey, baseWallet.address);
53
+ }
38
54
  }
39
55
  const responseHeaders = {};
40
56
  response.headers.forEach((v, k) => {
@@ -71,19 +87,11 @@ export function createProxy(options) {
71
87
  });
72
88
  return server;
73
89
  }
74
- async function handlePayment(response, url, method, headers, body, privateKey, fromAddress) {
75
- let paymentHeader = response.headers.get('payment-required');
76
- if (!paymentHeader) {
77
- try {
78
- const respBody = (await response.json());
79
- if (respBody.x402 || respBody.accepts) {
80
- paymentHeader = btoa(JSON.stringify(respBody));
81
- }
82
- }
83
- catch {
84
- // ignore parse errors
85
- }
86
- }
90
+ // ======================================================================
91
+ // Base (EIP-712) payment handler
92
+ // ======================================================================
93
+ async function handleBasePayment(response, url, method, headers, body, privateKey, fromAddress) {
94
+ const paymentHeader = await extractPaymentHeader(response);
87
95
  if (!paymentHeader) {
88
96
  throw new Error('402 response but no payment requirements found');
89
97
  }
@@ -104,3 +112,48 @@ async function handlePayment(response, url, method, headers, body, privateKey, f
104
112
  body: body || undefined,
105
113
  });
106
114
  }
115
+ // ======================================================================
116
+ // Solana payment handler
117
+ // ======================================================================
118
+ async function handleSolanaPayment(response, url, method, headers, body, privateKey, fromAddress) {
119
+ const paymentHeader = await extractPaymentHeader(response);
120
+ if (!paymentHeader) {
121
+ throw new Error('402 response but no payment requirements found');
122
+ }
123
+ const paymentRequired = parsePaymentRequired(paymentHeader);
124
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
125
+ const secretKey = await solanaKeyToBytes(privateKey);
126
+ const feePayer = details.extra?.feePayer || details.recipient;
127
+ const paymentPayload = await createSolanaPaymentPayload(secretKey, fromAddress, details.recipient, details.amount, feePayer, {
128
+ resourceUrl: details.resource?.url || url,
129
+ resourceDescription: details.resource?.description || 'BlockRun AI API call',
130
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
131
+ extra: details.extra,
132
+ });
133
+ return fetch(url, {
134
+ method,
135
+ headers: {
136
+ ...headers,
137
+ 'PAYMENT-SIGNATURE': paymentPayload,
138
+ },
139
+ body: body || undefined,
140
+ });
141
+ }
142
+ // ======================================================================
143
+ // Shared helpers
144
+ // ======================================================================
145
+ async function extractPaymentHeader(response) {
146
+ let paymentHeader = response.headers.get('payment-required');
147
+ if (!paymentHeader) {
148
+ try {
149
+ const respBody = (await response.json());
150
+ if (respBody.x402 || respBody.accepts) {
151
+ paymentHeader = btoa(JSON.stringify(respBody));
152
+ }
153
+ }
154
+ catch {
155
+ // ignore parse errors
156
+ }
157
+ }
158
+ return paymentHeader;
159
+ }
@@ -3,4 +3,8 @@ export declare function setupWallet(): {
3
3
  address: string;
4
4
  isNew: boolean;
5
5
  };
6
+ export declare function setupSolanaWallet(): Promise<{
7
+ address: string;
8
+ isNew: boolean;
9
+ }>;
6
10
  export declare function getAddress(): string;
@@ -1,12 +1,20 @@
1
- import { getOrCreateWallet, scanWallets, getWalletAddress, } from '@blockrun/llm';
1
+ import { getOrCreateWallet, scanWallets, getWalletAddress, getOrCreateSolanaWallet, scanSolanaWallets, } from '@blockrun/llm';
2
+ import { loadChain } from '../config.js';
2
3
  export function walletExists() {
3
- const wallets = scanWallets();
4
- return wallets.length > 0;
4
+ const chain = loadChain();
5
+ if (chain === 'solana') {
6
+ return scanSolanaWallets().length > 0;
7
+ }
8
+ return scanWallets().length > 0;
5
9
  }
6
10
  export function setupWallet() {
7
11
  const { address, isNew } = getOrCreateWallet();
8
12
  return { address, isNew };
9
13
  }
14
+ export async function setupSolanaWallet() {
15
+ const { address, isNew } = await getOrCreateSolanaWallet();
16
+ return { address, isNew };
17
+ }
10
18
  export function getAddress() {
11
19
  const addr = getWalletAddress();
12
20
  if (!addr)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/cc",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Run Claude Code with any model — no rate limits, no account locks, no phone verification. Pay per use with USDC.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -43,6 +43,9 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@blockrun/llm": "^1.4.2",
46
+ "@solana/spl-token": "^0.4.14",
47
+ "@solana/web3.js": "^1.98.4",
48
+ "bs58": "^6.0.0",
46
49
  "chalk": "^5.4.0",
47
50
  "commander": "^13.0.0"
48
51
  },