@exodus/solana-lib 1.2.3 → 1.2.5

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.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Exodus internal Solana low-level library",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -14,13 +14,13 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@exodus/asset-lib": "^3.4.3",
17
- "@exodus/assets": "^8.0.0",
17
+ "@exodus/assets": "^8.0.2",
18
+ "@exodus/buffer-layout": "^1.2.0-exodus1",
18
19
  "@exodus/models": "^8.4.0",
19
- "@exodus/solana-spl-token": "^0.0.13-exodus2",
20
- "@exodus/solana-web3.js": "^0.87.1-exodus4",
21
20
  "bn.js": "~4.11.0",
22
21
  "bs58": "~4.0.1",
22
+ "create-hash": "~1.1.3",
23
23
  "tweetnacl": "^1.0.3"
24
24
  },
25
- "gitHead": "5be453e5b6c47cf82b8c80a83e8a4bebbb487a2e"
25
+ "gitHead": "791e305a718246460d5ac93ed2eb7808955c0f5d"
26
26
  }
package/src/constants.js CHANGED
@@ -1,7 +1,8 @@
1
- import { PublicKey, SystemProgram } from '@exodus/solana-web3.js'
2
- export { TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
1
+ import { PublicKey, SystemProgram } from './vendor'
3
2
 
4
3
  export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
5
4
  'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
6
5
  )
7
6
  export const SYSTEM_PROGRAM_ID = SystemProgram.programId
7
+
8
+ export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
package/src/encode.js CHANGED
@@ -1,7 +1,6 @@
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
+ import { PublicKey } from './vendor'
3
+ import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID } from './constants'
5
4
  import { getPublicKey } from './keypair'
6
5
  import bs58 from 'bs58'
7
6
  import BN from 'bn.js'
@@ -0,0 +1,108 @@
1
+ import assert from 'assert'
2
+ import BN from 'bn.js'
3
+ import * as BufferLayout from '@exodus/buffer-layout'
4
+ import { Account, PublicKey, TransactionInstruction } from '../vendor'
5
+ import * as Layout from '../vendor/utils/layout'
6
+
7
+ // Extracted from https://github.com/ExodusMovement/solana-spl-token/blob/master/src/index.js#L263
8
+
9
+ /**
10
+ * 64-bit value
11
+ */
12
+ export class U64 extends BN {
13
+ /**
14
+ * Convert to Buffer representation
15
+ */
16
+ toBuffer(): typeof Buffer {
17
+ const a = super.toArray().reverse()
18
+ const b = Buffer.from(a)
19
+ if (b.length === 8) {
20
+ return b
21
+ }
22
+ assert(b.length < 8, 'u64 too large')
23
+
24
+ const zeroPad = Buffer.alloc(8)
25
+ b.copy(zeroPad)
26
+ return zeroPad
27
+ }
28
+
29
+ /**
30
+ * Construct a u64 from Buffer representation
31
+ */
32
+ static fromBuffer(buffer: typeof Buffer): U64 {
33
+ assert(buffer.length === 8, `Invalid buffer length: ${buffer.length}`)
34
+ return new U64(
35
+ [...buffer]
36
+ .reverse()
37
+ .map((i) => `00${i.toString(16)}`.slice(-2))
38
+ .join(''),
39
+ 16
40
+ )
41
+ }
42
+ }
43
+
44
+ const u64 = U64 // alias
45
+
46
+ /**
47
+ * An ERC20-like Token
48
+ */
49
+ export class Token {
50
+ /**
51
+ * Construct a Transfer instruction
52
+ *
53
+ * @param programId SPL Token program account
54
+ * @param source Source account
55
+ * @param destination Destination account
56
+ * @param owner Owner of the source account
57
+ * @param multiSigners Signing accounts if `authority` is a multiSig
58
+ * @param amount Number of tokens to transfer
59
+ */
60
+ static createTransferInstruction(
61
+ programId: PublicKey,
62
+ source: PublicKey,
63
+ destination: PublicKey,
64
+ owner: PublicKey,
65
+ multiSigners: Array<Account>,
66
+ amount: number | u64
67
+ ): TransactionInstruction {
68
+ const dataLayout = BufferLayout.struct([
69
+ BufferLayout.u8('instruction'),
70
+ Layout.uint64('amount'),
71
+ ])
72
+
73
+ const data = Buffer.alloc(dataLayout.span)
74
+ dataLayout.encode(
75
+ {
76
+ instruction: 3, // Transfer instruction
77
+ amount: new U64(amount).toBuffer(),
78
+ },
79
+ data
80
+ )
81
+
82
+ let keys = [
83
+ { pubkey: source, isSigner: false, isWritable: true },
84
+ { pubkey: destination, isSigner: false, isWritable: true },
85
+ ]
86
+ if (multiSigners.length === 0) {
87
+ keys.push({
88
+ pubkey: owner,
89
+ isSigner: true,
90
+ isWritable: false,
91
+ })
92
+ } else {
93
+ keys.push({ pubkey: owner, isSigner: false, isWritable: false })
94
+ multiSigners.forEach((signer) =>
95
+ keys.push({
96
+ pubkey: signer.publicKey,
97
+ isSigner: true,
98
+ isWritable: false,
99
+ })
100
+ )
101
+ }
102
+ return new TransactionInstruction({
103
+ keys,
104
+ programId: programId,
105
+ data,
106
+ })
107
+ }
108
+ }
@@ -1,12 +1,7 @@
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'
1
+ import { Token } from './spl-token'
2
+ import { PublicKey, TransactionInstruction, SystemProgram, SYSVAR_RENT_PUBKEY } from '../vendor'
3
+ import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../constants'
4
+ import { findAssociatedTokenAddress } from '../encode'
10
5
 
