@exodus/solana-api 2.5.31-alpha.3 → 2.5.31
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 +12 -2
- package/src/account-state.js +1 -0
- package/src/api.js +52 -46
- 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 +23 -66
- package/src/tx-send.js +162 -166
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-api",
|
|
3
|
-
"version": "2.5.31
|
|
3
|
+
"version": "2.5.31",
|
|
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
24
|
"@exodus/asset-lib": "^4.0.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
47
|
"gitHead": "9ec7084bcdfe14f1c6f11df393351885773046a6"
|
|
38
48
|
}
|
package/src/account-state.js
CHANGED
package/src/api.js
CHANGED
|
@@ -25,7 +25,6 @@ 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 {
|
|
@@ -60,16 +59,18 @@ export class Api {
|
|
|
60
59
|
async watchAddress({
|
|
61
60
|
address,
|
|
62
61
|
tokensAddresses = [],
|
|
62
|
+
onMessage,
|
|
63
63
|
handleAccounts,
|
|
64
64
|
handleTransfers,
|
|
65
65
|
handleReconnect,
|
|
66
66
|
reconnectDelay,
|
|
67
67
|
}) {
|
|
68
|
-
if (
|
|
68
|
+
if (this.connections[address]) return // already subscribed
|
|
69
69
|
const conn = new Connection({
|
|
70
70
|
endpoint: this.wsUrl,
|
|
71
71
|
address,
|
|
72
72
|
tokensAddresses,
|
|
73
|
+
onMsg: (json) => onMessage(json),
|
|
73
74
|
callback: (updates) =>
|
|
74
75
|
this.handleUpdates({ updates, address, handleAccounts, handleTransfers }),
|
|
75
76
|
reconnectCallback: handleReconnect,
|
|
@@ -98,6 +99,7 @@ export class Api {
|
|
|
98
99
|
if (lodash.get(connection, 'isOpen') && !lodash.get(connection, 'shutdown') && !forceHttp) {
|
|
99
100
|
return connection.sendMessage(method, params)
|
|
100
101
|
}
|
|
102
|
+
|
|
101
103
|
// http fallback
|
|
102
104
|
return this.api.post({ method, params })
|
|
103
105
|
}
|
|
@@ -120,7 +122,7 @@ export class Api {
|
|
|
120
122
|
return state
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
async getRecentBlockHash(commitment
|
|
125
|
+
async getRecentBlockHash(commitment) {
|
|
124
126
|
const result = await this.rpcCall(
|
|
125
127
|
'getLatestBlockhash',
|
|
126
128
|
[{ commitment: commitment || 'finalized', encoding: 'jsonParsed' }],
|
|
@@ -169,7 +171,7 @@ export class Api {
|
|
|
169
171
|
// cursor is a txHash
|
|
170
172
|
|
|
171
173
|
try {
|
|
172
|
-
|
|
174
|
+
const until = cursor
|
|
173
175
|
|
|
174
176
|
const tokenAccountsByOwner = await this.getTokenAccountsByOwner(address) // Array
|
|
175
177
|
const tokenAccountAddresses = tokenAccountsByOwner
|
|
@@ -186,7 +188,7 @@ export class Api {
|
|
|
186
188
|
})
|
|
187
189
|
)
|
|
188
190
|
)
|
|
189
|
-
let txsId = txsResultsByAccount.
|
|
191
|
+
let txsId = txsResultsByAccount.flat() // merge arrays
|
|
190
192
|
txsId = lodash.uniqBy(txsId, 'signature')
|
|
191
193
|
|
|
192
194
|
// get txs details in parallel
|
|
@@ -236,14 +238,8 @@ export class Api {
|
|
|
236
238
|
tokenAccountsByOwner,
|
|
237
239
|
{ includeUnparsed = false } = {}
|
|
238
240
|
) {
|
|
239
|
-
let {
|
|
240
|
-
|
|
241
|
-
preBalances,
|
|
242
|
-
postBalances,
|
|
243
|
-
preTokenBalances,
|
|
244
|
-
postTokenBalances,
|
|
245
|
-
innerInstructions,
|
|
246
|
-
} = txDetails.meta
|
|
241
|
+
let { fee, preBalances, postBalances, preTokenBalances, postTokenBalances, innerInstructions } =
|
|
242
|
+
txDetails.meta
|
|
247
243
|
preBalances = preBalances || []
|
|
248
244
|
postBalances = postBalances || []
|
|
249
245
|
preTokenBalances = preTokenBalances || []
|
|
@@ -339,7 +335,7 @@ export class Api {
|
|
|
339
335
|
}))
|
|
340
336
|
innerInstructions = innerInstructions
|
|
341
337
|
.reduce((acc, val) => {
|
|
342
|
-
return acc
|
|
338
|
+
return [...acc, ...val.instructions]
|
|
343
339
|
}, [])
|
|
344
340
|
.map((ix) => {
|
|
345
341
|
const type = lodash.get(ix, 'parsed.type')
|
|
@@ -355,7 +351,7 @@ export class Api {
|
|
|
355
351
|
const tokenAccount = tokenAccountsByOwner.find(({ tokenAccountAddress }) => {
|
|
356
352
|
return [source, destination].includes(tokenAccountAddress)
|
|
357
353
|
})
|
|
358
|
-
const isSending =
|
|
354
|
+
const isSending = tokenAccountsByOwner.some(({ tokenAccountAddress }) => {
|
|
359
355
|
return [source].includes(tokenAccountAddress)
|
|
360
356
|
})
|
|
361
357
|
|
|
@@ -382,7 +378,7 @@ export class Api {
|
|
|
382
378
|
const stakeTx = lodash.find(instructions, { program: 'system', type: 'createAccountWithSeed' })
|
|
383
379
|
const stakeWithdraw = lodash.find(instructions, { program: 'stake', type: 'withdraw' })
|
|
384
380
|
const stakeUndelegate = lodash.find(instructions, { program: 'stake', type: 'deactivate' })
|
|
385
|
-
const hasSolanaTx = solanaTx &&
|
|
381
|
+
const hasSolanaTx = solanaTx && preTokenBalances.length === 0 && postTokenBalances.length === 0 // only SOL moved and no tokens movements
|
|
386
382
|
|
|
387
383
|
let tx = {}
|
|
388
384
|
if (hasSolanaTx) {
|
|
@@ -449,7 +445,7 @@ export class Api {
|
|
|
449
445
|
Array.isArray(tokenAccountsByOwner),
|
|
450
446
|
'tokenAccountsByOwner is required when parsing token tx'
|
|
451
447
|
)
|
|
452
|
-
|
|
448
|
+
const tokenTxs = lodash
|
|
453
449
|
.filter(instructions, ({ program, type }) => {
|
|
454
450
|
return program === 'spl-token' && ['transfer', 'transferChecked'].includes(type)
|
|
455
451
|
}) // get Token transfer: could have more than 1 instructions
|
|
@@ -476,7 +472,7 @@ export class Api {
|
|
|
476
472
|
}
|
|
477
473
|
})
|
|
478
474
|
|
|
479
|
-
if (tokenTxs.length) {
|
|
475
|
+
if (tokenTxs.length > 0) {
|
|
480
476
|
// found spl-token simple transfer/transferChecked instruction
|
|
481
477
|
// .reduce to sum/sub (based on isSending) all the same tokens amount (From instructions -> 1 single tx)
|
|
482
478
|
tx = tokenTxs.reduce((finalTx, ix) => {
|
|
@@ -491,9 +487,11 @@ export class Api {
|
|
|
491
487
|
const accountIndexes = lodash.mapKeys(accountKeys, (x, i) => i)
|
|
492
488
|
Object.values(accountIndexes).forEach((acc) => {
|
|
493
489
|
// filter by ownerAddress
|
|
490
|
+
// eslint-disable-next-line unicorn/prefer-array-some
|
|
494
491
|
const hasKnownOwner = !!lodash.find(tokenAccountsByOwner, {
|
|
495
492
|
tokenAccountAddress: acc.pubkey,
|
|
496
493
|
})
|
|
494
|
+
// eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
|
|
497
495
|
acc.owner = hasKnownOwner ? ownerAddress : null
|
|
498
496
|
})
|
|
499
497
|
|
|
@@ -509,15 +507,15 @@ export class Api {
|
|
|
509
507
|
)
|
|
510
508
|
})
|
|
511
509
|
|
|
512
|
-
if (preBalances.length || postBalances.length) {
|
|
510
|
+
if (preBalances.length > 0 || postBalances.length > 0) {
|
|
513
511
|
tx = {}
|
|
514
512
|
|
|
515
|
-
if (includeUnparsed && innerInstructions.length) {
|
|
513
|
+
if (includeUnparsed && innerInstructions.length > 0) {
|
|
516
514
|
// when using includeUnparsed for DEX tx we want to keep SOL tx as "unparsed"
|
|
517
515
|
// 1. we want to treat all SOL dex transactions as "Contract transaction", not "Sent SOL"
|
|
518
516
|
// 2. default behavior is not perfect. For example it doesn't see SOL-side tx in
|
|
519
517
|
// SOL->SPL swaps on Raydium and Orca.
|
|
520
|
-
tx = getUnparsedTx(
|
|
518
|
+
tx = getUnparsedTx()
|
|
521
519
|
tx.dexTxs = getInnerTxsFromBalanceChanges()
|
|
522
520
|
} else {
|
|
523
521
|
if (solanaTx) {
|
|
@@ -532,7 +530,7 @@ export class Api {
|
|
|
532
530
|
}
|
|
533
531
|
|
|
534
532
|
// If it has inner instructions then it's a DEX tx that moved SPL -> SPL
|
|
535
|
-
if (innerInstructions.length) {
|
|
533
|
+
if (innerInstructions.length > 0) {
|
|
536
534
|
tx.dexTxs = innerInstructions
|
|
537
535
|
// if tx involves only SPL swaps. Expand DEX ix (first element as tx base and the other kept there)
|
|
538
536
|
if (!tx.from && !solanaTx) {
|
|
@@ -548,7 +546,7 @@ export class Api {
|
|
|
548
546
|
const unparsed = Object.keys(tx).length === 0
|
|
549
547
|
|
|
550
548
|
if (unparsed && includeUnparsed) {
|
|
551
|
-
tx = getUnparsedTx(
|
|
549
|
+
tx = getUnparsedTx()
|
|
552
550
|
}
|
|
553
551
|
|
|
554
552
|
// How tokens tx are parsed:
|
|
@@ -572,7 +570,7 @@ export class Api {
|
|
|
572
570
|
|
|
573
571
|
async getWalletTokensList({ tokenAccounts }) {
|
|
574
572
|
const tokensMint = []
|
|
575
|
-
for (
|
|
573
|
+
for (const account of tokenAccounts) {
|
|
576
574
|
const mint = account.mintAddress
|
|
577
575
|
|
|
578
576
|
// skip cached NFT
|
|
@@ -585,6 +583,7 @@ export class Api {
|
|
|
585
583
|
this.tokensToSkip[mint] = true
|
|
586
584
|
continue
|
|
587
585
|
}
|
|
586
|
+
|
|
588
587
|
// OK
|
|
589
588
|
tokensMint.push(mint)
|
|
590
589
|
}
|
|
@@ -600,7 +599,7 @@ export class Api {
|
|
|
600
599
|
)
|
|
601
600
|
|
|
602
601
|
const tokenAccounts = []
|
|
603
|
-
for (
|
|
602
|
+
for (const entry of accountsList) {
|
|
604
603
|
const { pubkey, account } = entry
|
|
605
604
|
|
|
606
605
|
const mint = lodash.get(account, 'data.parsed.info.mint')
|
|
@@ -618,6 +617,7 @@ export class Api {
|
|
|
618
617
|
mintAddress: mint,
|
|
619
618
|
})
|
|
620
619
|
}
|
|
620
|
+
|
|
621
621
|
// eventually filter by token
|
|
622
622
|
return tokenTicker
|
|
623
623
|
? tokenAccounts.filter(({ ticker }) => ticker === tokenTicker)
|
|
@@ -625,18 +625,24 @@ export class Api {
|
|
|
625
625
|
}
|
|
626
626
|
|
|
627
627
|
async getTokensBalance({ address, filterByTokens = [], tokenAccounts }) {
|
|
628
|
-
|
|
628
|
+
const accounts = tokenAccounts || (await this.getTokenAccountsByOwner(address))
|
|
629
629
|
|
|
630
|
-
|
|
631
|
-
if (
|
|
630
|
+
return accounts.reduce((acc, { tokenName, balance }) => {
|
|
631
|
+
if (
|
|
632
|
+
tokenName === 'unknown' ||
|
|
633
|
+
(filterByTokens.length > 0 && !filterByTokens.includes(tokenName))
|
|
634
|
+
)
|
|
632
635
|
return acc // filter by supported tokens only
|
|
633
|
-
if (
|
|
636
|
+
if (acc[tokenName]) {
|
|
637
|
+
acc[tokenName] += Number(balance)
|
|
638
|
+
}
|
|
634
639
|
// e.g { 'serum': 123 }
|
|
635
|
-
else
|
|
640
|
+
else {
|
|
641
|
+
acc[tokenName] = Number(balance)
|
|
642
|
+
} // merge same token account balance
|
|
643
|
+
|
|
636
644
|
return acc
|
|
637
645
|
}, {})
|
|
638
|
-
|
|
639
|
-
return tokensBalance
|
|
640
646
|
}
|
|
641
647
|
|
|
642
648
|
async isAssociatedTokenAccountActive(tokenAddress) {
|
|
@@ -644,7 +650,7 @@ export class Api {
|
|
|
644
650
|
try {
|
|
645
651
|
await this.rpcCall('getTokenAccountBalance', [tokenAddress])
|
|
646
652
|
return true
|
|
647
|
-
} catch
|
|
653
|
+
} catch {
|
|
648
654
|
return false
|
|
649
655
|
}
|
|
650
656
|
}
|
|
@@ -697,20 +703,19 @@ export class Api {
|
|
|
697
703
|
return account.owner === SYSTEM_PROGRAM_ID.toBase58()
|
|
698
704
|
? 'solana'
|
|
699
705
|
: account.owner === TOKEN_PROGRAM_ID.toBase58()
|
|
700
|
-
|
|
701
|
-
|
|
706
|
+
? 'token'
|
|
707
|
+
: null
|
|
702
708
|
}
|
|
703
709
|
|
|
704
710
|
async getTokenAddressOwner(address) {
|
|
705
711
|
const value = await this.getAccountInfo(address)
|
|
706
|
-
|
|
707
|
-
return owner
|
|
712
|
+
return lodash.get(value, 'data.parsed.info.owner', null)
|
|
708
713
|
}
|
|
709
714
|
|
|
710
715
|
async getAddressMint(address) {
|
|
711
716
|
const value = await this.getAccountInfo(address)
|
|
712
|
-
|
|
713
|
-
return
|
|
717
|
+
// token mint
|
|
718
|
+
return lodash.get(value, 'data.parsed.info.mint', null)
|
|
714
719
|
}
|
|
715
720
|
|
|
716
721
|
async isTokenAddress(address) {
|
|
@@ -745,7 +750,7 @@ export class Api {
|
|
|
745
750
|
let locked = 0
|
|
746
751
|
let withdrawable = 0
|
|
747
752
|
let pending = 0
|
|
748
|
-
for (
|
|
753
|
+
for (const entry of res) {
|
|
749
754
|
const addr = entry.pubkey
|
|
750
755
|
const lamports = lodash.get(entry, 'account.lamports', 0)
|
|
751
756
|
const delegation = lodash.get(entry, 'account.data.parsed.info.stake.delegation', {})
|
|
@@ -766,11 +771,12 @@ export class Api {
|
|
|
766
771
|
withdrawable += accounts[addr].canWithdraw ? lamports : 0
|
|
767
772
|
pending += accounts[addr].isDeactivating ? lamports : 0
|
|
768
773
|
}
|
|
774
|
+
|
|
769
775
|
return { accounts, totalStake, locked, withdrawable, pending }
|
|
770
776
|
}
|
|
771
777
|
|
|
772
778
|
async getRewards(stakingAddresses = []) {
|
|
773
|
-
if (
|
|
779
|
+
if (stakingAddresses.length === 0) return 0
|
|
774
780
|
|
|
775
781
|
// custom endpoint!
|
|
776
782
|
const rewards = await this.request('rewards')
|
|
@@ -780,11 +786,9 @@ export class Api {
|
|
|
780
786
|
.json()
|
|
781
787
|
|
|
782
788
|
// sum rewards for all addresses
|
|
783
|
-
|
|
789
|
+
return Object.values(rewards).reduce((total, x) => {
|
|
784
790
|
return total + x
|
|
785
791
|
}, 0)
|
|
786
|
-
|
|
787
|
-
return earnings
|
|
788
792
|
}
|
|
789
793
|
|
|
790
794
|
async getMinimumBalanceForRentExemption(size) {
|
|
@@ -820,7 +824,7 @@ export class Api {
|
|
|
820
824
|
} catch (error) {
|
|
821
825
|
if (
|
|
822
826
|
error.message &&
|
|
823
|
-
!errorMessagesToRetry.
|
|
827
|
+
!errorMessagesToRetry.some((errorMessage) => error.message.includes(errorMessage))
|
|
824
828
|
) {
|
|
825
829
|
error.finalError = true
|
|
826
830
|
}
|
|
@@ -868,6 +872,7 @@ export class Api {
|
|
|
868
872
|
if (error.message && error.message.includes('could not find account')) {
|
|
869
873
|
return defaultValue
|
|
870
874
|
}
|
|
875
|
+
|
|
871
876
|
throw error
|
|
872
877
|
}
|
|
873
878
|
}
|
|
@@ -959,11 +964,12 @@ export class Api {
|
|
|
959
964
|
simulateAndRetrieveSideEffects = async (
|
|
960
965
|
message,
|
|
961
966
|
publicKey,
|
|
962
|
-
transactionMessage
|
|
967
|
+
transactionMessage // decompiled TransactionMessage
|
|
963
968
|
) => {
|
|
964
969
|
const { config, accountAddresses } = getTransactionSimulationParams(
|
|
965
970
|
transactionMessage || message
|
|
966
971
|
)
|
|
972
|
+
// eslint-disable-next-line unicorn/no-new-array
|
|
967
973
|
const signatures = new Array(message.header.numRequiredSignatures || 1).fill(null)
|
|
968
974
|
const encodedTransaction = buildRawTransaction(
|
|
969
975
|
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) => {
|
|
@@ -10,25 +10,14 @@ const DEFAULT_REMOTE_CONFIG = {
|
|
|
10
10
|
staking: { enabled: true, pool: DEFAULT_POOL_ADDRESS },
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const TICKS_BETWEEN_HISTORY_FETCHES = 10
|
|
14
|
-
const TICKS_BETWEEN_STAKE_FETCHES = 5
|
|
15
|
-
|
|
16
13
|
export class SolanaMonitor extends BaseMonitor {
|
|
17
|
-
constructor({
|
|
18
|
-
api,
|
|
19
|
-
includeUnparsed = false,
|
|
20
|
-
ticksBetweenHistoryFetches = TICKS_BETWEEN_HISTORY_FETCHES,
|
|
21
|
-
ticksBetweenStakeFetches = TICKS_BETWEEN_STAKE_FETCHES,
|
|
22
|
-
...args
|
|
23
|
-
}) {
|
|
14
|
+
constructor({ api, includeUnparsed = false, ...args }) {
|
|
24
15
|
super(args)
|
|
25
16
|
assert(api, 'api is required')
|
|
26
17
|
this.api = api
|
|
27
18
|
this.cursors = {}
|
|
28
19
|
this.assets = {}
|
|
29
20
|
this.staking = DEFAULT_REMOTE_CONFIG.staking
|
|
30
|
-
this.ticksBetweenStakeFetches = ticksBetweenStakeFetches
|
|
31
|
-
this.ticksBetweenHistoryFetches = ticksBetweenHistoryFetches
|
|
32
21
|
this.includeUnparsed = includeUnparsed
|
|
33
22
|
this.addHook('before-stop', (...args) => this.beforeStop(...args))
|
|
34
23
|
}
|
|
@@ -52,15 +41,10 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
52
41
|
})
|
|
53
42
|
return this.api.watchAddress({
|
|
54
43
|
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.
|
|
44
|
+
onMessage: (json) => {
|
|
45
|
+
// new SOL tx event, tick monitor with 15 sec delay (to avoid hitting delayed nodes)
|
|
46
|
+
setTimeout(() => this.tick({ walletAccount }), 15_000)
|
|
62
47
|
},
|
|
63
|
-
*/
|
|
64
48
|
})
|
|
65
49
|
}
|
|
66
50
|
|
|
@@ -97,24 +81,10 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
97
81
|
|
|
98
82
|
async getStakingAddressesFromTxLog({ assetName, walletAccount }) {
|
|
99
83
|
const txLog = await this.aci.getTxLog({ assetName: this.asset.name, walletAccount })
|
|
100
|
-
const stakingAddresses =
|
|
84
|
+
const stakingAddresses = [...txLog]
|
|
101
85
|
.filter((tx) => _.get(tx, 'data.staking.stakeAddresses'))
|
|
102
86
|
.map((tx) => tx.data.staking.stakeAddresses)
|
|
103
|
-
return _.uniq(
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
balanceChanged({ account, newAccount }) {
|
|
107
|
-
const solBalanceChanged = !account.balance || !account.balance.equals(newAccount.balance)
|
|
108
|
-
if (solBalanceChanged) return true
|
|
109
|
-
|
|
110
|
-
const tokenBalanceChanged =
|
|
111
|
-
!account.tokenBalances ||
|
|
112
|
-
Object.entries(newAccount.tokenBalances).some(
|
|
113
|
-
([token, balance]) =>
|
|
114
|
-
!account.tokenBalances[token] || !account.tokenBalances[token].equals(balance)
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
return tokenBalanceChanged
|
|
87
|
+
return _.uniq(stakingAddresses.flat())
|
|
118
88
|
}
|
|
119
89
|
|
|
120
90
|
async tick({ walletAccount, refresh }) {
|
|
@@ -129,43 +99,28 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
129
99
|
const address = await this.aci.getReceiveAddress({ assetName, walletAccount, useCache: true })
|
|
130
100
|
const stakingAddresses = await this.getStakingAddressesFromTxLog({ assetName, walletAccount })
|
|
131
101
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const account = await this.getAccount({ address, staking, tokenAccounts })
|
|
102
|
+
const { logItemsByAsset, hasNewTxs, cursorState } = await this.getHistory({
|
|
103
|
+
address,
|
|
104
|
+
accountState,
|
|
105
|
+
walletAccount,
|
|
106
|
+
refresh,
|
|
107
|
+
})
|
|
139
108
|
|
|
140
|
-
const
|
|
109
|
+
const cursorChanged = this.hasNewCursor({ walletAccount, cursorState })
|
|
141
110
|
|
|
142
|
-
|
|
143
|
-
|
|
111
|
+
if (refresh || hasNewTxs || cursorChanged) {
|
|
112
|
+
const staking =
|
|
113
|
+
refresh || cursorChanged
|
|
114
|
+
? await this.getStakingInfo({ address, stakingAddresses })
|
|
115
|
+
: accountState.mem
|
|
144
116
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const shouldUpdateBalanceBeforeHistory = true
|
|
117
|
+
const tokenAccounts = await this.api.getTokenAccountsByOwner(address)
|
|
118
|
+
const account = await this.getAccount({ address, staking, tokenAccounts })
|
|
148
119
|
|
|
149
|
-
// getHistory is more likely to fail/be rate limited, so we want to update users balance only on a lot of ticks
|
|
150
|
-
if (shouldUpdateBalanceBeforeHistory || shouldUpdateOnlyBalance) {
|
|
151
120
|
// update all state at once
|
|
152
|
-
await this.updateState({ account, walletAccount, staking })
|
|
153
121
|
await this.emitUnknownTokensEvent({ tokenAccounts })
|
|
154
|
-
}
|
|
155
|
-
if (shouldUpdateHistory) {
|
|
156
|
-
const { logItemsByAsset, cursorState } = await this.getHistory({
|
|
157
|
-
address,
|
|
158
|
-
accountState,
|
|
159
|
-
walletAccount,
|
|
160
|
-
refresh,
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
const cursorChanged = this.hasNewCursor({ walletAccount, cursorState })
|
|
164
|
-
|
|
165
|
-
// update all state at once
|
|
166
122
|
await this.updateTxLogByAsset({ walletAccount, logItemsByAsset, refresh })
|
|
167
123
|
await this.updateState({ account, cursorState, walletAccount, staking })
|
|
168
|
-
await this.emitUnknownTokensEvent({ tokenAccounts })
|
|
169
124
|
if (refresh || cursorChanged) {
|
|
170
125
|
this.cursors[walletAccount] = cursorState.cursor
|
|
171
126
|
}
|
|
@@ -173,7 +128,7 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
173
128
|
}
|
|
174
129
|
|
|
175
130
|
async getHistory({ address, accountState, refresh } = {}) {
|
|
176
|
-
|
|
131
|
+
const cursor = refresh ? '' : accountState.cursor
|
|
177
132
|
const baseAsset = this.asset
|
|
178
133
|
|
|
179
134
|
const { transactions, newCursor } = await this.api.getTransactions(address, {
|
|
@@ -226,6 +181,7 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
226
181
|
|
|
227
182
|
item.data.meta = tx.data.meta
|
|
228
183
|
}
|
|
184
|
+
|
|
229
185
|
if (asset.assetType === 'SOLANA_TOKEN' && item.feeAmount && item.feeAmount.isPositive) {
|
|
230
186
|
const feeItem = {
|
|
231
187
|
..._.clone(item),
|
|
@@ -235,6 +191,7 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
235
191
|
}
|
|
236
192
|
mappedTransactions.push(feeItem)
|
|
237
193
|
}
|
|
194
|
+
|
|
238
195
|
mappedTransactions.push(item)
|
|
239
196
|
}
|
|
240
197
|
|
package/src/tx-send.js
CHANGED
|
@@ -1,183 +1,179 @@
|
|
|
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 ? { staking: stakingParams } : Object.create(null)
|
|
140
|
+
const tx = {
|
|
161
141
|
txId,
|
|
162
142
|
confirmations: 0,
|
|
163
|
-
coinName:
|
|
164
|
-
coinAmount
|
|
165
|
-
tokens: [assetName],
|
|
143
|
+
coinName: assetName,
|
|
144
|
+
coinAmount,
|
|
166
145
|
feeAmount,
|
|
167
|
-
feeCoinName:
|
|
168
|
-
to: address,
|
|
146
|
+
feeCoinName: asset.feeAsset.name,
|
|
169
147
|
selfSend,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
148
|
+
to: address,
|
|
149
|
+
data,
|
|
150
|
+
currencies: { [assetName]: asset.currency, [asset.feeAsset.name]: asset.feeAsset.currency },
|
|
151
|
+
}
|
|
152
|
+
await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
|
|
153
|
+
|
|
154
|
+
if (isToken) {
|
|
155
|
+
// write tx entry in solana for token fee
|
|
156
|
+
const txForFee = {
|
|
157
|
+
txId,
|
|
158
|
+
confirmations: 0,
|
|
159
|
+
coinName: baseAsset.name,
|
|
160
|
+
coinAmount: baseAsset.currency.ZERO,
|
|
161
|
+
tokens: [assetName],
|
|
162
|
+
feeAmount,
|
|
163
|
+
feeCoinName: baseAsset.feeAsset.name,
|
|
164
|
+
to: address,
|
|
165
|
+
selfSend,
|
|
166
|
+
currencies: {
|
|
167
|
+
[baseAsset.name]: baseAsset.currency,
|
|
168
|
+
[baseAsset.feeAsset.name]: baseAsset.feeAsset.currency,
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
await assetClientInterface.updateTxLogAndNotify({
|
|
172
|
+
assetName: baseAsset.name,
|
|
173
|
+
walletAccount,
|
|
174
|
+
txs: [txForFee],
|
|
175
|
+
})
|
|
174
176
|
}
|
|
175
|
-
await assetClientInterface.updateTxLogAndNotify({
|
|
176
|
-
assetName: baseAsset.name,
|
|
177
|
-
walletAccount,
|
|
178
|
-
txs: [txForFee],
|
|
179
|
-
})
|
|
180
|
-
}
|
|
181
177
|
|
|
182
|
-
|
|
183
|
-
}
|
|
178
|
+
return { txId }
|
|
179
|
+
}
|