@vultisig/rujira 12.0.0 → 13.0.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.
@@ -0,0 +1,467 @@
1
+ import { RujiraError, RujiraErrorCode, wrapError } from '../errors.js';
2
+ import { base64Encode } from '../utils/encoding.js';
3
+ import { validateThorAddress } from '../validation/address-validator.js';
4
+ const RUJIRA_GRAPHQL_URL = 'https://api.vultisig.com/ruji/api/graphql';
5
+ const GRAPHQL_TIMEOUT_MS = 15_000;
6
+ /** Scale of MOIC and DPI fields in analytics (divide raw bigint by this). */
7
+ export const RANGE_MOIC_SCALE = 1e12;
8
+ /** Scale of APR field in analytics (divide raw bigint by this). */
9
+ export const RANGE_APR_SCALE = 1e10;
10
+ /** Fractional digits used by config Decimal fields (high/low/spread/skew/fee). */
11
+ export const RANGE_CONFIG_DECIMALS = 12;
12
+ /** Fractional digits used by the withdraw share parameter. */
13
+ export const RANGE_WITHDRAW_SHARE_DECIMALS = 4;
14
+ // ---------------------------------------------------------------------------
15
+ // Validation helpers
16
+ // ---------------------------------------------------------------------------
17
+ const DECIMAL_12_RE = /^-?\d+(\.\d{1,12})?$/;
18
+ const DECIMAL_4_RE = /^\d+(\.\d{1,4})?$/;
19
+ const IDX_RE = /^\d+$/;
20
+ function assertDecimal12(label, v) {
21
+ if (typeof v !== 'string' || !DECIMAL_12_RE.test(v)) {
22
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `${label} must be a Decimal string with up to 12 fractional digits (got ${JSON.stringify(v)})`);
23
+ }
24
+ }
25
+ function assertDecimal4(label, v) {
26
+ if (typeof v !== 'string' || !DECIMAL_4_RE.test(v)) {
27
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `${label} must be a Decimal string with up to 4 fractional digits (got ${JSON.stringify(v)})`);
28
+ }
29
+ }
30
+ function assertIdx(v) {
31
+ if (typeof v !== 'string' || !IDX_RE.test(v)) {
32
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `idx must be a non-negative integer string (got ${JSON.stringify(v)})`);
33
+ }
34
+ }
35
+ function assertCoin(label, c) {
36
+ if (!c || typeof c.denom !== 'string' || !c.denom || typeof c.amount !== 'string' || !/^\d+$/.test(c.amount)) {
37
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `${label} must be a Coin with string denom and integer-string amount`);
38
+ }
39
+ }
40
+ const RANGE_CONFIG_SCALE = 10n ** BigInt(RANGE_CONFIG_DECIMALS);
41
+ const RANGE_WITHDRAW_SHARE_SCALE = 10n ** BigInt(RANGE_WITHDRAW_SHARE_DECIMALS);
42
+ // Scale a validated Decimal-string (matched by DECIMAL_12_RE / DECIMAL_4_RE) to a BigInt
43
+ // with `decimals` fractional digits. Avoids parseFloat to preserve precision.
44
+ function decimalToScaled(v, decimals) {
45
+ const scale = 10n ** BigInt(decimals);
46
+ const sign = v.startsWith('-') ? -1n : 1n;
47
+ const unsigned = v.startsWith('-') ? v.slice(1) : v;
48
+ const [whole, fraction = ''] = unsigned.split('.');
49
+ const paddedFraction = fraction.padEnd(decimals, '0').slice(0, decimals);
50
+ return sign * (BigInt(whole || '0') * scale + BigInt(paddedFraction || '0'));
51
+ }
52
+ function assertShare(v) {
53
+ assertDecimal4('share', v);
54
+ // 0 < share <= 1, compared in scaled BigInt space (reject 0 and >1).
55
+ const scaled = decimalToScaled(v, RANGE_WITHDRAW_SHARE_DECIMALS);
56
+ if (!(scaled > 0n && scaled <= RANGE_WITHDRAW_SHARE_SCALE)) {
57
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `share must be in (0, 1] (got ${v})`);
58
+ }
59
+ }
60
+ function assertPairAddress(a) {
61
+ if (typeof a !== 'string') {
62
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `pairAddress must be a bech32 thor1... contract address (got ${JSON.stringify(a)})`);
63
+ }
64
+ try {
65
+ validateThorAddress(a);
66
+ }
67
+ catch {
68
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `pairAddress must be a bech32 thor1... contract address (got ${JSON.stringify(a)})`);
69
+ }
70
+ }
71
+ function assertConfig(c) {
72
+ assertDecimal12('config.high', c.high);
73
+ assertDecimal12('config.low', c.low);
74
+ assertDecimal12('config.spread', c.spread);
75
+ assertDecimal12('config.skew', c.skew);
76
+ assertDecimal12('config.fee', c.fee);
77
+ // Scaled BigInt comparisons — avoids parseFloat so 12dp precision is preserved.
78
+ const high = decimalToScaled(c.high, RANGE_CONFIG_DECIMALS);
79
+ const low = decimalToScaled(c.low, RANGE_CONFIG_DECIMALS);
80
+ const spread = decimalToScaled(c.spread, RANGE_CONFIG_DECIMALS);
81
+ const fee = decimalToScaled(c.fee, RANGE_CONFIG_DECIMALS);
82
+ if (low <= 0n) {
83
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `config.low (${c.low}) must be > 0`);
84
+ }
85
+ if (high <= low) {
86
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `config.high (${c.high}) must be > config.low (${c.low})`);
87
+ }
88
+ // Spread reasonability: 0 < spread < 1 (contract likely enforces, but catch early).
89
+ if (!(spread > 0n && spread < RANGE_CONFIG_SCALE)) {
90
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `config.spread must be in (0, 1) (got ${c.spread})`);
91
+ }
92
+ // Fee must not exceed spread.
93
+ if (fee < 0n || fee > spread) {
94
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `config.fee must be in [0, spread] (got ${c.fee} vs spread ${c.spread})`);
95
+ }
96
+ }
97
+ async function gqlFetch(query, variables) {
98
+ const controller = new AbortController();
99
+ const timeout = setTimeout(() => controller.abort(), GRAPHQL_TIMEOUT_MS);
100
+ // Wrap the ENTIRE fetch + body read in one try block so the timeout
101
+ // covers both phases. Previously `clearTimeout` ran in `finally` right
102
+ // after `fetch` resolved (headers only), leaving `response.json()` with
103
+ // no deadline. A slow body stream past GRAPHQL_TIMEOUT_MS would hang
104
+ // indefinitely — defeating the guard on the exact case we were trying
105
+ // to protect against.
106
+ try {
107
+ const response = await fetch(RUJIRA_GRAPHQL_URL, {
108
+ method: 'POST',
109
+ headers: { 'Content-Type': 'application/json' },
110
+ body: JSON.stringify({ query, variables }),
111
+ signal: controller.signal,
112
+ });
113
+ if (!response.ok) {
114
+ throw new RujiraError(RujiraErrorCode.NETWORK_ERROR, `GraphQL request failed: ${response.status}`);
115
+ }
116
+ const json = (await response.json());
117
+ if (json.errors?.length) {
118
+ throw new RujiraError(RujiraErrorCode.NETWORK_ERROR, `GraphQL errors: ${json.errors[0].message}`);
119
+ }
120
+ // Review finding: a malformed backend response (200 OK, no `errors`,
121
+ // no `data`) would return undefined and downstream callers would
122
+ // collapse it to "no positions" / "no pair" via their `?? []` / `??
123
+ // null` fallbacks — masking a real backend/schema break as a silent
124
+ // empty result. Users would make fund-moving decisions on false
125
+ // negatives. Reject at the transport boundary instead.
126
+ if (json.data === undefined || json.data === null) {
127
+ throw new RujiraError(RujiraErrorCode.NETWORK_ERROR, 'GraphQL response missing `data` field (backend or schema error)');
128
+ }
129
+ return json.data;
130
+ }
131
+ catch (error) {
132
+ // AbortError from the timeout above would otherwise fall through to wrapError's
133
+ // default NETWORK_ERROR branch (its "timeout"/"timed out" string match misses
134
+ // the "The operation was aborted." message). Translate it to a proper TIMEOUT.
135
+ // Now covers both `fetch` and `response.json()` abort paths.
136
+ if (error instanceof Error && error.name === 'AbortError') {
137
+ throw new RujiraError(RujiraErrorCode.TIMEOUT, `GraphQL request timed out after ${GRAPHQL_TIMEOUT_MS}ms`, error, true);
138
+ }
139
+ throw error;
140
+ }
141
+ finally {
142
+ clearTimeout(timeout);
143
+ }
144
+ }
145
+ // Query shapes — keep narrow, only fields we surface.
146
+ // NOTE: FinRange + FinPair schema is taken from rujira-ui `TradeSubscriptions`.
147
+ // If upstream schema changes, these queries must be updated in lockstep.
148
+ // Queries follow the schema at api.vultisig.com/ruji/api/graphql as of
149
+ // 2026-04-21. Paths verified via __type introspection:
150
+ // Account.fin.ranges → FinRangeConnection (Relay edges/node)
151
+ // FinRange.{idx,base,quote,feesBase,feesQuote,principalUsd,yieldUsd,
152
+ // high,low,spread,skew,fee,price,valueUsd,pair,analytics}
153
+ // Config fields are FLAT on FinRange (not under a nested config object).
154
+ const POSITIONS_QUERY = `
155
+ query RangePositions($id: ID!) {
156
+ node(id: $id) {
157
+ ... on Account {
158
+ fin {
159
+ ranges(first: 100) {
160
+ edges {
161
+ node {
162
+ id idx base quote
163
+ feesBase feesQuote
164
+ principalUsd yieldUsd
165
+ high low spread skew fee price
166
+ pair { id address }
167
+ analytics { moic dpi apr firstDepositDate status }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ `;
176
+ const POSITION_QUERY = `
177
+ query RangePosition($id: ID!) {
178
+ node(id: $id) {
179
+ ... on FinRange {
180
+ id idx base quote
181
+ feesBase feesQuote
182
+ principalUsd yieldUsd
183
+ high low spread skew fee price
184
+ pair { id address }
185
+ analytics { moic dpi apr firstDepositDate status }
186
+ }
187
+ }
188
+ }
189
+ `;
190
+ // Pair lookup goes through finV3.pairs sorted by volume. We fetch the top N
191
+ // and do (base, quote) matching client-side on symbols AND denoms — tolerating
192
+ // LLM-mangled inputs like "xruji" (stripped "x/ruji") or "thorrune"
193
+ // (stripped "thor.rune") by normalising separators on both sides.
194
+ const PAIR_QUERY = `
195
+ query FinPairsAll {
196
+ finV3 {
197
+ pairs(first: 200, sortBy: VOLUME, sortDir: DESC) {
198
+ edges {
199
+ node {
200
+ address
201
+ assetBase { metadata { symbol } variants { native { denom } } }
202
+ assetQuote { metadata { symbol } variants { native { denom } } }
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ `;
209
+ function mapRange(r) {
210
+ if (!r.pair?.address) {
211
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `FinRange ${r.idx} is missing pair.address (partial/error GraphQL response?)`);
212
+ }
213
+ const config = r.high && r.low && r.spread && r.skew !== undefined && r.fee !== undefined
214
+ ? { high: r.high, low: r.low, spread: r.spread, skew: r.skew, fee: r.fee }
215
+ : undefined;
216
+ return {
217
+ idx: r.idx,
218
+ pairAddress: r.pair.address,
219
+ base: r.base,
220
+ quote: r.quote,
221
+ feesBase: r.feesBase,
222
+ feesQuote: r.feesQuote,
223
+ principalUsd: r.principalUsd,
224
+ yieldUsd: r.yieldUsd,
225
+ config,
226
+ analytics: r.analytics,
227
+ };
228
+ }
229
+ // ---------------------------------------------------------------------------
230
+ // RujiraRange
231
+ // ---------------------------------------------------------------------------
232
+ /**
233
+ * Builders + queries for RUJI Trade CCL range positions.
234
+ *
235
+ * ExecuteMsg shapes (keep in sync with `rujira-ui` Range.tsx / RangeManage.tsx):
236
+ *
237
+ * - create: `{ range: { create: { config: { high, low, spread, skew, fee } } } }`
238
+ * - deposit: `{ range: { deposit: { idx } } }`
239
+ * - withdraw: `{ range: { withdraw: { idx, amount } } }` (amount is the share Decimal 4dp)
240
+ * - claim: `{ range: { claim: { idx } } }`
241
+ * - transfer: `{ range: { transfer: { idx, to } } }`
242
+ */
243
+ export class RujiraRange {
244
+ constructor(client) {
245
+ this.client = client;
246
+ }
247
+ // ------------------- Builders -------------------
248
+ buildCreatePosition(params) {
249
+ assertPairAddress(params.pairAddress);
250
+ assertConfig(params.config);
251
+ assertCoin('base', params.base);
252
+ assertCoin('quote', params.quote);
253
+ return {
254
+ contractAddress: params.pairAddress,
255
+ executeMsg: {
256
+ range: {
257
+ create: {
258
+ config: {
259
+ high: params.config.high,
260
+ low: params.config.low,
261
+ spread: params.config.spread,
262
+ skew: params.config.skew,
263
+ fee: params.config.fee,
264
+ },
265
+ },
266
+ },
267
+ },
268
+ funds: [params.base, params.quote].sort((a, b) => (a.denom < b.denom ? -1 : 1)),
269
+ };
270
+ }
271
+ buildDeposit(params) {
272
+ assertPairAddress(params.pairAddress);
273
+ assertIdx(params.idx);
274
+ assertCoin('base', params.base);
275
+ assertCoin('quote', params.quote);
276
+ return {
277
+ contractAddress: params.pairAddress,
278
+ executeMsg: { range: { deposit: { idx: params.idx } } },
279
+ funds: [params.base, params.quote].sort((a, b) => (a.denom < b.denom ? -1 : 1)),
280
+ };
281
+ }
282
+ buildWithdraw(params) {
283
+ assertPairAddress(params.pairAddress);
284
+ assertIdx(params.idx);
285
+ assertShare(params.share);
286
+ return {
287
+ contractAddress: params.pairAddress,
288
+ executeMsg: { range: { withdraw: { idx: params.idx, amount: params.share } } },
289
+ funds: [],
290
+ };
291
+ }
292
+ buildClaim(params) {
293
+ assertPairAddress(params.pairAddress);
294
+ assertIdx(params.idx);
295
+ return {
296
+ contractAddress: params.pairAddress,
297
+ executeMsg: { range: { claim: { idx: params.idx } } },
298
+ funds: [],
299
+ };
300
+ }
301
+ buildTransfer(params) {
302
+ assertPairAddress(params.pairAddress);
303
+ assertIdx(params.idx);
304
+ validateThorAddress(params.to);
305
+ return {
306
+ contractAddress: params.pairAddress,
307
+ executeMsg: { range: { transfer: { idx: params.idx, to: params.to } } },
308
+ funds: [],
309
+ };
310
+ }
311
+ /**
312
+ * Atomic close: claim fees + withdraw 100%. Emits two MsgExecuteContract
313
+ * payloads that MUST be signed + broadcast in a single cosmos tx. The order
314
+ * matters — claim first, then withdraw — to guarantee fees are harvested.
315
+ */
316
+ buildWithdrawAll(params) {
317
+ assertPairAddress(params.pairAddress);
318
+ assertIdx(params.idx);
319
+ return {
320
+ msgs: [
321
+ {
322
+ contractAddress: params.pairAddress,
323
+ executeMsg: { range: { claim: { idx: params.idx } } },
324
+ funds: [],
325
+ },
326
+ {
327
+ contractAddress: params.pairAddress,
328
+ executeMsg: { range: { withdraw: { idx: params.idx, amount: '1' } } },
329
+ funds: [],
330
+ },
331
+ ],
332
+ };
333
+ }
334
+ // ------------------- Queries -------------------
335
+ /**
336
+ * List open range positions for a THORChain address.
337
+ * Returns [] when the account has no positions.
338
+ */
339
+ async getPositions(owner) {
340
+ validateThorAddress(owner);
341
+ try {
342
+ const nodeId = base64Encode(`Account:${owner}`);
343
+ const data = await gqlFetch(POSITIONS_QUERY, { id: nodeId });
344
+ const edges = data?.node?.fin?.ranges?.edges ?? [];
345
+ // Filter out partial/malformed rows before mapping — mapRange throws on missing pair.address.
346
+ return edges.filter(e => !!e.node?.pair?.address).map(e => mapRange(e.node));
347
+ }
348
+ catch (error) {
349
+ throw wrapError(error);
350
+ }
351
+ }
352
+ /**
353
+ * Fetch a single range position by (pairAddress, idx).
354
+ * Returns null when the position doesn't exist.
355
+ */
356
+ async getPosition(pairAddress, idx) {
357
+ assertPairAddress(pairAddress);
358
+ assertIdx(idx);
359
+ try {
360
+ const nodeId = base64Encode(`FinRange:${pairAddress}:${idx}`);
361
+ const data = await gqlFetch(POSITION_QUERY, { id: nodeId });
362
+ if (!data?.node || !data.node.pair?.address)
363
+ return null;
364
+ return mapRange(data.node);
365
+ }
366
+ catch (error) {
367
+ throw wrapError(error);
368
+ }
369
+ }
370
+ /**
371
+ * Resolve a FIN pair contract address from base + quote asset identifiers
372
+ * (denom or THORChain ticker). Returns null when the pair doesn't exist.
373
+ */
374
+ async getPairAddress(base, quote) {
375
+ if (!base || !quote) {
376
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, 'base and quote are required');
377
+ }
378
+ try {
379
+ const data = await gqlFetch(PAIR_QUERY, {});
380
+ const edges = data?.finV3?.pairs?.edges ?? [];
381
+ // Accept tickers, bank denoms, FIN-pair denoms ("thor.rune"), and
382
+ // LLM-mangled forms ("xruji" from "x/ruji", "thorrune" from "thor.rune")
383
+ // by normalising separators out and matching with suffix tolerance
384
+ // (normalised "thorrune" ends with normalised "rune" → match).
385
+ const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, '');
386
+ // Three-tier match: exact > prefix > suffix. Exact takes precedence
387
+ // so ambiguous inputs like "ETH" don't silently match a WETH pair
388
+ // just because `weth.endsWith("eth")`. We also reject multi-hit
389
+ // fuzzy ambiguity — if two different pairs would pass the fuzzy
390
+ // tier, the caller must disambiguate.
391
+ const matchTier = (input, candidate) => {
392
+ if (!input || !candidate)
393
+ return null;
394
+ const a = norm(input);
395
+ const b = norm(candidate);
396
+ if (a === b)
397
+ return 'exact';
398
+ if (a.endsWith(b) || b.endsWith(a))
399
+ return 'fuzzy';
400
+ return null;
401
+ };
402
+ const scoreNode = (n) => {
403
+ const bs = n.assetBase.metadata?.symbol ?? '';
404
+ const qs = n.assetQuote.metadata?.symbol ?? '';
405
+ const bd = n.assetBase.variants?.native?.denom ?? '';
406
+ const qd = n.assetQuote.variants?.native?.denom ?? '';
407
+ const baseTier = (() => {
408
+ const s = matchTier(base, bs);
409
+ const d = matchTier(base, bd);
410
+ if (s === 'exact' || d === 'exact')
411
+ return 'exact';
412
+ if (s === 'fuzzy' || d === 'fuzzy')
413
+ return 'fuzzy';
414
+ return null;
415
+ })();
416
+ const quoteTier = (() => {
417
+ const s = matchTier(quote, qs);
418
+ const d = matchTier(quote, qd);
419
+ if (s === 'exact' || d === 'exact')
420
+ return 'exact';
421
+ if (s === 'fuzzy' || d === 'fuzzy')
422
+ return 'fuzzy';
423
+ return null;
424
+ })();
425
+ if (!baseTier || !quoteTier)
426
+ return null;
427
+ // Pair is as weak as its weakest side.
428
+ return baseTier === 'exact' && quoteTier === 'exact' ? 'exact' : 'fuzzy';
429
+ };
430
+ const scored = edges.map(e => e.node).map(n => ({ node: n, tier: scoreNode(n) }));
431
+ const exact = scored.filter(s => s.tier === 'exact').map(s => s.node);
432
+ const fuzzy = scored.filter(s => s.tier === 'fuzzy').map(s => s.node);
433
+ let match;
434
+ if (exact.length === 1) {
435
+ match = exact[0];
436
+ }
437
+ else if (exact.length > 1) {
438
+ // Multiple exact-match pairs (shouldn't happen on a sane pair
439
+ // list, but reject rather than pick-first to avoid routing to
440
+ // the wrong pair).
441
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `ambiguous pair: ${base}/${quote} matches ${exact.length} pairs exactly`);
442
+ }
443
+ else if (fuzzy.length === 1) {
444
+ match = fuzzy[0];
445
+ }
446
+ else if (fuzzy.length > 1) {
447
+ throw new RujiraError(RujiraErrorCode.INVALID_PARAMS, `ambiguous pair: ${base}/${quote} matches ${fuzzy.length} pairs fuzzily — use exact denoms or tickers`);
448
+ }
449
+ if (!match)
450
+ return null;
451
+ return {
452
+ address: match.address,
453
+ base: {
454
+ symbol: match.assetBase.metadata?.symbol ?? base,
455
+ denom: match.assetBase.variants?.native?.denom ?? '',
456
+ },
457
+ quote: {
458
+ symbol: match.assetQuote.metadata?.symbol ?? quote,
459
+ denom: match.assetQuote.variants?.native?.denom ?? '',
460
+ },
461
+ };
462
+ }
463
+ catch (error) {
464
+ throw wrapError(error);
465
+ }
466
+ }
467
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@vultisig/rujira",
3
- "version": "12.0.0",
3
+ "version": "13.0.0",
4
4
  "type": "module",
