crumb-alpha-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +38 -0
  3. package/dist/commands/balance.d.ts +2 -0
  4. package/dist/commands/balance.d.ts.map +1 -0
  5. package/dist/commands/balance.js +43 -0
  6. package/dist/commands/balance.js.map +1 -0
  7. package/dist/commands/deposit.d.ts +2 -0
  8. package/dist/commands/deposit.d.ts.map +1 -0
  9. package/dist/commands/deposit.js +52 -0
  10. package/dist/commands/deposit.js.map +1 -0
  11. package/dist/commands/history.d.ts +4 -0
  12. package/dist/commands/history.d.ts.map +1 -0
  13. package/dist/commands/history.js +10 -0
  14. package/dist/commands/history.js.map +1 -0
  15. package/dist/commands/init.d.ts +2 -0
  16. package/dist/commands/init.d.ts.map +1 -0
  17. package/dist/commands/init.js +76 -0
  18. package/dist/commands/init.js.map +1 -0
  19. package/dist/commands/pay.d.ts +4 -0
  20. package/dist/commands/pay.d.ts.map +1 -0
  21. package/dist/commands/pay.js +50 -0
  22. package/dist/commands/pay.js.map +1 -0
  23. package/dist/commands/serve.d.ts +5 -0
  24. package/dist/commands/serve.d.ts.map +1 -0
  25. package/dist/commands/serve.js +43 -0
  26. package/dist/commands/serve.js.map +1 -0
  27. package/dist/commands/wallet.d.ts +4 -0
  28. package/dist/commands/wallet.d.ts.map +1 -0
  29. package/dist/commands/wallet.js +69 -0
  30. package/dist/commands/wallet.js.map +1 -0
  31. package/dist/commands/withdraw.d.ts +4 -0
  32. package/dist/commands/withdraw.d.ts.map +1 -0
  33. package/dist/commands/withdraw.js +54 -0
  34. package/dist/commands/withdraw.js.map +1 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +69 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/utils/config.d.ts +11 -0
  40. package/dist/utils/config.d.ts.map +1 -0
  41. package/dist/utils/config.js +30 -0
  42. package/dist/utils/config.js.map +1 -0
  43. package/dist/utils/keystore.d.ts +5 -0
  44. package/dist/utils/keystore.d.ts.map +1 -0
  45. package/dist/utils/keystore.js +51 -0
  46. package/dist/utils/keystore.js.map +1 -0
  47. package/dist/utils/output.d.ts +8 -0
  48. package/dist/utils/output.d.ts.map +1 -0
  49. package/dist/utils/output.js +35 -0
  50. package/dist/utils/output.js.map +1 -0
  51. package/package.json +39 -0
  52. package/src/commands/balance.ts +48 -0
  53. package/src/commands/deposit.ts +57 -0
  54. package/src/commands/history.ts +11 -0
  55. package/src/commands/init.ts +86 -0
  56. package/src/commands/pay.ts +55 -0
  57. package/src/commands/serve.ts +58 -0
  58. package/src/commands/wallet.ts +81 -0
  59. package/src/commands/withdraw.ts +62 -0
  60. package/src/index.ts +83 -0
  61. package/src/utils/config.ts +41 -0
  62. package/src/utils/keystore.ts +65 -0
  63. package/src/utils/output.ts +40 -0
  64. package/test/config.test.ts +109 -0
  65. package/test/keystore.test.ts +118 -0
  66. package/tsconfig.json +8 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface CrumbConfig {
2
+ activeWallet?: string;
3
+ chain: string;
4
+ }
5
+ export declare function ensureCrumbDir(): void;
6
+ export declare function getCrumbDir(): string;
7
+ export declare function getConfigPath(): string;
8
+ export declare function readConfig(): CrumbConfig;
9
+ export declare function writeConfig(config: CrumbConfig): void;
10
+ export declare function configExists(): boolean;
11
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,cAAc,IAAI,IAAI,CAIrC;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,UAAU,IAAI,WAAW,CAKxC;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAGrD;AAED,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
@@ -0,0 +1,30 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ const CRUMB_DIR = join(homedir(), '.crumb');
5
+ const CONFIG_PATH = join(CRUMB_DIR, 'config.json');
6
+ export function ensureCrumbDir() {
7
+ if (!existsSync(CRUMB_DIR)) {
8
+ mkdirSync(CRUMB_DIR, { recursive: true });
9
+ }
10
+ }
11
+ export function getCrumbDir() {
12
+ return CRUMB_DIR;
13
+ }
14
+ export function getConfigPath() {
15
+ return CONFIG_PATH;
16
+ }
17
+ export function readConfig() {
18
+ if (!existsSync(CONFIG_PATH)) {
19
+ return { chain: 'arcTestnet' };
20
+ }
21
+ return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
22
+ }
23
+ export function writeConfig(config) {
24
+ ensureCrumbDir();
25
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
26
+ }
27
+ export function configExists() {
28
+ return existsSync(CONFIG_PATH);
29
+ }
30
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAA;AAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAA;AAOlD,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;IAChC,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAA;AACvD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAmB;IAC7C,cAAc,EAAE,CAAA;IAChB,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;AAC7D,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,UAAU,CAAC,WAAW,CAAC,CAAA;AAChC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function keystoreExists(): boolean;
2
+ export declare function encryptAndSave(privateKey: string, password: string): void;
3
+ export declare function loadPrivateKey(password: string): string;
4
+ export declare function getPassword(): string;
5
+ //# sourceMappingURL=keystore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keystore.d.ts","sourceRoot":"","sources":["../../src/utils/keystore.ts"],"names":[],"mappings":"AAWA,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAoBzE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmBvD;AAED,wBAAgB,WAAW,IAAI,MAAM,CAMpC"}
@@ -0,0 +1,51 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'node:crypto';
4
+ import { getCrumbDir, ensureCrumbDir } from './config.js';
5
+ const KEYSTORE_FILE = 'keystore.enc';
6
+ function getKeystorePath() {
7
+ return join(getCrumbDir(), KEYSTORE_FILE);
8
+ }
9
+ export function keystoreExists() {
10
+ return existsSync(getKeystorePath());
11
+ }
12
+ export function encryptAndSave(privateKey, password) {
13
+ ensureCrumbDir();
14
+ const salt = randomBytes(32);
15
+ const key = scryptSync(password, salt, 32);
16
+ const iv = randomBytes(16);
17
+ const cipher = createCipheriv('aes-256-gcm', key, iv);
18
+ let encrypted = cipher.update(privateKey, 'utf-8', 'hex');
19
+ encrypted += cipher.final('hex');
20
+ const authTag = cipher.getAuthTag();
21
+ const payload = {
22
+ salt: salt.toString('hex'),
23
+ iv: iv.toString('hex'),
24
+ authTag: authTag.toString('hex'),
25
+ encrypted,
26
+ };
27
+ writeFileSync(getKeystorePath(), JSON.stringify(payload, null, 2));
28
+ }
29
+ export function loadPrivateKey(password) {
30
+ const path = getKeystorePath();
31
+ if (!existsSync(path)) {
32
+ throw new Error('No keystore found. Run `crumb init` first.');
33
+ }
34
+ const payload = JSON.parse(readFileSync(path, 'utf-8'));
35
+ const salt = Buffer.from(payload.salt, 'hex');
36
+ const iv = Buffer.from(payload.iv, 'hex');
37
+ const authTag = Buffer.from(payload.authTag, 'hex');
38
+ const key = scryptSync(password, salt, 32);
39
+ const decipher = createDecipheriv('aes-256-gcm', key, iv);
40
+ decipher.setAuthTag(authTag);
41
+ let decrypted = decipher.update(payload.encrypted, 'hex', 'utf-8');
42
+ decrypted += decipher.final('utf-8');
43
+ return decrypted;
44
+ }
45
+ export function getPassword() {
46
+ const envPassword = process.env.CRUMB_KEYSTORE_PASSWORD;
47
+ if (envPassword)
48
+ return envPassword;
49
+ throw new Error('No password provided. Set CRUMB_KEYSTORE_PASSWORD env var or pass --password flag.');
50
+ }
51
+ //# sourceMappingURL=keystore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keystore.js","sourceRoot":"","sources":["../../src/utils/keystore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACvF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEzD,MAAM,aAAa,GAAG,cAAc,CAAA;AAEpC,SAAS,eAAe;IACtB,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,aAAa,CAAC,CAAA;AAC3C,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,UAAU,CAAC,eAAe,EAAE,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,QAAgB;IACjE,cAAc,EAAE,CAAA;IAEhB,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAA;IAC5B,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IAC1C,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAA;IAC1B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAA;IAErD,IAAI,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;IACzD,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAChC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAA;IAEnC,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC1B,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;QACtB,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QAChC,SAAS;KACV,CAAA;IAED,aAAa,CAAC,eAAe,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,IAAI,GAAG,eAAe,EAAE,CAAA;IAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;IAC/D,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC7C,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;IACzC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IACnD,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IAE1C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAA;IACzD,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IAE5B,IAAI,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;IAClE,SAAS,IAAI,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAEpC,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAA;IACvD,IAAI,WAAW;QAAE,OAAO,WAAW,CAAA;IACnC,MAAM,IAAI,KAAK,CACb,oFAAoF,CACrF,CAAA;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare function heading(text: string): void;
2
+ export declare function success(text: string): void;
3
+ export declare function error(text: string): void;
4
+ export declare function info(text: string): void;
5
+ export declare function keyValue(key: string, value: string): void;
6
+ export declare function box(lines: string[]): void;
7
+ export declare function truncateAddress(address: string): string;
8
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"AAEA,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI1C;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExC;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEvC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAUzC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGvD"}
@@ -0,0 +1,35 @@
1
+ import chalk from 'chalk';
2
+ export function heading(text) {
3
+ console.log();
4
+ console.log(chalk.bold.cyan(` ◆ ${text}`));
5
+ console.log();
6
+ }
7
+ export function success(text) {
8
+ console.log(chalk.green(` ✔ ${text}`));
9
+ }
10
+ export function error(text) {
11
+ console.log(chalk.red(` ✖ ${text}`));
12
+ }
13
+ export function info(text) {
14
+ console.log(chalk.gray(` ${text}`));
15
+ }
16
+ export function keyValue(key, value) {
17
+ console.log(` ${chalk.dim(key.padEnd(12))} ${value}`);
18
+ }
19
+ export function box(lines) {
20
+ const maxLen = Math.max(...lines.map((l) => l.length));
21
+ const border = '─'.repeat(maxLen + 4);
22
+ console.log();
23
+ console.log(` ┌${border}┐`);
24
+ for (const line of lines) {
25
+ console.log(` │ ${line.padEnd(maxLen + 2)}│`);
26
+ }
27
+ console.log(` └${border}┘`);
28
+ console.log();
29
+ }
30
+ export function truncateAddress(address) {
31
+ if (address.length <= 12)
32
+ return address;
33
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
34
+ }
35
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAA;IAC3C,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,IAAY;IAChC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAA;AACvC,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,IAAY;IAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,KAAa;IACjD,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAA;AACxD,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,KAAe;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;IACtD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACrC,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,MAAM,MAAM,GAAG,CAAC,CAAA;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;IACjD,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,MAAM,MAAM,GAAG,CAAC,CAAA;IAC5B,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE;QAAE,OAAO,OAAO,CAAA;IACxC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACxD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "crumb-alpha-cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "crumb": "./dist/index.js"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "dependencies": {
12
+ "commander": "^13.1.0",
13
+ "inquirer": "^12.3.0",
14
+ "chalk": "^5.4.0",
15
+ "ora": "^8.2.0",
16
+ "viem": "^2.23.0",
17
+ "@circle-fin/x402-batching": "^2.0.4",
18
+ "crumb-alpha-core": "0.1.0",
19
+ "crumb-alpha-sdk": "0.1.0"
20
+ },
21
+ "peerDependencies": {
22
+ "express": ">=4.0.0"
23
+ },
24
+ "peerDependenciesMeta": {
25
+ "express": {
26
+ "optional": true
27
+ }
28
+ },
29
+ "devDependencies": {
30
+ "@types/express": "^5.0.0",
31
+ "@types/inquirer": "^9.0.0"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "dev": "tsc --watch",
36
+ "clean": "rm -rf dist",
37
+ "test": "vitest run"
38
+ }
39
+ }
@@ -0,0 +1,48 @@
1
+ import { GatewayClient, walletFromPrivateKey } from 'crumb-alpha-core'
2
+ import type { SupportedChainName } from 'crumb-alpha-core'
3
+ import { readConfig } from '../utils/config.js'
4
+ import { loadPrivateKey, getPassword } from '../utils/keystore.js'
5
+ import { heading, error, box, truncateAddress, keyValue } from '../utils/output.js'
6
+
7
+ export async function balanceCommand(): Promise<void> {
8
+ const config = readConfig()
9
+
10
+ if (!config.activeWallet) {
11
+ heading('Balance')
12
+ error('No wallet configured. Run `crumb init` first.')
13
+ return
14
+ }
15
+
16
+ let password: string
17
+ try {
18
+ password = getPassword()
19
+ } catch {
20
+ error('No password provided. Set CRUMB_KEYSTORE_PASSWORD env var.')
21
+ return
22
+ }
23
+
24
+ let privateKey: string
25
+ try {
26
+ privateKey = loadPrivateKey(password)
27
+ } catch (err: any) {
28
+ error(`Failed to decrypt keystore: ${err.message}`)
29
+ return
30
+ }
31
+
32
+ const chain = (config.chain ?? 'arcTestnet') as SupportedChainName
33
+ const gateway = new GatewayClient({
34
+ chain,
35
+ privateKey: privateKey as `0x${string}`,
36
+ })
37
+
38
+ const balances = await gateway.getBalances()
39
+
40
+ box([
41
+ `Wallet ${truncateAddress(config.activeWallet)}`,
42
+ `Chain ${chain}`,
43
+ ``,
44
+ `Wallet ${balances.wallet.formatted} USDC`,
45
+ `Gateway ${balances.gateway.formattedAvailable} USDC available`,
46
+ ` ${balances.gateway.formattedTotal} USDC total`,
47
+ ])
48
+ }
@@ -0,0 +1,57 @@
1
+ import { GatewayClient } from 'crumb-alpha-core'
2
+ import type { SupportedChainName } from 'crumb-alpha-core'
3
+ import { readConfig } from '../utils/config.js'
4
+ import { loadPrivateKey, getPassword } from '../utils/keystore.js'
5
+ import { heading, success, error, info, keyValue } from '../utils/output.js'
6
+ import ora from 'ora'
7
+
8
+ export async function depositCommand(amount: string): Promise<void> {
9
+ heading('Deposit')
10
+
11
+ const config = readConfig()
12
+ if (!config.activeWallet) {
13
+ error('No wallet configured. Run `crumb init` first.')
14
+ return
15
+ }
16
+
17
+ let password: string
18
+ try {
19
+ password = getPassword()
20
+ } catch {
21
+ error('No password provided. Set CRUMB_KEYSTORE_PASSWORD env var.')
22
+ return
23
+ }
24
+
25
+ let privateKey: string
26
+ try {
27
+ privateKey = loadPrivateKey(password)
28
+ } catch (err: any) {
29
+ error(`Failed to decrypt keystore: ${err.message}`)
30
+ return
31
+ }
32
+
33
+ const chain = (config.chain ?? 'arcTestnet') as SupportedChainName
34
+ const gateway = new GatewayClient({
35
+ chain,
36
+ privateKey: privateKey as `0x${string}`,
37
+ })
38
+
39
+ info(`Depositing ${amount} USDC into Gateway...`)
40
+ const spinner = ora(' Processing deposit...').start()
41
+
42
+ try {
43
+ const result = await gateway.deposit(amount)
44
+ spinner.stop()
45
+
46
+ console.log()
47
+ success('Deposited')
48
+ keyValue('Amount:', `${result.formattedAmount} USDC`)
49
+ keyValue('Deposit Tx:', result.depositTxHash)
50
+ if (result.approvalTxHash) {
51
+ keyValue('Approval Tx:', result.approvalTxHash)
52
+ }
53
+ } catch (err: any) {
54
+ spinner.stop()
55
+ error(`Deposit failed: ${err.message}`)
56
+ }
57
+ }
@@ -0,0 +1,11 @@
1
+ import { heading, info } from '../utils/output.js'
2
+
3
+ export async function historyCommand(options: { limit?: number }): Promise<void> {
4
+ heading('Transaction History')
5
+
6
+ // TODO: Integrate with Arc indexer or local transaction log
7
+ // For now, this is a placeholder that will be implemented once
8
+ // we have access to a transaction indexing API
9
+ info('Transaction history is not yet available.')
10
+ info('This feature will be added in a future release.')
11
+ }
@@ -0,0 +1,86 @@
1
+ import { createWallet } from 'crumb-alpha-core'
2
+ import { configExists, writeConfig, getConfigPath } from '../utils/config.js'
3
+ import { encryptAndSave } from '../utils/keystore.js'
4
+ import { heading, success, info, keyValue } from '../utils/output.js'
5
+ import inquirer from 'inquirer'
6
+
7
+ export async function initCommand(): Promise<void> {
8
+ heading('Welcome to Crumb')
9
+
10
+ if (configExists()) {
11
+ info(`Config already exists at ${getConfigPath()}`)
12
+
13
+ const { overwrite } = await inquirer.prompt([
14
+ {
15
+ type: 'confirm',
16
+ name: 'overwrite',
17
+ message: 'Overwrite existing config?',
18
+ default: false,
19
+ },
20
+ ])
21
+
22
+ if (!overwrite) {
23
+ info('Aborted.')
24
+ return
25
+ }
26
+ }
27
+
28
+ info(`Creating config at ${getConfigPath()}`)
29
+ console.log()
30
+
31
+ const { chain } = await inquirer.prompt([
32
+ {
33
+ type: 'list',
34
+ name: 'chain',
35
+ message: 'Select chain:',
36
+ choices: [
37
+ { name: 'Arc Testnet', value: 'arcTestnet' },
38
+ ],
39
+ default: 'arcTestnet',
40
+ },
41
+ ])
42
+
43
+ const { generateWallet } = await inquirer.prompt([
44
+ {
45
+ type: 'confirm',
46
+ name: 'generateWallet',
47
+ message: 'Generate a new wallet?',
48
+ default: true,
49
+ },
50
+ ])
51
+
52
+ if (generateWallet) {
53
+ const wallet = createWallet()
54
+
55
+ const { password } = await inquirer.prompt([
56
+ {
57
+ type: 'password',
58
+ name: 'password',
59
+ message: 'Set a keystore password:',
60
+ mask: '*',
61
+ },
62
+ ])
63
+
64
+ encryptAndSave(wallet.privateKey, password)
65
+
66
+ writeConfig({
67
+ activeWallet: wallet.address,
68
+ chain,
69
+ })
70
+
71
+ console.log()
72
+ success('Wallet created')
73
+ keyValue('Address:', wallet.address)
74
+ keyValue('Chain:', chain)
75
+ keyValue('Saved to:', '~/.crumb/keystore.enc (AES-256-GCM, password-protected)')
76
+ console.log()
77
+ info('Next steps:')
78
+ info(' 1. Fund your wallet with testnet USDC')
79
+ info(' 2. Deposit into Gateway: npx crumb deposit <amount>')
80
+ info(' 3. Start paying: npx crumb pay <url>')
81
+ } else {
82
+ writeConfig({ chain })
83
+ success('Config created')
84
+ info('Run `npx crumb wallet create` to create a wallet later.')
85
+ }
86
+ }
@@ -0,0 +1,55 @@
1
+ import { GatewayClient } from 'crumb-alpha-core'
2
+ import type { SupportedChainName } from 'crumb-alpha-core'
3
+ import { readConfig } from '../utils/config.js'
4
+ import { loadPrivateKey, getPassword } from '../utils/keystore.js'
5
+ import { heading, success, error, info, keyValue } from '../utils/output.js'
6
+ import ora from 'ora'
7
+
8
+ export async function payCommand(url: string, options: { maxPayment?: string }): Promise<void> {
9
+ heading('Pay')
10
+
11
+ const config = readConfig()
12
+ if (!config.activeWallet) {
13
+ error('No wallet configured. Run `crumb init` first.')
14
+ return
15
+ }
16
+
17
+ let password: string
18
+ try {
19
+ password = getPassword()
20
+ } catch {
21
+ error('No password provided. Set CRUMB_KEYSTORE_PASSWORD env var.')
22
+ return
23
+ }
24
+
25
+ let privateKey: string
26
+ try {
27
+ privateKey = loadPrivateKey(password)
28
+ } catch (err: any) {
29
+ error(`Failed to decrypt keystore: ${err.message}`)
30
+ return
31
+ }
32
+
33
+ const chain = (config.chain ?? 'arcTestnet') as SupportedChainName
34
+ const gateway = new GatewayClient({
35
+ chain,
36
+ privateKey: privateKey as `0x${string}`,
37
+ })
38
+
39
+ info(`Paying for ${url}...`)
40
+ const spinner = ora(' Processing payment...').start()
41
+
42
+ try {
43
+ const result = await gateway.pay(url)
44
+ spinner.stop()
45
+
46
+ console.log()
47
+ success('Payment complete')
48
+ keyValue('Amount:', `${result.formattedAmount} USDC`)
49
+ keyValue('Tx:', result.transaction)
50
+ keyValue('Status:', result.status.toString())
51
+ } catch (err: any) {
52
+ spinner.stop()
53
+ error(`Payment failed: ${err.message}`)
54
+ }
55
+ }
@@ -0,0 +1,58 @@
1
+ import { readConfig } from '../utils/config.js'
2
+ import { heading, success, error, info, keyValue } from '../utils/output.js'
3
+
4
+ export async function serveCommand(
5
+ file: string,
6
+ options: { port?: string; price?: string },
7
+ ): Promise<void> {
8
+ heading('Crumb Provider')
9
+
10
+ const config = readConfig()
11
+ if (!config.activeWallet) {
12
+ error('No wallet configured. Run `crumb init` first.')
13
+ return
14
+ }
15
+
16
+ const port = parseInt(options.port ?? '3000', 10)
17
+ const price = options.price ?? '$0.01'
18
+
19
+ try {
20
+ const express = (await import('express')).default
21
+ const { createGatewayMiddleware } = await import('@circle-fin/x402-batching/server')
22
+
23
+ const handler = await import(file)
24
+ const userHandler = handler.default ?? handler
25
+
26
+ const app = express()
27
+ app.use(express.json())
28
+
29
+ const gateway = createGatewayMiddleware({
30
+ sellerAddress: config.activeWallet,
31
+ description: `Local provider — ${price} per call`,
32
+ })
33
+
34
+ app.all(
35
+ '*',
36
+ gateway.require(price),
37
+ (req: any, res: any, next: any) => {
38
+ if (req.payment) {
39
+ success(`Payment received — ${req.payment.amount} USDC from ${req.payment.payer} (tx: ${req.payment.transaction?.slice(0, 8)}...)`)
40
+ }
41
+ next()
42
+ },
43
+ userHandler,
44
+ )
45
+
46
+ app.listen(port, () => {
47
+ keyValue('Endpoint:', `http://localhost:${port}`)
48
+ keyValue('Price:', `${price} per call`)
49
+ keyValue('Wallet:', config.activeWallet!)
50
+ console.log()
51
+ info('Waiting for payments...')
52
+ console.log()
53
+ })
54
+ } catch (err: any) {
55
+ error(`Failed to start server: ${err.message}`)
56
+ info('Make sure express is installed: pnpm add express')
57
+ }
58
+ }
@@ -0,0 +1,81 @@
1
+ import { createWallet } from 'crumb-alpha-core'
2
+ import { readConfig, writeConfig } from '../utils/config.js'
3
+ import { encryptAndSave, keystoreExists } from '../utils/keystore.js'
4
+ import { heading, success, info, keyValue, error } from '../utils/output.js'
5
+ import inquirer from 'inquirer'
6
+
7
+ export async function walletCreateCommand(): Promise<void> {
8
+ heading('Create Wallet')
9
+
10
+ if (keystoreExists()) {
11
+ const { overwrite } = await inquirer.prompt([
12
+ {
13
+ type: 'confirm',
14
+ name: 'overwrite',
15
+ message: 'A wallet already exists. Overwrite?',
16
+ default: false,
17
+ },
18
+ ])
19
+ if (!overwrite) {
20
+ info('Aborted.')
21
+ return
22
+ }
23
+ }
24
+
25
+ const wallet = createWallet()
26
+
27
+ const { password } = await inquirer.prompt([
28
+ {
29
+ type: 'password',
30
+ name: 'password',
31
+ message: 'Set a keystore password:',
32
+ mask: '*',
33
+ },
34
+ ])
35
+
36
+ encryptAndSave(wallet.privateKey, password)
37
+
38
+ const config = readConfig()
39
+ config.activeWallet = wallet.address
40
+ writeConfig(config)
41
+
42
+ console.log()
43
+ success('Wallet created')
44
+ keyValue('Address:', wallet.address)
45
+ keyValue('Saved to:', '~/.crumb/keystore.enc')
46
+ }
47
+
48
+ export async function walletShowCommand(): Promise<void> {
49
+ heading('Wallet')
50
+
51
+ const config = readConfig()
52
+ if (!config.activeWallet) {
53
+ error('No wallet configured. Run `crumb wallet create` first.')
54
+ return
55
+ }
56
+
57
+ keyValue('Address:', config.activeWallet)
58
+ keyValue('Chain:', config.chain)
59
+ keyValue('Keystore:', '~/.crumb/keystore.enc')
60
+ }
61
+
62
+ export async function walletFundCommand(): Promise<void> {
63
+ heading('Fund Wallet')
64
+
65
+ const config = readConfig()
66
+ if (!config.activeWallet) {
67
+ error('No wallet configured. Run `crumb wallet create` first.')
68
+ return
69
+ }
70
+
71
+ info('To fund your wallet with testnet USDC:')
72
+ console.log()
73
+ keyValue('Address:', config.activeWallet)
74
+ keyValue('Chain:', config.chain)
75
+ console.log()
76
+ info('1. Get testnet USDC from a faucet for your chain')
77
+ info('2. Send USDC to your address above')
78
+ info('3. Run `npx crumb deposit <amount>` to move USDC into Gateway')
79
+ console.log()
80
+ info('Once deposited, run `npx crumb balance` to check your balance.')
81
+ }