@derivexyz/derive-ts 3.0.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 3.0.0
4
+
5
+ Initial release of the Derive v3 TypeScript SDK.
6
+
7
+ - Typed client generated from the canonical v3 API spec (market data, orders,
8
+ RFQ, transfers, withdrawals, deposits, vaults, session keys, subscriptions).
9
+ - EIP-712 action signing and EIP-191 login, verified against the protocol
10
+ encoders.
11
+ - Websocket transport with reconnect/resubscribe; native-fetch HTTP transport.
12
+ - `mainnet` / `testnet` / `local` network presets.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Derive Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # @derivexyz/derive-ts
2
+
3
+ TypeScript SDK for the [Derive](https://derive.xyz) v3 exchange API — a typed
4
+ client for market data, orders, RFQs, transfers, withdrawals, deposits, vaults,
5
+ session keys, and websocket subscriptions.
6
+
7
+ - **Typed end to end.** Request params and responses are generated from the
8
+ exchange's canonical API spec, so `client.orders.place(...)` and
9
+ `client.send('public/get_ticker', ...)` are both fully typed.
10
+ - **Correct signing.** EIP-712 action signing and EIP-191 login are built in and
11
+ verified byte-for-byte against the protocol's own encoders.
12
+
13
+ > Status: `3.0.0`. Requires Node 18+ (uses the built-in `fetch`). Peer runtime
14
+ > deps: `ethers` v6 and `ws`.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install @derivexyz/derive-ts ethers ws
20
+ ```
21
+
22
+ ## Quickstart
23
+
24
+ ```ts
25
+ import { DeriveClient } from '@derivexyz/derive-ts';
26
+
27
+ const client = new DeriveClient({
28
+ network: 'testnet', // 'mainnet' | 'testnet' | 'local'
29
+ wallet: process.env.PRIVATE_KEY, // account owner key (or an ethers Wallet)
30
+ });
31
+
32
+ // Public data needs no auth:
33
+ const ticker = await client.marketData.getTicker('ETH-PERP');
34
+ console.log('ETH index price', ticker.I);
35
+
36
+ // Private calls sign automatically. Open the websocket and log in first:
37
+ await client.connect();
38
+ const subaccountIds = await client.login();
39
+
40
+ await client.orders.place({
41
+ subaccountId: subaccountIds[0],
42
+ instrumentName: 'ETH-PERP',
43
+ direction: 'buy',
44
+ amount: '0.1',
45
+ limitPrice: '1000', // rests below market
46
+ });
47
+
48
+ await client.close();
49
+ ```
50
+
51
+ ## Authentication & session keys
52
+
53
+ Login and per-request auth use an **EIP-191** signature over a millisecond
54
+ timestamp. Trading/transfer/withdrawal actions are separately **EIP-712** signed.
55
+ Both are handled for you — you only provide a signing key.
56
+
57
+ For bots and frontends, do **not** ship the account owner key. Register a scoped
58
+ **session key** once (signed by the owner), then run with only that key:
59
+
60
+ ```ts
61
+ import { DeriveClient, ProtocolScopeCode } from '@derivexyz/derive-ts';
62
+ import { Wallet } from 'ethers';
63
+
64
+ const sessionKey = Wallet.createRandom();
65
+ const owner = new DeriveClient({ network: 'testnet', wallet: process.env.PRIVATE_KEY });
66
+ await owner.connect();
67
+ await owner.login();
68
+ await owner.sessionKeys.create({
69
+ publicSessionKey: sessionKey,
70
+ expirySec: Math.floor(Date.now() / 1000) + 7 * 24 * 3600,
71
+ protocolScopes: [ProtocolScopeCode.Trade], // least privilege — never Admin for a bot
72
+ ipWhitelist: ['203.0.113.7'],
73
+ });
74
+
75
+ // Later, in the bot, with only the session key:
76
+ const bot = new DeriveClient({
77
+ network: 'testnet',
78
+ sessionKey: sessionKey.privateKey,
79
+ ownerAddress: await (new Wallet(process.env.PRIVATE_KEY!)).getAddress(),
80
+ });
81
+ ```
82
+
83
+ Session keys expire at their signed `expirySec` and cannot be revoked via the
84
+ API — set a sensible expiry. `sessionKeys.edit` changes only off-chain
85
+ attributes (label, IP allowlist).
86
+
87
+ ## API surface
88
+
89
+ `DeriveClient` exposes namespaced sub-APIs:
90
+
91
+ | Namespace | What it does |
92
+ |---|---|
93
+ | `client.marketData` | instruments, tickers, currencies (public, no auth) |
94
+ | `client.subaccounts` | portfolio, positions, margin, trade history |
95
+ | `client.orders` | place / cancel / query orders |
96
+ | `client.rfq` | RFQ taker (request → quote → execute) and maker (quote) flows |
97
+ | `client.spotTransfers` | `transferInternal` (own subaccounts), `transferExternal` (other wallets), `updateWhitelistedRecipients` |
98
+ | `client.positionTransfers` | RFQ-backed `transferPositions` between subaccounts owned by the same wallet |
99
+ | `client.withdrawals` | signed L1 withdrawals |
100
+ | `client.deposits` | `contractCall.*` (self-custody on-chain) and `depositAddress.*` (CEX-style) |
101
+ | `client.vaults` | public reads + `vaults.shareholder.*` and `vaults.curator.*` |
102
+ | `client.sessionKeys` | create / list / edit session keys |
103
+ | `client.subscriptions` | websocket channels (callback or async-iterator) |
104
+
105
+ Anything not wrapped is reachable, fully typed, via `client.send(method, params)`.
106
+
107
+ ### Type-only usage (stubs)
108
+
109
+ If you only want the endpoint type stubs — the request/response shape of every
110
+ public RPC method, with no runtime client — import them from the `types/endpoints`
111
+ subpath. They're generated from the exchange's canonical API spec, so they stay in
112
+ lockstep with the live surface; nothing here pulls in `ethers`, `ws`, or any runtime.
113
+
114
+ ```ts
115
+ import type { EndpointMap, ParamsOf, ResultFor } from '@derivexyz/derive-ts/types/endpoints';
116
+
117
+ type PlaceParams = ParamsOf<'private/order'>;
118
+ type Ticker = ResultFor<'public/get_ticker'>;
119
+ // or reach into the map structurally:
120
+ type PlaceParamsStructural = EndpointMap['private/order']['request']['params'];
121
+ ```
122
+
123
+ Useful for codegen, tooling, or typing a hand-rolled client. `EndpointMap` is keyed by
124
+ method string; each entry is `{ request, response }`, where `response` is the
125
+ `{ result } | { error }` JSON-RPC union — use `ResultFor<M>` to pull out the success
126
+ payload.
127
+
128
+ ### Subscriptions
129
+
130
+ ```ts
131
+ import { channel } from '@derivexyz/derive-ts';
132
+
133
+ await client.connect();
134
+ const book = channel('orderbook.{instrument_name}.{group}.{depth}', {
135
+ instrument_name: 'ETH-PERP',
136
+ group: '1',
137
+ depth: '10',
138
+ });
139
+
140
+ const sub = await client.subscriptions.subscribe(book, (data) => console.log(data));
141
+ // ...later
142
+ await sub.unsubscribe();
143
+
144
+ // Or as an async iterator:
145
+ for await (const update of client.subscriptions.stream(channel('trades.{instrument_name}', { instrument_name: 'ETH-PERP' }))) {
146
+ console.log(update);
147
+ }
148
+ ```
149
+
150
+ ## Value conventions (read this before signing)
151
+
152
+ - **Prices, amounts, and fees** are passed as decimal strings/numbers (e.g.
153
+ `'1500.5'`). On the wire the SDK sends decimal strings; inside signed action
154
+ data it scales them to 1e18. The protocol runs at 1e12 precision and *rejects*
155
+ (does not truncate) values with more than 12 decimal places.
156
+ - **Withdrawal amounts** are the one exception — they are in the ERC-20's
157
+ **native decimals** (e.g. USDC has 6), because they are the L1 payout value.
158
+ - **Withdrawals always pay out to the signer.** If a session key signs a
159
+ withdrawal, funds go to the session key's address, not the owner's.
160
+ - **Deposits are two distinct flows, chosen explicitly:**
161
+ - `deposits.depositAddress.register(...)` returns a deterministic address; send
162
+ funds to it from anywhere and the exchange sweeps and credits them.
163
+ - `deposits.contractCall.*` builds the on-chain approve + ActionManager
164
+ transaction from an ethers `Signer` you provide.
165
+
166
+ ## Examples
167
+
168
+ Runnable, heavily commented walkthroughs live in [`examples/`](./examples):
169
+
170
+ ```bash
171
+ DERIVE_NETWORK=testnet PRIVATE_KEY=0x... npx tsx examples/01-deposit.ts
172
+ ```
173
+
174
+ Ordered by the v3 onboarding flow — your first **deposit creates your account**,
175
+ then **session keys**, then the rest: `01-deposit`, `02-session-keys`,
176
+ `03-market-data`, `04-subscribe`, `05-place-order`, `06-rfq-taker`,
177
+ `07-rfq-maker`, `08-transfers`, `09-withdraw`, `10-vaults`,
178
+ `11-position-transfers`.
179
+
180
+ ## Networks & signing params
181
+
182
+ The EIP-712 domain separator is computed from the network's chain id and the
183
+ protocol's constant `Matching` verifying contract; the action typehash is a
184
+ protocol constant. You normally just pass `network: 'mainnet' | 'testnet' |
185
+ 'local'`. To point at a custom deployment, pass a full `NetworkConfig`.
186
+
187
+ > **Security note:** auth headers are named `X-Lyra*` on the wire — this is the
188
+ > live protocol format and predates the Derive rebrand. Keep your private keys
189
+ > in a secret manager; the SDK never logs key material.
190
+
191
+ ## License
192
+
193
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "@derivexyz/derive-ts-codecs",
3
+ "private": true,
4
+ "main": "../dist/codecs/index.cjs",
5
+ "module": "../dist/codecs/index.js",
6
+ "types": "../dist/codecs/index.d.ts"
7
+ }
@@ -0,0 +1,289 @@
1
+ // src/signing/encoding.ts
2
+ import { parseUnits } from "ethers";
3
+ function toE18(value) {
4
+ return toScaled(value, 18);
5
+ }
6
+ function assertE12Precision(e18, field) {
7
+ if (e18 % 1000000n !== 0n) {
8
+ throw new Error(`${field} has more than 12 decimal places \u2014 the protocol runs at 1e12 precision`);
9
+ }
10
+ }
11
+ function unsignedE18(value, field) {
12
+ const scaled = toE18(value);
13
+ if (scaled < 0n) throw new Error(`${field} must not be negative`);
14
+ assertE12Precision(scaled, field);
15
+ return scaled;
16
+ }
17
+ function nonNegativeId(value, field) {
18
+ const valid = typeof value === "bigint" ? value >= 0n : Number.isSafeInteger(value) && value >= 0;
19
+ if (!valid) throw new Error(`${field} must be a non-negative integer`);
20
+ return value;
21
+ }
22
+ var DEFAULT_SIGNATURE_EXPIRY_SEC = 300;
23
+ function toScaled(value, decimals) {
24
+ if (typeof value === "bigint") return value;
25
+ const text = typeof value === "number" ? String(value) : value.trim();
26
+ if (!/^-?\d+(\.\d+)?$/.test(text)) {
27
+ throw new Error(
28
+ `invalid decimal value: ${JSON.stringify(value)} (scientific notation and empty strings are not accepted)`
29
+ );
30
+ }
31
+ return parseUnits(text, decimals);
32
+ }
33
+ function randomNonce(now = Date.now()) {
34
+ const nanos = BigInt(now) * 1000000n + BigInt(Math.floor(Math.random() * 1e6));
35
+ return nanos.toString();
36
+ }
37
+ function expiresIn(seconds, now = Date.now()) {
38
+ return Math.floor(now / 1e3) + seconds;
39
+ }
40
+
41
+ // src/codecs/trade.ts
42
+ import { concat, getAddress, toBeHex, zeroPadValue } from "ethers";
43
+ var I128_MIN = -(2n ** 127n);
44
+ var I128_MAX = 2n ** 127n - 1n;
45
+ var U64_MAX = 2n ** 64n - 1n;
46
+ var U128_MAX = 2n ** 128n - 1n;
47
+ function assertE12Precision2(e18, field) {
48
+ if (e18 % 1000000n !== 0n) {
49
+ throw new Error(`${field} has more than 12 decimal places \u2014 the protocol runs at 1e12 precision`);
50
+ }
51
+ }
52
+ function i128LowHalfWord(e18, field) {
53
+ if (e18 < I128_MIN || e18 > I128_MAX) {
54
+ throw new Error(`${field} out of range: e18-scaled value must fit in an i128`);
55
+ }
56
+ const twosComplement = e18 < 0n ? e18 + 2n ** 128n : e18;
57
+ return zeroPadValue(toBeHex(twosComplement, 16), 32);
58
+ }
59
+ function uintWord(value, max, field) {
60
+ if (value < 0n || value > max) {
61
+ throw new Error(`${field} out of range: must be an unsigned integer <= ${max}`);
62
+ }
63
+ return zeroPadValue(toBeHex(value), 32);
64
+ }
65
+ function encodeTradeData(fields) {
66
+ const limitPrice = toE18(fields.limitPrice);
67
+ const amount = toE18(fields.amount);
68
+ const maxFee = toE18(fields.maxFee);
69
+ assertE12Precision2(limitPrice, "limitPrice");
70
+ assertE12Precision2(amount, "amount");
71
+ assertE12Precision2(maxFee, "maxFee");
72
+ return concat([
73
+ zeroPadValue(getAddress(fields.assetAddress), 32),
74
+ uintWord(BigInt(fields.subId), U128_MAX, "subId"),
75
+ i128LowHalfWord(limitPrice, "limitPrice"),
76
+ i128LowHalfWord(amount, "amount"),
77
+ uintWord(maxFee, U128_MAX, "maxFee"),
78
+ uintWord(BigInt(fields.recipientSubaccountId), U64_MAX, "recipientSubaccountId"),
79
+ zeroPadValue(fields.isBid ? "0x01" : "0x00", 32)
80
+ ]);
81
+ }
82
+
83
+ // src/codecs/rfq.ts
84
+ import { AbiCoder, getAddress as getAddress2, keccak256 } from "ethers";
85
+ function sortRfqLegs(legs) {
86
+ return [...legs].sort(
87
+ (a, b) => a.instrumentName < b.instrumentName ? -1 : a.instrumentName > b.instrumentName ? 1 : 0
88
+ );
89
+ }
90
+ var LEGS_ABI = "(address,uint256,uint256,int256)[]";
91
+ function encodeRfqQuote(fields) {
92
+ return AbiCoder.defaultAbiCoder().encode(
93
+ [`(uint256,${LEGS_ABI})`],
94
+ [[maxFeeE18(fields.maxFee), legTuples(fields, 1n)]]
95
+ );
96
+ }
97
+ function encodeRfqExecute(fields) {
98
+ const coder = AbiCoder.defaultAbiCoder();
99
+ const orderHash = keccak256(coder.encode([LEGS_ABI], [legTuples(fields, -1n)]));
100
+ return coder.encode(["bytes32", "uint256"], [orderHash, maxFeeE18(fields.maxFee)]);
101
+ }
102
+ function maxFeeE18(maxFee) {
103
+ return unsignedE18(maxFee, "rfq maxFee");
104
+ }
105
+ function legTuples(fields, perspectiveSign) {
106
+ const directionSign = fields.direction === "buy" ? 1n : -1n;
107
+ return sortRfqLegs(fields.legs).map((leg) => {
108
+ const price = unsignedE18(leg.price, `rfq leg price (${leg.instrumentName})`);
109
+ const amount = toE18(leg.amount);
110
+ if (amount <= 0n)
111
+ throw new Error(`rfq leg amount must be positive (direction carries the sign): ${leg.instrumentName}`);
112
+ assertE12Precision(amount, `rfq leg amount (${leg.instrumentName})`);
113
+ const legSign = leg.direction === "buy" ? 1n : -1n;
114
+ return [
115
+ getAddress2(leg.assetAddress),
116
+ BigInt(leg.subId),
117
+ price,
118
+ amount * legSign * directionSign * perspectiveSign
119
+ ];
120
+ });
121
+ }
122
+
123
+ // src/codecs/sessionKey.ts
124
+ import { concat as concat2, getAddress as getAddress3, toBeHex as toBeHex2, zeroPadValue as zeroPadValue2 } from "ethers";
125
+ function encodeCreateSessionKeyActionData(data) {
126
+ if (!Number.isInteger(data.expirySec) || data.expirySec < 0) {
127
+ throw new Error(`session key expirySec must be a non-negative unix-seconds integer, got ${data.expirySec}`);
128
+ }
129
+ return concat2([
130
+ zeroPadValue2(getAddress3(data.sessionKey), 32),
131
+ uintWord2(data.expirySec),
132
+ uintWord2(data.scopes.length),
133
+ uintWord2(data.subaccountIds.length),
134
+ ...data.scopes.map((code) => uintWord2(code)),
135
+ ...data.subaccountIds.map((id) => uintWord2(id))
136
+ ]);
137
+ }
138
+ function uintWord2(value) {
139
+ const valid = typeof value === "bigint" ? value >= 0n : Number.isSafeInteger(value) && value >= 0;
140
+ if (!valid) throw new Error(`expected a non-negative integer, got ${value}`);
141
+ return zeroPadValue2(toBeHex2(value), 32);
142
+ }
143
+
144
+ // src/codecs/transfer.ts
145
+ import { AbiCoder as AbiCoder2, getAddress as getAddress4 } from "ethers";
146
+ var TRANSFER_ABI = ["uint256", "uint256", "address", "uint256", "uint256", "uint256"];
147
+ function encodeTransfer(fields) {
148
+ const amount = unsignedE18(fields.amount, "amount");
149
+ if (amount === 0n) throw new Error("transfer amount must be strictly positive");
150
+ return AbiCoder2.defaultAbiCoder().encode(TRANSFER_ABI, [
151
+ nonNegativeId(fields.toSubaccountId, "toSubaccountId"),
152
+ nonNegativeId(fields.newSubaccountManager, "newSubaccountManager"),
153
+ getAddress4(fields.asset),
154
+ nonNegativeId(fields.subId, "subId"),
155
+ amount,
156
+ unsignedE18(fields.maxFeeUsd, "maxFeeUsd")
157
+ ]);
158
+ }
159
+
160
+ // src/codecs/externalTransfer.ts
161
+ import { concat as concat3, getAddress as getAddress5, zeroPadValue as zeroPadValue3 } from "ethers";
162
+ function encodeExternalTransfer(fields) {
163
+ return concat3([encodeTransfer(fields), zeroPadValue3(getAddress5(fields.recipient), 32)]);
164
+ }
165
+
166
+ // src/codecs/whitelistedRecipients.ts
167
+ import { concat as concat4, getAddress as getAddress6, toBeHex as toBeHex3, zeroPadValue as zeroPadValue4 } from "ethers";
168
+ function encodeUpdateWhitelistedRecipients(fields) {
169
+ const add = fields.add.map((a) => getAddress6(a));
170
+ const remove = fields.remove.map((a) => getAddress6(a));
171
+ return concat4([
172
+ zeroPadValue4(toBeHex3(add.length), 32),
173
+ zeroPadValue4(toBeHex3(remove.length), 32),
174
+ ...add.map((a) => zeroPadValue4(a, 32)),
175
+ ...remove.map((a) => zeroPadValue4(a, 32))
176
+ ]);
177
+ }
178
+
179
+ // src/codecs/vault.ts
180
+ import { AbiCoder as AbiCoder3, ZeroAddress, getAddress as getAddress7, isHexString } from "ethers";
181
+ var abi = AbiCoder3.defaultAbiCoder();
182
+ var VAULT_ACTION_KIND = {
183
+ create: 0,
184
+ deposit: 1,
185
+ withdraw: 2,
186
+ cancel: 3,
187
+ mintShares: 4,
188
+ burnShares: 5
189
+ };
190
+ function positiveE18(label, value) {
191
+ const e18 = toE18(value);
192
+ if (e18 <= 0n) throw new Error(`${label} must be positive`);
193
+ assertE12Precision(e18, label);
194
+ return e18;
195
+ }
196
+ function encodeVaultDeposit(fields) {
197
+ return abi.encode(
198
+ ["uint256", "uint256", "address", "uint256"],
199
+ [
200
+ VAULT_ACTION_KIND.deposit,
201
+ fields.vaultSubaccountId,
202
+ getAddress7(fields.depositSpotAsset),
203
+ positiveE18("amount", fields.amount)
204
+ ]
205
+ );
206
+ }
207
+ function encodeVaultWithdraw(fields) {
208
+ return abi.encode(
209
+ ["uint256", "uint256", "uint256"],
210
+ [VAULT_ACTION_KIND.withdraw, fields.vaultSubaccountId, positiveE18("sharesToBurn", fields.sharesToBurn)]
211
+ );
212
+ }
213
+ function encodeVaultCancel(vaultSubaccountId) {
214
+ return abi.encode(["uint256", "uint256"], [VAULT_ACTION_KIND.cancel, vaultSubaccountId]);
215
+ }
216
+ function encodeVaultCreate(fields) {
217
+ const benchmark = fields.benchmarkAsset;
218
+ return abi.encode(
219
+ // prettier-ignore
220
+ ["uint256", "uint256", "address", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256", "address", "bool"],
221
+ [
222
+ VAULT_ACTION_KIND.create,
223
+ fields.managerId,
224
+ getAddress7(fields.depositSpotAsset),
225
+ unsignedE18(fields.initialDeposit, "initialDeposit"),
226
+ fields.managementFeeBps,
227
+ fields.performanceFeeBps,
228
+ fields.maxSlippageBps,
229
+ fields.cooldownSec,
230
+ unsignedE18(fields.maxFeeUsd, "maxFeeUsd"),
231
+ unsignedE18(fields.initialSharePriceUsd, "initialSharePriceUsd"),
232
+ benchmark === void 0 ? ZeroAddress : getAddress7(benchmark),
233
+ benchmark !== void 0
234
+ ]
235
+ );
236
+ }
237
+ function encodeVaultMintShares(fields) {
238
+ return encodeSettleApproval(VAULT_ACTION_KIND.mintShares, fields.sharePrice, "depositHash", fields.depositHash);
239
+ }
240
+ function encodeVaultBurnShares(fields) {
241
+ return encodeSettleApproval(VAULT_ACTION_KIND.burnShares, fields.sharePrice, "withdrawHash", fields.withdrawHash);
242
+ }
243
+ function encodeSettleApproval(kind, sharePrice, hashLabel, hash) {
244
+ if (!isHexString(hash, 32)) throw new Error(`${hashLabel} must be a 0x-prefixed 32-byte hex string`);
245
+ return abi.encode(["uint256", "uint256", "bytes32"], [kind, unsignedE18(sharePrice, "sharePrice"), hash]);
246
+ }
247
+
248
+ // src/codecs/withdrawal.ts
249
+ import { AbiCoder as AbiCoder4, getAddress as getAddress8 } from "ethers";
250
+ var WITHDRAWAL_ABI = ["address", "uint256", "address", "uint256", "bool"];
251
+ function encodeWithdrawal(fields) {
252
+ if (!Number.isInteger(fields.decimals) || fields.decimals < 0 || fields.decimals > 255) {
253
+ throw new Error("decimals must be an integer between 0 and 255");
254
+ }
255
+ const amount = toScaled(fields.amount, fields.decimals);
256
+ if (amount <= 0n) throw new Error("withdrawal amount must be strictly positive");
257
+ return AbiCoder4.defaultAbiCoder().encode(WITHDRAWAL_ABI, [
258
+ getAddress8(fields.protocolAsset),
259
+ unsignedE18(fields.maxFeeUsd, "maxFeeUsd"),
260
+ getAddress8(fields.recipient),
261
+ amount,
262
+ fields.forceBatch
263
+ ]);
264
+ }
265
+
266
+ export {
267
+ toE18,
268
+ DEFAULT_SIGNATURE_EXPIRY_SEC,
269
+ toScaled,
270
+ randomNonce,
271
+ expiresIn,
272
+ encodeTradeData,
273
+ sortRfqLegs,
274
+ encodeRfqQuote,
275
+ encodeRfqExecute,
276
+ encodeCreateSessionKeyActionData,
277
+ encodeTransfer,
278
+ encodeExternalTransfer,
279
+ encodeUpdateWhitelistedRecipients,
280
+ VAULT_ACTION_KIND,
281
+ encodeVaultDeposit,
282
+ encodeVaultWithdraw,
283
+ encodeVaultCancel,
284
+ encodeVaultCreate,
285
+ encodeVaultMintShares,
286
+ encodeVaultBurnShares,
287
+ encodeWithdrawal
288
+ };
289
+ //# sourceMappingURL=chunk-WR2IIVUC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/signing/encoding.ts","../src/codecs/trade.ts","../src/codecs/rfq.ts","../src/codecs/sessionKey.ts","../src/codecs/transfer.ts","../src/codecs/externalTransfer.ts","../src/codecs/whitelistedRecipients.ts","../src/codecs/vault.ts","../src/codecs/withdrawal.ts"],"sourcesContent":["import { parseUnits } from 'ethers';\n\n/**\n * A decimal value accepted from callers: a decimal string (\"1500.5\"),\n * a safe number, or a bigint already at the target field's wire scale\n * (e18 for most values; the ERC-20's native decimals for withdrawal\n * amounts).\n */\nexport type DecimalLike = string | number | bigint;\n\n/**\n * Converts a decimal value to the protocol's e18 wire scale. Signed\n * actions ABI-encode all prices/amounts/fees at 1e18 and signatures\n * commit to those exact bytes. (Withdrawal amounts are the exception —\n * they stay in the ERC-20's native decimals; see codecs/withdrawal.)\n */\nexport function toE18(value: DecimalLike): bigint {\n return toScaled(value, 18);\n}\n\n/**\n * The exchange holds decimals at 1e12 and REJECTS (does not truncate) e18\n * words carrying sub-1e12 precision, so more than 12 decimal places can\n * never produce a valid signature. Holds for negative words too.\n */\nexport function assertE12Precision(e18: bigint, field: string): void {\n if (e18 % 1_000_000n !== 0n) {\n throw new Error(`${field} has more than 12 decimal places — the protocol runs at 1e12 precision`);\n }\n}\n\n/** Scales to e18, rejecting negatives and sub-1e12 precision. Shared by the value-bearing codecs. */\nexport function unsignedE18(value: DecimalLike, field: string): bigint {\n const scaled = toE18(value);\n if (scaled < 0n) throw new Error(`${field} must not be negative`);\n assertE12Precision(scaled, field);\n return scaled;\n}\n\nexport function nonNegativeId(value: number | bigint, field: string): number | bigint {\n const valid = typeof value === 'bigint' ? value >= 0n : Number.isSafeInteger(value) && value >= 0;\n if (!valid) throw new Error(`${field} must be a non-negative integer`);\n return value;\n}\n\n/** Default envelope validity for signed actions; comfortably above the API's minimum. */\nexport const DEFAULT_SIGNATURE_EXPIRY_SEC = 300;\n\nexport function toScaled(value: DecimalLike, decimals: number): bigint {\n if (typeof value === 'bigint') return value;\n const text = typeof value === 'number' ? String(value) : value.trim();\n if (!/^-?\\d+(\\.\\d+)?$/.test(text)) {\n throw new Error(\n `invalid decimal value: ${JSON.stringify(value)} (scientific notation and empty strings are not accepted)`,\n );\n }\n return parseUnits(text, decimals);\n}\n\n/**\n * Action nonces are UTC-nanosecond timestamps (the API accepts a\n * ±5-minute intake window), with sub-millisecond digits randomized so\n * concurrent actions never collide.\n */\nexport function randomNonce(now: number = Date.now()): string {\n const nanos = BigInt(now) * 1_000_000n + BigInt(Math.floor(Math.random() * 1_000_000));\n return nanos.toString();\n}\n\n/** Signature expiry as a unix-seconds timestamp, `seconds` from now. */\nexport function expiresIn(seconds: number, now: number = Date.now()): number {\n return Math.floor(now / 1000) + seconds;\n}\n","import { concat, getAddress, toBeHex, zeroPadValue } from 'ethers';\nimport { toE18, type DecimalLike } from '../signing/encoding';\n\n/**\n * Inputs to the trade (order) action payload, verified by the trade\n * module (`network.modules.trade`).\n */\nexport interface TradeActionFields {\n /** Protocol asset contract of the instrument (`base_asset_address`). */\n assetAddress: string;\n /** Asset sub id (`base_asset_sub_id`): 0 for perps/spot, the option id for options. */\n subId: string | number | bigint;\n limitPrice: DecimalLike;\n amount: DecimalLike;\n /** Worst acceptable fee per unit traded the signature allows. */\n maxFee: DecimalLike;\n /** Subaccount credited with the fill — the trading subaccount itself. */\n recipientSubaccountId: number | bigint;\n isBid: boolean;\n}\n\nconst I128_MIN = -(2n ** 127n);\nconst I128_MAX = 2n ** 127n - 1n;\nconst U64_MAX = 2n ** 64n - 1n;\nconst U128_MAX = 2n ** 128n - 1n;\n\n/**\n * The exchange holds decimals at 1e12 and rejects (not truncates) signed\n * e18 words with sub-1e12 precision, so more than 12 decimal places can\n * never produce a valid signature.\n */\nfunction assertE12Precision(e18: bigint, field: string): void {\n if (e18 % 1_000_000n !== 0n) {\n throw new Error(`${field} has more than 12 decimal places — the protocol runs at 1e12 precision`);\n }\n}\n\n/**\n * The exchange reads limit price and amount as an i128 from the LOW 16\n * bytes of the word; the high 16 bytes stay zero even for negative\n * values. Standard ABI int256 encoding would sign-extend the high half\n * and produce bytes the exchange rejects.\n */\nfunction i128LowHalfWord(e18: bigint, field: string): string {\n if (e18 < I128_MIN || e18 > I128_MAX) {\n throw new Error(`${field} out of range: e18-scaled value must fit in an i128`);\n }\n const twosComplement = e18 < 0n ? e18 + 2n ** 128n : e18;\n return zeroPadValue(toBeHex(twosComplement, 16), 32);\n}\n\nfunction uintWord(value: bigint, max: bigint, field: string): string {\n if (value < 0n || value > max) {\n throw new Error(`${field} out of range: must be an unsigned integer <= ${max}`);\n }\n return zeroPadValue(toBeHex(value), 32);\n}\n\n/**\n * Encodes the order action data: seven static words — asset, subId,\n * limitPrice, amount, maxFee, recipientId, isBid — with\n * prices/amounts/fee e18-scaled.\n */\nexport function encodeTradeData(fields: TradeActionFields): string {\n const limitPrice = toE18(fields.limitPrice);\n const amount = toE18(fields.amount);\n const maxFee = toE18(fields.maxFee);\n assertE12Precision(limitPrice, 'limitPrice');\n assertE12Precision(amount, 'amount');\n assertE12Precision(maxFee, 'maxFee');\n return concat([\n zeroPadValue(getAddress(fields.assetAddress), 32),\n uintWord(BigInt(fields.subId), U128_MAX, 'subId'),\n i128LowHalfWord(limitPrice, 'limitPrice'),\n i128LowHalfWord(amount, 'amount'),\n uintWord(maxFee, U128_MAX, 'maxFee'),\n uintWord(BigInt(fields.recipientSubaccountId), U64_MAX, 'recipientSubaccountId'),\n zeroPadValue(fields.isBid ? '0x01' : '0x00', 32),\n ]);\n}\n","import { AbiCoder, getAddress, keccak256 } from 'ethers';\nimport { assertE12Precision, toE18, unsignedE18, type DecimalLike } from '../signing/encoding';\nimport type { Direction } from '../types';\n\n/**\n * RFQ action-data codecs.\n *\n * Maker and taker must hash byte-identical leg bundles: legs are sorted\n * lexicographically by instrument name, and each signed leg amount is\n * `amount × legDirectionSign × directionSign × perspectiveSign`. The maker\n * encodes with their own quote direction (perspective +1); the taker\n * executes with the opposite direction and perspective −1, which cancels\n * back to the maker's exact values — the server rebuilds the taker's\n * order hash from the maker's stored quote.\n */\n\n/** A quoted leg carrying everything the signature commits to. */\nexport interface RfqSignedLeg {\n /** Canonical ordering key — both sides sign legs sorted by this. */\n instrumentName: string;\n /** Protocol asset contract for the instrument (option or perp). */\n assetAddress: string;\n /** Asset sub id: '0' for perps, the option's encoded sub id otherwise. */\n subId: string | bigint;\n /** Per-unit price, always positive; e18-scaled into the signed bytes. */\n price: DecimalLike;\n /** Unsigned size — direction carries the sign. */\n amount: DecimalLike;\n direction: Direction;\n}\n\nexport interface RfqQuoteFields {\n /** Worst fee the signer accepts, in USD; e18 in the signed bytes. */\n maxFee: DecimalLike;\n /** The maker's quote direction (taker execute: the taker's own direction). */\n direction: Direction;\n legs: RfqSignedLeg[];\n}\n\n/** Sorts legs into the canonical order both counterparties sign and send. */\nexport function sortRfqLegs<T extends { instrumentName: string }>(legs: readonly T[]): T[] {\n return [...legs].sort((a, b) =>\n a.instrumentName < b.instrumentName ? -1 : a.instrumentName > b.instrumentName ? 1 : 0,\n );\n}\n\nconst LEGS_ABI = '(address,uint256,uint256,int256)[]';\n\n/**\n * Maker quote data: `abi.encode(RfqOrder{ maxFee, trades[] })` —\n * `[0x20][maxFee][0x40][N][4 words per leg]`. Unlike the trade codec's\n * low-16-byte i128 amount words, a negative leg amount here is a full\n * sign-extended int256 (high 16 bytes 0xff), which is standard AbiCoder\n * output.\n */\nexport function encodeRfqQuote(fields: RfqQuoteFields): string {\n return AbiCoder.defaultAbiCoder().encode(\n [`(uint256,${LEGS_ABI})`],\n [[maxFeeE18(fields.maxFee), legTuples(fields, 1n)]],\n );\n}\n\n/**\n * Taker execute data: `abi.encode(bytes32 orderHash, uint256 maxFee)` where\n * `orderHash = keccak256(abi.encode(trades[]))` — the maker's legs only,\n * maker maxFee excluded. `fields.direction` is the TAKER's direction\n * (opposite of the quote's); `fields.maxFee` is the taker's own fee cap.\n */\nexport function encodeRfqExecute(fields: RfqQuoteFields): string {\n const coder = AbiCoder.defaultAbiCoder();\n const orderHash = keccak256(coder.encode([LEGS_ABI], [legTuples(fields, -1n)]));\n return coder.encode(['bytes32', 'uint256'], [orderHash, maxFeeE18(fields.maxFee)]);\n}\n\nfunction maxFeeE18(maxFee: DecimalLike): bigint {\n return unsignedE18(maxFee, 'rfq maxFee');\n}\n\nfunction legTuples(\n fields: RfqQuoteFields,\n perspectiveSign: 1n | -1n,\n): Array<readonly [string, bigint, bigint, bigint]> {\n const directionSign = fields.direction === 'buy' ? 1n : -1n;\n return sortRfqLegs(fields.legs).map((leg) => {\n const price = unsignedE18(leg.price, `rfq leg price (${leg.instrumentName})`);\n const amount = toE18(leg.amount);\n if (amount <= 0n)\n throw new Error(`rfq leg amount must be positive (direction carries the sign): ${leg.instrumentName}`);\n assertE12Precision(amount, `rfq leg amount (${leg.instrumentName})`);\n const legSign = leg.direction === 'buy' ? 1n : -1n;\n return [\n getAddress(leg.assetAddress),\n BigInt(leg.subId),\n price,\n amount * legSign * directionSign * perspectiveSign,\n ] as const;\n });\n}\n","import { concat, getAddress, toBeHex, zeroPadValue } from 'ethers';\nimport type { ProtocolScopeCode } from '../auth/scopes';\n\nexport interface CreateSessionKeyData {\n /** Address of the key being authorized. */\n sessionKey: string;\n /**\n * The KEY's lifetime as a unix-seconds timestamp — distinct from the\n * envelope's signature expiry. The key stops working once `expirySec <= now`;\n * pass 0 to deactivate an existing key of this address.\n */\n expirySec: number;\n /**\n * Scope codes granted to the key (auth/scopes.ts). Typed as `number` at\n * this low level so raw codes pass through — the typed\n * `ProtocolScopeCode` enum guides callers, and the server is the\n * authority on which codes are valid.\n */\n scopes: readonly (ProtocolScopeCode | number)[];\n /** Subaccounts the key may act on; empty = ALL current and future subaccounts. */\n subaccountIds: readonly (number | bigint)[];\n}\n\n/**\n * Encodes the create-session-key action data.\n *\n * Hand-packed 32-byte words — NOT standard dynamic ABI (no offset or\n * length-prefix head): `[session_key][expiry_sec][S][A][scope codes…]\n * [subaccount ids…]`, every value right-aligned in its word.\n */\nexport function encodeCreateSessionKeyActionData(data: CreateSessionKeyData): string {\n if (!Number.isInteger(data.expirySec) || data.expirySec < 0) {\n throw new Error(`session key expirySec must be a non-negative unix-seconds integer, got ${data.expirySec}`);\n }\n return concat([\n zeroPadValue(getAddress(data.sessionKey), 32),\n uintWord(data.expirySec),\n uintWord(data.scopes.length),\n uintWord(data.subaccountIds.length),\n ...data.scopes.map((code) => uintWord(code)),\n ...data.subaccountIds.map((id) => uintWord(id)),\n ]);\n}\n\nfunction uintWord(value: number | bigint): string {\n const valid = typeof value === 'bigint' ? value >= 0n : Number.isSafeInteger(value) && value >= 0;\n if (!valid) throw new Error(`expected a non-negative integer, got ${value}`);\n return zeroPadValue(toBeHex(value), 32);\n}\n","import { AbiCoder, getAddress } from 'ethers';\nimport { nonNegativeId, unsignedE18, type DecimalLike } from '../signing/encoding';\n\n/**\n * Spot transfer between subaccounts of the same owner, verified by the\n * transfer module. Position transfers are booked as RFQ trades — this\n * action only ever moves a single spot asset.\n */\nexport interface TransferFields {\n toSubaccountId: number | bigint;\n /**\n * Routing sentinel: 0 credits the existing `toSubaccountId`; non-zero\n * creates a new subaccount under this manager id instead (the\n * destination id is then ignored).\n */\n newSubaccountManager: number | bigint;\n /** Protocol spot-asset address (not the underlying ERC-20). */\n asset: string;\n subId: number | bigint;\n /** Strictly positive; signed at e18. */\n amount: DecimalLike;\n /** Maximum fee the signer authorises, in USD; signed at e18. */\n maxFeeUsd: DecimalLike;\n}\n\nconst TRANSFER_ABI = ['uint256', 'uint256', 'address', 'uint256', 'uint256', 'uint256'];\n\n/** ABI-encodes transfer action data: six static 32-byte words. */\nexport function encodeTransfer(fields: TransferFields): string {\n const amount = unsignedE18(fields.amount, 'amount');\n if (amount === 0n) throw new Error('transfer amount must be strictly positive');\n return AbiCoder.defaultAbiCoder().encode(TRANSFER_ABI, [\n nonNegativeId(fields.toSubaccountId, 'toSubaccountId'),\n nonNegativeId(fields.newSubaccountManager, 'newSubaccountManager'),\n getAddress(fields.asset),\n nonNegativeId(fields.subId, 'subId'),\n amount,\n unsignedE18(fields.maxFeeUsd, 'maxFeeUsd'),\n ]);\n}\n","import { concat, getAddress, zeroPadValue } from 'ethers';\nimport { encodeTransfer, type TransferFields } from './transfer';\n\n/**\n * Spot transfer to another owner's wallet, verified by the external\n * transfer module: the six transfer words plus a seventh holding the\n * recipient wallet.\n */\nexport interface ExternalTransferFields extends TransferFields {\n /**\n * Destination owner wallet. `toSubaccountId` must be one of its existing\n * subaccounts, or 0 to create one for it under `newSubaccountManager`.\n */\n recipient: string;\n}\n\n/**\n * All seven words are static, so the encoding is exactly the transfer\n * payload with the recipient appended as a left-padded address word.\n */\nexport function encodeExternalTransfer(fields: ExternalTransferFields): string {\n return concat([encodeTransfer(fields), zeroPadValue(getAddress(fields.recipient), 32)]);\n}\n","import { concat, getAddress, toBeHex, zeroPadValue } from 'ethers';\n\n/**\n * Whitelisted-recipient delta, verified by the whitelisted-recipient\n * module: the resulting list is `(current ∪ add) \\ remove`.\n */\nexport interface WhitelistedRecipientsFields {\n add: string[];\n remove: string[];\n}\n\n/**\n * Hand-packed layout (2 + A + R words), NOT standard ABI dynamic arrays:\n * word 0 : add count A\n * word 1 : remove count R\n * word 2+i : add[i] (address, left-padded)\n * word 2+A+j : remove[j] (address, left-padded)\n */\nexport function encodeUpdateWhitelistedRecipients(fields: WhitelistedRecipientsFields): string {\n const add = fields.add.map((a) => getAddress(a));\n const remove = fields.remove.map((a) => getAddress(a));\n return concat([\n zeroPadValue(toBeHex(add.length), 32),\n zeroPadValue(toBeHex(remove.length), 32),\n ...add.map((a) => zeroPadValue(a, 32)),\n ...remove.map((a) => zeroPadValue(a, 32)),\n ]);\n}\n","import { AbiCoder, ZeroAddress, getAddress, isHexString } from 'ethers';\nimport { assertE12Precision, toE18, unsignedE18, type DecimalLike } from '../signing/encoding';\n\n/**\n * Vault action encodings — shareholder intents (deposit/withdraw/cancel)\n * and curator actions (create, and the mint/burn settle approvals; in v3\n * anyone can curate a vault). Every vault action is signed under the\n * single vault module; word 0 is a uint256 kind word — the protocol's\n * ONLY discriminator between vault action types, so its high bytes must\n * be exactly zero. Deposits and withdrawals are queued intents the\n * curator later settles at a quoted share price committed to the\n * keccak256 of these exact bytes.\n */\n\nconst abi = AbiCoder.defaultAbiCoder();\n\n/** Word-0 kind discriminators. */\nexport const VAULT_ACTION_KIND = {\n create: 0,\n deposit: 1,\n withdraw: 2,\n cancel: 3,\n mintShares: 4,\n burnShares: 5,\n} as const;\n\n/**\n * Vault amounts are signed at e18 but held at e12 by the protocol,\n * which rejects (not truncates) sub-1e12 precision, and requires\n * strictly positive values.\n */\nfunction positiveE18(label: string, value: DecimalLike): bigint {\n const e18 = toE18(value);\n if (e18 <= 0n) throw new Error(`${label} must be positive`);\n assertE12Precision(e18, label);\n return e18;\n}\n\nexport interface VaultDepositFields {\n vaultSubaccountId: number | bigint;\n /** Must equal the vault's configured deposit asset (protocol spot-asset address). */\n depositSpotAsset: string;\n /** Deposit amount in the deposit asset's units; max 12 decimal places. */\n amount: DecimalLike;\n}\n\n/** VaultDepositData (kind = 1, 4 words): [kind, vaultSubaccountId, depositSpotAsset, amountE18]. */\nexport function encodeVaultDeposit(fields: VaultDepositFields): string {\n return abi.encode(\n ['uint256', 'uint256', 'address', 'uint256'],\n [\n VAULT_ACTION_KIND.deposit,\n fields.vaultSubaccountId,\n getAddress(fields.depositSpotAsset),\n positiveE18('amount', fields.amount),\n ],\n );\n}\n\nexport interface VaultWithdrawFields {\n vaultSubaccountId: number | bigint;\n /** Vault shares to redeem; max 12 decimal places. */\n sharesToBurn: DecimalLike;\n}\n\n/** VaultWithdrawData (kind = 2, 3 words): [kind, vaultSubaccountId, sharesToBurnE18]. */\nexport function encodeVaultWithdraw(fields: VaultWithdrawFields): string {\n return abi.encode(\n ['uint256', 'uint256', 'uint256'],\n [VAULT_ACTION_KIND.withdraw, fields.vaultSubaccountId, positiveE18('sharesToBurn', fields.sharesToBurn)],\n );\n}\n\n/**\n * VaultCancelData (kind = 3, 2 words): [kind, vaultSubaccountId].\n * Invalidates ALL of the signer's pending intents for the vault by\n * bumping their per-(vault, holder) nonce to the envelope's nonce.\n */\nexport function encodeVaultCancel(vaultSubaccountId: number | bigint): string {\n return abi.encode(['uint256', 'uint256'], [VAULT_ACTION_KIND.cancel, vaultSubaccountId]);\n}\n\nexport interface VaultCreateFields {\n managerId: number;\n depositSpotAsset: string;\n /** Initial deposit moved funding subaccount → vault; max 12 decimal places. */\n initialDeposit: DecimalLike;\n managementFeeBps: number;\n performanceFeeBps: number;\n maxSlippageBps: number;\n cooldownSec: number | bigint;\n /** Maximum settlement fee authorised, in USD; max 12 decimal places. */\n maxFeeUsd: DecimalLike;\n /** Share price the vault is seeded at, in USD; max 12 decimal places. */\n initialSharePriceUsd: DecimalLike;\n /**\n * Spot asset the high-water mark is denominated in. Omit for the\n * feed-less USD default — presence drives the encoded hasBenchmark\n * flag, exactly as the exchange derives it from the wire param.\n */\n benchmarkAsset?: string;\n}\n\n/**\n * Create-vault action data (kind = 0, 12 words):\n * [kind, managerId, depositSpotAsset, initialDepositE18,\n * managementFeeBps, performanceFeeBps, maxSlippageBps, cooldownSec,\n * maxFeeUsdE18, initialSharePriceUsdE18, benchmarkAsset, hasBenchmark].\n * bps rates and cooldownSec are plain integers, not e18.\n */\nexport function encodeVaultCreate(fields: VaultCreateFields): string {\n const benchmark = fields.benchmarkAsset;\n return abi.encode(\n // prettier-ignore\n ['uint256', 'uint256', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'address', 'bool'],\n [\n VAULT_ACTION_KIND.create,\n fields.managerId,\n getAddress(fields.depositSpotAsset),\n unsignedE18(fields.initialDeposit, 'initialDeposit'),\n fields.managementFeeBps,\n fields.performanceFeeBps,\n fields.maxSlippageBps,\n fields.cooldownSec,\n unsignedE18(fields.maxFeeUsd, 'maxFeeUsd'),\n unsignedE18(fields.initialSharePriceUsd, 'initialSharePriceUsd'),\n benchmark === undefined ? ZeroAddress : getAddress(benchmark),\n benchmark !== undefined,\n ],\n );\n}\n\nexport interface VaultMintSharesFields {\n /** Quoted share price in USD per share; max 12 decimal places. */\n sharePrice: DecimalLike;\n /** keccak256 of the user's exact encoded deposit action (0x hex, 32 bytes). */\n depositHash: string;\n}\n\n/** MintSharesActionData (kind = 4): the curator's approval to settle one queued deposit. */\nexport function encodeVaultMintShares(fields: VaultMintSharesFields): string {\n return encodeSettleApproval(VAULT_ACTION_KIND.mintShares, fields.sharePrice, 'depositHash', fields.depositHash);\n}\n\nexport interface VaultBurnSharesFields {\n /** Quoted share price in USD per share; max 12 decimal places. */\n sharePrice: DecimalLike;\n /** keccak256 of the user's exact encoded withdraw action (0x hex, 32 bytes). */\n withdrawHash: string;\n}\n\n/** BurnSharesActionData (kind = 5): the curator's approval to settle one queued withdrawal. */\nexport function encodeVaultBurnShares(fields: VaultBurnSharesFields): string {\n return encodeSettleApproval(VAULT_ACTION_KIND.burnShares, fields.sharePrice, 'withdrawHash', fields.withdrawHash);\n}\n\n/**\n * Shared mint/burn layout (3 words): [kind, sharePriceE18, userActionHash].\n * The hash commits the quoted price to one exact user action, so the\n * exchange cannot pair it with a different deposit/withdrawal.\n */\nfunction encodeSettleApproval(kind: number, sharePrice: DecimalLike, hashLabel: string, hash: string): string {\n if (!isHexString(hash, 32)) throw new Error(`${hashLabel} must be a 0x-prefixed 32-byte hex string`);\n return abi.encode(['uint256', 'uint256', 'bytes32'], [kind, unsignedE18(sharePrice, 'sharePrice'), hash]);\n}\n","import { AbiCoder, getAddress } from 'ethers';\nimport { toScaled, unsignedE18, type DecimalLike } from '../signing/encoding';\n\n/** Withdrawal to L1, verified by the withdrawal module. */\nexport interface WithdrawalFields {\n /** Protocol asset contract address (not the underlying ERC-20). */\n protocolAsset: string;\n /** Maximum fee the signer authorises, in USD; signed at e18 like every other action value. */\n maxFeeUsd: DecimalLike;\n /** L1 payout recipient. The exchange only constructs withdrawals paying out to the action signer. */\n recipient: string;\n /**\n * Withdrawal amount in the ERC-20's NATIVE decimals (USDC = 6) — the\n * protocol's only never-scaled amount, since it is the L1 payout value.\n */\n amount: DecimalLike;\n /** The underlying ERC-20's decimals, used to scale `amount`. */\n decimals: number;\n /** Pay a higher fee for immediate batch proving. */\n forceBatch: boolean;\n}\n\nconst WITHDRAWAL_ABI = ['address', 'uint256', 'address', 'uint256', 'bool'];\n\n/** ABI-encodes withdrawal action data: five static 32-byte words. */\nexport function encodeWithdrawal(fields: WithdrawalFields): string {\n if (!Number.isInteger(fields.decimals) || fields.decimals < 0 || fields.decimals > 255) {\n throw new Error('decimals must be an integer between 0 and 255');\n }\n const amount = toScaled(fields.amount, fields.decimals);\n if (amount <= 0n) throw new Error('withdrawal amount must be strictly positive');\n return AbiCoder.defaultAbiCoder().encode(WITHDRAWAL_ABI, [\n getAddress(fields.protocolAsset),\n unsignedE18(fields.maxFeeUsd, 'maxFeeUsd'),\n getAddress(fields.recipient),\n amount,\n fields.forceBatch,\n ]);\n}\n"],"mappings":"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","names":["assertE12Precision","getAddress","getAddress","concat","getAddress","toBeHex","zeroPadValue","uintWord","AbiCoder","getAddress","AbiCoder","getAddress","concat","getAddress","zeroPadValue","concat","zeroPadValue","getAddress","concat","getAddress","toBeHex","zeroPadValue","AbiCoder","getAddress","AbiCoder","getAddress","AbiCoder","getAddress","AbiCoder","getAddress"]}