@exodus/solana-lib 1.2.4 → 1.2.7
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 +3 -3
- package/src/constants.js +5 -1
- package/src/encode.js +15 -2
- package/src/helpers/tokenTransfer.js +4 -10
- package/src/tokens.js +7 -14
- package/src/transaction.js +60 -15
- package/src/tx/create-unsigned-tx.js +10 -0
- package/src/tx/parse-unsigned-tx.js +15 -1
- package/src/tx/sign-unsigned-tx.js +14 -1
- package/src/vendor/index.js +1 -0
- package/src/vendor/publickey.js +20 -0
- package/src/vendor/stake-program.js +675 -0
- package/src/vendor/transaction.js +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-lib",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "Exodus internal Solana low-level library",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@exodus/asset-lib": "^3.4.3",
|
|
17
|
-
"@exodus/assets": "^8.0.
|
|
17
|
+
"@exodus/assets": "^8.0.2",
|
|
18
18
|
"@exodus/buffer-layout": "^1.2.0-exodus1",
|
|
19
19
|
"@exodus/models": "^8.4.0",
|
|
20
20
|
"bn.js": "~4.11.0",
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
"create-hash": "~1.1.3",
|
|
23
23
|
"tweetnacl": "^1.0.3"
|
|
24
24
|
},
|
|
25
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "892c55db784616256e065a2c931a5e5611f5404f"
|
|
26
26
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { PublicKey, SystemProgram } from './vendor'
|
|
1
|
+
import { PublicKey, SystemProgram, StakeProgram } from './vendor'
|
|
2
2
|
|
|
3
3
|
export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
|
|
4
4
|
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
|
|
5
5
|
)
|
|
6
6
|
export const SYSTEM_PROGRAM_ID = SystemProgram.programId
|
|
7
7
|
|
|
8
|
+
export const STAKE_PROGRAM_ID = StakeProgram.programId
|
|
9
|
+
|
|
8
10
|
export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
|
|
11
|
+
|
|
12
|
+
export const SEED = 'stake:0'
|
package/src/encode.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import { PublicKey } from './vendor'
|
|
3
|
-
import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID } from './constants'
|
|
2
|
+
import { PublicKey, StakeProgram } from './vendor'
|
|
3
|
+
import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID, SEED } from './constants'
|
|
4
4
|
import { getPublicKey } from './keypair'
|
|
5
5
|
import bs58 from 'bs58'
|
|
6
6
|
import BN from 'bn.js'
|
|
@@ -42,3 +42,16 @@ export function findAssociatedTokenAddress(
|
|
|
42
42
|
SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
|
|
43
43
|
)[0].toBase58() // returns encoded PublicKey
|
|
44
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
|
+
}
|
|
@@ -52,17 +52,11 @@ function createIx(
|
|
|
52
52
|
})
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// https://github.com/paul-schaaf/spl-token-ui/blob/main/src/solana/token/editing.ts#
|
|
56
|
-
export const createTokenTransferInstruction = (
|
|
57
|
-
owner,
|
|
58
|
-
fromTokenAddress,
|
|
59
|
-
tokenMintAddress,
|
|
60
|
-
to,
|
|
61
|
-
amount
|
|
62
|
-
) => {
|
|
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) => {
|
|
63
57
|
const sourcePubkey = new PublicKey(fromTokenAddress) // the token ADDRESS needed!
|
|
64
|
-
const destinationPubkey = new PublicKey(
|
|
65
|
-
console.log(`destination
|
|
58
|
+
const destinationPubkey = new PublicKey(to)
|
|
59
|
+
console.log(`destination token address: ${destinationPubkey.toBase58()}`)
|
|
66
60
|
const ownerAccountOrWalletPublicKey = new PublicKey(owner) // the only native SOL address
|
|
67
61
|
|
|
68
62
|
const transferIx = Token.createTransferInstruction(
|
package/src/tokens.js
CHANGED
|
@@ -1,26 +1,19 @@
|
|
|
1
|
-
//
|
|
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: '
|
|
6
|
+
tokenName: 'serum',
|
|
7
|
+
tokenProperName: 'Serum',
|
|
7
8
|
tokenSymbol: 'SRM',
|
|
8
9
|
},
|
|
9
10
|
{
|
|
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
11
|
mintAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
19
|
-
tokenName: '
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
tokenName: 'usdcoin_solana',
|
|
13
|
+
tokenProperName: 'USD Coin Solana',
|
|
14
|
+
tokenSymbol: 'USDC Solana',
|
|
22
15
|
},
|
|
23
|
-
//
|
|
16
|
+
// ....
|
|
24
17
|
]
|
|
25
18
|
|
|
26
19
|
export default TOKENS
|
package/src/transaction.js
CHANGED
|
@@ -2,11 +2,21 @@
|
|
|
2
2
|
import assert from 'assert'
|
|
3
3
|
|
|
4
4
|
import { getKeyPairFromPrivateKey } from './keypair'
|
|
5
|
+
import { findAssociatedTokenAddress, createStakeAddress } from './encode'
|
|
5
6
|
import {
|
|
6
7
|
createAssociatedTokenAccount,
|
|
7
8
|
createTokenTransferInstruction,
|
|
8
9
|
} from './helpers/tokenTransfer'
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
PublicKey,
|
|
12
|
+
Account,
|
|
13
|
+
Transaction,
|
|
14
|
+
SystemProgram,
|
|
15
|
+
StakeProgram,
|
|
16
|
+
Authorized,
|
|
17
|
+
Lockup,
|
|
18
|
+
} from './vendor'
|
|
19
|
+
import { SEED } from './constants'
|
|
10
20
|
import TOKENS from './tokens'
|
|
11
21
|
import bs58 from 'bs58'
|
|
12
22
|
|
|
@@ -18,7 +28,8 @@ class Tx {
|
|
|
18
28
|
recentBlockhash,
|
|
19
29
|
// fee, // (Fee per Signature: 5000 lamports)
|
|
20
30
|
// Tokens related:
|
|
21
|
-
|
|
31
|
+
tokenName,
|
|
32
|
+
destinationAddressType,
|
|
22
33
|
isAssociatedTokenAccountActive, // true when recipient balance !== 0
|
|
23
34
|
fromTokenAddresses, // sender token addresses
|
|
24
35
|
} = {}) {
|
|
@@ -27,7 +38,11 @@ class Tx {
|
|
|
27
38
|
assert(amount, 'amount is required')
|
|
28
39
|
assert(typeof amount === 'number', 'amount must be a number')
|
|
29
40
|
assert(recentBlockhash, 'recentBlockhash is required')
|
|
30
|
-
if (
|
|
41
|
+
if (tokenName) {
|
|
42
|
+
assert(
|
|
43
|
+
typeof destinationAddressType !== 'undefined',
|
|
44
|
+
'destinationAddressType is required when sending tokens'
|
|
45
|
+
)
|
|
31
46
|
assert(
|
|
32
47
|
typeof isAssociatedTokenAccountActive !== 'undefined',
|
|
33
48
|
'isAssociatedTokenAccountActive is required when sending tokens'
|
|
@@ -35,13 +50,14 @@ class Tx {
|
|
|
35
50
|
assert(Array.isArray(fromTokenAddresses), 'fromTokenAddresses Array is required')
|
|
36
51
|
}
|
|
37
52
|
|
|
38
|
-
const token = TOKENS.find(({
|
|
53
|
+
const token = TOKENS.find(({ tokenName: name }) => name === tokenName)
|
|
39
54
|
this.txObj = {
|
|
40
55
|
from,
|
|
41
56
|
to,
|
|
42
57
|
amount,
|
|
43
58
|
recentBlockhash,
|
|
44
59
|
token,
|
|
60
|
+
destinationAddressType,
|
|
45
61
|
isAssociatedTokenAccountActive,
|
|
46
62
|
fromTokenAddresses,
|
|
47
63
|
}
|
|
@@ -74,14 +90,18 @@ class Tx {
|
|
|
74
90
|
amount,
|
|
75
91
|
recentBlockhash,
|
|
76
92
|
token,
|
|
93
|
+
destinationAddressType,
|
|
77
94
|
isAssociatedTokenAccountActive,
|
|
78
95
|
fromTokenAddresses,
|
|
79
96
|
}) {
|
|
80
97
|
this.transaction = new Transaction({
|
|
81
98
|
recentBlockhash,
|
|
82
99
|
})
|
|
100
|
+
const isUnknown = destinationAddressType === null
|
|
101
|
+
const isSOLaddress = destinationAddressType === 'solana'
|
|
83
102
|
// crete account instruction
|
|
84
|
-
if (
|
|
103
|
+
if (isUnknown) throw new Error('Destination SOL balance cannot be zero (address not active)') // cannot initialize without knowing the owner
|
|
104
|
+
if (isSOLaddress && !isAssociatedTokenAccountActive)
|
|
85
105
|
this.transaction.add(createAssociatedTokenAccount(from, token.mintAddress, to))
|
|
86
106
|
|
|
87
107
|
let amountLeft = amount
|
|
@@ -100,28 +120,47 @@ class Tx {
|
|
|
100
120
|
amountLeft -= amountToSend
|
|
101
121
|
}
|
|
102
122
|
|
|
123
|
+
const dest = isSOLaddress ? findAssociatedTokenAddress(to, token.mintAddress) : to
|
|
103
124
|
// add transfer token instruction
|
|
104
125
|
this.transaction.add(
|
|
105
|
-
createTokenTransferInstruction(
|
|
106
|
-
from,
|
|
107
|
-
tokenAccountAddress,
|
|
108
|
-
token.mintAddress,
|
|
109
|
-
to,
|
|
110
|
-
amountToSend
|
|
111
|
-
)
|
|
126
|
+
createTokenTransferInstruction(from, tokenAccountAddress, dest, amountToSend)
|
|
112
127
|
)
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
assert(amountLeft === 0, `Not enough balance to send ${amount} ${token.tokenSymbol}`)
|
|
116
131
|
}
|
|
117
132
|
|
|
118
|
-
static
|
|
133
|
+
static createStakeAccountTransaction({ address, amount, seed = SEED, recentBlockhash }) {
|
|
134
|
+
const fromPubkey = new PublicKey(address)
|
|
135
|
+
const stakeAddress = createStakeAddress(address, seed)
|
|
136
|
+
const stakePublicKey = new PublicKey(stakeAddress)
|
|
137
|
+
|
|
138
|
+
const authorized = new Authorized(fromPubkey, fromPubkey) // staker, withdrawer
|
|
139
|
+
const lockup = new Lockup(0, 0, fromPubkey) // no lockup
|
|
140
|
+
|
|
141
|
+
const transaction = StakeProgram.createAccountWithSeed({
|
|
142
|
+
fromPubkey,
|
|
143
|
+
stakePubkey: stakePublicKey,
|
|
144
|
+
basePubkey: fromPubkey,
|
|
145
|
+
seed,
|
|
146
|
+
authorized,
|
|
147
|
+
lockup,
|
|
148
|
+
lamports: amount, // number
|
|
149
|
+
})
|
|
150
|
+
transaction.recentBlockhash = recentBlockhash
|
|
151
|
+
return transaction
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static sign(tx, privateKey: Buffer | string) {
|
|
119
155
|
if (!privateKey) throw new Error('Please provide a secretKey')
|
|
120
156
|
const { secretKey } = getKeyPairFromPrivateKey(privateKey)
|
|
121
157
|
const signers = [new Account(secretKey)]
|
|
122
158
|
|
|
123
|
-
tx
|
|
124
|
-
if (
|
|
159
|
+
let transaction = tx
|
|
160
|
+
if (tx instanceof Tx) transaction = tx.transaction
|
|
161
|
+
|
|
162
|
+
transaction.sign(...signers)
|
|
163
|
+
if (!transaction.signature) {
|
|
125
164
|
throw new Error('!signature') // should never happen
|
|
126
165
|
}
|
|
127
166
|
}
|
|
@@ -131,6 +170,12 @@ class Tx {
|
|
|
131
170
|
return wireTransaction.toString('base64')
|
|
132
171
|
}
|
|
133
172
|
|
|
173
|
+
static serialize(tx) {
|
|
174
|
+
let transaction = tx
|
|
175
|
+
if (tx instanceof Tx) transaction = tx.transaction
|
|
176
|
+
return transaction.serialize().toString('base64')
|
|
177
|
+
}
|
|
178
|
+
|
|
134
179
|
getTxId() {
|
|
135
180
|
if (!this.transaction.signature) {
|
|
136
181
|
throw new Error('Cannot get txId, tx is not signed')
|
|
@@ -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 {
|
|
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 {
|
|
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
|
package/src/vendor/index.js
CHANGED
package/src/vendor/publickey.js
CHANGED
|
@@ -97,6 +97,26 @@ export class PublicKey {
|
|
|
97
97
|
throw new Error('Unable to find a viable program address nonce')
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Derive a public key from another key, a seed, and a program ID.
|
|
102
|
+
* HACK: transformed in sync function
|
|
103
|
+
*/
|
|
104
|
+
static createWithSeed(
|
|
105
|
+
fromPublicKey: PublicKey,
|
|
106
|
+
seed: string,
|
|
107
|
+
programId: PublicKey
|
|
108
|
+
): Promise<PublicKey> {
|
|
109
|
+
const buffer = Buffer.concat([
|
|
110
|
+
fromPublicKey.toBuffer(),
|
|
111
|
+
Buffer.from(seed),
|
|
112
|
+
programId.toBuffer(),
|
|
113
|
+
])
|
|
114
|
+
const hash = createHash('sha256')
|
|
115
|
+
.update(buffer)
|
|
116
|
+
.digest('hex')
|
|
117
|
+
return new PublicKey(Buffer.from(hash, 'hex'))
|
|
118
|
+
}
|
|
119
|
+
|
|
100
120
|
/**
|
|
101
121
|
* Derive a program address from seeds and a program ID.
|
|
102
122
|
*/
|
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import * as BufferLayout from '@exodus/buffer-layout'
|
|
4
|
+
|
|
5
|
+
import { encodeData, decodeData } from './instruction'
|
|
6
|
+
import * as Layout from './utils/layout'
|
|
7
|
+
import { PublicKey } from './publickey'
|
|
8
|
+
import { SystemProgram } from './system-program'
|
|
9
|
+
import { SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY, SYSVAR_STAKE_HISTORY_PUBKEY } from './sysvar'
|
|
10
|
+
import { Transaction, TransactionInstruction } from './transaction'
|
|
11
|
+
|
|
12
|
+
export const STAKE_CONFIG_ID = new PublicKey('StakeConfig11111111111111111111111111111111')
|
|
13
|
+
|
|
14
|
+
export class Authorized {
|
|
15
|
+
staker: PublicKey
|
|
16
|
+
withdrawer: PublicKey
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a new Authorized object
|
|
20
|
+
*/
|
|
21
|
+
constructor(staker: PublicKey, withdrawer: PublicKey) {
|
|
22
|
+
this.staker = staker
|
|
23
|
+
this.withdrawer = withdrawer
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class Lockup {
|
|
28
|
+
unixTimestamp: number
|
|
29
|
+
epoch: number
|
|
30
|
+
custodian: PublicKey
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a new Lockup object
|
|
34
|
+
*/
|
|
35
|
+
constructor(unixTimestamp: number, epoch: number, custodian: PublicKey) {
|
|
36
|
+
this.unixTimestamp = unixTimestamp
|
|
37
|
+
this.epoch = epoch
|
|
38
|
+
this.custodian = custodian
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create stake account transaction params
|
|
44
|
+
* @typedef {Object} CreateStakeAccountParams
|
|
45
|
+
* @property {PublicKey} fromPubkey
|
|
46
|
+
* @property {PublicKey} stakePubkey
|
|
47
|
+
* @property {Authorized} authorized
|
|
48
|
+
* @property {Lockup} lockup
|
|
49
|
+
* @property {number} lamports
|
|
50
|
+
*/
|
|
51
|
+
export type CreateStakeAccountParams = {|
|
|
52
|
+
fromPubkey: PublicKey,
|
|
53
|
+
stakePubkey: PublicKey,
|
|
54
|
+
authorized: Authorized,
|
|
55
|
+
lockup: Lockup,
|
|
56
|
+
lamports: number,
|
|
57
|
+
|}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create stake account with seed transaction params
|
|
61
|
+
* @typedef {Object} CreateStakeAccountWithSeedParams
|
|
62
|
+
* @property {PublicKey} fromPubkey
|
|
63
|
+
* @property {PublicKey} stakePubkey
|
|
64
|
+
* @property {PublicKey} basePubkey
|
|
65
|
+
* @property {string} seed
|
|
66
|
+
* @property {Authorized} authorized
|
|
67
|
+
* @property {Lockup} lockup
|
|
68
|
+
* @property {number} lamports
|
|
69
|
+
*/
|
|
70
|
+
export type CreateStakeAccountWithSeedParams = {|
|
|
71
|
+
fromPubkey: PublicKey,
|
|
72
|
+
stakePubkey: PublicKey,
|
|
73
|
+
basePubkey: PublicKey,
|
|
74
|
+
seed: string,
|
|
75
|
+
authorized: Authorized,
|
|
76
|
+
lockup: Lockup,
|
|
77
|
+
lamports: number,
|
|
78
|
+
|}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Initialize stake instruction params
|
|
82
|
+
* @typedef {Object} InitializeStakeParams
|
|
83
|
+
* @property {PublicKey} stakePubkey
|
|
84
|
+
* @property {Authorized} authorized
|
|
85
|
+
* @property {Lockup} lockup
|
|
86
|
+
*/
|
|
87
|
+
export type InitializeStakeParams = {|
|
|
88
|
+
stakePubkey: PublicKey,
|
|
89
|
+
authorized: Authorized,
|
|
90
|
+
lockup: Lockup,
|
|
91
|
+
|}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Delegate stake instruction params
|
|
95
|
+
* @typedef {Object} DelegateStakeParams
|
|
96
|
+
* @property {PublicKey} stakePubkey
|
|
97
|
+
* @property {PublicKey} authorizedPubkey
|
|
98
|
+
* @property {PublicKey} votePubkey
|
|
99
|
+
*/
|
|
100
|
+
export type DelegateStakeParams = {|
|
|
101
|
+
stakePubkey: PublicKey,
|
|
102
|
+
authorizedPubkey: PublicKey,
|
|
103
|
+
votePubkey: PublicKey,
|
|
104
|
+
|}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @typedef {Object} StakeAuthorizationType
|
|
108
|
+
* @property (index} The Stake Authorization index (from solana-stake-program)
|
|
109
|
+
*/
|
|
110
|
+
export type StakeAuthorizationType = {|
|
|
111
|
+
index: number,
|
|
112
|
+
|}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Authorize stake instruction params
|
|
116
|
+
* @typedef {Object} AuthorizeStakeParams
|
|
117
|
+
* @property {PublicKey} stakePubkey
|
|
118
|
+
* @property {PublicKey} authorizedPubkey
|
|
119
|
+
* @property {PublicKey} newAuthorizedPubkey
|
|
120
|
+
* @property {StakeAuthorizationType} stakeAuthorizationType
|
|
121
|
+
*/
|
|
122
|
+
export type AuthorizeStakeParams = {|
|
|
123
|
+
stakePubkey: PublicKey,
|
|
124
|
+
authorizedPubkey: PublicKey,
|
|
125
|
+
newAuthorizedPubkey: PublicKey,
|
|
126
|
+
stakeAuthorizationType: StakeAuthorizationType,
|
|
127
|
+
|}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Authorize stake instruction params using a derived key
|
|
131
|
+
* @typedef {Object} AuthorizeWithSeedStakeParams
|
|
132
|
+
* @property {PublicKey} stakePubkey
|
|
133
|
+
* @property {PublicKey} authorityBase
|
|
134
|
+
* @property {string} authoritySeed
|
|
135
|
+
* @property {PublicKey} authorityOwner
|
|
136
|
+
* @property {PublicKey} newAuthorizedPubkey
|
|
137
|
+
* @property {StakeAuthorizationType} stakeAuthorizationType
|
|
138
|
+
*/
|
|
139
|
+
export type AuthorizeWithSeedStakeParams = {|
|
|
140
|
+
stakePubkey: PublicKey,
|
|
141
|
+
authorityBase: PublicKey,
|
|
142
|
+
authoritySeed: string,
|
|
143
|
+
authorityOwner: PublicKey,
|
|
144
|
+
newAuthorizedPubkey: PublicKey,
|
|
145
|
+
stakeAuthorizationType: StakeAuthorizationType,
|
|
146
|
+
|}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Split stake instruction params
|
|
150
|
+
* @typedef {Object} SplitStakeParams
|
|
151
|
+
* @property {PublicKey} stakePubkey
|
|
152
|
+
* @property {PublicKey} authorizedPubkey
|
|
153
|
+
* @property {PublicKey} splitStakePubkey
|
|
154
|
+
* @property {number} lamports
|
|
155
|
+
*/
|
|
156
|
+
export type SplitStakeParams = {|
|
|
157
|
+
stakePubkey: PublicKey,
|
|
158
|
+
authorizedPubkey: PublicKey,
|
|
159
|
+
splitStakePubkey: PublicKey,
|
|
160
|
+
lamports: number,
|
|
161
|
+
|}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Withdraw stake instruction params
|
|
165
|
+
* @typedef {Object} WithdrawStakeParams
|
|
166
|
+
* @property {PublicKey} stakePubkey
|
|
167
|
+
* @property {PublicKey} authorizedPubkey
|
|
168
|
+
* @property {PublicKey} toPubkey
|
|
169
|
+
* @property {number} lamports
|
|
170
|
+
*/
|
|
171
|
+
export type WithdrawStakeParams = {|
|
|
172
|
+
stakePubkey: PublicKey,
|
|
173
|
+
authorizedPubkey: PublicKey,
|
|
174
|
+
toPubkey: PublicKey,
|
|
175
|
+
lamports: number,
|
|
176
|
+
|}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Deactivate stake instruction params
|
|
180
|
+
* @typedef {Object} DeactivateStakeParams
|
|
181
|
+
* @property {PublicKey} stakePubkey
|
|
182
|
+
* @property {PublicKey} authorizedPubkey
|
|
183
|
+
*/
|
|
184
|
+
export type DeactivateStakeParams = {|
|
|
185
|
+
stakePubkey: PublicKey,
|
|
186
|
+
authorizedPubkey: PublicKey,
|
|
187
|
+
|}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* An enumeration of valid stake InstructionType's
|
|
191
|
+
*/
|
|
192
|
+
export const STAKE_INSTRUCTION_LAYOUTS = Object.freeze({
|
|
193
|
+
Initialize: {
|
|
194
|
+
index: 0,
|
|
195
|
+
layout: BufferLayout.struct([
|
|
196
|
+
BufferLayout.u32('instruction'),
|
|
197
|
+
Layout.authorized(),
|
|
198
|
+
Layout.lockup(),
|
|
199
|
+
]),
|
|
200
|
+
},
|
|
201
|
+
Authorize: {
|
|
202
|
+
index: 1,
|
|
203
|
+
layout: BufferLayout.struct([
|
|
204
|
+
BufferLayout.u32('instruction'),
|
|
205
|
+
Layout.publicKey('newAuthorized'),
|
|
206
|
+
BufferLayout.u32('stakeAuthorizationType'),
|
|
207
|
+
]),
|
|
208
|
+
},
|
|
209
|
+
Delegate: {
|
|
210
|
+
index: 2,
|
|
211
|
+
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
|
|
212
|
+
},
|
|
213
|
+
Split: {
|
|
214
|
+
index: 3,
|
|
215
|
+
layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]),
|
|
216
|
+
},
|
|
217
|
+
Withdraw: {
|
|
218
|
+
index: 4,
|
|
219
|
+
layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]),
|
|
220
|
+
},
|
|
221
|
+
Deactivate: {
|
|
222
|
+
index: 5,
|
|
223
|
+
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
|
|
224
|
+
},
|
|
225
|
+
AuthorizeWithSeed: {
|
|
226
|
+
index: 8,
|
|
227
|
+
layout: BufferLayout.struct([
|
|
228
|
+
BufferLayout.u32('instruction'),
|
|
229
|
+
Layout.publicKey('newAuthorized'),
|
|
230
|
+
BufferLayout.u32('stakeAuthorizationType'),
|
|
231
|
+
Layout.rustString('authoritySeed'),
|
|
232
|
+
Layout.publicKey('authorityOwner'),
|
|
233
|
+
]),
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Stake Instruction class
|
|
239
|
+
*/
|
|
240
|
+
export class StakeInstruction {
|
|
241
|
+
/**
|
|
242
|
+
* Decode a stake instruction and retrieve the instruction type.
|
|
243
|
+
*/
|
|
244
|
+
static decodeInstructionType(instruction: TransactionInstruction) {
|
|
245
|
+
this.checkProgramId(instruction.programId)
|
|
246
|
+
|
|
247
|
+
const instructionTypeLayout = BufferLayout.u32('instruction')
|
|
248
|
+
const typeIndex = instructionTypeLayout.decode(instruction.data)
|
|
249
|
+
|
|
250
|
+
let type
|
|
251
|
+
for (const t of Object.keys(STAKE_INSTRUCTION_LAYOUTS)) {
|
|
252
|
+
if (STAKE_INSTRUCTION_LAYOUTS[t].index === typeIndex) {
|
|
253
|
+
type = t
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!type) {
|
|
258
|
+
throw new Error('Instruction type incorrect; not a StakeInstruction')
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return type
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Decode a initialize stake instruction and retrieve the instruction params.
|
|
266
|
+
*/
|
|
267
|
+
static decodeInitialize(instruction: TransactionInstruction): InitializeStakeParams {
|
|
268
|
+
this.checkProgramId(instruction.programId)
|
|
269
|
+
this.checkKeyLength(instruction.keys, 2)
|
|
270
|
+
|
|
271
|
+
const { authorized, lockup } = decodeData(
|
|
272
|
+
STAKE_INSTRUCTION_LAYOUTS.Initialize,
|
|
273
|
+
instruction.data
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
stakePubkey: instruction.keys[0].pubkey,
|
|
278
|
+
authorized: new Authorized(
|
|
279
|
+
new PublicKey(authorized.staker),
|
|
280
|
+
new PublicKey(authorized.withdrawer)
|
|
281
|
+
),
|
|
282
|
+
lockup: new Lockup(lockup.unixTimestamp, lockup.epoch, new PublicKey(lockup.custodian)),
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Decode a delegate stake instruction and retrieve the instruction params.
|
|
288
|
+
*/
|
|
289
|
+
static decodeDelegate(instruction: TransactionInstruction): DelegateStakeParams {
|
|
290
|
+
this.checkProgramId(instruction.programId)
|
|
291
|
+
this.checkKeyLength(instruction.keys, 6)
|
|
292
|
+
decodeData(STAKE_INSTRUCTION_LAYOUTS.Delegate, instruction.data)
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
stakePubkey: instruction.keys[0].pubkey,
|
|
296
|
+
votePubkey: instruction.keys[1].pubkey,
|
|
297
|
+
authorizedPubkey: instruction.keys[5].pubkey,
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Decode an authorize stake instruction and retrieve the instruction params.
|
|
303
|
+
*/
|
|
304
|
+
static decodeAuthorize(instruction: TransactionInstruction): AuthorizeStakeParams {
|
|
305
|
+
this.checkProgramId(instruction.programId)
|
|
306
|
+
this.checkKeyLength(instruction.keys, 3)
|
|
307
|
+
const { newAuthorized, stakeAuthorizationType } = decodeData(
|
|
308
|
+
STAKE_INSTRUCTION_LAYOUTS.Authorize,
|
|
309
|
+
instruction.data
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
stakePubkey: instruction.keys[0].pubkey,
|
|
314
|
+
authorizedPubkey: instruction.keys[2].pubkey,
|
|
315
|
+
newAuthorizedPubkey: new PublicKey(newAuthorized),
|
|
316
|
+
stakeAuthorizationType: {
|
|
317
|
+
index: stakeAuthorizationType,
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Decode an authorize-with-seed stake instruction and retrieve the instruction params.
|
|
324
|
+
*/
|
|
325
|
+
static decodeAuthorizeWithSeed(
|
|
326
|
+
instruction: TransactionInstruction
|
|
327
|
+
): AuthorizeWithSeedStakeParams {
|
|
328
|
+
this.checkProgramId(instruction.programId)
|
|
329
|
+
this.checkKeyLength(instruction.keys, 2)
|
|
330
|
+
const { newAuthorized, stakeAuthorizationType, authoritySeed, authorityOwner } = decodeData(
|
|
331
|
+
STAKE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed,
|
|
332
|
+
instruction.data
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
stakePubkey: instruction.keys[0].pubkey,
|
|
337
|
+
authorityBase: instruction.keys[1].pubkey,
|
|
338
|
+
authoritySeed: authoritySeed,
|
|
339
|
+
authorityOwner: new PublicKey(authorityOwner),
|
|
340
|
+
newAuthorizedPubkey: new PublicKey(newAuthorized),
|
|
341
|
+
stakeAuthorizationType: {
|
|
342
|
+
index: stakeAuthorizationType,
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Decode a split stake instruction and retrieve the instruction params.
|
|
349
|
+
*/
|
|
350
|
+
static decodeSplit(instruction: TransactionInstruction): SplitStakeParams {
|
|
351
|
+
this.checkProgramId(instruction.programId)
|
|
352
|
+
this.checkKeyLength(instruction.keys, 3)
|
|
353
|
+
const { lamports } = decodeData(STAKE_INSTRUCTION_LAYOUTS.Split, instruction.data)
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
stakePubkey: instruction.keys[0].pubkey,
|
|
357
|
+
splitStakePubkey: instruction.keys[1].pubkey,
|
|
358
|
+
authorizedPubkey: instruction.keys[2].pubkey,
|
|
359
|
+
lamports,
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Decode a withdraw stake instruction and retrieve the instruction params.
|
|
365
|
+
*/
|
|
366
|
+
static decodeWithdraw(instruction: TransactionInstruction): WithdrawStakeParams {
|
|
367
|
+
this.checkProgramId(instruction.programId)
|
|
368
|
+
this.checkKeyLength(instruction.keys, 5)
|
|
369
|
+
const { lamports } = decodeData(STAKE_INSTRUCTION_LAYOUTS.Withdraw, instruction.data)
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
stakePubkey: instruction.keys[0].pubkey,
|
|
373
|
+
toPubkey: instruction.keys[1].pubkey,
|
|
374
|
+
authorizedPubkey: instruction.keys[4].pubkey,
|
|
375
|
+
lamports,
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Decode a deactivate stake instruction and retrieve the instruction params.
|
|
381
|
+
*/
|
|
382
|
+
static decodeDeactivate(instruction: TransactionInstruction): DeactivateStakeParams {
|
|
383
|
+
this.checkProgramId(instruction.programId)
|
|
384
|
+
this.checkKeyLength(instruction.keys, 3)
|
|
385
|
+
decodeData(STAKE_INSTRUCTION_LAYOUTS.Deactivate, instruction.data)
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
stakePubkey: instruction.keys[0].pubkey,
|
|
389
|
+
authorizedPubkey: instruction.keys[2].pubkey,
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* @private
|
|
395
|
+
*/
|
|
396
|
+
static checkProgramId(programId: PublicKey) {
|
|
397
|
+
if (!programId.equals(StakeProgram.programId)) {
|
|
398
|
+
throw new Error('invalid instruction; programId is not StakeProgram')
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* @private
|
|
404
|
+
*/
|
|
405
|
+
static checkKeyLength(keys: Array<any>, expectedLength: number) {
|
|
406
|
+
if (keys.length < expectedLength) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* An enumeration of valid StakeAuthorizationLayout's
|
|
416
|
+
*/
|
|
417
|
+
export const StakeAuthorizationLayout = Object.freeze({
|
|
418
|
+
Staker: {
|
|
419
|
+
index: 0,
|
|
420
|
+
},
|
|
421
|
+
Withdrawer: {
|
|
422
|
+
index: 1,
|
|
423
|
+
},
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Factory class for transactions to interact with the Stake program
|
|
428
|
+
*/
|
|
429
|
+
export class StakeProgram {
|
|
430
|
+
/**
|
|
431
|
+
* Public key that identifies the Stake program
|
|
432
|
+
*/
|
|
433
|
+
static get programId(): PublicKey {
|
|
434
|
+
return new PublicKey('Stake11111111111111111111111111111111111111')
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Max space of a Stake account
|
|
439
|
+
*
|
|
440
|
+
* This is generated from the solana-stake-program StakeState struct as
|
|
441
|
+
* `std::mem::size_of::<StakeState>()`:
|
|
442
|
+
* https://docs.rs/solana-stake-program/1.4.4/solana_stake_program/stake_state/enum.StakeState.html
|
|
443
|
+
*/
|
|
444
|
+
static get space(): number {
|
|
445
|
+
return 200
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Generate an Initialize instruction to add to a Stake Create transaction
|
|
450
|
+
*/
|
|
451
|
+
static initialize(params: InitializeStakeParams): TransactionInstruction {
|
|
452
|
+
const { stakePubkey, authorized, lockup } = params
|
|
453
|
+
const type = STAKE_INSTRUCTION_LAYOUTS.Initialize
|
|
454
|
+
const data = encodeData(type, {
|
|
455
|
+
authorized: {
|
|
456
|
+
staker: authorized.staker.toBuffer(),
|
|
457
|
+
withdrawer: authorized.withdrawer.toBuffer(),
|
|
458
|
+
},
|
|
459
|
+
lockup: {
|
|
460
|
+
unixTimestamp: lockup.unixTimestamp,
|
|
461
|
+
epoch: lockup.epoch,
|
|
462
|
+
custodian: lockup.custodian.toBuffer(),
|
|
463
|
+
},
|
|
464
|
+
})
|
|
465
|
+
const instructionData = {
|
|
466
|
+
keys: [
|
|
467
|
+
{ pubkey: stakePubkey, isSigner: false, isWritable: true },
|
|
468
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
469
|
+
],
|
|
470
|
+
programId: this.programId,
|
|
471
|
+
data,
|
|
472
|
+
}
|
|
473
|
+
return new TransactionInstruction(instructionData)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Generate a Transaction that creates a new Stake account at
|
|
478
|
+
* an address generated with `from`, a seed, and the Stake programId
|
|
479
|
+
*/
|
|
480
|
+
static createAccountWithSeed(params: CreateStakeAccountWithSeedParams): Transaction {
|
|
481
|
+
const transaction = new Transaction()
|
|
482
|
+
transaction.add(
|
|
483
|
+
SystemProgram.createAccountWithSeed({
|
|
484
|
+
fromPubkey: params.fromPubkey,
|
|
485
|
+
newAccountPubkey: params.stakePubkey,
|
|
486
|
+
basePubkey: params.basePubkey,
|
|
487
|
+
seed: params.seed,
|
|
488
|
+
lamports: params.lamports,
|
|
489
|
+
space: this.space,
|
|
490
|
+
programId: this.programId,
|
|
491
|
+
})
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
const { stakePubkey, authorized, lockup } = params
|
|
495
|
+
return transaction.add(this.initialize({ stakePubkey, authorized, lockup }))
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Generate a Transaction that creates a new Stake account
|
|
500
|
+
*/
|
|
501
|
+
static createAccount(params: CreateStakeAccountParams): Transaction {
|
|
502
|
+
const transaction = new Transaction()
|
|
503
|
+
transaction.add(
|
|
504
|
+
SystemProgram.createAccount({
|
|
505
|
+
fromPubkey: params.fromPubkey,
|
|
506
|
+
newAccountPubkey: params.stakePubkey,
|
|
507
|
+
lamports: params.lamports,
|
|
508
|
+
space: this.space,
|
|
509
|
+
programId: this.programId,
|
|
510
|
+
})
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
const { stakePubkey, authorized, lockup } = params
|
|
514
|
+
return transaction.add(this.initialize({ stakePubkey, authorized, lockup }))
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Generate a Transaction that delegates Stake tokens to a validator
|
|
519
|
+
* Vote PublicKey. This transaction can also be used to redelegate Stake
|
|
520
|
+
* to a new validator Vote PublicKey.
|
|
521
|
+
*/
|
|
522
|
+
static delegate(params: DelegateStakeParams): Transaction {
|
|
523
|
+
const { stakePubkey, authorizedPubkey, votePubkey } = params
|
|
524
|
+
|
|
525
|
+
const type = STAKE_INSTRUCTION_LAYOUTS.Delegate
|
|
526
|
+
const data = encodeData(type)
|
|
527
|
+
|
|
528
|
+
return new Transaction().add({
|
|
529
|
+
keys: [
|
|
530
|
+
{ pubkey: stakePubkey, isSigner: false, isWritable: true },
|
|
531
|
+
{ pubkey: votePubkey, isSigner: false, isWritable: false },
|
|
532
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
533
|
+
{
|
|
534
|
+
pubkey: SYSVAR_STAKE_HISTORY_PUBKEY,
|
|
535
|
+
isSigner: false,
|
|
536
|
+
isWritable: false,
|
|
537
|
+
},
|
|
538
|
+
{ pubkey: STAKE_CONFIG_ID, isSigner: false, isWritable: false },
|
|
539
|
+
{ pubkey: authorizedPubkey, isSigner: true, isWritable: false },
|
|
540
|
+
],
|
|
541
|
+
programId: this.programId,
|
|
542
|
+
data,
|
|
543
|
+
})
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Generate a Transaction that authorizes a new PublicKey as Staker
|
|
548
|
+
* or Withdrawer on the Stake account.
|
|
549
|
+
*/
|
|
550
|
+
static authorize(params: AuthorizeStakeParams): Transaction {
|
|
551
|
+
const { stakePubkey, authorizedPubkey, newAuthorizedPubkey, stakeAuthorizationType } = params
|
|
552
|
+
|
|
553
|
+
const type = STAKE_INSTRUCTION_LAYOUTS.Authorize
|
|
554
|
+
const data = encodeData(type, {
|
|
555
|
+
newAuthorized: newAuthorizedPubkey.toBuffer(),
|
|
556
|
+
stakeAuthorizationType: stakeAuthorizationType.index,
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
return new Transaction().add({
|
|
560
|
+
keys: [
|
|
561
|
+
{ pubkey: stakePubkey, isSigner: false, isWritable: true },
|
|
562
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: true },
|
|
563
|
+
{ pubkey: authorizedPubkey, isSigner: true, isWritable: false },
|
|
564
|
+
],
|
|
565
|
+
programId: this.programId,
|
|
566
|
+
data,
|
|
567
|
+
})
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Generate a Transaction that authorizes a new PublicKey as Staker
|
|
572
|
+
* or Withdrawer on the Stake account.
|
|
573
|
+
*/
|
|
574
|
+
static authorizeWithSeed(params: AuthorizeWithSeedStakeParams): Transaction {
|
|
575
|
+
const {
|
|
576
|
+
stakePubkey,
|
|
577
|
+
authorityBase,
|
|
578
|
+
authoritySeed,
|
|
579
|
+
authorityOwner,
|
|
580
|
+
newAuthorizedPubkey,
|
|
581
|
+
stakeAuthorizationType,
|
|
582
|
+
} = params
|
|
583
|
+
|
|
584
|
+
const type = STAKE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed
|
|
585
|
+
const data = encodeData(type, {
|
|
586
|
+
newAuthorized: newAuthorizedPubkey.toBuffer(),
|
|
587
|
+
stakeAuthorizationType: stakeAuthorizationType.index,
|
|
588
|
+
authoritySeed: authoritySeed,
|
|
589
|
+
authorityOwner: authorityOwner.toBuffer(),
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
return new Transaction().add({
|
|
593
|
+
keys: [
|
|
594
|
+
{ pubkey: stakePubkey, isSigner: false, isWritable: true },
|
|
595
|
+
{ pubkey: authorityBase, isSigner: true, isWritable: false },
|
|
596
|
+
],
|
|
597
|
+
programId: this.programId,
|
|
598
|
+
data,
|
|
599
|
+
})
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Generate a Transaction that splits Stake tokens into another stake account
|
|
604
|
+
*/
|
|
605
|
+
static split(params: SplitStakeParams): Transaction {
|
|
606
|
+
const { stakePubkey, authorizedPubkey, splitStakePubkey, lamports } = params
|
|
607
|
+
|
|
608
|
+
const transaction = new Transaction()
|
|
609
|
+
transaction.add(
|
|
610
|
+
SystemProgram.createAccount({
|
|
611
|
+
fromPubkey: authorizedPubkey,
|
|
612
|
+
newAccountPubkey: splitStakePubkey,
|
|
613
|
+
lamports: 0,
|
|
614
|
+
space: this.space,
|
|
615
|
+
programId: this.programId,
|
|
616
|
+
})
|
|
617
|
+
)
|
|
618
|
+
const type = STAKE_INSTRUCTION_LAYOUTS.Split
|
|
619
|
+
const data = encodeData(type, { lamports })
|
|
620
|
+
|
|
621
|
+
return transaction.add({
|
|
622
|
+
keys: [
|
|
623
|
+
{ pubkey: stakePubkey, isSigner: false, isWritable: true },
|
|
624
|
+
{ pubkey: splitStakePubkey, isSigner: false, isWritable: true },
|
|
625
|
+
{ pubkey: authorizedPubkey, isSigner: true, isWritable: false },
|
|
626
|
+
],
|
|
627
|
+
programId: this.programId,
|
|
628
|
+
data,
|
|
629
|
+
})
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Generate a Transaction that withdraws deactivated Stake tokens.
|
|
634
|
+
*/
|
|
635
|
+
static withdraw(params: WithdrawStakeParams): Transaction {
|
|
636
|
+
const { stakePubkey, authorizedPubkey, toPubkey, lamports } = params
|
|
637
|
+
const type = STAKE_INSTRUCTION_LAYOUTS.Withdraw
|
|
638
|
+
const data = encodeData(type, { lamports })
|
|
639
|
+
|
|
640
|
+
return new Transaction().add({
|
|
641
|
+
keys: [
|
|
642
|
+
{ pubkey: stakePubkey, isSigner: false, isWritable: true },
|
|
643
|
+
{ pubkey: toPubkey, isSigner: false, isWritable: true },
|
|
644
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
645
|
+
{
|
|
646
|
+
pubkey: SYSVAR_STAKE_HISTORY_PUBKEY,
|
|
647
|
+
isSigner: false,
|
|
648
|
+
isWritable: false,
|
|
649
|
+
},
|
|
650
|
+
{ pubkey: authorizedPubkey, isSigner: true, isWritable: false },
|
|
651
|
+
],
|
|
652
|
+
programId: this.programId,
|
|
653
|
+
data,
|
|
654
|
+
})
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Generate a Transaction that deactivates Stake tokens.
|
|
659
|
+
*/
|
|
660
|
+
static deactivate(params: DeactivateStakeParams): Transaction {
|
|
661
|
+
const { stakePubkey, authorizedPubkey } = params
|
|
662
|
+
const type = STAKE_INSTRUCTION_LAYOUTS.Deactivate
|
|
663
|
+
const data = encodeData(type)
|
|
664
|
+
|
|
665
|
+
return new Transaction().add({
|
|
666
|
+
keys: [
|
|
667
|
+
{ pubkey: stakePubkey, isSigner: false, isWritable: true },
|
|
668
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
669
|
+
{ pubkey: authorizedPubkey, isSigner: true, isWritable: false },
|
|
670
|
+
],
|
|
671
|
+
programId: this.programId,
|
|
672
|
+
data,
|
|
673
|
+
})
|
|
674
|
+
}
|
|
675
|
+
}
|
|
@@ -316,7 +316,9 @@ export class Transaction {
|
|
|
316
316
|
})
|
|
317
317
|
|
|
318
318
|
if (numRequiredSignatures !== this.signatures.length) {
|
|
319
|
-
throw new Error(
|
|
319
|
+
throw new Error(
|
|
320
|
+
`missing signer(s) - required signatures: ${numRequiredSignatures}, got ${this.signatures.length}`
|
|
321
|
+
)
|
|
320
322
|
}
|
|
321
323
|
|
|
322
324
|
const accountKeys = signedKeys.concat(unsignedKeys)
|