@0xmonaco/core 1.1.7 → 1.1.9
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/api/fees/api.d.ts +2 -0
- package/dist/api/fees/api.js +2 -0
- package/dist/api/rfq/api.d.ts +7 -1
- package/dist/api/rfq/api.js +16 -1
- package/dist/api/sweeper/api.d.ts +109 -1
- package/dist/api/sweeper/api.js +225 -1
- package/dist/api/sweeper/hypercore.d.ts +103 -0
- package/dist/api/sweeper/hypercore.js +357 -0
- package/dist/api/sweeper/hyperevm.d.ts +50 -0
- package/dist/api/sweeper/hyperevm.js +282 -0
- package/dist/api/vault/api.d.ts +28 -0
- package/dist/api/vault/api.js +136 -42
- package/dist/coverage.d.ts +1 -0
- package/dist/coverage.js +1 -0
- package/dist/errors/errors.d.ts +44 -0
- package/dist/errors/errors.js +69 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sdk.js +4 -0
- package/package.json +3 -3
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* USDC withdrawals from Hyperliquid (HyperCore) to a Sei deposit address.
|
|
3
|
+
*
|
|
4
|
+
* Circle's CCTP integration on HyperCore withdraws USDC to any CCTP domain
|
|
5
|
+
* through one user-signed HyperCore action, `sendToEvmWithData`
|
|
6
|
+
* (developers.circle.com/cctp/howtos/withdraw-usdc-from-hypercore-to-evm).
|
|
7
|
+
* Hyperliquid executes the signed action itself, so the user pays no gas: its
|
|
8
|
+
* system call into Circle's CoreDepositWallet on HyperEVM burns the USDC with
|
|
9
|
+
* Sei as the destination domain and the deposit address as the mint
|
|
10
|
+
* recipient. Empty `data` selects Circle's default forwarding hook, so Circle
|
|
11
|
+
* mints on Sei and keeps its fee out of the mint. The hub then sweeps the
|
|
12
|
+
* deposit address like any deposit made straight to it.
|
|
13
|
+
*/
|
|
14
|
+
import { concat, createPublicClient, hashDomain, hashStruct, http, keccak256, parseAbi, parseSignature, serializeTypedData, } from "viem";
|
|
15
|
+
import { hyperEvm, hyperliquidEvmTestnet, sei, seiTestnet } from "viem/chains";
|
|
16
|
+
import { APIError } from "../../errors";
|
|
17
|
+
/** Sei's CCTP domain: where the withdrawal mints. Not its EVM chain id. */
|
|
18
|
+
export const SEI_CCTP_DOMAIN = 16;
|
|
19
|
+
/** Gas for Hyperliquid's call into the CoreDepositWallet on HyperEVM, as in Circle's guide. */
|
|
20
|
+
const HYPEREVM_GAS_LIMIT = 200_000;
|
|
21
|
+
/** USDC's decimals on HyperEVM and Sei. */
|
|
22
|
+
export const USDC_DECIMALS = 6;
|
|
23
|
+
/**
|
|
24
|
+
* The Hyperliquid network that pairs with the SDK's Sei chain: mainnet with
|
|
25
|
+
* Sei mainnet, testnet with Sei testnet (where every non-mainnet Monaco
|
|
26
|
+
* network settles). `undefined` for any other chain.
|
|
27
|
+
*/
|
|
28
|
+
export function hypercoreNetworkFor(chainId) {
|
|
29
|
+
if (chainId === sei.id) {
|
|
30
|
+
return {
|
|
31
|
+
hyperliquidChain: "Mainnet",
|
|
32
|
+
exchangeUrl: "https://api.hyperliquid.xyz/exchange",
|
|
33
|
+
infoUrl: "https://api.hyperliquid.xyz/info",
|
|
34
|
+
hyperEvm,
|
|
35
|
+
coreDepositWallet: "0x6B9E773128f453f5c2C60935Ee2DE2CBc5390A24",
|
|
36
|
+
seiUsdc: "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392",
|
|
37
|
+
hyperEvmUsdc: "0xb88339CB7199b77E23DB6E890353E22632Ba630f",
|
|
38
|
+
tokenMessenger: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
|
|
39
|
+
irisUrl: "https://iris-api.circle.com",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (chainId === seiTestnet.id) {
|
|
43
|
+
return {
|
|
44
|
+
hyperliquidChain: "Testnet",
|
|
45
|
+
exchangeUrl: "https://api.hyperliquid-testnet.xyz/exchange",
|
|
46
|
+
infoUrl: "https://api.hyperliquid-testnet.xyz/info",
|
|
47
|
+
hyperEvm: hyperliquidEvmTestnet,
|
|
48
|
+
coreDepositWallet: "0x0B80659a4076E9E93C7DbE0f10675A16a3e5C206",
|
|
49
|
+
seiUsdc: "0x4fCF1784B31630811181f670Aea7A7bEF803eaED",
|
|
50
|
+
hyperEvmUsdc: "0x2B3370eE501B4a559b57D449569354196457D8Ab",
|
|
51
|
+
tokenMessenger: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
|
|
52
|
+
irisUrl: "https://iris-api-sandbox.circle.com",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const CORE_DEPOSIT_WALLET_ABI = parseAbi(["function calculateCrossChainWithdrawalFee(bool shouldForward, uint32 destinationChainId) view returns (uint256)"]);
|
|
58
|
+
/**
|
|
59
|
+
* The most Circle deducts from a forwarded withdrawal to Sei: the CCTP
|
|
60
|
+
* `maxFee` its CoreDepositWallet burns with (its forwarding fee for the
|
|
61
|
+
* domain plus CCTP's own cap). Owner-configurable per domain, so it is read
|
|
62
|
+
* rather than assumed. The wallet also reverts a withdrawal that does not
|
|
63
|
+
* exceed it.
|
|
64
|
+
*/
|
|
65
|
+
export async function readMaxFee(network) {
|
|
66
|
+
const client = createPublicClient({ chain: network.hyperEvm, transport: http() });
|
|
67
|
+
return client.readContract({
|
|
68
|
+
address: network.coreDepositWallet,
|
|
69
|
+
abi: CORE_DEPOSIT_WALLET_ABI,
|
|
70
|
+
functionName: "calculateCrossChainWithdrawalFee",
|
|
71
|
+
args: [true, SEI_CCTP_DOMAIN],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** USDC's token index on HyperCore, on mainnet and testnet alike (`spotMeta`). */
|
|
75
|
+
const HYPERCORE_USDC_TOKEN = 0;
|
|
76
|
+
/**
|
|
77
|
+
* Account modes that keep one balance per asset. Hyperliquid reports such an
|
|
78
|
+
* account's whole balance in its spot state, and its perp state "is not
|
|
79
|
+
* meaningful" (hyperliquid-docs: trading/account-abstraction-modes).
|
|
80
|
+
*/
|
|
81
|
+
function isUnified(mode) {
|
|
82
|
+
return mode === "unifiedAccount" || mode === "portfolioMargin";
|
|
83
|
+
}
|
|
84
|
+
/** Read the account's mode and USDC balances from Hyperliquid's public info endpoint. */
|
|
85
|
+
export async function readHypercoreAccount(network, user) {
|
|
86
|
+
const query = (type) => postToInfo(network, { type, user: user.toLowerCase() });
|
|
87
|
+
const [mode, perpState, spotState] = await Promise.all([
|
|
88
|
+
query("userAbstraction"),
|
|
89
|
+
query("clearinghouseState"),
|
|
90
|
+
query("spotClearinghouseState"),
|
|
91
|
+
]);
|
|
92
|
+
const usdc = spotState.balances?.find((balance) => balance.token === HYPERCORE_USDC_TOKEN);
|
|
93
|
+
// Subtract at Hyperliquid's own precision and truncate only the result:
|
|
94
|
+
// truncating total and hold separately can overstate the unheld balance.
|
|
95
|
+
const spot = usdc ? toExact(usdc.total, network) - toExact(usdc.hold, network) : 0n;
|
|
96
|
+
return {
|
|
97
|
+
mode,
|
|
98
|
+
perp: isUnified(mode) ? 0n : toRawUsdc(toExact(perpState.withdrawable, network)),
|
|
99
|
+
spot: spot > 0n ? toRawUsdc(spot) : 0n,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Raw USDC kept back for Hyperliquid's own fee on a withdrawal to HyperEVM,
|
|
104
|
+
* which it charges on top of the amount. Hyperliquid documents the fee as
|
|
105
|
+
* 200k gas at the next HyperEVM block's base fee but does not publish the
|
|
106
|
+
* USDC rate it charges. Derived, not quoted: on 2026-11-05 a mainnet
|
|
107
|
+
* withdrawal was charged 0.012948 USDC and testnet withdrawals 0.07122 to
|
|
108
|
+
* 0.072664, and 0.1 covers both with room.
|
|
109
|
+
*/
|
|
110
|
+
const HYPERLIQUID_FEE_RESERVE = 100000n;
|
|
111
|
+
/**
|
|
112
|
+
* The most a perp draw can take. Hyperliquid charges its fee to spot when
|
|
113
|
+
* spot covers it, and to the balance withdrawn otherwise (both measured on
|
|
114
|
+
* testnet, the second with 0.01 USDC in spot), so perp keeps the reserve back
|
|
115
|
+
* only when spot holds less than it.
|
|
116
|
+
*/
|
|
117
|
+
function perpDrawable(account) {
|
|
118
|
+
if (account.spot >= HYPERLIQUID_FEE_RESERVE) {
|
|
119
|
+
return account.perp;
|
|
120
|
+
}
|
|
121
|
+
return account.perp > HYPERLIQUID_FEE_RESERVE ? account.perp - HYPERLIQUID_FEE_RESERVE : 0n;
|
|
122
|
+
}
|
|
123
|
+
/** The most a spot draw can take: spot pays Hyperliquid's fee on top of it. */
|
|
124
|
+
function spotDrawable(account) {
|
|
125
|
+
return account.spot > HYPERLIQUID_FEE_RESERVE ? account.spot - HYPERLIQUID_FEE_RESERVE : 0n;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The balance one withdrawal of `amount` draws on, or `undefined` when none
|
|
129
|
+
* covers it with room for Hyperliquid's fee. A unified or portfolio-margin
|
|
130
|
+
* account draws on spot: Hyperliquid refuses any other source for it
|
|
131
|
+
* ("Unified account only supports sending assets through spot"). Otherwise
|
|
132
|
+
* perp comes first, where a deposit to Hyperliquid lands by default.
|
|
133
|
+
*/
|
|
134
|
+
export function chooseSource(account, amount) {
|
|
135
|
+
if (!isUnified(account.mode) && amount <= perpDrawable(account)) {
|
|
136
|
+
return "perp";
|
|
137
|
+
}
|
|
138
|
+
return amount <= spotDrawable(account) ? "spot" : undefined;
|
|
139
|
+
}
|
|
140
|
+
/** The most one withdrawal can draw: it draws on a single balance and leaves room for Hyperliquid's fee. */
|
|
141
|
+
export function availableToDraw(account) {
|
|
142
|
+
const spot = spotDrawable(account);
|
|
143
|
+
if (isUnified(account.mode)) {
|
|
144
|
+
return spot;
|
|
145
|
+
}
|
|
146
|
+
// Not Math.max: it does not take bigints.
|
|
147
|
+
const perp = perpDrawable(account);
|
|
148
|
+
if (perp > spot) {
|
|
149
|
+
return perp;
|
|
150
|
+
}
|
|
151
|
+
return spot;
|
|
152
|
+
}
|
|
153
|
+
/** POST one query to Hyperliquid's info endpoint; any failure is an `APIError`. */
|
|
154
|
+
async function postToInfo(network, body) {
|
|
155
|
+
const endpoint = network.infoUrl;
|
|
156
|
+
let response;
|
|
157
|
+
try {
|
|
158
|
+
response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
throw new APIError("Network request to Hyperliquid failed", { endpoint, cause: error instanceof Error ? error : new Error(String(error)) });
|
|
162
|
+
}
|
|
163
|
+
const text = await response.text().catch(() => "");
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
throw new APIError(`Hyperliquid's ${body.type} query answered ${response.status}`, {
|
|
166
|
+
endpoint,
|
|
167
|
+
statusCode: response.status,
|
|
168
|
+
responseBody: text.slice(0, 500),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
return JSON.parse(text);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
throw new APIError(`Hyperliquid's ${body.type} reply was not readable`, { endpoint, statusCode: response.status, responseBody: text.slice(0, 500) });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Fixed-point scale for Hyperliquid's decimal strings: above any precision it
|
|
180
|
+
* reports (USDC carries 8 `weiDecimals`), so arithmetic on it is exact.
|
|
181
|
+
*/
|
|
182
|
+
const EXACT_DECIMALS = 18;
|
|
183
|
+
/** Hyperliquid's decimal-string amount at {@link EXACT_DECIMALS}. */
|
|
184
|
+
function toExact(value, network) {
|
|
185
|
+
const match = typeof value === "string" ? /^(\d+)(?:\.(\d+))?$/.exec(value) : null;
|
|
186
|
+
if (!match) {
|
|
187
|
+
throw new APIError(`Hyperliquid reported an unreadable USDC amount: ${JSON.stringify(value)}`, { endpoint: network.infoUrl });
|
|
188
|
+
}
|
|
189
|
+
const [, whole = "0", fraction = ""] = match;
|
|
190
|
+
return BigInt(whole) * 10n ** BigInt(EXACT_DECIMALS) + BigInt(fraction.slice(0, EXACT_DECIMALS).padEnd(EXACT_DECIMALS, "0"));
|
|
191
|
+
}
|
|
192
|
+
/** A non-negative exact amount in raw 6-decimal USDC, truncated so the SDK never offers more than the account holds. */
|
|
193
|
+
function toRawUsdc(exact) {
|
|
194
|
+
return exact / 10n ** BigInt(EXACT_DECIMALS - USDC_DECIMALS);
|
|
195
|
+
}
|
|
196
|
+
const PRIMARY_TYPE = "HyperliquidTransaction:SendToEvmWithData";
|
|
197
|
+
/** The action's EIP-712 type, as Circle documents it; field order is part of the hash. */
|
|
198
|
+
const SEND_TO_EVM_WITH_DATA_TYPES = {
|
|
199
|
+
[PRIMARY_TYPE]: [
|
|
200
|
+
{ name: "hyperliquidChain", type: "string" },
|
|
201
|
+
{ name: "token", type: "string" },
|
|
202
|
+
{ name: "amount", type: "string" },
|
|
203
|
+
{ name: "sourceDex", type: "string" },
|
|
204
|
+
{ name: "destinationRecipient", type: "string" },
|
|
205
|
+
{ name: "addressEncoding", type: "string" },
|
|
206
|
+
{ name: "destinationChainId", type: "uint32" },
|
|
207
|
+
{ name: "gasLimit", type: "uint64" },
|
|
208
|
+
{ name: "data", type: "bytes" },
|
|
209
|
+
{ name: "nonce", type: "uint64" },
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
/** The signature domain's type: every field `HyperliquidSignTransaction` sets. */
|
|
213
|
+
const EIP712_DOMAIN_TYPE = [
|
|
214
|
+
{ name: "name", type: "string" },
|
|
215
|
+
{ name: "version", type: "string" },
|
|
216
|
+
{ name: "chainId", type: "uint256" },
|
|
217
|
+
{ name: "verifyingContract", type: "address" },
|
|
218
|
+
];
|
|
219
|
+
/**
|
|
220
|
+
* Sign a USDC withdrawal from Hyperliquid to `recipient` on Sei.
|
|
221
|
+
*
|
|
222
|
+
* The signature's domain uses the wallet's own chain (Sei), which Hyperliquid
|
|
223
|
+
* accepts from any chain and wallets that hold the domain to the active
|
|
224
|
+
* network sign without switching.
|
|
225
|
+
*/
|
|
226
|
+
export async function signWithdrawalToSei(params) {
|
|
227
|
+
const { walletClient, network, amount, sourceDex, recipient, nonce } = params;
|
|
228
|
+
const chainId = walletClient.chain?.id;
|
|
229
|
+
const account = walletClient.account;
|
|
230
|
+
if (chainId === undefined || account === undefined) {
|
|
231
|
+
throw new Error("signWithdrawalToSei needs a wallet client with a chain and an account");
|
|
232
|
+
}
|
|
233
|
+
const message = {
|
|
234
|
+
hyperliquidChain: network.hyperliquidChain,
|
|
235
|
+
token: "USDC",
|
|
236
|
+
amount,
|
|
237
|
+
sourceDex,
|
|
238
|
+
destinationRecipient: recipient,
|
|
239
|
+
addressEncoding: "hex",
|
|
240
|
+
destinationChainId: SEI_CCTP_DOMAIN,
|
|
241
|
+
gasLimit: BigInt(HYPEREVM_GAS_LIMIT),
|
|
242
|
+
data: "0x",
|
|
243
|
+
nonce: BigInt(nonce),
|
|
244
|
+
};
|
|
245
|
+
const domain = {
|
|
246
|
+
name: "HyperliquidSignTransaction",
|
|
247
|
+
version: "1",
|
|
248
|
+
chainId,
|
|
249
|
+
verifyingContract: "0x0000000000000000000000000000000000000000",
|
|
250
|
+
};
|
|
251
|
+
const signature = await signHyperliquidTypedData(walletClient, account, domain, message);
|
|
252
|
+
const { r, s, yParity } = parseSignature(signature);
|
|
253
|
+
return {
|
|
254
|
+
action: {
|
|
255
|
+
type: "sendToEvmWithData",
|
|
256
|
+
signatureChainId: `0x${chainId.toString(16)}`,
|
|
257
|
+
...message,
|
|
258
|
+
gasLimit: HYPEREVM_GAS_LIMIT,
|
|
259
|
+
nonce,
|
|
260
|
+
},
|
|
261
|
+
nonce,
|
|
262
|
+
signature: { r, s, v: 27 + yParity },
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Sign Hyperliquid typed data with the wallet's account.
|
|
267
|
+
*
|
|
268
|
+
* viem's `signTypedData` refuses this type from 2.57.12 (wevm/viem#5308 lets
|
|
269
|
+
* a struct name hold only letters, digits, `_` and `$`; wevm/viem#5403 allows
|
|
270
|
+
* `:` again in a later release), and Hyperliquid names every user-signed
|
|
271
|
+
* action `HyperliquidTransaction:<Action>` — the colon is part of the hashed
|
|
272
|
+
* type, so it cannot be dropped. The SDK runs on whatever viem the app
|
|
273
|
+
* resolves, so it does not rely on the fix being installed. A local account therefore
|
|
274
|
+
* signs the EIP-712 digest built from viem's unvalidated `hashDomain` and
|
|
275
|
+
* `hashStruct`; any other account (a browser or embedded wallet) is sent the
|
|
276
|
+
* typed data as `eth_signTypedData_v4`, exactly as viem would send it.
|
|
277
|
+
*/
|
|
278
|
+
async function signHyperliquidTypedData(walletClient, account, domain, message) {
|
|
279
|
+
const types = { EIP712Domain: EIP712_DOMAIN_TYPE, ...SEND_TO_EVM_WITH_DATA_TYPES };
|
|
280
|
+
if (account.type === "local") {
|
|
281
|
+
const sign = account.sign;
|
|
282
|
+
if (!sign) {
|
|
283
|
+
throw new Error("The wallet's local account cannot sign a raw digest");
|
|
284
|
+
}
|
|
285
|
+
const digest = keccak256(concat([
|
|
286
|
+
"0x1901",
|
|
287
|
+
hashDomain({ domain: { ...domain, chainId: BigInt(domain.chainId) }, types: { EIP712Domain: EIP712_DOMAIN_TYPE } }),
|
|
288
|
+
hashStruct({ data: message, primaryType: PRIMARY_TYPE, types }),
|
|
289
|
+
]));
|
|
290
|
+
return sign({ hash: digest });
|
|
291
|
+
}
|
|
292
|
+
return walletClient.request({
|
|
293
|
+
method: "eth_signTypedData_v4",
|
|
294
|
+
params: [account.address, serializeTypedData({ domain, message, primaryType: PRIMARY_TYPE, types })],
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The suggestion every unknown outcome carries. Calling the method again signs
|
|
299
|
+
* a fresh nonce, so a retry after an executed withdrawal moves the funds twice.
|
|
300
|
+
*/
|
|
301
|
+
const UNKNOWN_OUTCOME_SUGGESTION = "Hyperliquid may have executed the withdrawal. Do not call depositFromHypercore again, which signs a second one: check sdk.sweeper.listDeposits (the deposit lists once it is minted on Sei) or the Hyperliquid balance first.";
|
|
302
|
+
/** Clear `retryable` on an error whose outcome is unknown (see {@link UNKNOWN_OUTCOME_SUGGESTION}). */
|
|
303
|
+
function unknownOutcome(error) {
|
|
304
|
+
error.retryable = false;
|
|
305
|
+
error.suggestion = UNKNOWN_OUTCOME_SUGGESTION;
|
|
306
|
+
return error;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Post a signed withdrawal to Hyperliquid's exchange endpoint.
|
|
310
|
+
*
|
|
311
|
+
* Hyperliquid answers a decided rejection with HTTP 200 and
|
|
312
|
+
* `{"status":"err","response":"<reason>"}`, so the status alone does not tell
|
|
313
|
+
* success from refusal. A refusal is a decided outcome (nothing moved); a
|
|
314
|
+
* dropped connection, a 5xx, or a reply that is neither `ok` nor `err` leaves
|
|
315
|
+
* the outcome unknown. The signed body is never attached to an error.
|
|
316
|
+
*/
|
|
317
|
+
export async function postToExchange(network, withdrawal) {
|
|
318
|
+
const endpoint = network.exchangeUrl;
|
|
319
|
+
let response;
|
|
320
|
+
try {
|
|
321
|
+
response = await fetch(endpoint, {
|
|
322
|
+
method: "POST",
|
|
323
|
+
headers: { "Content-Type": "application/json" },
|
|
324
|
+
body: JSON.stringify(withdrawal),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
throw unknownOutcome(new APIError(`Network request to Hyperliquid failed`, { endpoint, cause: error instanceof Error ? error : new Error(String(error)) }));
|
|
329
|
+
}
|
|
330
|
+
const text = await response.text().catch(() => "");
|
|
331
|
+
if (response.status >= 500) {
|
|
332
|
+
throw unknownOutcome(new APIError(`Hyperliquid answered ${response.status}`, { endpoint, statusCode: response.status, responseBody: text.slice(0, 500) }));
|
|
333
|
+
}
|
|
334
|
+
if (!response.ok) {
|
|
335
|
+
throw new APIError(`Hyperliquid refused the withdrawal: ${response.status}`, { endpoint, statusCode: response.status, responseBody: text.slice(0, 500) });
|
|
336
|
+
}
|
|
337
|
+
let body;
|
|
338
|
+
try {
|
|
339
|
+
body = JSON.parse(text);
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
body = undefined;
|
|
343
|
+
}
|
|
344
|
+
const status = body !== null && typeof body === "object" ? body.status : undefined;
|
|
345
|
+
if (status === "ok") {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (status === "err") {
|
|
349
|
+
const reason = body.response;
|
|
350
|
+
throw new APIError(`Hyperliquid rejected the withdrawal: ${typeof reason === "string" ? reason : JSON.stringify(reason)}`, {
|
|
351
|
+
endpoint,
|
|
352
|
+
statusCode: response.status,
|
|
353
|
+
responseBody: body,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
throw unknownOutcome(new APIError("Hyperliquid's reply was not readable", { endpoint, statusCode: response.status, responseBody: text.slice(0, 500) }));
|
|
357
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* USDC deposits from HyperEVM to a Sei deposit address.
|
|
3
|
+
*
|
|
4
|
+
* The wallet burns its Circle USDC on HyperEVM through Circle's
|
|
5
|
+
* TokenMessengerV2, naming the Sei deposit address as the mint recipient and
|
|
6
|
+
* attaching Circle's forwarding hook, so Circle mints on Sei
|
|
7
|
+
* (developers.circle.com/cctp/concepts/forwarding-service). The hub sweeps the
|
|
8
|
+
* mint like any deposit made straight to the address, so nothing of Monaco's
|
|
9
|
+
* runs on HyperEVM. The wallet pays HYPE for the approval and burn.
|
|
10
|
+
*/
|
|
11
|
+
import { type Address, type Hash, type WalletClient } from "viem";
|
|
12
|
+
import { type HypercoreNetwork } from "./hypercore";
|
|
13
|
+
/**
|
|
14
|
+
* Circle's forwarding fee for a HyperEVM-to-Sei transfer on the standard lane,
|
|
15
|
+
* at Circle's "high" level. Every forwarded transfer we sent was charged its
|
|
16
|
+
* whole `maxFee`, so the quote is used as is rather than padded; and a
|
|
17
|
+
* `maxFee` below the forwarding fee would leave the mint unforwarded, so the
|
|
18
|
+
* top of Circle's range is the safe choice.
|
|
19
|
+
*/
|
|
20
|
+
export declare function quoteForwardFee(network: HypercoreNetwork): Promise<bigint>;
|
|
21
|
+
/** The wallet's Circle USDC, its allowance to TokenMessengerV2, and its HYPE, on HyperEVM. */
|
|
22
|
+
export interface HyperEvmAccount {
|
|
23
|
+
usdc: bigint;
|
|
24
|
+
allowance: bigint;
|
|
25
|
+
gas: bigint;
|
|
26
|
+
}
|
|
27
|
+
export declare function readHyperEvmAccount(network: HypercoreNetwork, address: Address): Promise<HyperEvmAccount>;
|
|
28
|
+
/**
|
|
29
|
+
* Approve (when the allowance is short) and burn `amount` of the wallet's
|
|
30
|
+
* HyperEVM USDC to `recipient` on Sei, with Circle forwarding the mint.
|
|
31
|
+
*
|
|
32
|
+
* Decided failures — nothing was burned — throw as they are: a wallet's
|
|
33
|
+
* refusal, a failure before the burn was broadcast, and a `ContractError` for
|
|
34
|
+
* a reverted, cancelled or replaced approval or burn. A burn that may have
|
|
35
|
+
* been broadcast but whose outcome cannot be read is an unknown outcome: the
|
|
36
|
+
* `APIError` carries `retryable: false` and, when known, the burn's hash,
|
|
37
|
+
* because calling again burns a second time. An approval in that state stays
|
|
38
|
+
* retryable, since the next call re-reads the allowance.
|
|
39
|
+
*/
|
|
40
|
+
export declare function burnToSei(params: {
|
|
41
|
+
walletClient: WalletClient;
|
|
42
|
+
network: HypercoreNetwork;
|
|
43
|
+
amount: bigint;
|
|
44
|
+
recipient: string;
|
|
45
|
+
maxFee: bigint;
|
|
46
|
+
allowance: bigint;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
approveTxHash?: Hash;
|
|
49
|
+
burnTxHash: Hash;
|
|
50
|
+
}>;
|