@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/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 +7 -5
- package/dist/deposit.js +95 -64
- 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 +119 -8
- package/dist/gen/blink/v1/api_pb.js +32 -12
- 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 +34 -26
- package/dist/sui.d.ts +10 -21
- package/dist/sui.js +55 -168
- package/dist/svm.d.ts +2 -4
- package/dist/svm.js +187 -203
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
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
|
-
|
|
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
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
254
|
-
rejectSwap(`system instruction tag ${tag} is not permitted`);
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
148
|
+
});
|
|
257
149
|
const funder = metaPubkey(ix, 0);
|
|
258
|
-
|
|
259
|
-
|
|
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 (
|
|
262
|
-
|
|
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 (
|
|
272
|
-
rejectSwap(
|
|
160
|
+
if (!isTokenProgram(tokenProgram)) {
|
|
161
|
+
rejectSwap(`setup creates a token account under ${tokenProgram.toBase58()}`);
|
|
273
162
|
}
|
|
274
|
-
|
|
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
|
|
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
|
-
|
|
309
|
-
|
|
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,
|
|
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(
|
|
352
|
-
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
|
+
});
|
|
353
238
|
}
|
|
354
239
|
}
|
|
355
240
|
}
|
|
356
241
|
return watched;
|
|
357
242
|
};
|
|
358
|
-
const simulateSwapDeposit = async (rpc, transaction, watched
|
|
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 =
|
|
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,
|
|
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
|
|
414
|
-
}).compileToV0Message(
|
|
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
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
const
|
|
435
|
-
|
|
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
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
...
|
|
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 {
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
+
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",
|