@overcast-xyz/cli 0.1.0

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 (44) hide show
  1. package/README.md +272 -0
  2. package/dist/bin.d.ts +2 -0
  3. package/dist/bin.js +200 -0
  4. package/dist/client.d.ts +12 -0
  5. package/dist/client.js +18 -0
  6. package/dist/commands/accept.d.ts +11 -0
  7. package/dist/commands/accept.js +112 -0
  8. package/dist/commands/assets.d.ts +9 -0
  9. package/dist/commands/assets.js +80 -0
  10. package/dist/commands/balance.d.ts +9 -0
  11. package/dist/commands/balance.js +74 -0
  12. package/dist/commands/deposit.d.ts +7 -0
  13. package/dist/commands/deposit.js +67 -0
  14. package/dist/commands/exercise.d.ts +14 -0
  15. package/dist/commands/exercise.js +88 -0
  16. package/dist/commands/key.d.ts +15 -0
  17. package/dist/commands/key.js +98 -0
  18. package/dist/commands/mm-webhook.d.ts +36 -0
  19. package/dist/commands/mm-webhook.js +152 -0
  20. package/dist/commands/mm.d.ts +37 -0
  21. package/dist/commands/mm.js +345 -0
  22. package/dist/commands/offer.d.ts +29 -0
  23. package/dist/commands/offer.js +149 -0
  24. package/dist/commands/option.d.ts +28 -0
  25. package/dist/commands/option.js +138 -0
  26. package/dist/commands/quote-webhook.d.ts +36 -0
  27. package/dist/commands/quote-webhook.js +152 -0
  28. package/dist/commands/quote.d.ts +37 -0
  29. package/dist/commands/quote.js +345 -0
  30. package/dist/commands/redeem.d.ts +14 -0
  31. package/dist/commands/redeem.js +88 -0
  32. package/dist/commands/rfq-view.d.ts +25 -0
  33. package/dist/commands/rfq-view.js +172 -0
  34. package/dist/commands/rfq.d.ts +4 -0
  35. package/dist/commands/rfq.js +280 -0
  36. package/dist/commands/shared.d.ts +81 -0
  37. package/dist/commands/shared.js +296 -0
  38. package/dist/commands/withdraw.d.ts +7 -0
  39. package/dist/commands/withdraw.js +67 -0
  40. package/dist/config.d.ts +53 -0
  41. package/dist/config.js +58 -0
  42. package/dist/index.d.ts +18 -0
  43. package/dist/index.js +50 -0
  44. package/package.json +38 -0
