@exodus/solana-api 3.25.4 → 3.26.1

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/src/api.js CHANGED
@@ -20,22 +20,14 @@ import lodash from 'lodash'
20
20
  import ms from 'ms'
21
21
  import urljoin from 'url-join'
22
22
 
23
- import { Connection } from './connection.js'
24
23
  import { getStakeActivation } from './get-stake-activation/index.js'
25
- import {
26
- isSolTransferInstruction,
27
- isSplMintInstruction,
28
- isSplTransferInstruction,
29
- } from './txs-utils.js'
24
+ import { parseTransaction } from './tx-parser.js'
30
25
 
31
26
  const createApi = createApiCJS.default || createApiCJS
32
27
 
33
28
  // Doc: https://docs.solana.com/apps/jsonrpc-api
34
29
 
35
30
  const RPC_URL = 'https://solana.a.exodus.io' // https://vip-api.mainnet-beta.solana.com/, https://api.mainnet-beta.solana.com
36
- const WS_ENDPOINT = 'wss://solana.a.exodus.io/ws' // not standard across all node providers (we're compatible only with Quicknode)
37
- const FORCE_HTTP = true // use https over ws
38
- const ZERO = BigInt(0)
39
31
 
40
32
  const errorMessagesToRetry = [
41
33
  'Blockhash not found',
@@ -44,14 +36,11 @@ const errorMessagesToRetry = [
44
36
 
45
37
  // Tokens + SOL api support
46
38
  export class Api {
47
- constructor({ rpcUrl, wsUrl, assets, txsLimit, tokenAssetType = 'SOLANA_TOKEN' }) {
48
- this.tokenAssetType = tokenAssetType
39
+ constructor({ rpcUrl, wsUrl, assets, txsLimit }) {
49
40
  this.setServer(rpcUrl)
50
- this.setWsEndpoint(wsUrl)
51
41
  this.setTokens(assets)
52
42
  this.tokensToSkip = {}
53
43
  this.txsLimit = txsLimit
54
- this.connections = {}
55
44
  this.getSupply = memoize(async (mintAddress) => {
56
45
  // cached getSupply
57
46
  const result = await this.rpcCall('getTokenSupply', [mintAddress])
@@ -76,12 +65,8 @@ export class Api {
76
65
  this.api = createApi(this.rpcUrl)
77
66
  }
78
67
 
79
- setWsEndpoint(wsUrl) {
80
- this.wsUrl = wsUrl || WS_ENDPOINT
81
- }
82
-
83
68
  setTokens(assets = {}) {
84
- const solTokens = pickBy(assets, (asset) => asset.assetType === this.tokenAssetType)
69
+ const solTokens = pickBy(assets, (asset) => asset.name !== asset.baseAsset.name)
85
70
  this.tokens = new Map(Object.values(solTokens).map((v) => [v.mintAddress, v]))
86
71
  }
87
72
 
@@ -91,51 +76,7 @@ export class Api {
91
76
  })
92
77
  }
93
78
 
94
- async watchAddress({
95
- address,
96
- tokensAddresses = [],
97
- onMessage,
98
- handleAccounts,
99
- handleTransfers,
100
- handleReconnect,
101
- reconnectDelay,
102
- }) {
103
- if (this.connections[address]) return // already subscribed
104
- const conn = new Connection({
105
- endpoint: this.wsUrl,
106
- address,
107
- tokensAddresses,
108
- onMsg: (json) => onMessage(json),
109
- callback: (updates) =>
110
- this.handleUpdates({ updates, address, handleAccounts, handleTransfers }),
111
- reconnectCallback: handleReconnect,
112
- reconnectDelay,
113
- })
114
-
115
- this.connections[address] = conn
116
- return conn.start()
117
- }
118
-
119
- async unwatchAddress({ address }) {
120
- if (this.connections[address]) {
121
- await this.connections[address].stop()
122
- delete this.connections[address]
123
- }
124
- }
125
-
126
- async handleUpdates({ updates, address, handleAccounts, handleTransfers }) {
127
- // console.log(`got ws updates from ${address}:`, updates)
128
- if (handleTransfers) return handleTransfers(updates)
129
- }
130
-
131
- async rpcCall(method, params = [], { address = '', forceHttp = FORCE_HTTP } = {}) {
132
- // ws request
133
- const connection = this.connections[address] || lodash.sample(Object.values(this.connections)) // pick random connection
134
- if (lodash.get(connection, 'isOpen') && !lodash.get(connection, 'shutdown') && !forceHttp) {
135
- return connection.sendMessage(method, params)
136
- }
137
-
138
- // http fallback
79
+ async rpcCall(method, params = []) {
139
80
  return this.api.post({ method, params })
140
81
  }
141
82
 
@@ -172,11 +113,9 @@ export class Api {
172
113
  }
173
114
 
174
115
  async getRecentBlockHash(commitment) {
175
- const result = await this.rpcCall(
176
- 'getLatestBlockhash',
177
- [{ commitment: commitment || 'confirmed', encoding: 'jsonParsed' }],
178
- { forceHttp: true }
179
- )
116
+ const result = await this.rpcCall('getLatestBlockhash', [
117
+ { commitment: commitment || 'confirmed', encoding: 'jsonParsed' },
118
+ ])
180
119
  return lodash.get(result, 'value.blockhash')
181
120
  }
182
121
 
@@ -188,18 +127,8 @@ export class Api {
188
127
  ])
189
128
  }
190
129
 
191
- async getPriorityFee(transaction) {
192
- // https://docs.helius.dev/solana-rpc-nodes/alpha-priority-fee-api
193
- const result = await this.rpcCall('getPriorityFeeEstimate', [
194
- { transaction, options: { recommended: true } },
195
- ])
196
- return result.priorityFeeEstimate
197
- }
198
-
199
130
  async getBalance(address) {
200
- const result = await this.rpcCall('getBalance', [address, { encoding: 'jsonParsed' }], {
201
- address,
202
- })
131
+ const result = await this.rpcCall('getBalance', [address, { encoding: 'jsonParsed' }])
203
132
  return lodash.get(result, 'value', 0)
204
133
  }
205
134
 
@@ -240,13 +169,7 @@ export class Api {
240
169
  const fetchRetry = retry(
241
170
  async () => {
242
171
  try {
243
- return await this.rpcCall(
244
- 'getSignaturesForAddress',
245
- [address, { until, before, limit }],
246
- {
247
- address,
248
- }
249
- )
172
+ return await this.rpcCall('getSignaturesForAddress', [address, { until, before, limit }])
250
173
  } catch (error) {
251
174
  if (
252
175
  error.message &&
@@ -353,535 +276,8 @@ export class Api {
353
276
  return { transactions, newCursor }
354
277
  }
355
278
 
356
- parseTransaction(
357
- ownerAddress,
358
- txDetails,
359
- tokenAccountsByOwner,
360
- { includeUnparsed = false } = {}
361
- ) {
362
- let { fee, preBalances, postBalances, preTokenBalances, postTokenBalances, innerInstructions } =
363
- txDetails.meta
364
- preBalances = preBalances || []
365
- postBalances = postBalances || []
366
- preTokenBalances = preTokenBalances || []
367
- postTokenBalances = postTokenBalances || []
368
- innerInstructions = innerInstructions || []
369
-
370
- let { instructions, accountKeys = [] } = txDetails.transaction.message
371
- const feePayerPubkey = accountKeys[0].pubkey
372
- const ownerIsFeePayer = feePayerPubkey === ownerAddress
373
- const txId = txDetails.transaction.signatures[0]
374
-
375
- const getUnparsedTx = () => {
376
- const ownerIndex = accountKeys.findIndex((accountKey) => accountKey.pubkey === ownerAddress)
377
- const feePaid = ownerIndex === 0 ? fee : 0
378
-
379
- return {
380
- unparsed: true,
381
- amount:
382
- ownerIndex === -1 ? 0 : postBalances[ownerIndex] - preBalances[ownerIndex] + feePaid,
383
- fee: feePaid,
384
- data: {
385
- meta: txDetails.meta,
386
- },
387
- }
388
- }
389
-
390
- const getInnerTxsFromBalanceChanges = () => {
391
- const ownPreTokenBalances = preTokenBalances.filter(
392
- (balance) => balance.owner === ownerAddress
393
- )
394
- const ownPostTokenBalances = postTokenBalances.filter(
395
- (balance) => balance.owner === ownerAddress
396
- )
397
-
398
- return ownPostTokenBalances
399
- .map((postBalance) => {
400
- const tokenAccount = tokenAccountsByOwner.find(
401
- (tokenAccount) => tokenAccount.mintAddress === postBalance.mint
402
- )
403
-
404
- const preBalance = ownPreTokenBalances.find(
405
- (balance) => balance.accountIndex === postBalance.accountIndex
406
- )
407
-
408
- const preAmount = BigInt(preBalance?.uiTokenAmount?.amount ?? '0')
409
- const postAmount = BigInt(postBalance?.uiTokenAmount?.amount ?? '0')
410
-
411
- const amount = postAmount - preAmount
412
-
413
- if (!tokenAccount || amount === ZERO) return null
414
-
415
- // This is not perfect as there could be multiple same-token transfers in single
416
- // transaction, but our wallet only supports one transaction with single txId
417
- // so we are picking first that matches (correct token + type - send or receive)
418
- const match = innerInstructions.find((inner) => {
419
- const targetOwner = amount < ZERO ? ownerAddress : null
420
- return (
421
- inner.token.mintAddress === tokenAccount.mintAddress && targetOwner === inner.owner
422
- )
423
- })
424
-
425
- // It's possible we won't find a match, because our innerInstructions only contain
426
- // spl-token transfers, but balances of SPL tokens can change in different ways too.
427
- // for now, we are ignoring this to simplify as those cases are not that common, but
428
- // they should be handled eventually. It was already a scretch to add unparsed txs logic
429
- // to existing parser, expanding it further is not going to end well.
430
- // this probably should be refactored from ground to handle all those transactions
431
- // as a core part of it in the future
432
- if (!match) return null
433
-
434
- const { from, to, owner } = match
435
-
436
- return {
437
- id: txId,
438
- slot: txDetails.slot,
439
- owner,
440
- from,
441
- to,
442
- amount: (amount < ZERO ? -amount : amount).toString(), // inconsistent with the rest, but it can and did overflow
443
- fee: 0,
444
- token: tokenAccount,
445
- data: {
446
- inner: true,
447
- },
448
- }
449
- })
450
- .filter((ix) => !!ix)
451
- }
452
-
453
- instructions = instructions
454
- .filter((ix) => ix.parsed) // only known instructions
455
- .map((ix) => ({
456
- program: ix.program, // system or spl-token
457
- type: ix.parsed.type, // transfer, createAccount, initializeAccount
458
- ...ix.parsed.info,
459
- }))
460
-
461
- let solanaTransferTx = lodash.find(instructions, (ix) => {
462
- if (![ix.source, ix.destination].includes(ownerAddress)) return false
463
- return ix.program === 'system' && ix.type === 'transfer'
464
- }) // get SOL transfer
465
-
466
- // check if there is a temp account created & closed within the instructions when there is no direct solana transfer
467
- const accountToRedeemToOwner = solanaTransferTx
468
- ? undefined
469
- : instructions.find(
470
- ({ type, owner, destination }) =>
471
- type === 'closeAccount' && owner === ownerAddress && destination === ownerAddress
472
- )?.account
473
-
474
- innerInstructions = innerInstructions
475
- .reduce((acc, val) => {
476
- return [...acc, ...val.instructions]
477
- }, [])
478
- .filter(
479
- (ix) =>
480
- ix.parsed &&
481
- (isSplTransferInstruction({ program: ix.program, type: ix.parsed.type }) ||
482
- isSplMintInstruction({ program: ix.program, type: ix.parsed.type }) ||
483
- (!includeUnparsed &&
484
- isSolTransferInstruction({ program: ix.program, type: ix.parsed.type })))
485
- )
486
- .map((ix) => {
487
- let source = lodash.get(ix, 'parsed.info.source')
488
- const destination = isSplMintInstruction({ program: ix.program, type: ix.parsed.type })
489
- ? lodash.get(ix, 'parsed.info.account') // only for minting
490
- : lodash.get(ix, 'parsed.info.destination')
491
- const amount = Number(
492
- lodash.get(ix, 'parsed.info.amount', 0) ||
493
- lodash.get(ix, 'parsed.info.tokenAmount.amount', 0)
494
- )
495
- const authority = lodash.get(ix, 'parsed.info.authority')
496
-
497
- if (accountToRedeemToOwner && destination === accountToRedeemToOwner) {
498
- solanaTransferTx = {
499
- from: authority || source,
500
- to: ownerAddress,
501
- amount,
502
- fee,
503
- }
504
- return
505
- }
506
-
507
- if (
508
- source === ownerAddress &&
509
- isSolTransferInstruction({ program: ix.program, type: ix.parsed.type })
510
- ) {
511
- const lamports = Number(lodash.get(ix, 'parsed.info.lamports', 0))
512
- if (solanaTransferTx) {
513
- solanaTransferTx.lamports += lamports
514
- solanaTransferTx.amount = solanaTransferTx.lamports
515
- if (!Array.isArray(solanaTransferTx.to)) {
516
- solanaTransferTx.data = {
517
- sent: [
518
- {
519
- address: solanaTransferTx.to,
520
- amount: lamports,
521
- },
522
- ],
523
- }
524
- solanaTransferTx.to = [solanaTransferTx.to]
525
- }
526
-
527
- solanaTransferTx.to.push(destination)
528
- solanaTransferTx.data.sent.push({ address: destination, amount: lamports })
529
- } else {
530
- solanaTransferTx = {
531
- source,
532
- owner: source,
533
- from: source,
534
- to: [destination],
535
- lamports,
536
- amount: lamports,
537
- data: {
538
- sent: [
539
- {
540
- address: destination,
541
- amount: lamports,
542
- },
543
- ],
544
- },
545
- fee,
546
- }
547
- }
548
-
549
- return
550
- }
551
-
552
- const tokenAccount = tokenAccountsByOwner.find(({ tokenAccountAddress }) => {
553
- return [source, destination].includes(tokenAccountAddress)
554
- })
555
- if (!tokenAccount) return
556
-
557
- if (isSplMintInstruction({ program: ix.program, type: ix.parsed.type })) {
558
- source = lodash.get(ix, 'parsed.info.mintAuthority')
559
- }
560
-
561
- const isSending = tokenAccountsByOwner.some(({ tokenAccountAddress }) => {
562
- return [source].includes(tokenAccountAddress)
563
- })
564
-
565
- // owner if it's a send tx
566
- return {
567
- id: txId,
568
- program: ix.program,
569
- type: ix.parsed.type,
570
- slot: txDetails.slot,
571
- owner: isSending ? ownerAddress : null,
572
- from: isSending ? ownerAddress : source,
573
- to: isSending ? destination : ownerAddress,
574
- amount,
575
- token: tokenAccount,
576
- // Attribute fee only when owner is the actual fee payer
577
- fee: isSending && ownerIsFeePayer ? fee : 0,
578
- }
579
- })
580
- .filter((ix) => !!ix)
581
-
582
- // Collect inner instructions into batch sends
583
- for (let i = 0; i < innerInstructions.length - 1; i++) {
584
- const tx = innerInstructions[i]
585
-
586
- for (let j = i + 1; j < innerInstructions.length; j++) {
587
- const next = innerInstructions[j]
588
- if (
589
- tx.id === next.id &&
590
- tx.token === next.token &&
591
- tx.owner === ownerAddress &&
592
- tx.from === next.from
593
- ) {
594
- if (!tx.data) {
595
- tx.data = { sent: [{ address: tx.to, amount: tx.amount }] }
596
- tx.to = [tx.to]
597
- tx.fee = 0
598
- }
599
-
600
- tx.data.sent.push({
601
- address: next.to,
602
- amount: next.amount,
603
- })
604
- tx.to.push(next.to)
605
-
606
- tx.amount += next.amount
607
-
608
- innerInstructions.splice(j, 1)
609
- j--
610
- }
611
- }
612
- }
613
-
614
- // program:type tells us if it's a SOL or Token transfer
615
-
616
- const stakeTx = lodash.find(instructions, { program: 'system', type: 'createAccountWithSeed' })
617
- const stakeWithdraw = lodash.find(instructions, { program: 'stake', type: 'withdraw' })
618
- const stakeUndelegate = lodash.find(instructions, { program: 'stake', type: 'deactivate' })
619
-
620
- let tx = {}
621
- if (stakeTx) {
622
- // start staking
623
- tx = {
624
- owner: stakeTx.base,
625
- from: stakeTx.base,
626
- to: stakeTx.base,
627
- amount: stakeTx.lamports,
628
- fee,
629
- staking: {
630
- method: 'createAccountWithSeed',
631
- seed: stakeTx.seed,
632
- stakeAddresses: [stakeTx.newAccount],
633
- stake: stakeTx.lamports,
634
- },
635
- }
636
- } else if (stakeWithdraw) {
637
- const stakeAccounts = lodash.map(
638
- lodash.filter(instructions, { program: 'stake', type: 'withdraw' }),
639
- 'stakeAccount'
640
- )
641
- tx = {
642
- owner: stakeWithdraw.withdrawAuthority,
643
- from: stakeWithdraw.stakeAccount,
644
- to: stakeWithdraw.destination,
645
- amount: stakeWithdraw.lamports,
646
- fee,
647
- staking: {
648
- method: 'withdraw',
649
- stakeAddresses: stakeAccounts,
650
- stake: stakeWithdraw.lamports,
651
- },
652
- }
653
- } else if (stakeUndelegate) {
654
- const stakeAccounts = lodash.map(
655
- lodash.filter(instructions, { program: 'stake', type: 'deactivate' }),
656
- 'stakeAccount'
657
- )
658
- tx = {
659
- owner: stakeUndelegate.stakeAuthority,
660
- from: stakeUndelegate.stakeAuthority,
661
- to: stakeUndelegate.stakeAccount, // obsolete
662
- amount: 0,
663
- fee,
664
- staking: {
665
- method: 'undelegate',
666
- stakeAddresses: stakeAccounts,
667
- },
668
- }
669
- } else {
670
- if (solanaTransferTx) {
671
- const isSending = ownerAddress === solanaTransferTx.source
672
- tx = {
673
- owner: solanaTransferTx.source,
674
- from: solanaTransferTx.source,
675
- to: solanaTransferTx.destination,
676
- amount: solanaTransferTx.lamports, // number
677
- fee: isSending && ownerIsFeePayer ? fee : 0,
678
- data: solanaTransferTx.data,
679
- }
680
- }
681
-
682
- const accountIndexes = accountKeys.reduce((acc, key, i) => {
683
- const hasKnownOwner = tokenAccountsByOwner.some(
684
- (tokenAccount) => tokenAccount.tokenAccountAddress === key.pubkey
685
- )
686
-
687
- acc[i] = {
688
- ...key,
689
- owner: hasKnownOwner ? ownerAddress : null, // not know (like in an outgoing tx)
690
- }
691
-
692
- return acc
693
- }, Object.create(null)) // { 0: { pubkey, owner }, 1: { ... }, ... }
694
-
695
- // Parse Token txs
696
- const tokenTxs = this._parseTokenTransfers({
697
- instructions,
698
- innerInstructions,
699
- tokenAccountsByOwner,
700
- ownerAddress,
701
- fee: ownerIsFeePayer ? fee : 0,
702
- accountIndexes,
703
- preTokenBalances,
704
- postTokenBalances,
705
- })
706
-
707
- if (tokenTxs.length > 0) {
708
- // found spl-token simple transfer/transferChecked instruction
709
- tx.tokenTxs = tokenTxs.map((tx) => ({
710
- id: txId,
711
- slot: txDetails.slot,
712
- ...tx,
713
- }))
714
- } else if (preTokenBalances && postTokenBalances) {
715
- // probably a DEX program is involved (multiple instructions), compute balance changes
716
- // group by owner and supported token
717
- const preBalances = preTokenBalances.filter((t) => {
718
- return (
719
- accountIndexes[t.accountIndex].owner === ownerAddress && this.isTokenSupported(t.mint)
720
- )
721
- })
722
- const postBalances = postTokenBalances.filter((t) => {
723
- return (
724
- accountIndexes[t.accountIndex].owner === ownerAddress && this.isTokenSupported(t.mint)
725
- )
726
- })
727
-
728
- if (preBalances.length > 0 || postBalances.length > 0 || solanaTransferTx) {
729
- tx = {}
730
-
731
- if (includeUnparsed && innerInstructions.length > 0) {
732
- // when using includeUnparsed for DEX tx we want to keep SOL tx as "unparsed"
733
- // 1. we want to treat all SOL dex transactions as "Contract transaction", not "Sent SOL"
734
- // 2. default behavior is not perfect. For example it doesn't see SOL-side tx in
735
- // SOL->SPL swaps on Raydium and Orca.
736
- tx = getUnparsedTx()
737
- tx.dexTxs = getInnerTxsFromBalanceChanges()
738
- } else {
739
- if (solanaTransferTx) {
740
- // the base tx will be the one that moved solana.
741
- tx =
742
- solanaTransferTx.from && solanaTransferTx.to
743
- ? solanaTransferTx
744
- : {
745
- owner: solanaTransferTx.source,
746
- from: solanaTransferTx.source,
747
- to: solanaTransferTx.destination,
748
- amount: solanaTransferTx.lamports, // number
749
- fee: ownerAddress === solanaTransferTx.source ? fee : 0,
750
- }
751
- }
752
-
753
- // If it has inner instructions then it's a DEX tx that moved SPL -> SPL
754
- if (innerInstructions.length > 0) {
755
- tx.dexTxs = innerInstructions
756
- // if tx involves only SPL swaps. Expand DEX ix (first element as tx base and the other kept there)
757
- if (!tx.from && !solanaTransferTx) {
758
- tx = tx.dexTxs[0]
759
- tx.dexTxs = innerInstructions.slice(1)
760
- }
761
- }
762
- }
763
- }
764
- }
765
- }
766
-
767
- const unparsed = Object.keys(tx).length === 0
768
-
769
- if (unparsed && includeUnparsed) {
770
- tx = getUnparsedTx()
771
- }
772
-
773
- // How tokens tx are parsed:
774
- // 0. compute incoming or outgoing tx: it's outgoing if spl-token:transfer has source/destination included in tokenAccountsByOwner
775
- // 1. if it's a sent tx: sum all instructions amount (spl-token:transfer)
776
- // 2. if it's an incoming tx: sum all the amounts with destination included in tokenAccountsByOwner (aggregating by ticker)
777
- // QUESTION: How do I know what are my tokens addresses deterministically? It's not possible, gotta use tokenAccountsByOwner
778
-
779
- return {
780
- id: txId,
781
- slot: txDetails.slot,
782
- error: !(txDetails.meta.err === null),
783
- ...tx,
784
- }
785
- }
786
-
787
- _parseTokenTransfers({
788
- instructions,
789
- innerInstructions = [],
790
- tokenAccountsByOwner,
791
- ownerAddress,
792
- fee,
793
- accountIndexes = {},
794
- preTokenBalances,
795
- postTokenBalances,
796
- }) {
797
- if (
798
- preTokenBalances.length === 0 &&
799
- postTokenBalances.length === 0 &&
800
- !Array.isArray(tokenAccountsByOwner)
801
- ) {
802
- return []
803
- }
804
-
805
- const tokenTxs = []
806
-
807
- instructions.forEach((instruction) => {
808
- const { type, program, source, destination, amount, tokenAmount } = instruction
809
-
810
- if (isSplTransferInstruction({ program, type })) {
811
- let tokenAccount = lodash.find(tokenAccountsByOwner, { tokenAccountAddress: source })
812
- const isSending = !!tokenAccount
813
- if (!isSending) {
814
- // receiving
815
- tokenAccount = lodash.find(tokenAccountsByOwner, {
816
- tokenAccountAddress: destination,
817
- })
818
- }
819
-
820
- if (!tokenAccount) return // no transfers with our addresses involved
821
-
822
- const owner = isSending ? ownerAddress : null
823
-
824
- delete tokenAccount.balance
825
- delete tokenAccount.owner
826
-
827
- // If it's a sending tx we want to have the destination's owner as "to" address
828
- let to = ownerAddress
829
- let from = ownerAddress
830
- if (isSending) {
831
- to = destination // token account address (trying to get the owner below, we don't always have postTokenBalances...)
832
- postTokenBalances.forEach((t) => {
833
- if (accountIndexes[t.accountIndex].pubkey === destination) to = t.owner
834
- })
835
- } else {
836
- // is receiving tx
837
- from = source // token account address
838
- preTokenBalances.forEach((t) => {
839
- if (accountIndexes[t.accountIndex].pubkey === source) from = t.owner
840
- })
841
- }
842
-
843
- tokenTxs.push({
844
- owner,
845
- token: tokenAccount,
846
- from,
847
- to,
848
- amount: Number(amount || tokenAmount?.amount || 0), // supporting types: transfer, transferChecked, transferCheckedWithFee
849
- fee: isSending ? fee : 0, // in lamports
850
- })
851
- }
852
- })
853
-
854
- innerInstructions.forEach((parsedIx) => {
855
- const { type, program, amount, from, to } = parsedIx
856
-
857
- // Handle token minting (mintTo, mintToChecked)
858
- if (isSplMintInstruction({ program, type })) {
859
- const {
860
- token: { tokenAccountAddress },
861
- } = parsedIx
862
-
863
- // Check if the destination token account belongs to our owner
864
- const tokenAccount = lodash.find(tokenAccountsByOwner, {
865
- tokenAccountAddress,
866
- })
867
-
868
- if (!tokenAccount) return // not our token account
869
-
870
- delete tokenAccount.balance
871
- delete tokenAccount.owner
872
-
873
- tokenTxs.push({
874
- owner: null, // no owner for minting (it's created from thin air)
875
- token: tokenAccount,
876
- from, // mint address as the source
877
- to, // our address as recipient
878
- amount: Number(amount || 0),
879
- fee: 0, // no fee for receiving minted tokens
880
- })
881
- }
882
- })
883
-
884
- return tokenTxs
279
+ parseTransaction(...args) {
280
+ return parseTransaction(...args)
885
281
  }
886
282
 
887
283
  async getWalletTokensList({ tokenAccounts }) {
@@ -911,16 +307,16 @@ export class Api {
911
307
 
912
308
  async getTokenAccountsByOwner(address, tokenTicker) {
913
309
  const [{ value: standardTokenAccounts }, { value: token2022Accounts }] = await Promise.all([
914
- this.rpcCall(
915
- 'getTokenAccountsByOwner',
916
- [address, { programId: TOKEN_PROGRAM_ID.toBase58() }, { encoding: 'jsonParsed' }],
917
- { address }
918
- ),
919
- this.rpcCall(
920
- 'getTokenAccountsByOwner',
921
- [address, { programId: TOKEN_2022_PROGRAM_ID.toBase58() }, { encoding: 'jsonParsed' }],
922
- { address }
923
- ),
310
+ this.rpcCall('getTokenAccountsByOwner', [
311
+ address,
312
+ { programId: TOKEN_PROGRAM_ID.toBase58() },
313
+ { encoding: 'jsonParsed' },
314
+ ]),
315
+ this.rpcCall('getTokenAccountsByOwner', [
316
+ address,
317
+ { programId: TOKEN_2022_PROGRAM_ID.toBase58() },
318
+ { encoding: 'jsonParsed' },
319
+ ]),
924
320
  ])
925
321
 
926
322
  // merge regular token and token2022 program tokens
@@ -1033,11 +429,10 @@ export class Api {
1033
429
  }
1034
430
 
1035
431
  async getAccountInfo(address, encoding = 'jsonParsed') {
1036
- const { value } = await this.rpcCall(
1037
- 'getAccountInfo',
1038
- [address, { encoding, commitment: 'confirmed' }],
1039
- { address }
1040
- )
432
+ const { value } = await this.rpcCall('getAccountInfo', [
433
+ address,
434
+ { encoding, commitment: 'confirmed' },
435
+ ])
1041
436
  return value
1042
437
  }
1043
438
 
@@ -1130,7 +525,7 @@ export class Api {
1130
525
  encoding: 'jsonParsed',
1131
526
  },
1132
527
  ]
1133
- const res = await this.rpcCall('getProgramAccounts', params, { address })
528
+ const res = await this.rpcCall('getProgramAccounts', params)
1134
529
 
1135
530
  const accounts = {}
1136
531
  let totalStake = 0
@@ -1206,7 +601,7 @@ export class Api {
1206
601
  const broadcastTxWithRetry = retry(
1207
602
  async () => {
1208
603
  try {
1209
- const result = await this.rpcCall('sendTransaction', params, { forceHttp: true })
604
+ const result = await this.rpcCall('sendTransaction', params)
1210
605
  console.log(`tx ${JSON.stringify(result)} sent!`)
1211
606
 
1212
607
  return result || null