@ftptech/canton-agent-wallet 2.2.0 → 2.3.1

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.
Files changed (49) hide show
  1. package/README.md +55 -10
  2. package/dist/cli-args.d.ts +27 -0
  3. package/dist/cli-args.d.ts.map +1 -1
  4. package/dist/cli-args.js +74 -0
  5. package/dist/cli-args.js.map +1 -1
  6. package/dist/cli.js +159 -34
  7. package/dist/cli.js.map +1 -1
  8. package/dist/merge.d.ts +26 -2
  9. package/dist/merge.d.ts.map +1 -1
  10. package/dist/merge.js +212 -31
  11. package/dist/merge.js.map +1 -1
  12. package/dist/pay-lock.d.ts.map +1 -1
  13. package/dist/pay-lock.js +8 -14
  14. package/dist/pay-lock.js.map +1 -1
  15. package/dist/pay.d.ts +5 -1
  16. package/dist/pay.d.ts.map +1 -1
  17. package/dist/pay.js +43 -8
  18. package/dist/pay.js.map +1 -1
  19. package/dist/registry-parties.d.ts +6 -0
  20. package/dist/registry-parties.d.ts.map +1 -1
  21. package/dist/registry-parties.js +8 -0
  22. package/dist/registry-parties.js.map +1 -1
  23. package/dist/relay-client.d.ts +39 -0
  24. package/dist/relay-client.d.ts.map +1 -1
  25. package/dist/relay-client.js +91 -11
  26. package/dist/relay-client.js.map +1 -1
  27. package/dist/relay-signer.d.ts.map +1 -1
  28. package/dist/relay-signer.js +15 -0
  29. package/dist/relay-signer.js.map +1 -1
  30. package/dist/swap.d.ts +14 -1
  31. package/dist/swap.d.ts.map +1 -1
  32. package/dist/swap.js +55 -12
  33. package/dist/swap.js.map +1 -1
  34. package/dist/tx.d.ts +4 -9
  35. package/dist/tx.d.ts.map +1 -1
  36. package/dist/tx.js +11 -75
  37. package/dist/tx.js.map +1 -1
  38. package/dist/verify-prepared.d.ts +14 -26
  39. package/dist/verify-prepared.d.ts.map +1 -1
  40. package/dist/verify-prepared.js +12 -73
  41. package/dist/verify-prepared.js.map +1 -1
  42. package/dist/withdraw.d.ts.map +1 -1
  43. package/dist/withdraw.js +66 -20
  44. package/dist/withdraw.js.map +1 -1
  45. package/package.json +4 -4
  46. package/dist/quest-fund.d.ts +0 -142
  47. package/dist/quest-fund.d.ts.map +0 -1
  48. package/dist/quest-fund.js +0 -355
  49. package/dist/quest-fund.js.map +0 -1
package/README.md CHANGED
@@ -24,22 +24,31 @@ Bin: `canton-agent-wallet`.
24
24
  ```
25
25
  canton-agent-wallet create --relay-url <url> # generate + onboard a self-custody wallet (idempotent)
26
26
  canton-agent-wallet address # print the party id (fund this)
27
- canton-agent-wallet balance # print the balance of every instrument the wallet holds (CC + any CIP-56 registry token, e.g. USDCx)
27
+ canton-agent-wallet balance # print the balance of every instrument the wallet holds (CC + any CIP-56 registry token, e.g. USDCx; "≥" when the wallet is too large to list in one read)
28
28
  canton-agent-wallet claim # accept incoming transfers — CC and registry tokens (expired offers are skipped; one bad offer never blocks the rest)
29
- canton-agent-wallet merge [--target <n>] [--batch <n>] [--max-rounds <n>] [--dry-run] [--admin <registrar> --id <instrument>] # consolidate dust (CC, or a registry token) so a busy wallet can be enumerated + spent again
30
- canton-agent-wallet preapproval [--status] [--admin <DSO>] [--days <n>] # MERCHANT: self-provision this wallet's own TransferPreapproval so transfer-factory payments settle in one round-trip (--status just checks)
31
- canton-agent-wallet pay --relay-url <url> <url> # fetch a URL, auto-paying any x402 402 challenge
32
- canton-agent-wallet swap --in <SYM> --out <SYM> --amount <n> [--venue cantex|tradecraft|auto] [--slippage <pct>] [--direct] [--no-wait] [--max-fee <cc>] [--swap-merchant <party>] [--no-preapprove] # swap CC <-> a CIP-56 registry token via Tradecraft; waits for the output to land (see below)
29
+ canton-agent-wallet merge [--target <n>] [--batch <n>] [--max-rounds <n>] [--dry-run] [--yes] [--admin <registrar> --id <instrument> | --all-tokens] # consolidate dust (CC, one registry token, or every trusted token) so a busy wallet can be enumerated + spent again
30
+ canton-agent-wallet preapproval [--status] [--admin <DSO|registrar>] [--id <instrument>] [--days <n>] # MERCHANT: self-provision this wallet's own TransferPreapproval so transfer-factory payments settle in one round-trip (--status just checks)
31
+ canton-agent-wallet pay [--relay-url <url>] [--asset <symbol>] [--max <amount>] [--pay-to <party>] <url> # fetch a URL, auto-paying any x402 402 challenge (--max caps one payment, --pay-to pins the payee)
32
+ canton-agent-wallet swap --in <SYM> --out <SYM> --amount <n> [--venue cantex|tradecraft|auto] [--slippage <pct>] [--direct] [--no-wait] [--max-fee <cc>] [--swap-merchant <party>] [--no-preapprove] # swap CC <-> a CIP-56 registry token on Cantex (default) or Tradecraft; waits for the output to land (see below)
33
33
  canton-agent-wallet withdraw --to <party> [--amount <n>] [--admin <registrar> --id <instrument>] # send CC (default: full balance) or a registry token back out
34
34
  canton-agent-wallet export # print the private key (backup; guard it)
35
+ canton-agent-wallet set-relay <url> # switch the relay saved in wallet.json (refuses one on another network)
35
36
  ```