5
- "description": "Rujira DEX integration for Vultisig SDK - Modular TypeScript SDK for swaps and limit orders on THORChain",
5
+ "description": "Rujira DEX SDK for Vultisig THORChain-native swaps, limit orders, staking. Built for MPC wallets and AI agents. TypeScript, modular, self-custody.",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
@@ -26,9 +26,13 @@
26
26
  "import": "./dist/modules/ghost.js",
27
27
  "types": "./dist/modules/ghost.d.ts"
28
28
  },
29
- "./perps": {
30
- "import": "./dist/modules/perps.js",
31
- "types": "./dist/modules/perps.d.ts"
29
+ "./range": {
30
+ "import": "./dist/modules/range.js",
31
+ "types": "./dist/modules/range.d.ts"
32
+ },
33
+ "./ccl": {
34
+ "import": "./dist/ccl/index.js",
35
+ "types": "./dist/ccl/index.d.ts"
32
36
  },
33
37
  "./signer": {
34
38
  "import": "./dist/signer/vultisig-provider.js",
@@ -67,12 +71,12 @@
67
71
  "devDependencies": {
68
72
  "@types/big.js": "^6.2.2",
69
73
  "@types/node": "^25.5.0",
70
- "@vultisig/sdk": "0.17.0",
74
+ "@vultisig/sdk": "0.18.0",
71
75
  "typescript": "^5.9.3",
72
76
  "vitest": "^3.0.9"
73
77
  },
74
78
  "peerDependencies": {
75
- "@vultisig/sdk": ">=0.17.0"
79
+ "@vultisig/sdk": ">=0.18.0"
76
80
  },
77
81
  "peerDependenciesMeta": {
78
82
  "@vultisig/sdk": {
@@ -83,10 +87,21 @@
83
87
  "vultisig",
84
88
  "rujira",
85
89
  "thorchain",
90
+ "thorchain-swap",
91
+ "thorchain-dex",
92
+ "limit-order",
93
+ "cross-chain-swap",
94
+ "rune",
95
+ "cosmwasm",
96
+ "cosmos-sdk",
97
+ "defi-sdk",
98
+ "dex-sdk",
99
+ "mpc-wallet-sdk",
100
+ "ai-agent-dex",
101
+ "typescript-dex",
102
+ "defi",
86
103
  "dex",
87
104
  "swap",
88
- "defi",
89
- "cosmwasm",
90
105
  "cosmos",
91
106
  "crypto",
92
107
  "wallet"
@@ -1,127 +0,0 @@
1
- /**
2
- * Perpetual futures module for Levana perps on Rujira
3
- * @module modules/perps
4
- */
5
- import type { Coin } from '@cosmjs/proto-signing';
6
- import type { RujiraClient } from '../client.js';
7
- export type PerpsMarket = {
8
- /** Market contract address */
9
- address: string;
10
- /** Market name (e.g. 'BTC_USDC') */
11
- name: string;
12
- /** Base asset (e.g. 'BTC.BTC') */
13
- baseAsset: string;
14
- /** Quote asset (e.g. 'ETH-USDC-...') */
15
- quoteAsset: string;
16
- };
17
- export type PerpsTransactionParams = {
18
- contractAddress: string;
19
- executeMsg: object;
20
- funds: Coin[];
21
- };
22
- /**
23
- * Levana perpetual futures module.
24
- *
25
- * @example
26
- * ```typescript
27
- * const client = new RujiraClient();
28
- * await client.connect();
29
- *
30
- * // List markets
31
- * const markets = await client.perps.getMarkets();
32
- *
33
- * // Build open position
34
- * const tx = client.perps.buildOpenPosition({
35
- * market: 'thor1cyd6...',
36
- * direction: 'long',
37
- * leverage: '10',
38
- * collateralDenom: 'eth-usdc-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
39
- * collateralAmount: '100000000',
40
- * });
41
- * ```
42
- */
43
- export declare class RujiraPerps {
44
- private readonly client;
45
- constructor(client: RujiraClient);
46
- /**
47
- * Get available perps markets from GraphQL.
48
- */
49
- getMarkets(): Promise<PerpsMarket[]>;
50
- /**
51
- * Query market status from the Levana contract.
52
- */
53
- getMarketStatus(marketAddress: string): Promise<Record<string, unknown>>;
54
- /**
55
- * Query positions for an owner on a market.
56
- */
57
- getPositions(marketAddress: string, owner: string): Promise<Record<string, unknown>>;
58
- /**
59
- * Query limit orders for an owner on a market.
60
- */
61
- getLimitOrders(marketAddress: string, owner: string): Promise<Record<string, unknown>>;
62
- /**
63
- * Build open position transaction.
64
- */
65
- buildOpenPosition(params: {
66
- market: string;
67
- direction: 'long' | 'short';
68
- leverage: string;
69
- collateralDenom: string;
70
- collateralAmount: string;
71
- takeProfit?: string;
72
- stopLoss?: string;
73
- }): PerpsTransactionParams;
74
- /**
75
- * Build close position transaction.
76
- */
77
- buildClosePosition(params: {
78
- market: string;
79
- positionId: string;
80
- }): PerpsTransactionParams;
81
- /**
82
- * Build update take profit transaction.
83
- */
84
- buildUpdateTakeProfit(params: {
85
- market: string;
86
- positionId: string;
87
- price: string;
88
- }): PerpsTransactionParams;
89
- /**
90
- * Build update stop loss transaction.
91
- */
92
- buildUpdateStopLoss(params: {
93
- market: string;
94
- positionId: string;
95
- stopLoss: string | 'remove';
96
- }): PerpsTransactionParams;
97
- /**
98
- * Build add collateral transaction.
99
- */
100
- buildAddCollateral(params: {
101
- market: string;
102
- positionId: string;
103
- denom: string;
104
- amount: string;
105
- }): PerpsTransactionParams;
106
- /**
107
- * Build place limit order transaction.
108
- */
109
- buildPlaceLimitOrder(params: {
110
- market: string;
111
- direction: 'long' | 'short';
112
- leverage: string;
113
- triggerPrice: string;
114
- collateralDenom: string;
115
- collateralAmount: string;
116
- takeProfit?: string;
117
- stopLoss?: string;
118
- }): PerpsTransactionParams;
119
- /**
120
- * Build cancel limit order transaction.
121
- */
122
- buildCancelLimitOrder(params: {
123
- market: string;
124
- orderId: string;
125
- }): PerpsTransactionParams;
126
- }
127
- //# sourceMappingURL=perps.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"perps.d.ts","sourceRoot":"","sources":["../../src/modules/perps.ts"],"names":[],"mappings":"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"}