@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.
@@ -0,0 +1,409 @@
1
+ import { getWallets } from "@wallet-standard/app";
2
+ const STORAGE_KEY = "blink_wallets";
3
+ const REMOVED_KEY = "blink_removed_wallets";
4
+ const removedIds = () => JSON.parse(localStorage.getItem(REMOVED_KEY) ?? "[]");
5
+ export const visibleWallets = (wallets) => {
6
+ const removed = removedIds();
7
+ return wallets.filter((w) => !removed.includes(w.id));
8
+ };
9
+ export const rememberRemoved = (wallet, remaining) => {
10
+ const kept = remaining.some((w) => w.walletId === wallet.walletId);
11
+ const removed = removedIds().filter((id) => kept || !id.startsWith(`${wallet.walletId}:`));
12
+ localStorage.setItem(REMOVED_KEY, JSON.stringify(kept ? [...removed, wallet.id] : removed));
13
+ };
14
+ export const clearRemoved = (wallets) => {
15
+ const ids = new Set(wallets.map((w) => w.id));
16
+ localStorage.setItem(REMOVED_KEY, JSON.stringify(removedIds().filter((id) => !ids.has(id))));
17
+ };
18
+ const evmWallets = async () => {
19
+ const found = new Map();
20
+ const listener = (event) => {
21
+ const detail = event.detail;
22
+ found.set(detail.info.rdns, detail);
23
+ };
24
+ window.addEventListener("eip6963:announceProvider", listener);
25
+ window.dispatchEvent(new Event("eip6963:requestProvider"));
26
+ await new Promise((resolve) => setTimeout(resolve, 300));
27
+ window.removeEventListener("eip6963:announceProvider", listener);
28
+ return [...found.values()].map((d) => ({
29
+ id: `evm:${d.info.rdns}`,
30
+ kind: "evm",
31
+ name: d.info.name,
32
+ icon: d.info.icon,
33
+ provider: d.provider,
34
+ }));
35
+ };
36
+ const svmWallets = () => getWallets()
37
+ .get()
38
+ .filter((w) => "solana:signAndSendTransaction" in w.features &&
39
+ "standard:connect" in w.features)
40
+ .map((w) => ({
41
+ id: `solana:${w.name}`,
42
+ kind: "svm",
43
+ name: w.name,
44
+ icon: w.icon,
45
+ wallet: w,
46
+ }));
47
+ const suiWallets = () => getWallets()
48
+ .get()
49
+ .filter((w) => "sui:signAndExecuteTransaction" in w.features &&
50
+ "standard:connect" in w.features)
51
+ .map((w) => ({
52
+ id: `sui:${w.name}`,
53
+ kind: "sui",
54
+ name: w.name,
55
+ icon: w.icon,
56
+ wallet: w,
57
+ }));
58
+ export const discoverWallets = async () => [
59
+ ...(await evmWallets()),
60
+ ...svmWallets(),
61
+ ...suiWallets(),
62
+ ];
63
+ const CHAIN_PREFIXES = {
64
+ svm: "solana:",
65
+ sui: "sui:",
66
+ };
67
+ const standardAccounts = (kind, accounts) => accounts.filter((a) => a.chains.some((c) => c.startsWith(CHAIN_PREFIXES[kind])));
68
+ const pendingConnects = new Map();
69
+ const exclusiveConnect = async (id, run) => {
70
+ await pendingConnects.get(id)?.catch(() => undefined);
71
+ const promise = run();
72
+ pendingConnects.set(id, promise);
73
+ try {
74
+ return await promise;
75
+ }
76
+ finally {
77
+ if (pendingConnects.get(id) === promise) {
78
+ pendingConnects.delete(id);
79
+ }
80
+ }
81
+ };
82
+ const connectedFrom = (wallet, addresses, accounts) => {
83
+ if (wallet.kind === "evm") {
84
+ return addresses.map((address) => ({
85
+ ...wallet,
86
+ walletId: wallet.id,
87
+ id: `${wallet.id}:${address.toLowerCase()}`,
88
+ address,
89
+ }));
90
+ }
91
+ return accounts.map((account) => ({
92
+ ...wallet,
93
+ walletId: wallet.id,
94
+ id: `${wallet.id}:${account.address}`,
95
+ address: account.address,
96
+ }));
97
+ };
98
+ const availableFrom = (wallet) => wallet.kind === "evm"
99
+ ? {
100
+ id: wallet.walletId,
101
+ kind: "evm",
102
+ name: wallet.name,
103
+ icon: wallet.icon,
104
+ provider: wallet.provider,
105
+ }
106
+ : {
107
+ id: wallet.walletId,
108
+ kind: wallet.kind,
109
+ name: wallet.name,
110
+ icon: wallet.icon,
111
+ wallet: wallet.wallet,
112
+ };
113
+ export const walletCanSign = (wallet) => wallet.kind === "evm" ||
114
+ wallet.wallet.accounts.some((a) => a.address === wallet.address);
115
+ const UNSUPPORTED_METHOD = [4200, -32601];
116
+ const isUnsupportedMethod = (e) => UNSUPPORTED_METHOD.includes(e.code ?? 0);
117
+ export const connectWallet = async (wallet, pickAnother) => {
118
+ if (wallet.kind === "evm") {
119
+ const addresses = await exclusiveConnect(wallet.id, async () => {
120
+ if (pickAnother) {
121
+ try {
122
+ await wallet.provider.request({
123
+ method: "wallet_requestPermissions",
124
+ params: [{ eth_accounts: {} }],
125
+ });
126
+ }
127
+ catch (e) {
128
+ if (!isUnsupportedMethod(e)) {
129
+ throw e;
130
+ }
131
+ }
132
+ }
133
+ return (await wallet.provider.request({
134
+ method: "eth_requestAccounts",
135
+ }));
136
+ });
137
+ if (addresses.length === 0) {
138
+ throw new Error("no account connected");
139
+ }
140
+ return connectedFrom(wallet, addresses, []);
141
+ }
142
+ const feature = wallet.wallet.features["standard:connect"];
143
+ const { accounts } = await exclusiveConnect(wallet.id, () => feature.connect());
144
+ const usable = standardAccounts(wallet.kind, accounts);
145
+ if (usable.length === 0) {
146
+ throw new Error(`no ${wallet.kind} account connected`);
147
+ }
148
+ return connectedFrom(wallet, [], usable);
149
+ };
150
+ const restoreWallet = async (wallet) => {
151
+ if (wallet.kind === "evm") {
152
+ const addresses = (await wallet.provider.request({
153
+ method: "eth_accounts",
154
+ }));
155
+ return connectedFrom(wallet, addresses, []);
156
+ }
157
+ const feature = wallet.wallet.features["standard:connect"];
158
+ const accounts = wallet.wallet.accounts.length > 0
159
+ ? wallet.wallet.accounts
160
+ : pendingConnects.has(wallet.id)
161
+ ? []
162
+ : await exclusiveConnect(wallet.id, () => feature.connect({ silent: true }))
163
+ .then((r) => r.accounts)
164
+ .catch(() => []);
165
+ return connectedFrom(wallet, [], standardAccounts(wallet.kind, accounts));
166
+ };
167
+ export const rememberWallets = (wallets) => localStorage.setItem(STORAGE_KEY, JSON.stringify(wallets.map((w) => w.id)));
168
+ export const restoreWallets = async () => {
169
+ const stored = localStorage.getItem(STORAGE_KEY);
170
+ if (!stored) {
171
+ return [];
172
+ }
173
+ const ids = JSON.parse(stored);
174
+ if (ids.length === 0) {
175
+ return [];
176
+ }
177
+ const available = await discoverWallets();
178
+ const wallets = available.filter((w) => ids.some((id) => id.startsWith(`${w.id}:`)));
179
+ const restored = await Promise.all(wallets.map(restoreWallet));
180
+ return visibleWallets(restored.flatMap((accounts) => {
181
+ const remembered = accounts.filter((w) => ids.includes(w.id));
182
+ return remembered.length === 0 ? accounts : remembered;
183
+ }));
184
+ };
185
+ export const watchWallets = (connected, onAccounts) => {
186
+ const wallets = new Map();
187
+ for (const wallet of connected) {
188
+ if (!wallets.has(wallet.walletId)) {
189
+ wallets.set(wallet.walletId, availableFrom(wallet));
190
+ }
191
+ }
192
+ const listeners = [];
193
+ for (const wallet of wallets.values()) {
194
+ if (wallet.kind === "evm") {
195
+ const provider = wallet.provider;
196
+ const off = provider.off ?? provider.removeListener;
197
+ if (!provider.on || !off) {
198
+ continue;
199
+ }
200
+ const listener = (addresses) => {
201
+ if (addresses.length > 0) {
202
+ onAccounts(connectedFrom(wallet, addresses, []));
203
+ }
204
+ };
205
+ provider.on("accountsChanged", listener);
206
+ listeners.push(() => off.call(provider, "accountsChanged", listener));
207
+ continue;
208
+ }
209
+ const feature = wallet.wallet.features["standard:events"];
210
+ if (feature) {
211
+ listeners.push(feature.on("change", ({ accounts }) => {
212
+ if (accounts && accounts.length > 0) {
213
+ onAccounts(connectedFrom(wallet, [], standardAccounts(wallet.kind, accounts)));
214
+ }
215
+ }));
216
+ }
217
+ }
218
+ const rescan = () => {
219
+ for (const wallet of wallets.values()) {
220
+ restoreWallet(wallet).then((accounts) => {
221
+ if (accounts.length > 0) {
222
+ onAccounts(accounts);
223
+ }
224
+ });
225
+ }
226
+ };
227
+ window.addEventListener("focus", rescan);
228
+ listeners.push(() => window.removeEventListener("focus", rescan));
229
+ return () => listeners.forEach((off) => off());
230
+ };
231
+ export const disconnectWallet = async (wallet) => {
232
+ if (wallet.kind !== "evm") {
233
+ const feature = wallet.wallet.features["standard:disconnect"];
234
+ await feature?.disconnect();
235
+ }
236
+ };
237
+ const UNKNOWN_CHAIN = 4902;
238
+ export const switchEvmChain = async (provider, chainId, chainName, nativeSymbol, rpcUrl) => {
239
+ const wanted = `0x${chainId.toString(16)}`;
240
+ const current = (await provider.request({ method: "eth_chainId" }));
241
+ if (current.toLowerCase() === wanted.toLowerCase()) {
242
+ return;
243
+ }
244
+ const switchChain = () => provider.request({
245
+ method: "wallet_switchEthereumChain",
246
+ params: [{ chainId: wanted }],
247
+ });
248
+ try {
249
+ await switchChain();
250
+ }
251
+ catch (e) {
252
+ const unknown = e.code === UNKNOWN_CHAIN;
253
+ if (!unknown || !rpcUrl) {
254
+ throw e;
255
+ }
256
+ await provider.request({
257
+ method: "wallet_addEthereumChain",
258
+ params: [
259
+ {
260
+ chainId: wanted,
261
+ chainName,
262
+ rpcUrls: [rpcUrl],
263
+ nativeCurrency: {
264
+ name: nativeSymbol,
265
+ symbol: nativeSymbol,
266
+ decimals: 18,
267
+ },
268
+ },
269
+ ],
270
+ });
271
+ await switchChain();
272
+ }
273
+ };
274
+ export const sendEvmTransaction = async (provider, tx) => (await provider.request({
275
+ method: "eth_sendTransaction",
276
+ params: [tx],
277
+ }));
278
+ export const signEvmTypedData = async (provider, from, typedData) => (await provider.request({
279
+ method: "eth_signTypedData_v4",
280
+ params: [from, JSON.stringify(typedData)],
281
+ }));
282
+ export const supportsAtomicBatch = async (provider, from, chainId) => {
283
+ const wanted = `0x${chainId.toString(16)}`;
284
+ try {
285
+ const capabilities = (await provider.request({
286
+ method: "wallet_getCapabilities",
287
+ params: [from, [wanted]],
288
+ }));
289
+ const status = capabilities?.[wanted]?.atomic?.status;
290
+ return status === "supported" || status === "ready";
291
+ }
292
+ catch {
293
+ return false;
294
+ }
295
+ };
296
+ export const sendEvmCalls = async (provider, from, chainId, calls) => {
297
+ const { id } = (await provider.request({
298
+ method: "wallet_sendCalls",
299
+ params: [
300
+ {
301
+ version: "2.0.0",
302
+ chainId: `0x${chainId.toString(16)}`,
303
+ from,
304
+ atomicRequired: true,
305
+ calls,
306
+ },
307
+ ],
308
+ }));
309
+ for (;;) {
310
+ const result = (await provider.request({
311
+ method: "wallet_getCallsStatus",
312
+ params: [id],
313
+ }));
314
+ if (result.status === 200) {
315
+ const receipt = result.receipts?.at(-1);
316
+ if (!receipt) {
317
+ throw new Error("batch confirmed without a receipt");
318
+ }
319
+ return receipt.transactionHash;
320
+ }
321
+ if (result.status >= 300) {
322
+ throw new Error("batch transaction failed");
323
+ }
324
+ await new Promise((resolve) => setTimeout(resolve, 1000));
325
+ }
326
+ };
327
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
328
+ export const base58 = (bytes) => {
329
+ const digits = [];
330
+ for (const byte of bytes) {
331
+ let carry = byte;
332
+ for (let i = 0; i < digits.length; i++) {
333
+ carry += digits[i] << 8;
334
+ digits[i] = carry % 58;
335
+ carry = (carry / 58) | 0;
336
+ }
337
+ while (carry > 0) {
338
+ digits.push(carry % 58);
339
+ carry = (carry / 58) | 0;
340
+ }
341
+ }
342
+ let out = "";
343
+ for (const byte of bytes) {
344
+ if (byte !== 0)
345
+ break;
346
+ out += "1";
347
+ }
348
+ for (let i = digits.length - 1; i >= 0; i--) {
349
+ out += BASE58_ALPHABET[digits[i]];
350
+ }
351
+ return out;
352
+ };
353
+ export const signSvmTransaction = async (wallet, address, transaction) => {
354
+ const account = wallet.accounts.find((a) => a.address === address);
355
+ if (!account) {
356
+ throw new Error(`switch ${wallet.name} to ${address} to sign this deposit`);
357
+ }
358
+ const feature = wallet.features["solana:signTransaction"];
359
+ if (!feature) {
360
+ throw new Error(`${wallet.name} does not support signing transactions`);
361
+ }
362
+ const chain = account.chains.find((c) => c.startsWith("solana:")) ?? "solana:mainnet";
363
+ const [result] = await feature.signTransaction({
364
+ transaction,
365
+ account,
366
+ chain,
367
+ });
368
+ return result.signedTransaction;
369
+ };
370
+ export const signAndSendSvmTransaction = async (wallet, address, transaction) => {
371
+ const account = wallet.accounts.find((a) => a.address === address);
372
+ if (!account) {
373
+ throw new Error(`switch ${wallet.name} to ${address} to sign this deposit`);
374
+ }
375
+ const feature = wallet.features["solana:signAndSendTransaction"];
376
+ const chain = account.chains.find((c) => c.startsWith("solana:")) ?? "solana:mainnet";
377
+ const [result] = await feature.signAndSendTransaction({
378
+ transaction,
379
+ account,
380
+ chain,
381
+ });
382
+ return base58(result.signature);
383
+ };
384
+ export const signSuiTransaction = async (wallet, address, transaction) => {
385
+ const account = wallet.accounts.find((a) => a.address === address);
386
+ if (!account) {
387
+ throw new Error(`switch ${wallet.name} to ${address} to sign this deposit`);
388
+ }
389
+ const feature = wallet.features["sui:signTransaction"];
390
+ if (!feature) {
391
+ throw new Error(`${wallet.name} does not support signing transactions`);
392
+ }
393
+ const chain = account.chains.find((c) => c.startsWith("sui:")) ?? "sui:mainnet";
394
+ return feature.signTransaction({ transaction, account, chain });
395
+ };
396
+ export const signAndSendSuiTransaction = async (wallet, address, transaction) => {
397
+ const account = wallet.accounts.find((a) => a.address === address);
398
+ if (!account) {
399
+ throw new Error(`switch ${wallet.name} to ${address} to sign this deposit`);
400
+ }
401
+ const feature = wallet.features["sui:signAndExecuteTransaction"];
402
+ const chain = account.chains.find((c) => c.startsWith("sui:")) ?? "sui:mainnet";
403
+ const result = await feature.signAndExecuteTransaction({
404
+ transaction,
405
+ account,
406
+ chain,
407
+ });
408
+ return result.digest;
409
+ };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@mayanfinance/blink-sdk",
3
+ "description": "A SDK to swap with Blink",
4
+ "version": "0.1.8",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "homepage": "https://github.com/mayan-finance/blink/tree/main/sdk#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/mayan-finance/blink.git",
12
+ "directory": "sdk"
13
+ },
14
+ "keywords": [
15
+ "mayan",
16
+ "mayanfinance",
17
+ "blink",
18
+ "blink-sdk"
19
+ ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "LICENSE",
29
+ "README.md"
30
+ ],
31
+ "scripts": {
32
+ "generate": "buf generate",
33
+ "build": "npm run generate && tsc -b",
34
+ "test": "npm run build && node scripts/check-hash-vectors.mjs",
35
+ "prepack": "npm run build",
36
+ "version": "node scripts/sync-version.js && git add src/version.ts",
37
+ "postversion": "git commit -am \"$npm_package_version\" && git tag sdk-v$npm_package_version",
38
+ "release": "npm run prepack && npx np --no-tests"
39
+ },
40
+ "license": "MIT",
41
+ "publishConfig": {
42
+ "registry": "https://registry.npmjs.org",
43
+ "access": "public"
44
+ },
45
+ "dependencies": {
46
+ "@bufbuild/protobuf": "^2.2.0",
47
+ "@connectrpc/connect": "^2.0.0",
48
+ "@connectrpc/connect-web": "^2.0.0",
49
+ "@mysten/sui": "^2.30.0",
50
+ "@noble/curves": "^1.9.7",
51
+ "@solana/web3.js": "^1.98.0",
52
+ "@wallet-standard/app": "^1.1.1",
53
+ "buffer": "^6.0.3",
54
+ "viem": "^2.21.0"
55
+ },
56
+ "devDependencies": {
57
+ "@bufbuild/buf": "^1.47.2",
58
+ "@bufbuild/protoc-gen-es": "^2.2.0",
59
+ "@types/node": "^26.4.1",
60
+ "typescript": "~5.6.3"
61
+ }
62
+ }