@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 +5 -6
- package/src/constants.js +7 -0
- package/src/encode.js +16 -0
- package/src/helpers.js +83 -0
- package/src/index.js +2 -0
- package/src/tokens.js +26 -0
- package/src/transaction.js +76 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-lib",
|
|
3
|
-
"version": "1.1
|
|
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/
|
|
19
|
-
"@exodus/
|
|
20
|
-
"@exodus/solana-web3.js": "0.87.1-
|
|
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": "
|
|
25
|
+
"gitHead": "51f71e5bcebd886116bf433c8fe79859a185d16c"
|
|
27
26
|
}
|
package/src/constants.js
ADDED
|
@@ -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
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
|
package/src/transaction.js
CHANGED
|
@@ -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)
|