@totalis/hip4-client 0.0.0-stage → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +1903 -0
  2. package/LICENSE +202 -0
  3. package/README.md +143 -2
  4. package/dist/api-keys.d.ts +118 -0
  5. package/dist/api-keys.js +104 -0
  6. package/dist/audience-contract.d.ts +52 -0
  7. package/dist/audience-contract.js +1 -0
  8. package/dist/base.d.ts +41 -0
  9. package/dist/base.js +60 -0
  10. package/dist/client.d.ts +16 -0
  11. package/dist/client.js +15 -0
  12. package/dist/controller.d.ts +53 -0
  13. package/dist/controller.js +63 -0
  14. package/dist/errors.d.ts +3 -0
  15. package/dist/errors.js +3 -0
  16. package/dist/flow-errors.d.ts +62 -0
  17. package/dist/flow-errors.js +70 -0
  18. package/dist/generated/account-read.d.ts +2015 -0
  19. package/dist/generated/account-read.js +1 -0
  20. package/dist/generated/api-keys.d.ts +1303 -0
  21. package/dist/generated/api-keys.js +1 -0
  22. package/dist/generated/browse.d.ts +965 -0
  23. package/dist/generated/browse.js +1 -0
  24. package/dist/generated/client.d.ts +104 -0
  25. package/dist/generated/client.js +1 -0
  26. package/dist/generated/errors.d.ts +467 -0
  27. package/dist/generated/errors.js +1792 -0
  28. package/dist/generated/maker.d.ts +47 -0
  29. package/dist/generated/maker.js +1 -0
  30. package/dist/generated/openapi.d.ts +10867 -0
  31. package/dist/generated/openapi.js +1 -0
  32. package/dist/generated/operator.d.ts +7 -0
  33. package/dist/generated/operator.js +1 -0
  34. package/dist/generated/realtime.d.ts +1486 -0
  35. package/dist/generated/realtime.js +1 -0
  36. package/dist/generated/vault-abi.d.ts +49 -0
  37. package/dist/generated/vault-abi.js +70 -0
  38. package/dist/generated/version.d.ts +24 -0
  39. package/dist/generated/version.js +183 -0
  40. package/dist/hypercore.d.ts +111 -0
  41. package/dist/hypercore.js +132 -0
  42. package/dist/maker.d.ts +206 -0
  43. package/dist/maker.js +608 -0
  44. package/dist/money.d.ts +59 -0
  45. package/dist/money.js +66 -0
  46. package/dist/operator.d.ts +4 -0
  47. package/dist/operator.js +4 -0
  48. package/dist/pagination.d.ts +20 -0
  49. package/dist/pagination.js +27 -0
  50. package/dist/partner.d.ts +71 -0
  51. package/dist/partner.js +66 -0
  52. package/dist/realtime.d.ts +19 -0
  53. package/dist/realtime.js +10 -0
  54. package/dist/request.d.ts +115 -0
  55. package/dist/request.js +190 -0
  56. package/dist/signer.d.ts +51 -0
  57. package/dist/signer.js +90 -0
  58. package/dist/stream.d.ts +142 -0
  59. package/dist/stream.js +331 -0
  60. package/dist/totalis.d.ts +719 -0
  61. package/dist/totalis.js +1251 -0
  62. package/dist/typed-data.d.ts +330 -0
  63. package/dist/typed-data.js +340 -0
  64. package/dist/ui.d.ts +54 -0
  65. package/dist/ui.js +77 -0
  66. package/dist/wallet-session.d.ts +52 -0
  67. package/dist/wallet-session.js +63 -0
  68. package/examples/browser-wallet.html +69 -0
  69. package/examples/browser-wallet.mjs +57 -0
  70. package/examples/node-bot.mjs +57 -0
  71. package/examples/onboard.mjs +132 -0
  72. package/examples/partner-frontend.mjs +44 -0
  73. package/examples/partner-server.mjs +22 -0
  74. package/package.json +105 -4
