Skip to content

Commit 87ad8fa

Browse files
chore: sync from internal
1 parent ee45e93 commit 87ad8fa

188 files changed

Lines changed: 3821 additions & 1879 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
---
2+
name: new-cli-action
3+
description: Scaffold a new CLI action for the internal-connect-sdk. Use when the user asks to add a new command, tool, or action to the Phantom CLI/MCP. Guides through creating the action file, registering the command in the appropriate CLI group, and adding the tool to the OpenClaw registry.
4+
user-invocable: true
5+
allowed-tools:
6+
- Read
7+
- Write
8+
- Edit
9+
- Bash
10+
- Agent
11+
---
12+
13+
# /new-cli-action — Scaffold a New CLI Action
14+
15+
Arguments passed: `$ARGUMENTS`
16+
17+
You are helping the user add a new command to the Phantom CLI (`packages/cli`). Every action is automatically exposed in three places: the CLI binary, the MCP server, and OpenClaw. All three come for free from a single `createAction` call — the user only needs to write one file and make two registration edits.
18+
19+
> **Framework:** The CLI is built on [incur](https://github.com/wevm/incur), a TypeScript framework for building CLIs that work as both binaries and MCP servers. The `Cli` and `z` imports in every action file come from `incur`. If you need deeper guidance on incur patterns — schemas, Cli APIs, or advanced usage — run `npx incur skills add` in the project root to pull the official incur skill into Claude Code and piggyback off it.
20+
21+
---
22+
23+
## Step 1 — Understand the requirement
24+
25+
If `$ARGUMENTS` is empty or vague, ask the user:
26+
27+
1. What should the command do? (one sentence)
28+
2. What inputs does it need?
29+
3. What does a success response look like?
30+
4. Where should it live in the CLI? (top-level like `phantom buy`, or under a sub-group like `phantom perps open`)
31+
32+
If `$ARGUMENTS` describes the command well enough, proceed directly.
33+
34+
---
35+
36+
## Step 2 — Determine the file location and CLI group
37+
38+
The CLI is structured as follows. Read `packages/cli/src/index.ts` and the relevant command file to confirm the current shape before writing anything.
39+
40+
| CLI path | Command file | Action files |
41+
| ---------------------- | ------------------------------------ | ----------------------- |
42+
| `phantom <cmd>` | `src/index.ts` (registered directly) | `src/actions/<name>.ts` |
43+
| `phantom wallet <cmd>` | `src/commands/wallet.ts` | `src/actions/<name>.ts` |
44+
| `phantom solana <cmd>` | `src/commands/solana.ts` | `src/actions/<name>.ts` |
45+
| `phantom evm <cmd>` | `src/commands/evm.ts` | `src/actions/<name>.ts` |
46+
| `phantom perps <cmd>` | `src/commands/perps.ts` | `src/actions/<name>.ts` |
47+
| `phantom token <cmd>` | `src/commands/token.ts` | `src/actions/<name>.ts` |
48+
49+
If the command belongs to a **new group** that doesn't have a command file yet, create `src/commands/<group>.ts` following the pattern of an existing one (e.g. `wallet.ts`), then register `<group>Cli` in `src/index.ts`.
50+
51+
Decide the kebab-case filename: `src/actions/<verb>-<noun>.ts` (e.g. `get-perp-account.ts`, `send-evm-transaction.ts`).
52+
53+
---
54+
55+
## Step 3 — Write the action file
56+
57+
Create `packages/cli/src/actions/<name>.ts` using the template below. Fill every section — do not leave TODOs or placeholders.
58+
59+
```typescript
60+
/**
61+
* <mcp_command_name> tool
62+
*
63+
* <One or two sentences describing what this action does and any important
64+
* preconditions (e.g. "Wallet must hold USDC in the perps account").>
65+
*/
66+
67+
import { Cli, z } from "incur";
68+
import { createAction } from "../utils/actions.js";
69+
// Import helpers as needed:
70+
// Session/wallet: import { WalletSchema } from "../utils/schemas.js";
71+
// Solana address: import { getSolanaAddress } from "../utils/solana.js";
72+
// EVM address: import { getEthereumAddress } from "../utils/evm.js";
73+
// Perps client: import { createPerpsClient } from "../utils/perps.js";
74+
// Shared schemas: import { ActionResponseSchema, PendingConfirmationSchema } from "../utils/output-schemas.js";
75+
76+
// Use WalletSchema.safeExtend({...}) if the action touches the wallet (adds walletId resolver + derivationIndex).
77+
// Use z.object({...}) if it needs no wallet access.
78+
const <PascalName>Schema = WalletSchema.safeExtend({
79+
// Each field needs a .describe() explaining its purpose and any defaults.
80+
});
81+
82+
const <PascalName>OutputSchema = z.object({
83+
// Define every field the run() function returns.
84+
// Use z.string().nullable() when a field can be null.
85+
// Use .optional() for fields that are sometimes absent.
86+
});
87+
88+
const <camelName>Action = createAction({
89+
// description: Shown to the LLM agent. Should be detailed enough that the
90+
// agent knows when and how to use this tool. Include:
91+
// - What it does
92+
// - Any required preconditions
93+
// - What the success response looks like
94+
// - Any important defaults or two-step flow notes
95+
description:
96+
"Phantom Wallet — <detailed description>.",
97+
options: <PascalName>Schema,
98+
output: <PascalName>OutputSchema,
99+
mcp: {
100+
// command: The MCP/RPC method name. Use snake_case. Should be more
101+
// descriptive than the CLI sub-command name because MCP tools are flat
102+
// (no nesting), e.g. "get_perp_account" not just "account".
103+
command: "<snake_case_mcp_name>",
104+
annotations: {
105+
// readOnlyHint: true → no side effects (reads only)
106+
// readOnlyHint: false → has side effects (writes, transactions)
107+
readOnlyHint: <true|false>,
108+
// destructiveHint: true → irreversible (sends tx, deletes data)
109+
destructiveHint: <true|false>,
110+
// openWorldHint: true → makes network/external calls
111+
openWorldHint: <true|false>,
112+
},
113+
},
114+
run: async ({ options: params, var: context }) => {
115+
const { logger } = context;
116+
const walletId = params.walletId(context.manager);
117+
const derivationIndex = params.derivationIndex;
118+
// Resolve walletId BEFORE calling getClient() so auth errors surface clearly.
119+
const client = context.manager.getClient();
120+
121+
// client → PhantomClient (sign, send, addresses)
122+
// context.apiClient → PhantomApiClient (REST endpoints)
123+
// walletId → resolved wallet ID (param or authenticated session)
124+
// derivationIndex → BIP-44 account index (default: 0)
125+
126+
// ... implementation ...
127+
128+
return { /* must match <PascalName>OutputSchema */ };
129+
},
130+
});
131+
132+
export const <camelName>Command = Cli.create("<cli-subcommand>", <camelName>Action.command);
133+
export const <camelName>Tool = <camelName>Action.tool;
134+
```
135+
136+
### Key rules when filling out the template
137+
138+
- **Input schema first, output schema second** — the input schema is defined above the output schema in every action file.
139+
- **`description` on every input field** — each `z.*` field must have `.describe(...)`. Include the default value in the description text if one exists (e.g. `"Default: false."`).
140+
- **No `output: z.any()`** — always define a concrete output schema.
141+
- **`mcp.command` vs CLI sub-command** — the MCP command name is flat and globally unique (`get_perp_account`); the CLI sub-command is the leaf word under its group (`account` under `phantom perps`).
142+
- **`readOnlyHint: true`** only for pure reads with no side effects.
143+
- **Shared output schemas** — if the output shape is also used by another action, add it to `src/utils/output-schemas.ts` and import it. Otherwise define it inline.
144+
- **`ActionResponseSchema`** — use for any Hyperliquid write operation that returns `{ status, data? }`.
145+
- **`PendingConfirmationSchema`** — use in a union for two-step flows (simulate first, then confirm).
146+
147+
---
148+
149+
## Step 4 — Register the CLI command
150+
151+
Open the relevant command file and add one import + one `.<command>()` call.
152+
153+
**Example — adding to `perps`:**
154+
155+
```typescript
156+
// In src/commands/perps.ts
157+
import { <camelName>Command } from "../actions/<name>.js";
158+
// ...
159+
perpsCli.command(<camelName>Command);
160+
```
161+
162+
**Example — adding top-level to `src/index.ts`:**
163+
164+
```typescript
165+
import { <camelName>Command } from "./actions/<name>.js";
166+
// ...
167+
cli.command(<camelName>Command);
168+
```
169+
170+
---
171+
172+
## Step 5 — Register the MCP tool for OpenClaw
173+
174+
Open `src/tools/index.ts`, add the import, and add the tool to the `tools` array in the appropriate section:
175+
176+
```typescript
177+
import { <camelName>Tool } from "../actions/<name>.js";
178+
179+
export const tools: ToolHandler[] = [
180+
// ... existing tools ...
181+
<camelName>Tool, // add in the logical group (wallet / solana / evm / perps read / perps write)
182+
];
183+
```
184+
185+
---
186+
187+
## Step 6 — Verify
188+
189+
Run the TypeScript check to confirm zero errors:
190+
191+
```bash
192+
cd packages/cli && npx tsc --noEmit
193+
```
194+
195+
If there are type errors, fix them before reporting the work as done. Common issues:
196+
197+
- Return type of `run()` doesn't match `<PascalName>OutputSchema` — check every return path.
198+
- `z.string().nullable()` needed where the value can be `null`.
199+
- Missing `as const` on literal returns inside discriminated unions.
200+
201+
---
202+
203+
## Step 7 — Update documentation
204+
205+
Four places need updating. Do all four before reporting done.
206+
207+
### 1. `packages/cli/src/index.ts` — MCP_INSTRUCTIONS
208+
209+
Add the new tool to the `Available tools:` sentence in `MCP_INSTRUCTIONS`:
210+
211+
```typescript
212+
"<mcp_command> (<one-line description>), " +
213+
```
214+
215+
### 2. `packages/cli/README.md`
216+
217+
- **Commands section**: Add the CLI command under the appropriate group heading (or add a new heading for a new group).
218+
- **MCP Tools table**: Add a row `| \`<mcp_name>\` | <description> |` in the logical group.
219+
220+
### 3. `packages/mcp-server/README.md`
221+
222+
Add a row to the relevant tools table (or a new table + heading for a new category):
223+
224+
```markdown
225+
| `<mcp_name>` | <One-sentence description.> |
226+
```
227+
228+
### 4. `packages/phantom-openclaw-plugin/README.md`
229+
230+
Add a full tool entry in the **Available Tools** section following the existing pattern:
231+
232+
```markdown
233+
### `<mcp_name>`
234+
235+
<Two-sentence description: what it does and when to use it.>
236+
237+
**Parameters:**
238+
239+
- `<param>` (<type>, required/optional): <description>
240+
241+
**Example:**
242+
243+
\`\`\`json
244+
{ "<param>": "<value>" }
245+
\`\`\`
246+
247+
**Response:** `{ <key fields> }`
248+
```
249+
250+
---
251+
252+
## Reference: context object
253+
254+
```typescript
255+
context.logger; // Logger — .info(), .warn(), .error(), .debug()
256+
context.apiClient; // PhantomApiClient — Phantom REST endpoints
257+
context.manager.getClient(); // PhantomClient — KMS signing, wallet ops
258+
context.manager.getSession(); // { walletId, organizationId }
259+
context.manager.isInitialized(); // boolean — false if not yet authed
260+
```
261+
262+
A session is initialized on first use (triggering the browser auth flow) and persisted automatically. The `createAction` wrapper handles `AUTH_EXPIRED` by resetting the session and surfacing a clear error — no manual handling needed.
263+
264+
---
265+
266+
## Reference: commonly used helpers
267+
268+
| Helper | Import path | When to use |
269+
| -------------------------------------------------------- | ------------------------ | ----------------------------------------------------------------------------------------- |
270+
| `getSolanaAddress(context, walletId, derivationIndex)` | `../utils/solana.js` | Derive Solana public key |
271+
| `getEthereumAddress(context, walletId, derivationIndex)` | `../utils/evm.js` | Derive EVM address |
272+
| `createPerpsClient(context, walletId, derivationIndex)` | `../utils/perps.js` | All Hyperliquid perps ops |
273+
| `normalizeNetworkId(id)` | `../utils/network.js` | Normalise CAIP-2 chain IDs |
274+
| `parseBaseUnitAmount(amount)` | `../utils/amount.js` | String/number → `bigint` base units |
275+
| `parseUiAmount(amount, decimals)` | `../utils/amount.js` | UI units → `bigint` base units |
276+
| `runSimulation(body, context)` | `../utils/simulation.js` | Simulate before submitting |
277+
| `WalletSchema` | `../utils/schemas.js` | Base schema with `walletId` resolver + `derivationIndex`; extend it instead of `z.object` |
278+
| `Caip2ChainIdSchema` | `../utils/schemas.js` | CAIP-2 chain ID input field |

.github/workflows/release.yml

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,35 +58,11 @@ jobs:
5858
- name: Build
5959
run: yarn build
6060

61-
- name: MCP server smoke test
62-
run: yarn workspace @phantom/mcp-server test:smoke
63-
env:
64-
PHANTOM_AUTH_BASE_URL: http://127.0.0.1:1
65-
PHANTOM_API_BASE_URL: http://127.0.0.1:1
66-
67-
- name: Run Lint
68-
run: yarn lint
69-
70-
- name: Run Tests
71-
run: yarn test
72-
env:
73-
ORGANIZATION_PRIVATE_KEY: ${{ secrets.ORGANIZATION_PRIVATE_KEY }}
74-
ORGANIZATION_ID: ${{ secrets.ORGANIZATION_ID }}
75-
APP_ID: ${{ secrets.APP_ID }}
76-
WALLET_API: ${{ secrets.WALLET_API }}
77-
SOLANA_RPC_URL: ${{ secrets.SOLANA_RPC_URL }}
78-
7961
- name: Publish to npm
8062
id: changesets
8163
uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba
8264
with:
8365
publish: yarn release
8466
commitMode: git-cli
8567
env:
86-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87-
88-
- name: Verify MCP package installs cleanly
89-
run: yarn workspace @phantom/mcp-server test:pack
90-
env:
91-
PHANTOM_AUTH_BASE_URL: http://127.0.0.1:1
92-
PHANTOM_API_BASE_URL: http://127.0.0.1:1
68+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

examples/with-nextjs/tsconfig.json

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"compilerOptions": {
33
"target": "ES2017",
4-
"lib": ["dom", "dom.iterable", "esnext"],
4+
"lib": [
5+
"dom",
6+
"dom.iterable",
7+
"esnext"
8+
],
59
"allowJs": true,
610
"skipLibCheck": true,
711
"strict": true,
@@ -11,17 +15,27 @@
1115
"moduleResolution": "bundler",
1216
"resolveJsonModule": true,
1317
"isolatedModules": true,
14-
"jsx": "preserve",
18+
"jsx": "react-jsx",
1519
"incremental": true,
1620
"plugins": [
1721
{
1822
"name": "next"
1923
}
2024
],
2125
"paths": {
22-
"@/*": ["./src/*"]
26+
"@/*": [
27+
"./src/*"
28+
]
2329
}
2430
},
25-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26-
"exclude": ["node_modules"]
31+
"include": [
32+
"next-env.d.ts",
33+
"**/*.ts",
34+
"**/*.tsx",
35+
".next/types/**/*.ts",
36+
".next/dev/types/**/*.ts"
37+
],
38+
"exclude": [
39+
"node_modules"
40+
]
2741
}

packages/api-key-stamper/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# @phantom/api-key-stamper
22

3+
## 2.0.2
4+
5+
### Patch Changes
6+
7+
- 1e542fb: upgrade packages, improving commands
8+
- Updated dependencies [1e542fb]
9+
- @phantom/base64url@2.0.2
10+
- @phantom/constants@2.0.2
11+
- @phantom/crypto@2.0.2
12+
- @phantom/sdk-types@2.0.2
13+
314
## 2.0.1
415

516
### Patch Changes

packages/api-key-stamper/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@phantom/api-key-stamper",
3-
"version": "2.0.1",
3+
"version": "2.0.2",
44
"description": "API key stamper for Phantom Wallet SDK",
55
"repository": {
66
"type": "git",

0 commit comments

Comments
 (0)