aavegotchi-cli 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +27 -0
- package/dist/commands/onchain.js +5 -0
- package/dist/commands/tx.js +5 -0
- package/dist/output.js +3 -2
- package/dist/tx-engine.js +41 -5
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1 - 2026-02-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `--dry-run` mode for write surfaces:
|
|
8
|
+
- `ag tx send --dry-run`
|
|
9
|
+
- `ag onchain send --dry-run`
|
|
10
|
+
- mapped writes (for example `ag token approve --dry-run`)
|
|
11
|
+
- `scripts/smoke-write-dryrun.sh` and npm script `smoke:write-dryrun` for automated write-path smoke checks without broadcasting.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Dry-run execution now returns `status: "simulated"` with simulation details and skips journal mutation and transaction submission.
|
|
16
|
+
- `--dry-run` is explicitly blocked with `--wait` / `--confirm`.
|
|
17
|
+
|
|
3
18
|
## 0.2.0 - 2026-02-27
|
|
4
19
|
|
|
5
20
|
### Added
|
package/README.md
CHANGED
|
@@ -55,6 +55,26 @@ Planned domain namespaces are stubbed for parity tracking:
|
|
|
55
55
|
Many Base-era write flows are already executable as mapped aliases in those namespaces (internally routed through `onchain send`).
|
|
56
56
|
Example: `ag lending create --abi-file ./abis/GotchiLendingFacet.json --address 0x... --args-json '[...]' --json`
|
|
57
57
|
|
|
58
|
+
## Dry-run writes
|
|
59
|
+
|
|
60
|
+
Use `--dry-run` on write commands to run full preflight without broadcasting:
|
|
61
|
+
|
|
62
|
+
- runs simulation (`eth_call`)
|
|
63
|
+
- runs gas + fee estimation
|
|
64
|
+
- enforces policy checks
|
|
65
|
+
- resolves nonce
|
|
66
|
+
- returns `status: \"simulated\"` with simulation details
|
|
67
|
+
|
|
68
|
+
Supported write surfaces:
|
|
69
|
+
|
|
70
|
+
- `tx send --dry-run`
|
|
71
|
+
- `onchain send --dry-run`
|
|
72
|
+
- mapped write aliases (for example: `token approve --dry-run`)
|
|
73
|
+
|
|
74
|
+
Safety rule:
|
|
75
|
+
|
|
76
|
+
- `--dry-run` cannot be combined with `--wait` / `--confirm`
|
|
77
|
+
|
|
58
78
|
## Subgraph sources and endpoint policy
|
|
59
79
|
|
|
60
80
|
Canonical source aliases:
|
|
@@ -186,5 +206,12 @@ npm run typecheck
|
|
|
186
206
|
npm test
|
|
187
207
|
npm run build
|
|
188
208
|
npm run parity:check
|
|
209
|
+
npm run smoke:write-dryrun
|
|
189
210
|
npm run ag -- help
|
|
190
211
|
```
|
|
212
|
+
|
|
213
|
+
Write dry-run smoke test notes:
|
|
214
|
+
|
|
215
|
+
- `npm run smoke:write-dryrun` validates write paths without broadcasting any transaction.
|
|
216
|
+
- To run against an installed binary instead of local source:
|
|
217
|
+
- `AG_BIN=/absolute/path/to/ag npm run smoke:write-dryrun`
|
package/dist/commands/onchain.js
CHANGED
|
@@ -184,6 +184,10 @@ async function runOnchainSendWithFunction(ctx, forcedFunctionName, commandOverri
|
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
const waitForReceipt = (0, args_1.getFlagBoolean)(ctx.args.flags, "wait");
|
|
187
|
+
const dryRun = (0, args_1.getFlagBoolean)(ctx.args.flags, "dry-run");
|
|
188
|
+
if (dryRun && waitForReceipt) {
|
|
189
|
+
throw new errors_1.CliError("INVALID_ARGUMENT", "--dry-run cannot be combined with --wait.", 2);
|
|
190
|
+
}
|
|
187
191
|
const intent = {
|
|
188
192
|
idempotencyKey: (0, args_1.getFlagString)(ctx.args.flags, "idempotency-key"),
|
|
189
193
|
profileName: profile.name,
|
|
@@ -197,6 +201,7 @@ async function runOnchainSendWithFunction(ctx, forcedFunctionName, commandOverri
|
|
|
197
201
|
noncePolicy: noncePolicyRaw,
|
|
198
202
|
nonce,
|
|
199
203
|
waitForReceipt,
|
|
204
|
+
dryRun,
|
|
200
205
|
timeoutMs: parseTimeoutMs((0, args_1.getFlagString)(ctx.args.flags, "timeout-ms")),
|
|
201
206
|
command: commandOverride || `onchain send ${functionName}`,
|
|
202
207
|
};
|
package/dist/commands/tx.js
CHANGED
|
@@ -75,10 +75,14 @@ async function runTxSendCommand(ctx) {
|
|
|
75
75
|
const nonceValue = (0, args_1.getFlagString)(ctx.args.flags, "nonce");
|
|
76
76
|
const nonce = nonceValue ? parseNumberFlag(nonceValue, "--nonce", 0) : undefined;
|
|
77
77
|
const waitForReceipt = (0, args_1.getFlagBoolean)(ctx.args.flags, "wait") || (0, args_1.getFlagBoolean)(ctx.args.flags, "confirm");
|
|
78
|
+
const dryRun = (0, args_1.getFlagBoolean)(ctx.args.flags, "dry-run");
|
|
78
79
|
const timeoutMs = parseNumberFlag((0, args_1.getFlagString)(ctx.args.flags, "timeout-ms"), "--timeout-ms", 120000);
|
|
79
80
|
if (noncePolicy === "manual" && nonce === undefined) {
|
|
80
81
|
throw new errors_1.CliError("MISSING_NONCE", "--nonce is required when --nonce-policy=manual.", 2);
|
|
81
82
|
}
|
|
83
|
+
if (dryRun && waitForReceipt) {
|
|
84
|
+
throw new errors_1.CliError("INVALID_ARGUMENT", "--dry-run cannot be combined with --wait/--confirm.", 2);
|
|
85
|
+
}
|
|
82
86
|
const intent = {
|
|
83
87
|
idempotencyKey,
|
|
84
88
|
profileName: profile.name,
|
|
@@ -92,6 +96,7 @@ async function runTxSendCommand(ctx) {
|
|
|
92
96
|
noncePolicy,
|
|
93
97
|
nonce,
|
|
94
98
|
waitForReceipt,
|
|
99
|
+
dryRun,
|
|
95
100
|
timeoutMs,
|
|
96
101
|
command: "tx send",
|
|
97
102
|
};
|
package/dist/output.js
CHANGED
|
@@ -69,7 +69,7 @@ Core commands:
|
|
|
69
69
|
rpc check Verify RPC connectivity + signer backend health
|
|
70
70
|
|
|
71
71
|
Tx commands:
|
|
72
|
-
tx send Send a raw EVM transaction
|
|
72
|
+
tx send Send a raw EVM transaction (or simulate only with --dry-run)
|
|
73
73
|
tx status Read tx status by idempotency key/hash or list recent
|
|
74
74
|
tx resume Resume waiting for a previously submitted tx
|
|
75
75
|
tx watch Poll journal until tx is confirmed
|
|
@@ -79,7 +79,7 @@ Automation commands:
|
|
|
79
79
|
|
|
80
80
|
Power-user commands:
|
|
81
81
|
onchain call Call any ABI function from --abi-file
|
|
82
|
-
onchain send Send any ABI function as a transaction
|
|
82
|
+
onchain send Send any ABI function as a transaction (or simulate with --dry-run)
|
|
83
83
|
subgraph list|check|query List/check/query canonical Goldsky subgraphs
|
|
84
84
|
|
|
85
85
|
Domain namespaces:
|
|
@@ -112,6 +112,7 @@ Examples:
|
|
|
112
112
|
ag bootstrap --mode agent --profile prod --chain base --signer env:AGCLI_PRIVATE_KEY --json
|
|
113
113
|
AGCLI_KEYCHAIN_PASSPHRASE=... AGCLI_PRIVATE_KEY=0x... ag signer keychain import --account-id bot --private-key-env AGCLI_PRIVATE_KEY --json
|
|
114
114
|
ag tx send --profile prod --to 0xabc... --value-wei 1000000000000000 --wait --json
|
|
115
|
+
ag tx send --profile prod --to 0xabc... --value-wei 0 --dry-run --json
|
|
115
116
|
ag subgraph check --source core-base --json
|
|
116
117
|
ag baazaar listing active --kind erc721 --first 20 --json
|
|
117
118
|
ag auction active --first 20 --json
|
package/dist/tx-engine.js
CHANGED
|
@@ -72,9 +72,10 @@ async function waitForConfirmation(intent, ctx, txHash) {
|
|
|
72
72
|
async function executeTxIntent(intent, chain, customHome) {
|
|
73
73
|
const idempotencyKey = (0, idempotency_1.resolveIdempotencyKey)(intent);
|
|
74
74
|
const journal = new journal_1.JournalStore((0, config_1.resolveJournalPath)(customHome));
|
|
75
|
+
const dryRun = Boolean(intent.dryRun);
|
|
75
76
|
try {
|
|
76
77
|
const preflight = await (0, rpc_1.runRpcPreflight)(chain, intent.rpcUrl);
|
|
77
|
-
const existing = journal.getByIdempotencyKey(idempotencyKey);
|
|
78
|
+
const existing = dryRun ? undefined : journal.getByIdempotencyKey(idempotencyKey);
|
|
78
79
|
if (existing && existing.status === "confirmed") {
|
|
79
80
|
return mapJournalToResult(existing);
|
|
80
81
|
}
|
|
@@ -91,7 +92,13 @@ async function executeTxIntent(intent, chain, customHome) {
|
|
|
91
92
|
}
|
|
92
93
|
const viemChain = (0, chains_1.toViemChain)(chain, intent.rpcUrl);
|
|
93
94
|
const signerRuntime = await (0, signer_1.resolveSignerRuntime)(intent.signer, preflight.client, intent.rpcUrl, viemChain, customHome);
|
|
94
|
-
if (!signerRuntime.summary.
|
|
95
|
+
if (!signerRuntime.summary.address) {
|
|
96
|
+
throw new errors_1.CliError("MISSING_SIGNER_ADDRESS", "Signer address is required for simulation and transaction execution.", 2, {
|
|
97
|
+
signerType: signerRuntime.summary.signerType,
|
|
98
|
+
backendStatus: signerRuntime.summary.backendStatus,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (!dryRun && (!signerRuntime.summary.canSign || !signerRuntime.sendTransaction)) {
|
|
95
102
|
throw new errors_1.CliError("READONLY_SIGNER", "Selected signer cannot submit transactions.", 2, {
|
|
96
103
|
signerType: signerRuntime.summary.signerType,
|
|
97
104
|
backendStatus: signerRuntime.summary.backendStatus,
|
|
@@ -125,7 +132,7 @@ async function executeTxIntent(intent, chain, customHome) {
|
|
|
125
132
|
const maxPriorityFeePerGas = feeEstimate.maxPriorityFeePerGas;
|
|
126
133
|
const balanceWei = await preflight.client.getBalance({ address: fromAddress });
|
|
127
134
|
const requiredWei = (intent.valueWei || 0n) + gasLimit * (maxFeePerGas || 0n);
|
|
128
|
-
if (balanceWei < requiredWei) {
|
|
135
|
+
if (!dryRun && balanceWei < requiredWei) {
|
|
129
136
|
throw new errors_1.CliError("INSUFFICIENT_FUNDS_PRECHECK", "Account balance is below estimated transaction requirement.", 2, {
|
|
130
137
|
from: fromAddress,
|
|
131
138
|
balanceWei: balanceWei.toString(),
|
|
@@ -147,6 +154,31 @@ async function executeTxIntent(intent, chain, customHome) {
|
|
|
147
154
|
existing,
|
|
148
155
|
};
|
|
149
156
|
const nonce = await resolveNonce(intent, ctx, fromAddress);
|
|
157
|
+
if (dryRun) {
|
|
158
|
+
return {
|
|
159
|
+
idempotencyKey,
|
|
160
|
+
from: fromAddress,
|
|
161
|
+
to: toAddress,
|
|
162
|
+
nonce,
|
|
163
|
+
gasLimit: gasLimit.toString(),
|
|
164
|
+
maxFeePerGasWei: maxFeePerGas?.toString(),
|
|
165
|
+
maxPriorityFeePerGasWei: maxPriorityFeePerGas?.toString(),
|
|
166
|
+
status: "simulated",
|
|
167
|
+
dryRun: true,
|
|
168
|
+
simulation: {
|
|
169
|
+
requiredWei: requiredWei.toString(),
|
|
170
|
+
balanceWei: balanceWei.toString(),
|
|
171
|
+
signerCanSign: signerRuntime.summary.canSign,
|
|
172
|
+
noncePolicy: intent.noncePolicy,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (!signerRuntime.sendTransaction) {
|
|
177
|
+
throw new errors_1.CliError("READONLY_SIGNER", "Selected signer cannot submit transactions.", 2, {
|
|
178
|
+
signerType: signerRuntime.summary.signerType,
|
|
179
|
+
backendStatus: signerRuntime.summary.backendStatus,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
150
182
|
journal.upsertPrepared({
|
|
151
183
|
idempotencyKey,
|
|
152
184
|
profileName: intent.profileName,
|
|
@@ -208,14 +240,18 @@ async function executeTxIntent(intent, chain, customHome) {
|
|
|
208
240
|
}
|
|
209
241
|
catch (error) {
|
|
210
242
|
if (error instanceof errors_1.CliError) {
|
|
211
|
-
|
|
243
|
+
if (!dryRun) {
|
|
244
|
+
journal.markFailed(idempotencyKey, error.code, error.message);
|
|
245
|
+
}
|
|
212
246
|
throw error;
|
|
213
247
|
}
|
|
214
248
|
const unknown = new errors_1.CliError("TX_EXECUTION_FAILED", "Transaction execution failed.", 1, {
|
|
215
249
|
correlationId: (0, crypto_1.randomUUID)(),
|
|
216
250
|
message: error instanceof Error ? error.message : String(error),
|
|
217
251
|
});
|
|
218
|
-
|
|
252
|
+
if (!dryRun) {
|
|
253
|
+
journal.markFailed(idempotencyKey, unknown.code, unknown.message);
|
|
254
|
+
}
|
|
219
255
|
throw unknown;
|
|
220
256
|
}
|
|
221
257
|
finally {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aavegotchi-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Agent-first CLI for automating Aavegotchi app and onchain workflows",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"test": "vitest run",
|
|
31
31
|
"test:watch": "vitest",
|
|
32
32
|
"parity:check": "node scripts/check-parity.mjs",
|
|
33
|
+
"smoke:write-dryrun": "bash scripts/smoke-write-dryrun.sh",
|
|
33
34
|
"ag": "tsx src/index.ts",
|
|
34
35
|
"prepack": "npm run build",
|
|
35
36
|
"bootstrap:smoke": "AGCLI_HOME=/tmp/agcli-smoke tsx src/index.ts bootstrap --mode agent --profile smoke --chain base --signer readonly --json"
|