36
37
 
37
38
  `create` and `pay` **require** a relay (facilitator) URL: pass `--relay-url
38
39
  <url>` or set `CANTON_AGENT_RELAY_URL`. There is no default: a stale built-in
39
40
  default would silently send payments to a dead host, so the CLI fails fast when
40
- none is given. `address`, `balance`, `claim`, `withdraw` and `export` reuse the
41
- relay stored in the wallet at `create` time. The current FTP facilitator is
42
- `https://facilitator.ftptech.xyz`.
41
+ none is given. Every other command uses the relay stored in the wallet at
42
+ `create` time, unless `--relay-url` / `CANTON_AGENT_RELAY_URL` names another for
43
+ that run (said on stderr; the saved one is not changed). `set-relay <url>`
44
+ changes the saved relay, after checking it settles on the wallet's network. The
45
+ current FTP facilitator is `https://facilitator.ftptech.xyz`.
46
+
47
+ `pay --max <amount>` caps what one payment may cost, in the unit of what is
48
+ paid: with `--asset <token>` that token's own amount, otherwise Canton Coin (and
49
+ a 402 asking for a token is then refused — add `--asset`). `--pay-to <party>`
50
+ pins the only payee the wallet will sign for. Both refuse before anything is
51
+ signed; without them `pay` pays whatever the 402 asks, as before.
43
52
 
44
53
  ### Merchant onboarding: self-provision a `TransferPreapproval`
45
54
 
@@ -77,7 +86,7 @@ batch collapses ~`--batch` amulets into ~2, restoring a spendable wallet:
77
86
 
78
87
  ```bash
79
88
  canton-agent-wallet merge --dry-run # show the plan (counts + batches), change nothing
80
- canton-agent-wallet merge # consolidate (defaults: --batch 90, --target 2, --max-rounds 30)
89
+ canton-agent-wallet merge # consolidate (defaults: --batch 70, --target 2, --max-rounds 30)
81
90
  canton-agent-wallet merge --yes # required for a LARGE whale pass (>20 batches; see cost note)
82
91
  ```
83
92
 
@@ -94,7 +103,40 @@ holdings), so each batch is self-transferred for its exact total, holdings are
94
103
  read through the token-standard `HoldingV1` view, locked holdings are never used
95
104
  as inputs, and the default target is 1 holding. The registrar must be a known or
96
105
  trusted registry admin (`CANTON_AGENT_REGISTRY_TRUSTED_PARTIES` for one the
97
- wallet does not ship with).
106
+ wallet does not ship with). The default batch is 90 for a registry token and 70
107
+ for Canton Coin (a transaction is capped at 256 nodes: a registry input costs
108
+ ~1, an Amulet input ~3).
109
+
110
+ **A wallet with more holdings than the participant lists in one read** (a
111
+ merchant paid thousands of times in a token: the participant refuses an
112
+ unbounded list past its element cap, 200 by default). The relay then answers a
113
+ bounded read of the wallet's **oldest** holdings instead of an error, and
114
+ `merge` works through it: merge a batch, read again — the merged holdings drop
115
+ out and the ones behind them come into view. Because the size is unknown up
116
+ front, such a run needs `--yes`, and `--max-rounds` (default 30) bounds it.
117
+ A per-token merge can only work on its own token; when the oldest holdings are
118
+ mostly another token's dust it says so and points at `--all-tokens`, which
119
+ merges every token of a trusted registrar, the largest group in view first,
120
+ until each is down to one holding:
121
+
122
+ ```bash
123
+ canton-agent-wallet merge --all-tokens --dry-run
124
+ canton-agent-wallet merge --all-tokens --yes
125
+ ```
126
+
127
+ A current relay narrows each read to what was asked for: Canton Coin by its own
128
+ templates, a token by its registry's holding template. So thousands of Canton
129
+ Coin dust holdings no longer hide a token, or make its total a lower bound —
130
+ only more holdings of that token's own registry than one read lists do (tokens
131
+ of one registry share a template), and on an older relay any mix does.
132
+
133
+ On such a wallet `balance` shows token totals as lower bounds ("≥"), `withdraw`
134
+ spends the fewest holdings that cover the amount (and refuses a full-balance
135
+ withdraw, whose amount it cannot know), and a swap into that token says it
136
+ cannot confirm delivery instead of reporting a false timeout (it still claims a
137
+ returned input during its window; `claim` picks up one that comes back later).
138
+ Paying from it works while the amount is coverable from the visible holdings;
139
+ otherwise the relay answers `holdings_merge_required` with the merge to run.
98
140
 
99
141
  For a wallet already too large to enumerate on-ledger, `merge` reads the holdings
100
142
  from the public SV Scan ACS snapshot (paginated, no node cap), sweeps over them
@@ -266,6 +308,9 @@ const r = await executeSwap({ amount: "60", ticket }); // sends, waits for
266
308
  settable via `--network <net>`.
267
309
  - `CANTON_AGENT_API_KEY`: only if the relay requires one (sent as the
268
310
  `X-Agent-Key` header).
