@exodus/solana-api 3.14.4 → 3.14.6

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/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [3.14.6](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.14.5...@exodus/solana-api@3.14.6) (2025-04-01)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: SOL staking method param (#5362)
13
+
14
+
15
+
16
+ ## [3.14.5](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.14.4...@exodus/solana-api@3.14.5) (2025-03-25)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+
22
+ * fix: SOL insufficient funds for rent (#5308)
23
+
24
+
25
+
6
26
  ## [3.14.4](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.14.3...@exodus/solana-api@3.14.4) (2025-03-20)
7
27
 
8
28
  **Note:** Version bump only for package @exodus/solana-api
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "3.14.4",
3
+ "version": "3.14.6",
4
4
  "description": "Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Solana",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -46,7 +46,7 @@
46
46
  "@exodus/assets-testing": "^1.0.0",
47
47
  "@exodus/solana-web3.js": "^1.63.1-exodus.9-rc3"
48
48
  },
49
- "gitHead": "ece04a00d01566ab21cd4b4af57501fb7e50d1af",
49
+ "gitHead": "310d0d34a574d1da978f862e42c481de1e57bade",
50
50
  "bugs": {
51
51
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
52
52
  },
package/src/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import createApiCJS from '@exodus/asset-json-rpc'
2
+ import { memoizeLruCache } from '@exodus/asset-lib'
2
3
  import { memoize } from '@exodus/basic-utils'
3
4
  import wretch from '@exodus/fetch/wretch'
4
5
  import { retry } from '@exodus/simple-retry'
@@ -58,6 +59,12 @@ export class Api {
58
59
  (accountSize) => accountSize,
59
60
  ms('15m')
60
61
  )
62
+
63
+ this.getAccountSize = memoize(
64
+ (address) => this.getAccountInfo(address),
65
+ (address) => address,
66
+ ms('3m')
67
+ )
61
68
  }
62
69
 
