@exodus/solana-api 2.5.3 → 2.5.5

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/lib/connection.js DELETED
@@ -1,263 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.Connection = void 0;
7
-
8
- var _ms = _interopRequireDefault(require("ms"));
9
-
10
- var _delay = _interopRequireDefault(require("delay"));
11
-
12
- var _url = _interopRequireDefault(require("url"));
13
-
14
- var _lodash = _interopRequireDefault(require("lodash"));
15
-
16
- var _debug = _interopRequireDefault(require("debug"));
17
-
18
- var _fetch = require("@exodus/fetch");
19
-
20
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
-
22
- const SOLANA_DEFAULT_ENDPOINT = 'wss://solana.a.exodus.io/ws';
23
- const DEFAULT_RECONNECT_DELAY = (0, _ms.default)('15s');
24
- const PING_INTERVAL = (0, _ms.default)('60s');
25
- const TIMEOUT = (0, _ms.default)('50s');
26
- const debug = (0, _debug.default)('exodus:solana-api');
27
-
28
- class Connection {
29
- constructor({
30
- endpoint = SOLANA_DEFAULT_ENDPOINT,
31
- address,
32
- tokensAddresses = [],
33
- callback,
34
- reconnectCallback = () => {},
35
- reconnectDelay = DEFAULT_RECONNECT_DELAY
36
- }) {
37
- this.address = address;
38
- this.tokensAddresses = tokensAddresses;
39
- this.endpoint = endpoint;
40
- this.callback = callback;
41
- this.reconnectCallback = reconnectCallback;
42
- this.reconnectDelay = reconnectDelay;
43
- this.shutdown = false;
44
- this.ws = null;
45
- this.rpcQueue = {};
46
- this.messageQueue = [];
47
- this.inProcessMessages = false;
48
- this.pingTimeout = null;
49
- this.reconnectTimeout = null;
50
- this.txCache = {};
51
- }
52
-
53
- newSocket(reqUrl) {
54
- // eslint-disable-next-line
55
- const obj = _url.default.parse(reqUrl);
56
-
57
- obj.protocol = 'wss:';
58
- reqUrl = _url.default.format(obj);
59
- debug('Opening WS to:', reqUrl);
60
- const ws = new _fetch.WebSocket(`${reqUrl}`);
61
- ws.onmessage = this.onMessage.bind(this);
62
- ws.onopen = this.onOpen.bind(this);
63
- ws.onclose = this.onClose.bind(this);
64
- ws.onerror = this.onError.bind(this);
65
- return ws;
66
- }
67
-
68
- get isConnecting() {
69
- return !!(this.ws && this.ws.readyState === _fetch.WebSocket.CONNECTING);
70
- }
71
-
72
- get isOpen() {
73
- return !!(this.ws && this.ws.readyState === _fetch.WebSocket.OPEN);
74
- }
75
-
76
- get isClosing() {
77
- return !!(this.ws && this.ws.readyState === _fetch.WebSocket.CLOSING);
78
- }
79
-
80
- get isClosed() {
81
- return !!(!this.ws || this.ws.readyState === _fetch.WebSocket.CLOSED);
82
- }
83
-
84
- get running() {
85
- return !!(!this.isClosed || this.inProcessMessages || this.messageQueue.length);
86
- }
87
-
88
- get connectionState() {
89
- if (this.isConnecting) return 'CONNECTING';else if (this.isOpen) return 'OPEN';else if (this.isClosing) return 'CLOSING';else if (this.isClosed) return 'CLOSED';
90
- return 'NONE';
91
- }
92
-
93
- doPing() {
94
- if (this.ws) {
95
- this.ws.ping();
96
- this.pingTimeout = setTimeout(this.doPing.bind(this), PING_INTERVAL);
97
- }
98
- }
99
-
100
- doRestart() {
101
- // debug('Restarting WS:')
102
- this.reconnectTimeout = setTimeout(async () => {
103
- try {
104
- debug('reconnecting ws...');
105
- this.start();
106
- await this.reconnectCallback();
107
- } catch (e) {
108
- console.log(`Error in reconnect callback: ${e.message}`);
109
- }
110
- }, this.reconnectDelay);
111
- }
112
-
113
- onMessage(evt) {
114
- try {
115
- const json = JSON.parse(evt.data);
116
- debug('new ws msg:', json);
117
-
118
- if (!json.error) {
119
- if (_lodash.default.get(this.rpcQueue, json.id)) {
120
- // json-rpc reply
121
- clearTimeout(this.rpcQueue[json.id].timeout);
122
- this.rpcQueue[json.id].resolve(json.result);
123
- delete this.rpcQueue[json.id];
124
- } else if (json.method) {
125
- const msg = {
126
- method: json.method,
127
- ..._lodash.default.get(json, 'params.result', json.result)
128
- };
129
- debug('pushing msg to queue', msg);
130
- this.messageQueue.push(msg); // sub results
131
- }
132
-
133
- this.processMessages();
134
- } else {
135
- if (_lodash.default.get(this.rpcQueue, json.id)) {
136
- this.rpcQueue[json.id].reject(new Error(json.error.message));
137
- clearTimeout(this.rpcQueue[json.id].timeout);
138
- delete this.rpcQueue[json.id];
139
- } else debug('Unsupported WS message:', json.error.message);
140
- }
141
- } catch (e) {
142
- debug(e);
143
- debug('Cannot parse msg:', evt.data);
144
- }
145
- }
146
-
147
- onOpen(evt) {
148
- debug('Opened WS'); // subscribe to each addresses (SOL and ASA addr)
149
-
150
- this.tokensAddresses.concat(this.address).forEach(address => {
151
- // sub for account state changes
152
- this.ws.send(JSON.stringify({
153
- jsonrpc: '2.0',
154
- method: 'accountSubscribe',
155
- params: [address, {
156
- encoding: 'jsonParsed'
157
- }],
158
- id: 1
159
- })); // sub for incoming/outcoming txs
160
-
161
- this.ws.send(JSON.stringify({
162
- jsonrpc: '2.0',
163
- method: 'logsSubscribe',
164
- params: [{
165
- mentions: [address]
166
- }, {
167
- commitment: 'finalized'
168
- }],
169
- id: 2
170
- }));
171
- }); // this.doPing()
172
- }
173
-
174
- onError(evt) {
175
- debug('Error on WS:', evt.data);
176
- }
177
-
178
- onClose(evt) {
179
- debug('Closing WS');
180
- clearTimeout(this.pingTimeout);
181
- clearTimeout(this.reconnectTimeout);
182
-
183
- if (!this.shutdown) {
184
- this.doRestart();
185
- }
186
- }
187
-
188
- async sendMessage(method, params = []) {
189
- return new Promise((resolve, reject) => {
190
- if (this.isClosed || this.shutdown) return reject(new Error('connection not started'));
191
- const id = Math.floor(Math.random() * 1e7) + 1;
192
- this.rpcQueue[id] = {
193
- resolve,
194
- reject
195
- };
196
- this.rpcQueue[id].timeout = setTimeout(() => {
197
- delete this.rpcQueue[id];
198
- debug(`ws timeout command: ${method} - ${JSON.stringify(params)} - ${id}`);
199
- reject(new Error('solana ws: reply timeout'));
200
- }, TIMEOUT);
201
- if (typeof this.rpcQueue[id].timeout.unref === 'function') this.rpcQueue[id].timeout.unref();
202
- this.ws.send(JSON.stringify({
203
- jsonrpc: '2.0',
204
- method,
205
- params,
206
- id
207
- }));
208
- });
209
- }
210
-
211
- async processMessages() {
212
- if (this.inProcessMessages) return null;
213
- this.inProcessMessages = true;
214
-
215
- try {
216
- while (this.messageQueue.length) {
217
- const items = this.messageQueue.splice(0, this.messageQueue.length);
218
- await this.callback(items);
219
- }
220
- } catch (e) {
221
- console.log(`Solana: error processing streams: ${e.message}`);
222
- } finally {
223
- this.inProcessMessages = false;
224
- }
225
- }
226
-
227
- async close() {
228
- clearTimeout(this.reconnectTimeout);
229
- clearTimeout(this.pingTimeout);
230
-
231
- if (this.ws && (this.isConnecting || this.isOpen)) {
232
- // this.ws.send(JSON.stringify({ method: 'close' }))
233
- await (0, _delay.default)((0, _ms.default)('1s')); // allow for the 'close' round-trip
234
-
235
- await this.ws.close();
236
- await this.ws.terminate();
237
- }
238
- }
239
-
240
- async start() {
241
- try {
242
- if (!this.isClosed || this.shutdown) return;
243
- this.ws = this.newSocket(this.endpoint);
244
- } catch (e) {
245
- console.log('Solana: error starting WS:', e);
246
- this.doRestart();
247
- }
248
- }
249
-
250
- async stop() {
251
- if (this.shutdown) return;
252
- this.shutdown = true;
253
- await this.close();
254
-
255
- while (this.running) {
256
- await (0, _delay.default)((0, _ms.default)('1s'));
257
- }
258
- }
259
-
260
- } // Connection
261
-
262
-
263
- exports.Connection = Connection;
package/lib/index.js DELETED
@@ -1,68 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- var _exportNames = {};
7
- exports.default = void 0;
8
-
9
- var _basicUtils = require("@exodus/basic-utils");
10
-
11
- var _assets = require("@exodus/assets");
12
-
13
- var _solanaMeta = _interopRequireDefault(require("@exodus/solana-meta"));
14
-
15
- var _api = require("./api");
16
-
17
- Object.keys(_api).forEach(function (key) {
18
- if (key === "default" || key === "__esModule") return;
19
- if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
20
- if (key in exports && exports[key] === _api[key]) return;
21
- Object.defineProperty(exports, key, {
22
- enumerable: true,
23
- get: function () {
24
- return _api[key];
25
- }
26
- });
27
- });
28
-
29
- var _txLog = require("./tx-log");
30
-
31
- Object.keys(_txLog).forEach(function (key) {
32
- if (key === "default" || key === "__esModule") return;
33
- if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
34
- if (key in exports && exports[key] === _txLog[key]) return;
35
- Object.defineProperty(exports, key, {
36
- enumerable: true,
37
- get: function () {
38
- return _txLog[key];
39
- }
40
- });
41
- });
42
-
43
- var _accountState = require("./account-state");
44
-
45
- Object.keys(_accountState).forEach(function (key) {
46
- if (key === "default" || key === "__esModule") return;
47
- if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
48
- if (key in exports && exports[key] === _accountState[key]) return;
49
- Object.defineProperty(exports, key, {
50
- enumerable: true,
51
- get: function () {
52
- return _accountState[key];
53
- }
54
- });
55
- });
56
-
57
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
58
-
59
- // These are not the same asset objects as the wallet creates, so they should never be returned to the wallet.
60
- // Initially this may be violated by the Solana code until the first monitor tick updates assets with setTokens()
61
- const assets = (0, _assets.connectAssets)((0, _basicUtils.keyBy)(_solanaMeta.default, asset => asset.name)); // At some point we would like to exclude this export. Default export should be the whole asset "plugin" ready to be injected.
62
- // Clients should not call an specific server api directly.
63
-
64
- const serverApi = new _api.Api({
65
- assets
66
- });
67
- var _default = serverApi;
68
- exports.default = _default;
@@ -1,18 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
-
7
- var _solanaMonitor = require("./solana-monitor");
8
-
9
- Object.keys(_solanaMonitor).forEach(function (key) {
10
- if (key === "default" || key === "__esModule") return;
11
- if (key in exports && exports[key] === _solanaMonitor[key]) return;
12
- Object.defineProperty(exports, key, {
13
- enumerable: true,
14
- get: function () {
15
- return _solanaMonitor[key];
16
- }
17
- });
18
- });
@@ -1,357 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.SolanaMonitor = void 0;
7
-
8
- var _assetLib = require("@exodus/asset-lib");
9
-
10
- var _lodash = _interopRequireDefault(require("lodash"));
11
-
12
- var _minimalisticAssert = _interopRequireDefault(require("minimalistic-assert"));
13
-
14
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
-
16
- const DEFAULT_POOL_ADDRESS = '9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF'; // Everstake
17
-
18
- const DEFAULT_REMOTE_CONFIG = {
19
- rpcs: [],
20
- ws: [],
21
- staking: {
22
- enabled: true,
23
- pool: DEFAULT_POOL_ADDRESS
24
- }
25
- };
26
-
27
- class SolanaMonitor extends _assetLib.BaseMonitor {
28
- constructor({
29
- api,
30
- includeUnparsed = false,
31
- ...args
32
- }) {
33
- super(args);
34
- (0, _minimalisticAssert.default)(api, 'api is required');
35
- this.api = api;
36
- this.cursors = {};
37
- this.assets = {};
38
- this.includeUnparsed = includeUnparsed;
39
- this.addHook('before-stop', (...args) => this.beforeStop(...args));
40
- }
41
-
42
- async beforeStop() {
43
- const walletAccounts = await this.aci.getWalletAccounts({
44
- assetName: this.asset.name
45
- });
46
- return Promise.all(walletAccounts.map(walletAccount => this.stopListener({
47
- walletAccount
48
- })));
49
- }
50
-
51
- async initWalletAccount({
52
- walletAccount
53
- }) {
54
- if (this.tickCount[walletAccount] === 0) {
55
- await this.startListener({
56
- walletAccount
57
- });
58
- }
59
- }
60
-
61
- async startListener({
62
- walletAccount
63
- }) {
64
- const address = await this.aci.getReceiveAddress({
65
- assetName: this.asset.name,
66
- walletAccount
67
- });
68
- return this.api.watchAddress({
69
- address
70
- /*
71
- // OPTIONAL. Relying on polling through ws
72
- tokensAddresses: [], // needed for ASA subs
73
- handleAccounts: (updates) => this.accountsCallback({ updates, walletAccount }),
74
- handleTransfers: (txs) => {
75
- // new SOL tx, ticking monitor
76
- this.tick({ walletAccount }) // it will cause refresh for both sender/receiver. Without necessarily fetching the tx if it's not finalized in the node.
77
- },
78
- */
79
-
80
- });
81
- }
82
-
83
- async stopListener({
84
- walletAccount
85
- }) {
86
- const address = await this.aci.getReceiveAddress({
87
- assetName: this.asset.name,
88
- walletAccount
89
- });
90
- return this.api.unwatchAddress({
91
- address
92
- });
93
- }
94
-
95
- setServer(config = {}) {
96
- const {
97
- rpcs,
98
- ws,
99
- staking = {}
100
- } = { ...DEFAULT_REMOTE_CONFIG,
101
- ...config
102
- };
103
- this.api.setServer(rpcs[0]);
104
- this.api.setWsEndpoint(ws[0]);
105
- this.staking = staking;
106
- }
107
-
108
- hasNewCursor({
109
- walletAccount,
110
- cursorState
111
- }) {
112
- const {
113
- cursor
114
- } = cursorState;
115
- return this.cursors[walletAccount] !== cursor;
116
- }
117
-
118
- async emitUnknownTokensEvent({
119
- tokenAccounts
120
- }) {
121
- const tokensList = await this.api.getWalletTokensList({
122
- tokenAccounts
123
- });
124
- const unknownTokensList = tokensList.filter(mintAddress => {
125
- return !this.api.tokens[mintAddress];
126
- });
127
-
128
- if (unknownTokensList.length > 0) {
129
- this.emit('unknown-tokens', unknownTokensList);
130
- }
131
- }
132
-
133
- async tick({
134
- walletAccount,
135
- refresh
136
- }) {
137
- // Check for new wallet account
138
- await this.initWalletAccount({
139
- walletAccount
140
- });
141
- const assetName = this.asset.name;
142
- this.assets = await this.aci.getAssetsForNetwork({
143
- baseAssetName: assetName
144
- });
145
- this.api.setTokens(this.assets);
146
- const accountState = await this.aci.getAccountState({
147
- assetName,
148
- walletAccount
149
- });
150
- const address = await this.aci.getReceiveAddress({
151
- assetName,
152
- walletAccount
153
- });
154
- const {
155
- logItemsByAsset,
156
- hasNewTxs,
157
- cursorState
158
- } = await this.getHistory({
159
- address,
160
- accountState,
161
- walletAccount,
162
- refresh
163
- });
164
- let staking = accountState.mem;
165
- const cursorChanged = this.hasNewCursor({
166
- walletAccount,
167
- cursorState
168
- });
169
-
170
- if (refresh || cursorChanged) {
171
- staking = await this.updateStakingInfo({
172
- walletAccount,
173
- address
174
- });
175
- this.cursors[walletAccount] = cursorState.cursor;
176
- }
177
-
178
- await this.updateTxLogByAsset({
179
- walletAccount,
180
- logItemsByAsset,
181
- refresh
182
- });
183
-
184
- if (refresh || hasNewTxs || cursorChanged) {
185
- const tokenAccounts = await this.api.getTokenAccountsByOwner(address);
186
- await this.emitUnknownTokensEvent({
187
- tokenAccounts
188
- });
189
- const account = await this.getAccount({
190
- address,
191
- staking,
192
- tokenAccounts
193
- });
194
- await this.updateState({
195
- account,
196
- cursorState,
197
- walletAccount
198
- });
199
- }
200
- }
201
-
202
- async getHistory({
203
- address,
204
- accountState,
205
- refresh
206
- } = {}) {
207
- let cursor = refresh ? '' : accountState.cursor;
208
- const baseAsset = this.asset;
209
- const {
210
- transactions,
211
- newCursor
212
- } = await this.api.getTransactions(address, {
213
- cursor,
214
- includeUnparsed: this.includeUnparsed
215
- });
216
- const mappedTransactions = [];
217
-
218
- for (const tx of transactions) {
219
- const assetName = _lodash.default.get(tx, 'token.tokenName', baseAsset.name);
220
-
221
- const asset = this.assets[assetName];
222
- if (assetName === 'unknown' || !asset) continue; // skip unknown tokens
223
-
224
- const coinAmount = asset.currency.baseUnit(tx.amount).toDefault();
225
- const item = {
226
- coinName: assetName,
227
- txId: tx.id,
228
- from: [tx.from],
229
- coinAmount,
230
- confirmations: 1,
231
- // tx.confirmations, // avoid multiple notifications
232
- date: tx.date,
233
- error: tx.error,
234
- data: {
235
- staking: tx.staking || null,
236
- unparsed: !!tx.unparsed,
237
- swapTx: !!(tx.data && tx.data.inner)
238
- }
239
- };
240
-
241
- if (tx.owner === address) {
242
- // send transaction
243
- item.to = tx.to;
244
- item.feeAmount = baseAsset.currency.baseUnit(tx.fee).toDefault(); // in SOL
245
-
246
- item.coinAmount = item.coinAmount.negate();
247
-
248
- if (tx.to === tx.owner) {
249
- item.selfSend = true;
250
- item.coinAmount = asset.currency.ZERO;
251
- }
252
- } else if (tx.unparsed) {
253
- if (tx.fee !== 0) item.feeAmount = baseAsset.currency.baseUnit(tx.fee).toDefault(); // in SOL
254
-
255
- item.data.meta = tx.data.meta;
256
- }
257
-
258
- if (asset.assetType === 'SOLANA_TOKEN' && item.feeAmount && item.feeAmount.isPositive) {
259
- const feeAsset = asset.feeAsset;
260
- const feeItem = { ..._lodash.default.clone(item),
261
- coinName: feeAsset.name,
262
- tokens: [asset.name],
263
- coinAmount: feeAsset.currency.ZERO
264
- };
265
- mappedTransactions.push(feeItem);
266
- }
267
-
268
- mappedTransactions.push(item);
269
- } // logItemsByAsset = { 'solana:': [...], 'serum': [...] }
270
-
271
-
272
- return {
273
- logItemsByAsset: _lodash.default.groupBy(mappedTransactions, item => item.coinName),
274
- hasNewTxs: transactions.length > 0,
275
- cursorState: {
276
- cursor: newCursor
277
- }
278
- };
279
- }
280
-
281
- async getAccount({
282
- address,
283
- staking,
284
- tokenAccounts
285
- }) {
286
- const tokens = Object.keys(this.assets).filter(name => name !== this.asset.name);
287
- const [solBalance, splBalances] = await Promise.all([this.api.getBalance(address), this.api.getTokensBalance({
288
- address,
289
- filterByTokens: tokens,
290
- tokenAccounts
291
- })]);
292
- const stakedBalance = this.asset.currency.baseUnit(staking.locked).toDefault();
293
- const withdrawableBalance = this.asset.currency.baseUnit(staking.withdrawable).toDefault();
294
- const pendingBalance = this.asset.currency.baseUnit(staking.pending).toDefault();
295
- const balance = this.asset.currency.baseUnit(solBalance).toDefault().add(stakedBalance).add(withdrawableBalance).add(pendingBalance);
296
-
297
- const tokenBalances = _lodash.default.mapValues(splBalances, (balance, name) => this.assets[name].currency.baseUnit(balance).toDefault());
298
-
299
- return {
300
- balance,
301
- tokenBalances
302
- };
303
- }
304
-
305
- async updateState({
306
- account,
307
- cursorState,
308
- walletAccount
309
- }) {
310
- const {
311
- balance,
312
- tokenBalances
313
- } = account;
314
- const newData = {
315
- balance,
316
- tokenBalances,
317
- ...cursorState
318
- };
319
- return this.updateAccountState({
320
- newData,
321
- walletAccount
322
- });
323
- }
324
-
325
- async updateStakingInfo({
326
- walletAccount,
327
- address
328
- }) {
329
- const stakingInfo = await this.api.getStakeAccountsInfo(address);
330
- const rewards = await this.api.getRewards(Object.keys(stakingInfo.accounts));
331
- const mem = {
332
- loaded: true,
333
- staking: this.staking,
334
- isDelegating: Object.values(stakingInfo.accounts).some(({
335
- state
336
- }) => ['active', 'activating', 'inactive'].includes(state)),
337
- // true if at least 1 account is delegating
338
- locked: this.asset.currency.baseUnit(stakingInfo.locked).toDefault(),
339
- withdrawable: this.asset.currency.baseUnit(stakingInfo.withdrawable).toDefault(),
340
- pending: this.asset.currency.baseUnit(stakingInfo.pending).toDefault(),
341
- // still undelegating (not yet available for withdraw)
342
- earned: this.asset.currency.baseUnit(rewards).toDefault(),
343
- accounts: stakingInfo.accounts // Obj
344
-
345
- };
346
- await this.updateAccountState({
347
- walletAccount,
348
- newData: {
349
- mem
350
- }
351
- });
352
- return mem;
353
- }
354
-
355
- }
356
-
357
- exports.SolanaMonitor = SolanaMonitor;