@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
package/dist/withdraw.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { LaMargeError } from "./client.js";
|
|
2
|
+
import { checkPaymentRefund, checkRefundLocks } from "./protocol/consents.js";
|
|
3
|
+
import { CHAINS, checkWithdrawal, escrowOf, insist, same } from "./protocol/protocol.js";
|
|
4
|
+
import { address, array, hex, record, required } from "./protocol/validation.js";
|
|
5
|
+
import { signItems, signTyped } from "./signer.js";
|
|
6
|
+
import { exclusive, memoryStore, typedData } from "./store.js";
|
|
7
|
+
/**
|
|
8
|
+
* One withdrawal's signature, stored under `key` (with `context`) before its first submission and
|
|
9
|
+
* submitted again after a restart; a stored one is checked at its own signing time. The caller
|
|
10
|
+
* decides when the record may go.
|
|
11
|
+
*/
|
|
12
|
+
export async function submitWithdrawal(options) {
|
|
13
|
+
const { key, venue, signer, destination, amount, store, signal } = options;
|
|
14
|
+
const stored = await store.load(key);
|
|
15
|
+
let saved = stored === undefined || record(stored).typed === undefined ? undefined : { typed: typedData(record(stored).typed), signature: hex(record(stored).signature) };
|
|
16
|
+
if (!saved) {
|
|
17
|
+
const typed = await options.prepare();
|
|
18
|
+
checkWithdrawal(typed, venue, signer.address, destination, amount);
|
|
19
|
+
saved = { typed, signature: await signTyped(signer, typed) };
|
|
20
|
+
signal?.throwIfAborted();
|
|
21
|
+
await store.save(key, { ...options.context, ...saved });
|
|
22
|
+
}
|
|
23
|
+
const message = saved.typed.message;
|
|
24
|
+
const signedAt = venue === "hyperliquid" ? Number(message.nonce) / 1000 : Number(message.ts);
|
|
25
|
+
checkWithdrawal(saved.typed, venue, signer.address, destination, amount, signedAt);
|
|
26
|
+
const kept = { ...options.context, ...saved };
|
|
27
|
+
return { result: await options.submit(saved.typed, saved.signature), saved: kept };
|
|
28
|
+
}
|
|
29
|
+
/** A withdrawal read back from a store, in the API's shape, or refused. */
|
|
30
|
+
function withdrawalOf(value) {
|
|
31
|
+
const row = record(value);
|
|
32
|
+
const text = (name) => { const field = row[name]; insist(typeof field === "string", "Unreadable stored withdrawal"); return field; };
|
|
33
|
+
const optional = (name) => row[name] === null ? null : text(name);
|
|
34
|
+
return { id: text("id"), venue: text("venue"), amount: text("amount"), destination: text("destination"), state: text("state"),
|
|
35
|
+
venue_ref: optional("venue_ref"), error: optional("error"), created_at: text("created_at"), relayed_at: optional("relayed_at") };
|
|
36
|
+
}
|
|
37
|
+
const FINAL = new Set(["relayed", "refused"]);
|
|
38
|
+
/**
|
|
39
|
+
* Withdraws exactly `amount` from the signer's Hyperliquid or Polymarket account. Its signature
|
|
40
|
+
* and outcome stay in `store` under `id`: running the same request again returns the outcome or
|
|
41
|
+
* submits the same signature, and a refused one asks for a new id. Aster relays a withdrawal in a
|
|
42
|
+
* second envelope this flow does not sign yet.
|
|
43
|
+
*/
|
|
44
|
+
export async function withdraw(client, signer, request, options = {}) {
|
|
45
|
+
const { id, venue, amount, to } = request;
|
|
46
|
+
const { store = memoryStore(), signal } = options;
|
|
47
|
+
insist(venue === "hyperliquid" || venue === "polymarket", `Withdrawals from ${venue} are not supported here`);
|
|
48
|
+
const key = `withdrawal:${id}`;
|
|
49
|
+
return exclusive(store, key, async () => {
|
|
50
|
+
const context = { venue, amount: amount.toString(), to, signer: signer.address.toLowerCase() };
|
|
51
|
+
const stored = await store.load(key);
|
|
52
|
+
if (stored !== undefined) {
|
|
53
|
+
const known = record(stored);
|
|
54
|
+
insist(Object.entries(context).every(([name, value]) => known[name] === value), `Withdrawal ${id} names another withdrawal`);
|
|
55
|
+
if (typeof known.refused === "string")
|
|
56
|
+
throw new Error(`Withdrawal ${id} was refused (${known.refused}); use a new id`);
|
|
57
|
+
const outcome = known.result === undefined ? undefined : withdrawalOf(known.result);
|
|
58
|
+
// Queued, submitting or unknown: the same signature, submitted again, reads the current state.
|
|
59
|
+
if (outcome && FINAL.has(outcome.state))
|
|
60
|
+
return outcome;
|
|
61
|
+
}
|
|
62
|
+
const balance = (await client.call("getBalances", { signal })).find(row => row.venue === venue);
|
|
63
|
+
insist(balance && same(balance.address, signer.address), "Linked account changed; refresh before signing");
|
|
64
|
+
const destination = required(to === "wallet" ? balance.venue_wallet : signer.address, "No linked destination wallet");
|
|
65
|
+
try {
|
|
66
|
+
const { result, saved } = await submitWithdrawal({
|
|
67
|
+
key, context, venue, signer, destination, amount, store, signal,
|
|
68
|
+
prepare: async () => (await client.call("postWithdrawalPrepare", { body: { venue, amount: amount.toString(), to }, signal })).typed_data,
|
|
69
|
+
submit: (typed, signature) => client.call("postWithdrawal", { body: { venue, typed_data: typed, signature }, signal, retry: true }),
|
|
70
|
+
});
|
|
71
|
+
await store.save(key, { ...saved, result });
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof LaMargeError && (error.code === "VENUE_REJECTED" || error.code === "WITHDRAWAL_EXPIRED"))
|
|
76
|
+
await store.save(key, { ...record(await store.load(key) ?? {}), refused: error.code });
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const LOCKS = [{ type: "function", name: "locks", stateMutability: "view",
|
|
82
|
+
inputs: [{ name: "owner", type: "address" }, { name: "offerId", type: "bytes32" }, { name: "swapId", type: "bytes32" }],
|
|
83
|
+
outputs: [{ name: "amount", type: "uint256" }, { name: "hashlock", type: "bytes32" }, { name: "recipient", type: "address" }, { name: "refundAt", type: "uint64" }, { name: "state", type: "uint8" }, { name: "dex", type: "uint32" }] }];
|
|
84
|
+
const DIGEST = [{ type: "function", name: "refundPaymentDigest", stateMutability: "view",
|
|
85
|
+
inputs: [{ name: "consent", type: "tuple", components: [{ name: "payer", type: "address" }, { name: "swapId", type: "bytes32" }, { name: "deadline", type: "uint256" }] }],
|
|
86
|
+
outputs: [{ type: "bytes32" }] }];
|
|
87
|
+
const ZERO = `0x${"0".repeat(64)}`;
|
|
88
|
+
function lockOf(value) {
|
|
89
|
+
const fields = array(value);
|
|
90
|
+
insist(fields.length === 6, "Unreadable escrow lock");
|
|
91
|
+
const [amount, hashlock, recipient, refundAt, state, dex] = fields;
|
|
92
|
+
insist(typeof amount === "bigint" && typeof hashlock === "string" && typeof recipient === "string" && typeof refundAt === "bigint" && typeof state === "number" && typeof dex === "number", "Unreadable escrow lock");
|
|
93
|
+
return [amount, hashlock, recipient, refundAt, state, dex];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The pinned contracts, read directly: the payment lock at a finalized block, the maker's lock at
|
|
97
|
+
* the latest, where the destination has one, and the consent digest, which an old ABI cannot
|
|
98
|
+
* answer. A server's label cannot authorize giving up a payment for capital locked or delivered.
|
|
99
|
+
*/
|
|
100
|
+
export async function checkEscrowLocks(fill, deadline, readers) {
|
|
101
|
+
const reader = (venue) => required(readers[required(CHAINS[venue], "Unknown venue").id], `No reader for ${venue}'s chain`);
|
|
102
|
+
const escrow = (venue) => address(escrowOf(venue));
|
|
103
|
+
const [payment, maker] = await Promise.all([
|
|
104
|
+
reader(fill.from_venue).readContract({ address: escrow(fill.from_venue), abi: LOCKS, functionName: "locks", args: [address(fill.payer), ZERO, hex(fill.swap_id)], blockTag: "finalized" }),
|
|
105
|
+
fill.deliver_by ? null : reader(fill.to_venue).readContract({ address: escrow(fill.to_venue), abi: LOCKS, functionName: "locks", args: [address(fill.maker_owner), hex(fill.order_id), hex(fill.swap_id)], blockTag: "latest" }),
|
|
106
|
+
// An old ABI cannot answer the consent digest: the call fails.
|
|
107
|
+
reader(fill.from_venue).readContract({ address: escrow(fill.from_venue), abi: DIGEST, functionName: "refundPaymentDigest", args: [{ payer: address(fill.payer), swapId: hex(fill.swap_id), deadline: BigInt(deadline) }] }),
|
|
108
|
+
]);
|
|
109
|
+
checkRefundLocks(fill, lockOf(payment), maker === null ? null : lockOf(maker));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* As the maker of an abandoned fill, gives the taker's locked payment back before `t2`. Without a
|
|
113
|
+
* reader of each chain the fill touches, nothing is signed.
|
|
114
|
+
*/
|
|
115
|
+
export async function refundPayment(client, signer, fill, readers, signal) {
|
|
116
|
+
const prepared = await client.call("preparePaymentRefund", { path: { id: fill.id }, signal });
|
|
117
|
+
checkPaymentRefund(fill, prepared);
|
|
118
|
+
await checkEscrowLocks(fill, prepared.deadline, readers);
|
|
119
|
+
const [signed] = await signItems(signer, prepared.to_sign);
|
|
120
|
+
await checkEscrowLocks(fill, prepared.deadline, readers);
|
|
121
|
+
await client.call("submitPaymentRefund", { path: { id: fill.id }, body: { deadline: prepared.deadline, signature: required(signed).signature }, signal, retry: true });
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lamarge/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript client for the LaMarge API: sign-in, transfers, takes and market making, with every signature checked first.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22.12"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./protocol": {
|
|
17
|
+
"types": "./dist/protocol/index.d.ts",
|
|
18
|
+
"default": "./dist/protocol/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./aster": {
|
|
21
|
+
"types": "./dist/aster.d.ts",
|
|
22
|
+
"default": "./dist/aster.js"
|
|
23
|
+
},
|
|
24
|
+
"./lighter": {
|
|
25
|
+
"types": "./dist/lighter.d.ts",
|
|
26
|
+
"default": "./dist/lighter.js"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"!dist/**/*.test.*"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"api:generate": "bun run scripts/generate-api.ts",
|
|
36
|
+
"api:check": "bun run scripts/generate-api.ts --check",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"test": "bun test",
|
|
39
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
40
|
+
"check": "bun run api:check && bun run typecheck && bun test && bun run build && bun run check:package",
|
|
41
|
+
"check:package": "bun run scripts/check-package.ts"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"viem": "^2.38.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"viem": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
53
|
+
"@types/bun": "^1.4.2",
|
|
54
|
+
"openapi-typescript": "^7.13.0",
|
|
55
|
+
"publint": "0.3.25",
|
|
56
|
+
"typescript": "^5.9.0",
|
|
57
|
+
"viem": "2.56.9"
|
|
58
|
+
}
|
|
59
|
+
}
|