63
70
  setServer(rpcUrl) {
@@ -136,6 +143,20 @@ export class Api {
136
143
  return this.tokens.has(mint)
137
144
  }
138
145
 
146
+ async getRentExemptionMinAmount(address) {
147
+ // minimum amount required for the destination account to be rent-exempt
148
+ const accountInfo = await this.getAccountSize(address).catch(() => {})
149
+ if (accountInfo?.space === 0) {
150
+ // no rent required
151
+ return 0
152
+ }
153
+
154
+ const accountSize = accountInfo?.space || 0
155
+
156
+ // Lamports number
157
+ return this.getMinimumBalanceForRentExemption(accountSize)
158
+ }
159
+
139
160
  async getEpochInfo() {
140
161
  const { epoch } = await this.rpcCall('getEpochInfo')
141
162
  return Number(epoch)
@@ -881,6 +902,12 @@ export class Api {
881
902
  return owner && owner !== address
882
903
  }
883
904
 
905
+ ataOwnershipChangedCached = memoizeLruCache(
906
+ (...args) => this.ataOwnershipChanged(...args),
907
+ (address, tokenAddress) => `${address}:${tokenAddress}`,
908
+ { max: 1000 }
909
+ )
910
+
884
911
  // Returns account balance of a SPL Token account.
885
912
  async getTokenBalance(tokenAddress) {
886
913
  const result = await this.rpcCall('getTokenAccountBalance', [tokenAddress])
@@ -0,0 +1,209 @@
1
+ import {
2
+ createUnsignedTx,
3
+ findAssociatedTokenAddress,
4
+ prepareForSigning,
5
+ TOKEN_2022_PROGRAM_ID,
6
+ TOKEN_PROGRAM_ID,
7
+ } from '@exodus/solana-lib'
8
+
9
+ const CU_FOR_COMPUTE_BUDGET_INSTRUCTIONS = 300
10
+
11
+ export const createUnsignedTxForSend = async ({
12
+ api,
13
+ asset,
14
+ feeData,
15
+ toAddress,
16
+ fromAddress,
17
+ amount,
18
+ reference,
19
+ memo,
20
+ nft,
21
+ // token related
22
+ tokenStandard,
23
+ customMintAddress,
24
+ // staking
25
+ method,
26
+ stakeAddresses,
27
+ seed,
28
+ pool,
29
+ // <MagicEden>
30
+ initializerAddress,
31
+ initializerDepositTokenAddress,
32
+ takerAmount,
33
+ escrowAddress,
34
+ escrowBump,
35
+ pdaAddress,
36
+ takerAddress,
37
+ expectedTakerAmount,
38
+ expectedMintAddress,
39
+ metadataAddress,
40
+ creators,
41
+ // </MagicEden>
42
+ }) => {
43
+ let tokenParams = Object.create(null)
44
+ const baseAsset = asset.baseAsset
45
+
46
+ if (nft) {
47
+ const [, nftAddress] = nft.id.split(':')
48
+ customMintAddress = nftAddress
49
+ tokenStandard = nft.tokenStandard
50
+ method = tokenStandard === 4 ? 'metaplexTransfer' : undefined
51
+ amount = asset.currency.baseUnit(1)
52
+ }
53
+
54
+ const isToken = asset.assetType === api.tokenAssetType
55
+
56
+ // Check if receiver has address active when sending tokens.
57
+ if (isToken) {
58
+ // check address mint is the same
59
+ const targetMint = await api.getAddressMint(toAddress) // null if it's a SOL address
60
+ if (targetMint && targetMint !== asset.mintAddress) {
61
+ const err = new Error('Wrong Destination Wallet')
62
+ err.mintAddressMismatch = true
63
+ throw err
64
+ }
65
+ } else {
66
+ // sending SOL
67
+ const addressType = await api.getAddressType(toAddress)
68
+ if (addressType === 'token') {
69
+ const err = new Error('Destination Wallet is a Token address')
70
+ err.wrongAddressType = true
71
+ throw err
72
+ }
73
+ }
74
+
75
+ if (isToken || customMintAddress) {
76
+ const tokenMintAddress = customMintAddress || asset.mintAddress
77
+ const tokenProgramPublicKey =
78
+ (await api.getAddressType(tokenMintAddress)) === 'token-2022'
79
+ ? TOKEN_2022_PROGRAM_ID
80
+ : TOKEN_PROGRAM_ID
81
+
82
+ const tokenProgram = tokenProgramPublicKey.toBase58()
83
+ const tokenAddress = findAssociatedTokenAddress(toAddress, tokenMintAddress, tokenProgram)
84
+
85
+ const [destinationAddressType, isAssociatedTokenAccountActive, fromTokenAccountAddresses] =
86
+ await Promise.all([
87
+ api.getAddressType(toAddress),
88
+ api.isAssociatedTokenAccountActive(tokenAddress),
89
+ api.getTokenAccountsByOwner(fromAddress),
90
+ ])
91
+
92
+ const changedOwnership = await api.ataOwnershipChangedCached(toAddress, tokenAddress)
93
+ if (changedOwnership) {
94
+ const err = new Error('Destination ATA changed ownership')
95
+ err.ownershipChanged = true
96
+ throw err
97
+ }
98
+
99
+ const fromTokenAddresses = fromTokenAccountAddresses.filter(
100
+ ({ mintAddress }) => mintAddress === tokenMintAddress
101
+ )
102
+
103
+ tokenParams = {
104
+ tokenMintAddress,
105
+ destinationAddressType,
106
+ isAssociatedTokenAccountActive,
107
+ fromTokenAddresses,
108
+ tokenStandard,
109
+ tokenProgram,
110
+ }
111
+ }
112
+
113
+ const stakingParams = {
114
+ method,
115
+ stakeAddresses,
116
+ seed,
117
+ pool,
118
+ }
119
+
120
+ const recentBlockhash = await api.getRecentBlockHash()
121
+
122
+ const magicEdenParams = {
123
+ method,
124
+ initializerAddress,
125
+ initializerDepositTokenAddress,
126
+ takerAmount,
127
+ escrowAddress,
128
+ escrowBump,
129
+ pdaAddress,
130
+ takerAddress,
131
+ expectedTakerAmount,
132
+ expectedMintAddress,
133
+ metadataAddress,
134
+ creators,
135
+ }
136
+
137
+ const unsignedTx = createUnsignedTx({
138
+ asset,
139
+ from: fromAddress,
140
+ to: toAddress,
141
+ amount,
142
+ recentBlockhash,
143
+ reference,
144
+ memo,
145
+ ...tokenParams,
146
+ ...stakingParams,
147
+ ...magicEdenParams,
148
+ })
149
+
150
+ const resolveUnitConsumed = async () => {
151
+ // this avoids unnecessary simulations. Also the simulation fails with InsufficientFundsForRent when sending all.
152
+ if (asset.name === asset.baseAsset.name && amount && !nft && !method) {
153
+ return 150 + CU_FOR_COMPUTE_BUDGET_INSTRUCTIONS
154
+ }
155
+
156
+ const transactionForFeeEstimation = prepareForSigning(unsignedTx)
157
+ const { unitsConsumed, err } = await api.simulateUnsignedTransaction({
158
+ message: transactionForFeeEstimation.message,
159
+ })
160
+ if (err) throw new Error(JSON.stringify(err))
161
+ return unitsConsumed + CU_FOR_COMPUTE_BUDGET_INSTRUCTIONS
162
+ }
163
+
164
+ const priorityFee = feeData.priorityFee
165
+ let computeUnits
166
+ if (priorityFee) {
167
+ const unitsConsumed = await resolveUnitConsumed()
168
+ computeUnits = unitsConsumed * feeData.computeUnitsMultiplier
169
+ unsignedTx.txData.priorityFee = priorityFee
170
+ unsignedTx.txData.computeUnits = computeUnits
171
+ }
172
+
173
+ unsignedTx.txMeta.stakingParams = stakingParams
174
+
175
+ const fee = feeData.baseFee.add(
176
+ asset.feeAsset.currency
177
+ .baseUnit(unsignedTx.txData.priorityFee ?? 0)
178
+ .mul(unsignedTx.txData.computeUnits ?? 0)
179
+ .div(1_000_000) // micro lamports to lamports
180
+ )
181
+
182
+ // serialization friendlier
183
+ unsignedTx.txMeta.fee = fee.toBaseNumber()
184
+
185
+ const rentExemptValue = await api.getRentExemptionMinAmount(toAddress)
186
+ const rentExemptAmount = baseAsset.currency.baseUnit(rentExemptValue)
187
+
188
+ // differentiate between SOL and Solana token
189
+ let isEnoughForRent = false
190
+ if (asset.name === baseAsset.name) {
191
+ // sending SOL
192
+ isEnoughForRent = amount.gte(rentExemptAmount)
193
+ } else {
194
+ // sending token/nft
195
+ const baseAssetBalance = await api.getBalance(fromAddress)
196
+ isEnoughForRent = baseAsset.currency
197
+ .baseUnit(baseAssetBalance)
198
+ .sub(fee || asset.feeAsset.currency.ZERO)
199
+ .gte(rentExemptAmount)
200
+ }
201
+
202
+ if (!isEnoughForRent) {
203
+ const err = new Error('Sending SOL amount is too low to cover the rent exemption fee.')
204
+ err.rentExemptAmount = true
205
+ throw err
206
+ }
207
+
208
+ return unsignedTx
209
+ }
@@ -4,7 +4,7 @@ import { TxSet } from '@exodus/models'
4
4
  // In this case, The wallet should exclude the staking balance from the general balance
5
5
 
6
6
  export const getBalancesFactory =
7
- ({ stakingFeatureAvailable }) =>
7
+ ({ stakingFeatureAvailable, allowSendingAll }) =>
8
8
  ({ asset, accountState, txLog }) => {
9
9
  const zero = asset.currency.ZERO
10
10
  const { balance, locked, withdrawable, pending } = fixBalances({
@@ -34,15 +34,16 @@ export const getBalancesFactory =
34
34
 
35
35
  const total = stakingFeatureAvailable ? balance : balanceWithoutStaking
36
36
 
37
- const networkReserve = accountState.rentExemptAmount || zero
37
+ // there is no wallet reserve when there are no tokens nor staking actions. Just network reserve for the rent exempt amount.
38
+ const needsReserve =
39
+ hasStakedFunds({ locked, withdrawable, pending }) || hasTokensBalance({ accountState })
40
+
41
+ const networkReserve =
42
+ allowSendingAll && !needsReserve ? zero : accountState.rentExemptAmount || zero
38
43
 
39
44
  const accountReserve = asset.accountReserve || zero
40
45
 
41
- // there is no wallet reserve when there are no tokens nor staking actions. Just network reserve for the rent exempt amount.
42
- const walletReserve =
43
- hasStakedFunds({ locked, withdrawable, pending }) || hasTokensBalance({ accountState })
44
- ? accountReserve.sub(networkReserve).clampLowerZero()
45
- : zero
46
+ const walletReserve = needsReserve ? accountReserve.sub(networkReserve).clampLowerZero() : zero
46
47
 
47
48
  const spendable = balanceWithoutStaking.sub(walletReserve).sub(networkReserve).clampLowerZero()
48
49
 
@@ -0,0 +1,27 @@
1
+ import assert from 'minimalistic-assert'
2
+
3
+ import { createUnsignedTxForSend } from './create-unsigned-tx-for-send.js'
4
+
5
+ export const getFeeAsyncFactory = ({ api }) => {
6
+ assert(api, 'api is required')
7
+ return async ({ asset, feeData, unsignedTx: providedUnsignedTx, amount, toAddress, ...rest }) => {
8
+ const unsignedTx =
9
+ providedUnsignedTx ||
10
+ (await createUnsignedTxForSend({
11
+ asset,
12
+ feeData,
13
+ api,
14
+ amount: amount ?? asset.currency.baseUnit(1),
15
+ toAddress: toAddress ?? rest.fromAddress,
16
+ ...rest,
17
+ }))
18
+
19
+ return { fee: asset.feeAsset.currency.baseUnit(unsignedTx.txMeta.fee), unsignedTx }
20
+ }
21
+ }
22
+
23
+ export const getFeeFactory =
24
+ ({ asset }) =>
25
+ ({ feeData }) => {
26
+ return { fee: feeData.fee }
27
+ }
package/src/index.js CHANGED
@@ -15,6 +15,7 @@ export {
15
15
  } from './txs-utils.js'
16
16
  export { createAndBroadcastTXFactory } from './tx-send.js'
17
17
  export { getBalancesFactory } from './get-balances.js'
18
+ export { getFeeAsyncFactory } from './get-fees.js'
18
19
  export { stakingProviderClientFactory } from './staking-provider-client.js'
19
20
 
20
21
  // These are not the same asset objects as the wallet creates, so they should never be returned to the wallet.
@@ -297,10 +297,9 @@ export class SolanaMonitor extends BaseMonitor {
297
297
  }),
298
298
  ])
299
299
 
300
- const accountSize = accountInfo?.space || 0
301
300
  const solBalance = accountInfo?.lamports || 0
302
301
 
303
- const rentExemptValue = await this.api.getMinimumBalanceForRentExemption(accountSize)
302
+ const rentExemptValue = await this.api.getRentExemptionMinAmount(address)
304
303
  const rentExemptAmount = this.asset.currency.baseUnit(rentExemptValue)
305
304
 
306
305
  const tokenBalances = _.mapValues(splBalances, (balance, name) =>
package/src/tx-send.js CHANGED
@@ -1,222 +1,112 @@
1
- import {
2
- createUnsignedTx,
3
- findAssociatedTokenAddress,
4
- prepareForSigning,
5
- TOKEN_2022_PROGRAM_ID,
6
- TOKEN_PROGRAM_ID,
7
- } from '@exodus/solana-lib'
8
1
  import assert from 'minimalistic-assert'
9
2
 
3
+ import { createUnsignedTxForSend } from './create-unsigned-tx-for-send.js'
4
+
10
5
  export const createAndBroadcastTXFactory =
11
6
  ({ api, assetClientInterface }) =>
12
- async ({ asset, walletAccount, address, amount, options = {} }) => {
7
+ async ({ asset, walletAccount, unsignedTx: predefinedUnsignedTx, ...legacyParams }) => {
13
8
  const assetName = asset.name
14
9
  assert(assetClientInterface, `assetClientInterface must be supplied in sendTx for ${assetName}`)
15
10
 
16
- const {
17
- feeAmount,
18
- stakeAddresses,
19
- seed,
20
- pool,
21
- nft,
22
- // <MagicEden>
23
- initializerAddress,
24
- initializerDepositTokenAddress,
25
- takerAmount,
26
- escrowAddress,
27
- escrowBump,
28
- pdaAddress,
29
- takerAddress,
30
- expectedTakerAmount,
31
- expectedMintAddress,
32
- metadataAddress,
33
- creators,
34
- priorityFee,
35
- // </MagicEden>
36
- reference,
37
- memo,
38
- } = options
39
-
40
- let { method, customMintAddress, tokenStandard } = options
41
-
42
- const { baseAsset } = asset
43
- const from = await assetClientInterface.getReceiveAddress({
44
- assetName: baseAsset.name,
45
- walletAccount,
46
- })
11
+ const baseAsset = asset.baseAsset
47
12
 
48
- if (nft) {
49
- customMintAddress = nft.mintAddress
50
- tokenStandard = nft.tokenStandard
51
- method = tokenStandard === 4 ? 'metaplexTransfer' : undefined
52
- amount = asset.currency.baseUnit(1)
53
- }
54
-
55
- const isToken = asset.assetType === api.tokenAssetType
56
-
57
- // Check if receiver has address active when sending tokens.
58
- if (isToken) {
59
- // check address mint is the same
60
- const targetMint = await api.getAddressMint(address) // null if it's a SOL address
61
- if (targetMint && targetMint !== asset.mintAddress) {
62
- const err = new Error('Wrong Destination Wallet')
63
- err.reason = { mintAddressMismatch: true }
64
- throw err
65
- }
66
- } else {
67
- // sending SOL
68
- const addressType = await api.getAddressType(address)
69
- if (addressType === 'token') {
70
- const err = new Error('Destination Wallet is a Token address')
71
- err.reason = { wrongAddressType: true }
72
- throw err
13
+ const resolveTxs = async () => {
14
+ if (predefinedUnsignedTx) {
15
+ return predefinedUnsignedTx
73
16
  }
74
- }
75
-
76
- const recentBlockhash = options.recentBlockhash || (await api.getRecentBlockHash())
77
-
78
- const feeData = await assetClientInterface.getFeeData({ assetName })
79
-
80
- let tokenParams = Object.create(null)
81
- if (isToken || customMintAddress) {
82
- const tokenMintAddress = customMintAddress || asset.mintAddress
83
- const tokenProgramPublicKey =
84
- (await api.getAddressType(tokenMintAddress)) === 'token-2022'
85
- ? TOKEN_2022_PROGRAM_ID
86
- : TOKEN_PROGRAM_ID
87
-
88
- const tokenProgram = tokenProgramPublicKey.toBase58()
89
- const tokenAddress = findAssociatedTokenAddress(address, tokenMintAddress, tokenProgram)
90
- const [destinationAddressType, isAssociatedTokenAccountActive, fromTokenAccountAddresses] =
91
- await Promise.all([
92
- api.getAddressType(address),
93
- api.isAssociatedTokenAccountActive(tokenAddress),
94
- api.getTokenAccountsByOwner(from),
95
- ])
96
-
97
- const changedOwnership = await api.ataOwnershipChanged(address, tokenAddress)
98
- if (changedOwnership) {
99
- const err = new Error('Destination ATA changed ownership')
100
- err.reason = { ownershipChanged: true }
101
- throw err
102
- }
103
-
104
- const fromTokenAddresses = fromTokenAccountAddresses.filter(
105
- ({ mintAddress }) => mintAddress === tokenMintAddress
106
- )
107
-
108
- tokenParams = {
109
- tokenMintAddress,
110
- destinationAddressType,
111
- isAssociatedTokenAccountActive,
112
- fromTokenAddresses,
113
- tokenStandard,
114
- tokenProgram,
115
- }
116
- }
117
17
 
118
- const stakingParams = {
119
- method,
120
- stakeAddresses,
121
- seed,
122
- pool,
123
- }
18
+ const feeData = await assetClientInterface.getFeeData({ assetName })
19
+ const fromAddress = await assetClientInterface.getReceiveAddress({
20
+ assetName: baseAsset.name,
21
+ walletAccount,
22
+ })
124
23
 
125
- const magicEdenParams = {
126
- method,
127
- initializerAddress,
128
- initializerDepositTokenAddress,
129
- takerAmount,
130
- escrowAddress,
131
- escrowBump,
132
- pdaAddress,
133
- takerAddress,
134
- expectedTakerAmount,
135
- expectedMintAddress,
136
- metadataAddress,
137
- creators,
24
+ return createUnsignedTxForSend({
25
+ api,
26
+ asset,
27
+ feeData,
28
+ fromAddress,
29
+ amount: legacyParams.amount,
30
+ toAddress: legacyParams.address,
31
+ ...legacyParams.options,
32
+ })
138
33
  }
139
34
 
140
- const unsignedTransaction = createUnsignedTx({
141
- asset,
142
- from,
143
- to: address,
144
- amount,
145
- fee: feeData.fee, // feeAmount includes the priortyFee
146
- recentBlockhash,
147
- feeData,
148
- reference,
149
- memo,
150
- ...tokenParams,
151
- ...stakingParams,
152
- ...magicEdenParams,
153
- })
154
-
155
- const transactionForFeeEstimation = prepareForSigning(unsignedTransaction)
35
+ const unsignedTx = await resolveTxs()
156
36
 
157
- const { unitsConsumed: computeUnits, err } = await api.simulateUnsignedTransaction({
158
- message: transactionForFeeEstimation.message,
159
- })
160
- if (err) throw new Error(JSON.stringify(err))
161
-
162
- unsignedTransaction.txData.priorityFee = priorityFee ?? 0
163
- unsignedTransaction.txData.computeUnits = computeUnits * feeData.computeUnitsMultiplier
164
-
165
- const { txId, rawTx } = await assetClientInterface.signTransaction({
37
+ const signedTx = await assetClientInterface.signTransaction({
166
38
  assetName: baseAsset.name,
167
- unsignedTx: unsignedTransaction,
39
+ unsignedTx,
168
40
  walletAccount,
169
41
  })
170
42
 
171
- await baseAsset.api.broadcastTx(rawTx)
172
-
173
- const selfSend = from === address
174
- const isStakingTx = ['delegate', 'undelegate', 'withdraw'].includes(method)
175
- const coinAmount = isStakingTx
176
- ? amount.abs()
177
- : selfSend
178
- ? asset.currency.ZERO
179
- : amount.abs().negate()
180
-
181
- const data = isStakingTx
182
- ? { staking: { ...stakingParams, stake: coinAmount.toBaseNumber() } }
183
- : Object.create(null)
184
- const tx = {
185
- txId,
186
- confirmations: 0,
187
- coinName: assetName,
188
- coinAmount,
189
- feeAmount,
190
- feeCoinName: asset.feeAsset.name,
191
- selfSend,
192
- to: address,
193
- data,
194
- currencies: { [assetName]: asset.currency, [asset.feeAsset.name]: asset.feeAsset.currency },
195
- }
196
- await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
43
+ const txId = signedTx.txId
197
44
 
198
- if (isToken) {
199
- // write tx entry in solana for token fee
200
- const txForFee = {
45
+ const isToken = asset.assetType === api.tokenAssetType
46
+
47
+ await baseAsset.api.broadcastTx(signedTx.rawTx)
48
+
49
+ try {
50
+ // collecting data from unsignedTx, it should be sufficient
51
+ const method = unsignedTx.txData.method
52
+ const fromAddress = unsignedTx.txData.from
53
+ const toAddress = unsignedTx.txData.to
54
+ const selfSend = fromAddress === toAddress
55
+ const amount = asset.currency.baseUnit(unsignedTx.txData.amount)
56
+ const feeAmount = asset.feeAsset.currency.baseUnit(unsignedTx.txMeta.fee)
57
+
58
+ const isStakingTx = ['delegate', 'undelegate', 'withdraw'].includes(method)
59
+ const coinAmount =
60
+ (isStakingTx ? amount?.abs() : selfSend ? asset.currency.ZERO : amount?.abs().negate()) ||
61
+ asset.currency.ZERO
62
+
63
+ let data
64
+ if (isStakingTx) {
65
+ data = { ...unsignedTx?.txMeta.stakingParams, stake: coinAmount.toBaseNumber() }
66
+ } else {
67
+ data = Object.create(null)
68
+ }
69
+
70
+ const tx = {
201
71
  txId,
202
72
  confirmations: 0,
203
- coinName: baseAsset.name,
204
- coinAmount: baseAsset.currency.ZERO,
205
- tokens: [assetName],
73
+ coinName: assetName,
74
+ coinAmount,
206
75
  feeAmount,
207
- feeCoinName: baseAsset.feeAsset.name,
208
- to: address,
76
+ feeCoinName: asset.feeAsset.name,
209
77
  selfSend,
210
- currencies: {
211
- [baseAsset.name]: baseAsset.currency,
212
- [baseAsset.feeAsset.name]: baseAsset.feeAsset.currency,
213
- },
78
+ to: toAddress,
79
+ data,
80
+ currencies: { [assetName]: asset.currency, [asset.feeAsset.name]: asset.feeAsset.currency },
214
81
  }
215
- await assetClientInterface.updateTxLogAndNotify({
216
- assetName: baseAsset.name,
217
- walletAccount,
218
- txs: [txForFee],
219
- })
82
+ await assetClientInterface.updateTxLogAndNotify({ assetName, walletAccount, txs: [tx] })
83
+
84
+ if (isToken) {
85
+ // write tx entry in solana for token fee
86
+ const txForFee = {
87
+ txId,
88
+ confirmations: 0,
89
+ coinName: baseAsset.name,
90
+ coinAmount: baseAsset.currency.ZERO,
91
+ tokens: [assetName],
92
+ feeAmount,
93
+ feeCoinName: baseAsset.feeAsset.name,
94
+ to: toAddress,
95
+ selfSend,
96
+ currencies: {
97
+ [baseAsset.name]: baseAsset.currency,
98
+ [baseAsset.feeAsset.name]: baseAsset.feeAsset.currency,
99
+ },
100
+ }
101
+ await assetClientInterface.updateTxLogAndNotify({
102
+ assetName: baseAsset.name,
103
+ walletAccount,
104
+ txs: [txForFee],
105
+ })
106
+ }
107
+ } catch (err) {
108
+ console.log('error writing SOL txLog', err)
109
+ return { txId, txLogError: true }
220
110
  }
221
111
 
222
112
  return { txId }