@moonpay/cli 1.43.0 → 1.44.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.
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: moonpay-take-profit-ladder
3
+ description: Sell a position in chunks at preset price targets, hold the runner. Each rung sells a slice of the CURRENT remaining position when its target price is crossed.
4
+ tags: [trading, automation, take-profit]
5
+ ---
6
+
7
+ # Take-profit ladder
8
+
9
+ ## Goal
10
+
11
+ Lock in profit on a position progressively as price rises. The user defines rungs as `(price_multiplier, % of current position to sell)` pairs. As each rung is crossed, the strategy sells that slice and lets the rest ride.
12
+
13
+ This skill works in two contexts:
14
+ - **Inside an automation harness** (MoonPay Agents desktop app): chat session memory tracks fired/unfired rungs.
15
+ - **Headless via cron / launchd**: a JSON file at `~/.config/moonpay/state/take-profit-ladder-<id>.json`.
16
+
17
+ ## Prerequisites
18
+
19
+ - `mp` authenticated: `mp user retrieve`
20
+ - The target position is held in a wallet `mp wallet list` knows about
21
+ - `jq` and `bc` installed
22
+
23
+ ## Configuration
24
+
25
+ Captured on first run:
26
+
27
+ | Field | Notes |
28
+ |---|---|
29
+ | `token` | Token address. Resolve via `mp token search` if user gives a ticker |
30
+ | `chain` | e.g. `solana`, `ethereum`, `base` |
31
+ | `wallet` | Local wallet name |
32
+ | `entry_price` | Cost basis the rungs are computed off — required |
33
+ | `rungs` | Array of `{multiplier, sell_pct, fired}`. Default: `[{x: 1.5, sell: 25, fired: false}, {x: 2.0, sell: 25, fired: false}, {x: 3.0, sell: 25, fired: false}]` (hold remaining 25%) |
34
+ | `sell_into` | Default: USDC |
35
+
36
+ Compute absolute target prices once: `target = entry_price * multiplier`. Persist them so price math is consistent across fires.
37
+
38
+ ## Per-fire workflow
39
+
40
+ ```
41
+ 1. price = mp --json token retrieve --token $TOKEN --chain $CHAIN | jq '.marketData.price'
42
+ 2. balance = mp --json token balance list --wallet $WALLET_ADDR --chain $CHAIN
43
+ | jq --arg t $TOKEN '.items[] | select(.address==$t) | .balance.amount'
44
+ 3. If balance == 0: mark all rungs as fired or strategy as complete; log "ladder complete"; exit.
45
+ 4. crossable = rungs where fired==false AND price >= target
46
+ 5. If crossable is empty: log "holding — price $price, next rung at $next_target ($gap_pct% away)"; exit.
47
+ 6. For each crossable rung (in target order, lowest first):
48
+ a. sell_amount = current_balance * (rung.sell_pct / 100)
49
+ b. mp token swap --from-token $TOKEN --from-amount $sell_amount --to-token $SELL_INTO ...
50
+ c. Mark rung.fired = true. Update balance for next iteration.
51
+ 7. Report: fill price for each rung, USD realized this fire, cumulative USD realized, % of original position remaining.
52
+ ```
53
+
54
+ The sell command:
55
+
56
+ ```bash
57
+ mp --json token swap \
58
+ --wallet "$WALLET" \
59
+ --chain "$CHAIN" \
60
+ --from-token "$TOKEN" \
61
+ --from-amount "$SELL_AMOUNT" \
62
+ --to-token "$SELL_INTO"
63
+ ```
64
+
65
+ ## Schedule
66
+
67
+ 15 minutes is a reasonable default. Volatile small-caps may want 5 minutes so a fast pump doesn't blow past multiple rungs unnoticed (the strategy handles gap-ups by firing all crossed rungs in one pass — but you'd rather fire each rung close to its actual price than fire all three at one mid-pump price).
68
+
69
+ ## Headless reference script
70
+
71
+ ```bash
72
+ #!/bin/bash
73
+ set -euo pipefail
74
+ MP="$(which mp)"
75
+ ID="tp-ladder-bonk-main"
76
+ STATE="$HOME/.config/moonpay/state/${ID}.json"
77
+ LOG="$HOME/.config/moonpay/logs/trading.log"
78
+ mkdir -p "$(dirname "$STATE")" "$(dirname "$LOG")"
79
+ log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $ID $*" >> "$LOG"; }
80
+
81
+ # --- Config ---
82
+ TOKEN="<token-mint>"
83
+ CHAIN="solana"
84
+ WALLET="main"
85
+ WALLET_ADDR="<address>"
86
+ SELL_INTO="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
87
+ ENTRY_PRICE=0.000020 # user-provided
88
+
89
+ # Initialize state on first run.
90
+ # We compute targets via jq (not bc) because bc strips leading zeros on
91
+ # fractional results — bc would produce ".000030" for a 0.000020 entry × 1.5,
92
+ # which is invalid JSON. jq formats numbers correctly.
93
+ if [ ! -f "$STATE" ]; then
94
+ jq -n --argjson e "$ENTRY_PRICE" '
95
+ {
96
+ entry_price: $e,
97
+ rungs: [
98
+ {target: ($e * 1.5), sell_pct: 25, fired: false},
99
+ {target: ($e * 2.0), sell_pct: 25, fired: false},
100
+ {target: ($e * 3.0), sell_pct: 25, fired: false}
101
+ ]
102
+ }
103
+ ' > "$STATE"
104
+ log "initialized ladder at entry $ENTRY_PRICE"
105
+ fi
106
+
107
+ # --- Load price + balance ---
108
+ PRICE=$("$MP" --json token retrieve --token "$TOKEN" --chain "$CHAIN" | jq '.marketData.price')
109
+ BALANCE=$("$MP" --json token balance list --wallet "$WALLET_ADDR" --chain "$CHAIN" \
110
+ | jq --arg t "$TOKEN" '[.items[] | select(.address==$t) | .balance.amount][0] // 0')
111
+
112
+ if (( $(echo "$BALANCE <= 0" | bc -l) )); then
113
+ log "balance is zero, ladder complete"; exit 0
114
+ fi
115
+
116
+ # --- Iterate rungs in order ---
117
+ COUNT=$(jq '.rungs | length' "$STATE")
118
+ for i in $(seq 0 $((COUNT-1))); do
119
+ FIRED=$(jq -r ".rungs[$i].fired" "$STATE")
120
+ TARGET=$(jq -r ".rungs[$i].target" "$STATE")
121
+ PCT=$(jq -r ".rungs[$i].sell_pct" "$STATE")
122
+ if [ "$FIRED" = "true" ] || (( $(echo "$PRICE < $TARGET" | bc -l) )); then continue; fi
123
+
124
+ CURRENT=$("$MP" --json token balance list --wallet "$WALLET_ADDR" --chain "$CHAIN" \
125
+ | jq --arg t "$TOKEN" '[.items[] | select(.address==$t) | .balance.amount][0] // 0')
126
+ SELL=$(echo "scale=8; $CURRENT * $PCT / 100" | bc -l)
127
+
128
+ log "RUNG $i: price $PRICE >= target $TARGET — selling $SELL ($PCT% of $CURRENT)"
129
+ RESULT=$("$MP" --json token swap \
130
+ --wallet "$WALLET" --chain "$CHAIN" \
131
+ --from-token "$TOKEN" --from-amount "$SELL" \
132
+ --to-token "$SELL_INTO" 2>&1) || { log "RUNG $i FAILED: $RESULT"; exit 1; }
133
+ log "RUNG $i OK: $RESULT"
134
+
135
+ jq ".rungs[$i].fired = true" "$STATE" > "$STATE.tmp" && mv "$STATE.tmp" "$STATE"
136
+ done
137
+ ```
138
+
139
+ ## Inside an automation harness
140
+
141
+ When this skill runs inside the MoonPay Agents desktop app, the harness's persistent chat session holds the rung state — no `state.json` needed. The agent reports each fire conversationally and continues to track unfired rungs across runs.
142
+
143
+ ## Common pitfalls
144
+
145
+ - **Selling a fixed % of the original position instead of the current**. If rung 1 sold 25% of original and rung 2 also sells "25% of original," you're selling 33% of what's left. Always compute against the current balance at the moment of firing.
146
+ - **Storing only multipliers without absolute targets**. If you recompute `entry_price * multiplier` from memory each fire and entry_price drifts (say, the agent re-asks the user), you'll fire on different prices. Persist absolute target prices once.
147
+ - **Not handling gap-ups**. If price jumps from 1.4× entry to 2.5× entry between fires, both rung 1 and rung 2 should fire. The reference script iterates all unfired rungs — match that pattern.
148
+
149
+ ## Related skills
150
+
151
+ - **moonpay-trading-automation** — General shell-script + cron patterns
152
+ - **moonpay-trailing-stop** — Companion strategy for the downside
153
+ - **moonpay-discover-tokens** — Resolve tickers to addresses
154
+ - **moonpay-swap-tokens** — Swap syntax
@@ -40,7 +40,7 @@ AMOUNT=5
40
40
 
