@chainstream-io/cli 0.0.6 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,14 +10,12 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/lib/constants.ts
13
- var CHAINSTREAM_API_URL, CHAINSTREAM_AUTH_URL, TURNKEY_AUTH_PROXY_CONFIG_ID, TURNKEY_SUB_ORG_PREFIX, SOLANA_RPC_URL, BASE_RPC_URL, BASE_CHAIN_ID;
13
+ var CHAINSTREAM_API_URL, CHAINSTREAM_AUTH_URL, SOLANA_RPC_URL, BASE_RPC_URL, BASE_CHAIN_ID;
14
14
  var init_constants = __esm({
15
15
  "src/lib/constants.ts"() {
16
16
  "use strict";
17
17
  CHAINSTREAM_API_URL = process.env.CHAINSTREAM_API_URL ?? "https://api.chainstream.io";
18
- CHAINSTREAM_AUTH_URL = process.env.CHAINSTREAM_AUTH_URL ?? "https://auth.chainstream.io";
19
- TURNKEY_AUTH_PROXY_CONFIG_ID = process.env.TURNKEY_AUTH_PROXY_CONFIG_ID ?? "7550819d-2607-4910-a3d9-8e6e3ff870f9";
20
- TURNKEY_SUB_ORG_PREFIX = "chainstream-personal";
18
+ CHAINSTREAM_AUTH_URL = process.env.CHAINSTREAM_AUTH_URL ?? "https://api.chainstream.io";
21
19
  SOLANA_RPC_URL = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";
22
20
  BASE_RPC_URL = process.env.BASE_RPC_URL ?? "https://mainnet.base.org";
23
21
  BASE_CHAIN_ID = Number(process.env.BASE_CHAIN_ID ?? "8453");
@@ -29,6 +27,8 @@ var config_exports = {};
29
27
  __export(config_exports, {
30
28
  getConfigDir: () => getConfigDir,
31
29
  getWalletMode: () => getWalletMode,
30
+ isEvmChain: () => isEvmChain,
31
+ isSolanaChain: () => isSolanaChain,
32
32
  loadConfig: () => loadConfig,
33
33
  saveConfig: () => saveConfig,
34
34
  updateConfig: () => updateConfig
@@ -36,6 +36,12 @@ __export(config_exports, {
36
36
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
37
37
  import { homedir } from "os";
38
38
  import { join } from "path";
39
+ function isEvmChain(chain) {
40
+ return chain === "base";
41
+ }
42
+ function isSolanaChain(chain) {
43
+ return chain === "sol";
44
+ }
39
45
  function getConfigDir() {
40
46
  if (!existsSync(CONFIG_DIR)) {
41
47
  mkdirSync(CONFIG_DIR, { recursive: true });
@@ -78,6 +84,16 @@ var init_config = __esm({
78
84
  }
79
85
  });
80
86
 
87
+ // src/wallet/types.ts
88
+ function toSdkChain(chain) {
89
+ return chain === "sol" ? "solana" : "evm";
90
+ }
91
+ var init_types = __esm({
92
+ "src/wallet/types.ts"() {
93
+ "use strict";
94
+ }
95
+ });
96
+
81
97
  // src/wallet/raw-wallet.ts
82
98
  import { privateKeyToAccount } from "viem/accounts";
83
99
  import { Keypair, VersionedTransaction } from "@solana/web3.js";
@@ -109,14 +125,18 @@ var RawKeyWallet;
109
125
  var init_raw_wallet = __esm({
110
126
  "src/wallet/raw-wallet.ts"() {
111
127
  "use strict";
128
+ init_types();
129
+ init_config();
112
130
  RawKeyWallet = class {
113
131
  chain;
132
+ paymentChain;
114
133
  address;
115
134
  privateKey;
116
135
  constructor(key, chain) {
117
- this.chain = chain;
136
+ this.paymentChain = chain;
137
+ this.chain = toSdkChain(chain);
118
138
  this.privateKey = key;
119
- if (chain === "evm") {
139
+ if (isEvmChain(this.paymentChain)) {
120
140
  const hex = key.startsWith("0x") ? key : `0x${key}`;
121
141
  const account = privateKeyToAccount(hex);
122
142
  this.address = account.address;
@@ -127,7 +147,7 @@ var init_raw_wallet = __esm({
127
147
  }
128
148
  }
129
149
  async signMessage(message) {
130
- if (this.chain === "evm") {
150
+ if (isEvmChain(this.paymentChain)) {
131
151
  const hex = this.privateKey.startsWith("0x") ? this.privateKey : `0x${this.privateKey}`;
132
152
  const account = privateKeyToAccount(hex);
133
153
  return account.signMessage({ message });
@@ -142,7 +162,7 @@ var init_raw_wallet = __esm({
142
162
  }
143
163
  async signTransaction(serializedTx) {
144
164
  const txBytes = Buffer.from(serializedTx, "base64");
145
- if (this.chain === "solana") {
165
+ if (!isEvmChain(this.paymentChain)) {
146
166
  const tx = VersionedTransaction.deserialize(new Uint8Array(txBytes));
147
167
  const decoded = bs58Decode(this.privateKey);
148
168
  const keypair = Keypair.fromSecretKey(decoded);
@@ -162,20 +182,12 @@ var init_raw_wallet = __esm({
162
182
  var turnkey_exports = {};
163
183
  __export(turnkey_exports, {
164
184
  TURNKEY_API_BASE: () => TURNKEY_API_BASE,
165
- TURNKEY_CONFIG_ID: () => TURNKEY_CONFIG_ID,
166
- checkAccount: () => checkAccount,
167
- completeLogin: () => completeLogin,
168
185
  createApiStamp: () => createApiStamp,
169
186
  generateP256KeyPair: () => generateP256KeyPair,
170
187
  getEvmAddress: () => getEvmAddress,
171
188
  getSolanaAddress: () => getSolanaAddress,
172
189
  listWalletAccounts: () => listWalletAccounts,
173
- otpInit: () => otpInit,
174
- otpLogin: () => otpLogin,
175
- otpVerify: () => otpVerify,
176
190
  refreshTurnkeySession: () => refreshTurnkeySession,
177
- signRawP256: () => signRawP256,
178
- signup: () => signup,
179
191
  turnkeyRequest: () => turnkeyRequest,
180
192
  updateTurnkeyUserEmail: () => updateTurnkeyUserEmail
181
193
  });
@@ -199,160 +211,12 @@ function generateP256KeyPair() {
199
211
  privateKeyDer: Buffer.from(privateKey).toString("base64")
200
212
  };
201
213
  }
202
- function signRawP256(message, privateKeyDerBase64) {
203
- const signer = createSign("SHA256");
204
- signer.update(message);
205
- signer.end();
206
- const derSig = signer.sign({
207
- key: Buffer.from(privateKeyDerBase64, "base64"),
208
- format: "der",
209
- type: "pkcs8"
210
- });
211
- return derToRawSignature(derSig);
212
- }
213
- function derToRawSignature(der) {
214
- let offset = 0;
215
- if (der[offset++] !== 48) throw new Error("Invalid DER signature");
216
- offset++;
217
- if (der[offset++] !== 2) throw new Error("Invalid DER signature");
218
- const rLen = der[offset++];
219
- const rBytes = der.subarray(offset, offset + rLen);
220
- offset += rLen;
221
- if (der[offset++] !== 2) throw new Error("Invalid DER signature");
222
- const sLen = der[offset++];
223
- const sBytes = der.subarray(offset, offset + sLen);
224
- const r = padOrTrimTo32(rBytes);
225
- const s = padOrTrimTo32(sBytes);
226
- return Buffer.concat([r, s]).toString("hex");
227
- }
228
- function padOrTrimTo32(buf) {
229
- if (buf.length === 32) return buf;
230
- if (buf.length === 33 && buf[0] === 0) return buf.subarray(1);
231
- if (buf.length < 32) {
232
- const padded = Buffer.alloc(32);
233
- buf.copy(padded, 32 - buf.length);
234
- return padded;
235
- }
236
- return buf.subarray(buf.length - 32);
237
- }
238
214
  function decodeJwtPayload(jwt) {
239
215
  const parts = jwt.split(".");
240
216
  if (parts.length !== 3) throw new Error("Invalid JWT");
241
217
  const payloadB64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
242
218
  return JSON.parse(Buffer.from(payloadB64, "base64").toString("utf-8"));
243
219
  }
244
- async function proxyRequest(path, body, configId) {
245
- if (!configId) throw new Error("TURNKEY_AUTH_PROXY_CONFIG_ID not configured.");
246
- const res = await fetch(`${AUTH_PROXY_BASE}${path}`, {
247
- method: "POST",
248
- headers: {
249
- "Content-Type": "application/json",
250
- "X-Auth-Proxy-Config-ID": configId
251
- },
252
- body: JSON.stringify(body)
253
- });
254
- if (!res.ok) {
255
- const text = await res.text().catch(() => "");
256
- let detail = text;
257
- try {
258
- detail = JSON.parse(text).message ?? text;
259
- } catch {
260
- }
261
- throw new Error(`Turnkey auth proxy error (${res.status}): ${detail}`);
262
- }
263
- return res.json();
264
- }
265
- async function otpInit(email, configId) {
266
- return proxyRequest("/v1/otp_init", { otpType: "OTP_TYPE_EMAIL", contact: email }, configId);
267
- }
268
- async function otpVerify(otpId, otpCode, publicKeyHex, configId) {
269
- return proxyRequest("/v1/otp_verify", { otpId, otpCode, publicKey: publicKeyHex }, configId);
270
- }
271
- async function checkAccount(email, configId, verificationToken) {
272
- try {
273
- const result = await proxyRequest(
274
- "/v1/account",
275
- { filterType: "EMAIL", filterValue: email, ...verificationToken && { verificationToken } },
276
- configId
277
- );
278
- return { exists: !!result.organizationId, organizationId: result.organizationId };
279
- } catch {
280
- return { exists: false };
281
- }
282
- }
283
- async function signup(email, configId, verificationToken, publicKeyHex, privateKeyDer) {
284
- const decoded = decodeJwtPayload(verificationToken);
285
- const verificationPublicKey = decoded.public_key ?? publicKeyHex;
286
- const signupBody = {
287
- userName: `${TURNKEY_SUB_ORG_PREFIX}-${email.split("@")[0]}`,
288
- organizationName: `${TURNKEY_SUB_ORG_PREFIX}-${email}`,
289
- userEmail: email,
290
- verificationToken,
291
- apiKeys: [],
292
- authenticators: [],
293
- oauthProviders: [],
294
- wallet: {
295
- walletName: "ChainStream Wallet",
296
- accounts: [
297
- { curve: "CURVE_SECP256K1", pathFormat: "PATH_FORMAT_BIP32", path: "m/44'/60'/0'/0/0", addressFormat: "ADDRESS_FORMAT_ETHEREUM" },
298
- { curve: "CURVE_ED25519", pathFormat: "PATH_FORMAT_BIP32", path: "m/44'/501'/0'/0'", addressFormat: "ADDRESS_FORMAT_SOLANA" }
299
- ]
300
- }
301
- };
302
- const signatureMessage = JSON.stringify({
303
- signup: { email, apiKeys: signupBody.apiKeys, authenticators: signupBody.authenticators, oauthProviders: signupBody.oauthProviders },
304
- tokenId: decoded.id,
305
- type: "USAGE_TYPE_SIGNUP"
306
- });
307
- const signature = signRawP256(signatureMessage, privateKeyDer);
308
- return proxyRequest("/v1/signup", {
309
- ...signupBody,
310
- clientSignature: {
311
- message: signatureMessage,
312
- publicKey: verificationPublicKey,
313
- scheme: "CLIENT_SIGNATURE_SCHEME_API_P256",
314
- signature
315
- }
316
- }, configId);
317
- }
318
- async function otpLogin(verificationToken, publicKeyHex, privateKeyDerBase64, configId) {
319
- const decoded = decodeJwtPayload(verificationToken);
320
- const tokenUsageMessage = JSON.stringify({
321
- login: { publicKey: publicKeyHex },
322
- tokenId: decoded.id,
323
- type: "USAGE_TYPE_LOGIN"
324
- });
325
- const signature = signRawP256(tokenUsageMessage, privateKeyDerBase64);
326
- return proxyRequest("/v1/otp_login", {
327
- verificationToken,
328
- publicKey: publicKeyHex,
329
- clientSignature: {
330
- message: tokenUsageMessage,
331
- publicKey: decoded.public_key ?? publicKeyHex,
332
- scheme: "CLIENT_SIGNATURE_SCHEME_API_P256",
333
- signature
334
- }
335
- }, configId);
336
- }
337
- async function completeLogin(otpId, otpCode, configId, email) {
338
- const keyPair = generateP256KeyPair();
339
- const { verificationToken } = await otpVerify(otpId, otpCode, keyPair.publicKeyHex, configId);
340
- const account = await checkAccount(email, configId, verificationToken);
341
- let isNewUser = false;
342
- if (!account.exists) {
343
- await signup(email, configId, verificationToken, keyPair.publicKeyHex, keyPair.privateKeyDer);
344
- isNewUser = true;
345
- }
346
- const { session } = await otpLogin(verificationToken, keyPair.publicKeyHex, keyPair.privateKeyDer, configId);
347
- const sessionPayload = decodeJwtPayload(session);
348
- return {
349
- session,
350
- keyPair,
351
- organizationId: sessionPayload.organization_id ?? "",
352
- sessionExpiry: sessionPayload.exp,
353
- isNewUser
354
- };
355
- }
356
220
  function createApiStamp(body, publicKeyHex, privateKeyDerBase64) {
357
221
  const signer = createSign("SHA256");
358
222
  signer.update(body);
@@ -454,14 +318,11 @@ async function getSolanaAddress(creds) {
454
318
  if (!sol) throw new Error("No Solana wallet found in Turnkey.");
455
319
  return sol.address;
456
320
  }
457
- var AUTH_PROXY_BASE, TURNKEY_API_BASE, TURNKEY_CONFIG_ID;
321
+ var TURNKEY_API_BASE;
458
322
  var init_turnkey = __esm({
459
323
  "src/lib/turnkey.ts"() {
460
324
  "use strict";
461
- init_constants();
462
- AUTH_PROXY_BASE = "https://authproxy.turnkey.com";
463
325
  TURNKEY_API_BASE = "https://api.turnkey.com";
464
- TURNKEY_CONFIG_ID = TURNKEY_AUTH_PROXY_CONFIG_ID;
465
326
  }
466
327
  });
467
328
 
@@ -470,14 +331,18 @@ var TurnkeyWallet;
470
331
  var init_turnkey_wallet = __esm({
471
332
  "src/wallet/turnkey-wallet.ts"() {
472
333
  "use strict";
334
+ init_types();
335
+ init_config();
473
336
  init_turnkey();
474
337
  TurnkeyWallet = class _TurnkeyWallet {
475
338
  chain;
339
+ paymentChain;
476
340
  address;
477
341
  creds;
478
342
  constructor(creds, chain, address) {
479
343
  this.creds = creds;
480
- this.chain = chain;
344
+ this.paymentChain = chain;
345
+ this.chain = toSdkChain(chain);
481
346
  this.address = address;
482
347
  }
483
348
  static async create(creds) {
@@ -486,14 +351,14 @@ var init_turnkey_wallet = __esm({
486
351
  getSolanaAddress(creds)
487
352
  ]);
488
353
  return {
489
- evm: new _TurnkeyWallet(creds, "evm", evmAddr),
490
- solana: new _TurnkeyWallet(creds, "solana", solAddr)
354
+ base: new _TurnkeyWallet(creds, "base", evmAddr),
355
+ sol: new _TurnkeyWallet(creds, "sol", solAddr)
491
356
  };
492
357
  }
493
358
  async signMessage(message) {
494
359
  let payloadHex;
495
360
  let hashFunction;
496
- if (this.chain === "evm") {
361
+ if (isEvmChain(this.paymentChain)) {
497
362
  const prefix = `Ethereum Signed Message:
498
363
  ${message.length}`;
499
364
  const prefixed = Buffer.concat([Buffer.from(prefix, "utf-8"), Buffer.from(message, "utf-8")]);
@@ -520,7 +385,7 @@ ${message.length}`;
520
385
  );
521
386
  const sigResult = result.activity?.result?.signRawPayloadResult;
522
387
  if (!sigResult) throw new Error("Turnkey signMessage: no signature in response");
523
- if (this.chain === "evm") {
388
+ if (isEvmChain(this.paymentChain)) {
524
389
  const v = sigResult.v === "00" ? "1b" : "1c";
525
390
  return `0x${sigResult.r}${sigResult.s}${v}`;
526
391
  }
@@ -529,7 +394,7 @@ ${message.length}`;
529
394
  async signTransaction(serializedTx) {
530
395
  const txBytes = Buffer.from(serializedTx, "base64");
531
396
  const unsignedHex = txBytes.toString("hex");
532
- const txType = this.chain === "evm" ? "TRANSACTION_TYPE_ETHEREUM" : "TRANSACTION_TYPE_SOLANA";
397
+ const txType = isEvmChain(this.paymentChain) ? "TRANSACTION_TYPE_ETHEREUM" : "TRANSACTION_TYPE_SOLANA";
533
398
  const result = await turnkeyRequest(
534
399
  "/public/v1/submit/sign_transaction",
535
400
  {
@@ -563,13 +428,15 @@ function createWallet(config, mode) {
563
428
  return new RawKeyWallet(config.rawWallet.key, config.rawWallet.chain);
564
429
  }
565
430
  if (mode === "turnkey" && config.turnkey) {
566
- const address = config.turnkey.evmAddress;
431
+ const chain = config.walletChain ?? "base";
432
+ const address = isEvmChain(chain) ? config.turnkey.evmAddress : config.turnkey.solanaAddress;
567
433
  if (!address) {
568
434
  throw new Error(
569
- "Turnkey wallet address not found in config.\nPlease re-login to resolve addresses: chainstream login"
435
+ `Turnkey ${chain} address not found in config.
436
+ Please re-login to resolve addresses: chainstream login`
570
437
  );
571
438
  }
572
- return new TurnkeyWallet(config.turnkey, "evm", address);
439
+ return new TurnkeyWallet(config.turnkey, chain, address);
573
440
  }
574
441
  throw new Error(
575
442
  "No wallet configured. Run:\n chainstream login # Create Turnkey wallet\n chainstream wallet set-raw # Use raw private key (dev)"
@@ -578,17 +445,18 @@ function createWallet(config, mode) {
578
445
  async function createWalletWithAddresses(config, mode) {
579
446
  if (mode === "raw" && config.rawWallet) {
580
447
  const w = new RawKeyWallet(config.rawWallet.key, config.rawWallet.chain);
581
- return config.rawWallet.chain === "evm" ? { evm: w } : { solana: w };
448
+ return isEvmChain(config.rawWallet.chain) ? { base: w } : { sol: w };
582
449
  }
583
450
  if (mode === "turnkey" && config.turnkey) {
584
451
  const wallets = await TurnkeyWallet.create(config.turnkey);
585
- return { evm: wallets.evm, solana: wallets.solana };
452
+ return { base: wallets.base, sol: wallets.sol };
586
453
  }
587
454
  throw new Error("No wallet configured.");
588
455
  }
589
456
  var init_wallet = __esm({
590
457
  "src/wallet/index.ts"() {
591
458
  "use strict";
459
+ init_config();
592
460
  init_raw_wallet();
593
461
  init_turnkey_wallet();
594
462
  }
@@ -597,15 +465,18 @@ var init_wallet = __esm({
597
465
  // src/lib/x402.ts
598
466
  var x402_exports = {};
599
467
  __export(x402_exports, {
600
- autoPurchaseOnDemand: () => autoPurchaseOnDemand,
601
468
  getPricing: () => getPricing,
602
- getX402Fetch: () => getX402Fetch
469
+ getX402Fetch: () => getX402Fetch,
470
+ selectPlanInteractive: () => selectPlanInteractive
603
471
  });
604
472
  import { x402Client } from "@x402/core/client";
605
473
  import { wrapFetchWithPayment } from "@x402/fetch";
606
474
  import { ExactEvmScheme } from "@x402/evm/exact/client";
475
+ import { ExactSvmScheme } from "@x402/svm/exact/client";
607
476
  import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
608
477
  import { hashTypedData } from "viem";
478
+ import { createKeyPairSignerFromBytes } from "@solana/kit";
479
+ import * as readline from "readline/promises";
609
480
  function createTurnkeyPaymentAccount(creds) {
610
481
  const address = creds.evmAddress;
611
482
  return {
@@ -637,55 +508,136 @@ function createTurnkeyPaymentAccount(creds) {
637
508
  }
638
509
  };
639
510
  }
640
- function getX402Fetch(config) {
511
+ async function turnkeySolanaSign(creds, payload) {
512
+ const payloadHex = Buffer.from(payload).toString("hex");
513
+ const result = await turnkeyRequest(
514
+ "/public/v1/submit/sign_raw_payload",
515
+ {
516
+ type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
517
+ timestampMs: Date.now().toString(),
518
+ organizationId: creds.organizationId,
519
+ parameters: {
520
+ signWith: creds.solanaAddress,
521
+ payload: payloadHex,
522
+ encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
523
+ hashFunction: "HASH_FUNCTION_NOT_APPLICABLE"
524
+ }
525
+ },
526
+ creds
527
+ );
528
+ const sig = result.activity?.result?.signRawPayloadResult;
529
+ if (!sig) throw new Error("Turnkey Solana sign: no signature");
530
+ return new Uint8Array(Buffer.from(sig.r + sig.s, "hex"));
531
+ }
532
+ async function createTurnkeySolanaSigner(creds) {
533
+ const { createNoopSigner, address: solAddress } = await import("@solana/kit");
534
+ return createNoopSigner(solAddress(creds.solanaAddress));
535
+ }
536
+ function createTurnkeySvmScheme(creds, noopSigner) {
537
+ const inner = new ExactSvmScheme(noopSigner, { rpcUrl: "https://api.mainnet-beta.solana.com" });
538
+ return {
539
+ scheme: "exact",
540
+ async createPaymentPayload(x402Version, paymentRequirements) {
541
+ const payload = await inner.createPaymentPayload(x402Version, paymentRequirements);
542
+ const wrapped = payload;
543
+ const txB64 = wrapped.payload.transaction;
544
+ const txBytes = new Uint8Array(Buffer.from(txB64, "base64"));
545
+ const sigCount = txBytes[0];
546
+ const messageStart = 1 + sigCount * 64;
547
+ const messageBytes = txBytes.slice(messageStart);
548
+ const sig = await turnkeySolanaSign(creds, messageBytes);
549
+ txBytes.set(sig, 1 + 64);
550
+ wrapped.payload.transaction = Buffer.from(txBytes).toString("base64");
551
+ return payload;
552
+ }
553
+ };
554
+ }
555
+ function bs58Decode2(str) {
556
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
557
+ const BASE = 58;
558
+ const result = [0];
559
+ for (const char of str) {
560
+ let carry = ALPHABET.indexOf(char);
561
+ if (carry < 0) throw new Error(`Invalid base58 character: ${char}`);
562
+ for (let j = 0; j < result.length; j++) {
563
+ carry += result[j] * BASE;
564
+ result[j] = carry & 255;
565
+ carry >>= 8;
566
+ }
567
+ while (carry > 0) {
568
+ result.push(carry & 255);
569
+ carry >>= 8;
570
+ }
571
+ }
572
+ for (const char of str) {
573
+ if (char !== "1") break;
574
+ result.push(0);
575
+ }
576
+ return new Uint8Array(result.reverse());
577
+ }
578
+ async function getX402Fetch(config) {
641
579
  if (_x402Fetch) return _x402Fetch;
642
580
  const client = new x402Client();
643
- if (config.rawWallet?.chain === "evm") {
644
- const hex = config.rawWallet.key.startsWith("0x") ? config.rawWallet.key : `0x${config.rawWallet.key}`;
645
- const account = privateKeyToAccount2(hex);
646
- client.register("eip155:*", new ExactEvmScheme(account));
647
- } else if (config.turnkey?.evmAddress && config.turnkey?.organizationId) {
648
- const account = createTurnkeyPaymentAccount(config.turnkey);
649
- client.register("eip155:*", new ExactEvmScheme(account));
581
+ if (config.rawWallet) {
582
+ if (isEvmChain(config.rawWallet.chain)) {
583
+ const hex = config.rawWallet.key.startsWith("0x") ? config.rawWallet.key : `0x${config.rawWallet.key}`;
584
+ const account = privateKeyToAccount2(hex);
585
+ client.register("eip155:8453", new ExactEvmScheme(account));
586
+ } else if (isSolanaChain(config.rawWallet.chain)) {
587
+ const decoded = bs58Decode2(config.rawWallet.key);
588
+ const signer = await createKeyPairSignerFromBytes(new Uint8Array(decoded));
589
+ client.register("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", new ExactSvmScheme(signer, { rpcUrl: "https://api.mainnet-beta.solana.com" }));
590
+ }
591
+ } else if (config.turnkey?.organizationId) {
592
+ const preferredChain = config.walletChain ?? "base";
593
+ if (isEvmChain(preferredChain) && config.turnkey.evmAddress) {
594
+ const account = createTurnkeyPaymentAccount(config.turnkey);
595
+ client.register("eip155:8453", new ExactEvmScheme(account));
596
+ } else if (isSolanaChain(preferredChain) && config.turnkey.solanaAddress) {
597
+ const noopSigner = await createTurnkeySolanaSigner(config.turnkey);
598
+ const scheme = createTurnkeySvmScheme(config.turnkey, noopSigner);
599
+ client.register("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", scheme);
600
+ }
650
601
  }
651
602
  _x402Fetch = wrapFetchWithPayment(fetch, client);
652
603
  return _x402Fetch;
653
604
  }
654
- async function autoPurchaseOnDemand(config, plan = "nano") {
655
- const x402Fetch = getX402Fetch(config);
656
- process.stderr.write(`[chainstream] No active subscription. Auto-purchasing ${plan} plan...
657
- `);
658
- try {
659
- const resp = await x402Fetch(`${config.baseUrl}/x402/purchase?plan=${encodeURIComponent(plan)}`);
660
- if (!resp.ok) {
661
- const text = await resp.text().catch(() => "");
662
- process.stderr.write(`[chainstream] Purchase failed (${resp.status}): ${text}
663
- `);
664
- return { success: false };
665
- }
666
- const result = await resp.json();
667
- process.stderr.write(`[chainstream] Subscription activated: ${result.plan} (expires: ${result.expires_at})
668
- `);
669
- return {
670
- success: true,
671
- plan: result.plan,
672
- expiresAt: result.expires_at
673
- };
674
- } catch (err) {
675
- process.stderr.write(`[chainstream] Auto-purchase error: ${err instanceof Error ? err.message : err}
676
- `);
677
- return { success: false };
678
- }
679
- }
680
605
  async function getPricing(baseUrl) {
681
606
  const resp = await fetch(`${baseUrl}/x402/pricing`);
682
607
  if (!resp.ok) throw new Error(`Failed to get pricing (${resp.status})`);
683
608
  return resp.json();
684
609
  }
610
+ async function selectPlanInteractive(baseUrl) {
611
+ const pricing = await getPricing(baseUrl);
612
+ const plans = pricing.plans.sort((a, b) => a.price_usd - b.price_usd);
613
+ process.stderr.write("\n[chainstream] No active subscription. Available plans:\n\n");
614
+ process.stderr.write(" # Plan Price Quota Duration\n");
615
+ process.stderr.write(" \u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
616
+ for (let i = 0; i < plans.length; i++) {
617
+ const p = plans[i];
618
+ const num = String(i + 1).padStart(2);
619
+ const name = p.name.padEnd(10);
620
+ const price = `$${p.price_usd}`.padEnd(8);
621
+ const quota = p.quota_total.toLocaleString().padStart(14) + " CU";
622
+ const days = `${p.duration_days} days`;
623
+ process.stderr.write(` ${num} ${name} ${price} ${quota} ${days}
624
+ `);
625
+ }
626
+ process.stderr.write("\n");
627
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
628
+ const answer = await rl.question(`Select plan (1-${plans.length}): `);
629
+ rl.close();
630
+ const idx = parseInt(answer.trim(), 10) - 1;
631
+ if (idx < 0 || idx >= plans.length || Number.isNaN(idx)) {
632
+ throw new Error("Invalid plan selection. Aborting purchase.");
633
+ }
634
+ return plans[idx].name;
635
+ }
685
636
  var _x402Fetch;
686
637
  var init_x402 = __esm({
687
638
  "src/lib/x402.ts"() {
688
639
  "use strict";
640
+ init_config();
689
641
  init_turnkey();
690
642
  _x402Fetch = null;
691
643
  }
@@ -721,7 +673,7 @@ function createClient() {
721
673
  return _client;
722
674
  }
723
675
  throw new Error(
724
- "Not authenticated. Run one of:\n chainstream login # Email OTP (creates Turnkey wallet)\n chainstream login --key # P-256 key (no email)\n chainstream wallet set-raw # Raw private key (dev)\n chainstream config set --key apiKey --value <key> # API key only"
676
+ "Not authenticated. Run one of:\n chainstream login # Create Turnkey wallet (no email)\n chainstream login --email # Email OTP login\n chainstream wallet set-raw # Import private key (dev)\n chainstream config set --key apiKey --value <key> # API key only"
725
677
  );
726
678
  }
727
679
  function requireWallet() {
@@ -738,27 +690,78 @@ async function callWithAutoPayment(fn, retried = false) {
738
690
  try {
739
691
  return await fn();
740
692
  } catch (err) {
741
- const is402 = isPaymentRequired(err);
742
- if (is402 && !retried && _wallet) {
693
+ const paymentInfo = extractPaymentInfo(err);
694
+ if (paymentInfo && !retried && _wallet) {
743
695
  const config = loadConfig();
744
- const result = await autoPurchaseOnDemand(config);
745
- if (result.success) {
696
+ let purchaseUrl = resolvePurchaseUrl(config.baseUrl, paymentInfo);
697
+ if (config.plan) {
698
+ purchaseUrl = replacePlanInUrl(purchaseUrl, config.plan);
699
+ process.stderr.write(`[chainstream] No active subscription. Purchasing ${config.plan} plan...
700
+ `);
701
+ } else if (process.stdin.isTTY) {
702
+ const chosenPlan = await selectPlanInteractive(config.baseUrl);
703
+ purchaseUrl = replacePlanInUrl(purchaseUrl, chosenPlan);
704
+ process.stderr.write(`[chainstream] Purchasing ${chosenPlan} plan...
705
+ `);
706
+ } else {
707
+ process.stderr.write(`[chainstream] No active subscription. Purchasing via x402...
708
+ `);
709
+ process.stderr.write(`[chainstream] Resource: ${purchaseUrl}
710
+ `);
711
+ }
712
+ const x402Fetch = await getX402Fetch(config);
713
+ const resp = await x402Fetch(purchaseUrl);
714
+ if (resp.ok) {
715
+ const result = await resp.json();
716
+ process.stderr.write(`[chainstream] Subscription activated: ${result.plan} (expires: ${result.expires_at})
717
+ `);
746
718
  _client = null;
747
719
  createClient();
748
720
  return callWithAutoPayment(fn, true);
749
721
  }
722
+ const text = await resp.text().catch(() => "");
723
+ process.stderr.write(`[chainstream] Purchase failed (${resp.status}): ${text}
724
+ `);
750
725
  }
751
726
  throw err;
752
727
  }
753
728
  }
754
- function isPaymentRequired(err) {
755
- if (!err || typeof err !== "object") return false;
729
+ function extractPaymentInfo(err) {
730
+ if (!err || typeof err !== "object") return null;
756
731
  const axiosErr = err;
757
- if (axiosErr.response?.status === 402) return true;
758
- if (axiosErr.status === 402) return true;
759
- if (axiosErr.message?.includes("402")) return true;
760
- if (axiosErr.message?.includes("PAYMENT_REQUIRED")) return true;
761
- return false;
732
+ const status = axiosErr.response?.status ?? axiosErr.status;
733
+ const message = axiosErr.message ?? "";
734
+ if (status === 402) {
735
+ const header = axiosErr.response?.headers?.["payment-required"] ?? axiosErr.response?.headers?.["x-payment-required"];
736
+ if (header) {
737
+ try {
738
+ const decoded = JSON.parse(Buffer.from(header, "base64").toString());
739
+ return { resourceUrl: decoded.resource?.url };
740
+ } catch {
741
+ }
742
+ }
743
+ return {};
744
+ }
745
+ if (message.includes("402") || message.includes("PAYMENT_REQUIRED") || message.includes("no active x402 subscription")) {
746
+ return {};
747
+ }
748
+ return null;
749
+ }
750
+ function resolvePurchaseUrl(baseUrl, info) {
751
+ if (info.resourceUrl) {
752
+ if (info.resourceUrl.startsWith("http")) return info.resourceUrl;
753
+ return `${baseUrl}${info.resourceUrl}`;
754
+ }
755
+ return `${baseUrl}/x402/purchase?plan=nano`;
756
+ }
757
+ function replacePlanInUrl(url, plan) {
758
+ try {
759
+ const u = new URL(url);
760
+ u.searchParams.set("plan", plan);
761
+ return u.toString();
762
+ } catch {
763
+ return url.replace(/plan=[^&]+/, `plan=${encodeURIComponent(plan)}`);
764
+ }
762
765
  }
763
766
 
764
767
  // src/lib/validate.ts
@@ -971,7 +974,7 @@ function registerMarketCommands(program2) {
971
974
 
972
975
  // src/commands/wallet.ts
973
976
  init_config();
974
- import * as readline from "readline/promises";
977
+ import * as readline2 from "readline/promises";
975
978
  function registerWalletCommands(program2) {
976
979
  const wallet = program2.command("wallet").description("Wallet analytics and management");
977
980
  wallet.command("profile").description("Wallet profile: PnL + net worth + top holdings").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Wallet address").option("--raw", "Single-line JSON output").action(async (opts) => {
@@ -1040,22 +1043,22 @@ function registerWalletCommands(program2) {
1040
1043
  const walletMode = (await Promise.resolve().then(() => (init_config(), config_exports))).getWalletMode(config);
1041
1044
  if (walletMode === "turnkey" && config.turnkey) {
1042
1045
  if (config.turnkey.evmAddress || config.turnkey.solanaAddress) {
1043
- if (config.turnkey.evmAddress) process.stdout.write(`EVM: ${config.turnkey.evmAddress}
1046
+ if (config.turnkey.evmAddress) process.stdout.write(`Base: ${config.turnkey.evmAddress}
1044
1047
  `);
1045
1048
  if (config.turnkey.solanaAddress) process.stdout.write(`Solana: ${config.turnkey.solanaAddress}
1046
1049
  `);
1047
1050
  } else {
1048
1051
  const { createWalletWithAddresses: createWalletWithAddresses2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
1049
1052
  const wallets = await createWalletWithAddresses2(config, "turnkey");
1050
- if (wallets.evm) process.stdout.write(`EVM: ${wallets.evm.address}
1053
+ if (wallets.base) process.stdout.write(`Base: ${wallets.base.address}
1051
1054
  `);
1052
- if (wallets.solana) process.stdout.write(`Solana: ${wallets.solana.address}
1055
+ if (wallets.sol) process.stdout.write(`Solana: ${wallets.sol.address}
1053
1056
  `);
1054
1057
  }
1055
1058
  } else if (config.rawWallet) {
1056
1059
  const { createWallet: createWallet2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
1057
1060
  const w = createWallet2(config, "raw");
1058
- process.stdout.write(`${w.chain.toUpperCase()}: ${w.address}
1061
+ process.stdout.write(`${w.paymentChain.toUpperCase()}: ${w.address}
1059
1062
  `);
1060
1063
  } else {
1061
1064
  process.stdout.write("No wallet configured. Run: chainstream login\n");
@@ -1091,9 +1094,9 @@ function registerWalletCommands(program2) {
1091
1094
  exitOnError(err);
1092
1095
  }
1093
1096
  });
1094
- wallet.command("set-raw").description("Set raw private key (dev/testing only)").requiredOption("--chain <chain>", "Chain: evm/solana").action(async (opts) => {
1097
+ wallet.command("set-raw").description("Set raw private key (dev/testing only)").requiredOption("--chain <chain>", "Chain: base/sol").action(async (opts) => {
1095
1098
  try {
1096
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1099
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
1097
1100
  process.stdout.write("\u26A0 WARNING: Raw private key will be stored in plaintext.\n");
1098
1101
  process.stdout.write(" Use Turnkey (chainstream login) for production.\n\n");
1099
1102
  const key = await rl.question("Enter private key: ");
@@ -1139,7 +1142,7 @@ function registerKytCommands(program2) {
1139
1142
  }
1140
1143
 
1141
1144
  // src/commands/dex.ts
1142
- import * as readline2 from "readline/promises";
1145
+ import * as readline3 from "readline/promises";
1143
1146
  function registerDexCommands(program2) {
1144
1147
  const dex = program2.command("dex").description("DEX swap, quote, and token creation");
1145
1148
  dex.command("quote").description("Get swap quote (read-only)").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--input-token <addr>", "Input token (address or SOL/ETH/BNB/USDC)").requiredOption("--output-token <addr>", "Output token (address or SOL/ETH/BNB/USDC)").requiredOption("--amount <amount>", "Input amount (smallest unit)").option("--raw", "Single-line JSON output").action(async (opts) => {
@@ -1191,7 +1194,7 @@ function registerDexCommands(program2) {
1191
1194
  `);
1192
1195
  process.stderr.write("--------------------\n\n");
1193
1196
  if (!opts.yes) {
1194
- const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
1197
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
1195
1198
  const answer = await rl.question("Confirm swap? (y/N): ");
1196
1199
  rl.close();
1197
1200
  if (answer.toLowerCase() !== "y") {
@@ -1346,7 +1349,7 @@ function saveKey(keyPair, profile = DEFAULT_PROFILE) {
1346
1349
  // src/commands/auth.ts
1347
1350
  init_turnkey();
1348
1351
  init_constants();
1349
- import * as readline3 from "readline/promises";
1352
+ import * as readline4 from "readline/promises";
1350
1353
  async function resolveAndStoreAddresses(turnkeyCreds) {
1351
1354
  process.stderr.write("Resolving wallet addresses...\n");
1352
1355
  const [evmAddress, solanaAddress] = await Promise.all([
@@ -1358,7 +1361,7 @@ async function resolveAndStoreAddresses(turnkeyCreds) {
1358
1361
  });
1359
1362
  }
1360
1363
  function registerAuthCommands(program2) {
1361
- program2.command("login").description("Create wallet (default) or login via email OTP (--email)").argument("[email]", "Email address for OTP login (optional)").option("--email", "Use email OTP login instead of key-based").action(async (emailArg, opts) => {
1364
+ program2.command("login").description("Create wallet (default: --key) or login via email OTP").argument("[email]", "Email address for OTP login").option("--key", "Key-based login \u2014 no email required (default)").option("--email", "Email OTP login").action(async (emailArg, opts) => {
1362
1365
  try {
1363
1366
  if (opts.email || emailArg) {
1364
1367
  await doEmailLogin(emailArg);
@@ -1519,7 +1522,7 @@ async function doKeyLogin() {
1519
1522
  }
1520
1523
  async function doEmailLogin(email) {
1521
1524
  if (!email) {
1522
- const rl2 = readline3.createInterface({ input: process.stdin, output: process.stderr });
1525
+ const rl2 = readline4.createInterface({ input: process.stdin, output: process.stderr });
1523
1526
  email = await rl2.question("Enter your email: ");
1524
1527
  rl2.close();
1525
1528
  if (!email?.trim()) throw new Error("Email required.");
@@ -1535,7 +1538,7 @@ async function doEmailLogin(email) {
1535
1538
  `);
1536
1539
  return;
1537
1540
  }
1538
- const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
1541
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
1539
1542
  const code = await rl.question("Enter OTP code: ");
1540
1543
  rl.close();
1541
1544
  if (!code?.trim()) throw new Error("OTP code required.");
@@ -1584,7 +1587,7 @@ async function doBindEmail(email) {
1584
1587
  throw new Error("No wallet found. Run 'chainstream login' first.");
1585
1588
  }
1586
1589
  if (!email) {
1587
- const rl2 = readline3.createInterface({ input: process.stdin, output: process.stderr });
1590
+ const rl2 = readline4.createInterface({ input: process.stdin, output: process.stderr });
1588
1591
  email = await rl2.question("Enter email to bind: ");
1589
1592
  rl2.close();
1590
1593
  if (!email?.trim()) throw new Error("Email required.");
@@ -1592,14 +1595,15 @@ async function doBindEmail(email) {
1592
1595
  }
1593
1596
  process.stderr.write(`Sending verification code to ${email}...
1594
1597
  `);
1595
- const { otpId } = await callOtpInit(email, config.turnkey.organizationId);
1598
+ const { otpId } = await callOtpInit(email);
1596
1599
  if (!process.stdin.isTTY) {
1597
- process.stdout.write(JSON.stringify({ otpId, email }) + "\n");
1598
- process.stderr.write("Non-interactive mode. To complete binding, verify the OTP\n");
1599
- process.stderr.write("and call the Turnkey update_user_email API with the verificationToken.\n");
1600
+ process.stdout.write(JSON.stringify({ otpId, email, step: "verify" }) + "\n");
1601
+ process.stderr.write("Non-interactive mode. Complete with:\n");
1602
+ process.stderr.write(` chainstream bind-email-verify --otp-id ${otpId} --code <code> --email ${email}
1603
+ `);
1600
1604
  return;
1601
1605
  }
1602
- const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
1606
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
1603
1607
  const code = await rl.question("Enter verification code: ");
1604
1608
  rl.close();
1605
1609
  if (!code?.trim()) throw new Error("Verification code required.");
@@ -1607,14 +1611,13 @@ async function doBindEmail(email) {
1607
1611
  const { verificationToken } = await callOtpVerify(
1608
1612
  otpId,
1609
1613
  code.trim(),
1610
- config.turnkey.publicKeyHex,
1611
- config.turnkey.organizationId
1614
+ config.turnkey.publicKeyHex
1612
1615
  );
1613
- process.stderr.write("Binding email to Turnkey wallet...\n");
1616
+ process.stderr.write("Binding email to wallet...\n");
1614
1617
  await updateTurnkeyUserEmail(email, verificationToken, config.turnkey);
1615
1618
  process.stdout.write(`Email ${email} bound successfully.
1616
1619
  `);
1617
- process.stdout.write("You can now use this email for account recovery.\n");
1620
+ process.stdout.write("You can now use 'chainstream login --email' with this email.\n");
1618
1621
  }
1619
1622
 
1620
1623
  // src/commands/config-cmd.ts
@@ -1623,7 +1626,7 @@ function registerConfigCommands(program2) {
1623
1626
  const config = program2.command("config").description("Configuration management");
1624
1627
  config.command("set").description("Set a configuration value").requiredOption("--key <key>", "Config key (apiKey, baseUrl)").requiredOption("--value <value>", "Config value").action((opts) => {
1625
1628
  try {
1626
- const allowedKeys = ["apiKey", "baseUrl"];
1629
+ const allowedKeys = ["apiKey", "baseUrl", "walletChain", "plan"];
1627
1630
  if (!allowedKeys.includes(opts.key)) {
1628
1631
  throw new Error(`Invalid key "${opts.key}". Allowed: ${allowedKeys.join(", ")}`);
1629
1632
  }
@@ -1683,7 +1686,7 @@ function registerConfigCommands(program2) {
1683
1686
  } else {
1684
1687
  process.stdout.write("Auth: Not configured\n");
1685
1688
  process.stdout.write(" Run: chainstream login # Turnkey wallet\n");
1686
- process.stdout.write(" Run: chainstream config set apiKey <key> # API key\n");
1689
+ process.stdout.write(" Run: chainstream config set --key apiKey --value <key> # API key\n");
1687
1690
  }
1688
1691
  } catch (err) {
1689
1692
  exitOnError(err);