@ignitionfi/spl-stake-pool 1.1.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.
- package/LICENSE +176 -0
- package/dist/codecs.d.ts +15 -0
- package/dist/constants.d.ts +12 -0
- package/dist/index.browser.cjs.js +2569 -0
- package/dist/index.browser.cjs.js.map +1 -0
- package/dist/index.browser.esm.js +2523 -0
- package/dist/index.browser.esm.js.map +1 -0
- package/dist/index.cjs.js +2569 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +195 -0
- package/dist/index.esm.js +2523 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.iife.js +23754 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.iife.min.js +19 -0
- package/dist/index.iife.min.js.map +1 -0
- package/dist/instructions.d.ts +329 -0
- package/dist/layouts.d.ts +318 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/instruction.d.ts +21 -0
- package/dist/utils/math.d.ts +3 -0
- package/dist/utils/program-address.d.ts +26 -0
- package/dist/utils/stake.d.ts +29 -0
- package/package.json +90 -0
- package/src/codecs.ts +159 -0
- package/src/constants.ts +29 -0
- package/src/index.ts +1523 -0
- package/src/instructions.ts +1293 -0
- package/src/layouts.ts +248 -0
- package/src/types/buffer-layout.d.ts +29 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/instruction.ts +46 -0
- package/src/utils/math.ts +29 -0
- package/src/utils/program-address.ts +103 -0
- package/src/utils/stake.ts +230 -0
|
@@ -0,0 +1,2523 @@
|
|
|
1
|
+
import { TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, createAssociatedTokenAccountIdempotentInstruction, NATIVE_MINT, getAccount, createApproveInstruction } from '@solana/spl-token';
|
|
2
|
+
import { PublicKey, LAMPORTS_PER_SOL, StakeProgram, Keypair, SystemProgram, SYSVAR_RENT_PUBKEY, SYSVAR_CLOCK_PUBKEY, SYSVAR_STAKE_HISTORY_PUBKEY, STAKE_CONFIG_ID, TransactionInstruction, StakeAuthorizationLayout } from '@solana/web3.js';
|
|
3
|
+
import BN from 'bn.js';
|
|
4
|
+
import { Buffer as Buffer$1 } from 'node:buffer';
|
|
5
|
+
import * as BufferLayout from '@solana/buffer-layout';
|
|
6
|
+
import { blob, u32, struct, seq, offset, Layout, u8 } from 'buffer-layout';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A `StructFailure` represents a single specific failure in validation.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* `StructError` objects are thrown (or returned) when validation fails.
|
|
13
|
+
*
|
|
14
|
+
* Validation logic is design to exit early for maximum performance. The error
|
|
15
|
+
* represents the first error encountered during validation. For more detail,
|
|
16
|
+
* the `error.failures` property is a generator function that can be run to
|
|
17
|
+
* continue validation and receive all the failures in the data.
|
|
18
|
+
*/
|
|
19
|
+
class StructError extends TypeError {
|
|
20
|
+
constructor(failure, failures) {
|
|
21
|
+
let cached;
|
|
22
|
+
const { message, explanation, ...rest } = failure;
|
|
23
|
+
const { path } = failure;
|
|
24
|
+
const msg = path.length === 0 ? message : `At path: ${path.join('.')} -- ${message}`;
|
|
25
|
+
super(explanation ?? msg);
|
|
26
|
+
if (explanation != null)
|
|
27
|
+
this.cause = msg;
|
|
28
|
+
Object.assign(this, rest);
|
|
29
|
+
this.name = this.constructor.name;
|
|
30
|
+
this.failures = () => {
|
|
31
|
+
return (cached ?? (cached = [failure, ...failures()]));
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if a value is an iterator.
|
|
38
|
+
*/
|
|
39
|
+
function isIterable(x) {
|
|
40
|
+
return isObject(x) && typeof x[Symbol.iterator] === 'function';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if a value is a plain object.
|
|
44
|
+
*/
|
|
45
|
+
function isObject(x) {
|
|
46
|
+
return typeof x === 'object' && x != null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if a value is a non-array object.
|
|
50
|
+
*/
|
|
51
|
+
function isNonArrayObject(x) {
|
|
52
|
+
return isObject(x) && !Array.isArray(x);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Return a value as a printable string.
|
|
56
|
+
*/
|
|
57
|
+
function print(value) {
|
|
58
|
+
if (typeof value === 'symbol') {
|
|
59
|
+
return value.toString();
|
|
60
|
+
}
|
|
61
|
+
return typeof value === 'string' ? JSON.stringify(value) : `${value}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Shifts (removes and returns) the first value from the `input` iterator.
|
|
65
|
+
* Like `Array.prototype.shift()` but for an `Iterator`.
|
|
66
|
+
*/
|
|
67
|
+
function shiftIterator(input) {
|
|
68
|
+
const { done, value } = input.next();
|
|
69
|
+
return done ? undefined : value;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Convert a single validation result to a failure.
|
|
73
|
+
*/
|
|
74
|
+
function toFailure(result, context, struct, value) {
|
|
75
|
+
if (result === true) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
else if (result === false) {
|
|
79
|
+
result = {};
|
|
80
|
+
}
|
|
81
|
+
else if (typeof result === 'string') {
|
|
82
|
+
result = { message: result };
|
|
83
|
+
}
|
|
84
|
+
const { path, branch } = context;
|
|
85
|
+
const { type } = struct;
|
|
86
|
+
const { refinement, message = `Expected a value of type \`${type}\`${refinement ? ` with refinement \`${refinement}\`` : ''}, but received: \`${print(value)}\``, } = result;
|
|
87
|
+
return {
|
|
88
|
+
value,
|
|
89
|
+
type,
|
|
90
|
+
refinement,
|
|
91
|
+
key: path[path.length - 1],
|
|
92
|
+
path,
|
|
93
|
+
branch,
|
|
94
|
+
...result,
|
|
95
|
+
message,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Convert a validation result to an iterable of failures.
|
|
100
|
+
*/
|
|
101
|
+
function* toFailures(result, context, struct, value) {
|
|
102
|
+
if (!isIterable(result)) {
|
|
103
|
+
result = [result];
|
|
104
|
+
}
|
|
105
|
+
for (const r of result) {
|
|
106
|
+
const failure = toFailure(r, context, struct, value);
|
|
107
|
+
if (failure) {
|
|
108
|
+
yield failure;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Check a value against a struct, traversing deeply into nested values, and
|
|
114
|
+
* returning an iterator of failures or success.
|
|
115
|
+
*/
|
|
116
|
+
function* run(value, struct, options = {}) {
|
|
117
|
+
const { path = [], branch = [value], coerce = false, mask = false } = options;
|
|
118
|
+
const ctx = { path, branch, mask };
|
|
119
|
+
if (coerce) {
|
|
120
|
+
value = struct.coercer(value, ctx);
|
|
121
|
+
}
|
|
122
|
+
let status = 'valid';
|
|
123
|
+
for (const failure of struct.validator(value, ctx)) {
|
|
124
|
+
failure.explanation = options.message;
|
|
125
|
+
status = 'not_valid';
|
|
126
|
+
yield [failure, undefined];
|
|
127
|
+
}
|
|
128
|
+
for (let [k, v, s] of struct.entries(value, ctx)) {
|
|
129
|
+
const ts = run(v, s, {
|
|
130
|
+
path: k === undefined ? path : [...path, k],
|
|
131
|
+
branch: k === undefined ? branch : [...branch, v],
|
|
132
|
+
coerce,
|
|
133
|
+
mask,
|
|
134
|
+
message: options.message,
|
|
135
|
+
});
|
|
136
|
+
for (const t of ts) {
|
|
137
|
+
if (t[0]) {
|
|
138
|
+
status = t[0].refinement != null ? 'not_refined' : 'not_valid';
|
|
139
|
+
yield [t[0], undefined];
|
|
140
|
+
}
|
|
141
|
+
else if (coerce) {
|
|
142
|
+
v = t[1];
|
|
143
|
+
if (k === undefined) {
|
|
144
|
+
value = v;
|
|
145
|
+
}
|
|
146
|
+
else if (value instanceof Map) {
|
|
147
|
+
value.set(k, v);
|
|
148
|
+
}
|
|
149
|
+
else if (value instanceof Set) {
|
|
150
|
+
value.add(v);
|
|
151
|
+
}
|
|
152
|
+
else if (isObject(value)) {
|
|
153
|
+
if (v !== undefined || k in value)
|
|
154
|
+
value[k] = v;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (status !== 'not_valid') {
|
|
160
|
+
for (const failure of struct.refiner(value, ctx)) {
|
|
161
|
+
failure.explanation = options.message;
|
|
162
|
+
status = 'not_refined';
|
|
163
|
+
yield [failure, undefined];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (status === 'valid') {
|
|
167
|
+
yield [undefined, value];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* `Struct` objects encapsulate the validation logic for a specific type of
|
|
173
|
+
* values. Once constructed, you use the `assert`, `is` or `validate` helpers to
|
|
174
|
+
* validate unknown input data against the struct.
|
|
175
|
+
*/
|
|
176
|
+
class Struct {
|
|
177
|
+
constructor(props) {
|
|
178
|
+
const { type, schema, validator, refiner, coercer = (value) => value, entries = function* () { }, } = props;
|
|
179
|
+
this.type = type;
|
|
180
|
+
this.schema = schema;
|
|
181
|
+
this.entries = entries;
|
|
182
|
+
this.coercer = coercer;
|
|
183
|
+
if (validator) {
|
|
184
|
+
this.validator = (value, context) => {
|
|
185
|
+
const result = validator(value, context);
|
|
186
|
+
return toFailures(result, context, this, value);
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
this.validator = () => [];
|
|
191
|
+
}
|
|
192
|
+
if (refiner) {
|
|
193
|
+
this.refiner = (value, context) => {
|
|
194
|
+
const result = refiner(value, context);
|
|
195
|
+
return toFailures(result, context, this, value);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
this.refiner = () => [];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Assert that a value passes the struct's validation, throwing if it doesn't.
|
|
204
|
+
*/
|
|
205
|
+
assert(value, message) {
|
|
206
|
+
return assert(value, this, message);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Create a value with the struct's coercion logic, then validate it.
|
|
210
|
+
*/
|
|
211
|
+
create(value, message) {
|
|
212
|
+
return create(value, this, message);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Check if a value passes the struct's validation.
|
|
216
|
+
*/
|
|
217
|
+
is(value) {
|
|
218
|
+
return is(value, this);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Mask a value, coercing and validating it, but returning only the subset of
|
|
222
|
+
* properties defined by the struct's schema. Masking applies recursively to
|
|
223
|
+
* props of `object` structs only.
|
|
224
|
+
*/
|
|
225
|
+
mask(value, message) {
|
|
226
|
+
return mask(value, this, message);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Validate a value with the struct's validation logic, returning a tuple
|
|
230
|
+
* representing the result.
|
|
231
|
+
*
|
|
232
|
+
* You may optionally pass `true` for the `coerce` argument to coerce
|
|
233
|
+
* the value before attempting to validate it. If you do, the result will
|
|
234
|
+
* contain the coerced result when successful. Also, `mask` will turn on
|
|
235
|
+
* masking of the unknown `object` props recursively if passed.
|
|
236
|
+
*/
|
|
237
|
+
validate(value, options = {}) {
|
|
238
|
+
return validate(value, this, options);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Assert that a value passes a struct, throwing if it doesn't.
|
|
243
|
+
*/
|
|
244
|
+
function assert(value, struct, message) {
|
|
245
|
+
const result = validate(value, struct, { message });
|
|
246
|
+
if (result[0]) {
|
|
247
|
+
throw result[0];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Create a value with the coercion logic of struct and validate it.
|
|
252
|
+
*/
|
|
253
|
+
function create(value, struct, message) {
|
|
254
|
+
const result = validate(value, struct, { coerce: true, message });
|
|
255
|
+
if (result[0]) {
|
|
256
|
+
throw result[0];
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
return result[1];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Mask a value, returning only the subset of properties defined by a struct.
|
|
264
|
+
*/
|
|
265
|
+
function mask(value, struct, message) {
|
|
266
|
+
const result = validate(value, struct, { coerce: true, mask: true, message });
|
|
267
|
+
if (result[0]) {
|
|
268
|
+
throw result[0];
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
return result[1];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Check if a value passes a struct.
|
|
276
|
+
*/
|
|
277
|
+
function is(value, struct) {
|
|
278
|
+
const result = validate(value, struct);
|
|
279
|
+
return !result[0];
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Validate a value against a struct, returning an error if invalid, or the
|
|
283
|
+
* value (with potential coercion) if valid.
|
|
284
|
+
*/
|
|
285
|
+
function validate(value, struct, options = {}) {
|
|
286
|
+
const tuples = run(value, struct, options);
|
|
287
|
+
const tuple = shiftIterator(tuples);
|
|
288
|
+
if (tuple[0]) {
|
|
289
|
+
const error = new StructError(tuple[0], function* () {
|
|
290
|
+
for (const t of tuples) {
|
|
291
|
+
if (t[0]) {
|
|
292
|
+
yield t[0];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
return [error, undefined];
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const v = tuple[1];
|
|
300
|
+
return [undefined, v];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Define a new struct type with a custom validation function.
|
|
305
|
+
*/
|
|
306
|
+
function define(name, validator) {
|
|
307
|
+
return new Struct({ type: name, schema: null, validator });
|
|
308
|
+
}
|
|
309
|
+
function enums(values) {
|
|
310
|
+
const schema = {};
|
|
311
|
+
const description = values.map((v) => print(v)).join();
|
|
312
|
+
for (const key of values) {
|
|
313
|
+
schema[key] = key;
|
|
314
|
+
}
|
|
315
|
+
return new Struct({
|
|
316
|
+
type: 'enums',
|
|
317
|
+
schema,
|
|
318
|
+
validator(value) {
|
|
319
|
+
return (values.includes(value) ||
|
|
320
|
+
`Expected one of \`${description}\`, but received: ${print(value)}`);
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Ensure that a value is an instance of a specific class.
|
|
326
|
+
*/
|
|
327
|
+
function instance(Class) {
|
|
328
|
+
return define('instance', (value) => {
|
|
329
|
+
return (value instanceof Class ||
|
|
330
|
+
`Expected a \`${Class.name}\` instance, but received: ${print(value)}`);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Augment an existing struct to allow `null` values.
|
|
335
|
+
*/
|
|
336
|
+
function nullable(struct) {
|
|
337
|
+
return new Struct({
|
|
338
|
+
...struct,
|
|
339
|
+
validator: (value, ctx) => value === null || struct.validator(value, ctx),
|
|
340
|
+
refiner: (value, ctx) => value === null || struct.refiner(value, ctx),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Ensure that a value is a number.
|
|
345
|
+
*/
|
|
346
|
+
function number() {
|
|
347
|
+
return define('number', (value) => {
|
|
348
|
+
return ((typeof value === 'number' && !isNaN(value)) ||
|
|
349
|
+
`Expected a number, but received: ${print(value)}`);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Augment a struct to allow `undefined` values.
|
|
354
|
+
*/
|
|
355
|
+
function optional(struct) {
|
|
356
|
+
return new Struct({
|
|
357
|
+
...struct,
|
|
358
|
+
validator: (value, ctx) => value === undefined || struct.validator(value, ctx),
|
|
359
|
+
refiner: (value, ctx) => value === undefined || struct.refiner(value, ctx),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Ensure that a value is a string.
|
|
364
|
+
*/
|
|
365
|
+
function string() {
|
|
366
|
+
return define('string', (value) => {
|
|
367
|
+
return (typeof value === 'string' ||
|
|
368
|
+
`Expected a string, but received: ${print(value)}`);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Ensure that a value has a set of known properties of specific types.
|
|
373
|
+
*
|
|
374
|
+
* Note: Unrecognized properties are allowed and untouched. This is similar to
|
|
375
|
+
* how TypeScript's structural typing works.
|
|
376
|
+
*/
|
|
377
|
+
function type(schema) {
|
|
378
|
+
const keys = Object.keys(schema);
|
|
379
|
+
return new Struct({
|
|
380
|
+
type: 'type',
|
|
381
|
+
schema,
|
|
382
|
+
*entries(value) {
|
|
383
|
+
if (isObject(value)) {
|
|
384
|
+
for (const k of keys) {
|
|
385
|
+
yield [k, value[k], schema[k]];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
validator(value) {
|
|
390
|
+
return (isNonArrayObject(value) ||
|
|
391
|
+
`Expected an object, but received: ${print(value)}`);
|
|
392
|
+
},
|
|
393
|
+
coercer(value) {
|
|
394
|
+
return isNonArrayObject(value) ? { ...value } : value;
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Augment a `Struct` to add an additional coercion step to its input.
|
|
401
|
+
*
|
|
402
|
+
* This allows you to transform input data before validating it, to increase the
|
|
403
|
+
* likelihood that it passes validation—for example for default values, parsing
|
|
404
|
+
* different formats, etc.
|
|
405
|
+
*
|
|
406
|
+
* Note: You must use `create(value, Struct)` on the value to have the coercion
|
|
407
|
+
* take effect! Using simply `assert()` or `is()` will not use coercion.
|
|
408
|
+
*/
|
|
409
|
+
function coerce(struct, condition, coercer) {
|
|
410
|
+
return new Struct({
|
|
411
|
+
...struct,
|
|
412
|
+
coercer: (value, ctx) => {
|
|
413
|
+
return is(value, condition)
|
|
414
|
+
? struct.coercer(coercer(value, ctx), ctx)
|
|
415
|
+
: struct.coercer(value, ctx);
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Public key that identifies the metadata program.
|
|
421
|
+
const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
|
|
422
|
+
const METADATA_MAX_NAME_LENGTH = 32;
|
|
423
|
+
const METADATA_MAX_SYMBOL_LENGTH = 10;
|
|
424
|
+
const METADATA_MAX_URI_LENGTH = 200;
|
|
425
|
+
// Public key that identifies the SPL Stake Pool program.
|
|
426
|
+
const STAKE_POOL_PROGRAM_ID = new PublicKey('SP1s4uFeTAX9jsXXmwyDs1gxYYf7cdDZ8qHUHVxE1yr');
|
|
427
|
+
// Public key that identifies the SPL Stake Pool program deployed to devnet.
|
|
428
|
+
const DEVNET_STAKE_POOL_PROGRAM_ID = new PublicKey('DPoo15wWDqpPJJtS2MUZ49aRxqz5ZaaJCJP4z8bLuib');
|
|
429
|
+
// Maximum number of validators to update during UpdateValidatorListBalance.
|
|
430
|
+
const MAX_VALIDATORS_TO_UPDATE = 4;
|
|
431
|
+
// Seed for ephemeral stake account
|
|
432
|
+
const EPHEMERAL_STAKE_SEED_PREFIX = Buffer$1.from('ephemeral');
|
|
433
|
+
// Seed used to derive transient stake accounts.
|
|
434
|
+
const TRANSIENT_STAKE_SEED_PREFIX = Buffer$1.from('transient');
|
|
435
|
+
// Minimum amount of staked SOL required in a validator stake account to allow
|
|
436
|
+
// for merges without a mismatch on credits observed
|
|
437
|
+
const MINIMUM_ACTIVE_STAKE = LAMPORTS_PER_SOL;
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Populate a buffer of instruction data using an InstructionType
|
|
441
|
+
* @internal
|
|
442
|
+
*/
|
|
443
|
+
function encodeData(type, fields) {
|
|
444
|
+
const allocLength = type.layout.span;
|
|
445
|
+
const data = Buffer$1.alloc(allocLength);
|
|
446
|
+
const layoutFields = Object.assign({ instruction: type.index }, fields);
|
|
447
|
+
type.layout.encode(layoutFields, data);
|
|
448
|
+
return data;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Decode instruction data buffer using an InstructionType
|
|
452
|
+
* @internal
|
|
453
|
+
*/
|
|
454
|
+
function decodeData(type, buffer) {
|
|
455
|
+
let data;
|
|
456
|
+
try {
|
|
457
|
+
data = type.layout.decode(buffer);
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
throw new Error(`invalid instruction; ${err}`);
|
|
461
|
+
}
|
|
462
|
+
if (data.instruction !== type.index) {
|
|
463
|
+
throw new Error(`invalid instruction; instruction index mismatch ${data.instruction} != ${type.index}`);
|
|
464
|
+
}
|
|
465
|
+
return data;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function solToLamports(amount) {
|
|
469
|
+
if (isNaN(amount)) {
|
|
470
|
+
return Number(0);
|
|
471
|
+
}
|
|
472
|
+
return Number(amount * LAMPORTS_PER_SOL);
|
|
473
|
+
}
|
|
474
|
+
function lamportsToSol(lamports) {
|
|
475
|
+
if (typeof lamports === 'number') {
|
|
476
|
+
return Math.abs(lamports) / LAMPORTS_PER_SOL;
|
|
477
|
+
}
|
|
478
|
+
if (typeof lamports === 'bigint') {
|
|
479
|
+
return Math.abs(Number(lamports)) / LAMPORTS_PER_SOL;
|
|
480
|
+
}
|
|
481
|
+
let signMultiplier = 1;
|
|
482
|
+
if (lamports.isNeg()) {
|
|
483
|
+
signMultiplier = -1;
|
|
484
|
+
}
|
|
485
|
+
const absLamports = lamports.abs();
|
|
486
|
+
const lamportsString = absLamports.toString(10).padStart(10, '0');
|
|
487
|
+
const splitIndex = lamportsString.length - 9;
|
|
488
|
+
const solString = `${lamportsString.slice(0, splitIndex)}.${lamportsString.slice(splitIndex)}`;
|
|
489
|
+
return signMultiplier * Number.parseFloat(solString);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Generates the wSOL transient program address for the stake pool
|
|
494
|
+
*/
|
|
495
|
+
function findWsolTransientProgramAddress(programId, userPubkey) {
|
|
496
|
+
const [publicKey] = PublicKey.findProgramAddressSync([Buffer$1.from('transient_wsol'), userPubkey.toBuffer()], programId);
|
|
497
|
+
return publicKey;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Generates the withdraw authority program address for the stake pool
|
|
501
|
+
*/
|
|
502
|
+
async function findWithdrawAuthorityProgramAddress(programId, stakePoolAddress) {
|
|
503
|
+
const [publicKey] = PublicKey.findProgramAddressSync([stakePoolAddress.toBuffer(), Buffer$1.from('withdraw')], programId);
|
|
504
|
+
return publicKey;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Generates the stake program address for a validator's vote account
|
|
508
|
+
*/
|
|
509
|
+
async function findStakeProgramAddress(programId, voteAccountAddress, stakePoolAddress, seed) {
|
|
510
|
+
const [publicKey] = PublicKey.findProgramAddressSync([
|
|
511
|
+
voteAccountAddress.toBuffer(),
|
|
512
|
+
stakePoolAddress.toBuffer(),
|
|
513
|
+
seed ? new BN(seed).toArrayLike(Buffer$1, 'le', 4) : Buffer$1.alloc(0),
|
|
514
|
+
], programId);
|
|
515
|
+
return publicKey;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Generates the stake program address for a validator's vote account
|
|
519
|
+
*/
|
|
520
|
+
async function findTransientStakeProgramAddress(programId, voteAccountAddress, stakePoolAddress, seed) {
|
|
521
|
+
const [publicKey] = PublicKey.findProgramAddressSync([
|
|
522
|
+
TRANSIENT_STAKE_SEED_PREFIX,
|
|
523
|
+
voteAccountAddress.toBuffer(),
|
|
524
|
+
stakePoolAddress.toBuffer(),
|
|
525
|
+
seed.toArrayLike(Buffer$1, 'le', 8),
|
|
526
|
+
], programId);
|
|
527
|
+
return publicKey;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Generates the ephemeral program address for stake pool redelegation
|
|
531
|
+
*/
|
|
532
|
+
async function findEphemeralStakeProgramAddress(programId, stakePoolAddress, seed) {
|
|
533
|
+
const [publicKey] = PublicKey.findProgramAddressSync([EPHEMERAL_STAKE_SEED_PREFIX, stakePoolAddress.toBuffer(), seed.toArrayLike(Buffer$1, 'le', 8)], programId);
|
|
534
|
+
return publicKey;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Generates the metadata program address for the stake pool
|
|
538
|
+
*/
|
|
539
|
+
function findMetadataAddress(stakePoolMintAddress) {
|
|
540
|
+
const [publicKey] = PublicKey.findProgramAddressSync([Buffer$1.from('metadata'), METADATA_PROGRAM_ID.toBuffer(), stakePoolMintAddress.toBuffer()], METADATA_PROGRAM_ID);
|
|
541
|
+
return publicKey;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
class BNLayout extends Layout {
|
|
545
|
+
constructor(span, signed, property) {
|
|
546
|
+
super(span, property);
|
|
547
|
+
this.blob = blob(span);
|
|
548
|
+
this.signed = signed;
|
|
549
|
+
}
|
|
550
|
+
decode(b, offset = 0) {
|
|
551
|
+
const num = new BN(this.blob.decode(b, offset), 10, 'le');
|
|
552
|
+
if (this.signed) {
|
|
553
|
+
return num.fromTwos(this.span * 8).clone();
|
|
554
|
+
}
|
|
555
|
+
return num;
|
|
556
|
+
}
|
|
557
|
+
encode(src, b, offset = 0) {
|
|
558
|
+
if (this.signed) {
|
|
559
|
+
src = src.toTwos(this.span * 8);
|
|
560
|
+
}
|
|
561
|
+
return this.blob.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function u64(property) {
|
|
565
|
+
return new BNLayout(8, false, property);
|
|
566
|
+
}
|
|
567
|
+
class WrappedLayout extends Layout {
|
|
568
|
+
constructor(layout, decoder, encoder, property) {
|
|
569
|
+
super(layout.span, property);
|
|
570
|
+
this.layout = layout;
|
|
571
|
+
this.decoder = decoder;
|
|
572
|
+
this.encoder = encoder;
|
|
573
|
+
}
|
|
574
|
+
decode(b, offset) {
|
|
575
|
+
return this.decoder(this.layout.decode(b, offset));
|
|
576
|
+
}
|
|
577
|
+
encode(src, b, offset) {
|
|
578
|
+
return this.layout.encode(this.encoder(src), b, offset);
|
|
579
|
+
}
|
|
580
|
+
getSpan(b, offset) {
|
|
581
|
+
return this.layout.getSpan(b, offset);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function publicKey(property) {
|
|
585
|
+
return new WrappedLayout(blob(32), (b) => new PublicKey(b), (key) => key.toBuffer(), property);
|
|
586
|
+
}
|
|
587
|
+
class OptionLayout extends Layout {
|
|
588
|
+
constructor(layout, property) {
|
|
589
|
+
super(-1, property);
|
|
590
|
+
this.layout = layout;
|
|
591
|
+
this.discriminator = u8();
|
|
592
|
+
}
|
|
593
|
+
encode(src, b, offset = 0) {
|
|
594
|
+
if (src === null || src === undefined) {
|
|
595
|
+
return this.discriminator.encode(0, b, offset);
|
|
596
|
+
}
|
|
597
|
+
this.discriminator.encode(1, b, offset);
|
|
598
|
+
return this.layout.encode(src, b, offset + 1) + 1;
|
|
599
|
+
}
|
|
600
|
+
decode(b, offset = 0) {
|
|
601
|
+
const discriminator = this.discriminator.decode(b, offset);
|
|
602
|
+
if (discriminator === 0) {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
else if (discriminator === 1) {
|
|
606
|
+
return this.layout.decode(b, offset + 1);
|
|
607
|
+
}
|
|
608
|
+
throw new Error(`Invalid option ${this.property}`);
|
|
609
|
+
}
|
|
610
|
+
getSpan(b, offset = 0) {
|
|
611
|
+
const discriminator = this.discriminator.decode(b, offset);
|
|
612
|
+
if (discriminator === 0) {
|
|
613
|
+
return 1;
|
|
614
|
+
}
|
|
615
|
+
else if (discriminator === 1) {
|
|
616
|
+
return this.layout.getSpan(b, offset + 1) + 1;
|
|
617
|
+
}
|
|
618
|
+
throw new Error(`Invalid option ${this.property}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function option(layout, property) {
|
|
622
|
+
return new OptionLayout(layout, property);
|
|
623
|
+
}
|
|
624
|
+
function vec(elementLayout, property) {
|
|
625
|
+
const length = u32('length');
|
|
626
|
+
const layout = struct([
|
|
627
|
+
length,
|
|
628
|
+
seq(elementLayout, offset(length, -length.span), 'values'),
|
|
629
|
+
]);
|
|
630
|
+
return new WrappedLayout(layout, ({ values }) => values, values => ({ values }), property);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const feeFields = [u64('denominator'), u64('numerator')];
|
|
634
|
+
var AccountType;
|
|
635
|
+
(function (AccountType) {
|
|
636
|
+
AccountType[AccountType["Uninitialized"] = 0] = "Uninitialized";
|
|
637
|
+
AccountType[AccountType["StakePool"] = 1] = "StakePool";
|
|
638
|
+
AccountType[AccountType["ValidatorList"] = 2] = "ValidatorList";
|
|
639
|
+
})(AccountType || (AccountType = {}));
|
|
640
|
+
const BigNumFromString = coerce(instance(BN), string(), (value) => {
|
|
641
|
+
if (typeof value === 'string') {
|
|
642
|
+
return new BN(value, 10);
|
|
643
|
+
}
|
|
644
|
+
throw new Error('invalid big num');
|
|
645
|
+
});
|
|
646
|
+
const PublicKeyFromString = coerce(instance(PublicKey), string(), value => new PublicKey(value));
|
|
647
|
+
class FutureEpochLayout extends Layout {
|
|
648
|
+
constructor(layout, property) {
|
|
649
|
+
super(-1, property);
|
|
650
|
+
this.layout = layout;
|
|
651
|
+
this.discriminator = u8();
|
|
652
|
+
}
|
|
653
|
+
encode(src, b, offset = 0) {
|
|
654
|
+
if (src === null || src === undefined) {
|
|
655
|
+
return this.discriminator.encode(0, b, offset);
|
|
656
|
+
}
|
|
657
|
+
// This isn't right, but we don't typically encode outside of tests
|
|
658
|
+
this.discriminator.encode(2, b, offset);
|
|
659
|
+
return this.layout.encode(src, b, offset + 1) + 1;
|
|
660
|
+
}
|
|
661
|
+
decode(b, offset = 0) {
|
|
662
|
+
const discriminator = this.discriminator.decode(b, offset);
|
|
663
|
+
if (discriminator === 0) {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
else if (discriminator === 1 || discriminator === 2) {
|
|
667
|
+
return this.layout.decode(b, offset + 1);
|
|
668
|
+
}
|
|
669
|
+
throw new Error(`Invalid future epoch ${this.property}`);
|
|
670
|
+
}
|
|
671
|
+
getSpan(b, offset = 0) {
|
|
672
|
+
const discriminator = this.discriminator.decode(b, offset);
|
|
673
|
+
if (discriminator === 0) {
|
|
674
|
+
return 1;
|
|
675
|
+
}
|
|
676
|
+
else if (discriminator === 1 || discriminator === 2) {
|
|
677
|
+
return this.layout.getSpan(b, offset + 1) + 1;
|
|
678
|
+
}
|
|
679
|
+
throw new Error(`Invalid future epoch ${this.property}`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function futureEpoch(layout, property) {
|
|
683
|
+
return new FutureEpochLayout(layout, property);
|
|
684
|
+
}
|
|
685
|
+
const StakeAccountType = enums(['uninitialized', 'initialized', 'delegated', 'rewardsPool']);
|
|
686
|
+
const StakeMeta = type({
|
|
687
|
+
rentExemptReserve: BigNumFromString,
|
|
688
|
+
authorized: type({
|
|
689
|
+
staker: PublicKeyFromString,
|
|
690
|
+
withdrawer: PublicKeyFromString,
|
|
691
|
+
}),
|
|
692
|
+
lockup: type({
|
|
693
|
+
unixTimestamp: number(),
|
|
694
|
+
epoch: number(),
|
|
695
|
+
custodian: PublicKeyFromString,
|
|
696
|
+
}),
|
|
697
|
+
});
|
|
698
|
+
const StakeAccountInfo = type({
|
|
699
|
+
meta: StakeMeta,
|
|
700
|
+
stake: nullable(type({
|
|
701
|
+
delegation: type({
|
|
702
|
+
voter: PublicKeyFromString,
|
|
703
|
+
stake: BigNumFromString,
|
|
704
|
+
activationEpoch: BigNumFromString,
|
|
705
|
+
deactivationEpoch: BigNumFromString,
|
|
706
|
+
warmupCooldownRate: number(),
|
|
707
|
+
}),
|
|
708
|
+
creditsObserved: number(),
|
|
709
|
+
})),
|
|
710
|
+
});
|
|
711
|
+
const StakeAccount = type({
|
|
712
|
+
type: StakeAccountType,
|
|
713
|
+
info: optional(StakeAccountInfo),
|
|
714
|
+
});
|
|
715
|
+
const StakePoolLayout = struct([
|
|
716
|
+
u8('accountType'),
|
|
717
|
+
publicKey('manager'),
|
|
718
|
+
publicKey('staker'),
|
|
719
|
+
publicKey('stakeDepositAuthority'),
|
|
720
|
+
u8('stakeWithdrawBumpSeed'),
|
|
721
|
+
publicKey('validatorList'),
|
|
722
|
+
publicKey('reserveStake'),
|
|
723
|
+
publicKey('poolMint'),
|
|
724
|
+
publicKey('managerFeeAccount'),
|
|
725
|
+
publicKey('tokenProgramId'),
|
|
726
|
+
u64('totalLamports'),
|
|
727
|
+
u64('poolTokenSupply'),
|
|
728
|
+
u64('lastUpdateEpoch'),
|
|
729
|
+
struct([u64('unixTimestamp'), u64('epoch'), publicKey('custodian')], 'lockup'),
|
|
730
|
+
struct(feeFields, 'epochFee'),
|
|
731
|
+
futureEpoch(struct(feeFields), 'nextEpochFee'),
|
|
732
|
+
option(publicKey(), 'preferredDepositValidatorVoteAddress'),
|
|
733
|
+
option(publicKey(), 'preferredWithdrawValidatorVoteAddress'),
|
|
734
|
+
struct(feeFields, 'stakeDepositFee'),
|
|
735
|
+
struct(feeFields, 'stakeWithdrawalFee'),
|
|
736
|
+
futureEpoch(struct(feeFields), 'nextStakeWithdrawalFee'),
|
|
737
|
+
u8('stakeReferralFee'),
|
|
738
|
+
option(publicKey(), 'solDepositAuthority'),
|
|
739
|
+
struct(feeFields, 'solDepositFee'),
|
|
740
|
+
u8('solReferralFee'),
|
|
741
|
+
option(publicKey(), 'solWithdrawAuthority'),
|
|
742
|
+
struct(feeFields, 'solWithdrawalFee'),
|
|
743
|
+
futureEpoch(struct(feeFields), 'nextSolWithdrawalFee'),
|
|
744
|
+
u64('lastEpochPoolTokenSupply'),
|
|
745
|
+
u64('lastEpochTotalLamports'),
|
|
746
|
+
]);
|
|
747
|
+
var ValidatorStakeInfoStatus;
|
|
748
|
+
(function (ValidatorStakeInfoStatus) {
|
|
749
|
+
ValidatorStakeInfoStatus[ValidatorStakeInfoStatus["Active"] = 0] = "Active";
|
|
750
|
+
ValidatorStakeInfoStatus[ValidatorStakeInfoStatus["DeactivatingTransient"] = 1] = "DeactivatingTransient";
|
|
751
|
+
ValidatorStakeInfoStatus[ValidatorStakeInfoStatus["ReadyForRemoval"] = 2] = "ReadyForRemoval";
|
|
752
|
+
})(ValidatorStakeInfoStatus || (ValidatorStakeInfoStatus = {}));
|
|
753
|
+
const ValidatorStakeInfoLayout = struct([
|
|
754
|
+
/// Amount of active stake delegated to this validator
|
|
755
|
+
/// Note that if `last_update_epoch` does not match the current epoch then
|
|
756
|
+
/// this field may not be accurate
|
|
757
|
+
u64('activeStakeLamports'),
|
|
758
|
+
/// Amount of transient stake delegated to this validator
|
|
759
|
+
/// Note that if `last_update_epoch` does not match the current epoch then
|
|
760
|
+
/// this field may not be accurate
|
|
761
|
+
u64('transientStakeLamports'),
|
|
762
|
+
/// Last epoch the active and transient stake lamports fields were updated
|
|
763
|
+
u64('lastUpdateEpoch'),
|
|
764
|
+
/// Start of the validator transient account seed suffixes
|
|
765
|
+
u64('transientSeedSuffixStart'),
|
|
766
|
+
/// End of the validator transient account seed suffixes
|
|
767
|
+
u64('transientSeedSuffixEnd'),
|
|
768
|
+
/// Status of the validator stake account
|
|
769
|
+
u8('status'),
|
|
770
|
+
/// Validator vote account address
|
|
771
|
+
publicKey('voteAccountAddress'),
|
|
772
|
+
]);
|
|
773
|
+
const ValidatorListLayout = struct([
|
|
774
|
+
u8('accountType'),
|
|
775
|
+
u32('maxValidators'),
|
|
776
|
+
vec(ValidatorStakeInfoLayout, 'validators'),
|
|
777
|
+
]);
|
|
778
|
+
|
|
779
|
+
async function getValidatorListAccount(connection, pubkey) {
|
|
780
|
+
const account = await connection.getAccountInfo(pubkey);
|
|
781
|
+
if (!account) {
|
|
782
|
+
throw new Error('Invalid validator list account');
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
pubkey,
|
|
786
|
+
account: {
|
|
787
|
+
data: ValidatorListLayout.decode(account === null || account === void 0 ? void 0 : account.data),
|
|
788
|
+
executable: account.executable,
|
|
789
|
+
lamports: account.lamports,
|
|
790
|
+
owner: account.owner,
|
|
791
|
+
},
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, amount, compareFn, skipFee) {
|
|
795
|
+
var _a, _b;
|
|
796
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
797
|
+
const validatorListAcc = await connection.getAccountInfo(stakePool.validatorList);
|
|
798
|
+
const validatorList = ValidatorListLayout.decode(validatorListAcc === null || validatorListAcc === void 0 ? void 0 : validatorListAcc.data);
|
|
799
|
+
if (!(validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators) || (validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators.length) == 0) {
|
|
800
|
+
throw new Error('No accounts found');
|
|
801
|
+
}
|
|
802
|
+
const minBalanceForRentExemption = await connection.getMinimumBalanceForRentExemption(StakeProgram.space);
|
|
803
|
+
const minBalance = new BN(minBalanceForRentExemption + MINIMUM_ACTIVE_STAKE);
|
|
804
|
+
let accounts = [];
|
|
805
|
+
// Prepare accounts
|
|
806
|
+
for (const validator of validatorList.validators) {
|
|
807
|
+
if (validator.status !== ValidatorStakeInfoStatus.Active) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const stakeAccountAddress = await findStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress);
|
|
811
|
+
if (!validator.activeStakeLamports.isZero()) {
|
|
812
|
+
const isPreferred = (_a = stakePool === null || stakePool === void 0 ? void 0 : stakePool.preferredWithdrawValidatorVoteAddress) === null || _a === void 0 ? void 0 : _a.equals(validator.voteAccountAddress);
|
|
813
|
+
accounts.push({
|
|
814
|
+
type: isPreferred ? 'preferred' : 'active',
|
|
815
|
+
voteAddress: validator.voteAccountAddress,
|
|
816
|
+
stakeAddress: stakeAccountAddress,
|
|
817
|
+
lamports: validator.activeStakeLamports,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
const transientStakeLamports = validator.transientStakeLamports.sub(minBalance);
|
|
821
|
+
if (transientStakeLamports.gt(new BN(0))) {
|
|
822
|
+
const transientStakeAccountAddress = await findTransientStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress, validator.transientSeedSuffixStart);
|
|
823
|
+
accounts.push({
|
|
824
|
+
type: 'transient',
|
|
825
|
+
voteAddress: validator.voteAccountAddress,
|
|
826
|
+
stakeAddress: transientStakeAccountAddress,
|
|
827
|
+
lamports: transientStakeLamports,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
// Sort from highest to lowest balance
|
|
832
|
+
accounts = accounts.sort(compareFn || ((a, b) => b.lamports.sub(a.lamports).toNumber()));
|
|
833
|
+
const reserveStake = await connection.getAccountInfo(stakePool.reserveStake);
|
|
834
|
+
const reserveStakeBalance = new BN(((_b = reserveStake === null || reserveStake === void 0 ? void 0 : reserveStake.lamports) !== null && _b !== void 0 ? _b : 0) - minBalanceForRentExemption);
|
|
835
|
+
if (reserveStakeBalance.gt(new BN(0))) {
|
|
836
|
+
accounts.push({
|
|
837
|
+
type: 'reserve',
|
|
838
|
+
stakeAddress: stakePool.reserveStake,
|
|
839
|
+
lamports: reserveStakeBalance,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
// Prepare the list of accounts to withdraw from
|
|
843
|
+
const withdrawFrom = [];
|
|
844
|
+
let remainingAmount = new BN(amount);
|
|
845
|
+
const fee = stakePool.stakeWithdrawalFee;
|
|
846
|
+
const inverseFee = {
|
|
847
|
+
numerator: fee.denominator.sub(fee.numerator),
|
|
848
|
+
denominator: fee.denominator,
|
|
849
|
+
};
|
|
850
|
+
for (const type of ['preferred', 'active', 'transient', 'reserve']) {
|
|
851
|
+
const filteredAccounts = accounts.filter(a => a.type == type);
|
|
852
|
+
for (const { stakeAddress, voteAddress, lamports } of filteredAccounts) {
|
|
853
|
+
if (lamports.lte(minBalance) && type == 'transient') {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
let availableForWithdrawal = calcPoolTokensForDeposit(stakePool, lamports);
|
|
857
|
+
if (!skipFee && !inverseFee.numerator.isZero()) {
|
|
858
|
+
availableForWithdrawal = availableForWithdrawal
|
|
859
|
+
.mul(inverseFee.denominator)
|
|
860
|
+
.div(inverseFee.numerator);
|
|
861
|
+
}
|
|
862
|
+
const poolAmount = BN.min(availableForWithdrawal, remainingAmount);
|
|
863
|
+
if (poolAmount.lte(new BN(0))) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
// Those accounts will be withdrawn completely with `claim` instruction
|
|
867
|
+
withdrawFrom.push({ stakeAddress, voteAddress, poolAmount });
|
|
868
|
+
remainingAmount = remainingAmount.sub(poolAmount);
|
|
869
|
+
if (remainingAmount.isZero()) {
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (remainingAmount.isZero()) {
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
// Not enough stake to withdraw the specified amount
|
|
878
|
+
if (remainingAmount.gt(new BN(0))) {
|
|
879
|
+
throw new Error(`No stake accounts found in this pool with enough balance to withdraw ${lamportsToSol(amount)} pool tokens.`);
|
|
880
|
+
}
|
|
881
|
+
return withdrawFrom;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Calculate the pool tokens that should be minted for a deposit of `stakeLamports`
|
|
885
|
+
*/
|
|
886
|
+
function calcPoolTokensForDeposit(stakePool, stakeLamports) {
|
|
887
|
+
if (stakePool.poolTokenSupply.isZero() || stakePool.totalLamports.isZero()) {
|
|
888
|
+
return stakeLamports;
|
|
889
|
+
}
|
|
890
|
+
const numerator = stakeLamports.mul(stakePool.poolTokenSupply);
|
|
891
|
+
return numerator.div(stakePool.totalLamports);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Calculate lamports amount on withdrawal
|
|
895
|
+
*/
|
|
896
|
+
function calcLamportsWithdrawAmount(stakePool, poolTokens) {
|
|
897
|
+
const numerator = poolTokens.mul(stakePool.totalLamports);
|
|
898
|
+
const denominator = stakePool.poolTokenSupply;
|
|
899
|
+
if (numerator.lt(denominator)) {
|
|
900
|
+
return new BN(0);
|
|
901
|
+
}
|
|
902
|
+
return numerator.div(denominator);
|
|
903
|
+
}
|
|
904
|
+
function newStakeAccount(feePayer, instructions, lamports) {
|
|
905
|
+
// Account for tokens not specified, creating one
|
|
906
|
+
const stakeReceiverKeypair = Keypair.generate();
|
|
907
|
+
console.log(`Creating account to receive stake ${stakeReceiverKeypair.publicKey}`);
|
|
908
|
+
instructions.push(
|
|
909
|
+
// Creating new account
|
|
910
|
+
SystemProgram.createAccount({
|
|
911
|
+
fromPubkey: feePayer,
|
|
912
|
+
newAccountPubkey: stakeReceiverKeypair.publicKey,
|
|
913
|
+
lamports,
|
|
914
|
+
space: StakeProgram.space,
|
|
915
|
+
programId: StakeProgram.programId,
|
|
916
|
+
}));
|
|
917
|
+
return stakeReceiverKeypair;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function arrayChunk(array, size) {
|
|
921
|
+
const result = [];
|
|
922
|
+
for (let i = 0; i < array.length; i += size) {
|
|
923
|
+
result.push(array.slice(i, i + size));
|
|
924
|
+
}
|
|
925
|
+
return result;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// 'UpdateTokenMetadata' and 'CreateTokenMetadata' have dynamic layouts
|
|
929
|
+
const MOVE_STAKE_LAYOUT = BufferLayout.struct([
|
|
930
|
+
BufferLayout.u8('instruction'),
|
|
931
|
+
BufferLayout.ns64('lamports'),
|
|
932
|
+
BufferLayout.ns64('transientStakeSeed'),
|
|
933
|
+
]);
|
|
934
|
+
const UPDATE_VALIDATOR_LIST_BALANCE_LAYOUT = BufferLayout.struct([
|
|
935
|
+
BufferLayout.u8('instruction'),
|
|
936
|
+
BufferLayout.u32('startIndex'),
|
|
937
|
+
BufferLayout.u8('noMerge'),
|
|
938
|
+
]);
|
|
939
|
+
function tokenMetadataLayout(instruction, nameLength, symbolLength, uriLength) {
|
|
940
|
+
if (nameLength > METADATA_MAX_NAME_LENGTH) {
|
|
941
|
+
// eslint-disable-next-line no-throw-literal
|
|
942
|
+
throw 'maximum token name length is 32 characters';
|
|
943
|
+
}
|
|
944
|
+
if (symbolLength > METADATA_MAX_SYMBOL_LENGTH) {
|
|
945
|
+
// eslint-disable-next-line no-throw-literal
|
|
946
|
+
throw 'maximum token symbol length is 10 characters';
|
|
947
|
+
}
|
|
948
|
+
if (uriLength > METADATA_MAX_URI_LENGTH) {
|
|
949
|
+
// eslint-disable-next-line no-throw-literal
|
|
950
|
+
throw 'maximum token uri length is 200 characters';
|
|
951
|
+
}
|
|
952
|
+
return {
|
|
953
|
+
index: instruction,
|
|
954
|
+
layout: BufferLayout.struct([
|
|
955
|
+
BufferLayout.u8('instruction'),
|
|
956
|
+
BufferLayout.u32('nameLen'),
|
|
957
|
+
BufferLayout.blob(nameLength, 'name'),
|
|
958
|
+
BufferLayout.u32('symbolLen'),
|
|
959
|
+
BufferLayout.blob(symbolLength, 'symbol'),
|
|
960
|
+
BufferLayout.u32('uriLen'),
|
|
961
|
+
BufferLayout.blob(uriLength, 'uri'),
|
|
962
|
+
]),
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* An enumeration of valid stake InstructionType's
|
|
967
|
+
* @internal
|
|
968
|
+
*/
|
|
969
|
+
const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
|
|
970
|
+
AddValidatorToPool: {
|
|
971
|
+
index: 1,
|
|
972
|
+
layout: BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.u32('seed')]),
|
|
973
|
+
},
|
|
974
|
+
RemoveValidatorFromPool: {
|
|
975
|
+
index: 2,
|
|
976
|
+
layout: BufferLayout.struct([BufferLayout.u8('instruction')]),
|
|
977
|
+
},
|
|
978
|
+
DecreaseValidatorStake: {
|
|
979
|
+
index: 3,
|
|
980
|
+
layout: MOVE_STAKE_LAYOUT,
|
|
981
|
+
},
|
|
982
|
+
IncreaseValidatorStake: {
|
|
983
|
+
index: 4,
|
|
984
|
+
layout: MOVE_STAKE_LAYOUT,
|
|
985
|
+
},
|
|
986
|
+
UpdateValidatorListBalance: {
|
|
987
|
+
index: 6,
|
|
988
|
+
layout: UPDATE_VALIDATOR_LIST_BALANCE_LAYOUT,
|
|
989
|
+
},
|
|
990
|
+
UpdateStakePoolBalance: {
|
|
991
|
+
index: 7,
|
|
992
|
+
layout: BufferLayout.struct([BufferLayout.u8('instruction')]),
|
|
993
|
+
},
|
|
994
|
+
CleanupRemovedValidatorEntries: {
|
|
995
|
+
index: 8,
|
|
996
|
+
layout: BufferLayout.struct([BufferLayout.u8('instruction')]),
|
|
997
|
+
},
|
|
998
|
+
DepositStake: {
|
|
999
|
+
index: 9,
|
|
1000
|
+
layout: BufferLayout.struct([BufferLayout.u8('instruction')]),
|
|
1001
|
+
},
|
|
1002
|
+
/// Withdraw the token from the pool at the current ratio.
|
|
1003
|
+
WithdrawStake: {
|
|
1004
|
+
index: 10,
|
|
1005
|
+
layout: BufferLayout.struct([
|
|
1006
|
+
BufferLayout.u8('instruction'),
|
|
1007
|
+
BufferLayout.ns64('poolTokens'),
|
|
1008
|
+
]),
|
|
1009
|
+
},
|
|
1010
|
+
/// Deposit SOL directly into the pool's reserve account. The output is a "pool" token
|
|
1011
|
+
/// representing ownership into the pool. Inputs are converted to the current ratio.
|
|
1012
|
+
DepositSol: {
|
|
1013
|
+
index: 14,
|
|
1014
|
+
layout: BufferLayout.struct([
|
|
1015
|
+
BufferLayout.u8('instruction'),
|
|
1016
|
+
BufferLayout.ns64('lamports'),
|
|
1017
|
+
]),
|
|
1018
|
+
},
|
|
1019
|
+
/// Withdraw SOL directly from the pool's reserve account. Fails if the
|
|
1020
|
+
/// reserve does not have enough SOL.
|
|
1021
|
+
WithdrawSol: {
|
|
1022
|
+
index: 16,
|
|
1023
|
+
layout: BufferLayout.struct([
|
|
1024
|
+
BufferLayout.u8('instruction'),
|
|
1025
|
+
BufferLayout.ns64('poolTokens'),
|
|
1026
|
+
]),
|
|
1027
|
+
},
|
|
1028
|
+
IncreaseAdditionalValidatorStake: {
|
|
1029
|
+
index: 19,
|
|
1030
|
+
layout: BufferLayout.struct([
|
|
1031
|
+
BufferLayout.u8('instruction'),
|
|
1032
|
+
BufferLayout.ns64('lamports'),
|
|
1033
|
+
BufferLayout.ns64('transientStakeSeed'),
|
|
1034
|
+
BufferLayout.ns64('ephemeralStakeSeed'),
|
|
1035
|
+
]),
|
|
1036
|
+
},
|
|
1037
|
+
DecreaseAdditionalValidatorStake: {
|
|
1038
|
+
index: 20,
|
|
1039
|
+
layout: BufferLayout.struct([
|
|
1040
|
+
BufferLayout.u8('instruction'),
|
|
1041
|
+
BufferLayout.ns64('lamports'),
|
|
1042
|
+
BufferLayout.ns64('transientStakeSeed'),
|
|
1043
|
+
BufferLayout.ns64('ephemeralStakeSeed'),
|
|
1044
|
+
]),
|
|
1045
|
+
},
|
|
1046
|
+
DecreaseValidatorStakeWithReserve: {
|
|
1047
|
+
index: 21,
|
|
1048
|
+
layout: MOVE_STAKE_LAYOUT,
|
|
1049
|
+
},
|
|
1050
|
+
Redelegate: {
|
|
1051
|
+
index: 22,
|
|
1052
|
+
layout: BufferLayout.struct([BufferLayout.u8('instruction')]),
|
|
1053
|
+
},
|
|
1054
|
+
DepositStakeWithSlippage: {
|
|
1055
|
+
index: 23,
|
|
1056
|
+
layout: BufferLayout.struct([
|
|
1057
|
+
BufferLayout.u8('instruction'),
|
|
1058
|
+
BufferLayout.ns64('lamports'),
|
|
1059
|
+
]),
|
|
1060
|
+
},
|
|
1061
|
+
WithdrawStakeWithSlippage: {
|
|
1062
|
+
index: 24,
|
|
1063
|
+
layout: BufferLayout.struct([
|
|
1064
|
+
BufferLayout.u8('instruction'),
|
|
1065
|
+
BufferLayout.ns64('lamports'),
|
|
1066
|
+
]),
|
|
1067
|
+
},
|
|
1068
|
+
DepositSolWithSlippage: {
|
|
1069
|
+
index: 25,
|
|
1070
|
+
layout: BufferLayout.struct([
|
|
1071
|
+
BufferLayout.u8('instruction'),
|
|
1072
|
+
BufferLayout.ns64('lamports'),
|
|
1073
|
+
]),
|
|
1074
|
+
},
|
|
1075
|
+
WithdrawSolWithSlippage: {
|
|
1076
|
+
index: 26,
|
|
1077
|
+
layout: BufferLayout.struct([
|
|
1078
|
+
BufferLayout.u8('instruction'),
|
|
1079
|
+
BufferLayout.ns64('lamports'),
|
|
1080
|
+
]),
|
|
1081
|
+
},
|
|
1082
|
+
DepositWsolWithSession: {
|
|
1083
|
+
index: 27,
|
|
1084
|
+
layout: BufferLayout.struct([
|
|
1085
|
+
BufferLayout.u8('instruction'),
|
|
1086
|
+
BufferLayout.ns64('lamports'),
|
|
1087
|
+
]),
|
|
1088
|
+
},
|
|
1089
|
+
WithdrawWsolWithSession: {
|
|
1090
|
+
index: 28,
|
|
1091
|
+
layout: BufferLayout.struct([
|
|
1092
|
+
BufferLayout.u8('instruction'),
|
|
1093
|
+
BufferLayout.ns64('poolTokens'),
|
|
1094
|
+
]),
|
|
1095
|
+
},
|
|
1096
|
+
});
|
|
1097
|
+
/**
|
|
1098
|
+
* Stake Pool Instruction class
|
|
1099
|
+
*/
|
|
1100
|
+
class StakePoolInstruction {
|
|
1101
|
+
/**
|
|
1102
|
+
* Creates instruction to add a validator into the stake pool.
|
|
1103
|
+
*/
|
|
1104
|
+
static addValidatorToPool(params) {
|
|
1105
|
+
const { programId, stakePool, staker, reserveStake, withdrawAuthority, validatorList, validatorStake, validatorVote, seed, } = params;
|
|
1106
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.AddValidatorToPool;
|
|
1107
|
+
const data = encodeData(type, { seed: seed !== null && seed !== void 0 ? seed : 0 });
|
|
1108
|
+
const keys = [
|
|
1109
|
+
{ pubkey: stakePool, isSigner: false, isWritable: true },
|
|
1110
|
+
{ pubkey: staker, isSigner: true, isWritable: false },
|
|
1111
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
|
|
1112
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1113
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1114
|
+
{ pubkey: validatorStake, isSigner: false, isWritable: true },
|
|
1115
|
+
{ pubkey: validatorVote, isSigner: false, isWritable: false },
|
|
1116
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
1117
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1118
|
+
{ pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
|
|
1119
|
+
{ pubkey: STAKE_CONFIG_ID, isSigner: false, isWritable: false },
|
|
1120
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1121
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1122
|
+
];
|
|
1123
|
+
return new TransactionInstruction({
|
|
1124
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1125
|
+
keys,
|
|
1126
|
+
data,
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Creates instruction to remove a validator from the stake pool.
|
|
1131
|
+
*/
|
|
1132
|
+
static removeValidatorFromPool(params) {
|
|
1133
|
+
const { programId, stakePool, staker, withdrawAuthority, validatorList, validatorStake, transientStake, } = params;
|
|
1134
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.RemoveValidatorFromPool;
|
|
1135
|
+
const data = encodeData(type);
|
|
1136
|
+
const keys = [
|
|
1137
|
+
{ pubkey: stakePool, isSigner: false, isWritable: true },
|
|
1138
|
+
{ pubkey: staker, isSigner: true, isWritable: false },
|
|
1139
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1140
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1141
|
+
{ pubkey: validatorStake, isSigner: false, isWritable: true },
|
|
1142
|
+
{ pubkey: transientStake, isSigner: false, isWritable: true },
|
|
1143
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1144
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1145
|
+
];
|
|
1146
|
+
return new TransactionInstruction({
|
|
1147
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1148
|
+
keys,
|
|
1149
|
+
data,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Creates instruction to update a set of validators in the stake pool.
|
|
1154
|
+
*/
|
|
1155
|
+
static updateValidatorListBalance(params) {
|
|
1156
|
+
const { programId, stakePool, withdrawAuthority, validatorList, reserveStake, startIndex, noMerge, validatorAndTransientStakePairs, } = params;
|
|
1157
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.UpdateValidatorListBalance;
|
|
1158
|
+
const data = encodeData(type, { startIndex, noMerge: noMerge ? 1 : 0 });
|
|
1159
|
+
const keys = [
|
|
1160
|
+
{ pubkey: stakePool, isSigner: false, isWritable: false },
|
|
1161
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1162
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1163
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
|
|
1164
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1165
|
+
{ pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
|
|
1166
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1167
|
+
...validatorAndTransientStakePairs.map(pubkey => ({
|
|
1168
|
+
pubkey,
|
|
1169
|
+
isSigner: false,
|
|
1170
|
+
isWritable: true,
|
|
1171
|
+
})),
|
|
1172
|
+
];
|
|
1173
|
+
return new TransactionInstruction({
|
|
1174
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1175
|
+
keys,
|
|
1176
|
+
data,
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Creates instruction to update the overall stake pool balance.
|
|
1181
|
+
*/
|
|
1182
|
+
static updateStakePoolBalance(params) {
|
|
1183
|
+
const { programId, stakePool, withdrawAuthority, validatorList, reserveStake, managerFeeAccount, poolMint, } = params;
|
|
1184
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.UpdateStakePoolBalance;
|
|
1185
|
+
const data = encodeData(type);
|
|
1186
|
+
const keys = [
|
|
1187
|
+
{ pubkey: stakePool, isSigner: false, isWritable: true },
|
|
1188
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1189
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1190
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: false },
|
|
1191
|
+
{ pubkey: managerFeeAccount, isSigner: false, isWritable: true },
|
|
1192
|
+
{ pubkey: poolMint, isSigner: false, isWritable: true },
|
|
1193
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1194
|
+
];
|
|
1195
|
+
return new TransactionInstruction({
|
|
1196
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1197
|
+
keys,
|
|
1198
|
+
data,
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Creates instruction to cleanup removed validator entries.
|
|
1203
|
+
*/
|
|
1204
|
+
static cleanupRemovedValidatorEntries(params) {
|
|
1205
|
+
const { programId, stakePool, validatorList } = params;
|
|
1206
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.CleanupRemovedValidatorEntries;
|
|
1207
|
+
const data = encodeData(type);
|
|
1208
|
+
const keys = [
|
|
1209
|
+
{ pubkey: stakePool, isSigner: false, isWritable: false },
|
|
1210
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1211
|
+
];
|
|
1212
|
+
return new TransactionInstruction({
|
|
1213
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1214
|
+
keys,
|
|
1215
|
+
data,
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Creates `IncreaseValidatorStake` instruction (rebalance from reserve account to
|
|
1220
|
+
* transient account)
|
|
1221
|
+
*/
|
|
1222
|
+
static increaseValidatorStake(params) {
|
|
1223
|
+
const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, transientStake, validatorStake, validatorVote, lamports, transientStakeSeed, } = params;
|
|
1224
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.IncreaseValidatorStake;
|
|
1225
|
+
const data = encodeData(type, { lamports, transientStakeSeed });
|
|
1226
|
+
const keys = [
|
|
1227
|
+
{ pubkey: stakePool, isSigner: false, isWritable: false },
|
|
1228
|
+
{ pubkey: staker, isSigner: true, isWritable: false },
|
|
1229
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1230
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1231
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
|
|
1232
|
+
{ pubkey: transientStake, isSigner: false, isWritable: true },
|
|
1233
|
+
{ pubkey: validatorStake, isSigner: false, isWritable: false },
|
|
1234
|
+
{ pubkey: validatorVote, isSigner: false, isWritable: false },
|
|
1235
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1236
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
1237
|
+
{ pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
|
|
1238
|
+
{ pubkey: STAKE_CONFIG_ID, isSigner: false, isWritable: false },
|
|
1239
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1240
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1241
|
+
];
|
|
1242
|
+
return new TransactionInstruction({
|
|
1243
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1244
|
+
keys,
|
|
1245
|
+
data,
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Creates `IncreaseAdditionalValidatorStake` instruction (rebalance from reserve account to
|
|
1250
|
+
* transient account)
|
|
1251
|
+
*/
|
|
1252
|
+
static increaseAdditionalValidatorStake(params) {
|
|
1253
|
+
const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, transientStake, validatorStake, validatorVote, lamports, transientStakeSeed, ephemeralStake, ephemeralStakeSeed, } = params;
|
|
1254
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.IncreaseAdditionalValidatorStake;
|
|
1255
|
+
const data = encodeData(type, { lamports, transientStakeSeed, ephemeralStakeSeed });
|
|
1256
|
+
const keys = [
|
|
1257
|
+
{ pubkey: stakePool, isSigner: false, isWritable: false },
|
|
1258
|
+
{ pubkey: staker, isSigner: true, isWritable: false },
|
|
1259
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1260
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1261
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
|
|
1262
|
+
{ pubkey: ephemeralStake, isSigner: false, isWritable: true },
|
|
1263
|
+
{ pubkey: transientStake, isSigner: false, isWritable: true },
|
|
1264
|
+
{ pubkey: validatorStake, isSigner: false, isWritable: false },
|
|
1265
|
+
{ pubkey: validatorVote, isSigner: false, isWritable: false },
|
|
1266
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1267
|
+
{ pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
|
|
1268
|
+
{ pubkey: STAKE_CONFIG_ID, isSigner: false, isWritable: false },
|
|
1269
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1270
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1271
|
+
];
|
|
1272
|
+
return new TransactionInstruction({
|
|
1273
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1274
|
+
keys,
|
|
1275
|
+
data,
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Creates `DecreaseValidatorStake` instruction (rebalance from validator account to
|
|
1280
|
+
* transient account)
|
|
1281
|
+
*/
|
|
1282
|
+
static decreaseValidatorStake(params) {
|
|
1283
|
+
const { programId, stakePool, staker, withdrawAuthority, validatorList, validatorStake, transientStake, lamports, transientStakeSeed, } = params;
|
|
1284
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DecreaseValidatorStake;
|
|
1285
|
+
const data = encodeData(type, { lamports, transientStakeSeed });
|
|
1286
|
+
const keys = [
|
|
1287
|
+
{ pubkey: stakePool, isSigner: false, isWritable: false },
|
|
1288
|
+
{ pubkey: staker, isSigner: true, isWritable: false },
|
|
1289
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1290
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1291
|
+
{ pubkey: validatorStake, isSigner: false, isWritable: true },
|
|
1292
|
+
{ pubkey: transientStake, isSigner: false, isWritable: true },
|
|
1293
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1294
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
1295
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1296
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1297
|
+
];
|
|
1298
|
+
return new TransactionInstruction({
|
|
1299
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1300
|
+
keys,
|
|
1301
|
+
data,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Creates `DecreaseValidatorStakeWithReserve` instruction (rebalance from
|
|
1306
|
+
* validator account to transient account)
|
|
1307
|
+
*/
|
|
1308
|
+
static decreaseValidatorStakeWithReserve(params) {
|
|
1309
|
+
const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, validatorStake, transientStake, lamports, transientStakeSeed, } = params;
|
|
1310
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DecreaseValidatorStakeWithReserve;
|
|
1311
|
+
const data = encodeData(type, { lamports, transientStakeSeed });
|
|
1312
|
+
const keys = [
|
|
1313
|
+
{ pubkey: stakePool, isSigner: false, isWritable: false },
|
|
1314
|
+
{ pubkey: staker, isSigner: true, isWritable: false },
|
|
1315
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1316
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1317
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
|
|
1318
|
+
{ pubkey: validatorStake, isSigner: false, isWritable: true },
|
|
1319
|
+
{ pubkey: transientStake, isSigner: false, isWritable: true },
|
|
1320
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1321
|
+
{ pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
|
|
1322
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1323
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1324
|
+
];
|
|
1325
|
+
return new TransactionInstruction({
|
|
1326
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1327
|
+
keys,
|
|
1328
|
+
data,
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Creates `DecreaseAdditionalValidatorStake` instruction (rebalance from
|
|
1333
|
+
* validator account to transient account)
|
|
1334
|
+
*/
|
|
1335
|
+
static decreaseAdditionalValidatorStake(params) {
|
|
1336
|
+
const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, validatorStake, transientStake, lamports, transientStakeSeed, ephemeralStakeSeed, ephemeralStake, } = params;
|
|
1337
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DecreaseAdditionalValidatorStake;
|
|
1338
|
+
const data = encodeData(type, { lamports, transientStakeSeed, ephemeralStakeSeed });
|
|
1339
|
+
const keys = [
|
|
1340
|
+
{ pubkey: stakePool, isSigner: false, isWritable: false },
|
|
1341
|
+
{ pubkey: staker, isSigner: true, isWritable: false },
|
|
1342
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1343
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1344
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
|
|
1345
|
+
{ pubkey: validatorStake, isSigner: false, isWritable: true },
|
|
1346
|
+
{ pubkey: ephemeralStake, isSigner: false, isWritable: true },
|
|
1347
|
+
{ pubkey: transientStake, isSigner: false, isWritable: true },
|
|
1348
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1349
|
+
{ pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
|
|
1350
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1351
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1352
|
+
];
|
|
1353
|
+
return new TransactionInstruction({
|
|
1354
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1355
|
+
keys,
|
|
1356
|
+
data,
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Creates a transaction instruction to deposit a stake account into a stake pool.
|
|
1361
|
+
*/
|
|
1362
|
+
static depositStake(params) {
|
|
1363
|
+
const { programId, stakePool, validatorList, depositAuthority, withdrawAuthority, depositStake, validatorStake, reserveStake, destinationPoolAccount, managerFeeAccount, referralPoolAccount, poolMint, } = params;
|
|
1364
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DepositStake;
|
|
1365
|
+
const data = encodeData(type);
|
|
1366
|
+
const keys = [
|
|
1367
|
+
{ pubkey: stakePool, isSigner: false, isWritable: true },
|
|
1368
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1369
|
+
{ pubkey: depositAuthority, isSigner: false, isWritable: false },
|
|
1370
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1371
|
+
{ pubkey: depositStake, isSigner: false, isWritable: true },
|
|
1372
|
+
{ pubkey: validatorStake, isSigner: false, isWritable: true },
|
|
1373
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
|
|
1374
|
+
{ pubkey: destinationPoolAccount, isSigner: false, isWritable: true },
|
|
1375
|
+
{ pubkey: managerFeeAccount, isSigner: false, isWritable: true },
|
|
1376
|
+
{ pubkey: referralPoolAccount, isSigner: false, isWritable: true },
|
|
1377
|
+
{ pubkey: poolMint, isSigner: false, isWritable: true },
|
|
1378
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1379
|
+
{ pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
|
|
1380
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1381
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1382
|
+
];
|
|
1383
|
+
return new TransactionInstruction({
|
|
1384
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1385
|
+
keys,
|
|
1386
|
+
data,
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Creates a transaction instruction to deposit SOL into a stake pool.
|
|
1391
|
+
*/
|
|
1392
|
+
static depositSol(params) {
|
|
1393
|
+
const { programId, stakePool, withdrawAuthority, depositAuthority, reserveStake, fundingAccount, destinationPoolAccount, managerFeeAccount, referralPoolAccount, poolMint, lamports, } = params;
|
|
1394
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DepositSol;
|
|
1395
|
+
const data = encodeData(type, { lamports });
|
|
1396
|
+
const keys = [
|
|
1397
|
+
{ pubkey: stakePool, isSigner: false, isWritable: true },
|
|
1398
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1399
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
|
|
1400
|
+
{ pubkey: fundingAccount, isSigner: true, isWritable: true },
|
|
1401
|
+
{ pubkey: destinationPoolAccount, isSigner: false, isWritable: true },
|
|
1402
|
+
{ pubkey: managerFeeAccount, isSigner: false, isWritable: true },
|
|
1403
|
+
{ pubkey: referralPoolAccount, isSigner: false, isWritable: true },
|
|
1404
|
+
{ pubkey: poolMint, isSigner: false, isWritable: true },
|
|
1405
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1406
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1407
|
+
];
|
|
1408
|
+
if (depositAuthority) {
|
|
1409
|
+
keys.push({
|
|
1410
|
+
pubkey: depositAuthority,
|
|
1411
|
+
isSigner: true,
|
|
1412
|
+
isWritable: false,
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
return new TransactionInstruction({
|
|
1416
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1417
|
+
keys,
|
|
1418
|
+
data,
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Creates a transaction instruction to deposit WSOL into a stake pool.
|
|
1423
|
+
*/
|
|
1424
|
+
static depositWsolWithSession(params) {
|
|
1425
|
+
var _a;
|
|
1426
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DepositWsolWithSession;
|
|
1427
|
+
const data = encodeData(type, { lamports: params.lamports });
|
|
1428
|
+
const keys = [
|
|
1429
|
+
{ pubkey: params.stakePool, isSigner: false, isWritable: true },
|
|
1430
|
+
{ pubkey: params.withdrawAuthority, isSigner: false, isWritable: false },
|
|
1431
|
+
{ pubkey: params.reserveStake, isSigner: false, isWritable: true },
|
|
1432
|
+
{ pubkey: params.fundingAccount, isSigner: true, isWritable: true },
|
|
1433
|
+
{ pubkey: params.destinationPoolAccount, isSigner: false, isWritable: true },
|
|
1434
|
+
{ pubkey: params.managerFeeAccount, isSigner: false, isWritable: true },
|
|
1435
|
+
{ pubkey: params.referralPoolAccount, isSigner: false, isWritable: true },
|
|
1436
|
+
{ pubkey: params.poolMint, isSigner: false, isWritable: true },
|
|
1437
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1438
|
+
{ pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
|
|
1439
|
+
// wsol specific accounts
|
|
1440
|
+
{ pubkey: params.wsolMint, isSigner: false, isWritable: false },
|
|
1441
|
+
{ pubkey: params.wsolTokenAccount, isSigner: false, isWritable: true },
|
|
1442
|
+
{ pubkey: params.wsolTransientAccount, isSigner: false, isWritable: true },
|
|
1443
|
+
{ pubkey: params.programSigner, isSigner: false, isWritable: true },
|
|
1444
|
+
{ pubkey: (_a = params.payer) !== null && _a !== void 0 ? _a : params.fundingAccount, isSigner: true, isWritable: true },
|
|
1445
|
+
];
|
|
1446
|
+
if (params.depositAuthority) {
|
|
1447
|
+
keys.push({
|
|
1448
|
+
pubkey: params.depositAuthority,
|
|
1449
|
+
isSigner: true,
|
|
1450
|
+
isWritable: false,
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
return new TransactionInstruction({
|
|
1454
|
+
programId: params.programId,
|
|
1455
|
+
keys,
|
|
1456
|
+
data,
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Creates a transaction instruction to withdraw active stake from a stake pool.
|
|
1461
|
+
*/
|
|
1462
|
+
static withdrawStake(params) {
|
|
1463
|
+
const { programId, stakePool, validatorList, withdrawAuthority, validatorStake, destinationStake, destinationStakeAuthority, sourceTransferAuthority, sourcePoolAccount, managerFeeAccount, poolMint, poolTokens, } = params;
|
|
1464
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawStake;
|
|
1465
|
+
const data = encodeData(type, { poolTokens });
|
|
1466
|
+
const keys = [
|
|
1467
|
+
{ pubkey: stakePool, isSigner: false, isWritable: true },
|
|
1468
|
+
{ pubkey: validatorList, isSigner: false, isWritable: true },
|
|
1469
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1470
|
+
{ pubkey: validatorStake, isSigner: false, isWritable: true },
|
|
1471
|
+
{ pubkey: destinationStake, isSigner: false, isWritable: true },
|
|
1472
|
+
{ pubkey: destinationStakeAuthority, isSigner: false, isWritable: false },
|
|
1473
|
+
{ pubkey: sourceTransferAuthority, isSigner: true, isWritable: false },
|
|
1474
|
+
{ pubkey: sourcePoolAccount, isSigner: false, isWritable: true },
|
|
1475
|
+
{ pubkey: managerFeeAccount, isSigner: false, isWritable: true },
|
|
1476
|
+
{ pubkey: poolMint, isSigner: false, isWritable: true },
|
|
1477
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1478
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1479
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1480
|
+
];
|
|
1481
|
+
return new TransactionInstruction({
|
|
1482
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1483
|
+
keys,
|
|
1484
|
+
data,
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Creates a transaction instruction to withdraw SOL from a stake pool.
|
|
1489
|
+
*/
|
|
1490
|
+
static withdrawSol(params) {
|
|
1491
|
+
const { programId, stakePool, withdrawAuthority, sourceTransferAuthority, sourcePoolAccount, reserveStake, destinationSystemAccount, managerFeeAccount, solWithdrawAuthority, poolMint, poolTokens, } = params;
|
|
1492
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawSol;
|
|
1493
|
+
const data = encodeData(type, { poolTokens });
|
|
1494
|
+
const keys = [
|
|
1495
|
+
{ pubkey: stakePool, isSigner: false, isWritable: true },
|
|
1496
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1497
|
+
{ pubkey: sourceTransferAuthority, isSigner: true, isWritable: false },
|
|
1498
|
+
{ pubkey: sourcePoolAccount, isSigner: false, isWritable: true },
|
|
1499
|
+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
|
|
1500
|
+
{ pubkey: destinationSystemAccount, isSigner: false, isWritable: true },
|
|
1501
|
+
{ pubkey: managerFeeAccount, isSigner: false, isWritable: true },
|
|
1502
|
+
{ pubkey: poolMint, isSigner: false, isWritable: true },
|
|
1503
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1504
|
+
{ pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
|
|
1505
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1506
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1507
|
+
];
|
|
1508
|
+
if (solWithdrawAuthority) {
|
|
1509
|
+
keys.push({
|
|
1510
|
+
pubkey: solWithdrawAuthority,
|
|
1511
|
+
isSigner: true,
|
|
1512
|
+
isWritable: false,
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
return new TransactionInstruction({
|
|
1516
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1517
|
+
keys,
|
|
1518
|
+
data,
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Creates a transaction instruction to withdraw WSOL from a stake pool using a session.
|
|
1523
|
+
*/
|
|
1524
|
+
static withdrawWsolWithSession(params) {
|
|
1525
|
+
const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawWsolWithSession;
|
|
1526
|
+
const data = encodeData(type, { poolTokens: params.poolTokens });
|
|
1527
|
+
const keys = [
|
|
1528
|
+
{ pubkey: params.stakePool, isSigner: false, isWritable: true },
|
|
1529
|
+
{ pubkey: params.withdrawAuthority, isSigner: false, isWritable: false },
|
|
1530
|
+
{ pubkey: params.userTransferAuthority, isSigner: true, isWritable: true },
|
|
1531
|
+
{ pubkey: params.poolTokensFrom, isSigner: false, isWritable: true },
|
|
1532
|
+
{ pubkey: params.reserveStake, isSigner: false, isWritable: true },
|
|
1533
|
+
{ pubkey: params.userWsolAccount, isSigner: false, isWritable: true },
|
|
1534
|
+
{ pubkey: params.managerFeeAccount, isSigner: false, isWritable: true },
|
|
1535
|
+
{ pubkey: params.poolMint, isSigner: false, isWritable: true },
|
|
1536
|
+
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
|
1537
|
+
{ pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
|
|
1538
|
+
{ pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
|
|
1539
|
+
{ pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
|
|
1540
|
+
{ pubkey: params.wsolMint, isSigner: false, isWritable: false },
|
|
1541
|
+
{ pubkey: params.programSigner, isSigner: false, isWritable: true },
|
|
1542
|
+
];
|
|
1543
|
+
if (params.solWithdrawAuthority) {
|
|
1544
|
+
keys.push({
|
|
1545
|
+
pubkey: params.solWithdrawAuthority,
|
|
1546
|
+
isSigner: true,
|
|
1547
|
+
isWritable: false,
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
return new TransactionInstruction({
|
|
1551
|
+
programId: params.programId,
|
|
1552
|
+
keys,
|
|
1553
|
+
data,
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Creates an instruction to create metadata
|
|
1558
|
+
* using the mpl token metadata program for the pool token
|
|
1559
|
+
*/
|
|
1560
|
+
static createTokenMetadata(params) {
|
|
1561
|
+
const { programId, stakePool, withdrawAuthority, tokenMetadata, manager, payer, poolMint, name, symbol, uri, } = params;
|
|
1562
|
+
const keys = [
|
|
1563
|
+
{ pubkey: stakePool, isSigner: false, isWritable: false },
|
|
1564
|
+
{ pubkey: manager, isSigner: true, isWritable: false },
|
|
1565
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1566
|
+
{ pubkey: poolMint, isSigner: false, isWritable: false },
|
|
1567
|
+
{ pubkey: payer, isSigner: true, isWritable: true },
|
|
1568
|
+
{ pubkey: tokenMetadata, isSigner: false, isWritable: true },
|
|
1569
|
+
{ pubkey: METADATA_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1570
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1571
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
1572
|
+
];
|
|
1573
|
+
const type = tokenMetadataLayout(17, name.length, symbol.length, uri.length);
|
|
1574
|
+
const data = encodeData(type, {
|
|
1575
|
+
nameLen: name.length,
|
|
1576
|
+
name: Buffer.from(name),
|
|
1577
|
+
symbolLen: symbol.length,
|
|
1578
|
+
symbol: Buffer.from(symbol),
|
|
1579
|
+
uriLen: uri.length,
|
|
1580
|
+
uri: Buffer.from(uri),
|
|
1581
|
+
});
|
|
1582
|
+
return new TransactionInstruction({
|
|
1583
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1584
|
+
keys,
|
|
1585
|
+
data,
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Creates an instruction to update metadata
|
|
1590
|
+
* in the mpl token metadata program account for the pool token
|
|
1591
|
+
*/
|
|
1592
|
+
static updateTokenMetadata(params) {
|
|
1593
|
+
const { programId, stakePool, withdrawAuthority, tokenMetadata, manager, name, symbol, uri } = params;
|
|
1594
|
+
const keys = [
|
|
1595
|
+
{ pubkey: stakePool, isSigner: false, isWritable: false },
|
|
1596
|
+
{ pubkey: manager, isSigner: true, isWritable: false },
|
|
1597
|
+
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
|
|
1598
|
+
{ pubkey: tokenMetadata, isSigner: false, isWritable: true },
|
|
1599
|
+
{ pubkey: METADATA_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1600
|
+
];
|
|
1601
|
+
const type = tokenMetadataLayout(18, name.length, symbol.length, uri.length);
|
|
1602
|
+
const data = encodeData(type, {
|
|
1603
|
+
nameLen: name.length,
|
|
1604
|
+
name: Buffer.from(name),
|
|
1605
|
+
symbolLen: symbol.length,
|
|
1606
|
+
symbol: Buffer.from(symbol),
|
|
1607
|
+
uriLen: uri.length,
|
|
1608
|
+
uri: Buffer.from(uri),
|
|
1609
|
+
});
|
|
1610
|
+
return new TransactionInstruction({
|
|
1611
|
+
programId: programId !== null && programId !== void 0 ? programId : STAKE_POOL_PROGRAM_ID,
|
|
1612
|
+
keys,
|
|
1613
|
+
data,
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Decode a deposit stake pool instruction and retrieve the instruction params.
|
|
1618
|
+
*/
|
|
1619
|
+
static decodeDepositStake(instruction) {
|
|
1620
|
+
this.checkProgramId(instruction.programId);
|
|
1621
|
+
this.checkKeyLength(instruction.keys, 11);
|
|
1622
|
+
decodeData(STAKE_POOL_INSTRUCTION_LAYOUTS.DepositStake, instruction.data);
|
|
1623
|
+
return {
|
|
1624
|
+
programId: instruction.programId,
|
|
1625
|
+
stakePool: instruction.keys[0].pubkey,
|
|
1626
|
+
validatorList: instruction.keys[1].pubkey,
|
|
1627
|
+
depositAuthority: instruction.keys[2].pubkey,
|
|
1628
|
+
withdrawAuthority: instruction.keys[3].pubkey,
|
|
1629
|
+
depositStake: instruction.keys[4].pubkey,
|
|
1630
|
+
validatorStake: instruction.keys[5].pubkey,
|
|
1631
|
+
reserveStake: instruction.keys[6].pubkey,
|
|
1632
|
+
destinationPoolAccount: instruction.keys[7].pubkey,
|
|
1633
|
+
managerFeeAccount: instruction.keys[8].pubkey,
|
|
1634
|
+
referralPoolAccount: instruction.keys[9].pubkey,
|
|
1635
|
+
poolMint: instruction.keys[10].pubkey,
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Decode a deposit sol instruction and retrieve the instruction params.
|
|
1640
|
+
*/
|
|
1641
|
+
static decodeDepositSol(instruction) {
|
|
1642
|
+
this.checkProgramId(instruction.programId);
|
|
1643
|
+
this.checkKeyLength(instruction.keys, 9);
|
|
1644
|
+
const { amount } = decodeData(STAKE_POOL_INSTRUCTION_LAYOUTS.DepositSol, instruction.data);
|
|
1645
|
+
return {
|
|
1646
|
+
programId: instruction.programId,
|
|
1647
|
+
stakePool: instruction.keys[0].pubkey,
|
|
1648
|
+
depositAuthority: instruction.keys[1].pubkey,
|
|
1649
|
+
withdrawAuthority: instruction.keys[2].pubkey,
|
|
1650
|
+
reserveStake: instruction.keys[3].pubkey,
|
|
1651
|
+
fundingAccount: instruction.keys[4].pubkey,
|
|
1652
|
+
destinationPoolAccount: instruction.keys[5].pubkey,
|
|
1653
|
+
managerFeeAccount: instruction.keys[6].pubkey,
|
|
1654
|
+
referralPoolAccount: instruction.keys[7].pubkey,
|
|
1655
|
+
poolMint: instruction.keys[8].pubkey,
|
|
1656
|
+
lamports: amount,
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* @internal
|
|
1661
|
+
*/
|
|
1662
|
+
static checkProgramId(programId) {
|
|
1663
|
+
if (!programId.equals(STAKE_POOL_PROGRAM_ID)
|
|
1664
|
+
&& !programId.equals(DEVNET_STAKE_POOL_PROGRAM_ID)) {
|
|
1665
|
+
throw new Error('Invalid instruction; programId is not the stake pool program');
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* @internal
|
|
1670
|
+
*/
|
|
1671
|
+
static checkKeyLength(keys, expectedLength) {
|
|
1672
|
+
if (keys.length < expectedLength) {
|
|
1673
|
+
throw new Error(`Invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
function getStakePoolProgramId(rpcEndpoint) {
|
|
1679
|
+
if (rpcEndpoint.includes('devnet')) {
|
|
1680
|
+
return DEVNET_STAKE_POOL_PROGRAM_ID;
|
|
1681
|
+
}
|
|
1682
|
+
else {
|
|
1683
|
+
return STAKE_POOL_PROGRAM_ID;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Retrieves and deserializes a StakePool account using a web3js connection and the stake pool address.
|
|
1688
|
+
* @param connection An active web3js connection.
|
|
1689
|
+
* @param stakePoolAddress The public key (address) of the stake pool account.
|
|
1690
|
+
*/
|
|
1691
|
+
async function getStakePoolAccount(connection, stakePoolAddress) {
|
|
1692
|
+
const account = await connection.getAccountInfo(stakePoolAddress);
|
|
1693
|
+
if (!account) {
|
|
1694
|
+
throw new Error('Invalid stake pool account');
|
|
1695
|
+
}
|
|
1696
|
+
return {
|
|
1697
|
+
pubkey: stakePoolAddress,
|
|
1698
|
+
account: {
|
|
1699
|
+
data: StakePoolLayout.decode(account.data),
|
|
1700
|
+
executable: account.executable,
|
|
1701
|
+
lamports: account.lamports,
|
|
1702
|
+
owner: account.owner,
|
|
1703
|
+
},
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Retrieves and deserializes a Stake account using a web3js connection and the stake address.
|
|
1708
|
+
* @param connection An active web3js connection.
|
|
1709
|
+
* @param stakeAccount The public key (address) of the stake account.
|
|
1710
|
+
*/
|
|
1711
|
+
async function getStakeAccount(connection, stakeAccount) {
|
|
1712
|
+
const result = (await connection.getParsedAccountInfo(stakeAccount)).value;
|
|
1713
|
+
if (!result || !('parsed' in result.data)) {
|
|
1714
|
+
throw new Error('Invalid stake account');
|
|
1715
|
+
}
|
|
1716
|
+
const program = result.data.program;
|
|
1717
|
+
if (program !== 'stake') {
|
|
1718
|
+
throw new Error('Not a stake account');
|
|
1719
|
+
}
|
|
1720
|
+
return create(result.data.parsed, StakeAccount);
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Retrieves all StakePool and ValidatorList accounts that are running a particular StakePool program.
|
|
1724
|
+
* @param connection An active web3js connection.
|
|
1725
|
+
* @param stakePoolProgramAddress The public key (address) of the StakePool program.
|
|
1726
|
+
*/
|
|
1727
|
+
async function getStakePoolAccounts(connection, stakePoolProgramAddress) {
|
|
1728
|
+
const response = await connection.getProgramAccounts(stakePoolProgramAddress);
|
|
1729
|
+
return response
|
|
1730
|
+
.map((a) => {
|
|
1731
|
+
try {
|
|
1732
|
+
if (a.account.data.readUInt8() === 1) {
|
|
1733
|
+
const data = StakePoolLayout.decode(a.account.data);
|
|
1734
|
+
return {
|
|
1735
|
+
pubkey: a.pubkey,
|
|
1736
|
+
account: {
|
|
1737
|
+
data,
|
|
1738
|
+
executable: a.account.executable,
|
|
1739
|
+
lamports: a.account.lamports,
|
|
1740
|
+
owner: a.account.owner,
|
|
1741
|
+
},
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
else if (a.account.data.readUInt8() === 2) {
|
|
1745
|
+
const data = ValidatorListLayout.decode(a.account.data);
|
|
1746
|
+
return {
|
|
1747
|
+
pubkey: a.pubkey,
|
|
1748
|
+
account: {
|
|
1749
|
+
data,
|
|
1750
|
+
executable: a.account.executable,
|
|
1751
|
+
lamports: a.account.lamports,
|
|
1752
|
+
owner: a.account.owner,
|
|
1753
|
+
},
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
else {
|
|
1757
|
+
console.error(`Could not decode. StakePoolAccount Enum is ${a.account.data.readUInt8()}, expected 1 or 2!`);
|
|
1758
|
+
return undefined;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
catch (error) {
|
|
1762
|
+
console.error('Could not decode account. Error:', error);
|
|
1763
|
+
return undefined;
|
|
1764
|
+
}
|
|
1765
|
+
})
|
|
1766
|
+
.filter(a => a !== undefined);
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Creates instructions required to deposit stake to stake pool.
|
|
1770
|
+
*/
|
|
1771
|
+
async function depositStake(connection, stakePoolAddress, authorizedPubkey, validatorVote, depositStake, poolTokenReceiverAccount) {
|
|
1772
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
|
|
1773
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
1774
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
1775
|
+
const validatorStake = await findStakeProgramAddress(stakePoolProgramId, validatorVote, stakePoolAddress);
|
|
1776
|
+
const instructions = [];
|
|
1777
|
+
const signers = [];
|
|
1778
|
+
const poolMint = stakePool.account.data.poolMint;
|
|
1779
|
+
// Create token account if not specified
|
|
1780
|
+
if (!poolTokenReceiverAccount) {
|
|
1781
|
+
const associatedAddress = getAssociatedTokenAddressSync(poolMint, authorizedPubkey);
|
|
1782
|
+
instructions.push(createAssociatedTokenAccountIdempotentInstruction(authorizedPubkey, associatedAddress, authorizedPubkey, poolMint));
|
|
1783
|
+
poolTokenReceiverAccount = associatedAddress;
|
|
1784
|
+
}
|
|
1785
|
+
instructions.push(...StakeProgram.authorize({
|
|
1786
|
+
stakePubkey: depositStake,
|
|
1787
|
+
authorizedPubkey,
|
|
1788
|
+
newAuthorizedPubkey: stakePool.account.data.stakeDepositAuthority,
|
|
1789
|
+
stakeAuthorizationType: StakeAuthorizationLayout.Staker,
|
|
1790
|
+
}).instructions);
|
|
1791
|
+
instructions.push(...StakeProgram.authorize({
|
|
1792
|
+
stakePubkey: depositStake,
|
|
1793
|
+
authorizedPubkey,
|
|
1794
|
+
newAuthorizedPubkey: stakePool.account.data.stakeDepositAuthority,
|
|
1795
|
+
stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
|
|
1796
|
+
}).instructions);
|
|
1797
|
+
instructions.push(StakePoolInstruction.depositStake({
|
|
1798
|
+
programId: stakePoolProgramId,
|
|
1799
|
+
stakePool: stakePoolAddress,
|
|
1800
|
+
validatorList: stakePool.account.data.validatorList,
|
|
1801
|
+
depositAuthority: stakePool.account.data.stakeDepositAuthority,
|
|
1802
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
1803
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount,
|
|
1804
|
+
referralPoolAccount: poolTokenReceiverAccount,
|
|
1805
|
+
destinationPoolAccount: poolTokenReceiverAccount,
|
|
1806
|
+
withdrawAuthority,
|
|
1807
|
+
depositStake,
|
|
1808
|
+
validatorStake,
|
|
1809
|
+
poolMint,
|
|
1810
|
+
}));
|
|
1811
|
+
return {
|
|
1812
|
+
instructions,
|
|
1813
|
+
signers,
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Creates instructions required to deposit sol to stake pool.
|
|
1818
|
+
*/
|
|
1819
|
+
async function depositWsolWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, lamports, destinationTokenAccount, referrerTokenAccount, depositAuthority, payer) {
|
|
1820
|
+
const wsolTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, userPubkey);
|
|
1821
|
+
const tokenAccountInfo = await connection.getTokenAccountBalance(wsolTokenAccount, 'confirmed');
|
|
1822
|
+
const wsolBalance = tokenAccountInfo
|
|
1823
|
+
? parseInt(tokenAccountInfo.value.amount)
|
|
1824
|
+
: 0;
|
|
1825
|
+
if (wsolBalance < lamports) {
|
|
1826
|
+
throw new Error(`Not enough WSOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(wsolBalance)} WSOL.`);
|
|
1827
|
+
}
|
|
1828
|
+
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
|
|
1829
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
1830
|
+
const stakePool = stakePoolAccount.account.data;
|
|
1831
|
+
const instructions = [];
|
|
1832
|
+
// Create token account if not specified
|
|
1833
|
+
if (!destinationTokenAccount) {
|
|
1834
|
+
const associatedAddress = getAssociatedTokenAddressSync(stakePool.poolMint, userPubkey);
|
|
1835
|
+
instructions.push(createAssociatedTokenAccountIdempotentInstruction(signerOrSession, associatedAddress, userPubkey, stakePool.poolMint));
|
|
1836
|
+
destinationTokenAccount = associatedAddress;
|
|
1837
|
+
}
|
|
1838
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
1839
|
+
const [programSigner] = PublicKey.findProgramAddressSync([Buffer.from('fogo_session_program_signer')], stakePoolProgramId);
|
|
1840
|
+
const wsolTransientAccount = findWsolTransientProgramAddress(stakePoolProgramId, userPubkey);
|
|
1841
|
+
instructions.push(StakePoolInstruction.depositWsolWithSession({
|
|
1842
|
+
programId: stakePoolProgramId,
|
|
1843
|
+
stakePool: stakePoolAddress,
|
|
1844
|
+
reserveStake: stakePool.reserveStake,
|
|
1845
|
+
fundingAccount: signerOrSession,
|
|
1846
|
+
destinationPoolAccount: destinationTokenAccount,
|
|
1847
|
+
managerFeeAccount: stakePool.managerFeeAccount,
|
|
1848
|
+
referralPoolAccount: referrerTokenAccount !== null && referrerTokenAccount !== void 0 ? referrerTokenAccount : destinationTokenAccount,
|
|
1849
|
+
poolMint: stakePool.poolMint,
|
|
1850
|
+
lamports,
|
|
1851
|
+
withdrawAuthority,
|
|
1852
|
+
depositAuthority,
|
|
1853
|
+
wsolMint: NATIVE_MINT,
|
|
1854
|
+
wsolTokenAccount,
|
|
1855
|
+
wsolTransientAccount,
|
|
1856
|
+
tokenProgramId: stakePool.tokenProgramId,
|
|
1857
|
+
programSigner,
|
|
1858
|
+
payer,
|
|
1859
|
+
}));
|
|
1860
|
+
return {
|
|
1861
|
+
instructions,
|
|
1862
|
+
signers: [],
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Creates instructions required to deposit sol to stake pool.
|
|
1867
|
+
*/
|
|
1868
|
+
async function depositSol(connection, stakePoolAddress, from, lamports, destinationTokenAccount, referrerTokenAccount, depositAuthority) {
|
|
1869
|
+
const fromBalance = await connection.getBalance(from, 'confirmed');
|
|
1870
|
+
if (fromBalance < lamports) {
|
|
1871
|
+
throw new Error(`Not enough SOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(fromBalance)} SOL.`);
|
|
1872
|
+
}
|
|
1873
|
+
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
|
|
1874
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
1875
|
+
const stakePool = stakePoolAccount.account.data;
|
|
1876
|
+
// Ephemeral SOL account just to do the transfer
|
|
1877
|
+
const userSolTransfer = new Keypair();
|
|
1878
|
+
const signers = [userSolTransfer];
|
|
1879
|
+
const instructions = [];
|
|
1880
|
+
// Create the ephemeral SOL account
|
|
1881
|
+
instructions.push(SystemProgram.transfer({
|
|
1882
|
+
fromPubkey: from,
|
|
1883
|
+
toPubkey: userSolTransfer.publicKey,
|
|
1884
|
+
lamports,
|
|
1885
|
+
}));
|
|
1886
|
+
// Create token account if not specified
|
|
1887
|
+
if (!destinationTokenAccount) {
|
|
1888
|
+
const associatedAddress = getAssociatedTokenAddressSync(stakePool.poolMint, from);
|
|
1889
|
+
instructions.push(createAssociatedTokenAccountIdempotentInstruction(from, associatedAddress, from, stakePool.poolMint));
|
|
1890
|
+
destinationTokenAccount = associatedAddress;
|
|
1891
|
+
}
|
|
1892
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
1893
|
+
instructions.push(StakePoolInstruction.depositSol({
|
|
1894
|
+
programId: stakePoolProgramId,
|
|
1895
|
+
stakePool: stakePoolAddress,
|
|
1896
|
+
reserveStake: stakePool.reserveStake,
|
|
1897
|
+
fundingAccount: userSolTransfer.publicKey,
|
|
1898
|
+
destinationPoolAccount: destinationTokenAccount,
|
|
1899
|
+
managerFeeAccount: stakePool.managerFeeAccount,
|
|
1900
|
+
referralPoolAccount: referrerTokenAccount !== null && referrerTokenAccount !== void 0 ? referrerTokenAccount : destinationTokenAccount,
|
|
1901
|
+
poolMint: stakePool.poolMint,
|
|
1902
|
+
lamports,
|
|
1903
|
+
withdrawAuthority,
|
|
1904
|
+
depositAuthority,
|
|
1905
|
+
}));
|
|
1906
|
+
return {
|
|
1907
|
+
instructions,
|
|
1908
|
+
signers,
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Creates instructions required to withdraw stake from a stake pool.
|
|
1913
|
+
*/
|
|
1914
|
+
async function withdrawStake(connection, stakePoolAddress, tokenOwner, amount, useReserve = false, voteAccountAddress, stakeReceiver, poolTokenAccount, validatorComparator) {
|
|
1915
|
+
var _c, _d, _e, _f;
|
|
1916
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
|
|
1917
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
1918
|
+
const poolAmount = new BN(solToLamports(amount));
|
|
1919
|
+
if (!poolTokenAccount) {
|
|
1920
|
+
poolTokenAccount = getAssociatedTokenAddressSync(stakePool.account.data.poolMint, tokenOwner);
|
|
1921
|
+
}
|
|
1922
|
+
const tokenAccount = await getAccount(connection, poolTokenAccount);
|
|
1923
|
+
// Check withdrawFrom balance
|
|
1924
|
+
if (tokenAccount.amount < poolAmount.toNumber()) {
|
|
1925
|
+
throw new Error(`Not enough token balance to withdraw ${lamportsToSol(poolAmount)} pool tokens.
|
|
1926
|
+
Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`);
|
|
1927
|
+
}
|
|
1928
|
+
const stakeAccountRentExemption = await connection.getMinimumBalanceForRentExemption(StakeProgram.space);
|
|
1929
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
1930
|
+
let stakeReceiverAccount = null;
|
|
1931
|
+
if (stakeReceiver) {
|
|
1932
|
+
stakeReceiverAccount = await getStakeAccount(connection, stakeReceiver);
|
|
1933
|
+
}
|
|
1934
|
+
const withdrawAccounts = [];
|
|
1935
|
+
if (useReserve) {
|
|
1936
|
+
withdrawAccounts.push({
|
|
1937
|
+
stakeAddress: stakePool.account.data.reserveStake,
|
|
1938
|
+
voteAddress: undefined,
|
|
1939
|
+
poolAmount,
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
else if (stakeReceiverAccount
|
|
1943
|
+
&& (stakeReceiverAccount === null || stakeReceiverAccount === void 0 ? void 0 : stakeReceiverAccount.type) === 'delegated') {
|
|
1944
|
+
const voteAccount = (_d = (_c = stakeReceiverAccount.info) === null || _c === void 0 ? void 0 : _c.stake) === null || _d === void 0 ? void 0 : _d.delegation.voter;
|
|
1945
|
+
if (!voteAccount) {
|
|
1946
|
+
throw new Error(`Invalid stake receiver ${stakeReceiver} delegation`);
|
|
1947
|
+
}
|
|
1948
|
+
const validatorListAccount = await connection.getAccountInfo(stakePool.account.data.validatorList);
|
|
1949
|
+
const validatorList = ValidatorListLayout.decode(validatorListAccount === null || validatorListAccount === void 0 ? void 0 : validatorListAccount.data);
|
|
1950
|
+
const isValidVoter = validatorList.validators.find(val => val.voteAccountAddress.equals(voteAccount));
|
|
1951
|
+
if (voteAccountAddress && voteAccountAddress !== voteAccount) {
|
|
1952
|
+
throw new Error(`Provided withdrawal vote account ${voteAccountAddress} does not match delegation on stake receiver account ${voteAccount},
|
|
1953
|
+
remove this flag or provide a different stake account delegated to ${voteAccountAddress}`);
|
|
1954
|
+
}
|
|
1955
|
+
if (isValidVoter) {
|
|
1956
|
+
const stakeAccountAddress = await findStakeProgramAddress(stakePoolProgramId, voteAccount, stakePoolAddress);
|
|
1957
|
+
const stakeAccount = await connection.getAccountInfo(stakeAccountAddress);
|
|
1958
|
+
if (!stakeAccount) {
|
|
1959
|
+
throw new Error(`Preferred withdraw valdator's stake account is invalid`);
|
|
1960
|
+
}
|
|
1961
|
+
const availableForWithdrawal = calcLamportsWithdrawAmount(stakePool.account.data, new BN(stakeAccount.lamports
|
|
1962
|
+
- MINIMUM_ACTIVE_STAKE
|
|
1963
|
+
- stakeAccountRentExemption));
|
|
1964
|
+
if (availableForWithdrawal.lt(poolAmount)) {
|
|
1965
|
+
throw new Error(`Not enough lamports available for withdrawal from ${stakeAccountAddress},
|
|
1966
|
+
${poolAmount} asked, ${availableForWithdrawal} available.`);
|
|
1967
|
+
}
|
|
1968
|
+
withdrawAccounts.push({
|
|
1969
|
+
stakeAddress: stakeAccountAddress,
|
|
1970
|
+
voteAddress: voteAccount,
|
|
1971
|
+
poolAmount,
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
else {
|
|
1975
|
+
throw new Error(`Provided stake account is delegated to a vote account ${voteAccount} which does not exist in the stake pool`);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
else if (voteAccountAddress) {
|
|
1979
|
+
const stakeAccountAddress = await findStakeProgramAddress(stakePoolProgramId, voteAccountAddress, stakePoolAddress);
|
|
1980
|
+
const stakeAccount = await connection.getAccountInfo(stakeAccountAddress);
|
|
1981
|
+
if (!stakeAccount) {
|
|
1982
|
+
throw new Error('Invalid Stake Account');
|
|
1983
|
+
}
|
|
1984
|
+
const availableLamports = new BN(stakeAccount.lamports - MINIMUM_ACTIVE_STAKE - stakeAccountRentExemption);
|
|
1985
|
+
if (availableLamports.lt(new BN(0))) {
|
|
1986
|
+
throw new Error('Invalid Stake Account');
|
|
1987
|
+
}
|
|
1988
|
+
const availableForWithdrawal = calcLamportsWithdrawAmount(stakePool.account.data, availableLamports);
|
|
1989
|
+
if (availableForWithdrawal.lt(poolAmount)) {
|
|
1990
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
1991
|
+
throw new Error(`Not enough lamports available for withdrawal from ${stakeAccountAddress},
|
|
1992
|
+
${poolAmount} asked, ${availableForWithdrawal} available.`);
|
|
1993
|
+
}
|
|
1994
|
+
withdrawAccounts.push({
|
|
1995
|
+
stakeAddress: stakeAccountAddress,
|
|
1996
|
+
voteAddress: voteAccountAddress,
|
|
1997
|
+
poolAmount,
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
else {
|
|
2001
|
+
// Get the list of accounts to withdraw from
|
|
2002
|
+
withdrawAccounts.push(...(await prepareWithdrawAccounts(connection, stakePool.account.data, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.account.data.managerFeeAccount))));
|
|
2003
|
+
}
|
|
2004
|
+
// Construct transaction to withdraw from withdrawAccounts account list
|
|
2005
|
+
const instructions = [];
|
|
2006
|
+
const userTransferAuthority = Keypair.generate();
|
|
2007
|
+
const signers = [userTransferAuthority];
|
|
2008
|
+
instructions.push(createApproveInstruction(poolTokenAccount, userTransferAuthority.publicKey, tokenOwner, poolAmount.toNumber()));
|
|
2009
|
+
let totalRentFreeBalances = 0;
|
|
2010
|
+
// Max 5 accounts to prevent an error: "Transaction too large"
|
|
2011
|
+
const maxWithdrawAccounts = 5;
|
|
2012
|
+
let i = 0;
|
|
2013
|
+
// Go through prepared accounts and withdraw/claim them
|
|
2014
|
+
for (const withdrawAccount of withdrawAccounts) {
|
|
2015
|
+
if (i > maxWithdrawAccounts) {
|
|
2016
|
+
break;
|
|
2017
|
+
}
|
|
2018
|
+
// Convert pool tokens amount to lamports
|
|
2019
|
+
const solWithdrawAmount = calcLamportsWithdrawAmount(stakePool.account.data, withdrawAccount.poolAmount);
|
|
2020
|
+
let infoMsg = `Withdrawing ◎${solWithdrawAmount},
|
|
2021
|
+
from stake account ${(_e = withdrawAccount.stakeAddress) === null || _e === void 0 ? void 0 : _e.toBase58()}`;
|
|
2022
|
+
if (withdrawAccount.voteAddress) {
|
|
2023
|
+
infoMsg = `${infoMsg}, delegated to ${(_f = withdrawAccount.voteAddress) === null || _f === void 0 ? void 0 : _f.toBase58()}`;
|
|
2024
|
+
}
|
|
2025
|
+
console.info(infoMsg);
|
|
2026
|
+
let stakeToReceive;
|
|
2027
|
+
if (!stakeReceiver
|
|
2028
|
+
|| (stakeReceiverAccount && stakeReceiverAccount.type === 'delegated')) {
|
|
2029
|
+
const stakeKeypair = newStakeAccount(tokenOwner, instructions, stakeAccountRentExemption);
|
|
2030
|
+
signers.push(stakeKeypair);
|
|
2031
|
+
totalRentFreeBalances += stakeAccountRentExemption;
|
|
2032
|
+
stakeToReceive = stakeKeypair.publicKey;
|
|
2033
|
+
}
|
|
2034
|
+
else {
|
|
2035
|
+
stakeToReceive = stakeReceiver;
|
|
2036
|
+
}
|
|
2037
|
+
instructions.push(StakePoolInstruction.withdrawStake({
|
|
2038
|
+
programId: stakePoolProgramId,
|
|
2039
|
+
stakePool: stakePoolAddress,
|
|
2040
|
+
validatorList: stakePool.account.data.validatorList,
|
|
2041
|
+
validatorStake: withdrawAccount.stakeAddress,
|
|
2042
|
+
destinationStake: stakeToReceive,
|
|
2043
|
+
destinationStakeAuthority: tokenOwner,
|
|
2044
|
+
sourceTransferAuthority: userTransferAuthority.publicKey,
|
|
2045
|
+
sourcePoolAccount: poolTokenAccount,
|
|
2046
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount,
|
|
2047
|
+
poolMint: stakePool.account.data.poolMint,
|
|
2048
|
+
poolTokens: withdrawAccount.poolAmount.toNumber(),
|
|
2049
|
+
withdrawAuthority,
|
|
2050
|
+
}));
|
|
2051
|
+
i++;
|
|
2052
|
+
}
|
|
2053
|
+
if (stakeReceiver
|
|
2054
|
+
&& stakeReceiverAccount
|
|
2055
|
+
&& stakeReceiverAccount.type === 'delegated') {
|
|
2056
|
+
signers.forEach((newStakeKeypair) => {
|
|
2057
|
+
instructions.concat(StakeProgram.merge({
|
|
2058
|
+
stakePubkey: stakeReceiver,
|
|
2059
|
+
sourceStakePubKey: newStakeKeypair.publicKey,
|
|
2060
|
+
authorizedPubkey: tokenOwner,
|
|
2061
|
+
}).instructions);
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
return {
|
|
2065
|
+
instructions,
|
|
2066
|
+
signers,
|
|
2067
|
+
stakeReceiver,
|
|
2068
|
+
totalRentFreeBalances,
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Creates instructions required to withdraw SOL directly from a stake pool.
|
|
2073
|
+
*/
|
|
2074
|
+
async function withdrawSol(connection, stakePoolAddress, tokenOwner, solReceiver, amount, solWithdrawAuthority) {
|
|
2075
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
|
|
2076
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2077
|
+
const poolAmount = solToLamports(amount);
|
|
2078
|
+
const poolTokenAccount = getAssociatedTokenAddressSync(stakePool.account.data.poolMint, tokenOwner);
|
|
2079
|
+
const tokenAccount = await getAccount(connection, poolTokenAccount);
|
|
2080
|
+
// Check withdrawFrom balance
|
|
2081
|
+
if (tokenAccount.amount < poolAmount) {
|
|
2082
|
+
throw new Error(`Not enough token balance to withdraw ${lamportsToSol(poolAmount)} pool tokens.
|
|
2083
|
+
Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`);
|
|
2084
|
+
}
|
|
2085
|
+
// Construct transaction to withdraw from withdrawAccounts account list
|
|
2086
|
+
const instructions = [];
|
|
2087
|
+
const userTransferAuthority = Keypair.generate();
|
|
2088
|
+
const signers = [userTransferAuthority];
|
|
2089
|
+
instructions.push(createApproveInstruction(poolTokenAccount, userTransferAuthority.publicKey, tokenOwner, poolAmount));
|
|
2090
|
+
const poolWithdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2091
|
+
if (solWithdrawAuthority) {
|
|
2092
|
+
const expectedSolWithdrawAuthority = stakePool.account.data.solWithdrawAuthority;
|
|
2093
|
+
if (!expectedSolWithdrawAuthority) {
|
|
2094
|
+
throw new Error('SOL withdraw authority specified in arguments but stake pool has none');
|
|
2095
|
+
}
|
|
2096
|
+
if (solWithdrawAuthority.toBase58() !== expectedSolWithdrawAuthority.toBase58()) {
|
|
2097
|
+
throw new Error(`Invalid deposit withdraw specified, expected ${expectedSolWithdrawAuthority.toBase58()}, received ${solWithdrawAuthority.toBase58()}`);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
const withdrawTransaction = StakePoolInstruction.withdrawSol({
|
|
2101
|
+
programId: stakePoolProgramId,
|
|
2102
|
+
stakePool: stakePoolAddress,
|
|
2103
|
+
withdrawAuthority: poolWithdrawAuthority,
|
|
2104
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
2105
|
+
sourcePoolAccount: poolTokenAccount,
|
|
2106
|
+
sourceTransferAuthority: userTransferAuthority.publicKey,
|
|
2107
|
+
destinationSystemAccount: solReceiver,
|
|
2108
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount,
|
|
2109
|
+
poolMint: stakePool.account.data.poolMint,
|
|
2110
|
+
poolTokens: poolAmount,
|
|
2111
|
+
solWithdrawAuthority,
|
|
2112
|
+
});
|
|
2113
|
+
instructions.push(withdrawTransaction);
|
|
2114
|
+
return {
|
|
2115
|
+
instructions,
|
|
2116
|
+
signers,
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Creates instructions required to withdraw wSOL from a stake pool.
|
|
2121
|
+
*/
|
|
2122
|
+
async function withdrawWsolWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, solWithdrawAuthority) {
|
|
2123
|
+
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
|
|
2124
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2125
|
+
const stakePool = stakePoolAccount.account.data;
|
|
2126
|
+
const poolTokens = solToLamports(amount);
|
|
2127
|
+
const poolTokenAccount = getAssociatedTokenAddressSync(stakePool.poolMint, userPubkey);
|
|
2128
|
+
const tokenAccount = await getAccount(connection, poolTokenAccount);
|
|
2129
|
+
if (tokenAccount.amount < poolTokens) {
|
|
2130
|
+
throw new Error(`Not enough token balance to withdraw ${amount} pool tokens.
|
|
2131
|
+
Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`);
|
|
2132
|
+
}
|
|
2133
|
+
const userWsolAccount = getAssociatedTokenAddressSync(NATIVE_MINT, userPubkey);
|
|
2134
|
+
const instructions = [];
|
|
2135
|
+
const signers = [];
|
|
2136
|
+
instructions.push(createAssociatedTokenAccountIdempotentInstruction(signerOrSession, userWsolAccount, userPubkey, NATIVE_MINT));
|
|
2137
|
+
const [programSigner] = PublicKey.findProgramAddressSync([Buffer.from('fogo_session_program_signer')], stakePoolProgramId);
|
|
2138
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2139
|
+
instructions.push(StakePoolInstruction.withdrawWsolWithSession({
|
|
2140
|
+
programId: stakePoolProgramId,
|
|
2141
|
+
stakePool: stakePoolAddress,
|
|
2142
|
+
withdrawAuthority,
|
|
2143
|
+
userTransferAuthority: signerOrSession,
|
|
2144
|
+
poolTokensFrom: poolTokenAccount,
|
|
2145
|
+
reserveStake: stakePool.reserveStake,
|
|
2146
|
+
userWsolAccount,
|
|
2147
|
+
managerFeeAccount: stakePool.managerFeeAccount,
|
|
2148
|
+
poolMint: stakePool.poolMint,
|
|
2149
|
+
tokenProgramId: stakePool.tokenProgramId,
|
|
2150
|
+
solWithdrawAuthority,
|
|
2151
|
+
wsolMint: NATIVE_MINT,
|
|
2152
|
+
programSigner,
|
|
2153
|
+
poolTokens,
|
|
2154
|
+
}));
|
|
2155
|
+
return {
|
|
2156
|
+
instructions,
|
|
2157
|
+
signers,
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
async function addValidatorToPool(connection, stakePoolAddress, validatorVote, seed) {
|
|
2161
|
+
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
|
|
2162
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2163
|
+
const stakePool = stakePoolAccount.account.data;
|
|
2164
|
+
const { reserveStake, staker, validatorList } = stakePool;
|
|
2165
|
+
const validatorListAccount = await getValidatorListAccount(connection, validatorList);
|
|
2166
|
+
const validatorInfo = validatorListAccount.account.data.validators.find(v => v.voteAccountAddress.toBase58() === validatorVote.toBase58());
|
|
2167
|
+
if (validatorInfo) {
|
|
2168
|
+
throw new Error('Vote account is already in validator list');
|
|
2169
|
+
}
|
|
2170
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2171
|
+
const validatorStake = await findStakeProgramAddress(stakePoolProgramId, validatorVote, stakePoolAddress, seed);
|
|
2172
|
+
const instructions = [
|
|
2173
|
+
StakePoolInstruction.addValidatorToPool({
|
|
2174
|
+
programId: stakePoolProgramId,
|
|
2175
|
+
stakePool: stakePoolAddress,
|
|
2176
|
+
staker,
|
|
2177
|
+
reserveStake,
|
|
2178
|
+
withdrawAuthority,
|
|
2179
|
+
validatorList,
|
|
2180
|
+
validatorStake,
|
|
2181
|
+
validatorVote,
|
|
2182
|
+
}),
|
|
2183
|
+
];
|
|
2184
|
+
return {
|
|
2185
|
+
instructions,
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
async function removeValidatorFromPool(connection, stakePoolAddress, validatorVote, seed) {
|
|
2189
|
+
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
|
|
2190
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2191
|
+
const stakePool = stakePoolAccount.account.data;
|
|
2192
|
+
const { staker, validatorList } = stakePool;
|
|
2193
|
+
const validatorListAccount = await getValidatorListAccount(connection, validatorList);
|
|
2194
|
+
const validatorInfo = validatorListAccount.account.data.validators.find(v => v.voteAccountAddress.toBase58() === validatorVote.toBase58());
|
|
2195
|
+
if (!validatorInfo) {
|
|
2196
|
+
throw new Error('Vote account is not already in validator list');
|
|
2197
|
+
}
|
|
2198
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2199
|
+
const validatorStake = await findStakeProgramAddress(stakePoolProgramId, validatorVote, stakePoolAddress, seed);
|
|
2200
|
+
const transientStakeSeed = validatorInfo.transientSeedSuffixStart;
|
|
2201
|
+
const transientStake = await findTransientStakeProgramAddress(stakePoolProgramId, validatorInfo.voteAccountAddress, stakePoolAddress, transientStakeSeed);
|
|
2202
|
+
const instructions = [
|
|
2203
|
+
StakePoolInstruction.removeValidatorFromPool({
|
|
2204
|
+
programId: stakePoolProgramId,
|
|
2205
|
+
stakePool: stakePoolAddress,
|
|
2206
|
+
staker,
|
|
2207
|
+
withdrawAuthority,
|
|
2208
|
+
validatorList,
|
|
2209
|
+
validatorStake,
|
|
2210
|
+
transientStake,
|
|
2211
|
+
}),
|
|
2212
|
+
];
|
|
2213
|
+
return {
|
|
2214
|
+
instructions,
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Creates instructions required to increase validator stake.
|
|
2219
|
+
*/
|
|
2220
|
+
async function increaseValidatorStake(connection, stakePoolAddress, validatorVote, lamports, ephemeralStakeSeed) {
|
|
2221
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
|
|
2222
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2223
|
+
const validatorList = await getValidatorListAccount(connection, stakePool.account.data.validatorList);
|
|
2224
|
+
const validatorInfo = validatorList.account.data.validators.find(v => v.voteAccountAddress.toBase58() === validatorVote.toBase58());
|
|
2225
|
+
if (!validatorInfo) {
|
|
2226
|
+
throw new Error('Vote account not found in validator list');
|
|
2227
|
+
}
|
|
2228
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2229
|
+
// Bump transient seed suffix by one to avoid reuse when not using the increaseAdditionalStake instruction
|
|
2230
|
+
const transientStakeSeed = ephemeralStakeSeed === undefined
|
|
2231
|
+
? validatorInfo.transientSeedSuffixStart.addn(1)
|
|
2232
|
+
: validatorInfo.transientSeedSuffixStart;
|
|
2233
|
+
const transientStake = await findTransientStakeProgramAddress(stakePoolProgramId, validatorInfo.voteAccountAddress, stakePoolAddress, transientStakeSeed);
|
|
2234
|
+
const validatorStake = await findStakeProgramAddress(stakePoolProgramId, validatorInfo.voteAccountAddress, stakePoolAddress);
|
|
2235
|
+
const instructions = [];
|
|
2236
|
+
if (ephemeralStakeSeed !== undefined) {
|
|
2237
|
+
const ephemeralStake = await findEphemeralStakeProgramAddress(stakePoolProgramId, stakePoolAddress, new BN(ephemeralStakeSeed));
|
|
2238
|
+
instructions.push(StakePoolInstruction.increaseAdditionalValidatorStake({
|
|
2239
|
+
programId: stakePoolProgramId,
|
|
2240
|
+
stakePool: stakePoolAddress,
|
|
2241
|
+
staker: stakePool.account.data.staker,
|
|
2242
|
+
validatorList: stakePool.account.data.validatorList,
|
|
2243
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
2244
|
+
transientStakeSeed: transientStakeSeed.toNumber(),
|
|
2245
|
+
withdrawAuthority,
|
|
2246
|
+
transientStake,
|
|
2247
|
+
validatorStake,
|
|
2248
|
+
validatorVote,
|
|
2249
|
+
lamports,
|
|
2250
|
+
ephemeralStake,
|
|
2251
|
+
ephemeralStakeSeed,
|
|
2252
|
+
}));
|
|
2253
|
+
}
|
|
2254
|
+
else {
|
|
2255
|
+
instructions.push(StakePoolInstruction.increaseValidatorStake({
|
|
2256
|
+
programId: stakePoolProgramId,
|
|
2257
|
+
stakePool: stakePoolAddress,
|
|
2258
|
+
staker: stakePool.account.data.staker,
|
|
2259
|
+
validatorList: stakePool.account.data.validatorList,
|
|
2260
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
2261
|
+
transientStakeSeed: transientStakeSeed.toNumber(),
|
|
2262
|
+
withdrawAuthority,
|
|
2263
|
+
transientStake,
|
|
2264
|
+
validatorStake,
|
|
2265
|
+
validatorVote,
|
|
2266
|
+
lamports,
|
|
2267
|
+
}));
|
|
2268
|
+
}
|
|
2269
|
+
return {
|
|
2270
|
+
instructions,
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Creates instructions required to decrease validator stake.
|
|
2275
|
+
*/
|
|
2276
|
+
async function decreaseValidatorStake(connection, stakePoolAddress, validatorVote, lamports, ephemeralStakeSeed) {
|
|
2277
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
|
|
2278
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2279
|
+
const validatorList = await getValidatorListAccount(connection, stakePool.account.data.validatorList);
|
|
2280
|
+
const validatorInfo = validatorList.account.data.validators.find(v => v.voteAccountAddress.toBase58() === validatorVote.toBase58());
|
|
2281
|
+
if (!validatorInfo) {
|
|
2282
|
+
throw new Error('Vote account not found in validator list');
|
|
2283
|
+
}
|
|
2284
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2285
|
+
const validatorStake = await findStakeProgramAddress(stakePoolProgramId, validatorInfo.voteAccountAddress, stakePoolAddress);
|
|
2286
|
+
// Bump transient seed suffix by one to avoid reuse when not using the decreaseAdditionalStake instruction
|
|
2287
|
+
const transientStakeSeed = ephemeralStakeSeed === undefined
|
|
2288
|
+
? validatorInfo.transientSeedSuffixStart.addn(1)
|
|
2289
|
+
: validatorInfo.transientSeedSuffixStart;
|
|
2290
|
+
const transientStake = await findTransientStakeProgramAddress(stakePoolProgramId, validatorInfo.voteAccountAddress, stakePoolAddress, transientStakeSeed);
|
|
2291
|
+
const instructions = [];
|
|
2292
|
+
if (ephemeralStakeSeed !== undefined) {
|
|
2293
|
+
const ephemeralStake = await findEphemeralStakeProgramAddress(stakePoolProgramId, stakePoolAddress, new BN(ephemeralStakeSeed));
|
|
2294
|
+
instructions.push(StakePoolInstruction.decreaseAdditionalValidatorStake({
|
|
2295
|
+
programId: stakePoolProgramId,
|
|
2296
|
+
stakePool: stakePoolAddress,
|
|
2297
|
+
staker: stakePool.account.data.staker,
|
|
2298
|
+
validatorList: stakePool.account.data.validatorList,
|
|
2299
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
2300
|
+
transientStakeSeed: transientStakeSeed.toNumber(),
|
|
2301
|
+
withdrawAuthority,
|
|
2302
|
+
validatorStake,
|
|
2303
|
+
transientStake,
|
|
2304
|
+
lamports,
|
|
2305
|
+
ephemeralStake,
|
|
2306
|
+
ephemeralStakeSeed,
|
|
2307
|
+
}));
|
|
2308
|
+
}
|
|
2309
|
+
else {
|
|
2310
|
+
instructions.push(StakePoolInstruction.decreaseValidatorStakeWithReserve({
|
|
2311
|
+
programId: stakePoolProgramId,
|
|
2312
|
+
stakePool: stakePoolAddress,
|
|
2313
|
+
staker: stakePool.account.data.staker,
|
|
2314
|
+
validatorList: stakePool.account.data.validatorList,
|
|
2315
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
2316
|
+
transientStakeSeed: transientStakeSeed.toNumber(),
|
|
2317
|
+
withdrawAuthority,
|
|
2318
|
+
validatorStake,
|
|
2319
|
+
transientStake,
|
|
2320
|
+
lamports,
|
|
2321
|
+
}));
|
|
2322
|
+
}
|
|
2323
|
+
return {
|
|
2324
|
+
instructions,
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Creates instructions required to completely update a stake pool after epoch change.
|
|
2329
|
+
*/
|
|
2330
|
+
async function updateStakePool(connection, stakePool, noMerge = false) {
|
|
2331
|
+
const stakePoolAddress = stakePool.pubkey;
|
|
2332
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2333
|
+
const validatorList = await getValidatorListAccount(connection, stakePool.account.data.validatorList);
|
|
2334
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2335
|
+
const updateListInstructions = [];
|
|
2336
|
+
const instructions = [];
|
|
2337
|
+
let startIndex = 0;
|
|
2338
|
+
const validatorChunks = arrayChunk(validatorList.account.data.validators, MAX_VALIDATORS_TO_UPDATE);
|
|
2339
|
+
for (const validatorChunk of validatorChunks) {
|
|
2340
|
+
const validatorAndTransientStakePairs = [];
|
|
2341
|
+
for (const validator of validatorChunk) {
|
|
2342
|
+
const validatorStake = await findStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress);
|
|
2343
|
+
validatorAndTransientStakePairs.push(validatorStake);
|
|
2344
|
+
const transientStake = await findTransientStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress, validator.transientSeedSuffixStart);
|
|
2345
|
+
validatorAndTransientStakePairs.push(transientStake);
|
|
2346
|
+
}
|
|
2347
|
+
updateListInstructions.push(StakePoolInstruction.updateValidatorListBalance({
|
|
2348
|
+
programId: stakePoolProgramId,
|
|
2349
|
+
stakePool: stakePoolAddress,
|
|
2350
|
+
validatorList: stakePool.account.data.validatorList,
|
|
2351
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
2352
|
+
validatorAndTransientStakePairs,
|
|
2353
|
+
withdrawAuthority,
|
|
2354
|
+
startIndex,
|
|
2355
|
+
noMerge,
|
|
2356
|
+
}));
|
|
2357
|
+
startIndex += MAX_VALIDATORS_TO_UPDATE;
|
|
2358
|
+
}
|
|
2359
|
+
instructions.push(StakePoolInstruction.updateStakePoolBalance({
|
|
2360
|
+
programId: stakePoolProgramId,
|
|
2361
|
+
stakePool: stakePoolAddress,
|
|
2362
|
+
validatorList: stakePool.account.data.validatorList,
|
|
2363
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
2364
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount,
|
|
2365
|
+
poolMint: stakePool.account.data.poolMint,
|
|
2366
|
+
withdrawAuthority,
|
|
2367
|
+
}));
|
|
2368
|
+
instructions.push(StakePoolInstruction.cleanupRemovedValidatorEntries({
|
|
2369
|
+
programId: stakePoolProgramId,
|
|
2370
|
+
stakePool: stakePoolAddress,
|
|
2371
|
+
validatorList: stakePool.account.data.validatorList,
|
|
2372
|
+
}));
|
|
2373
|
+
return {
|
|
2374
|
+
updateListInstructions,
|
|
2375
|
+
finalInstructions: instructions,
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Retrieves detailed information about the StakePool.
|
|
2380
|
+
*/
|
|
2381
|
+
async function stakePoolInfo(connection, stakePoolAddress) {
|
|
2382
|
+
var _c, _d;
|
|
2383
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
|
|
2384
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2385
|
+
const reserveAccountStakeAddress = stakePool.account.data.reserveStake;
|
|
2386
|
+
const totalLamports = stakePool.account.data.totalLamports;
|
|
2387
|
+
const lastUpdateEpoch = stakePool.account.data.lastUpdateEpoch;
|
|
2388
|
+
const validatorList = await getValidatorListAccount(connection, stakePool.account.data.validatorList);
|
|
2389
|
+
const maxNumberOfValidators = validatorList.account.data.maxValidators;
|
|
2390
|
+
const currentNumberOfValidators = validatorList.account.data.validators.length;
|
|
2391
|
+
const epochInfo = await connection.getEpochInfo();
|
|
2392
|
+
const reserveStake = await connection.getAccountInfo(reserveAccountStakeAddress);
|
|
2393
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2394
|
+
const minimumReserveStakeBalance = await connection.getMinimumBalanceForRentExemption(StakeProgram.space);
|
|
2395
|
+
const stakeAccounts = await Promise.all(validatorList.account.data.validators.map(async (validator) => {
|
|
2396
|
+
const stakeAccountAddress = await findStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress);
|
|
2397
|
+
const transientStakeAccountAddress = await findTransientStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress, validator.transientSeedSuffixStart);
|
|
2398
|
+
const updateRequired = !validator.lastUpdateEpoch.eqn(epochInfo.epoch);
|
|
2399
|
+
return {
|
|
2400
|
+
voteAccountAddress: validator.voteAccountAddress.toBase58(),
|
|
2401
|
+
stakeAccountAddress: stakeAccountAddress.toBase58(),
|
|
2402
|
+
validatorActiveStakeLamports: validator.activeStakeLamports.toString(),
|
|
2403
|
+
validatorLastUpdateEpoch: validator.lastUpdateEpoch.toString(),
|
|
2404
|
+
validatorLamports: validator.activeStakeLamports
|
|
2405
|
+
.add(validator.transientStakeLamports)
|
|
2406
|
+
.toString(),
|
|
2407
|
+
validatorTransientStakeAccountAddress: transientStakeAccountAddress.toBase58(),
|
|
2408
|
+
validatorTransientStakeLamports: validator.transientStakeLamports.toString(),
|
|
2409
|
+
updateRequired,
|
|
2410
|
+
};
|
|
2411
|
+
}));
|
|
2412
|
+
const totalPoolTokens = lamportsToSol(stakePool.account.data.poolTokenSupply);
|
|
2413
|
+
const updateRequired = !lastUpdateEpoch.eqn(epochInfo.epoch);
|
|
2414
|
+
return {
|
|
2415
|
+
address: stakePoolAddress.toBase58(),
|
|
2416
|
+
poolWithdrawAuthority: withdrawAuthority.toBase58(),
|
|
2417
|
+
manager: stakePool.account.data.manager.toBase58(),
|
|
2418
|
+
staker: stakePool.account.data.staker.toBase58(),
|
|
2419
|
+
stakeDepositAuthority: stakePool.account.data.stakeDepositAuthority.toBase58(),
|
|
2420
|
+
stakeWithdrawBumpSeed: stakePool.account.data.stakeWithdrawBumpSeed,
|
|
2421
|
+
maxValidators: maxNumberOfValidators,
|
|
2422
|
+
validatorList: validatorList.account.data.validators.map((validator) => {
|
|
2423
|
+
return {
|
|
2424
|
+
activeStakeLamports: validator.activeStakeLamports.toString(),
|
|
2425
|
+
transientStakeLamports: validator.transientStakeLamports.toString(),
|
|
2426
|
+
lastUpdateEpoch: validator.lastUpdateEpoch.toString(),
|
|
2427
|
+
transientSeedSuffixStart: validator.transientSeedSuffixStart.toString(),
|
|
2428
|
+
transientSeedSuffixEnd: validator.transientSeedSuffixEnd.toString(),
|
|
2429
|
+
status: validator.status.toString(),
|
|
2430
|
+
voteAccountAddress: validator.voteAccountAddress.toString(),
|
|
2431
|
+
};
|
|
2432
|
+
}), // CliStakePoolValidator
|
|
2433
|
+
validatorListStorageAccount: stakePool.account.data.validatorList.toBase58(),
|
|
2434
|
+
reserveStake: stakePool.account.data.reserveStake.toBase58(),
|
|
2435
|
+
poolMint: stakePool.account.data.poolMint.toBase58(),
|
|
2436
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount.toBase58(),
|
|
2437
|
+
tokenProgramId: stakePool.account.data.tokenProgramId.toBase58(),
|
|
2438
|
+
totalLamports: stakePool.account.data.totalLamports.toString(),
|
|
2439
|
+
poolTokenSupply: stakePool.account.data.poolTokenSupply.toString(),
|
|
2440
|
+
lastUpdateEpoch: stakePool.account.data.lastUpdateEpoch.toString(),
|
|
2441
|
+
lockup: stakePool.account.data.lockup, // pub lockup: CliStakePoolLockup
|
|
2442
|
+
epochFee: stakePool.account.data.epochFee,
|
|
2443
|
+
nextEpochFee: stakePool.account.data.nextEpochFee,
|
|
2444
|
+
preferredDepositValidatorVoteAddress: stakePool.account.data.preferredDepositValidatorVoteAddress,
|
|
2445
|
+
preferredWithdrawValidatorVoteAddress: stakePool.account.data.preferredWithdrawValidatorVoteAddress,
|
|
2446
|
+
stakeDepositFee: stakePool.account.data.stakeDepositFee,
|
|
2447
|
+
stakeWithdrawalFee: stakePool.account.data.stakeWithdrawalFee,
|
|
2448
|
+
// CliStakePool the same
|
|
2449
|
+
nextStakeWithdrawalFee: stakePool.account.data.nextStakeWithdrawalFee,
|
|
2450
|
+
stakeReferralFee: stakePool.account.data.stakeReferralFee,
|
|
2451
|
+
solDepositAuthority: (_c = stakePool.account.data.solDepositAuthority) === null || _c === void 0 ? void 0 : _c.toBase58(),
|
|
2452
|
+
solDepositFee: stakePool.account.data.solDepositFee,
|
|
2453
|
+
solReferralFee: stakePool.account.data.solReferralFee,
|
|
2454
|
+
solWithdrawAuthority: (_d = stakePool.account.data.solWithdrawAuthority) === null || _d === void 0 ? void 0 : _d.toBase58(),
|
|
2455
|
+
solWithdrawalFee: stakePool.account.data.solWithdrawalFee,
|
|
2456
|
+
nextSolWithdrawalFee: stakePool.account.data.nextSolWithdrawalFee,
|
|
2457
|
+
lastEpochPoolTokenSupply: stakePool.account.data.lastEpochPoolTokenSupply.toString(),
|
|
2458
|
+
lastEpochTotalLamports: stakePool.account.data.lastEpochTotalLamports.toString(),
|
|
2459
|
+
details: {
|
|
2460
|
+
reserveStakeLamports: reserveStake === null || reserveStake === void 0 ? void 0 : reserveStake.lamports,
|
|
2461
|
+
reserveAccountStakeAddress: reserveAccountStakeAddress.toBase58(),
|
|
2462
|
+
minimumReserveStakeBalance,
|
|
2463
|
+
stakeAccounts,
|
|
2464
|
+
totalLamports,
|
|
2465
|
+
totalPoolTokens,
|
|
2466
|
+
currentNumberOfValidators,
|
|
2467
|
+
maxNumberOfValidators,
|
|
2468
|
+
updateRequired,
|
|
2469
|
+
}, // CliStakePoolDetails
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
/**
|
|
2473
|
+
* Creates instructions required to create pool token metadata.
|
|
2474
|
+
*/
|
|
2475
|
+
async function createPoolTokenMetadata(connection, stakePoolAddress, payer, name, symbol, uri) {
|
|
2476
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
|
|
2477
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2478
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2479
|
+
const tokenMetadata = findMetadataAddress(stakePool.account.data.poolMint);
|
|
2480
|
+
const manager = stakePool.account.data.manager;
|
|
2481
|
+
const instructions = [];
|
|
2482
|
+
instructions.push(StakePoolInstruction.createTokenMetadata({
|
|
2483
|
+
programId: stakePoolProgramId,
|
|
2484
|
+
stakePool: stakePoolAddress,
|
|
2485
|
+
poolMint: stakePool.account.data.poolMint,
|
|
2486
|
+
payer,
|
|
2487
|
+
manager,
|
|
2488
|
+
tokenMetadata,
|
|
2489
|
+
withdrawAuthority,
|
|
2490
|
+
name,
|
|
2491
|
+
symbol,
|
|
2492
|
+
uri,
|
|
2493
|
+
}));
|
|
2494
|
+
return {
|
|
2495
|
+
instructions,
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Creates instructions required to update pool token metadata.
|
|
2500
|
+
*/
|
|
2501
|
+
async function updatePoolTokenMetadata(connection, stakePoolAddress, name, symbol, uri) {
|
|
2502
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
|
|
2503
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
|
|
2504
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
|
|
2505
|
+
const tokenMetadata = findMetadataAddress(stakePool.account.data.poolMint);
|
|
2506
|
+
const instructions = [];
|
|
2507
|
+
instructions.push(StakePoolInstruction.updateTokenMetadata({
|
|
2508
|
+
programId: stakePoolProgramId,
|
|
2509
|
+
stakePool: stakePoolAddress,
|
|
2510
|
+
manager: stakePool.account.data.manager,
|
|
2511
|
+
tokenMetadata,
|
|
2512
|
+
withdrawAuthority,
|
|
2513
|
+
name,
|
|
2514
|
+
symbol,
|
|
2515
|
+
uri,
|
|
2516
|
+
}));
|
|
2517
|
+
return {
|
|
2518
|
+
instructions,
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
export { DEVNET_STAKE_POOL_PROGRAM_ID, STAKE_POOL_INSTRUCTION_LAYOUTS, STAKE_POOL_PROGRAM_ID, StakePoolInstruction, StakePoolLayout, ValidatorListLayout, ValidatorStakeInfoLayout, addValidatorToPool, createPoolTokenMetadata, decreaseValidatorStake, depositSol, depositStake, depositWsolWithSession, getStakeAccount, getStakePoolAccount, getStakePoolAccounts, getStakePoolProgramId, increaseValidatorStake, removeValidatorFromPool, stakePoolInfo, tokenMetadataLayout, updatePoolTokenMetadata, updateStakePool, withdrawSol, withdrawStake, withdrawWsolWithSession };
|
|
2523
|
+
//# sourceMappingURL=index.esm.js.map
|