@lamarge/sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -2
- package/dist/account.d.ts +32 -0
- package/dist/account.js +113 -0
- package/dist/aster.d.ts +74 -0
- package/dist/aster.js +169 -0
- package/dist/book.d.ts +39 -0
- package/dist/book.js +64 -0
- package/dist/client.d.ts +92 -0
- package/dist/client.js +100 -0
- package/dist/delivery.d.ts +68 -0
- package/dist/delivery.js +118 -0
- package/dist/events.d.ts +30 -0
- package/dist/events.js +93 -0
- package/dist/funds.d.ts +19 -0
- package/dist/funds.js +17 -0
- package/dist/generated/api.d.ts +5707 -0
- package/dist/generated/api.js +281 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/dist/lighter.d.ts +92 -0
- package/dist/lighter.js +206 -0
- package/dist/maker.d.ts +251 -0
- package/dist/maker.js +201 -0
- package/dist/protocol/consents.d.ts +82 -0
- package/dist/protocol/consents.js +78 -0
- package/dist/protocol/deployments.d.ts +54 -0
- package/dist/protocol/deployments.js +44 -0
- package/dist/protocol/index.d.ts +6 -0
- package/dist/protocol/index.js +6 -0
- package/dist/protocol/intents.d.ts +126 -0
- package/dist/protocol/intents.js +141 -0
- package/dist/protocol/lighter-constants.d.ts +4 -0
- package/dist/protocol/lighter-constants.js +49 -0
- package/dist/protocol/lighter.d.ts +52 -0
- package/dist/protocol/lighter.js +132 -0
- package/dist/protocol/protocol.d.ts +168 -0
- package/dist/protocol/protocol.js +317 -0
- package/dist/protocol/validation.d.ts +11 -0
- package/dist/protocol/validation.js +34 -0
- package/dist/retry-after.d.ts +2 -0
- package/dist/retry-after.js +7 -0
- package/dist/session.d.ts +14 -0
- package/dist/session.js +28 -0
- package/dist/signer.d.ts +43 -0
- package/dist/signer.js +28 -0
- package/dist/store.d.ts +21 -0
- package/dist/store.js +46 -0
- package/dist/take.d.ts +27 -0
- package/dist/take.js +81 -0
- package/dist/transfer.d.ts +63 -0
- package/dist/transfer.js +253 -0
- package/dist/withdraw.d.ts +67 -0
- package/dist/withdraw.js +122 -0
- package/package.json +57 -4
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { type Verified } from "./validation.ts";
|
|
2
|
+
import { type DeploymentManifest, type EscrowDeployment } from "./deployments.ts";
|
|
3
|
+
/** The reviewed proxy, the same address on HyperEVM, Polygon and BSC (contracts/README.md#deployments). */
|
|
4
|
+
export declare const ESCROW = "0x3100b7b49c0f9631c6af25877b5a4e13a47afad6";
|
|
5
|
+
/** Each venue's escrow chain, token decimals and permit domain. A venue the API enables but that is not pinned here is not traded. */
|
|
6
|
+
export declare const CHAINS: Record<string, {
|
|
7
|
+
id: number;
|
|
8
|
+
token: string;
|
|
9
|
+
decimals: number;
|
|
10
|
+
permit?: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
escrow: string;
|
|
13
|
+
exitEscrow?: string;
|
|
14
|
+
}>;
|
|
15
|
+
/** Venues where the maker delivers inside the venue, read by the server, instead of an escrow lock. */
|
|
16
|
+
export declare const VENUE_SETTLED: Set<string>;
|
|
17
|
+
/** The escrow holding a venue's commitments; for a venue now settled inside itself, its former escrow. */
|
|
18
|
+
export declare function escrowOf(venue: string): string;
|
|
19
|
+
/** Aster's BSC chain, where its approvals and withdrawals are signed. */
|
|
20
|
+
export declare const ASTER_CHAIN_ID = 56;
|
|
21
|
+
/** Chain id of Aster's signing domain for transfers between accounts; no such chain exists. */
|
|
22
|
+
export declare const ASTER_TRANSFER_CHAIN_ID = 1666;
|
|
23
|
+
export declare const MAKER_TIMEOUT_SECONDS = 3600;
|
|
24
|
+
/** The allowance a permit grants, once: the escrow spends it only on actions the owner signs. */
|
|
25
|
+
export declare const UNLIMITED_ALLOWANCE: bigint;
|
|
26
|
+
/** Year-9999 admission sentinel for browser orders with no expiry. */
|
|
27
|
+
export declare const NEVER_VALID_UNTIL = 253402214400;
|
|
28
|
+
export declare const TAKER_TIMEOUT_SECONDS = 7200;
|
|
29
|
+
export type Typed = {
|
|
30
|
+
types: Record<string, {
|
|
31
|
+
name: string;
|
|
32
|
+
type: string;
|
|
33
|
+
}[]>;
|
|
34
|
+
primaryType: string;
|
|
35
|
+
domain: {
|
|
36
|
+
name: string;
|
|
37
|
+
version?: string;
|
|
38
|
+
chainId: number;
|
|
39
|
+
verifyingContract: string;
|
|
40
|
+
};
|
|
41
|
+
message: Record<string, unknown>;
|
|
42
|
+
};
|
|
43
|
+
export type SignItem = {
|
|
44
|
+
kind: string;
|
|
45
|
+
signer: string;
|
|
46
|
+
typed_data: Typed;
|
|
47
|
+
fill_id?: string | undefined;
|
|
48
|
+
};
|
|
49
|
+
export declare function convert(types: Typed["types"], kind: string, value: unknown): unknown;
|
|
50
|
+
export declare const same: (a: unknown, b: unknown) => boolean;
|
|
51
|
+
export declare function insist(ok: unknown, message?: string): asserts ok;
|
|
52
|
+
export declare const uint: (value: unknown) => bigint;
|
|
53
|
+
export declare const EIP712Domain: {
|
|
54
|
+
name: string;
|
|
55
|
+
type: string;
|
|
56
|
+
}[];
|
|
57
|
+
export declare const schemas: Record<string, Record<string, {
|
|
58
|
+
name: string;
|
|
59
|
+
type: string;
|
|
60
|
+
}[]>>;
|
|
61
|
+
/** A signed take's payments, each with exactly the `LockPayment` fields. */
|
|
62
|
+
export declare function takePayments(typed: Typed): Record<string, unknown>[];
|
|
63
|
+
/** Exact EIP-712 field types and pinned domain, before any wallet prompt. */
|
|
64
|
+
export declare function checkEscrowTyped(typed: Typed, venue: string): void;
|
|
65
|
+
/** Proof verification accepts the current protocol on reviewed deployments only. */
|
|
66
|
+
export declare function checkEscrowProofTyped(typed: Typed, venue: string, manifest?: DeploymentManifest): EscrowDeployment;
|
|
67
|
+
export declare function checkRegistry(venue: string, chain: {
|
|
68
|
+
id: number;
|
|
69
|
+
token: string;
|
|
70
|
+
decimals: number;
|
|
71
|
+
escrow: string;
|
|
72
|
+
}): void;
|
|
73
|
+
export declare function checkDeadline(value: unknown, seconds: number, now?: number): void;
|
|
74
|
+
export declare function checkPermit(item: SignItem, venue: string, owner: string, maxSeconds?: number): asserts item is SignItem & {
|
|
75
|
+
typed_data: Verified<Typed>;
|
|
76
|
+
};
|
|
77
|
+
/** A decimal amount with at most six decimals, in base units of a token with `decimals`. */
|
|
78
|
+
export declare function decimalUnits(value: unknown, decimals: number): bigint;
|
|
79
|
+
export declare function decimalsOf(venue: string): number;
|
|
80
|
+
/** One millionth of `venue`'s token: venues carry six decimals at most. */
|
|
81
|
+
export declare const venueStep: (venue: string) => bigint;
|
|
82
|
+
/** `value` rounded up to whole millionths: what `venue` moves for it. */
|
|
83
|
+
export declare function venueUnits(value: bigint, venue: string): bigint;
|
|
84
|
+
/** What `venue` moves for `held` to reach `needed`: an 18-decimal wallet keeps the dust past the shortfall. Never above `needed`. */
|
|
85
|
+
export declare function shortfall(needed: bigint, held: bigint, venue: string): bigint;
|
|
86
|
+
/** One token in native units, the automatic deposit sweep threshold. */
|
|
87
|
+
export declare const sweepMinimum: (venue: string) => bigint;
|
|
88
|
+
/** Venue withdrawals can only move this amount to the reviewed destination. */
|
|
89
|
+
export declare function checkWithdrawal(typed: Typed, venue: string, signer: string, destination: string, amount: bigint, now?: number): asserts typed is Verified<Typed>;
|
|
90
|
+
/** What a venue withdrawal moves out of the margin for `amount` to arrive: Aster adds its fee. */
|
|
91
|
+
export declare function withdrawalCost(venue: string, typed: Typed): bigint;
|
|
92
|
+
export type AsterChallenge = {
|
|
93
|
+
message: string;
|
|
94
|
+
nonce: string;
|
|
95
|
+
expires_at: string;
|
|
96
|
+
typed_data?: Typed | undefined;
|
|
97
|
+
};
|
|
98
|
+
/** `Message{msg}` in Aster's signing domain at `chainId`: its transfers' chain, or BSC's for agent approvals and withdrawals. */
|
|
99
|
+
export declare const asterRequest: (msg: string, chainId: number) => Typed;
|
|
100
|
+
export declare function checkAsterRequest(typed: Typed, chainId: number): asserts typed is Verified<Typed>;
|
|
101
|
+
/**
|
|
102
|
+
* The agent approval an Aster account signs to link: LaMarge's agent for this address, valid one
|
|
103
|
+
* year, on BSC, with only `canPerpTrade`, which can move margin only to the account's own Spot
|
|
104
|
+
* wallet. The agent name must be one of the environments' names; every other field is fixed.
|
|
105
|
+
*/
|
|
106
|
+
export declare function checkAsterApproval(challenge: AsterChallenge, address: string, now?: number): asserts challenge is AsterChallenge & {
|
|
107
|
+
typed_data: Verified<Typed>;
|
|
108
|
+
};
|
|
109
|
+
/** A transfer of USDT between Aster accounts the payer's wallet signs: its decimal amount, recipient, id, microsecond nonce and payer. */
|
|
110
|
+
export declare function asterTransfer(typed: Typed): {
|
|
111
|
+
msg: string;
|
|
112
|
+
amount: bigint;
|
|
113
|
+
to: `0x${string}`;
|
|
114
|
+
id: string;
|
|
115
|
+
nonce: number;
|
|
116
|
+
user: `0x${string}`;
|
|
117
|
+
};
|
|
118
|
+
/** The id a fill's Aster transfer carries: its UUID without dashes, 32 lowercase hex digits. A repeated request with it moves nothing more. */
|
|
119
|
+
export declare const asterClientId: (fillId: string) => string;
|
|
120
|
+
/** Base units of USDT as Aster takes them: a decimal amount of six decimals at most, rounded up, so the recipient never gets less. */
|
|
121
|
+
export declare const asterAmount: (units: bigint) => string;
|
|
122
|
+
/** The transfer pays `amount_in`, as Aster carries it in six decimals rounded up, from `payer` to the maker's `recipient`, under the fill id. */
|
|
123
|
+
export declare function checkAsterTransfer(transfer: ReturnType<typeof asterTransfer>, fill: {
|
|
124
|
+
id: string;
|
|
125
|
+
amount_in: string;
|
|
126
|
+
payer: string;
|
|
127
|
+
maker_recipient: string;
|
|
128
|
+
}): void;
|
|
129
|
+
export type QuoteIntent = {
|
|
130
|
+
venue: string;
|
|
131
|
+
recipient: string;
|
|
132
|
+
supply: string;
|
|
133
|
+
receive: string;
|
|
134
|
+
min_fill: string;
|
|
135
|
+
};
|
|
136
|
+
type QuoteOrder = {
|
|
137
|
+
id: string;
|
|
138
|
+
venue: string;
|
|
139
|
+
owner_address: string;
|
|
140
|
+
quote_signer: string;
|
|
141
|
+
};
|
|
142
|
+
export type PreparedQuote = {
|
|
143
|
+
draft_id: string;
|
|
144
|
+
sequence: number;
|
|
145
|
+
valid_until: number;
|
|
146
|
+
quote_size: string;
|
|
147
|
+
expires_at: number;
|
|
148
|
+
quotes: QuoteIntent[];
|
|
149
|
+
to_sign: SignItem[];
|
|
150
|
+
};
|
|
151
|
+
/** The published draft metadata and signed quote must both match the requested terms. */
|
|
152
|
+
export declare function checkPreparedQuote(prepared: PreparedQuote, order: QuoteOrder & {
|
|
153
|
+
last_sequence: number;
|
|
154
|
+
}, requested: QuoteIntent | QuoteIntent[], validUntil: number, size: string): asserts prepared is PreparedQuote & {
|
|
155
|
+
to_sign: Verified<SignItem[]>;
|
|
156
|
+
};
|
|
157
|
+
/** The entire replacement set is reviewed, in order: no extra route, recipient or amount may be
|
|
158
|
+
* added, and each position is the quote index a fill names. */
|
|
159
|
+
export declare function checkSignedQuote(item: SignItem, order: QuoteOrder, asked: QuoteIntent | QuoteIntent[], sequence: number, expires: number): void;
|
|
160
|
+
export declare function checkCloseOrders(items: SignItem[], ids: string[], master: string): asserts items is Verified<SignItem[]>;
|
|
161
|
+
/** SIWE intent pinned to `origin`, tolerating two minutes of local clock skew. */
|
|
162
|
+
export declare function checkLoginChallenge(challenge: {
|
|
163
|
+
message: string;
|
|
164
|
+
nonce: string;
|
|
165
|
+
expires_at: string;
|
|
166
|
+
address: string;
|
|
167
|
+
}, origin: string, now?: number): void;
|
|
168
|
+
export {};
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { address, array, hex, record, required } from "./validation.js";
|
|
2
|
+
import { ESCROW_PROOF_DEPLOYMENTS, escrowDeployment } from "./deployments.js";
|
|
3
|
+
/** The reviewed proxy, the same address on HyperEVM, Polygon and BSC (contracts/README.md#deployments). */
|
|
4
|
+
export const ESCROW = "0x3100b7b49c0f9631c6af25877b5a4e13a47afad6";
|
|
5
|
+
/** Each venue's escrow chain, token decimals and permit domain. A venue the API enables but that is not pinned here is not traded. */
|
|
6
|
+
export const CHAINS = {
|
|
7
|
+
hyperliquid: { id: 999, token: "0xb88339cb7199b77e23db6e890353e22632ba630f", decimals: 6, permit: "USDC", version: "2", escrow: ESCROW },
|
|
8
|
+
polymarket: { id: 137, token: "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb", decimals: 6, permit: "Polymarket USD", version: "1", escrow: ESCROW },
|
|
9
|
+
// Settled inside the venue, no escrow. Quote sets and payment terms name a signing chain id,
|
|
10
|
+
// contract and token: Lighter's L2 chain id, its zkLighter contract on Ethereum and its USDC;
|
|
11
|
+
// BSC, Aster's vault and USDT. Aster's former BSC escrow only lets owners close and withdraw.
|
|
12
|
+
aster: { id: 56, token: "0x55d398326f99059ff775485246999027b3197955", decimals: 18, escrow: "0x128463a60784c4d3f46c23af3f65ed859ba87974", exitEscrow: ESCROW },
|
|
13
|
+
lighter: { id: 304, token: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", decimals: 6, escrow: "0x3b4d794a66304f130a4db8f2551b0070dfcf5ca7" },
|
|
14
|
+
};
|
|
15
|
+
/** Venues where the maker delivers inside the venue, read by the server, instead of an escrow lock. */
|
|
16
|
+
export const VENUE_SETTLED = new Set(["lighter", "aster"]);
|
|
17
|
+
/** The escrow holding a venue's commitments; for a venue now settled inside itself, its former escrow. */
|
|
18
|
+
export function escrowOf(venue) {
|
|
19
|
+
const chain = CHAINS[venue];
|
|
20
|
+
insist(chain, "Unknown venue");
|
|
21
|
+
return chain.exitEscrow ?? chain.escrow;
|
|
22
|
+
}
|
|
23
|
+
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
24
|
+
/** Aster's BSC chain, where its approvals and withdrawals are signed. */
|
|
25
|
+
export const ASTER_CHAIN_ID = 56;
|
|
26
|
+
/** Chain id of Aster's signing domain for transfers between accounts; no such chain exists. */
|
|
27
|
+
export const ASTER_TRANSFER_CHAIN_ID = 1666;
|
|
28
|
+
/** USDT on BSC. */
|
|
29
|
+
const ASTER_DECIMALS = 18;
|
|
30
|
+
/** Cap on an Aster withdrawal fee: 2 USDT (Aster quoted 0.11 in October 2026). */
|
|
31
|
+
const ASTER_MAX_FEE = 2n * 10n ** BigInt(ASTER_DECIMALS);
|
|
32
|
+
const ASTER_APPROVAL_DAYS = 365;
|
|
33
|
+
/** One agent name per environment: approving the same name from another environment's addresses revokes the other agent. */
|
|
34
|
+
const ASTER_AGENT_NAMES = ["LaMarge", "LaMargeDev"];
|
|
35
|
+
export const MAKER_TIMEOUT_SECONDS = 3600;
|
|
36
|
+
/** The allowance a permit grants, once: the escrow spends it only on actions the owner signs. */
|
|
37
|
+
export const UNLIMITED_ALLOWANCE = (1n << 256n) - 1n;
|
|
38
|
+
/** Year-9999 admission sentinel for browser orders with no expiry. */
|
|
39
|
+
export const NEVER_VALID_UNTIL = 253_402_214_400;
|
|
40
|
+
export const TAKER_TIMEOUT_SECONDS = 7200;
|
|
41
|
+
export function convert(types, kind, value) {
|
|
42
|
+
if (kind.endsWith("[]"))
|
|
43
|
+
return array(value).map((item) => convert(types, kind.slice(0, -2), item));
|
|
44
|
+
const fields = types[kind];
|
|
45
|
+
if (fields) {
|
|
46
|
+
const struct = record(value);
|
|
47
|
+
return Object.fromEntries(fields.map((field) => [field.name, convert(types, field.type, struct[field.name])]));
|
|
48
|
+
}
|
|
49
|
+
if (kind === "address")
|
|
50
|
+
return address(value);
|
|
51
|
+
if (kind === "string" && typeof value === "string" || kind === "bool" && typeof value === "boolean")
|
|
52
|
+
return value;
|
|
53
|
+
const bytes = /^bytes(\d*)$/.exec(kind);
|
|
54
|
+
if (bytes) {
|
|
55
|
+
const data = hex(value);
|
|
56
|
+
const width = bytes[1] ? Number(bytes[1]) : null;
|
|
57
|
+
if (width !== null && (width < 1 || width > 32 || data.length !== 2 + width * 2))
|
|
58
|
+
throw new Error("Invalid signing bytes");
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
const integer = /^(u?int)(\d*)$/.exec(kind);
|
|
62
|
+
if (!integer)
|
|
63
|
+
throw new Error("Invalid signing value");
|
|
64
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint"
|
|
65
|
+
|| typeof value === "number" && !Number.isSafeInteger(value)
|
|
66
|
+
|| typeof value === "string" && !/^-?\d+$/.test(value))
|
|
67
|
+
throw new Error("Invalid signing integer");
|
|
68
|
+
const width = integer[2] ? Number(integer[2]) : 256;
|
|
69
|
+
if (width < 8 || width > 256 || width % 8 !== 0)
|
|
70
|
+
throw new Error("Invalid integer width");
|
|
71
|
+
const signed = integer[1] === "int";
|
|
72
|
+
const limit = 1n << BigInt(width - (signed ? 1 : 0));
|
|
73
|
+
const result = BigInt(value);
|
|
74
|
+
if (result < (signed ? -limit : 0n) || result >= limit)
|
|
75
|
+
throw new Error("Signing integer out of range");
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
export const same = (a, b) => typeof a === "string" && typeof b === "string" && a.toLowerCase() === b.toLowerCase();
|
|
79
|
+
export function insist(ok, message = "The signing request differs from your instructions") { if (!ok)
|
|
80
|
+
throw new Error(message); }
|
|
81
|
+
export const uint = (value) => { insist(typeof value === "string" && /^\d+$/.test(value) || typeof value === "number" && Number.isSafeInteger(value) && value >= 0, "Invalid signing integer"); return BigInt(value); };
|
|
82
|
+
const fields = (description) => description.split(",").map(pair => { const [name, type] = pair.split(":"); return { name: required(name), type: required(type) }; });
|
|
83
|
+
export const EIP712Domain = fields("name:string,version:string,chainId:uint256,verifyingContract:address");
|
|
84
|
+
const quote = fields("venue:string,chainId:uint256,token:address,recipient:address,supply:uint256,receive:uint256,minFill:uint256");
|
|
85
|
+
const lockPayment = fields("payer:address,swapId:bytes32,recipient:address,amount:uint256,hashlock:bytes32,refundAt:uint64,deadline:uint256,orderId:bytes32,sequence:uint64,amountOut:uint256,receiveChainId:uint256,receiveToken:address,receiveEscrow:address,receiveRecipient:address,dex:uint32");
|
|
86
|
+
export const schemas = {
|
|
87
|
+
RefundPayment: { RefundPayment: fields("payer:address,swapId:bytes32,deadline:uint256") },
|
|
88
|
+
FundOffer: { FundOffer: fields("owner:address,offerId:bytes32,amount:uint256,deposit:uint256,quoteSigner:address,operator:address,minQuoteSequence:uint64,nonce:uint256,deadline:uint256") },
|
|
89
|
+
QuoteSet: { QuoteSet: fields("owner:address,offerId:bytes32,sequence:uint64,expiresAt:uint64,quotes:Quote[]"), Quote: quote },
|
|
90
|
+
PayTake: { PayTake: fields("payer:address,deposit:uint256,payments:LockPayment[]"), LockPayment: lockPayment },
|
|
91
|
+
CloseOrders: { CloseOrders: fields("account:address,orders:bytes32[],nonce:uint256,deadline:uint256") },
|
|
92
|
+
CloseOffer: { CloseOffer: fields("owner:address,offerId:bytes32,toMargin:bool,dex:uint32,nonce:uint256,deadline:uint256") },
|
|
93
|
+
Permit: { Permit: fields("owner:address,spender:address,value:uint256,nonce:uint256,deadline:uint256") },
|
|
94
|
+
Withdraw: { Withdraw: fields("account:address,token:address,amount:uint256,fee:uint256,to:address,salt:uint64,ts:uint64") },
|
|
95
|
+
"HyperliquidTransaction:SendAsset": { "HyperliquidTransaction:SendAsset": fields("hyperliquidChain:string,destination:string,sourceDex:string,destinationDex:string,token:string,amount:string,fromSubAccount:string,nonce:uint64") },
|
|
96
|
+
Action: { Action: fields("type:string,destination:address,destination Chain:string,token:string,amount:string,fee:string,nonce:uint256,aster chain:string") },
|
|
97
|
+
Message: { Message: fields("msg:string") },
|
|
98
|
+
};
|
|
99
|
+
/** A signed take's payments, each with exactly the `LockPayment` fields. */
|
|
100
|
+
export function takePayments(typed) {
|
|
101
|
+
const payments = array(typed.message.payments).map(record);
|
|
102
|
+
insist(payments.length > 0 && payments.length <= 8 && payments.every(payment => Object.keys(payment).length === lockPayment.length), "Unexpected signing fields");
|
|
103
|
+
return payments;
|
|
104
|
+
}
|
|
105
|
+
function checkTypes(typed) {
|
|
106
|
+
const schema = schemas[typed.primaryType];
|
|
107
|
+
insist(schema, "Unsupported signing request");
|
|
108
|
+
const expected = { EIP712Domain: EIP712Domain, ...schema };
|
|
109
|
+
insist(Object.keys(typed.types).length === Object.keys(expected).length && Object.entries(expected).every(([name, fields]) => JSON.stringify(typed.types[name]) === JSON.stringify(fields)), "Unexpected signing types");
|
|
110
|
+
insist(Object.keys(typed.message).length === required(schema[typed.primaryType]).length, "Unexpected signing fields");
|
|
111
|
+
}
|
|
112
|
+
/** Exact EIP-712 field types and pinned domain, before any wallet prompt. */
|
|
113
|
+
export function checkEscrowTyped(typed, venue) {
|
|
114
|
+
const chain = CHAINS[venue];
|
|
115
|
+
insist(chain, "Unknown venue");
|
|
116
|
+
// A venue settled inside itself signs quote sets in its own domain; its former escrow only
|
|
117
|
+
// accepts closes and refunds.
|
|
118
|
+
const exit = chain.exitEscrow !== undefined && ["CloseOffer", "RefundPayment"].includes(typed.primaryType);
|
|
119
|
+
insist(chain.exitEscrow === undefined || exit || typed.primaryType === "QuoteSet", "Unexpected signing request");
|
|
120
|
+
checkEscrowTypedAt(typed, venue, exit ? escrowOf(venue) : chain.escrow);
|
|
121
|
+
}
|
|
122
|
+
/** Proof verification accepts the current protocol on reviewed deployments only. */
|
|
123
|
+
export function checkEscrowProofTyped(typed, venue, manifest = ESCROW_PROOF_DEPLOYMENTS) {
|
|
124
|
+
insist(["QuoteSet", "FundOffer", "PayTake"].includes(typed.primaryType), "Unexpected signed agreement");
|
|
125
|
+
const escrow = typed.domain.verifyingContract;
|
|
126
|
+
const chain = CHAINS[venue];
|
|
127
|
+
const deployment = chain && escrowDeployment(chain.id, escrow, manifest);
|
|
128
|
+
insist(deployment && deployment.version === 5, "Unexpected signing domain");
|
|
129
|
+
checkEscrowTypedAt(typed, venue, escrow);
|
|
130
|
+
return deployment;
|
|
131
|
+
}
|
|
132
|
+
function checkEscrowTypedAt(typed, venue, escrow) {
|
|
133
|
+
checkTypes(typed);
|
|
134
|
+
const chain = CHAINS[venue];
|
|
135
|
+
insist(chain && !["Withdraw", "HyperliquidTransaction:SendAsset", "Action", "Message"].includes(typed.primaryType));
|
|
136
|
+
const permit = typed.primaryType === "Permit";
|
|
137
|
+
if (permit)
|
|
138
|
+
insist(chain.permit !== undefined && chain.version !== undefined, "The token has no permit");
|
|
139
|
+
insist(typed.domain.chainId === chain.id && same(typed.domain.verifyingContract, permit ? chain.token : escrow) && typed.domain.name === (permit ? chain.permit : "LaMarge") && typed.domain.version === (permit ? chain.version : "1"), "Unexpected signing domain");
|
|
140
|
+
if (permit)
|
|
141
|
+
insist(same(typed.message.spender, escrow), "Unexpected permit spender");
|
|
142
|
+
}
|
|
143
|
+
export function checkRegistry(venue, chain) {
|
|
144
|
+
const known = CHAINS[venue];
|
|
145
|
+
insist(known && known.id === chain.id && same(known.token, chain.token) && known.decimals === chain.decimals && same(chain.escrow, known.escrow), "API deployment differs from the reviewed deployment");
|
|
146
|
+
}
|
|
147
|
+
export function checkDeadline(value, seconds, now = Date.now() / 1000) {
|
|
148
|
+
const at = Number(uint(value));
|
|
149
|
+
insist(at > now && at <= now + seconds + 30, "Signing deadline is expired or too long");
|
|
150
|
+
}
|
|
151
|
+
export function checkPermit(item, venue, owner, maxSeconds = 600) {
|
|
152
|
+
checkEscrowTyped(item.typed_data, venue);
|
|
153
|
+
const m = item.typed_data.message;
|
|
154
|
+
insist(item.kind === "permit" && item.typed_data.primaryType === "Permit" && same(item.signer, owner) && same(m.owner, owner) && uint(m.value) === UNLIMITED_ALLOWANCE, "Unexpected token allowance");
|
|
155
|
+
checkDeadline(m.deadline, maxSeconds);
|
|
156
|
+
}
|
|
157
|
+
/** A decimal amount with at most six decimals, in base units of a token with `decimals`. */
|
|
158
|
+
export function decimalUnits(value, decimals) {
|
|
159
|
+
insist(typeof value === "string" && /^\d+(\.\d{1,6})?$/.test(value), "Invalid decimal amount");
|
|
160
|
+
const [whole, fraction = ""] = value.split(".");
|
|
161
|
+
return BigInt(required(whole)) * 10n ** BigInt(decimals) + BigInt(fraction.padEnd(decimals, "0"));
|
|
162
|
+
}
|
|
163
|
+
export function decimalsOf(venue) {
|
|
164
|
+
const chain = CHAINS[venue];
|
|
165
|
+
insist(chain, "Unknown venue");
|
|
166
|
+
return chain.decimals;
|
|
167
|
+
}
|
|
168
|
+
/** One millionth of `venue`'s token: venues carry six decimals at most. */
|
|
169
|
+
export const venueStep = (venue) => 10n ** BigInt(Math.max(decimalsOf(venue) - 6, 0));
|
|
170
|
+
/** `value` rounded up to whole millionths: what `venue` moves for it. */
|
|
171
|
+
export function venueUnits(value, venue) {
|
|
172
|
+
const step = venueStep(venue);
|
|
173
|
+
return (value + step - 1n) / step * step;
|
|
174
|
+
}
|
|
175
|
+
/** What `venue` moves for `held` to reach `needed`: an 18-decimal wallet keeps the dust past the shortfall. Never above `needed`. */
|
|
176
|
+
export function shortfall(needed, held, venue) {
|
|
177
|
+
if (held >= needed)
|
|
178
|
+
return 0n;
|
|
179
|
+
const rounded = venueUnits(needed - held, venue);
|
|
180
|
+
return rounded < needed ? rounded : needed;
|
|
181
|
+
}
|
|
182
|
+
/** One token in native units, the automatic deposit sweep threshold. */
|
|
183
|
+
export const sweepMinimum = (venue) => 10n ** BigInt(decimalsOf(venue));
|
|
184
|
+
/** Venue withdrawals can only move this amount to the reviewed destination. */
|
|
185
|
+
export function checkWithdrawal(typed, venue, signer, destination, amount, now = Date.now() / 1000) {
|
|
186
|
+
checkTypes(typed);
|
|
187
|
+
const m = typed.message, d = typed.domain;
|
|
188
|
+
insist(amount > 0n);
|
|
189
|
+
if (venue === "hyperliquid") {
|
|
190
|
+
insist(typed.primaryType === "HyperliquidTransaction:SendAsset" && d.name === "HyperliquidSignTransaction" && d.version === "1" && d.chainId === 999 && same(d.verifyingContract, "0x0000000000000000000000000000000000000000"));
|
|
191
|
+
insist(same(destination, signer) && m.hyperliquidChain === "Mainnet" && same(m.destination, "0x2000000000000000000000000000000000000000") && ["", "spot"].includes(String(m.sourceDex)) && m.destinationDex === "spot" && m.token === "USDC:0x6d1e7cde53ba9467b783cb7c530ce054" && m.fromSubAccount === "" && decimalUnits(m.amount, decimalsOf(venue)) === amount);
|
|
192
|
+
insist(Math.abs(Number(uint(m.nonce)) / 1000 - now) <= 60, "Stale withdrawal nonce");
|
|
193
|
+
}
|
|
194
|
+
else if (venue === "aster") {
|
|
195
|
+
// Aster takes the fee out of the signed amount: `amount` is what arrives on BSC.
|
|
196
|
+
insist(typed.primaryType === "Action" && d.name === "Aster" && d.version === "1" && d.chainId === ASTER_CHAIN_ID && same(d.verifyingContract, ZERO_ADDRESS));
|
|
197
|
+
const decimals = decimalsOf(venue);
|
|
198
|
+
const fee = decimalUnits(m.fee, decimals);
|
|
199
|
+
insist(same(destination, signer) && m.type === "Withdraw" && same(m.destination, signer) && m["destination Chain"] === "BSC" && m.token === "USDT" && m["aster chain"] === "Mainnet" && fee <= ASTER_MAX_FEE && decimalUnits(m.amount, decimals) === amount + fee);
|
|
200
|
+
insist(Math.abs(Number(uint(m.nonce)) / 1_000_000 - now) <= 60, "Stale withdrawal nonce");
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
insist(venue === "polymarket" && typed.primaryType === "Withdraw" && d.name === "Polymarket" && d.version === "1" && d.chainId === 137 && same(d.verifyingContract, "0xdca4af75705dbb50f62437045aff9921947917d2"));
|
|
204
|
+
insist(same(m.account, signer) && same(m.token, required(CHAINS.polymarket).token) && same(m.to, destination) && uint(m.amount) === amount && uint(m.fee) === 0n && uint(m.salt) <= 0xffffffffffffffffn);
|
|
205
|
+
insist(Math.abs(Number(uint(m.ts)) - now) <= 60, "Stale withdrawal timestamp");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** What a venue withdrawal moves out of the margin for `amount` to arrive: Aster adds its fee. */
|
|
209
|
+
export function withdrawalCost(venue, typed) {
|
|
210
|
+
const m = typed.message;
|
|
211
|
+
if (venue === "hyperliquid")
|
|
212
|
+
return decimalUnits(m.amount, decimalsOf(venue));
|
|
213
|
+
if (venue === "aster")
|
|
214
|
+
return decimalUnits(m.amount, decimalsOf(venue)) - decimalUnits(m.fee, decimalsOf(venue));
|
|
215
|
+
return uint(m.amount);
|
|
216
|
+
}
|
|
217
|
+
/** `Message{msg}` in Aster's signing domain at `chainId`: its transfers' chain, or BSC's for agent approvals and withdrawals. */
|
|
218
|
+
export const asterRequest = (msg, chainId) => ({
|
|
219
|
+
types: { EIP712Domain, ...schemas.Message }, primaryType: "Message",
|
|
220
|
+
domain: { name: "AsterSignTransaction", version: "1", chainId, verifyingContract: ZERO_ADDRESS }, message: { msg },
|
|
221
|
+
});
|
|
222
|
+
export function checkAsterRequest(typed, chainId) {
|
|
223
|
+
checkTypes(typed);
|
|
224
|
+
const d = typed.domain;
|
|
225
|
+
insist(typed.primaryType === "Message" && d.name === "AsterSignTransaction" && d.version === "1" && d.chainId === chainId && same(d.verifyingContract, ZERO_ADDRESS) && typeof typed.message.msg === "string", "Unexpected Aster request");
|
|
226
|
+
}
|
|
227
|
+
function asterMessage(typed, chainId) {
|
|
228
|
+
checkAsterRequest(typed, chainId);
|
|
229
|
+
return String(typed.message.msg);
|
|
230
|
+
}
|
|
231
|
+
/** An Aster request's query string, its values by position under exactly `names`. */
|
|
232
|
+
function asterFields(msg, names, message) {
|
|
233
|
+
const pairs = msg.split("&").map(pair => pair.split("="));
|
|
234
|
+
insist(pairs.length === names.length && pairs.every((pair, index) => pair.length === 2 && pair[0] === names[index]), message);
|
|
235
|
+
return pairs.map(pair => required(pair[1]));
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The agent approval an Aster account signs to link: LaMarge's agent for this address, valid one
|
|
239
|
+
* year, on BSC, with only `canPerpTrade`, which can move margin only to the account's own Spot
|
|
240
|
+
* wallet. The agent name must be one of the environments' names; every other field is fixed.
|
|
241
|
+
*/
|
|
242
|
+
export function checkAsterApproval(challenge, address, now = Date.now()) {
|
|
243
|
+
insist(challenge.typed_data, "Missing agent approval");
|
|
244
|
+
const msg = asterMessage(challenge.typed_data, ASTER_CHAIN_ID);
|
|
245
|
+
const names = ["user", "nonce", "agentName", "agentAddress", "expired", "signatureChainId", "canSpotTrade", "canPerpTrade", "canWithdraw"];
|
|
246
|
+
const whitelisted = msg.split("&").length > names.length;
|
|
247
|
+
const [user, nonce, name, agent, expired, chain, spot, perp, withdraw, ips] = asterFields(msg, whitelisted ? [...names, "ipWhitelist"] : names, "Unexpected agent approval");
|
|
248
|
+
insist(msg === challenge.message, "Unexpected agent approval");
|
|
249
|
+
insist(same(user, address), "The approval names another account");
|
|
250
|
+
insist(nonce === challenge.nonce && /^\d{16}$/.test(nonce) && Math.abs(Number(nonce) / 1_000_000 - now / 1000) <= 60, "Stale agent approval");
|
|
251
|
+
insist(ASTER_AGENT_NAMES.includes(required(name)) && /^0x[0-9a-fA-F]{40}$/.test(required(agent)) && !same(agent, address), "Unexpected agent");
|
|
252
|
+
insist(BigInt(required(expired)) === BigInt(nonce) / 1000n + BigInt(ASTER_APPROVAL_DAYS) * 86400000n, "Unexpected approval expiry");
|
|
253
|
+
insist(chain === String(ASTER_CHAIN_ID) && spot === "false" && perp === "true" && withdraw === "false" && ips !== "", "The agent may only move margin to Spot");
|
|
254
|
+
}
|
|
255
|
+
/** A transfer of USDT between Aster accounts the payer's wallet signs: its decimal amount, recipient, id, microsecond nonce and payer. */
|
|
256
|
+
export function asterTransfer(typed) {
|
|
257
|
+
const msg = asterMessage(typed, ASTER_TRANSFER_CHAIN_ID);
|
|
258
|
+
const [asset, amount, to, id, nonce, user] = asterFields(msg, ["asset", "amount", "toAddress", "clientTranId", "nonce", "user"], "Unexpected Aster transfer");
|
|
259
|
+
insist(asset === "USDT" && /^\d{16}$/.test(required(nonce)), "Unexpected Aster transfer");
|
|
260
|
+
return { msg, amount: decimalUnits(amount, ASTER_DECIMALS), to: address(to), id: required(id), nonce: Number(nonce) / 1_000_000, user: address(user) };
|
|
261
|
+
}
|
|
262
|
+
/** The id a fill's Aster transfer carries: its UUID without dashes, 32 lowercase hex digits. A repeated request with it moves nothing more. */
|
|
263
|
+
export const asterClientId = (fillId) => fillId.replaceAll("-", "").toLowerCase();
|
|
264
|
+
/** Base units of USDT as Aster takes them: a decimal amount of six decimals at most, rounded up, so the recipient never gets less. */
|
|
265
|
+
export const asterAmount = (units) => {
|
|
266
|
+
const rounded = venueUnits(units, "aster"), unit = 10n ** BigInt(ASTER_DECIMALS);
|
|
267
|
+
const fraction = (rounded % unit).toString().padStart(ASTER_DECIMALS, "0").replace(/0+$/, "");
|
|
268
|
+
return fraction ? `${rounded / unit}.${fraction}` : String(rounded / unit);
|
|
269
|
+
};
|
|
270
|
+
/** The transfer pays `amount_in`, as Aster carries it in six decimals rounded up, from `payer` to the maker's `recipient`, under the fill id. */
|
|
271
|
+
export function checkAsterTransfer(transfer, fill) {
|
|
272
|
+
const owed = BigInt(fill.amount_in);
|
|
273
|
+
insist(transfer.amount >= owed && transfer.amount < owed + venueStep("aster") && same(transfer.to, fill.maker_recipient) && same(transfer.user, fill.payer) && transfer.id === asterClientId(fill.id), "The Aster transfer differs from the take");
|
|
274
|
+
}
|
|
275
|
+
/** The published draft metadata and signed quote must both match the requested terms. */
|
|
276
|
+
export function checkPreparedQuote(prepared, order, requested, validUntil, size) {
|
|
277
|
+
const quotes = Array.isArray(requested) ? requested : [requested];
|
|
278
|
+
insist(quotes.length > 0);
|
|
279
|
+
insist(prepared.valid_until === validUntil && prepared.quote_size === size && prepared.expires_at === validUntil + MAKER_TIMEOUT_SECONDS + 1);
|
|
280
|
+
insist(Number.isSafeInteger(prepared.sequence) && prepared.sequence > order.last_sequence && prepared.to_sign.length === 1 && prepared.quotes.length === quotes.length);
|
|
281
|
+
quotes.forEach((quote, index) => {
|
|
282
|
+
const received = required(prepared.quotes[index]);
|
|
283
|
+
insist(received.venue === quote.venue && same(received.recipient, quote.recipient) && received.supply === quote.supply && received.receive === quote.receive && received.min_fill === quote.min_fill);
|
|
284
|
+
});
|
|
285
|
+
checkSignedQuote(required(prepared.to_sign[0]), order, quotes, prepared.sequence, prepared.expires_at);
|
|
286
|
+
}
|
|
287
|
+
/** The entire replacement set is reviewed, in order: no extra route, recipient or amount may be
|
|
288
|
+
* added, and each position is the quote index a fill names. */
|
|
289
|
+
export function checkSignedQuote(item, order, asked, sequence, expires) {
|
|
290
|
+
const requested = Array.isArray(asked) ? asked : [asked];
|
|
291
|
+
checkEscrowTyped(item.typed_data, order.venue);
|
|
292
|
+
const m = item.typed_data.message;
|
|
293
|
+
const quotes = array(m.quotes).map(record);
|
|
294
|
+
insist(item.kind === "quote_set" && item.typed_data.primaryType === "QuoteSet" && same(item.signer, order.quote_signer) && same(m.owner, order.owner_address) && same(m.offerId, order.id) && uint(m.sequence) === BigInt(sequence) && uint(m.expiresAt) === BigInt(expires));
|
|
295
|
+
insist(requested.length > 0 && quotes.length === requested.length);
|
|
296
|
+
for (const [index, expected] of requested.entries()) {
|
|
297
|
+
const q = required(quotes[index]), chain = CHAINS[expected.venue];
|
|
298
|
+
insist(chain && q.venue === expected.venue && Object.keys(q).length === quote.length && expected.venue !== order.venue && uint(q.chainId) === BigInt(chain.id) && same(q.token, chain.token) && same(q.recipient, expected.recipient) && uint(q.supply) === BigInt(expected.supply) && uint(q.receive) === BigInt(expected.receive) && uint(q.minFill) === BigInt(expected.min_fill));
|
|
299
|
+
insist(uint(q.supply) > 0n && uint(q.receive) > 0n && uint(q.minFill) <= uint(q.supply));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
export function checkCloseOrders(items, ids, master) {
|
|
303
|
+
insist(items.length === 1);
|
|
304
|
+
const item = required(items[0]), m = item.typed_data.message;
|
|
305
|
+
// CloseOrders is an off-chain operator authorization in the HyperEVM domain.
|
|
306
|
+
checkEscrowTyped(item.typed_data, "hyperliquid");
|
|
307
|
+
const orders = array(m.orders);
|
|
308
|
+
insist(item.kind === "close_orders" && item.typed_data.primaryType === "CloseOrders" && same(item.signer, master) && same(m.account, master) && orders.length > 0 && new Set(orders).size === orders.length && orders.every(id => ids.some(asked => same(id, asked))));
|
|
309
|
+
checkDeadline(m.deadline, 3600);
|
|
310
|
+
}
|
|
311
|
+
/** SIWE intent pinned to `origin`, tolerating two minutes of local clock skew. */
|
|
312
|
+
export function checkLoginChallenge(challenge, origin, now = Date.now()) {
|
|
313
|
+
const lines = challenge.message.split("\n");
|
|
314
|
+
const issued = Date.parse(lines[9]?.slice("Issued At: ".length) ?? "");
|
|
315
|
+
const expires = Date.parse(challenge.expires_at);
|
|
316
|
+
insist(lines.length === 11 && lines[0] === `${new URL(origin).host} wants you to sign in with your Ethereum account:` && same(lines[1], challenge.address) && lines[2] === "" && lines[3] === "Sign in to LaMarge." && lines[4] === "" && lines[5] === `URI: ${origin}` && lines[6] === "Version: 1" && lines[7] === "Chain ID: 1" && lines[8] === `Nonce: ${challenge.nonce}` && lines[9]?.startsWith("Issued At: ") && lines[10] === `Expiration Time: ${challenge.expires_at}` && issued <= now + 120_000 && expires > now - 120_000 && expires > issued && expires - issued <= 600_000, "Unexpected sign-in challenge");
|
|
317
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const verifiedIntent: unique symbol;
|
|
2
|
+
export type Verified<T> = T & {
|
|
3
|
+
readonly [verifiedIntent]: true;
|
|
4
|
+
};
|
|
5
|
+
export declare function required<T>(value: T | null | undefined, message?: string): T;
|
|
6
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
7
|
+
export declare function record(value: unknown): Record<string, unknown>;
|
|
8
|
+
export declare function array(value: unknown): unknown[];
|
|
9
|
+
export declare function address(value: unknown): `0x${string}`;
|
|
10
|
+
export declare function hex(value: unknown): `0x${string}`;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function required(value, message = "Required value is missing") {
|
|
2
|
+
if (value === undefined || value === null)
|
|
3
|
+
throw new Error(message);
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
export function isRecord(value) {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
export function record(value) {
|
|
10
|
+
if (!isRecord(value))
|
|
11
|
+
throw new Error("Expected an object");
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
export function array(value) {
|
|
15
|
+
if (!Array.isArray(value))
|
|
16
|
+
throw new Error("Expected an array");
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
export function address(value) {
|
|
20
|
+
if (!isAddress(value))
|
|
21
|
+
throw new Error("Invalid EVM address");
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function isAddress(value) {
|
|
25
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value);
|
|
26
|
+
}
|
|
27
|
+
export function hex(value) {
|
|
28
|
+
if (!isHex(value))
|
|
29
|
+
throw new Error("Invalid hexadecimal bytes");
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function isHex(value) {
|
|
33
|
+
return typeof value === "string" && /^0x(?:[0-9a-fA-F]{2})*$/.test(value);
|
|
34
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Retry-After accepts either seconds or an HTTP date. */
|
|
2
|
+
export function retryAfterMs(value, now = Date.now()) {
|
|
3
|
+
if (!value?.trim())
|
|
4
|
+
return undefined;
|
|
5
|
+
const milliseconds = /^\d+$/.test(value.trim()) ? Number(value) * 1000 : Date.parse(value) - now;
|
|
6
|
+
return Number.isFinite(milliseconds) ? Math.max(0, milliseconds) : undefined;
|
|
7
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Client } from "./client.ts";
|
|
2
|
+
import type { Signer } from "./signer.ts";
|
|
3
|
+
/** Every chain the API names must be the reviewed deployment pinned in this release of the SDK. */
|
|
4
|
+
export declare function checkDeployment(client: Client, signal?: AbortSignal): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Sign-In with Ethereum for `origin`, the app the challenge names (`https://app.lamarge.trade`).
|
|
7
|
+
* The deployment is checked before anything is signed; the client then carries the session.
|
|
8
|
+
*/
|
|
9
|
+
export declare function signIn(client: Client, signer: Pick<Signer, "address" | "signMessage">, origin: string, signal?: AbortSignal): Promise<{
|
|
10
|
+
token: string;
|
|
11
|
+
expires_at: import("./index.ts").components["schemas"]["Timestamp"];
|
|
12
|
+
session_id: import("./index.ts").components["schemas"]["Uuid"];
|
|
13
|
+
}>;
|
|
14
|
+
export declare function signOut(client: Client, sessionId: string): Promise<void>;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { checkLoginChallenge, checkRegistry } from "./protocol/protocol.js";
|
|
2
|
+
/** Every chain the API names must be the reviewed deployment pinned in this release of the SDK. */
|
|
3
|
+
export async function checkDeployment(client, signal) {
|
|
4
|
+
for (const venue of await client.call("getVenues", { signal }))
|
|
5
|
+
if (venue.chain)
|
|
6
|
+
checkRegistry(venue.id, venue.chain);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Sign-In with Ethereum for `origin`, the app the challenge names (`https://app.lamarge.trade`).
|
|
10
|
+
* The deployment is checked before anything is signed; the client then carries the session.
|
|
11
|
+
*/
|
|
12
|
+
export async function signIn(client, signer, origin, signal) {
|
|
13
|
+
const address = signer.address;
|
|
14
|
+
// Asked while the deployment is checked; that check still decides first, before any signature.
|
|
15
|
+
const asked = client.call("postAuthChallenge", { body: { address }, signal });
|
|
16
|
+
asked.catch(() => undefined);
|
|
17
|
+
await checkDeployment(client, signal);
|
|
18
|
+
const challenge = await asked;
|
|
19
|
+
checkLoginChallenge({ ...challenge, address }, origin);
|
|
20
|
+
const signature = await signer.signMessage({ message: challenge.message });
|
|
21
|
+
const session = await client.call("postAuthVerify", { body: { address, nonce: challenge.nonce, signature }, signal });
|
|
22
|
+
client.token = session.token;
|
|
23
|
+
return session;
|
|
24
|
+
}
|
|
25
|
+
export async function signOut(client, sessionId) {
|
|
26
|
+
await client.call("deleteV1AuthSessionsId", { path: { id: sessionId } });
|
|
27
|
+
client.token = undefined;
|
|
28
|
+
}
|
package/dist/signer.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type DeploymentManifest } from "./protocol/deployments.ts";
|
|
2
|
+
import { type SignItem, type Typed } from "./protocol/protocol.ts";
|
|
3
|
+
export type Hex = `0x${string}`;
|
|
4
|
+
export interface TypedDataRequest {
|
|
5
|
+
domain: {
|
|
6
|
+
name: string;
|
|
7
|
+
version?: string;
|
|
8
|
+
chainId: number;
|
|
9
|
+
verifyingContract: Hex;
|
|
10
|
+
};
|
|
11
|
+
types: Record<string, {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
}[]>;
|
|
15
|
+
primaryType: string;
|
|
16
|
+
message: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Whatever holds the key: a viem `LocalAccount` (a private key, or KMS through `toAccount`) fits as
|
|
20
|
+
* is; a browser wallet wraps its client. The SDK hands it only data its checks already accepted.
|
|
21
|
+
*/
|
|
22
|
+
export interface Signer {
|
|
23
|
+
readonly address: Hex;
|
|
24
|
+
signMessage(request: {
|
|
25
|
+
message: string;
|
|
26
|
+
}): Promise<Hex>;
|
|
27
|
+
/** Optional for a contract quote delegate (ERC-1271), whose signature is collected elsewhere. */
|
|
28
|
+
signTypedData?(request: TypedDataRequest): Promise<Hex>;
|
|
29
|
+
}
|
|
30
|
+
/** `typed` as the API sends it, in the form wallets sign: the domain's type implied, values converted. */
|
|
31
|
+
export declare function typedDataRequest(typed: Typed): TypedDataRequest;
|
|
32
|
+
export declare function signTyped(signer: Pick<Signer, "signTypedData">, typed: Typed): Promise<Hex>;
|
|
33
|
+
export interface SignedItem {
|
|
34
|
+
kind: string;
|
|
35
|
+
fill_id?: string;
|
|
36
|
+
signature: Hex;
|
|
37
|
+
}
|
|
38
|
+
export declare const signedItem: (item: {
|
|
39
|
+
kind: string;
|
|
40
|
+
fill_id?: string | undefined;
|
|
41
|
+
}, signature: Hex) => SignedItem;
|
|
42
|
+
/** A checked bundle, signed item by item once every domain is a pinned deployment. */
|
|
43
|
+
export declare function signItems(signer: Pick<Signer, "address" | "signTypedData">, items: readonly SignItem[], manifest?: DeploymentManifest): Promise<SignedItem[]>;
|