@mayanfinance/blink-sdk 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +4 -1
- package/dist/contracts.d.ts +40 -0
- package/dist/contracts.js +160 -0
- package/dist/deposit.d.ts +9 -5
- package/dist/deposit.js +106 -60
- package/dist/gen/blink/signer/v1/signer_pb.d.ts +763 -0
- package/dist/gen/blink/signer/v1/signer_pb.js +182 -0
- package/dist/gen/blink/v1/admin_pb.d.ts +590 -20
- package/dist/gen/blink/v1/admin_pb.js +132 -37
- package/dist/gen/blink/v1/api_pb.d.ts +155 -8
- package/dist/gen/blink/v1/api_pb.js +40 -10
- package/dist/gen/blink/v1/partner_pb.d.ts +118 -0
- package/dist/gen/blink/v1/partner_pb.js +46 -21
- package/dist/gen/hular/v1/admin_pb.d.ts +1220 -0
- package/dist/gen/hular/v1/admin_pb.js +219 -0
- package/dist/gen/hular/v1/api_pb.d.ts +1755 -0
- package/dist/gen/hular/v1/api_pb.js +284 -0
- package/dist/gen/hular/v1/partner_pb.d.ts +1020 -0
- package/dist/gen/hular/v1/partner_pb.js +247 -0
- package/dist/gen/hular/v1/ratelimit_pb.d.ts +10 -0
- package/dist/gen/hular/v1/ratelimit_pb.js +13 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/order.d.ts +9 -5
- package/dist/order.js +36 -26
- package/dist/sui.d.ts +10 -21
- package/dist/sui.js +55 -168
- package/dist/svm.d.ts +4 -4
- package/dist/svm.js +209 -211
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/dist/svm.js
CHANGED
|
@@ -1,31 +1,25 @@
|
|
|
1
|
+
import { addExtraAccountMetasForExecute, createAssociatedTokenAccountIdempotentInstruction, createHarvestWithheldTokensToMintInstruction, createSyncNativeInstruction, createTransferCheckedInstruction, createTransferCheckedWithFeeInstruction, getEpochFee, getTransferFeeConfig, getTransferHook, unpackMint, } from "@solana/spl-token";
|
|
1
2
|
import { ComputeBudgetProgram, Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction, } from "@solana/web3.js";
|
|
2
|
-
import { ed25519 } from "@noble/curves/ed25519";
|
|
3
3
|
import { Buffer } from "buffer";
|
|
4
4
|
import { sha256 } from "viem";
|
|
5
|
+
if (!("Buffer" in globalThis))
|
|
6
|
+
Object.assign(globalThis, { Buffer });
|
|
5
7
|
const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
|
6
8
|
const TOKEN_PROGRAM_ID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
7
9
|
const TOKEN_2022_PROGRAM_ID = new PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
|
|
8
10
|
const WSOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
|
|
9
|
-
|
|
10
|
-
new PublicKey("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"),
|
|
11
|
-
];
|
|
12
|
-
const SPL_TOKEN_ACCOUNT_RENT_LAMPORTS = 2039280n;
|
|
13
|
-
const SWAP_LAMPORTS_ALLOWANCE = 3n * SPL_TOKEN_ACCOUNT_RENT_LAMPORTS;
|
|
14
|
-
const TOKEN_INITIALIZE_ACCOUNT = 1;
|
|
15
|
-
const TOKEN_TRANSFER = 3;
|
|
16
|
-
const TOKEN_CLOSE_ACCOUNT = 9;
|
|
17
|
-
const TOKEN_TRANSFER_CHECKED = 12;
|
|
18
|
-
const TOKEN_INITIALIZE_ACCOUNT2 = 16;
|
|
19
|
-
const TOKEN_SYNC_NATIVE = 17;
|
|
20
|
-
const TOKEN_INITIALIZE_ACCOUNT3 = 18;
|
|
21
|
-
const SYSTEM_CREATE_ACCOUNT = 0;
|
|
22
|
-
const SYSTEM_TRANSFER = 2;
|
|
23
|
-
const SYSTEM_CREATE_ACCOUNT_WITH_SEED = 3;
|
|
11
|
+
const ONE_IN_BASIS_POINTS = 10000n;
|
|
24
12
|
const TOKEN_ACCOUNT_LEN = 165;
|
|
25
13
|
const TOKEN_2022_ACCOUNT_TYPE_ACCOUNT = 2;
|
|
26
14
|
export const isNativeSvmToken = (address) => address === SystemProgram.programId.toBase58();
|
|
27
15
|
const DEPOSIT_COMPUTE_UNIT_LIMIT = 50_000;
|
|
28
|
-
const
|
|
16
|
+
const MAX_COMPUTE_UNIT_LIMIT = 1_400_000;
|
|
17
|
+
const SWAP_COMPUTE_UNIT_HEADROOM = 1.3;
|
|
18
|
+
const computeBudgetInstructions = (units, microLamports) => [
|
|
19
|
+
ComputeBudgetProgram.setComputeUnitLimit({ units }),
|
|
20
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports }),
|
|
21
|
+
];
|
|
22
|
+
const swapComputeUnitLimit = (simulatedUnits) => Math.min(MAX_COMPUTE_UNIT_LIMIT, Math.ceil(simulatedUnits * SWAP_COMPUTE_UNIT_HEADROOM));
|
|
29
23
|
const connection = (rpcUrl) => new Connection(rpcUrl, "confirmed");
|
|
30
24
|
const tokenProgramOf = async (rpc, mint) => {
|
|
31
25
|
const info = await rpc.getAccountInfo(mint);
|
|
@@ -36,6 +30,7 @@ const tokenProgramOf = async (rpc, mint) => {
|
|
|
36
30
|
};
|
|
37
31
|
const associatedTokenAddress = (owner, mint, tokenProgram) => PublicKey.findProgramAddressSync([owner.toBytes(), tokenProgram.toBytes(), mint.toBytes()], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
|
|
38
32
|
const escrowAuthority = (programId) => PublicKey.findProgramAddressSync([new TextEncoder().encode("escrow")], programId)[0];
|
|
33
|
+
export const svmSwapAuthority = (programId, owner, quoteHash) => PublicKey.findProgramAddressSync([new TextEncoder().encode("swap_authority"), owner.toBytes(), hashBytes(quoteHash)], programId)[0];
|
|
39
34
|
const discriminator = (name) => sha256(new TextEncoder().encode(`global:${name}`), "bytes").slice(0, 8);
|
|
40
35
|
const hashBytes = (quoteHash) => {
|
|
41
36
|
const hex = quoteHash.replace(/^0x/, "");
|
|
@@ -129,44 +124,6 @@ const toInstruction = (ix) => new TransactionInstruction({
|
|
|
129
124
|
const rejectSwap = (reason) => {
|
|
130
125
|
throw new Error(`swap instructions rejected: ${reason}`);
|
|
131
126
|
};
|
|
132
|
-
const hexBytes = (hex) => {
|
|
133
|
-
const raw = hex.replace(/^0x/, "");
|
|
134
|
-
if (raw.length % 2 !== 0) {
|
|
135
|
-
rejectSwap("signature is not valid hex");
|
|
136
|
-
}
|
|
137
|
-
const out = new Uint8Array(raw.length / 2);
|
|
138
|
-
for (let i = 0; i < out.length; i++) {
|
|
139
|
-
const byte = parseInt(raw.slice(i * 2, i * 2 + 2), 16);
|
|
140
|
-
if (Number.isNaN(byte)) {
|
|
141
|
-
rejectSwap("signature is not valid hex");
|
|
142
|
-
}
|
|
143
|
-
out[i] = byte;
|
|
144
|
-
}
|
|
145
|
-
return out;
|
|
146
|
-
};
|
|
147
|
-
export const resolveSwapPrograms = (chain, swap, expectedSigner) => {
|
|
148
|
-
if (swap.allowedSwapPrograms.length === 0) {
|
|
149
|
-
return DEFAULT_SWAP_PROGRAMS;
|
|
150
|
-
}
|
|
151
|
-
if (!expectedSigner) {
|
|
152
|
-
return rejectSwap("swap program allowlist has no expected signer to verify against");
|
|
153
|
-
}
|
|
154
|
-
if (!swap.swapProgramsSignature) {
|
|
155
|
-
rejectSwap("swap program allowlist is unsigned");
|
|
156
|
-
}
|
|
157
|
-
const message = new TextEncoder().encode(`blink-swap-programs:${chain}:${swap.allowedSwapPrograms.join(",")}`);
|
|
158
|
-
const valid = ed25519.verify(hexBytes(swap.swapProgramsSignature), message, new PublicKey(expectedSigner).toBytes());
|
|
159
|
-
if (!valid) {
|
|
160
|
-
rejectSwap("swap program allowlist signature is invalid");
|
|
161
|
-
}
|
|
162
|
-
return swap.allowedSwapPrograms.map((p) => new PublicKey(p));
|
|
163
|
-
};
|
|
164
|
-
const dataU64 = (data, offset) => {
|
|
165
|
-
if (data.length < offset + 8) {
|
|
166
|
-
rejectSwap("truncated instruction data");
|
|
167
|
-
}
|
|
168
|
-
return new DataView(data.buffer, data.byteOffset + offset, 8).getBigUint64(0, true);
|
|
169
|
-
};
|
|
170
127
|
const metaPubkey = (ix, index) => {
|
|
171
128
|
const meta = ix.accounts[index];
|
|
172
129
|
if (!meta) {
|
|
@@ -174,133 +131,62 @@ const metaPubkey = (ix, index) => {
|
|
|
174
131
|
}
|
|
175
132
|
return new PublicKey(meta.pubkey);
|
|
176
133
|
};
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
if (meta.isWritable) {
|
|
184
|
-
writable.push(pubkey);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
const checkTokenInstruction = (authority, inputAccount, ix, data, budgets) => {
|
|
189
|
-
const tag = data[0];
|
|
190
|
-
switch (tag) {
|
|
191
|
-
case TOKEN_INITIALIZE_ACCOUNT:
|
|
192
|
-
case TOKEN_INITIALIZE_ACCOUNT2:
|
|
193
|
-
case TOKEN_INITIALIZE_ACCOUNT3:
|
|
194
|
-
case TOKEN_SYNC_NATIVE:
|
|
195
|
-
return;
|
|
196
|
-
case TOKEN_CLOSE_ACCOUNT: {
|
|
197
|
-
const destination = metaPubkey(ix, 1);
|
|
198
|
-
const owner = metaPubkey(ix, 2);
|
|
199
|
-
if (!destination.equals(authority) || !owner.equals(authority)) {
|
|
200
|
-
rejectSwap("close account must return funds to the depositor");
|
|
201
|
-
}
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
case TOKEN_TRANSFER:
|
|
205
|
-
case TOKEN_TRANSFER_CHECKED: {
|
|
206
|
-
const source = metaPubkey(ix, 0);
|
|
207
|
-
const owner = metaPubkey(ix, tag === TOKEN_TRANSFER ? 2 : 3);
|
|
208
|
-
if (!source.equals(inputAccount)) {
|
|
209
|
-
rejectSwap(`token transfer from ${source.toBase58()} is not the swap input account`);
|
|
210
|
-
}
|
|
211
|
-
if (!owner.equals(authority)) {
|
|
212
|
-
rejectSwap("token transfer authority is not the depositor");
|
|
213
|
-
}
|
|
214
|
-
const amount = dataU64(data, 1);
|
|
215
|
-
if (amount > budgets.tokenIn) {
|
|
216
|
-
rejectSwap("token transfers exceed the swap input amount");
|
|
217
|
-
}
|
|
218
|
-
budgets.tokenIn -= amount;
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
default:
|
|
222
|
-
rejectSwap(`token instruction tag ${tag} is not permitted`);
|
|
134
|
+
const isTokenProgram = (program) => program.equals(TOKEN_PROGRAM_ID) || program.equals(TOKEN_2022_PROGRAM_ID);
|
|
135
|
+
const authorityTokenAccountCreated = (depositor, authority, ix) => {
|
|
136
|
+
const program = new PublicKey(ix.programId);
|
|
137
|
+
if (!program.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
|
|
138
|
+
rejectSwap(`setup instruction targets disallowed program ${program.toBase58()}`);
|
|
223
139
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
rejectSwap("truncated system instruction");
|
|
140
|
+
const data = base64Bytes(ix.data);
|
|
141
|
+
if (data.length > 1 || (data.length === 1 && data[0] > 1)) {
|
|
142
|
+
rejectSwap("associated token instruction is not a create");
|
|
228
143
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
switch (tag) {
|
|
233
|
-
case SYSTEM_TRANSFER:
|
|
234
|
-
lamports = dataU64(data, 4);
|
|
235
|
-
ownerOffset = null;
|
|
236
|
-
break;
|
|
237
|
-
case SYSTEM_CREATE_ACCOUNT:
|
|
238
|
-
lamports = dataU64(data, 4);
|
|
239
|
-
ownerOffset = 20;
|
|
240
|
-
break;
|
|
241
|
-
case SYSTEM_CREATE_ACCOUNT_WITH_SEED: {
|
|
242
|
-
const seedLen = Number(dataU64(data, 36));
|
|
243
|
-
lamports = dataU64(data, 44 + seedLen);
|
|
244
|
-
ownerOffset = 60 + seedLen;
|
|
245
|
-
break;
|
|
144
|
+
ix.accounts.forEach((meta, index) => {
|
|
145
|
+
if (meta.isSigner && index !== 0) {
|
|
146
|
+
rejectSwap(`setup instruction requires signature from ${meta.pubkey}`);
|
|
246
147
|
}
|
|
247
|
-
|
|
248
|
-
rejectSwap(`system instruction tag ${tag} is not permitted`);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
148
|
+
});
|
|
251
149
|
const funder = metaPubkey(ix, 0);
|
|
252
|
-
|
|
253
|
-
|
|
150
|
+
const address = metaPubkey(ix, 1);
|
|
151
|
+
const wallet = metaPubkey(ix, 2);
|
|
152
|
+
const mint = metaPubkey(ix, 3);
|
|
153
|
+
const tokenProgram = metaPubkey(ix, 5);
|
|
154
|
+
if (!funder.equals(depositor)) {
|
|
155
|
+
rejectSwap(`token account rent is paid by ${funder.toBase58()}, not the depositor`);
|
|
254
156
|
}
|
|
255
|
-
if (
|
|
256
|
-
|
|
257
|
-
if (ownerBytes.length !== 32) {
|
|
258
|
-
rejectSwap("truncated system create instruction");
|
|
259
|
-
}
|
|
260
|
-
const owner = new PublicKey(ownerBytes);
|
|
261
|
-
if (!owner.equals(TOKEN_PROGRAM_ID) && !owner.equals(TOKEN_2022_PROGRAM_ID)) {
|
|
262
|
-
rejectSwap(`created account owner ${owner.toBase58()} is not a token program`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
if (lamports > budgets.lamportsIn) {
|
|
266
|
-
rejectSwap("lamport spends exceed the swap budget");
|
|
267
|
-
}
|
|
268
|
-
budgets.lamportsIn -= lamports;
|
|
269
|
-
};
|
|
270
|
-
const checkSupportInstruction = (authority, inputAccount, ix, budgets, writable) => {
|
|
271
|
-
checkSwapMetas(authority, ix, writable);
|
|
272
|
-
const program = new PublicKey(ix.programId);
|
|
273
|
-
const data = base64Bytes(ix.data);
|
|
274
|
-
if (program.equals(TOKEN_PROGRAM_ID) || program.equals(TOKEN_2022_PROGRAM_ID)) {
|
|
275
|
-
checkTokenInstruction(authority, inputAccount, ix, data, budgets);
|
|
276
|
-
}
|
|
277
|
-
else if (program.equals(SystemProgram.programId)) {
|
|
278
|
-
checkSystemInstruction(authority, ix, data, budgets);
|
|
157
|
+
if (!wallet.equals(authority)) {
|
|
158
|
+
rejectSwap(`setup creates a token account for ${wallet.toBase58()}, not the swap authority`);
|
|
279
159
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
rejectSwap("associated token instruction is not a create");
|
|
283
|
-
}
|
|
160
|
+
if (!isTokenProgram(tokenProgram)) {
|
|
161
|
+
rejectSwap(`setup creates a token account under ${tokenProgram.toBase58()}`);
|
|
284
162
|
}
|
|
285
|
-
|
|
286
|
-
rejectSwap(`setup
|
|
163
|
+
if (!address.equals(associatedTokenAddress(authority, mint, tokenProgram))) {
|
|
164
|
+
rejectSwap(`setup creates ${address.toBase58()}, not the swap authority's associated token account`);
|
|
287
165
|
}
|
|
166
|
+
return { address, mint, tokenProgram };
|
|
288
167
|
};
|
|
289
|
-
const
|
|
290
|
-
const writable = [];
|
|
291
|
-
const budgets = {
|
|
292
|
-
tokenIn: maxTokenIn,
|
|
293
|
-
lamportsIn: maxLamportsIn,
|
|
294
|
-
};
|
|
295
|
-
for (const ix of [...swap.setup, ...swap.cleanup]) {
|
|
296
|
-
checkSupportInstruction(authority, inputAccount, ix, budgets, writable);
|
|
297
|
-
}
|
|
168
|
+
const checkSwapStep = (depositor, authority, swapIx, allowedSwapPrograms) => {
|
|
298
169
|
const program = new PublicKey(swapIx.programId);
|
|
299
170
|
if (!allowedSwapPrograms.some((p) => p.equals(program))) {
|
|
300
171
|
rejectSwap(`swap instruction targets non-allowlisted program ${program.toBase58()}`);
|
|
301
172
|
}
|
|
302
|
-
|
|
303
|
-
|
|
173
|
+
for (const meta of swapIx.accounts) {
|
|
174
|
+
const pubkey = new PublicKey(meta.pubkey);
|
|
175
|
+
if (pubkey.equals(depositor)) {
|
|
176
|
+
rejectSwap("swap instruction is handed the depositor");
|
|
177
|
+
}
|
|
178
|
+
if (meta.isSigner && !pubkey.equals(authority)) {
|
|
179
|
+
rejectSwap(`swap instruction requires signature from ${pubkey.toBase58()}, only the swap authority may sign`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const checkSwapInstructions = (depositor, authority, swap, swapIx, allowedSwapPrograms) => {
|
|
184
|
+
if (swap.cleanup.length > 0) {
|
|
185
|
+
rejectSwap("aggregator cleanup instructions are not permitted");
|
|
186
|
+
}
|
|
187
|
+
const created = swap.setup.map((ix) => authorityTokenAccountCreated(depositor, authority, ix));
|
|
188
|
+
checkSwapStep(depositor, authority, swapIx, allowedSwapPrograms);
|
|
189
|
+
return created;
|
|
304
190
|
};
|
|
305
191
|
const isDepositorTokenAccount = (owner, programId, data) => {
|
|
306
192
|
if (!programId.equals(TOKEN_PROGRAM_ID) && !programId.equals(TOKEN_2022_PROGRAM_ID)) {
|
|
@@ -330,7 +216,8 @@ const describeSimulationError = (err, logs) => {
|
|
|
330
216
|
}
|
|
331
217
|
return `swap would fail on-chain (${JSON.stringify(err)}), see the console for logs`;
|
|
332
218
|
};
|
|
333
|
-
const collectDepositorTokenAccounts = async (rpc,
|
|
219
|
+
const collectDepositorTokenAccounts = async (rpc, instructions, depositor, spend) => {
|
|
220
|
+
const writable = instructions.flatMap((ix) => ix.keys.filter((k) => k.isWritable).map((k) => k.pubkey));
|
|
334
221
|
const unique = [...new Map(writable.map((k) => [k.toBase58(), k])).values()];
|
|
335
222
|
const watched = [];
|
|
336
223
|
for (let i = 0; i < unique.length; i += 100) {
|
|
@@ -342,17 +229,18 @@ const collectDepositorTokenAccounts = async (rpc, writable, authority) => {
|
|
|
342
229
|
continue;
|
|
343
230
|
}
|
|
344
231
|
const data = new Uint8Array(account.data);
|
|
345
|
-
if (isDepositorTokenAccount(
|
|
346
|
-
watched.push({
|
|
232
|
+
if (isDepositorTokenAccount(depositor, account.owner, data)) {
|
|
233
|
+
watched.push({
|
|
234
|
+
pubkey: chunk[j],
|
|
235
|
+
preAmount: tokenAccountAmount(data),
|
|
236
|
+
maxDrop: spend && chunk[j].equals(spend.account) ? spend.amount : 0n,
|
|
237
|
+
});
|
|
347
238
|
}
|
|
348
239
|
}
|
|
349
240
|
}
|
|
350
241
|
return watched;
|
|
351
242
|
};
|
|
352
|
-
const
|
|
353
|
-
if (watched.length === 0) {
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
243
|
+
const simulateSwapDeposit = async (rpc, transaction, watched) => {
|
|
356
244
|
const sim = await rpc.simulateTransaction(transaction, {
|
|
357
245
|
sigVerify: false,
|
|
358
246
|
replaceRecentBlockhash: true,
|
|
@@ -368,7 +256,7 @@ const assertSwapBalances = async (rpc, transaction, watched, inputAccount, maxIn
|
|
|
368
256
|
}
|
|
369
257
|
const postAccounts = sim.value.accounts ?? [];
|
|
370
258
|
for (let i = 0; i < watched.length; i++) {
|
|
371
|
-
const { pubkey, preAmount } = watched[i];
|
|
259
|
+
const { pubkey, preAmount, maxDrop } = watched[i];
|
|
372
260
|
const post = postAccounts[i];
|
|
373
261
|
let postAmount = 0n;
|
|
374
262
|
if (post && Array.isArray(post.data)) {
|
|
@@ -379,13 +267,91 @@ const assertSwapBalances = async (rpc, transaction, watched, inputAccount, maxIn
|
|
|
379
267
|
postAmount = tokenAccountAmount(data);
|
|
380
268
|
}
|
|
381
269
|
}
|
|
382
|
-
const floor =
|
|
383
|
-
? preAmount - (preAmount < maxInputSpend ? preAmount : maxInputSpend)
|
|
384
|
-
: preAmount;
|
|
270
|
+
const floor = preAmount - (preAmount < maxDrop ? preAmount : maxDrop);
|
|
385
271
|
if (postAmount < floor) {
|
|
386
272
|
rejectSwap(`simulated balance of depositor token account ${pubkey.toBase58()} drops from ${preAmount} to ${postAmount}, beyond the swap input`);
|
|
387
273
|
}
|
|
388
274
|
}
|
|
275
|
+
if (sim.value.unitsConsumed === undefined) {
|
|
276
|
+
throw new Error("swap deposit simulation did not report compute units");
|
|
277
|
+
}
|
|
278
|
+
return sim.value.unitsConsumed;
|
|
279
|
+
};
|
|
280
|
+
const loadMint = async (rpc, address) => {
|
|
281
|
+
const info = await rpc.getAccountInfo(address);
|
|
282
|
+
if (!info) {
|
|
283
|
+
throw new Error(`mint ${address.toBase58()} not found`);
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
address,
|
|
287
|
+
tokenProgram: info.owner,
|
|
288
|
+
state: unpackMint(address, info, info.owner),
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
const grossForNetTransfer = (fee, net) => {
|
|
292
|
+
const bps = BigInt(fee.transferFeeBasisPoints);
|
|
293
|
+
if (bps === 0n || net === 0n) {
|
|
294
|
+
return net;
|
|
295
|
+
}
|
|
296
|
+
if (bps === ONE_IN_BASIS_POINTS) {
|
|
297
|
+
return net + fee.maximumFee;
|
|
298
|
+
}
|
|
299
|
+
const denominator = ONE_IN_BASIS_POINTS - bps;
|
|
300
|
+
const raw = (net * ONE_IN_BASIS_POINTS + denominator - 1n) / denominator;
|
|
301
|
+
return raw - net >= fee.maximumFee ? net + fee.maximumFee : raw;
|
|
302
|
+
};
|
|
303
|
+
const fundAuthorityWithLamports = (depositor, authorityInput, amount) => ({
|
|
304
|
+
instructions: [
|
|
305
|
+
SystemProgram.transfer({
|
|
306
|
+
fromPubkey: depositor,
|
|
307
|
+
toPubkey: authorityInput,
|
|
308
|
+
lamports: amount,
|
|
309
|
+
}),
|
|
310
|
+
createSyncNativeInstruction(authorityInput),
|
|
311
|
+
],
|
|
312
|
+
});
|
|
313
|
+
const fundAuthorityWithTokens = async (rpc, depositor, authorityInput, source, amount) => {
|
|
314
|
+
const depositorInput = associatedTokenAddress(depositor, source.address, source.tokenProgram);
|
|
315
|
+
const feeConfig = getTransferFeeConfig(source.state);
|
|
316
|
+
const gross = feeConfig
|
|
317
|
+
? grossForNetTransfer(getEpochFee(feeConfig, BigInt((await rpc.getEpochInfo()).epoch)), amount)
|
|
318
|
+
: amount;
|
|
319
|
+
const transfer = feeConfig
|
|
320
|
+
? createTransferCheckedWithFeeInstruction(depositorInput, source.address, authorityInput, depositor, gross, source.state.decimals, gross - amount, [], source.tokenProgram)
|
|
321
|
+
: createTransferCheckedInstruction(depositorInput, source.address, authorityInput, depositor, amount, source.state.decimals, [], source.tokenProgram);
|
|
322
|
+
const hook = getTransferHook(source.state);
|
|
323
|
+
if (hook && !hook.programId.equals(PublicKey.default)) {
|
|
324
|
+
await addExtraAccountMetasForExecute(rpc, transfer, hook.programId, depositorInput, source.address, authorityInput, depositor, gross, "confirmed");
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
instructions: [transfer],
|
|
328
|
+
spend: { account: depositorInput, amount: gross },
|
|
329
|
+
};
|
|
330
|
+
};
|
|
331
|
+
const closeSwapAccountData = (quoteHash) => {
|
|
332
|
+
const out = new Uint8Array(40);
|
|
333
|
+
out.set(discriminator("close_swap_account"), 0);
|
|
334
|
+
out.set(hashBytes(quoteHash), 8);
|
|
335
|
+
return Buffer.from(out);
|
|
336
|
+
};
|
|
337
|
+
const closeAuthorityAccount = (programId, quoteHash, depositor, authority, account, mint) => {
|
|
338
|
+
const close = new TransactionInstruction({
|
|
339
|
+
programId,
|
|
340
|
+
keys: [
|
|
341
|
+
{ pubkey: depositor, isSigner: true, isWritable: true },
|
|
342
|
+
{ pubkey: authority, isSigner: false, isWritable: false },
|
|
343
|
+
{ pubkey: account.address, isSigner: false, isWritable: true },
|
|
344
|
+
{ pubkey: account.tokenProgram, isSigner: false, isWritable: false },
|
|
345
|
+
],
|
|
346
|
+
data: closeSwapAccountData(quoteHash),
|
|
347
|
+
});
|
|
348
|
+
if (!getTransferFeeConfig(mint.state)) {
|
|
349
|
+
return [close];
|
|
350
|
+
}
|
|
351
|
+
return [
|
|
352
|
+
createHarvestWithheldTokensToMintInstruction(mint.address, [account.address], account.tokenProgram),
|
|
353
|
+
close,
|
|
354
|
+
];
|
|
389
355
|
};
|
|
390
356
|
const swapDepositData = (quoteHash, srcMint, srcAmount, minBridgeOut, swapData) => {
|
|
391
357
|
const out = new Uint8Array(92 + swapData.length);
|
|
@@ -399,14 +365,17 @@ const swapDepositData = (quoteHash, srcMint, srcAmount, minBridgeOut, swapData)
|
|
|
399
365
|
out.set(swapData, 92);
|
|
400
366
|
return Buffer.from(out);
|
|
401
367
|
};
|
|
402
|
-
const compileSwapDeposit = async (rpc, owner,
|
|
368
|
+
const compileSwapDeposit = async (rpc, owner, instructions, lookupTables) => {
|
|
403
369
|
const message = new TransactionMessage({
|
|
404
370
|
payerKey: owner,
|
|
405
371
|
recentBlockhash: (await rpc.getLatestBlockhash()).blockhash,
|
|
406
|
-
instructions
|
|
407
|
-
}).compileToV0Message(
|
|
372
|
+
instructions,
|
|
373
|
+
}).compileToV0Message(lookupTables);
|
|
408
374
|
return new VersionedTransaction(message);
|
|
409
375
|
};
|
|
376
|
+
const uniqueAuthorityAccounts = (accounts) => [
|
|
377
|
+
...new Map(accounts.map((a) => [a.address.toBase58(), a])).values(),
|
|
378
|
+
];
|
|
410
379
|
const assembleSvmSwapDeposit = async (request) => {
|
|
411
380
|
const swapIx = request.swap.swap;
|
|
412
381
|
if (!swapIx) {
|
|
@@ -414,18 +383,30 @@ const assembleSvmSwapDeposit = async (request) => {
|
|
|
414
383
|
}
|
|
415
384
|
const rpc = connection(request.rpcUrl);
|
|
416
385
|
const owner = new PublicKey(request.owner);
|
|
417
|
-
const bridgeMint = new PublicKey(request.bridgeToken);
|
|
418
386
|
const programId = new PublicKey(request.programId);
|
|
387
|
+
const authority = svmSwapAuthority(programId, owner, request.quoteHash);
|
|
388
|
+
const bridgeMint = new PublicKey(request.bridgeToken);
|
|
419
389
|
const escrow = escrowAuthority(programId);
|
|
420
390
|
const tokenProgram = await tokenProgramOf(rpc, bridgeMint);
|
|
421
|
-
const ownerWsolAta = associatedTokenAddress(owner, WSOL_MINT, TOKEN_PROGRAM_ID);
|
|
422
391
|
const nativeSrc = isNativeSvmToken(request.srcToken);
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
|
|
392
|
+
const source = await loadMint(rpc, nativeSrc ? WSOL_MINT : new PublicKey(request.srcToken));
|
|
393
|
+
const authorityInput = associatedTokenAddress(authority, source.address, source.tokenProgram);
|
|
394
|
+
const swapPrograms = request.swapPrograms.map((p) => new PublicKey(p));
|
|
395
|
+
const created = checkSwapInstructions(owner, authority, request.swap, swapIx, swapPrograms);
|
|
396
|
+
const funding = nativeSrc
|
|
397
|
+
? fundAuthorityWithLamports(owner, authorityInput, request.amount)
|
|
398
|
+
: await fundAuthorityWithTokens(rpc, owner, authorityInput, source, request.amount);
|
|
399
|
+
const authorityAccounts = uniqueAuthorityAccounts([
|
|
400
|
+
{
|
|
401
|
+
address: authorityInput,
|
|
402
|
+
mint: source.address,
|
|
403
|
+
tokenProgram: source.tokenProgram,
|
|
404
|
+
},
|
|
405
|
+
...created,
|
|
406
|
+
]);
|
|
407
|
+
const authorityMints = await Promise.all(authorityAccounts.map((account) => account.mint.equals(source.address)
|
|
408
|
+
? source
|
|
409
|
+
: loadMint(rpc, account.mint)));
|
|
429
410
|
const depositIx = new TransactionInstruction({
|
|
430
411
|
programId,
|
|
431
412
|
keys: [
|
|
@@ -437,25 +418,32 @@ const assembleSvmSwapDeposit = async (request) => {
|
|
|
437
418
|
isSigner: false,
|
|
438
419
|
isWritable: true,
|
|
439
420
|
},
|
|
421
|
+
{ pubkey: authority, isSigner: false, isWritable: false },
|
|
440
422
|
{
|
|
441
423
|
pubkey: new PublicKey(swapIx.programId),
|
|
442
424
|
isSigner: false,
|
|
443
425
|
isWritable: false,
|
|
444
426
|
},
|
|
445
427
|
{ pubkey: tokenProgram, isSigner: false, isWritable: false },
|
|
446
|
-
...swapIx.accounts.map((a) =>
|
|
447
|
-
pubkey
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
428
|
+
...swapIx.accounts.map((a) => {
|
|
429
|
+
const pubkey = new PublicKey(a.pubkey);
|
|
430
|
+
return {
|
|
431
|
+
pubkey,
|
|
432
|
+
isSigner: a.isSigner && !pubkey.equals(authority),
|
|
433
|
+
isWritable: a.isWritable,
|
|
434
|
+
};
|
|
435
|
+
}),
|
|
451
436
|
],
|
|
452
437
|
data: swapDepositData(request.quoteHash, new PublicKey(request.srcToken), request.amount, request.minBridgeOut, base64Bytes(swapIx.data)),
|
|
453
438
|
});
|
|
454
439
|
const instructions = [
|
|
440
|
+
createAssociatedTokenAccountIdempotentInstruction(owner, authorityInput, authority, source.address, source.tokenProgram),
|
|
441
|
+
...funding.instructions,
|
|
455
442
|
...request.swap.setup.map(toInstruction),
|
|
456
443
|
depositIx,
|
|
457
|
-
...
|
|
444
|
+
...authorityAccounts.flatMap((account, i) => closeAuthorityAccount(programId, request.quoteHash, owner, authority, account, authorityMints[i])),
|
|
458
445
|
];
|
|
446
|
+
const watched = await collectDepositorTokenAccounts(rpc, instructions, owner, funding.spend);
|
|
459
447
|
const tables = await Promise.all(request.swap.lookupTables.map(async (t) => {
|
|
460
448
|
const table = await rpc.getAddressLookupTable(new PublicKey(t.address));
|
|
461
449
|
if (!table.value) {
|
|
@@ -463,18 +451,33 @@ const assembleSvmSwapDeposit = async (request) => {
|
|
|
463
451
|
}
|
|
464
452
|
return table.value;
|
|
465
453
|
}));
|
|
466
|
-
return {
|
|
454
|
+
return {
|
|
455
|
+
rpc,
|
|
456
|
+
owner,
|
|
457
|
+
watched,
|
|
458
|
+
instructions,
|
|
459
|
+
lookupTables: tables,
|
|
460
|
+
};
|
|
467
461
|
};
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
462
|
+
const budgetedSwapDeposit = async (request, assembled) => {
|
|
463
|
+
const probe = await compileSwapDeposit(assembled.rpc, assembled.owner, [
|
|
464
|
+
ComputeBudgetProgram.setComputeUnitLimit({ units: MAX_COMPUTE_UNIT_LIMIT }),
|
|
465
|
+
...assembled.instructions,
|
|
466
|
+
], assembled.lookupTables);
|
|
467
|
+
const simulatedUnits = await simulateSwapDeposit(assembled.rpc, probe, assembled.watched);
|
|
468
|
+
return {
|
|
469
|
+
instructions: [
|
|
470
|
+
...computeBudgetInstructions(swapComputeUnitLimit(simulatedUnits), request.computeUnitPriceMicroLamports),
|
|
471
|
+
...assembled.instructions,
|
|
472
|
+
],
|
|
473
|
+
lookupTables: assembled.lookupTables,
|
|
474
|
+
};
|
|
473
475
|
};
|
|
476
|
+
export const buildSvmSwapDepositInstructions = async (request) => budgetedSwapDeposit(request, await assembleSvmSwapDeposit(request));
|
|
474
477
|
export const buildSvmSwapDeposit = async (request) => {
|
|
475
478
|
const assembled = await assembleSvmSwapDeposit(request);
|
|
476
|
-
const
|
|
477
|
-
await
|
|
479
|
+
const deposit = await budgetedSwapDeposit(request, assembled);
|
|
480
|
+
const transaction = await compileSwapDeposit(assembled.rpc, assembled.owner, deposit.instructions, deposit.lookupTables);
|
|
478
481
|
return transaction.serialize();
|
|
479
482
|
};
|
|
480
483
|
export const buildSvmDepositInstructions = async (request) => {
|
|
@@ -483,12 +486,7 @@ export const buildSvmDepositInstructions = async (request) => {
|
|
|
483
486
|
? depositNativeInstruction(request)
|
|
484
487
|
: await depositTokenInstruction(rpc, request);
|
|
485
488
|
return [
|
|
486
|
-
|
|
487
|
-
units: DEPOSIT_COMPUTE_UNIT_LIMIT,
|
|
488
|
-
}),
|
|
489
|
-
ComputeBudgetProgram.setComputeUnitPrice({
|
|
490
|
-
microLamports: DEPOSIT_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS,
|
|
491
|
-
}),
|
|
489
|
+
...computeBudgetInstructions(DEPOSIT_COMPUTE_UNIT_LIMIT, request.computeUnitPriceMicroLamports),
|
|
492
490
|
instruction,
|
|
493
491
|
];
|
|
494
492
|
};
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.
|
|
1
|
+
export declare const SDK_VERSION = "0.2.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.
|
|
1
|
+
export const SDK_VERSION = "0.2.0";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mayanfinance/blink-sdk",
|
|
3
3
|
"description": "A SDK to swap with Blink",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"generate": "buf generate",
|
|
33
33
|
"build": "npm run generate && tsc -b",
|
|
34
|
-
"test": "npm run build && node scripts/check-hash-vectors.mjs",
|
|
34
|
+
"test": "npm run build && node scripts/check-hash-vectors.mjs && node scripts/check-contracts.mjs",
|
|
35
35
|
"prepack": "npm run build",
|
|
36
36
|
"version": "node scripts/sync-version.js && git add src/version.ts",
|
|
37
37
|
"postversion": "git commit -am \"$npm_package_version\" && git tag sdk-v$npm_package_version",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@connectrpc/connect": "^2.0.0",
|
|
48
48
|
"@connectrpc/connect-web": "^2.0.0",
|
|
49
49
|
"@mysten/sui": "^2.30.0",
|
|
50
|
-
"@
|
|
50
|
+
"@solana/spl-token": "^0.4.15",
|
|
51
51
|
"@solana/web3.js": "^1.98.0",
|
|
52
52
|
"@wallet-standard/app": "^1.1.1",
|
|
53
53
|
"buffer": "^6.0.3",
|