@exodus/solana-lib 1.2.9-build2 → 1.2.10

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.
Files changed (60) hide show
  1. package/package.json +5 -4
  2. package/src/constants.js +12 -0
  3. package/src/encode.js +57 -0
  4. package/src/fee-data/index.js +1 -0
  5. package/src/fee-data/solana.js +9 -0
  6. package/src/helpers/spl-token.js +108 -0
  7. package/src/helpers/tokenTransfer.js +72 -0
  8. package/src/index.js +9 -0
  9. package/src/keypair.js +32 -0
  10. package/src/tokens.js +19 -0
  11. package/src/transaction.js +292 -0
  12. package/src/tx/create-and-sign-tx.js +8 -0
  13. package/{lib → src}/tx/create-unsigned-tx.js +11 -18
  14. package/src/tx/index.js +4 -0
  15. package/src/tx/parse-unsigned-tx.js +41 -0
  16. package/src/tx/sign-unsigned-tx.js +78 -0
  17. package/src/vendor/account.js +38 -0
  18. package/src/vendor/index.js +9 -0
  19. package/src/vendor/instruction.js +46 -0
  20. package/src/vendor/message.js +216 -0
  21. package/src/vendor/nonce-account.js +46 -0
  22. package/src/vendor/publickey.js +212 -0
  23. package/src/vendor/stake-program.js +527 -0
  24. package/src/vendor/system-program.js +782 -0
  25. package/src/vendor/sysvar.js +16 -0
  26. package/src/vendor/transaction.js +594 -0
  27. package/src/vendor/utils/blockhash.js +6 -0
  28. package/src/vendor/utils/fee-calculator.js +17 -0
  29. package/src/vendor/utils/layout.js +80 -0
  30. package/src/vendor/utils/shortvec-encoding.js +30 -0
  31. package/src/vendor/utils/to-buffer.js +9 -0
  32. package/lib/constants.js +0 -19
  33. package/lib/encode.js +0 -67
  34. package/lib/fee-data/index.js +0 -15
  35. package/lib/fee-data/solana.js +0 -14
  36. package/lib/helpers/spl-token.js +0 -122
  37. package/lib/helpers/tokenTransfer.js +0 -78
  38. package/lib/index.js +0 -88
  39. package/lib/keypair.js +0 -38
  40. package/lib/tokens.js +0 -21
  41. package/lib/transaction.js +0 -338
  42. package/lib/tx/create-and-sign-tx.js +0 -15
  43. package/lib/tx/index.js +0 -53
  44. package/lib/tx/parse-unsigned-tx.js +0 -55
  45. package/lib/tx/sign-unsigned-tx.js +0 -90
  46. package/lib/vendor/account.js +0 -48
  47. package/lib/vendor/index.js +0 -113
  48. package/lib/vendor/instruction.js +0 -48
  49. package/lib/vendor/message.js +0 -167
  50. package/lib/vendor/nonce-account.js +0 -56
  51. package/lib/vendor/publickey.js +0 -211
  52. package/lib/vendor/stake-program.js +0 -476
  53. package/lib/vendor/system-program.js +0 -640
  54. package/lib/vendor/sysvar.js +0 -19
  55. package/lib/vendor/transaction.js +0 -594
  56. package/lib/vendor/utils/blockhash.js +0 -1
  57. package/lib/vendor/utils/fee-calculator.js +0 -25
  58. package/lib/vendor/utils/layout.js +0 -97
  59. package/lib/vendor/utils/shortvec-encoding.js +0 -41
  60. package/lib/vendor/utils/to-buffer.js +0 -18
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@exodus/solana-lib",
3
- "version": "1.2.9-build2",
3
+ "version": "1.2.10",
4
4
  "description": "Exodus internal Solana low-level library",
5
- "main": "lib/index.js",
5
+ "main": "src/index.js",
6
6
  "files": [
7
- "lib/",
7
+ "src/",
8
8
  "!src/__tests__"
9
9
  ],
10
10
  "author": "Exodus",
@@ -21,5 +21,6 @@
21
21
  "bs58": "~4.0.1",