@@ -0,0 +1,345 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runQuoter = runQuoter;
40
+ const p = __importStar(require("@clack/prompts"));
41
+ const core_1 = require("@overcast-xyz/core");
42
+ const picocolors_1 = __importDefault(require("picocolors"));
43
+ const client_1 = require("../client");
44
+ const config_1 = require("../config");
45
+ const quote_webhook_1 = require("./quote-webhook");
46
+ /** Quoter edge as a fraction of fair value (2%). */
47
+ const DEFAULT_SPREAD = 0.02;
48
+ /** Annualized implied volatility used for every quote (80%). */
49
+ const DEFAULT_VOL = 0.8;
50
+ /** Annualized risk-free rate (crypto convention: assume 0). */
51
+ const DEFAULT_RATE = 0;
52
+ /** Per-event webhook timeout when `--webhook-timeout` is omitted. */
53
+ const DEFAULT_WEBHOOK_TIMEOUT_MS = 5000;
54
+ const SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
55
+ /**
56
+ * Symbols treated as the cash / numeraire leg. A quote needs exactly one cash
57
+ * leg and one volatile-underlying leg; the cash side is assumed to be ~$1.
58
+ */
59
+ const STABLE_SYMBOLS = new Set([
60
+ "USDC",
61
+ "USDT",
62
+ "USDH",
63
+ "PYUSD",
64
+ "DAI",
65
+ "USDP",
66
+ "TUSD",
67
+ "USD",
68
+ ]);
69
+ /**
70
+ * `overcast quote` — run an RFQ quoter that auto-quotes every incoming RFQ
71
+ * using a Black-Scholes price.
72
+ *
73
+ * A cash-secured put and a covered call are the same instrument — an option to
74
+ * swap `settlementAmount` of one asset for `collateralAmount` of the other —
75
+ * differing only in which leg is collateral. We identify the stablecoin (cash)
76
+ * leg and the volatile (underlying) leg, then price:
77
+ * - settlement is cash → collateral is the underlying → **covered call**
78
+ * - collateral is cash → settlement is the underlying → **cash-secured put**
79
+ * See {@link blackScholesPricer}.
80
+ */
81
+ async function runQuoter(opts) {
82
+ const config = (0, config_1.resolveConfig)(opts);
83
+ const app = await (0, client_1.createClient)(config);
84
+ const spread = opts.spread ? Number(opts.spread) : DEFAULT_SPREAD;
85
+ const vol = opts.vol ? Number(opts.vol) : DEFAULT_VOL;
86
+ const rate = opts.rate ? Number(opts.rate) : DEFAULT_RATE;
87
+ const rfq = app.rfq;
88
+ // Webhook mode: delegate pricing (and forward every lifecycle event) to a
89
+ // developer-supplied local endpoint. Falls back to the built-in Black-Scholes
90
+ // pricer when no `--webhook-url` (or `OVERCAST_QUOTE_WEBHOOK_URL`) is set.
91
+ const webhookUrl = opts.webhookUrl ?? process.env.OVERCAST_QUOTE_WEBHOOK_URL;
92
+ const webhook = webhookUrl
93
+ ? {
94
+ url: webhookUrl,
95
+ timeoutMs: Number(opts.webhookTimeout) || DEFAULT_WEBHOOK_TIMEOUT_MS,
96
+ }
97
+ : null;
98
+ const pricer = webhook
99
+ ? (0, quote_webhook_1.webhookPricer)(webhook, app.publicKey)
100
+ : blackScholesPricer({ spread, vol, rate });
101
+ p.intro(picocolors_1.default.magenta("Overcast RFQ Quoter"));
102
+ // The quoter takes the option (pays the premium), so it acts on the
103
+ // TAKER side — the opposite of the RFQ creator's MAKER side.
104
+ await rfq.registerRole("TAKER");
105
+ p.log.info(`Maker ${picocolors_1.default.bold(app.publicKey)}`);
106
+ p.log.info(picocolors_1.default.dim(webhook
107
+ ? `Webhook — ${webhook.url} (timeout ${webhook.timeoutMs}ms)`
108
+ : `Black-Scholes — vol ${vol * 100}%, rate ${rate * 100}%, edge ${spread * 100}%`));
109
+ p.log.info(picocolors_1.default.dim("Listening for RFQs. Press Ctrl-C to stop."));
110
+ rfq.onQuoteAccepted = (a) => {
111
+ p.log.success(`Quote ${short(a.quoteId)} accepted — option ${picocolors_1.default.bold(a.optionId)} (RFQ ${short(a.rfqId)})`);
112
+ if (webhook)
113
+ (0, quote_webhook_1.postEvent)(webhook, "quoteAccepted", a, app.publicKey);
114
+ };
115
+ rfq.onError = (e) => {
116
+ p.log.error(`RFQ error: ${e.message}`);
117
+ if (webhook)
118
+ (0, quote_webhook_1.postEvent)(webhook, "error", e, app.publicKey);
119
+ };
120
+ // In webhook mode, forward the remaining lifecycle events too so the endpoint
121
+ // has full visibility (these are fire-and-forget — their replies are ignored).
122
+ if (webhook) {
123
+ rfq.onNewQuote = (q) => (0, quote_webhook_1.postEvent)(webhook, "newQuote", q, app.publicKey);
124
+ rfq.onRfqCancelled = (c) => (0, quote_webhook_1.postEvent)(webhook, "rfqCancelled", c, app.publicKey);
125
+ rfq.onRfqExpired = (e) => (0, quote_webhook_1.postEvent)(webhook, "rfqExpired", e, app.publicKey);
126
+ }
127
+ // Auto-quote every incoming RFQ (Black-Scholes, or the webhook's reply).
128
+ rfq.onNewRfq = (incoming) => {
129
+ void quoteRfq(rfq, app.publicKey, incoming, pricer);
130
+ };
131
+ // Run until interrupted.
132
+ await new Promise((resolve) => {
133
+ process.on("SIGINT", () => {
134
+ p.log.warn("Shutting down…");
135
+ rfq.disconnect();
136
+ resolve();
137
+ });
138
+ });
139
+ p.outro(picocolors_1.default.magenta("Quoter stopped."));
140
+ }
141
+ /**
142
+ * Price an incoming RFQ and submit a quote over the full option terms — the
143
+ * RFQ's legs completed with the maker's premium — on the TAKER side. The client
144
+ * authors the settlement offer and signs it with the protocol signer, so no
145
+ * signature is passed here.
146
+ */
147
+ async function quoteRfq(rfq, creator, incoming, pricer) {
148
+ try {
149
+ const premium = await pricer(incoming);
150
+ if (!premium)
151
+ return; // the pricer already logged why it skipped
152
+ const details = (0, core_1.completeOptionDetails)(incoming.details, premium);
153
+ if (!details) {
154
+ skip(incoming.rfqId, "RFQ is missing option terms");
155
+ return;
156
+ }
157
+ const { quoteId } = await rfq.submitQuote({
158
+ rfqId: incoming.rfqId,
159
+ details,
160
+ creator,
161
+ side: "TAKER",
162
+ });
163
+ p.log.success(`Quoted ${premium.premiumAmount} ${short(premium.premiumAsset)} on RFQ ${short(incoming.rfqId)} (quote ${short(quoteId)})`);
164
+ }
165
+ catch (err) {
166
+ p.log.error(`Failed to quote RFQ ${short(incoming.rfqId)}: ${err.message}`);
167
+ }
168
+ }
169
+ /**
170
+ * Black-Scholes pricer. Maps an RFQ onto European-option terms and quotes the
171
+ * fair premium plus a `spread` edge, denominated in the cash (stablecoin) leg.
172
+ *
173
+ * The contract lets the holder swap the underlying for cash at the implied
174
+ * strike `K = cashAmount / underlyingAmount` (USD per underlying unit), with
175
+ * `underlyingAmount` as the contract size.
176
+ *
177
+ * Spot comes from a live CoinGecko lookup, falling back to the asset's
178
+ * backend-supplied {@link Asset.lastKnownPriceUsd} (the only source for RWAs,
179
+ * which have no `coinGeckoId`). Volatility uses the asset's own
180
+ * {@link Asset.volatility} when present, else the configured default. Returns
181
+ * `null` (skips the RFQ) when the legs can't be priced — no single cash leg, no
182
+ * usable spot price, or the option has already expired.
183
+ */
184
+ function blackScholesPricer(params) {
185
+ const { spread, vol, rate } = params;
186
+ return async (rfq) => {
187
+ const Sleep = (ms) => new Promise((r) => setTimeout(r, ms));
188
+ await Sleep(Math.random() * 1000);
189
+ // `d` is the curated (wire) shape — assets resolved, amounts as decimal
190
+ // strings — but only *partially* filled: the RFQ may omit some terms. We can
191
+ // only price a fully-specified pair of legs, so bail otherwise.
192
+ const d = rfq.details;
193
+ if (d.endTimestamp == null ||
194
+ !d.collateralAsset ||
195
+ d.collateralAmount == null ||
196
+ !d.settlementAsset ||
197
+ d.settlementAmount == null) {
198
+ skip(rfq.rfqId, "RFQ does not specify both option legs");
199
+ return null;
200
+ }
201
+ // Capture the now-narrowed terms before any further `await` widens them back.
202
+ const endTimestamp = d.endTimestamp;
203
+ const collateral = {
204
+ asset: d.collateralAsset,
205
+ amount: (0, core_1.toDisplayAmount)((0, core_1.amountFromWire)(d.collateralAmount), d.collateralAsset),
206
+ };
207
+ const settlement = {
208
+ asset: d.settlementAsset,
209
+ amount: (0, core_1.toDisplayAmount)((0, core_1.amountFromWire)(d.settlementAmount), d.settlementAsset),
210
+ };
211
+ // Need exactly one cash leg and one volatile leg to define an option.
212
+ const collateralIsCash = isStable(collateral.asset);
213
+ const settlementIsCash = isStable(settlement.asset);
214
+ if (collateralIsCash === settlementIsCash) {
215
+ skip(rfq.rfqId, `need exactly one stablecoin leg (got ${symbol(collateral.asset)}/${symbol(settlement.asset)})`);
216
+ return null;
217
+ }
218
+ // settlement = cash → covered call; collateral = cash → cash-secured put.
219
+ const isCall = settlementIsCash;
220
+ const cash = isCall ? settlement : collateral;
221
+ const underlying = isCall ? collateral : settlement;
222
+ // Prefer a live CoinGecko spot, falling back to the backend-supplied last
223
+ // known price. For RWAs (no `coinGeckoId`) the last known price is the only
224
+ // source — they're never auto-refreshed against a live feed.
225
+ const liveSpot = underlying.asset.coinGeckoId
226
+ ? await fetchSpotUsd(underlying.asset.coinGeckoId)
227
+ : null;
228
+ const spot = liveSpot ?? underlying.asset.lastKnownPriceUsd ?? null;
229
+ if (spot == null) {
230
+ skip(rfq.rfqId, `no spot or last known price for ${symbol(underlying.asset)}`);
231
+ return null;
232
+ }
233
+ // Use the asset's own volatility when the backend provides one; otherwise
234
+ // fall back to the configured default.
235
+ const sigma = underlying.asset.volatility ?? vol;
236
+ const nowSec = Math.floor(Date.now() / 1000);
237
+ const years = (endTimestamp - nowSec) / SECONDS_PER_YEAR;
238
+ if (years <= 0) {
239
+ skip(rfq.rfqId, "option has already expired");
240
+ return null;
241
+ }
242
+ const strike = Number(cash.amount) / Number(underlying.amount); // USD per underlying unit
243
+ const perUnit = blackScholes(isCall, spot, strike, years, rate, sigma);
244
+ const fairCash = perUnit * Number(underlying.amount); // total premium in cash units
245
+ const quotedCash = fairCash * (1 + spread) * (Math.random() * 0.05 + 0.95); // add the quoter's edge
246
+ // Pricing is done in floats (display units); the quoted premium becomes an
247
+ // exact native `bigint`. The backend requires a positive premium, so clamp
248
+ // to at least one native unit.
249
+ const decimals = cash.asset.decimals ?? 0;
250
+ const native = (0, core_1.parseUnits)(quotedCash.toFixed(decimals), decimals);
251
+ const premiumAmount = native < 1n ? 1n : native;
252
+ return { premiumAsset: cash.asset.address, premiumAmount };
253
+ };
254
+ }
255
+ /**
256
+ * Black-Scholes price of one European option, in the same currency as `S`/`K`.
257
+ *
258
+ * @param isCall true for a call, false for a put
259
+ * @param S spot price of the underlying
260
+ * @param K strike price
261
+ * @param T time to expiry, in years
262
+ * @param r annualized risk-free rate
263
+ * @param sigma annualized volatility
264
+ */
265
+ function blackScholes(isCall, S, K, T, r, sigma) {
266
+ // Degenerate inputs: the option is worth its (undiscounted) intrinsic value.
267
+ if (sigma <= 0 || T <= 0) {
268
+ return Math.max(0, isCall ? S - K : K - S);
269
+ }
270
+ const sqrtT = Math.sqrt(T);
271
+ const d1 = (Math.log(S / K) + (r + (sigma * sigma) / 2) * T) / (sigma * sqrtT);
272
+ const d2 = d1 - sigma * sqrtT;
273
+ const disc = Math.exp(-r * T);
274
+ return isCall
275
+ ? S * normCdf(d1) - K * disc * normCdf(d2)
276
+ : K * disc * normCdf(-d2) - S * normCdf(-d1);
277
+ }
278
+ /**
279
+ * Standard normal CDF via the Zelen & Severo rational approximation
280
+ * (Abramowitz & Stegun 26.2.17), accurate to ~7.5e-8.
281
+ */
282
+ function normCdf(x) {
283
+ if (x < 0)
284
+ return 1 - normCdf(-x);
285
+ const t = 1 / (1 + 0.2316419 * x);
286
+ const poly = t *
287
+ (0.31938153 +
288
+ t *
289
+ (-0.356563782 +
290
+ t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
291
+ const pdf = 0.3989422804014327 * Math.exp((-x * x) / 2);
292
+ return 1 - pdf * poly;
293
+ }
294
+ // --- spot prices -------------------------------------------------------------
295
+ /** Cached CoinGecko USD prices, refreshed on a short TTL to avoid rate limits. */
296
+ const SPOT_TTL_MS = 60_000;
297
+ const spotCache = new Map();
298
+ /**
299
+ * Fetch the USD spot price for a CoinGecko id, served from a short-lived cache.
300
+ * Returns `null` if the lookup fails (network error or unknown id) so the
301
+ * pricer can skip the RFQ rather than quote on stale or missing data.
302
+ */
303
+ async function fetchSpotUsd(coinGeckoId) {
304
+ const cached = spotCache.get(coinGeckoId);
305
+ if (cached && Date.now() - cached.at < SPOT_TTL_MS)
306
+ return cached.price;
307
+ try {
308
+ const url = `https://api.coingecko.com/api/v3/simple/price?ids=${encodeURIComponent(coinGeckoId)}&vs_currencies=usd`;
309
+ const res = await fetch(url);
310
+ if (!res.ok)
311
+ return null;
312
+ const body = (await res.json());
313
+ const price = body[coinGeckoId]?.usd;
314
+ if (typeof price !== "number" || !(price > 0))
315
+ return null;
316
+ spotCache.set(coinGeckoId, { price, at: Date.now() });
317
+ return price;
318
+ }
319
+ catch {
320
+ return null;
321
+ }
322
+ }
323
+ // --- helpers -----------------------------------------------------------------
324
+ /**
325
+ * Whether an asset is treated as the cash / numeraire leg. The backend's own
326
+ * {@link Asset.cashLike} classification wins when present; otherwise fall back to
327
+ * the stablecoin-symbol heuristic for assets that predate the flag.
328
+ */
329
+ function isStable(asset) {
330
+ if (asset.cashLike != null)
331
+ return asset.cashLike;
332
+ return asset.symbol ? STABLE_SYMBOLS.has(asset.symbol.toUpperCase()) : false;
333
+ }
334
+ /** A display label for an asset (symbol, falling back to a short address). */
335
+ function symbol(asset) {
336
+ return asset.symbol ?? short(asset.address);
337
+ }
338
+ /** Log why an RFQ was skipped without quoting. */
339
+ function skip(rfqId, reason) {
340
+ p.log.warn(`Skipping RFQ ${short(rfqId)}: ${reason}`);
341
+ }
342
+ /** Shorten a base58 id for log lines. */
343
+ function short(id) {
344
+ return id.length <= 12 ? id : `${id.slice(0, 6)}…${id.slice(-4)}`;
345
+ }
@@ -0,0 +1,14 @@
1
+ import { GlobalFlags } from "../config";
2
+ export interface RedeemOptions extends GlobalFlags {
3
+ collateralOffer?: string;
4
+ settlementOffer?: string;
5
+ /** Native-unit amount of collateral-return claims to burn. */
6
+ amount?: string;
7
+ }
8
+ /**
9
+ * `overcast redeem` — redeem (burn collateral-return claims of) an option,
10
+ * pulling the maker's collateral (and any settlement paid on exercise) back
11
+ * out. Payouts always go to the maker; the wallet must be the maker to hold
12
+ * claims to redeem.
13
+ */
14
+ export declare function redeem(opts: RedeemOptions): Promise<void>;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.redeem = redeem;
40
+ const p = __importStar(require("@clack/prompts"));
41
+ const picocolors_1 = __importDefault(require("picocolors"));
42
+ const client_1 = require("../client");
43
+ const config_1 = require("../config");
44
+ const shared_1 = require("./shared");
45
+ /**
46
+ * `overcast redeem` — redeem (burn collateral-return claims of) an option,
47
+ * pulling the maker's collateral (and any settlement paid on exercise) back
48
+ * out. Payouts always go to the maker; the wallet must be the maker to hold
49
+ * claims to redeem.
50
+ */
51
+ async function redeem(opts) {
52
+ const config = (0, config_1.resolveConfig)(opts);
53
+ const app = await (0, client_1.createClient)(config);
54
+ const registry = await (0, shared_1.loadRegistry)(app.api);
55
+ p.intro(picocolors_1.default.cyan("Overcast redeem"));
56
+ const option = await (0, shared_1.resolveOption)(app.chain, opts);
57
+ if (app.publicKey !== option.maker) {
58
+ p.log.warn(`This wallet is not the option maker (${(0, shared_1.short)(option.maker)}); the redeem will fail.`);
59
+ }
60
+ const claims = await app.chain.redeemClaims(option, app.publicKey);
61
+ p.note([
62
+ (0, shared_1.describeOptionDetails)(option.details, registry),
63
+ "",
64
+ `Your redeem claims ${picocolors_1.default.bold(claims.toString())}`,
65
+ ].join("\n"), `Option ${(0, shared_1.short)(option.id)}`);
66
+ if (claims === 0n && opts.amount == null) {
67
+ p.outro(picocolors_1.default.yellow("You hold no collateral-return claims for this option."));
68
+ return;
69
+ }
70
+ const amount = opts.amount != null
71
+ ? BigInt(opts.amount)
72
+ : await (0, shared_1.promptAmountUpTo)("Amount to redeem", claims);
73
+ if (!(await (0, shared_1.confirm)(`Redeem ${amount} claim(s)?`))) {
74
+ p.outro(picocolors_1.default.yellow("Cancelled."));
75
+ return;
76
+ }
77
+ const spinner = p.spinner();
78
+ spinner.start(`Redeeming ${amount} claim(s)`);
79
+ try {
80
+ const { hash } = await app.chain.redeemOption(option, amount);
81
+ spinner.stop(picocolors_1.default.green(`Redeemed. tx ${picocolors_1.default.bold(hash)}`));
82
+ p.outro(picocolors_1.default.green("Done."));
83
+ }
84
+ catch (err) {
85
+ spinner.stop(picocolors_1.default.red(`Redeem failed: ${err.message}`));
86
+ throw err;
87
+ }
88
+ }
@@ -0,0 +1,25 @@
1
+ import { GlobalFlags } from "../config";
2
+ export interface RfqListOptions extends GlobalFlags {
3
+ /** Filter to RFQs created by this address. */
4
+ creator?: string;
5
+ /** Filter by side: MAKER or TAKER. */
6
+ side?: string;
7
+ /** Filter by lifecycle status (open, quoted, accepted, cancelled, expired). */
8
+ status?: string;
9
+ /** Max rows to return. */
10
+ limit?: string;
11
+ }
12
+ /**
13
+ * `overcast rfq list` — browse persisted RFQs (open and historical) via the
14
+ * off-chain {@link OvercastView}. Read-only: no wallet required.
15
+ */
16
+ export declare function rfqList(opts: RfqListOptions): Promise<void>;
17
+ export interface RfqShowOptions extends GlobalFlags {
18
+ /** RFQ id to show; prompts when omitted. */
19
+ id?: string;
20
+ }
21
+ /**
22
+ * `overcast rfq show` — show a single RFQ's terms and every quote submitted
23
+ * against it, via the off-chain {@link OvercastView}. Read-only.
24
+ */
25
+ export declare function rfqShow(opts: RfqShowOptions): Promise<void>;
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.rfqList = rfqList;
40
+ exports.rfqShow = rfqShow;
41
+ const p = __importStar(require("@clack/prompts"));
42
+ const core_1 = require("@overcast/core");
43
+ const picocolors_1 = __importDefault(require("picocolors"));
44
+ const client_1 = require("../client");
45
+ const config_1 = require("../config");
46
+ const shared_1 = require("./shared");
47
+ const SIDES = ["MAKER", "TAKER"];
48
+ const STATUSES = [
49
+ "open",
50
+ "quoted",
51
+ "accepted",
52
+ "cancelled",
53
+ "expired",
54
+ ];
55
+ /**
56
+ * `overcast rfq list` — browse persisted RFQs (open and historical) via the
57
+ * off-chain {@link OvercastView}. Read-only: no wallet required.
58
+ */
59
+ async function rfqList(opts) {
60
+ const config = (0, config_1.resolveConfig)(opts, { keypairOptional: true });
61
+ const app = await (0, client_1.createClient)(config);
62
+ const filter = {
63
+ creator: (0, shared_1.optionalAddress)("--creator", opts.creator),
64
+ side: parseSide(opts.side),
65
+ status: parseStatus(opts.status),
66
+ };
67
+ const limit = (0, shared_1.parseLimit)(opts.limit);
68
+ p.intro(picocolors_1.default.cyan("Overcast RFQs"));
69
+ const spinner = p.spinner();
70
+ spinner.start("Loading RFQs");
71
+ try {
72
+ const page = await app.view.listRfqs({
73
+ filter,
74
+ page: limit ? { limit } : undefined,
75
+ });
76
+ spinner.stop(`${page.items.length} RFQ(s)`);
77
+ if (page.items.length === 0) {
78
+ p.outro(picocolors_1.default.yellow("No RFQs match."));
79
+ return;
80
+ }
81
+ for (const rfq of page.items) {
82
+ p.log.message([
83
+ `${picocolors_1.default.bold((0, shared_1.short)(rfq.rfqId))} ${statusTag(rfq.status)} ${picocolors_1.default.dim(rfq.side)} ${(0, shared_1.summarizeCuratedDetails)(rfq.details)}`,
84
+ picocolors_1.default.dim(`creator ${(0, shared_1.short)(rfq.creator)} · ${(0, shared_1.formatTimestamp)(Math.floor(rfq.createdAt / 1000))}`),
85
+ ].join("\n"));
86
+ }
87
+ p.outro(picocolors_1.default.green(`${page.total ?? page.items.length} total.`));
88
+ }
89
+ catch (err) {
90
+ spinner.stop(picocolors_1.default.red(`List failed: ${err.message}`));
91
+ throw err;
92
+ }
93
+ }
94
+ /**
95
+ * `overcast rfq show` — show a single RFQ's terms and every quote submitted
96
+ * against it, via the off-chain {@link OvercastView}. Read-only.
97
+ */
98
+ async function rfqShow(opts) {
99
+ const config = (0, config_1.resolveConfig)(opts, { keypairOptional: true });
100
+ const app = await (0, client_1.createClient)(config);
101
+ if (opts.id && (0, shared_1.validateAddress)(opts.id)) {
102
+ throw new Error(`invalid RFQ id: ${opts.id}`);
103
+ }
104
+ const id = opts.id ?? (await (0, shared_1.promptId)("RFQ id"));
105
+ p.intro(picocolors_1.default.cyan("Overcast RFQ"));
106
+ const spinner = p.spinner();
107
+ spinner.start("Loading RFQ");
108
+ try {
109
+ const rfq = await app.view.getRfq(id);
110
+ if (!rfq) {
111
+ spinner.stop(picocolors_1.default.yellow(`No RFQ found for ${(0, shared_1.short)(id)}.`));
112
+ return;
113
+ }
114
+ const quotes = await app.view.listQuotes(id);
115
+ spinner.stop(`${quotes.items.length} quote(s)`);
116
+ p.note([
117
+ `Creator ${(0, shared_1.short)(rfq.creator)}`,
118
+ `Side ${rfq.side}`,
119
+ `Status ${statusTag(rfq.status)}`,
120
+ "",
121
+ (0, shared_1.summarizeCuratedDetails)(rfq.details),
122
+ ].join("\n"), `RFQ ${(0, shared_1.short)(rfq.rfqId)}`);
123
+ if (quotes.items.length === 0) {
124
+ p.log.info(picocolors_1.default.dim("No quotes yet."));
125
+ }
126
+ else {
127
+ for (const quote of quotes.items) {
128
+ p.log.message([
129
+ `${picocolors_1.default.bold((0, shared_1.short)(quote.quoteId))} ${(0, core_1.formatAssetAmount)((0, core_1.amountFromWire)(quote.details.premiumAmount), quote.details.premiumAsset)} ${picocolors_1.default.dim(quote.status)}`,
130
+ picocolors_1.default.dim(`from ${(0, shared_1.short)(quote.creator)}`),
131
+ ].join("\n"));
132
+ }
133
+ }
134
+ p.outro(picocolors_1.default.green("Done."));
135
+ }
136
+ catch (err) {
137
+ spinner.stop(picocolors_1.default.red(`Lookup failed: ${err.message}`));
138
+ throw err;
139
+ }
140
+ }
141
+ /** Colour an RFQ status for scannable list output. */
142
+ function statusTag(status) {
143
+ switch (status) {
144
+ case "open":
145
+ case "quoted":
146
+ return picocolors_1.default.green(status);
147
+ case "accepted":
148
+ return picocolors_1.default.cyan(status);
149
+ default:
150
+ return picocolors_1.default.dim(status);
151
+ }
152
+ }
153
+ /** Validate the optional `--side` filter against the {@link ClientRole} union. */
154
+ function parseSide(value) {
155
+ if (value == null)
156
+ return undefined;
157
+ const side = value.toUpperCase();
158
+ if (!SIDES.includes(side)) {
159
+ throw new Error(`invalid --side: ${value} (expected ${SIDES.join(" or ")})`);
160
+ }
161
+ return side;
162
+ }
163
+ /** Validate the optional `--status` filter against the {@link RfqStatus} union. */
164
+ function parseStatus(value) {
165
+ if (value == null)
166
+ return undefined;
167
+ const status = value.toLowerCase();
168
+ if (!STATUSES.includes(status)) {
169
+ throw new Error(`invalid --status: ${value} (expected ${STATUSES.join(", ")})`);
170
+ }
171
+ return status;
172
+ }
@@ -0,0 +1,4 @@
1
+ import { GlobalFlags } from "../config";
2
+ export type RfqOptions = GlobalFlags;
3
+ /** `overcast rfq create` — request quotes, pick one, and settle it. */
4
+ export declare function rfqCreate(opts: RfqOptions): Promise<void>;