11
6
  // https://github.com/paul-schaaf/spl-token-ui/blob/main/src/solana/token/associatedToken.ts#L59
12
7
  export const createAssociatedTokenAccount = (
@@ -57,17 +52,11 @@ function createIx(
57
52
  })
58
53
  }
59
54
 
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
- ) => {
55
+ // https://github.com/paul-schaaf/spl-token-ui/blob/main/src/solana/token/editing.ts#L211
56
+ export const createTokenTransferInstruction = (owner, fromTokenAddress, to, amount) => {
68
57
  const sourcePubkey = new PublicKey(fromTokenAddress) // the token ADDRESS needed!
69
- const destinationPubkey = new PublicKey(findAssociatedTokenAddress(to, tokenMintAddress))
70
- console.log(`destination associatedTokenAddress: ${destinationPubkey.toBase58()}`)
58
+ const destinationPubkey = new PublicKey(to)
59
+ console.log(`destination token address: ${destinationPubkey.toBase58()}`)
71
60
  const ownerAccountOrWalletPublicKey = new PublicKey(owner) // the only native SOL address
72
61
 
73
62
  const transferIx = Token.createTransferInstruction(
package/src/tokens.js CHANGED
@@ -1,26 +1,13 @@
1
- // reference https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/tokens/names.js
1
+ // adapted from https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/tokens/names.js
2
2
 
3
3
  const TOKENS = [
4
4
  {
5
5
  mintAddress: 'SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt',
6
- tokenName: 'Serum',
6
+ tokenName: 'serum',
7
+ tokenProperName: 'Serum',
7
8
  tokenSymbol: 'SRM',
8
9
  },
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)
10
+ // ....
24
11
  ]
25
12
 
26
13
  export default TOKENS
@@ -1,9 +1,13 @@
1
1
  // @flow
2
2
  import assert from 'assert'
3
3
 
4
- import { Account, SystemProgram, Transaction, PublicKey } from '@exodus/solana-web3.js'
5
4
  import { getKeyPairFromPrivateKey } from './keypair'
6
- import { createAssociatedTokenAccount, createTokenTransferInstruction } from './helpers'
5
+ import { findAssociatedTokenAddress } from './encode'
6
+ import {
7
+ createAssociatedTokenAccount,
8
+ createTokenTransferInstruction,
9
+ } from './helpers/tokenTransfer'
10
+ import { PublicKey, Account, Transaction, SystemProgram } from './vendor'
7
11
  import TOKENS from './tokens'
8
12
  import bs58 from 'bs58'
9
13
 
@@ -15,7 +19,8 @@ class Tx {
15
19
  recentBlockhash,
16
20
  // fee, // (Fee per Signature: 5000 lamports)
17
21
  // Tokens related:
18
- tokenTicker,
22
+ tokenName,
23
+ destinationAddressType,
19
24
  isAssociatedTokenAccountActive, // true when recipient balance !== 0
20
25
  fromTokenAddresses, // sender token addresses
21
26
  } = {}) {
@@ -24,7 +29,11 @@ class Tx {
24
29
  assert(amount, 'amount is required')
25
30
  assert(typeof amount === 'number', 'amount must be a number')
26
31
  assert(recentBlockhash, 'recentBlockhash is required')
27
- if (tokenTicker) {
32
+ if (tokenName) {
33
+ assert(
34
+ typeof destinationAddressType !== 'undefined',
35
+ 'destinationAddressType is required when sending tokens'
36
+ )
28
37
  assert(
29
38
  typeof isAssociatedTokenAccountActive !== 'undefined',
30
39
  'isAssociatedTokenAccountActive is required when sending tokens'
@@ -32,13 +41,14 @@ class Tx {
32
41
  assert(Array.isArray(fromTokenAddresses), 'fromTokenAddresses Array is required')
33
42
  }
34
43
 
35
- const token = TOKENS.find(({ tokenSymbol }) => tokenSymbol === tokenTicker)
44
+ const token = TOKENS.find(({ tokenName: name }) => name === tokenName)
36
45
  this.txObj = {
37
46
  from,
38
47
  to,
39
48
  amount,
40
49
  recentBlockhash,
41
50
  token,
51
+ destinationAddressType,
42
52
  isAssociatedTokenAccountActive,
43
53
  fromTokenAddresses,
44
54
  }
@@ -71,14 +81,18 @@ class Tx {
71
81
  amount,
72
82
  recentBlockhash,
73
83
  token,
84
+ destinationAddressType,
74
85
  isAssociatedTokenAccountActive,
75
86
  fromTokenAddresses,
76
87
  }) {
77
88
  this.transaction = new Transaction({
78
89
  recentBlockhash,
79
90
  })
91
+ const isUnknown = destinationAddressType === null
92
+ const isSOLaddress = destinationAddressType === 'solana'
80
93
  // crete account instruction
81
- if (!isAssociatedTokenAccountActive)
94
+ if (isUnknown) throw new Error('Destination SOL balance cannot be zero (address not active)') // cannot initialize without knowing the owner
95
+ if (isSOLaddress && !isAssociatedTokenAccountActive)
82
96
  this.transaction.add(createAssociatedTokenAccount(from, token.mintAddress, to))
83
97
 
84
98
  let amountLeft = amount
@@ -97,15 +111,10 @@ class Tx {
97
111
  amountLeft -= amountToSend
98
112
  }
99
113
 
114
+ const dest = isSOLaddress ? findAssociatedTokenAddress(to, token.mintAddress) : to
100
115
  // add transfer token instruction
101
116
  this.transaction.add(
102
- createTokenTransferInstruction(
103
- from,
104
- tokenAccountAddress,
105
- token.mintAddress,
106
- to,
107
- amountToSend
108
- )
117
+ createTokenTransferInstruction(from, tokenAccountAddress, dest, amountToSend)
109
118
  )
110
119
  }
111
120
 
@@ -6,6 +6,11 @@ export function createUnsignedTx({
6
6
  to,
7
7
  amount,
8
8
  recentBlockhash,
9
+ // Tokens related:
10
+ tokenName,
11
+ destinationAddressType,
12
+ isAssociatedTokenAccountActive, // true when recipient balance !== 0
13
+ fromTokenAddresses, // sender token addresses
9
14
  }: ParsedTransaction): UnsignedTransaction {
10
15
  return {
11
16
  txData: {
@@ -13,6 +18,11 @@ export function createUnsignedTx({
13
18
  to,
14
19
  amount: amount.toBase().toNumber(),
15
20
  recentBlockhash,
21
+ // Tokens related:
22
+ tokenName,
23
+ destinationAddressType,
24
+ isAssociatedTokenAccountActive,
25
+ fromTokenAddresses,
16
26
  },
17
27
  txMeta: {
18
28
  assetName: asset.name,
@@ -3,7 +3,16 @@ import type { UnsignedTransaction, ParsedTransaction } from '@exodus/models/lib/
3
3
 
4
4
  export function parseUnsignedTx(unsignedTx: UnsignedTransaction): ParsedTransaction {
5
5
  const asset = assets.solana
6
- const { from, to, recentBlockhash, ...txData } = unsignedTx.txData
6
+ const {
7
+ from,
8
+ to,
9
+ recentBlockhash,
10
+ tokenName,
11
+ destinationAddressType,
12
+ isAssociatedTokenAccountActive,
13
+ fromTokenAddresses,
14
+ ...txData
15
+ } = unsignedTx.txData
7
16
 
8
17
  const amount = asset.currency.baseUnit(txData.amount)
9
18
  return {
@@ -12,5 +21,10 @@ export function parseUnsignedTx(unsignedTx: UnsignedTransaction): ParsedTransact
12
21
  to,
13
22
  amount,
14
23
  recentBlockhash,
24
+ // token related
25
+ tokenName,
26
+ destinationAddressType,
27
+ isAssociatedTokenAccountActive,
28
+ fromTokenAddresses,
15
29
  }
16
30
  }
@@ -6,7 +6,16 @@ export function signUnsignedTx(
6
6
  unsignedTx: UnsignedTransaction,
7
7
  privateKey: Buffer
8
8
  ): SignedTransaction {
9
- const { from, to, amount, recentBlockhash } = unsignedTx.txData
9
+ const {
10
+ from,
11
+ to,
12
+ amount,
13
+ recentBlockhash,
14
+ tokenName,
15
+ destinationAddressType,
16
+ isAssociatedTokenAccountActive,
17
+ fromTokenAddresses,
18
+ } = unsignedTx.txData
10
19
 
11
20
  const asset = assets.solana
12
21
  const tx = new Transaction({
@@ -14,6 +23,10 @@ export function signUnsignedTx(
14
23
  to,
15
24
  amount: asset.currency.baseUnit(amount).toNumber(),
16
25
  recentBlockhash,
26
+ tokenName,
27
+ destinationAddressType,
28
+ isAssociatedTokenAccountActive,
29
+ fromTokenAddresses,
17
30
  })
18
31
 
19
32
  // sign plain tx
@@ -0,0 +1,38 @@
1
+ // @flow
2
+ import type { KeyPair } from 'tweetnacl'
3
+ import { PublicKey } from './publickey'
4
+ import { getKeyPairFromPrivateKey } from '../keypair'
5
+
6
+ /**
7
+ * An account key pair (public and secret keys).
8
+ */
9
+ export class Account {
10
+ _keypair: KeyPair
11
+
12
+ /**
13
+ * Create a new Account object
14
+ *
15
+ * @param secretKey Secret key for the account
16
+ */
17
+ constructor(secretKey: Buffer | Uint8Array | Array<number>) {
18
+ if (secretKey) {
19
+ this._keypair = getKeyPairFromPrivateKey(Buffer.from(secretKey, 'hex'))
20
+ } else {
21
+ throw new Error('Please provide a secretKey')
22
+ }
23
+ }
24
+
25
+ /**
26
+ * The public key for this account
27
+ */
28
+ get publicKey(): PublicKey {
29
+ return new PublicKey(this._keypair.publicKey)
30
+ }
31
+
32
+ /**
33
+ * The **unencrypted** secret key for this account
34
+ */
35
+ get secretKey(): Buffer {
36
+ return this._keypair.secretKey
37
+ }
38
+ }
@@ -0,0 +1,8 @@
1
+ export * from './account'
2
+ export * from './instruction'
3
+ export * from './message'
4
+ export * from './nonce-account'
5
+ export * from './publickey'
6
+ export * from './system-program'
7
+ export * from './sysvar'
8
+ export * from './transaction'
@@ -0,0 +1,46 @@
1
+ // @flow
2
+
3
+ import * as BufferLayout from '@exodus/buffer-layout'
4
+
5
+ import * as Layout from './utils/layout'
6
+
7
+ /**
8
+ * @typedef {Object} InstructionType
9
+ * @property (index} The Instruction index (from solana upstream program)
10
+ * @property (BufferLayout} The BufferLayout to use to build data
11
+ */
12
+ export type InstructionType = {|
13
+ index: number,
14
+ layout: typeof BufferLayout,
15
+ |}
16
+
17
+ /**
18
+ * Populate a buffer of instruction data using an InstructionType
19
+ */
20
+ export function encodeData(type: InstructionType, fields: Object): Buffer {
21
+ const allocLength = type.layout.span >= 0 ? type.layout.span : Layout.getAlloc(type, fields)
22
+ const data = Buffer.alloc(allocLength)
23
+ const layoutFields = Object.assign({ instruction: type.index }, fields)
24
+ type.layout.encode(layoutFields, data)
25
+ return data
26
+ }
27
+
28
+ /**
29
+ * Decode instruction data buffer using an InstructionType
30
+ */
31
+ export function decodeData(type: InstructionType, buffer: Buffer): Object {
32
+ let data
33
+ try {
34
+ data = type.layout.decode(buffer)
35
+ } catch (err) {
36
+ throw new Error('invalid instruction; ' + err)
37
+ }
38
+
39
+ if (data.instruction !== type.index) {
40
+ throw new Error(
41
+ `invalid instruction; instruction index mismatch ${data.instruction} != ${type.index}`
42
+ )
43
+ }
44
+
45
+ return data
46
+ }