@exodus/solana-api 1.4.0 → 1.4.2

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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/index.js +93 -46
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@exodus/asset-json-rpc": "^1.0.0",
17
- "@exodus/solana-lib": "^1.3.0",
17
+ "@exodus/solana-lib": "^1.3.1",
18
18
  "lodash": "^4.17.11",
19
19
  "url-join": "4.0.0",
20
20
  "wretch": "^1.5.2"
@@ -22,5 +22,5 @@
22
22
  "devDependencies": {
23
23
  "node-fetch": "~1.6.3"
24
24
  },
25
- "gitHead": "158aae6b4aeb871b1566b2500456a5f475ef9d5f"
25
+ "gitHead": "d7cd72173e6645069824b0669559481e3ff762c2"
26
26
  }
package/src/index.js CHANGED
@@ -5,13 +5,13 @@ import {
5
5
  deserializeMetaplexMetadata,
6
6
  getTransactionSimulationParams,
7
7
  filterAccountsByOwner,
8
- tokens,
9
8
  SYSTEM_PROGRAM_ID,
10
9
  STAKE_PROGRAM_ID,
11
10
  TOKEN_PROGRAM_ID,
12
11
  SOL_DECIMAL,
13
12
  computeBalance,
14
13
  } from '@exodus/solana-lib'
14
+ import assets from '@exodus/assets'
15
15
  import assert from 'assert'
16
16
  import lodash from 'lodash'
17
17
  import urljoin from 'url-join'
