@exodus/solana-api 1.2.3 → 1.2.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.
Files changed (2) hide show
  1. package/package.json +9 -5
  2. package/src/index.js +109 -45
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -13,9 +13,13 @@
13
13
  "access": "restricted"
14
14
  },
15
15
  "dependencies": {
16
- "@exodus/solana-lib": "^1.2.3",
17
- "@exodus/solana-web3.js": "^0.87.1-exodus4",
18
- "lodash": "^4.17.11"
16
+ "@exodus/asset-json-rpc": "^1.0.0",
17
+ "@exodus/solana-lib": "^1.2.5",
18
+ "lodash": "^4.17.11",
19
+ "wretch": "^1.5.2"
19
20
  },
20
- "gitHead": "5be453e5b6c47cf82b8c80a83e8a4bebbb487a2e"
21
+ "devDependencies": {
22
+ "node-fetch": "~1.6.3"
23
+ },
24
+ "gitHead": "791e305a718246460d5ac93ed2eb7808955c0f5d"
21
25
  }
package/src/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // @flow
2
- import { Connection, PublicKey } from '@exodus/solana-web3.js'
2
+ import createApi from '@exodus/asset-json-rpc'
3
3
  import { tokens, SYSTEM_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@exodus/solana-lib'
4
4
  import assert from 'assert'
5
5
  import lodash from 'lodash'
6
6
 
7
- // Doc: https://solana-labs.github.io/solana-web3.js/
7
+ // Doc: https://docs.solana.com/apps/jsonrpc-api
8
8
 
9
- const RPC_URL = 'https://api.mainnet-beta.solana.com' // https://solana-api.projectserum.com
9
+ const RPC_URL = 'https://vip-api.mainnet-beta.solana.com/' // https://api.mainnet-beta.solana.com, https://solana-api.projectserum.com
10
10
 
11
11
  // Tokens + SOL api support
12
12
  class Api {
@@ -16,70 +16,91 @@ class Api {
16
16
 
17
17
  setServer(rpcUrl) {
18
18
  this.rpcUrl = rpcUrl || RPC_URL
19
- this.connection = new Connection(this.rpcUrl)
19
+ this.api = createApi(this.rpcUrl)
20
20
  }
21
21
 
22
22
  async getRecentBlockHash(): string {
23
- const { blockhash } = await this.connection.getRecentBlockhash()
23
+ const {
24
+ value: { blockhash },
25
+ } = await this.api.post({
26
+ method: 'getRecentBlockhash',
27
+ })
24
28
  return blockhash
25
29
  }
26
30
 
27
31
  // Transaction structure: https://docs.solana.com/apps/jsonrpc-api#transaction-structure
28
32
  async getTransactionById(id: string) {
29
- const { result } = await this.connection._rpcRequest('getConfirmedTransaction', [
30
- id,
31
- 'jsonParsed',
32
- ])
33
+ const result = await this.api.post({
34
+ method: 'getConfirmedTransaction',
35
+ params: [id, 'jsonParsed'],
36
+ })
33
37
  return result
34
38
  }
35
39
 
36
40
  async getFee(): number {
37
41
  const {
38
- feeCalculator: { lamportsPerSignature },
39
- } = await this.connection.getRecentBlockhash()
42
+ value: {
43
+ feeCalculator: { lamportsPerSignature },
44
+ },
45
+ } = await this.api.post({
46
+ method: 'getRecentBlockhash',
47
+ })
40
48
  return lamportsPerSignature
41
49
  }
42
50
 
43
51
  async getBalance(address: string): number {
44
- return this.connection.getBalance(new PublicKey(address))
52
+ const res = await this.api.post({
53
+ method: 'getBalance',
54
+ params: [address],
55
+ })
56
+ return res.value || 0
45
57
  }
46
58
 
47
59
  async getBlockTime(slot: number) {
48
60
  // might result in error if executed on a validator with partial ledger (https://github.com/solana-labs/solana/issues/12413)
49
- return this.connection.getBlockTime(slot)
61
+ return this.api.post({
62
+ method: 'getBlockTime',
63
+ params: [slot],
64
+ })
50
65
  }
51
66
 
52
67
  async getConfirmedSignaturesForAddress(address: string, { until, before, limit } = {}): any {
53
68
  until = until || undefined
54
- return this.connection.getConfirmedSignaturesForAddress2(new PublicKey(address), {
55
- until,
56
- before,
57
- limit,
69
+ return this.api.post({
70
+ method: 'getConfirmedSignaturesForAddress2',
71
+ params: [address, { until, before, limit }],
58
72
  })
59
73
  }
60
74
 
61
75
  /**
62
76
  * Get transactions from an address
63
77
  */
64
- async getTransactions(address: string, { cursor, before, limit, tokenTicker } = {}): any {
78
+ async getTransactions(address: string, { cursor, before, limit } = {}): any {
65
79
  let transactions = []
66
80
  // cursor is a txHash
67
81
 
68
82
  try {
69
83
  let until = cursor
70
84
 
71
- const txsId = await this.getConfirmedSignaturesForAddress(address, {
72
- until,
73
- before,
74
- limit,
75
- })
76
-
77
- let tokenAccountsByOwner // Array
78
- if (txsId.length) tokenAccountsByOwner = await this.getTokenAccountsByOwner(address)
79
-
80
- if (tokenTicker) {
81
- // TODO: eventually filter txs by token ticker (is it done at monitor level?)
82
- }
85
+ const tokenAccountsByOwner = await this.getTokenAccountsByOwner(address) // Array
86
+ const tokenAccountAddresses = tokenAccountsByOwner
87
+ .filter(({ tokenName }) => tokenName !== 'unknown')
88
+ .map(({ tokenAccountAddress }) => tokenAccountAddress)
89
+ const accountsToCheck = [address, ...tokenAccountAddresses]
90
+
91
+ console.log('accountsToCheck::', accountsToCheck)
92
+
93
+ const txsResultsByAccount = await Promise.all(
94
+ accountsToCheck.map((addr) =>
95
+ this.getConfirmedSignaturesForAddress(addr, {
96
+ until,
97
+ before,
98
+ limit,
99
+ })
100
+ )
101
+ )
102
+ let txsId = txsResultsByAccount.reduce((arr, row) => arr.concat(row), []) // merge arrays
103
+ txsId = lodash.uniqBy(txsId, 'signature')
83
104
 
84
105
  for (let tx of txsId) {
85
106
  // get tx details
@@ -101,6 +122,8 @@ class Api {
101
122
  throw err
102
123
  }
103
124
 
125
+ transactions = lodash.orderBy(transactions, ['timestamp'], ['desc'])
126
+
104
127
  const newCursor = transactions[0] ? transactions[0].id : cursor
105
128
 
106
129
  return { transactions, newCursor }
@@ -134,6 +157,7 @@ class Api {
134
157
  // Solana tx
135
158
  const isSending = ownerAddress === solanaTx.source
136
159
  tx = {
160
+ owner: solanaTx.source,
137
161
  from: solanaTx.source,
138
162
  to: solanaTx.destination,
139
163
  amount: solanaTx.lamports, // number
@@ -156,10 +180,12 @@ class Api {
156
180
  tokenAccountAddress: ix.destination,
157
181
  }) // receiving
158
182
  if (!tokenAccount) return null // no transfers with our addresses involved
183
+ const owner = isSending ? ownerAddress : null
184
+
159
185
  delete tokenAccount.balance
160
186
  delete tokenAccount.owner
161
-
162
187
  return {
188
+ owner,
163
189
  token: tokenAccount,
164
190
  from: ix.source,
165
191
  to: ix.destination,
@@ -167,6 +193,7 @@ class Api {
167
193
  fee: isSending ? fee : 0, // in lamports
168
194
  }
169
195
  })
196
+
170
197
  // .reduce to sum/sub (based on isSending) all the same tokens amount (From instructions -> 1 single tx)
171
198
  tx = tokenTxs.reduce((finalTx, ix) => {
172
199
  if (!ix) return finalTx // skip null instructions
@@ -191,13 +218,10 @@ class Api {
191
218
  }
192
219
 
193
220
  async getTokenAccountsByOwner(address: string, tokenTicker: ?string): Array {
194
- const {
195
- result: { value: accountsList },
196
- } = await this.connection._rpcRequest('getTokenAccountsByOwner', [
197
- address,
198
- { programId: TOKEN_PROGRAM_ID.toBase58() },
199
- { encoding: 'jsonParsed' },
200
- ])
221
+ const { value: accountsList } = await this.api.post({
222
+ method: 'getTokenAccountsByOwner',
223
+ params: [address, { programId: TOKEN_PROGRAM_ID.toBase58() }, { encoding: 'jsonParsed' }],
224
+ })
201
225
 
202
226
  const tokenAccounts = []
203
227
  for (let entry of accountsList) {
@@ -223,13 +247,51 @@ class Api {
223
247
  : tokenAccounts
224
248
  }
225
249
 
250
+ async getTokensBalance(address: string) {
251
+ let accounts = await this.getTokenAccountsByOwner(address) // Tokens
252
+
253
+ const tokensBalance = accounts.reduce((acc, { tokenName, balance }) => {
254
+ if (tokenName === 'unknown') return acc // filter by supported tokens only
255
+ if (!acc[tokenName]) acc[tokenName] = Number(balance)
256
+ // e.g { 'serum': 123 }
257
+ else acc[tokenName] += Number(balance) // merge same token account balance
258
+ return acc
259
+ }, {})
260
+
261
+ return tokensBalance
262
+ }
263
+
264
+ async isAssociatedTokenAccountActive(tokenAddress: string) {
265
+ // Returns the token balance of an SPL Token account.
266
+ try {
267
+ await this.api.post({
268
+ method: 'getTokenAccountBalance',
269
+ params: [tokenAddress],
270
+ })
271
+ return true
272
+ } catch (e) {
273
+ return false
274
+ }
275
+ }
276
+
226
277
  async getAddressType(address: string) {
227
278
  // solana, token or null (unknown), meaning address has never been initialized
228
- const account = await this.connection.getAccountInfo(new PublicKey(address))
229
- if (account === null) return null
230
- return account.owner.equals(SYSTEM_PROGRAM_ID)
279
+ const { value } = await this.api.post({
280
+ method: 'getAccountInfo',
281
+ params: [address, { encoding: 'base64' }],
282
+ })
283
+ if (value === null) return null
284
+
285
+ const account = {
286
+ executable: value.executable,
287
+ owner: value.owner,
288
+ lamports: value.lamports,
289
+ data: value.data,
290
+ }
291
+
292
+ return account.owner === SYSTEM_PROGRAM_ID.toBase58()
231
293
  ? 'solana'
232
- : account.owner.equals(TOKEN_PROGRAM_ID)
294
+ : account.owner === TOKEN_PROGRAM_ID.toBase58()
233
295
  ? 'token'
234
296
  : null
235
297
  }
@@ -250,10 +312,12 @@ class Api {
250
312
  broadcastTransaction = async (signedTx: string): string => {
251
313
  console.log('Solana broadcasting TX:', signedTx) // base64
252
314
 
253
- const { message, ...result } = await this.connection.sendEncodedTransaction(signedTx)
254
- if (message) throw new Error(message)
315
+ const result = await this.api.post({
316
+ method: 'sendTransaction',
317
+ params: [signedTx, { encoding: 'base64' }],
318
+ })
255
319
 
256
- console.log(`tx ${result} sent!`)
320
+ console.log(`tx ${JSON.stringify(result)} sent!`)
257
321
  return result || null
258
322
  }
259
323
  }