Skip to content

Commit d408938

Browse files
feat: add first-class bankr signer backend (#6)
1 parent 3ae9f7d commit d408938

9 files changed

Lines changed: 386 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
- ABI-derived signature/input introspection for mapped help when `--abi-file` is passed.
1313
- Unknown command suggestion list in `UNKNOWN_COMMAND` error details.
1414
- CLI UX audit report: `docs/ux/cli-ux-audit-2026-02-27.md`.
15+
- Native `bankr` signer backend:
16+
- signer spec: `bankr[:address|apiKeyEnv|apiUrl]`
17+
- default auth env var: `BANKR_API_KEY`
18+
- default API URL: `https://api.bankr.bot`
19+
- address auto-resolution via `GET /agent/me` when address is not pinned
20+
- transaction submit via `POST /agent/submit`
21+
- Bootstrap overrides now support `bankr`:
22+
- `--signer-address` to pin wallet address
23+
- `--signer-auth-env-var` to customize Bankr API key env var
1524

1625
### Changed
1726

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,26 @@ npm run ag -- auction active --first 5 --raw --json
175175
- `keychain:ACCOUNT_ID` (encrypted local key store; requires `AGCLI_KEYCHAIN_PASSPHRASE`)
176176
- `remote:URL|ADDRESS|AUTH_ENV` (HTTP signer service)
177177
- `ledger:DERIVATION_PATH|ADDRESS|BRIDGE_ENV` (external bridge command signer)
178+
- `bankr[:ADDRESS|API_KEY_ENV|API_URL]` (Bankr-native signer via `/agent/me` + `/agent/submit`; defaults: `BANKR_API_KEY`, `https://api.bankr.bot`)
178179

179180
Remote signer contract:
180181

181182
- `GET /address` -> `{ "address": "0x..." }` (optional if address configured)
182183
- `POST /sign-transaction` -> `{ "rawTransaction": "0x..." }` or `{ "txHash": "0x..." }`
183184

185+
Bankr signer contract:
186+
187+
- `GET /agent/me` -> resolves wallet address when signer address is not pinned
188+
- `POST /agent/submit` -> submits transaction and returns transaction hash
189+
- auth header: `x-api-key: <BANKR_API_KEY>`
190+
191+
Bankr bootstrap example:
192+
193+
```bash
194+
BANKR_API_KEY=... \
195+
npm run ag -- bootstrap --mode agent --profile bankr --chain base --signer bankr --json
196+
```
197+
184198
Ledger bridge contract:
185199

186200
- Set `AGCLI_LEDGER_BRIDGE_CMD` (or custom env var in signer config) to a command that reads tx payload JSON from stdin and outputs JSON containing either `rawTransaction` or `txHash`.

src/commands/bootstrap.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ function buildSignerConfig(ctx: CommandContext, signerInput: string): ProfileCon
3939
};
4040
}
4141

