@mayanfinance/blink-sdk 0.1.9 → 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/dist/svm.js CHANGED
@@ -1,26 +1,14 @@
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
- export const DEFAULT_SWAP_PROGRAMS = [
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();
@@ -42,6 +30,7 @@ const tokenProgramOf = async (rpc, mint) => {
42
30
  };
43
31
  const associatedTokenAddress = (owner, mint, tokenProgram) => PublicKey.findProgramAddressSync([owner.toBytes(), tokenProgram.toBytes(), mint.toBytes()], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
44
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];
45
34
  const discriminator = (name) => sha256(new TextEncoder().encode(`global:${name}`), "bytes").slice(0, 8);
46
35
  const hashBytes = (quoteHash) => {
47
36
  const hex = quoteHash.replace(/^0x/, "");
@@ -135,44 +124,6 @@ const toInstruction = (ix) => new TransactionInstruction({
135
124
  const rejectSwap = (reason) => {
136
125
  throw new Error(`swap instructions rejected: ${reason}`);
137
126
  };
138
- const hexBytes = (hex) => {
139
- const raw = hex.replace(/^0x/, "");
140
- if (raw.length % 2 !== 0) {
141
- rejectSwap("signature is not valid hex");
142
- }
143
- const out = new Uint8Array(raw.length / 2);
144
- for (let i = 0; i < out.length; i++) {
145
- const byte = parseInt(raw.slice(i * 2, i * 2 + 2), 16);
146
- if (Number.isNaN(byte)) {
147
- rejectSwap("signature is not valid hex");
148
- }
149
- out[i] = byte;
150
- }
151
- return out;
152
- };
153
- export const resolveSwapPrograms = (chain, swap, expectedSigner) => {
154
- if (swap.allowedSwapPrograms.length === 0) {
155
- return DEFAULT_SWAP_PROGRAMS;
156
- }
157
- if (!expectedSigner) {
158
- return rejectSwap("swap program allowlist has no expected signer to verify against");
159
- }
160
- if (!swap.swapProgramsSignature) {
161
- rejectSwap("swap program allowlist is unsigned");
162
- }
163
- const message = new TextEncoder().encode(`blink-swap-programs:${chain}:${swap.allowedSwapPrograms.join(",")}`);
164
- const valid = ed25519.verify(hexBytes(swap.swapProgramsSignature), message, new PublicKey(expectedSigner).toBytes());
165
- if (!valid) {
166
- rejectSwap("swap program allowlist signature is invalid");
167
- }
168
- return swap.allowedSwapPrograms.map((p) => new PublicKey(p));
169
- };
170
- const dataU64 = (data, offset) => {
171
- if (data.length < offset + 8) {
172
- rejectSwap("truncated instruction data");
173
- }
174
- return new DataView(data.buffer, data.byteOffset + offset, 8).getBigUint64(0, true);
175
- };
176
127
  const metaPubkey = (ix, index) => {
177
128
  const meta = ix.accounts[index];
178
129
  if (!meta) {
@@ -180,133 +131,62 @@ const metaPubkey = (ix, index) => {
180
131
  }
181
132
  return new PublicKey(meta.pubkey);
182
133
  };
183
- const checkSwapMetas = (authority, ix, writable) => {
184
- for (const meta of ix.accounts) {
185
- const pubkey = new PublicKey(meta.pubkey);
186
- if (meta.isSigner && !pubkey.equals(authority)) {
187
- rejectSwap(`instruction requires signature from ${pubkey.toBase58()}, only the depositor may sign`);
188
- }
189
- if (meta.isWritable) {
190
- writable.push(pubkey);
191
- }
192
- }
193
- };
194
- const checkTokenInstruction = (authority, inputAccount, ix, data, budgets) => {
195
- const tag = data[0];
196
- switch (tag) {
197
- case TOKEN_INITIALIZE_ACCOUNT:
198
- case TOKEN_INITIALIZE_ACCOUNT2:
199
- case TOKEN_INITIALIZE_ACCOUNT3:
200
- case TOKEN_SYNC_NATIVE:
201
- return;
202
- case TOKEN_CLOSE_ACCOUNT: {
203
- const destination = metaPubkey(ix, 1);
204
- const owner = metaPubkey(ix, 2);
205
- if (!destination.equals(authority) || !owner.equals(authority)) {
206
- rejectSwap("close account must return funds to the depositor");
207
- }
208
- return;
209
- }
210
- case TOKEN_TRANSFER:
211
- case TOKEN_TRANSFER_CHECKED: {
212
- const source = metaPubkey(ix, 0);
213
- const owner = metaPubkey(ix, tag === TOKEN_TRANSFER ? 2 : 3);
214
- if (!source.equals(inputAccount)) {
215
- rejectSwap(`token transfer from ${source.toBase58()} is not the swap input account`);
216
- }
217
- if (!owner.equals(authority)) {
218
- rejectSwap("token transfer authority is not the depositor");
219
- }
220
- const amount = dataU64(data, 1);
221
- if (amount > budgets.tokenIn) {
222
- rejectSwap("token transfers exceed the swap input amount");
223
- }
224
- budgets.tokenIn -= amount;
225
- return;
226
- }
227
- default:
228
- 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()}`);
229
139
  }
230
- };
231
- const checkSystemInstruction = (authority, ix, data, budgets) => {
232
- if (data.length < 4) {
233
- 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");
234
143
  }
235
- const tag = new DataView(data.buffer, data.byteOffset, 4).getUint32(0, true);
236
- let lamports;
237
- let ownerOffset;
238
- switch (tag) {
239
- case SYSTEM_TRANSFER:
240
- lamports = dataU64(data, 4);
241
- ownerOffset = null;
242
- break;
243
- case SYSTEM_CREATE_ACCOUNT:
244
- lamports = dataU64(data, 4);
245
- ownerOffset = 20;
246
- break;
247
- case SYSTEM_CREATE_ACCOUNT_WITH_SEED: {
248
- const seedLen = Number(dataU64(data, 36));
249
- lamports = dataU64(data, 44 + seedLen);
250
- ownerOffset = 60 + seedLen;
251
- break;
144
+ ix.accounts.forEach((meta, index) => {
145
+ if (meta.isSigner && index !== 0) {
146
+ rejectSwap(`setup instruction requires signature from ${meta.pubkey}`);
252
147
  }
253
- default:
254
- rejectSwap(`system instruction tag ${tag} is not permitted`);
255
- return;
256
- }
148
+ });
257
149
  const funder = metaPubkey(ix, 0);
258
- if (!funder.equals(authority)) {
259
- rejectSwap(`system lamport spend from ${funder.toBase58()} is not the depositor`);
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`);
260
156
  }
261
- if (ownerOffset !== null) {
262
- const ownerBytes = data.slice(ownerOffset, ownerOffset + 32);
263
- if (ownerBytes.length !== 32) {
264
- rejectSwap("truncated system create instruction");
265
- }
266
- const owner = new PublicKey(ownerBytes);
267
- if (!owner.equals(TOKEN_PROGRAM_ID) && !owner.equals(TOKEN_2022_PROGRAM_ID)) {
268
- rejectSwap(`created account owner ${owner.toBase58()} is not a token program`);
269
- }
157
+ if (!wallet.equals(authority)) {
158
+ rejectSwap(`setup creates a token account for ${wallet.toBase58()}, not the swap authority`);
270
159
  }
271
- if (lamports > budgets.lamportsIn) {
272
- rejectSwap("lamport spends exceed the swap budget");
160
+ if (!isTokenProgram(tokenProgram)) {
161
+ rejectSwap(`setup creates a token account under ${tokenProgram.toBase58()}`);
273
162
  }
274
- budgets.lamportsIn -= lamports;
275
- };
276
- const checkSupportInstruction = (authority, inputAccount, ix, budgets, writable) => {
277
- checkSwapMetas(authority, ix, writable);
278
- const program = new PublicKey(ix.programId);
279
- const data = base64Bytes(ix.data);
280
- if (program.equals(TOKEN_PROGRAM_ID) || program.equals(TOKEN_2022_PROGRAM_ID)) {
281
- checkTokenInstruction(authority, inputAccount, ix, data, budgets);
282
- }
283
- else if (program.equals(SystemProgram.programId)) {
284
- checkSystemInstruction(authority, ix, data, budgets);
285
- }
286
- else if (program.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
287
- if (data.length > 1 || (data.length === 1 && data[0] > 1)) {
288
- rejectSwap("associated token instruction is not a create");
289
- }
290
- }
291
- else {
292
- rejectSwap(`setup or cleanup instruction targets disallowed program ${program.toBase58()}`);
163
+ if (!address.equals(associatedTokenAddress(authority, mint, tokenProgram))) {
164
+ rejectSwap(`setup creates ${address.toBase58()}, not the swap authority's associated token account`);
293
165
  }
166
+ return { address, mint, tokenProgram };
294
167
  };
295
- const checkSwapInstructions = (authority, inputAccount, maxTokenIn, maxLamportsIn, swap, swapIx, allowedSwapPrograms) => {
296
- const writable = [];
297
- const budgets = {
298
- tokenIn: maxTokenIn,
299
- lamportsIn: maxLamportsIn,
300
- };
301
- for (const ix of [...swap.setup, ...swap.cleanup]) {
302
- checkSupportInstruction(authority, inputAccount, ix, budgets, writable);
303
- }
168
+ const checkSwapStep = (depositor, authority, swapIx, allowedSwapPrograms) => {
304
169
  const program = new PublicKey(swapIx.programId);
305
170
  if (!allowedSwapPrograms.some((p) => p.equals(program))) {
306
171
  rejectSwap(`swap instruction targets non-allowlisted program ${program.toBase58()}`);
307
172
  }
308
- checkSwapMetas(authority, swapIx, writable);
309
- return writable;
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;
310
190
  };
311
191
  const isDepositorTokenAccount = (owner, programId, data) => {
312
192
  if (!programId.equals(TOKEN_PROGRAM_ID) && !programId.equals(TOKEN_2022_PROGRAM_ID)) {
@@ -336,7 +216,8 @@ const describeSimulationError = (err, logs) => {
336
216
  }
337
217
  return `swap would fail on-chain (${JSON.stringify(err)}), see the console for logs`;
338
218
  };
339
- const collectDepositorTokenAccounts = async (rpc, writable, authority) => {
219
+ const collectDepositorTokenAccounts = async (rpc, instructions, depositor, spend) => {
220
+ const writable = instructions.flatMap((ix) => ix.keys.filter((k) => k.isWritable).map((k) => k.pubkey));
340
221
  const unique = [...new Map(writable.map((k) => [k.toBase58(), k])).values()];
341
222
  const watched = [];
342
223
  for (let i = 0; i < unique.length; i += 100) {
@@ -348,14 +229,18 @@ const collectDepositorTokenAccounts = async (rpc, writable, authority) => {
348
229
  continue;
349
230
  }
350
231
  const data = new Uint8Array(account.data);
351
- if (isDepositorTokenAccount(authority, account.owner, data)) {
352
- watched.push({ pubkey: chunk[j], preAmount: tokenAccountAmount(data) });
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
+ });
353
238
  }
354
239
  }
355
240
  }
356
241
  return watched;
357
242
  };
358
- const simulateSwapDeposit = async (rpc, transaction, watched, inputAccount, maxInputSpend) => {
243
+ const simulateSwapDeposit = async (rpc, transaction, watched) => {
359
244
  const sim = await rpc.simulateTransaction(transaction, {
360
245
  sigVerify: false,
361
246
  replaceRecentBlockhash: true,
@@ -371,7 +256,7 @@ const simulateSwapDeposit = async (rpc, transaction, watched, inputAccount, maxI
371
256
  }
372
257
  const postAccounts = sim.value.accounts ?? [];
373
258
  for (let i = 0; i < watched.length; i++) {
374
- const { pubkey, preAmount } = watched[i];
259
+ const { pubkey, preAmount, maxDrop } = watched[i];
375
260
  const post = postAccounts[i];
376
261
  let postAmount = 0n;
377
262
  if (post && Array.isArray(post.data)) {
@@ -382,9 +267,7 @@ const simulateSwapDeposit = async (rpc, transaction, watched, inputAccount, maxI
382
267
  postAmount = tokenAccountAmount(data);
383
268
  }
384
269
  }
385
- const floor = pubkey.equals(inputAccount)
386
- ? preAmount - (preAmount < maxInputSpend ? preAmount : maxInputSpend)
387
- : preAmount;
270
+ const floor = preAmount - (preAmount < maxDrop ? preAmount : maxDrop);
388
271
  if (postAmount < floor) {
389
272
  rejectSwap(`simulated balance of depositor token account ${pubkey.toBase58()} drops from ${preAmount} to ${postAmount}, beyond the swap input`);
390
273
  }
@@ -394,6 +277,82 @@ const simulateSwapDeposit = async (rpc, transaction, watched, inputAccount, maxI
394
277
  }
395
278
  return sim.value.unitsConsumed;
396
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
+ ];
355
+ };
397
356
  const swapDepositData = (quoteHash, srcMint, srcAmount, minBridgeOut, swapData) => {
398
357
  const out = new Uint8Array(92 + swapData.length);
399
358
  out.set(discriminator("deposit_swap"), 0);
@@ -406,14 +365,17 @@ const swapDepositData = (quoteHash, srcMint, srcAmount, minBridgeOut, swapData)
406
365
  out.set(swapData, 92);
407
366
  return Buffer.from(out);
408
367
  };
409
- const compileSwapDeposit = async (rpc, owner, deposit) => {
368
+ const compileSwapDeposit = async (rpc, owner, instructions, lookupTables) => {
410
369
  const message = new TransactionMessage({
411
370
  payerKey: owner,
412
371
  recentBlockhash: (await rpc.getLatestBlockhash()).blockhash,
413
- instructions: deposit.instructions,
414
- }).compileToV0Message(deposit.lookupTables);
372
+ instructions,
373
+ }).compileToV0Message(lookupTables);
415
374
  return new VersionedTransaction(message);
416
375
  };
376
+ const uniqueAuthorityAccounts = (accounts) => [
377
+ ...new Map(accounts.map((a) => [a.address.toBase58(), a])).values(),
378
+ ];
417
379
  const assembleSvmSwapDeposit = async (request) => {
418
380
  const swapIx = request.swap.swap;
419
381
  if (!swapIx) {
@@ -421,18 +383,30 @@ const assembleSvmSwapDeposit = async (request) => {
421
383
  }
422
384
  const rpc = connection(request.rpcUrl);
423
385
  const owner = new PublicKey(request.owner);
424
- const bridgeMint = new PublicKey(request.bridgeToken);
425
386
  const programId = new PublicKey(request.programId);
387
+ const authority = svmSwapAuthority(programId, owner, request.quoteHash);
388
+ const bridgeMint = new PublicKey(request.bridgeToken);
426
389
  const escrow = escrowAuthority(programId);
427
390
  const tokenProgram = await tokenProgramOf(rpc, bridgeMint);
428
- const ownerWsolAta = associatedTokenAddress(owner, WSOL_MINT, TOKEN_PROGRAM_ID);
429
391
  const nativeSrc = isNativeSvmToken(request.srcToken);
430
- const inputAccount = nativeSrc
431
- ? ownerWsolAta
432
- : associatedTokenAddress(owner, new PublicKey(request.srcToken), await tokenProgramOf(rpc, new PublicKey(request.srcToken)));
433
- const swapPrograms = resolveSwapPrograms(request.chain, request.swap, request.swapProgramSigner);
434
- const writable = checkSwapInstructions(owner, inputAccount, request.amount, (nativeSrc ? request.amount : 0n) + SWAP_LAMPORTS_ALLOWANCE, request.swap, swapIx, swapPrograms);
435
- const watched = await collectDepositorTokenAccounts(rpc, writable, owner);
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)));
436
410
  const depositIx = new TransactionInstruction({
437
411
  programId,
438
412
  keys: [
@@ -444,25 +418,32 @@ const assembleSvmSwapDeposit = async (request) => {
444
418
  isSigner: false,
445
419
  isWritable: true,
446
420
  },
421
+ { pubkey: authority, isSigner: false, isWritable: false },
447
422
  {
448
423
  pubkey: new PublicKey(swapIx.programId),
449
424
  isSigner: false,
450
425
  isWritable: false,
451
426
  },
452
427
  { pubkey: tokenProgram, isSigner: false, isWritable: false },
453
- ...swapIx.accounts.map((a) => ({
454
- pubkey: new PublicKey(a.pubkey),
455
- isSigner: a.isSigner,
456
- isWritable: a.isWritable,
457
- })),
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
+ }),
458
436
  ],
459
437
  data: swapDepositData(request.quoteHash, new PublicKey(request.srcToken), request.amount, request.minBridgeOut, base64Bytes(swapIx.data)),
460
438
  });
461
439
  const instructions = [
440
+ createAssociatedTokenAccountIdempotentInstruction(owner, authorityInput, authority, source.address, source.tokenProgram),
441
+ ...funding.instructions,
462
442
  ...request.swap.setup.map(toInstruction),
463
443
  depositIx,
464
- ...request.swap.cleanup.map(toInstruction),
444
+ ...authorityAccounts.flatMap((account, i) => closeAuthorityAccount(programId, request.quoteHash, owner, authority, account, authorityMints[i])),
465
445
  ];
446
+ const watched = await collectDepositorTokenAccounts(rpc, instructions, owner, funding.spend);
466
447
  const tables = await Promise.all(request.swap.lookupTables.map(async (t) => {
467
448
  const table = await rpc.getAddressLookupTable(new PublicKey(t.address));
468
449
  if (!table.value) {
@@ -470,17 +451,20 @@ const assembleSvmSwapDeposit = async (request) => {
470
451
  }
471
452
  return table.value;
472
453
  }));
473
- return { rpc, owner, watched, inputAccount, instructions, lookupTables: tables };
454
+ return {
455
+ rpc,
456
+ owner,
457
+ watched,
458
+ instructions,
459
+ lookupTables: tables,
460
+ };
474
461
  };
475
462
  const budgetedSwapDeposit = async (request, assembled) => {
476
- const probe = await compileSwapDeposit(assembled.rpc, assembled.owner, {
477
- instructions: [
478
- ComputeBudgetProgram.setComputeUnitLimit({ units: MAX_COMPUTE_UNIT_LIMIT }),
479
- ...assembled.instructions,
480
- ],
481
- lookupTables: assembled.lookupTables,
482
- });
483
- const simulatedUnits = await simulateSwapDeposit(assembled.rpc, probe, assembled.watched, assembled.inputAccount, request.amount);
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);
484
468
  return {
485
469
  instructions: [
486
470
  ...computeBudgetInstructions(swapComputeUnitLimit(simulatedUnits), request.computeUnitPriceMicroLamports),
@@ -493,7 +477,7 @@ export const buildSvmSwapDepositInstructions = async (request) => budgetedSwapDe
493
477
  export const buildSvmSwapDeposit = async (request) => {
494
478
  const assembled = await assembleSvmSwapDeposit(request);
495
479
  const deposit = await budgetedSwapDeposit(request, assembled);
496
- const transaction = await compileSwapDeposit(assembled.rpc, assembled.owner, deposit);
480
+ const transaction = await compileSwapDeposit(assembled.rpc, assembled.owner, deposit.instructions, deposit.lookupTables);
497
481
  return transaction.serialize();
498
482
  };
499
483
  export const buildSvmDepositInstructions = async (request) => {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.1.9";
1
+ export declare const SDK_VERSION = "0.2.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.1.9";
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.1.9",
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
- "@noble/curves": "^1.9.7",
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",