Skip to content
Open
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
10 changes: 7 additions & 3 deletions skills/ampersend/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ Two-step flow: `setup start` generates a key and requests approval, `setup finis
```bash
# Step 1: Request agent creation — returns immediately with approval URL
ampersend setup start --name "my-agent"
# {"ok": true, "data": {"token": "...", "user_approve_url": "https://...", "agentKeyAddress": "0x..."}}
# {"ok": true, "data": {"token": "...", "user_approve_url": "https://...", "agentKeyAddress": "0x...", "verificationCode": "123456"}}

# Show the user_approve_url to the user so they can approve in their browser.
# Show the user_approve_url AND the verificationCode to the user.
# The user opens the URL in their browser and confirms the code in the
# dashboard matches the one you showed them before approving. The code
# protects against MITM key substitution.

# Step 2: Poll for approval and activate config
ampersend setup finish
Expand Down Expand Up @@ -105,7 +108,8 @@ ampersend setup start [--mode <create|connect>] [--name <name>] [--agent <addres
| `--per-transaction-limit <amt>` | Per-transaction spending limit in atomic units (create mode only) |
| `--auto-topup` | Allow automatic balance top-up from main account (create mode only) |

Returns `token`, `user_approve_url`, and `agentKeyAddress`. Show the `user_approve_url` to the user.
Returns `token`, `user_approve_url`, `agentKeyAddress`, and `verificationCode`. Show the `user_approve_url` and
`verificationCode` to the user — they confirm the code shown in the dashboard matches before approving.

#### setup finish

Expand Down
9 changes: 8 additions & 1 deletion typescript/packages/ampersend-sdk/src/cli/commands/setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Command } from "commander"
import { isAddress } from "viem"
import { isAddress, keccak256 } from "viem"
import { generatePrivateKey, privateKeyToAddress } from "viem/accounts"

import { ApprovalClient } from "../../ampersend/approval.ts"
Expand Down Expand Up @@ -123,10 +123,16 @@ export async function executeSetupStart(options: SetupStartOptions): Promise<voi
// Call the approval API
const client = new ApprovalClient({ apiUrl })

// Derive a 6-digit verification code from the key address.
// The user sees this code in the dashboard and can confirm it matches
// the one shown by the agent, preventing MITM key substitution.
const verificationCode = String(BigInt(keccak256(agentKeyAddress as `0x${string}`)) % 1000000n).padStart(6, "0")

let result: JsonEnvelope<{
token: string
user_approve_url: string
agentKeyAddress: string
verificationCode: string
}>

try {
Expand All @@ -151,6 +157,7 @@ export async function executeSetupStart(options: SetupStartOptions): Promise<voi
token: response.token,
user_approve_url: response.user_approve_url,
agentKeyAddress,
verificationCode,
})
} catch (error) {
result = err("API_ERROR", error instanceof Error ? error.message : String(error))
Expand Down
3 changes: 2 additions & 1 deletion typescript/packages/ampersend-sdk/tests/cli/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,13 @@ describe("CLI Setup Commands", () => {

const output = getLastOutput() as {
ok: boolean
data: { token: string; user_approve_url: string; agentKeyAddress: string }
data: { token: string; user_approve_url: string; agentKeyAddress: string; verificationCode: string }
}
expect(output.ok).toBe(true)
expect(output.data.token).toBe("test-token-123")
expect(output.data.user_approve_url).toBe("https://app.ampersend.ai/approvals/create-agent/test-token-123")
expect(output.data.agentKeyAddress).toMatch(/^0x[a-fA-F0-9]{40}$/)
expect(output.data.verificationCode).toMatch(/^\d{6}$/)

// Verify pending was stored
const config = readConfig()
Expand Down
Loading