Skip to content

Commit 8413ad2

Browse files
authored
feat: add --dry-run flag to all transacting commands (#11)
## Summary - Adds a global `--dry-run` flag that previews any on-chain transaction without signing or submitting - The API still simulates the transaction (with taker address), so errors like insufficient balance, minimum size violations, etc. are caught before execution - JSON output includes `dryRun: true`, `signature: null`, and the unsigned base64 `transaction` for external signing workflows - Table output prints a `[DRY RUN]` label at the top and omits the signature row ### Commands affected `spot swap`, `spot transfer`, `spot reclaim`, `perps open`, `perps set`, `perps close`, `lend earn deposit`, `lend earn withdraw`, `predictions open`, `predictions close`
1 parent 7692e0b commit 8413ad2

13 files changed

Lines changed: 432 additions & 76 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ jup predictions positions
6262

6363
> [!NOTE]
6464
> This CLI is designed to be LLM friendly and **all commands are non-interactive**. Set JSON output mode globally for structured responses: `jup config set --output json`, or use `-f json` flag on individual commands.
65+
>
66+
> Use `--dry-run` on any transacting command to preview the result without signing or submitting on-chain. In JSON mode, the response includes the unsigned base64 `transaction` for external signing.
6567
6668
[Read the docs](./docs/) for specific guides, examples, and workflows:
6769

docs/lend.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,15 @@ jup lend earn positions --token USDC
6666
jup lend earn deposit --token USDC --amount 100
6767
jup lend earn deposit --token SOL --amount 1.5 --key mykey
6868
jup lend earn deposit --token USDC --raw-amount 100000000
69+
jup lend earn deposit --token USDC --amount 100 --dry-run
6970
```
7071

7172
- `--token` (required) — underlying token to deposit (symbol or mint address)
7273
- `--amount` uses human-readable units (e.g. `100` USDC = 100 USDC)
7374
- `--raw-amount` uses on-chain units (e.g. `100000000` = 100 USDC)
7475
- Exactly one of `--amount` or `--raw-amount` is required
7576
- `--key` overrides the active key for this transaction
77+
- `--dry-run` previews the deposit without signing. JSON response includes the unsigned base64 `transaction`.
7678

7779
```js
7880
// Example JSON response:
@@ -94,13 +96,15 @@ jup lend earn withdraw --token USDC --amount 50
9496
jup lend earn withdraw --token USDC # withdraw entire position
9597
jup lend earn withdraw --token jlUSDC --amount 50 # also accepts jlToken directly
9698
jup lend earn withdraw --token USDC --raw-amount 50000000
99+
jup lend earn withdraw --token USDC --amount 50 --dry-run
97100
```
98101

99102
- `--token` (required) — token to withdraw (accepts underlying symbol/address or jlToken symbol/address)
100103
- `--amount` in human-readable units of the underlying token
101104
- `--raw-amount` in on-chain units of the jlToken
102105
- When neither `--amount` nor `--raw-amount` is provided, withdraws the entire position
103106
- `--key` overrides the active key for this transaction
107+
- `--dry-run` previews the withdrawal without signing. JSON response includes the unsigned base64 `transaction`.
104108

105109
```js
106110
// Example JSON response:

docs/perps.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,16 @@ jup perps open --asset ETH --side long --amount 10 --input USDC --leverage 3 --t
139139

140140
# Limit order (triggers when price reaches --limit)
141141
jup perps open --asset BTC --side long --amount 10 --input USDC --leverage 2 --limit 65000
142+
143+
# Dry-run to preview without executing
144+
jup perps open --asset SOL --side long --amount 0.2 --leverage 2 --dry-run
142145
```
143146

144147
- `--side` accepts `long`, `short`, `buy` (= long), or `sell` (= short)
145148
- `--input` defaults to SOL; accepts SOL, BTC, ETH, or USDC
146149
- `--slippage` defaults to 200 (2%); set in basis points
147150
- `--tp` and `--sl` cannot be combined with `--limit`
151+
- `--dry-run` previews the order without signing. The API simulates the transaction, returning entry price, size, leverage, liquidation price, and fees. JSON response includes the unsigned base64 `transaction`.
148152

149153
```js
150154
// Example JSON response (market order):
@@ -188,9 +192,13 @@ jup perps set --position <pubkey> --tp 100 --sl 70
188192

189193
# Update a limit order's trigger price
190194
jup perps set --order <pubkey> --limit 64000
195+
196+
# Dry-run
197+
jup perps set --position <pubkey> --tp 100 --sl 70 --dry-run
191198
```
192199

193200
- Get the `positionPubkey` or `orderPubkey` from `jup perps positions`
201+
- `--dry-run` previews the update without signing. JSON response includes the unsigned base64 `transaction` for each update.
194202

195203
```js
196204
// Example JSON response (update limit order):
@@ -240,10 +248,14 @@ jup perps close --order <pubkey>
240248

241249
# Cancel a TP/SL order
242250
jup perps close --tpsl <pubkey>
251+
252+
# Dry-run
253+
jup perps close --position <pubkey> --dry-run
243254
```
244255

245256
- `--receive` defaults to the position's collateral token; must be USDC or the market token (e.g. BTC for a BTC position)
246257
- `--size` for partial close; omit to close entirely
258+
- `--dry-run` previews the close without signing, showing PnL, fees, and received amount. JSON response includes the unsigned base64 `transaction`.
247259

248260
```js
249261
// Example JSON response (close/decrease position):

docs/predictions.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@ jup predictions positions --position <pubkey>
8989
jup predictions open --market <marketId> --side yes --amount 10
9090
jup predictions open --market <marketId> --side no --amount 5 --input USDC
9191
jup predictions open --market <marketId> --side y --amount 10 --key mykey
92+
jup predictions open --market <marketId> --side yes --amount 10 --dry-run
9293
```
9394

9495
- `--market`: market ID from `jup predictions events`
9596
- `--side`: `yes`, `no`, `y`, `n`
9697
- `--amount`: input token amount (human-readable)
9798
- `--input`: input token symbol or mint (default: `USDC`)
99+
- `--dry-run` previews the order without signing, showing cost, fees, and payout. JSON response includes the unsigned base64 `transaction`.
98100

99101
```js
100102
// Example JSON response:
@@ -123,11 +125,15 @@ jup predictions close --position <pubkey>
123125

124126
# Close all positions
125127
jup predictions close --position all
128+
129+
# Dry-run
130+
jup predictions close --position <pubkey> --dry-run
126131
```
127132

128133
- The CLI auto-detects whether to sell or claim based on the market result
129134
- Claimable positions (market resolved in your favor) are claimed for the full payout
130135
- Open positions on live markets are sold at the current market price
136+
- `--dry-run` previews the close without signing. JSON response includes the unsigned base64 `transaction`.
131137

132138
```js
133139
// Example JSON response (close):

docs/spot.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ jup spot quote --from <mint> --to <mint> --raw-amount 1000000000
5555
jup spot swap --from SOL --to USDC --amount 1
5656
jup spot swap --from SOL --to USDC --amount 1 --key mykey
5757
jup spot swap --from SOL --to USDC --amount 1 --slippage 50
58+
jup spot swap --from SOL --to USDC --amount 1 --dry-run
5859
```
5960

6061
- `--key` overrides the active key for this transaction
6162
- `--slippage` sets max slippage in basis points (e.g. `50` = 0.5%). Recommended to be emtpy: Jupiter's Real-Time Slippage Estimation (RTSE) automatically picks an optimal value.
63+
- `--dry-run` previews the swap without signing or submitting. The API still simulates the transaction, so errors like insufficient balance are caught. In JSON mode, the response includes the unsigned base64 `transaction` for external signing.
6264

6365
```js
6466
// Example JSON response:
@@ -113,9 +115,11 @@ jup spot portfolio --address <wallet-address>
113115
jup spot reclaim
114116
jup spot reclaim --key mykey
115117
jup spot reclaim --token USDC
118+
jup spot reclaim --dry-run
116119
```
117120

118121
- With no options, reclaims rent from all empty Associated Token Accounts (ATA) owned by the active key's wallet
122+
- `--dry-run` previews reclaimable amount without executing. JSON response includes the unsigned base64 `transactions` array.
119123

120124
```js
121125
// Example JSON response:
@@ -171,10 +175,12 @@ jup spot transfer --token SOL --to <recipient-address> --amount 1
171175
jup spot transfer --token USDC --to <recipient-address> --amount 50
172176
jup spot transfer --token <mint> --to <recipient-address> --raw-amount 1000000000
173177
jup spot transfer --token SOL --to <recipient-address> --amount 1 --key mykey
178+
jup spot transfer --token SOL --to <recipient-address> --amount 1 --dry-run
174179
```
175180

176181
- Works with both SOL and any SPL token
177182
- `--token` accepts a symbol or mint address
183+
- `--dry-run` previews the transfer without signing. JSON response includes the unsigned base64 `transaction`.
178184

179185
```js
180186
// Example JSON response:
@@ -191,11 +197,11 @@ jup spot transfer --token SOL --to <recipient-address> --amount 1 --key mykey
191197

192198
## Workflows
193199

194-
### Check price then swap
200+
### Dry-run then swap
195201

196202
```bash
197-
jup spot quote --from SOL --to USDC --amount 1
198-
# Review the quoted output and price impact
203+
jup spot swap --from SOL --to USDC --amount 1 --dry-run
204+
# Review the output, fees, and simulation result
199205
jup spot swap --from SOL --to USDC --amount 1
200206
```
201207

src/commands/LendCommand.ts

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,47 @@ export class LendCommand {
236236
rawAmount: opts.rawAmount,
237237
});
238238

