aavegotchi-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.
- package/README.md +120 -0
- package/dist/args.js +97 -0
- package/dist/chains.js +74 -0
- package/dist/command-runner.js +184 -0
- package/dist/commands/batch.js +189 -0
- package/dist/commands/bootstrap.js +97 -0
- package/dist/commands/index.js +6 -0
- package/dist/commands/mapped.js +71 -0
- package/dist/commands/onchain.js +215 -0
- package/dist/commands/policy.js +75 -0
- package/dist/commands/profile.js +67 -0
- package/dist/commands/rpc.js +29 -0
- package/dist/commands/signer.js +51 -0
- package/dist/commands/stubs.js +32 -0
- package/dist/commands/tx.js +173 -0
- package/dist/config.js +198 -0
- package/dist/errors.js +31 -0
- package/dist/idempotency.js +21 -0
- package/dist/index.js +33 -0
- package/dist/journal.js +377 -0
- package/dist/keychain.js +198 -0
- package/dist/logger.js +20 -0
- package/dist/output.js +112 -0
- package/dist/policy.js +38 -0
- package/dist/rpc.js +40 -0
- package/dist/schemas.js +79 -0
- package/dist/signer.js +348 -0
- package/dist/tx-engine.js +285 -0
- package/dist/types.js +2 -0
- package/package.json +50 -0
package/dist/journal.js
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.JournalStore = void 0;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
42
|
+
function mapRow(row) {
|
|
43
|
+
return {
|
|
44
|
+
id: Number(row.id),
|
|
45
|
+
idempotencyKey: String(row.idempotency_key),
|
|
46
|
+
profileName: String(row.profile_name),
|
|
47
|
+
chainId: Number(row.chain_id),
|
|
48
|
+
command: String(row.command),
|
|
49
|
+
toAddress: String(row.to_address),
|
|
50
|
+
fromAddress: String(row.from_address),
|
|
51
|
+
valueWei: String(row.value_wei),
|
|
52
|
+
dataHex: String(row.data_hex),
|
|
53
|
+
nonce: Number(row.nonce),
|
|
54
|
+
gasLimit: String(row.gas_limit),
|
|
55
|
+
maxFeePerGasWei: String(row.max_fee_per_gas_wei),
|
|
56
|
+
maxPriorityFeePerGasWei: String(row.max_priority_fee_per_gas_wei),
|
|
57
|
+
txHash: String(row.tx_hash),
|
|
58
|
+
status: String(row.status),
|
|
59
|
+
errorCode: String(row.error_code),
|
|
60
|
+
errorMessage: String(row.error_message),
|
|
61
|
+
receiptJson: String(row.receipt_json),
|
|
62
|
+
createdAt: String(row.created_at),
|
|
63
|
+
updatedAt: String(row.updated_at),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function clone(entry) {
|
|
67
|
+
return JSON.parse(JSON.stringify(entry));
|
|
68
|
+
}
|
|
69
|
+
function createEntry(id, params, timestamp) {
|
|
70
|
+
return {
|
|
71
|
+
id,
|
|
72
|
+
idempotencyKey: params.idempotencyKey,
|
|
73
|
+
profileName: params.profileName,
|
|
74
|
+
chainId: params.chainId,
|
|
75
|
+
command: params.command,
|
|
76
|
+
toAddress: params.toAddress,
|
|
77
|
+
fromAddress: params.fromAddress,
|
|
78
|
+
valueWei: params.valueWei,
|
|
79
|
+
dataHex: params.dataHex,
|
|
80
|
+
nonce: params.nonce,
|
|
81
|
+
gasLimit: params.gasLimit,
|
|
82
|
+
maxFeePerGasWei: params.maxFeePerGasWei,
|
|
83
|
+
maxPriorityFeePerGasWei: params.maxPriorityFeePerGasWei,
|
|
84
|
+
txHash: "",
|
|
85
|
+
status: params.status,
|
|
86
|
+
errorCode: "",
|
|
87
|
+
errorMessage: "",
|
|
88
|
+
receiptJson: "",
|
|
89
|
+
createdAt: timestamp,
|
|
90
|
+
updatedAt: timestamp,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
class JournalStore {
|
|
94
|
+
dbPath;
|
|
95
|
+
fallbackPath;
|
|
96
|
+
db;
|
|
97
|
+
fallback = { nextId: 1, entries: [] };
|
|
98
|
+
constructor(path) {
|
|
99
|
+
this.dbPath = path;
|
|
100
|
+
this.fallbackPath = `${path}.json`;
|
|
101
|
+
try {
|
|
102
|
+
this.db = new better_sqlite3_1.default(path);
|
|
103
|
+
this.db.pragma("journal_mode = WAL");
|
|
104
|
+
this.db.exec(`
|
|
105
|
+
CREATE TABLE IF NOT EXISTS tx_journal (
|
|
106
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
107
|
+
idempotency_key TEXT NOT NULL UNIQUE,
|
|
108
|
+
profile_name TEXT NOT NULL,
|
|
109
|
+
chain_id INTEGER NOT NULL,
|
|
110
|
+
command TEXT NOT NULL,
|
|
111
|
+
to_address TEXT NOT NULL,
|
|
112
|
+
from_address TEXT NOT NULL DEFAULT '',
|
|
113
|
+
value_wei TEXT NOT NULL DEFAULT '0',
|
|
114
|
+
data_hex TEXT NOT NULL DEFAULT '0x',
|
|
115
|
+
nonce INTEGER NOT NULL DEFAULT -1,
|
|
116
|
+
gas_limit TEXT NOT NULL DEFAULT '',
|
|
117
|
+
max_fee_per_gas_wei TEXT NOT NULL DEFAULT '',
|
|
118
|
+
max_priority_fee_per_gas_wei TEXT NOT NULL DEFAULT '',
|
|
119
|
+
tx_hash TEXT NOT NULL DEFAULT '',
|
|
120
|
+
status TEXT NOT NULL,
|
|
121
|
+
error_code TEXT NOT NULL DEFAULT '',
|
|
122
|
+
error_message TEXT NOT NULL DEFAULT '',
|
|
123
|
+
receipt_json TEXT NOT NULL DEFAULT '',
|
|
124
|
+
created_at TEXT NOT NULL,
|
|
125
|
+
updated_at TEXT NOT NULL
|
|
126
|
+
);
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_tx_journal_tx_hash ON tx_journal(tx_hash);
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_tx_journal_status ON tx_journal(status);
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
this.db = undefined;
|
|
133
|
+
this.loadFallback();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
close() {
|
|
137
|
+
if (this.db) {
|
|
138
|
+
this.db.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
loadFallback() {
|
|
142
|
+
if (!fs.existsSync(this.fallbackPath)) {
|
|
143
|
+
this.fallback = { nextId: 1, entries: [] };
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const parsed = JSON.parse(fs.readFileSync(this.fallbackPath, "utf8"));
|
|
148
|
+
this.fallback = parsed;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
this.fallback = { nextId: 1, entries: [] };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
saveFallback() {
|
|
155
|
+
fs.writeFileSync(this.fallbackPath, `${JSON.stringify(this.fallback, null, 2)}\n`, "utf8");
|
|
156
|
+
}
|
|
157
|
+
getByIdempotencyKeyFallback(idempotencyKey) {
|
|
158
|
+
const found = this.fallback.entries.find((entry) => entry.idempotencyKey === idempotencyKey);
|
|
159
|
+
return found ? clone(found) : undefined;
|
|
160
|
+
}
|
|
161
|
+
getByTxHashFallback(txHash) {
|
|
162
|
+
const found = this.fallback.entries.find((entry) => entry.txHash === txHash);
|
|
163
|
+
return found ? clone(found) : undefined;
|
|
164
|
+
}
|
|
165
|
+
upsertPrepared(params) {
|
|
166
|
+
const timestamp = new Date().toISOString();
|
|
167
|
+
if (!this.db) {
|
|
168
|
+
const existingIndex = this.fallback.entries.findIndex((entry) => entry.idempotencyKey === params.idempotencyKey);
|
|
169
|
+
if (existingIndex >= 0) {
|
|
170
|
+
const existing = this.fallback.entries[existingIndex];
|
|
171
|
+
this.fallback.entries[existingIndex] = {
|
|
172
|
+
...existing,
|
|
173
|
+
...createEntry(existing.id, params, existing.createdAt),
|
|
174
|
+
createdAt: existing.createdAt,
|
|
175
|
+
updatedAt: timestamp,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
this.fallback.entries.push(createEntry(this.fallback.nextId++, params, timestamp));
|
|
180
|
+
}
|
|
181
|
+
this.saveFallback();
|
|
182
|
+
return this.getByIdempotencyKeyFallback(params.idempotencyKey);
|
|
183
|
+
}
|
|
184
|
+
const statement = this.db.prepare(`
|
|
185
|
+
INSERT INTO tx_journal (
|
|
186
|
+
idempotency_key,
|
|
187
|
+
profile_name,
|
|
188
|
+
chain_id,
|
|
189
|
+
command,
|
|
190
|
+
to_address,
|
|
191
|
+
from_address,
|
|
192
|
+
value_wei,
|
|
193
|
+
data_hex,
|
|
194
|
+
nonce,
|
|
195
|
+
gas_limit,
|
|
196
|
+
max_fee_per_gas_wei,
|
|
197
|
+
max_priority_fee_per_gas_wei,
|
|
198
|
+
status,
|
|
199
|
+
created_at,
|
|
200
|
+
updated_at
|
|
201
|
+
) VALUES (
|
|
202
|
+
@idempotencyKey,
|
|
203
|
+
@profileName,
|
|
204
|
+
@chainId,
|
|
205
|
+
@command,
|
|
206
|
+
@toAddress,
|
|
207
|
+
@fromAddress,
|
|
208
|
+
@valueWei,
|
|
209
|
+
@dataHex,
|
|
210
|
+
@nonce,
|
|
211
|
+
@gasLimit,
|
|
212
|
+
@maxFeePerGasWei,
|
|
213
|
+
@maxPriorityFeePerGasWei,
|
|
214
|
+
@status,
|
|
215
|
+
@createdAt,
|
|
216
|
+
@updatedAt
|
|
217
|
+
)
|
|
218
|
+
ON CONFLICT(idempotency_key) DO UPDATE SET
|
|
219
|
+
profile_name = excluded.profile_name,
|
|
220
|
+
chain_id = excluded.chain_id,
|
|
221
|
+
command = excluded.command,
|
|
222
|
+
to_address = excluded.to_address,
|
|
223
|
+
from_address = excluded.from_address,
|
|
224
|
+
value_wei = excluded.value_wei,
|
|
225
|
+
data_hex = excluded.data_hex,
|
|
226
|
+
nonce = excluded.nonce,
|
|
227
|
+
gas_limit = excluded.gas_limit,
|
|
228
|
+
max_fee_per_gas_wei = excluded.max_fee_per_gas_wei,
|
|
229
|
+
max_priority_fee_per_gas_wei = excluded.max_priority_fee_per_gas_wei,
|
|
230
|
+
status = excluded.status,
|
|
231
|
+
updated_at = excluded.updated_at
|
|
232
|
+
`);
|
|
233
|
+
statement.run({
|
|
234
|
+
...params,
|
|
235
|
+
createdAt: timestamp,
|
|
236
|
+
updatedAt: timestamp,
|
|
237
|
+
});
|
|
238
|
+
const entry = this.getByIdempotencyKey(params.idempotencyKey);
|
|
239
|
+
if (!entry) {
|
|
240
|
+
throw new Error("Journal write failed");
|
|
241
|
+
}
|
|
242
|
+
return entry;
|
|
243
|
+
}
|
|
244
|
+
markSubmitted(params) {
|
|
245
|
+
if (!this.db) {
|
|
246
|
+
const existing = this.fallback.entries.find((entry) => entry.idempotencyKey === params.idempotencyKey);
|
|
247
|
+
if (!existing) {
|
|
248
|
+
throw new Error("Journal update failed");
|
|
249
|
+
}
|
|
250
|
+
existing.txHash = params.txHash;
|
|
251
|
+
existing.status = params.status;
|
|
252
|
+
existing.errorCode = params.errorCode;
|
|
253
|
+
existing.errorMessage = params.errorMessage;
|
|
254
|
+
existing.updatedAt = new Date().toISOString();
|
|
255
|
+
this.saveFallback();
|
|
256
|
+
return clone(existing);
|
|
257
|
+
}
|
|
258
|
+
this.db
|
|
259
|
+
.prepare(`
|
|
260
|
+
UPDATE tx_journal
|
|
261
|
+
SET tx_hash = @txHash,
|
|
262
|
+
status = @status,
|
|
263
|
+
error_code = @errorCode,
|
|
264
|
+
error_message = @errorMessage,
|
|
265
|
+
updated_at = @updatedAt
|
|
266
|
+
WHERE idempotency_key = @idempotencyKey
|
|
267
|
+
`)
|
|
268
|
+
.run({
|
|
269
|
+
...params,
|
|
270
|
+
updatedAt: new Date().toISOString(),
|
|
271
|
+
});
|
|
272
|
+
const entry = this.getByIdempotencyKey(params.idempotencyKey);
|
|
273
|
+
if (!entry) {
|
|
274
|
+
throw new Error("Journal update failed");
|
|
275
|
+
}
|
|
276
|
+
return entry;
|
|
277
|
+
}
|
|
278
|
+
markConfirmed(idempotencyKey, receiptJson) {
|
|
279
|
+
if (!this.db) {
|
|
280
|
+
const existing = this.fallback.entries.find((entry) => entry.idempotencyKey === idempotencyKey);
|
|
281
|
+
if (!existing) {
|
|
282
|
+
throw new Error("Journal confirmation update failed");
|
|
283
|
+
}
|
|
284
|
+
existing.status = "confirmed";
|
|
285
|
+
existing.receiptJson = receiptJson;
|
|
286
|
+
existing.updatedAt = new Date().toISOString();
|
|
287
|
+
this.saveFallback();
|
|
288
|
+
return clone(existing);
|
|
289
|
+
}
|
|
290
|
+
this.db
|
|
291
|
+
.prepare(`
|
|
292
|
+
UPDATE tx_journal
|
|
293
|
+
SET status = 'confirmed',
|
|
294
|
+
receipt_json = @receiptJson,
|
|
295
|
+
updated_at = @updatedAt
|
|
296
|
+
WHERE idempotency_key = @idempotencyKey
|
|
297
|
+
`)
|
|
298
|
+
.run({
|
|
299
|
+
idempotencyKey,
|
|
300
|
+
receiptJson,
|
|
301
|
+
updatedAt: new Date().toISOString(),
|
|
302
|
+
});
|
|
303
|
+
const entry = this.getByIdempotencyKey(idempotencyKey);
|
|
304
|
+
if (!entry) {
|
|
305
|
+
throw new Error("Journal confirmation update failed");
|
|
306
|
+
}
|
|
307
|
+
return entry;
|
|
308
|
+
}
|
|
309
|
+
markFailed(idempotencyKey, errorCode, errorMessage) {
|
|
310
|
+
if (!this.db) {
|
|
311
|
+
const existing = this.fallback.entries.find((entry) => entry.idempotencyKey === idempotencyKey);
|
|
312
|
+
if (!existing) {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
existing.status = "failed";
|
|
316
|
+
existing.errorCode = errorCode;
|
|
317
|
+
existing.errorMessage = errorMessage;
|
|
318
|
+
existing.updatedAt = new Date().toISOString();
|
|
319
|
+
this.saveFallback();
|
|
320
|
+
return clone(existing);
|
|
321
|
+
}
|
|
322
|
+
this.db
|
|
323
|
+
.prepare(`
|
|
324
|
+
UPDATE tx_journal
|
|
325
|
+
SET status = 'failed',
|
|
326
|
+
error_code = @errorCode,
|
|
327
|
+
error_message = @errorMessage,
|
|
328
|
+
updated_at = @updatedAt
|
|
329
|
+
WHERE idempotency_key = @idempotencyKey
|
|
330
|
+
`)
|
|
331
|
+
.run({
|
|
332
|
+
idempotencyKey,
|
|
333
|
+
errorCode,
|
|
334
|
+
errorMessage,
|
|
335
|
+
updatedAt: new Date().toISOString(),
|
|
336
|
+
});
|
|
337
|
+
return this.getByIdempotencyKey(idempotencyKey);
|
|
338
|
+
}
|
|
339
|
+
getByIdempotencyKey(idempotencyKey) {
|
|
340
|
+
if (!this.db) {
|
|
341
|
+
return this.getByIdempotencyKeyFallback(idempotencyKey);
|
|
342
|
+
}
|
|
343
|
+
const row = this.db
|
|
344
|
+
.prepare("SELECT * FROM tx_journal WHERE idempotency_key = ? LIMIT 1")
|
|
345
|
+
.get(idempotencyKey);
|
|
346
|
+
if (!row) {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
return mapRow(row);
|
|
350
|
+
}
|
|
351
|
+
getByTxHash(txHash) {
|
|
352
|
+
if (!this.db) {
|
|
353
|
+
return this.getByTxHashFallback(txHash);
|
|
354
|
+
}
|
|
355
|
+
const row = this.db
|
|
356
|
+
.prepare("SELECT * FROM tx_journal WHERE tx_hash = ? LIMIT 1")
|
|
357
|
+
.get(txHash);
|
|
358
|
+
if (!row) {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
return mapRow(row);
|
|
362
|
+
}
|
|
363
|
+
listRecent(limit = 20) {
|
|
364
|
+
if (!this.db) {
|
|
365
|
+
return this.fallback.entries
|
|
366
|
+
.slice()
|
|
367
|
+
.sort((a, b) => b.id - a.id)
|
|
368
|
+
.slice(0, limit)
|
|
369
|
+
.map((entry) => clone(entry));
|
|
370
|
+
}
|
|
371
|
+
const rows = this.db
|
|
372
|
+
.prepare("SELECT * FROM tx_journal ORDER BY id DESC LIMIT ?")
|
|
373
|
+
.all(limit);
|
|
374
|
+
return rows.map((row) => mapRow(row));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
exports.JournalStore = JournalStore;
|
package/dist/keychain.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.keychainImportFromEnv = keychainImportFromEnv;
|
|
37
|
+
exports.keychainList = keychainList;
|
|
38
|
+
exports.keychainRemove = keychainRemove;
|
|
39
|
+
exports.keychainResolvePrivateKey = keychainResolvePrivateKey;
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const accounts_1 = require("viem/accounts");
|
|
43
|
+
const config_1 = require("./config");
|
|
44
|
+
const errors_1 = require("./errors");
|
|
45
|
+
function nowIso() {
|
|
46
|
+
return new Date().toISOString();
|
|
47
|
+
}
|
|
48
|
+
function createDefaultFile() {
|
|
49
|
+
return {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
entries: {},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function getPassphrase() {
|
|
55
|
+
const value = process.env.AGCLI_KEYCHAIN_PASSPHRASE;
|
|
56
|
+
if (!value || value.length < 8) {
|
|
57
|
+
throw new errors_1.CliError("MISSING_KEYCHAIN_PASSPHRASE", "Set AGCLI_KEYCHAIN_PASSPHRASE (8+ chars) to use keychain signer backend.", 2);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
function deriveKey(passphrase, saltHex) {
|
|
62
|
+
return crypto.scryptSync(passphrase, Buffer.from(saltHex, "hex"), 32);
|
|
63
|
+
}
|
|
64
|
+
function encryptPrivateKey(privateKey, passphrase) {
|
|
65
|
+
const iv = crypto.randomBytes(12);
|
|
66
|
+
const salt = crypto.randomBytes(16);
|
|
67
|
+
const key = deriveKey(passphrase, salt.toString("hex"));
|
|
68
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
69
|
+
const ciphertext = Buffer.concat([cipher.update(privateKey, "utf8"), cipher.final()]);
|
|
70
|
+
const tag = cipher.getAuthTag();
|
|
71
|
+
return {
|
|
72
|
+
ivHex: iv.toString("hex"),
|
|
73
|
+
saltHex: salt.toString("hex"),
|
|
74
|
+
tagHex: tag.toString("hex"),
|
|
75
|
+
ciphertextHex: ciphertext.toString("hex"),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function decryptPrivateKey(entry, passphrase) {
|
|
79
|
+
const key = deriveKey(passphrase, entry.saltHex);
|
|
80
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(entry.ivHex, "hex"));
|
|
81
|
+
decipher.setAuthTag(Buffer.from(entry.tagHex, "hex"));
|
|
82
|
+
const plaintext = Buffer.concat([
|
|
83
|
+
decipher.update(Buffer.from(entry.ciphertextHex, "hex")),
|
|
84
|
+
decipher.final(),
|
|
85
|
+
]).toString("utf8");
|
|
86
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(plaintext)) {
|
|
87
|
+
throw new errors_1.CliError("INVALID_PRIVATE_KEY", "Decrypted keychain secret is not a valid private key.", 2);
|
|
88
|
+
}
|
|
89
|
+
return plaintext;
|
|
90
|
+
}
|
|
91
|
+
function loadKeychainFile(customHome) {
|
|
92
|
+
const keychainPath = (0, config_1.resolveKeychainPath)(customHome);
|
|
93
|
+
if (!fs.existsSync(keychainPath)) {
|
|
94
|
+
return createDefaultFile();
|
|
95
|
+
}
|
|
96
|
+
let parsed;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(fs.readFileSync(keychainPath, "utf8"));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new errors_1.CliError("INVALID_KEYCHAIN", `Keychain file is not valid JSON: ${keychainPath}`, 2);
|
|
102
|
+
}
|
|
103
|
+
if (!parsed ||
|
|
104
|
+
typeof parsed !== "object" ||
|
|
105
|
+
parsed.schemaVersion !== 1 ||
|
|
106
|
+
typeof parsed.entries !== "object") {
|
|
107
|
+
throw new errors_1.CliError("INVALID_KEYCHAIN", `Unsupported keychain format: ${keychainPath}`, 2);
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
function saveKeychainFile(file, customHome) {
|
|
112
|
+
const home = (0, config_1.resolveAgcliHome)(customHome);
|
|
113
|
+
const keychainPath = (0, config_1.resolveKeychainPath)(customHome);
|
|
114
|
+
fs.mkdirSync(home, { recursive: true });
|
|
115
|
+
const tmpPath = `${keychainPath}.tmp`;
|
|
116
|
+
fs.writeFileSync(tmpPath, `${JSON.stringify(file, null, 2)}\n`, "utf8");
|
|
117
|
+
fs.renameSync(tmpPath, keychainPath);
|
|
118
|
+
return keychainPath;
|
|
119
|
+
}
|
|
120
|
+
function parsePrivateKey(value, hint) {
|
|
121
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(value)) {
|
|
122
|
+
throw new errors_1.CliError("INVALID_PRIVATE_KEY", `${hint} is not a valid private key.`, 2);
|
|
123
|
+
}
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
function keychainImportFromEnv(accountId, envVar, customHome) {
|
|
127
|
+
if (!/^[A-Za-z0-9._-]+$/.test(accountId)) {
|
|
128
|
+
throw new errors_1.CliError("INVALID_ARGUMENT", "account-id may only contain letters, numbers, dot, underscore, dash.", 2);
|
|
129
|
+
}
|
|
130
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(envVar)) {
|
|
131
|
+
throw new errors_1.CliError("INVALID_ARGUMENT", "private-key-env must be an uppercase env var name.", 2);
|
|
132
|
+
}
|
|
133
|
+
const privateKeyRaw = process.env[envVar];
|
|
134
|
+
if (!privateKeyRaw) {
|
|
135
|
+
throw new errors_1.CliError("MISSING_SIGNER_SECRET", `Missing environment variable '${envVar}'.`, 2);
|
|
136
|
+
}
|
|
137
|
+
const privateKey = parsePrivateKey(privateKeyRaw, `Environment variable '${envVar}'`);
|
|
138
|
+
const account = (0, accounts_1.privateKeyToAccount)(privateKey);
|
|
139
|
+
const address = account.address.toLowerCase();
|
|
140
|
+
const passphrase = getPassphrase();
|
|
141
|
+
const encrypted = encryptPrivateKey(privateKey, passphrase);
|
|
142
|
+
const file = loadKeychainFile(customHome);
|
|
143
|
+
const existing = file.entries[accountId];
|
|
144
|
+
const timestamp = nowIso();
|
|
145
|
+
file.entries[accountId] = {
|
|
146
|
+
accountId,
|
|
147
|
+
address,
|
|
148
|
+
...encrypted,
|
|
149
|
+
createdAt: existing?.createdAt || timestamp,
|
|
150
|
+
updatedAt: timestamp,
|
|
151
|
+
};
|
|
152
|
+
const keychainPath = saveKeychainFile(file, customHome);
|
|
153
|
+
return {
|
|
154
|
+
accountId,
|
|
155
|
+
address,
|
|
156
|
+
keychainPath,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function keychainList(customHome) {
|
|
160
|
+
const file = loadKeychainFile(customHome);
|
|
161
|
+
return Object.values(file.entries)
|
|
162
|
+
.sort((a, b) => a.accountId.localeCompare(b.accountId))
|
|
163
|
+
.map((entry) => ({
|
|
164
|
+
accountId: entry.accountId,
|
|
165
|
+
address: entry.address,
|
|
166
|
+
updatedAt: entry.updatedAt,
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
function keychainRemove(accountId, customHome) {
|
|
170
|
+
const file = loadKeychainFile(customHome);
|
|
171
|
+
const removed = Boolean(file.entries[accountId]);
|
|
172
|
+
if (removed) {
|
|
173
|
+
delete file.entries[accountId];
|
|
174
|
+
}
|
|
175
|
+
const keychainPath = saveKeychainFile(file, customHome);
|
|
176
|
+
return {
|
|
177
|
+
accountId,
|
|
178
|
+
removed,
|
|
179
|
+
keychainPath,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function keychainResolvePrivateKey(accountId, customHome) {
|
|
183
|
+
const file = loadKeychainFile(customHome);
|
|
184
|
+
const entry = file.entries[accountId];
|
|
185
|
+
if (!entry) {
|
|
186
|
+
throw new errors_1.CliError("KEYCHAIN_ENTRY_NOT_FOUND", `No keychain entry for '${accountId}'.`, 2);
|
|
187
|
+
}
|
|
188
|
+
const passphrase = getPassphrase();
|
|
189
|
+
const privateKey = decryptPrivateKey(entry, passphrase);
|
|
190
|
+
const account = (0, accounts_1.privateKeyToAccount)(privateKey);
|
|
191
|
+
if (account.address.toLowerCase() !== entry.address.toLowerCase()) {
|
|
192
|
+
throw new errors_1.CliError("KEYCHAIN_ADDRESS_MISMATCH", `Decrypted key does not match stored address for '${accountId}'.`, 2);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
privateKey,
|
|
196
|
+
address: entry.address.toLowerCase(),
|
|
197
|
+
};
|
|
198
|
+
}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initializeLogger = initializeLogger;
|
|
7
|
+
exports.getLogger = getLogger;
|
|
8
|
+
const pino_1 = __importDefault(require("pino"));
|
|
9
|
+
let cached = (0, pino_1.default)({ level: "silent" });
|
|
10
|
+
function initializeLogger(globals) {
|
|
11
|
+
const level = process.env.AGCLI_LOG_LEVEL || (globals.mode === "agent" ? "warn" : "info");
|
|
12
|
+
cached = (0, pino_1.default)({
|
|
13
|
+
level,
|
|
14
|
+
base: undefined,
|
|
15
|
+
timestamp: pino_1.default.stdTimeFunctions.isoTime,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function getLogger() {
|
|
19
|
+
return cached;
|
|
20
|
+
}
|