Skip to content

Commit c3927bd

Browse files
feat(mapped): auto-default GBM auction ABI/address metadata (#8)
1 parent 2abe021 commit c3927bd

9 files changed

Lines changed: 314 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
## Unreleased
44

5+
### Added
6+
7+
- Built-in mapped write metadata for GBM auction commands (`auction bid`, `auction buy-now`, `auction cancel`, `auction create`, `auction swap-bid`):
8+
- default contract address: `0x80320a0000c7a6a34086e2acad6915ff57ffda31`
9+
- built-in ABI signatures for mapped function encoding
10+
- Mapped help for auction writes now prints built-in ABI signature details without requiring `--abi-file`.
11+
12+
### Changed
13+
14+
- Mapped write execution now auto-uses built-in ABI/address defaults when available, while still allowing explicit `--abi-file` / `--address` overrides.
15+
516
## 0.2.3 - 2026-02-27
617

718
### Fixed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ Planned domain namespaces are stubbed for parity tracking:
5353
- `gotchi`, `portal`, `wearables`, `items`, `inventory`, `baazaar`, `auction`, `lending`, `staking`, `gotchi-points`, `realm`, `alchemica`, `forge`, `token`
5454

5555
Many Base-era write flows are already executable as mapped aliases in those namespaces (internally routed through `onchain send`).
56-
Example: `ag lending create --abi-file ./abis/GotchiLendingFacet.json --address 0x... --args-json '[...]' --json`
56+
Example with built-in defaults: `ag auction bid --args-json '[...]' --dry-run --json`
57+
Example with explicit metadata: `ag lending create --abi-file ./abis/GotchiLendingFacet.json --address 0x... --args-json '[...]' --json`
5758

5859
## Command help and discoverability
5960

@@ -65,10 +66,11 @@ ag tx send --help
6566
ag help baazaar buy-now
6667
```
6768

68-
Mapped write commands now expose their onchain function mapping and required flags:
69+
Mapped write commands now expose their onchain function mapping, defaults (if available), and required flags:
6970

7071
```bash
7172
ag baazaar buy-now --help
73+
ag auction bid --help
7274
```
7375

7476
If you provide `--abi-file` with `--help`, the CLI prints ABI-derived function signature and input names for the mapped method:

src/commands/mapped-defaults.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { parseAbi, type Abi } from "viem";
2+
3+
import { BASE_GBM_DIAMOND } from "../subgraph/sources";
4+
5+
export interface MappedWriteDefaults {
6+
address?: `0x${string}`;
7+
abi?: Abi;
8+
source: string;
9+
}
10+
11+
const GBM_MAPPED_WRITE_ABI = parseAbi([
12+
"function buyNow(uint256 _auctionID)",
13+
"function cancelAuction(uint256 _auctionID)",
14+
"function commitBid(uint256 _auctionID,uint256 _bidAmount,uint256 _highestBid,address _tokenContract,uint256 _tokenID,uint256 _amount,bytes _unused)",
15+
"function swapAndCommitBid((address tokenIn,uint256 swapAmount,uint256 minGhstOut,uint256 swapDeadline,address recipient,uint256 auctionID,uint256 bidAmount,uint256 highestBid,address tokenContract,uint256 _tokenID,uint256 _amount,bytes _signature) ctx)",
16+
"function createAuction((uint80 startTime,uint80 endTime,uint56 tokenAmount,uint8 category,bytes4 tokenKind,uint256 tokenID,uint96 buyItNowPrice,uint96 startingBid) _info,address _tokenContract,uint256 _auctionPresetID) returns (uint256)",
17+
]);
18+
19+
const MAPPED_WRITE_DEFAULTS: Record<string, MappedWriteDefaults> = {
20+
"auction bid": {
21+
address: BASE_GBM_DIAMOND,
22+
abi: GBM_MAPPED_WRITE_ABI,
23+
source: "base.gbm-diamond",
24+
},
25+
"auction buy-now": {
26+
address: BASE_GBM_DIAMOND,
27+
abi: GBM_MAPPED_WRITE_ABI,
28+
source: "base.gbm-diamond",
29+
},
30+
"auction cancel": {
31+
address: BASE_GBM_DIAMOND,
32+
abi: GBM_MAPPED_WRITE_ABI,
33+
source: "base.gbm-diamond",
34+
},
35+
"auction create": {
36+
address: BASE_GBM_DIAMOND,
37+
abi: GBM_MAPPED_WRITE_ABI,
38+
source: "base.gbm-diamond",
39+
},
40+
"auction swap-bid": {
41+
address: BASE_GBM_DIAMOND,
42+
abi: GBM_MAPPED_WRITE_ABI,
43+
source: "base.gbm-diamond",
44+
},
45+
};
46+
47+
export function getMappedWriteDefaults(commandPath: string[]): MappedWriteDefaults | undefined {
48+
return MAPPED_WRITE_DEFAULTS[commandPath.join(" ")];
49+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { afterEach, describe, expect, it, vi } from "vitest";
2+
3+
import { CommandContext } from "../types";
4+
import { BASE_GBM_DIAMOND } from "../subgraph/sources";
5+
6+
const { runOnchainSendWithFunctionMock } = vi.hoisted(() => ({
7+
runOnchainSendWithFunctionMock: vi.fn(),
8+
}));
9+
10+
vi.mock("./onchain", () => ({
11+
runOnchainSendWithFunction: runOnchainSendWithFunctionMock,
12+
}));
13+
14+
import { runMappedDomainCommand } from "./mapped";
15+
16+
function createCtx(path: string[]): CommandContext {
17+
return {
18+
commandPath: path,
19+
args: {
20+
positionals: path,
21+
flags: {
22+
"args-json": "[]",
23+
},
24+
},
25+
globals: {
26+
mode: "agent",
27+
json: true,
28+
yes: true,
29+
profile: "prod",
30+
},
31+
};
32+
}
33+
34+
describe("mapped command execution defaults", () => {
35+
afterEach(() => {
36+
vi.clearAllMocks();
37+
});
38+
39+
it("injects built-in GBM defaults for auction bid", async () => {
40+
runOnchainSendWithFunctionMock.mockResolvedValue({ ok: true });
41+
42+
const result = await runMappedDomainCommand(createCtx(["auction", "bid"]));
43+
44+
expect(runOnchainSendWithFunctionMock).toHaveBeenCalledTimes(1);
45+
expect(runOnchainSendWithFunctionMock).toHaveBeenCalledWith(
46+
expect.objectContaining({ commandPath: ["auction", "bid"] }),
47+
"commitBid",
48+
"auction bid",
49+
expect.objectContaining({
50+
address: BASE_GBM_DIAMOND,
51+
source: "base.gbm-diamond",
52+
}),
53+
);
54+
expect(result).toMatchObject({
55+
mappedMethod: "commitBid",
56+
defaults: {
57+
source: "base.gbm-diamond",
58+
address: BASE_GBM_DIAMOND,
59+
abi: "available",
60+
},
61+
result: { ok: true },
62+
});
63+
});
64+
65+
it("keeps non-defaulted mapped commands requiring explicit metadata", async () => {
66+
runOnchainSendWithFunctionMock.mockResolvedValue({ ok: true });
67+
68+
const result = await runMappedDomainCommand(createCtx(["baazaar", "buy-now"]));
69+
const call = runOnchainSendWithFunctionMock.mock.calls[0];
70+
const defaultsArg = call?.[3] as { abi?: unknown; address?: unknown; source?: unknown };
71+
72+
expect(runOnchainSendWithFunctionMock).toHaveBeenCalledTimes(1);
73+
expect(defaultsArg.abi).toBeUndefined();
74+
expect(defaultsArg.address).toBeUndefined();
75+
expect(defaultsArg.source).toBeUndefined();
76+
expect(result).toMatchObject({
77+
mappedMethod: "buyNow",
78+
defaults: null,
79+
result: { ok: true },
80+
});
81+
});
82+
});

src/commands/mapped.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CliError } from "../errors";
22
import { CommandContext, JsonValue } from "../types";
33

4+
import { getMappedWriteDefaults } from "./mapped-defaults";
45
import { runOnchainSendWithFunction } from "./onchain";
56

67
const MAPPED_WRITE_COMMANDS: Record<string, string> = {
@@ -74,10 +75,22 @@ export async function runMappedDomainCommand(ctx: CommandContext): Promise<JsonV
7475
});
7576
}
7677

77-
const result = await runOnchainSendWithFunction(ctx, method, key);
78+
const defaults = getMappedWriteDefaults(ctx.commandPath);
79+
const result = await runOnchainSendWithFunction(ctx, method, key, {
80+
abi: defaults?.abi,
81+
address: defaults?.address,
82+
source: defaults?.source,
83+
});
7884

7985
return {
8086
mappedMethod: method,
87+
defaults: defaults
88+
? {
89+
source: defaults.source,
90+
address: defaults.address || null,
91+
abi: defaults.abi ? "available" : "none",
92+
}
93+
: null,
8194
result,
8295
};
8396
}

src/commands/onchain.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { decodeFunctionResult, encodeFunctionData } from "viem";
1+
import { decodeFunctionResult, encodeFunctionData, type Abi } from "viem";
22

33
import { getFlagBoolean, getFlagString } from "../args";
44
import { parseAbiFile } from "../abi";
@@ -127,6 +127,11 @@ export async function runOnchainSendWithFunction(
127127
ctx: CommandContext,
128128
forcedFunctionName?: string,
129129
commandOverride?: string,
130+
defaults?: {
131+
abi?: Abi;
132+
address?: `0x${string}`;
133+
source?: string;
134+
},
130135
): Promise<JsonValue> {
131136
const config = loadConfig();
132137
const profileName = getFlagString(ctx.args.flags, "profile") || ctx.globals.profile;
@@ -136,10 +141,31 @@ export async function runOnchainSendWithFunction(
136141
const chain = resolveChain(profile.chain);
137142
const rpcUrl = resolveRpcUrl(chain, getFlagString(ctx.args.flags, "rpc-url") || profile.rpcUrl);
138143

139-
const abiFile = requireFlag(getFlagString(ctx.args.flags, "abi-file"), "--abi-file");
140-
const abi = parseAbiFile(abiFile);
141-
142-
const address = parseAddress(getFlagString(ctx.args.flags, "address"), "--address");
144+
const abiFile = getFlagString(ctx.args.flags, "abi-file");
145+
const abi = abiFile
146+
? parseAbiFile(abiFile)
147+
: defaults?.abi ||
148+
(() => {
149+
throw new CliError("MISSING_ARGUMENT", "--abi-file is required.", 2, {
150+
command: commandOverride || ctx.commandPath.join(" "),
151+
hint: forcedFunctionName
152+
? "This mapped command has no built-in ABI metadata yet. Provide --abi-file."
153+
: "Provide --abi-file <path>.",
154+
});
155+
})();
156+
157+
const addressInput = getFlagString(ctx.args.flags, "address");
158+
const address = addressInput
159+
? parseAddress(addressInput, "--address")
160+
: defaults?.address ||
161+
(() => {
162+
throw new CliError("MISSING_ARGUMENT", "--address is required.", 2, {
163+
command: commandOverride || ctx.commandPath.join(" "),
164+
hint: forcedFunctionName
165+
? "This mapped command has no built-in contract address yet. Provide --address."
166+
: "Provide --address <0x...>.",
167+
});
168+
})();
143169
const functionName = forcedFunctionName || requireFlag(getFlagString(ctx.args.flags, "function"), "--function");
144170
const args = parseArgsJson(getFlagString(ctx.args.flags, "args-json"));
145171

@@ -197,6 +223,11 @@ export async function runOnchainSendWithFunction(
197223
address,
198224
functionName,
199225
args,
226+
defaults: {
227+
abi: abiFile ? "flag" : defaults?.abi ? "mapped-default" : "none",
228+
address: addressInput ? "flag" : defaults?.address ? "mapped-default" : "none",
229+
source: defaults?.source || null,
230+
},
200231
result,
201232
};
202233
}

src/commands/write-dryrun.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as os from "os";
33
import * as path from "path";
44

55
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6+
import { parseAbi } from "viem";
67

78
import { CommandContext } from "../types";
89

@@ -37,7 +38,7 @@ vi.mock("../tx-engine", () => ({
3738
executeTxIntent: executeTxIntentMock,
3839
}));
3940

40-
import { runOnchainSendCommand } from "./onchain";
41+
import { runOnchainSendCommand, runOnchainSendWithFunction } from "./onchain";
4142
import { runTxSendCommand } from "./tx";
4243

4344
const files: string[] = [];
@@ -188,6 +189,42 @@ describe("write command dry-run flags", () => {
188189
);
189190
});
190191

192+
it("accepts mapped defaults when --abi-file and --address are omitted", async () => {
193+
const result = await runOnchainSendWithFunction(
194+
createContext(["auction", "bid"], {
195+
"args-json": '["1","1","1","0x1111111111111111111111111111111111111111","1","1","0x"]',
196+
"dry-run": true,
197+
}),
198+
"commitBid",
199+
"auction bid",
200+
{
201+
abi: parseAbi(["function commitBid(uint256,uint256,uint256,address,uint256,uint256,bytes)"]),
202+
address: "0x80320a0000c7a6a34086e2acad6915ff57ffda31",
203+
source: "base.gbm-diamond",
204+
},
205+
);
206+
207+
expect(executeTxIntentMock).toHaveBeenCalledWith(
208+
expect.objectContaining({
209+
command: "auction bid",
210+
to: "0x80320a0000c7a6a34086e2acad6915ff57ffda31",
211+
dryRun: true,
212+
}),
213+
expect.objectContaining({ chainId: 8453 }),
214+
);
215+
expect(
216+
result as {
217+
defaults: { abi: string; address: string; source: string };
218+
},
219+
).toMatchObject({
220+
defaults: {
221+
abi: "mapped-default",
222+
address: "mapped-default",
223+
source: "base.gbm-diamond",
224+
},
225+
});
226+
});
227+
191228
it("rejects --dry-run with --wait on onchain send", async () => {
192229
const abiFile = writeAbiFile(
193230
JSON.stringify([

src/output.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ describe("help output", () => {
1818
expect(text).toContain("Mapped to onchain function:");
1919
expect(text).toContain("buyNow");
2020
expect(text).toContain("--args-json");
21+
expect(text).toContain("--abi-file <path> --address <0x...> --args-json");
22+
});
23+
24+
it("prints built-in mapped defaults for auction bid without --abi-file", () => {
25+
const text = buildHelpText(["auction", "bid"]);
26+
expect(text).toContain("commitBid(uint256,uint256,uint256,address,uint256,uint256,bytes)");
27+
expect(text).toContain("source: base.gbm-diamond");
28+
expect(text).toContain("address: 0x80320a0000c7a6a34086e2acad6915ff57ffda31");
29+
expect(text).toContain("ag auction bid --profile <name> --args-json");
30+
expect(text).toContain("--abi-file (override built-in ABI)");
2131
});
2232

2333
it("prints ABI-derived mapped function signature when --abi-file is supplied", () => {

0 commit comments

Comments
 (0)