@elytro/cli 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +83 -37
  2. package/dist/index.js +45 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,89 +1,135 @@
1
1
  # Elytro CLI
2
2
 
3
- A command-line interface for ERC-4337 smart account wallets. Built for power users and AI Agents managing smart accounts across multiple chains.
3
+ A command-line interface for ERC-4337 smart account wallets. Built for power users and AI agents managing smart accounts across multiple chains.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @elytro/cli
9
+ # or
10
+ bun add -g @elytro/cli
11
+ # or
12
+ pnpm add -g @elytro/cli
13
+ ```
4
14
 
5
15
  ## Quick Start
6
16
 
7
17
  ```bash
8
18
  # Initialize wallet (creates vault + EOA)
9
- bun dev init
19
+ elytro init
10
20
 
11
- # Create a smart account on Sepolia
12
- bun dev account create --chain 11155420 --email user@example.com --daily-limit 100
21
+ # Create a smart account on OP Sepolia
22
+ elytro account create --chain 11155420 --email user@example.com --daily-limit 100
13
23
 
14
24
  # Send a transaction
15
- bun dev tx send --tx "to:0xRecipient,value:0.1"
25
+ elytro tx send --tx "to:0xRecipient,value:0.1"
16
26
 
17
27
  # Check balance
18
- bun dev query balance
28
+ elytro query balance
19
29
  ```
20
30
 
21
31
  ## Key Features
22
32
 
23
33
  - **Multi-account management** — Create multiple smart accounts per chain with user-friendly aliases
24
- - **Zero-interaction security** — macOS: vault key stored in Keychain; non-macOS: injected via `ELYTRO_VAULT_SECRET`
34
+ - **Zero-interaction security** — macOS/Linux/Windows: vault key stored in system keychain via `@napi-rs/keyring`; fallback: inject via `ELYTRO_VAULT_SECRET`
25
35
  - **Flexible transaction building** — Single transfers, batch operations, contract calls via unified `--tx` syntax
26
36
  - **Transaction simulation** — Preview gas, paymaster sponsorship, and balance impact before sending
27
- - **Cross-chain support** — Manage accounts across Sepolia, OP Sepolia, Arbitrum, and custom networks
28
- - **Security intents** — Declare email/spending limits at account creation; deployed atomically on activation
37
+ - **Cross-chain support** — Manage accounts across Ethereum, Optimism, Arbitrum, Base, and testnets
38
+ - **SecurityHook (2FA)** — Install on-chain 2FA with email OTP and daily spending limits
39
+ - **Self-updating** — `elytro update` detects your package manager and upgrades in place
40
+
41
+ ## Supported Chains
42
+
43
+ | Chain | Chain ID |
44
+ | ---------------- | --------- |
45
+ | Ethereum | 1 |
46
+ | Optimism | 10 |
47
+ | Arbitrum One | 42161 |
48
+ | Base | 8453 |
49
+ | Sepolia | 11155111 |
50
+ | Optimism Sepolia | 11155420 |
51
+
52
+ Public RPC and bundler endpoints are used by default. Provide your own Alchemy/Pimlico keys for higher rate limits.
29
53
 
30
54
  ## Architecture
31
55
 
32
56
  | Component | Purpose |
33
57
  | ------------------ | ------------------------------------------------ |
34
- | **SecretProvider** | Vault key management (Keychain/env var) |
58
+ | **SecretProvider** | Vault key management (Keychain / env var) |
35
59
  | **KeyringService** | EOA encryption + decryption (AES-GCM) |
36
60
  | **AccountService** | Smart account lifecycle (CREATE2, multi-account) |
37
- | **SdkService** | @elytro/sdk wrapper (UserOp building) |
61
+ | **SdkService** | `@elytro/sdk` wrapper (UserOp building) |
38
62
  | **FileStore** | Persistent state (`~/.elytro/`) |
39
63
 
40
- See [docs/architecture.md](docs/architecture.md) for detailed data flow.
41
-
42
64
  ## Security Model
43
65
 
44
- - **No plaintext keys on disk** — vault key stored in macOS Keychain or injected at runtime
66
+ - **No plaintext keys on disk** — vault key stored in system keychain or injected at runtime
45
67
  - **AES-GCM encryption** — all private keys encrypted with vault key before storage
46
68
  - **Consume-once env var** — `ELYTRO_VAULT_SECRET` deleted from process after load
47
69
  - **Memory cleanup** — all key buffers zeroed after use
48
70
 
49
- See [docs/security.md](docs/security.md) for threat model.
50
-
51
71
  ## Configuration
52
72
 
53
- | Variable | Purpose | Required |
54
- | --------------------- | ----------------------------- | -------------- |
55
- | `ELYTRO_VAULT_SECRET` | Base64 vault key (non-macOS) | Yes, non-macOS |
56
- | `ELYTRO_ALCHEMY_KEY` | Alchemy RPC endpoint | For queries |
57
- | `ELYTRO_PIMLICO_KEY` | Bundler + paymaster | For tx send |
58
- | `ELYTRO_ENV` | `development` or `production` | Optional |
73
+ | Variable | Purpose | Required |
74
+ | --------------------- | ----------------------------------------- | -------------------- |
75
+ | `ELYTRO_VAULT_SECRET` | Base64 vault key (non-keychain platforms) | Yes, if no keychain |
76
+ | `ELYTRO_ALCHEMY_KEY` | Alchemy RPC endpoint | Optional (rate limit)|
77
+ | `ELYTRO_PIMLICO_KEY` | Bundler + paymaster | Optional (rate limit)|
78
+ | `ELYTRO_ENV` | `development` or `production` | Optional |
59
79
 
60
- Persist API keys: `bun dev config set alchemy-key <key>`
80
+ Persist API keys:
81
+ ```bash
82
+ elytro config set alchemy-key <key>
83
+ elytro config set pimlico-key <key>
84
+ ```
61
85
 
62
86
  ## Commands
63
87
 
64
88
  ```bash
