@cryptorubic/web3 0.8.3 → 0.8.4

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/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@cryptorubic/web3",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "dependencies": {
5
5
  "tslib": "^2.3.0",
6
6
  "bignumber.js": "9.1.2",
7
- "@cryptorubic/core": "0.8.3",
7
+ "@cryptorubic/core": "0.8.4",
8
8
  "viem": "^2.19.1",
9
9
  "web3-utils": "^4.3.1",
10
10
  "@ton/ton": "^15.1.0",
11
11
  "@solana/web3.js": "1.95.3",
12
12
  "@solflare-wallet/utl-sdk": "^1.4.0",
13
13
  "@ethersproject/bignumber": "^5.7.0",
14
- "@cryptorubic/tron-types": "0.8.3",
14
+ "@cryptorubic/tron-types": "0.8.4",
15
15
  "bitcoin-address-validation": "^2.2.3",
16
16
  "axios": "0.27.2",
17
17
  "crc-32": "^1.2.2",
@@ -1,4 +1,35 @@
1
+ import { BlockchainName } from '@cryptorubic/core';
1
2
  import { AccountInfo, PublicKey, RpcResponseAndContext } from '@solana/web3.js';
3
+ export interface SolanaToken {
4
+ name: string;
5
+ symbol: string;
6
+ logoURI: string | null;
7
+ verified?: boolean;
8
+ address: string;
9
+ decimals: number | null;
10
+ holders?: number | null;
11
+ }
12
+ export interface SolanaTokensFetchingResp {
13
+ tokensList: SolanaToken[];
14
+ notFetchedMints: PublicKey[];
15
+ hasNotFetchedTokens: boolean;
16
+ }
17
+ export interface SolanaApiV2TokensResp {
18
+ count: number;
19
+ next: null | number;
20
+ previous: null | number;
21
+ results: Array<{
22
+ address: string;
23
+ name: string;
24
+ symbol: string;
25
+ network: BlockchainName;
26
+ decimals: number;
27
+ image: string;
28
+ rank: number;
29
+ source_rank: number;
30
+ usdPrice: number;
31
+ }>;
32
+ }
2
33
  /**
3
34
  * RPC response value.
4
35
  */
@@ -0,0 +1 @@
1
+ export type SupportedTokenField = 'decimals' | 'symbol' | 'name' | 'image';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,12 +1,13 @@
1
1
  import { Abi, MulticallResponse, MulticallParameters } from 'viem';
2
2
  import { AbstractAdapter } from './abstract-adapter';
3
3
  import { Connection } from '@solana/web3.js';
4
- import { ICustomLogger, PriceTokenAmount, SolanaBlockchainName, Token, TokenAmount } from '@cryptorubic/core';
4
+ import { HttpClient, ICustomLogger, PriceTokenAmount, SolanaBlockchainName, Token, TokenAmount } from '@cryptorubic/core';
5
5
  import { EvmTransactionConfig } from '../../utils/models/evm-transaction-config';
6
6
  import BigNumber from 'bignumber.js';
7
7
  export declare const NATIVE_SOLANA_MINT_ADDRESS = "So11111111111111111111111111111111111111111";
