@exodus/solana-api 2.5.31-alpha.3 → 2.5.32
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 +14 -4
- package/src/account-state.js +1 -0
- package/src/api.js +55 -47
- package/src/connection.js +24 -20
- package/src/get-balances.js +29 -30
- package/src/index.js +2 -1
- package/src/staking-utils.js +1 -3
- package/src/tx-log/solana-monitor.js +34 -16
- package/src/tx-send.js +164 -166
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-api",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.32",
|
|
4
4
|
"description": "Exodus internal Solana asset API wrapper",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -14,11 +14,17 @@
|
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "restricted"
|
|
16
16
|
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "run -T jest",
|
|
19
|
+
"lint": "run -T eslint ./src",
|
|
20
|
+
"lint:fix": "yarn lint --fix"
|
|
21
|
+
},
|
|
17
22
|
"dependencies": {
|
|
18
23
|
"@exodus/asset-json-rpc": "^1.0.0",
|
|
19
|
-
"@exodus/asset-lib": "^4.
|
|
24
|
+
"@exodus/asset-lib": "^4.1.0",
|
|
20
25
|
"@exodus/assets": "^9.0.1",
|
|
21
26
|
"@exodus/basic-utils": "^2.1.0",
|
|
27
|
+
"@exodus/currency": "^2.3.2",
|
|
22
28
|
"@exodus/fetch": "^1.2.0",
|
|
23
29
|
"@exodus/models": "^10.1.0",
|
|
24
30
|
"@exodus/nfts-core": "^0.5.0",
|
|
@@ -27,12 +33,16 @@
|
|
|
27
33
|
"@exodus/solana-meta": "^1.0.7",
|
|
28
34
|
"bn.js": "^4.11.0",
|
|
29
35
|
"debug": "^4.1.1",
|
|
36
|
+
"delay": "^4.0.1",
|
|
30
37
|
"lodash": "^4.17.11",
|
|
38
|
+
"make-concurrent": "^4.0.0",
|
|
39
|
+
"minimalistic-assert": "^1.0.1",
|
|
40
|
+
"ms": "^2.1.3",
|
|
31
41
|
"url-join": "4.0.0",
|
|
32
42
|
"wretch": "^1.5.2"
|
|
33
43
|
},
|
|
34
44
|
"devDependencies": {
|
|
35
|
-
"@exodus/assets-testing": "
|
|
45
|
+
"@exodus/assets-testing": "^1.0.0"
|
|
36
46
|
},
|
|
37
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "6b37a481bbf82a7ff63a453ca66446fbd45fb4a8"
|
|
38
48
|
}
|
package/src/account-state.js
CHANGED
package/src/api.js
CHANGED
|
@@ -25,15 +25,15 @@ import { Connection } from './connection'
|
|
|
25
25
|
const RPC_URL = 'https://solana.a.exodus.io' // https://vip-api.mainnet-beta.solana.com/, https://api.mainnet-beta.solana.com
|
|
26
26
|
const WS_ENDPOINT = 'wss://solana.a.exodus.io/ws' // not standard across all node providers (we're compatible only with Quicknode)
|
|
27
27
|
const FORCE_HTTP = true // use https over ws
|
|
28
|
-
const TXS_LIMIT = 100
|
|
29
28
|
|
|
30
29
|
// Tokens + SOL api support
|
|
31
30
|
export class Api {
|
|
32
|
-
constructor({ rpcUrl, wsUrl, assets }) {
|
|
31
|
+
constructor({ rpcUrl, wsUrl, assets, txsLimit }) {
|
|
33
32
|
this.setServer(rpcUrl)
|
|
34
33
|
this.setWsEndpoint(wsUrl)
|
|
35
34
|
this.setTokens(assets)
|
|
36
35
|
this.tokensToSkip = {}
|
|
36
|
+
this.txsLimit = txsLimit
|
|
37
37
|
this.connections = {}
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -60,16 +60,18 @@ export class Api {
|
|
|
60
60
|
async watchAddress({
|
|
61
61
|
address,
|
|
62
62
|
tokensAddresses = [],
|
|
63
|
+
onMessage,
|
|
63
64
|
handleAccounts,
|
|
64
65
|
handleTransfers,
|
|
65
66
|
handleReconnect,
|
|
66
67
|
reconnectDelay,
|
|
67
68
|
}) {
|
|
68
|
-
if (
|
|
69
|
+
if (this.connections[address]) return // already subscribed
|
|
69
70
|
const conn = new Connection({
|
|
70
71
|
endpoint: this.wsUrl,
|
|
71
72
|
address,
|
|
72
73
|
tokensAddresses,
|
|
74
|
+
onMsg: (json) => onMessage(json),
|
|
73
75
|
callback: (updates) =>
|
|
74
76
|
this.handleUpdates({ updates, address, handleAccounts, handleTransfers }),
|
|
75
77
|
reconnectCallback: handleReconnect,
|
|
@@ -98,6 +100,7 @@ export class Api {
|
|
|
98
100
|
if (lodash.get(connection, 'isOpen') && !lodash.get(connection, 'shutdown') && !forceHttp) {
|
|
99
101
|
return connection.sendMessage(method, params)
|
|
100
102
|
}
|
|
103
|
+
|
|
101
104
|
// http fallback
|
|
102
105
|
return this.api.post({ method, params })
|
|
103
106
|
}
|
|
@@ -120,7 +123,7 @@ export class Api {
|
|
|
120
123
|
return state
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
async getRecentBlockHash(commitment
|
|
126
|
+
async getRecentBlockHash(commitment) {
|
|
124
127
|
const result = await this.rpcCall(
|
|
125
128
|
'getLatestBlockhash',
|
|
126
129
|
[{ commitment: commitment || 'finalized', encoding: 'jsonParsed' }],
|
|
@@ -165,11 +168,12 @@ export class Api {
|
|
|
165
168
|
* Get transactions from an address
|
|
166
169
|
*/
|
|
167
170
|
async getTransactions(address, { cursor, before, limit, includeUnparsed = false } = {}) {
|
|
171
|
+
limit = limit || this.txsLimit
|
|
168
172
|
let transactions = []
|
|
169
173
|
// cursor is a txHash
|
|
170
174
|
|
|
171
175
|
try {
|
|
172
|
-
|
|
176
|
+
const until = cursor
|
|
173
177
|
|
|
174
178
|
const tokenAccountsByOwner = await this.getTokenAccountsByOwner(address) // Array
|
|
175
179
|
const tokenAccountAddresses = tokenAccountsByOwner
|
|
@@ -186,7 +190,7 @@ export class Api {
|
|
|
186
190
|
})
|
|
187
191
|
)
|
|
188
192
|
)
|
|
189
|
-
let txsId = txsResultsByAccount.
|
|
193
|
+
let txsId = txsResultsByAccount.flat() // merge arrays
|
|
190
194
|
txsId = lodash.uniqBy(txsId, 'signature')
|
|
191
195
|
|
|
192
196
|
// get txs details in parallel
|
|
@@ -236,14 +240,8 @@ export class Api {
|
|
|
236
240
|
tokenAccountsByOwner,
|
|
237
241
|
{ includeUnparsed = false } = {}
|
|
238
242
|
) {
|
|
239
|
-
let {
|
|
240
|
-
|
|
241
|
-
preBalances,
|
|
242
|
-
postBalances,
|
|
243
|
-
preTokenBalances,
|
|
244
|
-
postTokenBalances,
|
|
245
|
-
innerInstructions,
|
|
246
|
-
} = txDetails.meta
|
|
243
|
+
let { fee, preBalances, postBalances, preTokenBalances, postTokenBalances, innerInstructions } =
|
|
244
|
+
txDetails.meta
|
|
247
245
|
preBalances = preBalances || []
|
|
248
246
|
postBalances = postBalances || []
|
|
249
247
|
preTokenBalances = preTokenBalances || []
|
|
@@ -339,7 +337,7 @@ export class Api {
|
|
|
339
337
|
}))
|
|
340
338
|
innerInstructions = innerInstructions
|
|
341
339
|
.reduce((acc, val) => {
|
|
342
|
-
return acc
|
|
340
|
+
return [...acc, ...val.instructions]
|
|
343
341
|
}, [])
|
|
344
342
|
.map((ix) => {
|
|
345
343
|
const type = lodash.get(ix, 'parsed.type')
|
|
@@ -355,7 +353,7 @@ export class Api {
|
|
|
355
353
|
const tokenAccount = tokenAccountsByOwner.find(({ tokenAccountAddress }) => {
|
|
356
354
|
return [source, destination].includes(tokenAccountAddress)
|
|
357
355
|
})
|
|
358
|
-
const isSending =
|
|
356
|
+
const isSending = tokenAccountsByOwner.some(({ tokenAccountAddress }) => {
|
|
359
357
|
return [source].includes(tokenAccountAddress)
|
|
360
358
|
})
|
|
361
359
|
|
|
@@ -382,7 +380,7 @@ export class Api {
|
|
|
382
380
|
const stakeTx = lodash.find(instructions, { program: 'system', type: 'createAccountWithSeed' })
|
|
383
381
|
const stakeWithdraw = lodash.find(instructions, { program: 'stake', type: 'withdraw' })
|
|
384
382
|
const stakeUndelegate = lodash.find(instructions, { program: 'stake', type: 'deactivate' })
|
|
385
|
-
const hasSolanaTx = solanaTx &&
|
|
383
|
+
const hasSolanaTx = solanaTx && preTokenBalances.length === 0 && postTokenBalances.length === 0 // only SOL moved and no tokens movements
|
|
386
384
|
|
|
387
385
|
let tx = {}
|
|
388
386
|
if (hasSolanaTx) {
|
|
@@ -449,7 +447,7 @@ export class Api {
|
|
|
449
447
|
Array.isArray(tokenAccountsByOwner),
|
|
450
448
|
'tokenAccountsByOwner is required when parsing token tx'
|
|
451
449
|
)
|
|
452
|
-
|
|
450
|
+
const tokenTxs = lodash
|
|
453
451
|
.filter(instructions, ({ program, type }) => {
|
|
454
452
|
return program === 'spl-token' && ['transfer', 'transferChecked'].includes(type)
|
|
455
453
|
}) // get Token transfer: could have more than 1 instructions
|
|
@@ -476,7 +474,7 @@ export class Api {
|
|
|
476
474
|
}
|
|
477
475
|
})
|
|
478
476
|
|
|
479
|
-
if (tokenTxs.length) {
|
|
477
|
+
if (tokenTxs.length > 0) {
|
|
480
478
|
// found spl-token simple transfer/transferChecked instruction
|
|
481
479
|
// .reduce to sum/sub (based on isSending) all the same tokens amount (From instructions -> 1 single tx)
|
|
482
480
|
tx = tokenTxs.reduce((finalTx, ix) => {
|
|
@@ -491,9 +489,11 @@ export class Api {
|
|
|
491
489
|
const accountIndexes = lodash.mapKeys(accountKeys, (x, i) => i)
|
|
492
490
|
Object.values(accountIndexes).forEach((acc) => {
|
|
493
491
|
// filter by ownerAddress
|
|
492
|
+
// eslint-disable-next-line unicorn/prefer-array-some
|
|
494
493
|
const hasKnownOwner = !!lodash.find(tokenAccountsByOwner, {
|
|
495
494
|
tokenAccountAddress: acc.pubkey,
|
|
496
495
|
})
|
|
496
|
+
// eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
|
|
497
497
|
acc.owner = hasKnownOwner ? ownerAddress : null
|
|
498
498
|
})
|
|
499
499
|
|
|
@@ -509,15 +509,15 @@ export class Api {
|
|
|
509
509
|
)
|
|
510
510
|
})
|
|
511
511
|
|
|
512
|
-
if (preBalances.length || postBalances.length) {
|
|
512
|
+
if (preBalances.length > 0 || postBalances.length > 0) {
|
|
513
513
|
tx = {}
|
|
514
514
|
|
|
515
|
-
if (includeUnparsed && innerInstructions.length) {
|
|
515
|
+
if (includeUnparsed && innerInstructions.length > 0) {
|
|
516
516
|
// when using includeUnparsed for DEX tx we want to keep SOL tx as "unparsed"
|
|
517
517
|
// 1. we want to treat all SOL dex transactions as "Contract transaction", not "Sent SOL"
|
|
518
518
|
// 2. default behavior is not perfect. For example it doesn't see SOL-side tx in
|
|
519
519
|
// SOL->SPL swaps on Raydium and Orca.
|
|
520
|
-
tx = getUnparsedTx(
|
|
520
|
+
tx = getUnparsedTx()
|
|
521
521
|
tx.dexTxs = getInnerTxsFromBalanceChanges()
|
|
522
522
|
} else {
|
|
523
523
|
if (solanaTx) {
|
|
@@ -532,7 +532,7 @@ export class Api {
|
|
|
532
532
|
}
|
|
533
533
|
|
|
534
534
|
// If it has inner instructions then it's a DEX tx that moved SPL -> SPL
|
|
535
|
-
if (innerInstructions.length) {
|
|
535
|
+
if (innerInstructions.length > 0) {
|
|
536
536
|
tx.dexTxs = innerInstructions
|
|
537
537
|
// if tx involves only SPL swaps. Expand DEX ix (first element as tx base and the other kept there)
|
|
538
538
|
if (!tx.from && !solanaTx) {
|
|
@@ -548,7 +548,7 @@ export class Api {
|
|
|
548
548
|
const unparsed = Object.keys(tx).length === 0
|
|
549
549
|
|
|
550
550
|
if (unparsed && includeUnparsed) {
|
|
551
|
-
tx = getUnparsedTx(
|
|
551
|
+
tx = getUnparsedTx()
|
|
552
552
|
}
|
|
553
553
|
|
|
554
554
|
// How tokens tx are parsed:
|
|
@@ -572,7 +572,7 @@ export class Api {
|
|
|
572
572
|
|
|
573
573
|
async getWalletTokensList({ tokenAccounts }) {
|
|
574
574
|
const tokensMint = []
|
|
575
|
-
for (
|
|
575
|
+
for (const account of tokenAccounts) {
|
|
576
576
|
const mint = account.mintAddress
|
|
577
577
|
|
|
578
578
|
// skip cached NFT
|
|
@@ -585,6 +585,7 @@ export class Api {
|
|
|
585
585
|
this.tokensToSkip[mint] = true
|
|
586
586
|
continue
|
|
587
587
|
}
|
|
588
|
+
|
|
588
589
|
// OK
|
|
589
590
|
tokensMint.push(mint)
|
|
590
591
|
}
|
|
@@ -600,7 +601,7 @@ export class Api {
|
|
|
600
601
|
)
|
|
601
602
|
|
|
602
603
|
const tokenAccounts = []
|
|
603
|
-
for (
|
|
604
|
+
for (const entry of accountsList) {
|
|
604
605
|
const { pubkey, account } = entry
|
|
605
606
|
|
|
606
607
|
const mint = lodash.get(account, 'data.parsed.info.mint')
|
|
@@ -618,6 +619,7 @@ export class Api {
|
|
|
618
619
|
mintAddress: mint,
|
|
619
620
|
})
|
|
620
621
|
}
|
|
622
|
+
|
|
621
623
|
// eventually filter by token
|
|
622
624
|
return tokenTicker
|
|
623
625
|
? tokenAccounts.filter(({ ticker }) => ticker === tokenTicker)
|
|
@@ -625,18 +627,24 @@ export class Api {
|
|
|
625
627
|
}
|
|
626
628
|
|
|
627
629
|
async getTokensBalance({ address, filterByTokens = [], tokenAccounts }) {
|
|
628
|
-
|
|
630
|
+
const accounts = tokenAccounts || (await this.getTokenAccountsByOwner(address))
|
|
629
631
|
|
|
630
|
-
|
|
631
|
-
if (
|
|
632
|
+
return accounts.reduce((acc, { tokenName, balance }) => {
|
|
633
|
+
if (
|
|
634
|
+
tokenName === 'unknown' ||
|
|
635
|
+
(filterByTokens.length > 0 && !filterByTokens.includes(tokenName))
|
|
636
|
+
)
|
|
632
637
|
return acc // filter by supported tokens only
|
|
633
|
-
if (
|
|
638
|
+
if (acc[tokenName]) {
|
|
639
|
+
acc[tokenName] += Number(balance)
|
|
640
|
+
}
|
|
634
641
|
// e.g { 'serum': 123 }
|
|
635
|
-
else
|
|
642
|
+
else {
|
|
643
|
+
acc[tokenName] = Number(balance)
|
|
644
|
+
} // merge same token account balance
|
|
645
|
+
|
|
636
646
|
return acc
|
|
637
647
|
}, {})
|
|
638
|
-
|
|
639
|
-
return tokensBalance
|
|
640
648
|
}
|
|
641
649
|
|
|
642
650
|
async isAssociatedTokenAccountActive(tokenAddress) {
|
|
@@ -644,7 +652,7 @@ export class Api {
|
|
|
644
652
|
try {
|
|
645
653
|
await this.rpcCall('getTokenAccountBalance', [tokenAddress])
|
|
646
654
|
return true
|
|
647
|
-
} catch
|
|
655
|
+
} catch {
|
|
648
656
|
return false
|
|
649
657
|
}
|
|
650
658
|
}
|
|
@@ -697,20 +705,19 @@ export class Api {
|
|
|
697
705
|
return account.owner === SYSTEM_PROGRAM_ID.toBase58()
|
|
698
706
|
? 'solana'
|
|
699
707
|
: account.owner === TOKEN_PROGRAM_ID.toBase58()
|
|
700
|
-
|
|
701
|
-
|
|
708
|
+
? 'token'
|
|
709
|
+
: null
|
|
702
710
|
}
|
|
703
711
|
|
|
704
712
|
async getTokenAddressOwner(address) {
|
|
705
713
|
const value = await this.getAccountInfo(address)
|
|
706
|
-
|
|
707
|
-
return owner
|
|
714
|
+
return lodash.get(value, 'data.parsed.info.owner', null)
|
|
708
715
|
}
|
|
709
716
|
|
|
710
717
|
async getAddressMint(address) {
|
|
711
718
|
const value = await this.getAccountInfo(address)
|
|
712
|
-
|
|
713
|
-
return
|
|
719
|
+
// token mint
|
|
720
|
+
return lodash.get(value, 'data.parsed.info.mint', null)
|
|
714
721
|
}
|
|
715
722
|
|
|
716
723
|
async isTokenAddress(address) {
|
|
@@ -745,7 +752,7 @@ export class Api {
|
|
|
745
752
|
let locked = 0
|
|
746
753
|
let withdrawable = 0
|
|
747
754
|
let pending = 0
|
|
748
|
-
for (
|
|
755
|
+
for (const entry of res) {
|
|
749
756
|
const addr = entry.pubkey
|
|
750
757
|
const lamports = lodash.get(entry, 'account.lamports', 0)
|
|
751
758
|
const delegation = lodash.get(entry, 'account.data.parsed.info.stake.delegation', {})
|
|
@@ -766,11 +773,12 @@ export class Api {
|
|
|
766
773
|
withdrawable += accounts[addr].canWithdraw ? lamports : 0
|
|
767
774
|
pending += accounts[addr].isDeactivating ? lamports : 0
|
|
768
775
|
}
|
|
776
|
+
|
|
769
777
|
return { accounts, totalStake, locked, withdrawable, pending }
|
|
770
778
|
}
|
|
771
779
|
|
|
772
780
|
async getRewards(stakingAddresses = []) {
|
|
773
|
-
if (
|
|
781
|
+
if (stakingAddresses.length === 0) return 0
|
|
774
782
|
|
|
775
783
|
// custom endpoint!
|
|
776
784
|
const rewards = await this.request('rewards')
|
|
@@ -780,11 +788,9 @@ export class Api {
|
|
|
780
788
|
.json()
|
|
781
789
|
|
|
782
790
|
// sum rewards for all addresses
|
|
783
|
-
|
|
791
|
+
return Object.values(rewards).reduce((total, x) => {
|
|
784
792
|
return total + x
|
|
785
793
|
}, 0)
|
|
786
|
-
|
|
787
|
-
return earnings
|
|
788
794
|
}
|
|
789
795
|
|
|
790
796
|
async getMinimumBalanceForRentExemption(size) {
|
|
@@ -820,7 +826,7 @@ export class Api {
|
|
|
820
826
|
} catch (error) {
|
|
821
827
|
if (
|
|
822
828
|
error.message &&
|
|
823
|
-
!errorMessagesToRetry.
|
|
829
|
+
!errorMessagesToRetry.some((errorMessage) => error.message.includes(errorMessage))
|
|
824
830
|
) {
|
|
825
831
|
error.finalError = true
|
|
826
832
|
}
|
|
@@ -868,6 +874,7 @@ export class Api {
|
|
|
868
874
|
if (error.message && error.message.includes('could not find account')) {
|
|
869
875
|
return defaultValue
|
|
870
876
|
}
|
|
877
|
+
|
|
871
878
|
throw error
|
|
872
879
|
}
|
|
873
880
|
}
|
|
@@ -959,11 +966,12 @@ export class Api {
|
|
|
959
966
|
simulateAndRetrieveSideEffects = async (
|
|
960
967
|
message,
|
|
961
968
|
publicKey,
|
|
962
|
-
transactionMessage
|
|
969
|
+
transactionMessage // decompiled TransactionMessage
|
|
963
970
|
) => {
|
|
964
971
|
const { config, accountAddresses } = getTransactionSimulationParams(
|
|
965
972
|
transactionMessage || message
|
|
966
973
|
)
|
|
974
|
+
// eslint-disable-next-line unicorn/no-new-array
|
|
967
975
|
const signatures = new Array(message.header.numRequiredSignatures || 1).fill(null)
|
|
968
976
|
const encodedTransaction = buildRawTransaction(
|
|
969
977
|
Buffer.from(message.serialize()),
|
package/src/connection.js
CHANGED
|
@@ -20,6 +20,7 @@ export class Connection {
|
|
|
20
20
|
address,
|
|
21
21
|
tokensAddresses = [],
|
|
22
22
|
callback,
|
|
23
|
+
onMsg,
|
|
23
24
|
reconnectCallback = () => {},
|
|
24
25
|
reconnectDelay = DEFAULT_RECONNECT_DELAY,
|
|
25
26
|
}) {
|
|
@@ -27,6 +28,7 @@ export class Connection {
|
|
|
27
28
|
this.tokensAddresses = tokensAddresses
|
|
28
29
|
this.endpoint = endpoint
|
|
29
30
|
this.callback = callback
|
|
31
|
+
this.onMsg = onMsg
|
|
30
32
|
this.reconnectCallback = reconnectCallback
|
|
31
33
|
this.reconnectDelay = reconnectDelay
|
|
32
34
|
|
|
@@ -66,10 +68,10 @@ export class Connection {
|
|
|
66
68
|
reqUrl = `${obj}`
|
|
67
69
|
debug('Opening WS to:', reqUrl)
|
|
68
70
|
const ws = new WebSocket(`${reqUrl}`)
|
|
69
|
-
ws.
|
|
70
|
-
ws.
|
|
71
|
-
ws.
|
|
72
|
-
ws.
|
|
71
|
+
ws.addEventListener('message', this.onMessage.bind(this))
|
|
72
|
+
ws.addEventListener('open', this.onOpen.bind(this))
|
|
73
|
+
ws.addEventListener('close', this.onClose.bind(this))
|
|
74
|
+
ws.addEventListener('error', this.onError.bind(this))
|
|
73
75
|
return ws
|
|
74
76
|
}
|
|
75
77
|
|
|
@@ -90,14 +92,14 @@ export class Connection {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
get running() {
|
|
93
|
-
return !!(!this.isClosed || this.inProcessMessages || this.messageQueue.length)
|
|
95
|
+
return !!(!this.isClosed || this.inProcessMessages || this.messageQueue.length > 0)
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
get connectionState() {
|
|
97
99
|
if (this.isConnecting) return 'CONNECTING'
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
if (this.isOpen) return 'OPEN'
|
|
101
|
+
if (this.isClosing) return 'CLOSING'
|
|
102
|
+
if (this.isClosed) return 'CLOSED'
|
|
101
103
|
return 'NONE'
|
|
102
104
|
}
|
|
103
105
|
|
|
@@ -125,7 +127,13 @@ export class Connection {
|
|
|
125
127
|
try {
|
|
126
128
|
const json = JSON.parse(evt.data)
|
|
127
129
|
debug('new ws msg:', json)
|
|
128
|
-
if (
|
|
130
|
+
if (json.error) {
|
|
131
|
+
if (lodash.get(this.rpcQueue, json.id)) {
|
|
132
|
+
this.rpcQueue[json.id].reject(new Error(json.error.message))
|
|
133
|
+
clearTimeout(this.rpcQueue[json.id].timeout)
|
|
134
|
+
delete this.rpcQueue[json.id]
|
|
135
|
+
} else debug('Unsupported WS message:', json.error.message)
|
|
136
|
+
} else {
|
|
129
137
|
if (lodash.get(this.rpcQueue, json.id)) {
|
|
130
138
|
// json-rpc reply
|
|
131
139
|
clearTimeout(this.rpcQueue[json.id].timeout)
|
|
@@ -136,13 +144,8 @@ export class Connection {
|
|
|
136
144
|
debug('pushing msg to queue', msg)
|
|
137
145
|
this.messageQueue.push(msg) // sub results
|
|
138
146
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (lodash.get(this.rpcQueue, json.id)) {
|
|
142
|
-
this.rpcQueue[json.id].reject(new Error(json.error.message))
|
|
143
|
-
clearTimeout(this.rpcQueue[json.id].timeout)
|
|
144
|
-
delete this.rpcQueue[json.id]
|
|
145
|
-
} else debug('Unsupported WS message:', json.error.message)
|
|
147
|
+
|
|
148
|
+
this.processMessages(json)
|
|
146
149
|
}
|
|
147
150
|
} catch (e) {
|
|
148
151
|
debug(e)
|
|
@@ -153,8 +156,8 @@ export class Connection {
|
|
|
153
156
|
onOpen(evt) {
|
|
154
157
|
debug('Opened WS')
|
|
155
158
|
// subscribe to each addresses (SOL and ASA addr)
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
const addresses = [...this.tokensAddresses, this.address]
|
|
160
|
+
addresses.forEach((address) => {
|
|
158
161
|
// sub for account state changes
|
|
159
162
|
this.ws.send(
|
|
160
163
|
JSON.stringify({
|
|
@@ -195,11 +198,12 @@ export class Connection {
|
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
async processMessages() {
|
|
201
|
+
async processMessages(json) {
|
|
202
|
+
if (this.onMsg) await this.onMsg(json)
|
|
199
203
|
if (this.inProcessMessages) return null
|
|
200
204
|
this.inProcessMessages = true
|
|
201
205
|
try {
|
|
202
|
-
while (this.messageQueue.length) {
|
|
206
|
+
while (this.messageQueue.length > 0) {
|
|
203
207
|
const items = this.messageQueue.splice(0, this.messageQueue.length)
|
|
204
208
|
await this.callback(items)
|
|
205
209
|
}
|
package/src/get-balances.js
CHANGED
|
@@ -3,35 +3,33 @@ import { TxSet } from '@exodus/models'
|
|
|
3
3
|
// staking may be a feature that may not be available for a given wallet.
|
|
4
4
|
// In this case, The wallet should exclude the staking balance from the general balance
|
|
5
5
|
|
|
6
|
-
export const getBalancesFactory =
|
|
7
|
-
|
|
8
|
-
accountState,
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
asset
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return { balance, spendableBalance: balance }
|
|
22
|
-
}
|
|
6
|
+
export const getBalancesFactory =
|
|
7
|
+
({ stakingFeatureAvailable }) =>
|
|
8
|
+
({ asset, accountState, txLog }) => {
|
|
9
|
+
const zero = asset.currency.ZERO
|
|
10
|
+
const { balance, locked, withdrawable, pending } = fixBalances({
|
|
11
|
+
txLog,
|
|
12
|
+
balance: getBalanceFromAccountState({ asset, accountState }),
|
|
13
|
+
locked: accountState.mem?.locked || zero,
|
|
14
|
+
withdrawable: accountState.mem?.withdrawable || zero,
|
|
15
|
+
pending: accountState.mem?.pending || zero,
|
|
16
|
+
asset,
|
|
17
|
+
})
|
|
18
|
+
if (asset.baseAsset.name !== asset.name) {
|
|
19
|
+
return { balance, spendableBalance: balance }
|
|
20
|
+
}
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const balanceWithoutStaking = balance
|
|
23
|
+
.sub(locked)
|
|
24
|
+
.sub(withdrawable)
|
|
25
|
+
.sub(pending)
|
|
26
|
+
.clampLowerZero()
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
return {
|
|
29
|
+
balance: stakingFeatureAvailable ? balance : balanceWithoutStaking,
|
|
30
|
+
spendableBalance: balanceWithoutStaking.sub(asset.accountReserve || zero).clampLowerZero(),
|
|
31
|
+
}
|
|
33
32
|
}
|
|
34
|
-
}
|
|
35
33
|
|
|
36
34
|
const fixBalances = ({ txLog = TxSet.EMPTY, balance, locked, withdrawable, pending, asset }) => {
|
|
37
35
|
for (const tx of txLog) {
|
|
@@ -40,10 +38,7 @@ const fixBalances = ({ txLog = TxSet.EMPTY, balance, locked, withdrawable, pendi
|
|
|
40
38
|
balance = balance.sub(tx.feeAmount)
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
if (
|
|
44
|
-
// coinAmount is negative for sent tx
|
|
45
|
-
balance = balance.sub(tx.coinAmount.abs())
|
|
46
|
-
} else {
|
|
41
|
+
if (tx.data.staking) {
|
|
47
42
|
// staking tx
|
|
48
43
|
switch (tx.data.staking?.method) {
|
|
49
44
|
case 'delegate':
|
|
@@ -57,9 +52,13 @@ const fixBalances = ({ txLog = TxSet.EMPTY, balance, locked, withdrawable, pendi
|
|
|
57
52
|
locked = asset.currency.ZERO
|
|
58
53
|
break
|
|
59
54
|
}
|
|
55
|
+
} else {
|
|
56
|
+
// coinAmount is negative for sent tx
|
|
57
|
+
balance = balance.sub(tx.coinAmount.abs())
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
}
|
|
61
|
+
|
|
63
62
|
return {
|
|
64
63
|
balance: balance.clampLowerZero(),
|
|
65
64
|
locked: locked.clampLowerZero(),
|
package/src/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import assetsList from '@exodus/solana-meta'
|
|
|
4
4
|
|
|
5
5
|
import { Api } from './api'
|
|
6
6
|
|
|
7
|
-
export { Api }
|
|
8
7
|
export { default as SolanaFeeMonitor } from './fee-monitor'
|
|
9
8
|
export { SolanaMonitor } from './tx-log'
|
|
10
9
|
export { SolanaAccountState } from './account-state'
|
|
@@ -26,3 +25,5 @@ const assets = connectAssets(keyBy(assetsList, (asset) => asset.name))
|
|
|
26
25
|
// Clients should not call an specific server api directly.
|
|
27
26
|
const serverApi = new Api({ assets })
|
|
28
27
|
export default serverApi
|
|
28
|
+
|
|
29
|
+
export { Api } from './api'
|
package/src/staking-utils.js
CHANGED
|
@@ -9,9 +9,7 @@ export const getSolStakedFee = ({ asset, stakingInfo, fee }) => {
|
|
|
9
9
|
const { accounts } = stakingInfo
|
|
10
10
|
|
|
11
11
|
const allPending = Object.entries(accounts).length
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return pendingFee
|
|
12
|
+
return allPending > 0 ? fee.mul(allPending) : currency.ZERO
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
export const getStakingInfo = (accountMem) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BaseMonitor } from '@exodus/asset-lib'
|
|
2
2
|
import _ from 'lodash'
|
|
3
3
|
import assert from 'minimalistic-assert'
|
|
4
|
+
import ms from 'ms'
|
|
4
5
|
|
|
5
6
|
const DEFAULT_POOL_ADDRESS = '9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF' // Everstake
|
|
6
7
|
|
|
@@ -12,6 +13,7 @@ const DEFAULT_REMOTE_CONFIG = {
|
|
|
12
13
|
|
|
13
14
|
const TICKS_BETWEEN_HISTORY_FETCHES = 10
|
|
14
15
|
const TICKS_BETWEEN_STAKE_FETCHES = 5
|
|
16
|
+
const TX_STALE_AFTER = ms('2m') // mark txs as dropped after N minutes
|
|
15
17
|
|
|
16
18
|
export class SolanaMonitor extends BaseMonitor {
|
|
17
19
|
constructor({
|
|
@@ -52,15 +54,10 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
52
54
|
})
|
|
53
55
|
return this.api.watchAddress({
|
|
54
56
|
address,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
handleAccounts: (updates) => this.accountsCallback({ updates, walletAccount }),
|
|
59
|
-
handleTransfers: (txs) => {
|
|
60
|
-
// new SOL tx, ticking monitor
|
|
61
|
-
this.tick({ walletAccount }) // it will cause refresh for both sender/receiver. Without necessarily fetching the tx if it's not finalized in the node.
|
|
57
|
+
onMessage: (json) => {
|
|
58
|
+
// new SOL tx event, tick monitor with 15 sec delay (to avoid hitting delayed nodes)
|
|
59
|
+
setTimeout(() => this.tick({ walletAccount }), 15_000)
|
|
62
60
|
},
|
|
63
|
-
*/
|
|
64
61
|
})
|
|
65
62
|
}
|
|
66
63
|
|
|
@@ -97,24 +94,41 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
97
94
|
|
|
98
95
|
async getStakingAddressesFromTxLog({ assetName, walletAccount }) {
|
|
99
96
|
const txLog = await this.aci.getTxLog({ assetName: this.asset.name, walletAccount })
|
|
100
|
-
const stakingAddresses =
|
|
97
|
+
const stakingAddresses = [...txLog]
|
|
101
98
|
.filter((tx) => _.get(tx, 'data.staking.stakeAddresses'))
|
|
102
99
|
.map((tx) => tx.data.staking.stakeAddresses)
|
|
103
|
-
return _.uniq(
|
|
100
|
+
return _.uniq(stakingAddresses.flat())
|
|
104
101
|
}
|
|
105
102
|
|
|
106
|
-
balanceChanged({ account, newAccount }) {
|
|
103
|
+
#balanceChanged({ account, newAccount }) {
|
|
107
104
|
const solBalanceChanged = !account.balance || !account.balance.equals(newAccount.balance)
|
|
108
105
|
if (solBalanceChanged) return true
|
|
109
106
|
|
|
110
|
-
|
|
107
|
+
// token balance changed
|
|
108
|
+
return (
|
|
111
109
|
!account.tokenBalances ||
|
|
112
110
|
Object.entries(newAccount.tokenBalances).some(
|
|
113
111
|
([token, balance]) =>
|
|
114
112
|
!account.tokenBalances[token] || !account.tokenBalances[token].equals(balance)
|
|
115
113
|
)
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async markStaleTransactions({ walletAccount, logItemsByAsset = {} }) {
|
|
118
|
+
// mark stale txs as dropped in logItemsByAsset
|
|
119
|
+
const clearedLogItems = logItemsByAsset
|
|
120
|
+
const tokenNames = [...this.api.tokens.values()].map(({ name }) => name)
|
|
121
|
+
const assets = [this.asset.name, ...tokenNames]
|
|
122
|
+
|
|
123
|
+
for (const assetName of assets) {
|
|
124
|
+
const txSet = await this.aci.getTxLog({ assetName, walletAccount })
|
|
125
|
+
const { stale } = this.getUnconfirmed({ txSet, staleTxAge: TX_STALE_AFTER })
|
|
126
|
+
if (stale.length > 0) {
|
|
127
|
+
clearedLogItems[assetName] = _.unionBy(stale, logItemsByAsset[assetName], 'txId')
|
|
128
|
+
}
|
|
129
|
+
}
|
|
116
130
|
|
|
117
|
-
return
|
|
131
|
+
return clearedLogItems
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
async tick({ walletAccount, refresh }) {
|
|
@@ -137,7 +151,7 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
137
151
|
const tokenAccounts = await this.api.getTokenAccountsByOwner(address)
|
|
138
152
|
const account = await this.getAccount({ address, staking, tokenAccounts })
|
|
139
153
|
|
|
140
|
-
const balanceChanged = this
|
|
154
|
+
const balanceChanged = this.#balanceChanged({ account: accountState, newAccount: account })
|
|
141
155
|
|
|
142
156
|
const isHistoryUpdateTick =
|
|
143
157
|
this.tickCount[walletAccount] % this.ticksBetweenHistoryFetches === 0
|
|
@@ -152,6 +166,7 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
152
166
|
await this.updateState({ account, walletAccount, staking })
|
|
153
167
|
await this.emitUnknownTokensEvent({ tokenAccounts })
|
|
154
168
|
}
|
|
169
|
+
|
|
155
170
|
if (shouldUpdateHistory) {
|
|
156
171
|
const { logItemsByAsset, cursorState } = await this.getHistory({
|
|
157
172
|
address,
|
|
@@ -163,7 +178,8 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
163
178
|
const cursorChanged = this.hasNewCursor({ walletAccount, cursorState })
|
|
164
179
|
|
|
165
180
|
// update all state at once
|
|
166
|
-
await this.
|
|
181
|
+
const clearedLogItems = await this.markStaleTransactions({ walletAccount, logItemsByAsset })
|
|
182
|
+
await this.updateTxLogByAsset({ walletAccount, logItemsByAsset: clearedLogItems, refresh })
|
|
167
183
|
await this.updateState({ account, cursorState, walletAccount, staking })
|
|
168
184
|
await this.emitUnknownTokensEvent({ tokenAccounts })
|
|
169
185
|
if (refresh || cursorChanged) {
|
|
@@ -173,7 +189,7 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
173
189
|
}
|
|
174
190
|
|
|
175
191
|
async getHistory({ address, accountState, refresh } = {}) {
|
|
176
|
-
|
|
192
|
+
const cursor = refresh ? '' : accountState.cursor
|
|
177
193
|
const baseAsset = this.asset
|
|
178
194
|
|
|
179
195
|
const { transactions, newCursor } = await this.api.getTransactions(address, {
|
|
@@ -226,6 +242,7 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
226
242
|
|
|
227
243
|
item.data.meta = tx.data.meta
|
|
228
244
|
}
|
|
245
|
+
|
|
229
246
|
if (asset.assetType === 'SOLANA_TOKEN' && item.feeAmount && item.feeAmount.isPositive) {
|
|
230
247
|
const feeItem = {
|
|
231
248
|
..._.clone(item),
|
|
@@ -235,6 +252,7 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
235
252
|
}
|
|
236
253
|
mappedTransactions.push(feeItem)
|
|
237
254
|
}
|
|
255
|
+
|
|
238
256
|
mappedTransactions.push(item)
|
|
239
257
|
}
|
|
240
258
|
|
package/src/tx-send.js
CHANGED
|
@@ -1,183 +1,181 @@
|
|
|
1
1
|
import { createUnsignedTx, findAssociatedTokenAddress } from '@exodus/solana-lib'
|
|
2
2
|
import assert from 'minimalistic-assert'
|
|
3
3
|
|
|
4
|
-
export const createAndBroadcastTXFactory =
|
|
5
|
-
|
|
6
|
-
{ assetClientInterface }
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
4
|
+
export const createAndBroadcastTXFactory =
|
|
5
|
+
(api) =>
|
|
6
|
+
async ({ asset, walletAccount, address, amount, options = {} }, { assetClientInterface }) => {
|
|
7
|
+
const assetName = asset.name
|
|
8
|
+
assert(assetClientInterface, `assetClientInterface must be supplied in sendTx for ${assetName}`)
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
feeAmount,
|
|
12
|
+
method,
|
|
13
|
+
stakeAddresses,
|
|
14
|
+
seed,
|
|
15
|
+
pool,
|
|
16
|
+
customMintAddress,
|
|
17
|
+
tokenStandard,
|
|
18
|
+
// <MagicEden>
|
|
19
|
+
initializerAddress,
|
|
20
|
+
initializerDepositTokenAddress,
|
|
21
|
+
takerAmount,
|
|
22
|
+
escrowAddress,
|
|
23
|
+
escrowBump,
|
|
24
|
+
pdaAddress,
|
|
25
|
+
takerAddress,
|
|
26
|
+
expectedTakerAmount,
|
|
27
|
+
expectedMintAddress,
|
|
28
|
+
metadataAddress,
|
|
29
|
+
creators,
|
|
30
|
+
// </MagicEden>
|
|
31
|
+
reference,
|
|
32
|
+
memo,
|
|
33
|
+
} = options
|
|
34
|
+
const { baseAsset } = asset
|
|
35
|
+
const from = await assetClientInterface.getReceiveAddress({
|
|
36
|
+
assetName: baseAsset.name,
|
|
37
|
+
walletAccount,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const isToken = asset.assetType === 'SOLANA_TOKEN'
|
|
41
|
+
|
|
42
|
+
// Check if receiver has address active when sending tokens.
|
|
43
|
+
if (isToken) {
|
|
44
|
+
// check address mint is the same
|
|
45
|
+
const targetMint = await api.getAddressMint(address) // null if it's a SOL address
|
|
46
|
+
if (targetMint && targetMint !== asset.mintAddress) {
|
|
47
|
+
const err = new Error('Wrong Destination Wallet')
|
|
48
|
+
err.reason = { mintAddressMismatch: true }
|
|
49
|
+
throw err
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// sending SOL
|
|
53
|
+
const addressType = await api.getAddressType(address)
|
|
54
|
+
if (addressType === 'token') {
|
|
55
|
+
const err = new Error('Destination Wallet is a Token address')
|
|
56
|
+
err.reason = { wrongAddressType: true }
|
|
57
|
+
throw err
|
|
58
|
+
}
|
|
51
59
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
|
|
61
|
+
const recentBlockhash = options.recentBlockhash || (await api.getRecentBlockHash())
|
|
62
|
+
|
|
63
|
+
let tokenParams = Object.create(null)
|
|
64
|
+
if (isToken || customMintAddress) {
|
|
65
|
+
const tokenMintAddress = customMintAddress || asset.mintAddress
|
|
66
|
+
const tokenAddress = findAssociatedTokenAddress(address, tokenMintAddress)
|
|
67
|
+
const [destinationAddressType, isAssociatedTokenAccountActive, fromTokenAccountAddresses] =
|
|
68
|
+
await Promise.all([
|
|
69
|
+
api.getAddressType(address),
|
|
70
|
+
api.isAssociatedTokenAccountActive(tokenAddress),
|
|
71
|
+
api.getTokenAccountsByOwner(from),
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
const fromTokenAddresses = fromTokenAccountAddresses.filter(
|
|
75
|
+
({ mintAddress }) => mintAddress === tokenMintAddress
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
tokenParams = {
|
|
79
|
+
tokenMintAddress,
|
|
80
|
+
destinationAddressType,
|
|
81
|
+
isAssociatedTokenAccountActive,
|
|
82
|
+
fromTokenAddresses,
|
|
83
|
+
tokenStandard,
|
|
84
|
+
}
|
|
59
85
|
}
|
|
60
|
-
}
|
|
61
86
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const tokenAddress = findAssociatedTokenAddress(address, tokenMintAddress)
|
|
68
|
-
const [
|
|
69
|
-
destinationAddressType,
|
|
70
|
-
isAssociatedTokenAccountActive,
|
|
71
|
-
fromTokenAccountAddresses,
|
|
72
|
-
] = await Promise.all([
|
|
73
|
-
api.getAddressType(address),
|
|
74
|
-
api.isAssociatedTokenAccountActive(tokenAddress),
|
|
75
|
-
api.getTokenAccountsByOwner(from),
|
|
76
|
-
])
|
|
77
|
-
|
|
78
|
-
const fromTokenAddresses = fromTokenAccountAddresses.filter(
|
|
79
|
-
({ mintAddress }) => mintAddress === tokenMintAddress
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
tokenParams = {
|
|
83
|
-
tokenMintAddress,
|
|
84
|
-
destinationAddressType,
|
|
85
|
-
isAssociatedTokenAccountActive,
|
|
86
|
-
fromTokenAddresses,
|
|
87
|
-
tokenStandard,
|
|
87
|
+
const stakingParams = {
|
|
88
|
+
method,
|
|
89
|
+
stakeAddresses,
|
|
90
|
+
seed,
|
|
91
|
+
pool,
|
|
88
92
|
}
|
|
89
|
-
}
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
const magicEdenParams = {
|
|
95
|
+
method,
|
|
96
|
+
initializerAddress,
|
|
97
|
+
initializerDepositTokenAddress,
|
|
98
|
+
takerAmount,
|
|
99
|
+
escrowAddress,
|
|
100
|
+
escrowBump,
|
|
101
|
+
pdaAddress,
|
|
102
|
+
takerAddress,
|
|
103
|
+
expectedTakerAmount,
|
|
104
|
+
expectedMintAddress,
|
|
105
|
+
metadataAddress,
|
|
106
|
+
creators,
|
|
107
|
+
}
|
|
97
108
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
109
|
+
const unsignedTransaction = createUnsignedTx({
|
|
110
|
+
asset,
|
|
111
|
+
from,
|
|
112
|
+
to: address,
|
|
113
|
+
amount,
|
|
114
|
+
fee: feeAmount,
|
|
115
|
+
recentBlockhash,
|
|
116
|
+
reference,
|
|
117
|
+
memo,
|
|
118
|
+
...tokenParams,
|
|
119
|
+
...stakingParams,
|
|
120
|
+
...magicEdenParams,
|
|
121
|
+
})
|
|
112
122
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
reference,
|
|
121
|
-
memo,
|
|
122
|
-
...tokenParams,
|
|
123
|
-
...stakingParams,
|
|
124
|
-
...magicEdenParams,
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
const { txId, rawTx } = await assetClientInterface.signTransaction({
|
|
128
|
-
assetName: baseAsset.name,
|
|
129
|
-
unsignedTx: unsignedTransaction,
|
|
130
|
-
walletAccount,
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
await baseAsset.api.broadcastTx(rawTx)
|
|
134
|
-
|
|
135
|
-
const selfSend = from === address
|
|
136
|
-
const isStakingTx = ['delegate', 'undelegate', 'withdraw'].includes(method)
|
|
137
|
-
const coinAmount = isStakingTx
|
|
138
|
-
? amount.abs()
|
|
139
|
-
: selfSend
|
|
140
|
-
? asset.currency.ZERO
|
|
141
|
-
: amount.abs().negate()
|
|
142
|
-
|
|
143
|
-
const data = isStakingTx ? { staking: stakingParams } : Object.create(null)
|
|
144
|
-
const tx = {
|
|
145
|
-
txId,
|
|
146
|
-
confirmations: 0,
|
|
147
|
-
coinName: assetName,
|
|
148
|
-
coinAmount,
|
|
149
|
-
feeAmount,
|
|
150
|
-
feeCoinName: asset.feeAsset.name,
|
|
151
|
-
selfSend,
|
|
152
|
-
to: address,
|
|
153
|
-
data,
|
|
154
|
-
currencies: { [assetName]: asset.currency, [asset.feeAsset.name]: asset.feeAsset.currency },
|
|
155
|
-
}
|
|
156
|
-
await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
|
|
123
|
+
const { txId, rawTx } = await assetClientInterface.signTransaction({
|
|
124
|
+
assetName: baseAsset.name,
|
|
125
|
+
unsignedTx: unsignedTransaction,
|
|
126
|
+
walletAccount,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await baseAsset.api.broadcastTx(rawTx)
|
|
157
130
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
131
|
+
const selfSend = from === address
|
|
132
|
+
const isStakingTx = ['delegate', 'undelegate', 'withdraw'].includes(method)
|
|
133
|
+
const coinAmount = isStakingTx
|
|
134
|
+
? amount.abs()
|
|
135
|
+
: selfSend
|
|
136
|
+
? asset.currency.ZERO
|
|
137
|
+
: amount.abs().negate()
|
|
138
|
+
|
|
139
|
+
const data = isStakingTx
|
|
140
|
+
? { staking: { ...stakingParams, stake: coinAmount.toBaseNumber() } }
|
|
141
|
+
: Object.create(null)
|
|
142
|
+
const tx = {
|
|
161
143
|
txId,
|
|
162
144
|
confirmations: 0,
|
|
163
|
-
coinName:
|
|
164
|
-
coinAmount
|
|
165
|
-
tokens: [assetName],
|
|
145
|
+
coinName: assetName,
|
|
146
|
+
coinAmount,
|
|
166
147
|
feeAmount,
|
|
167
|
-
feeCoinName:
|
|
168
|
-
to: address,
|
|
148
|
+
feeCoinName: asset.feeAsset.name,
|
|
169
149
|
selfSend,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
150
|
+
to: address,
|
|
151
|
+
data,
|
|
152
|
+
currencies: { [assetName]: asset.currency, [asset.feeAsset.name]: asset.feeAsset.currency },
|
|
153
|
+
}
|
|
154
|
+
await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
|
|
155
|
+
|
|
156
|
+
if (isToken) {
|
|
157
|
+
// write tx entry in solana for token fee
|
|
158
|
+
const txForFee = {
|
|
159
|
+
txId,
|
|
160
|
+
confirmations: 0,
|
|
161
|
+
coinName: baseAsset.name,
|
|
162
|
+
coinAmount: baseAsset.currency.ZERO,
|
|
163
|
+
tokens: [assetName],
|
|
164
|
+
feeAmount,
|
|
165
|
+
feeCoinName: baseAsset.feeAsset.name,
|
|
166
|
+
to: address,
|
|
167
|
+
selfSend,
|
|
168
|
+
currencies: {
|
|
169
|
+
[baseAsset.name]: baseAsset.currency,
|
|
170
|
+
[baseAsset.feeAsset.name]: baseAsset.feeAsset.currency,
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
await assetClientInterface.updateTxLogAndNotify({
|
|
174
|
+
assetName: baseAsset.name,
|
|
175
|
+
walletAccount,
|
|
176
|
+
txs: [txForFee],
|
|
177
|
+
})
|
|
174
178
|
}
|
|
175
|
-
await assetClientInterface.updateTxLogAndNotify({
|
|
176
|
-
assetName: baseAsset.name,
|
|
177
|
-
walletAccount,
|
|
178
|
-
txs: [txForFee],
|
|
179
|
-
})
|
|
180
|
-
}
|
|
181
179
|
|
|
182
|
-
|
|
183
|
-
}
|
|
180
|
+
return { txId }
|
|
181
|
+
}
|