aavegotchi-cli 0.2.2 → 0.2.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.
@@ -0,0 +1,815 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAuctionBidCommand = runAuctionBidCommand;
4
+ exports.runAuctionBidUnbidCommand = runAuctionBidUnbidCommand;
5
+ const viem_1 = require("viem");
6
+ const args_1 = require("../args");
7
+ const chains_1 = require("../chains");
8
+ const config_1 = require("../config");
9
+ const errors_1 = require("../errors");
10
+ const profile_env_1 = require("../profile-env");
11
+ const rpc_1 = require("../rpc");
12
+ const signer_1 = require("../signer");
13
+ const normalize_1 = require("../subgraph/normalize");
14
+ const client_1 = require("../subgraph/client");
15
+ const queries_1 = require("../subgraph/queries");
16
+ const sources_1 = require("../subgraph/sources");
17
+ const tx_engine_1 = require("../tx-engine");
18
+ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
19
+ const GHST_DECIMALS = 18;
20
+ const BATCH_DEFAULT_FIRST = 200;
21
+ const BATCH_MAX_FIRST = 200;
22
+ // Canonical addresses from aavegotchi-base deployments.
23
+ const GHST_BY_CHAIN_ID = {
24
+ 8453: "0xcd2f22236dd9dfe2356d7c543161d4d260fd9bcb",
25
+ 84532: "0xe97f36a00058aa7dfc4e85d23532c3f70453a7ae",
26
+ };
27
+ const GBM_DIAMOND_BY_CHAIN_ID = {
28
+ 8453: sources_1.BASE_GBM_DIAMOND,
29
+ 84532: "0x8572ce8ad6c9788bb6da3509117646047dd8b543",
30
+ };
31
+ const GBM_BID_WRITE_ABI = (0, viem_1.parseAbi)(["function commitBid(uint256,uint256,uint256,address,uint256,uint256,bytes)"]);
32
+ const GBM_AUCTION_READ_ABI = (0, viem_1.parseAbi)([
33
+ "function getAuctionHighestBid(uint256 _auctionId) view returns (uint256)",
34
+ "function getAuctionHighestBidder(uint256 _auctionId) view returns (address)",
35
+ "function getContractAddress(uint256 _auctionId) view returns (address)",
36
+ "function getTokenId(uint256 _auctionId) view returns (uint256)",
37
+ "function getAuctionStartTime(uint256 _auctionId) view returns (uint256)",
38
+ "function getAuctionEndTime(uint256 _auctionId) view returns (uint256)",
39
+ "function getAuctionIncMin(uint256 _auctionId) view returns (uint64)",
40
+ ]);
41
+ const ERC20_ABI = (0, viem_1.parseAbi)([
42
+ "function balanceOf(address) view returns (uint256)",
43
+ "function allowance(address,address) view returns (uint256)",
44
+ "function approve(address,uint256) returns (bool)",
45
+ ]);
46
+ function parseAddress(value, hint) {
47
+ if (typeof value !== "string" || !/^0x[a-fA-F0-9]{40}$/.test(value)) {
48
+ throw new errors_1.CliError("INVALID_ARGUMENT", `${hint} must be a valid EVM address.`, 2, { value });
49
+ }
50
+ return value.toLowerCase();
51
+ }
52
+ function parseAuctionId(value, flagName) {
53
+ if (!value) {
54
+ throw new errors_1.CliError("MISSING_ARGUMENT", `${flagName} is required.`, 2);
55
+ }
56
+ if (!/^\d+$/.test(value)) {
57
+ throw new errors_1.CliError("INVALID_ARGUMENT", `${flagName} must be an unsigned integer string.`, 2, { value });
58
+ }
59
+ return value;
60
+ }
61
+ function parseNonNegativeBigint(value, label) {
62
+ if (!/^\d+$/.test(value)) {
63
+ throw new errors_1.CliError("INVALID_ARGUMENT", `${label} must be a non-negative integer string.`, 2, { value });
64
+ }
65
+ return BigInt(value);
66
+ }
67
+ function parseGhstAmount(value, label, allowZero = false) {
68
+ if (!/^\d+(\.\d{1,18})?$/.test(value)) {
69
+ throw new errors_1.CliError("INVALID_ARGUMENT", `${label} must be a decimal GHST amount (up to 18 decimals).`, 2, {
70
+ value,
71
+ });
72
+ }
73
+ const parsed = (0, viem_1.parseUnits)(value, GHST_DECIMALS);
74
+ if (!allowZero && parsed <= 0n) {
75
+ throw new errors_1.CliError("INVALID_ARGUMENT", `${label} must be greater than 0.`, 2, { value });
76
+ }
77
+ return parsed;
78
+ }
79
+ function parseAmountWeiFromFlags(flags, options) {
80
+ const weiRaw = (0, args_1.getFlagString)(flags, options.weiKey);
81
+ const ghstRaw = (0, args_1.getFlagString)(flags, options.ghstKey);
82
+ if (!weiRaw && !ghstRaw) {
83
+ throw new errors_1.CliError("MISSING_ARGUMENT", `${options.label} is required. Use --${options.ghstKey} <amount> or --${options.weiKey} <wei>.`, 2);
84
+ }
85
+ if (weiRaw && ghstRaw) {
86
+ throw new errors_1.CliError("INVALID_ARGUMENT", `Use either --${options.ghstKey} or --${options.weiKey}, not both.`, 2);
87
+ }
88
+ const parsed = weiRaw ? parseNonNegativeBigint(weiRaw, `--${options.weiKey}`) : parseGhstAmount(ghstRaw, `--${options.ghstKey}`);
89
+ if (!options.allowZero && parsed <= 0n) {
90
+ throw new errors_1.CliError("INVALID_ARGUMENT", `${options.label} must be greater than 0.`, 2);
91
+ }
92
+ return parsed;
93
+ }
94
+ function parseOptionalExpectedHighestBid(flags) {
95
+ const expectedWei = (0, args_1.getFlagString)(flags, "expected-highest-bid-wei");
96
+ const expectedGhst = (0, args_1.getFlagString)(flags, "expected-highest-bid-ghst");
97
+ if (!expectedWei && !expectedGhst) {
98
+ return undefined;
99
+ }
100
+ if (expectedWei && expectedGhst) {
101
+ throw new errors_1.CliError("INVALID_ARGUMENT", "Use either --expected-highest-bid-ghst or --expected-highest-bid-wei, not both.", 2);
102
+ }
103
+ return expectedWei
104
+ ? parseNonNegativeBigint(expectedWei, "--expected-highest-bid-wei")
105
+ : parseGhstAmount(expectedGhst, "--expected-highest-bid-ghst", true);
106
+ }
107
+ function parseNoncePolicy(flags) {
108
+ const noncePolicy = (0, args_1.getFlagString)(flags, "nonce-policy") || "safe";
109
+ if (noncePolicy !== "safe" && noncePolicy !== "replace" && noncePolicy !== "manual") {
110
+ throw new errors_1.CliError("INVALID_NONCE_POLICY", `Unsupported nonce policy '${noncePolicy}'.`, 2);
111
+ }
112
+ return noncePolicy;
113
+ }
114
+ function parseNonce(flags) {
115
+ const raw = (0, args_1.getFlagString)(flags, "nonce");
116
+ if (!raw) {
117
+ return undefined;
118
+ }
119
+ const parsed = Number(raw);
120
+ if (!Number.isInteger(parsed) || parsed < 0) {
121
+ throw new errors_1.CliError("INVALID_ARGUMENT", "--nonce must be a non-negative integer.", 2, {
122
+ value: raw,
123
+ });
124
+ }
125
+ return parsed;
126
+ }
127
+ function parseTimeoutMs(flags) {
128
+ const raw = (0, args_1.getFlagString)(flags, "timeout-ms");
129
+ if (!raw) {
130
+ return 120000;
131
+ }
132
+ const parsed = Number(raw);
133
+ if (!Number.isInteger(parsed) || parsed <= 0) {
134
+ throw new errors_1.CliError("INVALID_ARGUMENT", "--timeout-ms must be a positive integer.", 2, {
135
+ value: raw,
136
+ });
137
+ }
138
+ return parsed;
139
+ }
140
+ function parseBoundedIntFlag(value, flagName, fallback, min, max) {
141
+ if (!value) {
142
+ return fallback;
143
+ }
144
+ const parsed = Number(value);
145
+ if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
146
+ throw new errors_1.CliError("INVALID_ARGUMENT", `${flagName} must be an integer between ${min} and ${max}.`, 2, {
147
+ value,
148
+ });
149
+ }
150
+ return parsed;
151
+ }
152
+ function classifyRevert(message) {
153
+ const normalized = message.toLowerCase();
154
+ if (normalized.includes("allowance")) {
155
+ return {
156
+ reasonCode: "INSUFFICIENT_ALLOWANCE",
157
+ reason: "Allowance is below required amount for transferFrom.",
158
+ };
159
+ }
160
+ if ((normalized.includes("bid") && normalized.includes("low")) || normalized.includes("start bid")) {
161
+ return {
162
+ reasonCode: "BID_BELOW_START",
163
+ reason: "Bid amount is below minimum required value.",
164
+ };
165
+ }
166
+ if (normalized.includes("auction") &&
167
+ (normalized.includes("ended") ||
168
+ normalized.includes("not active") ||
169
+ normalized.includes("already") ||
170
+ normalized.includes("state"))) {
171
+ return {
172
+ reasonCode: "AUCTION_STATE_CHANGED",
173
+ reason: "Auction state changed between preflight and submit.",
174
+ };
175
+ }
176
+ return {
177
+ reasonCode: "UNKNOWN_REVERT",
178
+ reason: "Revert reason could not be classified.",
179
+ };
180
+ }
181
+ function throwPreflightError(reasonCode, message, checks, details) {
182
+ throw new errors_1.CliError(reasonCode, message, 2, {
183
+ reasonCode,
184
+ checks,
185
+ ...(details || {}),
186
+ });
187
+ }
188
+ function parseBatchIdempotencyKey(base, auctionId, amountWei) {
189
+ if (base) {
190
+ return `${base}:${auctionId}:${amountWei.toString()}`;
191
+ }
192
+ return `auction.bid-unbid:${auctionId}:${amountWei.toString()}`;
193
+ }
194
+ async function resolveBidContext(ctx) {
195
+ const config = (0, config_1.loadConfig)();
196
+ const profileName = (0, args_1.getFlagString)(ctx.args.flags, "profile") || ctx.globals.profile;
197
+ const profile = (0, config_1.getProfileOrThrow)(config, profileName);
198
+ const policy = (0, config_1.getPolicyOrThrow)(config, profile.policy);
199
+ const environment = (0, profile_env_1.applyProfileEnvironment)(profile);
200
+ const chain = (0, chains_1.resolveChain)(profile.chain);
201
+ const rpcUrl = (0, chains_1.resolveRpcUrl)(chain, (0, args_1.getFlagString)(ctx.args.flags, "rpc-url") || profile.rpcUrl);
202
+ const preflight = await (0, rpc_1.runRpcPreflight)(chain, rpcUrl);
203
+ const signerRuntime = await (0, signer_1.resolveSignerRuntime)(profile.signer, preflight.client, rpcUrl, (0, chains_1.toViemChain)(chain, rpcUrl));
204
+ const signerAddress = signerRuntime.summary.address;
205
+ if (!signerAddress) {
206
+ throw new errors_1.CliError("MISSING_SIGNER_ADDRESS", "Signer address is required for auction bidding.", 2, {
207
+ signerType: signerRuntime.summary.signerType,
208
+ backendStatus: signerRuntime.summary.backendStatus,
209
+ });
210
+ }
211
+ const gbmDiamond = GBM_DIAMOND_BY_CHAIN_ID[chain.chainId];
212
+ const ghstToken = GHST_BY_CHAIN_ID[chain.chainId];
213
+ if (!gbmDiamond || !ghstToken) {
214
+ throw new errors_1.CliError("UNSUPPORTED_CHAIN", `auction bid currently supports chains ${Object.keys(GBM_DIAMOND_BY_CHAIN_ID).join(", ")}.`, 2, {
215
+ chainId: chain.chainId,
216
+ });
217
+ }
218
+ return {
219
+ profileName: profile.name,
220
+ policy,
221
+ chain,
222
+ rpcUrl,
223
+ signer: profile.signer,
224
+ signerAddress,
225
+ gbmDiamond,
226
+ ghstToken,
227
+ environment,
228
+ };
229
+ }
230
+ async function fetchAuctionFromSubgraph(auctionId) {
231
+ const response = await (0, client_1.executeSubgraphQuery)({
232
+ source: "gbm-base",
233
+ queryName: "auction.bid.preflight",
234
+ query: queries_1.GBM_AUCTION_BY_ID_QUERY,
235
+ variables: { id: auctionId },
236
+ });
237
+ if (!response.data.auction) {
238
+ throw new errors_1.CliError("AUCTION_NOT_FOUND", `Auction '${auctionId}' was not found in the GBM subgraph.`, 2, {
239
+ auctionId,
240
+ source: response.source,
241
+ endpoint: response.endpoint,
242
+ });
243
+ }
244
+ return (0, normalize_1.normalizeGbmAuction)(response.data.auction);
245
+ }
246
+ async function readOnchainSnapshot(bidContext, auctionId) {
247
+ const preflight = await (0, rpc_1.runRpcPreflight)(bidContext.chain, bidContext.rpcUrl);
248
+ const id = BigInt(auctionId);
249
+ const [highestBidWeiRaw, highestBidderRaw, contractAddressRaw, tokenIdRaw, startsAtRaw, endsAtRaw, incMinRaw] = await Promise.all([
250
+ preflight.client.readContract({
251
+ address: bidContext.gbmDiamond,
252
+ abi: GBM_AUCTION_READ_ABI,
253
+ functionName: "getAuctionHighestBid",
254
+ args: [id],
255
+ }),
256
+ preflight.client.readContract({
257
+ address: bidContext.gbmDiamond,
258
+ abi: GBM_AUCTION_READ_ABI,
259
+ functionName: "getAuctionHighestBidder",
260
+ args: [id],
261
+ }),
262
+ preflight.client.readContract({
263
+ address: bidContext.gbmDiamond,
264
+ abi: GBM_AUCTION_READ_ABI,
265
+ functionName: "getContractAddress",
266
+ args: [id],
267
+ }),
268
+ preflight.client.readContract({
269
+ address: bidContext.gbmDiamond,
270
+ abi: GBM_AUCTION_READ_ABI,
271
+ functionName: "getTokenId",
272
+ args: [id],
273
+ }),
274
+ preflight.client.readContract({
275
+ address: bidContext.gbmDiamond,
276
+ abi: GBM_AUCTION_READ_ABI,
277
+ functionName: "getAuctionStartTime",
278
+ args: [id],
279
+ }),
280
+ preflight.client.readContract({
281
+ address: bidContext.gbmDiamond,
282
+ abi: GBM_AUCTION_READ_ABI,
283
+ functionName: "getAuctionEndTime",
284
+ args: [id],
285
+ }),
286
+ preflight.client.readContract({
287
+ address: bidContext.gbmDiamond,
288
+ abi: GBM_AUCTION_READ_ABI,
289
+ functionName: "getAuctionIncMin",
290
+ args: [id],
291
+ }),
292
+ ]);
293
+ return {
294
+ auctionId,
295
+ highestBidWei: highestBidWeiRaw,
296
+ highestBidder: parseAddress(highestBidderRaw, "auction highest bidder"),
297
+ contractAddress: parseAddress(contractAddressRaw, "auction token contract"),
298
+ tokenId: tokenIdRaw,
299
+ startsAt: startsAtRaw,
300
+ endsAt: endsAtRaw,
301
+ incMin: incMinRaw,
302
+ };
303
+ }
304
+ function computeMinimumBidWei(snapshot, startBidPriceWei) {
305
+ if (snapshot.highestBidWei > 0n) {
306
+ const denominator = 10000n;
307
+ return (snapshot.highestBidWei * (denominator + snapshot.incMin)) / denominator;
308
+ }
309
+ return startBidPriceWei;
310
+ }
311
+ function mapSimulationError(error) {
312
+ if (error.code !== "SIMULATION_REVERT") {
313
+ return error;
314
+ }
315
+ const message = String(error.details?.message || "");
316
+ const classified = classifyRevert(message);
317
+ return new errors_1.CliError("AUCTION_BID_REVERT", "Auction bid simulation reverted.", 2, {
318
+ reasonCode: classified.reasonCode,
319
+ reason: classified.reason,
320
+ rawMessage: message,
321
+ });
322
+ }
323
+ async function runSingleBid(ctx, options) {
324
+ const dryRun = (0, args_1.getFlagBoolean)(ctx.args.flags, "dry-run");
325
+ const waitForReceipt = (0, args_1.getFlagBoolean)(ctx.args.flags, "wait");
326
+ const autoApprove = (0, args_1.getFlagBoolean)(ctx.args.flags, "auto-approve");
327
+ const requireUnbid = options?.forceRequireUnbid ?? (0, args_1.getFlagBoolean)(ctx.args.flags, "require-unbid");
328
+ const timeoutMs = parseTimeoutMs(ctx.args.flags);
329
+ const noncePolicy = parseNoncePolicy(ctx.args.flags);
330
+ const nonce = parseNonce(ctx.args.flags);
331
+ if (dryRun && waitForReceipt) {
332
+ throw new errors_1.CliError("INVALID_ARGUMENT", "--dry-run cannot be combined with --wait.", 2);
333
+ }
334
+ if (noncePolicy === "manual" && nonce === undefined) {
335
+ throw new errors_1.CliError("MISSING_NONCE", "--nonce is required when --nonce-policy=manual.", 2);
336
+ }
337
+ const auctionId = options?.forceAuctionId || parseAuctionId((0, args_1.getFlagString)(ctx.args.flags, "auction-id"), "--auction-id");
338
+ const amountWei = options?.forceAmountWei ||
339
+ parseAmountWeiFromFlags(ctx.args.flags, {
340
+ weiKey: "amount-wei",
341
+ ghstKey: "amount-ghst",
342
+ label: "Bid amount",
343
+ });
344
+ const expectedHighestBidWei = options?.forceExpectedHighestBidWei ?? parseOptionalExpectedHighestBid(ctx.args.flags);
345
+ const idempotencyKey = options?.forceIdempotencyKey || (0, args_1.getFlagString)(ctx.args.flags, "idempotency-key");
346
+ const autoApproveMaxWei = (0, args_1.getFlagString)(ctx.args.flags, "auto-approve-max-wei")
347
+ ? parseNonNegativeBigint((0, args_1.getFlagString)(ctx.args.flags, "auto-approve-max-wei"), "--auto-approve-max-wei")
348
+ : (0, args_1.getFlagString)(ctx.args.flags, "auto-approve-max-ghst")
349
+ ? parseGhstAmount((0, args_1.getFlagString)(ctx.args.flags, "auto-approve-max-ghst"), "--auto-approve-max-ghst")
350
+ : undefined;
351
+ const bidContext = await resolveBidContext(ctx);
352
+ const subgraphAuction = await fetchAuctionFromSubgraph(auctionId);
353
+ const startBidPriceWei = parseNonNegativeBigint(subgraphAuction.startBidPrice || "0", "startBidPrice");
354
+ const quantity = parseNonNegativeBigint(subgraphAuction.quantity, "quantity");
355
+ const nowSec = BigInt(Math.floor(Date.now() / 1000));
356
+ const checks = [];
357
+ const onchainBefore = await readOnchainSnapshot(bidContext, auctionId);
358
+ const minimumBidWei = computeMinimumBidWei(onchainBefore, startBidPriceWei);
359
+ if (onchainBefore.startsAt <= nowSec && nowSec < onchainBefore.endsAt) {
360
+ checks.push({ check: "AUCTION_OPEN", status: "pass" });
361
+ }
362
+ else {
363
+ checks.push({
364
+ check: "AUCTION_OPEN",
365
+ status: "fail",
366
+ reasonCode: "AUCTION_NOT_OPEN",
367
+ details: {
368
+ nowSec: nowSec.toString(),
369
+ startsAt: onchainBefore.startsAt.toString(),
370
+ endsAt: onchainBefore.endsAt.toString(),
371
+ },
372
+ });
373
+ throwPreflightError("AUCTION_NOT_OPEN", "Auction is not currently open for bidding.", checks, {
374
+ auctionId,
375
+ });
376
+ }
377
+ if (requireUnbid) {
378
+ const unbid = onchainBefore.highestBidWei === 0n && onchainBefore.highestBidder === ZERO_ADDRESS;
379
+ if (!unbid) {
380
+ checks.push({
381
+ check: "REQUIRE_UNBID",
382
+ status: "fail",
383
+ reasonCode: "AUCTION_ALREADY_BID",
384
+ details: {
385
+ highestBidWei: onchainBefore.highestBidWei.toString(),
386
+ highestBidder: onchainBefore.highestBidder,
387
+ },
388
+ });
389
+ throwPreflightError("AUCTION_ALREADY_BID", "Auction already has a highest bid.", checks, {
390
+ auctionId,
391
+ });
392
+ }
393
+ checks.push({ check: "REQUIRE_UNBID", status: "pass" });
394
+ }
395
+ else {
396
+ checks.push({ check: "REQUIRE_UNBID", status: "skip" });
397
+ }
398
+ if (expectedHighestBidWei !== undefined) {
399
+ if (onchainBefore.highestBidWei !== expectedHighestBidWei) {
400
+ checks.push({
401
+ check: "EXPECTED_HIGHEST_BID",
402
+ status: "fail",
403
+ reasonCode: "EXPECTED_HIGHEST_BID_MISMATCH",
404
+ details: {
405
+ expectedHighestBidWei: expectedHighestBidWei.toString(),
406
+ currentHighestBidWei: onchainBefore.highestBidWei.toString(),
407
+ },
408
+ });
409
+ throwPreflightError("EXPECTED_HIGHEST_BID_MISMATCH", "Current highest bid does not match expected value.", checks, {
410
+ auctionId,
411
+ });
412
+ }
413
+ checks.push({ check: "EXPECTED_HIGHEST_BID", status: "pass" });
414
+ }
415
+ else {
416
+ checks.push({ check: "EXPECTED_HIGHEST_BID", status: "skip" });
417
+ }
418
+ if (amountWei >= minimumBidWei) {
419
+ checks.push({ check: "BID_MINIMUM", status: "pass" });
420
+ }
421
+ else {
422
+ checks.push({
423
+ check: "BID_MINIMUM",
424
+ status: "fail",
425
+ reasonCode: "BID_BELOW_START",
426
+ details: {
427
+ amountWei: amountWei.toString(),
428
+ minimumBidWei: minimumBidWei.toString(),
429
+ highestBidWei: onchainBefore.highestBidWei.toString(),
430
+ startBidPriceWei: startBidPriceWei.toString(),
431
+ },
432
+ });
433
+ throwPreflightError("BID_BELOW_START", "Bid amount is below the current minimum bid requirement.", checks, {
434
+ auctionId,
435
+ });
436
+ }
437
+ const preflight = await (0, rpc_1.runRpcPreflight)(bidContext.chain, bidContext.rpcUrl);
438
+ const [ghstBalanceWeiRaw, allowanceWeiRaw] = await Promise.all([
439
+ preflight.client.readContract({
440
+ address: bidContext.ghstToken,
441
+ abi: ERC20_ABI,
442
+ functionName: "balanceOf",
443
+ args: [bidContext.signerAddress],
444
+ }),
445
+ preflight.client.readContract({
446
+ address: bidContext.ghstToken,
447
+ abi: ERC20_ABI,
448
+ functionName: "allowance",
449
+ args: [bidContext.signerAddress, bidContext.gbmDiamond],
450
+ }),
451
+ ]);
452
+ const ghstBalanceWei = ghstBalanceWeiRaw;
453
+ const allowanceWei = allowanceWeiRaw;
454
+ if (ghstBalanceWei >= amountWei) {
455
+ checks.push({ check: "GHST_BALANCE", status: "pass" });
456
+ }
457
+ else {
458
+ checks.push({
459
+ check: "GHST_BALANCE",
460
+ status: "fail",
461
+ reasonCode: "INSUFFICIENT_GHST_BALANCE",
462
+ details: {
463
+ balanceWei: ghstBalanceWei.toString(),
464
+ requiredWei: amountWei.toString(),
465
+ },
466
+ });
467
+ throwPreflightError("INSUFFICIENT_GHST_BALANCE", "GHST balance is below bid amount.", checks, {
468
+ auctionId,
469
+ signer: bidContext.signerAddress,
470
+ });
471
+ }
472
+ let approval;
473
+ let approvalNeeded = allowanceWei < amountWei;
474
+ if (approvalNeeded) {
475
+ if (!autoApprove) {
476
+ checks.push({
477
+ check: "GHST_ALLOWANCE",
478
+ status: "fail",
479
+ reasonCode: "INSUFFICIENT_ALLOWANCE",
480
+ details: {
481
+ allowanceWei: allowanceWei.toString(),
482
+ requiredWei: amountWei.toString(),
483
+ },
484
+ });
485
+ throwPreflightError("INSUFFICIENT_ALLOWANCE", "GHST allowance is below bid amount. Re-run with --auto-approve to submit approve() automatically.", checks, {
486
+ auctionId,
487
+ ghstToken: bidContext.ghstToken,
488
+ spender: bidContext.gbmDiamond,
489
+ });
490
+ }
491
+ const approveAmountWei = autoApproveMaxWei ?? amountWei;
492
+ if (approveAmountWei < amountWei) {
493
+ throw new errors_1.CliError("AUTO_APPROVE_LIMIT_TOO_LOW", "auto-approve cap is below required bid amount.", 2, {
494
+ approveAmountWei: approveAmountWei.toString(),
495
+ requiredWei: amountWei.toString(),
496
+ });
497
+ }
498
+ checks.push({
499
+ check: "GHST_ALLOWANCE",
500
+ status: "auto-fixed",
501
+ reasonCode: "INSUFFICIENT_ALLOWANCE",
502
+ details: {
503
+ allowanceWei: allowanceWei.toString(),
504
+ requiredWei: amountWei.toString(),
505
+ approveAmountWei: approveAmountWei.toString(),
506
+ },
507
+ });
508
+ const approveData = (0, viem_1.encodeFunctionData)({
509
+ abi: ERC20_ABI,
510
+ functionName: "approve",
511
+ args: [bidContext.gbmDiamond, approveAmountWei],
512
+ });
513
+ const approveIntent = {
514
+ idempotencyKey: idempotencyKey ? `${idempotencyKey}:approve` : undefined,
515
+ profileName: bidContext.profileName,
516
+ chainId: bidContext.chain.chainId,
517
+ rpcUrl: bidContext.rpcUrl,
518
+ signer: bidContext.signer,
519
+ policy: bidContext.policy,
520
+ to: bidContext.ghstToken,
521
+ data: approveData,
522
+ noncePolicy: "safe",
523
+ waitForReceipt: dryRun ? false : true,
524
+ dryRun,
525
+ timeoutMs,
526
+ command: "auction approve-ghst",
527
+ };
528
+ try {
529
+ const approvalResult = await (0, tx_engine_1.executeTxIntent)(approveIntent, bidContext.chain);
530
+ approval = {
531
+ autoApprove: true,
532
+ approveAmountWei: approveAmountWei.toString(),
533
+ result: approvalResult,
534
+ };
535
+ }
536
+ catch (error) {
537
+ if (error instanceof errors_1.CliError) {
538
+ throw mapSimulationError(error);
539
+ }
540
+ throw error;
541
+ }
542
+ }
543
+ else {
544
+ checks.push({ check: "GHST_ALLOWANCE", status: "pass" });
545
+ }
546
+ if (!dryRun) {
547
+ const onchainBeforeSend = await readOnchainSnapshot(bidContext, auctionId);
548
+ if (onchainBeforeSend.highestBidWei !== onchainBefore.highestBidWei ||
549
+ onchainBeforeSend.highestBidder !== onchainBefore.highestBidder) {
550
+ throw new errors_1.CliError("AUCTION_STATE_CHANGED", "Auction state changed before submit; aborting.", 2, {
551
+ reasonCode: "AUCTION_STATE_CHANGED",
552
+ previousHighestBidWei: onchainBefore.highestBidWei.toString(),
553
+ currentHighestBidWei: onchainBeforeSend.highestBidWei.toString(),
554
+ previousHighestBidder: onchainBefore.highestBidder,
555
+ currentHighestBidder: onchainBeforeSend.highestBidder,
556
+ auctionId,
557
+ });
558
+ }
559
+ }
560
+ const commitArgs = [
561
+ BigInt(auctionId),
562
+ amountWei,
563
+ onchainBefore.highestBidWei,
564
+ onchainBefore.contractAddress,
565
+ onchainBefore.tokenId,
566
+ quantity,
567
+ "0x",
568
+ ];
569
+ if (dryRun && approvalNeeded) {
570
+ return {
571
+ profile: bidContext.profileName,
572
+ chainId: bidContext.chain.chainId,
573
+ command: "auction bid",
574
+ environment: bidContext.environment,
575
+ auction: {
576
+ id: auctionId,
577
+ gbmDiamond: bidContext.gbmDiamond,
578
+ tokenContract: onchainBefore.contractAddress,
579
+ tokenId: onchainBefore.tokenId.toString(),
580
+ quantity: quantity.toString(),
581
+ startBidPriceWei: startBidPriceWei.toString(),
582
+ highestBidWei: onchainBefore.highestBidWei.toString(),
583
+ highestBidder: onchainBefore.highestBidder,
584
+ minBidWei: minimumBidWei.toString(),
585
+ },
586
+ preflight: {
587
+ checks,
588
+ signer: bidContext.signerAddress,
589
+ ghstToken: bidContext.ghstToken,
590
+ balanceWei: ghstBalanceWei.toString(),
591
+ allowanceWei: allowanceWei.toString(),
592
+ amountWei: amountWei.toString(),
593
+ },
594
+ ...(approval ? { approval } : {}),
595
+ result: {
596
+ status: "simulated",
597
+ dryRun: true,
598
+ skippedBidSimulation: true,
599
+ reasonCode: "INSUFFICIENT_ALLOWANCE",
600
+ reason: "Approval was simulated, so bid simulation is skipped in dry-run mode.",
601
+ commitBidArgs: commitArgs,
602
+ },
603
+ };
604
+ }
605
+ const bidData = (0, viem_1.encodeFunctionData)({
606
+ abi: GBM_BID_WRITE_ABI,
607
+ functionName: "commitBid",
608
+ args: commitArgs,
609
+ });
610
+ const bidIntent = {
611
+ idempotencyKey,
612
+ profileName: bidContext.profileName,
613
+ chainId: bidContext.chain.chainId,
614
+ rpcUrl: bidContext.rpcUrl,
615
+ signer: bidContext.signer,
616
+ policy: bidContext.policy,
617
+ to: bidContext.gbmDiamond,
618
+ data: bidData,
619
+ noncePolicy,
620
+ nonce,
621
+ waitForReceipt,
622
+ dryRun,
623
+ timeoutMs,
624
+ command: "auction bid",
625
+ };
626
+ let bidResult;
627
+ try {
628
+ bidResult = await (0, tx_engine_1.executeTxIntent)(bidIntent, bidContext.chain);
629
+ }
630
+ catch (error) {
631
+ if (error instanceof errors_1.CliError) {
632
+ throw mapSimulationError(error);
633
+ }
634
+ throw error;
635
+ }
636
+ return {
637
+ profile: bidContext.profileName,
638
+ chainId: bidContext.chain.chainId,
639
+ command: "auction bid",
640
+ environment: bidContext.environment,
641
+ auction: {
642
+ id: auctionId,
643
+ gbmDiamond: bidContext.gbmDiamond,
644
+ tokenContract: onchainBefore.contractAddress,
645
+ tokenId: onchainBefore.tokenId.toString(),
646
+ quantity: quantity.toString(),
647
+ startBidPriceWei: startBidPriceWei.toString(),
648
+ highestBidWei: onchainBefore.highestBidWei.toString(),
649
+ highestBidder: onchainBefore.highestBidder,
650
+ minBidWei: minimumBidWei.toString(),
651
+ },
652
+ preflight: {
653
+ checks,
654
+ signer: bidContext.signerAddress,
655
+ ghstToken: bidContext.ghstToken,
656
+ balanceWei: ghstBalanceWei.toString(),
657
+ allowanceWei: allowanceWei.toString(),
658
+ amountWei: amountWei.toString(),
659
+ },
660
+ ...(approval ? { approval } : {}),
661
+ result: bidResult,
662
+ };
663
+ }
664
+ async function runAuctionBidCommand(ctx) {
665
+ return runSingleBid(ctx);
666
+ }
667
+ async function runAuctionBidUnbidCommand(ctx) {
668
+ const amountWei = parseAmountWeiFromFlags(ctx.args.flags, {
669
+ weiKey: "amount-wei",
670
+ ghstKey: "amount-ghst",
671
+ label: "Bid amount",
672
+ });
673
+ const maxTotalWei = parseAmountWeiFromFlags(ctx.args.flags, {
674
+ weiKey: "max-total-wei",
675
+ ghstKey: "max-total-ghst",
676
+ label: "Max total bid amount",
677
+ });
678
+ if (maxTotalWei < amountWei) {
679
+ throw new errors_1.CliError("INVALID_ARGUMENT", "max total amount must be greater than or equal to amount per auction.", 2, {
680
+ amountWei: amountWei.toString(),
681
+ maxTotalWei: maxTotalWei.toString(),
682
+ });
683
+ }
684
+ const first = parseBoundedIntFlag((0, args_1.getFlagString)(ctx.args.flags, "first"), "--first", BATCH_DEFAULT_FIRST, 1, BATCH_MAX_FIRST);
685
+ const skip = parseBoundedIntFlag((0, args_1.getFlagString)(ctx.args.flags, "skip"), "--skip", 0, 0, 100000);
686
+ const now = Math.floor(Date.now() / 1000).toString();
687
+ const response = await (0, client_1.executeSubgraphQuery)({
688
+ source: "gbm-base",
689
+ queryName: "auction.bid-unbid.active",
690
+ query: queries_1.GBM_ACTIVE_AUCTIONS_QUERY,
691
+ variables: {
692
+ now,
693
+ first,
694
+ skip,
695
+ },
696
+ });
697
+ if (!Array.isArray(response.data.auctions)) {
698
+ throw new errors_1.CliError("SUBGRAPH_INVALID_RESPONSE", "Expected auctions to be an array.", 2, {
699
+ source: response.source,
700
+ endpoint: response.endpoint,
701
+ queryName: response.queryName,
702
+ });
703
+ }
704
+ const activeAuctions = (0, normalize_1.normalizeGbmAuctions)(response.data.auctions);
705
+ const skipped = [];
706
+ const selected = [];
707
+ let plannedTotalWei = 0n;
708
+ for (const auction of activeAuctions) {
709
+ const auctionId = auction.id;
710
+ const highestBidWei = parseNonNegativeBigint(auction.highestBid, "highestBid");
711
+ const startBidWei = parseNonNegativeBigint(auction.startBidPrice || "0", "startBidPrice");
712
+ const highestBidder = auction.highestBidder || ZERO_ADDRESS;
713
+ const unbid = highestBidWei === 0n && highestBidder === ZERO_ADDRESS;
714
+ if (!unbid) {
715
+ skipped.push({
716
+ auctionId,
717
+ reasonCode: "NOT_UNBID",
718
+ reason: "Auction already has a highest bid.",
719
+ highestBidWei: highestBidWei.toString(),
720
+ highestBidder,
721
+ });
722
+ continue;
723
+ }
724
+ if (startBidWei > amountWei) {
725
+ skipped.push({
726
+ auctionId,
727
+ reasonCode: "START_BID_ABOVE_AMOUNT",
728
+ reason: "Auction start bid is above the configured amount.",
729
+ startBidWei: startBidWei.toString(),
730
+ amountWei: amountWei.toString(),
731
+ });
732
+ continue;
733
+ }
734
+ if (plannedTotalWei + amountWei > maxTotalWei) {
735
+ skipped.push({
736
+ auctionId,
737
+ reasonCode: "MAX_TOTAL_REACHED",
738
+ reason: "Adding this auction would exceed max total amount.",
739
+ plannedTotalWei: plannedTotalWei.toString(),
740
+ maxTotalWei: maxTotalWei.toString(),
741
+ });
742
+ continue;
743
+ }
744
+ selected.push(auctionId);
745
+ plannedTotalWei += amountWei;
746
+ }
747
+ const baseIdempotencyKey = (0, args_1.getFlagString)(ctx.args.flags, "idempotency-key");
748
+ const results = [];
749
+ for (const auctionId of selected) {
750
+ const stepFlags = { ...ctx.args.flags };
751
+ delete stepFlags["amount-ghst"];
752
+ delete stepFlags["max-total-ghst"];
753
+ delete stepFlags["max-total-wei"];
754
+ delete stepFlags.first;
755
+ delete stepFlags.skip;
756
+ stepFlags["auction-id"] = auctionId;
757
+ stepFlags["amount-wei"] = amountWei.toString();
758
+ stepFlags["require-unbid"] = true;
759
+ stepFlags["expected-highest-bid-wei"] = "0";
760
+ stepFlags["idempotency-key"] = parseBatchIdempotencyKey(baseIdempotencyKey, auctionId, amountWei);
761
+ const stepCtx = {
762
+ commandPath: ["auction", "bid"],
763
+ args: {
764
+ positionals: ["auction", "bid"],
765
+ flags: stepFlags,
766
+ },
767
+ globals: ctx.globals,
768
+ };
769
+ try {
770
+ const result = await runSingleBid(stepCtx, {
771
+ forceAuctionId: auctionId,
772
+ forceAmountWei: amountWei,
773
+ forceExpectedHighestBidWei: 0n,
774
+ forceRequireUnbid: true,
775
+ forceIdempotencyKey: parseBatchIdempotencyKey(baseIdempotencyKey, auctionId, amountWei),
776
+ });
777
+ results.push({
778
+ auctionId,
779
+ status: "ok",
780
+ result,
781
+ });
782
+ }
783
+ catch (error) {
784
+ if (error instanceof errors_1.CliError) {
785
+ results.push({
786
+ auctionId,
787
+ status: "error",
788
+ code: error.code,
789
+ message: error.message,
790
+ details: error.details || null,
791
+ });
792
+ continue;
793
+ }
794
+ throw error;
795
+ }
796
+ }
797
+ const okCount = results.filter((result) => result.status === "ok").length;
798
+ const errorCount = results.length - okCount;
799
+ return {
800
+ command: "auction bid-unbid",
801
+ amountWei: amountWei.toString(),
802
+ maxTotalWei: maxTotalWei.toString(),
803
+ atTime: now,
804
+ scanned: activeAuctions.length,
805
+ selected: selected.length,
806
+ plannedTotalWei: plannedTotalWei.toString(),
807
+ summary: {
808
+ success: okCount,
809
+ error: errorCount,
810
+ skipped: skipped.length,
811
+ },
812
+ skipped,
813
+ results,
814
+ };
815
+ }