@agentwonderland/mcp 0.1.23 → 0.1.25
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.
- package/dist/core/__tests__/amount-utils.test.d.ts +1 -0
- package/dist/core/__tests__/amount-utils.test.js +11 -0
- package/dist/core/__tests__/card-setup.test.d.ts +1 -0
- package/dist/core/__tests__/card-setup.test.js +99 -0
- package/dist/core/__tests__/formatters.test.d.ts +1 -0
- package/dist/core/__tests__/formatters.test.js +15 -0
- package/dist/core/__tests__/passes.test.d.ts +1 -0
- package/dist/core/__tests__/passes.test.js +82 -0
- package/dist/core/__tests__/payments.test.d.ts +1 -0
- package/dist/core/__tests__/payments.test.js +101 -0
- package/dist/core/__tests__/principal.test.d.ts +1 -0
- package/dist/core/__tests__/principal.test.js +67 -0
- package/dist/core/__tests__/spend-policy.test.d.ts +1 -0
- package/dist/core/__tests__/spend-policy.test.js +40 -0
- package/dist/core/amount-utils.d.ts +1 -0
- package/dist/core/amount-utils.js +4 -0
- package/dist/core/api-client.d.ts +9 -4
- package/dist/core/api-client.js +52 -22
- package/dist/core/base-charge.js +3 -2
- package/dist/core/card-setup.d.ts +20 -13
- package/dist/core/card-setup.js +85 -29
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.js +46 -2
- package/dist/core/formatters.d.ts +4 -3
- package/dist/core/formatters.js +10 -8
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/ows-adapter.d.ts +10 -2
- package/dist/core/ows-adapter.js +54 -10
- package/dist/core/passes.d.ts +40 -0
- package/dist/core/passes.js +32 -0
- package/dist/core/payments.d.ts +8 -0
- package/dist/core/payments.js +111 -17
- package/dist/core/principal.d.ts +2 -0
- package/dist/core/principal.js +109 -0
- package/dist/core/solana-charge.d.ts +9 -0
- package/dist/core/solana-charge.js +96 -0
- package/dist/core/spend-policy.d.ts +12 -0
- package/dist/core/spend-policy.js +53 -0
- package/dist/core/types.d.ts +11 -2
- package/dist/index.js +11 -3
- package/dist/prompts/index.js +4 -2
- package/dist/resources/agents.js +1 -1
- package/dist/resources/wallet.js +8 -1
- package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
- package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
- package/dist/tools/_payment-confirmation.d.ts +6 -0
- package/dist/tools/_payment-confirmation.js +28 -0
- package/dist/tools/agent-info.js +16 -2
- package/dist/tools/favorites.js +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/observability.d.ts +2 -0
- package/dist/tools/observability.js +20 -0
- package/dist/tools/passes.d.ts +2 -0
- package/dist/tools/passes.js +157 -0
- package/dist/tools/run.js +127 -53
- package/dist/tools/solve.js +115 -51
- package/dist/tools/wallet.js +110 -59
- package/package.json +3 -1
- package/src/core/__tests__/amount-utils.test.ts +13 -0
- package/src/core/__tests__/card-setup.test.ts +118 -0
- package/src/core/__tests__/formatters.test.ts +17 -0
- package/src/core/__tests__/passes.test.ts +94 -0
- package/src/core/__tests__/payments.test.ts +122 -0
- package/src/core/__tests__/principal.test.ts +87 -0
- package/src/core/__tests__/spend-policy.test.ts +58 -0
- package/src/core/amount-utils.ts +5 -0
- package/src/core/api-client.ts +70 -23
- package/src/core/base-charge.ts +3 -2
- package/src/core/card-setup.ts +109 -34
- package/src/core/config.ts +74 -3
- package/src/core/formatters.ts +13 -9
- package/src/core/index.ts +2 -0
- package/src/core/ows-adapter.ts +74 -8
- package/src/core/passes.ts +74 -0
- package/src/core/payments.ts +130 -17
- package/src/core/principal.ts +128 -0
- package/src/core/solana-charge.ts +150 -0
- package/src/core/spend-policy.ts +69 -0
- package/src/core/types.ts +11 -2
- package/src/index.ts +11 -3
- package/src/prompts/index.ts +4 -2
- package/src/resources/agents.ts +1 -1
- package/src/resources/wallet.ts +8 -1
- package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
- package/src/tools/_payment-confirmation.ts +52 -0
- package/src/tools/agent-info.ts +25 -2
- package/src/tools/favorites.ts +1 -4
- package/src/tools/index.ts +1 -0
- package/src/tools/observability.ts +43 -0
- package/src/tools/passes.ts +228 -0
- package/src/tools/run.ts +174 -57
- package/src/tools/solve.ts +147 -59
- package/src/tools/wallet.ts +132 -62
package/src/tools/wallet.ts
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getWallets,
|
|
5
|
+
getCardConfig,
|
|
6
|
+
setCardConfig,
|
|
7
|
+
addWallet,
|
|
8
|
+
getPendingCardSetupToken,
|
|
9
|
+
getSpendPolicy,
|
|
10
|
+
setSpendPolicy,
|
|
11
|
+
} from "../core/config.js";
|
|
4
12
|
import { getWalletAddress } from "../core/payments.js";
|
|
5
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getOrCreatePendingCardSetup,
|
|
15
|
+
formatCardSetupBlocks,
|
|
16
|
+
getCardCapabilities,
|
|
17
|
+
pollCardSetup,
|
|
18
|
+
} from "../core/card-setup.js";
|
|
6
19
|
import {
|
|
7
20
|
isOwsAvailable,
|
|
8
21
|
createOwsWallet,
|
|
9
22
|
importKeyToOws,
|
|
10
23
|
listOwsWallets,
|
|
24
|
+
listOwsWalletsByChain,
|
|
11
25
|
} from "../core/ows-adapter.js";
|
|
26
|
+
import { ensureConsumerPrincipal, getConsumerPrincipal } from "../core/principal.js";
|
|
12
27
|
|
|
13
28
|
function text(t: string) {
|
|
14
29
|
return { content: [{ type: "text" as const, text: t }] };
|
|
15
30
|
}
|
|
16
31
|
|
|
17
|
-
// Pending card setup — stored between the QR display call and the confirmation poll
|
|
18
|
-
let pendingCardSetup: { token: string } | null = null;
|
|
19
|
-
|
|
20
32
|
export function registerWalletTools(server: McpServer): void {
|
|
21
33
|
// ── wallet_status (extracted from check_wallet) ─────────────────
|
|
22
34
|
server.tool(
|
|
@@ -26,8 +38,10 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
26
38
|
async () => {
|
|
27
39
|
const wallets = getWallets();
|
|
28
40
|
const card = getCardConfig();
|
|
41
|
+
const pendingCardSetupToken = getPendingCardSetupToken();
|
|
42
|
+
const consumerPrincipal = await getConsumerPrincipal();
|
|
29
43
|
|
|
30
|
-
if (wallets.length === 0 && !card) {
|
|
44
|
+
if (wallets.length === 0 && !card && !pendingCardSetupToken) {
|
|
31
45
|
return text(
|
|
32
46
|
"No payment methods configured.\nUse wallet_setup to create or import a wallet.",
|
|
33
47
|
);
|
|
@@ -36,17 +50,38 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
36
50
|
const lines = ["Payment methods:"];
|
|
37
51
|
|
|
38
52
|
for (const w of wallets) {
|
|
39
|
-
const addr = await getWalletAddress(w.id);
|
|
40
53
|
const label = w.label ? ` (${w.label})` : "";
|
|
41
54
|
const storage =
|
|
42
55
|
w.keyType === "ows" ? " [encrypted]" : " [plaintext]";
|
|
56
|
+
const chainAddresses = await Promise.all(
|
|
57
|
+
w.chains.map(async (chainName) => {
|
|
58
|
+
const addr = await getWalletAddress(chainName);
|
|
59
|
+
return `${chainName}: ${addr ?? "unknown"}`;
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
43
62
|
lines.push(
|
|
44
|
-
` ${w.id}${label}${storage}: ${
|
|
63
|
+
` ${w.id}${label}${storage}: ${chainAddresses.join(" | ")}`,
|
|
45
64
|
);
|
|
46
65
|
}
|
|
47
66
|
|
|
48
67
|
if (card) {
|
|
49
68
|
lines.push(` Card: ${card.brand} ****${card.last4}`);
|
|
69
|
+
const capabilities = await getCardCapabilities();
|
|
70
|
+
if (capabilities.spt_status === "enabled") {
|
|
71
|
+
lines.push(" Card MPP: ready");
|
|
72
|
+
} else if (capabilities.spt_status === "unavailable") {
|
|
73
|
+
lines.push(` Card MPP: unavailable — ${capabilities.message ?? "Stripe Shared Payment Token access is not enabled."}`);
|
|
74
|
+
} else {
|
|
75
|
+
lines.push(` Card MPP: unknown — ${capabilities.message ?? "Could not determine card payment readiness."}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (pendingCardSetupToken) {
|
|
80
|
+
lines.push(" Card setup: pending confirmation");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (consumerPrincipal) {
|
|
84
|
+
lines.push("", `Consumer principal: ${consumerPrincipal}`);
|
|
50
85
|
}
|
|
51
86
|
|
|
52
87
|
return text(lines.join("\n"));
|
|
@@ -72,7 +107,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
72
107
|
"Private key hex string (required for 'import', ignored for 'create')",
|
|
73
108
|
),
|
|
74
109
|
chain: z.enum(["tempo", "base", "solana"]).optional()
|
|
75
|
-
.describe("Primary chain (default: tempo). Solana
|
|
110
|
+
.describe("Primary chain (default: tempo). Solana uses an OWS wallet plus Stripe deposit-mode USDC."),
|
|
76
111
|
},
|
|
77
112
|
async ({ action, name, key, chain }) => {
|
|
78
113
|
// ── Card setup flow ──────────────────────────────────────
|
|
@@ -82,53 +117,37 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
82
117
|
return text(`Card already connected: ${existing.brand} ****${existing.last4}\n\nTo replace it, remove the current card first.`);
|
|
83
118
|
}
|
|
84
119
|
|
|
120
|
+
const pendingToken = getPendingCardSetupToken();
|
|
121
|
+
|
|
85
122
|
// Check if there's a pending setup to complete (user said they're done)
|
|
86
|
-
if (
|
|
87
|
-
const
|
|
88
|
-
const result = await pollCardSetup(pendingToken, 120_000);
|
|
89
|
-
pendingCardSetup = null;
|
|
123
|
+
if (pendingToken) {
|
|
124
|
+
const result = await pollCardSetup(pendingToken, 250);
|
|
90
125
|
|
|
91
126
|
if (result) {
|
|
92
|
-
|
|
127
|
+
const principal = await ensureConsumerPrincipal();
|
|
128
|
+
return text(
|
|
129
|
+
`Connected! ${result.brand} ****${result.last4} is ready for payments.\n\n` +
|
|
130
|
+
`Consumer principal: ${principal}`,
|
|
131
|
+
);
|
|
93
132
|
}
|
|
94
|
-
return text("Card setup timed out. Run wallet_setup({ action: \"add-card\" }) to try again.");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Create new card setup session with QR code, then auto-poll
|
|
98
|
-
try {
|
|
99
|
-
const { qr, url, token } = await initiateCardSetup();
|
|
100
|
-
|
|
101
|
-
// Auto-poll for up to 90 seconds — if the user enters their card
|
|
102
|
-
// quickly, the tool returns success without a second call
|
|
103
|
-
const result = await pollCardSetup(token, 90_000);
|
|
104
133
|
|
|
105
|
-
|
|
134
|
+
try {
|
|
135
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
136
|
+
const blocks = formatCardSetupBlocks(url);
|
|
106
137
|
return {
|
|
107
|
-
content:
|
|
108
|
-
{ type: "text" as const, text: "\n" + qr.trim() },
|
|
109
|
-
{ type: "text" as const, text: [
|
|
110
|
-
`Open to connect a card: ${url}`,
|
|
111
|
-
"",
|
|
112
|
-
`Connected! ${result.brand} ****${result.last4} is ready for payments.`,
|
|
113
|
-
].join("\n") },
|
|
114
|
-
],
|
|
138
|
+
content: blocks.map((t) => ({ type: "text" as const, text: t })),
|
|
115
139
|
};
|
|
140
|
+
} catch {
|
|
141
|
+
return text("Card setup timed out. Run wallet_setup({ action: \"add-card\" }) to try again.");
|
|
116
142
|
}
|
|
143
|
+
}
|
|
117
144
|
|
|
118
|
-
|
|
119
|
-
|
|
145
|
+
// Create new card setup session and return immediately so clients
|
|
146
|
+
// don't appear hung while the user is still in the browser flow.
|
|
147
|
+
try {
|
|
148
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
120
149
|
return {
|
|
121
|
-
content:
|
|
122
|
-
{ type: "text" as const, text: "\n" + qr.trim() },
|
|
123
|
-
{ type: "text" as const, text: [
|
|
124
|
-
`IMPORTANT: Present this link to the user to connect a payment card:`,
|
|
125
|
-
"",
|
|
126
|
-
url,
|
|
127
|
-
"",
|
|
128
|
-
`Tell the user: "Scan the QR code above or open this link to connect your card. Let me know when you're done."`,
|
|
129
|
-
`After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
|
|
130
|
-
].join("\n") },
|
|
131
|
-
],
|
|
150
|
+
content: formatCardSetupBlocks(url).map((t) => ({ type: "text" as const, text: t })),
|
|
132
151
|
};
|
|
133
152
|
} catch {
|
|
134
153
|
return text("Error: Could not create card setup session. Try again later.");
|
|
@@ -155,18 +174,23 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
155
174
|
|
|
156
175
|
const selectedChains = chain === "solana" ? ["solana"] : ["tempo", "base"];
|
|
157
176
|
const defaultCh = chain ?? "tempo";
|
|
177
|
+
const preferredOwsChain = defaultCh === "solana" ? "solana" : "evm";
|
|
158
178
|
|
|
159
179
|
const owsReady = await isOwsAvailable();
|
|
160
180
|
|
|
161
181
|
if (action === "create") {
|
|
162
182
|
// Check for existing OWS wallets first
|
|
163
183
|
if (owsReady) {
|
|
164
|
-
const existing =
|
|
184
|
+
const existing = preferredOwsChain === "solana"
|
|
185
|
+
? await listOwsWalletsByChain("solana")
|
|
186
|
+
: await listOwsWallets();
|
|
165
187
|
if (existing.length > 0) {
|
|
166
188
|
const w = existing[0];
|
|
167
189
|
// Check if already in config
|
|
168
190
|
const wallets = getWallets();
|
|
169
|
-
const alreadyLinked = wallets.find(wl => wl.owsWalletId === w.id);
|
|
191
|
+
const alreadyLinked = wallets.find((wl) => wl.owsWalletId === w.id);
|
|
192
|
+
let chainStatus = "Linked to Agent Wonderland.";
|
|
193
|
+
|
|
170
194
|
if (!alreadyLinked) {
|
|
171
195
|
addWallet({
|
|
172
196
|
id: w.name,
|
|
@@ -176,15 +200,28 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
176
200
|
defaultChain: defaultCh,
|
|
177
201
|
label: w.name,
|
|
178
202
|
});
|
|
203
|
+
} else {
|
|
204
|
+
const mergedChains = [...new Set([...alreadyLinked.chains, ...selectedChains])];
|
|
205
|
+
if (mergedChains.length !== alreadyLinked.chains.length) {
|
|
206
|
+
addWallet({
|
|
207
|
+
...alreadyLinked,
|
|
208
|
+
chains: mergedChains,
|
|
209
|
+
});
|
|
210
|
+
chainStatus = `Updated linked chains: ${mergedChains.join(", ")}.`;
|
|
211
|
+
} else {
|
|
212
|
+
chainStatus = "Already linked to Agent Wonderland.";
|
|
213
|
+
}
|
|
179
214
|
}
|
|
215
|
+
const principal = await ensureConsumerPrincipal();
|
|
180
216
|
return text([
|
|
181
217
|
"Found existing OWS wallet:",
|
|
182
218
|
` Name: ${w.name}`,
|
|
183
219
|
` Address: ${w.address}`,
|
|
184
220
|
` Chains: ${selectedChains.join(", ")}`,
|
|
185
221
|
` Storage: ~/.ows/ (encrypted)`,
|
|
222
|
+
` Consumer principal: ${principal}`,
|
|
186
223
|
"",
|
|
187
|
-
|
|
224
|
+
chainStatus,
|
|
188
225
|
"",
|
|
189
226
|
`Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
|
|
190
227
|
].join("\n"));
|
|
@@ -195,12 +232,14 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
195
232
|
return text(
|
|
196
233
|
"Cannot create wallet: OWS (Open Wallet Standard) is not installed.\n" +
|
|
197
234
|
"Install with: npm install -g @open-wallet-standard/core\n\n" +
|
|
198
|
-
|
|
235
|
+
(defaultCh === "solana"
|
|
236
|
+
? "Solana wallets currently require OWS-managed encrypted storage."
|
|
237
|
+
: "Alternatively, use action 'import' with an existing private key."),
|
|
199
238
|
);
|
|
200
239
|
}
|
|
201
240
|
|
|
202
241
|
const walletName = name ?? `aw-${Date.now()}`;
|
|
203
|
-
const result = await createOwsWallet(walletName);
|
|
242
|
+
const result = await createOwsWallet(walletName, preferredOwsChain);
|
|
204
243
|
|
|
205
244
|
// Persist to config so payment flows can find it
|
|
206
245
|
addWallet({
|
|
@@ -211,6 +250,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
211
250
|
defaultChain: defaultCh,
|
|
212
251
|
label: walletName,
|
|
213
252
|
});
|
|
253
|
+
const principal = await ensureConsumerPrincipal();
|
|
214
254
|
|
|
215
255
|
return text(
|
|
216
256
|
[
|
|
@@ -220,6 +260,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
220
260
|
` Name: ${walletName}`,
|
|
221
261
|
` Chains: ${selectedChains.join(", ")}`,
|
|
222
262
|
` Storage: ~/.ows/ (AES-256-GCM encrypted)`,
|
|
263
|
+
` Consumer principal: ${principal}`,
|
|
223
264
|
"",
|
|
224
265
|
`Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
|
|
225
266
|
"For testnet: npx mppx account fund",
|
|
@@ -230,7 +271,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
230
271
|
// action === "import"
|
|
231
272
|
if (owsReady) {
|
|
232
273
|
const walletName = name ?? `imported-${Date.now()}`;
|
|
233
|
-
const result = await importKeyToOws(key!, walletName);
|
|
274
|
+
const result = await importKeyToOws(key!, walletName, preferredOwsChain);
|
|
234
275
|
|
|
235
276
|
addWallet({
|
|
236
277
|
id: walletName,
|
|
@@ -240,6 +281,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
240
281
|
defaultChain: defaultCh,
|
|
241
282
|
label: walletName,
|
|
242
283
|
});
|
|
284
|
+
const principal = await ensureConsumerPrincipal();
|
|
243
285
|
|
|
244
286
|
return text(
|
|
245
287
|
[
|
|
@@ -248,10 +290,18 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
248
290
|
` Address: ${result.address}`,
|
|
249
291
|
` Name: ${walletName}`,
|
|
250
292
|
` Chains: ${selectedChains.join(", ")}`,
|
|
293
|
+
` Consumer principal: ${principal}`,
|
|
251
294
|
].join("\n"),
|
|
252
295
|
);
|
|
253
296
|
}
|
|
254
297
|
|
|
298
|
+
if (defaultCh === "solana") {
|
|
299
|
+
return text(
|
|
300
|
+
"Solana key import currently requires OWS (Open Wallet Standard) so the ed25519 key can be stored safely.\n" +
|
|
301
|
+
"Install with: npm install -g @open-wallet-standard/core",
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
255
305
|
// No OWS — store plaintext
|
|
256
306
|
try {
|
|
257
307
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
@@ -269,6 +319,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
269
319
|
defaultChain: defaultCh,
|
|
270
320
|
label: walletName,
|
|
271
321
|
});
|
|
322
|
+
const principal = await ensureConsumerPrincipal();
|
|
272
323
|
|
|
273
324
|
return text(
|
|
274
325
|
[
|
|
@@ -276,6 +327,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
276
327
|
` Address: ${account.address}`,
|
|
277
328
|
` Name: ${walletName}`,
|
|
278
329
|
` Chains: ${selectedChains.join(", ")}`,
|
|
330
|
+
` Consumer principal: ${principal}`,
|
|
279
331
|
"",
|
|
280
332
|
"Install OWS for encrypted storage: npm install -g @open-wallet-standard/core",
|
|
281
333
|
].join("\n"),
|
|
@@ -302,8 +354,13 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
302
354
|
.positive()
|
|
303
355
|
.optional()
|
|
304
356
|
.describe("Maximum USD per day across all transactions"),
|
|
357
|
+
require_confirmation_above: z
|
|
358
|
+
.number()
|
|
359
|
+
.positive()
|
|
360
|
+
.optional()
|
|
361
|
+
.describe("Require manual confirmation above this USD amount even when auto-spend is enabled"),
|
|
305
362
|
},
|
|
306
|
-
async ({ wallet_id, max_per_tx, max_per_day }) => {
|
|
363
|
+
async ({ wallet_id, max_per_tx, max_per_day, require_confirmation_above }) => {
|
|
307
364
|
const wallets = getWallets();
|
|
308
365
|
const wallet = wallets.find((w) => w.id === wallet_id);
|
|
309
366
|
|
|
@@ -314,23 +371,36 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
314
371
|
);
|
|
315
372
|
}
|
|
316
373
|
|
|
317
|
-
if (
|
|
374
|
+
if (
|
|
375
|
+
max_per_tx == null &&
|
|
376
|
+
max_per_day == null &&
|
|
377
|
+
require_confirmation_above == null
|
|
378
|
+
) {
|
|
318
379
|
return text(
|
|
319
|
-
"No policy changes specified. Provide
|
|
380
|
+
"No policy changes specified. Provide at least one policy field.",
|
|
320
381
|
);
|
|
321
382
|
}
|
|
322
383
|
|
|
384
|
+
const existing = getSpendPolicy(wallet_id) ?? {};
|
|
385
|
+
const nextPolicy = {
|
|
386
|
+
...existing,
|
|
387
|
+
...(max_per_tx != null ? { maxPerTxUsd: max_per_tx } : {}),
|
|
388
|
+
...(max_per_day != null ? { maxPerDayUsd: max_per_day } : {}),
|
|
389
|
+
...(require_confirmation_above != null ? { requireConfirmationAboveUsd: require_confirmation_above } : {}),
|
|
390
|
+
};
|
|
391
|
+
setSpendPolicy(wallet_id, nextPolicy);
|
|
392
|
+
|
|
323
393
|
// Build policy summary
|
|
324
394
|
const policies: string[] = [];
|
|
325
|
-
if (
|
|
326
|
-
policies.push(`Max per transaction: $${
|
|
395
|
+
if (nextPolicy.maxPerTxUsd != null) {
|
|
396
|
+
policies.push(`Max per transaction: $${nextPolicy.maxPerTxUsd.toFixed(2)}`);
|
|
327
397
|
}
|
|
328
|
-
if (
|
|
329
|
-
policies.push(`Max per day: $${
|
|
398
|
+
if (nextPolicy.maxPerDayUsd != null) {
|
|
399
|
+
policies.push(`Max per day: $${nextPolicy.maxPerDayUsd.toFixed(2)}`);
|
|
400
|
+
}
|
|
401
|
+
if (nextPolicy.requireConfirmationAboveUsd != null) {
|
|
402
|
+
policies.push(`Require confirmation above: $${nextPolicy.requireConfirmationAboveUsd.toFixed(2)}`);
|
|
330
403
|
}
|
|
331
|
-
|
|
332
|
-
// Note: actual persistence depends on the config module supporting policy fields.
|
|
333
|
-
// For now, we report what would be set. The core/config module will handle storage.
|
|
334
404
|
return text(
|
|
335
405
|
[
|
|
336
406
|
`Spending policy set for wallet "${wallet_id}":`,
|