@perena/vault-sdk 1.0.43 → 1.0.46
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/dist/browser/nest.d.mts +208 -0
- package/dist/browser/nest.mjs +757 -0
- package/dist/index.d.ts +820 -16
- package/dist/index.js +1185 -110
- package/package.json +4 -1
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
// src/services/nestWithdrawalService.ts
|
|
2
|
+
import { address as address4 } from "@solana/kit";
|
|
3
|
+
import {
|
|
4
|
+
ASSOCIATED_TOKEN_PROGRAM_ID as ASSOCIATED_TOKEN_PROGRAM_ID2,
|
|
5
|
+
getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2,
|
|
6
|
+
TOKEN_PROGRAM_ID
|
|
7
|
+
} from "@solana/spl-token";
|
|
8
|
+
import {
|
|
9
|
+
ComputeBudgetProgram,
|
|
10
|
+
PublicKey as PublicKey3,
|
|
11
|
+
TransactionMessage as TransactionMessage2,
|
|
12
|
+
VersionedTransaction as VersionedTransaction2
|
|
13
|
+
} from "@solana/web3.js";
|
|
14
|
+
|
|
15
|
+
// ../common/src/utils/accounts/ata.ts
|
|
16
|
+
import {
|
|
17
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
18
|
+
getAssociatedTokenAddressSync
|
|
19
|
+
} from "@solana/spl-token";
|
|
20
|
+
|
|
21
|
+
// ../common/src/utils/compat.ts
|
|
22
|
+
import {
|
|
23
|
+
AccountRole,
|
|
24
|
+
address
|
|
25
|
+
} from "@solana/kit";
|
|
26
|
+
import {
|
|
27
|
+
PublicKey,
|
|
28
|
+
TransactionInstruction
|
|
29
|
+
} from "@solana/web3.js";
|
|
30
|
+
function toKitInstruction(ix) {
|
|
31
|
+
return {
|
|
32
|
+
programAddress: ix.programId.toBase58(),
|
|
33
|
+
data: new Uint8Array(ix.data),
|
|
34
|
+
accounts: ix.keys.map((key) => ({
|
|
35
|
+
address: key.pubkey.toBase58(),
|
|
36
|
+
role: key.isWritable ? key.isSigner ? AccountRole.WRITABLE_SIGNER : AccountRole.WRITABLE : key.isSigner ? AccountRole.READONLY_SIGNER : AccountRole.READONLY
|
|
37
|
+
}))
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ../common/src/utils/accounts/roles.ts
|
|
42
|
+
import {
|
|
43
|
+
AccountRole as AccountRole2,
|
|
44
|
+
upgradeRoleToSigner,
|
|
45
|
+
upgradeRoleToWritable
|
|
46
|
+
} from "@solana/kit";
|
|
47
|
+
|
|
48
|
+
// ../common/src/utils/cpi/refs.ts
|
|
49
|
+
import {
|
|
50
|
+
upgradeRoleToSigner as upgradeRoleToSigner2,
|
|
51
|
+
upgradeRoleToWritable as upgradeRoleToWritable2
|
|
52
|
+
} from "@solana/kit";
|
|
53
|
+
|
|
54
|
+
// ../common/src/utils/numbers.ts
|
|
55
|
+
function toUiAmount(amount, decimals) {
|
|
56
|
+
const divisor = BigInt(10 ** decimals);
|
|
57
|
+
const wholePart = amount / divisor;
|
|
58
|
+
const remainder = amount % divisor;
|
|
59
|
+
return Number(wholePart) + Number(remainder) / 10 ** decimals;
|
|
60
|
+
}
|
|
61
|
+
function fromUiAmount(amount, decimals) {
|
|
62
|
+
return BigInt(Math.round(amount * 10 ** decimals));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ../common/src/utils/transaction.ts
|
|
66
|
+
import {
|
|
67
|
+
PublicKey as PublicKey2,
|
|
68
|
+
TransactionMessage,
|
|
69
|
+
VersionedTransaction
|
|
70
|
+
} from "@solana/web3.js";
|
|
71
|
+
|
|
72
|
+
// ../common/src/services/mints/constants.ts
|
|
73
|
+
import { address as address2 } from "@solana/kit";
|
|
74
|
+
var SOL_MINT = address2("So11111111111111111111111111111111111111112");
|
|
75
|
+
var WBTC_MINT = address2(
|
|
76
|
+
"3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh"
|
|
77
|
+
);
|
|
78
|
+
var CBBTC_MINT = address2(
|
|
79
|
+
"cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij"
|
|
80
|
+
);
|
|
81
|
+
var WETH_MINT = address2(
|
|
82
|
+
"7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs"
|
|
83
|
+
);
|
|
84
|
+
var JLP_MINT = address2("27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4");
|
|
85
|
+
var SPYX_MINT = address2("XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W");
|
|
86
|
+
var QQQX_MINT = address2("Xs8S1uUs1zvS2p7iwtsG3b6fkhpvmwz4GYU3gWAmWHZ");
|
|
87
|
+
var USDC_MINT = address2(
|
|
88
|
+
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
|
89
|
+
);
|
|
90
|
+
var USDT_MINT = address2(
|
|
91
|
+
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
|
92
|
+
);
|
|
93
|
+
var USDS_MINT = address2("USDSwr9ApdHk5bvJKMjzff41FfuX8bSxdKcR81vTwcA");
|
|
94
|
+
var USDG_MINT = address2(
|
|
95
|
+
"2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH"
|
|
96
|
+
);
|
|
97
|
+
var PYUSD_MINT = address2(
|
|
98
|
+
"2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo"
|
|
99
|
+
);
|
|
100
|
+
var CASH_MINT = address2(
|
|
101
|
+
"CASHx9KJUStyftLFWGvEVf59SGeG9sh5FfcnZMVPCASH"
|
|
102
|
+
);
|
|
103
|
+
var USD1_MINT = address2("USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB");
|
|
104
|
+
var JITOSOL_MINT = address2(
|
|
105
|
+
"J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn"
|
|
106
|
+
);
|
|
107
|
+
var JUPSOL_MINT = address2(
|
|
108
|
+
"jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v"
|
|
109
|
+
);
|
|
110
|
+
var USD_STAR_MINT = address2(
|
|
111
|
+
"star9agSpjiFe3M49B3RniVU4CMBBEK3Qnaqn3RGiFM"
|
|
112
|
+
);
|
|
113
|
+
var USD_STAR_JUNIOR_MINT = address2(
|
|
114
|
+
"3AJYCjrhKitmBd8tRUurPA5dGHCAQh6A8L9DkuSC37Rm"
|
|
115
|
+
);
|
|
116
|
+
var PST_MINT = address2("59obFNBzyTBGowrkif5uK7ojS58vsuWz3ZCvg6tfZAGw");
|
|
117
|
+
var PRIME_MINT = address2(
|
|
118
|
+
"3b8X44fLF9ooXaUm3hhSgjpmVs6rZZ3pPoGnGahc3Uu7"
|
|
119
|
+
);
|
|
120
|
+
var NEST_RWA_SHARE_MINT = address2(
|
|
121
|
+
"VyXKJnVhkSkB6KAozSZCiLQbuB1k1r6Nu4etNYVo5LJ"
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// ../common/src/services/mints/defaults.ts
|
|
125
|
+
var DEFAULT_MINTS = [
|
|
126
|
+
{
|
|
127
|
+
address: SOL_MINT,
|
|
128
|
+
symbol: "SOL",
|
|
129
|
+
name: "Solana",
|
|
130
|
+
decimals: 9,
|
|
131
|
+
assetType: "Standard"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
address: USDC_MINT,
|
|
135
|
+
symbol: "USDC",
|
|
136
|
+
name: "USD Coin",
|
|
137
|
+
decimals: 6,
|
|
138
|
+
assetType: "Stablecoin"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
address: USDT_MINT,
|
|
142
|
+
symbol: "USDT",
|
|
143
|
+
name: "Tether USD",
|
|
144
|
+
decimals: 6,
|
|
145
|
+
assetType: "Stablecoin"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
address: USDS_MINT,
|
|
149
|
+
symbol: "USDS",
|
|
150
|
+
decimals: 6,
|
|
151
|
+
assetType: "Stablecoin"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
address: USDG_MINT,
|
|
155
|
+
symbol: "USDG",
|
|
156
|
+
name: "Global Dollar",
|
|
157
|
+
decimals: 6,
|
|
158
|
+
assetType: "Stablecoin",
|
|
159
|
+
isToken2022: true
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
address: PYUSD_MINT,
|
|
163
|
+
symbol: "PYUSD",
|
|
164
|
+
name: "PayPal USD",
|
|
165
|
+
decimals: 6,
|
|
166
|
+
assetType: "Stablecoin",
|
|
167
|
+
isToken2022: true
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
address: CASH_MINT,
|
|
171
|
+
symbol: "CASH",
|
|
172
|
+
decimals: 6,
|
|
173
|
+
assetType: "Stablecoin",
|
|
174
|
+
isToken2022: true
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
address: USD_STAR_MINT,
|
|
178
|
+
symbol: "USD*",
|
|
179
|
+
name: "USD Star",
|
|
180
|
+
decimals: 6,
|
|
181
|
+
assetType: "YBA"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
address: USD_STAR_JUNIOR_MINT,
|
|
185
|
+
symbol: "USD*-J",
|
|
186
|
+
name: "USD Star Junior",
|
|
187
|
+
decimals: 6,
|
|
188
|
+
assetType: "YBA"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
address: PST_MINT,
|
|
192
|
+
symbol: "PST",
|
|
193
|
+
name: "PayFi Strategy Token",
|
|
194
|
+
decimals: 6,
|
|
195
|
+
assetType: "YBA"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
address: PRIME_MINT,
|
|
199
|
+
symbol: "PRIME",
|
|
200
|
+
name: "Hastra PRIME",
|
|
201
|
+
decimals: 6,
|
|
202
|
+
assetType: "YBA"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
address: NEST_RWA_SHARE_MINT,
|
|
206
|
+
symbol: "nPERENA",
|
|
207
|
+
name: "Nest Vault",
|
|
208
|
+
decimals: 6,
|
|
209
|
+
assetType: "YBA"
|
|
210
|
+
}
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
// ../common/src/services/mints/registry.ts
|
|
214
|
+
import { TOKEN_PROGRAM_ADDRESS } from "@solana-program/token";
|
|
215
|
+
import { TOKEN_2022_PROGRAM_ADDRESS } from "@solana-program/token-2022";
|
|
216
|
+
var MintRegistry = class {
|
|
217
|
+
constructor(options = {}) {
|
|
218
|
+
this.byAddress = /* @__PURE__ */ new Map();
|
|
219
|
+
this.bySymbol = /* @__PURE__ */ new Map();
|
|
220
|
+
const { includeDefaults = true, custom = [] } = options;
|
|
221
|
+
if (includeDefaults) {
|
|
222
|
+
for (const config of DEFAULT_MINTS) {
|
|
223
|
+
this.register(config);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const config of custom) {
|
|
227
|
+
this.register(config);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
register(config) {
|
|
231
|
+
const mintInfo = {
|
|
232
|
+
...config,
|
|
233
|
+
tokenProgram: config.isToken2022 ? TOKEN_2022_PROGRAM_ADDRESS : TOKEN_PROGRAM_ADDRESS,
|
|
234
|
+
toUiAmount: (amount) => toUiAmount(amount, config.decimals),
|
|
235
|
+
fromUiAmount: (amount) => fromUiAmount(amount, config.decimals)
|
|
236
|
+
};
|
|
237
|
+
this.byAddress.set(config.address, mintInfo);
|
|
238
|
+
this.bySymbol.set(config.symbol.toUpperCase(), mintInfo);
|
|
239
|
+
}
|
|
240
|
+
get(mint) {
|
|
241
|
+
const key = mint.toString();
|
|
242
|
+
const bySymbol = this.bySymbol.get(key.toUpperCase());
|
|
243
|
+
if (bySymbol) {
|
|
244
|
+
return bySymbol;
|
|
245
|
+
}
|
|
246
|
+
return this.byAddress.get(key);
|
|
247
|
+
}
|
|
248
|
+
all() {
|
|
249
|
+
return Array.from(this.byAddress.values());
|
|
250
|
+
}
|
|
251
|
+
standardAssets() {
|
|
252
|
+
return this.filterAssetsByType("Standard");
|
|
253
|
+
}
|
|
254
|
+
stablecoins() {
|
|
255
|
+
return this.filterAssetsByType("Stablecoin");
|
|
256
|
+
}
|
|
257
|
+
lsts() {
|
|
258
|
+
return this.filterAssetsByType("LST");
|
|
259
|
+
}
|
|
260
|
+
yieldBearingAssets() {
|
|
261
|
+
return this.filterAssetsByType("YBA");
|
|
262
|
+
}
|
|
263
|
+
filterAssetsByType(assetType) {
|
|
264
|
+
return Array.from(this.byAddress.values()).filter(
|
|
265
|
+
(mint) => mint.assetType === assetType
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
symbols() {
|
|
269
|
+
return Array.from(this.bySymbol.keys());
|
|
270
|
+
}
|
|
271
|
+
addresses() {
|
|
272
|
+
return this.all().map((mint) => mint.address);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
var mints = new MintRegistry();
|
|
276
|
+
|
|
277
|
+
// ../common/src/services/baseAccountClient.ts
|
|
278
|
+
import { fetchMaybeToken } from "@solana-program/token";
|
|
279
|
+
var DEFAULT_SPL_ATA_TTL_MS = 6e4 * 10;
|
|
280
|
+
|
|
281
|
+
// ../nest/src/priceApi.ts
|
|
282
|
+
var NEST_API_BASE_URL = "https://api.nest.credit/v1";
|
|
283
|
+
|
|
284
|
+
// ../nest/src/redemptionApi.ts
|
|
285
|
+
var NestApiError = class extends Error {
|
|
286
|
+
constructor(status, message) {
|
|
287
|
+
super(`Nest API (${status}): ${message}`);
|
|
288
|
+
this.status = status;
|
|
289
|
+
this.name = "NestApiError";
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
function assertNestShareAmount(amount) {
|
|
293
|
+
if (typeof amount !== "bigint" || amount <= 0n || amount > 0xffffffffffffffffn) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
"Nest share amount must be a positive u64 bigint in base units"
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function request(path, options, body) {
|
|
300
|
+
const response = await (options.fetchFn ?? fetch)(
|
|
301
|
+
`${(options.baseUrl ?? NEST_API_BASE_URL).replace(/\/$/, "")}${path}`,
|
|
302
|
+
{
|
|
303
|
+
method: body ? "POST" : "GET",
|
|
304
|
+
headers: body ? { "Content-Type": "application/json" } : void 0,
|
|
305
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
306
|
+
signal: options.signal
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
const payload = await response.json().catch(() => null);
|
|
310
|
+
if (!response.ok) {
|
|
311
|
+
throw new NestApiError(
|
|
312
|
+
response.status,
|
|
313
|
+
typeof payload?.error === "string" ? payload.error : response.statusText
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (!payload || typeof payload !== "object" || !payload.data) {
|
|
317
|
+
throw new Error("Nest API returned an invalid response");
|
|
318
|
+
}
|
|
319
|
+
return payload.data;
|
|
320
|
+
}
|
|
321
|
+
async function fetchNestRedemptionTransaction(args, options = {}) {
|
|
322
|
+
assertNestShareAmount(args.rawAmountNestToken);
|
|
323
|
+
const data = await request(
|
|
324
|
+
"/solana/nest/async-redeem/request/build-tx",
|
|
325
|
+
options,
|
|
326
|
+
{
|
|
327
|
+
...args,
|
|
328
|
+
rawAmountNestToken: args.rawAmountNestToken.toString(),
|
|
329
|
+
// Squads must sign both the token authority and LayerZero fee payer at execution.
|
|
330
|
+
feePayer: args.owner
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
if (typeof data.txBase64 !== "string" || !data.txBase64.length) {
|
|
334
|
+
throw new Error("Nest API returned no transaction");
|
|
335
|
+
}
|
|
336
|
+
return data.txBase64;
|
|
337
|
+
}
|
|
338
|
+
async function fetchNestRedemptionQuote(args, options = {}) {
|
|
339
|
+
assertNestShareAmount(args.rawAmountNestToken);
|
|
340
|
+
const data = await request("/solana/nest/async-redeem/quote", options, {
|
|
341
|
+
...args,
|
|
342
|
+
rawAmountNestToken: args.rawAmountNestToken.toString()
|
|
343
|
+
});
|
|
344
|
+
if (data.nestVaultSlug !== args.nestVaultSlug || data.redemptionAsset !== "USDC" || data.shareAmount !== args.rawAmountNestToken.toString() || !Number.isInteger(data.shareDecimals) || data.shareDecimals < 0 || data.shareDecimals > 18 || data.redemptionDecimals !== 6 || typeof data.redemptionAmount !== "string" || typeof data.feeAmount !== "string" || !/^\d+$/.test(data.redemptionAmount) || !/^\d+$/.test(data.feeAmount)) {
|
|
345
|
+
throw new Error("Nest API returned an invalid redemption quote");
|
|
346
|
+
}
|
|
347
|
+
return data;
|
|
348
|
+
}
|
|
349
|
+
async function fetchNestRedemptionStatus(signature, options = {}) {
|
|
350
|
+
if (!/^[1-9A-HJ-NP-Za-km-z]{64,88}$/.test(signature)) {
|
|
351
|
+
throw new Error("Enter a Solana withdrawal execution signature");
|
|
352
|
+
}
|
|
353
|
+
let data;
|
|
354
|
+
try {
|
|
355
|
+
data = await request(
|
|
356
|
+
`/solana/redeem-status/${encodeURIComponent(signature)}`,
|
|
357
|
+
options
|
|
358
|
+
);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
if (error instanceof NestApiError && error.status === 404) return null;
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
if (data.solanaBurnSignature !== signature || typeof data.solanaWallet !== "string" || typeof data.overallStatus !== "string" || !["solanaBurn", "plumeProcessing", "cctpAttestation", "solanaClaim"].every(
|
|
364
|
+
(key) => typeof data.steps?.[key]?.status === "string"
|
|
365
|
+
)) {
|
|
366
|
+
throw new Error("Nest API returned an invalid redemption status");
|
|
367
|
+
}
|
|
368
|
+
return data;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/services/consensusOracle/sources/nestPriceSource.ts
|
|
372
|
+
import { address as address3 } from "@solana/kit";
|
|
373
|
+
var NEST_VAULT_SLUG = "nest-perena-vault";
|
|
374
|
+
var NEST_RWA_SHARE_MINT2 = address3(
|
|
375
|
+
"VyXKJnVhkSkB6KAozSZCiLQbuB1k1r6Nu4etNYVo5LJ"
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// src/services/nestWithdrawalService.ts
|
|
379
|
+
var NestOftProgram = new PublicKey3(
|
|
380
|
+
"ChEfPd3RzLeYiRwp1K9evimmaFSd6DV1S4Mv5q5Aj1th"
|
|
381
|
+
);
|
|
382
|
+
var NestOftStore = new PublicKey3(
|
|
383
|
+
"k8mJj8Gyw2gFAqut21AFVUUZhxb4RVDMnb4PrSYiDhV"
|
|
384
|
+
);
|
|
385
|
+
var UsdcMint = new PublicKey3("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
386
|
+
var NestPerenaComposer = Buffer.from(
|
|
387
|
+
"000000000000000000000000908dcb5531691c2124c54e30bb645cf11647090d",
|
|
388
|
+
"hex"
|
|
389
|
+
);
|
|
390
|
+
async function buildNestWithdrawalRequest(args) {
|
|
391
|
+
const txBase64 = await fetchNestRedemptionTransaction(
|
|
392
|
+
{
|
|
393
|
+
owner: args.owner.toBase58(),
|
|
394
|
+
nestVaultSlug: NEST_VAULT_SLUG,
|
|
395
|
+
rawAmountNestToken: args.rawAmountNestToken
|
|
396
|
+
},
|
|
397
|
+
args.apiOptions
|
|
398
|
+
);
|
|
399
|
+
return decodeNestWithdrawalRequest({ ...args, txBase64 });
|
|
400
|
+
}
|
|
401
|
+
async function decodeNestWithdrawalRequest(args) {
|
|
402
|
+
const transaction = VersionedTransaction2.deserialize(
|
|
403
|
+
Buffer.from(args.txBase64, "base64")
|
|
404
|
+
);
|
|
405
|
+
const message = transaction.message;
|
|
406
|
+
if (!message.staticAccountKeys[0].equals(args.owner) || message.header.numRequiredSignatures !== 1) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
"Nest withdrawal must use the Squads vault as its only signer and fee payer"
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
const tables = await Promise.all(
|
|
412
|
+
message.addressTableLookups.map(async (lookup) => {
|
|
413
|
+
const { value } = await args.connection.getAddressLookupTable(
|
|
414
|
+
lookup.accountKey
|
|
415
|
+
);
|
|
416
|
+
if (!value)
|
|
417
|
+
throw new Error(
|
|
418
|
+
`Nest lookup table not found: ${lookup.accountKey.toBase58()}`
|
|
419
|
+
);
|
|
420
|
+
return value;
|
|
421
|
+
})
|
|
422
|
+
);
|
|
423
|
+
const decoded = TransactionMessage2.decompile(message, {
|
|
424
|
+
addressLookupTableAccounts: tables
|
|
425
|
+
});
|
|
426
|
+
const nestMint = new PublicKey3(NEST_RWA_SHARE_MINT2);
|
|
427
|
+
const sourceAta = getAssociatedTokenAddressSync2(nestMint, args.owner, true);
|
|
428
|
+
const usdcAta = getAssociatedTokenAddressSync2(UsdcMint, args.owner, true);
|
|
429
|
+
const instructions2 = decoded.instructions.filter(
|
|
430
|
+
(ix) => !ix.programId.equals(ComputeBudgetProgram.programId)
|
|
431
|
+
);
|
|
432
|
+
let sends = 0;
|
|
433
|
+
for (const ix of instructions2) {
|
|
434
|
+
if (ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID2)) {
|
|
435
|
+
if (ix.data.length !== 1 || ix.data[0] !== 1 || !ix.keys[0]?.pubkey.equals(args.owner) || !ix.keys[1]?.pubkey.equals(usdcAta) || !ix.keys[2]?.pubkey.equals(args.owner) || !ix.keys[3]?.pubkey.equals(UsdcMint) || !ix.keys[5]?.pubkey.equals(TOKEN_PROGRAM_ID)) {
|
|
436
|
+
throw new Error("Unexpected token account creation in Nest withdrawal");
|
|
437
|
+
}
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (!ix.programId.equals(NestOftProgram) || ix.data.length < 52 || !ix.data.subarray(0, 8).equals(Buffer.from([102, 251, 20, 187, 65, 75, 12, 69])) || !ix.data.subarray(12, 44).equals(NestPerenaComposer) || ix.data.readBigUInt64LE(44) !== args.rawAmountNestToken || ![args.owner, nestMint, sourceAta, NestOftStore].every(
|
|
441
|
+
(key) => ix.keys.some((meta) => meta.pubkey.equals(key))
|
|
442
|
+
)) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
"Unexpected instruction or share amount in Nest withdrawal"
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
sends += 1;
|
|
448
|
+
}
|
|
449
|
+
if (sends !== 1)
|
|
450
|
+
throw new Error("Nest withdrawal must contain exactly one OFT send");
|
|
451
|
+
return {
|
|
452
|
+
instructions: instructions2.map(toKitInstruction),
|
|
453
|
+
lookupTables: tables.map((table) => address4(table.key.toBase58()))
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/utils/squads.ts
|
|
458
|
+
import * as squads from "@sqds/multisig";
|
|
459
|
+
import {
|
|
460
|
+
PublicKey as PublicKey4,
|
|
461
|
+
Transaction,
|
|
462
|
+
TransactionMessage as TransactionMessage3,
|
|
463
|
+
VersionedTransaction as VersionedTransaction3
|
|
464
|
+
} from "@solana/web3.js";
|
|
465
|
+
var SQUADS_SIMULATION_COMMITMENT = "processed";
|
|
466
|
+
var SquadsProposalExecutionSimulationError = class extends Error {
|
|
467
|
+
constructor(transactionError, logs, unitsConsumed) {
|
|
468
|
+
const programLogs = logs.length > 0 ? `
|
|
469
|
+
${logs.join("\n")}` : "";
|
|
470
|
+
super(
|
|
471
|
+
`Squads proposal execution simulation failed: ${JSON.stringify(
|
|
472
|
+
transactionError
|
|
473
|
+
)}${programLogs}`
|
|
474
|
+
);
|
|
475
|
+
this.name = "SquadsProposalExecutionSimulationError";
|
|
476
|
+
this.transactionError = transactionError;
|
|
477
|
+
this.logs = [...logs];
|
|
478
|
+
this.unitsConsumed = unitsConsumed;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
function validateVaultIndex(vaultIndex) {
|
|
482
|
+
if (!Number.isSafeInteger(vaultIndex) || vaultIndex < 0) {
|
|
483
|
+
throw new Error("Squads vaultIndex must be a non-negative safe integer");
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function resolveSquadsWalletRoute(route) {
|
|
487
|
+
validateVaultIndex(route.vaultIndex);
|
|
488
|
+
const wallet = new PublicKey4(route.wallet);
|
|
489
|
+
const multisigPda = new PublicKey4(route.multisigPda);
|
|
490
|
+
const [vaultPda] = squads.getVaultPda({
|
|
491
|
+
multisigPda,
|
|
492
|
+
index: route.vaultIndex
|
|
493
|
+
});
|
|
494
|
+
return { ...route, wallet, multisigPda, vaultPda };
|
|
495
|
+
}
|
|
496
|
+
function findSquadsWalletRoute(wallet, routes) {
|
|
497
|
+
const walletKey = typeof wallet === "string" ? new PublicKey4(wallet) : wallet;
|
|
498
|
+
const route = routes.find(
|
|
499
|
+
(candidate) => candidate.wallet === walletKey.toBase58()
|
|
500
|
+
);
|
|
501
|
+
return route ? resolveSquadsWalletRoute(route) : void 0;
|
|
502
|
+
}
|
|
503
|
+
function validateSquadsWalletRoutes(routes) {
|
|
504
|
+
const wallets = /* @__PURE__ */ new Set();
|
|
505
|
+
for (const route of routes) {
|
|
506
|
+
const resolved = resolveSquadsWalletRoute(route);
|
|
507
|
+
const wallet = resolved.wallet.toBase58();
|
|
508
|
+
if (wallets.has(wallet)) {
|
|
509
|
+
throw new Error(`Duplicate Squads route for wallet ${wallet}`);
|
|
510
|
+
}
|
|
511
|
+
wallets.add(wallet);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function vaultAuthorityForWallet(wallet, routes) {
|
|
515
|
+
const walletKey = typeof wallet === "string" ? new PublicKey4(wallet) : wallet;
|
|
516
|
+
return findSquadsWalletRoute(walletKey, routes)?.vaultPda ?? walletKey;
|
|
517
|
+
}
|
|
518
|
+
async function simulateSquadsProposalExecution(args) {
|
|
519
|
+
const { connection, feePayer, instructions: instructions2 } = args;
|
|
520
|
+
const recentBlockhash = args.recentBlockhash ?? (await connection.getLatestBlockhash(SQUADS_SIMULATION_COMMITMENT)).blockhash;
|
|
521
|
+
const message = new TransactionMessage3({
|
|
522
|
+
payerKey: feePayer,
|
|
523
|
+
recentBlockhash,
|
|
524
|
+
instructions: [...instructions2]
|
|
525
|
+
}).compileToV0Message(args.addressLookupTableAccounts);
|
|
526
|
+
const transaction = new VersionedTransaction3(message);
|
|
527
|
+
const { value } = await connection.simulateTransaction(transaction, {
|
|
528
|
+
sigVerify: false,
|
|
529
|
+
commitment: SQUADS_SIMULATION_COMMITMENT,
|
|
530
|
+
innerInstructions: true
|
|
531
|
+
});
|
|
532
|
+
if (value.err) {
|
|
533
|
+
throw new SquadsProposalExecutionSimulationError(
|
|
534
|
+
value.err,
|
|
535
|
+
value.logs ?? [],
|
|
536
|
+
value.unitsConsumed
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
return value;
|
|
540
|
+
}
|
|
541
|
+
async function prepareVaultTransaction(args) {
|
|
542
|
+
const { connection, proposer, instructions: instructions2 } = args;
|
|
543
|
+
const route = findSquadsWalletRoute(proposer, args.squadsRoutes ?? []);
|
|
544
|
+
if (!route) {
|
|
545
|
+
return {
|
|
546
|
+
kind: "direct",
|
|
547
|
+
transaction: new Transaction().add(...instructions2),
|
|
548
|
+
authority: proposer
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
const multisig = await squads.accounts.Multisig.fromAccountAddress(
|
|
552
|
+
connection,
|
|
553
|
+
route.multisigPda
|
|
554
|
+
);
|
|
555
|
+
const member = multisig.members.find(
|
|
556
|
+
(candidate) => candidate.key.equals(proposer)
|
|
557
|
+
);
|
|
558
|
+
if (!member) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`Wallet ${proposer.toBase58()} is not a member of Squads multisig ${route.multisigPda.toBase58()}`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
if (!squads.types.Permissions.has(
|
|
564
|
+
member.permissions,
|
|
565
|
+
squads.types.Permission.Initiate
|
|
566
|
+
)) {
|
|
567
|
+
throw new Error(
|
|
568
|
+
`Wallet ${proposer.toBase58()} cannot initiate proposals for Squads multisig ${route.multisigPda.toBase58()}`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
const transactionIndex = BigInt(multisig.transactionIndex.toString()) + 1n;
|
|
572
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
573
|
+
if (!args.skipSimulation) {
|
|
574
|
+
await simulateSquadsProposalExecution({
|
|
575
|
+
connection,
|
|
576
|
+
feePayer: proposer,
|
|
577
|
+
instructions: instructions2,
|
|
578
|
+
recentBlockhash: blockhash,
|
|
579
|
+
addressLookupTableAccounts: args.addressLookupTableAccounts
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
const transactionMessage = new TransactionMessage3({
|
|
583
|
+
payerKey: route.vaultPda,
|
|
584
|
+
recentBlockhash: blockhash,
|
|
585
|
+
instructions: [...instructions2]
|
|
586
|
+
});
|
|
587
|
+
const createVaultTransactionIx = squads.instructions.vaultTransactionCreate({
|
|
588
|
+
multisigPda: route.multisigPda,
|
|
589
|
+
transactionIndex,
|
|
590
|
+
creator: proposer,
|
|
591
|
+
vaultIndex: route.vaultIndex,
|
|
592
|
+
ephemeralSigners: 0,
|
|
593
|
+
transactionMessage,
|
|
594
|
+
addressLookupTableAccounts: args.addressLookupTableAccounts,
|
|
595
|
+
memo: route.memo
|
|
596
|
+
});
|
|
597
|
+
const createProposalIx = squads.instructions.proposalCreate({
|
|
598
|
+
multisigPda: route.multisigPda,
|
|
599
|
+
transactionIndex,
|
|
600
|
+
creator: proposer
|
|
601
|
+
});
|
|
602
|
+
const [proposalPda] = squads.getProposalPda({
|
|
603
|
+
multisigPda: route.multisigPda,
|
|
604
|
+
transactionIndex
|
|
605
|
+
});
|
|
606
|
+
const [vaultTransactionPda] = squads.getTransactionPda({
|
|
607
|
+
multisigPda: route.multisigPda,
|
|
608
|
+
index: transactionIndex
|
|
609
|
+
});
|
|
610
|
+
return {
|
|
611
|
+
kind: "squads",
|
|
612
|
+
transaction: new Transaction().add(
|
|
613
|
+
createVaultTransactionIx,
|
|
614
|
+
createProposalIx
|
|
615
|
+
),
|
|
616
|
+
authority: route.vaultPda,
|
|
617
|
+
multisigPda: route.multisigPda,
|
|
618
|
+
vaultIndex: route.vaultIndex,
|
|
619
|
+
transactionIndex,
|
|
620
|
+
proposalPda,
|
|
621
|
+
vaultTransactionPda
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/utils/squadsBuffer.ts
|
|
626
|
+
import * as squads2 from "@sqds/multisig";
|
|
627
|
+
import {
|
|
628
|
+
PublicKey as PublicKey5,
|
|
629
|
+
SystemProgram,
|
|
630
|
+
Transaction as Transaction2
|
|
631
|
+
} from "@solana/web3.js";
|
|
632
|
+
var PacketSize = 1232;
|
|
633
|
+
var BufferChunkSize = 700;
|
|
634
|
+
var MaxBufferSize = 4e3;
|
|
635
|
+
async function prepareSquadsProposalUpload(args) {
|
|
636
|
+
const { connection, proposer, prepared } = args;
|
|
637
|
+
const transaction = prepared.transaction;
|
|
638
|
+
const sizeProbe = new Transaction2({
|
|
639
|
+
feePayer: proposer,
|
|
640
|
+
recentBlockhash: PublicKey5.default.toBase58()
|
|
641
|
+
}).add(...transaction.instructions);
|
|
642
|
+
let fits = false;
|
|
643
|
+
try {
|
|
644
|
+
fits = sizeProbe.serialize({
|
|
645
|
+
requireAllSignatures: false,
|
|
646
|
+
verifySignatures: false
|
|
647
|
+
}).length <= PacketSize;
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
if (fits) return { setupTransactions: [], proposalTransaction: transaction };
|
|
651
|
+
const [decoded] = squads2.generated.vaultTransactionCreateStruct.deserialize(
|
|
652
|
+
transaction.instructions[0].data
|
|
653
|
+
);
|
|
654
|
+
const bytes = decoded.args.transactionMessage;
|
|
655
|
+
if (bytes.length > MaxBufferSize)
|
|
656
|
+
throw new Error("Squads proposal exceeds the 4,000-byte message limit");
|
|
657
|
+
let bufferPda;
|
|
658
|
+
let bufferIndex = 0;
|
|
659
|
+
for (; bufferIndex <= 255; bufferIndex++) {
|
|
660
|
+
const [candidate] = PublicKey5.findProgramAddressSync(
|
|
661
|
+
[
|
|
662
|
+
Buffer.from("multisig"),
|
|
663
|
+
prepared.multisigPda.toBuffer(),
|
|
664
|
+
Buffer.from("transaction_buffer"),
|
|
665
|
+
proposer.toBuffer(),
|
|
666
|
+
Buffer.from([bufferIndex])
|
|
667
|
+
],
|
|
668
|
+
squads2.PROGRAM_ID
|
|
669
|
+
);
|
|
670
|
+
if (!await connection.getAccountInfo(candidate, "confirmed")) {
|
|
671
|
+
bufferPda = candidate;
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (!bufferPda)
|
|
676
|
+
throw new Error(
|
|
677
|
+
"All Squads upload buffers are occupied; close an old upload first"
|
|
678
|
+
);
|
|
679
|
+
const finalBufferHash = Array.from(
|
|
680
|
+
new Uint8Array(await crypto.subtle.digest("SHA-256", new Uint8Array(bytes)))
|
|
681
|
+
);
|
|
682
|
+
const accounts2 = {
|
|
683
|
+
multisig: prepared.multisigPda,
|
|
684
|
+
transactionBuffer: bufferPda,
|
|
685
|
+
creator: proposer
|
|
686
|
+
};
|
|
687
|
+
const setupTransactions = [
|
|
688
|
+
new Transaction2().add(
|
|
689
|
+
squads2.generated.createTransactionBufferCreateInstruction(
|
|
690
|
+
{
|
|
691
|
+
...accounts2,
|
|
692
|
+
rentPayer: proposer
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
args: {
|
|
696
|
+
bufferIndex,
|
|
697
|
+
vaultIndex: prepared.vaultIndex,
|
|
698
|
+
finalBufferHash,
|
|
699
|
+
finalBufferSize: bytes.length,
|
|
700
|
+
buffer: bytes.slice(0, BufferChunkSize)
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
)
|
|
704
|
+
)
|
|
705
|
+
];
|
|
706
|
+
for (let offset = BufferChunkSize; offset < bytes.length; offset += BufferChunkSize) {
|
|
707
|
+
setupTransactions.push(
|
|
708
|
+
new Transaction2().add(
|
|
709
|
+
squads2.generated.createTransactionBufferExtendInstruction(accounts2, {
|
|
710
|
+
args: { buffer: bytes.slice(offset, offset + BufferChunkSize) }
|
|
711
|
+
})
|
|
712
|
+
)
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
const finalize = squads2.generated.createVaultTransactionCreateFromBufferInstruction(
|
|
716
|
+
{
|
|
717
|
+
vaultTransactionCreateItemMultisig: prepared.multisigPda,
|
|
718
|
+
vaultTransactionCreateItemTransaction: prepared.vaultTransactionPda,
|
|
719
|
+
vaultTransactionCreateItemCreator: proposer,
|
|
720
|
+
vaultTransactionCreateItemRentPayer: proposer,
|
|
721
|
+
vaultTransactionCreateItemSystemProgram: SystemProgram.programId,
|
|
722
|
+
transactionBuffer: bufferPda,
|
|
723
|
+
creator: proposer
|
|
724
|
+
},
|
|
725
|
+
{ args: { ...decoded.args, transactionMessage: new Uint8Array(6) } }
|
|
726
|
+
);
|
|
727
|
+
const closeBufferInstruction = squads2.generated.createTransactionBufferCloseInstruction(accounts2);
|
|
728
|
+
closeBufferInstruction.keys.find(
|
|
729
|
+
(key) => key.pubkey.equals(proposer)
|
|
730
|
+
).isWritable = true;
|
|
731
|
+
return {
|
|
732
|
+
setupTransactions,
|
|
733
|
+
proposalTransaction: new Transaction2().add(
|
|
734
|
+
finalize,
|
|
735
|
+
...transaction.instructions.slice(1)
|
|
736
|
+
),
|
|
737
|
+
bufferPda,
|
|
738
|
+
closeBufferInstruction
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
export {
|
|
742
|
+
NEST_RWA_SHARE_MINT2 as NEST_RWA_SHARE_MINT,
|
|
743
|
+
NEST_VAULT_SLUG,
|
|
744
|
+
NestApiError,
|
|
745
|
+
SquadsProposalExecutionSimulationError,
|
|
746
|
+
buildNestWithdrawalRequest,
|
|
747
|
+
decodeNestWithdrawalRequest,
|
|
748
|
+
fetchNestRedemptionQuote,
|
|
749
|
+
fetchNestRedemptionStatus,
|
|
750
|
+
findSquadsWalletRoute,
|
|
751
|
+
prepareSquadsProposalUpload,
|
|
752
|
+
prepareVaultTransaction,
|
|
753
|
+
resolveSquadsWalletRoute,
|
|
754
|
+
simulateSquadsProposalExecution,
|
|
755
|
+
validateSquadsWalletRoutes,
|
|
756
|
+
vaultAuthorityForWallet
|
|
757
|
+
};
|