@exodus/solana-api 3.3.1 → 3.4.0
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 +3 -3
- package/src/api.js +93 -35
- package/src/tx-send.js +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-api",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "Exodus internal Solana asset API wrapper",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@exodus/models": "^10.1.0",
|
|
30
30
|
"@exodus/nfts-core": "^0.5.0",
|
|
31
31
|
"@exodus/simple-retry": "^0.0.6",
|
|
32
|
-
"@exodus/solana-lib": "^3.
|
|
32
|
+
"@exodus/solana-lib": "^3.1.0",
|
|
33
33
|
"@exodus/solana-meta": "^1.0.7",
|
|
34
34
|
"bn.js": "^4.11.0",
|
|
35
35
|
"debug": "^4.1.1",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@exodus/assets-testing": "^1.0.0"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "0214827414fb3ac874b22cef41dae41bcef98ed6"
|
|
48
48
|
}
|
package/src/api.js
CHANGED
|
@@ -26,6 +26,7 @@ import { Connection } from './connection'
|
|
|
26
26
|
const RPC_URL = 'https://solana.a.exodus.io' // https://vip-api.mainnet-beta.solana.com/, https://api.mainnet-beta.solana.com
|
|
27
27
|
const WS_ENDPOINT = 'wss://solana.a.exodus.io/ws' // not standard across all node providers (we're compatible only with Quicknode)
|
|
28
28
|
const FORCE_HTTP = true // use https over ws
|
|
29
|
+
const WRAPPED_SOLANA_TOKEN_MINT_ADDRESS = `So11111111111111111111111111111111111111112`
|
|
29
30
|
|
|
30
31
|
// Tokens + SOL api support
|
|
31
32
|
export class Api {
|
|
@@ -342,6 +343,20 @@ export class Api {
|
|
|
342
343
|
type: ix.parsed.type, // transfer, createAccount, initializeAccount
|
|
343
344
|
...ix.parsed.info,
|
|
344
345
|
}))
|
|
346
|
+
|
|
347
|
+
let solanaTransferTx = lodash.find(instructions, (ix) => {
|
|
348
|
+
if (![ix.source, ix.destination].includes(ownerAddress)) return false
|
|
349
|
+
return ix.program === 'system' && ix.type === 'transfer'
|
|
350
|
+
}) // get SOL transfer
|
|
351
|
+
|
|
352
|
+
// check if there is a temp account created & closed within the instructions when there is no direct solana transfer
|
|
353
|
+
const accountToRedeemToOwner = solanaTransferTx
|
|
354
|
+
? undefined
|
|
355
|
+
: instructions.find(
|
|
356
|
+
({ type, owner, destination }) =>
|
|
357
|
+
type === 'closeAccount' && owner === ownerAddress && destination === ownerAddress
|
|
358
|
+
)?.account
|
|
359
|
+
|
|
345
360
|
innerInstructions = innerInstructions
|
|
346
361
|
.reduce((acc, val) => {
|
|
347
362
|
return [...acc, ...val.instructions]
|
|
@@ -352,23 +367,47 @@ export class Api {
|
|
|
352
367
|
ix.parsed &&
|
|
353
368
|
ix.program === 'spl-token' &&
|
|
354
369
|
['transfer', 'transferChecked', 'transferCheckedWithFee'].includes(type)
|
|
370
|
+
|
|
371
|
+
if (!isTransferTx) return null
|
|
372
|
+
|
|
355
373
|
const source = lodash.get(ix, 'parsed.info.source')
|
|
356
374
|
const destination = lodash.get(ix, 'parsed.info.destination')
|
|
375
|
+
const mint = lodash.get(ix, 'parsed.info.mint')
|
|
357
376
|
const amount = Number(
|
|
358
377
|
lodash.get(ix, 'parsed.info.amount', 0) ||
|
|
359
378
|
lodash.get(ix, 'parsed.info.tokenAmount.amount', 0)
|
|
360
379
|
)
|
|
380
|
+
const txId = txDetails.transaction.signatures[0]
|
|
381
|
+
if (
|
|
382
|
+
accountToRedeemToOwner &&
|
|
383
|
+
[source, destination].includes(accountToRedeemToOwner) &&
|
|
384
|
+
mint === WRAPPED_SOLANA_TOKEN_MINT_ADDRESS
|
|
385
|
+
) {
|
|
386
|
+
const ownerOrAccount = (account) =>
|
|
387
|
+
account && account === accountToRedeemToOwner ? ownerAddress : account
|
|
388
|
+
|
|
389
|
+
solanaTransferTx = {
|
|
390
|
+
id: txId,
|
|
391
|
+
from: ownerOrAccount(source),
|
|
392
|
+
to: ownerOrAccount(destination),
|
|
393
|
+
amount,
|
|
394
|
+
fee,
|
|
395
|
+
}
|
|
396
|
+
return
|
|
397
|
+
}
|
|
361
398
|
|
|
362
399
|
const tokenAccount = tokenAccountsByOwner.find(({ tokenAccountAddress }) => {
|
|
363
400
|
return [source, destination].includes(tokenAccountAddress)
|
|
364
401
|
})
|
|
402
|
+
if (!tokenAccount) return
|
|
403
|
+
|
|
365
404
|
const isSending = tokenAccountsByOwner.some(({ tokenAccountAddress }) => {
|
|
366
405
|
return [source].includes(tokenAccountAddress)
|
|
367
406
|
})
|
|
368
407
|
|
|
369
408
|
// owner if it's a send tx
|
|
370
409
|
const instruction = {
|
|
371
|
-
id:
|
|
410
|
+
id: txId,
|
|
372
411
|
slot: txDetails.slot,
|
|
373
412
|
owner: isSending ? ownerAddress : null,
|
|
374
413
|
from: source,
|
|
@@ -377,29 +416,27 @@ export class Api {
|
|
|
377
416
|
token: tokenAccount,
|
|
378
417
|
fee: isSending ? fee : 0,
|
|
379
418
|
}
|
|
380
|
-
return
|
|
419
|
+
return tokenAccount ? instruction : null
|
|
381
420
|
})
|
|
382
421
|
.filter((ix) => !!ix)
|
|
383
422
|
|
|
384
423
|
// program:type tells us if it's a SOL or Token transfer
|
|
385
|
-
|
|
386
|
-
if (![ix.source, ix.destination].includes(ownerAddress)) return false
|
|
387
|
-
return ix.program === 'system' && ix.type === 'transfer'
|
|
388
|
-
}) // get SOL transfer
|
|
424
|
+
|
|
389
425
|
const stakeTx = lodash.find(instructions, { program: 'system', type: 'createAccountWithSeed' })
|
|
390
426
|
const stakeWithdraw = lodash.find(instructions, { program: 'stake', type: 'withdraw' })
|
|
391
427
|
const stakeUndelegate = lodash.find(instructions, { program: 'stake', type: 'deactivate' })
|
|
392
|
-
const
|
|
428
|
+
const hasOnlySolanaTx =
|
|
429
|
+
solanaTransferTx && preTokenBalances.length === 0 && postTokenBalances.length === 0 // only SOL moved and no tokens movements
|
|
393
430
|
|
|
394
431
|
let tx = {}
|
|
395
|
-
if (
|
|
432
|
+
if (hasOnlySolanaTx) {
|
|
396
433
|
// Solana tx
|
|
397
|
-
const isSending = ownerAddress ===
|
|
434
|
+
const isSending = ownerAddress === solanaTransferTx.source
|
|
398
435
|
tx = {
|
|
399
|
-
owner:
|
|
400
|
-
from:
|
|
401
|
-
to:
|
|
402
|
-
amount:
|
|
436
|
+
owner: solanaTransferTx.source,
|
|
437
|
+
from: solanaTransferTx.source,
|
|
438
|
+
to: solanaTransferTx.destination,
|
|
439
|
+
amount: solanaTransferTx.lamports, // number
|
|
403
440
|
fee: isSending ? fee : 0,
|
|
404
441
|
}
|
|
405
442
|
} else if (stakeTx) {
|
|
@@ -456,6 +493,7 @@ export class Api {
|
|
|
456
493
|
Array.isArray(tokenAccountsByOwner),
|
|
457
494
|
'tokenAccountsByOwner is required when parsing token tx'
|
|
458
495
|
)
|
|
496
|
+
|
|
459
497
|
const tokenTxs = lodash
|
|
460
498
|
.filter(instructions, ({ program, type }) => {
|
|
461
499
|
return (
|
|
@@ -521,7 +559,7 @@ export class Api {
|
|
|
521
559
|
)
|
|
522
560
|
})
|
|
523
561
|
|
|
524
|
-
if (preBalances.length > 0 || postBalances.length > 0) {
|
|
562
|
+
if (preBalances.length > 0 || postBalances.length > 0 || solanaTransferTx) {
|
|
525
563
|
tx = {}
|
|
526
564
|
|
|
527
565
|
if (includeUnparsed && innerInstructions.length > 0) {
|
|
@@ -532,22 +570,25 @@ export class Api {
|
|
|
532
570
|
tx = getUnparsedTx()
|
|
533
571
|
tx.dexTxs = getInnerTxsFromBalanceChanges()
|
|
534
572
|
} else {
|
|
535
|
-
if (
|
|
573
|
+
if (solanaTransferTx) {
|
|
536
574
|
// the base tx will be the one that moved solana.
|
|
537
|
-
tx =
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
575
|
+
tx =
|
|
576
|
+
solanaTransferTx.from && solanaTransferTx.to
|
|
577
|
+
? solanaTransferTx
|
|
578
|
+
: {
|
|
579
|
+
owner: solanaTransferTx.source,
|
|
580
|
+
from: solanaTransferTx.source,
|
|
581
|
+
to: solanaTransferTx.destination,
|
|
582
|
+
amount: solanaTransferTx.lamports, // number
|
|
583
|
+
fee: ownerAddress === solanaTransferTx.source ? fee : 0,
|
|
584
|
+
}
|
|
544
585
|
}
|
|
545
586
|
|
|
546
587
|
// If it has inner instructions then it's a DEX tx that moved SPL -> SPL
|
|
547
588
|
if (innerInstructions.length > 0) {
|
|
548
589
|
tx.dexTxs = innerInstructions
|
|
549
590
|
// if tx involves only SPL swaps. Expand DEX ix (first element as tx base and the other kept there)
|
|
550
|
-
if (!tx.from && !
|
|
591
|
+
if (!tx.from && !solanaTransferTx) {
|
|
551
592
|
tx = tx.dexTxs[0]
|
|
552
593
|
tx.dexTxs = innerInstructions.slice(1)
|
|
553
594
|
}
|
|
@@ -888,10 +929,10 @@ export class Api {
|
|
|
888
929
|
|
|
889
930
|
simulateTransaction = async (encodedTransaction, options) => {
|
|
890
931
|
const {
|
|
891
|
-
value: { accounts },
|
|
932
|
+
value: { accounts, unitsConsumed, err },
|
|
892
933
|
} = await this.rpcCall('simulateTransaction', [encodedTransaction, options])
|
|
893
934
|
|
|
894
|
-
return accounts
|
|
935
|
+
return { accounts, unitsConsumed, err }
|
|
895
936
|
}
|
|
896
937
|
|
|
897
938
|
resolveSimulationSideEffects = async (solAccounts, tokenAccounts) => {
|
|
@@ -1004,14 +1045,7 @@ export class Api {
|
|
|
1004
1045
|
}
|
|
1005
1046
|
}
|
|
1006
1047
|
|
|
1007
|
-
|
|
1008
|
-
* Simulate transaction and return side effects
|
|
1009
|
-
*/
|
|
1010
|
-
simulateAndRetrieveSideEffects = async (
|
|
1011
|
-
message,
|
|
1012
|
-
publicKey,
|
|
1013
|
-
transactionMessage // decompiled TransactionMessage
|
|
1014
|
-
) => {
|
|
1048
|
+
simulateUnsignedTransaction = async ({ message, transactionMessage }) => {
|
|
1015
1049
|
const { config, accountAddresses } = getTransactionSimulationParams(
|
|
1016
1050
|
transactionMessage || message
|
|
1017
1051
|
)
|
|
@@ -1021,9 +1055,33 @@ export class Api {
|
|
|
1021
1055
|
Buffer.from(message.serialize()),
|
|
1022
1056
|
signatures
|
|
1023
1057
|
).toString('base64')
|
|
1024
|
-
const
|
|
1058
|
+
const { accounts, unitsConsumed, err } = await this.simulateTransaction(encodedTransaction, {
|
|
1059
|
+
...config,
|
|
1060
|
+
replaceRecentBlockhash: true,
|
|
1061
|
+
sigVerify: false,
|
|
1062
|
+
})
|
|
1063
|
+
return {
|
|
1064
|
+
accounts,
|
|
1065
|
+
accountAddresses,
|
|
1066
|
+
unitsConsumed,
|
|
1067
|
+
err,
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Simulate transaction and return side effects
|
|
1073
|
+
*/
|
|
1074
|
+
simulateAndRetrieveSideEffects = async (
|
|
1075
|
+
message,
|
|
1076
|
+
publicKey,
|
|
1077
|
+
transactionMessage // decompiled TransactionMessage
|
|
1078
|
+
) => {
|
|
1079
|
+
const { accounts, accountAddresses } = await this.simulateUnsignedTransaction({
|
|
1080
|
+
message,
|
|
1081
|
+
transactionMessage,
|
|
1082
|
+
})
|
|
1025
1083
|
const { solAccounts, tokenAccounts } = filterAccountsByOwner(
|
|
1026
|
-
|
|
1084
|
+
accounts,
|
|
1027
1085
|
accountAddresses,
|
|
1028
1086
|
publicKey
|
|
1029
1087
|
)
|
package/src/tx-send.js
CHANGED
|
@@ -141,9 +141,10 @@ export const createAndBroadcastTXFactory =
|
|
|
141
141
|
|
|
142
142
|
let { priorityFee } = feeData
|
|
143
143
|
|
|
144
|
+
const transactionForFeeEstimation = prepareForSigning(unsignedTransaction)
|
|
145
|
+
|
|
144
146
|
if (!priorityFee) {
|
|
145
147
|
try {
|
|
146
|
-
const transactionForFeeEstimation = prepareForSigning(unsignedTransaction)
|
|
147
148
|
priorityFee = await api.getPriorityFee(transactionToBase58(transactionForFeeEstimation))
|
|
148
149
|
} catch (e) {
|
|
149
150
|
console.warn(`Failed to fetch priority fee: ${e.message}`)
|
|
@@ -151,7 +152,12 @@ export const createAndBroadcastTXFactory =
|
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
const { unitsConsumed: computeUnits } = await api.simulateUnsignedTransaction({
|
|
156
|
+
message: transactionForFeeEstimation.compileMessage(),
|
|
157
|
+
})
|
|
158
|
+
|
|
154
159
|
unsignedTransaction.txData.priorityFee = priorityFee
|
|
160
|
+
unsignedTransaction.txData.computeUnits = computeUnits
|
|
155
161
|
|
|
156
162
|
const { txId, rawTx } = await assetClientInterface.signTransaction({
|
|
157
163
|
assetName: baseAsset.name,
|