@exodus/solana-api 2.5.6 → 2.5.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/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "2.5.6",
3
+ "version": "2.5.8",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
7
- "src/**",
8
- "!src/__tests__",
7
+ "src",
8
+ "!src/**/__tests__",
9
9
  "README.md",
10
10
  "package.json"
11
11
  ],
@@ -31,5 +31,5 @@
31
31
  "url-join": "4.0.0",
32
32
  "wretch": "^1.5.2"
33
33
  },
34
- "gitHead": "9ec7084bcdfe14f1c6f11df393351885773046a6"
34
+ "gitHead": "46b0e2b60d3b21c3aa9e33be5785d34b0ba29cca"
35
35
  }
package/src/api.js CHANGED
@@ -48,7 +48,7 @@ export class Api {
48
48
 
49
49
  setTokens(assets = {}) {
50
50
  const solTokens = lodash.pickBy(assets, (asset) => asset.assetType === 'SOLANA_TOKEN')
51
- this.tokens = lodash.mapKeys(solTokens, (v) => v.mintAddress)
51
+ this.tokens = new Map(Object.values(solTokens).map((v) => [v.mintAddress, v]))
52
52
  }
53
53
 
54
54
  request(path, contentType = 'application/json'): Wretcher {
@@ -102,11 +102,11 @@ export class Api {
102
102
  }
103
103
 
104
104
  getTokenByAddress(mint: string) {
105
- return this.tokens[mint]
105
+ return this.tokens.get(mint)
106
106
  }
107
107
 
108
108
  isTokenSupported(mint: string) {
109
- return !!this.getTokenByAddress(mint)
109
+ return this.tokens.has(mint)
110
110
  }
111
111
 
112
112
  async getEpochInfo(): number {
@@ -68,7 +68,7 @@ export class SolanaMonitor extends BaseMonitor {
68
68
  async emitUnknownTokensEvent({ tokenAccounts }) {
69
69
  const tokensList = await this.api.getWalletTokensList({ tokenAccounts })
70
70
  const unknownTokensList = tokensList.filter((mintAddress) => {
71
- return !this.api.tokens[mintAddress]
71
+ return !this.api.tokens.has(mintAddress)
72
72
  })
73
73
  if (unknownTokensList.length > 0) {
74
74
  this.emit('unknown-tokens', unknownTokensList)
@@ -1,273 +0,0 @@
1
- import lodash from 'lodash'
2
- import { fetch } from '@exodus/fetch'
3
- import wretch from 'wretch'
4
- import * as splToken from '@exodus/solana-spl-token'
5
- import {
6
- generateKeyPair,
7
- PublicKey,
8
- LAMPORTS_PER_SOL,
9
- TOKEN_PROGRAM_ID,
10
- SystemProgram,
11
- U64,
12
- SolanaWeb3Transaction as Transaction,
13
- } from '@exodus/solana-lib'
14
- import { Api } from '../index'
15
- import { SOL_TRANSFER_TX } from './fixtures'
16
- import assets from './assets'
17
-
18
- const api = new Api({ assets })
19
-
20
- wretch().polyfills({
21
- fetch,
22
- })
23
-
24
- const SECONDS = 1000
25
- jest.setTimeout(60 * SECONDS)
26
-
27
- const ADDRESS_WITH_HISTORY = 'Cd1uLoQwGZiDSyJxrwbpYmuDZM2tDftkowPjWv5YE5Z7'
28
- const ADDRESS_FRESH = '3bbnZt1mzp1EBRR9Rm6TX2cXzZYbVvM9THutWL8bCmVH' // no txs history
29
- const TXID =
30
- '4AwqVmXFzciF89JyzMrTr5FT2tX1iMg7ypzCzUsGMD54dwjrE8w4GLNTbXXRpNcE8JkTSGRbZ8SzEc5umQJNFS3m'
31
- const SRM_TOKEN_TXID =
32
- '2zJ7dnXMZYUhw4uaeLaBRcoDsgfM9HZ1GSbfjJp4SxWrFhzEdwNKFfFFJ8LL6ckKwh5Xg5ik5pUuRtjMkGMDZpgV'
33
-
34
- beforeAll(() => {
35
- return api.watchAddress({ address: ADDRESS_WITH_HISTORY }) // Open WS connection
36
- })
37
-
38
- test('Solana: getRecentBlockHash', async () => {
39
- const blockHash = await api.getRecentBlockHash()
40
- expect(typeof blockHash).toEqual('string')
41
- expect(blockHash.length > 40).toEqual(true)
42
- })
43
-
44
- test('Solana: getTransactionById', async () => {
45
- const result = await api.getTransactionById(TXID)
46
- expect(result.transaction.signatures[0]).toEqual(TXID) // string
47
- expect(result.meta.fee > 0).toEqual(true)
48
- expect(Array.isArray(result.meta.preBalances)).toEqual(true)
49
- expect(Array.isArray(result.meta.postBalances)).toEqual(true)
50
- expect(result.transaction.message.accountKeys.length > 1).toEqual(true)
51
- expect(result.transaction.message.accountKeys[0].pubkey).toEqual(
52
- 'Bnjx2F242ZK6LsKbAvRwDoeKczJTK8LFM918p8sZCb69'
53
- )
54
- })
55
-
56
- test('Solana: getFee', async () => {
57
- const fee = await api.getFee()
58
- expect(fee >= 4000).toEqual(true) // usually 5000 lamports
59
- })
60
-
61
- test('Solana: getBalance', async () => {
62
- const [balance1, balance2] = await Promise.all([
63
- api.getBalance(ADDRESS_WITH_HISTORY),
64
- api.getBalance(ADDRESS_FRESH),
65
- ])
66
- expect(typeof balance1 === 'number').toEqual(true)
67
- expect(typeof balance2 === 'number').toEqual(true)
68
- expect(balance2).toEqual(0)
69
- })
70
-
71
- test('Solana: getBlockTime', async () => {
72
- const ts = await api.getBlockTime(49901505)
73
- expect(ts) // if null the backend started pruning old tx entries and has partial ledger!
74
- })
75
-
76
- test('Solana: getConfirmedSignaturesForAddress', async () => {
77
- const addrTxs = await api.getConfirmedSignaturesForAddress(ADDRESS_WITH_HISTORY)
78
- expect(addrTxs.length > 4)
79
- const addrTxs2 = await api.getConfirmedSignaturesForAddress(ADDRESS_WITH_HISTORY, { until: '' })
80
- expect(addrTxs2.length > 4).toEqual(true)
81
- })
82
-
83
- describe('getTransactions', () => {
84
- test('Solana: Returns empty transactions if account is not created', async () => {
85
- const { transactions } = await api.getTransactions(ADDRESS_FRESH)
86
- expect(transactions.length === 0).toBeTruthy()
87
- })
88
-
89
- test('Solana: Returns correct transactions if account is created', async () => {
90
- const { transactions, newCursor } = await api.getTransactions(ADDRESS_WITH_HISTORY)
91
- expect(transactions.length >= 1).toBeTruthy()
92
- expect(typeof newCursor).toEqual('string')
93
- // check tokens txs returned as well
94
- const tokenTx = transactions.find(({ id }) => id === SRM_TOKEN_TXID)
95
- // console.log('tokenTx:', tokenTx)
96
- expect(tokenTx.timestamp).toEqual(1608598478000)
97
- expect(tokenTx.id).toEqual(SRM_TOKEN_TXID)
98
- expect(tokenTx.token.tokenName).toEqual('serum')
99
- expect(tokenTx.owner).toEqual(null)
100
- expect(tokenTx.from).toEqual('71GbXnJkHz15kzjagB2f7N9H7HPA99v6BFuqfdYL5qtf')
101
- expect(tokenTx.amount).toEqual(1000)
102
- expect(tokenTx.fee).toEqual(0)
103
- })
104
-
105
- test('Solana: Returns expected transactions with cursor', async () => {
106
- const OLD_TXID =
107
- '3qFUz73ZPiexM8yHyTFhnEhXgVinzXCQMieJbTsfZKcW5V3vPSJJvRCSKjCHJDYtWqReeBrFV98hcCFaAznpjgUC' // existing old txid that should not be returned in txs history after cursor date.
108
- const { transactions } = await api.getTransactions(ADDRESS_WITH_HISTORY, {
109
- cursor:
110
- '4kzJjAbaT1PVSuk7JDh6697Y63MrGGMwDgi8CKRtctHbXWRjPtrzY1RnbMuedPDefru2hrGeLfRDpqtdFmQQAMXm',
111
- })
112
- expect(transactions.length >= 1).toBeTruthy()
113
- expect(lodash.find(transactions, { id: OLD_TXID })).toBeFalsy()
114
- expect(
115
- lodash.find(transactions, {
116
- id:
117
- '3V7eDr5BbgZfgiADpUxM2fHQ1ZAoJHddxxfFRd8KtDQeTqLsv9wkfeg6NrVHzQc7wnEmVRQPdsYBT3RKLztL6Suf',
118
- })
119
- ).toBeTruthy()
120
- })
121
- })
122
-
123
- test('Solana: parseTransaction on a SOL tx', async () => {
124
- const owner = 'Bnjx2F242ZK6LsKbAvRwDoeKczJTK8LFM918p8sZCb69'
125
- const tx = await api.parseTransaction(owner, SOL_TRANSFER_TX)
126
- // console.log(tx)
127
- expect(tx.id).toEqual(
128
- '4AwqVmXFzciF89JyzMrTr5FT2tX1iMg7ypzCzUsGMD54dwjrE8w4GLNTbXXRpNcE8JkTSGRbZ8SzEc5umQJNFS3m'
129
- )
130
- expect(tx.slot).toEqual(36446581)
131
- expect(tx.error).toEqual(false)
132
- expect(tx.from).toEqual('Bnjx2F242ZK6LsKbAvRwDoeKczJTK8LFM918p8sZCb69')
133
- expect(tx.to).toEqual('84HuMpN8JPy9DLerT5nRcEHjkb1UQS3TD4CvR9y8MqgD')
134
- expect(tx.amount).toEqual(2e13)
135
- expect(tx.fee).toEqual(5e3)
136
- })
137
-
138
- test('Solana: getMinimumBalanceForRentExemption', async () => {
139
- const minimumBalance = await api.getMinimumBalanceForRentExemption(80)
140
- expect(typeof minimumBalance === 'number').toEqual(true)
141
- })
142
-
143
- test('Solana: getMetaplexMetadata', async () => {
144
- const MANGO_SPL_TOKEN = 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'
145
-
146
- const metadata = await api.getMetaplexMetadata(MANGO_SPL_TOKEN)
147
- // console.log('metadata', metadata)
148
- expect(metadata.name).toEqual('Mango')
149
- expect(metadata.symbol).toEqual('MNGO')
150
- })
151
-
152
- test('Solana: getMetaplexMetadata of invalid address returns null', async () => {
153
- const RANDOM_SPL_TOKEN = 'Dhg9XnzJWzSQqH2aAnhPTEJHGQAkALDfD98MA499A7pa' // doesn't have metaplex data
154
-
155
- const metadata = await api.getMetaplexMetadata(RANDOM_SPL_TOKEN)
156
- expect(metadata).toEqual(null)
157
- })
158
-
159
- test('Solana: broadcastTransaction', async () => {
160
- // execute setTimeout callback immediately (no wait)
161
- jest.spyOn(global, 'setTimeout')
162
- global.setTimeout.mockImplementation((callback) => {
163
- return callback()
164
- })
165
-
166
- try {
167
- // send transaction
168
- const signedTx =
169
- 'AVXo5X7UNzpuOmYzkZ+fqHDGiRLTSMlWlUCcZKzEV5CIKlrdvZa3/2GrJJfPrXgZqJbYDaGiOnP99tI/sRJfiwwBAAEDRQ/n5E5CLbMbHanUG3+iVvBAWZu0WFM6NoB5xfybQ7kNwwgfIhv6odn2qTUu/gOisDtaeCW1qlwW/gx3ccr/4wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvsInicc+E3IZzLqeA+iM5cn9kSaeFzOuClz1Z2kZQy0BAgIAAQwCAAAAAPIFKgEAAAA='
170
- await api.broadcastTransaction(signedTx)
171
- expect('To Fail').toEqual(true)
172
- } catch (err) {
173
- expect(err.message.match(/Blockhash not found/)).toBeTruthy()
174
- } finally {
175
- global.setTimeout.mockRestore()
176
- }
177
- })
178
-
179
- describe('Solana: getDecimals', () => {
180
- test('returns decimals if valid SPL address', async () => {
181
- const decimals = await api.getDecimals('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') // USDC
182
- expect(decimals).toEqual(6)
183
- })
184
-
185
- test('Solana: getDecimals: errors if invalid SPL address', async () => {
186
- try {
187
- await api.getDecimals('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1w')
188
- expect('To Fail').toEqual(true)
189
- } catch (err) {
190
- expect(err.message.match(/could not find account/)).toBeTruthy()
191
- }
192
-
193
- try {
194
- await api.getDecimals('EPjFWdd5ZwTDt1')
195
- expect('To Fail').toEqual(true)
196
- } catch (err) {
197
- expect(err.message.match(/WrongSize/)).toBeTruthy()
198
- }
199
- })
200
- })
201
-
202
- describe('Solana: isSpl', () => {
203
- test('returns true for addresses owned by the Token Program', async () => {
204
- expect(await api.isSpl('BmxZ1pghpcoyT7aykj7D1o4AxWirTqvD7zD2tNngjirT')).toBeTruthy()
205
- expect(await api.isSpl('buMnhMd5xSyXBssTQo15jouu8VhuEZJCfbtBUZgRcuW')).toBeTruthy()
206
- expect(await api.isSpl('MAPS41MDahZ9QdKXhVa4dWB9RuyfV4XqhyAZ8XcYepb')).toBeTruthy()
207
- })
208
-
209
- test('returns false for addresses not owned by the Token Program', async () => {
210
- expect(await api.isSpl('9zyPU1mjgzaVyQsYwKJJ7AhVz5bgx5uc1NPABvAcUXsT')).toBeFalsy()
211
- })
212
- })
213
-
214
- describe('Solana: simulateAndRetrieveSideEffects', () => {
215
- const ownerSolAccount = new PublicKey('3b1rT3knyRnZHKHCCxKWnJDWo8k6SDdSTvYzqyXUYwxL') // 1 SOL
216
- const ownerTokenAddress = new PublicKey('FRaWiAGc3bvQKAkqqAxwmLvCVH7AaUGo6MrBUYhCmojK') // 0 Wrapped SOL (Token)
217
-
218
- const secondarySolAccount = new PublicKey('41TDiDgCLz2eh5vPMhWCdNeaoMfrs3tM4rGdZgX4jL56') // 1 SOL
219
- const secondaryTokenAddress = new PublicKey('8HPbjj4QVnYGUcRrfeZ3XdMhq36RnaJqvUHjHZYZ2p65') // 1 Wrapped SOL (Token)
220
-
221
- // Use solana devnet since we need actual accounts with available balance for simulation
222
- const testApi = new Api({ rpcUrl: 'https://api.devnet.solana.com', assets })
223
-
224
- test('returns willSend and willReceive account changes', async () => {
225
- const SOL_SEND_AMOUNT = LAMPORTS_PER_SOL * 0.1
226
- const TOKEN_SEND_AMOUNT = LAMPORTS_PER_SOL * 0.531
227
-
228
- const txFee = await api.getFee()
229
- const transaction = new Transaction()
230
- // Owner sends SOL
231
- transaction.add(
232
- SystemProgram.transfer({
233
- fromPubkey: ownerSolAccount,
234
- toPubkey: generateKeyPair().publicKey,
235
- lamports: SOL_SEND_AMOUNT,
236
- })
237
- )
238
- // Owner receives Wrapped SOL token
239
- transaction.add(
240
- splToken.Token.createTransferInstruction(
241
- TOKEN_PROGRAM_ID,
242
- secondaryTokenAddress,
243
- ownerTokenAddress,
244
- secondarySolAccount,
245
- [],
246
- TOKEN_SEND_AMOUNT
247
- )
248
- )
249
- transaction.recentBlockhash = await testApi.getRecentBlockHash()
250
- transaction.feePayer = ownerSolAccount
251
- transaction.signatures = [
252
- { signature: null, publicKey: ownerSolAccount },
253
- { signature: null, publicKey: secondarySolAccount },
254
- ]
255
-
256
- const result = await testApi.simulateAndRetrieveSideEffects(
257
- transaction.compileMessage(),
258
- ownerSolAccount.toString()
259
- )
260
-
261
- expect(result.willSend.length === 1).toBeTruthy()
262
- expect(result.willSend[0].type).toEqual('SOL')
263
- expect(result.willSend[0].balance).toEqual(new U64((SOL_SEND_AMOUNT + txFee * 2) * -1)) // two tx and will send would be negative
264
-
265
- expect(result.willReceive.length === 1).toBeTruthy()
266
- expect(result.willReceive[0].type).toEqual('TOKEN')
267
- expect(result.willReceive[0].balance).toEqual(new U64(TOKEN_SEND_AMOUNT))
268
- })
269
- })
270
-
271
- afterAll(async () => {
272
- return api.unwatchAddress({ address: ADDRESS_WITH_HISTORY })
273
- })
@@ -1,7 +0,0 @@
1
- import { keyBy } from '@exodus/basic-utils'
2
- import { connectAssets } from '@exodus/assets'
3
- import assetsList from '@exodus/solana-meta'
4
-
5
- const assets = connectAssets(keyBy(assetsList, (asset) => asset.name))
6
-
7
- export default assets