239+
const apyPct = this.rateToPct(lendToken.totalRate);
240+
241+
if (Config.dryRun) {
242+
if (Output.isJson()) {
243+
Output.json({
244+
dryRun: true,
245+
token: {
246+
id: lendToken.assetAddress,
247+
symbol: lendToken.asset.symbol,
248+
decimals: lendToken.asset.decimals,
249+
},
250+
depositedAmount: swap.inAmount,
251+
depositedUsd: swap.order.inUsdValue,
252+
apyPct,
253+
signature: null,
254+
transaction: swap.order.transaction,
255+
});
256+
return;
257+
}
258+
259+
console.log(Output.DRY_RUN_LABEL);
260+
Output.table({
261+
type: "vertical",
262+
rows: [
263+
{
264+
label: "Deposited",
265+
value: `${swap.inAmount} ${lendToken.asset.symbol} (${Output.formatDollar(swap.order.inUsdValue)})`,
266+
},
267+
{
268+
label: "APY",
269+
value: Output.formatPercentageChange(apyPct),
270+
},
271+
],
272+
});
273+
return;
274+
}
275+
239276
const { positionAmount, price } = await this.fetchCurrentPosition(
240277
signer.address,
241278
lendToken
242279
);
243-
const apyPct = this.rateToPct(lendToken.totalRate);
244280

