@mayanfinance/blink-sdk 0.1.8
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/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/amount.d.ts +4 -0
- package/dist/amount.js +32 -0
- package/dist/client.d.ts +17 -0
- package/dist/client.js +32 -0
- package/dist/deposit.d.ts +64 -0
- package/dist/deposit.js +526 -0
- package/dist/evm.d.ts +237 -0
- package/dist/evm.js +679 -0
- package/dist/gen/blink/v1/admin_pb.d.ts +1338 -0
- package/dist/gen/blink/v1/admin_pb.js +234 -0
- package/dist/gen/blink/v1/api_pb.d.ts +1811 -0
- package/dist/gen/blink/v1/api_pb.js +294 -0
- package/dist/gen/blink/v1/partner_pb.d.ts +1020 -0
- package/dist/gen/blink/v1/partner_pb.js +247 -0
- package/dist/gen/blink/v1/ratelimit_pb.d.ts +10 -0
- package/dist/gen/blink/v1/ratelimit_pb.js +13 -0
- package/dist/gen/google/api/annotations_pb.d.ts +11 -0
- package/dist/gen/google/api/annotations_pb.js +14 -0
- package/dist/gen/google/api/http_pb.d.ts +111 -0
- package/dist/gen/google/api/http_pb.js +23 -0
- package/dist/hash.d.ts +7 -0
- package/dist/hash.js +76 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/order.d.ts +42 -0
- package/dist/order.js +173 -0
- package/dist/sui.d.ts +74 -0
- package/dist/sui.js +310 -0
- package/dist/svm.d.ts +36 -0
- package/dist/svm.js +504 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/dist/wallets.d.ts +84 -0
- package/dist/wallets.js +409 -0
- package/package.json +62 -0
package/dist/svm.js
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { ComputeBudgetProgram, Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction, } from "@solana/web3.js";
|
|
2
|
+
import { ed25519 } from "@noble/curves/ed25519";
|
|
3
|
+
import { Buffer } from "buffer";
|
|
4
|
+
import { sha256 } from "viem";
|
|
5
|
+
const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
|
6
|
+
const TOKEN_PROGRAM_ID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
7
|
+
const TOKEN_2022_PROGRAM_ID = new PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
|
|
8
|
+
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;
|
|
24
|
+
const TOKEN_ACCOUNT_LEN = 165;
|
|
25
|
+
const TOKEN_2022_ACCOUNT_TYPE_ACCOUNT = 2;
|
|
26
|
+
export const isNativeSvmToken = (address) => address === SystemProgram.programId.toBase58();
|
|
27
|
+
const DEPOSIT_COMPUTE_UNIT_LIMIT = 50_000;
|
|
28
|
+
const DEPOSIT_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS = 10_000;
|
|
29
|
+
const connection = (rpcUrl) => new Connection(rpcUrl, "confirmed");
|
|
30
|
+
const tokenProgramOf = async (rpc, mint) => {
|
|
31
|
+
const info = await rpc.getAccountInfo(mint);
|
|
32
|
+
if (!info) {
|
|
33
|
+
throw new Error(`mint ${mint.toBase58()} not found`);
|
|
34
|
+
}
|
|
35
|
+
return info.owner;
|
|
36
|
+
};
|
|
37
|
+
const associatedTokenAddress = (owner, mint, tokenProgram) => PublicKey.findProgramAddressSync([owner.toBytes(), tokenProgram.toBytes(), mint.toBytes()], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
|
|
38
|
+
const escrowAuthority = (programId) => PublicKey.findProgramAddressSync([new TextEncoder().encode("escrow")], programId)[0];
|
|
39
|
+
const discriminator = (name) => sha256(new TextEncoder().encode(`global:${name}`), "bytes").slice(0, 8);
|
|
40
|
+
const hashBytes = (quoteHash) => {
|
|
41
|
+
const hex = quoteHash.replace(/^0x/, "");
|
|
42
|
+
const out = new Uint8Array(32);
|
|
43
|
+
for (let i = 0; i < 32; i++) {
|
|
44
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
};
|
|
48
|
+
const depositData = (name, quoteHash, amount) => {
|
|
49
|
+
const out = new Uint8Array(48);
|
|
50
|
+
out.set(discriminator(name), 0);
|
|
51
|
+
out.set(hashBytes(quoteHash), 8);
|
|
52
|
+
new DataView(out.buffer).setBigUint64(40, amount, true);
|
|
53
|
+
return Buffer.from(out);
|
|
54
|
+
};
|
|
55
|
+
export const svmBalance = async (rpcUrl, token, owner) => {
|
|
56
|
+
const rpc = connection(rpcUrl);
|
|
57
|
+
if (isNativeSvmToken(token)) {
|
|
58
|
+
return BigInt(await rpc.getBalance(new PublicKey(owner)));
|
|
59
|
+
}
|
|
60
|
+
const mint = new PublicKey(token);
|
|
61
|
+
const ata = associatedTokenAddress(new PublicKey(owner), mint, await tokenProgramOf(rpc, mint));
|
|
62
|
+
const balance = await rpc.getTokenAccountBalance(ata).catch(() => null);
|
|
63
|
+
return balance ? BigInt(balance.value.amount) : 0n;
|
|
64
|
+
};
|
|
65
|
+
const depositNativeInstruction = (request) => {
|
|
66
|
+
const programId = new PublicKey(request.programId);
|
|
67
|
+
return new TransactionInstruction({
|
|
68
|
+
programId,
|
|
69
|
+
keys: [
|
|
70
|
+
{ pubkey: new PublicKey(request.owner), isSigner: true, isWritable: true },
|
|
71
|
+
{
|
|
72
|
+
pubkey: escrowAuthority(programId),
|
|
73
|
+
isSigner: false,
|
|
74
|
+
isWritable: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
pubkey: SystemProgram.programId,
|
|
78
|
+
isSigner: false,
|
|
79
|
+
isWritable: false,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
data: depositData("deposit_native", request.quoteHash, request.amount),
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const depositTokenInstruction = async (rpc, request) => {
|
|
86
|
+
const mint = new PublicKey(request.token);
|
|
87
|
+
const owner = new PublicKey(request.owner);
|
|
88
|
+
const programId = new PublicKey(request.programId);
|
|
89
|
+
const escrow = escrowAuthority(programId);
|
|
90
|
+
const tokenProgram = await tokenProgramOf(rpc, mint);
|
|
91
|
+
return new TransactionInstruction({
|
|
92
|
+
programId,
|
|
93
|
+
keys: [
|
|
94
|
+
{ pubkey: owner, isSigner: true, isWritable: true },
|
|
95
|
+
{ pubkey: mint, isSigner: false, isWritable: false },
|
|
96
|
+
{
|
|
97
|
+
pubkey: associatedTokenAddress(owner, mint, tokenProgram),
|
|
98
|
+
isSigner: false,
|
|
99
|
+
isWritable: true,
|
|
100
|
+
},
|
|
101
|
+
{ pubkey: escrow, isSigner: false, isWritable: false },
|
|
102
|
+
{
|
|
103
|
+
pubkey: associatedTokenAddress(escrow, mint, tokenProgram),
|
|
104
|
+
isSigner: false,
|
|
105
|
+
isWritable: true,
|
|
106
|
+
},
|
|
107
|
+
{ pubkey: tokenProgram, isSigner: false, isWritable: false },
|
|
108
|
+
],
|
|
109
|
+
data: depositData("deposit_token", request.quoteHash, request.amount),
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
const base64Bytes = (data) => {
|
|
113
|
+
const raw = atob(data);
|
|
114
|
+
const out = new Uint8Array(raw.length);
|
|
115
|
+
for (let i = 0; i < raw.length; i++) {
|
|
116
|
+
out[i] = raw.charCodeAt(i);
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
};
|
|
120
|
+
const toInstruction = (ix) => new TransactionInstruction({
|
|
121
|
+
programId: new PublicKey(ix.programId),
|
|
122
|
+
keys: ix.accounts.map((a) => ({
|
|
123
|
+
pubkey: new PublicKey(a.pubkey),
|
|
124
|
+
isSigner: a.isSigner,
|
|
125
|
+
isWritable: a.isWritable,
|
|
126
|
+
})),
|
|
127
|
+
data: Buffer.from(base64Bytes(ix.data)),
|
|
128
|
+
});
|
|
129
|
+
const rejectSwap = (reason) => {
|
|
130
|
+
throw new Error(`swap instructions rejected: ${reason}`);
|
|
131
|
+
};
|
|
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
|
+
const metaPubkey = (ix, index) => {
|
|
171
|
+
const meta = ix.accounts[index];
|
|
172
|
+
if (!meta) {
|
|
173
|
+
rejectSwap(`instruction is missing account ${index}`);
|
|
174
|
+
}
|
|
175
|
+
return new PublicKey(meta.pubkey);
|
|
176
|
+
};
|
|
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`);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const checkSystemInstruction = (authority, ix, data, budgets) => {
|
|
226
|
+
if (data.length < 4) {
|
|
227
|
+
rejectSwap("truncated system instruction");
|
|
228
|
+
}
|
|
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;
|
|
246
|
+
}
|
|
247
|
+
default:
|
|
248
|
+
rejectSwap(`system instruction tag ${tag} is not permitted`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const funder = metaPubkey(ix, 0);
|
|
252
|
+
if (!funder.equals(authority)) {
|
|
253
|
+
rejectSwap(`system lamport spend from ${funder.toBase58()} is not the depositor`);
|
|
254
|
+
}
|
|
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);
|
|
279
|
+
}
|
|
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
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
rejectSwap(`setup or cleanup instruction targets disallowed program ${program.toBase58()}`);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
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
|
+
}
|
|
298
|
+
const program = new PublicKey(swapIx.programId);
|
|
299
|
+
if (!allowedSwapPrograms.some((p) => p.equals(program))) {
|
|
300
|
+
rejectSwap(`swap instruction targets non-allowlisted program ${program.toBase58()}`);
|
|
301
|
+
}
|
|
302
|
+
checkSwapMetas(authority, swapIx, writable);
|
|
303
|
+
return writable;
|
|
304
|
+
};
|
|
305
|
+
const isDepositorTokenAccount = (owner, programId, data) => {
|
|
306
|
+
if (!programId.equals(TOKEN_PROGRAM_ID) && !programId.equals(TOKEN_2022_PROGRAM_ID)) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
if (data.length < TOKEN_ACCOUNT_LEN) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
if (data.length > TOKEN_ACCOUNT_LEN &&
|
|
313
|
+
data[TOKEN_ACCOUNT_LEN] !== TOKEN_2022_ACCOUNT_TYPE_ACCOUNT) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
return new PublicKey(data.slice(32, 64)).equals(owner);
|
|
317
|
+
};
|
|
318
|
+
const tokenAccountAmount = (data) => new DataView(data.buffer, data.byteOffset + 64, 8).getBigUint64(0, true);
|
|
319
|
+
const JUPITER_SLIPPAGE_EXCEEDED = 6001;
|
|
320
|
+
const describeSimulationError = (err, logs) => {
|
|
321
|
+
for (let i = logs.length - 1; i >= 0; i--) {
|
|
322
|
+
const anchor = logs[i].match(/Error Message: (.+?)\.?$/);
|
|
323
|
+
if (anchor) {
|
|
324
|
+
return `swap would fail on-chain: ${anchor[1]}`;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const custom = JSON.stringify(err).match(/"Custom":(\d+)/);
|
|
328
|
+
if (custom && Number(custom[1]) === JUPITER_SLIPPAGE_EXCEEDED) {
|
|
329
|
+
return "swap would fail on-chain: price moved beyond the slippage tolerance, try again";
|
|
330
|
+
}
|
|
331
|
+
return `swap would fail on-chain (${JSON.stringify(err)}), see the console for logs`;
|
|
332
|
+
};
|
|
333
|
+
const collectDepositorTokenAccounts = async (rpc, writable, authority) => {
|
|
334
|
+
const unique = [...new Map(writable.map((k) => [k.toBase58(), k])).values()];
|
|
335
|
+
const watched = [];
|
|
336
|
+
for (let i = 0; i < unique.length; i += 100) {
|
|
337
|
+
const chunk = unique.slice(i, i + 100);
|
|
338
|
+
const accounts = await rpc.getMultipleAccountsInfo(chunk);
|
|
339
|
+
for (let j = 0; j < chunk.length; j++) {
|
|
340
|
+
const account = accounts[j];
|
|
341
|
+
if (!account) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const data = new Uint8Array(account.data);
|
|
345
|
+
if (isDepositorTokenAccount(authority, account.owner, data)) {
|
|
346
|
+
watched.push({ pubkey: chunk[j], preAmount: tokenAccountAmount(data) });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return watched;
|
|
351
|
+
};
|
|
352
|
+
const assertSwapBalances = async (rpc, transaction, watched, inputAccount, maxInputSpend) => {
|
|
353
|
+
if (watched.length === 0) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const sim = await rpc.simulateTransaction(transaction, {
|
|
357
|
+
sigVerify: false,
|
|
358
|
+
replaceRecentBlockhash: true,
|
|
359
|
+
accounts: {
|
|
360
|
+
encoding: "base64",
|
|
361
|
+
addresses: watched.map((w) => w.pubkey.toBase58()),
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
if (sim.value.err) {
|
|
365
|
+
const logs = sim.value.logs ?? [];
|
|
366
|
+
console.error("swap deposit simulation failed", sim.value.err, logs);
|
|
367
|
+
rejectSwap(describeSimulationError(sim.value.err, logs));
|
|
368
|
+
}
|
|
369
|
+
const postAccounts = sim.value.accounts ?? [];
|
|
370
|
+
for (let i = 0; i < watched.length; i++) {
|
|
371
|
+
const { pubkey, preAmount } = watched[i];
|
|
372
|
+
const post = postAccounts[i];
|
|
373
|
+
let postAmount = 0n;
|
|
374
|
+
if (post && Array.isArray(post.data)) {
|
|
375
|
+
const data = base64Bytes(post.data[0]);
|
|
376
|
+
const programId = new PublicKey(post.owner);
|
|
377
|
+
if ((programId.equals(TOKEN_PROGRAM_ID) || programId.equals(TOKEN_2022_PROGRAM_ID)) &&
|
|
378
|
+
data.length >= TOKEN_ACCOUNT_LEN) {
|
|
379
|
+
postAmount = tokenAccountAmount(data);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const floor = pubkey.equals(inputAccount)
|
|
383
|
+
? preAmount - (preAmount < maxInputSpend ? preAmount : maxInputSpend)
|
|
384
|
+
: preAmount;
|
|
385
|
+
if (postAmount < floor) {
|
|
386
|
+
rejectSwap(`simulated balance of depositor token account ${pubkey.toBase58()} drops from ${preAmount} to ${postAmount}, beyond the swap input`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
const swapDepositData = (quoteHash, srcMint, srcAmount, minBridgeOut, swapData) => {
|
|
391
|
+
const out = new Uint8Array(92 + swapData.length);
|
|
392
|
+
out.set(discriminator("deposit_swap"), 0);
|
|
393
|
+
out.set(hashBytes(quoteHash), 8);
|
|
394
|
+
out.set(srcMint.toBytes(), 40);
|
|
395
|
+
const view = new DataView(out.buffer);
|
|
396
|
+
view.setBigUint64(72, srcAmount, true);
|
|
397
|
+
view.setBigUint64(80, minBridgeOut, true);
|
|
398
|
+
view.setUint32(88, swapData.length, true);
|
|
399
|
+
out.set(swapData, 92);
|
|
400
|
+
return Buffer.from(out);
|
|
401
|
+
};
|
|
402
|
+
const compileSwapDeposit = async (rpc, owner, deposit) => {
|
|
403
|
+
const message = new TransactionMessage({
|
|
404
|
+
payerKey: owner,
|
|
405
|
+
recentBlockhash: (await rpc.getLatestBlockhash()).blockhash,
|
|
406
|
+
instructions: deposit.instructions,
|
|
407
|
+
}).compileToV0Message(deposit.lookupTables);
|
|
408
|
+
return new VersionedTransaction(message);
|
|
409
|
+
};
|
|
410
|
+
const assembleSvmSwapDeposit = async (request) => {
|
|
411
|
+
const swapIx = request.swap.swap;
|
|
412
|
+
if (!swapIx) {
|
|
413
|
+
throw new Error("swap instructions are missing the swap step");
|
|
414
|
+
}
|
|
415
|
+
const rpc = connection(request.rpcUrl);
|
|
416
|
+
const owner = new PublicKey(request.owner);
|
|
417
|
+
const bridgeMint = new PublicKey(request.bridgeToken);
|
|
418
|
+
const programId = new PublicKey(request.programId);
|
|
419
|
+
const escrow = escrowAuthority(programId);
|
|
420
|
+
const tokenProgram = await tokenProgramOf(rpc, bridgeMint);
|
|
421
|
+
const ownerWsolAta = associatedTokenAddress(owner, WSOL_MINT, TOKEN_PROGRAM_ID);
|
|
422
|
+
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);
|
|
429
|
+
const depositIx = new TransactionInstruction({
|
|
430
|
+
programId,
|
|
431
|
+
keys: [
|
|
432
|
+
{ pubkey: owner, isSigner: true, isWritable: true },
|
|
433
|
+
{ pubkey: bridgeMint, isSigner: false, isWritable: false },
|
|
434
|
+
{ pubkey: escrow, isSigner: false, isWritable: false },
|
|
435
|
+
{
|
|
436
|
+
pubkey: associatedTokenAddress(escrow, bridgeMint, tokenProgram),
|
|
437
|
+
isSigner: false,
|
|
438
|
+
isWritable: true,
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
pubkey: new PublicKey(swapIx.programId),
|
|
442
|
+
isSigner: false,
|
|
443
|
+
isWritable: false,
|
|
444
|
+
},
|
|
445
|
+
{ 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
|
+
})),
|
|
451
|
+
],
|
|
452
|
+
data: swapDepositData(request.quoteHash, new PublicKey(request.srcToken), request.amount, request.minBridgeOut, base64Bytes(swapIx.data)),
|
|
453
|
+
});
|
|
454
|
+
const instructions = [
|
|
455
|
+
...request.swap.setup.map(toInstruction),
|
|
456
|
+
depositIx,
|
|
457
|
+
...request.swap.cleanup.map(toInstruction),
|
|
458
|
+
];
|
|
459
|
+
const tables = await Promise.all(request.swap.lookupTables.map(async (t) => {
|
|
460
|
+
const table = await rpc.getAddressLookupTable(new PublicKey(t.address));
|
|
461
|
+
if (!table.value) {
|
|
462
|
+
throw new Error(`lookup table ${t.address} not found`);
|
|
463
|
+
}
|
|
464
|
+
return table.value;
|
|
465
|
+
}));
|
|
466
|
+
return { rpc, owner, watched, inputAccount, instructions, lookupTables: tables };
|
|
467
|
+
};
|
|
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 };
|
|
473
|
+
};
|
|
474
|
+
export const buildSvmSwapDeposit = async (request) => {
|
|
475
|
+
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);
|
|
478
|
+
return transaction.serialize();
|
|
479
|
+
};
|
|
480
|
+
export const buildSvmDepositInstructions = async (request) => {
|
|
481
|
+
const rpc = connection(request.rpcUrl);
|
|
482
|
+
const instruction = isNativeSvmToken(request.token)
|
|
483
|
+
? depositNativeInstruction(request)
|
|
484
|
+
: await depositTokenInstruction(rpc, request);
|
|
485
|
+
return [
|
|
486
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
487
|
+
units: DEPOSIT_COMPUTE_UNIT_LIMIT,
|
|
488
|
+
}),
|
|
489
|
+
ComputeBudgetProgram.setComputeUnitPrice({
|
|
490
|
+
microLamports: DEPOSIT_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS,
|
|
491
|
+
}),
|
|
492
|
+
instruction,
|
|
493
|
+
];
|
|
494
|
+
};
|
|
495
|
+
export const buildSvmDeposit = async (request) => {
|
|
496
|
+
const rpc = connection(request.rpcUrl);
|
|
497
|
+
const transaction = new Transaction().add(...(await buildSvmDepositInstructions(request)));
|
|
498
|
+
transaction.feePayer = new PublicKey(request.feePayer ?? request.owner);
|
|
499
|
+
transaction.recentBlockhash = (await rpc.getLatestBlockhash()).blockhash;
|
|
500
|
+
return new Uint8Array(transaction.serialize({
|
|
501
|
+
requireAllSignatures: false,
|
|
502
|
+
verifySignatures: false,
|
|
503
|
+
}));
|
|
504
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SDK_VERSION = "0.1.8";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SDK_VERSION = "0.1.8";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Transaction } from "@mysten/sui/transactions";
|
|
2
|
+
export type WalletKind = "evm" | "svm" | "sui";
|
|
3
|
+
export interface Eip1193Provider {
|
|
4
|
+
request(args: {
|
|
5
|
+
method: string;
|
|
6
|
+
params?: unknown[];
|
|
7
|
+
}): Promise<unknown>;
|
|
8
|
+
on?(event: "accountsChanged", listener: (addresses: string[]) => void): void;
|
|
9
|
+
off?(event: "accountsChanged", listener: (addresses: string[]) => void): void;
|
|
10
|
+
removeListener?(event: "accountsChanged", listener: (addresses: string[]) => void): void;
|
|
11
|
+
}
|
|
12
|
+
export interface StandardWalletAccount {
|
|
13
|
+
address: string;
|
|
14
|
+
chains: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export interface StandardWallet {
|
|
17
|
+
name: string;
|
|
18
|
+
icon: string;
|
|
19
|
+
features: Record<string, unknown>;
|
|
20
|
+
accounts: readonly StandardWalletAccount[];
|
|
21
|
+
}
|
|
22
|
+
export type AvailableWallet = {
|
|
23
|
+
id: string;
|
|
24
|
+
kind: "evm";
|
|
25
|
+
name: string;
|
|
26
|
+
icon: string;
|
|
27
|
+
provider: Eip1193Provider;
|
|
28
|
+
} | {
|
|
29
|
+
id: string;
|
|
30
|
+
kind: "svm" | "sui";
|
|
31
|
+
name: string;
|
|
32
|
+
icon: string;
|
|
33
|
+
wallet: StandardWallet;
|
|
34
|
+
};
|
|
35
|
+
export type ConnectedWallet = {
|
|
36
|
+
id: string;
|
|
37
|
+
walletId: string;
|
|
38
|
+
kind: "evm";
|
|
39
|
+
name: string;
|
|
40
|
+
icon: string;
|
|
41
|
+
provider: Eip1193Provider;
|
|
42
|
+
address: string;
|
|
43
|
+
} | {
|
|
44
|
+
id: string;
|
|
45
|
+
walletId: string;
|
|
46
|
+
kind: "svm" | "sui";
|
|
47
|
+
name: string;
|
|
48
|
+
icon: string;
|
|
49
|
+
wallet: StandardWallet;
|
|
50
|
+
address: string;
|
|
51
|
+
};
|
|
52
|
+
export declare const visibleWallets: (wallets: ConnectedWallet[]) => ConnectedWallet[];
|
|
53
|
+
export declare const rememberRemoved: (wallet: ConnectedWallet, remaining: ConnectedWallet[]) => void;
|
|
54
|
+
export declare const clearRemoved: (wallets: ConnectedWallet[]) => void;
|
|
55
|
+
export declare const discoverWallets: () => Promise<AvailableWallet[]>;
|
|
56
|
+
export declare const walletCanSign: (wallet: ConnectedWallet) => boolean;
|
|
57
|
+
export declare const connectWallet: (wallet: AvailableWallet, pickAnother: boolean) => Promise<ConnectedWallet[]>;
|
|
58
|
+
export declare const rememberWallets: (wallets: ConnectedWallet[]) => void;
|
|
59
|
+
export declare const restoreWallets: () => Promise<ConnectedWallet[]>;
|
|
60
|
+
export declare const watchWallets: (connected: ConnectedWallet[], onAccounts: (accounts: ConnectedWallet[]) => void) => (() => void);
|
|
61
|
+
export declare const disconnectWallet: (wallet: ConnectedWallet) => Promise<void>;
|
|
62
|
+
export declare const switchEvmChain: (provider: Eip1193Provider, chainId: bigint, chainName: string, nativeSymbol: string, rpcUrl?: string) => Promise<void>;
|
|
63
|
+
export declare const sendEvmTransaction: (provider: Eip1193Provider, tx: {
|
|
64
|
+
from: string;
|
|
65
|
+
to: string;
|
|
66
|
+
data: string;
|
|
67
|
+
value?: string;
|
|
68
|
+
}) => Promise<string>;
|
|
69
|
+
export declare const signEvmTypedData: (provider: Eip1193Provider, from: string, typedData: object) => Promise<string>;
|
|
70
|
+
export declare const supportsAtomicBatch: (provider: Eip1193Provider, from: string, chainId: bigint) => Promise<boolean>;
|
|
71
|
+
export declare const sendEvmCalls: (provider: Eip1193Provider, from: string, chainId: bigint, calls: {
|
|
72
|
+
to: string;
|
|
73
|
+
data: string;
|
|
74
|
+
value?: string;
|
|
75
|
+
}[]) => Promise<string>;
|
|
76
|
+
export declare const base58: (bytes: Uint8Array) => string;
|
|
77
|
+
export declare const signSvmTransaction: (wallet: StandardWallet, address: string, transaction: Uint8Array) => Promise<Uint8Array>;
|
|
78
|
+
export declare const signAndSendSvmTransaction: (wallet: StandardWallet, address: string, transaction: Uint8Array) => Promise<string>;
|
|
79
|
+
export interface SignedSuiTransaction {
|
|
80
|
+
bytes: string;
|
|
81
|
+
signature: string;
|
|
82
|
+
}
|
|
83
|
+
export declare const signSuiTransaction: (wallet: StandardWallet, address: string, transaction: Transaction) => Promise<SignedSuiTransaction>;
|
|
84
|
+
export declare const signAndSendSuiTransaction: (wallet: StandardWallet, address: string, transaction: Transaction) => Promise<string>;
|