65
89
  # Account Management
66
- bun dev account create --chain 11155420 [--alias name] [--email addr] [--daily-limit amount]
67
- bun dev account list [alias|address]
68
- bun dev account info [alias|address]
69
- bun dev account switch [alias|address]
70
- bun dev account activate [alias|address] # Deploy to chain
90
+ elytro account create --chain <chainId> [--alias name] [--email addr] [--daily-limit amount]
91
+ elytro account list [alias|address]
92
+ elytro account info [alias|address]
93
+ elytro account switch [alias|address]
94
+ elytro account activate [alias|address] # Deploy to chain
71
95
 
72
96
  # Transactions
73
- bun dev tx send --tx "to:0xAddr,value:0.1" [--tx ...]
74
- bun dev tx build --tx "to:0xAddr,data:0xab..."
75
- bun dev tx simulate --tx "to:0xAddr,value:0.1"
97
+ elytro tx send --tx "to:0xAddr,value:0.1" [--tx ...]
98
+ elytro tx build --tx "to:0xAddr,data:0xab..."
99
+ elytro tx simulate --tx "to:0xAddr,value:0.1"
76
100
 
77
101
  # Queries
78
- bun dev query balance [account] [--token erc20Addr]
79
- bun dev query tokens [account]
80
- bun dev query tx <hash>
81
- bun dev query chain
82
- bun dev query address <address>
102
+ elytro query balance [account] [--token erc20Addr]
103
+ elytro query tokens [account]
104
+ elytro query tx <hash>
105
+ elytro query chain
106
+ elytro query address <address>
107
+
108
+ # Security (2FA + spending limits)
109
+ elytro security status
110
+ elytro security 2fa install [--capability 1|2|3]
111
+ elytro security 2fa uninstall
112
+ elytro security 2fa uninstall --force # Start safety-delay countdown
113
+ elytro security 2fa uninstall --force --execute # Execute after delay
114
+ elytro security email bind <email>
115
+ elytro security email change <email>
116
+ elytro security spending-limit [amount] # View or set daily USD limit
117
+
118
+ # Updates
119
+ elytro update # Check and upgrade to latest
120
+ elytro update check # Check without installing
121
+
122
+ # Config
123
+ elytro config set <key> <value>
124
+ elytro config get <key>
125
+ elytro config list
83
126
  ```
84
127
 
85
128
  ## Development
86
129
 
87
130
  ```bash