@@ -25,6 +25,7 @@ const RPC_URL = 'https://solana.a.exodus.io' // https://vip-api.mainnet-beta.sol
25
25
  class Api {
26
26
  constructor(rpcUrl) {
27
27
  this.setServer(rpcUrl)
28
+ this.setTokens(assets)
28
29
  }
29
30
 
30
31
  setServer(rpcUrl) {
@@ -32,12 +33,21 @@ class Api {
32
33
  this.api = createApi(this.rpcUrl)
33
34
  }
34
35
 
36
+ setTokens(assets = {}) {
37
+ const solTokens = lodash.pickBy(assets, (asset) => asset.assetType === 'SOLANA_TOKEN')
38
+ this.tokens = lodash.mapKeys(solTokens, (v) => v.mintAddress)
39
+ }
40
+
35
41
  request(path, contentType = 'application/json'): Wretcher {
36
42
  return wretch(urljoin(this.rpcUrl, path)).headers({
37
43
  'Content-type': contentType,
38
44
  })
39
45
  }
40
46
 
47
+ isTokenSupported(mint: string) {
48
+ return !!this.tokens[mint]
49
+ }
50
+
41
51
  async getEpochInfo(): number {
42
52
  const { epoch } = await this.api.post({
43
53
  method: 'getEpochInfo',
@@ -140,9 +150,21 @@ class Api {
140
150
  if (txDetail === null) return
141
151
 
142
152
  const timestamp = txDetail.blockTime * 1000
143
- const parsedTx = Api.parseTransaction(address, txDetail, tokenAccountsByOwner)
153
+ const parsedTx = this.parseTransaction(address, txDetail, tokenAccountsByOwner)
144
154
  if (!parsedTx.from) return // cannot parse it
145
155
 
156
+ // split dexTx in separate txs
157
+ if (parsedTx.dexTxs) {
158
+ parsedTx.dexTxs.forEach((tx) => {
159
+ transactions.push({
160
+ timestamp,
161
+ date: new Date(timestamp),
162
+ ...tx,
163
+ })
164
+ })
165
+ delete parsedTx.dexTxs
166
+ }
167
+
146
168
  transactions.push({
147
169
  timestamp,
148
170
  date: new Date(timestamp),
@@ -161,17 +183,11 @@ class Api {
161
183
  return { transactions, newCursor }
162
184
  }
163
185
 
164
- parseTransaction(...args) {
165
- // alias
166
- return Api.parseTransaction(...args)
167
- }
168
-
169
- static parseTransaction(
170
- ownerAddress: string,
171
- txDetails: Object,
172
- tokenAccountsByOwner: ?Array
173
- ): Object {
174
- const { fee, preTokenBalances, postTokenBalances } = txDetails.meta
186
+ parseTransaction(ownerAddress: string, txDetails: Object, tokenAccountsByOwner: ?Array): Object {
187
+ let { fee, preTokenBalances, postTokenBalances, innerInstructions } = txDetails.meta
188
+ preTokenBalances = preTokenBalances || []
189
+ postTokenBalances = postTokenBalances || []
190
+ innerInstructions = innerInstructions || []
175
191
  let { instructions, accountKeys } = txDetails.transaction.message
176
192
  instructions = instructions
177
193
  .filter((ix) => ix.parsed) // only known instructions
@@ -180,6 +196,38 @@ class Api {
180
196
  type: ix.parsed.type, // transfer, createAccount, initializeAccount
181
197
  ...ix.parsed.info,
182
198
  }))
199
+ innerInstructions = innerInstructions
200
+ .reduce((acc, val) => {
201
+ return acc.concat(val.instructions)
202
+ }, [])
203
+ .map((ix) => {
204
+ const type = lodash.get(ix, 'parsed.type')
205
+ const isTransferTx = ix.parsed && ix.program === 'spl-token' && type === 'transfer'
206
+ const source = lodash.get(ix, 'parsed.info.source')
207
+ const destination = lodash.get(ix, 'parsed.info.destination')
208
+ const amount = Number(lodash.get(ix, 'parsed.info.amount', 0))
209
+
210
+ const tokenAccount = tokenAccountsByOwner.find(({ tokenAccountAddress }) => {
211
+ return [source, destination].includes(tokenAccountAddress)
212
+ })
213
+ const isSending = !!tokenAccountsByOwner.find(({ tokenAccountAddress }) => {
214
+ return [source].includes(tokenAccountAddress)
215
+ })
216
+
217
+ // owner if it's a send tx
218
+ const instruction = {
219
+ id: txDetails.transaction.signatures[0],
220
+ slot: txDetails.slot,
221
+ owner: isSending ? ownerAddress : null,
222
+ from: source,
223
+ to: destination,
224
+ amount,
225
+ token: tokenAccount,
226
+ fee: isSending ? fee : 0,
227
+ }
228
+ return isTransferTx && tokenAccount ? instruction : null
229
+ })
230
+ .filter((ix) => !!ix)
183
231
 
184
232
  // program:type tells us if it's a SOL or Token transfer
185
233
  const solanaTx = lodash.find(instructions, (ix) => {
@@ -189,9 +237,10 @@ class Api {
189
237
  const stakeTx = lodash.find(instructions, { program: 'system', type: 'createAccountWithSeed' })
190
238
  const stakeWithdraw = lodash.find(instructions, { program: 'stake', type: 'withdraw' })
191
239
  const stakeUndelegate = lodash.find(instructions, { program: 'stake', type: 'deactivate' })
240
+ const hasSolanaTx = solanaTx && !preTokenBalances.length && !postTokenBalances.length // only SOL moved and no tokens movements
192
241
 
193
242
  let tx = {}
194
- if (solanaTx) {
243
+ if (hasSolanaTx) {
195
244
  // Solana tx
196
245
  const isSending = ownerAddress === solanaTx.source
197
246
  tx = {
@@ -305,36 +354,38 @@ class Api {
305
354
 
306
355
  // group by owner and supported token
307
356
  const preBalances = preTokenBalances.filter((t) => {
308
- return accountIndexes[t.accountIndex].owner === ownerAddress && isTokenSupported(t.mint)
357
+ return (
358
+ accountIndexes[t.accountIndex].owner === ownerAddress && this.isTokenSupported(t.mint)
359
+ )
309
360
  })
310
361
  const postBalances = postTokenBalances.filter((t) => {
311
- return accountIndexes[t.accountIndex].owner === ownerAddress && isTokenSupported(t.mint)
362
+ return (
363
+ accountIndexes[t.accountIndex].owner === ownerAddress && this.isTokenSupported(t.mint)
364
+ )
312
365
  })
313
366
 
314
- if (preBalances.length && preBalances.length === postBalances.length) {
315
- // parse and compute pre-balances/post-balances difference (1 token per tx spported at the moment)
316
- const amount =
317
- Number(lodash.get(postBalances, '[0].uiTokenAmount.amount', 0)) -
318
- Number(lodash.get(preBalances, '[0].uiTokenAmount.amount', 0))
319
- const isSending = Math.sign(amount) <= 0
320
-
321
- const mint = lodash.get(postBalances, '[0].mint', '')
322
- const { tokenName, ticker } = tokens.find(({ mintAddress }) => mintAddress === mint) || {
323
- tokenName: 'unknown',
324
- ticker: 'UNKNOWN',
367
+ if (preBalances.length || postBalances.length) {
368
+ tx = {}
369
+
370
+ if (solanaTx) {
371
+ // the base tx will be the one that moved solana.
372
+ tx = {
373
+ owner: solanaTx.source,
374
+ from: solanaTx.source,
375
+ to: solanaTx.destination,
376
+ amount: solanaTx.lamports, // number
377
+ fee: ownerAddress === solanaTx.source ? fee : 0,
378
+ }
325
379
  }
326
380
 
327
- tx = {
328
- owner: isSending ? ownerAddress : TOKEN_PROGRAM_ID.toBase58(),
329
- token: {
330
- tokenAccountAddress: '', // token account is closed after the swap
331
- tokenName,
332
- ticker,
333
- },
334
- from: isSending ? ownerAddress : TOKEN_PROGRAM_ID.toBase58(),
335
- to: ownerAddress, // DEX always interact with the owner address
336
- amount,
337
- fee: isSending ? fee : 0, // in lamports
381
+ // If it has inner instructions then it's a DEX tx that moved SPL -> SPL
382
+ if (innerInstructions.length) {
383
+ tx.dexTxs = innerInstructions
384
+ // if tx involves only SPL swaps. Expand DEX ix (first element as tx base and the other kept there)
385
+ if (!tx.from && !solanaTx) {
386
+ tx = tx.dexTxs[0]
387
+ tx.dexTxs = innerInstructions.slice(1)
388
+ }
338
389
  }
339
390
  }
340
391
  }
@@ -343,7 +394,7 @@ class Api {
343
394
  // How tokens tx are parsed:
344
395
  // 0. compute incoming or outgoing tx: it's outgoing if spl-token:transfer has source/destination included in tokenAccountsByOwner
345
396
  // 1. if it's a sent tx: sum all instructions amount (spl-token:transfer)
346
- // 2. if it's an incoming tx: sull all the amounts with destination included in tokenAccountsByOwner (aggregating by ticker)
397
+ // 2. if it's an incoming tx: sum all the amounts with destination included in tokenAccountsByOwner (aggregating by ticker)
347
398
  // QUESTION: How do I know what are my tokens addresses deterministically? It's not possible, gotta use tokenAccountsByOwner
348
399
 
349
400
  return {
@@ -365,15 +416,15 @@ class Api {
365
416
  const { pubkey, account } = entry
366
417
 
367
418
  const mint = lodash.get(account, 'data.parsed.info.mint')
368
- const token = tokens.find(({ mintAddress }) => mintAddress === mint) || {
369
- tokenName: 'unknown',
419
+ const token = this.tokens[mint] || {
420
+ name: 'unknown',
370
421
  ticker: 'UNKNOWN',
371
422
  }
372
423
  const balance = lodash.get(account, 'data.parsed.info.tokenAmount.amount', '0')
373
424
  tokenAccounts.push({
374
425
  tokenAccountAddress: pubkey,
375
426
  owner: address,
376
- tokenName: token.tokenName,
427
+ tokenName: token.name,
377
428
  ticker: token.ticker,
378
429
  balance,
379
430
  mintAddress: mint,
@@ -663,8 +714,4 @@ class Api {
663
714
  }
664
715
  }
665
716
 
666
- function isTokenSupported(mint: string) {
667
- return !!tokens.find(({ mintAddress }) => mintAddress === mint)
668
- }
669
-
670
717
  export default new Api()