zoa-wallet 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +425 -79
- package/dist/index.mjs +224 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,13 +24,16 @@ yarn global add zoa-wallet
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
# Create a new wallet
|
|
27
|
-
zoa
|
|
27
|
+
zoa wallet create --password mypassword
|
|
28
28
|
|
|
29
29
|
# Check balances across all chains
|
|
30
|
-
zoa balance
|
|
30
|
+
zoa balance --password mypassword
|
|
31
|
+
|
|
32
|
+
# Send ETH on Base
|
|
33
|
+
zoa send --chain base --to 0x1234...abcd --amount 0.01 --password mypassword --yes
|
|
31
34
|
|
|
32
35
|
# Show receive addresses with QR codes
|
|
33
|
-
zoa receive
|
|
36
|
+
zoa receive --password mypassword
|
|
34
37
|
|
|
35
38
|
# View supported networks
|
|
36
39
|
zoa chains
|
|
@@ -39,133 +42,476 @@ zoa chains
|
|
|
39
42
|
zoa prices
|
|
40
43
|
```
|
|
41
44
|
|
|
42
|
-
##
|
|
45
|
+
## Command Reference
|
|
46
|
+
|
|
47
|
+
### `wallet` — Manage Wallets
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Create a new wallet
|
|
51
|
+
zoa wallet create [--label <name>] [--color <hex>] [--password <pw>] [--word-count 12|24]
|
|
52
|
+
|
|
53
|
+
# Import from recovery phrase
|
|
54
|
+
zoa wallet import [--label <name>] [--mnemonic "phrase ..."] [--password <pw>]
|
|
55
|
+
|
|
56
|
+
# List all wallets
|
|
57
|
+
zoa wallet list [--password <pw>]
|
|
58
|
+
|
|
59
|
+
# Switch active wallet
|
|
60
|
+
zoa wallet switch [id-or-label]
|
|
61
|
+
|
|
62
|
+
# Rename a wallet
|
|
63
|
+
zoa wallet rename <id> <new-label>
|
|
64
|
+
|
|
65
|
+
# Change wallet color
|
|
66
|
+
zoa wallet color <id> <hex>
|
|
67
|
+
|
|
68
|
+
# Remove a wallet
|
|
69
|
+
zoa wallet remove <id> [--yes]
|
|
70
|
+
|
|
71
|
+
# Show active wallet details
|
|
72
|
+
zoa wallet info [--password <pw>]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `balance` — Check Balances
|
|
43
76
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| `zoa balance` | Check balances across all supported networks |
|
|
48
|
-
| `zoa send` | Send tokens to an address |
|
|
49
|
-
| `zoa receive` | Show wallet addresses with QR codes |
|
|
50
|
-
| `zoa history` | View transaction history |
|
|
51
|
-
| `zoa export` | Export private keys or recovery phrase |
|
|
52
|
-
| `zoa chains` | List all supported blockchain networks |
|
|
53
|
-
| `zoa prices` | View live token prices |
|
|
54
|
-
| `zoa config` | Manage configuration (~/.zoa/config.json) |
|
|
55
|
-
| `zoa api` | Manage API keys for programmatic access |
|
|
77
|
+
```bash
|
|
78
|
+
zoa balance [--password <pw>] [--chain <chain>]
|
|
79
|
+
```
|
|
56
80
|
|
|
57
|
-
|
|
81
|
+
| Flag | Description |
|
|
82
|
+
|------|-------------|
|
|
83
|
+
| `--password <pw>` | Wallet password (skip prompt) |
|
|
84
|
+
| `--chain <chain>` | Filter to a specific chain (`base`, `ethereum`, `solana`, etc.) |
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
### `send` — Send Tokens
|
|
60
87
|
|
|
88
|
+
Interactive:
|
|
61
89
|
```bash
|
|
62
|
-
|
|
63
|
-
|
|
90
|
+
zoa send
|
|
91
|
+
```
|
|
64
92
|
|
|
65
|
-
|
|
66
|
-
|
|
93
|
+
One-liner:
|
|
94
|
+
```bash
|
|
95
|
+
zoa send --chain base --to 0x... --amount 0.01 --password pw --yes
|
|
96
|
+
```
|
|
67
97
|
|
|
68
|
-
|
|
69
|
-
|
|
98
|
+
| Flag | Description |
|
|
99
|
+
|------|-------------|
|
|
100
|
+
| `--password <pw>` | Wallet password |
|
|
101
|
+
| `--to <address>` | Recipient address |
|
|
102
|
+
| `--amount <amount>` | Amount to send |
|
|
103
|
+
| `--chain <chain>` | Network (`base`, `ethereum`, `arbitrum`, `optimism`, `bsc`, `solana`) |
|
|
104
|
+
| `--token <symbol>` | Token symbol (e.g. `USDT`, `USDC`). Defaults to native currency |
|
|
105
|
+
| `--max` | Send maximum available balance (auto-deducts gas for native transfers) |
|
|
106
|
+
| `--batch <transfers>` | Batch send: comma-separated `address:amount[:token[:chain]]` |
|
|
107
|
+
| `--yes` | Skip confirmation prompt |
|
|
70
108
|
|
|
71
|
-
|
|
72
|
-
|
|
109
|
+
### `receive` — Show Addresses & QR Codes
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
zoa receive [--password <pw>] [--chain <chain>]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `history` — Transaction History
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
zoa history [--password <pw>] [--chain <chain>] [--limit <n>]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `export` — Export Keys
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Export private key
|
|
125
|
+
zoa export --password pw --type key --chain evm
|
|
126
|
+
|
|
127
|
+
# Export recovery phrase
|
|
128
|
+
zoa export --password pw --type mnemonic
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| Flag | Description |
|
|
132
|
+
|------|-------------|
|
|
133
|
+
| `--password <pw>` | Wallet password |
|
|
134
|
+
| `--type <type>` | `key` or `mnemonic` |
|
|
135
|
+
| `--chain <chain>` | `evm` or `solana` (for `--type key`) |
|
|
136
|
+
|
|
137
|
+
### `chains` — List Supported Networks
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
zoa chains
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `prices` — Token Prices
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Default price table
|
|
147
|
+
zoa prices
|
|
148
|
+
|
|
149
|
+
# Search for a specific token
|
|
150
|
+
zoa prices search bitcoin
|
|
151
|
+
|
|
152
|
+
# Browse popular tokens interactively
|
|
153
|
+
zoa prices browse
|
|
73
154
|
|
|
74
|
-
#
|
|
75
|
-
zoa
|
|
155
|
+
# Specify tokens
|
|
156
|
+
zoa prices list --tokens "ethereum,solana,bitcoin"
|
|
76
157
|
```
|
|
77
158
|
|
|
78
|
-
###
|
|
159
|
+
### `api` — Manage API Keys
|
|
79
160
|
|
|
80
161
|
```bash
|
|
81
|
-
|
|
162
|
+
# Set API key
|
|
163
|
+
zoa api set <key>
|
|
164
|
+
|
|
165
|
+
# Show current key
|
|
166
|
+
zoa api show
|
|
167
|
+
|
|
168
|
+
# Remove key
|
|
169
|
+
zoa api remove
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### `config` — Configuration
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Show config
|
|
176
|
+
zoa config show
|
|
177
|
+
|
|
178
|
+
# Set a value
|
|
179
|
+
zoa config set <key> <value>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## One-Liner Reference
|
|
183
|
+
|
|
184
|
+
| Task | Command |
|
|
185
|
+
|------|---------|
|
|
186
|
+
| Send ETH on Base | `zoa send --chain base --to 0x... --amount 0.01 --password pw --yes` |
|
|
187
|
+
| Send USDC on Ethereum | `zoa send --chain ethereum --token USDC --to 0x... --amount 100 --password pw --yes` |
|
|
188
|
+
| Send max ETH | `zoa send --chain base --to 0x... --max --password pw --yes` |
|
|
189
|
+
| Send SOL on Solana | `zoa send --chain solana --to ABC... --amount 1.5 --password pw --yes` |
|
|
190
|
+
| Check balance (Base only) | `zoa balance --chain base --password pw` |
|
|
191
|
+
| Export EVM private key | `zoa export --type key --chain evm --password pw` |
|
|
192
|
+
| Get JSON balances | `zoa --json balance --password pw` |
|
|
193
|
+
| Get JSON prices | `zoa --json prices --tokens "ethereum,solana"` |
|
|
194
|
+
|
|
195
|
+
## Batch Send
|
|
196
|
+
|
|
197
|
+
Send to multiple recipients, tokens, and chains in a single command.
|
|
198
|
+
|
|
199
|
+
**Format:** `address:amount[:token[:chain]]`
|
|
200
|
+
- `token` defaults to the native currency (ETH, SOL, BNB, etc.)
|
|
201
|
+
- `chain` defaults to the `--chain` flag value, or `base` if not specified
|
|
202
|
+
|
|
203
|
+
### Examples
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Same chain, same token, multiple recipients
|
|
207
|
+
zoa send --chain base --batch "0xAAA...111:0.01,0xBBB...222:0.02,0xCCC...333:0.03" --password pw --yes
|
|
208
|
+
|
|
209
|
+
# Multiple tokens on the same chain
|
|
210
|
+
zoa send --chain ethereum --batch "0xAAA...111:100:USDC,0xBBB...222:50:USDT,0xCCC...333:0.1" --password pw --yes
|
|
211
|
+
|
|
212
|
+
# Multiple chains and tokens in one command
|
|
213
|
+
zoa send --batch "0xAAA...111:100:USDC:ethereum,4Jyn...abc:0.5:SOL:solana,0xBBB...222:50:USDT:base" --password pw --yes
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Batch Output
|
|
217
|
+
|
|
218
|
+
- Each transfer executes sequentially with progress indicators
|
|
219
|
+
- Failed transfers do not stop remaining transfers
|
|
220
|
+
- A summary is shown at the end with success/failure counts
|
|
221
|
+
- JSON mode returns an array of results
|
|
222
|
+
|
|
223
|
+
## JSON Mode for AI Agents
|
|
224
|
+
|
|
225
|
+
Every command supports `--json` for machine-readable output. Place the flag before the command name.
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
zoa --json <command> [options]
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Example Outputs
|
|
232
|
+
|
|
233
|
+
**Create wallet:**
|
|
234
|
+
```bash
|
|
235
|
+
zoa --json wallet create --password "securepass"
|
|
236
|
+
```
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"address": "0x1234...abcd",
|
|
240
|
+
"solanaAddress": "ABC...xyz",
|
|
241
|
+
"label": "Wallet 1"
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Get balances:**
|
|
246
|
+
```bash
|
|
247
|
+
zoa --json balance --password pw
|
|
248
|
+
```
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"balances": [
|
|
252
|
+
{ "chain": "Base", "token": "ETH", "balance": "0.0542", "usd": "170.21" },
|
|
253
|
+
{ "chain": "Base", "token": "USDC", "balance": "100.0", "usd": "100.00" }
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Send tokens:**
|
|
259
|
+
```bash
|
|
260
|
+
zoa --json send --chain base --to 0x... --amount 0.01 --password pw --yes
|
|
261
|
+
```
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"status": "sent",
|
|
265
|
+
"hash": "0xabc...def",
|
|
266
|
+
"from": "0x1234...abcd",
|
|
267
|
+
"to": "0x5678...efgh",
|
|
268
|
+
"token": "ETH",
|
|
269
|
+
"amount": "0.01",
|
|
270
|
+
"chain": "Base",
|
|
271
|
+
"explorerUrl": "https://basescan.org/tx/0xabc...def"
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Batch send:**
|
|
276
|
+
```bash
|
|
277
|
+
zoa --json send --batch "0xA:0.01,0xB:0.02" --chain base --password pw --yes
|
|
278
|
+
```
|
|
279
|
+
```json
|
|
280
|
+
{
|
|
281
|
+
"batch": true,
|
|
282
|
+
"results": [
|
|
283
|
+
{ "to": "0xA...", "amount": "0.01", "token": "ETH", "chain": "Base", "status": "sent", "hash": "0x..." },
|
|
284
|
+
{ "to": "0xB...", "amount": "0.02", "token": "ETH", "chain": "Base", "status": "sent", "hash": "0x..." }
|
|
285
|
+
],
|
|
286
|
+
"summary": { "total": 2, "succeeded": 2, "failed": 0 }
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Get chains:**
|
|
291
|
+
```bash
|
|
292
|
+
zoa --json chains
|
|
82
293
|
```
|
|
83
294
|
```json
|
|
84
295
|
{
|
|
85
296
|
"chains": [
|
|
86
297
|
{ "name": "Ethereum", "symbol": "ETH", "chainId": 1 },
|
|
87
298
|
{ "name": "Base", "symbol": "ETH", "chainId": 8453 },
|
|
88
|
-
{ "name": "Solana", "symbol": "SOL", "chainId": -1 }
|
|
89
|
-
|
|
299
|
+
{ "name": "Solana", "symbol": "SOL", "chainId": -1 }
|
|
300
|
+
]
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Get prices:**
|
|
305
|
+
```bash
|
|
306
|
+
zoa --json prices --tokens "ethereum,solana"
|
|
307
|
+
```
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"prices": [
|
|
311
|
+
{ "id": "ethereum", "symbol": "ETH", "price": 3245.67, "change24h": 2.34 },
|
|
312
|
+
{ "id": "solana", "symbol": "SOL", "price": 142.89, "change24h": -1.12 }
|
|
90
313
|
]
|
|
91
314
|
}
|
|
92
315
|
```
|
|
93
316
|
|
|
94
|
-
##
|
|
317
|
+
## API Reference
|
|
95
318
|
|
|
96
|
-
|
|
319
|
+
The ZOA backend API provides programmatic access. All `/v1` endpoints require an API key in the `Authorization` header.
|
|
320
|
+
|
|
321
|
+
### Authentication
|
|
97
322
|
|
|
98
323
|
```bash
|
|
99
|
-
#
|
|
100
|
-
zoa
|
|
324
|
+
# Set your API key
|
|
325
|
+
zoa api set zoa_sk_your_key_here
|
|
101
326
|
|
|
102
|
-
#
|
|
103
|
-
|
|
327
|
+
# Use with curl
|
|
328
|
+
curl -H "Authorization: Bearer zoa_sk_your_key_here" https://api.zoa.fun/v1/chains
|
|
329
|
+
```
|
|
104
330
|
|
|
105
|
-
|
|
106
|
-
zoa send --password "..." --to 0x... --amount 0.1 --chain base --yes
|
|
331
|
+
### Endpoints
|
|
107
332
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
333
|
+
#### `GET /v1/chains`
|
|
334
|
+
List all supported blockchain networks.
|
|
335
|
+
```bash
|
|
336
|
+
curl -H "Authorization: Bearer $API_KEY" https://api.zoa.fun/v1/chains
|
|
337
|
+
```
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"data": {
|
|
341
|
+
"chains": [
|
|
342
|
+
{ "id": 1, "name": "Ethereum", "symbol": "ETH", "type": "evm" },
|
|
343
|
+
{ "id": 8453, "name": "Base", "symbol": "ETH", "type": "evm" },
|
|
344
|
+
{ "id": 56, "name": "BNB Smart Chain", "symbol": "BNB", "type": "evm" },
|
|
345
|
+
{ "id": 42161, "name": "Arbitrum One", "symbol": "ETH", "type": "evm" },
|
|
346
|
+
{ "id": 10, "name": "Optimism", "symbol": "ETH", "type": "evm" },
|
|
347
|
+
{ "id": 0, "name": "Solana", "symbol": "SOL", "type": "non-evm" }
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
}
|
|
111
351
|
```
|
|
112
352
|
|
|
113
|
-
|
|
353
|
+
#### `POST /v1/wallets`
|
|
354
|
+
Create a managed wallet. Requires `write` permission.
|
|
355
|
+
```bash
|
|
356
|
+
curl -X POST -H "Authorization: Bearer $API_KEY" \
|
|
357
|
+
-H "Content-Type: application/json" \
|
|
358
|
+
-d '{"label": "Trading Bot"}' \
|
|
359
|
+
https://api.zoa.fun/v1/wallets
|
|
360
|
+
```
|
|
114
361
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
362
|
+
#### `GET /v1/wallets`
|
|
363
|
+
List all wallets associated with your API key.
|
|
364
|
+
```bash
|
|
365
|
+
curl -H "Authorization: Bearer $API_KEY" https://api.zoa.fun/v1/wallets
|
|
366
|
+
```
|
|
120
367
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
368
|
+
#### `GET /v1/wallets/{id}`
|
|
369
|
+
Get a specific wallet by UUID.
|
|
370
|
+
```bash
|
|
371
|
+
curl -H "Authorization: Bearer $API_KEY" https://api.zoa.fun/v1/wallets/{id}
|
|
372
|
+
```
|
|
125
373
|
|
|
126
|
-
|
|
374
|
+
#### `GET /v1/balances/{address}`
|
|
375
|
+
Get token balances for an address. Optionally filter by chain.
|
|
376
|
+
```bash
|
|
377
|
+
curl -H "Authorization: Bearer $API_KEY" "https://api.zoa.fun/v1/balances/0x1234...?chain=8453"
|
|
378
|
+
```
|
|
127
379
|
|
|
128
|
-
|
|
380
|
+
#### `GET /v1/balances/{address}/all`
|
|
381
|
+
Get balances across all supported chains.
|
|
382
|
+
```bash
|
|
383
|
+
curl -H "Authorization: Bearer $API_KEY" https://api.zoa.fun/v1/balances/0x1234.../all
|
|
384
|
+
```
|
|
129
385
|
|
|
386
|
+
#### `POST /v1/transfer`
|
|
387
|
+
Create a transfer. Requires `write` permission.
|
|
130
388
|
```bash
|
|
131
|
-
|
|
132
|
-
|
|
389
|
+
curl -X POST -H "Authorization: Bearer $API_KEY" \
|
|
390
|
+
-H "Content-Type: application/json" \
|
|
391
|
+
-d '{
|
|
392
|
+
"from_wallet_id": "uuid",
|
|
393
|
+
"to_address": "0x...",
|
|
394
|
+
"chain_id": 8453,
|
|
395
|
+
"amount": "0.01",
|
|
396
|
+
"token_address": null,
|
|
397
|
+
"priority": "standard"
|
|
398
|
+
}' \
|
|
399
|
+
https://api.zoa.fun/v1/transfer
|
|
400
|
+
```
|
|
133
401
|
|
|
134
|
-
|
|
135
|
-
|
|
402
|
+
#### `GET /v1/transactions/{address}`
|
|
403
|
+
Get transaction history for an address.
|
|
404
|
+
```bash
|
|
405
|
+
curl -H "Authorization: Bearer $API_KEY" "https://api.zoa.fun/v1/transactions/0x1234...?chain=8453&limit=10&offset=0"
|
|
406
|
+
```
|
|
136
407
|
|
|
137
|
-
|
|
138
|
-
|
|
408
|
+
#### `GET /v1/transactions/hash/{hash}`
|
|
409
|
+
Get a specific transaction by hash.
|
|
410
|
+
```bash
|
|
411
|
+
curl -H "Authorization: Bearer $API_KEY" "https://api.zoa.fun/v1/transactions/hash/0xabc...?chain=8453"
|
|
412
|
+
```
|
|
139
413
|
|
|
140
|
-
|
|
141
|
-
|
|
414
|
+
#### `GET /v1/prices`
|
|
415
|
+
Get cached token prices.
|
|
416
|
+
```bash
|
|
417
|
+
curl -H "Authorization: Bearer $API_KEY" "https://api.zoa.fun/v1/prices?tokens=ethereum,solana,bitcoin"
|
|
142
418
|
```
|
|
143
419
|
|
|
144
|
-
|
|
420
|
+
#### `GET /v1/prices/{token_id}`
|
|
421
|
+
Get price for a single token.
|
|
422
|
+
```bash
|
|
423
|
+
curl -H "Authorization: Bearer $API_KEY" https://api.zoa.fun/v1/prices/ethereum
|
|
424
|
+
```
|
|
145
425
|
|
|
146
|
-
|
|
426
|
+
#### `GET /v1/gas/{chain_id}`
|
|
427
|
+
Get gas estimates (slow/standard/fast) for a chain.
|
|
428
|
+
```bash
|
|
429
|
+
curl -H "Authorization: Bearer $API_KEY" https://api.zoa.fun/v1/gas/8453
|
|
430
|
+
```
|
|
431
|
+
```json
|
|
432
|
+
{
|
|
433
|
+
"data": {
|
|
434
|
+
"chain_id": 8453,
|
|
435
|
+
"slow": { "gwei": "0.001", "estimated_seconds": 30 },
|
|
436
|
+
"standard": { "gwei": "0.002", "estimated_seconds": 15 },
|
|
437
|
+
"fast": { "gwei": "0.005", "estimated_seconds": 5 }
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
```
|
|
147
441
|
|
|
148
|
-
|
|
149
|
-
import { ZoaWallet } from '@zoa/sdk';
|
|
442
|
+
#### Key Management
|
|
150
443
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
444
|
+
```bash
|
|
445
|
+
# Create API key (requires admin permission)
|
|
446
|
+
curl -X POST -H "Authorization: Bearer $ADMIN_KEY" \
|
|
447
|
+
-H "Content-Type: application/json" \
|
|
448
|
+
-d '{"name": "My Bot", "permissions": ["read", "write"], "rate_limit_per_min": 60}' \
|
|
449
|
+
https://api.zoa.fun/v1/keys
|
|
155
450
|
|
|
156
|
-
|
|
157
|
-
|
|
451
|
+
# List API keys
|
|
452
|
+
curl -H "Authorization: Bearer $ADMIN_KEY" https://api.zoa.fun/v1/keys
|
|
158
453
|
|
|
159
|
-
|
|
160
|
-
|
|
454
|
+
# Revoke an API key
|
|
455
|
+
curl -X DELETE -H "Authorization: Bearer $ADMIN_KEY" https://api.zoa.fun/v1/keys/{id}
|
|
161
456
|
```
|
|
162
457
|
|
|
458
|
+
## Supported Networks
|
|
459
|
+
|
|
460
|
+
| Network | Symbol | Chain ID | Type | Explorer |
|
|
461
|
+
|---------|--------|----------|------|----------|
|
|
462
|
+
| Ethereum | ETH | 1 | EVM | etherscan.io |
|
|
463
|
+
| Base | ETH | 8453 | EVM | basescan.org |
|
|
464
|
+
| BNB Smart Chain | BNB | 56 | EVM | bscscan.com |
|
|
465
|
+
| Arbitrum One | ETH | 42161 | EVM | arbiscan.io |
|
|
466
|
+
| Optimism | ETH | 10 | EVM | optimistic.etherscan.io |
|
|
467
|
+
| Solana | SOL | — | Non-EVM | solscan.io |
|
|
468
|
+
| Hyperliquid | HYPE | 999 | Non-EVM | — |
|
|
469
|
+
|
|
470
|
+
### Known ERC-20 Tokens
|
|
471
|
+
|
|
472
|
+
Automatically detected in balance checks and available in the send token picker:
|
|
473
|
+
|
|
474
|
+
| Token | Ethereum | Base | Arbitrum | Optimism | BSC |
|
|
475
|
+
|-------|----------|------|----------|----------|-----|
|
|
476
|
+
| USDT | Yes | — | Yes | Yes | Yes |
|
|
477
|
+
| USDC | Yes | Yes | Yes | Yes | Yes |
|
|
478
|
+
| DAI | Yes | Yes | Yes | Yes | Yes |
|
|
479
|
+
| WBTC | Yes | — | Yes | Yes | Yes |
|
|
480
|
+
| LINK | Yes | — | — | — | — |
|
|
481
|
+
| UNI | Yes | — | — | — | — |
|
|
482
|
+
| WETH | — | Yes | — | — | — |
|
|
483
|
+
|
|
163
484
|
## Security
|
|
164
485
|
|
|
165
|
-
- Wallet data is encrypted with AES-256-GCM
|
|
166
|
-
- Private keys are held in memory only during the session
|
|
167
|
-
- Vault is stored locally at `~/.zoa/vault.json`
|
|
168
|
-
- No data is sent to any server unless you explicitly use API features
|
|
486
|
+
- **Encryption:** Wallet data is encrypted with AES-256-GCM using PBKDF2 (600k iterations) key derivation
|
|
487
|
+
- **Key isolation:** Private keys are held in memory only during the active session
|
|
488
|
+
- **Local storage:** Vault is stored locally at `~/.zoa/vault.json` — no cloud sync
|
|
489
|
+
- **No telemetry:** No data is sent to any server unless you explicitly use API features
|
|
490
|
+
- **API keys:** Scoped permissions (read/write/admin) with optional expiry and rate limits
|
|
491
|
+
|
|
492
|
+
## Configuration
|
|
493
|
+
|
|
494
|
+
ZOA stores data in `~/.zoa/`:
|
|
495
|
+
|
|
496
|
+
| File | Description |
|
|
497
|
+
|------|-------------|
|
|
498
|
+
| `vault.json` | Encrypted wallet data |
|
|
499
|
+
| `config.json` | User preferences and settings |
|
|
500
|
+
| `wallets.json` | Wallet metadata (labels, colors, active wallet) |
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
# View config
|
|
504
|
+
zoa config show
|
|
505
|
+
|
|
506
|
+
# Set default chain
|
|
507
|
+
zoa config set defaultChain base
|
|
508
|
+
|
|
509
|
+
# Set API URL
|
|
510
|
+
zoa config set apiUrl https://api.zoa.fun
|
|
511
|
+
|
|
512
|
+
# Set API key
|
|
513
|
+
zoa api set zoa_sk_your_key_here
|
|
514
|
+
```
|
|
169
515
|
|
|
170
516
|
## License
|
|
171
517
|
|
package/dist/index.mjs
CHANGED
|
@@ -36001,13 +36001,11 @@ var BaseEVMAdapter = class {
|
|
|
36001
36001
|
if (!token || !result || result.status !== "success")
|
|
36002
36002
|
continue;
|
|
36003
36003
|
const balance = BigInt(result.result);
|
|
36004
|
-
|
|
36005
|
-
|
|
36006
|
-
|
|
36007
|
-
|
|
36008
|
-
|
|
36009
|
-
});
|
|
36010
|
-
}
|
|
36004
|
+
balances.push({
|
|
36005
|
+
token,
|
|
36006
|
+
balance,
|
|
36007
|
+
formatted: formatUnits(balance, token.decimals)
|
|
36008
|
+
});
|
|
36011
36009
|
}
|
|
36012
36010
|
} catch {
|
|
36013
36011
|
}
|
|
@@ -36072,17 +36070,35 @@ var BaseEVMAdapter = class {
|
|
|
36072
36070
|
});
|
|
36073
36071
|
}
|
|
36074
36072
|
async signAndSendNativeTransfer(params) {
|
|
36073
|
+
this.initClient();
|
|
36075
36074
|
const account = privateKeyToAccount(params.privateKey);
|
|
36075
|
+
const viemChain = this.getViemChain();
|
|
36076
|
+
const balance = await this.client.getBalance({ address: account.address });
|
|
36077
|
+
let sendValue = params.value;
|
|
36078
|
+
if (sendValue > 0n && sendValue >= balance) {
|
|
36079
|
+
try {
|
|
36080
|
+
const feeData = await this.client.estimateFeesPerGas();
|
|
36081
|
+
const maxFeePerGas = feeData.maxFeePerGas ?? 0n;
|
|
36082
|
+
const gasCost = 21000n * maxFeePerGas;
|
|
36083
|
+
if (balance <= gasCost) {
|
|
36084
|
+
throw new Error(`Insufficient balance. You have ${formatEther(balance)} ${this.config.nativeCurrency.symbol} which is not enough to cover gas fees.`);
|
|
36085
|
+
}
|
|
36086
|
+
sendValue = balance - gasCost;
|
|
36087
|
+
} catch (err3) {
|
|
36088
|
+
if (err3 instanceof Error && err3.message.includes("Insufficient balance"))
|
|
36089
|
+
throw err3;
|
|
36090
|
+
}
|
|
36091
|
+
}
|
|
36076
36092
|
const walletClient = createWalletClient({
|
|
36077
36093
|
account,
|
|
36078
|
-
chain:
|
|
36094
|
+
chain: viemChain,
|
|
36079
36095
|
transport: http(this.config.rpcUrls[0])
|
|
36080
36096
|
});
|
|
36081
36097
|
const hash3 = await walletClient.sendTransaction({
|
|
36082
36098
|
account,
|
|
36083
36099
|
to: params.to,
|
|
36084
|
-
value:
|
|
36085
|
-
chain:
|
|
36100
|
+
value: sendValue,
|
|
36101
|
+
chain: viemChain
|
|
36086
36102
|
});
|
|
36087
36103
|
return { hash: hash3 };
|
|
36088
36104
|
}
|
|
@@ -47984,12 +48000,13 @@ var SolanaAdapter = class {
|
|
|
47984
48000
|
const secretBytes = Uint8Array.from((params.privateKey.startsWith("0x") ? params.privateKey.slice(2) : params.privateKey).match(/.{1,2}/g).map((byte) => Number.parseInt(byte, 16)));
|
|
47985
48001
|
const keypair = Keypair.fromSecretKey(secretBytes);
|
|
47986
48002
|
const balance = await this.connection.getBalance(keypair.publicKey);
|
|
47987
|
-
|
|
48003
|
+
let lamports = Number(params.value);
|
|
47988
48004
|
const estimatedFee = 5e3;
|
|
47989
|
-
if (balance <
|
|
47990
|
-
|
|
47991
|
-
|
|
47992
|
-
|
|
48005
|
+
if (balance < estimatedFee + 1) {
|
|
48006
|
+
throw new Error(`Insufficient SOL balance. You have ${(balance / LAMPORTS_PER_SOL).toFixed(6)} SOL which is not enough to cover the transaction fee.`);
|
|
48007
|
+
}
|
|
48008
|
+
if (lamports + estimatedFee > balance) {
|
|
48009
|
+
lamports = balance - estimatedFee;
|
|
47993
48010
|
}
|
|
47994
48011
|
const transaction = new Transaction().add(SystemProgram.transfer({
|
|
47995
48012
|
fromPubkey: keypair.publicKey,
|
|
@@ -49804,9 +49821,27 @@ function cleanErrorMessage(error) {
|
|
|
49804
49821
|
return firstLine;
|
|
49805
49822
|
return full.slice(0, 200);
|
|
49806
49823
|
}
|
|
49824
|
+
function parseBatchString(batch) {
|
|
49825
|
+
return batch.split(",").map((entry) => {
|
|
49826
|
+
const parts = entry.trim().split(":");
|
|
49827
|
+
if (parts.length < 2) {
|
|
49828
|
+
throw new Error(`Invalid batch entry "${entry}". Format: address:amount[:token[:chain]]`);
|
|
49829
|
+
}
|
|
49830
|
+
return {
|
|
49831
|
+
to: parts[0],
|
|
49832
|
+
amount: parts[1],
|
|
49833
|
+
token: parts[2] || void 0,
|
|
49834
|
+
chain: parts[3] || void 0
|
|
49835
|
+
};
|
|
49836
|
+
});
|
|
49837
|
+
}
|
|
49807
49838
|
function registerSendCommand(program2) {
|
|
49808
|
-
program2.command("send").description("Send tokens to an address").option("--password <password>", "Wallet password (non-interactive)").option("--to <address>", "Recipient address").option("--amount <amount>", "Amount to send").option("--chain <chain>", "Chain to use (base, ethereum, solana, etc.)").option("--token <token>", "Token to send (symbol, e.g. USDT, USDC). Defaults to native.").option("--yes", "Skip confirmation prompt").action(async (opts) => {
|
|
49839
|
+
program2.command("send").description("Send tokens to an address").option("--password <password>", "Wallet password (non-interactive)").option("--to <address>", "Recipient address").option("--amount <amount>", "Amount to send").option("--chain <chain>", "Chain to use (base, ethereum, solana, etc.)").option("--token <token>", "Token to send (symbol, e.g. USDT, USDC). Defaults to native.").option("--max", "Send maximum available balance (auto-deducts gas)").option("--batch <transfers>", 'Batch send: comma-separated "address:amount[:token[:chain]]"').option("--yes", "Skip confirmation prompt").action(async (opts) => {
|
|
49809
49840
|
try {
|
|
49841
|
+
if (opts.batch) {
|
|
49842
|
+
await handleBatchSend(opts);
|
|
49843
|
+
return;
|
|
49844
|
+
}
|
|
49810
49845
|
let password;
|
|
49811
49846
|
if (opts.password) {
|
|
49812
49847
|
password = opts.password;
|
|
@@ -49885,10 +49920,11 @@ function registerSendCommand(program2) {
|
|
|
49885
49920
|
} else if (tokenBalances.length === 1) {
|
|
49886
49921
|
selectedToken = tokenBalances[0];
|
|
49887
49922
|
} else {
|
|
49888
|
-
const choices = tokenBalances.map((tb) =>
|
|
49889
|
-
|
|
49890
|
-
|
|
49891
|
-
|
|
49923
|
+
const choices = tokenBalances.map((tb) => {
|
|
49924
|
+
const bal = formatBalance(tb.formatted);
|
|
49925
|
+
const label = tb.balance === 0n ? colors.muted(`${tb.token.symbol.padEnd(8)} 0.00 ${tb.token.name}`) : `${tb.token.symbol.padEnd(8)} ${bal.padEnd(10)} ${tb.token.name}`;
|
|
49926
|
+
return { name: label, value: tb.token.address };
|
|
49927
|
+
});
|
|
49892
49928
|
const { tokenAddr } = await inquirer6.prompt([{
|
|
49893
49929
|
type: "list",
|
|
49894
49930
|
name: "tokenAddr",
|
|
@@ -49926,7 +49962,15 @@ function registerSendCommand(program2) {
|
|
|
49926
49962
|
throw new Error(`Invalid EVM address: ${to}. Must be 0x followed by 40 hex characters.`);
|
|
49927
49963
|
}
|
|
49928
49964
|
let amount;
|
|
49929
|
-
if (opts.
|
|
49965
|
+
if (opts.max) {
|
|
49966
|
+
amount = selectedToken.formatted;
|
|
49967
|
+
if (Number.parseFloat(amount) <= 0) {
|
|
49968
|
+
throw new Error(`No ${tokenSymbol} balance available to send.`);
|
|
49969
|
+
}
|
|
49970
|
+
if (!isJsonMode()) {
|
|
49971
|
+
console.log(colors.muted(` Using max balance: ${formatBalance(amount)} ${tokenSymbol}`));
|
|
49972
|
+
}
|
|
49973
|
+
} else if (opts.amount) {
|
|
49930
49974
|
amount = opts.amount;
|
|
49931
49975
|
} else {
|
|
49932
49976
|
const { sendAmount } = await inquirer6.prompt([{
|
|
@@ -50074,6 +50118,163 @@ function registerSendCommand(program2) {
|
|
|
50074
50118
|
}
|
|
50075
50119
|
});
|
|
50076
50120
|
}
|
|
50121
|
+
async function handleBatchSend(opts) {
|
|
50122
|
+
const transfers = parseBatchString(opts.batch);
|
|
50123
|
+
const defaultChain = opts.chain?.toLowerCase() || "base";
|
|
50124
|
+
for (const t of transfers) {
|
|
50125
|
+
const chain2 = t.chain || defaultChain;
|
|
50126
|
+
const chainId = CHAIN_MAP2[chain2];
|
|
50127
|
+
if (chainId === void 0)
|
|
50128
|
+
throw new Error(`Unsupported chain "${chain2}" in batch entry for ${t.to}`);
|
|
50129
|
+
const isSolana = chainId === ChainId.Solana;
|
|
50130
|
+
if (isSolana && !isValidSolanaAddress(t.to)) {
|
|
50131
|
+
throw new Error(`Invalid Solana address in batch: ${t.to}`);
|
|
50132
|
+
}
|
|
50133
|
+
if (!isSolana && !isValidEvmAddress(t.to)) {
|
|
50134
|
+
throw new Error(`Invalid EVM address in batch: ${t.to}`);
|
|
50135
|
+
}
|
|
50136
|
+
const num2 = Number.parseFloat(t.amount);
|
|
50137
|
+
if (Number.isNaN(num2) || num2 <= 0) {
|
|
50138
|
+
throw new Error(`Invalid amount "${t.amount}" in batch entry for ${t.to}`);
|
|
50139
|
+
}
|
|
50140
|
+
}
|
|
50141
|
+
let password;
|
|
50142
|
+
if (opts.password) {
|
|
50143
|
+
password = opts.password;
|
|
50144
|
+
} else {
|
|
50145
|
+
const { pwd } = await inquirer6.prompt([{
|
|
50146
|
+
type: "password",
|
|
50147
|
+
name: "pwd",
|
|
50148
|
+
message: "Enter your wallet password:",
|
|
50149
|
+
mask: "*"
|
|
50150
|
+
}]);
|
|
50151
|
+
password = pwd;
|
|
50152
|
+
}
|
|
50153
|
+
const spinner = createSpinner("Unlocking wallet...");
|
|
50154
|
+
if (!isJsonMode())
|
|
50155
|
+
spinner.start();
|
|
50156
|
+
let keyring2, account;
|
|
50157
|
+
try {
|
|
50158
|
+
({ keyring: keyring2, account } = await loadActiveWallet(password));
|
|
50159
|
+
} catch (err3) {
|
|
50160
|
+
if (!isJsonMode())
|
|
50161
|
+
spinner.stop();
|
|
50162
|
+
throw err3;
|
|
50163
|
+
}
|
|
50164
|
+
if (!isJsonMode())
|
|
50165
|
+
spinner.stop();
|
|
50166
|
+
const results = [];
|
|
50167
|
+
if (!isJsonMode()) {
|
|
50168
|
+
console.log(colors.brandBold(" Batch Send"));
|
|
50169
|
+
console.log(colors.muted(` ${transfers.length} transfer(s) queued
|
|
50170
|
+
`));
|
|
50171
|
+
}
|
|
50172
|
+
for (let i = 0; i < transfers.length; i++) {
|
|
50173
|
+
const t = transfers[i];
|
|
50174
|
+
const chain2 = t.chain || defaultChain;
|
|
50175
|
+
const chainId = CHAIN_MAP2[chain2];
|
|
50176
|
+
const adapter = chainRegistry.get(chainId);
|
|
50177
|
+
const isSolana = chainId === ChainId.Solana;
|
|
50178
|
+
const from14 = isSolana ? account.solanaAddress : account.evmAddress;
|
|
50179
|
+
const transferLabel = `[${i + 1}/${transfers.length}] ${t.amount} ${t.token || adapter.config.nativeCurrency.symbol} \u2192 ${t.to.slice(0, 10)}...`;
|
|
50180
|
+
if (!isJsonMode()) {
|
|
50181
|
+
spinner.text = ` ${transferLabel}`;
|
|
50182
|
+
spinner.start();
|
|
50183
|
+
}
|
|
50184
|
+
try {
|
|
50185
|
+
let tokenBalances;
|
|
50186
|
+
try {
|
|
50187
|
+
tokenBalances = await adapter.getTokenBalances(from14);
|
|
50188
|
+
} catch {
|
|
50189
|
+
tokenBalances = [{
|
|
50190
|
+
token: {
|
|
50191
|
+
address: "native",
|
|
50192
|
+
symbol: adapter.config.nativeCurrency.symbol,
|
|
50193
|
+
name: adapter.config.nativeCurrency.name,
|
|
50194
|
+
decimals: adapter.config.nativeCurrency.decimals,
|
|
50195
|
+
chainId: adapter.config.chainId
|
|
50196
|
+
},
|
|
50197
|
+
balance: 0n,
|
|
50198
|
+
formatted: "0"
|
|
50199
|
+
}];
|
|
50200
|
+
}
|
|
50201
|
+
const tokenSymbol = t.token || adapter.config.nativeCurrency.symbol;
|
|
50202
|
+
const selectedToken = tokenBalances.find((tb) => tb.token.symbol.toLowerCase() === tokenSymbol.toLowerCase());
|
|
50203
|
+
if (!selectedToken)
|
|
50204
|
+
throw new Error(`Token ${tokenSymbol} not found on ${adapter.config.name}`);
|
|
50205
|
+
const isNativeTransfer = selectedToken.token.address === "native";
|
|
50206
|
+
const value = BigInt(Math.floor(Number.parseFloat(t.amount) * 10 ** selectedToken.token.decimals));
|
|
50207
|
+
if (value > selectedToken.balance) {
|
|
50208
|
+
throw new Error(`Insufficient ${tokenSymbol} balance. Have ${formatBalance(selectedToken.formatted)}, need ${t.amount}.`);
|
|
50209
|
+
}
|
|
50210
|
+
const chainFamily = isSolana ? "solana" : "evm";
|
|
50211
|
+
const privateKey = keyring2.exportPrivateKey(0, chainFamily);
|
|
50212
|
+
let hash3;
|
|
50213
|
+
if (isNativeTransfer) {
|
|
50214
|
+
if (!adapter.signAndSendNativeTransfer)
|
|
50215
|
+
throw new Error(`Native transfers not supported for ${adapter.config.name}`);
|
|
50216
|
+
const result = await adapter.signAndSendNativeTransfer({ privateKey, to: t.to, value });
|
|
50217
|
+
hash3 = result.hash;
|
|
50218
|
+
} else {
|
|
50219
|
+
if (!adapter.signAndSendTokenTransfer)
|
|
50220
|
+
throw new Error(`Token transfers not supported for ${adapter.config.name}`);
|
|
50221
|
+
const result = await adapter.signAndSendTokenTransfer({
|
|
50222
|
+
privateKey,
|
|
50223
|
+
tokenAddress: selectedToken.token.address,
|
|
50224
|
+
to: t.to,
|
|
50225
|
+
amount: value
|
|
50226
|
+
});
|
|
50227
|
+
hash3 = result.hash;
|
|
50228
|
+
}
|
|
50229
|
+
const explorerUrl = `${adapter.config.blockExplorerUrl}/tx/${hash3}`;
|
|
50230
|
+
if (!isJsonMode())
|
|
50231
|
+
spinner.stop();
|
|
50232
|
+
if (!isJsonMode()) {
|
|
50233
|
+
console.log(colors.success(` \u2713 ${transferLabel}`));
|
|
50234
|
+
console.log(colors.muted(` ${explorerUrl}
|
|
50235
|
+
`));
|
|
50236
|
+
}
|
|
50237
|
+
results.push({
|
|
50238
|
+
to: t.to,
|
|
50239
|
+
amount: t.amount,
|
|
50240
|
+
token: tokenSymbol,
|
|
50241
|
+
chain: adapter.config.name,
|
|
50242
|
+
status: "sent",
|
|
50243
|
+
hash: hash3,
|
|
50244
|
+
explorerUrl
|
|
50245
|
+
});
|
|
50246
|
+
} catch (err3) {
|
|
50247
|
+
if (!isJsonMode())
|
|
50248
|
+
spinner.stop();
|
|
50249
|
+
const errMsg = cleanErrorMessage(err3);
|
|
50250
|
+
if (!isJsonMode()) {
|
|
50251
|
+
console.log(colors.error(` \u2717 ${transferLabel}`));
|
|
50252
|
+
console.log(colors.muted(` ${errMsg}
|
|
50253
|
+
`));
|
|
50254
|
+
}
|
|
50255
|
+
results.push({
|
|
50256
|
+
to: t.to,
|
|
50257
|
+
amount: t.amount,
|
|
50258
|
+
token: t.token || chain2,
|
|
50259
|
+
chain: chain2,
|
|
50260
|
+
status: "failed",
|
|
50261
|
+
error: errMsg
|
|
50262
|
+
});
|
|
50263
|
+
}
|
|
50264
|
+
}
|
|
50265
|
+
const succeeded = results.filter((r) => r.status === "sent").length;
|
|
50266
|
+
const failed = results.filter((r) => r.status === "failed").length;
|
|
50267
|
+
output({ batch: true, results, summary: { total: results.length, succeeded, failed } }, () => {
|
|
50268
|
+
console.log();
|
|
50269
|
+
const summaryLines = [
|
|
50270
|
+
kvLine("Total", String(results.length)),
|
|
50271
|
+
kvLine("Succeeded", colors.success(String(succeeded))),
|
|
50272
|
+
kvLine("Failed", failed > 0 ? colors.error(String(failed)) : "0")
|
|
50273
|
+
];
|
|
50274
|
+
console.log(successBox("Batch Complete", summaryLines));
|
|
50275
|
+
console.log();
|
|
50276
|
+
});
|
|
50277
|
+
}
|
|
50077
50278
|
|
|
50078
50279
|
// dist/commands/wallet.js
|
|
50079
50280
|
import chalk3 from "chalk";
|
|
@@ -50577,7 +50778,7 @@ var zoaGradient2 = gradient2([
|
|
|
50577
50778
|
"#c77dff"
|
|
50578
50779
|
]);
|
|
50579
50780
|
var subtleGradient = gradient2(["#6b7280", "#9ca3af", "#6b7280"]);
|
|
50580
|
-
var VERSION = "0.3.
|
|
50781
|
+
var VERSION = "0.3.4";
|
|
50581
50782
|
async function displayBanner() {
|
|
50582
50783
|
const banner = figlet.textSync("ZOA-wallet", { font: "ANSI Shadow" });
|
|
50583
50784
|
console.log();
|
|
@@ -50615,7 +50816,7 @@ program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
50615
50816
|
}
|
|
50616
50817
|
}
|
|
50617
50818
|
});
|
|
50618
|
-
program.name("zoa").description("ZOA Wallet \u2014 API-First Crypto Wallet for Power-traders, Developers, & AI Agents").version("0.3.
|
|
50819
|
+
program.name("zoa").description("ZOA Wallet \u2014 API-First Crypto Wallet for Power-traders, Developers, & AI Agents").version("0.3.4");
|
|
50619
50820
|
registerWalletCommand(program);
|
|
50620
50821
|
registerBalanceCommand(program);
|
|
50621
50822
|
registerSendCommand(program);
|
package/package.json
CHANGED