311
+ - `CANTON_AGENT_RELAY_TIMEOUT_MS`: per-request relay timeout (default 120000). A
312
+ timed-out read fails the command; a timed-out SUBMISSION is reported as
313
+ "outcome unknown" — check `balance` before retrying, it may have committed.
269
314
  - `HTTPS_PROXY` / `HTTP_PROXY` (or `ALL_PROXY`): if set, the CLI routes all
270
315
  relay calls through that proxy. Node's `fetch` does NOT honor these on its
271
316
  own, so behind a corporate/regional proxy you must set one of them or every
@@ -100,4 +100,31 @@ export declare function preapprovalMode(args: readonly string[], env: Record<str
100
100
  mode: "legacy";
101
101
  operatorToken: string;
102
102
  };
103
+ /**
104
+ * `pay --max <amount> [--pay-to <party>]` → the signer's spend limits.
105
+ *
106
+ * `--max` caps the payment in the unit of what is paid. With `--asset` naming a
107
+ * registry token that is that token's own ceiling; otherwise it is the Canton
108
+ * Coin ceiling, and a 402 that asks for a token is then refused by the signer
109
+ * (a CC number cannot cap a token whose unit is worth something else) with the
110
+ * hint to add `--asset`. An asset we cannot resolve cannot be capped, so it is
111
+ * refused rather than paid uncapped. Present-but-invalid values fail fast.
112
+ */
113
+ export declare function payLimits(args: readonly string[], env?: NodeJS.ProcessEnv): {
114
+ kind: "ok";
115
+ maxPaymentValue?: string;
116
+ maxPaymentValueByInstrument?: Record<string, string>;
117
+ expectedPayTo?: string;
118
+ } | {
119
+ kind: "error";
120
+ message: string;
121
+ };
122
+ /**
123
+ * The registry instrument id `preapproval` provisions for registrar `admin`:
124
+ * `--id` when given, else the one instrument this wallet knows for that
125
+ * registrar. Undefined when it cannot be decided — the caller must then
126
+ * require `--id` rather than guess (it used to default to USDCx for EVERY
127
+ * registrar, provisioning the wrong instrument for CBTC, USDXLR, …).
128
+ */
129
+ export declare function preapprovalInstrumentId(args: readonly string[], admin: string): string | undefined;
103
130
  //# sourceMappingURL=cli-args.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAEhD,8EAA8E;AAC9E,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,CAyB1C,CAAC;AAEH,gEAAgE;AAChE,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAU9E;AAED;;0EAE0E;AAC1E,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAUvE;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAW7D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,GAAG,SAAS,CAEpB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAER;AAED,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,QAEkD,CAAC;AAElF;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAY5F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CASvF"}
