Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

## Unreleased

### Added

- Built-in mapped write metadata for GBM auction commands (`auction bid`, `auction buy-now`, `auction cancel`, `auction create`, `auction swap-bid`):
- default contract address: `0x80320a0000c7a6a34086e2acad6915ff57ffda31`
- built-in ABI signatures for mapped function encoding
- Mapped help for auction writes now prints built-in ABI signature details without requiring `--abi-file`.

### Changed

- Mapped write execution now auto-uses built-in ABI/address defaults when available, while still allowing explicit `--abi-file` / `--address` overrides.

## 0.2.3 - 2026-02-27

### Fixed
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ Planned domain namespaces are stubbed for parity tracking:
- `gotchi`, `portal`, `wearables`, `items`, `inventory`, `baazaar`, `auction`, `lending`, `staking`, `gotchi-points`, `realm`, `alchemica`, `forge`, `token`

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

## Command help and discoverability

Expand All @@ -65,10 +66,11 @@ ag tx send --help
ag help baazaar buy-now
```

Mapped write commands now expose their onchain function mapping and required flags:
Mapped write commands now expose their onchain function mapping, defaults (if available), and required flags:

```bash
ag baazaar buy-now --help
ag auction bid --help
```

If you provide `--abi-file` with `--help`, the CLI prints ABI-derived function signature and input names for the mapped method:
Expand Down
49 changes: 49 additions & 0 deletions src/commands/mapped-defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { parseAbi, type Abi } from "viem";

import { BASE_GBM_DIAMOND } from "../subgraph/sources";

export interface MappedWriteDefaults {
address?: `0x${string}`;
abi?: Abi;
source: string;
}

const GBM_MAPPED_WRITE_ABI = parseAbi([
"function buyNow(uint256 _auctionID)",
"function cancelAuction(uint256 _auctionID)",
"function commitBid(uint256 _auctionID,uint256 _bidAmount,uint256 _highestBid,address _tokenContract,uint256 _tokenID,uint256 _amount,bytes _unused)",
"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)",
"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)",
]);

const MAPPED_WRITE_DEFAULTS: Record<string, MappedWriteDefaults> = {
"auction bid": {
address: BASE_GBM_DIAMOND,
abi: GBM_MAPPED_WRITE_ABI,
source: "base.gbm-diamond",
},
"auction buy-now": {
address: BASE_GBM_DIAMOND,
abi: GBM_MAPPED_WRITE_ABI,
source: "base.gbm-diamond",
},
"auction cancel": {
address: BASE_GBM_DIAMOND,
abi: GBM_MAPPED_WRITE_ABI,
source: "base.gbm-diamond",
},
"auction create": {
address: BASE_GBM_DIAMOND,
abi: GBM_MAPPED_WRITE_ABI,
source: "base.gbm-diamond",
},
"auction swap-bid": {
address: BASE_GBM_DIAMOND,
abi: GBM_MAPPED_WRITE_ABI,
source: "base.gbm-diamond",
},
};

export function getMappedWriteDefaults(commandPath: string[]): MappedWriteDefaults | undefined {
return MAPPED_WRITE_DEFAULTS[commandPath.join(" ")];
}
82 changes: 82 additions & 0 deletions src/commands/mapped-runtime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { afterEach, describe, expect, it, vi } from "vitest";

import { CommandContext } from "../types";
import { BASE_GBM_DIAMOND } from "../subgraph/sources";

const { runOnchainSendWithFunctionMock } = vi.hoisted(() => ({
runOnchainSendWithFunctionMock: vi.fn(),
}));

vi.mock("./onchain", () => ({
runOnchainSendWithFunction: runOnchainSendWithFunctionMock,
}));

import { runMappedDomainCommand } from "./mapped";

function createCtx(path: string[]): CommandContext {
return {
commandPath: path,
args: {
positionals: path,
flags: {
"args-json": "[]",
},
},
globals: {
mode: "agent",
json: true,
yes: true,
profile: "prod",
},
};
}

describe("mapped command execution defaults", () => {
afterEach(() => {
vi.clearAllMocks();
});

it("injects built-in GBM defaults for auction bid", async () => {
runOnchainSendWithFunctionMock.mockResolvedValue({ ok: true });

const result = await runMappedDomainCommand(createCtx(["auction", "bid"]));

expect(runOnchainSendWithFunctionMock).toHaveBeenCalledTimes(1);
expect(runOnchainSendWithFunctionMock).toHaveBeenCalledWith(
expect.objectContaining({ commandPath: ["auction", "bid"] }),
"commitBid",
"auction bid",
expect.objectContaining({
address: BASE_GBM_DIAMOND,
source: "base.gbm-diamond",
}),
);
expect(result).toMatchObject({
mappedMethod: "commitBid",
defaults: {
source: "base.gbm-diamond",
address: BASE_GBM_DIAMOND,
abi: "available",
},
result: { ok: true },
});
});

it("keeps non-defaulted mapped commands requiring explicit metadata", async () => {
runOnchainSendWithFunctionMock.mockResolvedValue({ ok: true });

const result = await runMappedDomainCommand(createCtx(["baazaar", "buy-now"]));
const call = runOnchainSendWithFunctionMock.mock.calls[0];
const defaultsArg = call?.[3] as { abi?: unknown; address?: unknown; source?: unknown };

expect(runOnchainSendWithFunctionMock).toHaveBeenCalledTimes(1);
expect(defaultsArg.abi).toBeUndefined();
expect(defaultsArg.address).toBeUndefined();
expect(defaultsArg.source).toBeUndefined();
expect(result).toMatchObject({
mappedMethod: "buyNow",
defaults: null,
result: { ok: true },
});
});
});
15 changes: 14 additions & 1 deletion src/commands/mapped.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CliError } from "../errors";
import { CommandContext, JsonValue } from "../types";

import { getMappedWriteDefaults } from "./mapped-defaults";
import { runOnchainSendWithFunction } from "./onchain";

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

const result = await runOnchainSendWithFunction(ctx, method, key);
const defaults = getMappedWriteDefaults(ctx.commandPath);
const result = await runOnchainSendWithFunction(ctx, method, key, {
abi: defaults?.abi,
address: defaults?.address,
source: defaults?.source,
});

return {
mappedMethod: method,
defaults: defaults
? {
source: defaults.source,
address: defaults.address || null,
abi: defaults.abi ? "available" : "none",
}
: null,
result,
};
}
41 changes: 36 additions & 5 deletions src/commands/onchain.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { decodeFunctionResult, encodeFunctionData } from "viem";
import { decodeFunctionResult, encodeFunctionData, type Abi } from "viem";

import { getFlagBoolean, getFlagString } from "../args";
import { parseAbiFile } from "../abi";
Expand Down Expand Up @@ -127,6 +127,11 @@ export async function runOnchainSendWithFunction(
ctx: CommandContext,
forcedFunctionName?: string,
commandOverride?: string,
defaults?: {
abi?: Abi;
address?: `0x${string}`;
source?: string;
},
): Promise<JsonValue> {
const config = loadConfig();
const profileName = getFlagString(ctx.args.flags, "profile") || ctx.globals.profile;
Expand All @@ -136,10 +141,31 @@ export async function runOnchainSendWithFunction(
const chain = resolveChain(profile.chain);
const rpcUrl = resolveRpcUrl(chain, getFlagString(ctx.args.flags, "rpc-url") || profile.rpcUrl);

const abiFile = requireFlag(getFlagString(ctx.args.flags, "abi-file"), "--abi-file");
const abi = parseAbiFile(abiFile);

const address = parseAddress(getFlagString(ctx.args.flags, "address"), "--address");
const abiFile = getFlagString(ctx.args.flags, "abi-file");
const abi = abiFile
? parseAbiFile(abiFile)
: defaults?.abi ||
(() => {
throw new CliError("MISSING_ARGUMENT", "--abi-file is required.", 2, {
command: commandOverride || ctx.commandPath.join(" "),
hint: forcedFunctionName
? "This mapped command has no built-in ABI metadata yet. Provide --abi-file."
: "Provide --abi-file <path>.",
});
})();

const addressInput = getFlagString(ctx.args.flags, "address");
const address = addressInput
? parseAddress(addressInput, "--address")
: defaults?.address ||
(() => {
throw new CliError("MISSING_ARGUMENT", "--address is required.", 2, {
command: commandOverride || ctx.commandPath.join(" "),
hint: forcedFunctionName
? "This mapped command has no built-in contract address yet. Provide --address."
: "Provide --address <0x...>.",
});
})();
const functionName = forcedFunctionName || requireFlag(getFlagString(ctx.args.flags, "function"), "--function");
const args = parseArgsJson(getFlagString(ctx.args.flags, "args-json"));

Expand Down Expand Up @@ -197,6 +223,11 @@ export async function runOnchainSendWithFunction(
address,
functionName,
args,
defaults: {
abi: abiFile ? "flag" : defaults?.abi ? "mapped-default" : "none",
address: addressInput ? "flag" : defaults?.address ? "mapped-default" : "none",
source: defaults?.source || null,
},
result,
};
}
Expand Down
39 changes: 38 additions & 1 deletion src/commands/write-dryrun.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as os from "os";
import * as path from "path";

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

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

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

import { runOnchainSendCommand } from "./onchain";
import { runOnchainSendCommand, runOnchainSendWithFunction } from "./onchain";
import { runTxSendCommand } from "./tx";

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

it("accepts mapped defaults when --abi-file and --address are omitted", async () => {
const result = await runOnchainSendWithFunction(
createContext(["auction", "bid"], {
"args-json": '["1","1","1","0x1111111111111111111111111111111111111111","1","1","0x"]',
"dry-run": true,
}),
"commitBid",
"auction bid",
{
abi: parseAbi(["function commitBid(uint256,uint256,uint256,address,uint256,uint256,bytes)"]),
address: "0x80320a0000c7a6a34086e2acad6915ff57ffda31",
source: "base.gbm-diamond",
},
);

expect(executeTxIntentMock).toHaveBeenCalledWith(
expect.objectContaining({
command: "auction bid",
to: "0x80320a0000c7a6a34086e2acad6915ff57ffda31",
dryRun: true,
}),
expect.objectContaining({ chainId: 8453 }),
);
expect(
result as {
defaults: { abi: string; address: string; source: string };
},
).toMatchObject({
defaults: {
abi: "mapped-default",
address: "mapped-default",
source: "base.gbm-diamond",
},
});
});

it("rejects --dry-run with --wait on onchain send", async () => {
const abiFile = writeAbiFile(
JSON.stringify([
Expand Down
10 changes: 10 additions & 0 deletions src/output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ describe("help output", () => {
expect(text).toContain("Mapped to onchain function:");
expect(text).toContain("buyNow");
expect(text).toContain("--args-json");
expect(text).toContain("--abi-file <path> --address <0x...> --args-json");
});

it("prints built-in mapped defaults for auction bid without --abi-file", () => {
const text = buildHelpText(["auction", "bid"]);
expect(text).toContain("commitBid(uint256,uint256,uint256,address,uint256,uint256,bytes)");
expect(text).toContain("source: base.gbm-diamond");
expect(text).toContain("address: 0x80320a0000c7a6a34086e2acad6915ff57ffda31");
expect(text).toContain("ag auction bid --profile <name> --args-json");
expect(text).toContain("--abi-file (override built-in ABI)");
});

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