22
22
  "create-hash": "~1.1.3",
23
23
  "tweetnacl": "^1.0.3"
24
- }
24
+ },
25
+ "gitHead": "9ea01d6b464b1a6a23e49f3634b9e661e755efc8"
25
26
  }
@@ -0,0 +1,12 @@
1
+ import { PublicKey, SystemProgram, StakeProgram } from './vendor'
2
+
3
+ export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
4
+ 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
5
+ )
6
+ export const SYSTEM_PROGRAM_ID = SystemProgram.programId
7
+
8
+ export const STAKE_PROGRAM_ID = StakeProgram.programId
9
+
10
+ export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
11
+
12
+ export const SEED = 'stake:0'
package/src/encode.js ADDED
@@ -0,0 +1,57 @@
1
+ // @flow
2
+ import { PublicKey, StakeProgram } from './vendor'
3
+ import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID, SEED } from './constants'
4
+ import { getPublicKey } from './keypair'
5
+ import bs58 from 'bs58'
6
+ import BN from 'bn.js'
7
+
8
+ export function getAddressFromPublicKey(publicKey: string | Buffer): string {
9
+ return bs58.encode(Buffer.from(publicKey, 'hex'))
10
+ }
11
+
12
+ export function getAddressFromPrivateKey(privateKey: string | Buffer): string {
13
+ return getAddressFromPublicKey(getPublicKey(privateKey))
14
+ }
15
+
16
+ export function isValidAddress(address: string): boolean {
17
+ try {
18
+ // assume base 58 encoding by default
19
+ const decoded = bs58.decode(address)
20
+ if (decoded.length !== 32) {
21
+ return false
22
+ }
23
+ const _bn = new BN(decoded)
24
+ if (_bn.byteLength() > 32) {
25
+ return false
26
+ }
27
+ return true
28
+ } catch (e) {
29
+ return false
30
+ }
31
+ }
32
+
33
+ // doc: https://spl.solana.com/associated-token-account (HACK: refactored to sync)
34
+ export function findAssociatedTokenAddress(
35
+ walletAddress: string,
36
+ tokenMintAddress: string
37
+ ): string {
38
+ walletAddress = new PublicKey(walletAddress)
39
+ tokenMintAddress = new PublicKey(tokenMintAddress)
40
+ return PublicKey.findProgramAddress(
41
+ [walletAddress.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), tokenMintAddress.toBuffer()],
42
+ SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
43
+ )[0].toBase58() // returns encoded PublicKey
44
+ }
45
+
46
+ export function createStakeAddress(walletAddress: string, seed = SEED): string {
47
+ const fromPubkey = new PublicKey(walletAddress)
48
+
49
+ const newAccountPubkey = PublicKey.createWithSeed(
50
+ // HACK: refactored to sync
51
+ fromPubkey,
52
+ seed,
53
+ StakeProgram.programId
54
+ )
55
+
56
+ return newAccountPubkey.toBase58()
57
+ }
@@ -0,0 +1 @@
1
+ export { default as solana } from './solana'
@@ -0,0 +1,9 @@
1
+ import { FeeData } from '@exodus/asset-lib'
2
+
3
+ export default new FeeData(
4
+ {
5
+ fee: '0.000005 SOL',
6
+ },
7
+ 'fee',
8
+ 'solana'
9
+ )
@@ -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
+ }
@@ -0,0 +1,72 @@
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'
5
+
6
+ // https://github.com/paul-schaaf/spl-token-ui/blob/main/src/solana/token/associatedToken.ts#L59
7
+ export const createAssociatedTokenAccount = (
8
+ senderAddress: string,
9
+ tokenMintAddress: string,
10
+ ownerAddress: string // destination SOL address
11
+ ) => {
12
+ const associatedTokenAccountPublicKey = new PublicKey(
13
+ findAssociatedTokenAddress(ownerAddress, tokenMintAddress)
14
+ )
15
+
16
+ const feePayerPublicKey = new PublicKey(senderAddress)
17
+ const ownerPublicKey = new PublicKey(ownerAddress)
18
+ const tokenMintPublicKey = new PublicKey(tokenMintAddress)
19
+
20
+ const ix = createIx(
21
+ feePayerPublicKey, // feePayer
22
+ associatedTokenAccountPublicKey,
23
+ ownerPublicKey,
24
+ tokenMintPublicKey
25
+ )
26
+
27
+ return ix // returns the instruction
28
+ }
29
+
30
+ function createIx(
31
+ funderPubkey: PublicKey,
32
+ associatedTokenAccountPublicKey: PublicKey,
33
+ ownerPublicKey: PublicKey,
34
+ tokenMintPublicKey: PublicKey
35
+ ) {
36
+ return new TransactionInstruction({
37
+ programId: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
38
+ data: Buffer.from([]),
39
+ keys: [
40
+ { pubkey: funderPubkey, isSigner: true, isWritable: true },
41
+ {
42
+ pubkey: associatedTokenAccountPublicKey,
43
+ isSigner: false,
44
+ isWritable: true,
45
+ },
46
+ { pubkey: ownerPublicKey, isSigner: false, isWritable: false },
47
+ { pubkey: tokenMintPublicKey, isSigner: false, isWritable: false },
48
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
49
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
50
+ { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
51
+ ],
52
+ })
53
+ }
54
+
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) => {
57
+ const sourcePubkey = new PublicKey(fromTokenAddress) // the token ADDRESS needed!
58
+ const destinationPubkey = new PublicKey(to)
59
+ console.log(`destination token address: ${destinationPubkey.toBase58()}`)
60
+ const ownerAccountOrWalletPublicKey = new PublicKey(owner) // the only native SOL address
61
+
62
+ const transferIx = Token.createTransferInstruction(
63
+ TOKEN_PROGRAM_ID,
64
+ sourcePubkey,
65
+ destinationPubkey,
66
+ ownerAccountOrWalletPublicKey,
67
+ [],
68
+ amount
69
+ )
70
+
71
+ return transferIx
72
+ }
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // @flow
2
+
3
+ export * from './constants'
4
+ export * from './encode'
5
+ export * from './keypair'
6
+ export * from './tx'
7
+ export { StakeInstruction } from './vendor'
8
+ export { default as tokens } from './tokens'
9
+ export { default as Transaction } from './transaction'
package/src/keypair.js ADDED
@@ -0,0 +1,32 @@
1
+ // @flow
2
+ import nacl from 'tweetnacl'
3
+
4
+ export function getKeyPairFromPrivateKey(privateKey: string | Buffer): Object {
5
+ const { publicKey, secretKey } = nacl.sign.keyPair.fromSeed(
6
+ Buffer.from(privateKey, 'hex').slice(0, 32)
7
+ )
8
+
9
+ return {
10
+ secretKey: Buffer.from(secretKey, 'hex'), // secretKey
11
+ publicKey: Buffer.from(publicKey, 'hex'),
12
+ }
13
+ }
14
+
15
+ export function getPublicKey(privateKey: string | Buffer): Buffer {
16
+ return getKeyPairFromPrivateKey(privateKey).publicKey
17
+ }
18
+
19
+ export function sign(data: Buffer, privateKey: string | Buffer): Buffer {
20
+ return Buffer.from(
21
+ nacl.sign.detached(data, getKeyPairFromPrivateKey(Buffer.from(privateKey, 'hex')).secretKey),
22
+ 'hex'
23
+ )
24
+ }
25
+
26
+ export function verifySignature(
27
+ data: Buffer,
28
+ signature: Buffer,
29
+ publicKey: string | Buffer
30
+ ): boolean {
31
+ return nacl.sign.detached.verify(data, signature, Buffer.from(publicKey, 'hex'))
32
+ }
package/src/tokens.js ADDED
@@ -0,0 +1,19 @@
1
+ // adapted from 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
+ tokenProperName: 'Serum',
8
+ tokenSymbol: 'SRM',
9
+ },
10
+ {
11
+ mintAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
12
+ tokenName: 'usdcoin_solana',
13
+ tokenProperName: 'USD Coin Solana',
14
+ tokenSymbol: 'USDCSOL',
15
+ },
16
+ // ....
17
+ ]
18
+
19
+ export default TOKENS
@@ -0,0 +1,292 @@
1
+ // @flow
2
+ import assert from 'assert'
3
+ import bs58 from 'bs58'
4
+ import { get } from 'lodash'
5
+
6
+ import { getKeyPairFromPrivateKey } from './keypair'
7
+ import { findAssociatedTokenAddress, createStakeAddress } from './encode'
8
+ import {
9
+ createAssociatedTokenAccount,
10
+ createTokenTransferInstruction,
11
+ } from './helpers/tokenTransfer'
12
+ import {
13
+ PublicKey,
14
+ Account,
15
+ Transaction,
16
+ SystemProgram,
17
+ StakeProgram,
18
+ StakeInstruction,
19
+ Authorized,
20
+ Lockup,
21
+ } from './vendor'
22
+ import { SEED, STAKE_PROGRAM_ID } from './constants'
23
+ import TOKENS from './tokens'
24
+
25
+ class Tx {
26
+ constructor({
27
+ from,
28
+ to,
29
+ amount,
30
+ recentBlockhash,
31
+ // fee, // (Fee per Signature: 5000 lamports)
32
+ // Tokens related:
33
+ tokenName,
34
+ destinationAddressType,
35
+ isAssociatedTokenAccountActive, // true when recipient balance !== 0
36
+ fromTokenAddresses, // sender token addresses
37
+ } = {}) {
38
+ assert(from, 'from is required')
39
+ assert(to, 'to is required')
40
+ assert(amount, 'amount is required')
41
+ assert(typeof amount === 'number', 'amount must be a number')
42
+ assert(recentBlockhash, 'recentBlockhash is required')
43
+ if (tokenName) {
44
+ assert(
45
+ typeof destinationAddressType !== 'undefined',
46
+ 'destinationAddressType is required when sending tokens'
47
+ )
48
+ assert(
49
+ typeof isAssociatedTokenAccountActive !== 'undefined',
50
+ 'isAssociatedTokenAccountActive is required when sending tokens'
51
+ ) // needed to create the recipient account
52
+ assert(Array.isArray(fromTokenAddresses), 'fromTokenAddresses Array is required')
53
+ }
54
+
55
+ const token = TOKENS.find(({ tokenName: name }) => name === tokenName)
56
+ this.txObj = {
57
+ from,
58
+ to,
59
+ amount,
60
+ recentBlockhash,
61
+ token,
62
+ destinationAddressType,
63
+ isAssociatedTokenAccountActive,
64
+ fromTokenAddresses,
65
+ }
66
+
67
+ if (token) {
68
+ // TOKEN transfer tx
69
+ this.buildTokenTransaction(this.txObj)
70
+ } else {
71
+ // SOL tx
72
+ this.buildSOLtransaction(this.txObj)
73
+ }
74
+ }
75
+
76
+ buildSOLtransaction({ from, to, amount, recentBlockhash }) {
77
+ const txInstruction = SystemProgram.transfer({
78
+ fromPubkey: new PublicKey(from),
79
+ toPubkey: new PublicKey(to),
80
+ lamports: amount,
81
+ })
82
+
83
+ this.transaction = new Transaction({
84
+ instructions: [txInstruction],
85
+ recentBlockhash,
86
+ })
87
+ }
88
+
89
+ buildTokenTransaction({
90
+ from,
91
+ to,
92
+ amount,
93
+ recentBlockhash,
94
+ token,
95
+ destinationAddressType,
96
+ isAssociatedTokenAccountActive,
97
+ fromTokenAddresses,
98
+ }) {
99
+ this.transaction = new Transaction({
100
+ recentBlockhash,
101
+ })
102
+ const isUnknown = destinationAddressType === null
103
+ const isSOLaddress = destinationAddressType === 'solana'
104
+ // crete account instruction
105
+ if (isUnknown) throw new Error('Destination SOL balance cannot be zero (address not active)') // cannot initialize without knowing the owner
106
+ if (isSOLaddress && !isAssociatedTokenAccountActive)
107
+ this.transaction.add(createAssociatedTokenAccount(from, token.mintAddress, to))
108
+
109
+ let amountLeft = amount
110
+ let amountToSend
111
+ for (let { ticker, tokenAccountAddress, balance } of fromTokenAddresses) {
112
+ // need to add more of this instruction until we reach the desired balance (amount) to send
113
+ assert(ticker === token.tokenSymbol, `Got unexpected ticker ${ticker}`)
114
+ if (amountLeft === 0) break
115
+
116
+ balance = Number(balance)
117
+ if (balance >= amountLeft) {
118
+ amountToSend = amountLeft
119
+ amountLeft = 0
120
+ } else {
121
+ amountToSend = balance // not enough balance
122
+ amountLeft -= amountToSend
123
+ }
124
+
125
+ const dest = isSOLaddress ? findAssociatedTokenAddress(to, token.mintAddress) : to
126
+ // add transfer token instruction
127
+ this.transaction.add(
128
+ createTokenTransferInstruction(from, tokenAccountAddress, dest, amountToSend)
129
+ )
130
+ }
131
+
132
+ assert(amountLeft === 0, `Not enough balance to send ${amount} ${token.tokenSymbol}`)
133
+ }
134
+
135
+ static createStakeAccountTransaction({ address, amount, seed = SEED, pool, recentBlockhash }) {
136
+ const fromPubkey = new PublicKey(address)
137
+ const stakeAddress = createStakeAddress(address, seed)
138
+ const stakePublicKey = new PublicKey(stakeAddress)
139
+ const poolKey = new PublicKey(pool)
140
+
141
+ const authorized = new Authorized(fromPubkey, fromPubkey) // staker, withdrawer
142
+ const lockup = new Lockup(0, 0, fromPubkey) // no lockup
143
+
144
+ // create account instruction
145
+ const programTx = StakeProgram.createAccountWithSeed({
146
+ fromPubkey,
147
+ stakePubkey: stakePublicKey,
148
+ basePubkey: fromPubkey,
149
+ seed,
150
+ authorized,
151
+ lockup,
152
+ lamports: amount, // number
153
+ })
154
+
155
+ // delegate funds instruction
156
+ const delegateTx = StakeProgram.delegate({
157
+ stakePubkey: stakePublicKey,
158
+ authorizedPubkey: fromPubkey,
159
+ votePubkey: poolKey, // pool vote key
160
+ })
161
+
162
+ const transaction = new Transaction({ recentBlockhash }).add(programTx).add(delegateTx)
163
+ return transaction
164
+ }
165
+
166
+ static undelegate({ address, stakeAddresses, recentBlockhash }) {
167
+ // undelegate all stake addresses
168
+ assert(Array.isArray(stakeAddresses), 'stakeAddresses Array is required')
169
+
170
+ const fromPubkey = new PublicKey(address)
171
+ const transaction = new Transaction({ recentBlockhash })
172
+
173
+ stakeAddresses.forEach((stakeAddress) => {
174
+ const stakePublicKey = new PublicKey(stakeAddress)
175
+ const programTx = StakeProgram.deactivate({
176
+ stakePubkey: stakePublicKey,
177
+ authorizedPubkey: fromPubkey,
178
+ })
179
+ transaction.add(programTx)
180
+ })
181
+
182
+ return transaction
183
+ }
184
+
185
+ static withdraw({ address, stakeAddresses, amount, recentBlockhash }) {
186
+ const fromPubkey = new PublicKey(address)
187
+ const stakeAddress = Array.isArray(stakeAddresses) ? stakeAddresses[0] : stakeAddresses
188
+ const stakePublicKey = new PublicKey(stakeAddress)
189
+
190
+ const transaction = StakeProgram.withdraw({
191
+ stakePubkey: stakePublicKey,
192
+ authorizedPubkey: fromPubkey,
193
+ toPubkey: fromPubkey,
194
+ lamports: amount,
195
+ })
196
+ transaction.recentBlockhash = recentBlockhash
197
+ return transaction
198
+ }
199
+
200
+ static sign(tx, privateKey: Buffer | string) {
201
+ if (!privateKey) throw new Error('Please provide a secretKey')
202
+ const { secretKey } = getKeyPairFromPrivateKey(privateKey)
203
+ const signers = [new Account(secretKey)]
204
+
205
+ let transaction = tx
206
+ if (tx instanceof Tx) transaction = tx.transaction
207
+
208
+ transaction.sign(...signers)
209
+ if (!transaction.signature) {
210
+ throw new Error('!signature') // should never happen
211
+ }
212
+ }
213
+
214
+ serialize() {
215
+ const wireTransaction = this.transaction.serialize()
216
+ return wireTransaction.toString('base64')
217
+ }
218
+
219
+ static serialize(tx) {
220
+ let transaction = tx
221
+ if (tx instanceof Tx) transaction = tx.transaction
222
+ return transaction.serialize().toString('base64')
223
+ }
224
+
225
+ static decodeStakingTx(serialized: string) {
226
+ // as base64
227
+ const wireTransaction = Buffer.from(serialized, 'base64')
228
+ const tx = Transaction.from(wireTransaction) // Transaction instance
229
+
230
+ const txId = bs58.encode(tx.signature)
231
+
232
+ const stakingInstructions = tx.instructions.filter(
233
+ (ix) => ix.programId.toString() === STAKE_PROGRAM_ID.toString()
234
+ )
235
+ const isStakingTx = !!stakingInstructions.length
236
+
237
+ if (!isStakingTx) return null // normal transfer tx
238
+
239
+ const info = {
240
+ txId,
241
+ owner: tx.feePayer.toString(), // SOL sender
242
+ }
243
+ const txDetails = stakingInstructions.reduce((info, ix) => {
244
+ const type = StakeInstruction.decodeInstructionType(ix)
245
+ switch (type) {
246
+ case 'Delegate':
247
+ info.type = 'Delegate'
248
+ info.stakeAddress = get(ix, 'keys[0].pubkey', '').toString()
249
+ info.validator = get(ix, 'keys[1].pubkey', '').toString() // pool
250
+ return info
251
+
252
+ case 'Deactivate': // undelegate
253
+ info.type = 'Deactivate'
254
+ info.stakeAddress = get(ix, 'keys[0].pubkey', '').toString()
255
+ // TODO: could have multiple addresses undelegating
256
+ return info
257
+
258
+ case 'Withdraw':
259
+ info.type = 'Withdraw'
260
+ info.stakeAddress = get(ix, 'keys[0].pubkey', '').toString()
261
+ const { lamports, toPubkey } = StakeInstruction.decodeWithdraw(ix)
262
+ info.to = toPubkey.toString()
263
+ info.lamports = lamports.toString()
264
+ return info
265
+
266
+ default:
267
+ // skip unknown instruction type
268
+ return info
269
+ }
270
+ }, info)
271
+
272
+ return txDetails
273
+ }
274
+
275
+ getTxId() {
276
+ if (!this.transaction.signature) {
277
+ throw new Error('Cannot get txId, tx is not signed')
278
+ }
279
+ return bs58.encode(this.transaction.signature)
280
+ }
281
+
282
+ static getTxId(tx) {
283
+ let transaction = tx
284
+ if (tx instanceof Tx) transaction = tx.transaction
285
+ if (!transaction.signature) {
286
+ throw new Error('Cannot get txId, tx is not signed')
287
+ }
288
+ return bs58.encode(transaction.signature)
289
+ }
290
+ }
291
+
292
+ export default Tx
@@ -0,0 +1,8 @@
1
+ import { createUnsignedTx } from './create-unsigned-tx'
2
+ import { signUnsignedTx } from './sign-unsigned-tx'
3
+ import type { ParsedTransaction, SignedTransaction } from '@exodus/models/lib/types'
4
+
5
+ export function createAndSignTx(input: ParsedTransaction, privateKey: Buffer): SignedTransaction {
6
+ const unsignedTx = createUnsignedTx(input)
7
+ return signUnsignedTx(unsignedTx, privateKey)
8
+ }