From 6c7745f583433c1a55b8b8bdbce880f07ac956a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:27:30 +0000 Subject: [PATCH 1/4] Initial plan From a22d117182bb242227a5fbf222cbc1e42bba28ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:32:56 +0000 Subject: [PATCH 2/4] feat: add --dry-run option to send and withdraw commands Co-authored-by: SynthLuvr <131367121+SynthLuvr@users.noreply.github.com> --- commands/send.ts | 53 ++++++++++++++++++++++++---------- commands/withdraw.ts | 23 +++++++++++++++ tests/send.test.ts | 42 +++++++++++++++++++++++++++ tests/withdraw.test.ts | 65 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 tests/send.test.ts create mode 100644 tests/withdraw.test.ts diff --git a/commands/send.ts b/commands/send.ts index 61bce85..ee58b42 100644 --- a/commands/send.ts +++ b/commands/send.ts @@ -106,32 +106,55 @@ program .description("Send balance to another account") .option("-j, --json", "Output results as JSON") .option("-t, --toon", "Output results as TOON") + .option( + "-d, --dry-run", + "Validate and preview the transaction without submitting it", + ) .argument("amount", "The amount of balance to send", parseAmount) .argument( "[destination]", "The email address or Mynth account address to send balance to. If omitted then a claim link will be created.", parseDestination, ) - .action(async (amount: Decimal, destination?: string) => { - const privateKey = getPrivateKey(); - if (privateKey) { - const sent = await sendWithPrivateKey(privateKey, amount, destination); + .action( + async ( + amount: Decimal, + destination: string | undefined, + options: { dryRun?: boolean }, + ) => { + if (options.dryRun) { + const result: { dryRun: true; amount: string; to?: string } = { + dryRun: true, + amount: amount.toString(), + }; + if (destination) result.to = destination; + printOk( + result, + `Would send ${amount}${destination ? ` to ${destination}` : ""}`, + ); + return; + } + + const privateKey = getPrivateKey(); + if (privateKey) { + const sent = await sendWithPrivateKey(privateKey, amount, destination); + if (!sent.ok) return logExit(sent.error); + + printOk( + createResult(amount, destination, sent), + `Sent ${amount} to ${destination ?? sent.data}; ${sent.data.txId}`, + ); + return; + } + + const sent = await send(amount, destination, getNetwork()); if (!sent.ok) return logExit(sent.error); printOk( createResult(amount, destination, sent), `Sent ${amount} to ${destination ?? sent.data}; ${sent.data.txId}`, ); - return; - } - - const sent = await send(amount, destination, getNetwork()); - if (!sent.ok) return logExit(sent.error); - - printOk( - createResult(amount, destination, sent), - `Sent ${amount} to ${destination ?? sent.data}; ${sent.data.txId}`, - ); - }); + }, + ); export { sendWithTokenOrKey }; diff --git a/commands/withdraw.ts b/commands/withdraw.ts index d9e5d1a..d050bb4 100644 --- a/commands/withdraw.ts +++ b/commands/withdraw.ts @@ -40,6 +40,10 @@ program .description("Withdraws balance to external blockchain") .option("-j, --json", "Output results as JSON") .option("-t, --toon", "Output results as TOON") + .option( + "-d, --dry-run", + "Validate and preview the transaction without submitting it", + ) .argument("amount", "The amount of balance to withdraw", parseAmount) .argument( "stablecoin", @@ -58,7 +62,26 @@ program stablecoin: string, address: string, blockchain: string, + options: { dryRun?: boolean }, ) => { + if (options.dryRun) { + const token = resolveStablecoin(stablecoin, blockchain, getNetwork()); + if (!token) + return logExit(`${stablecoin} does not exist for ${blockchain}`); + + printOk( + { + dryRun: true, + amount: amount.toString(), + blockchain, + stablecoin, + to: address, + }, + `Would withdraw ${amount} to ${address}`, + ); + return; + } + const withdrawn = await withdraw( amount, stablecoin, diff --git a/tests/send.test.ts b/tests/send.test.ts new file mode 100644 index 0000000..b4fc345 --- /dev/null +++ b/tests/send.test.ts @@ -0,0 +1,42 @@ +import { describe, expect } from "vitest"; +import { it } from "./base.js"; + +describe("nova send --dry-run", () => { + it("prints dry run result with destination", async ({ nova }) => { + const address = await nova(["address"]); + const stdout = await nova(["send", "--dry-run", "10", address]); + expect(stdout).toBe(`Would send 10 to ${address}`); + }); + + it("prints dry run result without destination", async ({ nova }) => { + const stdout = await nova(["send", "--dry-run", "5"]); + expect(stdout).toBe("Would send 5"); + }); + + it("prints dry run result in JSON format", async ({ nova }) => { + const address = await nova(["address"]); + const stdout = await nova(["-j", "send", "--dry-run", "10", address]); + const result = JSON.parse(stdout); + expect(result.status).toBe("ok"); + expect(result.result.dryRun).toBe(true); + expect(result.result.amount).toBe("10"); + expect(result.result.to).toBe(address); + }); + + it("prints dry run result in JSON format without destination", async ({ + nova, + }) => { + const stdout = await nova(["-j", "send", "--dry-run", "5"]); + const result = JSON.parse(stdout); + expect(result.status).toBe("ok"); + expect(result.result.dryRun).toBe(true); + expect(result.result.amount).toBe("5"); + expect(result.result.to).toBeUndefined(); + }); + + it("accepts short flag -d", async ({ nova }) => { + const address = await nova(["address"]); + const stdout = await nova(["send", "-d", "10", address]); + expect(stdout).toBe(`Would send 10 to ${address}`); + }); +}); diff --git a/tests/withdraw.test.ts b/tests/withdraw.test.ts new file mode 100644 index 0000000..3a6d975 --- /dev/null +++ b/tests/withdraw.test.ts @@ -0,0 +1,65 @@ +import { describe, expect } from "vitest"; +import { it } from "./base.js"; + +const SOLANA_ADDRESS = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; + +describe("nova withdraw --dry-run", () => { + it("prints dry run result", async ({ nova }) => { + const stdout = await nova([ + "withdraw", + "--dry-run", + "10", + "USDC", + SOLANA_ADDRESS, + "solana", + ]); + expect(stdout).toBe(`Would withdraw 10 to ${SOLANA_ADDRESS}`); + }); + + it("prints dry run result in JSON format", async ({ nova }) => { + const stdout = await nova([ + "-j", + "withdraw", + "--dry-run", + "10", + "USDC", + SOLANA_ADDRESS, + "solana", + ]); + const result = JSON.parse(stdout); + expect(result.status).toBe("ok"); + expect(result.result.dryRun).toBe(true); + expect(result.result.amount).toBe("10"); + expect(result.result.stablecoin).toBe("USDC"); + expect(result.result.blockchain).toBe("solana"); + expect(result.result.to).toBe(SOLANA_ADDRESS); + }); + + it("errors when stablecoin does not exist for blockchain", async ({ + nova, + }) => { + await expect( + nova([ + "-j", + "withdraw", + "--dry-run", + "10", + "USDA", + SOLANA_ADDRESS, + "solana", + ]), + ).rejects.toThrow(); + }); + + it("accepts short flag -d", async ({ nova }) => { + const stdout = await nova([ + "withdraw", + "-d", + "10", + "USDC", + SOLANA_ADDRESS, + "solana", + ]); + expect(stdout).toBe(`Would withdraw 10 to ${SOLANA_ADDRESS}`); + }); +}); From ee6a9ca9d553b6ae0b7d3ecb6077bf6dfa5df8c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:42:52 +0000 Subject: [PATCH 3/4] docs: update README, SKILL.md and REFERENCE.md with --dry-run option Co-authored-by: SynthLuvr <131367121+SynthLuvr@users.noreply.github.com> --- README.md | 8 ++++ skills/nova-wallet/SKILL.md | 10 +++- skills/nova-wallet/references/REFERENCE.md | 55 ++++++++++++++++++---- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0a98c71..57249ae 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,10 @@ nova send [destination] - `amount` — Amount to send - `destination` *(optional)* — Recipient email or Nova account address +**Options** + +- `-d, --dry-run` — Preview the transaction without submitting it + **Behavior** - If `destination` **is provided**, funds are sent directly to that @@ -291,6 +295,10 @@ nova withdraw
- `blockchain` — Target blockchain (required if it cannot be inferred from the address) +**Options** + +- `-d, --dry-run` — Preview the transaction without submitting it + #### `config` Manage Nova configuration values. diff --git a/skills/nova-wallet/SKILL.md b/skills/nova-wallet/SKILL.md index d78e589..7512176 100644 --- a/skills/nova-wallet/SKILL.md +++ b/skills/nova-wallet/SKILL.md @@ -147,6 +147,10 @@ Notes: nova send [destination] ``` +Options: + +- `-d`, `--dry-run` — Validate and preview without submitting + Behavior: - If `destination` omitted: @@ -168,8 +172,8 @@ Behavior: Rules: - No interactive confirmation. -- No dry-run. - Non-idempotent: re-running sends again. +- Use `--dry-run` (`-d`) to validate inputs and preview without submitting. - Always confirm with user whether `destination` is an email address or wallet address before execution. @@ -205,6 +209,10 @@ Properties (when `nova send ` has no destination): nova withdraw
``` +Options: + +- `-d`, `--dry-run` — Validate and preview without submitting + Rules: - Confirm: diff --git a/skills/nova-wallet/references/REFERENCE.md b/skills/nova-wallet/references/REFERENCE.md index c9b8fa5..16311fb 100644 --- a/skills/nova-wallet/references/REFERENCE.md +++ b/skills/nova-wallet/references/REFERENCE.md @@ -241,9 +241,16 @@ Wallet-address sends: and does **not** prompt for confirmation. - There is **no interactive confirmation step** -- There is **no dry-run mode** - Sends are **not idempotent** (re-running the same command will send funds again) +- Use `--dry-run` (`-d`) to validate inputs and preview the transaction + **without** submitting it + +Options: + +- `-j, --json` Output results as JSON +- `-t, --toon` Output results as TOON +- `-d, --dry-run` Validate and preview without submitting Agents must: @@ -327,6 +334,18 @@ Send to wallet address: nova send 10 pcr6cdcvwjf9297vv6jmy8284xwlscspj2g0fw ``` +Dry-run (preview without submitting): + +``` bash +nova -j send --dry-run 10 friend@email.com +``` + +Example dry-run output (JSON): + +``` bash +{"result":{"amount":"10","dryRun":true,"to":"friend@email.com"},"status":"ok"} +``` + Best Practices for Agents: - Confirm amount before sending @@ -342,12 +361,30 @@ Best Practices for Agents: nova withdraw
``` +Options: + +- `-j, --json` Output results as JSON +- `-t, --toon` Output results as TOON +- `-d, --dry-run` Validate and preview without submitting + Example: ``` bash nova withdraw 10 USDC 0x7600eFB256ae7519e73C14a55152B0806b5cfF28 base ``` +Dry-run (preview without submitting): + +``` bash +nova -j withdraw --dry-run 10 USDC 0x7600eFB256ae7519e73C14a55152B0806b5cfF28 base +``` + +Example dry-run output (JSON): + +``` bash +{"result":{"amount":"10","blockchain":"base","dryRun":true,"stablecoin":"USDC","to":"0x7600eFB256ae7519e73C14a55152B0806b5cfF28"},"status":"ok"} +``` + Parameters: - `amount` — numeric string @@ -691,13 +728,15 @@ Never: If user intent includes: -| Intent | Suggest | -|------------------|-----------------------------------| -| “receive funds” | `nova address` | -| “backup wallet” | `nova export phrase` | -| “send money” | `nova send` | -| “cash out” | `nova withdraw` | -| “switch testnet” | `nova config set network testnet` | +| Intent | Suggest | +|----------------------|-----------------------------------| +| “receive funds” | `nova address` | +| “backup wallet” | `nova export phrase` | +| “send money” | `nova send` | +| “preview send” | `nova send --dry-run` | +| “cash out” | `nova withdraw` | +| “preview withdraw” | `nova withdraw --dry-run` | +| “switch testnet” | `nova config set network testnet` | ## Summary From d40459778a5f56d4d476d279c5eb2e76a4ab8b2f Mon Sep 17 00:00:00 2001 From: SynthLuvr <131367121+SynthLuvr@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:46:43 +0100 Subject: [PATCH 4/4] format md --- skills/nova-wallet/SKILL.md | 3 ++- skills/nova-wallet/references/REFERENCE.md | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/skills/nova-wallet/SKILL.md b/skills/nova-wallet/SKILL.md index 7512176..48ba9a0 100644 --- a/skills/nova-wallet/SKILL.md +++ b/skills/nova-wallet/SKILL.md @@ -173,7 +173,8 @@ Rules: - No interactive confirmation. - Non-idempotent: re-running sends again. -- Use `--dry-run` (`-d`) to validate inputs and preview without submitting. +- Use `--dry-run` (`-d`) to validate inputs and preview without + submitting. - Always confirm with user whether `destination` is an email address or wallet address before execution. diff --git a/skills/nova-wallet/references/REFERENCE.md b/skills/nova-wallet/references/REFERENCE.md index 16311fb..8e1e278 100644 --- a/skills/nova-wallet/references/REFERENCE.md +++ b/skills/nova-wallet/references/REFERENCE.md @@ -728,8 +728,8 @@ Never: If user intent includes: -| Intent | Suggest | -|----------------------|-----------------------------------| +| Intent | Suggest | +|--------------------|-----------------------------------| | “receive funds” | `nova address` | | “backup wallet” | `nova export phrase` | | “send money” | `nova send` |