@exodus/solana-lib 1.1.0 → 1.2.1

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-lib",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Exodus internal Solana low-level library",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -15,13 +15,12 @@
15
15
  "dependencies": {
16
16
  "@exodus/asset-lib": "^3.4.3",
17
17
  "@exodus/assets": "^8.0.0",
18
- "@exodus/buffer-layout": "^1.2.0-exodus0",
19
- "@exodus/models": "^8.2.0",
20
- "@exodus/solana-web3.js": "0.87.1-exodus1",
18
+ "@exodus/models": "^8.4.0",
19
+ "@exodus/solana-spl-token": "^0.0.13-exodus1",
20
+ "@exodus/solana-web3.js": "^0.87.1-exodus4",
21
21
  "bn.js": "~4.11.0",
22
22
  "bs58": "~4.0.1",
23
- "create-hash": "~1.1.3",
24
23
  "tweetnacl": "^1.0.3"
25
24
  },
26
- "gitHead": "f18544ae45db87c7efec7d8139e1e04c51e0fc32"
25
+ "gitHead": "51f71e5bcebd886116bf433c8fe79859a185d16c"
27
26
  }
@@ -0,0 +1,7 @@
1
+ import { PublicKey, SystemProgram } from '@exodus/solana-web3.js'
2
+ export { TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
3
+
4
+ export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
5
+ 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
6
+ )
7
+ export const SYSTEM_PROGRAM_ID = SystemProgram.programId
package/src/encode.js CHANGED
@@ -1,4 +1,7 @@
1
1
  // @flow
2
+ import { PublicKey } from '@exodus/solana-web3.js'
3
+ import { TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
4
+ import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID } from './constants'
2
5
  import { getPublicKey } from './keypair'
3
6
  import bs58 from 'bs58'
4
7
  import BN from 'bn.js'
@@ -27,3 +30,16 @@ export function isValidAddress(address: string): boolean {
27
30
  return false
28
31
  }
29
32
  }
