@chainstream-io/cli 0.0.1

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 ADDED
@@ -0,0 +1,1468 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/lib/constants.ts
13
+ var CHAINSTREAM_API_URL, TURNKEY_AUTH_PROXY_CONFIG_ID, SOLANA_RPC_URL, BASE_RPC_URL, BASE_CHAIN_ID;
14
+ var init_constants = __esm({
15
+ "src/lib/constants.ts"() {
16
+ "use strict";
17
+ CHAINSTREAM_API_URL = process.env.CHAINSTREAM_API_URL ?? "https://api.chainstream.io";
18
+ TURNKEY_AUTH_PROXY_CONFIG_ID = process.env.TURNKEY_AUTH_PROXY_CONFIG_ID ?? "";
19
+ SOLANA_RPC_URL = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";
20
+ BASE_RPC_URL = process.env.BASE_RPC_URL ?? "https://mainnet.base.org";
21
+ BASE_CHAIN_ID = Number(process.env.BASE_CHAIN_ID ?? "8453");
22
+ }
23
+ });
24
+
25
+ // src/lib/config.ts
26
+ var config_exports = {};
27
+ __export(config_exports, {
28
+ getConfigDir: () => getConfigDir,
29
+ getWalletMode: () => getWalletMode,
30
+ loadConfig: () => loadConfig,
31
+ saveConfig: () => saveConfig,
32
+ updateConfig: () => updateConfig
33
+ });
34
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
35
+ import { homedir } from "os";
36
+ import { join } from "path";
37
+ function getConfigDir() {
38
+ if (!existsSync(CONFIG_DIR)) {
39
+ mkdirSync(CONFIG_DIR, { recursive: true });
40
+ }
41
+ return CONFIG_DIR;
42
+ }
43
+ function loadConfig() {
44
+ const defaults = {
45
+ baseUrl: CHAINSTREAM_API_URL
46
+ };
47
+ if (!existsSync(CONFIG_FILE)) return defaults;
48
+ try {
49
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
50
+ const parsed = JSON.parse(raw);
51
+ return { ...defaults, ...parsed };
52
+ } catch {
53
+ return defaults;
54
+ }
55
+ }
56
+ function saveConfig(config) {
57
+ getConfigDir();
58
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
59
+ }
60
+ function updateConfig(patch) {
61
+ const current = loadConfig();
62
+ saveConfig({ ...current, ...patch });
63
+ }
64
+ function getWalletMode(config) {
65
+ if (config.turnkey?.sessionToken) return "turnkey";
66
+ if (config.rawWallet?.key) return "raw";
67
+ return void 0;
68
+ }
69
+ var CONFIG_DIR, CONFIG_FILE;
70
+ var init_config = __esm({
71
+ "src/lib/config.ts"() {
72
+ "use strict";
73
+ init_constants();
74
+ CONFIG_DIR = join(homedir(), ".config", "chainstream");
75
+ CONFIG_FILE = join(CONFIG_DIR, "config.json");
76
+ }
77
+ });
78
+
79
+ // src/wallet/raw-wallet.ts
80
+ import { privateKeyToAccount } from "viem/accounts";
81
+ import { Keypair, VersionedTransaction } from "@solana/web3.js";
82
+ import { sign as ed25519Sign } from "crypto";
83
+ function bs58Decode(str) {
84
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
85
+ const BASE = 58;
86
+ const result = [0];
87
+ for (const char of str) {
88
+ let carry = ALPHABET.indexOf(char);
89
+ if (carry < 0) throw new Error(`Invalid base58 character: ${char}`);
90
+ for (let j = 0; j < result.length; j++) {
91
+ carry += result[j] * BASE;
92
+ result[j] = carry & 255;
93
+ carry >>= 8;
94
+ }
95
+ while (carry > 0) {
96
+ result.push(carry & 255);
97
+ carry >>= 8;
98
+ }
99
+ }
100
+ for (const char of str) {
101
+ if (char !== "1") break;
102
+ result.push(0);
103
+ }
104
+ return new Uint8Array(result.reverse());
105
+ }
106
+ var RawKeyWallet;
107
+ var init_raw_wallet = __esm({
108
+ "src/wallet/raw-wallet.ts"() {
109
+ "use strict";
110
+ RawKeyWallet = class {
111
+ chain;
112
+ address;
113
+ privateKey;
114
+ constructor(key, chain) {
115
+ this.chain = chain;
116
+ this.privateKey = key;
117
+ if (chain === "evm") {
118
+ const hex = key.startsWith("0x") ? key : `0x${key}`;
119
+ const account = privateKeyToAccount(hex);
120
+ this.address = account.address;
121
+ } else {
122
+ const decoded = bs58Decode(key);
123
+ const keypair = Keypair.fromSecretKey(decoded);
124
+ this.address = keypair.publicKey.toBase58();
125
+ }
126
+ }
127
+ async signMessage(message) {
128
+ if (this.chain === "evm") {
129
+ const hex = this.privateKey.startsWith("0x") ? this.privateKey : `0x${this.privateKey}`;
130
+ const account = privateKeyToAccount(hex);
131
+ return account.signMessage({ message });
132
+ }
133
+ const decoded = bs58Decode(this.privateKey);
134
+ const secretKeyRaw = decoded.slice(0, 32);
135
+ const pkcs8Prefix = Buffer.from("302e020100300506032b657004220420", "hex");
136
+ const pkcs8Der = Buffer.concat([pkcs8Prefix, Buffer.from(secretKeyRaw)]);
137
+ const msgBytes = Buffer.from(message, "utf-8");
138
+ const sig = ed25519Sign(null, msgBytes, { key: pkcs8Der, format: "der", type: "pkcs8" });
139
+ return sig.toString("hex");
140
+ }
141
+ async signTransaction(serializedTx) {
142
+ const txBytes = Buffer.from(serializedTx, "base64");
143
+ if (this.chain === "solana") {
144
+ const tx = VersionedTransaction.deserialize(new Uint8Array(txBytes));
145
+ const decoded = bs58Decode(this.privateKey);
146
+ const keypair = Keypair.fromSecretKey(decoded);
147
+ tx.sign([keypair]);
148
+ return Buffer.from(tx.serialize()).toString("base64");
149
+ }
150
+ const hex = this.privateKey.startsWith("0x") ? this.privateKey : `0x${this.privateKey}`;
151
+ const account = privateKeyToAccount(hex);
152
+ const signedHex = await account.signTransaction({ type: "legacy" });
153
+ return Buffer.from(signedHex.slice(2), "hex").toString("base64");
154
+ }
155
+ };
156
+ }
157
+ });
158
+
159
+ // src/lib/turnkey.ts
160
+ import { createSign, generateKeyPairSync } from "crypto";
161
+ function generateP256KeyPair() {
162
+ const { publicKey, privateKey } = generateKeyPairSync("ec", {
163
+ namedCurve: "prime256v1",
164
+ publicKeyEncoding: { type: "spki", format: "der" },
165
+ privateKeyEncoding: { type: "pkcs8", format: "der" }
166
+ });
167
+ const pubBuf = Buffer.from(publicKey);
168
+ const uncompressedBytes = pubBuf.subarray(26);
169
+ const x = uncompressedBytes.subarray(1, 33);
170
+ const y = uncompressedBytes.subarray(33, 65);
171
+ const prefix = y[31] % 2 === 0 ? 2 : 3;
172
+ const compressedHex = Buffer.concat([Buffer.from([prefix]), x]).toString("hex");
173
+ const uncompressedHex = Buffer.from(uncompressedBytes).toString("hex");
174
+ return {
175
+ publicKeyHex: compressedHex,
176
+ uncompressedPublicKeyHex: uncompressedHex,
177
+ privateKeyDer: Buffer.from(privateKey).toString("base64")
178
+ };
179
+ }
180
+ function signRawP256(message, privateKeyDerBase64) {
181
+ const signer = createSign("SHA256");
182
+ signer.update(message);
183
+ signer.end();
184
+ const derSig = signer.sign({
185
+ key: Buffer.from(privateKeyDerBase64, "base64"),
186
+ format: "der",
187
+ type: "pkcs8"
188
+ });
189
+ return derToRawSignature(derSig);
190
+ }
191
+ function derToRawSignature(der) {
192
+ let offset = 0;
193
+ if (der[offset++] !== 48) throw new Error("Invalid DER signature");
194
+ offset++;
195
+ if (der[offset++] !== 2) throw new Error("Invalid DER signature");
196
+ const rLen = der[offset++];
197
+ const rBytes = der.subarray(offset, offset + rLen);
198
+ offset += rLen;
199
+ if (der[offset++] !== 2) throw new Error("Invalid DER signature");
200
+ const sLen = der[offset++];
201
+ const sBytes = der.subarray(offset, offset + sLen);
202
+ const r = padOrTrimTo32(rBytes);
203
+ const s = padOrTrimTo32(sBytes);
204
+ return Buffer.concat([r, s]).toString("hex");
205
+ }
206
+ function padOrTrimTo32(buf) {
207
+ if (buf.length === 32) return buf;
208
+ if (buf.length === 33 && buf[0] === 0) return buf.subarray(1);
209
+ if (buf.length < 32) {
210
+ const padded = Buffer.alloc(32);
211
+ buf.copy(padded, 32 - buf.length);
212
+ return padded;
213
+ }
214
+ return buf.subarray(buf.length - 32);
215
+ }
216
+ function decodeJwtPayload(jwt) {
217
+ const parts = jwt.split(".");
218
+ if (parts.length !== 3) throw new Error("Invalid JWT");
219
+ const payloadB64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
220
+ return JSON.parse(Buffer.from(payloadB64, "base64").toString("utf-8"));
221
+ }
222
+ async function proxyRequest(path, body, configId) {
223
+ if (!configId) throw new Error("TURNKEY_AUTH_PROXY_CONFIG_ID not configured.");
224
+ const res = await fetch(`${AUTH_PROXY_BASE}${path}`, {
225
+ method: "POST",
226
+ headers: {
227
+ "Content-Type": "application/json",
228
+ "X-Auth-Proxy-Config-ID": configId
229
+ },
230
+ body: JSON.stringify(body)
231
+ });
232
+ if (!res.ok) {
233
+ const text = await res.text().catch(() => "");
234
+ let detail = text;
235
+ try {
236
+ detail = JSON.parse(text).message ?? text;
237
+ } catch {
238
+ }
239
+ throw new Error(`Turnkey auth proxy error (${res.status}): ${detail}`);
240
+ }
241
+ return res.json();
242
+ }
243
+ async function otpInit(email, configId) {
244
+ return proxyRequest("/v1/otp_init", { otpType: "OTP_TYPE_EMAIL", contact: email }, configId);
245
+ }
246
+ async function otpVerify(otpId, otpCode, publicKeyHex, configId) {
247
+ return proxyRequest("/v1/otp_verify", { otpId, otpCode, publicKey: publicKeyHex }, configId);
248
+ }
249
+ async function checkAccount(email, configId, verificationToken) {
250
+ try {
251
+ const result = await proxyRequest(
252
+ "/v1/account",
253
+ { filterType: "EMAIL", filterValue: email, ...verificationToken && { verificationToken } },
254
+ configId
255
+ );
256
+ return { exists: !!result.organizationId, organizationId: result.organizationId };
257
+ } catch {
258
+ return { exists: false };
259
+ }
260
+ }
261
+ async function signup(email, configId, verificationToken, publicKeyHex, privateKeyDer) {
262
+ const decoded = decodeJwtPayload(verificationToken);
263
+ const verificationPublicKey = decoded.public_key ?? publicKeyHex;
264
+ const signupBody = {
265
+ userName: email.split("@")[0],
266
+ organizationName: email,
267
+ userEmail: email,
268
+ verificationToken,
269
+ apiKeys: [],
270
+ authenticators: [],
271
+ oauthProviders: [],
272
+ wallet: {
273
+ walletName: "ChainStream Wallet",
274
+ accounts: [
275
+ { curve: "CURVE_SECP256K1", pathFormat: "PATH_FORMAT_BIP32", path: "m/44'/60'/0'/0/0", addressFormat: "ADDRESS_FORMAT_ETHEREUM" },
276
+ { curve: "CURVE_ED25519", pathFormat: "PATH_FORMAT_BIP32", path: "m/44'/501'/0'/0'", addressFormat: "ADDRESS_FORMAT_SOLANA" }
277
+ ]
278
+ }
279
+ };
280
+ const signatureMessage = JSON.stringify({
281
+ signup: { email, apiKeys: signupBody.apiKeys, authenticators: signupBody.authenticators, oauthProviders: signupBody.oauthProviders },
282
+ tokenId: decoded.id,
283
+ type: "USAGE_TYPE_SIGNUP"
284
+ });
285
+ const signature = signRawP256(signatureMessage, privateKeyDer);
286
+ return proxyRequest("/v1/signup", {
287
+ ...signupBody,
288
+ clientSignature: {
289
+ message: signatureMessage,
290
+ publicKey: verificationPublicKey,
291
+ scheme: "CLIENT_SIGNATURE_SCHEME_API_P256",
292
+ signature
293
+ }
294
+ }, configId);
295
+ }
296
+ async function otpLogin(verificationToken, publicKeyHex, privateKeyDerBase64, configId) {
297
+ const decoded = decodeJwtPayload(verificationToken);
298
+ const tokenUsageMessage = JSON.stringify({
299
+ login: { publicKey: publicKeyHex },
300
+ tokenId: decoded.id,
301
+ type: "USAGE_TYPE_LOGIN"
302
+ });
303
+ const signature = signRawP256(tokenUsageMessage, privateKeyDerBase64);
304
+ return proxyRequest("/v1/otp_login", {
305
+ verificationToken,
306
+ publicKey: publicKeyHex,
307
+ clientSignature: {
308
+ message: tokenUsageMessage,
309
+ publicKey: decoded.public_key ?? publicKeyHex,
310
+ scheme: "CLIENT_SIGNATURE_SCHEME_API_P256",
311
+ signature
312
+ }
313
+ }, configId);
314
+ }
315
+ async function completeLogin(otpId, otpCode, configId, email) {
316
+ const keyPair = generateP256KeyPair();
317
+ const { verificationToken } = await otpVerify(otpId, otpCode, keyPair.publicKeyHex, configId);
318
+ const account = await checkAccount(email, configId, verificationToken);
319
+ let isNewUser = false;
320
+ if (!account.exists) {
321
+ await signup(email, configId, verificationToken, keyPair.publicKeyHex, keyPair.privateKeyDer);
322
+ isNewUser = true;
323
+ }
324
+ const { session } = await otpLogin(verificationToken, keyPair.publicKeyHex, keyPair.privateKeyDer, configId);
325
+ const sessionPayload = decodeJwtPayload(session);
326
+ return {
327
+ session,
328
+ keyPair,
329
+ organizationId: sessionPayload.organization_id ?? "",
330
+ sessionExpiry: sessionPayload.exp,
331
+ isNewUser
332
+ };
333
+ }
334
+ function createApiStamp(body, publicKeyHex, privateKeyDerBase64) {
335
+ const signer = createSign("SHA256");
336
+ signer.update(body);
337
+ signer.end();
338
+ const signature = signer.sign({
339
+ key: Buffer.from(privateKeyDerBase64, "base64"),
340
+ format: "der",
341
+ type: "pkcs8"
342
+ });
343
+ const stamp = JSON.stringify({
344
+ publicKey: publicKeyHex,
345
+ scheme: "SIGNATURE_SCHEME_TK_API_P256",
346
+ signature: signature.toString("hex")
347
+ });
348
+ return Buffer.from(stamp).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
349
+ }
350
+ async function turnkeyRequest(path, body, creds) {
351
+ const bodyStr = JSON.stringify(body);
352
+ const stamp = createApiStamp(bodyStr, creds.publicKeyHex, creds.privateKeyDer);
353
+ const res = await fetch(`${TURNKEY_API_BASE}${path}`, {
354
+ method: "POST",
355
+ headers: { "Content-Type": "application/json", "X-Stamp": stamp },
356
+ body: bodyStr
357
+ });
358
+ if (!res.ok) {
359
+ const text = await res.text().catch(() => "");
360
+ throw new Error(`Turnkey API error (${res.status}): ${text}`);
361
+ }
362
+ return res.json();
363
+ }
364
+ async function refreshTurnkeySession(publicKeyHex, privateKeyDer, organizationId, expirationSeconds = 900) {
365
+ const newKeyPair = generateP256KeyPair();
366
+ const body = JSON.stringify({
367
+ type: "ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2",
368
+ timestampMs: Date.now().toString(),
369
+ organizationId,
370
+ parameters: {
371
+ targetPublicKey: newKeyPair.uncompressedPublicKeyHex,
372
+ expirationSeconds: String(expirationSeconds)
373
+ }
374
+ });
375
+ const stampHeader = createApiStamp(body, publicKeyHex, privateKeyDer);
376
+ const res = await fetch(`${TURNKEY_API_BASE}/public/v1/submit/create_read_write_session`, {
377
+ method: "POST",
378
+ headers: { "Content-Type": "application/json", "X-Stamp": stampHeader },
379
+ body
380
+ });
381
+ if (!res.ok) {
382
+ const text = await res.text().catch(() => "");
383
+ throw new Error(`Turnkey session refresh failed (${res.status}): ${text}`);
384
+ }
385
+ const data = await res.json();
386
+ const session = data.activity?.result?.createReadWriteSessionResultV2?.session;
387
+ if (!session) throw new Error("Turnkey session refresh: no session in response");
388
+ const payload = decodeJwtPayload(session);
389
+ return { session, expiry: payload.exp };
390
+ }
391
+ async function listWalletAccounts(creds) {
392
+ const walletsData = await turnkeyRequest(
393
+ "/public/v1/query/list_wallets",
394
+ { organizationId: creds.organizationId },
395
+ creds
396
+ );
397
+ const allAccounts = [];
398
+ for (const wallet of walletsData.wallets ?? []) {
399
+ const accountsData = await turnkeyRequest(
400
+ "/public/v1/query/list_wallet_accounts",
401
+ { organizationId: creds.organizationId, walletId: wallet.walletId, paginationOptions: { limit: "100" } },
402
+ creds
403
+ );
404
+ for (const acct of accountsData.accounts ?? []) {
405
+ allAccounts.push(acct);
406
+ }
407
+ }
408
+ return allAccounts;
409
+ }
410
+ async function getEvmAddress(creds) {
411
+ const accounts = await listWalletAccounts(creds);
412
+ const evm = accounts.find((a) => a.addressFormat === "ADDRESS_FORMAT_ETHEREUM");
413
+ if (!evm) throw new Error("No EVM wallet found in Turnkey.");
414
+ return evm.address;
415
+ }
416
+ async function getSolanaAddress(creds) {
417
+ const accounts = await listWalletAccounts(creds);
418
+ const sol = accounts.find((a) => a.addressFormat === "ADDRESS_FORMAT_SOLANA");
419
+ if (!sol) throw new Error("No Solana wallet found in Turnkey.");
420
+ return sol.address;
421
+ }
422
+ var AUTH_PROXY_BASE, TURNKEY_API_BASE, TURNKEY_CONFIG_ID;
423
+ var init_turnkey = __esm({
424
+ "src/lib/turnkey.ts"() {
425
+ "use strict";
426
+ init_constants();
427
+ AUTH_PROXY_BASE = "https://authproxy.turnkey.com";
428
+ TURNKEY_API_BASE = "https://api.turnkey.com";
429
+ TURNKEY_CONFIG_ID = TURNKEY_AUTH_PROXY_CONFIG_ID;
430
+ }
431
+ });
432
+
433
+ // src/wallet/turnkey-wallet.ts
434
+ var TurnkeyWallet;
435
+ var init_turnkey_wallet = __esm({
436
+ "src/wallet/turnkey-wallet.ts"() {
437
+ "use strict";
438
+ init_turnkey();
439
+ TurnkeyWallet = class _TurnkeyWallet {
440
+ chain;
441
+ address;
442
+ creds;
443
+ constructor(creds, chain, address) {
444
+ this.creds = creds;
445
+ this.chain = chain;
446
+ this.address = address;
447
+ }
448
+ static async create(creds) {
449
+ const [evmAddr, solAddr] = await Promise.all([
450
+ getEvmAddress(creds),
451
+ getSolanaAddress(creds)
452
+ ]);
453
+ return {
454
+ evm: new _TurnkeyWallet(creds, "evm", evmAddr),
455
+ solana: new _TurnkeyWallet(creds, "solana", solAddr)
456
+ };
457
+ }
458
+ async signMessage(message) {
459
+ const payloadHex = Buffer.from(message, "utf-8").toString("hex");
460
+ const result = await turnkeyRequest(
461
+ "/public/v1/submit/sign_raw_payload",
462
+ {
463
+ type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
464
+ timestampMs: Date.now().toString(),
465
+ organizationId: this.creds.organizationId,
466
+ parameters: {
467
+ signWith: this.address,
468
+ payload: payloadHex,
469
+ encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
470
+ hashFunction: this.chain === "evm" ? "HASH_FUNCTION_KECCAK256" : "HASH_FUNCTION_NOT_APPLICABLE"
471
+ }
472
+ },
473
+ this.creds
474
+ );
475
+ const sigResult = result.activity?.result?.signRawPayloadResult;
476
+ if (!sigResult) throw new Error("Turnkey signMessage: no signature in response");
477
+ if (this.chain === "evm") {
478
+ return `0x${sigResult.r}${sigResult.s}`;
479
+ }
480
+ return `${sigResult.r}${sigResult.s}`;
481
+ }
482
+ async signTransaction(serializedTx) {
483
+ const txBytes = Buffer.from(serializedTx, "base64");
484
+ const unsignedHex = txBytes.toString("hex");
485
+ const txType = this.chain === "evm" ? "TRANSACTION_TYPE_ETHEREUM" : "TRANSACTION_TYPE_SOLANA";
486
+ const result = await turnkeyRequest(
487
+ "/public/v1/submit/sign_transaction",
488
+ {
489
+ type: "ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
490
+ timestampMs: Date.now().toString(),
491
+ organizationId: this.creds.organizationId,
492
+ parameters: {
493
+ signWith: this.address,
494
+ unsignedTransaction: unsignedHex,
495
+ type: txType
496
+ }
497
+ },
498
+ this.creds
499
+ );
500
+ const signedHex = result.activity?.result?.signTransactionResult?.signedTransaction;
501
+ if (!signedHex) throw new Error("Turnkey signTransaction: no signed tx in response");
502
+ return Buffer.from(signedHex, "hex").toString("base64");
503
+ }
504
+ };
505
+ }
506
+ });
507
+
508
+ // src/wallet/index.ts
509
+ var wallet_exports = {};
510
+ __export(wallet_exports, {
511
+ createWallet: () => createWallet,
512
+ createWalletWithAddresses: () => createWalletWithAddresses
513
+ });
514
+ function createWallet(config, mode) {
515
+ if (mode === "raw" && config.rawWallet) {
516
+ return new RawKeyWallet(config.rawWallet.key, config.rawWallet.chain);
517
+ }
518
+ if (mode === "turnkey" && config.turnkey) {
519
+ return new TurnkeyWallet(config.turnkey, "evm", "");
520
+ }
521
+ throw new Error(
522
+ "No wallet configured. Run:\n chainstream login # Create Turnkey wallet\n chainstream wallet set-raw # Use raw private key (dev)"
523
+ );
524
+ }
525
+ async function createWalletWithAddresses(config, mode) {
526
+ if (mode === "raw" && config.rawWallet) {
527
+ const w = new RawKeyWallet(config.rawWallet.key, config.rawWallet.chain);
528
+ return config.rawWallet.chain === "evm" ? { evm: w } : { solana: w };
529
+ }
530
+ if (mode === "turnkey" && config.turnkey) {
531
+ const wallets = await TurnkeyWallet.create(config.turnkey);
532
+ return { evm: wallets.evm, solana: wallets.solana };
533
+ }
534
+ throw new Error("No wallet configured.");
535
+ }
536
+ var init_wallet = __esm({
537
+ "src/wallet/index.ts"() {
538
+ "use strict";
539
+ init_raw_wallet();
540
+ init_turnkey_wallet();
541
+ }
542
+ });
543
+
544
+ // src/lib/x402.ts
545
+ var x402_exports = {};
546
+ __export(x402_exports, {
547
+ autoPurchaseOnDemand: () => autoPurchaseOnDemand,
548
+ getPricing: () => getPricing,
549
+ getX402Fetch: () => getX402Fetch
550
+ });
551
+ import { x402Client } from "@x402/core/client";
552
+ import { wrapFetchWithPayment } from "@x402/fetch";
553
+ import { ExactEvmScheme } from "@x402/evm/exact/client";
554
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
555
+ function getX402Fetch(config) {
556
+ if (_x402Fetch) return _x402Fetch;
557
+ const client = new x402Client();
558
+ if (config.rawWallet?.chain === "evm") {
559
+ const hex = config.rawWallet.key.startsWith("0x") ? config.rawWallet.key : `0x${config.rawWallet.key}`;
560
+ const account = privateKeyToAccount2(hex);
561
+ client.register("eip155:*", new ExactEvmScheme(account));
562
+ } else if (config.rawWallet?.chain === "solana") {
563
+ process.stderr.write("[chainstream] Auto-purchase only supports EVM wallets currently.\n");
564
+ }
565
+ _x402Fetch = wrapFetchWithPayment(fetch, client);
566
+ return _x402Fetch;
567
+ }
568
+ async function autoPurchaseOnDemand(config, plan = "nano") {
569
+ const x402Fetch = getX402Fetch(config);
570
+ process.stderr.write(`[chainstream] No active subscription. Auto-purchasing ${plan} plan...
571
+ `);
572
+ try {
573
+ const resp = await x402Fetch(`${config.baseUrl}/x402/purchase`, {
574
+ method: "POST",
575
+ headers: { "Content-Type": "application/json" },
576
+ body: JSON.stringify({ plan })
577
+ });
578
+ if (!resp.ok) {
579
+ const text = await resp.text().catch(() => "");
580
+ process.stderr.write(`[chainstream] Purchase failed (${resp.status}): ${text}
581
+ `);
582
+ return { success: false };
583
+ }
584
+ const result = await resp.json();
585
+ process.stderr.write(`[chainstream] Subscription activated: ${result.plan} (expires: ${result.expires_at})
586
+ `);
587
+ return {
588
+ success: true,
589
+ plan: result.plan,
590
+ expiresAt: result.expires_at
591
+ };
592
+ } catch (err) {
593
+ process.stderr.write(`[chainstream] Auto-purchase error: ${err instanceof Error ? err.message : err}
594
+ `);
595
+ return { success: false };
596
+ }
597
+ }
598
+ async function getPricing(baseUrl) {
599
+ const resp = await fetch(`${baseUrl}/x402/pricing`);
600
+ if (!resp.ok) throw new Error(`Failed to get pricing (${resp.status})`);
601
+ return resp.json();
602
+ }
603
+ var _x402Fetch;
604
+ var init_x402 = __esm({
605
+ "src/lib/x402.ts"() {
606
+ "use strict";
607
+ _x402Fetch = null;
608
+ }
609
+ });
610
+
611
+ // src/index.ts
612
+ import { Command } from "commander";
613
+
614
+ // src/lib/client.ts
615
+ init_config();
616
+ init_wallet();
617
+ init_x402();
618
+ import { ChainStreamClient } from "@chainstream-io/sdk";
619
+ var _client = null;
620
+ var _wallet = null;
621
+ function createClient() {
622
+ if (_client) return _client;
623
+ const config = loadConfig();
624
+ const walletMode = getWalletMode(config);
625
+ if (walletMode) {
626
+ _wallet = createWallet(config, walletMode);
627
+ _client = new ChainStreamClient("", {
628
+ serverUrl: config.baseUrl,
629
+ walletSigner: _wallet
630
+ });
631
+ return _client;
632
+ }
633
+ if (config.apiKey) {
634
+ _client = new ChainStreamClient("", {
635
+ serverUrl: config.baseUrl,
636
+ apiKey: config.apiKey
637
+ });
638
+ return _client;
639
+ }
640
+ throw new Error(
641
+ "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"
642
+ );
643
+ }
644
+ function requireWallet() {
645
+ const config = loadConfig();
646
+ const walletMode = getWalletMode(config);
647
+ if (!walletMode) {
648
+ throw new Error(
649
+ "Wallet required for this operation. Run:\n chainstream login # Create Turnkey wallet\n chainstream wallet set-raw # Dev: use raw private key"
650
+ );
651
+ }
652
+ return createClient();
653
+ }
654
+ async function callWithAutoPayment(fn, retried = false) {
655
+ try {
656
+ return await fn();
657
+ } catch (err) {
658
+ const is402 = isPaymentRequired(err);
659
+ if (is402 && !retried && _wallet) {
660
+ const config = loadConfig();
661
+ const result = await autoPurchaseOnDemand(config);
662
+ if (result.success) {
663
+ _client = null;
664
+ createClient();
665
+ return callWithAutoPayment(fn, true);
666
+ }
667
+ }
668
+ throw err;
669
+ }
670
+ }
671
+ function isPaymentRequired(err) {
672
+ if (!err || typeof err !== "object") return false;
673
+ const axiosErr = err;
674
+ if (axiosErr.response?.status === 402) return true;
675
+ if (axiosErr.status === 402) return true;
676
+ if (axiosErr.message?.includes("402")) return true;
677
+ if (axiosErr.message?.includes("PAYMENT_REQUIRED")) return true;
678
+ return false;
679
+ }
680
+
681
+ // src/lib/validate.ts
682
+ var VALID_CHAINS = /* @__PURE__ */ new Set(["sol", "bsc", "eth"]);
683
+ var SOL_ADDRESS_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
684
+ var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
685
+ var CURRENCY_MAP = {
686
+ sol: {
687
+ SOL: "So11111111111111111111111111111111111111112",
688
+ USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
689
+ },
690
+ bsc: {
691
+ BNB: "0x0000000000000000000000000000000000000000",
692
+ USDC: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d"
693
+ },
694
+ eth: {
695
+ ETH: "0x0000000000000000000000000000000000000000",
696
+ USDC: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eB48"
697
+ }
698
+ };
699
+ function validateChain(chain) {
700
+ if (!VALID_CHAINS.has(chain)) {
701
+ throw new Error(`Invalid chain "${chain}". Must be one of: ${[...VALID_CHAINS].join(", ")}`);
702
+ }
703
+ }
704
+ function validateAddress(address, chain) {
705
+ const isEvm = chain === "bsc" || chain === "eth";
706
+ const valid = isEvm ? EVM_ADDRESS_RE.test(address) : SOL_ADDRESS_RE.test(address);
707
+ if (!valid) {
708
+ throw new Error(`Invalid address for chain "${chain}": "${address}"`);
709
+ }
710
+ }
711
+ function validateSlippage(value) {
712
+ if (value < 1e-3 || value > 0.5) {
713
+ throw new Error(`Invalid slippage: ${value}. Must be between 0.001 (0.1%) and 0.5 (50%).`);
714
+ }
715
+ }
716
+ function resolveCurrency(nameOrAddress, chain) {
717
+ const upper = nameOrAddress.toUpperCase();
718
+ const resolved = CURRENCY_MAP[chain]?.[upper];
719
+ if (resolved) return resolved;
720
+ return nameOrAddress;
721
+ }
722
+
723
+ // src/lib/output.ts
724
+ var EXPLORERS = {
725
+ sol: "https://solscan.io/tx/",
726
+ bsc: "https://bscscan.com/tx/",
727
+ eth: "https://etherscan.io/tx/"
728
+ };
729
+ function printResult(data, raw) {
730
+ if (raw) {
731
+ process.stdout.write(JSON.stringify(data) + "\n");
732
+ } else {
733
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
734
+ }
735
+ }
736
+ function printError(message) {
737
+ process.stderr.write(`[chainstream] Error: ${message}
738
+ `);
739
+ }
740
+ function exitOnError(err) {
741
+ const message = err instanceof Error ? err.message : String(err);
742
+ printError(message);
743
+ if (process.env.CHAINSTREAM_DEBUG && err instanceof Error && err.stack) {
744
+ process.stderr.write(err.stack + "\n");
745
+ }
746
+ process.exit(1);
747
+ }
748
+ function explorerUrl(chain, txHash) {
749
+ const base = EXPLORERS[chain] ?? EXPLORERS["eth"];
750
+ return `${base}${txHash}`;
751
+ }
752
+
753
+ // src/commands/token.ts
754
+ function registerTokenCommands(program2) {
755
+ const token = program2.command("token").description("Token information and analytics");
756
+ token.command("search").description("Search tokens by keyword").requiredOption("--keyword <keyword>", "Search keyword (name, symbol, or address)").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").option("--limit <n>", "Max results", "20").option("--raw", "Single-line JSON output").action(async (opts) => {
757
+ try {
758
+ validateChain(opts.chain);
759
+ const client = createClient();
760
+ const result = await callWithAutoPayment(
761
+ () => client.token.search({ q: opts.keyword, chains: [opts.chain], limit: Number(opts.limit) })
762
+ );
763
+ printResult(result, opts.raw);
764
+ } catch (err) {
765
+ exitOnError(err);
766
+ }
767
+ });
768
+ token.command("info").description("Get full token detail").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Token contract address").option("--raw", "Single-line JSON output").action(async (opts) => {
769
+ try {
770
+ validateChain(opts.chain);
771
+ validateAddress(opts.address, opts.chain);
772
+ const client = createClient();
773
+ const result = await callWithAutoPayment(
774
+ () => client.token.getToken(opts.chain, opts.address)
775
+ );
776
+ printResult(result, opts.raw);
777
+ } catch (err) {
778
+ exitOnError(err);
779
+ }
780
+ });
781
+ token.command("security").description("Check token security (honeypot, mint auth, freeze auth)").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Token contract address").option("--raw", "Single-line JSON output").action(async (opts) => {
782
+ try {
783
+ validateChain(opts.chain);
784
+ validateAddress(opts.address, opts.chain);
785
+ const client = createClient();
786
+ const result = await callWithAutoPayment(
787
+ () => client.token.getSecurity(opts.chain, opts.address)
788
+ );
789
+ printResult(result, opts.raw);
790
+ } catch (err) {
791
+ exitOnError(err);
792
+ }
793
+ });
794
+ token.command("holders").description("Get top token holders").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Token contract address").option("--limit <n>", "Max results", "20").option("--raw", "Single-line JSON output").action(async (opts) => {
795
+ try {
796
+ validateChain(opts.chain);
797
+ validateAddress(opts.address, opts.chain);
798
+ const client = createClient();
799
+ const result = await callWithAutoPayment(
800
+ () => client.token.getTopHolders(opts.chain, opts.address, { limit: Number(opts.limit) })
801
+ );
802
+ printResult(result, opts.raw);
803
+ } catch (err) {
804
+ exitOnError(err);
805
+ }
806
+ });
807
+ token.command("candles").description("Get OHLCV candlestick data").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Token contract address").requiredOption("--resolution <res>", "Resolution: 1m/5m/15m/1h/4h/1d").option("--from <timestamp>", "Start time (Unix seconds)").option("--to <timestamp>", "End time (Unix seconds)").option("--limit <n>", "Max candles", "100").option("--raw", "Single-line JSON output").action(async (opts) => {
808
+ try {
809
+ validateChain(opts.chain);
810
+ validateAddress(opts.address, opts.chain);
811
+ const client = createClient();
812
+ const params = {
813
+ resolution: opts.resolution,
814
+ limit: Number(opts.limit)
815
+ };
816
+ if (opts.from) params.from = Number(opts.from);
817
+ if (opts.to) params.to = Number(opts.to);
818
+ const result = await callWithAutoPayment(
819
+ () => client.token.getCandles(opts.chain, opts.address, params)
820
+ );
821
+ printResult(result, opts.raw);
822
+ } catch (err) {
823
+ exitOnError(err);
824
+ }
825
+ });
826
+ token.command("pools").description("Get liquidity pools for a token").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Token contract address").option("--raw", "Single-line JSON output").action(async (opts) => {
827
+ try {
828
+ validateChain(opts.chain);
829
+ validateAddress(opts.address, opts.chain);
830
+ const client = createClient();
831
+ const result = await callWithAutoPayment(
832
+ () => client.token.getPools(opts.chain, opts.address)
833
+ );
834
+ printResult(result, opts.raw);
835
+ } catch (err) {
836
+ exitOnError(err);
837
+ }
838
+ });
839
+ }
840
+
841
+ // src/commands/market.ts
842
+ function registerMarketCommands(program2) {
843
+ const market = program2.command("market").description("Market data and trending tokens");
844
+ market.command("trending").description("Get hot/trending tokens").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--duration <dur>", "Duration: 1h/6h/24h").option("--limit <n>", "Max results").option("--raw", "Single-line JSON output").action(async (opts) => {
845
+ try {
846
+ validateChain(opts.chain);
847
+ const client = createClient();
848
+ const params = {};
849
+ if (opts.limit) params.limit = Number(opts.limit);
850
+ const result = await callWithAutoPayment(
851
+ () => client.ranking.getHotTokens(opts.chain, opts.duration, params)
852
+ );
853
+ printResult(result, opts.raw);
854
+ } catch (err) {
855
+ exitOnError(err);
856
+ }
857
+ });
858
+ market.command("new").description("Get newly created tokens").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").option("--limit <n>", "Max results").option("--raw", "Single-line JSON output").action(async (opts) => {
859
+ try {
860
+ validateChain(opts.chain);
861
+ const client = createClient();
862
+ const params = {};
863
+ if (opts.limit) params.limit = Number(opts.limit);
864
+ const result = await callWithAutoPayment(
865
+ () => client.ranking.getNewTokens(opts.chain, params)
866
+ );
867
+ printResult(result, opts.raw);
868
+ } catch (err) {
869
+ exitOnError(err);
870
+ }
871
+ });
872
+ market.command("trades").description("Get recent trades").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").option("--token <address>", "Filter by token address").option("--limit <n>", "Max results").option("--raw", "Single-line JSON output").action(async (opts) => {
873
+ try {
874
+ validateChain(opts.chain);
875
+ const client = createClient();
876
+ const params = {};
877
+ if (opts.token) params.tokenAddress = opts.token;
878
+ if (opts.limit) params.limit = Number(opts.limit);
879
+ const result = await callWithAutoPayment(
880
+ () => client.trade.getTrades(opts.chain, params)
881
+ );
882
+ printResult(result, opts.raw);
883
+ } catch (err) {
884
+ exitOnError(err);
885
+ }
886
+ });
887
+ }
888
+
889
+ // src/commands/wallet.ts
890
+ init_config();
891
+ import * as readline from "readline/promises";
892
+ function registerWalletCommands(program2) {
893
+ const wallet = program2.command("wallet").description("Wallet analytics and management");
894
+ 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) => {
895
+ try {
896
+ validateChain(opts.chain);
897
+ validateAddress(opts.address, opts.chain);
898
+ const client = createClient();
899
+ const [pnl, netWorth, balance] = await callWithAutoPayment(
900
+ () => Promise.all([
901
+ client.wallet.getPnl(opts.chain, opts.address),
902
+ client.wallet.getNetWorth(opts.chain, opts.address),
903
+ client.wallet.getTokensBalance(opts.chain, opts.address)
904
+ ])
905
+ );
906
+ printResult({ pnl, netWorth, balance }, opts.raw);
907
+ } catch (err) {
908
+ exitOnError(err);
909
+ }
910
+ });
911
+ wallet.command("pnl").description("Get wallet PnL details").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Wallet address").option("--raw", "Single-line JSON output").action(async (opts) => {
912
+ try {
913
+ validateChain(opts.chain);
914
+ validateAddress(opts.address, opts.chain);
915
+ const client = createClient();
916
+ const result = await callWithAutoPayment(
917
+ () => client.wallet.getPnl(opts.chain, opts.address)
918
+ );
919
+ printResult(result, opts.raw);
920
+ } catch (err) {
921
+ exitOnError(err);
922
+ }
923
+ });
924
+ wallet.command("holdings").description("Get wallet token balances").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Wallet address").option("--limit <n>", "Max results").option("--raw", "Single-line JSON output").action(async (opts) => {
925
+ try {
926
+ validateChain(opts.chain);
927
+ validateAddress(opts.address, opts.chain);
928
+ const client = createClient();
929
+ const params = {};
930
+ if (opts.limit) params.limit = Number(opts.limit);
931
+ const result = await callWithAutoPayment(
932
+ () => client.wallet.getTokensBalance(opts.chain, opts.address, params)
933
+ );
934
+ printResult(result, opts.raw);
935
+ } catch (err) {
936
+ exitOnError(err);
937
+ }
938
+ });
939
+ wallet.command("activity").description("Get wallet transfer history").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Wallet address").option("--limit <n>", "Max results").option("--raw", "Single-line JSON output").action(async (opts) => {
940
+ try {
941
+ validateChain(opts.chain);
942
+ validateAddress(opts.address, opts.chain);
943
+ const client = createClient();
944
+ const params = {};
945
+ if (opts.limit) params.limit = Number(opts.limit);
946
+ const result = await callWithAutoPayment(
947
+ () => client.wallet.getWalletTransfers(opts.chain, opts.address, params)
948
+ );
949
+ printResult(result, opts.raw);
950
+ } catch (err) {
951
+ exitOnError(err);
952
+ }
953
+ });
954
+ wallet.command("address").description("Show current wallet addresses").action(async () => {
955
+ try {
956
+ const config = loadConfig();
957
+ const walletMode = (await Promise.resolve().then(() => (init_config(), config_exports))).getWalletMode(config);
958
+ if (walletMode === "turnkey" && config.turnkey) {
959
+ const { createWalletWithAddresses: createWalletWithAddresses2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
960
+ const wallets = await createWalletWithAddresses2(config, "turnkey");
961
+ if (wallets.evm) process.stdout.write(`EVM: ${wallets.evm.address}
962
+ `);
963
+ if (wallets.solana) process.stdout.write(`Solana: ${wallets.solana.address}
964
+ `);
965
+ } else if (config.rawWallet) {
966
+ const { createWallet: createWallet2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
967
+ const w = createWallet2(config, "raw");
968
+ process.stdout.write(`${w.chain.toUpperCase()}: ${w.address}
969
+ `);
970
+ } else {
971
+ process.stdout.write("No wallet configured. Run: chainstream login\n");
972
+ }
973
+ } catch (err) {
974
+ exitOnError(err);
975
+ }
976
+ });
977
+ wallet.command("balance").description("Show current wallet balance").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").option("--raw", "Single-line JSON output").action(async (opts) => {
978
+ try {
979
+ validateChain(opts.chain);
980
+ const config = loadConfig();
981
+ let address;
982
+ if (config.rawWallet) {
983
+ const { createWallet: createWallet2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
984
+ address = createWallet2(config, "raw").address;
985
+ }
986
+ if (!address) throw new Error("No wallet configured. Run: chainstream login");
987
+ const client = createClient();
988
+ const result = await callWithAutoPayment(
989
+ () => client.wallet.getTokensBalance(opts.chain, address)
990
+ );
991
+ printResult(result, opts.raw);
992
+ } catch (err) {
993
+ exitOnError(err);
994
+ }
995
+ });
996
+ wallet.command("set-raw").description("Set raw private key (dev/testing only)").requiredOption("--chain <chain>", "Chain: evm/solana").action(async (opts) => {
997
+ try {
998
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
999
+ process.stdout.write("\u26A0 WARNING: Raw private key will be stored in plaintext.\n");
1000
+ process.stdout.write(" Use Turnkey (chainstream login) for production.\n\n");
1001
+ const key = await rl.question("Enter private key: ");
1002
+ rl.close();
1003
+ if (!key.trim()) throw new Error("Empty key provided.");
1004
+ updateConfig({
1005
+ rawWallet: { key: key.trim(), chain: opts.chain }
1006
+ });
1007
+ process.stdout.write(`Raw wallet set (${opts.chain}). Run 'chainstream wallet address' to verify.
1008
+ `);
1009
+ } catch (err) {
1010
+ exitOnError(err);
1011
+ }
1012
+ });
1013
+ wallet.command("pricing").description("Show available x402 quota plans").option("--raw", "Single-line JSON output").action(async (opts) => {
1014
+ try {
1015
+ const config = loadConfig();
1016
+ const { getPricing: getPricing2 } = await Promise.resolve().then(() => (init_x402(), x402_exports));
1017
+ const plans = await getPricing2(config.baseUrl);
1018
+ printResult(plans, opts.raw);
1019
+ } catch (err) {
1020
+ exitOnError(err);
1021
+ }
1022
+ });
1023
+ }
1024
+
1025
+ // src/commands/kyt.ts
1026
+ function registerKytCommands(program2) {
1027
+ const kyt = program2.command("kyt").description("KYT address risk assessment");
1028
+ kyt.command("risk").description("Assess address risk score").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--address <address>", "Address to assess").option("--raw", "Single-line JSON output").action(async (opts) => {
1029
+ try {
1030
+ validateChain(opts.chain);
1031
+ validateAddress(opts.address, opts.chain);
1032
+ const client = createClient();
1033
+ const result = await callWithAutoPayment(
1034
+ () => client.kyt.getAddressRisk(opts.address)
1035
+ );
1036
+ printResult(result, opts.raw);
1037
+ } catch (err) {
1038
+ exitOnError(err);
1039
+ }
1040
+ });
1041
+ }
1042
+
1043
+ // src/commands/dex.ts
1044
+ import * as readline2 from "readline/promises";
1045
+ function registerDexCommands(program2) {
1046
+ const dex = program2.command("dex").description("DEX swap, quote, and token creation");
1047
+ 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) => {
1048
+ try {
1049
+ validateChain(opts.chain);
1050
+ const inputToken = resolveCurrency(opts.inputToken, opts.chain);
1051
+ const outputToken = resolveCurrency(opts.outputToken, opts.chain);
1052
+ const client = createClient();
1053
+ const result = await callWithAutoPayment(
1054
+ () => client.dex.quote(opts.chain, {
1055
+ inputMint: inputToken,
1056
+ outputMint: outputToken,
1057
+ amount: opts.amount
1058
+ })
1059
+ );
1060
+ printResult(result, opts.raw);
1061
+ } catch (err) {
1062
+ exitOnError(err);
1063
+ }
1064
+ });
1065
+ dex.command("swap").description("[FINANCIAL] Execute token swap (irreversible)").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--from <address>", "Sender wallet address").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("--slippage <n>", "Slippage tolerance (e.g. 0.01 = 1%)", "0.01").option("--raw", "Single-line JSON output").option("--yes", "Skip confirmation prompt").action(async (opts) => {
1066
+ try {
1067
+ validateChain(opts.chain);
1068
+ validateAddress(opts.from, opts.chain);
1069
+ if (opts.slippage) validateSlippage(Number(opts.slippage));
1070
+ const inputToken = resolveCurrency(opts.inputToken, opts.chain);
1071
+ const outputToken = resolveCurrency(opts.outputToken, opts.chain);
1072
+ const client = requireWallet();
1073
+ process.stderr.write("Fetching quote...\n");
1074
+ const quote = await callWithAutoPayment(
1075
+ () => client.dex.quote(opts.chain, {
1076
+ inputMint: inputToken,
1077
+ outputMint: outputToken,
1078
+ amount: opts.amount
1079
+ })
1080
+ );
1081
+ process.stderr.write("\n--- Swap Summary ---\n");
1082
+ process.stderr.write(`Chain: ${opts.chain}
1083
+ `);
1084
+ process.stderr.write(`Input: ${inputToken}
1085
+ `);
1086
+ process.stderr.write(`Output: ${outputToken}
1087
+ `);
1088
+ process.stderr.write(`Amount: ${opts.amount}
1089
+ `);
1090
+ process.stderr.write(`Slippage: ${opts.slippage}
1091
+ `);
1092
+ process.stderr.write(`Quote: ${JSON.stringify(quote)}
1093
+ `);
1094
+ process.stderr.write("--------------------\n\n");
1095
+ if (!opts.yes) {
1096
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
1097
+ const answer = await rl.question("Confirm swap? (y/N): ");
1098
+ rl.close();
1099
+ if (answer.toLowerCase() !== "y") {
1100
+ process.stderr.write("Swap cancelled.\n");
1101
+ return;
1102
+ }
1103
+ }
1104
+ process.stderr.write("Building transaction...\n");
1105
+ const swapResult = await callWithAutoPayment(
1106
+ () => client.dex.swap(opts.chain, {
1107
+ userAddress: opts.from,
1108
+ inputMint: inputToken,
1109
+ outputMint: outputToken,
1110
+ amount: opts.amount,
1111
+ slippage: Number(opts.slippage)
1112
+ })
1113
+ );
1114
+ const { createWallet: createWallet2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
1115
+ const { loadConfig: loadConfig2, getWalletMode: getWalletMode2 } = await Promise.resolve().then(() => (init_config(), config_exports));
1116
+ const config = loadConfig2();
1117
+ const walletMode = getWalletMode2(config);
1118
+ if (!walletMode) throw new Error("Wallet required for swap.");
1119
+ const wallet = createWallet2(config, walletMode);
1120
+ process.stderr.write("Signing transaction...\n");
1121
+ const signedTx = await wallet.signTransaction(swapResult.serializedTx);
1122
+ process.stderr.write("Broadcasting transaction...\n");
1123
+ const sendResult = await client.transaction.send(opts.chain, {
1124
+ signedTx
1125
+ });
1126
+ if (sendResult.jobId) {
1127
+ process.stderr.write(`Job ${sendResult.jobId} submitted. Waiting for confirmation...
1128
+ `);
1129
+ const jobResult = await client.waitForJob(sendResult.jobId);
1130
+ const result = {
1131
+ ...jobResult,
1132
+ explorer: explorerUrl(opts.chain, (sendResult.signature ?? jobResult.hash) || "")
1133
+ };
1134
+ printResult(result, opts.raw);
1135
+ } else {
1136
+ const result = {
1137
+ ...sendResult,
1138
+ explorer: explorerUrl(opts.chain, sendResult.signature ?? "")
1139
+ };
1140
+ printResult(result, opts.raw);
1141
+ }
1142
+ } catch (err) {
1143
+ exitOnError(err);
1144
+ }
1145
+ });
1146
+ dex.command("create").description("[FINANCIAL] Create token on launchpad").requiredOption("--chain <chain>", "Chain: sol/bsc/eth").requiredOption("--name <name>", "Token name").requiredOption("--symbol <symbol>", "Token symbol").requiredOption("--uri <uri>", "Metadata URI (IPFS/HTTP)").option("--platform <platform>", "Launchpad: pumpfun/raydium").option("--raw", "Single-line JSON output").action(async (opts) => {
1147
+ try {
1148
+ validateChain(opts.chain);
1149
+ const client = requireWallet();
1150
+ const result = await callWithAutoPayment(
1151
+ () => client.dex.createToken(opts.chain, {
1152
+ name: opts.name,
1153
+ symbol: opts.symbol,
1154
+ uri: opts.uri,
1155
+ platform: opts.platform
1156
+ })
1157
+ );
1158
+ printResult(result, opts.raw);
1159
+ } catch (err) {
1160
+ exitOnError(err);
1161
+ }
1162
+ });
1163
+ }
1164
+
1165
+ // src/commands/job.ts
1166
+ function registerJobCommands(program2) {
1167
+ const job = program2.command("job").description("Job status polling");
1168
+ job.command("status").description("Check job status").requiredOption("--id <jobId>", "Job ID").option("--wait", "Wait for job completion via SSE").option("--timeout <ms>", "Wait timeout in milliseconds", "60000").option("--raw", "Single-line JSON output").action(async (opts) => {
1169
+ try {
1170
+ const client = createClient();
1171
+ if (opts.wait) {
1172
+ process.stderr.write(`Waiting for job ${opts.id}...
1173
+ `);
1174
+ const result = await callWithAutoPayment(
1175
+ () => client.waitForJob(opts.id, Number(opts.timeout))
1176
+ );
1177
+ printResult(result, opts.raw);
1178
+ } else {
1179
+ const result = await callWithAutoPayment(
1180
+ () => client.job.get(opts.id)
1181
+ );
1182
+ printResult(result, opts.raw);
1183
+ }
1184
+ } catch (err) {
1185
+ exitOnError(err);
1186
+ }
1187
+ });
1188
+ }
1189
+
1190
+ // src/commands/auth.ts
1191
+ init_config();
1192
+
1193
+ // src/lib/keystore.ts
1194
+ init_config();
1195
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync, unlinkSync } from "fs";
1196
+ import { join as join2 } from "path";
1197
+ var KEYS_DIR = join2(getConfigDir(), "keys");
1198
+ var DEFAULT_PROFILE = "default";
1199
+ function ensureKeysDir() {
1200
+ if (!existsSync2(KEYS_DIR)) {
1201
+ mkdirSync2(KEYS_DIR, { recursive: true, mode: 448 });
1202
+ }
1203
+ }
1204
+ function privatePath(profile) {
1205
+ return join2(KEYS_DIR, `${profile}.private`);
1206
+ }
1207
+ function publicPath(profile) {
1208
+ return join2(KEYS_DIR, `${profile}.public`);
1209
+ }
1210
+ function metaPath(profile) {
1211
+ return join2(KEYS_DIR, `${profile}.json`);
1212
+ }
1213
+ function hasKey(profile = DEFAULT_PROFILE) {
1214
+ return existsSync2(privatePath(profile)) && existsSync2(publicPath(profile));
1215
+ }
1216
+ function loadKey(profile = DEFAULT_PROFILE) {
1217
+ if (!hasKey(profile)) return void 0;
1218
+ try {
1219
+ const privateKeyDer = readFileSync2(privatePath(profile), "utf-8").trim();
1220
+ const publicKeyHex = readFileSync2(publicPath(profile), "utf-8").trim();
1221
+ let uncompressedPublicKeyHex = "";
1222
+ let organizationId;
1223
+ if (existsSync2(metaPath(profile))) {
1224
+ const meta = JSON.parse(readFileSync2(metaPath(profile), "utf-8"));
1225
+ uncompressedPublicKeyHex = meta.uncompressedPublicKeyHex ?? "";
1226
+ organizationId = meta.organizationId;
1227
+ }
1228
+ return { publicKeyHex, uncompressedPublicKeyHex, privateKeyDer, organizationId };
1229
+ } catch {
1230
+ return void 0;
1231
+ }
1232
+ }
1233
+ function saveKey(keyPair, profile = DEFAULT_PROFILE) {
1234
+ ensureKeysDir();
1235
+ writeFileSync2(privatePath(profile), keyPair.privateKeyDer, { mode: 384 });
1236
+ writeFileSync2(publicPath(profile), keyPair.publicKeyHex, { mode: 384 });
1237
+ writeFileSync2(
1238
+ metaPath(profile),
1239
+ JSON.stringify({
1240
+ uncompressedPublicKeyHex: keyPair.uncompressedPublicKeyHex,
1241
+ ...keyPair.organizationId ? { organizationId: keyPair.organizationId } : {}
1242
+ }, null, 2),
1243
+ { mode: 384 }
1244
+ );
1245
+ }
1246
+
1247
+ // src/commands/auth.ts
1248
+ init_turnkey();
1249
+ import * as readline3 from "readline/promises";
1250
+ function registerAuthCommands(program2) {
1251
+ program2.command("login").description("Authenticate via Email OTP (default) or P-256 key (--key)").argument("[email]", "Email address for OTP login").option("--key", "Use P-256 key login (no email required)").action(async (email, opts) => {
1252
+ try {
1253
+ if (opts.key) {
1254
+ await doKeyLogin();
1255
+ } else {
1256
+ await doEmailLogin(email);
1257
+ }
1258
+ } catch (err) {
1259
+ exitOnError(err);
1260
+ }
1261
+ });
1262
+ program2.command("verify").description("Verify email OTP (second step of login)").requiredOption("--otp-id <id>", "OTP ID from login step").requiredOption("--code <code>", "OTP code from email").requiredOption("--email <email>", "Email used in login step").action(async (opts) => {
1263
+ try {
1264
+ const configId = TURNKEY_CONFIG_ID;
1265
+ if (!configId) throw new Error("TURNKEY_AUTH_PROXY_CONFIG_ID not set.");
1266
+ process.stderr.write("Verifying OTP...\n");
1267
+ const result = await completeLogin(opts.otpId, opts.code, configId, opts.email);
1268
+ saveKey({
1269
+ publicKeyHex: result.keyPair.publicKeyHex,
1270
+ uncompressedPublicKeyHex: result.keyPair.uncompressedPublicKeyHex,
1271
+ privateKeyDer: result.keyPair.privateKeyDer,
1272
+ organizationId: result.organizationId
1273
+ });
1274
+ updateConfig({
1275
+ turnkey: {
1276
+ publicKeyHex: result.keyPair.publicKeyHex,
1277
+ privateKeyDer: result.keyPair.privateKeyDer,
1278
+ organizationId: result.organizationId,
1279
+ sessionToken: result.session,
1280
+ sessionExpiry: result.sessionExpiry
1281
+ }
1282
+ });
1283
+ if (result.isNewUser) {
1284
+ process.stdout.write("Welcome! Your ChainStream wallet has been created.\n");
1285
+ process.stdout.write("Run 'chainstream wallet address' to see your addresses.\n");
1286
+ } else {
1287
+ process.stdout.write("Logged in successfully.\n");
1288
+ }
1289
+ } catch (err) {
1290
+ exitOnError(err);
1291
+ }
1292
+ });
1293
+ program2.command("logout").description("Clear session (P-256 keys preserved)").action(() => {
1294
+ const config = loadConfig();
1295
+ if (config.turnkey) {
1296
+ updateConfig({ turnkey: void 0 });
1297
+ process.stdout.write("Logged out. P-256 keys preserved in ~/.config/chainstream/keys/\n");
1298
+ process.stdout.write("Run 'chainstream login --key' to re-authenticate.\n");
1299
+ } else {
1300
+ process.stdout.write("Not logged in via Turnkey.\n");
1301
+ }
1302
+ });
1303
+ }
1304
+ async function doKeyLogin() {
1305
+ const existingKey = loadKey();
1306
+ if (existingKey?.organizationId) {
1307
+ process.stderr.write("Refreshing session with existing P-256 key...\n");
1308
+ const { session, expiry } = await refreshTurnkeySession(
1309
+ existingKey.publicKeyHex,
1310
+ existingKey.privateKeyDer,
1311
+ existingKey.organizationId
1312
+ );
1313
+ updateConfig({
1314
+ turnkey: {
1315
+ publicKeyHex: existingKey.publicKeyHex,
1316
+ privateKeyDer: existingKey.privateKeyDer,
1317
+ organizationId: existingKey.organizationId,
1318
+ sessionToken: session,
1319
+ sessionExpiry: expiry
1320
+ }
1321
+ });
1322
+ process.stdout.write("Logged in (key-based session refresh).\n");
1323
+ return;
1324
+ }
1325
+ if (hasKey()) {
1326
+ process.stderr.write("Found P-256 key but no organizationId.\n");
1327
+ process.stderr.write("You need to complete initial login via email first.\n");
1328
+ process.stderr.write("Run: chainstream login <your-email>\n");
1329
+ return;
1330
+ }
1331
+ process.stderr.write("No existing key found.\n");
1332
+ process.stderr.write("First-time users must log in via email to create a wallet:\n");
1333
+ process.stderr.write(" chainstream login <your-email>\n\n");
1334
+ process.stderr.write("After initial setup, you can use 'chainstream login --key' for future logins.\n");
1335
+ }
1336
+ async function doEmailLogin(email) {
1337
+ const configId = TURNKEY_CONFIG_ID;
1338
+ if (!configId) throw new Error("TURNKEY_AUTH_PROXY_CONFIG_ID not set. Contact ChainStream support.");
1339
+ if (!email) {
1340
+ const rl2 = readline3.createInterface({ input: process.stdin, output: process.stderr });
1341
+ email = await rl2.question("Enter your email: ");
1342
+ rl2.close();
1343
+ if (!email?.trim()) throw new Error("Email required.");
1344
+ email = email.trim();
1345
+ }
1346
+ process.stderr.write(`Sending OTP to ${email}...
1347
+ `);
1348
+ const { otpId } = await otpInit(email, configId);
1349
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
1350
+ const code = await rl.question("Enter OTP code: ");
1351
+ rl.close();
1352
+ if (!code?.trim()) throw new Error("OTP code required.");
1353
+ process.stderr.write("Verifying...\n");
1354
+ const result = await completeLogin(otpId, code.trim(), configId, email);
1355
+ saveKey({
1356
+ publicKeyHex: result.keyPair.publicKeyHex,
1357
+ uncompressedPublicKeyHex: result.keyPair.uncompressedPublicKeyHex,
1358
+ privateKeyDer: result.keyPair.privateKeyDer,
1359
+ organizationId: result.organizationId
1360
+ });
1361
+ updateConfig({
1362
+ turnkey: {
1363
+ publicKeyHex: result.keyPair.publicKeyHex,
1364
+ privateKeyDer: result.keyPair.privateKeyDer,
1365
+ organizationId: result.organizationId,
1366
+ sessionToken: result.session,
1367
+ sessionExpiry: result.sessionExpiry
1368
+ }
1369
+ });
1370
+ if (result.isNewUser) {
1371
+ process.stdout.write("Welcome! Your ChainStream wallet has been created (EVM + Solana).\n");
1372
+ process.stdout.write("Run 'chainstream wallet address' to see your addresses.\n");
1373
+ } else {
1374
+ process.stdout.write("Logged in successfully.\n");
1375
+ }
1376
+ }
1377
+
1378
+ // src/commands/config-cmd.ts
1379
+ init_config();
1380
+ function registerConfigCommands(program2) {
1381
+ const config = program2.command("config").description("Configuration management");
1382
+ config.command("set").description("Set a configuration value").requiredOption("--key <key>", "Config key (apiKey, baseUrl)").requiredOption("--value <value>", "Config value").action((opts) => {
1383
+ try {
1384
+ const allowedKeys = ["apiKey", "baseUrl"];
1385
+ if (!allowedKeys.includes(opts.key)) {
1386
+ throw new Error(`Invalid key "${opts.key}". Allowed: ${allowedKeys.join(", ")}`);
1387
+ }
1388
+ updateConfig({ [opts.key]: opts.value });
1389
+ process.stdout.write(`Set ${opts.key} = ${opts.key === "apiKey" ? "***" : opts.value}
1390
+ `);
1391
+ } catch (err) {
1392
+ exitOnError(err);
1393
+ }
1394
+ });
1395
+ config.command("get").description("Show configuration").option("--key <key>", "Specific key to show").action((opts) => {
1396
+ try {
1397
+ const cfg = loadConfig();
1398
+ if (opts.key) {
1399
+ const value = cfg[opts.key];
1400
+ if (value === void 0) {
1401
+ process.stdout.write(`${opts.key}: (not set)
1402
+ `);
1403
+ } else if (opts.key === "apiKey" && typeof value === "string") {
1404
+ process.stdout.write(`${opts.key}: ${value.slice(0, 8)}...${value.slice(-4)}
1405
+ `);
1406
+ } else {
1407
+ process.stdout.write(`${opts.key}: ${JSON.stringify(value)}
1408
+ `);
1409
+ }
1410
+ } else {
1411
+ const display = {
1412
+ apiKey: cfg.apiKey ? `${cfg.apiKey.slice(0, 8)}...` : void 0,
1413
+ baseUrl: cfg.baseUrl,
1414
+ walletMode: getWalletMode(cfg),
1415
+ turnkey: cfg.turnkey ? { organizationId: cfg.turnkey.organizationId } : void 0,
1416
+ rawWallet: cfg.rawWallet ? { chain: cfg.rawWallet.chain } : void 0
1417
+ };
1418
+ process.stdout.write(JSON.stringify(display, null, 2) + "\n");
1419
+ }
1420
+ } catch (err) {
1421
+ exitOnError(err);
1422
+ }
1423
+ });
1424
+ config.command("auth").description("Show current authentication status").action(() => {
1425
+ try {
1426
+ const cfg = loadConfig();
1427
+ const mode = getWalletMode(cfg);
1428
+ if (mode === "turnkey") {
1429
+ process.stdout.write(`Auth: Turnkey (org: ${cfg.turnkey.organizationId})
1430
+ `);
1431
+ const expiry = new Date(cfg.turnkey.sessionExpiry * 1e3);
1432
+ const expired = Date.now() > cfg.turnkey.sessionExpiry * 1e3;
1433
+ process.stdout.write(`Session: ${expired ? "expired" : "active"} (expires: ${expiry.toISOString()})
1434
+ `);
1435
+ } else if (mode === "raw") {
1436
+ process.stdout.write(`Auth: Raw Key (${cfg.rawWallet.chain})
1437
+ `);
1438
+ } else if (cfg.apiKey) {
1439
+ process.stdout.write(`Auth: API Key (${cfg.apiKey.slice(0, 8)}...)
1440
+ `);
1441
+ } else {
1442
+ process.stdout.write("Auth: Not configured\n");
1443
+ process.stdout.write(" Run: chainstream login # Turnkey wallet\n");
1444
+ process.stdout.write(" Run: chainstream config set apiKey <key> # API key\n");
1445
+ }
1446
+ } catch (err) {
1447
+ exitOnError(err);
1448
+ }
1449
+ });
1450
+ }
1451
+
1452
+ // src/index.ts
1453
+ var program = new Command();
1454
+ program.name("chainstream").version("0.1.0").description("ChainStream CLI \u2014 on-chain data and DeFi execution for AI agents");
1455
+ registerTokenCommands(program);
1456
+ registerMarketCommands(program);
1457
+ registerWalletCommands(program);
1458
+ registerKytCommands(program);
1459
+ registerDexCommands(program);
1460
+ registerJobCommands(program);
1461
+ registerAuthCommands(program);
1462
+ registerConfigCommands(program);
1463
+ program.parseAsync().catch((err) => {
1464
+ process.stderr.write(`[chainstream] ${err.message}
1465
+ `);
1466
+ process.exit(1);
1467
+ });
1468
+ //# sourceMappingURL=index.js.map