1
+ {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAEhD,8EAA8E;AAC9E,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,CA8B1C,CAAC;AAEH,gEAAgE;AAChE,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAU9E;AAED;;0EAE0E;AAC1E,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAUvE;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAW7D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,GAAG,SAAS,CAEpB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAER;AAED,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,QAEkD,CAAC;AAElF;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAY5F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CASvF;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAM,CAAC,UAAwB,GAElC;IACE,IAAI,EAAE,IAAI,CAAC;IACX,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2BAA2B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA+CrC;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,SAAS,CAKpB"}
package/dist/cli-args.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * `cli.ts` (which auto-runs `main()` on import and would call
6
6
  * `process.exit`). `cli.ts` composes these with its env + `fail()`.
7
7
  */
8
+ import { envTrustedRegistryAdmins, instrumentKey, KNOWN_INSTRUMENTS, resolveSwapSymbol, } from "./registry-parties.js";
8
9
  export const DEFAULT_NETWORK = "canton:testnet";
9
10
  /** Known `--flag <value>` options; used to strip flags out of positionals. */
10
11
  export const VALUE_FLAGS = new Set([
@@ -32,6 +33,11 @@ export const VALUE_FLAGS = new Set([
32
33
  "--expires-at",
33
34
  "--days",
34
35
  "--operator-token",
36
+ "--id",
37
+ "--venue",
38
+ // pay: spend ceiling and payee pin.
39
+ "--max",
40
+ "--pay-to",
35
41
  ]);
36
42
  /** First value following `--<name>` in `args`, or undefined. */
37
43
  export function flag(args, name) {
@@ -178,4 +184,72 @@ export function preapprovalMode(args, env) {
178
184
  ambientIgnored: Boolean(env["CANTON_X402_OPERATOR_TOKEN"]),
179
185
  };
180
186
  }
187
+ /**
188
+ * `pay --max <amount> [--pay-to <party>]` → the signer's spend limits.
189
+ *
190
+ * `--max` caps the payment in the unit of what is paid. With `--asset` naming a
191
+ * registry token that is that token's own ceiling; otherwise it is the Canton
192
+ * Coin ceiling, and a 402 that asks for a token is then refused by the signer
193
+ * (a CC number cannot cap a token whose unit is worth something else) with the
194
+ * hint to add `--asset`. An asset we cannot resolve cannot be capped, so it is
195
+ * refused rather than paid uncapped. Present-but-invalid values fail fast.
196
+ */
197
+ export function payLimits(args, env = process.env) {
198
+ const out = { kind: "ok" };
199
+ if (flagPresent(args, "--pay-to")) {
200
+ const to = flag(args, "--pay-to")?.trim();
201
+ if (!to)
202
+ return { kind: "error", message: "pay --pay-to was given with no party — refusing." };
203
+ out.expectedPayTo = to;
204
+ }
205
+ if (!flagPresent(args, "--max"))
206
+ return out;
207
+ const raw = flag(args, "--max")?.trim() ?? "";
208
+ if (!/^\d+(\.\d+)?$/.test(raw) || Number(raw) <= 0) {
209
+ return {
210
+ kind: "error",
211
+ message: `pay --max must be a positive amount (got ${JSON.stringify(raw)}) — refusing to pay uncapped.`,
212
+ };
213
+ }
214
+ const asset = flag(args, "--asset");
215
+ if (asset === undefined) {
216
+ out.maxPaymentValue = raw;
217
+ return out;
218
+ }
219
+ const sym = resolveSwapSymbol(asset);
220
+ if (sym.kind === "cc") {
221
+ out.maxPaymentValue = raw;
222
+ }
223
+ else if (sym.kind === "registry") {
224
+ // Keyed on the MainNet registrar AND every registrar trusted through
225
+ // CANTON_AGENT_REGISTRY_TRUSTED_PARTIES: on testnet/devnet the same symbol
226
+ // comes from such a registrar, and a cap keyed only on the MainNet one would
227
+ // never apply there. The signer looks the cap up by the instrument actually
228
+ // paid, so a key for a registrar that does not issue this id is inert.
229
+ const admins = new Set([sym.instrument.admin, ...envTrustedRegistryAdmins(env)]);
230
+ out.maxPaymentValueByInstrument = Object.fromEntries([...admins].map((admin) => [instrumentKey(admin, sym.instrument.id), raw]));
231
+ }
232
+ else {
233
+ return {
234
+ kind: "error",
235
+ message: `pay --max with --asset ${asset}: that asset is not one this wallet knows, so the cap ` +
236
+ `cannot be applied to it — refusing to pay uncapped.`,
237
+ };
238
+ }
239
+ return out;
240
+ }
241
+ /**
242
+ * The registry instrument id `preapproval` provisions for registrar `admin`:
243
+ * `--id` when given, else the one instrument this wallet knows for that
244
+ * registrar. Undefined when it cannot be decided — the caller must then
245
+ * require `--id` rather than guess (it used to default to USDCx for EVERY
246
+ * registrar, provisioning the wrong instrument for CBTC, USDXLR, …).
247
+ */
248
+ export function preapprovalInstrumentId(args, admin) {
249
+ const explicit = flag(args, "--id")?.trim();
250
+ if (explicit)
251
+ return explicit;
252
+ const known = Object.values(KNOWN_INSTRUMENTS).filter((i) => i.admin === admin);
253
+ return known.length === 1 ? known[0].id : undefined;
254
+ }
181
255
  //# sourceMappingURL=cli-args.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * address print the party id (fund this)
7
7
  * balance print every instrument the wallet holds (CC + registry tokens)
8
8
  * claim accept incoming transfers (e.g. the initial funding)
9
- * merge [--target <n>] [--batch <n>] [--max-rounds <n>] [--dry-run] [--yes] [--admin <registrar> --id <instrument>]
9
+ * merge [--target <n>] [--batch <n>] [--max-rounds <n>] [--dry-run] [--yes] [--admin <registrar> --id <instrument> | --all-tokens]
10
10
  * consolidate dust amulets (self-transfer in batches)
11
11
  * so a wallet with thousands of holdings can be
12
12
  * enumerated + spent again. --dry-run prints the plan;
@@ -18,13 +18,13 @@
18
18
  * transfer-factory path — SELF-provisions the
19
19
  * merchant's own preapproval so x402 CC payments
20
20
  * settle in one round-trip. --status just checks.
21
- * pay [--relay-url <url>] [--asset <symbol>] <url> fetch a URL, auto-paying any
21
+ * pay [--relay-url <url>] [--asset <symbol>] [--max <amount>] [--pay-to <party>] <url> fetch a URL, auto-paying any
22
22
  * x402 402 challenge; --asset prefers that
23
23
  * instrument (e.g. USDCx) when the 402 offers
24
24
  * several, else the first compatible entry
25
25
  * withdraw --to <p> send CC back out (--amount <cc> for a partial amount)
26
- * swap --in <SYM> --out <SYM> --amount <n> [--slippage <pct>] [--direct] [--no-wait]
27
- * swap on Tradecraft: pre-approve the output, send
26
+ * swap --in <SYM> --out <SYM> --amount <n> [--venue cantex|tradecraft|auto] [--slippage <pct>] [--direct] [--no-wait]
27
+ * swap on Cantex (default) or Tradecraft: pre-approve the output, send
28
28
  * the input to the pool, and WAIT until the output
29
29
  * lands (no manual claim). By DEFAULT the ticket (pool,
30
30
  * minimum, amm_cid) comes from our x402 swap endpoint (a
@@ -36,14 +36,17 @@
36
36
  * export print the private key (backup; guard it)
37
37
  * import [--relay-url <url>] [--key-file <p>] restore a wallet from a backed-up
38
38
  * key (stdin or --key-file); inverse of export
39
+ * set-relay <url> switch the relay saved in wallet.json (checks it
40
+ * settles on this wallet's network first)
39
41
  *
40
42
  * The relay (facilitator) URL is REQUIRED for the commands that talk to a
41
43
  * relay (`create`, `pay`, `import`). There is intentionally no built-in default — a
42
44
  * wrong/stale default would silently send an agent's payments to a dead host.
43
45
  * Supply it via `--relay-url <url>` or the `CANTON_AGENT_RELAY_URL` env var.
44
- * The current FTP facilitator is `https://facilitator.ftptech.xyz`. `address`,
45
- * `balance`, `claim`, `withdraw` and `export` reuse the relay stored in the
46
- * wallet at `create` time and do not need the flag.
46
+ * The current FTP facilitator is `https://facilitator.ftptech.xyz`. Every other
47
+ * command uses the relay saved in the wallet at `create` time, unless
48
+ * `--relay-url` / CANTON_AGENT_RELAY_URL names another one for that run;
49
+ * `set-relay` changes the saved one.
47
50
  *
48
51
  * Behind a proxy? Node's fetch ignores HTTP(S)_PROXY by default; this CLI wires
49
52
  * an undici ProxyAgent when HTTPS_PROXY/HTTP_PROXY/ALL_PROXY is set (see proxy.ts).
@@ -54,18 +57,18 @@
54
57
  /* eslint-disable no-console -- CLI entrypoint: stdout is its result channel */
55
58
  import { readFileSync } from "node:fs";
56
59
  import { ensureWallet } from "./onboard.js";
57
- import { loadWallet } from "./store.js";
60
+ import { loadWallet, saveWallet, walletPath } from "./store.js";
58
61
  import { agentKeyFromPrivatePem } from "./keys.js";
59
62
  import { makePayingFetch } from "./pay.js";
60
63
  import { RelayClient, isHoldingsExceedNodeLimitError } from "./relay-client.js";
61
64
  import { claimAll, selfProvisionPreapproval, selfProvisionRegistryPreapproval, } from "./tx.js";
62
65
  import { isTrustedRegistryAdmin } from "./registry-parties.js";
63
- import { mergeHoldings } from "./merge.js";
66
+ import { mergeHoldings, mergeAllRegistryHoldings } from "./merge.js";
64
67
  import { withdraw } from "./withdraw.js";
65
68
  import { executeSwap } from "./swap.js";
66
69
  import { resolveSwapIntent, resolveSwapTicket, swapFloorNotes, parseSwapVenue, parseSlippagePct, SWAP_MAX_FEE_CC, } from "./swap-route.js";
67
70
  import { installProxyFromEnv } from "./proxy.js";
68
- import { flag, boolFlag, intFlag, positionals, resolveNetwork, resolveRelayUrl, MISSING_RELAY_HELP, withdrawAmount, preapprovalMode, } from "./cli-args.js";
71
+ import { flag, boolFlag, intFlag, positionals, resolveNetwork, resolveRelayUrl, MISSING_RELAY_HELP, withdrawAmount, preapprovalMode, payLimits, preapprovalInstrumentId, } from "./cli-args.js";
69
72
  import { resolveTrustedDsoParty } from "./trusted-dso.js";
70
73
  const API_KEY = process.env.CANTON_AGENT_API_KEY;
71
74
  function fail(msg) {
@@ -94,11 +97,34 @@ async function main() {
94
97
  if (proxy)
95
98
  console.error(`(using proxy ${proxy})`);
96
99
  const [cmd, ...args] = process.argv.slice(2);
100
+ /** The relay this run talks to: `--relay-url` / CANTON_AGENT_RELAY_URL when
101
+ * given, else the one saved at `create`. A flag that was silently ignored
102
+ * pointed commands at the old relay after a cut-over; now it wins, and is
103
+ * never written back (`set-relay` does that on purpose). */
104
+ const relayFor = (w) => {
105
+ const url = resolveRelayUrl(args);
106
+ if (!url || url === w.relayUrl)
107
+ return w;
108
+ console.error(`(using relay ${url}; this wallet's saved relay is ${w.relayUrl} — ` +
109
+ `\`canton-agent-wallet set-relay <url>\` changes it)`);
110
+ return { ...w, relayUrl: url };
111
+ };
112
+ const requireWallet = () => {
113
+ const w = loadWallet();
114
+ if (!w)
115
+ return fail("no wallet yet — run: canton-agent-wallet create");
116
+ return relayFor(w);
117
+ };
97
118
  switch (cmd) {
98
119
  case "create": {
99
120
  const relayUrl = requireRelay(args);
100
- if (!loadWallet())
121
+ const existing = loadWallet();
122
+ if (!existing)
101
123
  console.error("creating the wallet (takes about half a minute)…");
124
+ else if (existing.relayUrl !== relayUrl) {
125
+ console.error(`a wallet already exists; it keeps its saved relay ${existing.relayUrl}. ` +
126
+ `To switch it to ${relayUrl}: canton-agent-wallet set-relay ${relayUrl}`);
127
+ }
102
128
  // A relay that funds new wallets sends a starter grant; ensureWallet
103
129
  // waits for it and accepts it before returning.
104
130
  const grant = {};
@@ -122,7 +148,7 @@ async function main() {
122
148
  ` party: ${w.party}\n` +
123
149
  starterLine +
124
150
  ` fund me: send CC to that party id, then run: canton-agent-wallet claim\n` +
125
- ` keyfile: ~/.canton-agent/wallet.json <- this IS your wallet; back it up\n` +
151
+ ` keyfile: ${walletPath()} <- this IS your wallet; back it up\n` +
126
152
  ` By creating a wallet you accept the Terms of Service: https://ftp-tech.mintlify.site/terms`);
127
153
  break;
128
154
  }
@@ -134,9 +160,7 @@ async function main() {
134
160
  break;
135
161
  }
136
162
  case "balance": {
137
- const w = loadWallet();
138
- if (!w)
139
- return fail("no wallet yet — run: canton-agent-wallet create");
163
+ const w = requireWallet();
140
164
  try {
141
165
  const relay = new RelayClient({ relayUrl: w.relayUrl, apiKey: API_KEY });
142
166
  const b = await relay.balance(w.party);
@@ -145,19 +169,39 @@ async function main() {
145
169
  // and printed after CC on purpose: a relay that predates /holdings makes
146
170
  // this line throw, and a wallet must keep showing its Canton Coin on an
147
171
  // older relay rather than fail the whole command over a token listing.
172
+ //
173
+ // Read BOUNDED: a wallet with more holdings than the participant lists in
174
+ // one read gets part of them (complete:false). The totals are then
175
+ // lower bounds, shown as "≥", with the command that makes them exact —
176
+ // this line used to swallow that 413 and print nothing at all, so a
177
+ // wallet full of USDCx looked like it held only Canton Coin. Only the
178
+ // tokens are asked for (`registryOnly`): on a current relay Canton Coin
179
+ // dust then no longer turns every token total into a lower bound.
148
180
  try {
149
- const h = await relay.holdings(w.party);
181
+ const h = await relay.holdings(w.party, undefined, { slice: true, registryOnly: true });
182
+ const ge = h.complete ? "" : "≥ ";
150
183
  for (const ins of h.instruments) {
151
184
  if (ins.id === "Amulet")
152
185
  continue; // already printed as CC above
153
186
  const locked = ins.holdings.filter((x) => x.locked).length;
154
- console.log(`${ins.total} ${ins.id} (${ins.holdings.length} holding${ins.holdings.length === 1 ? "" : "s"}` +
187
+ console.log(`${ge}${ins.total} ${ins.id} (${ge}${ins.holdings.length} holding${ins.holdings.length === 1 ? "" : "s"}` +
155
188
  (locked ? `, ${locked} locked` : "") +
156
189
  `) @ ${ins.admin}`);
157
190
  }
191
+ if (!h.complete) {
192
+ console.log(" (this wallet holds more than the participant lists in one read — token totals above " +
193
+ "are lower bounds; consolidate with: canton-agent-wallet merge --all-tokens, " +
194
+ "and plain `canton-agent-wallet merge` for Canton Coin dust)");
195
+ }
158
196
  }
159
- catch {
160
- /* older relay without /holdings — CC line above already shown */
197
+ catch (err) {
198
+ // An older relay without /holdings answers Fastify's route-not-found;
199
+ // that stays quiet (the CC line above is the whole answer there). Any
200
+ // other failure is said, not swallowed.
201
+ const notServed = err instanceof Error && /-> 404 .*Route GET:/.test(err.message);
202
+ if (!notServed) {
203
+ console.error(`tokens: could not list (${err instanceof Error ? err.message.split("\n")[0] : String(err)})`);
204
+ }
161
205
  }
162
206
  }
163
207
  catch (err) {
@@ -177,9 +221,7 @@ async function main() {
177
221
  // Consolidate dust amulets by self-transferring them in batches (receiver ==
178
222
  // this wallet, enforced by verify-before-sign). Restores `balance`/`pay` for
179
223
  // a wallet whose amulet count outgrew the participant's ACS element cap.
180
- const w = loadWallet();
181
- if (!w)
182
- return fail("no wallet yet — run: canton-agent-wallet create");
224
+ const w = requireWallet();
183
225
  const target = intFlag(args, "--target");
184
226
  const batch = intFlag(args, "--batch");
185
227
  const maxRounds = intFlag(args, "--max-rounds");
@@ -195,6 +237,24 @@ async function main() {
195
237
  if ((mAdmin && !mId) || (!mAdmin && mId)) {
196
238
  return fail("merge: --admin <registrar> and --id <instrument> must be given together");
197
239
  }
240
+ // --all-tokens: every token of a trusted registrar, whatever the mix — the
241
+ // way through a wallet whose oldest holdings are another token's dust.
242
+ if (boolFlag(args, "--all-tokens")) {
243
+ if (mAdmin)
244
+ return fail("merge: --all-tokens merges every trusted token — drop --admin/--id");
245
+ const res = await mergeAllRegistryHoldings(new RelayClient({ relayUrl: w.relayUrl, apiKey: API_KEY }), w, {
246
+ ...(batch !== undefined ? { batch } : {}),
247
+ ...(maxRounds !== undefined ? { maxRounds } : {}),
248
+ dryRun,
249
+ yes,
250
+ onProgress: (line) => console.log(line),
251
+ });
252
+ if (!dryRun) {
253
+ console.log(`merge complete: ${res.rounds} batch transfer(s), ${res.merged.length} token holding(s) consumed` +
254
+ (res.skippedStale > 0 ? ` (${res.skippedStale} stale batch(es) skipped)` : ""));
255
+ }
256
+ break;
257
+ }
198
258
  if (mAdmin && !isTrustedRegistryAdmin(mAdmin, process.env)) {
199
259
  return fail(`merge: ${mAdmin} is not a known registry admin — add it via ` +
200
260
  `CANTON_AGENT_REGISTRY_TRUSTED_PARTIES before merging its token`);
@@ -218,16 +278,16 @@ async function main() {
218
278
  `${res.merged.length} ${mId ? `${mId} holding(s)` : "amulet(s)"} consumed` +
219
279
  (extras.length ? ` (${extras.join(", ")})` : "") +
220
280
  (res.finalHoldings === null
221
- ? " — remaining holdings predate this run; re-run `merge` after the next " +
222
- "Scan snapshot (~12:00 UTC daily) to continue"
281
+ ? mId
282
+ ? " — more remain than one read lists; see above"
283
+ : " — remaining holdings predate this run; re-run `merge` after the next " +
284
+ "Scan snapshot (~12:00 UTC daily) to continue"
223
285
  : ` → ${res.finalHoldings} holding(s) remain`));
224
286
  }
225
287
  break;
226
288
  }
227
289
  case "claim": {
228
- const w = loadWallet();
229
- if (!w)
230
- return fail("no wallet yet — run: canton-agent-wallet create");
290
+ const w = requireWallet();
231
291
  const r = await claimAll(new RelayClient({ relayUrl: w.relayUrl, apiKey: API_KEY }), w);
232
292
  // Say what happened to EVERY pending row, not just the claimed ones. An
233
293
  // expired offer is skipped (the ledger would refuse its accept); a failed
@@ -269,9 +329,7 @@ async function main() {
269
329
  // pays the fee) — ONLY works if the merchant delegated
270
330
  // CanActAs to the facilitator user; NOT for
271
331
  // self-custody merchants. Prefer the self default.
272
- const w = loadWallet();
273
- if (!w)
274
- return fail("no wallet yet — run: canton-agent-wallet create");
332
+ const w = requireWallet();
275
333
  const admin = flag(args, "--admin") ||
276
334
  resolveTrustedDsoParty(process.env, w.network) ||
277
335
  "";
@@ -355,7 +413,11 @@ async function main() {
355
413
  // client's trusted-registry table/env, so an arbitrary --admin cannot be
356
414
  // blind-trusted.
357
415
  if (isTrustedRegistryAdmin(admin, process.env)) {
358
- const id = flag(args, "--id") || "USDCx";
416
+ const id = preapprovalInstrumentId(args, admin);
417
+ if (id === undefined) {
418
+ return fail(`preapproval: pass --id <instrument> — this wallet does not know which instrument ` +
419
+ `${admin} issues, and provisioning the wrong one is not harmless`);
420
+ }
359
421
  // ABSENCE MUST BE PROVEN BEFORE CREATING, because this create is not
360
422
  // idempotent: run it twice and the merchant ends up with two live
361
423
  // Utility.Registry TransferPreapproval contracts for the same
@@ -393,7 +455,7 @@ async function main() {
393
455
  // DEFAULT: SELF-provision with the merchant's OWN key — no token, no
394
456
  // delegation. The merchant prepays the fee from its own CC.
395
457
  try {
396
- const res = await selfProvisionPreapproval(relay, w, { expiresAt });
458
+ const res = await selfProvisionPreapproval(relay, w, { expiresAt, dso: admin });
397
459
  console.log(`TransferPreapproval self-provisioned for ${w.party} (ready for transfer-factory payments)\n` +
398
460
  ` provider: ${w.party} (self)\n` +
399
461
  ` expiresAt: ${expiresAt}\n` +
@@ -414,7 +476,7 @@ async function main() {
414
476
  case "pay": {
415
477
  const url = positionals(args)[0];
416
478
  if (!url)
417
- return fail("usage: canton-agent-wallet pay [--relay-url <url>] [--asset <symbol>] <url>");
479
+ return fail("usage: canton-agent-wallet pay [--relay-url <url>] [--asset <symbol>] [--max <amount>] [--pay-to <party>] <url>");
418
480
  // Reuse the wallet's stored relay (like claim/balance) when neither
419
481
  // --relay-url nor CANTON_AGENT_RELAY_URL is given, so an already-created
420
482
  // agent can pay without re-specifying the relay each time.
@@ -426,11 +488,22 @@ async function main() {
426
488
  // Selection only; spending a registry token still needs
427
489
  // CANTON_AGENT_PAYABLE_INSTRUMENTS.
428
490
  const preferAsset = flag(args, "--asset");
491
+ // --max caps what one payment may cost (in the paid asset's own unit);
492
+ // --pay-to pins the only payee the wallet will sign for. Both refuse
493
+ // BEFORE anything is signed; see payLimits.
494
+ const limits = payLimits(args);
495
+ if (limits.kind === "error")
496
+ return fail(limits.message);
429
497
  const f = await makePayingFetch({
430
498
  relayUrl,
431
499
  network: existing?.network ?? resolveNetwork(args),
432
500
  apiKey: API_KEY,
433
501
  ...(preferAsset ? { preferAsset } : {}),
502
+ ...(limits.maxPaymentValue !== undefined ? { maxPaymentValue: limits.maxPaymentValue } : {}),
503
+ ...(limits.maxPaymentValueByInstrument !== undefined
504
+ ? { maxPaymentValueByInstrument: limits.maxPaymentValueByInstrument }
505
+ : {}),
506
+ ...(limits.expectedPayTo !== undefined ? { expectedPayTo: limits.expectedPayTo } : {}),
434
507
  });
435
508
  const res = await f(url);
436
509
  console.log(`${res.status} ${res.statusText}`);
@@ -470,6 +543,7 @@ async function main() {
470
543
  }
471
544
  const r = await withdraw({
472
545
  to,
546
+ wallet: requireWallet(),
473
547
  ...(amt.kind === "amount" ? { amount: amt.amount } : {}),
474
548
  ...(wAdmin && wId ? { instrumentAdmin: wAdmin, instrumentId: wId } : {}),
475
549
  apiKey: API_KEY,
@@ -550,6 +624,7 @@ async function main() {
550
624
  const sr = await executeSwap({
551
625
  amount,
552
626
  ticket,
627
+ ...(existing ? { wallet: relayFor(existing) } : {}),
553
628
  apiKey: API_KEY,
554
629
  preapproveOutput: !boolFlag(args, "--no-preapprove"),
555
630
  waitForOutput: wait,
@@ -568,6 +643,29 @@ async function main() {
568
643
  console.log(`${head}\n ! input RETURNED — nothing was swapped: the CC balance is back at its ` +
569
644
  `pre-send level (the venue hands back a trade it will not fill, e.g. below its minimum)`);
570
645
  }
646
+ else if (sr.deliveryUnobservable) {
647
+ // Reached only after the input was sent (sentUpdateId above), so "do not
648
+ // resend" is true here. The wait claimed offers and watched for a
649
+ // returned input; only the delivery itself could not be seen.
650
+ console.log(`${head}\n ! cannot confirm delivery: the relay cannot list every ${outCanon} holding of ` +
651
+ `this wallet (more of that registry's holdings than the participant lists in one ` +
652
+ `read), so the new one is out of view. The input WAS sent: do NOT resend. A trade ` +
653
+ `the venue will not fill comes back as an offer — run \`canton-agent-wallet claim\` ` +
654
+ `in a minute to pick it up. Run \`canton-agent-wallet merge --all-tokens\` (plain ` +
655
+ `\`canton-agent-wallet merge\` first if Canton Coin dust fills the wallet), then ` +
656
+ `check \`balance\`.` +
657
+ (sr.partialRise !== undefined
658
+ ? `\n note: before that, the ${outCanon} balance rose ${sr.partialRise}, below the ` +
659
+ `ticket minimum ${ticket.minOutput} — possibly an unrelated inbound, not this swap's fill`
660
+ : "") +
661
+ (sr.claimed > 0
662
+ ? `\n claimed ${sr.claimed} incoming transfer(s): ${sr.claimedUpdateIds.join(", ")}`
663
+ : "") +
664
+ (sr.pollErrors > 0
665
+ ? `\n ! ${sr.pollErrors} poll error(s) while waiting — the relay was flaky, but ` +
666
+ "the input WAS sent (updateId above); do NOT resend, check `balance` first"
667
+ : ""));
668
+ }
571
669
  else {
572
670
  // Waited, no arrival: still settling, or a below-minimum trade whose input
573
671
  // was returned (the per-tick claim picks that return up — `claimed` shows
@@ -626,8 +724,35 @@ async function main() {
626
724
  ` run: canton-agent-wallet balance`);
627
725
  break;
628
726
  }
727
+ case "set-relay": {
728
+ // Change the relay SAVED in wallet.json — the one every command uses when
729
+ // no --relay-url is given. Refuses a relay that settles on a different
730
+ // network: the wallet's DSO pin and parties are network-specific.
731
+ const url = positionals(args)[0];
732
+ if (!url)
733
+ return fail("usage: canton-agent-wallet set-relay <url>");
734
+ const w = loadWallet();
735
+ if (!w)
736
+ return fail("no wallet yet — run: canton-agent-wallet create");
737
+ let net;
738
+ try {
739
+ const sup = await new RelayClient({ relayUrl: url, apiKey: API_KEY }).supported();
740
+ net = sup?.kinds?.find((k) => typeof k?.network === "string" && k.network.length > 0)?.network;
741
+ }
742
+ catch (err) {
743
+ return fail(`set-relay: could not reach ${url} (${err instanceof Error ? err.message : String(err)})`);
744
+ }
745
+ if (!net)
746
+ return fail(`set-relay: ${url} does not say which network it settles on — not switching`);
747
+ if (net !== w.network) {
748
+ return fail(`set-relay: ${url} settles on ${net}, but this wallet is on ${w.network} — not switching`);
749
+ }
750
+ saveWallet({ ...w, relayUrl: url });
751
+ console.log(`relay set: ${w.relayUrl} → ${url} (${net})`);
752
+ break;
753
+ }
629
754
  default:
630
- console.error("canton-agent-wallet: create | address | balance | claim | merge [--target <n>] [--batch <n>] [--max-rounds <n>] [--dry-run] [--yes] [--admin <registrar> --id <instrument>] | preapproval [--status] [--admin <DSO>] [--operator-token <t>] [--days <n>] [--expires-at <iso>] | pay [--asset <symbol>] <url> | swap --in <SYM> --out <SYM> --amount <n> [--venue cantex|tradecraft|auto] [--slippage <pct>] [--direct] [--no-wait] [--max-fee <cc>] [--swap-merchant <party>] [--no-preapprove] | withdraw --to <party> [--amount <n>] [--admin <registrar> --id <instrument>] | export | import\n" +
755
+ console.error("canton-agent-wallet: create | address | balance | claim | merge [--target <n>] [--batch <n>] [--max-rounds <n>] [--dry-run] [--yes] [--admin <registrar> --id <instrument> | --all-tokens] | preapproval [--status] [--admin <DSO|registrar>] [--id <instrument>] [--operator-token <t>] [--days <n>] [--expires-at <iso>] | pay [--asset <symbol>] [--max <amount>] [--pay-to <party>] <url> | swap --in <SYM> --out <SYM> --amount <n> [--venue cantex|tradecraft|auto] [--slippage <pct>] [--direct] [--no-wait] [--max-fee <cc>] [--swap-merchant <party>] [--no-preapprove] | withdraw --to <party> [--amount <n>] [--admin <registrar> --id <instrument>] | export | import | set-relay <url>\n" +
631
756
  " relay required for create/pay/import: --relay-url <url> or CANTON_AGENT_RELAY_URL\n" +
632
757
  " preapproval = merchant TransferPreapproval setup for the transfer-factory (V3, 1-tx) path (--status to just check).");
633
758
  process.exit(cmd ? 1 : 0);