33
+
34
+ // doc: https://spl.solana.com/associated-token-account (HACK: refactored to sync)
35
+ export function findAssociatedTokenAddress(
36
+ walletAddress: string,
37
+ tokenMintAddress: string
38
+ ): string {
39
+ walletAddress = new PublicKey(walletAddress)
40
+ tokenMintAddress = new PublicKey(tokenMintAddress)
41
+ return PublicKey.findProgramAddress(
42
+ [walletAddress.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), tokenMintAddress.toBuffer()],
43
+ SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
44
+ )[0].toBase58() // returns encoded PublicKey
45
+ }
package/src/helpers.js ADDED
@@ -0,0 +1,83 @@
1
+ import {
2
+ PublicKey,
3
+ TransactionInstruction,
4
+ SystemProgram,
5
+ SYSVAR_RENT_PUBKEY,
6
+ } from '@exodus/solana-web3.js'
7
+ import { Token, TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
8
+ import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID } from './constants'
9
+ import { findAssociatedTokenAddress } from './encode'
10
+
11
+ // https://github.com/paul-schaaf/spl-token-ui/blob/main/src/solana/token/associatedToken.ts#L59
12
+ export const createAssociatedTokenAccount = (
13
+ senderAddress: string,
14
+ tokenMintAddress: string,
15
+ ownerAddress: string // destination SOL address
16
+ ) => {
17
+ const associatedTokenAccountPublicKey = new PublicKey(
18
+ findAssociatedTokenAddress(ownerAddress, tokenMintAddress)
19
+ )
20
+
21
+ const feePayerPublicKey = new PublicKey(senderAddress)
22
+ const ownerPublicKey = new PublicKey(ownerAddress)
23
+ const tokenMintPublicKey = new PublicKey(tokenMintAddress)
24
+
25
+ const ix = createIx(
26
+ feePayerPublicKey, // feePayer
27
+ associatedTokenAccountPublicKey,
28
+ ownerPublicKey,
29
+ tokenMintPublicKey
30
+ )
31
+
32
+ return ix // returns the instruction
33
+ }
34
+
35
+ function createIx(
36
+ funderPubkey: PublicKey,
37
+ associatedTokenAccountPublicKey: PublicKey,
38
+ ownerPublicKey: PublicKey,
39
+ tokenMintPublicKey: PublicKey
40
+ ) {
41
+ return new TransactionInstruction({
42
+ programId: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
43
+ data: Buffer.from([]),
44
+ keys: [
45
+ { pubkey: funderPubkey, isSigner: true, isWritable: true },
46
+ {
47
+ pubkey: associatedTokenAccountPublicKey,
48
+ isSigner: false,
49
+ isWritable: true,
50
+ },
51
+ { pubkey: ownerPublicKey, isSigner: false, isWritable: false },
52
+ { pubkey: tokenMintPublicKey, isSigner: false, isWritable: false },
53
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
54
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
55
+ { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
56
+ ],
57
+ })
58
+ }
59
+
60
+ // https://github.com/paul-schaaf/spl-token-ui/blob/main/src/solana/token/editing.ts#L207
61
+ export const createTokenTransferInstruction = (
62
+ owner,
63
+ fromTokenAddress,
64
+ tokenMintAddress,
65
+ to,
66
+ amount
67
+ ) => {
68
+ const sourcePubkey = new PublicKey(fromTokenAddress) // the token ADDRESS needed!
69
+ const destinationPubkey = new PublicKey(findAssociatedTokenAddress(to, tokenMintAddress))
70
+ console.log(`destination associatedTokenAddress: ${destinationPubkey.toBase58()}`)
71
+ const ownerAccountOrWalletPublicKey = new PublicKey(owner) // the only native SOL address
72
+
73
+ const transferIx = Token.createTransferInstruction(
74
+ TOKEN_PROGRAM_ID,
75
+ sourcePubkey,
76
+ destinationPubkey,
77
+ ownerAccountOrWalletPublicKey,
78
+ [],
79
+ amount
80
+ )
81
+
82
+ return transferIx
83
+ }
package/src/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // @flow
2
2
 
3
+ export * from './constants'
3
4
  export * from './encode'
4
5
  export * from './keypair'
5
6
  export * from './tx'
7
+ export { default as tokens } from './tokens'
6
8
  export { default as Transaction } from './transaction'
package/src/tokens.js ADDED
@@ -0,0 +1,26 @@
1
+ // reference https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/tokens/names.js
2
+
3
+ const TOKENS = [
4
+ {
5
+ mintAddress: 'SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt',
6
+ tokenName: 'Serum',
7
+ tokenSymbol: 'SRM',
8
+ },
9
+ {
10
+ tokenSymbol: 'BTC',
11
+ mintAddress: '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E',
12
+ tokenName: 'Wrapped Bitcoin',
13
+ icon:
14
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/bitcoin/info/logo.png',
15
+ },
16
+ {
17
+ tokenSymbol: 'USDC',
18
+ mintAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
19
+ tokenName: 'USD Coin',
20
+ icon:
21
+ 'https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
22
+ },
23
+ // ... TODO: do we want to add decimals for every token? (Serum SRM is 6)
24
+ ]
25
+
26
+ export default TOKENS
@@ -3,6 +3,8 @@ import assert from 'assert'
3
3
 
4
4
  import { Account, SystemProgram, Transaction, PublicKey } from '@exodus/solana-web3.js'
5
5
  import { getKeyPairFromPrivateKey } from './keypair'
6
+ import { createAssociatedTokenAccount, createTokenTransferInstruction } from './helpers'
7
+ import TOKENS from './tokens'
6
8
  import bs58 from 'bs58'
7
9
 
8
10
  class Tx {
@@ -10,21 +12,47 @@ class Tx {
10
12
  from,
11
13
  to,
12
14
  amount,
13
- // fee, // (Fee per Signature: 5000 lamports)
14
15
  recentBlockhash,
16
+ // fee, // (Fee per Signature: 5000 lamports)
17
+ // Tokens related:
18
+ tokenTicker,
19
+ isAssociatedTokenAccountActive, // true when recipient balance !== 0
20
+ fromTokenAddresses, // sender token addresses
15
21
  } = {}) {
16
22
  assert(from, 'from is required')
17
23
  assert(to, 'to is required')
18
24
  assert(amount, 'amount is required')
19
25
  assert(typeof amount === 'number', 'amount must be a number')
20
26
  assert(recentBlockhash, 'recentBlockhash is required')
27
+ if (tokenTicker) {
28
+ assert(
29
+ typeof isAssociatedTokenAccountActive !== 'undefined',
30
+ 'isAssociatedTokenAccountActive is required when sending tokens'
31
+ ) // needed to create the recipient account
32
+ assert(Array.isArray(fromTokenAddresses), 'fromTokenAddresses Array is required')
33
+ }
34
+
35
+ const token = TOKENS.find(({ tokenSymbol }) => tokenSymbol === tokenTicker)
21
36
  this.txObj = {
22
37
  from,
23
38
  to,
24
39
  amount,
25
40
  recentBlockhash,
41
+ token,
42
+ isAssociatedTokenAccountActive,
43
+ fromTokenAddresses,
26
44
  }
27
45
 
46
+ if (token) {
47
+ // TOKEN transfer tx
48
+ this.buildTokenTransaction(this.txObj)
49
+ } else {
50
+ // SOL tx
51
+ this.buildSOLtransaction(this.txObj)
52
+ }
53
+ }
54
+
55
+ buildSOLtransaction({ from, to, amount, recentBlockhash }) {
28
56
  const txInstruction = SystemProgram.transfer({
29
57
  fromPubkey: new PublicKey(from),
30
58
  toPubkey: new PublicKey(to),
@@ -37,6 +65,53 @@ class Tx {
37
65
  })
38
66
  }
39
67
 
68
+ buildTokenTransaction({
69
+ from,
70
+ to,
71
+ amount,
72
+ recentBlockhash,
73
+ token,
74
+ isAssociatedTokenAccountActive,
75
+ fromTokenAddresses,
76
+ }) {
77
+ this.transaction = new Transaction({
78
+ recentBlockhash,
79
+ })
80
+ // crete account instruction
81
+ if (!isAssociatedTokenAccountActive)
82
+ this.transaction.add(createAssociatedTokenAccount(from, token.mintAddress, to))
83
+
84
+ let amountLeft = amount
85
+ let amountToSend
86
+ for (let { ticker, tokenAccountAddress, balance } of fromTokenAddresses) {
87
+ // need to add more of this instruction until we reach the desired balance (amount) to send
88
+ assert(ticker === token.tokenSymbol, `Got unexpected ticker ${ticker}`)
89
+ if (amountLeft === 0) break
90
+
91
+ balance = Number(balance)
92
+ if (balance >= amountLeft) {
93
+ amountToSend = amountLeft
94
+ amountLeft = 0
95
+ } else {
96
+ amountToSend = balance // not enough balance
97
+ amountLeft -= amountToSend
98
+ }
99
+
100
+ // add transfer token instruction
101
+ this.transaction.add(
102
+ createTokenTransferInstruction(
103
+ from,
104
+ tokenAccountAddress,
105
+ token.mintAddress,
106
+ to,
107
+ amountToSend
108
+ )
109
+ )
110
+ }
111
+
112
+ assert(amountLeft === 0, `Not enough balance to send ${amount} ${token.tokenSymbol}`)
113
+ }
114
+
40
115
  static sign(tx: Tx, privateKey: Buffer | string) {
41
116
  if (!privateKey) throw new Error('Please provide a secretKey')
42
117
  const { secretKey } = getKeyPairFromPrivateKey(privateKey)