42+
if (signer.type === "bankr") {
43+
const address = getFlagString(ctx.args.flags, "signer-address");
44+
const apiKeyEnvVar = getFlagString(ctx.args.flags, "signer-auth-env-var");
45+
46+
return {
47+
...signer,
48+
...(address ? { address: address as `0x${string}` } : {}),
49+
...(apiKeyEnvVar ? { apiKeyEnvVar } : {}),
50+
};
51+
}
52+
4253
if (signer.type === "ledger") {
4354
const address = getFlagString(ctx.args.flags, "signer-address");
4455
const bridgeCommandEnvVar = getFlagString(ctx.args.flags, "signer-bridge-env-var");

src/output.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ Examples:
8888
ag bootstrap --profile prod --chain base --signer readonly --json
8989
ag tx send --profile prod --to 0xabc... --value-wei 0 --dry-run --json
9090
ag onchain send --profile prod --abi-file ./abi.json --address 0xabc... --function approve --args-json '["0xdef...", "1"]' --dry-run --json
91+
BANKR_API_KEY=... ag bootstrap --profile bankr --chain base --signer bankr --json
9192
ag baazaar buy-now --help
9293
ag help tx send
9394
`;
@@ -107,6 +108,7 @@ Signer formats:
107108
keychain:ACCOUNT_ID
108109
ledger[:DERIVATION_PATH|ADDRESS|BRIDGE_ENV_VAR]
109110
remote:URL|ADDRESS|AUTH_ENV_VAR
111+
bankr[:ADDRESS|API_KEY_ENV|API_URL]
110112
`,
111113
profile: `
112114
Usage:

src/schemas.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export const signerSchema = z.discriminatedUnion("type", [
2121
address: addressSchema.optional(),
2222
authEnvVar: z.string().regex(/^[A-Z_][A-Z0-9_]*$/).optional(),
2323
}),
24+
z.object({
25+
type: z.literal("bankr"),
26+
address: addressSchema.optional(),
27+
apiKeyEnvVar: z.string().regex(/^[A-Z_][A-Z0-9_]*$/).optional(),
28+
apiUrl: z.string().url().optional(),
29+
}),
2430
]);
2531

2632
export const policySchema = z.object({

src/signer-runtime.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const tempFiles: string[] = [];
2020
afterEach(() => {
2121
delete process.env.AGCLI_REMOTE_TOKEN;
2222
delete process.env.AGCLI_LEDGER_BRIDGE_CMD;
23+
delete process.env.BANKR_API_KEY;
24+
delete process.env.BANKR_TEST_KEY;
2325

2426
for (const file of tempFiles.splice(0)) {
2527
fs.rmSync(file, { force: true });
@@ -134,4 +136,79 @@ describe("signer runtime", () => {
134136

135137
expect(hash).toBe("0x" + "c".repeat(64));
136138
});
139+
140+
it("supports bankr signer with wallet auto-discovery", async () => {
141+
process.env.BANKR_API_KEY = "bankr-token";
142+
143+
const fetchMock = vi.fn(async (url: string, init?: RequestInit) => {
144+
if (url.endsWith("/agent/me")) {
145+
expect((init?.headers as Record<string, string>)["x-api-key"]).toBe("bankr-token");
146+
return {
147+
ok: true,
148+
text: async () => JSON.stringify({ walletAddress: "0x0000000000000000000000000000000000000001" }),
149+
};
150+
}
151+
152+
expect(url.endsWith("/agent/submit")).toBe(true);
153+
expect((init?.headers as Record<string, string>)["x-api-key"]).toBe("bankr-token");
154+
const body = JSON.parse(String(init?.body));
155+
expect(body.waitForConfirmation).toBe(false);
156+
expect(body.transaction.to).toBe("0x0000000000000000000000000000000000000001");
157+
return {
158+
ok: true,
159+
text: async () => JSON.stringify({ transactionHash: "0x" + "e".repeat(64) }),
160+
};
161+
});
162+
vi.stubGlobal("fetch", fetchMock);
163+
164+
const signer = parseSigner("bankr");
165+
const runtime = await resolveSignerRuntime(signer, fakePublicClient(), "https://mainnet.base.org", base);
166+
167+
expect(runtime.summary.signerType).toBe("bankr");
168+
expect(runtime.summary.address).toBe("0x0000000000000000000000000000000000000001");
169+
expect(runtime.summary.canSign).toBe(true);
170+
171+
const hash = await runtime.sendTransaction!({
172+
chain: base,
173+
to: "0x0000000000000000000000000000000000000001",
174+
data: "0x",
175+
value: 0n,
176+
gas: 21000n,
177+
nonce: 1,
178+
});
179+
180+
expect(hash).toBe("0x" + "e".repeat(64));
181+
expect(fetchMock).toHaveBeenCalledTimes(2);
182+
});
183+
184+
it("supports bankr signer with explicit address and custom api key env var", async () => {
185+
process.env.BANKR_TEST_KEY = "bankr-token";
186+
187+
const fetchMock = vi.fn(async (url: string, init?: RequestInit) => {
188+
expect(url).toBe("https://api.bankr.bot/agent/submit");
189+
expect((init?.headers as Record<string, string>)["x-api-key"]).toBe("bankr-token");
190+
return {
191+
ok: true,
192+
text: async () => JSON.stringify({ txHash: "0x" + "f".repeat(64) }),
193+
};
194+
});
195+
vi.stubGlobal("fetch", fetchMock);
196+
197+
const signer = parseSigner(
198+
"bankr:0x0000000000000000000000000000000000000001|BANKR_TEST_KEY|https://api.bankr.bot",
199+
);
200+
const runtime = await resolveSignerRuntime(signer, fakePublicClient(), "https://mainnet.base.org", base);
201+
202+
const hash = await runtime.sendTransaction!({
203+
chain: base,
204+
to: "0x0000000000000000000000000000000000000001",
205+
data: "0x",
206+
value: 0n,
207+
gas: 21000n,
208+
nonce: 1,
209+
});
210+
211+
expect(hash).toBe("0x" + "f".repeat(64));
212+
expect(fetchMock).toHaveBeenCalledTimes(1);
213+
});
137214
});

src/signer.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ describe("agcli signer parser", () => {
5757
});
5858
});
5959

60+
it("parses bankr signer defaults", () => {
61+
expect(parseSigner("bankr")).toEqual({
62+
type: "bankr",
63+
});
64+
});
65+
66+
it("parses bankr signer with address, api env, and api url", () => {
67+
expect(
68+
parseSigner("bankr:0x0000000000000000000000000000000000000001|BANKR_KEY|https://api.bankr.bot"),
69+
).toEqual({
70+
type: "bankr",
71+
address: "0x0000000000000000000000000000000000000001",
72+
apiKeyEnvVar: "BANKR_KEY",
73+
apiUrl: "https://api.bankr.bot",
74+
});
75+
});
76+
6077
it("throws for malformed env name", () => {
6178
expect(() => parseSigner("env:agcli_key")).toThrowError();
6279
});

0 commit comments

Comments
 (0)