@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "3.3.1",
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.0.0",
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": "30fdc2a936b6776866467baf926c899e9a236beb"
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: txDetails.transaction.signatures[0],
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 isTransferTx && tokenAccount ? instruction : null
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
- const solanaTx = lodash.find(instructions, (ix) => {
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 hasSolanaTx = solanaTx && preTokenBalances.length === 0 && postTokenBalances.length === 0 // only SOL moved and no tokens movements
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 (hasSolanaTx) {
432
+ if (hasOnlySolanaTx) {
396
433
  // Solana tx
397
- const isSending = ownerAddress === solanaTx.source
434
+ const isSending = ownerAddress === solanaTransferTx.source
398
435
  tx = {
399
- owner: solanaTx.source,
400
- from: solanaTx.source,
401
- to: solanaTx.destination,
402
- amount: solanaTx.lamports, // number
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 (solanaTx) {
573
+ if (solanaTransferTx) {
536
574
  // the base tx will be the one that moved solana.
537
- tx = {
538
- owner: solanaTx.source,
539
- from: solanaTx.source,
540
- to: solanaTx.destination,
541
- amount: solanaTx.lamports, // number
542
- fee: ownerAddress === solanaTx.source ? fee : 0,
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 && !solanaTx) {
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 futureAccountsState = await this.simulateTransaction(encodedTransaction, config)
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
- futureAccountsState,
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,