8
8
  export declare class SolanaAdapter extends AbstractAdapter<Connection, Connection, SolanaBlockchainName> {
9
- constructor(rpcList: string[], logger?: ICustomLogger);
9
+ private readonly httpClient;
10
+ constructor(rpcList: string[], httpClient: HttpClient, logger?: ICustomLogger);
10
11
  private createPublicClient;
11
12
  read<T>(_address: string, _abi: Abi, _method: string, _methodArgs?: unknown[]): Promise<T>;
12
13
  write<T>(_address: string, _abi: Abi, _method: string, _methodArgs?: unknown[]): Promise<T>;
@@ -4,14 +4,15 @@ exports.SolanaAdapter = exports.NATIVE_SOLANA_MINT_ADDRESS = void 0;
4
4
  const spl_token_1 = require("@solana/spl-token");
5
5
  const abstract_adapter_1 = require("./abstract-adapter");
6
6
  const web3_js_1 = require("@solana/web3.js");
7
- const utl_sdk_1 = require("@solflare-wallet/utl-sdk");
8
7
  const core_1 = require("@cryptorubic/core");
9
8
  const web3_pure_1 = require("../../utils/web3-pure");
10
9
  const bignumber_js_1 = require("bignumber.js");
10
+ const solana_tokens_service_1 = require("./utils/solana-tokens-service");
11
11
  exports.NATIVE_SOLANA_MINT_ADDRESS = 'So11111111111111111111111111111111111111111';
12
12
  class SolanaAdapter extends abstract_adapter_1.AbstractAdapter {
13
- constructor(rpcList, logger) {
13
+ constructor(rpcList, httpClient, logger) {
14
14
  super(core_1.BLOCKCHAIN_NAME.SOLANA, logger);
15
+ this.httpClient = httpClient;
15
16
  this.public = this.createPublicClient(rpcList);
16
17
  }
17
18
  createPublicClient(rpcList) {
@@ -73,13 +74,12 @@ class SolanaAdapter extends abstract_adapter_1.AbstractAdapter {
73
74
  const nativeToken = core_1.nativeTokensList[core_1.BLOCKCHAIN_NAME.SOLANA];
74
75
  const nativeTokenIndex = tokenAddresses.findIndex((address) => web3_pure_1.Web3Pure.isNativeAddress(core_1.CHAIN_TYPE.SOLANA, address));
75
76
  const filteredTokenAddresses = tokenAddresses.filter((_, index) => index !== nativeTokenIndex);
76
- // only native token in tokenAddresses
77
- if (!filteredTokenAddresses.length) {
77
+ // only native token in array
78
+ if (!filteredTokenAddresses.length && nativeTokenIndex !== -1) {
78
79
  return [nativeToken];
79
80
  }
80
81
  const mints = filteredTokenAddresses.map((address) => new web3_js_1.PublicKey(address));
81
- const tokenSdk = new utl_sdk_1.Client();
82
- const tokensMint = await tokenSdk.fetchMints(mints);
82
+ const tokensMint = await new solana_tokens_service_1.SolanaTokensService(this.public, this.httpClient).fetchTokensData(mints);
83
83
  const tokens = tokensMint.map((token) => {
84
84
  return new core_1.Token({
85
85
  address: token.address.toString(),
@@ -0,0 +1,22 @@
1
+ import { Connection, PublicKey } from '@solana/web3.js';
2
+ import { HttpClient } from '@cryptorubic/core';
3
+ import { SolanaToken } from '../models/solana-web3-types';
4
+ export declare class SolanaTokensService {
5
+ private readonly httpClient;
6
+ private connection;
7
+ private tokensOrder;
8
+ private initialMints;
9
+ private readonly apiEndpoint;
10
+ private readonly newTokensEndpoint;
11
+ private readonly xApiKey;
12
+ constructor(connection: Connection, httpClient: HttpClient);
13
+ fetchTokensData(mints: PublicKey[]): Promise<SolanaToken[]>;
14
+ private fetchTokensFromBackend;
15
+ private fetchTokensFromOldBackend;
16
+ private fetchTokensFromMetaplex;
17
+ private fetchTokensFromSplApi;
18
+ private sortTokensByIdx;
19
+ private getNotFetchedTokensList;
20
+ private getTokensList;
21
+ private getTokensListOld;
22
+ }
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SolanaTokensService = void 0;
4
+ const spl_token_1 = require("@solana/spl-token");
5
+ const utl_sdk_1 = require("@solflare-wallet/utl-sdk");
6
+ const timeout_1 = require("./timeout");
7
+ class SolanaTokensService {
8
+ constructor(connection, httpClient) {
9
+ this.httpClient = httpClient;
10
+ // key - address of token, value - idx of token in initial tokensAddress
11
+ this.tokensOrder = {};
12
+ this.initialMints = [];
13
+ this.apiEndpoint = 'https://x-api.rubic.exchange/sol_token_list';
14
+ this.newTokensEndpoint = 'https://api.rubic.exchange/api/v2';
15
+ this.xApiKey = 'sndfje3u4b3fnNSDNFUSDNVSunw345842hrnfd3b4nt4';
16
+ this.connection = connection;
17
+ }
18
+ async fetchTokensData(mints) {
19
+ const tokensAddresses = mints.map((mint) => mint.toString());
20
+ this.initialMints = mints;
21
+ this.tokensOrder = tokensAddresses.reduce((acc, address, idx) => ({ ...acc, [address.toLowerCase()]: idx }), {});
22
+ const fromBackend = await this.fetchTokensFromBackend(mints);
23
+ if (!fromBackend.hasNotFetchedTokens)
24
+ return this.sortTokensByIdx(fromBackend.tokensList);
25
+ const fromOldBackend = await this.fetchTokensFromOldBackend(fromBackend.notFetchedMints, fromBackend.tokensList);
26
+ if (!fromOldBackend.hasNotFetchedTokens) {
27
+ return this.sortTokensByIdx([...fromBackend.tokensList, ...fromOldBackend.tokensList]);
28
+ }
29
+ const fromMetaplex = await this.fetchTokensFromMetaplex(fromOldBackend.notFetchedMints, [
30
+ ...fromBackend.tokensList,
31
+ ...fromOldBackend.tokensList
32
+ ]);
33
+ if (!fromMetaplex.hasNotFetchedTokens) {
34
+ return this.sortTokensByIdx([...fromBackend.tokensList, ...fromOldBackend.tokensList, ...fromMetaplex.tokensList]);
35
+ }
36
+ const fromSplApi = await this.fetchTokensFromSplApi(fromMetaplex.notFetchedMints, [
37
+ ...fromBackend.tokensList,
38
+ ...fromOldBackend.tokensList,
39
+ ...fromMetaplex.tokensList
40
+ ]);
41
+ return this.sortTokensByIdx([
42
+ ...fromBackend.tokensList,
43
+ ...fromOldBackend.tokensList,
44
+ ...fromMetaplex.tokensList,
45
+ ...fromSplApi.tokensList
46
+ ]);
47
+ }
48
+ async fetchTokensFromBackend(mints) {
49
+ const tokensAddresses = mints.map((mint) => mint.toString());
50
+ const tokensList = await this.getTokensList(tokensAddresses);
51
+ const notFetchedMints = this.getNotFetchedTokensList(tokensList);
52
+ return {
53
+ tokensList,
54
+ notFetchedMints,
55
+ hasNotFetchedTokens: notFetchedMints.length > 0
56
+ };
57
+ }
58
+ async fetchTokensFromOldBackend(mints, prevFetchedTokens) {
59
+ const tokensAddresses = mints.map((mint) => mint.toString());
60
+ const { content: tokensFromOlbBackend } = await (0, timeout_1.withTimeout)(this.getTokensListOld(tokensAddresses), 3000, 'Api Timeout!').catch(() => ({ content: [] }));
61
+ const notSortedTokensList = [...prevFetchedTokens, ...tokensFromOlbBackend];
62
+ const notFetchedMints = this.getNotFetchedTokensList(notSortedTokensList);
63
+ return {
64
+ tokensList: notSortedTokensList,
65
+ notFetchedMints,
66
+ hasNotFetchedTokens: notFetchedMints.length > 0
67
+ };
68
+ }
69
+ async fetchTokensFromMetaplex(mints, prevFetchedTokens) {
70
+ const tokenSDK = new utl_sdk_1.Client();
71
+ const metaplexTokens = await tokenSDK.getFromMetaplex(mints).catch(() => []);
72
+ const notSortedTokensList = [...prevFetchedTokens, ...metaplexTokens];
73
+ const notFetchedMints = this.getNotFetchedTokensList(notSortedTokensList);
74
+ return {
75
+ tokensList: metaplexTokens,
76
+ notFetchedMints,
77
+ hasNotFetchedTokens: notFetchedMints.length > 0
78
+ };
79
+ }
80
+ async fetchTokensFromSplApi(mints, prevFetchedTokens) {
81
+ const splApiResp = await Promise.all(mints.map((mint) => (0, spl_token_1.getMint)(this.connection, mint, 'confirmed'))).catch(() => []);
82
+ const splApiTokens = splApiResp.filter(Boolean).map((token) => ({
83
+ name: `Token ${token.address.toString().slice(0, 10)}`,
84
+ symbol: `Token ${token.address.toString().slice(0, 10)}`,
85
+ logoURI: null,
86
+ decimals: token.decimals,
87
+ address: token.address.toString(),
88
+ verified: token.isInitialized
89
+ }));
90
+ const notSortedTokensList = [...prevFetchedTokens, ...splApiTokens];
91
+ const notFetchedMints = this.getNotFetchedTokensList(notSortedTokensList);
92
+ return {
93
+ tokensList: notSortedTokensList,
94
+ notFetchedMints,
95
+ hasNotFetchedTokens: notFetchedMints.length > 0
96
+ };
97
+ }
98
+ sortTokensByIdx(notSortedList) {
99
+ const sortedList = [];
100
+ for (const token of notSortedList) {
101
+ const originalIdx = this.tokensOrder[token.address.toLowerCase()];
102
+ sortedList[originalIdx] = token;
103
+ }
104
+ return sortedList;
105
+ }
106
+ getNotFetchedTokensList(tokensList) {
107
+ const fetchedTokensMap = new Map();
108
+ tokensList.forEach((t) => fetchedTokensMap.set(t.address, t));
109
+ const notFetchedTokensList = this.initialMints.filter((mint) => !fetchedTokensMap.get(mint.toString()));
110
+ return notFetchedTokensList;
111
+ }
112
+ async getTokensList(tokenAddresses) {
113
+ try {
114
+ const queryParams = tokenAddresses.length > 1 ? { addresses: tokenAddresses.join(',') } : { query: tokenAddresses[0] };
115
+ const resp = await this.httpClient.get(`${this.newTokensEndpoint}/tokens`, {
116
+ params: queryParams
117
+ });
118
+ return resp.results.map((t) => ({
119
+ address: t.address,
120
+ decimals: t.decimals,
121
+ logoURI: t.image,
122
+ name: t.name,
123
+ symbol: t.symbol
124
+ }));
125
+ }
126
+ catch {
127
+ return [];
128
+ }
129
+ }
130
+ getTokensListOld(tokenAddresses) {
131
+ return this.httpClient.post(`${this.apiEndpoint}/mints?chainId=101`, { addresses: tokenAddresses }, {
132
+ headers: {
133
+ apiKey: this.xApiKey
134
+ }
135
+ });
136
+ }
137
+ }
138
+ exports.SolanaTokensService = SolanaTokensService;
@@ -0,0 +1 @@
1
+ export declare function withTimeout<T>(promise: Promise<T>, ms: number, timeoutText?: string): Promise<T>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withTimeout = withTimeout;
4
+ function withTimeout(promise, ms, timeoutText) {
5
+ let timeoutId = null;
6
+ const rejectOnDeadline = new Promise((_, reject) => {
7
+ timeoutId = setTimeout(() => {
8
+ reject(timeoutText);
9
+ }, ms);
10
+ });
11
+ return Promise.race([rejectOnDeadline, promise]).finally(() => {
12
+ if (timeoutId)
13
+ clearTimeout(timeoutId);
14
+ });
15
+ }
@@ -40,12 +40,6 @@ class BlockchainAdapterFactoryService {
40
40
  const adapters = Object.entries(this.rpcList).map(([blockchain, rpcs]) => {
41
41
  const adapter = this.createAdapter(blockchain, rpcs || []);
42
42
  return [blockchain, adapter || null];
43
- if (adapter) {
44
- this.logger?.customLog(`Creating adapter for ${blockchain} blockchain`);
45
- return [blockchain, adapter];
46
- }
47
- this.logger?.customWarn(`Failed to create adapter for ${blockchain} blockchain`);
48
- return [blockchain, null];
49
43
  });
50
44
  const tonAdapter = this.createAdapter(core_1.BLOCKCHAIN_NAME.TON, []);
51
45
  const btcAdapter = this.createAdapter(core_1.BLOCKCHAIN_NAME.BITCOIN, []);
@@ -80,10 +74,10 @@ class BlockchainAdapterFactoryService {
80
74
  return new sui_adapter_1.SuiAdapter(rpcs, this.createLogger?.(`SUI_ADAPTER`));
81
75
  }
82
76
  if (blockchainType === core_1.CHAIN_TYPE.SOLANA) {
83
- return new solana_adapter_1.SolanaAdapter(rpcs, this.createLogger?.(`SOLANA_ADAPTER`));
77
+ return new solana_adapter_1.SolanaAdapter(rpcs, this.httpClient, this.createLogger?.(`SOLANA_ADAPTER`));
84
78
  }
85
79
  }
86
- if (blockchain === core_1.BLOCKCHAIN_NAME.TON && this.httpClient && this.tonParams?.tonApiConfig && this.tonParams?.tonClientConfig) {
80
+ if (blockchain === core_1.BLOCKCHAIN_NAME.TON && this.tonParams?.tonApiConfig && this.tonParams?.tonClientConfig) {
87
81
  return new ton_adapter_1.TonAdapter(this.httpClient, this.tonParams, this.createLogger?.(`TON_ADAPTER`));
88
82
  }
89
83
  if (blockchain === core_1.BLOCKCHAIN_NAME.BITCOIN && this.httpClient) {