88
-
131
+ bun install
132
+ bun dev <command> # Run from source
133
+ bun run build # Build to dist/
134
+ bun run test # Smoke tests
89
135
  ```
package/dist/index.js CHANGED
@@ -2058,15 +2058,17 @@ var SecurityHookService = class {
2058
2058
  if (result.errors && result.errors.length > 0) {
2059
2059
  const ext = result.errors[0].extensions;
2060
2060
  if (ext) {
2061
+ const challengeDetails = typeof ext.challenge === "object" && ext.challenge !== null ? ext.challenge : void 0;
2062
+ const getChallengeValue = (key) => ext[key] ?? (challengeDetails ? challengeDetails[key] : void 0);
2061
2063
  return {
2062
2064
  error: {
2063
2065
  code: ext.code,
2064
- challengeId: ext.challengeId,
2065
- currentSpendUsdCents: ext.currentSpendUsdCents,
2066
- dailyLimitUsdCents: ext.dailyLimitUsdCents,
2067
- maskedEmail: ext.maskedEmail,
2068
- otpExpiresAt: ext.otpExpiresAt,
2069
- projectedSpendUsdCents: ext.projectedSpendUsdCents,
2066
+ challengeId: getChallengeValue("challengeId"),
2067
+ currentSpendUsdCents: ext.currentSpendUsdCents ?? getChallengeValue("currentSpendUsdCents"),
2068
+ dailyLimitUsdCents: ext.dailyLimitUsdCents ?? getChallengeValue("dailyLimitUsdCents"),
2069
+ maskedEmail: ext.maskedEmail ?? getChallengeValue("maskedEmail"),
2070
+ otpExpiresAt: ext.otpExpiresAt ?? getChallengeValue("otpExpiresAt"),
2071
+ projectedSpendUsdCents: ext.projectedSpendUsdCents ?? getChallengeValue("projectedSpendUsdCents"),
2070
2072
  message: result.errors[0].message
2071
2073
  }
2072
2074
  };
@@ -2078,15 +2080,17 @@ var SecurityHookService = class {
2078
2080
  if (err instanceof GraphQLClientError && err.errors?.length) {
2079
2081
  const ext = err.errors[0].extensions;
2080
2082
  if (ext?.code) {
2083
+ const challengeDetails = typeof ext.challenge === "object" && ext.challenge !== null ? ext.challenge : void 0;
2084
+ const getChallengeValue = (key) => ext[key] ?? (challengeDetails ? challengeDetails[key] : void 0);
2081
2085
  return {
2082
2086
  error: {
2083
2087
  code: ext.code,
2084
- challengeId: ext.challengeId,
2085
- currentSpendUsdCents: ext.currentSpendUsdCents,
2086
- dailyLimitUsdCents: ext.dailyLimitUsdCents,
2087
- maskedEmail: ext.maskedEmail,
2088
- otpExpiresAt: ext.otpExpiresAt,
2089
- projectedSpendUsdCents: ext.projectedSpendUsdCents,
2088
+ challengeId: getChallengeValue("challengeId"),
2089
+ currentSpendUsdCents: ext.currentSpendUsdCents ?? getChallengeValue("currentSpendUsdCents"),
2090
+ dailyLimitUsdCents: ext.dailyLimitUsdCents ?? getChallengeValue("dailyLimitUsdCents"),
2091
+ maskedEmail: ext.maskedEmail ?? getChallengeValue("maskedEmail"),
2092
+ otpExpiresAt: ext.otpExpiresAt ?? getChallengeValue("otpExpiresAt"),
2093
+ projectedSpendUsdCents: ext.projectedSpendUsdCents ?? getChallengeValue("projectedSpendUsdCents"),
2090
2094
  message: err.errors[0].message
2091
2095
  }
2092
2096
  };
@@ -3227,10 +3231,38 @@ function registerTxCommand(program2, ctx) {
3227
3231
  spinner.stop();
3228
3232
  const errCode = hookResult.error.code;
3229
3233
  if (errCode === "OTP_REQUIRED" || errCode === "SPENDING_LIMIT_EXCEEDED") {
3234
+ if (!hookResult.error.challengeId) {
3235
+ const otpRequestSpinner = ora3("Requesting OTP challenge...").start();
3236
+ try {
3237
+ const otpChallenge = await hookService.requestSecurityOtp(
3238
+ accountInfo.address,
3239
+ accountInfo.chainId,
3240
+ ctx.sdk.entryPoint,
3241
+ userOp
3242
+ );
3243
+ hookResult.error.challengeId = otpChallenge.challengeId;
3244
+ hookResult.error.maskedEmail ??= otpChallenge.maskedEmail;
3245
+ hookResult.error.otpExpiresAt ??= otpChallenge.otpExpiresAt;
3246
+ otpRequestSpinner.stop();
3247
+ } catch (otpErr) {
3248
+ otpRequestSpinner.fail("Failed to request OTP challenge.");
3249
+ throw new TxError(
3250
+ ERR_SEND_FAILED2,
3251
+ `Unable to request OTP challenge: ${otpErr.message}`
3252
+ );
3253
+ }
3254
+ }
3255
+ if (!hookResult.error.challengeId) {
3256
+ throw new TxError(
3257
+ ERR_SEND_FAILED2,
3258
+ "OTP challenge ID was not provided by Elytro API. Please try again."
3259
+ );
3260
+ }
3230
3261
  console.error(JSON.stringify({
3231
3262
  challenge: errCode,
3232
3263
  message: hookResult.error.message ?? `Verification required (${errCode}).`,
3233
3264
  ...hookResult.error.maskedEmail ? { maskedEmail: hookResult.error.maskedEmail } : {},
3265
+ ...hookResult.error.otpExpiresAt ? { otpExpiresAt: hookResult.error.otpExpiresAt } : {},
3234
3266
  ...errCode === "SPENDING_LIMIT_EXCEEDED" && hookResult.error.projectedSpendUsdCents !== void 0 ? { projectedSpendUsd: (hookResult.error.projectedSpendUsdCents / 100).toFixed(2), dailyLimitUsd: ((hookResult.error.dailyLimitUsdCents ?? 0) / 100).toFixed(2) } : {}
3235
3267
  }, null, 2));
3236
3268
  const otpCode = await askInput("Enter the 6-digit OTP code:");
@@ -4398,7 +4430,7 @@ import { execSync } from "child_process";
4398
4430
  import { createRequire } from "module";
4399
4431
  function resolveVersion() {
4400
4432
  if (true) {
4401
- return "0.5.2";
4433
+ return "0.5.3";
4402
4434
  }
4403
4435
  try {
4404
4436
  const require2 = createRequire(import.meta.url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elytro/cli",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Elytro ERC-4337 Smart Account Wallet CLI",
5
5
  "type": "module",
6
6
  "bin": {