@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/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
- 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();
27
15
  const DEPOSIT_COMPUTE_UNIT_LIMIT = 50_000;
28
- const DEPOSIT_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS = 10_000;
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 checkSwapMetas = (authority, ix, writable) => {
178
- for (const meta of ix.accounts) {
179
- const pubkey = new PublicKey(meta.pubkey);
180
- if (meta.isSigner && !pubkey.equals(authority)) {
181
- rejectSwap(`instruction requires signature from ${pubkey.toBase58()}, only the depositor may sign`);
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
- const checkSystemInstruction = (authority, ix, data, budgets) => {
226
- if (data.length < 4) {
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
- const tag = new DataView(data.buffer, data.byteOffset, 4).getUint32(0, true);
230
- let lamports;
231
- let ownerOffset;
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
- default:
248
- rejectSwap(`system instruction tag ${tag} is not permitted`);
249
- return;
250
- }
148
+ });
251
149
  const funder = metaPubkey(ix, 0);
252
- if (!funder.equals(authority)) {
253
- 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`);
254
156
  }
255
- if (ownerOffset !== null) {
256
- const ownerBytes = data.slice(ownerOffset, ownerOffset + 32);
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
- else if (program.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
281
- if (data.length > 1 || (data.length === 1 && data[0] > 1)) {
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
- else {
286
- 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`);
287
165
  }
166
+ return { address, mint, tokenProgram };
288
167
  };
289
- const checkSwapInstructions = (authority, inputAccount, maxTokenIn, maxLamportsIn, swap, swapIx, allowedSwapPrograms) => {
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
- checkSwapMetas(authority, swapIx, writable);
303
- 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;
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, 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));
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(authority, account.owner, data)) {
346
- 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
+ });
347
238
  }
348
239
  }
349
240
  }
350
241
  return watched;
351
242
  };
352
- const assertSwapBalances = async (rpc, transaction, watched, inputAccount, maxInputSpend) => {
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 = pubkey.equals(inputAccount)
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, deposit) => {
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: deposit.instructions,
407
- }).compileToV0Message(deposit.lookupTables);
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 inputAccount = nativeSrc
424
- ? ownerWsolAta
425
- : associatedTokenAddress(owner, new PublicKey(request.srcToken), await tokenProgramOf(rpc, new PublicKey(request.srcToken)));
426
- const swapPrograms = resolveSwapPrograms(request.chain, request.swap, request.swapProgramSigner);
427
- const writable = checkSwapInstructions(owner, inputAccount, request.amount, (nativeSrc ? request.amount : 0n) + SWAP_LAMPORTS_ALLOWANCE, request.swap, swapIx, swapPrograms);
428
- 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)));
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: new PublicKey(a.pubkey),
448
- isSigner: a.isSigner,
449
- isWritable: a.isWritable,
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
- ...request.swap.cleanup.map(toInstruction),
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 { rpc, owner, watched, inputAccount, instructions, lookupTables: tables };
454
+ return {
455
+ rpc,
456
+ owner,
457
+ watched,
458
+ instructions,
459
+ lookupTables: tables,
460
+ };
467
461
  };
468
- export const buildSvmSwapDepositInstructions = async (request) => {
469
- const assembled = await assembleSvmSwapDeposit(request);
470
- const transaction = await compileSwapDeposit(assembled.rpc, assembled.owner, assembled);
471
- await assertSwapBalances(assembled.rpc, transaction, assembled.watched, assembled.inputAccount, request.amount);
472
- return { instructions: assembled.instructions, lookupTables: assembled.lookupTables };
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 transaction = await compileSwapDeposit(assembled.rpc, assembled.owner, assembled);
477
- await assertSwapBalances(assembled.rpc, transaction, assembled.watched, assembled.inputAccount, request.amount);
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
- ComputeBudgetProgram.setComputeUnitLimit({
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.8";
1
+ export declare const SDK_VERSION = "0.2.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.1.8";
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.8",
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",