@moonpay/cli 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,22 +6,20 @@ import {
6
6
  defineToolSchema,
7
7
  messageSign,
8
8
  resolveBaseUrl,
9
- resolveSigningKey,
10
9
  schemas_default,
10
+ tokenSwap,
11
11
  transactionSign,
12
+ virtualAccountWalletRegister,
12
13
  walletCreate,
13
14
  walletDelete,
14
15
  walletImport,
15
16
  walletList,
16
- walletLock,
17
- walletRetrieve,
18
- walletUnlock
19
- } from "./chunk-DAQDHLPY.js";
17
+ walletRetrieve
18
+ } from "./chunk-AH5VMN63.js";
20
19
  import {
21
- loadWallet
22
- } from "./chunk-AGDVU2O5.js";
23
- import "./chunk-Z33PSOPD.js";
24
- import "./chunk-EEBB5MQP.js";
20
+ findWalletOrThrow,
21
+ resolveSigningKey
22
+ } from "./chunk-V7MA7WNX.js";
25
23
 
26
24
  // src/mcp.ts
27
25
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -46,8 +44,18 @@ var x402RequestSchema = defineToolSchema({
46
44
  description: "Make an HTTP request to an x402-protected endpoint. Automatically handles payment when a 402 Payment Required response is received, signing the payment transaction with the local wallet.",
47
45
  input: z.object({
48
46
  method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).describe("HTTP method"),
49
- url: z.string().url().describe(
50
- "Full URL of the x402-protected endpoint (e.g., 'https://agents.moonpay.com/api/x402/tools/market_digest_retrieve')"
47
+ url: z.string().url().refine((u) => u.startsWith("https://"), { message: "URL must use HTTPS" }).refine(
48
+ (u) => {
49
+ try {
50
+ const host = new URL(u).hostname;
51
+ return !host.match(/^(localhost|127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|0\.0\.0\.0|\[::1\])/);
52
+ } catch {
53
+ return false;
54
+ }
55
+ },
56
+ { message: "URL must not target private/internal addresses" }
57
+ ).describe(
58
+ "Full HTTPS URL of the x402-protected endpoint (e.g., 'https://agents.moonpay.com/api/x402/tools/market_digest_retrieve')"
51
59
  ),
52
60
  body: z.record(z.any()).nullable().describe("Request body (for POST, PUT, PATCH)"),
53
61
  params: z.record(z.any()).nullable().describe("Query parameters"),
@@ -99,7 +107,7 @@ function createLocalX402Client(walletAddress, secretKey) {
99
107
  var x402Request = createTool(
100
108
  x402RequestSchema,
101
109
  async ({ method, url, body, params, wallet: walletNameOrAddress }) => {
102
- const walletMetadata = loadWallet(walletNameOrAddress);
110
+ const walletMetadata = findWalletOrThrow(walletNameOrAddress);
103
111
  const { privateKey, address } = resolveSigningKey(walletMetadata, "solana");
104
112
  const client = createLocalX402Client(address, privateKey);
105
113
  let response;
@@ -137,12 +145,12 @@ var LOCAL_TOOLS = [
137
145
  walletList,
138
146
  walletRetrieve,
139
147
  walletDelete,
140
- walletLock,
141
- walletUnlock,
142
148
  transactionSign,
143
149
  messageSign,
144
150
  bitcoinBalanceRetrieve,
145
- x402Request
151
+ x402Request,
152
+ tokenSwap,
153
+ virtualAccountWalletRegister
146
154
  ];
147
155
  var localToolMap = new Map(LOCAL_TOOLS.map((t) => [t.schema.name, t]));
148
156
  function resolveRemoteSchema(schema) {
@@ -198,43 +206,55 @@ async function startMcpServer() {
198
206
  isError: true
199
207
  };
200
208
  }
201
- const localTool = localToolMap.get(name);
202
- if (localTool) {
203
- const shape = localTool.schema.input.shape ?? {};
204
- for (const [key, field] of Object.entries(shape)) {
205
- if (params[key] === void 0 && field._def.typeName === "ZodNullable") {
206
- params[key] = null;
209
+ try {
210
+ const localTool = localToolMap.get(name);
211
+ if (localTool) {
212
+ const shape = localTool.schema.input.shape ?? {};
213
+ for (const [key, field] of Object.entries(shape)) {
214
+ if (params[key] === void 0 && field._def.typeName === "ZodNullable") {
215
+ params[key] = null;
216
+ }
207
217
  }
218
+ const result2 = await localTool.handler(params);
219
+ return {
220
+ content: [
221
+ { type: "text", text: JSON.stringify(result2, null, 2) }
222
+ ]
223
+ };
208
224
  }
209
- const result2 = await localTool.handler(params);
225
+ const props = remotePropsMap.get(name) ?? {};
226
+ for (const [key, prop] of Object.entries(props)) {
227
+ if (params[key] === void 0) {
228
+ const nullable = Array.isArray(prop.type) ? prop.type.includes("null") : prop.anyOf?.some((t) => t.type === "null");
229
+ if (nullable) params[key] = null;
230
+ }
231
+ const isNum = prop.type === "number" || prop.type === "integer" || Array.isArray(prop.type) && prop.type.some(
232
+ (t) => t === "number" || t === "integer"
233
+ ) || prop.anyOf?.some(
234
+ (t) => t.type === "number" || t.type === "integer"
235
+ );
236
+ if (isNum && typeof params[key] === "string") {
237
+ params[key] = Number(params[key]);
238
+ }
239
+ }
240
+ const baseUrl = resolveBaseUrl();
241
+ const result = await callTool(baseUrl, name, params);
210
242
  return {
211
243
  content: [
212
- { type: "text", text: JSON.stringify(result2, null, 2) }
244
+ { type: "text", text: JSON.stringify(result, null, 2) }
213
245
  ]
214
246
  };
247
+ } catch (error) {
248
+ return {
249
+ content: [
250
+ {
251
+ type: "text",
252
+ text: error instanceof Error ? error.message : String(error)
253
+ }
254
+ ],
255
+ isError: true
256
+ };
215
257
  }
216
- const props = remotePropsMap.get(name) ?? {};
217
- for (const [key, prop] of Object.entries(props)) {
218
- if (params[key] === void 0) {
219
- const nullable = Array.isArray(prop.type) ? prop.type.includes("null") : prop.anyOf?.some((t) => t.type === "null");
220
- if (nullable) params[key] = null;
221
- }
222
- const isNum = prop.type === "number" || prop.type === "integer" || Array.isArray(prop.type) && prop.type.some(
223
- (t) => t === "number" || t === "integer"
224
- ) || prop.anyOf?.some(
225
- (t) => t.type === "number" || t.type === "integer"
226
- );
227
- if (isNum && typeof params[key] === "string") {
228
- params[key] = Number(params[key]);
229
- }
230
- }
231
- const baseUrl = resolveBaseUrl();
232
- const result = await callTool(baseUrl, name, params);
233
- return {
234
- content: [
235
- { type: "text", text: JSON.stringify(result, null, 2) }
236
- ]
237
- };
238
258
  });
239
259
  const transport = new StdioServerTransport();
240
260
  await server.connect(transport);
@@ -242,4 +262,4 @@ async function startMcpServer() {
242
262
  export {
243
263
  startMcpServer
244
264
  };
245
- //# sourceMappingURL=mcp-QLBMU7OH.js.map
265
+ //# sourceMappingURL=mcp-VOEHXIOY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp.ts","../src/tools/x402/request/tool.ts","../src/tools/x402/request/schema.ts"],"sourcesContent":["import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport { callTool } from \"./client\";\nimport { resolveBaseUrl } from \"./auth\";\nimport type { Tool } from \"./tools/shared\";\nimport { walletCreate } from \"./tools/wallet/create/tool\";\nimport { walletImport } from \"./tools/wallet/import/tool\";\nimport { walletList } from \"./tools/wallet/list/tool\";\nimport { walletRetrieve } from \"./tools/wallet/retrieve/tool\";\nimport { walletDelete } from \"./tools/wallet/delete/tool\";\nimport { transactionSign } from \"./tools/transaction/sign/tool\";\nimport { messageSign } from \"./tools/message/sign/tool\";\nimport { bitcoinBalanceRetrieve } from \"./tools/bitcoin/balance/tool\";\nimport { x402Request } from \"./tools/x402/request/tool\";\nimport { tokenSwap } from \"./tools/token/swap/tool\";\nimport { virtualAccountWalletRegister } from \"./tools/virtual-account/wallet/register/tool\";\nimport schemas from \"./generated/schemas.json\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst LOCAL_TOOLS: Tool<any>[] = [\n walletCreate,\n walletImport,\n walletList,\n walletRetrieve,\n walletDelete,\n transactionSign,\n messageSign,\n bitcoinBalanceRetrieve,\n x402Request,\n tokenSwap,\n virtualAccountWalletRegister,\n];\n\nconst localToolMap = new Map(LOCAL_TOOLS.map((t) => [t.schema.name, t]));\n\n/**\n * Resolve a remote schema's $ref to the actual properties object.\n */\nfunction resolveRemoteSchema(schema: (typeof schemas)[number]) {\n const input = schema.inputSchema as any;\n if (input.$ref && input.definitions) {\n const defName = input.$ref.replace(\"#/definitions/\", \"\");\n return input.definitions[defName] ?? input;\n }\n return input;\n}\n\nexport async function startMcpServer() {\n const server = new Server(\n { name: \"moonpay\", version: \"1.0.0\" },\n { capabilities: { tools: { listChanged: true } } },\n );\n\n // Build tool definitions — local tools use Zod→JSON, remote use JSON directly\n const toolDefs: Array<{\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n }> = LOCAL_TOOLS.map((tool) => ({\n name: tool.schema.name,\n description: tool.schema.description,\n inputSchema: zodToJsonSchema(tool.schema.input) as Record<string, unknown>,\n }));\n\n const remotePropsMap = new Map<string, Record<string, any>>();\n for (const schema of schemas) {\n if (localToolMap.has(schema.name)) continue;\n const resolved = resolveRemoteSchema(schema);\n remotePropsMap.set(\n schema.name,\n (resolved.properties ?? {}) as Record<string, any>,\n );\n toolDefs.push({\n name: schema.name,\n description: schema.description,\n inputSchema: {\n type: \"object\",\n properties: resolved.properties ?? {},\n ...(resolved.required ? { required: resolved.required } : {}),\n ...(resolved.additionalProperties !== undefined\n ? { additionalProperties: resolved.additionalProperties }\n : {}),\n },\n });\n }\n\n const toolDefMap = new Map(toolDefs.map((t) => [t.name, t]));\n\n // tools/list — return raw JSON schemas directly\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: toolDefs.map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n })),\n }));\n\n // tools/call\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: params = {} } = request.params;\n\n if (!toolDefMap.has(name)) {\n return {\n content: [{ type: \"text\" as const, text: `Unknown tool: ${name}` }],\n isError: true,\n };\n }\n\n try {\n // Local tool — coerce nullable fields then run handler\n const localTool = localToolMap.get(name);\n if (localTool) {\n // Fill missing nullable fields with null (MCP clients may omit them)\n const shape = (localTool.schema.input as z.ZodObject<z.ZodRawShape>).shape ?? {};\n for (const [key, field] of Object.entries(shape)) {\n if (\n params[key] === undefined &&\n (field as z.ZodTypeAny)._def.typeName === \"ZodNullable\"\n ) {\n params[key] = null;\n }\n }\n\n const result = await localTool.handler(params);\n return {\n content: [\n { type: \"text\" as const, text: JSON.stringify(result, null, 2) },\n ],\n };\n }\n\n // Remote tool — coerce types then call API\n const props = remotePropsMap.get(name) ?? {};\n for (const [key, prop] of Object.entries(props)) {\n if (params[key] === undefined) {\n const nullable = Array.isArray(prop.type)\n ? prop.type.includes(\"null\")\n : prop.anyOf?.some((t: any) => t.type === \"null\");\n if (nullable) params[key] = null;\n }\n const isNum =\n prop.type === \"number\" ||\n prop.type === \"integer\" ||\n (Array.isArray(prop.type) &&\n prop.type.some(\n (t: string) => t === \"number\" || t === \"integer\",\n )) ||\n prop.anyOf?.some(\n (t: any) => t.type === \"number\" || t.type === \"integer\",\n );\n if (isNum && typeof params[key] === \"string\") {\n params[key] = Number(params[key]);\n }\n }\n\n const baseUrl = resolveBaseUrl();\n const result = await callTool(baseUrl, name, params);\n return {\n content: [\n { type: \"text\" as const, text: JSON.stringify(result, null, 2) },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: error instanceof Error ? error.message : String(error),\n },\n ],\n isError: true,\n };\n }\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n","import { Keypair, VersionedTransaction, VersionedMessage } from \"@solana/web3.js\";\nimport axios from \"axios\";\nimport { wrapAxiosWithPayment } from \"@x402/axios\";\nimport { x402Client } from \"@x402/core/client\";\nimport { ExactSvmScheme } from \"@x402/svm\";\nimport type { Address } from \"@solana/addresses\";\nimport type { SignatureDictionary } from \"@solana/signers\";\nimport type { Transaction } from \"@solana/transactions\";\nimport { createTool } from \"../../shared\";\nimport { findWalletOrThrow, resolveSigningKey } from \"../../wallet/store\";\nimport { x402RequestSchema } from \"./schema\";\n\nconst SOLANA_NETWORK = \"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\" as const;\n\nfunction createLocalX402Client(walletAddress: string, secretKey: Uint8Array) {\n const keypair = Keypair.fromSecretKey(secretKey);\n\n const signer = {\n address: walletAddress as Address,\n signTransactions: async (\n transactions: readonly Transaction[],\n ): Promise<readonly SignatureDictionary[]> => {\n return transactions.map((transaction) => {\n const messageBytes = new Uint8Array(transaction.messageBytes);\n const message = VersionedMessage.deserialize(messageBytes);\n const numSigs = Object.keys(transaction.signatures).length;\n const tx = new VersionedTransaction(\n message,\n new Array(numSigs).fill(new Uint8Array(64)),\n );\n\n tx.sign([keypair]);\n\n const signerIndex = message.staticAccountKeys.findIndex(\n (key) => key.toBase58() === walletAddress,\n );\n if (signerIndex === -1) {\n throw new Error(\n `Wallet ${walletAddress} is not a signer for this transaction`,\n );\n }\n\n return {\n [walletAddress]: tx.signatures[signerIndex],\n } as SignatureDictionary;\n });\n },\n };\n\n const client = new x402Client();\n const svmScheme = new ExactSvmScheme(signer);\n client.register(SOLANA_NETWORK, svmScheme);\n client.registerV1(\"solana\", svmScheme);\n\n return wrapAxiosWithPayment(axios.create(), client);\n}\n\nexport const x402Request = createTool(\n x402RequestSchema,\n async ({ method, url, body, params, wallet: walletNameOrAddress }) => {\n const walletMetadata = findWalletOrThrow(walletNameOrAddress);\n const { privateKey, address } = resolveSigningKey(walletMetadata, \"solana\");\n const client = createLocalX402Client(address, privateKey);\n\n let response;\n switch (method) {\n case \"GET\":\n response = await client.get(url, { params });\n break;\n case \"POST\":\n response = await client.post(url, body || {}, { params });\n break;\n case \"PUT\":\n response = await client.put(url, body || {}, { params });\n break;\n case \"PATCH\":\n response = await client.patch(url, body || {}, { params });\n break;\n case \"DELETE\":\n response = await client.delete(url, { params });\n break;\n default:\n throw new Error(`Unsupported HTTP method: ${method}`);\n }\n\n return {\n status: response.status,\n data: response.data,\n headers: response.headers as Record<string, string>,\n };\n },\n);\n","import { z } from \"zod\";\nimport { defineToolSchema } from \"../../shared\";\n\nexport const x402RequestSchema = defineToolSchema({\n name: \"x402_request\",\n description:\n \"Make an HTTP request to an x402-protected endpoint. Automatically handles payment when a 402 Payment Required response is received, signing the payment transaction with the local wallet.\",\n input: z.object({\n method: z\n .enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"])\n .describe(\"HTTP method\"),\n url: z\n .string()\n .url()\n .refine((u) => u.startsWith(\"https://\"), { message: \"URL must use HTTPS\" })\n .refine(\n (u) => {\n try {\n const host = new URL(u).hostname;\n return !host.match(/^(localhost|127\\.|10\\.|172\\.(1[6-9]|2\\d|3[01])\\.|192\\.168\\.|169\\.254\\.|0\\.0\\.0\\.0|\\[::1\\])/);\n } catch { return false; }\n },\n { message: \"URL must not target private/internal addresses\" },\n )\n .describe(\n \"Full HTTPS URL of the x402-protected endpoint (e.g., 'https://agents.moonpay.com/api/x402/tools/market_digest_retrieve')\",\n ),\n body: z\n .record(z.any())\n .nullable()\n .describe(\"Request body (for POST, PUT, PATCH)\"),\n params: z.record(z.any()).nullable().describe(\"Query parameters\"),\n wallet: z.string().describe(\"Wallet name or address to pay with\"),\n }),\n output: z.object({\n status: z.number().describe(\"HTTP status code\"),\n data: z.any().describe(\"Response data\"),\n headers: z.record(z.string()).describe(\"Response headers\"),\n }),\n});\n"],"mappings":"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","names":["result"]}
@@ -0,0 +1,22 @@
1
+ import { createRequire as __createRequire } from "module"; const require = __createRequire(import.meta.url);
2
+ import {
3
+ addWallet,
4
+ findWallet,
5
+ findWalletOrThrow,
6
+ loadWallets,
7
+ mutateWallets,
8
+ removeWallet,
9
+ resolveSigningKey,
10
+ saveWallets
11
+ } from "./chunk-V7MA7WNX.js";
12
+ export {
13
+ addWallet,
14
+ findWallet,
15
+ findWalletOrThrow,
16
+ loadWallets,
17
+ mutateWallets,
18
+ removeWallet,
19
+ resolveSigningKey,
20
+ saveWallets
21
+ };
22
+ //# sourceMappingURL=store-HCN56E6A.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moonpay/cli",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -21,7 +21,8 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@modelcontextprotocol/sdk": "^1.26.0",
24
- "@scure/bip32": "^1.4.0",
24
+ "@noble/hashes": "^1.7.0",
25
+ "@scure/bip32": "^1.4.0",
25
26
  "@scure/bip39": "^1.6.0",
26
27
  "@solana/web3.js": "^1.98.4",
27
28
  "@x402/axios": "^2.4.0",
@@ -35,6 +36,7 @@
35
36
  "ed25519-hd-key": "^1.3.0",
36
37
  "tiny-secp256k1": "^2.2.4",
37
38
  "tweetnacl": "^1.0.3",
39
+ "viem": "^2.46.2",
38
40
  "zod": "^3.25.76",
39
41
  "zod-to-json-schema": "^3.25.1"
40
42
  },
@@ -36,14 +36,17 @@ mp logout
36
36
 
37
37
  ## Local wallet management
38
38
 
39
- The CLI manages local Solana wallets stored in `~/.config/moonpay/wallets/`. Private keys never leave the machinetransactions are signed locally.
39
+ The CLI manages local wallets stored encrypted in `~/.config/moonpay/wallets.json`. Private keys are encrypted with AES-256-GCM using a random key stored in your OS keychain. No password required keys never leave the machine.
40
40
 
41
41
  ```bash
42
- # Create a new wallet
42
+ # Create a new HD wallet (Solana, Ethereum, Bitcoin, Tron)
43
43
  mp wallet create --name "my-wallet"
44
44
 
45
- # Import from a base58 private key
46
- mp wallet import --name "imported" --privateKey <base58-key>
45
+ # Import from a mnemonic (all chains)
46
+ mp wallet import --name "restored" --mnemonic "word1 word2 ..."
47
+
48
+ # Import from a private key (single chain)
49
+ mp wallet import --name "imported" --key <hex-key> --chain ethereum
47
50
 
48
51
  # List all local wallets
49
52
  mp wallet list
@@ -51,6 +54,9 @@ mp wallet list
51
54
  # Get wallet details (by name or address)
52
55
  mp wallet retrieve --wallet "my-wallet"
53
56
 
57
+ # Export mnemonic/key (interactive only — agents cannot run this)
58
+ mp wallet export --wallet "my-wallet"
59
+
54
60
  # Delete a wallet (irreversible)
55
61
  mp wallet delete --wallet "my-wallet" --confirm
56
62
  ```
@@ -64,35 +70,20 @@ mp wallet delete --wallet "my-wallet" --confirm
64
70
 
65
71
  ## Config locations
66
72
 
73
+ - **Wallets:** `~/.config/moonpay/wallets.json` (encrypted, AES-256-GCM)
74
+ - **Encryption key:** OS keychain (`moonpay-cli` / `encryption-key`)
67
75
  - **Credentials:** `~/.config/moonpay/credentials.json` (OAuth tokens)
68
76
  - **Config:** `~/.config/moonpay/config.json` (base URL, client ID)
69
- - **Wallets:** `~/.config/moonpay/wallets/` (local keypairs, 0600 permissions)
70
-
71
- ## Output formats
72
-
73
- ```bash
74
- mp -f json wallet list # Pretty JSON (default)
75
- mp -f compact wallet list # Single-line JSON
76
- mp -f table wallet list # ASCII table
77
- ```
78
-
79
- ## Environment for scripts/agents
80
-
81
- If running `mp` from a script or agent subprocess, ensure PATH includes the npm global bin:
82
-
83
- ```bash
84
- export PATH="$(npm config get prefix)/bin:$PATH"
85
- mp --version
86
- ```
87
77
 
88
- ## Troubleshooting
78
+ ## Security
89
79
 
90
- - **"command not found"** `npm i -g @moonpay/cli` and check PATH.
91
- - **401 / auth errors** Run `mp login`.
92
- - **"fetch failed"**Check network connectivity to agents.moonpay.com.
93
- - Tokens auto-refresh on expiry. If refresh fails, run `mp login` again.
80
+ - Wallet secrets are always encrypted on disk
81
+ - Encryption key is stored in macOS Keychain / Linux libsecret
82
+ - No password to remember the OS handles authentication
83
+ - `wallet export` requires an interactive terminal (TTY) agents and scripts cannot extract secrets
84
+ - 24-word BIP39 mnemonics (256-bit entropy)
94
85
 
95
86
  ## Related skills
96
87
 
97
- - **moonpay-swap-tokens** — Swap tokens using local wallets.
88
+ - **moonpay-swap-tokens** — Swap or bridge tokens using local wallets.
98
89
  - **moonpay-check-wallet** — Check wallet balances.
@@ -20,82 +20,75 @@ mp wallet list
20
20
  If no wallets exist, create one:
21
21
 
22
22
  ```bash
23
- mp wallet create --name "my-wallet"
23
+ mp wallet create --name "main"
24
24
  ```
25
25
 
26
- Tell the user their email, wallet address, and that this wallet's private key lives locally on their machine.
26
+ Tell the user their email, all wallet addresses (Solana, Ethereum, Bitcoin, Tron), and that keys are encrypted locally with a random key in the OS keychain.
27
27
 
28
28
  ## Mission 2: Recon
29
29
 
30
30
  **Goal:** See what's trending and research a token.
31
31
 
32
- First, check what's hot:
33
-
34
32
  ```bash
35
33
  mp token trending list --chain solana --limit 5 --page 1
36
34
  ```
37
35
 
38
- Present the top trending tokens with price, 24h change, and volume.
39
-
40
- Then ask the user to pick a token to research (or default to SOL):
36
+ Present the top trending tokens. Then ask the user to pick one to research:
41
37
 
42
38
  ```bash
43
39
  mp token search --query "<token>" --chain solana
44
40
  mp token retrieve --token <address-from-search> --chain solana
45
41
  ```
46
42
 
47
- Present: name, symbol, price, 24h change, market cap, liquidity, volume, and trading activity. Flag anything interesting (high volume, thin liquidity, etc).
43
+ Present: name, symbol, price, market cap, liquidity, volume.
48
44
 
49
45
  ## Mission 3: Portfolio Check
50
46
 
51
- **Goal:** See what's in the wallet.
47
+ **Goal:** See what's in the wallet across chains.
52
48
 
53
49
  ```bash
54
- mp token balance list --wallet <address> --chain solana
50
+ mp token balance list --wallet <solana-address> --chain solana
51
+ mp token balance list --wallet <eth-address> --chain ethereum
52
+ mp bitcoin balance retrieve --wallet <btc-address>
55
53
  ```
56
54
 
57
- Present holdings as a mini portfolio report: each token, amount, USD value, and % allocation. Give the total portfolio value.
55
+ Present holdings as a multi-chain portfolio report with USD values and total.
58
56
 
59
57
  ## Mission 4: Swap
60
58
 
61
- **Goal:** Build a swap transaction, sign it locally, and show the quote.
62
-
63
- Pick a small swap based on what the wallet holds (e.g. 0.01 USDC → SOL). If the wallet has no USDC, pick any token pair that makes sense.
64
-
65
- ```bash
66
- mp token swap build --input <input-mint> --output <output-mint> --amount <small-amount> --wallet <address>
67
- ```
68
-
69
- Show the quote from the `message` field. Ask the user: **"Want to execute this swap?"**
59
+ **Goal:** Execute a swap end-to-end.
70
60
 
71
- If yes:
61
+ Pick a small swap based on what the wallet holds:
72
62
 
73
63
  ```bash
74
- mp transaction sign --transaction <tx> --wallet <address>
75
- mp token swap execute --transaction <signed-tx> --requestId <id>
64
+ mp token swap \
65
+ --from-wallet main --from-chain solana \
66
+ --from-token So11111111111111111111111111111111111111112 \
67
+ --from-amount 0.01 \
68
+ --to-token EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
76
69
  ```
77
70
 
78
- If no, move on. Either way, explain what just happened: the transaction was built on the server, signed locally (key never left the machine), and submitted on-chain.
71
+ This builds, signs locally, and broadcasts in one step. Show the result and explain: the transaction was built on the server, signed locally (key never left the machine), and submitted on-chain.
79
72
 
80
73
  ## Mission 5: Buy with Fiat
81
74
 
82
- **Goal:** Generate a fiat checkout link and open it.
75
+ **Goal:** Generate a fiat checkout link.
83
76
 
84
77
  ```bash
85
- mp buy --token sol --amount 1 --wallet <address> --email <email-from-mission-1>
78
+ mp buy --token sol --amount 1 --wallet <solana-address> --email <email>
86
79
  ```
87
80
 
88
- Present the checkout URL and open it in the user's browser. Explain: this is MoonPay's fiat gateway where they can buy crypto with a card or bank transfer. Tokens get sent directly to their wallet.
81
+ Open the checkout URL. Explain: MoonPay's fiat gateway for buying crypto with card or bank transfer.
89
82
 
90
83
  ## Mission 6: Message Signing
91
84
 
92
85
  **Goal:** Sign a message to prove wallet ownership.
93
86
 
94
87
  ```bash
95
- mp message sign --wallet <address> --message "I own this wallet"
88
+ mp message sign --wallet main --chain solana --message "I own this wallet"
96
89
  ```
97
90
 
98
- Present the signature. Explain: this is used for wallet verification (e.g. registering with virtual accounts, proving ownership to dApps).
91
+ Present the signature. Explain: used for wallet verification (e.g. registering with virtual accounts, proving ownership to dApps).
99
92
 
100
93
  ## Mission 7: Virtual Account (optional)
101
94
 
@@ -105,28 +98,28 @@ Present the signature. Explain: this is used for wallet verification (e.g. regis
105
98
  mp virtual-account retrieve
106
99
  ```
107
100
 
108
- If they have one, show the status and next step. If not, explain what a virtual account is (KYC-verified fiat bridge) and that they can set one up with `mp virtual-account create`.
101
+ If they have one, show the status and next step. If not, explain what it is and that they can set one up with `mp virtual-account create`.
109
102
 
110
103
  ## Mission 8: Skills
111
104
 
112
- **Goal:** Show the user what skills are available.
105
+ **Goal:** Show what skills are available.
113
106
 
114
107
  ```bash
115
108
  mp skill list
116
109
  ```
117
110
 
118
- Explain: skills are guides that teach agents how to use the CLI. They can install them for Claude Code with `mp skill install`.
111
+ Explain: skills are guides that teach agents how to use the CLI. Install them with `mp skill install`.
119
112
 
120
113
  ## Debrief
121
114
 
122
- Summarize everything the user just did:
123
- - Authenticated and set up a wallet
115
+ Summarize everything:
116
+ - Set up a multi-chain HD wallet (encrypted, OS keychain secured)
124
117
  - Searched and analyzed tokens
125
- - Checked portfolio
126
- - Built and signed a swap (and maybe executed it)
118
+ - Checked portfolio across Solana, Ethereum, Bitcoin
119
+ - Executed a swap (built, signed locally, broadcast)
127
120
  - Generated a fiat buy link
128
- - Signed a message
121
+ - Signed a message for verification
129
122
  - Explored virtual accounts
130
123
  - Discovered skills
131
124
 
132
- End with: "You're oriented. Run `mp --help` to see all commands, or ask me anything."
125
+ End with: "You're all set. Run `mp --help` to see all commands, or ask me anything."
@@ -1,73 +1,83 @@
1
1
  ---
2
2
  name: moonpay-swap-tokens
3
- description: Swap tokens using the build, sign, execute flow. Use when the user wants to swap token A for token B on Solana.
3
+ description: Swap or bridge tokens across chains. Use when the user wants to swap, bridge, or move tokens between wallets or chains.
4
4
  tags: [trading]
5
5
  ---
6
6
 
7
- # Swap tokens
7
+ # Swap or bridge tokens
8
8
 
9
9
  ## Goal
10
10
 
11
- Swap tokens on Solana using the three-step flow: build an unsigned transaction, sign it locally with a wallet, then execute it on-chain.
11
+ Swap tokens on any supported chain, or bridge tokens across chains. One command handles everything: builds the transaction via swaps.xyz, signs locally, broadcasts, and registers for tracking.
12
12
 
13
- ## Tools
13
+ ## Command
14
14
 
15
- - `token swap build` — Build an unsigned swap transaction (returns base64 tx + quote)
16
- - `transaction sign` — Sign the transaction with a local wallet
17
- - `token swap execute` — Submit the signed transaction on-chain
15
+ ```bash
16
+ mp token swap \
17
+ --from-wallet <wallet-name> \
18
+ --from-chain <chain> \
19
+ --from-token <token-address> \
20
+ --from-amount <amount> \
21
+ --to-token <token-address> \
22
+ --to-chain <chain> # optional, defaults to from-chain
23
+ --to-wallet <wallet-name> # optional, defaults to from-wallet
24
+ ```
18
25
 
19
- ## Workflow
26
+ ## Examples
20
27
 
21
- ### 1. Build the swap transaction
28
+ ### Same-chain swap (SOL USDC on Solana)
22
29
 
23
30
  ```bash
24
- mp token swap build \
25
- --input <input-token-mint> \
26
- --output <output-token-mint> \
27
- --amount <amount-of-input-token> \
28
- --wallet <wallet-address>
31
+ mp token swap \
32
+ --from-wallet main --from-chain solana \
33
+ --from-token So11111111111111111111111111111111111111112 \
34
+ --from-amount 0.1 \
35
+ --to-token EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
29
36
  ```
30
37
 
31
- This returns a `transaction` (base64), `message` (human-readable quote), and `requestId`.
32
-
33
- ### 2. Sign the transaction locally
38
+ ### Cross-chain bridge (ETH USDC.e on Polygon)
34
39
 
35
40
  ```bash
36
- mp transaction sign \
37
- --transaction <base64-transaction> \
38
- --wallet <wallet-address>
41
+ mp token swap \
42
+ --from-wallet funded --from-chain ethereum \
43
+ --from-token 0x0000000000000000000000000000000000000000 \
44
+ --from-amount 0.003 \
45
+ --to-chain polygon \
46
+ --to-token 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
39
47
  ```
40
48
 
41
- This returns a signed `transaction` (base64). The private key never leaves the machine.
42
-
43
- ### 3. Execute the swap
49
+ ### ERC20 swap (auto-approves if needed)
44
50
 
45
51
  ```bash
46
- mp token swap execute \
47
- --transaction <signed-base64-transaction> \
48
- --requestId <request-id-from-build>
52
+ mp token swap \
53
+ --from-wallet funded --from-chain polygon \
54
+ --from-token 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 \
55
+ --from-amount 5 \
56
+ --to-token 0x0000000000000000000000000000000000000000
49
57
  ```
50
58
 
51
- This returns a `signature` (the on-chain transaction hash).
59
+ ## Supported chains
60
+
61
+ solana, ethereum, base, polygon, arbitrum, optimism, bnb, avalanche, bitcoin (bridges only)
52
62
 
53
- ## Example flow
63
+ ## How it works
54
64
 
55
- 1. User: "Swap 1 USDC for WBTC"
56
- 2. Resolve token addresses via `mp token search --query "USDC" --chain solana`.
57
- 3. Build: `mp token swap build --input EPjF...Dt1v --output 3NZ9...cmJh --amount 1 --wallet <addr>`
58
- 4. Show the quote from the `message` field. Ask for confirmation.
59
- 5. Sign: `mp transaction sign --transaction <tx> --wallet <addr>`
60
- 6. Execute: `mp token swap execute --transaction <signed-tx> --requestId <id>`
61
- 7. Return the transaction signature.
65
+ 1. Resolves wallet name address
66
+ 2. Builds unsigned transaction via swaps.xyz (handles decimal conversion)
67
+ 3. If ERC20 token needs approval, sends an approve transaction first, then re-builds
68
+ 4. Signs locally private key never leaves the machine
69
+ 5. Broadcasts to the network
70
+ 6. Registers for tracking
62
71
 
63
- ## Notes
72
+ ## Tips
64
73
 
65
- - If the user provides token names/symbols, resolve to addresses with `mp token search`.
66
- - Always show the quote and ask for confirmation before signing.
67
- - The wallet must be a local wallet (see `mp wallet list`).
74
+ - If the user provides token names/symbols, resolve to addresses with `mp token search --query "USDC" --chain solana`
75
+ - Check balances first with `mp token balance list --wallet <address> --chain <chain>`
76
+ - For cross-chain bridges, specify `--to-chain`. The tool detects it and routes through a bridge protocol.
77
+ - Native tokens use `0x0000000000000000000000000000000000000000` (EVM) or `So11111111111111111111111111111111111111112` (Solana)
68
78
 
69
79
  ## Related skills
70
80
 
71
- - **moonpay-discover-tokens** — Search for token addresses.
72
- - **moonpay-check-wallet** — Check balances before swapping.
73
- - **moonpay-auth** — Set up wallets for signing.
81
+ - **moonpay-discover-tokens** — Search for token addresses
82
+ - **moonpay-check-wallet** — Check balances before swapping
83
+ - **moonpay-auth** — Set up wallets for signing