@invisible-labs/sdk 0.6.0-devnet.1
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/README.md +210 -0
- package/dist/chunk-7DSVN3MV.js +1145 -0
- package/dist/chunk-7DSVN3MV.js.map +1 -0
- package/dist/chunk-JNNVPZEU.js +599 -0
- package/dist/chunk-JNNVPZEU.js.map +1 -0
- package/dist/chunk-JRMKCE3S.js +50 -0
- package/dist/chunk-JRMKCE3S.js.map +1 -0
- package/dist/chunk-LBC7ALYO.js +3783 -0
- package/dist/chunk-LBC7ALYO.js.map +1 -0
- package/dist/chunk-NCY4FCYU.js +1911 -0
- package/dist/chunk-NCY4FCYU.js.map +1 -0
- package/dist/chunk-OHIM2YWU.js +126 -0
- package/dist/chunk-OHIM2YWU.js.map +1 -0
- package/dist/chunk-SZAYO2L5.js +123 -0
- package/dist/chunk-SZAYO2L5.js.map +1 -0
- package/dist/chunk-TQNNTV5F.js +17 -0
- package/dist/chunk-TQNNTV5F.js.map +1 -0
- package/dist/chunk-VR6T6OJS.js +24 -0
- package/dist/chunk-VR6T6OJS.js.map +1 -0
- package/dist/chunk-XUWBGET5.js +221 -0
- package/dist/chunk-XUWBGET5.js.map +1 -0
- package/dist/coordinator-7Y45MCCZ.js +4 -0
- package/dist/coordinator-7Y45MCCZ.js.map +1 -0
- package/dist/createSession-D3ym-Ira.d.ts +177 -0
- package/dist/dkgWorker.d.ts +2 -0
- package/dist/dkgWorker.js +61 -0
- package/dist/dkgWorker.js.map +1 -0
- package/dist/events.d.ts +54 -0
- package/dist/events.js +143 -0
- package/dist/events.js.map +1 -0
- package/dist/frostRuntime-JESDHN6O.js +4 -0
- package/dist/frostRuntime-JESDHN6O.js.map +1 -0
- package/dist/index.d.ts +499 -0
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -0
- package/dist/lp.d.ts +388 -0
- package/dist/lp.js +1711 -0
- package/dist/lp.js.map +1 -0
- package/dist/presets.d.ts +43 -0
- package/dist/presets.js +70 -0
- package/dist/presets.js.map +1 -0
- package/dist/session-DFuy54C-.d.ts +31 -0
- package/dist/stats.d.ts +47 -0
- package/dist/stats.js +21 -0
- package/dist/stats.js.map +1 -0
- package/dist/storage.d.ts +82 -0
- package/dist/storage.js +246 -0
- package/dist/storage.js.map +1 -0
- package/dist/types-ZhV7TIQY.d.ts +76 -0
- package/dist/types.generated-CHGSbmLp.d.ts +67 -0
- package/dist/user.d.ts +377 -0
- package/dist/user.js +1225 -0
- package/dist/user.js.map +1 -0
- package/package.json +82 -0
package/dist/lp.js
ADDED
|
@@ -0,0 +1,1711 @@
|
|
|
1
|
+
import { isValidLpPositionId, LP_DEFAULT_REFILL_BATCH_SIZE, LP_DEFAULT_TARGET_SHARDS, LP_MAX_TARGET_SHARDS, LP_MAX_REFILL_BATCH_SIZE } from './chunk-TQNNTV5F.js';
|
|
2
|
+
import { publicKey, assertAttested, lamports } from './chunk-SZAYO2L5.js';
|
|
3
|
+
import { requestEnvelope } from './chunk-XUWBGET5.js';
|
|
4
|
+
import { getSessionState } from './chunk-JRMKCE3S.js';
|
|
5
|
+
import { CommandError, PolicyValidationError, RoutingError, LpLifecycleError } from './chunk-OHIM2YWU.js';
|
|
6
|
+
import './chunk-VR6T6OJS.js';
|
|
7
|
+
|
|
8
|
+
// src/lp/positionCode.ts
|
|
9
|
+
var LEGACY_CODE_PREFIX = "ilp1";
|
|
10
|
+
var CODE_PREFIX = "ilp2";
|
|
11
|
+
var SECRET_HEX_LENGTH = 64;
|
|
12
|
+
var encoder = new TextEncoder();
|
|
13
|
+
async function generateLpPositionCode(lpPositionId) {
|
|
14
|
+
validateLpPositionId(lpPositionId);
|
|
15
|
+
const secret = new Uint8Array(32);
|
|
16
|
+
getCrypto().getRandomValues(secret);
|
|
17
|
+
const keyPair = await getCrypto().subtle.generateKey({ name: "Ed25519" }, true, [
|
|
18
|
+
"sign",
|
|
19
|
+
"verify"
|
|
20
|
+
]);
|
|
21
|
+
const [privatePkcs8, publicRaw] = await Promise.all([
|
|
22
|
+
getCrypto().subtle.exportKey("pkcs8", keyPair.privateKey),
|
|
23
|
+
getCrypto().subtle.exportKey("raw", keyPair.publicKey)
|
|
24
|
+
]);
|
|
25
|
+
const code = encodeCode(
|
|
26
|
+
lpPositionId,
|
|
27
|
+
bytesToHex(secret),
|
|
28
|
+
bytesToHex(new Uint8Array(privatePkcs8)),
|
|
29
|
+
bytesToHex(new Uint8Array(publicRaw))
|
|
30
|
+
);
|
|
31
|
+
secret.fill(0);
|
|
32
|
+
return deriveLpPositionCodeAuth(code);
|
|
33
|
+
}
|
|
34
|
+
async function deriveLpPositionCodeAuth(input) {
|
|
35
|
+
const code = normalizeLpPositionCode(input);
|
|
36
|
+
const parsed = parseCode(code);
|
|
37
|
+
const secret = hexToBytes(parsed.secretHex);
|
|
38
|
+
try {
|
|
39
|
+
const authPublicKey = parsed.authPublicKeyHex ? hexToBytes(parsed.authPublicKeyHex) : new Uint8Array();
|
|
40
|
+
const authPrivateKeyPkcs8 = parsed.authPrivateKeyPkcs8Hex ? hexToBytes(parsed.authPrivateKeyPkcs8Hex) : new Uint8Array();
|
|
41
|
+
if (authPublicKey.length > 0 || authPrivateKeyPkcs8.length > 0) {
|
|
42
|
+
await assertAuthKeyPair(authPrivateKeyPkcs8, authPublicKey);
|
|
43
|
+
}
|
|
44
|
+
const refillPublicKey = await deriveBytes(
|
|
45
|
+
"invisible-lp-refill-public-key-v0",
|
|
46
|
+
parsed.lpPositionId,
|
|
47
|
+
secret
|
|
48
|
+
);
|
|
49
|
+
const withdrawalCommitment = await deriveBytes(
|
|
50
|
+
"invisible-lp-redemption-commitment-v0",
|
|
51
|
+
parsed.lpPositionId,
|
|
52
|
+
secret
|
|
53
|
+
);
|
|
54
|
+
return {
|
|
55
|
+
code,
|
|
56
|
+
lpPositionId: parsed.lpPositionId,
|
|
57
|
+
authPublicKey,
|
|
58
|
+
authPrivateKeyPkcs8,
|
|
59
|
+
refillPublicKey,
|
|
60
|
+
withdrawalCommitment
|
|
61
|
+
};
|
|
62
|
+
} finally {
|
|
63
|
+
secret.fill(0);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function normalizeLpPositionCode(input) {
|
|
67
|
+
return input.trim();
|
|
68
|
+
}
|
|
69
|
+
function bytesToHex(bytes) {
|
|
70
|
+
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
71
|
+
}
|
|
72
|
+
async function signLpCommandBytes(auth, bytes) {
|
|
73
|
+
if (auth.authPrivateKeyPkcs8.length === 0 || auth.authPublicKey.length === 0) {
|
|
74
|
+
throw new Error("LP Position Code cannot sign owner commands; create a new ilp2 code");
|
|
75
|
+
}
|
|
76
|
+
const privateKey = await getCrypto().subtle.importKey(
|
|
77
|
+
"pkcs8",
|
|
78
|
+
bufferSource(auth.authPrivateKeyPkcs8),
|
|
79
|
+
{ name: "Ed25519" },
|
|
80
|
+
false,
|
|
81
|
+
["sign"]
|
|
82
|
+
);
|
|
83
|
+
return new Uint8Array(
|
|
84
|
+
await getCrypto().subtle.sign({ name: "Ed25519" }, privateKey, bufferSource(bytes))
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
function encodeCode(lpPositionId, secretHex, authPrivateKeyPkcs8Hex, authPublicKeyHex) {
|
|
88
|
+
return `${CODE_PREFIX}:${lpPositionId}:${secretHex}:${authPrivateKeyPkcs8Hex}:${authPublicKeyHex}`;
|
|
89
|
+
}
|
|
90
|
+
function parseCode(code) {
|
|
91
|
+
const parts = code.split(":");
|
|
92
|
+
if (parts.length === 3 && parts[0] === LEGACY_CODE_PREFIX) {
|
|
93
|
+
const [, lpPositionId2, secretHex2] = parts;
|
|
94
|
+
validateLpPositionId(lpPositionId2);
|
|
95
|
+
if (!new RegExp(`^[0-9a-fA-F]{${SECRET_HEX_LENGTH}}$`).test(secretHex2)) {
|
|
96
|
+
throw new Error("LP Position Code secret must be 32 bytes hex");
|
|
97
|
+
}
|
|
98
|
+
return { lpPositionId: lpPositionId2, secretHex: secretHex2.toLowerCase() };
|
|
99
|
+
}
|
|
100
|
+
if (parts.length !== 5 || parts[0] !== CODE_PREFIX) {
|
|
101
|
+
throw new Error("LP Position Code must use the ilp2 format");
|
|
102
|
+
}
|
|
103
|
+
const [, lpPositionId, secretHex, authPrivateKeyPkcs8Hex, authPublicKeyHex] = parts;
|
|
104
|
+
validateLpPositionId(lpPositionId);
|
|
105
|
+
if (!new RegExp(`^[0-9a-fA-F]{${SECRET_HEX_LENGTH}}$`).test(secretHex)) {
|
|
106
|
+
throw new Error("LP Position Code secret must be 32 bytes hex");
|
|
107
|
+
}
|
|
108
|
+
if (!/^[0-9a-fA-F]+$/.test(authPrivateKeyPkcs8Hex) || authPrivateKeyPkcs8Hex.length < 64) {
|
|
109
|
+
throw new Error("LP Position Code auth private key must be pkcs8 hex");
|
|
110
|
+
}
|
|
111
|
+
if (!new RegExp(`^[0-9a-fA-F]{${SECRET_HEX_LENGTH}}$`).test(authPublicKeyHex)) {
|
|
112
|
+
throw new Error("LP Position Code auth public key must be 32 bytes hex");
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
lpPositionId,
|
|
116
|
+
secretHex: secretHex.toLowerCase(),
|
|
117
|
+
authPrivateKeyPkcs8Hex: authPrivateKeyPkcs8Hex.toLowerCase(),
|
|
118
|
+
authPublicKeyHex: authPublicKeyHex.toLowerCase()
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function validateLpPositionId(lpPositionId) {
|
|
122
|
+
if (!isValidLpPositionId(lpPositionId)) {
|
|
123
|
+
throw new Error("LP Position Code has an invalid position id");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function deriveBytes(domain, lpPositionId, secret) {
|
|
127
|
+
const domainBytes = encoder.encode(`${domain}:${lpPositionId}:`);
|
|
128
|
+
const material = new Uint8Array(domainBytes.length + secret.length);
|
|
129
|
+
material.set(domainBytes);
|
|
130
|
+
material.set(secret, domainBytes.length);
|
|
131
|
+
const digest = await getCrypto().subtle.digest("SHA-256", material);
|
|
132
|
+
material.fill(0);
|
|
133
|
+
return new Uint8Array(digest);
|
|
134
|
+
}
|
|
135
|
+
async function assertAuthKeyPair(privatePkcs8, publicRaw) {
|
|
136
|
+
if (publicRaw.length !== 32) {
|
|
137
|
+
throw new Error("LP Position Code auth public key must be 32 bytes");
|
|
138
|
+
}
|
|
139
|
+
const [privateKey, publicKey2] = await Promise.all([
|
|
140
|
+
getCrypto().subtle.importKey("pkcs8", bufferSource(privatePkcs8), { name: "Ed25519" }, false, [
|
|
141
|
+
"sign"
|
|
142
|
+
]),
|
|
143
|
+
getCrypto().subtle.importKey("raw", bufferSource(publicRaw), { name: "Ed25519" }, false, [
|
|
144
|
+
"verify"
|
|
145
|
+
])
|
|
146
|
+
]);
|
|
147
|
+
const challenge = encoder.encode("invisible-lp-auth-self-test");
|
|
148
|
+
const signature = await getCrypto().subtle.sign({ name: "Ed25519" }, privateKey, challenge);
|
|
149
|
+
const ok = await getCrypto().subtle.verify({ name: "Ed25519" }, publicKey2, signature, challenge);
|
|
150
|
+
if (!ok) {
|
|
151
|
+
throw new Error("LP Position Code auth key pair is invalid");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function bufferSource(bytes) {
|
|
155
|
+
return new Uint8Array(bytes).buffer;
|
|
156
|
+
}
|
|
157
|
+
function hexToBytes(hex) {
|
|
158
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
159
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
160
|
+
bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
|
|
161
|
+
}
|
|
162
|
+
return bytes;
|
|
163
|
+
}
|
|
164
|
+
function getCrypto() {
|
|
165
|
+
if (globalThis.crypto === void 0 || globalThis.crypto.subtle === void 0) {
|
|
166
|
+
throw new Error("Web Crypto is required for LP Position Code handling");
|
|
167
|
+
}
|
|
168
|
+
return globalThis.crypto;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/lp/withdrawalValidation.ts
|
|
172
|
+
var SINGLE_DESTINATION_WITHDRAWAL_MESSAGE = "single-destination withdrawal is privacy-degrading; pass allowManyToOne: true to acknowledge";
|
|
173
|
+
var DESTINATION_SOURCE_COUNT_MESSAGE = "LP withdrawal requires one destination per withdrawable source unless allowManyToOne is true";
|
|
174
|
+
var SOURCE_COUNT_MESSAGE = "LP withdrawal source count must be a positive integer";
|
|
175
|
+
function validateLpWithdrawalPlan(input) {
|
|
176
|
+
const {
|
|
177
|
+
depositPrincipalLamports,
|
|
178
|
+
earnedFeeLamports,
|
|
179
|
+
destinationAddresses,
|
|
180
|
+
allowManyToOne,
|
|
181
|
+
sourceCount
|
|
182
|
+
} = input;
|
|
183
|
+
lamports(depositPrincipalLamports);
|
|
184
|
+
lamports(earnedFeeLamports);
|
|
185
|
+
if (depositPrincipalLamports === 0 && earnedFeeLamports === 0) {
|
|
186
|
+
throw new PolicyValidationError(
|
|
187
|
+
"INVALID_AMOUNT",
|
|
188
|
+
"LP withdrawal requires a non-zero principal or earned-fee claim"
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
validateDestinationAddresses(destinationAddresses);
|
|
192
|
+
if (sourceCount !== void 0 && (!Number.isInteger(sourceCount) || sourceCount < 1)) {
|
|
193
|
+
throw new PolicyValidationError("INVALID_WITHDRAWAL_PLAN", SOURCE_COUNT_MESSAGE);
|
|
194
|
+
}
|
|
195
|
+
if (sourceCount !== void 0 && allowManyToOne !== true && destinationAddresses.length !== sourceCount) {
|
|
196
|
+
throw new PolicyValidationError("INVALID_WITHDRAWAL_PLAN", DESTINATION_SOURCE_COUNT_MESSAGE);
|
|
197
|
+
}
|
|
198
|
+
if (destinationAddresses.length === 1 && sourceCount !== 1 && allowManyToOne !== true) {
|
|
199
|
+
throw new PolicyValidationError(
|
|
200
|
+
"INVALID_WITHDRAWAL_PLAN",
|
|
201
|
+
SINGLE_DESTINATION_WITHDRAWAL_MESSAGE
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function validateDestinationAddresses(destinationAddresses) {
|
|
206
|
+
if (destinationAddresses.length === 0) {
|
|
207
|
+
throw new PolicyValidationError(
|
|
208
|
+
"INVALID_WITHDRAWAL_PLAN",
|
|
209
|
+
"at least one destination address is required"
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const seen = /* @__PURE__ */ new Set();
|
|
213
|
+
for (const address of destinationAddresses) {
|
|
214
|
+
publicKey(address);
|
|
215
|
+
if (seen.has(address)) {
|
|
216
|
+
throw new PolicyValidationError(
|
|
217
|
+
"INVALID_WITHDRAWAL_PLAN",
|
|
218
|
+
`duplicate withdrawal destination: ${address}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
seen.add(address);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/lp/coordinatorClient.ts
|
|
226
|
+
var COMMAND_TTL_MS = 3e4;
|
|
227
|
+
var LP_COMMAND_REQUEST_TIMEOUT_MS = 12e4;
|
|
228
|
+
var encoder2 = new TextEncoder();
|
|
229
|
+
var protocolPromise = null;
|
|
230
|
+
function createLpCoordinatorClient(session) {
|
|
231
|
+
return {
|
|
232
|
+
initDkg(input, auth) {
|
|
233
|
+
return initDkg(session, input, auth);
|
|
234
|
+
},
|
|
235
|
+
dkgRound1(input, auth) {
|
|
236
|
+
return dkgRound1(session, input, auth);
|
|
237
|
+
},
|
|
238
|
+
dkgRound2(input, auth) {
|
|
239
|
+
return dkgRound2(session, input, auth);
|
|
240
|
+
},
|
|
241
|
+
openPosition(input, auth) {
|
|
242
|
+
return openPosition(session, input, auth);
|
|
243
|
+
},
|
|
244
|
+
getPosition(lpPositionId, auth) {
|
|
245
|
+
return getPosition(session, lpPositionId, auth);
|
|
246
|
+
},
|
|
247
|
+
finalizeDkgBatch(input, auth) {
|
|
248
|
+
return finalizeDkgBatch(session, input, auth);
|
|
249
|
+
},
|
|
250
|
+
refillPosition(input, auth) {
|
|
251
|
+
return refillPosition(session, input, auth);
|
|
252
|
+
},
|
|
253
|
+
reconcileFunding(lpPositionId, auth) {
|
|
254
|
+
return reconcileFunding(session, lpPositionId, auth);
|
|
255
|
+
},
|
|
256
|
+
planFunding(input, auth) {
|
|
257
|
+
return planFunding(session, input, auth);
|
|
258
|
+
},
|
|
259
|
+
confirmFunding(input, auth) {
|
|
260
|
+
return confirmFunding(session, input, auth);
|
|
261
|
+
},
|
|
262
|
+
prepareTopUp(lpPositionId, auth) {
|
|
263
|
+
return prepareTopUp(session, lpPositionId, auth);
|
|
264
|
+
},
|
|
265
|
+
getTopUpStatus(input, auth) {
|
|
266
|
+
return getTopUpStatus(session, input, auth);
|
|
267
|
+
},
|
|
268
|
+
makeAvailable(input, auth) {
|
|
269
|
+
return makeAvailable(session, input, auth);
|
|
270
|
+
},
|
|
271
|
+
executeWithdrawal(input, auth) {
|
|
272
|
+
return executeWithdrawal(session, input, auth);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async function initDkg(session, input, auth) {
|
|
277
|
+
const response = await requestForPosition(session, input.lpPositionId, auth, "dkg_init", {
|
|
278
|
+
case: "dkgInit",
|
|
279
|
+
value: { index: input.index }
|
|
280
|
+
});
|
|
281
|
+
if (response.case !== "dkgInit") throw new Error("Unexpected LP DKG init response");
|
|
282
|
+
return { sessionId: response.value.session_id };
|
|
283
|
+
}
|
|
284
|
+
async function dkgRound1(session, input, auth) {
|
|
285
|
+
const response = await request(session, auth, "dkg_round1", {
|
|
286
|
+
case: "dkgRound1",
|
|
287
|
+
value: {
|
|
288
|
+
session_id: input.sessionId,
|
|
289
|
+
package: Uint8Array.from(input.package)
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
if (response.case !== "dkgRound1") throw new Error("Unexpected LP DKG round1 response");
|
|
293
|
+
return {
|
|
294
|
+
package: response.value.package,
|
|
295
|
+
round2ForLp: response.value.round2_for_lp
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async function dkgRound2(session, input, auth) {
|
|
299
|
+
const response = await request(session, auth, "dkg_round2", {
|
|
300
|
+
case: "dkgRound2",
|
|
301
|
+
value: {
|
|
302
|
+
session_id: input.sessionId,
|
|
303
|
+
package: Uint8Array.from(input.package),
|
|
304
|
+
delegated_share: Uint8Array.from(input.delegatedShare)
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
if (response.case !== "dkgRound2") throw new Error("Unexpected LP DKG round2 response");
|
|
308
|
+
return {
|
|
309
|
+
address: response.value.address,
|
|
310
|
+
jointPubkey: response.value.joint_pubkey,
|
|
311
|
+
shardId: response.value.shard_id,
|
|
312
|
+
index: response.value.index
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async function openPosition(session, input, auth) {
|
|
316
|
+
const response = await requestForPosition(session, input.lpPositionId, auth, "position_open", {
|
|
317
|
+
case: "positionOpen",
|
|
318
|
+
value: {
|
|
319
|
+
auth_public_key: Uint8Array.from(input.authPublicKey),
|
|
320
|
+
refill_public_key: Uint8Array.from(input.refillPublicKey),
|
|
321
|
+
redemption_commitment: Uint8Array.from(input.withdrawalCommitment),
|
|
322
|
+
committed_lamports: input.committedLamports,
|
|
323
|
+
shard_count: input.shardCount,
|
|
324
|
+
shard_amount_lamports: input.shardAmountLamports,
|
|
325
|
+
refill_threshold: input.refillThreshold,
|
|
326
|
+
addresses: input.addresses,
|
|
327
|
+
created_at_ms: input.createdAtMs
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
return expectPosition(response);
|
|
331
|
+
}
|
|
332
|
+
async function getPosition(session, lpPositionId, auth) {
|
|
333
|
+
let cursor;
|
|
334
|
+
let position;
|
|
335
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
336
|
+
for (; ; ) {
|
|
337
|
+
const response = await requestForPosition(session, lpPositionId, auth, "position_get", {
|
|
338
|
+
case: "positionGet",
|
|
339
|
+
value: cursor === void 0 ? {} : { redemption_history_cursor: cursor }
|
|
340
|
+
});
|
|
341
|
+
const page = expectPosition(response);
|
|
342
|
+
const mergedPosition = position === void 0 ? page : mergePositionHistoryPages(position, page);
|
|
343
|
+
position = mergedPosition;
|
|
344
|
+
const actorSync = page.actorSync;
|
|
345
|
+
if (actorSync?.redemptionHistoryHasMore !== true) return mergedPosition;
|
|
346
|
+
const nextCursor = actorSync.redemptionHistoryNextCursor;
|
|
347
|
+
if (nextCursor === void 0 || seenCursors.has(nextCursor)) {
|
|
348
|
+
throw new CommandError(
|
|
349
|
+
"REJECTED_STATE",
|
|
350
|
+
"coordinator LP actor sync returned an invalid redemption history cursor"
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
seenCursors.add(nextCursor);
|
|
354
|
+
cursor = nextCursor;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function finalizeDkgBatch(session, input, auth) {
|
|
358
|
+
const response = await requestForPosition(session, input.lpPositionId, auth, "dkg_finalize", {
|
|
359
|
+
case: "dkgFinalize",
|
|
360
|
+
value: {
|
|
361
|
+
start_index: input.startIndex,
|
|
362
|
+
addresses: input.addresses,
|
|
363
|
+
finalized_at_ms: input.finalizedAtMs
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
return expectPosition(response);
|
|
367
|
+
}
|
|
368
|
+
async function refillPosition(session, input, auth) {
|
|
369
|
+
const response = await requestForPosition(session, input.lpPositionId, auth, "refill", {
|
|
370
|
+
case: "refill",
|
|
371
|
+
value: {
|
|
372
|
+
shard_count: input.shardCount,
|
|
373
|
+
shard_amount_lamports: input.shardAmountLamports,
|
|
374
|
+
addresses: input.addresses,
|
|
375
|
+
authorized_at_ms: input.authorizedAtMs
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
return expectPosition(response);
|
|
379
|
+
}
|
|
380
|
+
async function reconcileFunding(session, lpPositionId, auth) {
|
|
381
|
+
const response = await requestForPosition(session, lpPositionId, auth, "reconcile_funding", {
|
|
382
|
+
case: "reconcileFunding",
|
|
383
|
+
value: {}
|
|
384
|
+
});
|
|
385
|
+
return expectPosition(response);
|
|
386
|
+
}
|
|
387
|
+
async function planFunding(session, input, auth) {
|
|
388
|
+
const response = await requestForPosition(session, input.lpPositionId, auth, "funding_plan", {
|
|
389
|
+
case: "fundingPlan",
|
|
390
|
+
value: {
|
|
391
|
+
shard_count: input.shardCount,
|
|
392
|
+
planned_at_ms: input.nowMs
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
if (response.case !== "shards") throw new Error("Unexpected LP funding plan response");
|
|
396
|
+
return response.value.map(mapShard);
|
|
397
|
+
}
|
|
398
|
+
async function confirmFunding(session, input, auth) {
|
|
399
|
+
const response = await requestForPosition(
|
|
400
|
+
session,
|
|
401
|
+
input.lpPositionId,
|
|
402
|
+
auth,
|
|
403
|
+
"funding_confirmation",
|
|
404
|
+
{
|
|
405
|
+
case: "fundingConfirmation",
|
|
406
|
+
value: {
|
|
407
|
+
shard_id: input.shardId,
|
|
408
|
+
amount_lamports: input.amountLamports,
|
|
409
|
+
funding_tx_signature: input.fundingTxSignature
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
if (response.case !== "shard") throw new Error("Unexpected LP funding confirmation response");
|
|
414
|
+
return mapShard(response.value);
|
|
415
|
+
}
|
|
416
|
+
async function prepareTopUp(session, lpPositionId, auth) {
|
|
417
|
+
const response = await requestForPosition(session, lpPositionId, auth, "prepare_top_up", {
|
|
418
|
+
case: "prepareTopUp",
|
|
419
|
+
value: {}
|
|
420
|
+
});
|
|
421
|
+
if (response.case !== "topUpRequest") throw new Error("Unexpected LP top-up response");
|
|
422
|
+
return mapTopUpRequest(response.value);
|
|
423
|
+
}
|
|
424
|
+
async function getTopUpStatus(session, input, auth) {
|
|
425
|
+
const response = await requestForPosition(session, input.lpPositionId, auth, "top_up_status", {
|
|
426
|
+
case: "topUpStatus",
|
|
427
|
+
value: { top_up_id: input.topUpId }
|
|
428
|
+
});
|
|
429
|
+
if (response.case !== "topUpRequest") throw new Error("Unexpected LP top-up response");
|
|
430
|
+
return mapTopUpRequest(response.value);
|
|
431
|
+
}
|
|
432
|
+
async function makeAvailable(session, input, auth) {
|
|
433
|
+
const response = await requestForPosition(session, input.lpPositionId, auth, "make_available", {
|
|
434
|
+
case: "makeAvailable",
|
|
435
|
+
value: {
|
|
436
|
+
shard_id: input.shardId
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
if (response.case !== "shard") throw new Error("Unexpected LP availability response");
|
|
440
|
+
return mapShard(response.value);
|
|
441
|
+
}
|
|
442
|
+
async function executeWithdrawal(session, input, auth) {
|
|
443
|
+
validateLpWithdrawalPlan({
|
|
444
|
+
destinationAddresses: input.destinationAddresses,
|
|
445
|
+
depositPrincipalLamports: input.depositPrincipalLamports,
|
|
446
|
+
earnedFeeLamports: input.earnedFeeLamports,
|
|
447
|
+
allowManyToOne: input.allowManyToOne
|
|
448
|
+
});
|
|
449
|
+
const response = await requestForPosition(session, input.lpPositionId, auth, "withdraw_request", {
|
|
450
|
+
case: "withdrawRequest",
|
|
451
|
+
value: {
|
|
452
|
+
destination_addresses: input.destinationAddresses,
|
|
453
|
+
deposit_principal_lamports: input.depositPrincipalLamports,
|
|
454
|
+
earned_fee_lamports: input.earnedFeeLamports,
|
|
455
|
+
requested_at_ms: input.requestedAtMs
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
if (response.case !== "withdrawalExecution") {
|
|
459
|
+
throw new Error("Unexpected LP withdrawal execution response");
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
withdrawalId: response.value.withdrawal_id,
|
|
463
|
+
txSignatures: [...response.value.tx_signatures]
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function request(session, auth, action, payload) {
|
|
467
|
+
return requestForPosition(session, auth.lpPositionId, auth, action, payload);
|
|
468
|
+
}
|
|
469
|
+
async function requestForPosition(session, lpPositionId, auth, action, payload) {
|
|
470
|
+
assertAuthMatchesPosition(lpPositionId, auth);
|
|
471
|
+
const protocol = await loadProtocol();
|
|
472
|
+
const state = getSessionState(session);
|
|
473
|
+
const attestation = state.channel?.attestation;
|
|
474
|
+
if (!attestation) throw new Error("LP coordinator attestation is not available");
|
|
475
|
+
const command = {
|
|
476
|
+
lp_position_id: lpPositionId,
|
|
477
|
+
action,
|
|
478
|
+
nonce: randomBytes(32),
|
|
479
|
+
expires_at_ms: Date.now() + COMMAND_TTL_MS,
|
|
480
|
+
coordinator_attestation_hash: await coordinatorAttestationHash(
|
|
481
|
+
attestation.binaryHash,
|
|
482
|
+
attestation.pubkeyHex
|
|
483
|
+
),
|
|
484
|
+
payload
|
|
485
|
+
};
|
|
486
|
+
const signature = await signLpCommandBytes(auth, protocol.encodeLpCommandForSigning(command));
|
|
487
|
+
const { type, payload: envelopePayload } = protocol.lpSignedCommandEnvelope(command, signature);
|
|
488
|
+
const envelope = await requestEnvelope(session, type, envelopePayload, {
|
|
489
|
+
timeoutMs: LP_COMMAND_REQUEST_TIMEOUT_MS
|
|
490
|
+
});
|
|
491
|
+
return protocol.asLpCommandResponsePayload(envelope);
|
|
492
|
+
}
|
|
493
|
+
function assertAuthMatchesPosition(lpPositionId, auth) {
|
|
494
|
+
if (lpPositionId === auth.lpPositionId) return;
|
|
495
|
+
throw new PolicyValidationError(
|
|
496
|
+
"INVALID_POSITION_AUTH",
|
|
497
|
+
"LP Position Code does not match the target LP position"
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
function expectPosition(response) {
|
|
501
|
+
if (response.case !== "position") throw new Error("Unexpected LP position response");
|
|
502
|
+
return mapPosition(response.value);
|
|
503
|
+
}
|
|
504
|
+
function mapPosition(position) {
|
|
505
|
+
return {
|
|
506
|
+
id: position.lp_position_id,
|
|
507
|
+
status: position.status,
|
|
508
|
+
targetShardCount: position.target_shard_count,
|
|
509
|
+
authPublicKey: bytesToHex(position.auth_public_key),
|
|
510
|
+
refillPublicKey: bytesToHex(position.refill_public_key),
|
|
511
|
+
withdrawalCommitment: bytesToHex(position.redemption_commitment),
|
|
512
|
+
committedLamports: position.committed_lamports,
|
|
513
|
+
earnedLamports: position.earned_lamports,
|
|
514
|
+
refillThreshold: position.refill_threshold,
|
|
515
|
+
shards: position.shards.map(mapShard),
|
|
516
|
+
createdAt: position.created_at_ms,
|
|
517
|
+
...position.actor_sync === void 0 ? {} : { actorSync: mapLpActorSync(position.actor_sync) }
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function mapLpActorSync(actorSync) {
|
|
521
|
+
const redeemableBucketValues = [
|
|
522
|
+
actorSync.redeemable_principal_lamports,
|
|
523
|
+
actorSync.redeemable_fee_lamports,
|
|
524
|
+
actorSync.lifetime_earned_lamports,
|
|
525
|
+
actorSync.redeemable_principal_source_count,
|
|
526
|
+
actorSync.redeemable_fee_source_count
|
|
527
|
+
];
|
|
528
|
+
const redeemableBucketCount = redeemableBucketValues.filter(
|
|
529
|
+
(value) => value !== void 0
|
|
530
|
+
).length;
|
|
531
|
+
if (redeemableBucketCount > 0 && redeemableBucketCount < redeemableBucketValues.length) {
|
|
532
|
+
throw new CommandError(
|
|
533
|
+
"REJECTED_STATE",
|
|
534
|
+
"coordinator LP actor sync redeemable buckets must be all-or-none"
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
const redeemableProjectionAvailable = redeemableBucketCount === redeemableBucketValues.length;
|
|
538
|
+
return {
|
|
539
|
+
lpPositionId: actorSync.lp_position_id,
|
|
540
|
+
status: actorSync.status,
|
|
541
|
+
sourceLiquidityUse: actorSync.source_liquidity_use,
|
|
542
|
+
redeemableProjectionAvailable,
|
|
543
|
+
redeemablePrincipalLamports: actorSync.redeemable_principal_lamports ?? 0,
|
|
544
|
+
redeemableFeeLamports: actorSync.redeemable_fee_lamports ?? 0,
|
|
545
|
+
lifetimeEarnedLamports: actorSync.lifetime_earned_lamports ?? 0,
|
|
546
|
+
redeemablePrincipalSourceCount: actorSync.redeemable_principal_source_count ?? 0,
|
|
547
|
+
redeemableFeeSourceCount: actorSync.redeemable_fee_source_count ?? 0,
|
|
548
|
+
pendingReservationCount: actorSync.pending_reservation_count,
|
|
549
|
+
reservedPayoutLamports: actorSync.reserved_payout_lamports,
|
|
550
|
+
estimatedNetworkFeeLamports: actorSync.estimated_network_fee_lamports,
|
|
551
|
+
reimbursementDueCount: actorSync.reimbursement_due_count,
|
|
552
|
+
reimbursementDueSourceCount: actorSync.reimbursement_due_source_count,
|
|
553
|
+
reimbursementDuePayoutLamports: actorSync.reimbursement_due_payout_lamports,
|
|
554
|
+
reimbursementDueNetworkFeeLamports: actorSync.reimbursement_due_network_fee_lamports,
|
|
555
|
+
withdrawalRequestedLamports: actorSync.withdrawal_requested_lamports,
|
|
556
|
+
withdrawalLandedLamports: actorSync.withdrawal_landed_lamports,
|
|
557
|
+
withdrawalLandedFeeLamports: actorSync.withdrawal_landed_fee_lamports,
|
|
558
|
+
withdrawalCanceledLamports: actorSync.withdrawal_canceled_lamports,
|
|
559
|
+
withdrawalNetworkFeeLossLamports: actorSync.withdrawal_network_fee_loss_lamports,
|
|
560
|
+
withdrawalTxHashes: [...actorSync.withdrawal_tx_hashes],
|
|
561
|
+
redemptionHistory: (actorSync.redemption_history ?? []).map(mapLpRedemptionHistory),
|
|
562
|
+
redemptionHistoryHasMore: actorSync.redemption_history_has_more ?? false,
|
|
563
|
+
...actorSync.redemption_history_next_cursor === void 0 ? {} : { redemptionHistoryNextCursor: actorSync.redemption_history_next_cursor },
|
|
564
|
+
stateVersion: actorSync.state_version,
|
|
565
|
+
updatedAtMs: actorSync.updated_at_ms
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function mergePositionHistoryPages(first, next) {
|
|
569
|
+
if (first.actorSync === void 0) return next;
|
|
570
|
+
if (next.actorSync === void 0) return first;
|
|
571
|
+
const historyByRedemptionId = /* @__PURE__ */ new Map();
|
|
572
|
+
for (const history of first.actorSync.redemptionHistory ?? []) {
|
|
573
|
+
historyByRedemptionId.set(history.redemptionId, history);
|
|
574
|
+
}
|
|
575
|
+
for (const history of next.actorSync.redemptionHistory ?? []) {
|
|
576
|
+
historyByRedemptionId.set(history.redemptionId, history);
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
...first,
|
|
580
|
+
actorSync: {
|
|
581
|
+
...first.actorSync,
|
|
582
|
+
...next.actorSync,
|
|
583
|
+
redemptionHistory: [...historyByRedemptionId.values()]
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function mapLpRedemptionHistory(history) {
|
|
588
|
+
return {
|
|
589
|
+
redemptionId: history.redemption_id,
|
|
590
|
+
state: history.state,
|
|
591
|
+
acceptedLamports: history.accepted_lamports,
|
|
592
|
+
acceptedDepositPrincipalLamports: history.accepted_deposit_principal_lamports,
|
|
593
|
+
acceptedEarnedFeeLamports: history.accepted_earned_fee_lamports,
|
|
594
|
+
landedLamports: history.landed_lamports,
|
|
595
|
+
landedDepositPrincipalLamports: history.landed_deposit_principal_lamports,
|
|
596
|
+
landedEarnedFeeLamports: history.landed_earned_fee_lamports,
|
|
597
|
+
canceledLamports: history.canceled_lamports,
|
|
598
|
+
canceledDepositPrincipalLamports: history.canceled_deposit_principal_lamports,
|
|
599
|
+
canceledEarnedFeeLamports: history.canceled_earned_fee_lamports,
|
|
600
|
+
networkFeeLamports: history.network_fee_lamports,
|
|
601
|
+
networkFeeLossLamports: history.network_fee_loss_lamports,
|
|
602
|
+
failedAttempts: history.failed_attempts,
|
|
603
|
+
destinationAddresses: [...history.destination_addresses],
|
|
604
|
+
destinationCount: history.destination_count,
|
|
605
|
+
txSignatures: [...history.tx_signatures],
|
|
606
|
+
txSignatureCount: history.tx_signature_count,
|
|
607
|
+
detailsTruncated: history.details_truncated,
|
|
608
|
+
stateVersion: history.state_version,
|
|
609
|
+
createdAtMs: history.created_at_ms,
|
|
610
|
+
updatedAtMs: history.updated_at_ms
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function mapShard(shard) {
|
|
614
|
+
return {
|
|
615
|
+
kind: "lp",
|
|
616
|
+
lpPositionId: shard.lp_position_id,
|
|
617
|
+
shardId: shard.shard_id,
|
|
618
|
+
index: shard.index,
|
|
619
|
+
address: shard.address,
|
|
620
|
+
amountLamports: shard.amount_lamports,
|
|
621
|
+
...shard.required_lamports === void 0 ? {} : { requiredLamports: shard.required_lamports },
|
|
622
|
+
status: mapStatus(shard.status),
|
|
623
|
+
fundingTxSignature: shard.funding_tx_signature,
|
|
624
|
+
payoutTxSignature: shard.payout_tx_signature,
|
|
625
|
+
coolingUntilMs: shard.cooling_until_ms
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function mapTopUpRequest(request2) {
|
|
629
|
+
return {
|
|
630
|
+
topUpId: request2.top_up_id,
|
|
631
|
+
positionId: request2.lp_position_id,
|
|
632
|
+
depositAddress: request2.deposit_address,
|
|
633
|
+
expiresAtMs: request2.expires_at_ms,
|
|
634
|
+
status: request2.status
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function mapStatus(status) {
|
|
638
|
+
switch (status) {
|
|
639
|
+
case "pregenerated":
|
|
640
|
+
return "PREGENERATED";
|
|
641
|
+
case "funding_queued":
|
|
642
|
+
return "FUNDING_QUEUED";
|
|
643
|
+
case "funded":
|
|
644
|
+
return "FUNDED";
|
|
645
|
+
case "cooling":
|
|
646
|
+
return "COOLING";
|
|
647
|
+
case "available":
|
|
648
|
+
return "AVAILABLE";
|
|
649
|
+
case "reserved":
|
|
650
|
+
return "RESERVED";
|
|
651
|
+
case "retired":
|
|
652
|
+
return "RETIRED";
|
|
653
|
+
case "redeemed":
|
|
654
|
+
return "REDEEMED";
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
async function coordinatorAttestationHash(binaryHash, pubkeyHex) {
|
|
658
|
+
const material = encoder2.encode(
|
|
659
|
+
`invisible-lp-attestation-v1\0${binaryHash.toLowerCase()}\0${pubkeyHex.toLowerCase()}`
|
|
660
|
+
);
|
|
661
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", material));
|
|
662
|
+
}
|
|
663
|
+
function randomBytes(length) {
|
|
664
|
+
const bytes = new Uint8Array(length);
|
|
665
|
+
crypto.getRandomValues(bytes);
|
|
666
|
+
return bytes;
|
|
667
|
+
}
|
|
668
|
+
function loadProtocol() {
|
|
669
|
+
protocolPromise ??= import('./coordinator-7Y45MCCZ.js');
|
|
670
|
+
return protocolPromise;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/lp/dkgBatch.ts
|
|
674
|
+
var MAX_LP_DKG_CONCURRENCY = 8;
|
|
675
|
+
var DEFAULT_LP_DKG_CONCURRENCY = MAX_LP_DKG_CONCURRENCY;
|
|
676
|
+
var LP_DKG_COMPLETED_SHARD_POLL_INTERVAL_MS = 500;
|
|
677
|
+
var LP_DKG_COMPLETED_SHARD_POLL_TIMEOUT_MS = 5 * 6e4 + 15e3;
|
|
678
|
+
var LP_DKG_ACTIVE_SESSION_RETRY_INTERVAL_MS = 2e3;
|
|
679
|
+
var LP_DKG_ACTIVE_SESSION_RETRY_TIMEOUT_MS = LP_DKG_COMPLETED_SHARD_POLL_TIMEOUT_MS;
|
|
680
|
+
async function createLpDkgBatch(input) {
|
|
681
|
+
if (input.useDeterministicTestAddresses === true) {
|
|
682
|
+
const addresses2 = Array.from(
|
|
683
|
+
{ length: input.count },
|
|
684
|
+
(_, offset) => testAddress(input.lpPositionId, input.startIndex + offset)
|
|
685
|
+
);
|
|
686
|
+
for (const [offset, address] of addresses2.entries()) {
|
|
687
|
+
input.onProgress?.({
|
|
688
|
+
completed: offset + 1,
|
|
689
|
+
total: input.count,
|
|
690
|
+
address
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return addresses2;
|
|
694
|
+
}
|
|
695
|
+
const workerCount = resolveConcurrency(input.count, input.concurrency);
|
|
696
|
+
const workers = Array.from({ length: workerCount }, () => createWorker(input.workerFactory));
|
|
697
|
+
const addresses = new Array(input.count);
|
|
698
|
+
let completed = 0;
|
|
699
|
+
let nextOffset = 0;
|
|
700
|
+
try {
|
|
701
|
+
await Promise.all(
|
|
702
|
+
workers.map(async (worker) => {
|
|
703
|
+
for (; ; ) {
|
|
704
|
+
const offset = nextOffset;
|
|
705
|
+
nextOffset += 1;
|
|
706
|
+
if (offset >= input.count) return;
|
|
707
|
+
const index = input.startIndex + offset;
|
|
708
|
+
const address = await createLpDkgShard(input, worker, index);
|
|
709
|
+
addresses[offset] = address;
|
|
710
|
+
completed += 1;
|
|
711
|
+
input.onProgress?.({
|
|
712
|
+
completed,
|
|
713
|
+
total: input.count,
|
|
714
|
+
address
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
})
|
|
718
|
+
);
|
|
719
|
+
return addresses;
|
|
720
|
+
} finally {
|
|
721
|
+
workers.forEach((worker) => worker.terminate());
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async function createLpDkgShard(input, worker, index) {
|
|
725
|
+
const deadline = Date.now() + LP_DKG_ACTIVE_SESSION_RETRY_TIMEOUT_MS;
|
|
726
|
+
let sawActiveSession = false;
|
|
727
|
+
for (; ; ) {
|
|
728
|
+
if (sawActiveSession) {
|
|
729
|
+
const completedAddress = await getCompletedShardAddress(input, index);
|
|
730
|
+
if (completedAddress !== null) return completedAddress;
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
return await runLpDkgShard(input, worker, index);
|
|
734
|
+
} catch (error) {
|
|
735
|
+
if (isLpDkgCompletedOrVisibleError(error)) {
|
|
736
|
+
return waitForCompletedShardAddress(input, index);
|
|
737
|
+
}
|
|
738
|
+
if (!isLpDkgActiveSessionError(error)) throw error;
|
|
739
|
+
sawActiveSession = true;
|
|
740
|
+
const completedAddress = await getCompletedShardAddress(input, index);
|
|
741
|
+
if (completedAddress !== null) return completedAddress;
|
|
742
|
+
if (Date.now() >= deadline) throw error;
|
|
743
|
+
await sleep(LP_DKG_ACTIVE_SESSION_RETRY_INTERVAL_MS);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async function runLpDkgShard(input, worker, index) {
|
|
748
|
+
const roundId = `lp_dkg_${Date.now()}_${index}_${Math.random().toString(16).slice(2)}`;
|
|
749
|
+
const initResponse = await input.client.initDkg(
|
|
750
|
+
{ lpPositionId: input.lpPositionId, index },
|
|
751
|
+
input.auth
|
|
752
|
+
);
|
|
753
|
+
const round1 = await workerRequest(worker, {
|
|
754
|
+
id: roundId,
|
|
755
|
+
type: "round1"
|
|
756
|
+
});
|
|
757
|
+
if (round1.type !== "round1") throw new Error("LP DKG worker returned wrong round1 type");
|
|
758
|
+
const round1Response = await input.client.dkgRound1(
|
|
759
|
+
{
|
|
760
|
+
sessionId: initResponse.sessionId,
|
|
761
|
+
package: round1.package
|
|
762
|
+
},
|
|
763
|
+
input.auth
|
|
764
|
+
);
|
|
765
|
+
const round2 = await workerRequest(worker, {
|
|
766
|
+
id: roundId,
|
|
767
|
+
type: "round2",
|
|
768
|
+
teeRound1: Array.from(round1Response.package),
|
|
769
|
+
teeRound2ForLp: Array.from(round1Response.round2ForLp)
|
|
770
|
+
});
|
|
771
|
+
if (round2.type !== "round2") throw new Error("LP DKG worker returned wrong round2 type");
|
|
772
|
+
const round2Response = await input.client.dkgRound2(
|
|
773
|
+
{
|
|
774
|
+
sessionId: initResponse.sessionId,
|
|
775
|
+
package: round2.package,
|
|
776
|
+
delegatedShare: round2.delegatedShare
|
|
777
|
+
},
|
|
778
|
+
input.auth
|
|
779
|
+
);
|
|
780
|
+
if (!bytesEqual(Uint8Array.from(round2.jointPubkey), round2Response.jointPubkey)) {
|
|
781
|
+
throw new Error(`LP DKG joint pubkey mismatch at index ${index}`);
|
|
782
|
+
}
|
|
783
|
+
if (round2Response.index !== index) {
|
|
784
|
+
throw new Error(`LP DKG index mismatch: expected ${index}, got ${round2Response.index}`);
|
|
785
|
+
}
|
|
786
|
+
return round2Response.address;
|
|
787
|
+
}
|
|
788
|
+
async function waitForCompletedShardAddress(input, index) {
|
|
789
|
+
const deadline = Date.now() + LP_DKG_COMPLETED_SHARD_POLL_TIMEOUT_MS;
|
|
790
|
+
for (; ; ) {
|
|
791
|
+
const address = await getCompletedShardAddress(input, index);
|
|
792
|
+
if (address !== null) return address;
|
|
793
|
+
if (Date.now() >= deadline) {
|
|
794
|
+
throw new Error(`LP DKG completed shard ${index} did not become visible`);
|
|
795
|
+
}
|
|
796
|
+
await sleep(LP_DKG_COMPLETED_SHARD_POLL_INTERVAL_MS);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async function getCompletedShardAddress(input, index) {
|
|
800
|
+
const position = await input.client.getPosition(input.lpPositionId, input.auth);
|
|
801
|
+
return position.shards.find((candidate) => candidate.index === index)?.address ?? null;
|
|
802
|
+
}
|
|
803
|
+
function workerRequest(worker, request2) {
|
|
804
|
+
return new Promise((resolve, reject) => {
|
|
805
|
+
const onMessage = (event) => {
|
|
806
|
+
const message = event.data;
|
|
807
|
+
if (message.id !== request2.id) return;
|
|
808
|
+
cleanup();
|
|
809
|
+
if (message.type === "error") {
|
|
810
|
+
reject(new Error(message.error));
|
|
811
|
+
} else {
|
|
812
|
+
resolve(message);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
const onError = (event) => {
|
|
816
|
+
cleanup();
|
|
817
|
+
reject(new Error(event.message));
|
|
818
|
+
};
|
|
819
|
+
const cleanup = () => {
|
|
820
|
+
worker.removeEventListener("message", onMessage);
|
|
821
|
+
worker.removeEventListener("error", onError);
|
|
822
|
+
};
|
|
823
|
+
worker.addEventListener("message", onMessage);
|
|
824
|
+
worker.addEventListener("error", onError);
|
|
825
|
+
worker.postMessage(request2);
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
function createWorker(workerFactory) {
|
|
829
|
+
if (workerFactory !== void 0) return workerFactory();
|
|
830
|
+
return new Worker(new URL("./dkgWorker.js", import.meta.url), { type: "module" });
|
|
831
|
+
}
|
|
832
|
+
function bytesEqual(a, b) {
|
|
833
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
834
|
+
let diff = 0;
|
|
835
|
+
for (let index = 0; index < a.byteLength; index += 1) {
|
|
836
|
+
diff |= a[index] ^ b[index];
|
|
837
|
+
}
|
|
838
|
+
return diff === 0;
|
|
839
|
+
}
|
|
840
|
+
function isLpDkgCompletedOrVisibleError(error) {
|
|
841
|
+
if (!(error instanceof Error) || !error.message.includes("LP DKG index")) return false;
|
|
842
|
+
return error.message.includes("already completed for position") || error.message.includes("already exists for position");
|
|
843
|
+
}
|
|
844
|
+
function isLpDkgActiveSessionError(error) {
|
|
845
|
+
return error instanceof Error && error.message.includes("LP DKG session already active for position index");
|
|
846
|
+
}
|
|
847
|
+
function resolveConcurrency(count, requested) {
|
|
848
|
+
if (count <= 0) return 0;
|
|
849
|
+
const concurrency = requested ?? DEFAULT_LP_DKG_CONCURRENCY;
|
|
850
|
+
if (!Number.isFinite(concurrency)) return Math.min(count, DEFAULT_LP_DKG_CONCURRENCY);
|
|
851
|
+
return Math.max(1, Math.min(count, MAX_LP_DKG_CONCURRENCY, Math.floor(concurrency)));
|
|
852
|
+
}
|
|
853
|
+
function sleep(delayMs) {
|
|
854
|
+
return new Promise((resolve) => {
|
|
855
|
+
globalThis.setTimeout(resolve, delayMs);
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
function testAddress(lpPositionId, index) {
|
|
859
|
+
return `${lpPositionId}Shard${String(index).padStart(5, "0")}11111111111111111111`;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// src/lp/lifecycle.ts
|
|
863
|
+
var LP_ID_PREFIX = "lp";
|
|
864
|
+
var DEFAULT_SHARD_FUNDING_LAMPORTS = 101e6;
|
|
865
|
+
var DEFAULT_REFILL_THRESHOLD_RATIO = 0.2;
|
|
866
|
+
var LP_DKG_ACTIVE_SESSION_RETRY_DELAY_MS = 2e3;
|
|
867
|
+
var LP_DKG_ACTIVE_SESSION_RETRY_TIMEOUT_MS2 = 5 * 6e4 + 15e3;
|
|
868
|
+
var LP_WITHDRAWAL_CONFIRMATION_POLL_INTERVAL_MS = 3e3;
|
|
869
|
+
var LP_WITHDRAWAL_CONFIRMATION_TIMEOUT_MS = 15 * 6e4;
|
|
870
|
+
var LP_TOP_UP_WAIT_POLL_INTERVAL_MS = 1e3;
|
|
871
|
+
var LP_REDEMPTION_HISTORY_OPEN_STATES = /* @__PURE__ */ new Set([
|
|
872
|
+
"requested",
|
|
873
|
+
"match_requested",
|
|
874
|
+
"reserved",
|
|
875
|
+
"pending"
|
|
876
|
+
]);
|
|
877
|
+
var LP_REDEMPTION_RECONCILING_MESSAGE = "LP redemption is still settling on the coordinator; it will reconcile from sync";
|
|
878
|
+
var LP_MISSING_POSITION_CODE_MESSAGE = "Save or recover the LP Position Code before using LP funds";
|
|
879
|
+
var POSITION_CODE_MISMATCH_MESSAGE = "LP Position Code does not match this TEE position";
|
|
880
|
+
var FUNDING_PLAN_EMPTY_MESSAGE = "TEE returned an empty LP funding plan";
|
|
881
|
+
var WITHDRAWAL_MISSING_RECORD_MESSAGE = "LP withdrawal record is missing";
|
|
882
|
+
var WITHDRAWAL_RECORD_MISMATCH_MESSAGE = "LP withdrawal record does not match this confirmation request";
|
|
883
|
+
var WITHDRAWAL_NO_LAMPORTS_MESSAGE = "LP redemption did not land any lamports on-chain";
|
|
884
|
+
var WITHDRAWAL_HISTORY_MISMATCH_MESSAGE = "LP redemption history does not match this request";
|
|
885
|
+
var UNSUPPORTED_FEE_POLICY_MESSAGE = "LP fee policy overrides are not advertised by the coordinator";
|
|
886
|
+
var FEE_SOURCE_COUNT_UNAVAILABLE_MESSAGE = "LP earned-fee source count is not available; pass allowManyToOne: true to acknowledge many-to-one withdrawal risk";
|
|
887
|
+
var LpRedemptionReconcilingError = class extends LpLifecycleError {
|
|
888
|
+
constructor() {
|
|
889
|
+
super("LP_REDEMPTION_RECONCILING", LP_REDEMPTION_RECONCILING_MESSAGE);
|
|
890
|
+
this.name = "LpRedemptionReconcilingError";
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
var LpPositionAuthMissingError = class extends LpLifecycleError {
|
|
894
|
+
constructor() {
|
|
895
|
+
super("LP_POSITION_AUTH_MISSING", LP_MISSING_POSITION_CODE_MESSAGE);
|
|
896
|
+
this.name = "LpPositionAuthMissingError";
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
function isLpRedemptionReconcilingError(error) {
|
|
900
|
+
return error instanceof LpRedemptionReconcilingError || error instanceof Error && error.message === LP_REDEMPTION_RECONCILING_MESSAGE;
|
|
901
|
+
}
|
|
902
|
+
function isLpPositionAuthMissingError(error) {
|
|
903
|
+
return error instanceof LpPositionAuthMissingError || error instanceof Error && error.message === LP_MISSING_POSITION_CODE_MESSAGE;
|
|
904
|
+
}
|
|
905
|
+
var lifecycleClients = /* @__PURE__ */ new WeakMap();
|
|
906
|
+
function createLpLifecycleClient(session, runtime = {}) {
|
|
907
|
+
const state = {
|
|
908
|
+
authByPosition: /* @__PURE__ */ new Map(),
|
|
909
|
+
positions: /* @__PURE__ */ new Map(),
|
|
910
|
+
redemptions: /* @__PURE__ */ new Map(),
|
|
911
|
+
topUps: /* @__PURE__ */ new Map()
|
|
912
|
+
};
|
|
913
|
+
const client = runtime.coordinatorClient ?? createLpCoordinatorClient(session);
|
|
914
|
+
const nowMs = runtime.nowMs ?? Date.now;
|
|
915
|
+
const sleep2 = runtime.sleep ?? defaultSleep;
|
|
916
|
+
const requireAuth = (positionId) => {
|
|
917
|
+
const auth = state.authByPosition.get(positionId);
|
|
918
|
+
if (auth === void 0) throw new LpPositionAuthMissingError();
|
|
919
|
+
return auth;
|
|
920
|
+
};
|
|
921
|
+
const rememberPosition = (position) => {
|
|
922
|
+
const snapshot = clonePosition(position);
|
|
923
|
+
state.positions.set(snapshot.id, snapshot);
|
|
924
|
+
hydrateLpRedemptionsFromPosition(state, snapshot);
|
|
925
|
+
return position;
|
|
926
|
+
};
|
|
927
|
+
const rememberAuth = (auth) => {
|
|
928
|
+
state.authByPosition.set(auth.lpPositionId, auth);
|
|
929
|
+
};
|
|
930
|
+
const refreshPosition2 = async (positionId) => {
|
|
931
|
+
const auth = requireAuth(positionId);
|
|
932
|
+
return rememberPosition(await client.getPosition(positionId, auth));
|
|
933
|
+
};
|
|
934
|
+
const completeDkgBatch2 = async (positionId, options = {}) => {
|
|
935
|
+
assertAttested(session);
|
|
936
|
+
const auth = requireAuth(positionId);
|
|
937
|
+
let activeSessionRetryStartedAtMs = null;
|
|
938
|
+
for (; ; ) {
|
|
939
|
+
const current = rememberPosition(await client.getPosition(positionId, auth));
|
|
940
|
+
options.onPosition?.(current);
|
|
941
|
+
const startIndex = current.shards.length;
|
|
942
|
+
const remaining = current.targetShardCount - startIndex;
|
|
943
|
+
if (remaining <= 0) return current;
|
|
944
|
+
emitDkgProgress(options, current, startIndex, remaining, 0);
|
|
945
|
+
let expectedAddresses = null;
|
|
946
|
+
try {
|
|
947
|
+
const addresses = await createLpDkgBatch({
|
|
948
|
+
lpPositionId: positionId,
|
|
949
|
+
startIndex,
|
|
950
|
+
count: remaining,
|
|
951
|
+
auth,
|
|
952
|
+
client,
|
|
953
|
+
concurrency: options.concurrency ?? runtime.dkgConcurrency,
|
|
954
|
+
workerFactory: options.workerFactory ?? runtime.workerFactory,
|
|
955
|
+
useDeterministicTestAddresses: runtime.useDeterministicTestAddresses,
|
|
956
|
+
onProgress: (progress) => {
|
|
957
|
+
emitDkgProgress(options, current, startIndex, remaining, progress.completed);
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
expectedAddresses = addresses;
|
|
961
|
+
const position = await client.finalizeDkgBatch(
|
|
962
|
+
{
|
|
963
|
+
lpPositionId: positionId,
|
|
964
|
+
startIndex,
|
|
965
|
+
addresses,
|
|
966
|
+
finalizedAtMs: nowMs()
|
|
967
|
+
},
|
|
968
|
+
auth
|
|
969
|
+
);
|
|
970
|
+
options.onPosition?.(position);
|
|
971
|
+
emitDkgProgress(options, position, startIndex, remaining, remaining);
|
|
972
|
+
return rememberPosition(position);
|
|
973
|
+
} catch (error) {
|
|
974
|
+
const reconciled = await tryRefreshCompletedDkgWindow({
|
|
975
|
+
client,
|
|
976
|
+
positionId,
|
|
977
|
+
auth,
|
|
978
|
+
startIndex,
|
|
979
|
+
total: remaining,
|
|
980
|
+
expectedAddresses,
|
|
981
|
+
state
|
|
982
|
+
});
|
|
983
|
+
if (reconciled !== null) return reconciled;
|
|
984
|
+
if (isLpDkgIndexDriftError(error)) {
|
|
985
|
+
activeSessionRetryStartedAtMs = null;
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (!isLpDkgActiveSessionError2(error)) throw error;
|
|
989
|
+
const currentTime = nowMs();
|
|
990
|
+
activeSessionRetryStartedAtMs ??= currentTime;
|
|
991
|
+
if (currentTime - activeSessionRetryStartedAtMs >= LP_DKG_ACTIVE_SESSION_RETRY_TIMEOUT_MS2) {
|
|
992
|
+
throw error;
|
|
993
|
+
}
|
|
994
|
+
await sleep2(LP_DKG_ACTIVE_SESSION_RETRY_DELAY_MS);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
return {
|
|
999
|
+
async createPosition(args = {}) {
|
|
1000
|
+
assertNoUnsupportedFeePolicy(args.feePolicy);
|
|
1001
|
+
const shardCount = args.shardCount ?? LP_DEFAULT_TARGET_SHARDS;
|
|
1002
|
+
assertShardCount(shardCount, LP_MAX_TARGET_SHARDS, "shardCount");
|
|
1003
|
+
assertAttested(session);
|
|
1004
|
+
const positionId = runtime.idFactory?.() ?? generateId(LP_ID_PREFIX);
|
|
1005
|
+
const auth = await generateLpPositionCode(positionId);
|
|
1006
|
+
rememberAuth(auth);
|
|
1007
|
+
args.onPositionCode?.({ positionId, lpPositionCode: auth.code });
|
|
1008
|
+
const committedLamports = args.committedLamports ?? DEFAULT_SHARD_FUNDING_LAMPORTS;
|
|
1009
|
+
const position = rememberPosition(
|
|
1010
|
+
await client.openPosition(
|
|
1011
|
+
{
|
|
1012
|
+
lpPositionId: positionId,
|
|
1013
|
+
authPublicKey: Array.from(auth.authPublicKey),
|
|
1014
|
+
refillPublicKey: Array.from(auth.refillPublicKey),
|
|
1015
|
+
withdrawalCommitment: Array.from(auth.withdrawalCommitment),
|
|
1016
|
+
committedLamports,
|
|
1017
|
+
shardCount,
|
|
1018
|
+
shardAmountLamports: DEFAULT_SHARD_FUNDING_LAMPORTS,
|
|
1019
|
+
refillThreshold: args.refillThreshold ?? Math.ceil(shardCount * DEFAULT_REFILL_THRESHOLD_RATIO),
|
|
1020
|
+
addresses: [],
|
|
1021
|
+
createdAtMs: nowMs()
|
|
1022
|
+
},
|
|
1023
|
+
auth
|
|
1024
|
+
)
|
|
1025
|
+
);
|
|
1026
|
+
return { positionId, lpPositionCode: auth.code, position };
|
|
1027
|
+
},
|
|
1028
|
+
async recoverPosition(args) {
|
|
1029
|
+
const auth = await deriveLpPositionCodeAuth(args.code);
|
|
1030
|
+
rememberAuth(auth);
|
|
1031
|
+
const position = rememberPosition(await client.getPosition(auth.lpPositionId, auth));
|
|
1032
|
+
assertPositionMatchesAuth(position, auth);
|
|
1033
|
+
return position;
|
|
1034
|
+
},
|
|
1035
|
+
confirmPositionCodeSaved(_positionId) {
|
|
1036
|
+
return;
|
|
1037
|
+
},
|
|
1038
|
+
completeDkgBatch: completeDkgBatch2,
|
|
1039
|
+
refreshPosition: refreshPosition2,
|
|
1040
|
+
async refill(positionId, args = {}) {
|
|
1041
|
+
const requestedShardCount = args.shardCount ?? LP_DEFAULT_REFILL_BATCH_SIZE;
|
|
1042
|
+
assertShardCount(requestedShardCount, LP_MAX_REFILL_BATCH_SIZE, "shardCount");
|
|
1043
|
+
assertAttested(session);
|
|
1044
|
+
const auth = requireAuth(positionId);
|
|
1045
|
+
const current = await refreshPosition2(positionId);
|
|
1046
|
+
args.onPosition?.(current);
|
|
1047
|
+
const refillShardCount = resolveLpRefillShardCount(
|
|
1048
|
+
current,
|
|
1049
|
+
requestedShardCount,
|
|
1050
|
+
args.shardCount === void 0
|
|
1051
|
+
);
|
|
1052
|
+
const authorizedPosition = rememberPosition(
|
|
1053
|
+
await client.refillPosition(
|
|
1054
|
+
{
|
|
1055
|
+
lpPositionId: positionId,
|
|
1056
|
+
shardCount: refillShardCount,
|
|
1057
|
+
shardAmountLamports: DEFAULT_SHARD_FUNDING_LAMPORTS,
|
|
1058
|
+
addresses: [],
|
|
1059
|
+
authorizedAtMs: nowMs()
|
|
1060
|
+
},
|
|
1061
|
+
auth
|
|
1062
|
+
)
|
|
1063
|
+
);
|
|
1064
|
+
args.onPosition?.(authorizedPosition);
|
|
1065
|
+
if (authorizedPosition.shards.length >= authorizedPosition.targetShardCount) {
|
|
1066
|
+
return authorizedPosition;
|
|
1067
|
+
}
|
|
1068
|
+
return completeDkgBatch2(positionId, {
|
|
1069
|
+
onProgress: args.onProgress,
|
|
1070
|
+
onPosition: args.onPosition,
|
|
1071
|
+
workerFactory: args.workerFactory
|
|
1072
|
+
});
|
|
1073
|
+
},
|
|
1074
|
+
async prepareInitialFunding(positionId) {
|
|
1075
|
+
assertAttested(session);
|
|
1076
|
+
const auth = requireAuth(positionId);
|
|
1077
|
+
const currentPosition = rememberPosition(await client.getPosition(positionId, auth));
|
|
1078
|
+
if (!isLpPositionReadyForInitialFunding(currentPosition)) return null;
|
|
1079
|
+
const initialShard = currentPosition.shards.find((shard) => shard.index === 0);
|
|
1080
|
+
if (initialShard === void 0) return null;
|
|
1081
|
+
if (initialShard.status === "FUNDING_QUEUED") {
|
|
1082
|
+
return fundingPlanFromShard(currentPosition, initialShard);
|
|
1083
|
+
}
|
|
1084
|
+
if (initialShard.status !== "PREGENERATED") return null;
|
|
1085
|
+
const existingQueuedShard = currentPosition.shards.find(
|
|
1086
|
+
(shard) => shard.status === "FUNDING_QUEUED"
|
|
1087
|
+
);
|
|
1088
|
+
if (existingQueuedShard !== void 0) return null;
|
|
1089
|
+
const [plannedShard] = await client.planFunding(
|
|
1090
|
+
{ lpPositionId: positionId, shardCount: 1, nowMs: nowMs() },
|
|
1091
|
+
auth
|
|
1092
|
+
);
|
|
1093
|
+
if (plannedShard === void 0) throw new Error(FUNDING_PLAN_EMPTY_MESSAGE);
|
|
1094
|
+
if (plannedShard.index !== 0) {
|
|
1095
|
+
throw new PolicyValidationError(
|
|
1096
|
+
"INVALID_POSITION_AUTH",
|
|
1097
|
+
"Coordinator funding plan did not select LP_DKG_0"
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
const position = rememberPosition(await client.getPosition(positionId, auth));
|
|
1101
|
+
return fundingPlanFromShard(position, plannedShard);
|
|
1102
|
+
},
|
|
1103
|
+
async prepareTopUp(positionId) {
|
|
1104
|
+
assertAttested(session);
|
|
1105
|
+
const auth = requireAuth(positionId);
|
|
1106
|
+
const request2 = await client.prepareTopUp(positionId, auth);
|
|
1107
|
+
state.topUps.set(request2.topUpId, request2);
|
|
1108
|
+
return request2;
|
|
1109
|
+
},
|
|
1110
|
+
async waitForTopUp(topUpId, options = {}) {
|
|
1111
|
+
const cached = state.topUps.get(topUpId);
|
|
1112
|
+
if (cached !== void 0 && options.positionId !== void 0 && cached.positionId !== options.positionId) {
|
|
1113
|
+
throw new PolicyValidationError(
|
|
1114
|
+
"INVALID_POSITION_AUTH",
|
|
1115
|
+
"Top-up request does not belong to the requested LP position"
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
const initial = cached ?? (options.positionId === void 0 ? void 0 : await client.getTopUpStatus(
|
|
1119
|
+
{ lpPositionId: options.positionId, topUpId },
|
|
1120
|
+
requireAuth(options.positionId)
|
|
1121
|
+
));
|
|
1122
|
+
if (initial === void 0) {
|
|
1123
|
+
throw new PolicyValidationError(
|
|
1124
|
+
"INVALID_POSITION_AUTH",
|
|
1125
|
+
"Top-up request is not known to this LP session"
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
const pollIntervalMs = options.pollIntervalMs ?? LP_TOP_UP_WAIT_POLL_INTERVAL_MS;
|
|
1129
|
+
let current = initial;
|
|
1130
|
+
for (; ; ) {
|
|
1131
|
+
throwIfTopUpWaitAborted(options.signal);
|
|
1132
|
+
if (current.status !== "watching") return current;
|
|
1133
|
+
const auth = requireAuth(current.positionId);
|
|
1134
|
+
current = await client.getTopUpStatus({ lpPositionId: current.positionId, topUpId }, auth);
|
|
1135
|
+
state.topUps.set(topUpId, current);
|
|
1136
|
+
if (current.status !== "watching") return current;
|
|
1137
|
+
if (nowMs() >= current.expiresAtMs) {
|
|
1138
|
+
current = await client.getTopUpStatus(
|
|
1139
|
+
{ lpPositionId: current.positionId, topUpId },
|
|
1140
|
+
auth
|
|
1141
|
+
);
|
|
1142
|
+
state.topUps.set(topUpId, current);
|
|
1143
|
+
if (current.status !== "watching") return current;
|
|
1144
|
+
current = { ...current, status: "expired" };
|
|
1145
|
+
state.topUps.set(topUpId, current);
|
|
1146
|
+
return current;
|
|
1147
|
+
}
|
|
1148
|
+
await sleep2(pollIntervalMs);
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
async reconcileFunding(positionId) {
|
|
1152
|
+
assertAttested(session);
|
|
1153
|
+
const auth = requireAuth(positionId);
|
|
1154
|
+
return rememberPosition(await client.reconcileFunding(positionId, auth));
|
|
1155
|
+
},
|
|
1156
|
+
async withdrawPosition(positionId, args) {
|
|
1157
|
+
validateExplicitWithdrawalInput(args);
|
|
1158
|
+
validateWithdrawalDestinations(args);
|
|
1159
|
+
assertAttested(session);
|
|
1160
|
+
const auth = requireAuth(positionId);
|
|
1161
|
+
const before = rememberPosition(await client.reconcileFunding(positionId, auth));
|
|
1162
|
+
const existingRedemption = findOpenRedemptionHistory(before, args);
|
|
1163
|
+
if (existingRedemption !== void 0) {
|
|
1164
|
+
const execution2 = {
|
|
1165
|
+
withdrawalId: existingRedemption.redemptionId,
|
|
1166
|
+
txSignatures: [...existingRedemption.txSignatures]
|
|
1167
|
+
};
|
|
1168
|
+
const redemption2 = {
|
|
1169
|
+
...buildLpRedemptionRecord({
|
|
1170
|
+
execution: execution2,
|
|
1171
|
+
position: before,
|
|
1172
|
+
destinationAddresses: existingRedemption.destinationAddresses,
|
|
1173
|
+
acceptedLamports: existingRedemption.acceptedLamports,
|
|
1174
|
+
requestedTargetLamports: existingRedemption.acceptedLamports,
|
|
1175
|
+
landedBaselineLamports: 0,
|
|
1176
|
+
landedFeeBaselineLamports: 0,
|
|
1177
|
+
txSignatureBaseline: [],
|
|
1178
|
+
nowMs: existingRedemption.createdAtMs
|
|
1179
|
+
}),
|
|
1180
|
+
redeemedLamports: existingRedemption.landedLamports,
|
|
1181
|
+
coordinatorStateVersion: existingRedemption.stateVersion
|
|
1182
|
+
};
|
|
1183
|
+
state.redemptions.set(execution2.withdrawalId, redemption2);
|
|
1184
|
+
return { execution: execution2, position: before, redemption: redemption2 };
|
|
1185
|
+
}
|
|
1186
|
+
const claim = resolveWithdrawalClaim(before, args);
|
|
1187
|
+
const sourceCount = lpWithdrawalSourceCountForClaim(before, {
|
|
1188
|
+
...claim,
|
|
1189
|
+
allowManyToOne: args.allowManyToOne
|
|
1190
|
+
});
|
|
1191
|
+
const validationInput = {
|
|
1192
|
+
lpPositionId: positionId,
|
|
1193
|
+
destinationAddresses: args.destinationAddresses,
|
|
1194
|
+
depositPrincipalLamports: claim.depositPrincipalLamports,
|
|
1195
|
+
earnedFeeLamports: claim.earnedFeeLamports,
|
|
1196
|
+
requestedAtMs: nowMs(),
|
|
1197
|
+
allowManyToOne: args.allowManyToOne ?? false,
|
|
1198
|
+
sourceCount
|
|
1199
|
+
};
|
|
1200
|
+
validateLpWithdrawalPlan(validationInput);
|
|
1201
|
+
const execution = await client.executeWithdrawal(
|
|
1202
|
+
{
|
|
1203
|
+
lpPositionId: validationInput.lpPositionId,
|
|
1204
|
+
destinationAddresses: validationInput.destinationAddresses,
|
|
1205
|
+
depositPrincipalLamports: validationInput.depositPrincipalLamports,
|
|
1206
|
+
earnedFeeLamports: validationInput.earnedFeeLamports,
|
|
1207
|
+
requestedAtMs: validationInput.requestedAtMs,
|
|
1208
|
+
allowManyToOne: args.allowManyToOne === true || sourceCount === 1
|
|
1209
|
+
},
|
|
1210
|
+
auth
|
|
1211
|
+
);
|
|
1212
|
+
const pendingRedemption = buildLpRedemptionRecord({
|
|
1213
|
+
execution,
|
|
1214
|
+
position: before,
|
|
1215
|
+
destinationAddresses: args.destinationAddresses,
|
|
1216
|
+
acceptedLamports: claim.depositPrincipalLamports + claim.earnedFeeLamports,
|
|
1217
|
+
requestedTargetLamports: (before.actorSync?.withdrawalRequestedLamports ?? 0) + claim.depositPrincipalLamports + claim.earnedFeeLamports,
|
|
1218
|
+
landedBaselineLamports: before.actorSync?.withdrawalLandedLamports ?? 0,
|
|
1219
|
+
landedFeeBaselineLamports: before.actorSync?.withdrawalLandedFeeLamports ?? 0,
|
|
1220
|
+
txSignatureBaseline: before.actorSync?.withdrawalTxHashes ?? [],
|
|
1221
|
+
nowMs: nowMs()
|
|
1222
|
+
});
|
|
1223
|
+
state.redemptions.set(execution.withdrawalId, pendingRedemption);
|
|
1224
|
+
const position = await client.getPosition(positionId, auth).then((refreshed) => rememberPosition(refreshed)).catch(() => before);
|
|
1225
|
+
const redemption = { ...pendingRedemption, lpPositionId: position.id };
|
|
1226
|
+
state.redemptions.set(execution.withdrawalId, redemption);
|
|
1227
|
+
return { execution, position, redemption };
|
|
1228
|
+
},
|
|
1229
|
+
async waitForWithdrawalConfirmation(positionId, execution, options = {}) {
|
|
1230
|
+
const auth = requireAuth(positionId);
|
|
1231
|
+
let record = resolveWithdrawalRecord({
|
|
1232
|
+
state,
|
|
1233
|
+
positionId,
|
|
1234
|
+
execution,
|
|
1235
|
+
options
|
|
1236
|
+
});
|
|
1237
|
+
state.redemptions.set(record.redemptionId, record);
|
|
1238
|
+
const deadlineMs = nowMs() + (runtime.withdrawalConfirmationTimeoutMs ?? LP_WITHDRAWAL_CONFIRMATION_TIMEOUT_MS);
|
|
1239
|
+
const pollIntervalMs = runtime.withdrawalConfirmationPollIntervalMs ?? LP_WITHDRAWAL_CONFIRMATION_POLL_INTERVAL_MS;
|
|
1240
|
+
for (; ; ) {
|
|
1241
|
+
const position = rememberPosition(await client.getPosition(positionId, auth));
|
|
1242
|
+
options.onPosition?.(position);
|
|
1243
|
+
const reconciled = reconcileLpRedemptionFromActorSync(record, position);
|
|
1244
|
+
record = reconciled;
|
|
1245
|
+
state.redemptions.set(record.redemptionId, reconciled);
|
|
1246
|
+
options.onRedemptionUpdate?.(reconciled);
|
|
1247
|
+
if (reconciled.status === "confirmed") {
|
|
1248
|
+
return {
|
|
1249
|
+
withdrawalId: execution.withdrawalId,
|
|
1250
|
+
txSignatures: reconciled.txSignatures,
|
|
1251
|
+
redeemedLamports: reconciled.redeemedLamports
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
if (reconciled.status === "failed") {
|
|
1255
|
+
throw new Error(reconciled.error ?? WITHDRAWAL_NO_LAMPORTS_MESSAGE);
|
|
1256
|
+
}
|
|
1257
|
+
if (nowMs() > deadlineMs) {
|
|
1258
|
+
state.redemptions.set(record.redemptionId, { ...reconciled, status: "reconciling" });
|
|
1259
|
+
options.onRedemptionUpdate?.({ ...reconciled, status: "reconciling" });
|
|
1260
|
+
throw new LpRedemptionReconcilingError();
|
|
1261
|
+
}
|
|
1262
|
+
await sleep2(pollIntervalMs);
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
async withdrawFees(_positionId, args) {
|
|
1266
|
+
publicKey(args.destination);
|
|
1267
|
+
assertAttested(session);
|
|
1268
|
+
throw new RoutingError(
|
|
1269
|
+
"VERSION_NOT_SUPPORTED",
|
|
1270
|
+
"LP fee withdrawal is not advertised by the coordinator"
|
|
1271
|
+
);
|
|
1272
|
+
},
|
|
1273
|
+
async listPositions() {
|
|
1274
|
+
return [...state.positions.values()].map(clonePosition);
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function defaultLpLifecycleClient(session) {
|
|
1279
|
+
let client = lifecycleClients.get(session);
|
|
1280
|
+
if (client === void 0) {
|
|
1281
|
+
client = createLpLifecycleClient(session);
|
|
1282
|
+
lifecycleClients.set(session, client);
|
|
1283
|
+
}
|
|
1284
|
+
return client;
|
|
1285
|
+
}
|
|
1286
|
+
function emitDkgProgress(options, position, startIndex, total, completed) {
|
|
1287
|
+
options.onProgress?.({
|
|
1288
|
+
phase: "dkg",
|
|
1289
|
+
lpPositionId: position.id,
|
|
1290
|
+
startIndex,
|
|
1291
|
+
targetShardCount: startIndex + total,
|
|
1292
|
+
completed: Math.min(completed, total),
|
|
1293
|
+
total
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
async function tryRefreshCompletedDkgWindow({
|
|
1297
|
+
client,
|
|
1298
|
+
positionId,
|
|
1299
|
+
auth,
|
|
1300
|
+
startIndex,
|
|
1301
|
+
total,
|
|
1302
|
+
expectedAddresses,
|
|
1303
|
+
state
|
|
1304
|
+
}) {
|
|
1305
|
+
const position = await client.getPosition(positionId, auth).catch(() => null);
|
|
1306
|
+
if (position === null) return null;
|
|
1307
|
+
if (!hasCompletedDkgWindow(position, startIndex, total, expectedAddresses)) return null;
|
|
1308
|
+
state.positions.set(position.id, clonePosition(position));
|
|
1309
|
+
return position;
|
|
1310
|
+
}
|
|
1311
|
+
function hasCompletedDkgWindow(position, startIndex, total, expectedAddresses) {
|
|
1312
|
+
for (let offset = 0; offset < total; offset += 1) {
|
|
1313
|
+
const shard = position.shards.find((candidate) => candidate.index === startIndex + offset);
|
|
1314
|
+
if (shard === void 0) {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
if (expectedAddresses !== null && shard.address !== expectedAddresses[offset]) {
|
|
1318
|
+
return false;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return true;
|
|
1322
|
+
}
|
|
1323
|
+
function assertNoUnsupportedFeePolicy(feePolicy) {
|
|
1324
|
+
if (feePolicy === void 0) return;
|
|
1325
|
+
throw new RoutingError("VERSION_NOT_SUPPORTED", UNSUPPORTED_FEE_POLICY_MESSAGE);
|
|
1326
|
+
}
|
|
1327
|
+
function fundingPlanFromShard(position, shard) {
|
|
1328
|
+
if (shard.requiredLamports === void 0 || shard.requiredLamports <= 0) return null;
|
|
1329
|
+
return {
|
|
1330
|
+
address: shard.address,
|
|
1331
|
+
requiredLamports: shard.requiredLamports,
|
|
1332
|
+
qrPayload: `solana:${shard.address}`,
|
|
1333
|
+
position
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function assertPositionMatchesAuth(position, auth) {
|
|
1337
|
+
if (position.authPublicKey !== bytesToHex(auth.authPublicKey) || position.refillPublicKey !== bytesToHex(auth.refillPublicKey) || position.withdrawalCommitment !== bytesToHex(auth.withdrawalCommitment)) {
|
|
1338
|
+
throw new Error(POSITION_CODE_MISMATCH_MESSAGE);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function assertShardCount(value, max, field) {
|
|
1342
|
+
if (!Number.isInteger(value) || value < 1 || value > max) {
|
|
1343
|
+
throw new PolicyValidationError(
|
|
1344
|
+
"INVALID_AMOUNT",
|
|
1345
|
+
`${field} must be an integer in 1..${max}, received ${String(value)}`
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
function resolveLpRefillShardCount(position, requestedShardCount, capDefaultBatch) {
|
|
1350
|
+
const remainingShardCapacity = LP_MAX_TARGET_SHARDS - position.shards.length;
|
|
1351
|
+
if (remainingShardCapacity <= 0) {
|
|
1352
|
+
throw new PolicyValidationError(
|
|
1353
|
+
"INVALID_AMOUNT",
|
|
1354
|
+
`LP refill target_shard_count is already at ${LP_MAX_TARGET_SHARDS}`
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
const refillShardCount = capDefaultBatch ? Math.min(requestedShardCount, remainingShardCapacity) : requestedShardCount;
|
|
1358
|
+
if (position.shards.length + refillShardCount > LP_MAX_TARGET_SHARDS) {
|
|
1359
|
+
throw new PolicyValidationError(
|
|
1360
|
+
"INVALID_AMOUNT",
|
|
1361
|
+
`LP refill target_shard_count must be <= ${LP_MAX_TARGET_SHARDS}`
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
return refillShardCount;
|
|
1365
|
+
}
|
|
1366
|
+
function isLpPositionReadyForInitialFunding(position) {
|
|
1367
|
+
return position.status === "active" && position.shards.length >= position.targetShardCount;
|
|
1368
|
+
}
|
|
1369
|
+
function resolveWithdrawalClaim(position, args) {
|
|
1370
|
+
if (args.depositPrincipalLamports !== void 0 || args.earnedFeeLamports !== void 0) {
|
|
1371
|
+
return {
|
|
1372
|
+
depositPrincipalLamports: args.depositPrincipalLamports ?? 0,
|
|
1373
|
+
earnedFeeLamports: args.earnedFeeLamports ?? 0
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
const legacyDepositPrincipalLamports = position.shards.filter((shard) => shard.status === "AVAILABLE" && shard.amountLamports > 0).reduce((sum, shard) => sum + shard.amountLamports, 0);
|
|
1377
|
+
const actorRedeemablePrincipalLamports = Math.max(
|
|
1378
|
+
0,
|
|
1379
|
+
position.actorSync?.redeemablePrincipalLamports ?? 0
|
|
1380
|
+
);
|
|
1381
|
+
const actorRedeemableFeeLamports = Math.max(0, position.actorSync?.redeemableFeeLamports ?? 0);
|
|
1382
|
+
const hasActorProjection = hasActorRedeemableProjection(position.actorSync);
|
|
1383
|
+
const depositPrincipalLamports = resolveRedeemablePrincipalLamports(
|
|
1384
|
+
position.actorSync,
|
|
1385
|
+
legacyDepositPrincipalLamports,
|
|
1386
|
+
actorRedeemablePrincipalLamports,
|
|
1387
|
+
actorRedeemableFeeLamports
|
|
1388
|
+
);
|
|
1389
|
+
const earnedFeeLamports = hasActorProjection ? actorRedeemableFeeLamports : Math.max(0, position.earnedLamports);
|
|
1390
|
+
const totalLamports = depositPrincipalLamports + earnedFeeLamports;
|
|
1391
|
+
if (!Number.isSafeInteger(totalLamports) || totalLamports <= 0) {
|
|
1392
|
+
throw new PolicyValidationError(
|
|
1393
|
+
"INVALID_AMOUNT",
|
|
1394
|
+
"LP withdrawal requires available principal or earned fees"
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
return { depositPrincipalLamports, earnedFeeLamports };
|
|
1398
|
+
}
|
|
1399
|
+
function findOpenRedemptionHistory(position, args) {
|
|
1400
|
+
const hasExplicitClaim = args.depositPrincipalLamports !== void 0 || args.earnedFeeLamports !== void 0;
|
|
1401
|
+
const requestedPrincipalLamports = args.depositPrincipalLamports ?? 0;
|
|
1402
|
+
const requestedFeeLamports = args.earnedFeeLamports ?? 0;
|
|
1403
|
+
return position.actorSync?.redemptionHistory?.find(
|
|
1404
|
+
(history) => LP_REDEMPTION_HISTORY_OPEN_STATES.has(history.state) && history.detailsTruncated !== true && sameStringList(history.destinationAddresses, args.destinationAddresses) && (!hasExplicitClaim || history.acceptedDepositPrincipalLamports === requestedPrincipalLamports && history.acceptedEarnedFeeLamports === requestedFeeLamports)
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
function hasActorSyncLpActivity(sync) {
|
|
1408
|
+
if (sync === void 0) return false;
|
|
1409
|
+
return sync.status !== "idle" || sync.pendingReservationCount > 0 || sync.reservedPayoutLamports > 0 || sync.reimbursementDueCount > 0 || sync.reimbursementDueSourceCount > 0 || sync.reimbursementDuePayoutLamports > 0 || sync.reimbursementDueNetworkFeeLamports > 0 || sync.withdrawalRequestedLamports > 0 || sync.withdrawalLandedLamports > 0 || sync.withdrawalLandedFeeLamports > 0 || sync.withdrawalCanceledLamports > 0 || sync.withdrawalNetworkFeeLossLamports > 0 || sync.withdrawalTxHashes.length > 0;
|
|
1410
|
+
}
|
|
1411
|
+
function resolveRedeemablePrincipalLamports(sync, legacyAvailableLamports, actorRedeemablePrincipalLamports, actorRedeemableFeeLamports) {
|
|
1412
|
+
if (sync?.redeemableProjectionAvailable !== true) return legacyAvailableLamports;
|
|
1413
|
+
if (legacyAvailableLamports <= 0) return actorRedeemablePrincipalLamports;
|
|
1414
|
+
if (actorRedeemablePrincipalLamports >= legacyAvailableLamports || hasActorSyncLpActivity(sync)) {
|
|
1415
|
+
return actorRedeemablePrincipalLamports;
|
|
1416
|
+
}
|
|
1417
|
+
return Math.max(
|
|
1418
|
+
actorRedeemablePrincipalLamports,
|
|
1419
|
+
legacyAvailableLamports - actorRedeemableFeeLamports
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
function hasActorRedeemableProjection(sync) {
|
|
1423
|
+
return sync?.redeemableProjectionAvailable === true;
|
|
1424
|
+
}
|
|
1425
|
+
function validateExplicitWithdrawalInput(args) {
|
|
1426
|
+
if (args.depositPrincipalLamports === void 0 && args.earnedFeeLamports === void 0) return;
|
|
1427
|
+
validateLpWithdrawalPlan({
|
|
1428
|
+
destinationAddresses: args.destinationAddresses,
|
|
1429
|
+
depositPrincipalLamports: args.depositPrincipalLamports ?? 0,
|
|
1430
|
+
earnedFeeLamports: args.earnedFeeLamports ?? 0,
|
|
1431
|
+
allowManyToOne: args.allowManyToOne,
|
|
1432
|
+
sourceCount: args.destinationAddresses.length
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
function validateWithdrawalDestinations(args) {
|
|
1436
|
+
validateLpWithdrawalPlan({
|
|
1437
|
+
destinationAddresses: args.destinationAddresses,
|
|
1438
|
+
depositPrincipalLamports: 1,
|
|
1439
|
+
earnedFeeLamports: 0,
|
|
1440
|
+
allowManyToOne: true
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
function resolveWithdrawalRecord({
|
|
1444
|
+
state,
|
|
1445
|
+
positionId,
|
|
1446
|
+
execution,
|
|
1447
|
+
options
|
|
1448
|
+
}) {
|
|
1449
|
+
const record = state.redemptions.get(execution.withdrawalId) ?? options.redemptionRecord;
|
|
1450
|
+
if (record === void 0) throw new Error(WITHDRAWAL_MISSING_RECORD_MESSAGE);
|
|
1451
|
+
if (record.lpPositionId !== positionId || record.redemptionId !== execution.withdrawalId) {
|
|
1452
|
+
throw new PolicyValidationError("INVALID_REDEMPTION_PLAN", WITHDRAWAL_RECORD_MISMATCH_MESSAGE);
|
|
1453
|
+
}
|
|
1454
|
+
return {
|
|
1455
|
+
...record,
|
|
1456
|
+
destinationAddresses: [...record.destinationAddresses],
|
|
1457
|
+
txSignatures: [...record.txSignatures],
|
|
1458
|
+
txSignatureBaseline: [...record.txSignatureBaseline],
|
|
1459
|
+
landedFeeBaselineLamports: record.landedFeeBaselineLamports ?? 0
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
function lpWithdrawalSourceCountForClaim(position, claim) {
|
|
1463
|
+
let remainingPrincipalLamports = claim.depositPrincipalLamports;
|
|
1464
|
+
let principalSourceCount = 0;
|
|
1465
|
+
const principalCandidates = [...position.shards].filter((shard) => shard.status === "AVAILABLE" && shard.amountLamports > 0).sort((left, right) => left.shardId.localeCompare(right.shardId));
|
|
1466
|
+
for (const shard of principalCandidates) {
|
|
1467
|
+
if (remainingPrincipalLamports <= 0) break;
|
|
1468
|
+
principalSourceCount += 1;
|
|
1469
|
+
remainingPrincipalLamports -= shard.amountLamports;
|
|
1470
|
+
}
|
|
1471
|
+
const actorRedeemablePrincipalLamports = position.actorSync?.redeemablePrincipalLamports ?? 0;
|
|
1472
|
+
const actorRedeemablePrincipalSourceCount = position.actorSync?.redeemablePrincipalSourceCount ?? 0;
|
|
1473
|
+
if (claim.depositPrincipalLamports > 0 && position.actorSync?.redeemableProjectionAvailable === true && actorRedeemablePrincipalLamports >= claim.depositPrincipalLamports && actorRedeemablePrincipalSourceCount > 0) {
|
|
1474
|
+
principalSourceCount = actorRedeemablePrincipalSourceCount;
|
|
1475
|
+
}
|
|
1476
|
+
const observedEarnedFeeSourceCount = position.actorSync?.redeemableProjectionAvailable === true ? position.actorSync.redeemableFeeSourceCount ?? 0 : position.actorSync?.redeemableFeeSourceCount || position.actorSync?.reimbursementDueSourceCount || 0;
|
|
1477
|
+
if (claim.earnedFeeLamports > 0 && observedEarnedFeeSourceCount < 1 && claim.allowManyToOne !== true) {
|
|
1478
|
+
throw new PolicyValidationError(
|
|
1479
|
+
"INVALID_WITHDRAWAL_PLAN",
|
|
1480
|
+
FEE_SOURCE_COUNT_UNAVAILABLE_MESSAGE
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
const earnedFeeSourceCount = claim.earnedFeeLamports > 0 ? Math.max(1, observedEarnedFeeSourceCount) : 0;
|
|
1484
|
+
return principalSourceCount + earnedFeeSourceCount;
|
|
1485
|
+
}
|
|
1486
|
+
function buildLpRedemptionRecord({
|
|
1487
|
+
execution,
|
|
1488
|
+
position,
|
|
1489
|
+
destinationAddresses,
|
|
1490
|
+
acceptedLamports,
|
|
1491
|
+
requestedTargetLamports,
|
|
1492
|
+
landedBaselineLamports,
|
|
1493
|
+
landedFeeBaselineLamports,
|
|
1494
|
+
txSignatureBaseline,
|
|
1495
|
+
nowMs
|
|
1496
|
+
}) {
|
|
1497
|
+
return {
|
|
1498
|
+
redemptionId: execution.withdrawalId,
|
|
1499
|
+
lpPositionId: position.id,
|
|
1500
|
+
requestedAt: nowMs,
|
|
1501
|
+
status: "pending",
|
|
1502
|
+
destinationAddresses: [...destinationAddresses],
|
|
1503
|
+
acceptedLamports,
|
|
1504
|
+
requestedTargetLamports,
|
|
1505
|
+
landedBaselineLamports,
|
|
1506
|
+
landedFeeBaselineLamports,
|
|
1507
|
+
redeemedLamports: 0,
|
|
1508
|
+
txSignatures: [...execution.txSignatures],
|
|
1509
|
+
txSignatureBaseline: [...txSignatureBaseline]
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
function reconcileLpRedemptionFromActorSync(record, position) {
|
|
1513
|
+
const sync = position.actorSync;
|
|
1514
|
+
if (sync === void 0) return record;
|
|
1515
|
+
const history = sync.redemptionHistory?.find(
|
|
1516
|
+
(candidate) => candidate.redemptionId === record.redemptionId
|
|
1517
|
+
);
|
|
1518
|
+
if (history !== void 0) {
|
|
1519
|
+
return reconcileLpRedemptionFromHistory(record, history);
|
|
1520
|
+
}
|
|
1521
|
+
const resolvedLamports = sync.withdrawalLandedLamports + sync.withdrawalLandedFeeLamports + sync.withdrawalCanceledLamports + sync.withdrawalNetworkFeeLossLamports;
|
|
1522
|
+
const settled = sync.withdrawalRequestedLamports >= record.requestedTargetLamports && sync.withdrawalRequestedLamports <= resolvedLamports;
|
|
1523
|
+
const redeemedLamports = Math.max(
|
|
1524
|
+
sync.withdrawalLandedLamports - record.landedBaselineLamports + sync.withdrawalLandedFeeLamports - (record.landedFeeBaselineLamports ?? 0),
|
|
1525
|
+
0
|
|
1526
|
+
);
|
|
1527
|
+
const baselineSignatures = new Set(record.txSignatureBaseline);
|
|
1528
|
+
const txSignatures = sync.withdrawalTxHashes.filter(
|
|
1529
|
+
(signature) => !baselineSignatures.has(signature)
|
|
1530
|
+
);
|
|
1531
|
+
if (!settled) return { ...record, redeemedLamports, txSignatures };
|
|
1532
|
+
if (redeemedLamports <= 0) {
|
|
1533
|
+
return {
|
|
1534
|
+
...record,
|
|
1535
|
+
status: "failed",
|
|
1536
|
+
redeemedLamports,
|
|
1537
|
+
txSignatures,
|
|
1538
|
+
error: WITHDRAWAL_NO_LAMPORTS_MESSAGE
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
return { ...record, status: "confirmed", redeemedLamports, txSignatures };
|
|
1542
|
+
}
|
|
1543
|
+
function reconcileLpRedemptionFromHistory(record, history) {
|
|
1544
|
+
const observedStateVersion = record.coordinatorStateVersion ?? 0;
|
|
1545
|
+
if (history.stateVersion < observedStateVersion) return record;
|
|
1546
|
+
if (record.acceptedLamports !== void 0 && history.acceptedLamports !== record.acceptedLamports || history.detailsTruncated !== true && !sameStringList(history.destinationAddresses, record.destinationAddresses)) {
|
|
1547
|
+
return {
|
|
1548
|
+
...record,
|
|
1549
|
+
status: "failed",
|
|
1550
|
+
coordinatorStateVersion: Math.max(observedStateVersion, history.stateVersion),
|
|
1551
|
+
error: WITHDRAWAL_HISTORY_MISMATCH_MESSAGE
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
const txSignatures = uniqueStrings([...record.txSignatures, ...history.txSignatures]);
|
|
1555
|
+
const base = {
|
|
1556
|
+
...record,
|
|
1557
|
+
coordinatorStateVersion: history.stateVersion,
|
|
1558
|
+
redeemedLamports: history.landedLamports,
|
|
1559
|
+
txSignatures,
|
|
1560
|
+
destinationCount: history.destinationCount,
|
|
1561
|
+
txSignatureCount: history.txSignatureCount,
|
|
1562
|
+
detailsTruncated: history.detailsTruncated,
|
|
1563
|
+
error: void 0
|
|
1564
|
+
};
|
|
1565
|
+
if (history.state === "confirmed") {
|
|
1566
|
+
return history.landedLamports > 0 ? { ...base, status: "confirmed" } : { ...base, status: "failed", error: WITHDRAWAL_NO_LAMPORTS_MESSAGE };
|
|
1567
|
+
}
|
|
1568
|
+
if (history.state === "rejected") {
|
|
1569
|
+
return {
|
|
1570
|
+
...base,
|
|
1571
|
+
status: "failed",
|
|
1572
|
+
error: WITHDRAWAL_NO_LAMPORTS_MESSAGE
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
return {
|
|
1576
|
+
...base,
|
|
1577
|
+
status: record.status === "reconciling" ? "reconciling" : "pending"
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
function hydrateLpRedemptionsFromPosition(state, position) {
|
|
1581
|
+
for (const history of position.actorSync?.redemptionHistory ?? []) {
|
|
1582
|
+
const existing = state.redemptions.get(history.redemptionId);
|
|
1583
|
+
if (existing === void 0) {
|
|
1584
|
+
state.redemptions.set(history.redemptionId, lpWithdrawalRecordFromHistory(position, history));
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
state.redemptions.set(
|
|
1588
|
+
history.redemptionId,
|
|
1589
|
+
reconcileLpRedemptionFromHistory(existing, history)
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
function lpWithdrawalRecordFromHistory(position, history) {
|
|
1594
|
+
const record = {
|
|
1595
|
+
redemptionId: history.redemptionId,
|
|
1596
|
+
lpPositionId: position.id,
|
|
1597
|
+
requestedAt: history.createdAtMs,
|
|
1598
|
+
status: "pending",
|
|
1599
|
+
destinationAddresses: [...history.destinationAddresses],
|
|
1600
|
+
acceptedLamports: history.acceptedLamports,
|
|
1601
|
+
requestedTargetLamports: history.acceptedLamports,
|
|
1602
|
+
landedBaselineLamports: 0,
|
|
1603
|
+
landedFeeBaselineLamports: 0,
|
|
1604
|
+
redeemedLamports: history.landedLamports,
|
|
1605
|
+
txSignatures: [...history.txSignatures],
|
|
1606
|
+
txSignatureBaseline: [],
|
|
1607
|
+
coordinatorStateVersion: history.stateVersion,
|
|
1608
|
+
destinationCount: history.destinationCount,
|
|
1609
|
+
txSignatureCount: history.txSignatureCount,
|
|
1610
|
+
detailsTruncated: history.detailsTruncated
|
|
1611
|
+
};
|
|
1612
|
+
return reconcileLpRedemptionFromHistory(record, history);
|
|
1613
|
+
}
|
|
1614
|
+
function sameStringList(left, right) {
|
|
1615
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1616
|
+
}
|
|
1617
|
+
function uniqueStrings(values) {
|
|
1618
|
+
return Array.from(new Set(values));
|
|
1619
|
+
}
|
|
1620
|
+
function isLpDkgActiveSessionError2(error) {
|
|
1621
|
+
return error instanceof Error && error.message.includes("LP DKG session already active for position index");
|
|
1622
|
+
}
|
|
1623
|
+
function isLpDkgIndexDriftError(error) {
|
|
1624
|
+
if (!(error instanceof Error)) return false;
|
|
1625
|
+
return error.message.includes("LP DKG index") && error.message.includes("already exists for position") || error.message.includes("LP DKG expected index");
|
|
1626
|
+
}
|
|
1627
|
+
function clonePosition(position) {
|
|
1628
|
+
return {
|
|
1629
|
+
...position,
|
|
1630
|
+
shards: position.shards.map((shard) => ({ ...shard })),
|
|
1631
|
+
actorSync: position.actorSync === void 0 ? void 0 : {
|
|
1632
|
+
...position.actorSync,
|
|
1633
|
+
redemptionHistory: position.actorSync.redemptionHistory?.map((history) => ({
|
|
1634
|
+
...history,
|
|
1635
|
+
destinationAddresses: [...history.destinationAddresses],
|
|
1636
|
+
txSignatures: [...history.txSignatures]
|
|
1637
|
+
}))
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
function generateId(prefix) {
|
|
1642
|
+
return `${prefix}_${Array.from(randomBytes2(8)).map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
1643
|
+
}
|
|
1644
|
+
function randomBytes2(length) {
|
|
1645
|
+
const bytes = new Uint8Array(length);
|
|
1646
|
+
crypto.getRandomValues(bytes);
|
|
1647
|
+
return bytes;
|
|
1648
|
+
}
|
|
1649
|
+
function throwIfTopUpWaitAborted(signal) {
|
|
1650
|
+
if (signal?.aborted !== true) return;
|
|
1651
|
+
throw new DOMException("LP top-up wait aborted", "AbortError");
|
|
1652
|
+
}
|
|
1653
|
+
function defaultSleep(delayMs) {
|
|
1654
|
+
return new Promise((resolve) => {
|
|
1655
|
+
globalThis.setTimeout(resolve, delayMs);
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// src/lp/index.ts
|
|
1660
|
+
function createPosition(session, args = {}) {
|
|
1661
|
+
return defaultLpLifecycleClient(session).createPosition(args);
|
|
1662
|
+
}
|
|
1663
|
+
function recoverPosition(session, args) {
|
|
1664
|
+
return defaultLpLifecycleClient(session).recoverPosition(args);
|
|
1665
|
+
}
|
|
1666
|
+
function confirmPositionCodeSaved(session, positionId) {
|
|
1667
|
+
defaultLpLifecycleClient(session).confirmPositionCodeSaved(positionId);
|
|
1668
|
+
}
|
|
1669
|
+
async function completeDkgBatch(session, positionId, opts = {}) {
|
|
1670
|
+
return await defaultLpLifecycleClient(session).completeDkgBatch(positionId, opts);
|
|
1671
|
+
}
|
|
1672
|
+
function refreshPosition(session, positionId) {
|
|
1673
|
+
return defaultLpLifecycleClient(session).refreshPosition(positionId);
|
|
1674
|
+
}
|
|
1675
|
+
function refill(session, positionId, args = {}) {
|
|
1676
|
+
return defaultLpLifecycleClient(session).refill(positionId, args);
|
|
1677
|
+
}
|
|
1678
|
+
function prepareInitialFunding(session, positionId) {
|
|
1679
|
+
return defaultLpLifecycleClient(session).prepareInitialFunding(positionId);
|
|
1680
|
+
}
|
|
1681
|
+
function prepareTopUp2(session, positionId) {
|
|
1682
|
+
return defaultLpLifecycleClient(session).prepareTopUp(positionId);
|
|
1683
|
+
}
|
|
1684
|
+
function waitForTopUp(session, topUpId, options = {}) {
|
|
1685
|
+
return defaultLpLifecycleClient(session).waitForTopUp(topUpId, options);
|
|
1686
|
+
}
|
|
1687
|
+
function reconcileFunding2(session, positionId) {
|
|
1688
|
+
return defaultLpLifecycleClient(session).reconcileFunding(positionId);
|
|
1689
|
+
}
|
|
1690
|
+
function withdrawPosition(session, positionId, args) {
|
|
1691
|
+
return defaultLpLifecycleClient(session).withdrawPosition(positionId, args);
|
|
1692
|
+
}
|
|
1693
|
+
function waitForWithdrawalConfirmation(session, positionId, execution, options = {}) {
|
|
1694
|
+
return defaultLpLifecycleClient(session).waitForWithdrawalConfirmation(
|
|
1695
|
+
positionId,
|
|
1696
|
+
execution,
|
|
1697
|
+
options
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
async function withdrawFees(session, positionId, args) {
|
|
1701
|
+
publicKey(args.destination);
|
|
1702
|
+
assertAttested(session);
|
|
1703
|
+
return await defaultLpLifecycleClient(session).withdrawFees(positionId, args);
|
|
1704
|
+
}
|
|
1705
|
+
function listPositions(session) {
|
|
1706
|
+
return defaultLpLifecycleClient(session).listPositions();
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
export { LP_COMMAND_REQUEST_TIMEOUT_MS, LP_MISSING_POSITION_CODE_MESSAGE, LP_REDEMPTION_RECONCILING_MESSAGE, bytesToHex, completeDkgBatch, confirmPositionCodeSaved, createLpCoordinatorClient, createLpDkgBatch, createLpLifecycleClient, createPosition, defaultLpLifecycleClient, deriveLpPositionCodeAuth, generateLpPositionCode, isLpPositionAuthMissingError, isLpRedemptionReconcilingError, listPositions, normalizeLpPositionCode, prepareInitialFunding, prepareTopUp2 as prepareTopUp, reconcileFunding2 as reconcileFunding, recoverPosition, refill, refreshPosition, signLpCommandBytes, waitForTopUp, waitForWithdrawalConfirmation, withdrawFees, withdrawPosition };
|
|
1710
|
+
//# sourceMappingURL=lp.js.map
|
|
1711
|
+
//# sourceMappingURL=lp.js.map
|