@exodus/solana-api 2.1.0 → 2.1.2

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": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -20,16 +20,19 @@
20
20
  "@exodus/fetch": "^1.2.0",
21
21
  "@exodus/models": "^8.7.2",
22
22
  "@exodus/nfts-core": "^0.5.0",
23
- "@exodus/solana-lib": "^1.4.0",
23
+ "@exodus/solana-lib": "^1.4.2",
24
+ "@exodus/solana-web3.js": "1.31.0-exodus.3",
24
25
  "@ungap/url-search-params": "^0.2.2",
26
+ "bignumber.js": "^9.0.1",
25
27
  "bn.js": "^4.11.0",
26
28
  "debug": "^4.1.1",
27
29
  "lodash": "^4.17.11",
30
+ "tweetnacl": "^1.0.3",
28
31
  "url-join": "4.0.0",
29
32
  "wretch": "^1.5.2"
30
33
  },
31
34
  "devDependencies": {
32
35
  "node-fetch": "~2.6.0"
33
36
  },
34
- "gitHead": "6ffded97383f994c21ccaebb2703c32d48aa70c5"
37
+ "gitHead": "1da3192650571fda10b61596dbc2eaf7cc8f439d"
35
38
  }
package/src/api.js CHANGED
@@ -136,7 +136,7 @@ export class Api {
136
136
  ])
137
137
  }
138
138
 