245281
if (Output.isJson()) {
246282
Output.json({
@@ -254,7 +290,7 @@ export class LendCommand {
254290
positionAmount,
255291
positionUsd: positionAmount * price,
256292
apyPct,
257-
signature: swap.result.signature,
293+
signature: swap.result!.signature,
258294
});
259295
return;
260296
}
@@ -276,7 +312,7 @@ export class LendCommand {
276312
},
277313
{
278314
label: "Tx Signature",
279-
value: swap.result.signature,
315+
value: swap.result!.signature,
280316
},
281317
],
282318
});
@@ -336,11 +372,47 @@ export class LendCommand {
336372
rawAmount,
337373
});
338374

375+
const apyPct = this.rateToPct(lendToken.totalRate);
376+
377+
if (Config.dryRun) {
378+
if (Output.isJson()) {
379+
Output.json({
380+
dryRun: true,
381+
token: {
382+
id: lendToken.assetAddress,
383+
symbol: lendToken.asset.symbol,
384+
decimals: lendToken.asset.decimals,
385+
},
386+
withdrawnAmount: swap.outAmount,
387+
withdrawnUsd: swap.order.outUsdValue,
388+
apyPct,
389+
signature: null,
390+
transaction: swap.order.transaction,
391+
});
392+
return;
393+
}
394+
395+
console.log(Output.DRY_RUN_LABEL);
396+
Output.table({
397+
type: "vertical",
398+
rows: [
399+
{
400+
label: "Withdrawn",
401+
value: `${swap.outAmount} ${lendToken.asset.symbol} (${Output.formatDollar(swap.order.outUsdValue)})`,
402+
},
403+
{
404+
label: "APY",
405+
value: Output.formatPercentageChange(apyPct),
406+
},
407+
],
408+
});
409+
return;
410+
}
411+
339412
const { positionAmount, price } = await this.fetchCurrentPosition(
340413
signer.address,
341414
lendToken
342415
);
343-
const apyPct = this.rateToPct(lendToken.totalRate);
344416

345417
if (Output.isJson()) {
346418
Output.json({
@@ -354,7 +426,7 @@ export class LendCommand {
354426
positionAmount,
355427
positionUsd: positionAmount * price,
356428
apyPct,
357-
signature: swap.result.signature,
429+
signature: swap.result!.signature,
358430
});
359431
return;
360432
}
@@ -376,7 +448,7 @@ export class LendCommand {
376448
},
377449
{
378450
label: "Tx Signature",
379-
value: swap.result.signature,
451+
value: swap.result!.signature,
380452
},
381453
],
382454
});

0 commit comments

Comments
 (0)