41
41
  # --- Execute ---
42
42
  log "SWAP: $AMOUNT $FROM_TOKEN -> $TO_TOKEN on $CHAIN"
43
- RESULT=$("$MP" -f compact token swap \
43
+ RESULT=$("$MP" --json token swap \
44
44
  --wallet "$WALLET" --chain "$CHAIN" \
45
45
  --from-token "$FROM_TOKEN" --from-amount "$AMOUNT" \
46
46
  --to-token "$TO_TOKEN" 2>&1) || {
@@ -51,11 +51,23 @@ log "OK: $RESULT"
51
51
  ```
52
52
 
53
53
  Key points:
54
- - `mp -f compact` outputs single-line JSON, ideal for `jq` parsing
54
+ - `mp --json` outputs single-line JSON, ideal for `jq` parsing
55
55
  - Use `$(which mp)` and store as `MP` — cron/launchd have minimal PATH
56
56
  - Wallet names only in scripts — `mp` handles keychain decryption at runtime
57
57
  - If the user gives token names/symbols, resolve to addresses first with `mp token search`
58
58
 
59
+ ## Pre-built strategy skills
60
+
61
+ For three common strategies, prefer the dedicated skills over rolling your own:
62
+
63
+ - **moonpay-trailing-stop** — Sell automatically if price drops X% from its peak.
64
+ - **moonpay-take-profit-ladder** — Sell in chunks at preset price targets, hold the runner.
65
+ - **moonpay-buy-the-dip** — Buy a fixed USD amount when 24h drawdown exceeds a threshold, with a cooldown.
66
+
67
+ Each one comes with a verified shell-script template, the right cron cadence, and notes on common pitfalls. The patterns below (DCA, generic limit order, generic stop loss) are still the right starting point for strategies that don't fit those three.
68
+
69
+ If the user is on the **MoonPay Agents desktop app**, recommend using its in-app automation panel instead of generating a shell script — the app provides a persistent chat session that holds strategy state across fires (high-water-mark, fired rungs, last-fire timestamp) without a separate `state.json` file. The shell-script + cron pattern below is the right choice for headless / server / CI contexts where the app isn't running.
70
+
59
71
  ## DCA (Dollar-Cost Averaging)
60
72
 
61
73
  "Buy $5 of SOL every day at 9am"
@@ -137,7 +149,7 @@ TARGET_PRICE=80
137
149
  SCRIPT_NAME="limit-buy-sol"
138
150
 
139
151
  # --- Check price ---
140
- PRICE=$("$MP" -f compact token retrieve --token "$TOKEN" --chain "$CHAIN" | jq -r '.marketData.price')
152
+ PRICE=$("$MP" --json token retrieve --token "$TOKEN" --chain "$CHAIN" | jq -r '.marketData.price')
141
153
 
142
154
  if [ -z "$PRICE" ] || [ "$PRICE" = "null" ]; then
143
155
  log "LIMIT $SCRIPT_NAME: price fetch failed, skipping"
@@ -147,7 +159,7 @@ fi
147
159
  # --- Compare ---
148
160
  if (( $(echo "$PRICE < $TARGET_PRICE" | bc -l) )); then
149
161
  log "LIMIT $SCRIPT_NAME: price $PRICE < $TARGET_PRICE — executing buy"
150
- RESULT=$("$MP" -f compact token swap \
162
+ RESULT=$("$MP" --json token swap \
151
163
  --wallet "$WALLET" --chain "$CHAIN" \
152
164
  --from-token "$BUY_WITH" --from-amount "$BUY_AMOUNT" \
153
165
  --to-token "$TOKEN" 2>&1) || {
@@ -186,16 +198,16 @@ TRIGGER_PRICE=70
186
198
  SCRIPT_NAME="stop-loss-sol"
187
199
 
188
200
  # --- Check price ---
189
- PRICE=$("$MP" -f compact token retrieve --token "$SELL_TOKEN" --chain "$CHAIN" | jq -r '.marketData.price')
201
+ PRICE=$("$MP" --json token retrieve --token "$SELL_TOKEN" --chain "$CHAIN" | jq -r '.marketData.price')
190
202
 
191
203
  if (( $(echo "$PRICE < $TRIGGER_PRICE" | bc -l) )); then
192
204
  # Get current balance to sell all
193
- BALANCE=$("$MP" -f compact token balance list --wallet "$WALLET" --chain "$CHAIN" \
205
+ BALANCE=$("$MP" --json token balance list --wallet "$WALLET" --chain "$CHAIN" \
194
206
  | jq -r --arg addr "$SELL_TOKEN" '.items[] | select(.address == $addr) | .balance.amount')
195
207
 
196
208
  if [ -n "$BALANCE" ] && (( $(echo "$BALANCE > 0" | bc -l) )); then
197
209
  log "STOP-LOSS $SCRIPT_NAME: price $PRICE < $TRIGGER_PRICE — selling $BALANCE"
198
- RESULT=$("$MP" -f compact token swap \
210
+ RESULT=$("$MP" --json token swap \
199
211
  --wallet "$WALLET" --chain "$CHAIN" \
200
212
  --from-token "$SELL_TOKEN" --from-amount "$BALANCE" \
201
213
  --to-token "$TO_TOKEN" 2>&1) || {
@@ -270,6 +282,9 @@ fi
270
282
 
271
283
  ## Related skills
272
284
 
285
+ - **moonpay-trailing-stop** — Mechanical trailing stop with shipped reference script
286
+ - **moonpay-take-profit-ladder** — Laddered partial sells at preset price targets
287
+ - **moonpay-buy-the-dip** — Conditional buy on drawdown with cooldown
273
288
  - **moonpay-swap-tokens** — Swap and bridge command syntax
274
289
  - **moonpay-check-wallet** — Check balances before setting up automation
275
290
  - **moonpay-discover-tokens** — Research tokens and resolve addresses
@@ -0,0 +1,153 @@
1
+ ---
2
+ name: moonpay-trailing-stop
3
+ description: Run a trailing stop on one position — sell automatically if the price drops a fixed % from its peak since you started watching. Mechanical execution, no overrides.
4
+ tags: [trading, automation, stop-loss]
5
+ ---
6
+
7
+ # Trailing stop
8
+
9
+ ## Goal
10
+
11
+ Watch a single position and automatically sell it into a stable (USDC by default) if its price falls a fixed percent from the high-water-mark recorded since the strategy started. The whole point is mechanical execution — never widen the stop because "it'll come back."
12
+
13
+ This skill works in two contexts:
14
+ - **Inside an automation harness** (the MoonPay Agents desktop app): chat session memory holds the high-water-mark across fires.
15
+ - **Headless via cron / launchd**: a JSON file at `~/.config/moonpay/state/trailing-stop-<id>.json` holds the same state.
16
+
17
+ The decision logic is identical; only the persistence layer differs.
18
+
19
+ ## Prerequisites
20
+
21
+ - `mp` authenticated: `mp user retrieve`
22
+ - The target position is held in a wallet `mp wallet list` knows about
23
+ - `jq` installed: `which jq`
24
+
25
+ ## Configuration
26
+
27
+ Required inputs to capture on first run (ask the user, then persist):
28
+
29
+ | Field | Notes |
30
+ |---|---|
31
+ | `token` | Token address. Resolve from a ticker with `mp token search --query <ticker> --chain <chain>` |
32
+ | `chain` | e.g. `solana`, `ethereum`, `base` |
33
+ | `wallet` | Wallet name (the local label, not the address) |
34
+ | `trailing_pct` | Percent drop from peak that triggers the sell. Default: 12 |
35
+ | `sell_into` | Token address to sell into. Default: USDC on the same chain |
36
+ | `entry_price` | Optional — original cost basis if known. Used only for PnL reporting at exit |
37
+ | `hwm` | Initial high-water-mark = current price at first fire |
38
+ | `closed` | Boolean. False until the stop fires. After it fires, the strategy stops watching |
39
+
40
+ ## Per-fire workflow
41
+
42
+ ```
43
+ 1. If `closed`: log "position already closed" and exit.
44
+ 2. price = mp --json token retrieve --token $TOKEN --chain $CHAIN | jq '.marketData.price'
45
+ 3. balance = mp --json token balance list --wallet $WALLET_ADDR --chain $CHAIN
46
+ | jq --arg t $TOKEN '.items[] | select(.address==$t) | .balance.amount'
47
+ 4. If balance is null or 0: mark closed=true, log "no position, stopping", exit.
48
+ 5. If price > hwm: hwm = price. Log "new HWM $price (drawdown 0.00%)". Exit.
49
+ 6. drawdown_pct = (hwm - price) / hwm * 100
50
+ 7. If drawdown_pct >= trailing_pct: EXECUTE THE SELL (full balance). Log fill.
51
+ Mark closed=true. Optional: report PnL vs entry_price if known.
52
+ 8. Else: log "holding — price $price, HWM $hwm, drawdown $drawdown_pct%, gap to trigger $(trailing_pct - drawdown_pct)%". Exit.
53
+ ```
54
+
55
+ The sell command:
56
+
57
+ ```bash
58
+ mp --json token swap \
59
+ --wallet "$WALLET" \
60
+ --chain "$CHAIN" \
61
+ --from-token "$TOKEN" \
62
+ --from-amount "$BALANCE" \
63
+ --to-token "$SELL_INTO"
64
+ ```
65
+
66
+ ## Schedule
67
+
68
+ A 15-minute interval is the right balance for most positions:
69
+ - Cron: `*/15 * * * * /path/to/script.sh # moonpay:trailing-stop-<id>`
70
+ - Launchd: `<key>StartInterval</key><integer>900</integer>`
71
+
72
+ For volatile small-caps, drop to `*/5`. For large caps you're not actively trading, hourly is enough.
73
+
74
+ ## Headless reference script
75
+
76
+ ```bash
77
+ #!/bin/bash
78
+ set -euo pipefail
79
+ MP="$(which mp)"
80
+ ID="trailing-stop-sol-main" # unique per strategy
81
+ STATE="$HOME/.config/moonpay/state/${ID}.json"
82
+ LOG="$HOME/.config/moonpay/logs/trading.log"
83
+ mkdir -p "$(dirname "$STATE")" "$(dirname "$LOG")"
84
+ log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $ID $*" >> "$LOG"; }
85
+
86
+ # --- Config (fill these in once) ---
87
+ TOKEN="So11111111111111111111111111111111111111112" # SOL (WSOL mint)
88
+ CHAIN="solana"
89
+ WALLET="main"
90
+ WALLET_ADDR="<address from mp wallet list>"
91
+ TRAILING_PCT=12
92
+ SELL_INTO="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" # USDC
93
+
94
+ # --- Load state (initialize on first run) ---
95
+ if [ ! -f "$STATE" ]; then
96
+ PRICE=$("$MP" --json token retrieve --token "$TOKEN" --chain "$CHAIN" | jq '.marketData.price')
97
+ echo "{\"hwm\": $PRICE, \"closed\": false}" > "$STATE"
98
+ log "initialized hwm=$PRICE"
99
+ fi
100
+ CLOSED=$(jq -r '.closed' "$STATE")
101
+ HWM=$(jq -r '.hwm' "$STATE")
102
+ [ "$CLOSED" = "true" ] && { log "already closed, exit"; exit 0; }
103
+
104
+ # --- Check price + balance ---
105
+ PRICE=$("$MP" --json token retrieve --token "$TOKEN" --chain "$CHAIN" | jq '.marketData.price')
106
+ BALANCE=$("$MP" --json token balance list --wallet "$WALLET_ADDR" --chain "$CHAIN" \
107
+ | jq --arg t "$TOKEN" '[.items[] | select(.address==$t) | .balance.amount][0] // 0')
108
+
109
+ if (( $(echo "$BALANCE <= 0" | bc -l) )); then
110
+ jq '.closed = true' "$STATE" > "$STATE.tmp" && mv "$STATE.tmp" "$STATE"
111
+ log "no position, marked closed"; exit 0
112
+ fi
113
+
114
+ # --- Decide ---
115
+ if (( $(echo "$PRICE > $HWM" | bc -l) )); then
116
+ jq --argjson p "$PRICE" '.hwm = $p' "$STATE" > "$STATE.tmp" && mv "$STATE.tmp" "$STATE"
117
+ log "new HWM $PRICE"
118
+ exit 0
119
+ fi
120
+
121
+ DRAWDOWN_PCT=$(echo "scale=4; ($HWM - $PRICE) / $HWM * 100" | bc -l)
122
+ TRIGGER=$(echo "$DRAWDOWN_PCT >= $TRAILING_PCT" | bc -l)
123
+ if [ "$TRIGGER" = "1" ]; then
124
+ log "TRIGGER: drawdown ${DRAWDOWN_PCT}% >= ${TRAILING_PCT}% — selling $BALANCE"
125
+ RESULT=$("$MP" --json token swap \
126
+ --wallet "$WALLET" --chain "$CHAIN" \
127
+ --from-token "$TOKEN" --from-amount "$BALANCE" \
128
+ --to-token "$SELL_INTO" 2>&1) || { log "SELL FAILED: $RESULT"; exit 1; }
129
+ log "SELL OK: $RESULT"
130
+ jq '.closed = true' "$STATE" > "$STATE.tmp" && mv "$STATE.tmp" "$STATE"
131
+ else
132
+ log "holding — price $PRICE hwm $HWM drawdown ${DRAWDOWN_PCT}%"
133
+ fi
134
+ ```
135
+
136
+ ## Inside an automation harness
137
+
138
+ When this skill runs inside the MoonPay Agents desktop app, the harness provides a single resumed chat session — no state file needed. The agent persists `hwm` and `closed` in conversation memory and follows the same decision logic. The body of the in-app automation is a thin pointer to this skill.
139
+
140
+ ## Common pitfalls
141
+
142
+ - **Hard-coding the SOL mint as `...111`**. The wrapped SOL mint ends in `112`. Resolve via `mp token search --query SOL --chain solana` if unsure.
143
+ - **Skipping the balance check**. If the user manually sells the position between fires, the sell command will fail with insufficient balance. Always check balance first; mark closed and exit if zero.
144
+ - **Widening the stop after a loss**. The strategy is defined by mechanical execution. If you find yourself wanting to override, the strategy isn't for this position.
145
+ - **Not naming the cron entry**. Always tag scheduled entries with `# moonpay:trailing-stop-<id>` so they can be listed and removed cleanly.
146
+
147
+ ## Related skills
148
+
149
+ - **moonpay-trading-automation** — Shell-script + cron/launchd patterns for headless automation
150
+ - **moonpay-take-profit-ladder** — Companion strategy for partial-sell-on-the-way-up
151
+ - **moonpay-discover-tokens** — Resolve tickers to token addresses
152
+ - **moonpay-swap-tokens** — Underlying swap command syntax
153
+ - **moonpay-check-wallet** — Position size lookup