139
- async getFee(): number {
139
+ async getFee(): Promise<number> {
140
140
  const result = await this.rpcCall('getRecentBlockhash', [
141
141
  { commitment: 'finalized', encoding: 'jsonParsed' },
142
142
  ])
@@ -238,8 +238,14 @@ export class Api {
238
238
  tokenAccountsByOwner: ?Array,
239
239
  { includeUnparsed = false } = {}
240
240
  ): Object {
241
- let { fee, preBalances, postBalances, preTokenBalances, postTokenBalances, innerInstructions } =
242
- txDetails.meta
241
+ let {
242
+ fee,
243
+ preBalances,
244
+ postBalances,
245
+ preTokenBalances,
246
+ postTokenBalances,
247
+ innerInstructions,
248
+ } = txDetails.meta
243
249
  preBalances = preBalances || []
244
250
  postBalances = postBalances || []
245
251
  preTokenBalances = preTokenBalances || []
@@ -584,7 +590,7 @@ export class Api {
584
590
  return tokensMint
585
591
  }
586
592
 
587
- async getTokenAccountsByOwner(address: string, tokenTicker: ?string): Array {
593
+ async getTokenAccountsByOwner(address: string, tokenTicker: ?string): Promise<Array> {
588
594
  const { value: accountsList } = await this.rpcCall(
589
595
  'getTokenAccountsByOwner',
590
596
  [address, { programId: TOKEN_PROGRAM_ID.toBase58() }, { encoding: 'jsonParsed' }],
@@ -926,13 +932,16 @@ export class Api {
926
932
  * Simulate transaction and return side effects
927
933
  */
928
934
  simulateAndRetrieveSideEffects = async (
929
- transactionMessage: SolanaWeb3Message,
930
- publicKey: string
935
+ message: SolanaWeb3Message,
936
+ publicKey: string,
937
+ transactionMessage?: any // decompiled TransactionMessage
931
938
  ) => {
932
- const { config, accountAddresses } = getTransactionSimulationParams(transactionMessage)
933
- const signatures = new Array(transactionMessage.header.numRequiredSignatures || 1).fill(null)
939
+ const { config, accountAddresses } = getTransactionSimulationParams(
940
+ transactionMessage || message
941
+ )
942
+ const signatures = new Array(message.header.numRequiredSignatures || 1).fill(null)
934
943
  const encodedTransaction = buildRawTransaction(
935
- transactionMessage.serialize(),
944
+ Buffer.from(message.serialize()),
936
945
  signatures
937
946
  ).toString('base64')
938
947
  const futureAccountsState = await this.simulateTransaction(encodedTransaction, config)
@@ -0,0 +1,139 @@
1
+ // @flow
2
+ import nacl from 'tweetnacl'
3
+ import { fetchival } from '@exodus/fetch'
4
+ import ms from 'ms'
5
+ import { SystemInstruction, Transaction, PublicKey } from '@exodus/solana-web3.js'
6
+ import api from '..'
7
+ import BigNumber from 'bignumber.js'
8
+ import {
9
+ TOKEN_PROGRAM_ID,
10
+ decodeTokenProgramInstruction,
11
+ SYSTEM_PROGRAM_ID,
12
+ } from '@exodus/solana-lib'
13
+
14
+ export class FetchTransactionError extends Error {
15
+ name = 'FetchTransactionError'
16
+ }
17
+
18
+ export class ParseTransactionError extends Error {
19
+ name = 'ParseTransactionError'
20
+ }
21
+
22
+ const isTransferCheckedInstruction = (decodedInstruction) =>
23
+ decodedInstruction.type === 'transferChecked'
24
+ const isTransferInstruction = (decodedInstruction) => decodedInstruction.type === 'transfer'
25
+ const isSplAccount = (account) =>
26
+ account && account.owner === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
27
+
28
+ export async function fetchTransaction({
29
+ account,
30
+ link,
31
+ commitment,
32
+ }: {
33
+ account: string,
34
+ link: string | URL,
35
+ commitment?: string,
36
+ }) {
37
+ const response = await fetchival(String(link), {
38
+ mode: 'cors',
39
+ cache: 'no-cache',
40
+ credentials: 'omit',
41
+ timeout: ms('10s'),
42
+ headers: {
43
+ Accept: 'application/json',
44
+ 'Content-Type': 'application/json',
45
+ },
46
+ }).post({ account })
47
+ if (!response || !response.transaction) throw new FetchTransactionError('missing transaction')
48
+ const { transaction: txString } = response
49
+ if (typeof txString !== 'string') throw new FetchTransactionError('invalid transaction')
50
+ const transaction = Transaction.from(Buffer.from(txString, 'base64'))
51
+
52
+ const { signatures, feePayer, recentBlockhash } = transaction
53
+ if (signatures.length) {
54
+ if (!feePayer) throw new FetchTransactionError('missing fee payer')
55
+ if (!feePayer.equals(signatures[0].publicKey))
56
+ throw new FetchTransactionError('invalid fee payer')
57
+ if (!recentBlockhash) throw new FetchTransactionError('missing recent blockhash')
58
+
59
+ // A valid signature for everything except `account` must be provided.
60
+ const message = transaction.serializeMessage()
61
+ for (const { signature, publicKey } of signatures) {
62
+ if (signature) {
63
+ if (!nacl.sign.detached.verify(message, signature, publicKey.toBuffer()))
64
+ throw new FetchTransactionError('invalid signature')
65
+ } else if (publicKey.equals(new PublicKey(account))) {
66
+ // If the only signature expected is for `account`, ignore the recent blockhash in the transaction.
67
+ if (signatures.length === 1) {
68
+ transaction.recentBlockhash = await api.getRecentBlockHash(commitment)
69
+ }
70
+ } else {
71
+ throw new FetchTransactionError('missing signature')
72
+ }
73
+ }
74
+ } else {
75
+ // Ignore the fee payer and recent blockhash in the transaction and initialize them.
76
+ transaction.feePayer = account
77
+ transaction.recentBlockhash = await api.getRecentBlockHash(commitment)
78
+ }
79
+
80
+ return parseInstructions(transaction)
81
+ }
82
+
83
+ async function parseInstructions(transaction: Transaction) {
84
+ // Make a copy of the instructions we're going to mutate it.
85
+ const instructions = transaction.instructions.slice()
86
+
87
+ if (!instructions || !Array.isArray(instructions) || instructions.length !== 1)
88
+ throw new ParseTransactionError('Invalid transaction instructions')
89
+
90
+ // Transfer instruction must be the last instruction
91
+ const instruction = instructions.pop()
92
+ if (!instruction) throw new ParseTransactionError('missing transfer instruction')
93
+
94
+ const isTokenTransfer = instruction.programId.equals(TOKEN_PROGRAM_ID)
95
+ const isSolNativeTransfer = instruction.programId.equals(SYSTEM_PROGRAM_ID)
96
+
97
+ if (isTokenTransfer) {
98
+ const decodedInstruction = decodeTokenProgramInstruction(instruction)
99
+ if (
100
+ !isTransferCheckedInstruction(decodedInstruction) &&
101
+ !isTransferInstruction(decodedInstruction)
102
+ )
103
+ throw new ParseTransactionError('invalid token transfer')
104
+
105
+ const [, mint, destination, owner] = instruction.keys
106
+
107
+ const splToken = mint.pubkey.toBase58()
108
+ let asset
109
+ let recipient = destination.pubkey.toBase58()
110
+ if (splToken) {
111
+ if (!api.isTokenSupported(splToken))
112
+ throw new ParseTransactionError(`spl-token ${splToken} is not supported`)
113
+ asset = api.getTokenByAddress(splToken)
114
+
115
+ const account = await api.getAccountInfo(recipient)
116
+ if (isSplAccount(account)) recipient = account.data.parsed.info.owner
117
+ }
118
+
119
+ return {
120
+ amount: asset.currency
121
+ .baseUnit(new BigNumber(decodedInstruction.data.amount).toString())
122
+ .toDefaultString(),
123
+ decimals: decodedInstruction.data.decimals,
124
+ recipient,
125
+ sender: owner.pubkey.toBase58(),
126
+ splToken,
127
+ asset,
128
+ }
129
+ } else if (isSolNativeTransfer) {
130
+ const decodedTransaction = SystemInstruction.decodeTransfer(instruction)
131
+ return {
132
+ sender: decodedTransaction.fromPubkey.toString(),
133
+ amount: decodedTransaction.lamports.toString(),
134
+ recipient: decodedTransaction.toPubkey.toString(),
135
+ }
136
+ }
137
+
138
+ throw new Error('Invalid transfer instruction')
139
+ }
package/src/pay/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './parseURL.js'
2
2
  export * from './prepareSendData.js'
3
3
  export * from './validateBeforePay'
4
+ export * from './fetchTransaction'