@rhinestone/sdk 2.8.0 → 2.10.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.
- package/dist/src/api/account.d.ts +2 -2
- package/dist/src/api/account.d.ts.map +1 -1
- package/dist/src/api/account.js +11 -2
- package/dist/src/api/compose.d.ts.map +1 -1
- package/dist/src/api/compose.js +3 -4
- package/dist/src/api/sdk.d.ts +1 -0
- package/dist/src/api/sdk.d.ts.map +1 -1
- package/dist/src/api/sdk.js +2 -0
- package/dist/src/clients/orchestrator/client.js +1 -1
- package/dist/src/clients/orchestrator/mappers.d.ts.map +1 -1
- package/dist/src/clients/orchestrator/mappers.js +54 -22
- package/dist/src/clients/orchestrator/public.d.ts +154 -2
- package/dist/src/clients/orchestrator/public.d.ts.map +1 -1
- package/dist/src/clients/orchestrator/types.d.ts +5 -1
- package/dist/src/clients/orchestrator/types.d.ts.map +1 -1
- package/dist/src/clients/orchestrator/wire.d.ts +10 -1
- package/dist/src/clients/orchestrator/wire.d.ts.map +1 -1
- package/dist/src/clients/orchestrator/wire.gen.d.ts +416 -44
- package/dist/src/clients/orchestrator/wire.gen.d.ts.map +1 -1
- package/dist/src/config/account.d.ts +19 -1
- package/dist/src/config/account.d.ts.map +1 -1
- package/dist/src/config/input.d.ts +2 -0
- package/dist/src/config/input.d.ts.map +1 -1
- package/dist/src/config/legacy.d.ts +2 -0
- package/dist/src/config/legacy.d.ts.map +1 -1
- package/dist/src/config/legacy.js +2 -0
- package/dist/src/config/resolve.d.ts.map +1 -1
- package/dist/src/config/resolve.js +4 -0
- package/dist/src/config/resolved.d.ts +2 -0
- package/dist/src/config/resolved.d.ts.map +1 -1
- package/dist/src/errors/index.d.ts +2 -1
- package/dist/src/errors/index.d.ts.map +1 -1
- package/dist/src/errors/index.js +4 -1
- package/dist/src/hypercore/errors.d.ts +37 -0
- package/dist/src/hypercore/errors.d.ts.map +1 -0
- package/dist/src/hypercore/errors.js +54 -0
- package/dist/src/hypercore/index.d.ts +4 -0
- package/dist/src/hypercore/index.d.ts.map +1 -0
- package/dist/src/hypercore/index.js +2 -0
- package/dist/src/hypercore/market.d.ts +110 -0
- package/dist/src/hypercore/market.d.ts.map +1 -0
- package/dist/src/hypercore/market.js +120 -0
- package/dist/src/hypercore/orders.d.ts +26 -0
- package/dist/src/hypercore/orders.d.ts.map +1 -0
- package/dist/src/hypercore/orders.js +148 -0
- package/dist/src/hypercore/resolve.d.ts +10 -0
- package/dist/src/hypercore/resolve.d.ts.map +1 -0
- package/dist/src/hypercore/resolve.js +30 -0
- package/dist/src/hypercore/types.d.ts +80 -0
- package/dist/src/hypercore/types.d.ts.map +1 -0
- package/dist/src/hypercore/types.js +8 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/signing/typed-data.d.ts +6 -0
- package/dist/src/signing/typed-data.d.ts.map +1 -1
- package/dist/src/signing/typed-data.js +13 -0
- package/dist/src/transactions/intents/origin-chain.d.ts +35 -0
- package/dist/src/transactions/intents/origin-chain.d.ts.map +1 -0
- package/dist/src/transactions/intents/origin-chain.js +89 -0
- package/dist/src/transactions/intents/prepare.d.ts.map +1 -1
- package/dist/src/transactions/intents/prepare.js +2 -1
- package/dist/src/transactions/intents/session-signing.d.ts.map +1 -1
- package/dist/src/transactions/intents/session-signing.js +8 -0
- package/dist/src/transactions/intents/sign-transaction.d.ts.map +1 -1
- package/dist/src/transactions/intents/sign-transaction.js +9 -1
- package/package.json +5 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Reads of Hyperliquid's public `info` endpoint — the market metadata an order
|
|
2
|
+
// needs and cannot be derived: the asset INDEX an order carries, the size
|
|
3
|
+
// precision it must round to, and the mark to price against.
|
|
4
|
+
//
|
|
5
|
+
// `prepareTransaction` does these reads itself when a transaction carries
|
|
6
|
+
// `hyperCore.openPerp` or `hyperCore.closePerp`. What is exported here is for the
|
|
7
|
+
// questions a caller asks BEFORE sending one: which markets exist, what leverage
|
|
8
|
+
// one allows, whether there is a position to close at all.
|
|
9
|
+
import { HyperCoreInfoRequestError, UnknownPerpAssetError } from './errors.js';
|
|
10
|
+
/** Hyperliquid's mainnet API. */
|
|
11
|
+
const HYPERLIQUID_API_URL = 'https://api.hyperliquid.xyz';
|
|
12
|
+
async function readInfo(body, options) {
|
|
13
|
+
const url = `${options?.apiUrl ?? HYPERLIQUID_API_URL}/info`;
|
|
14
|
+
const request = options?.fetch ?? globalThis.fetch;
|
|
15
|
+
const response = await request(url, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'content-type': 'application/json' },
|
|
18
|
+
body: JSON.stringify(body),
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
const text = await response.text().catch(() => '');
|
|
22
|
+
throw new HyperCoreInfoRequestError(url, response.status, text);
|
|
23
|
+
}
|
|
24
|
+
return (await response.json());
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* List every tradeable Hyperliquid perpetual market.
|
|
28
|
+
*
|
|
29
|
+
* Delisted assets are left out, but the index of the ones returned is their
|
|
30
|
+
* position in the full universe — Hyperliquid never renumbers it, and it is
|
|
31
|
+
* what an order carries.
|
|
32
|
+
*
|
|
33
|
+
* @param options where to reach Hyperliquid's info endpoint
|
|
34
|
+
* @returns one entry per tradeable perp market, with a live mark price
|
|
35
|
+
* @example
|
|
36
|
+
* const markets = await getPerpMarkets()
|
|
37
|
+
* markets.map((market) => market.asset) // ['BTC', 'ETH', ...]
|
|
38
|
+
* @see {@link getPerpMarket}
|
|
39
|
+
*/
|
|
40
|
+
async function getPerpMarkets(options) {
|
|
41
|
+
const [meta, contexts] = await readInfo({ type: 'metaAndAssetCtxs' }, options);
|
|
42
|
+
return meta.universe
|
|
43
|
+
.map((entry, assetIndex) => ({ entry, assetIndex }))
|
|
44
|
+
.filter(({ entry }) => !entry.isDelisted)
|
|
45
|
+
.map(({ entry, assetIndex }) => ({
|
|
46
|
+
asset: entry.name,
|
|
47
|
+
assetIndex,
|
|
48
|
+
szDecimals: entry.szDecimals,
|
|
49
|
+
maxLeverage: entry.maxLeverage,
|
|
50
|
+
markPx: contexts[assetIndex]?.markPx ?? '',
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Read one Hyperliquid perpetual market by ticker.
|
|
55
|
+
*
|
|
56
|
+
* A transaction carrying `hyperCore.openPerp` does this read itself, so reach
|
|
57
|
+
* for this when you need the market's own facts — its mark to quote a size
|
|
58
|
+
* against, or its `maxLeverage` before offering one.
|
|
59
|
+
*
|
|
60
|
+
* @param asset ticker as Hyperliquid names it — the coin alone, e.g. `BTC`
|
|
61
|
+
* @param options where to reach Hyperliquid's info endpoint
|
|
62
|
+
* @returns the market, including its asset index, size precision, and mark
|
|
63
|
+
* @throws {UnknownPerpAssetError} if no tradeable market carries that ticker
|
|
64
|
+
* @example
|
|
65
|
+
* const market = await getPerpMarket('BTC')
|
|
66
|
+
* const maxNotional = Number(market.markPx) * market.maxLeverage
|
|
67
|
+
* @see {@link getPerpMarkets}
|
|
68
|
+
*/
|
|
69
|
+
async function getPerpMarket(asset, options) {
|
|
70
|
+
const markets = await getPerpMarkets(options);
|
|
71
|
+
const market = markets.find((entry) => entry.asset === asset);
|
|
72
|
+
if (!market)
|
|
73
|
+
throw new UnknownPerpAssetError(asset);
|
|
74
|
+
return market;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* List an account's open Hyperliquid perpetual positions.
|
|
78
|
+
*
|
|
79
|
+
* @param account the account holding the positions — the smart account itself,
|
|
80
|
+
* since that is what the intent delivers collateral to
|
|
81
|
+
* @param options where to reach Hyperliquid's info endpoint
|
|
82
|
+
* @returns one entry per open position; empty when the account holds none
|
|
83
|
+
* @see {@link getPerpPosition}
|
|
84
|
+
*/
|
|
85
|
+
async function getPerpPositions(account, options) {
|
|
86
|
+
const state = await readInfo({ type: 'clearinghouseState', user: account }, options);
|
|
87
|
+
return (state.assetPositions ?? []).flatMap(({ position }) => position?.coin && position.szi
|
|
88
|
+
? [
|
|
89
|
+
{
|
|
90
|
+
asset: position.coin,
|
|
91
|
+
size: position.szi,
|
|
92
|
+
entryPx: position.entryPx ?? '0',
|
|
93
|
+
leverage: position.leverage?.value ?? 1,
|
|
94
|
+
},
|
|
95
|
+
]
|
|
96
|
+
: []);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Read an account's open position on one Hyperliquid perpetual market.
|
|
100
|
+
*
|
|
101
|
+
* A transaction carrying `hyperCore.closePerp` does this read itself and is
|
|
102
|
+
* rejected when there is nothing to close, so this is the check to make before
|
|
103
|
+
* offering a close at all.
|
|
104
|
+
*
|
|
105
|
+
* @param account the account holding the position
|
|
106
|
+
* @param asset ticker as Hyperliquid names it, e.g. `BTC`
|
|
107
|
+
* @param options where to reach Hyperliquid's info endpoint
|
|
108
|
+
* @returns the position, or `null` when the account has none open on that asset
|
|
109
|
+
* @example
|
|
110
|
+
* const position = await getPerpPosition(account.getAddress(), 'BTC')
|
|
111
|
+
* if (position) {
|
|
112
|
+
* // offer a close
|
|
113
|
+
* }
|
|
114
|
+
* @see {@link getPerpPositions}
|
|
115
|
+
*/
|
|
116
|
+
async function getPerpPosition(account, asset, options) {
|
|
117
|
+
const positions = await getPerpPositions(account, options);
|
|
118
|
+
return positions.find((position) => position.asset === asset) ?? null;
|
|
119
|
+
}
|
|
120
|
+
export { getPerpMarket, getPerpMarkets, getPerpPosition, getPerpPositions };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HyperCoreOrderAction } from '../clients/orchestrator/public.js';
|
|
2
|
+
import type { PerpMarket, PerpPosition } from './market.js';
|
|
3
|
+
import type { ClosePerpRequest, OpenPerpRequest } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* A marketable IOC that opens a position: it fills against the book at up to
|
|
6
|
+
* `slippageBps` through the mark and cancels the rest, which is how a market
|
|
7
|
+
* order is expressed on Hyperliquid.
|
|
8
|
+
*/
|
|
9
|
+
declare function buildOpenPerpOrder(market: PerpMarket, request: OpenPerpRequest): HyperCoreOrderAction;
|
|
10
|
+
/**
|
|
11
|
+
* A reduce-only marketable IOC in the direction that flattens the position,
|
|
12
|
+
* sized from the position itself.
|
|
13
|
+
*/
|
|
14
|
+
declare function buildClosePerpOrder(market: PerpMarket, position: PerpPosition, request: ClosePerpRequest): HyperCoreOrderAction;
|
|
15
|
+
/**
|
|
16
|
+
* Round a price onto Hyperliquid's grid: at most 5 significant figures, and at
|
|
17
|
+
* most `6 - szDecimals` decimals.
|
|
18
|
+
*
|
|
19
|
+
* Rounded in the direction that keeps the order marketable — up for a buy, down
|
|
20
|
+
* for a sell — so the grid never eats the slippage it was given.
|
|
21
|
+
*/
|
|
22
|
+
declare function formatPerpPrice(price: number, szDecimals: number, roundUp: boolean): string;
|
|
23
|
+
/** Round a size down onto the asset's size grid — never up past what was asked. */
|
|
24
|
+
declare function formatPerpSize(size: number, szDecimals: number): string;
|
|
25
|
+
export { buildClosePerpOrder, buildOpenPerpOrder, formatPerpPrice, formatPerpSize, };
|
|
26
|
+
//# sourceMappingURL=orders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orders.d.ts","sourceRoot":"","sources":["../../../hypercore/orders.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAEV,oBAAoB,EACrB,MAAM,gCAAgC,CAAA;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAkBhE;;;;GAIG;AACH,iBAAS,kBAAkB,CACzB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,eAAe,GACvB,oBAAoB,CAetB;AAED;;;GAGG;AACH,iBAAS,mBAAmB,CAC1B,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,gBAAgB,GACxB,oBAAoB,CAmBtB;AAwED;;;;;;GAMG;AACH,iBAAS,eAAe,CACtB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,GACf,MAAM,CAUR;AAED,mFAAmF;AACnF,iBAAS,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhE;AA4CD,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,cAAc,GACf,CAAA"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Hyperliquid's order arithmetic, and nothing else. Pure: everything these need
|
|
2
|
+
// is an argument, and `resolve.ts` is what reads it from Hyperliquid.
|
|
3
|
+
//
|
|
4
|
+
// What they own is the part a caller should never have to reconstruct — the
|
|
5
|
+
// asset INDEX an order carries instead of a ticker, the tick and size grids a
|
|
6
|
+
// price and size must land on, and a limit priced far enough through the book to
|
|
7
|
+
// still cross when the intent delivers ~30s later.
|
|
8
|
+
import { HyperCoreError, PerpOrderTooSmallError } from './errors.js';
|
|
9
|
+
/** How far through the mark a marketable limit is placed, when not given. */
|
|
10
|
+
const DEFAULT_SLIPPAGE_BPS = 50;
|
|
11
|
+
/**
|
|
12
|
+
* Hyperliquid's cap on a perp price's significant figures. Integer prices are
|
|
13
|
+
* exempt from it, which we do not exploit — five figures is always accepted,
|
|
14
|
+
* and at any price where the difference shows it is far inside the slippage.
|
|
15
|
+
*/
|
|
16
|
+
const MAX_PRICE_SIGNIFICANT_FIGURES = 5;
|
|
17
|
+
/** A perp price may carry `6 - szDecimals` decimals. */
|
|
18
|
+
const PERP_PRICE_DECIMAL_BUDGET = 6;
|
|
19
|
+
/** Hyperliquid refuses an order worth less than this. */
|
|
20
|
+
const MIN_ORDER_VALUE_USD = 10;
|
|
21
|
+
/**
|
|
22
|
+
* A marketable IOC that opens a position: it fills against the book at up to
|
|
23
|
+
* `slippageBps` through the mark and cancels the rest, which is how a market
|
|
24
|
+
* order is expressed on Hyperliquid.
|
|
25
|
+
*/
|
|
26
|
+
function buildOpenPerpOrder(market, request) {
|
|
27
|
+
const mark = markPrice(market);
|
|
28
|
+
const size = request.size === undefined
|
|
29
|
+
? request.notionalUsd / mark
|
|
30
|
+
: Number(request.size);
|
|
31
|
+
return orderAction({
|
|
32
|
+
market,
|
|
33
|
+
mark,
|
|
34
|
+
isBuy: request.direction === 'long',
|
|
35
|
+
size,
|
|
36
|
+
reduceOnly: false,
|
|
37
|
+
slippageBps: request.slippageBps,
|
|
38
|
+
...(request.cloid ? { cloid: request.cloid } : {}),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A reduce-only marketable IOC in the direction that flattens the position,
|
|
43
|
+
* sized from the position itself.
|
|
44
|
+
*/
|
|
45
|
+
function buildClosePerpOrder(market, position, request) {
|
|
46
|
+
const held = Number(position.size);
|
|
47
|
+
const open = Math.abs(held);
|
|
48
|
+
const size = request.size === undefined ? open : Number(request.size);
|
|
49
|
+
if (size > open) {
|
|
50
|
+
throw new HyperCoreError(`Cannot close ${size} ${request.asset} against an open position of ${open}. Hyperliquid rejects a reduce-only order larger than the position it reduces.`);
|
|
51
|
+
}
|
|
52
|
+
return orderAction({
|
|
53
|
+
market,
|
|
54
|
+
mark: markPrice(market),
|
|
55
|
+
// Flatten: sell a long, buy back a short.
|
|
56
|
+
isBuy: held < 0,
|
|
57
|
+
size,
|
|
58
|
+
reduceOnly: true,
|
|
59
|
+
slippageBps: request.slippageBps,
|
|
60
|
+
...(request.cloid ? { cloid: request.cloid } : {}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function orderAction(input) {
|
|
64
|
+
const { market, mark, isBuy, reduceOnly } = input;
|
|
65
|
+
const slippageBps = input.slippageBps ?? DEFAULT_SLIPPAGE_BPS;
|
|
66
|
+
if (!Number.isInteger(slippageBps) ||
|
|
67
|
+
slippageBps < 0 ||
|
|
68
|
+
slippageBps > 10_000) {
|
|
69
|
+
throw new HyperCoreError(`slippageBps must be a whole number of basis points between 0 and 10000, got ${input.slippageBps}.`);
|
|
70
|
+
}
|
|
71
|
+
if (!Number.isFinite(input.size)) {
|
|
72
|
+
throw new HyperCoreError(`Order size for ${market.asset} is not a number: ${input.size}.`);
|
|
73
|
+
}
|
|
74
|
+
const limit = mark * (isBuy ? 1 + slippageBps / 10_000 : 1 - slippageBps / 10_000);
|
|
75
|
+
const p = formatPerpPrice(limit, market.szDecimals, isBuy);
|
|
76
|
+
const s = formatPerpSize(input.size, market.szDecimals);
|
|
77
|
+
if (Number(s) <= 0) {
|
|
78
|
+
throw new HyperCoreError(`An order size of ${input.size} rounds to zero at ${market.asset}'s ${market.szDecimals} decimals of size precision.`);
|
|
79
|
+
}
|
|
80
|
+
// Reduce-only orders are exempt from the minimum on Hyperliquid's side, and
|
|
81
|
+
// enforcing it here would leave a dust position with no way to close it.
|
|
82
|
+
const notional = Number(s) * mark;
|
|
83
|
+
if (!reduceOnly && notional < MIN_ORDER_VALUE_USD) {
|
|
84
|
+
throw new PerpOrderTooSmallError(market.asset, s, Math.round(notional * 100) / 100, MIN_ORDER_VALUE_USD);
|
|
85
|
+
}
|
|
86
|
+
const order = {
|
|
87
|
+
a: market.assetIndex,
|
|
88
|
+
b: isBuy,
|
|
89
|
+
p,
|
|
90
|
+
s,
|
|
91
|
+
r: reduceOnly,
|
|
92
|
+
t: { limit: { tif: 'Ioc' } },
|
|
93
|
+
...(input.cloid ? { c: input.cloid } : {}),
|
|
94
|
+
};
|
|
95
|
+
return { type: 'order', orders: [order], grouping: 'na' };
|
|
96
|
+
}
|
|
97
|
+
function markPrice(market) {
|
|
98
|
+
const mark = Number(market.markPx);
|
|
99
|
+
if (!Number.isFinite(mark) || mark <= 0) {
|
|
100
|
+
throw new HyperCoreError(`Hyperliquid reported no usable mark price for ${market.asset} (got ${JSON.stringify(market.markPx)}), so an order cannot be priced against it.`);
|
|
101
|
+
}
|
|
102
|
+
return mark;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Round a price onto Hyperliquid's grid: at most 5 significant figures, and at
|
|
106
|
+
* most `6 - szDecimals` decimals.
|
|
107
|
+
*
|
|
108
|
+
* Rounded in the direction that keeps the order marketable — up for a buy, down
|
|
109
|
+
* for a sell — so the grid never eats the slippage it was given.
|
|
110
|
+
*/
|
|
111
|
+
function formatPerpPrice(price, szDecimals, roundUp) {
|
|
112
|
+
const decimals = Math.max(0, PERP_PRICE_DECIMAL_BUDGET - szDecimals);
|
|
113
|
+
const significant = roundSignificant(price, MAX_PRICE_SIGNIFICANT_FIGURES, roundUp);
|
|
114
|
+
return trimDecimal(roundToGrid(significant, decimals, roundUp).toFixed(decimals));
|
|
115
|
+
}
|
|
116
|
+
/** Round a size down onto the asset's size grid — never up past what was asked. */
|
|
117
|
+
function formatPerpSize(size, szDecimals) {
|
|
118
|
+
return trimDecimal(roundToGrid(size, szDecimals, false).toFixed(szDecimals));
|
|
119
|
+
}
|
|
120
|
+
function roundSignificant(value, digits, roundUp) {
|
|
121
|
+
if (value === 0)
|
|
122
|
+
return 0;
|
|
123
|
+
const magnitude = Math.floor(Math.log10(Math.abs(value)));
|
|
124
|
+
return roundToFactor(value, 10 ** (digits - 1 - magnitude), roundUp);
|
|
125
|
+
}
|
|
126
|
+
function roundToGrid(value, decimals, roundUp) {
|
|
127
|
+
return roundToFactor(value, 10 ** decimals, roundUp);
|
|
128
|
+
}
|
|
129
|
+
function roundToFactor(value, factor, roundUp) {
|
|
130
|
+
// Binary floats leave dust in the last ulp, and `ceil`/`floor` would turn it
|
|
131
|
+
// into a whole extra tick. 15 digits is the widest a double carries exactly,
|
|
132
|
+
// so it clears the dust without rounding away a digit the caller meant.
|
|
133
|
+
const scaled = Number((value * factor).toPrecision(15));
|
|
134
|
+
return (roundUp ? Math.ceil(scaled) : Math.floor(scaled)) / factor;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Hyperliquid normalises a decimal before encoding it, so `"1.50"` and `"1.5"`
|
|
138
|
+
* are one price with two spellings. Emitting the normalised one keeps the
|
|
139
|
+
* string a caller reads in the action identical to the one that gets hashed
|
|
140
|
+
* into the agent authorising it.
|
|
141
|
+
*/
|
|
142
|
+
function trimDecimal(value) {
|
|
143
|
+
if (!value.includes('.'))
|
|
144
|
+
return value;
|
|
145
|
+
const trimmed = value.replace(/0+$/, '').replace(/\.$/, '');
|
|
146
|
+
return trimmed === '' || trimmed === '-' ? '0' : trimmed;
|
|
147
|
+
}
|
|
148
|
+
export { buildClosePerpOrder, buildOpenPerpOrder, formatPerpPrice, formatPerpSize, };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Address } from 'viem';
|
|
2
|
+
import type { HyperCoreAction } from '../clients/orchestrator/public.js';
|
|
3
|
+
import { type HyperliquidConfig } from './market.js';
|
|
4
|
+
import type { HyperCoreOptions } from './types.js';
|
|
5
|
+
export declare function resolveHyperCoreAction(input: {
|
|
6
|
+
readonly options: HyperCoreOptions | undefined;
|
|
7
|
+
readonly account: Address;
|
|
8
|
+
readonly hyperliquid?: HyperliquidConfig;
|
|
9
|
+
}): Promise<HyperCoreAction | undefined>;
|
|
10
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../hypercore/resolve.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAErE,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,UAAU,CAAA;AAEjB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,wBAAsB,sBAAsB,CAAC,KAAK,EAAE;IAClD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAA;IAC9C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAA;CACzC,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAsBvC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Turns the declarative `hyperCore` on a transaction into the concrete action
|
|
2
|
+
// the orchestrator quotes against.
|
|
3
|
+
//
|
|
4
|
+
// This runs inside `prepareTransaction`, before the quote, because that is the
|
|
5
|
+
// last moment it can: the agent authorising the action is derived from the
|
|
6
|
+
// action's own bytes, and the quote's `signData` carries a registration for that
|
|
7
|
+
// agent. Nothing about the action — the price included — can be chosen after.
|
|
8
|
+
import { NoOpenPerpPositionError } from './errors.js';
|
|
9
|
+
import { getPerpMarket, getPerpPosition, } from './market.js';
|
|
10
|
+
import { buildClosePerpOrder, buildOpenPerpOrder } from './orders.js';
|
|
11
|
+
export async function resolveHyperCoreAction(input) {
|
|
12
|
+
const { options } = input;
|
|
13
|
+
if (!options)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (options.action)
|
|
16
|
+
return options.action;
|
|
17
|
+
if (options.openPerp) {
|
|
18
|
+
const market = await getPerpMarket(options.openPerp.asset, input.hyperliquid);
|
|
19
|
+
return buildOpenPerpOrder(market, options.openPerp);
|
|
20
|
+
}
|
|
21
|
+
const request = options.closePerp;
|
|
22
|
+
const [market, position] = await Promise.all([
|
|
23
|
+
getPerpMarket(request.asset, input.hyperliquid),
|
|
24
|
+
getPerpPosition(input.account, request.asset, input.hyperliquid),
|
|
25
|
+
]);
|
|
26
|
+
if (!position) {
|
|
27
|
+
throw new NoOpenPerpPositionError(request.asset, input.account);
|
|
28
|
+
}
|
|
29
|
+
return buildClosePerpOrder(market, position, request);
|
|
30
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Hex } from 'viem';
|
|
2
|
+
import type { HyperCoreAction } from '../clients/orchestrator/public.js';
|
|
3
|
+
/** Size, either as a USD notional to convert or in units of the asset. */
|
|
4
|
+
type PerpOrderSize = {
|
|
5
|
+
/**
|
|
6
|
+
* Position size in USD, converted at the market's mark and rounded DOWN
|
|
7
|
+
* to the asset's size precision, so the result never exceeds what you
|
|
8
|
+
* asked for.
|
|
9
|
+
*
|
|
10
|
+
* This is the position's notional, not the collateral behind it — at 5x
|
|
11
|
+
* leverage a $25 position needs $5 of margin, and it is the margin the
|
|
12
|
+
* transaction's `tokenRequests` deliver.
|
|
13
|
+
*/
|
|
14
|
+
notionalUsd: number;
|
|
15
|
+
size?: never;
|
|
16
|
+
} | {
|
|
17
|
+
/** Position size in units of the asset, e.g. `'0.0002'` BTC. */
|
|
18
|
+
size: string;
|
|
19
|
+
notionalUsd?: never;
|
|
20
|
+
};
|
|
21
|
+
/** Open a perpetual position as part of this transaction. */
|
|
22
|
+
type OpenPerpRequest = PerpOrderSize & {
|
|
23
|
+
/** Ticker as Hyperliquid names it — the coin alone, e.g. `BTC`. */
|
|
24
|
+
asset: string;
|
|
25
|
+
direction: 'long' | 'short';
|
|
26
|
+
/**
|
|
27
|
+
* How far through the mark to place the limit, in basis points. Defaults to
|
|
28
|
+
* 50 (0.5%).
|
|
29
|
+
*
|
|
30
|
+
* The price is fixed when the transaction is signed but the order is not
|
|
31
|
+
* placed until the collateral has bridged, so this is the room the book is
|
|
32
|
+
* allowed to move in between. Too tight and the order is refused with the
|
|
33
|
+
* funds already on HyperCore.
|
|
34
|
+
*/
|
|
35
|
+
slippageBps?: number;
|
|
36
|
+
/** Optional client order id — 128-bit hex — echoed back by the exchange. */
|
|
37
|
+
cloid?: Hex;
|
|
38
|
+
};
|
|
39
|
+
/** Close an open perpetual position as part of this transaction. */
|
|
40
|
+
interface ClosePerpRequest {
|
|
41
|
+
/** Ticker as Hyperliquid names it — the coin alone, e.g. `BTC`. */
|
|
42
|
+
asset: string;
|
|
43
|
+
/** Close only part of the position, in units of the asset. */
|
|
44
|
+
size?: string;
|
|
45
|
+
/** How far through the mark to place the limit, in basis points. */
|
|
46
|
+
slippageBps?: number;
|
|
47
|
+
/** Optional client order id — 128-bit hex — echoed back by the exchange. */
|
|
48
|
+
cloid?: Hex;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* What this transaction does on HyperCore. Exactly one of the three.
|
|
52
|
+
*
|
|
53
|
+
* `openPerp` and `closePerp` are resolved for you: the asset index, the price
|
|
54
|
+
* and size grids, and the mark to price against are all read from Hyperliquid
|
|
55
|
+
* while the transaction is prepared. `action` is the escape hatch — a
|
|
56
|
+
* Hyperliquid L1 action passed through exactly as you wrote it, for the cases
|
|
57
|
+
* the two above do not cover.
|
|
58
|
+
*
|
|
59
|
+
* An action that needs collateral (opening a position) must be paired with
|
|
60
|
+
* `tokenRequests` that deliver it; one that does not (a close, a cancel, a
|
|
61
|
+
* leverage change) rides a transaction that requests no tokens.
|
|
62
|
+
*
|
|
63
|
+
* One per transaction, whichever form: an agent authorises exactly one action,
|
|
64
|
+
* and registering a second evicts the first.
|
|
65
|
+
*/
|
|
66
|
+
type HyperCoreOptions = {
|
|
67
|
+
openPerp: OpenPerpRequest;
|
|
68
|
+
closePerp?: never;
|
|
69
|
+
action?: never;
|
|
70
|
+
} | {
|
|
71
|
+
closePerp: ClosePerpRequest;
|
|
72
|
+
openPerp?: never;
|
|
73
|
+
action?: never;
|
|
74
|
+
} | {
|
|
75
|
+
action: HyperCoreAction;
|
|
76
|
+
openPerp?: never;
|
|
77
|
+
closePerp?: never;
|
|
78
|
+
};
|
|
79
|
+
export type { ClosePerpRequest, HyperCoreOptions, OpenPerpRequest, PerpOrderSize, };
|
|
80
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../hypercore/types.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAErE,0EAA0E;AAC1E,KAAK,aAAa,GACd;IACE;;;;;;;;OAQG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,KAAK,CAAA;CACb,GACD;IACE,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,KAAK,CAAA;CACpB,CAAA;AAEL,6DAA6D;AAC7D,KAAK,eAAe,GAAG,aAAa,GAAG;IACrC,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,GAAG,CAAA;CACZ,CAAA;AAED,oEAAoE;AACpE,UAAU,gBAAgB;IACxB,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,GAAG,CAAA;CACZ;AAED;;;;;;;;;;;;;;;GAeG;AACH,KAAK,gBAAgB,GACjB;IAAE,QAAQ,EAAE,eAAe,CAAC;IAAC,SAAS,CAAC,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAChE;IAAE,SAAS,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GACjE;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAC,SAAS,CAAC,EAAE,KAAK,CAAA;CAAE,CAAA;AAEpE,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,aAAa,GACd,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The HyperCore surface of a transaction: what you want done on Hyperliquid,
|
|
2
|
+
// stated as data rather than built by hand.
|
|
3
|
+
//
|
|
4
|
+
// `openPerp` and `closePerp` are DECLARATIVE — `prepareTransaction` resolves
|
|
5
|
+
// them into a concrete action before it quotes, because that is when the action
|
|
6
|
+
// has to exist: the agent authorising it is derived from its bytes, and the
|
|
7
|
+
// signature covers a registration carrying that agent's address.
|
|
8
|
+
export {};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ import { WEBAUTHN_VALIDATOR_ADDRESS } from './modules/validators/webauthn.js';
|
|
|
7
7
|
export { RhinestoneSDK, hyperCorePerp, hyperCoreSpot, solanaMainnet, stellarMainnet, tronMainnet, OWNABLE_VALIDATOR_ADDRESS, WEBAUTHN_VALIDATOR_ADDRESS, MULTI_FACTOR_VALIDATOR_ADDRESS, MULTI_FACTOR_VALIDATOR_V2_ADDRESS, SMART_SESSION_EMISSARY_ADDRESS, };
|
|
8
8
|
export type { RhinestoneAccount, SignedIntentData } from './api/account.js';
|
|
9
9
|
export type { DestinationChain, NonEvmAddress, NonEvmChain, } from './chains/non-evm.js';
|
|
10
|
-
export type { AppFeeBalances, AppFeeRate, ApprovalRequired, AuxiliaryFunds, BridgeFill, ChainOperation, FailureReason, IntentInput, IntentOpStatus, OperationStatus, OriginSignature, Portfolio, ProtocolFeeRate, Quote, SerializedIntentInput, SettlementLayer, SettlementLayerFilter, SignData, SplitIntentsInput, SplitIntentsResult, SwapQuoter, SwapQuoterFilter, TokenRequirements, WrapRequired, } from './clients/orchestrator/public.js';
|
|
11
|
-
export type { AccountProviderConfig, AccountType, BundlerConfig, Call, CallInput, ChainSessionConfig, CrossChainPermissionInput, CrossChainPermit, CrossChainSettlementLayer, FromLeg, GuardiansSignerSet, MultiFactorValidatorConfig, NonEvmTokenRequest, NonEvmTokenRequests, OwnableValidatorConfig, OwnerSet, ParamConstraint, PaymasterConfig, Permission, PermissionFunctionConfig, Permit2ClaimPolicy, Policy, ProviderConfig, QuorumOwner, QuorumValidatorConfig, Recovery, RhinestoneAccountConfig, Session, SessionDefinition, SessionSigning, SessionSigningContent, SignerSet, SourceCallInput, SourceCallProvidedFunds, SwapScope, TokenRequest, TokenSymbol, ToLeg, Transaction, UniversalActionPolicyParamCondition, WebauthnValidatorConfig, } from './config/account.js';
|
|
10
|
+
export type { AppFeeBalances, AppFeeRate, ApprovalRequired, AuxiliaryFunds, BridgeFill, ChainOperation, FailureReason, HyperCoreAction, HyperCoreBatchModifyAction, HyperCoreCancelAction, HyperCoreCancelByCloidAction, HyperCoreModifyAction, HyperCoreOrder, HyperCoreOrderAction, HyperCoreOrderType, HyperCoreTimeInForce, HyperCoreUpdateIsolatedMarginAction, HyperCoreUpdateLeverageAction, IntentInput, IntentOpStatus, OperationStatus, OriginSignature, Portfolio, ProtocolFeeRate, Quote, SerializedIntentInput, SettlementLayer, SettlementLayerFilter, SignData, SplitIntentsInput, SplitIntentsResult, SwapQuoter, SwapQuoterFilter, TokenRequirements, WrapRequired, } from './clients/orchestrator/public.js';
|
|
11
|
+
export type { AccountProviderConfig, AccountType, BundlerConfig, Call, CallInput, ChainSessionConfig, ClosePerpRequest, CrossChainPermissionInput, CrossChainPermit, CrossChainSettlementLayer, FromLeg, GuardiansSignerSet, HyperCoreOptions, HyperliquidConfig, MultiFactorValidatorConfig, NonEvmTokenRequest, NonEvmTokenRequests, OpenPerpRequest, OwnableValidatorConfig, OwnerSet, ParamConstraint, PaymasterConfig, Permission, PermissionFunctionConfig, Permit2ClaimPolicy, Policy, ProviderConfig, QuorumOwner, QuorumValidatorConfig, Recovery, RhinestoneAccountConfig, Session, SessionDefinition, SessionSigning, SessionSigningContent, SignerSet, SourceCallInput, SourceCallProvidedFunds, SwapScope, TokenRequest, TokenSymbol, ToLeg, Transaction, UniversalActionPolicyParamCondition, WebauthnValidatorConfig, } from './config/account.js';
|
|
12
12
|
export type { OwnerPasskeySignature, OwnerSignature, OwnerSignatureData, SignAsOwnerOptions, } from './signing/types.js';
|
|
13
13
|
export type { PreparedQuotes, PreparedTransactionData, QuoteSelection, SignedTransactionData, TransactionResult, } from './transactions/intents/types.js';
|
|
14
14
|
export type { PreparedUserOperationData, SignedUserOperationData, UserOperationResult, } from './transactions/user-operations/types.js';
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EACL,aAAa,EACb,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EACZ,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,8BAA8B,EAC9B,iCAAiC,EAClC,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAA;AACxE,OAAO,EAAE,8BAA8B,EAAE,MAAM,4CAA4C,CAAA;AAC3F,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAA;AAE1E,OAAO,EACL,aAAa,EAEb,aAAa,EACb,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EAEX,yBAAyB,EACzB,0BAA0B,EAC1B,8BAA8B,EAC9B,iCAAiC,EACjC,8BAA8B,GAC/B,CAAA;AAED,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACxE,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,WAAW,GACZ,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EACd,eAAe,EACf,eAAe,EACf,SAAS,EACT,eAAe,EACf,KAAK,EACL,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,GACb,MAAM,+BAA+B,CAAA;AACtC,YAAY,EACV,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,IAAI,EACJ,SAAS,EACT,kBAAkB,EAClB,yBAAyB,EACzB,gBAAgB,EAChB,yBAAyB,EACzB,OAAO,EACP,kBAAkB,EAClB,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,QAAQ,EACR,eAAe,EACf,eAAe,EACf,UAAU,EACV,wBAAwB,EACxB,kBAAkB,EAClB,MAAM,EACN,cAAc,EACd,WAAW,EACX,qBAAqB,EACrB,QAAQ,EACR,uBAAuB,EACvB,OAAO,EACP,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,SAAS,EACT,eAAe,EACf,uBAAuB,EACvB,SAAS,EACT,YAAY,EACZ,WAAW,EACX,KAAK,EACL,WAAW,EACX,mCAAmC,EACnC,uBAAuB,GACxB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,cAAc,EACd,uBAAuB,EACvB,cAAc,EACd,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,8BAA8B,CAAA;AACrC,YAAY,EACV,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,sCAAsC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EACL,aAAa,EACb,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EACZ,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,8BAA8B,EAC9B,iCAAiC,EAClC,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAA;AACxE,OAAO,EAAE,8BAA8B,EAAE,MAAM,4CAA4C,CAAA;AAC3F,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAA;AAE1E,OAAO,EACL,aAAa,EAEb,aAAa,EACb,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EAEX,yBAAyB,EACzB,0BAA0B,EAC1B,8BAA8B,EAC9B,iCAAiC,EACjC,8BAA8B,GAC/B,CAAA;AAED,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACxE,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,WAAW,GACZ,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,0BAA0B,EAC1B,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,mCAAmC,EACnC,6BAA6B,EAC7B,WAAW,EACX,cAAc,EACd,eAAe,EACf,eAAe,EACf,SAAS,EACT,eAAe,EACf,KAAK,EACL,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,GACb,MAAM,+BAA+B,CAAA;AACtC,YAAY,EACV,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,IAAI,EACJ,SAAS,EACT,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,gBAAgB,EAChB,yBAAyB,EACzB,OAAO,EACP,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,sBAAsB,EACtB,QAAQ,EACR,eAAe,EACf,eAAe,EACf,UAAU,EACV,wBAAwB,EACxB,kBAAkB,EAClB,MAAM,EACN,cAAc,EACd,WAAW,EACX,qBAAqB,EACrB,QAAQ,EACR,uBAAuB,EACvB,OAAO,EACP,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,SAAS,EACT,eAAe,EACf,uBAAuB,EACvB,SAAS,EACT,YAAY,EACZ,WAAW,EACX,KAAK,EACL,WAAW,EACX,mCAAmC,EACnC,uBAAuB,GACxB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,cAAc,EACd,uBAAuB,EACvB,cAAc,EACd,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,8BAA8B,CAAA;AACrC,YAAY,EACV,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,sCAAsC,CAAA"}
|
|
@@ -31,6 +31,12 @@ export declare function resolveAccountTypedDataSigning(input: {
|
|
|
31
31
|
* the EIP-712 hash of `typedData`.
|
|
32
32
|
*/
|
|
33
33
|
readonly validationHash?: Hex;
|
|
34
|
+
/**
|
|
35
|
+
* Set when one signature over this payload has to validate on more than one
|
|
36
|
+
* chain (a `MultiChainOps` set whose leaves span chains). `chain` is then
|
|
37
|
+
* only the leg being resolved, not the reach of the signature.
|
|
38
|
+
*/
|
|
39
|
+
readonly spansMultipleChains?: boolean;
|
|
34
40
|
}): AccountTypedDataSigningRoute;
|
|
35
41
|
export declare function createTypedDataSigningPlan(input: TypedDataSigningPlanInput): SigningPlan;
|
|
36
42
|
export declare function signAccountTypedData(input: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-data.d.ts","sourceRoot":"","sources":["../../../signing/typed-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAiB,KAAK,mBAAmB,EAAE,MAAM,MAAM,CAAA;AAMxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAG/C,OAAO,EAAsB,KAAK,yBAAyB,EAAE,MAAM,WAAW,CAAA;AAQ9E,OAAO,KAAK,EACV,oBAAoB,EACpB,2BAA2B,EAC3B,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EAClB,MAAM,SAAS,CAAA;AAEhB,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAA;IACvC,QAAQ,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAA;IACjD,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;IACjC,QAAQ,CAAC,kBAAkB,EAAE,2BAA2B,CAAA;IACxD,QAAQ,CAAC,kBAAkB,EAAE,wBAAwB,CAAA;IACrD,QAAQ,CAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAA;IAC7C,QAAQ,CAAC,KAAK,EAAE,IAAI,CAClB,oBAAoB,EACpB,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CACrC,CAAA;IACD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAC3B,OAAO,SAAS,EAAE,qBAAqB,EACvC;QAAE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAA;KAAE,CACxC,CAAA;CACF;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAA;IACzC,QAAQ,CAAC,WAAW,EAAE,SAAS,GAAG,YAAY,CAAA;IAC9C,QAAQ,CAAC,eAAe,EAAE,oBAAoB,GAAG,uBAAuB,CAAA;IACxE,QAAQ,CAAC,kBAAkB,EAAE,oBAAoB,GAAG,0BAA0B,CAAA;IAC9E,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAA;CAClD;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE;IACpD,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAA;IACvC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,GAAG,CAAA;
|
|
1
|
+
{"version":3,"file":"typed-data.d.ts","sourceRoot":"","sources":["../../../signing/typed-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAiB,KAAK,mBAAmB,EAAE,MAAM,MAAM,CAAA;AAMxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAG/C,OAAO,EAAsB,KAAK,yBAAyB,EAAE,MAAM,WAAW,CAAA;AAQ9E,OAAO,KAAK,EACV,oBAAoB,EACpB,2BAA2B,EAC3B,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EAClB,MAAM,SAAS,CAAA;AAEhB,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAA;IACvC,QAAQ,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAA;IACjD,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;IACjC,QAAQ,CAAC,kBAAkB,EAAE,2BAA2B,CAAA;IACxD,QAAQ,CAAC,kBAAkB,EAAE,wBAAwB,CAAA;IACrD,QAAQ,CAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAA;IAC7C,QAAQ,CAAC,KAAK,EAAE,IAAI,CAClB,oBAAoB,EACpB,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CACrC,CAAA;IACD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAC3B,OAAO,SAAS,EAAE,qBAAqB,EACvC;QAAE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAA;KAAE,CACxC,CAAA;CACF;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAA;IACzC,QAAQ,CAAC,WAAW,EAAE,SAAS,GAAG,YAAY,CAAA;IAC9C,QAAQ,CAAC,eAAe,EAAE,oBAAoB,GAAG,uBAAuB,CAAA;IACxE,QAAQ,CAAC,kBAAkB,EAAE,oBAAoB,GAAG,0BAA0B,CAAA;IAC9E,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAA;CAClD;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE;IACpD,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAA;IACvC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,GAAG,CAAA;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAA;CACvC,GAAG,4BAA4B,CA8D/B;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,yBAAyB,GAC/B,WAAW,CAkBb;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,QAAQ,CAAC,SAAS,EAAE,yBAAyB,CAAA;IAC7C,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;IAChC,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAA;CAC5C,GAAG,OAAO,CAAC;IACV,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAA;CACvC,CAAC,CAmBD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,yBAAyB,EAChC,OAAO,EAAE,cAAc,GACtB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CA6D/B"}
|
|
@@ -15,6 +15,19 @@ export function resolveAccountTypedDataSigning(input) {
|
|
|
15
15
|
input.context.validatorCapabilities.compatibilityKey.moduleAddress.toLowerCase() ===
|
|
16
16
|
K1_DEFAULT_VALIDATOR_ADDRESS.toLowerCase();
|
|
17
17
|
const requiresRawHash = accountKind === 'kernel' || input.context.validator.kind === 'quorum';
|
|
18
|
+
// A quorum validator hashes the chain id into what the signer signs, and
|
|
19
|
+
// Startale's K1 route puts it in the ERC-7739 verifier domain. Either one
|
|
20
|
+
// yields a signature that validates on `input.chain` and nowhere else, so a
|
|
21
|
+
// payload whose legs are spread across chains must be refused here — while
|
|
22
|
+
// the caller is still preparing, not after the user has approved a bundle
|
|
23
|
+
// whose later legs cannot execute. Same-chain legs are unaffected, and
|
|
24
|
+
// Kernel's wrapping is address-only, so it stays fine either way.
|
|
25
|
+
if (input.spansMultipleChains &&
|
|
26
|
+
(input.context.validator.kind === 'quorum' || startaleK1)) {
|
|
27
|
+
throw new Error(`Cannot sign a multi-chain intent payload with ${input.context.validator.kind === 'quorum'
|
|
28
|
+
? 'a quorum validator'
|
|
29
|
+
: "Startale's K1 validator"}: it binds the chain id into the signed hash, so the signature would validate only on chain ${input.chain.id} and fail on the intent's legs on other chains`);
|
|
30
|
+
}
|
|
18
31
|
const messagePayload = requiresRawHash
|
|
19
32
|
? resolveAccountValidatorSignableHash({
|
|
20
33
|
hash: payload,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { TypedDataDefinition } from 'viem';
|
|
2
|
+
/**
|
|
3
|
+
* The chain an origin payload is signed for.
|
|
4
|
+
*
|
|
5
|
+
* Throws rather than yielding `NaN` for a payload that names no chain at all:
|
|
6
|
+
* the value flows into signer and session resolution, and on this path a
|
|
7
|
+
* failure lands after the user has already approved the intent.
|
|
8
|
+
*/
|
|
9
|
+
export declare function originChainId(typedData: TypedDataDefinition): number;
|
|
10
|
+
/**
|
|
11
|
+
* The chain whose account runtime signs an intent, read off the last origin
|
|
12
|
+
* payload as the callers here have always done.
|
|
13
|
+
*
|
|
14
|
+
* A `MultiChainOps` quote carries exactly one origin entry however many legs it
|
|
15
|
+
* covers, so first and last are the same payload and the choice only matters
|
|
16
|
+
* for the per-leg shape.
|
|
17
|
+
*/
|
|
18
|
+
export declare function accountChainIdFromOrigins(origins: readonly TypedDataDefinition[]): number;
|
|
19
|
+
/**
|
|
20
|
+
* True when one signature over this payload has to validate on more than one
|
|
21
|
+
* chain.
|
|
22
|
+
*
|
|
23
|
+
* An absent domain chainId is not the test on its own: `MultiChainOps` does not
|
|
24
|
+
* require its leaves to be on DIFFERENT chains, and same-chain legs with
|
|
25
|
+
* distinct nonces are a valid set — that is how a bundle splits its destination
|
|
26
|
+
* ops across blocks. Such a payload is chainless in the domain yet every leg
|
|
27
|
+
* runs on one chain, so a chain-bound wrapper still validates on all of them.
|
|
28
|
+
*
|
|
29
|
+
* Callers that wrap the digest with anything chain-specific must refuse only
|
|
30
|
+
* the genuinely multi-chain case, where signing against the leg being resolved
|
|
31
|
+
* gives a signature that fails on the rest — on chain, after the user has
|
|
32
|
+
* approved.
|
|
33
|
+
*/
|
|
34
|
+
export declare function signatureSpansMultipleChains(typedData: TypedDataDefinition): boolean;
|
|
35
|
+
//# sourceMappingURL=origin-chain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin-chain.d.ts","sourceRoot":"","sources":["../../../../transactions/intents/origin-chain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;AA0C/C;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,MAAM,CAkBpE;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,mBAAmB,EAAE,GACtC,MAAM,CAIR;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,mBAAmB,GAC7B,OAAO,CAGT"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// A `SingleChainOps` origin payload binds its chain in the EIP-712 domain, so
|
|
2
|
+
// `domain.chainId` names it. A `MultiChainOps` payload is one signature over
|
|
3
|
+
// every leg of a multi-leg bundle, and the contract verifies it with
|
|
4
|
+
// `_hashTypedDataSansChainId` — the domain deliberately carries no chainId, and
|
|
5
|
+
// each leg's chain lives in its own `ChainOps` leaf instead.
|
|
6
|
+
//
|
|
7
|
+
// The signature is identical on every leg, so any leaf is a valid context for
|
|
8
|
+
// resolving a signer; the first is taken because the leaves are in signed order
|
|
9
|
+
// and the choice has to be deterministic.
|
|
10
|
+
function readChainId(value) {
|
|
11
|
+
if (typeof value !== 'bigint' &&
|
|
12
|
+
typeof value !== 'number' &&
|
|
13
|
+
typeof value !== 'string') {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const chainId = Number(value);
|
|
17
|
+
return Number.isFinite(chainId) ? chainId : undefined;
|
|
18
|
+
}
|
|
19
|
+
function domainChainId(typedData) {
|
|
20
|
+
const { chainId } = typedData.domain ?? {};
|
|
21
|
+
return chainId === null ? undefined : chainId;
|
|
22
|
+
}
|
|
23
|
+
function leafChainIds(typedData) {
|
|
24
|
+
const ops = typedData.message?.ops;
|
|
25
|
+
if (!Array.isArray(ops))
|
|
26
|
+
return [];
|
|
27
|
+
const chainIds = [];
|
|
28
|
+
for (const leaf of ops) {
|
|
29
|
+
const chainId = readChainId(leaf?.chainId);
|
|
30
|
+
if (chainId !== undefined)
|
|
31
|
+
chainIds.push(chainId);
|
|
32
|
+
}
|
|
33
|
+
return chainIds;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The chain an origin payload is signed for.
|
|
37
|
+
*
|
|
38
|
+
* Throws rather than yielding `NaN` for a payload that names no chain at all:
|
|
39
|
+
* the value flows into signer and session resolution, and on this path a
|
|
40
|
+
* failure lands after the user has already approved the intent.
|
|
41
|
+
*/
|
|
42
|
+
export function originChainId(typedData) {
|
|
43
|
+
const fromDomain = domainChainId(typedData);
|
|
44
|
+
if (fromDomain !== undefined) {
|
|
45
|
+
const chainId = readChainId(fromDomain);
|
|
46
|
+
if (chainId === undefined) {
|
|
47
|
+
throw new Error(`Intent origin payload has an unreadable domain chainId: ${String(fromDomain)}`);
|
|
48
|
+
}
|
|
49
|
+
return chainId;
|
|
50
|
+
}
|
|
51
|
+
const [fromLeaf] = leafChainIds(typedData);
|
|
52
|
+
if (fromLeaf !== undefined)
|
|
53
|
+
return fromLeaf;
|
|
54
|
+
throw new Error(`Intent origin payload "${String(typedData.primaryType)}" names no chain: it carries neither a domain chainId nor a ChainOps leaf to read one from`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The chain whose account runtime signs an intent, read off the last origin
|
|
58
|
+
* payload as the callers here have always done.
|
|
59
|
+
*
|
|
60
|
+
* A `MultiChainOps` quote carries exactly one origin entry however many legs it
|
|
61
|
+
* covers, so first and last are the same payload and the choice only matters
|
|
62
|
+
* for the per-leg shape.
|
|
63
|
+
*/
|
|
64
|
+
export function accountChainIdFromOrigins(origins) {
|
|
65
|
+
const last = origins.at(-1);
|
|
66
|
+
if (!last)
|
|
67
|
+
throw new Error('Intent quote has no origin payloads');
|
|
68
|
+
return originChainId(last);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* True when one signature over this payload has to validate on more than one
|
|
72
|
+
* chain.
|
|
73
|
+
*
|
|
74
|
+
* An absent domain chainId is not the test on its own: `MultiChainOps` does not
|
|
75
|
+
* require its leaves to be on DIFFERENT chains, and same-chain legs with
|
|
76
|
+
* distinct nonces are a valid set — that is how a bundle splits its destination
|
|
77
|
+
* ops across blocks. Such a payload is chainless in the domain yet every leg
|
|
78
|
+
* runs on one chain, so a chain-bound wrapper still validates on all of them.
|
|
79
|
+
*
|
|
80
|
+
* Callers that wrap the digest with anything chain-specific must refuse only
|
|
81
|
+
* the genuinely multi-chain case, where signing against the leg being resolved
|
|
82
|
+
* gives a signature that fails on the rest — on chain, after the user has
|
|
83
|
+
* approved.
|
|
84
|
+
*/
|
|
85
|
+
export function signatureSpansMultipleChains(typedData) {
|
|
86
|
+
if (domainChainId(typedData) !== undefined)
|
|
87
|
+
return false;
|
|
88
|
+
return new Set(leafChainIds(typedData)).size > 1;
|
|
89
|
+
}
|