@@ -0,0 +1,330 @@
1
+ import type { components } from "./generated/openapi.js";
2
+ type Schemas = components["schemas"];
3
+ export type Hex = `0x${string}`;
4
+ /**
5
+ * EIP-712 types for every Totalis vault signature. One module signs for
6
+ * takers and makers alike; the account's signer (the `wallet` of GET /v1/me,
7
+ * or maker capital's `signer`) is the address that signs.
8
+ */
9
+ export declare const totalisTypes: {
10
+ readonly Leg: readonly [{
11
+ readonly name: "outcomeId";
12
+ readonly type: "uint32";
13
+ }, {
14
+ readonly name: "side";
15
+ readonly type: "uint8";
16
+ }];
17
+ readonly Quote: readonly [{
18
+ readonly name: "makerId";
19
+ readonly type: "uint64";
20
+ }, {
21
+ readonly name: "legs";
22
+ readonly type: "Leg[]";
23
+ }, {
24
+ readonly name: "stake";
25
+ readonly type: "uint128";
26
+ }, {
27
+ readonly name: "payout";
28
+ readonly type: "uint128";
29
+ }, {
30
+ readonly name: "feeBps";
31
+ readonly type: "uint16";
32
+ }, {
33
+ readonly name: "takerFeeBps";
34
+ readonly type: "uint16";
35
+ }, {
36
+ readonly name: "taker";
37
+ readonly type: "address";
38
+ }, {
39
+ readonly name: "expiry";
40
+ readonly type: "uint64";
41
+ }, {
42
+ readonly name: "salt";
43
+ readonly type: "uint256";
44
+ }];
45
+ readonly TakerAccept: readonly [{
46
+ readonly name: "makerId";
47
+ readonly type: "uint64";
48
+ }, {
49
+ readonly name: "legs";
50
+ readonly type: "Leg[]";
51
+ }, {
52
+ readonly name: "stake";
53
+ readonly type: "uint128";
54
+ }, {
55
+ readonly name: "payout";
56
+ readonly type: "uint128";
57
+ }, {
58
+ readonly name: "feeBps";
59
+ readonly type: "uint16";
60
+ }, {
61
+ readonly name: "takerFeeBps";
62
+ readonly type: "uint16";
63
+ }, {
64
+ readonly name: "taker";
65
+ readonly type: "address";
66
+ }, {
67
+ readonly name: "expiry";
68
+ readonly type: "uint64";
69
+ }, {
70
+ readonly name: "salt";
71
+ readonly type: "uint256";
72
+ }];
73
+ readonly SellBack: readonly [{
74
+ readonly name: "ticketId";
75
+ readonly type: "bytes32";
76
+ }, {
77
+ readonly name: "price";
78
+ readonly type: "uint128";
79
+ }, {
80
+ readonly name: "owner";
81
+ readonly type: "address";
82
+ }, {
83
+ readonly name: "expiry";
84
+ readonly type: "uint64";
85
+ }, {
86
+ readonly name: "salt";
87
+ readonly type: "uint256";
88
+ }, {
89
+ readonly name: "legState";
90
+ readonly type: "bytes32";
91
+ }];
92
+ readonly CashoutTransfer: readonly [{
93
+ readonly name: "ticketId";
94
+ readonly type: "bytes32";
95
+ }, {
96
+ readonly name: "price";
97
+ readonly type: "uint128";
98
+ }, {
99
+ readonly name: "seller";
100
+ readonly type: "address";
101
+ }, {
102
+ readonly name: "buyer";
103
+ readonly type: "address";
104
+ }, {
105
+ readonly name: "expiry";
106
+ readonly type: "uint64";
107
+ }, {
108
+ readonly name: "salt";
109
+ readonly type: "uint256";
110
+ }, {
111
+ readonly name: "legState";
112
+ readonly type: "bytes32";
113
+ }];
114
+ readonly Exit: readonly [{
115
+ readonly name: "account";
116
+ readonly type: "address";
117
+ }, {
118
+ readonly name: "to";
119
+ readonly type: "address";
120
+ }, {
121
+ readonly name: "amount";
122
+ readonly type: "uint128";
123
+ }, {
124
+ readonly name: "expiry";
125
+ readonly type: "uint64";
126
+ }, {
127
+ readonly name: "salt";
128
+ readonly type: "uint256";
129
+ }];
130
+ };
131
+ export type TotalisPrimaryType = Exclude<keyof typeof totalisTypes, "Leg">;
132
+ /** Who signs each struct: the maker's signer, the taker's signer, or either side of a cash-out. */
133
+ export declare const signedBy: {
134
+ readonly Quote: "maker";
135
+ readonly TakerAccept: "taker";
136
+ readonly SellBack: "maker_and_taker";
137
+ readonly CashoutTransfer: "maker_and_taker";
138
+ readonly Exit: "taker";
139
+ };
140
+ /** The vault deployment a signature binds to: GET /v1/deployment. */
141
+ export type TotalisDeployment = Pick<Schemas["Deployment"], "chain_id" | "vault_address">;
142
+ /** The deployment's canonical USDC and its signing domain, as GET /v1/deployment publishes them. */
143
+ export type UsdcDeployment = Pick<Schemas["Deployment"], "usdc">;
144
+ export type TypedDataField = {
145
+ readonly name: string;
146
+ readonly type: string;
147
+ };
148
+ /** An EIP-712 payload with the domain every Totalis and USDC signature uses. */
149
+ export type TypedData = {
150
+ readonly domain: {
151
+ readonly name: string;
152
+ readonly version: string;
153
+ readonly chainId: bigint;
154
+ readonly verifyingContract: Hex;
155
+ };
156
+ readonly types: {
157
+ readonly [name: string]: readonly TypedDataField[];
158
+ };
159
+ readonly primaryType: string;
160
+ readonly message: Record<string, unknown>;
161
+ };
162
+ export type TotalisTypedData<P extends TotalisPrimaryType = TotalisPrimaryType> = {
163
+ readonly domain: {
164
+ readonly name: "TotalisParlay";
165
+ readonly version: "1";
166
+ readonly chainId: bigint;
167
+ readonly verifyingContract: Hex;
168
+ };
169
+ readonly types: Pick<typeof totalisTypes, P | (P extends "Quote" | "TakerAccept" ? "Leg" : never)>;
170
+ readonly primaryType: P;
171
+ readonly message: Record<string, unknown>;
172
+ };
173
+ /** A 20-byte hex address, or a TypeError. */
174
+ export declare function address(value: string): Hex;
175
+ /** A leg as the API names it: a decimal outcome ID and YES or NO. */
176
+ export type Leg = Schemas["QuoteTerms"]["legs"][number];
177
+ /** The signed Leg: outcome_id as uint32, and side 0 for YES, 1 for NO. */
178
+ export declare function signedLeg(leg: Leg): {
179
+ readonly outcomeId: number;
180
+ readonly side: 0 | 1;
181
+ };
182
+ /** A maker's signed quote on an entry RFQ, from a quote's `entry` terms. */
183
+ export declare function quoteTypedData(deployment: TotalisDeployment, quote: Schemas["QuoteTerms"]): TotalisTypedData<"Quote">;
184
+ /** A taker's acceptance of the exact quote it received. */
185
+ export declare function takerAcceptTypedData(deployment: TotalisDeployment, quote: Schemas["QuoteTerms"]): TotalisTypedData<"TakerAccept">;
186
+ /**
187
+ * A sell-back of a position to its original maker; the maker quotes it and the
188
+ * owner accepts it. `position_id` from the API goes into `ticketId`.
189
+ */
190
+ export declare function sellBackTypedData(deployment: TotalisDeployment, bid: Schemas["SellBackTerms"]): TotalisTypedData<"SellBack">;
191
+ /**
192
+ * A cash-out transfer of a position to a buying maker; the maker quotes it and
193
+ * the seller accepts it. `position_id` from the API goes into `ticketId`.
194
+ */
195
+ export declare function cashoutTransferTypedData(deployment: TotalisDeployment, bid: Schemas["TransferTerms"]): TotalisTypedData<"CashoutTransfer">;
196
+ /** The vault leg of a withdrawal: the Exit that moves its shortfall from the vault to the wallet. */
197
+ export declare function exitTypedData(deployment: TotalisDeployment, exit: {
198
+ account: Hex;
199
+ to: Hex;
200
+ amount: string;
201
+ expiry: string;
202
+ salt: string;
203
+ }): TotalisTypedData<"Exit">;
204
+ export declare const domainTypes: {
205
+ readonly EIP712Domain: readonly [{
206
+ readonly name: "name";
207
+ readonly type: "string";
208
+ }, {
209
+ readonly name: "version";
210
+ readonly type: "string";
211
+ }, {
212
+ readonly name: "chainId";
213
+ readonly type: "uint256";
214
+ }, {
215
+ readonly name: "verifyingContract";
216
+ readonly type: "address";
217
+ }];
218
+ };
219
+ export declare function hashDomain(domain: TypedData["domain"]): Hex;
220
+ /** The EIP-712 digest a signer signs; for a Quote this is the `quote_digest` the API expects. */
221
+ export declare function hashTypedData(typedData: TypedData): Hex;
222
+ export declare const usdcTypes: {
223
+ readonly ReceiveWithAuthorization: readonly [{
224
+ readonly name: "from";
225
+ readonly type: "address";
226
+ }, {
227
+ readonly name: "to";
228
+ readonly type: "address";
229
+ }, {
230
+ readonly name: "value";
231
+ readonly type: "uint256";
232
+ }, {
233
+ readonly name: "validAfter";
234
+ readonly type: "uint256";
235
+ }, {
236
+ readonly name: "validBefore";
237
+ readonly type: "uint256";
238
+ }, {
239
+ readonly name: "nonce";
240
+ readonly type: "bytes32";
241
+ }];
242
+ readonly TransferWithAuthorization: readonly [{
243
+ readonly name: "from";
244
+ readonly type: "address";
245
+ }, {
246
+ readonly name: "to";
247
+ readonly type: "address";
248
+ }, {
249
+ readonly name: "value";
250
+ readonly type: "uint256";
251
+ }, {
252
+ readonly name: "validAfter";
253
+ readonly type: "uint256";
254
+ }, {
255
+ readonly name: "validBefore";
256
+ readonly type: "uint256";
257
+ }, {
258
+ readonly name: "nonce";
259
+ readonly type: "bytes32";
260
+ }];
261
+ };
262
+ export type UsdcTypedData<P extends keyof typeof usdcTypes> = {
263
+ readonly domain: {
264
+ readonly name: "USDC";
265
+ readonly version: "2";
266
+ readonly chainId: bigint;
267
+ readonly verifyingContract: Hex;
268
+ };
269
+ readonly types: Pick<typeof usdcTypes, P>;
270
+ readonly primaryType: P;
271
+ readonly message: Record<string, unknown>;
272
+ };
273
+ /** The USDC EIP-712 domain GET /v1/deployment publishes, in signer form. */
274
+ export declare function usdcDomain(deployment: UsdcDeployment): UsdcTypedData<"ReceiveWithAuthorization">["domain"];
275
+ /** The wallet side of an accept's funding, in the API's wire shape. */
276
+ export type DepositAuthorization = {
277
+ readonly account: string;
278
+ readonly from: string;
279
+ readonly value: string;
280
+ readonly valid_after: string;
281
+ readonly valid_before: string;
282
+ readonly salt: string;
283
+ };
284
+ /** The EIP-3009 nonce `ParlayVault.depositUserWithAuthorization` derives: keccak256(abi.encode(vault, chainId, from, salt, account)). */
285
+ export declare function depositNonce(deployment: TotalisDeployment, authorization: Pick<DepositAuthorization, "account" | "from" | "salt">): Hex;
286
+ /** USDC ReceiveWithAuthorization that lets the vault pull a deposit from the wallet. */
287
+ export declare function receiveWithAuthorizationTypedData(deployment: TotalisDeployment & UsdcDeployment, authorization: DepositAuthorization): UsdcTypedData<"ReceiveWithAuthorization">;
288
+ /**
289
+ * The EIP-3009 nonce of a withdrawal's payout, derived from the create's
290
+ * Idempotency-Key: keccak256(abi.encode(keccak256("TOTALIS_PAYOUT_NONCE_V1"),
291
+ * chainId, vault, account, uint128(idempotencyKey))). Sign the payout under it;
292
+ * the venue derives the same bytes, so the signature is bound to that one request.
293
+ */
294
+ export declare function payoutNonce(deployment: TotalisDeployment, account: string, idempotencyKey: string): Hex;
295
+ /** A withdrawal's payout: exactly `value` from the embedded wallet to `to`, under `payoutNonce`. */
296
+ export type PayoutAuthorization = {
297
+ readonly from: string;
298
+ readonly to: string;
299
+ readonly value: string;
300
+ readonly valid_after: string;
301
+ readonly valid_before: string;
302
+ readonly nonce: Hex;
303
+ };
304
+ /** USDC TransferWithAuthorization for a Totalis withdrawal payout, under the deployment's USDC domain. */
305
+ export declare function transferWithAuthorizationTypedData(deployment: UsdcDeployment, payout: PayoutAuthorization): UsdcTypedData<"TransferWithAuthorization">;
306
+ /** Any EIP-712 signer. `toTotalisSigner` in `./signer` adapts viem, EIP-1193 and ethers signers. */
307
+ export type TypedDataSigner = {
308
+ signTypedData(typedData: TypedData): Promise<Hex>;
309
+ };
310
+ /** Lowercase hex with v in {27, 28}: the venue rejects v = 0/1. Longer ERC-1271 signatures pass through. */
311
+ export declare function canonicalSignature(signature: string): Hex;
312
+ /** Sign with the account's signer and return the canonical signature the API expects. */
313
+ export declare function signTotalis(signer: TypedDataSigner, typedData: TypedData): Promise<Hex>;
314
+ /** The canonical EIP-712 type string, for checking a signer's encoding against the contract. */
315
+ export declare function encodeType(primaryType: TotalisPrimaryType): string;
316
+ export type IssueKeyMessage = {
317
+ readonly domain: string;
318
+ readonly address: string;
319
+ readonly uri: string;
320
+ readonly chainId: string;
321
+ readonly nonce: string;
322
+ readonly issuedAt: string;
323
+ readonly expirationTime: string;
324
+ readonly requestId: string;
325
+ readonly resources: readonly string[];
326
+ };
327
+ export declare function issueKeyMessage(message: IssueKeyMessage): string;
328
+ /** Parse an issue_key message; throws unless it is exactly the server's template. */
329
+ export declare function parseIssueKeyMessage(text: string): IssueKeyMessage;
330
+ export {};
@@ -0,0 +1,340 @@
1
+ import { keccak_256 } from "@noble/hashes/sha3";
2
+ // A maker's quote and the taker's acceptance sign the same offer fields.
3
+ const offerFields = [
4
+ { name: "makerId", type: "uint64" },
5
+ { name: "legs", type: "Leg[]" },
6
+ { name: "stake", type: "uint128" },
7
+ { name: "payout", type: "uint128" },
8
+ { name: "feeBps", type: "uint16" },
9
+ { name: "takerFeeBps", type: "uint16" },
10
+ { name: "taker", type: "address" },
11
+ { name: "expiry", type: "uint64" },
12
+ { name: "salt", type: "uint256" },
13
+ ];
14
+ /**
15
+ * EIP-712 types for every Totalis vault signature. One module signs for
16
+ * takers and makers alike; the account's signer (the `wallet` of GET /v1/me,
17
+ * or maker capital's `signer`) is the address that signs.
18
+ */
19
+ export const totalisTypes = {
20
+ Leg: [
21
+ { name: "outcomeId", type: "uint32" },
22
+ { name: "side", type: "uint8" },
23
+ ],
24
+ Quote: offerFields,
25
+ TakerAccept: offerFields,
26
+ SellBack: [
27
+ { name: "ticketId", type: "bytes32" },
28
+ { name: "price", type: "uint128" },
29
+ { name: "owner", type: "address" },
30
+ { name: "expiry", type: "uint64" },
31
+ { name: "salt", type: "uint256" },
32
+ { name: "legState", type: "bytes32" },
33
+ ],
34
+ CashoutTransfer: [
35
+ { name: "ticketId", type: "bytes32" },
36
+ { name: "price", type: "uint128" },
37
+ { name: "seller", type: "address" },
38
+ { name: "buyer", type: "address" },
39
+ { name: "expiry", type: "uint64" },
40
+ { name: "salt", type: "uint256" },
41
+ { name: "legState", type: "bytes32" },
42
+ ],
43
+ Exit: [
44
+ { name: "account", type: "address" },
45
+ { name: "to", type: "address" },
46
+ { name: "amount", type: "uint128" },
47
+ { name: "expiry", type: "uint64" },
48
+ { name: "salt", type: "uint256" },
49
+ ],
50
+ };
51
+ /** Who signs each struct: the maker's signer, the taker's signer, or either side of a cash-out. */
52
+ export const signedBy = {
53
+ Quote: "maker",
54
+ TakerAccept: "taker",
55
+ SellBack: "maker_and_taker",
56
+ CashoutTransfer: "maker_and_taker",
57
+ Exit: "taker",
58
+ };
59
+ /** A 20-byte hex address, or a TypeError. */
60
+ export function address(value) {
61
+ if (!/^0x[0-9a-fA-F]{40}$/.test(value))
62
+ throw new TypeError(`not an address: ${value}`);
63
+ return value;
64
+ }
65
+ function domain(deployment) {
66
+ return { name: "TotalisParlay", version: "1", chainId: BigInt(deployment.chain_id), verifyingContract: address(deployment.vault_address) };
67
+ }
68
+ /** The signed Leg: outcome_id as uint32, and side 0 for YES, 1 for NO. */
69
+ export function signedLeg(leg) {
70
+ const outcomeId = Number(leg.outcome_id);
71
+ if (!/^(0|[1-9][0-9]*)$/.test(leg.outcome_id) || outcomeId > 0xffffffff)
72
+ throw new TypeError(`not a uint32 outcome_id: ${leg.outcome_id}`);
73
+ if (leg.side !== "YES" && leg.side !== "NO")
74
+ throw new TypeError(`not a side: ${String(leg.side)}`);
75
+ return { outcomeId, side: leg.side === "YES" ? 0 : 1 };
76
+ }
77
+ function offer(quote) {
78
+ return {
79
+ makerId: BigInt(quote.maker_id),
80
+ legs: quote.legs.map(signedLeg),
81
+ stake: BigInt(quote.stake),
82
+ payout: BigInt(quote.payout),
83
+ feeBps: quote.fee_bps,
84
+ takerFeeBps: quote.taker_fee_bps,
85
+ taker: quote.taker,
86
+ expiry: BigInt(quote.expiry),
87
+ salt: BigInt(quote.salt),
88
+ };
89
+ }
90
+ /** A maker's signed quote on an entry RFQ, from a quote's `entry` terms. */
91
+ export function quoteTypedData(deployment, quote) {
92
+ return { domain: domain(deployment), types: { Quote: totalisTypes.Quote, Leg: totalisTypes.Leg }, primaryType: "Quote", message: offer(quote) };
93
+ }
94
+ /** A taker's acceptance of the exact quote it received. */
95
+ export function takerAcceptTypedData(deployment, quote) {
96
+ return { domain: domain(deployment), types: { TakerAccept: totalisTypes.TakerAccept, Leg: totalisTypes.Leg }, primaryType: "TakerAccept", message: offer(quote) };
97
+ }
98
+ /**
99
+ * A sell-back of a position to its original maker; the maker quotes it and the
100
+ * owner accepts it. `position_id` from the API goes into `ticketId`.
101
+ */
102
+ export function sellBackTypedData(deployment, bid) {
103
+ return {
104
+ domain: domain(deployment), types: { SellBack: totalisTypes.SellBack }, primaryType: "SellBack",
105
+ message: { ticketId: bid.position_id, price: BigInt(bid.price), owner: bid.owner, expiry: BigInt(bid.expiry), salt: BigInt(bid.salt), legState: bid.leg_state },
106
+ };
107
+ }
108
+ /**
109
+ * A cash-out transfer of a position to a buying maker; the maker quotes it and
110
+ * the seller accepts it. `position_id` from the API goes into `ticketId`.
111
+ */
112
+ export function cashoutTransferTypedData(deployment, bid) {
113
+ return {
114
+ domain: domain(deployment), types: { CashoutTransfer: totalisTypes.CashoutTransfer }, primaryType: "CashoutTransfer",
115
+ message: { ticketId: bid.position_id, price: BigInt(bid.price), seller: bid.seller, buyer: bid.buyer, expiry: BigInt(bid.expiry), salt: BigInt(bid.salt), legState: bid.leg_state },
116
+ };
117
+ }
118
+ /** The vault leg of a withdrawal: the Exit that moves its shortfall from the vault to the wallet. */
119
+ export function exitTypedData(deployment, exit) {
120
+ return {
121
+ domain: domain(deployment), types: { Exit: totalisTypes.Exit }, primaryType: "Exit",
122
+ message: { account: exit.account, to: exit.to, amount: BigInt(exit.amount), expiry: BigInt(exit.expiry), salt: BigInt(exit.salt) },
123
+ };
124
+ }
125
+ // ---------------------------------------------------------------------------
126
+ // EIP-712 hashing, so makers can compute quote_digest and every signer can be
127
+ // checked against the contract's vectors without a wallet library.
128
+ const utf8 = new TextEncoder();
129
+ function hexBytes(value) {
130
+ if (!/^0x(?:[0-9a-fA-F]{2})*$/.test(value))
131
+ throw new TypeError(`not hex: ${value}`);
132
+ return Uint8Array.from(value.slice(2).match(/../g) ?? [], (byte) => Number.parseInt(byte, 16));
133
+ }
134
+ function toHex(value) {
135
+ return `0x${Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
136
+ }
137
+ function concat(parts) {
138
+ const out = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
139
+ let offset = 0;
140
+ for (const part of parts) {
141
+ out.set(part, offset);
142
+ offset += part.length;
143
+ }
144
+ return out;
145
+ }
146
+ function uintWord(value, bits = 256) {
147
+ if (value < 0n || value >= 1n << BigInt(bits))
148
+ throw new RangeError(`${value} does not fit uint${bits}`);
149
+ return hexBytes(`0x${value.toString(16).padStart(64, "0")}`);
150
+ }
151
+ function bytes32(value) {
152
+ const word = hexBytes(value);
153
+ if (word.length !== 32)
154
+ throw new TypeError(`not bytes32: ${value}`);
155
+ return word;
156
+ }
157
+ function fields(types, name) {
158
+ const struct = types[name];
159
+ if (struct === undefined)
160
+ throw new TypeError(`unknown EIP-712 struct ${name}`);
161
+ return struct;
162
+ }
163
+ function structType(types, primary) {
164
+ const referenced = new Set();
165
+ const visit = (name) => {
166
+ for (const field of fields(types, name)) {
167
+ const base = field.type.replace(/\[\]$/, "");
168
+ if (types[base] !== undefined && base !== primary && !referenced.has(base)) {
169
+ referenced.add(base);
170
+ visit(base);
171
+ }
172
+ }
173
+ };
174
+ visit(primary);
175
+ return [primary, ...[...referenced].sort()]
176
+ .map((name) => `${name}(${fields(types, name).map((field) => `${field.type} ${field.name}`).join(",")})`)
177
+ .join("");
178
+ }
179
+ function encodeValue(types, type, value) {
180
+ if (type.endsWith("[]")) {
181
+ if (!Array.isArray(value))
182
+ throw new TypeError(`${type} needs an array`);
183
+ return keccak_256(concat(value.map((item) => encodeValue(types, type.slice(0, -2), item))));
184
+ }
185
+ if (types[type] !== undefined) {
186
+ if (typeof value !== "object" || value === null || Array.isArray(value))
187
+ throw new TypeError(`${type} needs an object`);
188
+ return hashStruct(types, type, Object.fromEntries(Object.entries(value)));
189
+ }
190
+ if (type === "address")
191
+ return uintWord(BigInt(address(String(value))));
192
+ if (type === "bytes32")
193
+ return bytes32(String(value));
194
+ if (type === "string")
195
+ return keccak_256(utf8.encode(String(value)));
196
+ const uint = /^uint(\d+)$/.exec(type);
197
+ if (uint?.[1] !== undefined) {
198
+ if (typeof value !== "bigint" && typeof value !== "number" && typeof value !== "string")
199
+ throw new TypeError(`${type} needs an integer`);
200
+ return uintWord(BigInt(value), Number(uint[1]));
201
+ }
202
+ throw new TypeError(`unsupported EIP-712 type ${type}`);
203
+ }
204
+ function hashStruct(types, name, message) {
205
+ return keccak_256(concat([
206
+ keccak_256(utf8.encode(structType(types, name))),
207
+ ...fields(types, name).map((field) => encodeValue(types, field.type, message[field.name])),
208
+ ]));
209
+ }
210
+ export const domainTypes = {
211
+ EIP712Domain: [
212
+ { name: "name", type: "string" },
213
+ { name: "version", type: "string" },
214
+ { name: "chainId", type: "uint256" },
215
+ { name: "verifyingContract", type: "address" },
216
+ ],
217
+ };
218
+ export function hashDomain(domain) {
219
+ return toHex(hashStruct(domainTypes, "EIP712Domain", domain));
220
+ }
221
+ /** The EIP-712 digest a signer signs; for a Quote this is the `quote_digest` the API expects. */
222
+ export function hashTypedData(typedData) {
223
+ return toHex(keccak_256(concat([
224
+ Uint8Array.of(0x19, 0x01),
225
+ hashStruct(domainTypes, "EIP712Domain", typedData.domain),
226
+ hashStruct(typedData.types, typedData.primaryType, typedData.message),
227
+ ])));
228
+ }
229
+ // ---------------------------------------------------------------------------
230
+ // USDC (EIP-3009) authorizations: the wallet funding a deposit and the
231
+ // sponsored payout of a Totalis withdrawal.
232
+ const authorizationFields = [
233
+ { name: "from", type: "address" },
234
+ { name: "to", type: "address" },
235
+ { name: "value", type: "uint256" },
236
+ { name: "validAfter", type: "uint256" },
237
+ { name: "validBefore", type: "uint256" },
238
+ { name: "nonce", type: "bytes32" },
239
+ ];
240
+ export const usdcTypes = {
241
+ ReceiveWithAuthorization: authorizationFields,
242
+ TransferWithAuthorization: authorizationFields,
243
+ };
244
+ /** The USDC EIP-712 domain GET /v1/deployment publishes, in signer form. */
245
+ export function usdcDomain(deployment) {
246
+ const { name, version, chain_id, verifying_contract } = deployment.usdc.eip712_domain;
247
+ return { name, version, chainId: BigInt(chain_id), verifyingContract: address(verifying_contract) };
248
+ }
249
+ /** The EIP-3009 nonce `ParlayVault.depositUserWithAuthorization` derives: keccak256(abi.encode(vault, chainId, from, salt, account)). */
250
+ export function depositNonce(deployment, authorization) {
251
+ return toHex(keccak_256(concat([
252
+ uintWord(BigInt(address(deployment.vault_address))),
253
+ uintWord(BigInt(deployment.chain_id)),
254
+ uintWord(BigInt(address(authorization.from))),
255
+ bytes32(authorization.salt),
256
+ uintWord(BigInt(address(authorization.account))),
257
+ ])));
258
+ }
259
+ /** USDC ReceiveWithAuthorization that lets the vault pull a deposit from the wallet. */
260
+ export function receiveWithAuthorizationTypedData(deployment, authorization) {
261
+ return {
262
+ domain: usdcDomain(deployment), types: { ReceiveWithAuthorization: authorizationFields }, primaryType: "ReceiveWithAuthorization",
263
+ message: {
264
+ from: address(authorization.from), to: address(deployment.vault_address), value: BigInt(authorization.value),
265
+ validAfter: BigInt(authorization.valid_after), validBefore: BigInt(authorization.valid_before), nonce: depositNonce(deployment, authorization),
266
+ },
267
+ };
268
+ }
269
+ /**
270
+ * The EIP-3009 nonce of a withdrawal's payout, derived from the create's
271
+ * Idempotency-Key: keccak256(abi.encode(keccak256("TOTALIS_PAYOUT_NONCE_V1"),
272
+ * chainId, vault, account, uint128(idempotencyKey))). Sign the payout under it;
273
+ * the venue derives the same bytes, so the signature is bound to that one request.
274
+ */
275
+ export function payoutNonce(deployment, account, idempotencyKey) {
276
+ const key = idempotencyKey.replaceAll("-", "");
277
+ if (!/^[0-9a-f]{32}$/.test(key))
278
+ throw new TypeError("the Idempotency-Key is not a lowercase UUID");
279
+ return toHex(keccak_256(concat([
280
+ keccak_256(utf8.encode("TOTALIS_PAYOUT_NONCE_V1")),
281
+ uintWord(BigInt(deployment.chain_id)),
282
+ uintWord(BigInt(address(deployment.vault_address))),
283
+ uintWord(BigInt(address(account))),
284
+ uintWord(BigInt(`0x${key}`)),
285
+ ])));
286
+ }
287
+ /** USDC TransferWithAuthorization for a Totalis withdrawal payout, under the deployment's USDC domain. */
288
+ export function transferWithAuthorizationTypedData(deployment, payout) {
289
+ return {
290
+ domain: usdcDomain(deployment), types: { TransferWithAuthorization: authorizationFields }, primaryType: "TransferWithAuthorization",
291
+ message: {
292
+ from: address(payout.from), to: address(payout.to), value: BigInt(payout.value),
293
+ validAfter: BigInt(payout.valid_after), validBefore: BigInt(payout.valid_before), nonce: toHex(bytes32(payout.nonce)),
294
+ },
295
+ };
296
+ }
297
+ /** Lowercase hex with v in {27, 28}: the venue rejects v = 0/1. Longer ERC-1271 signatures pass through. */
298
+ export function canonicalSignature(signature) {
299
+ const lower = signature.toLowerCase();
300
+ if (!/^0x(?:[0-9a-f]{2})+$/.test(lower))
301
+ throw new TypeError("signature is not hex");
302
+ const v = lower.length === 132 ? Number.parseInt(lower.slice(130), 16) : undefined;
303
+ return (v === 0 || v === 1 ? `${lower.slice(0, 130)}${(v + 27).toString(16)}` : lower);
304
+ }
305
+ /** Sign with the account's signer and return the canonical signature the API expects. */
306
+ export async function signTotalis(signer, typedData) {
307
+ return canonicalSignature(await signer.signTypedData(typedData));
308
+ }
309
+ /** The canonical EIP-712 type string, for checking a signer's encoding against the contract. */
310
+ export function encodeType(primaryType) {
311
+ return structType(totalisTypes, primaryType);
312
+ }
313
+ // ---------------------------------------------------------------------------
314
+ // The SIWE (EIP-4361) message an account owner personal-signs to issue an API
315
+ // key (TT-1791). The server writes it; the client checks it before signing.
316
+ const issueKeyStatement = "Issue a Totalis API key. It grants scoped API access, not financial signing authority.";
317
+ export function issueKeyMessage(message) {
318
+ return `${message.domain} wants you to sign in with your Ethereum account:\n${message.address}\n\n${issueKeyStatement}\n\n` +
319
+ `URI: ${message.uri}\nVersion: 1\nChain ID: ${message.chainId}\nNonce: ${message.nonce}\nIssued At: ${message.issuedAt}\n` +
320
+ `Expiration Time: ${message.expirationTime}\nRequest ID: ${message.requestId}\nResources:\n${message.resources.map((resource) => `- ${resource}`).join("\n")}`;
321
+ }
322
+ const issueKeyPattern = new RegExp("^(?<domain>\\S+) wants you to sign in with your Ethereum account:\\n(?<address>0x[0-9a-fA-F]{40})\\n\\n" +
323
+ issueKeyStatement.replaceAll(".", "\\.") +
324
+ "\\n\\nURI: (?<uri>\\S+)\\nVersion: 1\\nChain ID: (?<chainId>[0-9]+)\\nNonce: (?<nonce>[0-9a-f]+)\\nIssued At: (?<issuedAt>\\S+)\\n" +
325
+ "Expiration Time: (?<expirationTime>\\S+)\\nRequest ID: (?<requestId>\\S+)\\nResources:\\n(?<resources>- \\S+(?:\\n- \\S+)*)$");
326
+ /** Parse an issue_key message; throws unless it is exactly the server's template. */
327
+ export function parseIssueKeyMessage(text) {
328
+ const groups = issueKeyPattern.exec(text)?.groups;
329
+ if (groups === undefined)
330
+ throw new TypeError("not a Totalis issue_key message");
331
+ const message = {
332
+ domain: groups.domain, address: groups.address, uri: groups.uri, chainId: groups.chainId, nonce: groups.nonce,
333
+ issuedAt: groups.issuedAt, expirationTime: groups.expirationTime, requestId: groups.requestId,
334
+ resources: groups.resources.split("\n").map((line) => line.slice(2)),
335
+ };
336
+ if (issueKeyMessage(message) !== text || !message.resources.includes("urn:totalis:purpose:issue_key")) {
337
+ throw new TypeError("not a Totalis issue_key message");
338
+ }
339
+ return message;
340
+ }