@solana/sysvars 6.3.1 → 6.3.2-canary-20260313112147
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -8
- package/src/clock.ts +80 -0
- package/src/epoch-rewards.ts +117 -0
- package/src/epoch-schedule.ts +97 -0
- package/src/index.ts +64 -0
- package/src/last-restart-slot.ts +73 -0
- package/src/recent-blockhashes.ts +108 -0
- package/src/rent.ts +77 -0
- package/src/slot-hashes.ts +71 -0
- package/src/slot-history.ts +198 -0
- package/src/stake-history.ts +102 -0
- package/src/sysvar.ts +72 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solana/sysvars",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.2-canary-20260313112147",
|
|
4
4
|
"description": "An abstraction layer over signing messages and transactions in Solana",
|
|
5
5
|
"homepage": "https://www.solanakit.com/api#solanasysvars",
|
|
6
6
|
"exports": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"types": "./dist/types/index.d.ts",
|
|
34
34
|
"type": "commonjs",
|
|
35
35
|
"files": [
|
|
36
|
-
"./dist/"
|
|
36
|
+
"./dist/",
|
|
37
|
+
"./src/"
|
|
37
38
|
],
|
|
38
39
|
"sideEffects": false,
|
|
39
40
|
"keywords": [
|
|
@@ -55,12 +56,12 @@
|
|
|
55
56
|
"maintained node versions"
|
|
56
57
|
],
|
|
57
58
|
"dependencies": {
|
|
58
|
-
"@solana/accounts": "6.3.
|
|
59
|
-
"@solana/codecs-
|
|
60
|
-
"@solana/codecs-
|
|
61
|
-
"@solana/codecs-numbers": "6.3.
|
|
62
|
-
"@solana/errors": "6.3.
|
|
63
|
-
"@solana/rpc-types": "6.3.
|
|
59
|
+
"@solana/accounts": "6.3.2-canary-20260313112147",
|
|
60
|
+
"@solana/codecs-core": "6.3.2-canary-20260313112147",
|
|
61
|
+
"@solana/codecs-data-structures": "6.3.2-canary-20260313112147",
|
|
62
|
+
"@solana/codecs-numbers": "6.3.2-canary-20260313112147",
|
|
63
|
+
"@solana/errors": "6.3.2-canary-20260313112147",
|
|
64
|
+
"@solana/rpc-types": "6.3.2-canary-20260313112147"
|
|
64
65
|
},
|
|
65
66
|
"peerDependencies": {
|
|
66
67
|
"typescript": "^5.0.0"
|
package/src/clock.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
|
|
2
|
+
import { combineCodec, type FixedSizeCodec, type FixedSizeDecoder, type FixedSizeEncoder } from '@solana/codecs-core';
|
|
3
|
+
import { getStructDecoder, getStructEncoder } from '@solana/codecs-data-structures';
|
|
4
|
+
import { getI64Decoder, getI64Encoder, getU64Decoder, getU64Encoder } from '@solana/codecs-numbers';
|
|
5
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
6
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
7
|
+
import type { Epoch, Slot, UnixTimestamp } from '@solana/rpc-types';
|
|
8
|
+
|
|
9
|
+
import { fetchEncodedSysvarAccount, SYSVAR_CLOCK_ADDRESS } from './sysvar';
|
|
10
|
+
|
|
11
|
+
type SysvarClockSize = 40;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Contains data on cluster time, including the current slot, epoch, and estimated wall-clock Unix
|
|
15
|
+
* timestamp. It is updated every slot.
|
|
16
|
+
*/
|
|
17
|
+
export type SysvarClock = Readonly<{
|
|
18
|
+
/** The current epoch */
|
|
19
|
+
epoch: Epoch;
|
|
20
|
+
/**
|
|
21
|
+
* The Unix timestamp of the first slot in this epoch.
|
|
22
|
+
*
|
|
23
|
+
* In the first slot of an epoch, this timestamp is identical to the `unixTimestamp`.
|
|
24
|
+
*/
|
|
25
|
+
epochStartTimestamp: UnixTimestamp;
|
|
26
|
+
/** The most recent epoch for which the leader schedule has already been generated */
|
|
27
|
+
leaderScheduleEpoch: Epoch;
|
|
28
|
+
/** The current slot */
|
|
29
|
+
slot: Slot;
|
|
30
|
+
/** The Unix timestamp of this slot */
|
|
31
|
+
unixTimestamp: UnixTimestamp;
|
|
32
|
+
}>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns an encoder that you can use to encode a {@link SysvarClock} to a byte array representing
|
|
36
|
+
* the `Clock` sysvar's account data.
|
|
37
|
+
*/
|
|
38
|
+
export function getSysvarClockEncoder(): FixedSizeEncoder<SysvarClock, SysvarClockSize> {
|
|
39
|
+
return getStructEncoder([
|
|
40
|
+
['slot', getU64Encoder()],
|
|
41
|
+
['epochStartTimestamp', getI64Encoder()],
|
|
42
|
+
['epoch', getU64Encoder()],
|
|
43
|
+
['leaderScheduleEpoch', getU64Encoder()],
|
|
44
|
+
['unixTimestamp', getI64Encoder()],
|
|
45
|
+
]) as FixedSizeEncoder<SysvarClock, SysvarClockSize>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns a decoder that you can use to decode a byte array representing the `Clock` sysvar's
|
|
50
|
+
* account data to a {@link SysvarClock}.
|
|
51
|
+
*/
|
|
52
|
+
export function getSysvarClockDecoder(): FixedSizeDecoder<SysvarClock, SysvarClockSize> {
|
|
53
|
+
return getStructDecoder([
|
|
54
|
+
['slot', getU64Decoder()],
|
|
55
|
+
['epochStartTimestamp', getI64Decoder()],
|
|
56
|
+
['epoch', getU64Decoder()],
|
|
57
|
+
['leaderScheduleEpoch', getU64Decoder()],
|
|
58
|
+
['unixTimestamp', getI64Decoder()],
|
|
59
|
+
]) as FixedSizeDecoder<SysvarClock, SysvarClockSize>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns a codec that you can use to encode from or decode to {@link SysvarClock}
|
|
64
|
+
*
|
|
65
|
+
* @see {@link getSysvarClockDecoder}
|
|
66
|
+
* @see {@link getSysvarClockEncoder}
|
|
67
|
+
*/
|
|
68
|
+
export function getSysvarClockCodec(): FixedSizeCodec<SysvarClock, SysvarClock, SysvarClockSize> {
|
|
69
|
+
return combineCodec(getSysvarClockEncoder(), getSysvarClockDecoder());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Fetches the `Clock` sysvar account using any RPC that supports the {@link GetAccountInfoApi}.
|
|
74
|
+
*/
|
|
75
|
+
export async function fetchSysvarClock(rpc: Rpc<GetAccountInfoApi>, config?: FetchAccountConfig): Promise<SysvarClock> {
|
|
76
|
+
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS, config);
|
|
77
|
+
assertAccountExists(account);
|
|
78
|
+
const decoded = decodeAccount(account, getSysvarClockDecoder());
|
|
79
|
+
return decoded.data;
|
|
80
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
|
|
2
|
+
import { combineCodec, type FixedSizeCodec, type FixedSizeDecoder, type FixedSizeEncoder } from '@solana/codecs-core';
|
|
3
|
+
import {
|
|
4
|
+
getBooleanDecoder,
|
|
5
|
+
getBooleanEncoder,
|
|
6
|
+
getStructDecoder,
|
|
7
|
+
getStructEncoder,
|
|
8
|
+
} from '@solana/codecs-data-structures';
|
|
9
|
+
import { getU64Decoder, getU64Encoder, getU128Decoder, getU128Encoder } from '@solana/codecs-numbers';
|
|
10
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
11
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
12
|
+
import {
|
|
13
|
+
Blockhash,
|
|
14
|
+
getBlockhashDecoder,
|
|
15
|
+
getBlockhashEncoder,
|
|
16
|
+
getDefaultLamportsDecoder,
|
|
17
|
+
getDefaultLamportsEncoder,
|
|
18
|
+
Lamports,
|
|
19
|
+
} from '@solana/rpc-types';
|
|
20
|
+
|
|
21
|
+
import { fetchEncodedSysvarAccount, SYSVAR_EPOCH_REWARDS_ADDRESS } from './sysvar';
|
|
22
|
+
|
|
23
|
+
type SysvarEpochRewardsSize = 81;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Tracks whether the rewards period (including calculation and distribution) is in progress, as
|
|
27
|
+
* well as the details needed to resume distribution when starting from a snapshot during the
|
|
28
|
+
* rewards period.
|
|
29
|
+
*
|
|
30
|
+
* The sysvar is repopulated at the start of the first block of each epoch. Therefore, the sysvar
|
|
31
|
+
* contains data about the current epoch until a new epoch begins.
|
|
32
|
+
*/
|
|
33
|
+
export type SysvarEpochRewards = Readonly<{
|
|
34
|
+
/** Whether the rewards period (including calculation and distribution) is active */
|
|
35
|
+
active: boolean;
|
|
36
|
+
/** The rewards currently distributed for the current epoch, in {@link Lamports} */
|
|
37
|
+
distributedRewards: Lamports;
|
|
38
|
+
/** The starting block height of the rewards distribution in the current epoch */
|
|
39
|
+
distributionStartingBlockHeight: bigint;
|
|
40
|
+
/**
|
|
41
|
+
* Number of partitions in the rewards distribution in the current epoch, used to generate an
|
|
42
|
+
* `EpochRewardsHasher`
|
|
43
|
+
*/
|
|
44
|
+
numPartitions: bigint;
|
|
45
|
+
/**
|
|
46
|
+
* The {@link Blockhash} of the parent block of the first block in the epoch, used to seed an
|
|
47
|
+
* `EpochRewardsHasher`
|
|
48
|
+
*/
|
|
49
|
+
parentBlockhash: Blockhash;
|
|
50
|
+
/**
|
|
51
|
+
* The total rewards points calculated for the current epoch, where points equals the sum of
|
|
52
|
+
* (delegated stake * credits observed) for all delegations
|
|
53
|
+
*/
|
|
54
|
+
totalPoints: bigint;
|
|
55
|
+
/** The total rewards for the current epoch, in {@link Lamports} */
|
|
56
|
+
totalRewards: Lamports;
|
|
57
|
+
}>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns an encoder that you can use to encode a {@link SysvarEpochRewards} to a byte array
|
|
61
|
+
* representing the `EpochRewards` sysvar's account data.
|
|
62
|
+
*/
|
|
63
|
+
export function getSysvarEpochRewardsEncoder(): FixedSizeEncoder<SysvarEpochRewards, SysvarEpochRewardsSize> {
|
|
64
|
+
return getStructEncoder([
|
|
65
|
+
['distributionStartingBlockHeight', getU64Encoder()],
|
|
66
|
+
['numPartitions', getU64Encoder()],
|
|
67
|
+
['parentBlockhash', getBlockhashEncoder()],
|
|
68
|
+
['totalPoints', getU128Encoder()],
|
|
69
|
+
['totalRewards', getDefaultLamportsEncoder()],
|
|
70
|
+
['distributedRewards', getDefaultLamportsEncoder()],
|
|
71
|
+
['active', getBooleanEncoder()],
|
|
72
|
+
]) as FixedSizeEncoder<SysvarEpochRewards, SysvarEpochRewardsSize>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns a decoder that you can use to decode a byte array representing the `EpochRewards`
|
|
77
|
+
* sysvar's account data to a {@link SysvarEpochRewards}.
|
|
78
|
+
*/
|
|
79
|
+
export function getSysvarEpochRewardsDecoder(): FixedSizeDecoder<SysvarEpochRewards, SysvarEpochRewardsSize> {
|
|
80
|
+
return getStructDecoder([
|
|
81
|
+
['distributionStartingBlockHeight', getU64Decoder()],
|
|
82
|
+
['numPartitions', getU64Decoder()],
|
|
83
|
+
['parentBlockhash', getBlockhashDecoder()],
|
|
84
|
+
['totalPoints', getU128Decoder()],
|
|
85
|
+
['totalRewards', getDefaultLamportsDecoder()],
|
|
86
|
+
['distributedRewards', getDefaultLamportsDecoder()],
|
|
87
|
+
['active', getBooleanDecoder()],
|
|
88
|
+
]) as FixedSizeDecoder<SysvarEpochRewards, SysvarEpochRewardsSize>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns a codec that you can use to encode from or decode to {@link SysvarEpochRewards}
|
|
93
|
+
*
|
|
94
|
+
* @see {@link getSysvarEpochRewardsDecoder}
|
|
95
|
+
* @see {@link getSysvarEpochRewardsEncoder}
|
|
96
|
+
*/
|
|
97
|
+
export function getSysvarEpochRewardsCodec(): FixedSizeCodec<
|
|
98
|
+
SysvarEpochRewards,
|
|
99
|
+
SysvarEpochRewards,
|
|
100
|
+
SysvarEpochRewardsSize
|
|
101
|
+
> {
|
|
102
|
+
return combineCodec(getSysvarEpochRewardsEncoder(), getSysvarEpochRewardsDecoder());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Fetch the `EpochRewards` sysvar account using any RPC that supports the
|
|
107
|
+
* {@link GetAccountInfoApi}.
|
|
108
|
+
*/
|
|
109
|
+
export async function fetchSysvarEpochRewards(
|
|
110
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
111
|
+
config?: FetchAccountConfig,
|
|
112
|
+
): Promise<SysvarEpochRewards> {
|
|
113
|
+
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_EPOCH_REWARDS_ADDRESS, config);
|
|
114
|
+
assertAccountExists(account);
|
|
115
|
+
const decoded = decodeAccount(account, getSysvarEpochRewardsDecoder());
|
|
116
|
+
return decoded.data;
|
|
117
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
|
|
2
|
+
import { combineCodec, type FixedSizeCodec, type FixedSizeDecoder, type FixedSizeEncoder } from '@solana/codecs-core';
|
|
3
|
+
import {
|
|
4
|
+
getBooleanDecoder,
|
|
5
|
+
getBooleanEncoder,
|
|
6
|
+
getStructDecoder,
|
|
7
|
+
getStructEncoder,
|
|
8
|
+
} from '@solana/codecs-data-structures';
|
|
9
|
+
import { getU64Decoder, getU64Encoder } from '@solana/codecs-numbers';
|
|
10
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
11
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
12
|
+
import type { Epoch, Slot } from '@solana/rpc-types';
|
|
13
|
+
|
|
14
|
+
import { fetchEncodedSysvarAccount, SYSVAR_EPOCH_SCHEDULE_ADDRESS } from './sysvar';
|
|
15
|
+
|
|
16
|
+
type SysvarEpochScheduleSize = 33;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Includes the number of slots per epoch, timing of leader schedule selection, and information
|
|
20
|
+
* about epoch warm-up time.
|
|
21
|
+
*/
|
|
22
|
+
export type SysvarEpochSchedule = Readonly<{
|
|
23
|
+
/**
|
|
24
|
+
* First normal-length epoch after the warmup period,
|
|
25
|
+
* log2(slotsPerEpoch) - log2(MINIMUM_SLOTS_PER_EPOCH)
|
|
26
|
+
*/
|
|
27
|
+
firstNormalEpoch: Epoch;
|
|
28
|
+
/**
|
|
29
|
+
* The first slot after the warmup period, MINIMUM_SLOTS_PER_EPOCH * (2^(firstNormalEpoch) - 1)
|
|
30
|
+
*/
|
|
31
|
+
firstNormalSlot: Slot;
|
|
32
|
+
/**
|
|
33
|
+
* A number of slots before beginning of an epoch to calculate a leader schedule for that
|
|
34
|
+
* epoch.
|
|
35
|
+
*/
|
|
36
|
+
leaderScheduleSlotOffset: bigint;
|
|
37
|
+
/** The maximum number of slots in each epoch */
|
|
38
|
+
slotsPerEpoch: bigint;
|
|
39
|
+
/** Whether epochs start short and grow */
|
|
40
|
+
warmup: boolean;
|
|
41
|
+
}>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns an encoder that you can use to encode a {@link SysvarEpochSchedule} to a byte array
|
|
45
|
+
* representing the `EpochSchedule` sysvar's account data.
|
|
46
|
+
*/
|
|
47
|
+
export function getSysvarEpochScheduleEncoder(): FixedSizeEncoder<SysvarEpochSchedule, SysvarEpochScheduleSize> {
|
|
48
|
+
return getStructEncoder([
|
|
49
|
+
['slotsPerEpoch', getU64Encoder()],
|
|
50
|
+
['leaderScheduleSlotOffset', getU64Encoder()],
|
|
51
|
+
['warmup', getBooleanEncoder()],
|
|
52
|
+
['firstNormalEpoch', getU64Encoder()],
|
|
53
|
+
['firstNormalSlot', getU64Encoder()],
|
|
54
|
+
]) as FixedSizeEncoder<SysvarEpochSchedule, SysvarEpochScheduleSize>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns a decoder that you can use to decode a byte array representing the `EpochSchedule`
|
|
59
|
+
* sysvar's account data to a {@link SysvarEpochSchedule}.
|
|
60
|
+
*/
|
|
61
|
+
export function getSysvarEpochScheduleDecoder(): FixedSizeDecoder<SysvarEpochSchedule, SysvarEpochScheduleSize> {
|
|
62
|
+
return getStructDecoder([
|
|
63
|
+
['slotsPerEpoch', getU64Decoder()],
|
|
64
|
+
['leaderScheduleSlotOffset', getU64Decoder()],
|
|
65
|
+
['warmup', getBooleanDecoder()],
|
|
66
|
+
['firstNormalEpoch', getU64Decoder()],
|
|
67
|
+
['firstNormalSlot', getU64Decoder()],
|
|
68
|
+
]) as FixedSizeDecoder<SysvarEpochSchedule, SysvarEpochScheduleSize>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns a codec that you can use to encode from or decode to {@link SysvarEpochSchedule}
|
|
73
|
+
*
|
|
74
|
+
* @see {@link getSysvarEpochScheduleDecoder}
|
|
75
|
+
* @see {@link getSysvarEpochScheduleEncoder}
|
|
76
|
+
*/
|
|
77
|
+
export function getSysvarEpochScheduleCodec(): FixedSizeCodec<
|
|
78
|
+
SysvarEpochSchedule,
|
|
79
|
+
SysvarEpochSchedule,
|
|
80
|
+
SysvarEpochScheduleSize
|
|
81
|
+
> {
|
|
82
|
+
return combineCodec(getSysvarEpochScheduleEncoder(), getSysvarEpochScheduleDecoder());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Fetches the `EpochSchedule` sysvar account using any RPC that supports the
|
|
87
|
+
* {@link GetAccountInfoApi}.
|
|
88
|
+
*/
|
|
89
|
+
export async function fetchSysvarEpochSchedule(
|
|
90
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
91
|
+
config?: FetchAccountConfig,
|
|
92
|
+
): Promise<SysvarEpochSchedule> {
|
|
93
|
+
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_EPOCH_SCHEDULE_ADDRESS, config);
|
|
94
|
+
assertAccountExists(account);
|
|
95
|
+
const decoded = decodeAccount(account, getSysvarEpochScheduleDecoder());
|
|
96
|
+
return decoded.data;
|
|
97
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This package contains types and helpers for fetching and decoding Solana sysvars.
|
|
3
|
+
* It can be used standalone, but it is also exported as part of Kit
|
|
4
|
+
* [`@solana/kit`](https://github.com/anza-xyz/kit/tree/main/packages/kit).
|
|
5
|
+
*
|
|
6
|
+
* More information about the available sysvars on Solana can be found in the docs at
|
|
7
|
+
* https://docs.solanalabs.com/runtime/sysvars.
|
|
8
|
+
*
|
|
9
|
+
* All currently available sysvars can be retrieved and/or decoded using this library.
|
|
10
|
+
*
|
|
11
|
+
* - `Clock`
|
|
12
|
+
* - `EpochRewards`
|
|
13
|
+
* - `EpochSchedule`
|
|
14
|
+
* - `Fees`
|
|
15
|
+
* - `LastRestartSlot`
|
|
16
|
+
* - `RecentBlockhashes`
|
|
17
|
+
* - `Rent`
|
|
18
|
+
* - `SlotHashes`
|
|
19
|
+
* - `SlotHistory`
|
|
20
|
+
* - `StakeHistory`
|
|
21
|
+
*
|
|
22
|
+
* The `Instructions` sysvar is also supported but does not exist on-chain, therefore has no
|
|
23
|
+
* corresponding module or codec.
|
|
24
|
+
*
|
|
25
|
+
* @example Fetch and decode a sysvar account.
|
|
26
|
+
* ```ts
|
|
27
|
+
* const clock: SysvarClock = await fetchSysvarClock(rpc);
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Fetch and decode a sysvar account manually.
|
|
31
|
+
* ```ts
|
|
32
|
+
* // Fetch.
|
|
33
|
+
* const clock = await fetchEncodedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS);
|
|
34
|
+
* clock satisfies MaybeEncodedAccount<'SysvarC1ock11111111111111111111111111111111'>;
|
|
35
|
+
*
|
|
36
|
+
* // Assert.
|
|
37
|
+
* assertAccountExists(clock);
|
|
38
|
+
* clock satisfies EncodedAccount<'SysvarC1ock11111111111111111111111111111111'>;
|
|
39
|
+
*
|
|
40
|
+
* // Decode.
|
|
41
|
+
* const decodedClock = decodeAccount(clock, getSysvarClockDecoder());
|
|
42
|
+
* decodedClock satisfies Account<SysvarClock, 'SysvarC1ock11111111111111111111111111111111'>;
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example Fetch a JSON-parsed sysvar account.
|
|
46
|
+
* ```ts
|
|
47
|
+
* const maybeJsonParsedClock = await fetchJsonParsedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS);
|
|
48
|
+
* maybeJsonParsedClock satisfies
|
|
49
|
+
* | MaybeAccount<JsonParsedSysvarAccount, 'SysvarC1ock11111111111111111111111111111111'>
|
|
50
|
+
* | MaybeEncodedAccount<'SysvarC1ock11111111111111111111111111111111'>;
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @packageDocumentation
|
|
54
|
+
*/
|
|
55
|
+
export * from './clock';
|
|
56
|
+
export * from './epoch-rewards';
|
|
57
|
+
export * from './epoch-schedule';
|
|
58
|
+
export * from './last-restart-slot';
|
|
59
|
+
export * from './recent-blockhashes';
|
|
60
|
+
export * from './rent';
|
|
61
|
+
export * from './slot-hashes';
|
|
62
|
+
export * from './slot-history';
|
|
63
|
+
export * from './stake-history';
|
|
64
|
+
export * from './sysvar';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
|
|
2
|
+
import { combineCodec, type FixedSizeCodec, type FixedSizeDecoder, type FixedSizeEncoder } from '@solana/codecs-core';
|
|
3
|
+
import { getStructDecoder, getStructEncoder } from '@solana/codecs-data-structures';
|
|
4
|
+
import { getU64Decoder, getU64Encoder } from '@solana/codecs-numbers';
|
|
5
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
6
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
7
|
+
import type { Slot } from '@solana/rpc-types';
|
|
8
|
+
|
|
9
|
+
import { fetchEncodedSysvarAccount, SYSVAR_LAST_RESTART_SLOT_ADDRESS } from './sysvar';
|
|
10
|
+
|
|
11
|
+
type SysvarLastRestartSlotSize = 8;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Information about the last restart slot (hard fork).
|
|
15
|
+
*
|
|
16
|
+
* The `LastRestartSlot` sysvar provides access to the last restart slot kept in the bank fork for
|
|
17
|
+
* the slot on the fork that executes the current transaction. In case there was no fork it returns
|
|
18
|
+
* `0`.
|
|
19
|
+
*/
|
|
20
|
+
export type SysvarLastRestartSlot = Readonly<{
|
|
21
|
+
/** The last restart {@link Slot} */
|
|
22
|
+
lastRestartSlot: Slot;
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns an encoder that you can use to encode a {@link SysvarLastRestartSlot} to a byte array
|
|
27
|
+
* representing the `LastRestartSlot` sysvar's account data.
|
|
28
|
+
*/
|
|
29
|
+
export function getSysvarLastRestartSlotEncoder(): FixedSizeEncoder<SysvarLastRestartSlot, SysvarLastRestartSlotSize> {
|
|
30
|
+
return getStructEncoder([['lastRestartSlot', getU64Encoder()]]) as FixedSizeEncoder<
|
|
31
|
+
SysvarLastRestartSlot,
|
|
32
|
+
SysvarLastRestartSlotSize
|
|
33
|
+
>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns a decoder that you can use to decode a byte array representing the `LastRestartSlot`
|
|
38
|
+
* sysvar's account data to a {@link SysvarLastRestartSlot}.
|
|
39
|
+
*/
|
|
40
|
+
export function getSysvarLastRestartSlotDecoder(): FixedSizeDecoder<SysvarLastRestartSlot, SysvarLastRestartSlotSize> {
|
|
41
|
+
return getStructDecoder([['lastRestartSlot', getU64Decoder()]]) as FixedSizeDecoder<
|
|
42
|
+
SysvarLastRestartSlot,
|
|
43
|
+
SysvarLastRestartSlotSize
|
|
44
|
+
>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns a codec that you can use to encode from or decode to {@link SysvarLastRestartSlot}
|
|
49
|
+
*
|
|
50
|
+
* @see {@link getSysvarLastRestartSlotDecoder}
|
|
51
|
+
* @see {@link getSysvarLastRestartSlotEncoder}
|
|
52
|
+
*/
|
|
53
|
+
export function getSysvarLastRestartSlotCodec(): FixedSizeCodec<
|
|
54
|
+
SysvarLastRestartSlot,
|
|
55
|
+
SysvarLastRestartSlot,
|
|
56
|
+
SysvarLastRestartSlotSize
|
|
57
|
+
> {
|
|
58
|
+
return combineCodec(getSysvarLastRestartSlotEncoder(), getSysvarLastRestartSlotDecoder());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fetches the `LastRestartSlot` sysvar account using any RPC that supports the
|
|
63
|
+
* {@link GetAccountInfoApi}.
|
|
64
|
+
*/
|
|
65
|
+
export async function fetchSysvarLastRestartSlot(
|
|
66
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
67
|
+
config?: FetchAccountConfig,
|
|
68
|
+
): Promise<SysvarLastRestartSlot> {
|
|
69
|
+
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_LAST_RESTART_SLOT_ADDRESS, config);
|
|
70
|
+
assertAccountExists(account);
|
|
71
|
+
const decoded = decodeAccount(account, getSysvarLastRestartSlotDecoder());
|
|
72
|
+
return decoded.data;
|
|
73
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
|
|
2
|
+
import {
|
|
3
|
+
combineCodec,
|
|
4
|
+
type VariableSizeCodec,
|
|
5
|
+
type VariableSizeDecoder,
|
|
6
|
+
type VariableSizeEncoder,
|
|
7
|
+
} from '@solana/codecs-core';
|
|
8
|
+
import { getArrayDecoder, getArrayEncoder, getStructDecoder, getStructEncoder } from '@solana/codecs-data-structures';
|
|
9
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
10
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
11
|
+
import {
|
|
12
|
+
type Blockhash,
|
|
13
|
+
getBlockhashDecoder,
|
|
14
|
+
getBlockhashEncoder,
|
|
15
|
+
getDefaultLamportsDecoder,
|
|
16
|
+
getDefaultLamportsEncoder,
|
|
17
|
+
type Lamports,
|
|
18
|
+
} from '@solana/rpc-types';
|
|
19
|
+
|
|
20
|
+
import { fetchEncodedSysvarAccount, SYSVAR_RECENT_BLOCKHASHES_ADDRESS } from './sysvar';
|
|
21
|
+
|
|
22
|
+
type FeeCalculator = Readonly<{
|
|
23
|
+
/**
|
|
24
|
+
* The current cost of a signature.
|
|
25
|
+
*
|
|
26
|
+
* This amount may increase/decrease over time based on cluster processing load
|
|
27
|
+
*/
|
|
28
|
+
lamportsPerSignature: Lamports;
|
|
29
|
+
}>;
|
|
30
|
+
type Entry = Readonly<{
|
|
31
|
+
blockhash: Blockhash;
|
|
32
|
+
feeCalculator: FeeCalculator;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Information about recent blocks and their fee calculators.
|
|
37
|
+
*
|
|
38
|
+
* @deprecated Transaction fees should be determined with the
|
|
39
|
+
* {@link GetFeeForMessageApi.getFeeForMessage} RPC method. For additional context see the
|
|
40
|
+
* [Comprehensive Compute Fees proposal](https://docs.anza.xyz/proposals/comprehensive-compute-fees/).
|
|
41
|
+
*/
|
|
42
|
+
export type SysvarRecentBlockhashes = Entry[];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns an encoder that you can use to encode a {@link SysvarRecentBlockhashes} to a byte array
|
|
46
|
+
* representing the `RecentBlockhashes` sysvar's account data.
|
|
47
|
+
*
|
|
48
|
+
* @deprecated Transaction fees should be determined with the
|
|
49
|
+
* {@link GetFeeForMessageApi.getFeeForMessage} RPC method. For additional context see the
|
|
50
|
+
* [Comprehensive Compute Fees proposal](https://docs.anza.xyz/proposals/comprehensive-compute-fees/).
|
|
51
|
+
*/
|
|
52
|
+
export function getSysvarRecentBlockhashesEncoder(): VariableSizeEncoder<SysvarRecentBlockhashes> {
|
|
53
|
+
return getArrayEncoder(
|
|
54
|
+
getStructEncoder([
|
|
55
|
+
['blockhash', getBlockhashEncoder()],
|
|
56
|
+
['feeCalculator', getStructEncoder([['lamportsPerSignature', getDefaultLamportsEncoder()]])],
|
|
57
|
+
]),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns a decoder that you can use to decode a byte array representing the `RecentBlockhashes`
|
|
63
|
+
* sysvar's account data to a {@link SysvarRecentBlockhashes}.
|
|
64
|
+
*
|
|
65
|
+
* @deprecated Transaction fees should be determined with the
|
|
66
|
+
* {@link GetFeeForMessageApi.getFeeForMessage} RPC method. For additional context see the
|
|
67
|
+
* [Comprehensive Compute Fees proposal](https://docs.anza.xyz/proposals/comprehensive-compute-fees/).
|
|
68
|
+
*/
|
|
69
|
+
export function getSysvarRecentBlockhashesDecoder(): VariableSizeDecoder<SysvarRecentBlockhashes> {
|
|
70
|
+
return getArrayDecoder(
|
|
71
|
+
getStructDecoder([
|
|
72
|
+
['blockhash', getBlockhashDecoder()],
|
|
73
|
+
['feeCalculator', getStructDecoder([['lamportsPerSignature', getDefaultLamportsDecoder()]])],
|
|
74
|
+
]),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns a codec that you can use to encode from or decode to {@link SysvarRecentBlockhashes}
|
|
80
|
+
*
|
|
81
|
+
* @deprecated Transaction fees should be determined with the
|
|
82
|
+
* {@link GetFeeForMessageApi.getFeeForMessage} RPC method. For additional context see the
|
|
83
|
+
* [Comprehensive Compute Fees proposal](https://docs.anza.xyz/proposals/comprehensive-compute-fees/).
|
|
84
|
+
*
|
|
85
|
+
* @see {@link getSysvarRecentBlockhashesDecoder}
|
|
86
|
+
* @see {@link getSysvarRecentBlockhashesEncoder}
|
|
87
|
+
*/
|
|
88
|
+
export function getSysvarRecentBlockhashesCodec(): VariableSizeCodec<SysvarRecentBlockhashes> {
|
|
89
|
+
return combineCodec(getSysvarRecentBlockhashesEncoder(), getSysvarRecentBlockhashesDecoder());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fetches the `RecentBlockhashes` sysvar account using any RPC that supports the
|
|
94
|
+
* {@link GetAccountInfoApi}.
|
|
95
|
+
*
|
|
96
|
+
* @deprecated Transaction fees should be determined with the
|
|
97
|
+
* {@link GetFeeForMessageApi.getFeeForMessage} RPC method. For additional context see the
|
|
98
|
+
* [Comprehensive Compute Fees proposal](https://docs.anza.xyz/proposals/comprehensive-compute-fees/).
|
|
99
|
+
*/
|
|
100
|
+
export async function fetchSysvarRecentBlockhashes(
|
|
101
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
102
|
+
config?: FetchAccountConfig,
|
|
103
|
+
): Promise<SysvarRecentBlockhashes> {
|
|
104
|
+
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_RECENT_BLOCKHASHES_ADDRESS, config);
|
|
105
|
+
assertAccountExists(account);
|
|
106
|
+
const decoded = decodeAccount(account, getSysvarRecentBlockhashesDecoder());
|
|
107
|
+
return decoded.data;
|
|
108
|
+
}
|
package/src/rent.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
|
|
2
|
+
import { combineCodec, type FixedSizeCodec, type FixedSizeDecoder, type FixedSizeEncoder } from '@solana/codecs-core';
|
|
3
|
+
import { getStructDecoder, getStructEncoder } from '@solana/codecs-data-structures';
|
|
4
|
+
import { getF64Decoder, getF64Encoder, getU8Decoder, getU8Encoder } from '@solana/codecs-numbers';
|
|
5
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
6
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
7
|
+
import {
|
|
8
|
+
F64UnsafeSeeDocumentation,
|
|
9
|
+
getDefaultLamportsDecoder,
|
|
10
|
+
getDefaultLamportsEncoder,
|
|
11
|
+
type Lamports,
|
|
12
|
+
} from '@solana/rpc-types';
|
|
13
|
+
|
|
14
|
+
import { fetchEncodedSysvarAccount, SYSVAR_RENT_ADDRESS } from './sysvar';
|
|
15
|
+
|
|
16
|
+
type SysvarRentSize = 17;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Configuration for network rent.
|
|
20
|
+
*/
|
|
21
|
+
export type SysvarRent = Readonly<{
|
|
22
|
+
/**
|
|
23
|
+
* The percentage of collected rent that is burned.
|
|
24
|
+
*
|
|
25
|
+
* Valid values are in the range [0, 100]. The remaining percentage is distributed to
|
|
26
|
+
* validators.
|
|
27
|
+
*/
|
|
28
|
+
burnPercent: number;
|
|
29
|
+
/** Amount of time (in years) a balance must include rent for the account to be rent exempt */
|
|
30
|
+
exemptionThreshold: F64UnsafeSeeDocumentation;
|
|
31
|
+
/** Rental rate in {@link Lamports}/byte-year. */
|
|
32
|
+
lamportsPerByteYear: Lamports;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns an encoder that you can use to encode a {@link SysvarRent} to a byte array representing
|
|
37
|
+
* the `Rent` sysvar's account data.
|
|
38
|
+
*/
|
|
39
|
+
export function getSysvarRentEncoder(): FixedSizeEncoder<SysvarRent, SysvarRentSize> {
|
|
40
|
+
return getStructEncoder([
|
|
41
|
+
['lamportsPerByteYear', getDefaultLamportsEncoder()],
|
|
42
|
+
['exemptionThreshold', getF64Encoder()],
|
|
43
|
+
['burnPercent', getU8Encoder()],
|
|
44
|
+
]) as FixedSizeEncoder<SysvarRent, SysvarRentSize>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns a decoder that you can use to decode a byte array representing the `Rent` sysvar's
|
|
49
|
+
* account data to a {@link SysvarRent}.
|
|
50
|
+
*/
|
|
51
|
+
export function getSysvarRentDecoder(): FixedSizeDecoder<SysvarRent, SysvarRentSize> {
|
|
52
|
+
return getStructDecoder([
|
|
53
|
+
['lamportsPerByteYear', getDefaultLamportsDecoder()],
|
|
54
|
+
['exemptionThreshold', getF64Decoder()],
|
|
55
|
+
['burnPercent', getU8Decoder()],
|
|
56
|
+
]) as FixedSizeDecoder<SysvarRent, SysvarRentSize>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns a codec that you can use to encode from or decode to {@link SysvarRent}
|
|
61
|
+
*
|
|
62
|
+
* @see {@link getSysvarRentDecoder}
|
|
63
|
+
* @see {@link getSysvarRentEncoder}
|
|
64
|
+
*/
|
|
65
|
+
export function getSysvarRentCodec(): FixedSizeCodec<SysvarRent, SysvarRent, SysvarRentSize> {
|
|
66
|
+
return combineCodec(getSysvarRentEncoder(), getSysvarRentDecoder());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fetches the `Rent` sysvar account using any RPC that supports the {@link GetAccountInfoApi}.
|
|
71
|
+
*/
|
|
72
|
+
export async function fetchSysvarRent(rpc: Rpc<GetAccountInfoApi>, config?: FetchAccountConfig): Promise<SysvarRent> {
|
|
73
|
+
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_RENT_ADDRESS, config);
|
|
74
|
+
assertAccountExists(account);
|
|
75
|
+
const decoded = decodeAccount(account, getSysvarRentDecoder());
|
|
76
|
+
return decoded.data;
|
|
77
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
|
|
2
|
+
import {
|
|
3
|
+
combineCodec,
|
|
4
|
+
type VariableSizeCodec,
|
|
5
|
+
type VariableSizeDecoder,
|
|
6
|
+
type VariableSizeEncoder,
|
|
7
|
+
} from '@solana/codecs-core';
|
|
8
|
+
import { getArrayDecoder, getArrayEncoder, getStructDecoder, getStructEncoder } from '@solana/codecs-data-structures';
|
|
9
|
+
import { getU64Decoder, getU64Encoder } from '@solana/codecs-numbers';
|
|
10
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
11
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
12
|
+
import { type Blockhash, getBlockhashDecoder, getBlockhashEncoder, type Slot } from '@solana/rpc-types';
|
|
13
|
+
|
|
14
|
+
import { fetchEncodedSysvarAccount, SYSVAR_SLOT_HASHES_ADDRESS } from './sysvar';
|
|
15
|
+
|
|
16
|
+
type Entry = Readonly<{
|
|
17
|
+
hash: Blockhash;
|
|
18
|
+
slot: Slot;
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
/** The most recent hashes of a slot's parent banks. */
|
|
22
|
+
export type SysvarSlotHashes = Entry[];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns an encoder that you can use to encode a {@link SysvarSlotHashes} to a byte array
|
|
26
|
+
* representing the `SlotHashes` sysvar's account data.
|
|
27
|
+
*/
|
|
28
|
+
export function getSysvarSlotHashesEncoder(): VariableSizeEncoder<SysvarSlotHashes> {
|
|
29
|
+
return getArrayEncoder(
|
|
30
|
+
getStructEncoder([
|
|
31
|
+
['slot', getU64Encoder()],
|
|
32
|
+
['hash', getBlockhashEncoder()],
|
|
33
|
+
]),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns a decoder that you can use to decode a byte array representing the `SlotHashes` sysvar's
|
|
39
|
+
* account data to a {@link SysvarSlotHashes}.
|
|
40
|
+
*/
|
|
41
|
+
export function getSysvarSlotHashesDecoder(): VariableSizeDecoder<SysvarSlotHashes> {
|
|
42
|
+
return getArrayDecoder(
|
|
43
|
+
getStructDecoder([
|
|
44
|
+
['slot', getU64Decoder()],
|
|
45
|
+
['hash', getBlockhashDecoder()],
|
|
46
|
+
]),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns a codec that you can use to encode from or decode to {@link SysvarSlotHashes}
|
|
52
|
+
*
|
|
53
|
+
* @see {@link getSysvarSlotHashesDecoder}
|
|
54
|
+
* @see {@link getSysvarSlotHashesEncoder}
|
|
55
|
+
*/
|
|
56
|
+
export function getSysvarSlotHashesCodec(): VariableSizeCodec<SysvarSlotHashes> {
|
|
57
|
+
return combineCodec(getSysvarSlotHashesEncoder(), getSysvarSlotHashesDecoder());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fetches the `SlotHashes` sysvar account using any RPC that supports the {@link GetAccountInfoApi}.
|
|
62
|
+
*/
|
|
63
|
+
export async function fetchSysvarSlotHashes(
|
|
64
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
65
|
+
config?: FetchAccountConfig,
|
|
66
|
+
): Promise<SysvarSlotHashes> {
|
|
67
|
+
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_SLOT_HASHES_ADDRESS, config);
|
|
68
|
+
assertAccountExists(account);
|
|
69
|
+
const decoded = decodeAccount(account, getSysvarSlotHashesDecoder());
|
|
70
|
+
return decoded.data;
|
|
71
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
|
|
2
|
+
import {
|
|
3
|
+
combineCodec,
|
|
4
|
+
createDecoder,
|
|
5
|
+
createEncoder,
|
|
6
|
+
type FixedSizeCodec,
|
|
7
|
+
type FixedSizeDecoder,
|
|
8
|
+
type FixedSizeEncoder,
|
|
9
|
+
ReadonlyUint8Array,
|
|
10
|
+
} from '@solana/codecs-core';
|
|
11
|
+
import { getArrayCodec } from '@solana/codecs-data-structures';
|
|
12
|
+
import { getU64Codec, getU64Decoder, getU64Encoder } from '@solana/codecs-numbers';
|
|
13
|
+
import {
|
|
14
|
+
SOLANA_ERROR__CODECS__ENUM_DISCRIMINATOR_OUT_OF_RANGE,
|
|
15
|
+
SOLANA_ERROR__CODECS__INVALID_BYTE_LENGTH,
|
|
16
|
+
SOLANA_ERROR__CODECS__INVALID_NUMBER_OF_ITEMS,
|
|
17
|
+
SolanaError,
|
|
18
|
+
} from '@solana/errors';
|
|
19
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
20
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
21
|
+
import type { Slot } from '@solana/rpc-types';
|
|
22
|
+
|
|
23
|
+
import { fetchEncodedSysvarAccount, SYSVAR_SLOT_HISTORY_ADDRESS } from './sysvar';
|
|
24
|
+
|
|
25
|
+
const BITVEC_DISCRIMINATOR = 1;
|
|
26
|
+
// Max number of bits in the bitvector.
|
|
27
|
+
// The Solana SDK defines a constant `MAX_ENTRIES` representing the maximum
|
|
28
|
+
// number of bits that can be represented by the bitvector in the `SlotHistory`
|
|
29
|
+
// sysvar. This value is 1024 * 1024 = 1_048_576.
|
|
30
|
+
// See https://github.com/anza-xyz/agave/blob/e0203f22dc83cb792fa97f91dbe6e924cbd08af1/sdk/program/src/slot_history.rs#L43
|
|
31
|
+
const BITVEC_NUM_BITS = 1024 * 1024;
|
|
32
|
+
// The length of the bitvector in blocks.
|
|
33
|
+
// At 64 bits per block, this is 1024 * 1024 / 64 = 16_384.
|
|
34
|
+
const BITVEC_LENGTH = BITVEC_NUM_BITS / 64;
|
|
35
|
+
|
|
36
|
+
const SLOT_HISTORY_ACCOUNT_DATA_STATIC_SIZE =
|
|
37
|
+
1 + // Discriminator
|
|
38
|
+
8 + // bitvector length (u64)
|
|
39
|
+
BITVEC_LENGTH * 8 +
|
|
40
|
+
8 + // Number of bits (u64)
|
|
41
|
+
8; // Next slot (u64)
|
|
42
|
+
|
|
43
|
+
let memoizedU64Encoder: FixedSizeEncoder<bigint, 8> | undefined;
|
|
44
|
+
let memoizedU64Decoder: FixedSizeDecoder<bigint, 8> | undefined;
|
|
45
|
+
let memoizedU64ArrayEncoder: FixedSizeEncoder<bigint[]> | undefined;
|
|
46
|
+
let memoizedU64ArrayDecoder: FixedSizeDecoder<bigint[]> | undefined;
|
|
47
|
+
|
|
48
|
+
function getMemoizedU64Encoder(): FixedSizeEncoder<bigint, 8> {
|
|
49
|
+
if (!memoizedU64Encoder) memoizedU64Encoder = getU64Encoder();
|
|
50
|
+
return memoizedU64Encoder;
|
|
51
|
+
}
|
|
52
|
+
function getMemoizedU64Decoder(): FixedSizeDecoder<bigint, 8> {
|
|
53
|
+
if (!memoizedU64Decoder) memoizedU64Decoder = getU64Decoder();
|
|
54
|
+
return memoizedU64Decoder;
|
|
55
|
+
}
|
|
56
|
+
function getMemoizedU64ArrayEncoder(): FixedSizeEncoder<bigint[], typeof BITVEC_LENGTH> {
|
|
57
|
+
if (!memoizedU64ArrayEncoder) memoizedU64ArrayEncoder = getArrayCodec(getU64Codec(), { size: BITVEC_LENGTH });
|
|
58
|
+
return memoizedU64ArrayEncoder;
|
|
59
|
+
}
|
|
60
|
+
function getMemoizedU64ArrayDecoder(): FixedSizeDecoder<bigint[], typeof BITVEC_LENGTH> {
|
|
61
|
+
if (!memoizedU64ArrayDecoder) memoizedU64ArrayDecoder = getArrayCodec(getU64Codec(), { size: BITVEC_LENGTH });
|
|
62
|
+
return memoizedU64ArrayDecoder;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type SysvarSlotHistorySize = typeof SLOT_HISTORY_ACCOUNT_DATA_STATIC_SIZE;
|
|
66
|
+
|
|
67
|
+
/** A bitvector of slots present over the last epoch. */
|
|
68
|
+
export type SysvarSlotHistory = {
|
|
69
|
+
/**
|
|
70
|
+
* A vector of 64-bit numbers which, when their bits are strung together, represent a record of
|
|
71
|
+
* non-skipped slots.
|
|
72
|
+
*
|
|
73
|
+
* The bit in position (slot % MAX_ENTRIES) is 0 if the slot was skipped and 1 otherwise, valid
|
|
74
|
+
* only when the candidate slot is less than `nextSlot` and greater than or equal to
|
|
75
|
+
* `MAX_ENTRIES - nextSlot`.
|
|
76
|
+
*/
|
|
77
|
+
bits: bigint[];
|
|
78
|
+
/** The number of the slot one newer than tracked by the bitvector */
|
|
79
|
+
nextSlot: Slot;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns an encoder that you can use to encode a {@link SysvarSlotHistory} to a byte array
|
|
84
|
+
* representing the `SlotHistory` sysvar's account data.
|
|
85
|
+
*/
|
|
86
|
+
export function getSysvarSlotHistoryEncoder(): FixedSizeEncoder<SysvarSlotHistory, SysvarSlotHistorySize> {
|
|
87
|
+
return createEncoder({
|
|
88
|
+
fixedSize: SLOT_HISTORY_ACCOUNT_DATA_STATIC_SIZE,
|
|
89
|
+
write: (value: SysvarSlotHistory, bytes, offset) => {
|
|
90
|
+
// First byte is the bitvector discriminator.
|
|
91
|
+
bytes.set([BITVEC_DISCRIMINATOR], offset);
|
|
92
|
+
offset += 1;
|
|
93
|
+
// Next 8 bytes are the bitvector length.
|
|
94
|
+
getMemoizedU64Encoder().write(BigInt(BITVEC_LENGTH), bytes, offset);
|
|
95
|
+
offset += 8;
|
|
96
|
+
// Next `BITVEC_LENGTH` bytes are the bitvector.
|
|
97
|
+
// Any missing bits are assumed to be 0.
|
|
98
|
+
getMemoizedU64ArrayEncoder().write(value.bits, bytes, offset);
|
|
99
|
+
offset += BITVEC_LENGTH * 8;
|
|
100
|
+
// Next 8 bytes are the number of bits.
|
|
101
|
+
getMemoizedU64Encoder().write(BigInt(BITVEC_NUM_BITS), bytes, offset);
|
|
102
|
+
offset += 8;
|
|
103
|
+
// Next 8 bytes are the next slot.
|
|
104
|
+
getMemoizedU64Encoder().write(value.nextSlot, bytes, offset);
|
|
105
|
+
offset += 8;
|
|
106
|
+
return offset;
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns a decoder that you can use to decode a byte array representing the `SlotHistory` sysvar's
|
|
113
|
+
* account data to a {@link SysvarSlotHistory}.
|
|
114
|
+
*/
|
|
115
|
+
export function getSysvarSlotHistoryDecoder(): FixedSizeDecoder<SysvarSlotHistory, SysvarSlotHistorySize> {
|
|
116
|
+
return createDecoder({
|
|
117
|
+
fixedSize: SLOT_HISTORY_ACCOUNT_DATA_STATIC_SIZE,
|
|
118
|
+
read: (bytes: ReadonlyUint8Array | Uint8Array, offset) => {
|
|
119
|
+
// Byte length should be exact.
|
|
120
|
+
if (bytes.length != SLOT_HISTORY_ACCOUNT_DATA_STATIC_SIZE) {
|
|
121
|
+
throw new SolanaError(SOLANA_ERROR__CODECS__INVALID_BYTE_LENGTH, {
|
|
122
|
+
actual: bytes.length,
|
|
123
|
+
expected: SLOT_HISTORY_ACCOUNT_DATA_STATIC_SIZE,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// First byte is the bitvector discriminator.
|
|
127
|
+
const discriminator = bytes[offset];
|
|
128
|
+
offset += 1;
|
|
129
|
+
if (discriminator !== BITVEC_DISCRIMINATOR) {
|
|
130
|
+
throw new SolanaError(SOLANA_ERROR__CODECS__ENUM_DISCRIMINATOR_OUT_OF_RANGE, {
|
|
131
|
+
actual: discriminator,
|
|
132
|
+
expected: BITVEC_DISCRIMINATOR,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Next 8 bytes are the bitvector length.
|
|
136
|
+
const bitVecLength = getMemoizedU64Decoder().read(bytes, offset)[0];
|
|
137
|
+
offset += 8;
|
|
138
|
+
if (bitVecLength !== BigInt(BITVEC_LENGTH)) {
|
|
139
|
+
throw new SolanaError(SOLANA_ERROR__CODECS__INVALID_NUMBER_OF_ITEMS, {
|
|
140
|
+
actual: bitVecLength,
|
|
141
|
+
codecDescription: 'SysvarSlotHistoryCodec',
|
|
142
|
+
expected: BITVEC_LENGTH,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
// Next `BITVEC_LENGTH` bytes are the bitvector.
|
|
146
|
+
const bits = getMemoizedU64ArrayDecoder().read(bytes, offset)[0];
|
|
147
|
+
offset += BITVEC_LENGTH * 8;
|
|
148
|
+
// Next 8 bytes are the number of bits.
|
|
149
|
+
const numBits = getMemoizedU64Decoder().read(bytes, offset)[0];
|
|
150
|
+
offset += 8;
|
|
151
|
+
if (numBits !== BigInt(BITVEC_NUM_BITS)) {
|
|
152
|
+
throw new SolanaError(SOLANA_ERROR__CODECS__INVALID_NUMBER_OF_ITEMS, {
|
|
153
|
+
actual: numBits,
|
|
154
|
+
codecDescription: 'SysvarSlotHistoryCodec',
|
|
155
|
+
expected: BITVEC_NUM_BITS,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// Next 8 bytes are the next slot.
|
|
159
|
+
const nextSlot = getMemoizedU64Decoder().read(bytes, offset)[0];
|
|
160
|
+
offset += 8;
|
|
161
|
+
return [
|
|
162
|
+
{
|
|
163
|
+
bits,
|
|
164
|
+
nextSlot,
|
|
165
|
+
},
|
|
166
|
+
offset,
|
|
167
|
+
];
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Returns a codec that you can use to encode from or decode to {@link SysvarSlotHistory}
|
|
174
|
+
*
|
|
175
|
+
* @see {@link getSysvarSlotHistoryDecoder}
|
|
176
|
+
* @see {@link getSysvarSlotHistoryEncoder}
|
|
177
|
+
*/
|
|
178
|
+
export function getSysvarSlotHistoryCodec(): FixedSizeCodec<
|
|
179
|
+
SysvarSlotHistory,
|
|
180
|
+
SysvarSlotHistory,
|
|
181
|
+
SysvarSlotHistorySize
|
|
182
|
+
> {
|
|
183
|
+
return combineCodec(getSysvarSlotHistoryEncoder(), getSysvarSlotHistoryDecoder());
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Fetches the `SlotHistory` sysvar account using any RPC that supports the
|
|
188
|
+
* {@link GetAccountInfoApi}.
|
|
189
|
+
*/
|
|
190
|
+
export async function fetchSysvarSlotHistory(
|
|
191
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
192
|
+
config?: FetchAccountConfig,
|
|
193
|
+
): Promise<SysvarSlotHistory> {
|
|
194
|
+
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_SLOT_HISTORY_ADDRESS, config);
|
|
195
|
+
assertAccountExists(account);
|
|
196
|
+
const decoded = decodeAccount(account, getSysvarSlotHistoryDecoder());
|
|
197
|
+
return decoded.data;
|
|
198
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
|
|
2
|
+
import {
|
|
3
|
+
combineCodec,
|
|
4
|
+
type VariableSizeCodec,
|
|
5
|
+
type VariableSizeDecoder,
|
|
6
|
+
type VariableSizeEncoder,
|
|
7
|
+
} from '@solana/codecs-core';
|
|
8
|
+
import { getArrayDecoder, getArrayEncoder, getStructDecoder, getStructEncoder } from '@solana/codecs-data-structures';
|
|
9
|
+
import { getU64Decoder, getU64Encoder } from '@solana/codecs-numbers';
|
|
10
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
11
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
12
|
+
import { Epoch, getDefaultLamportsDecoder, getDefaultLamportsEncoder, type Lamports } from '@solana/rpc-types';
|
|
13
|
+
|
|
14
|
+
import { fetchEncodedSysvarAccount, SYSVAR_STAKE_HISTORY_ADDRESS } from './sysvar';
|
|
15
|
+
|
|
16
|
+
type Entry = Readonly<{
|
|
17
|
+
/** The epoch to which this stake history entry pertains */
|
|
18
|
+
epoch: Epoch;
|
|
19
|
+
stakeHistory: Readonly<{
|
|
20
|
+
/**
|
|
21
|
+
* Sum of portion of stakes requested to be warmed up, but not fully activated yet, in
|
|
22
|
+
* {@link Lamports}
|
|
23
|
+
*/
|
|
24
|
+
activating: Lamports;
|
|
25
|
+
/**
|
|
26
|
+
* Sum of portion of stakes requested to be cooled down, but not fully deactivated yet, in
|
|
27
|
+
* {@link Lamports}
|
|
28
|
+
*/
|
|
29
|
+
deactivating: Lamports;
|
|
30
|
+
/** Effective stake at this epoch, in {@link Lamports} */
|
|
31
|
+
effective: Lamports;
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
/** History of stake activations and de-activations. */
|
|
36
|
+
export type SysvarStakeHistory = Entry[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns an encoder that you can use to encode a {@link SysvarStakeHistory} to a byte array
|
|
40
|
+
* representing the `StakeHistory` sysvar's account data.
|
|
41
|
+
*/
|
|
42
|
+
export function getSysvarStakeHistoryEncoder(): VariableSizeEncoder<SysvarStakeHistory> {
|
|
43
|
+
return getArrayEncoder(
|
|
44
|
+
getStructEncoder([
|
|
45
|
+
['epoch', getU64Encoder()],
|
|
46
|
+
[
|
|
47
|
+
'stakeHistory',
|
|
48
|
+
getStructEncoder([
|
|
49
|
+
['effective', getDefaultLamportsEncoder()],
|
|
50
|
+
['activating', getDefaultLamportsEncoder()],
|
|
51
|
+
['deactivating', getDefaultLamportsEncoder()],
|
|
52
|
+
]),
|
|
53
|
+
],
|
|
54
|
+
]),
|
|
55
|
+
{ size: getU64Encoder() },
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns a decoder that you can use to decode a byte array representing the `StakeHistory`
|
|
61
|
+
* sysvar's account data to a {@link SysvarStakeHistory}.
|
|
62
|
+
*/
|
|
63
|
+
export function getSysvarStakeHistoryDecoder(): VariableSizeDecoder<SysvarStakeHistory> {
|
|
64
|
+
return getArrayDecoder(
|
|
65
|
+
getStructDecoder([
|
|
66
|
+
['epoch', getU64Decoder()],
|
|
67
|
+
[
|
|
68
|
+
'stakeHistory',
|
|
69
|
+
getStructDecoder([
|
|
70
|
+
['effective', getDefaultLamportsDecoder()],
|
|
71
|
+
['activating', getDefaultLamportsDecoder()],
|
|
72
|
+
['deactivating', getDefaultLamportsDecoder()],
|
|
73
|
+
]),
|
|
74
|
+
],
|
|
75
|
+
]),
|
|
76
|
+
{ size: getU64Decoder() },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns a codec that you can use to encode from or decode to {@link SysvarStakeHistory}
|
|
82
|
+
*
|
|
83
|
+
* @see {@link getSysvarStakeHistoryDecoder}
|
|
84
|
+
* @see {@link getSysvarStakeHistoryEncoder}
|
|
85
|
+
*/
|
|
86
|
+
export function getSysvarStakeHistoryCodec(): VariableSizeCodec<SysvarStakeHistory> {
|
|
87
|
+
return combineCodec(getSysvarStakeHistoryEncoder(), getSysvarStakeHistoryDecoder());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Fetches the `StakeHistory` sysvar account using any RPC that supports the
|
|
92
|
+
* {@link GetAccountInfoApi}.
|
|
93
|
+
*/
|
|
94
|
+
export async function fetchSysvarStakeHistory(
|
|
95
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
96
|
+
config?: FetchAccountConfig,
|
|
97
|
+
): Promise<SysvarStakeHistory> {
|
|
98
|
+
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_STAKE_HISTORY_ADDRESS, config);
|
|
99
|
+
assertAccountExists(account);
|
|
100
|
+
const decoded = decodeAccount(account, getSysvarStakeHistoryDecoder());
|
|
101
|
+
return decoded.data;
|
|
102
|
+
}
|
package/src/sysvar.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FetchAccountConfig,
|
|
3
|
+
fetchEncodedAccount,
|
|
4
|
+
fetchJsonParsedAccount,
|
|
5
|
+
type MaybeAccount,
|
|
6
|
+
type MaybeEncodedAccount,
|
|
7
|
+
} from '@solana/accounts';
|
|
8
|
+
import type { Address } from '@solana/addresses';
|
|
9
|
+
import type { GetAccountInfoApi } from '@solana/rpc-api';
|
|
10
|
+
import type { JsonParsedSysvarAccount } from '@solana/rpc-parsed-types';
|
|
11
|
+
import type { Rpc } from '@solana/rpc-spec';
|
|
12
|
+
|
|
13
|
+
export const SYSVAR_CLOCK_ADDRESS =
|
|
14
|
+
'SysvarC1ock11111111111111111111111111111111' as Address<'SysvarC1ock11111111111111111111111111111111'>;
|
|
15
|
+
export const SYSVAR_EPOCH_REWARDS_ADDRESS =
|
|
16
|
+
'SysvarEpochRewards1111111111111111111111111' as Address<'SysvarEpochRewards1111111111111111111111111'>;
|
|
17
|
+
export const SYSVAR_EPOCH_SCHEDULE_ADDRESS =
|
|
18
|
+
'SysvarEpochSchedu1e111111111111111111111111' as Address<'SysvarEpochSchedu1e111111111111111111111111'>;
|
|
19
|
+
export const SYSVAR_INSTRUCTIONS_ADDRESS =
|
|
20
|
+
'Sysvar1nstructions1111111111111111111111111' as Address<'Sysvar1nstructions1111111111111111111111111'>;
|
|
21
|
+
export const SYSVAR_LAST_RESTART_SLOT_ADDRESS =
|
|
22
|
+
'SysvarLastRestartS1ot1111111111111111111111' as Address<'SysvarLastRestartS1ot1111111111111111111111'>;
|
|
23
|
+
export const SYSVAR_RECENT_BLOCKHASHES_ADDRESS =
|
|
24
|
+
'SysvarRecentB1ockHashes11111111111111111111' as Address<'SysvarRecentB1ockHashes11111111111111111111'>;
|
|
25
|
+
export const SYSVAR_RENT_ADDRESS =
|
|
26
|
+
'SysvarRent111111111111111111111111111111111' as Address<'SysvarRent111111111111111111111111111111111'>;
|
|
27
|
+
export const SYSVAR_SLOT_HASHES_ADDRESS =
|
|
28
|
+
'SysvarS1otHashes111111111111111111111111111' as Address<'SysvarS1otHashes111111111111111111111111111'>;
|
|
29
|
+
export const SYSVAR_SLOT_HISTORY_ADDRESS =
|
|
30
|
+
'SysvarS1otHistory11111111111111111111111111' as Address<'SysvarS1otHistory11111111111111111111111111'>;
|
|
31
|
+
export const SYSVAR_STAKE_HISTORY_ADDRESS =
|
|
32
|
+
'SysvarStakeHistory1111111111111111111111111' as Address<'SysvarStakeHistory1111111111111111111111111'>;
|
|
33
|
+
|
|
34
|
+
type SysvarAddress =
|
|
35
|
+
| typeof SYSVAR_CLOCK_ADDRESS
|
|
36
|
+
| typeof SYSVAR_EPOCH_REWARDS_ADDRESS
|
|
37
|
+
| typeof SYSVAR_EPOCH_SCHEDULE_ADDRESS
|
|
38
|
+
| typeof SYSVAR_INSTRUCTIONS_ADDRESS
|
|
39
|
+
| typeof SYSVAR_LAST_RESTART_SLOT_ADDRESS
|
|
40
|
+
| typeof SYSVAR_RECENT_BLOCKHASHES_ADDRESS
|
|
41
|
+
| typeof SYSVAR_RENT_ADDRESS
|
|
42
|
+
| typeof SYSVAR_SLOT_HASHES_ADDRESS
|
|
43
|
+
| typeof SYSVAR_SLOT_HISTORY_ADDRESS
|
|
44
|
+
| typeof SYSVAR_STAKE_HISTORY_ADDRESS;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fetch an encoded sysvar account.
|
|
48
|
+
*
|
|
49
|
+
* Sysvars are special accounts that contain dynamically-updated data about the network cluster, the
|
|
50
|
+
* blockchain history, and the executing transaction.
|
|
51
|
+
*/
|
|
52
|
+
export async function fetchEncodedSysvarAccount<TAddress extends SysvarAddress>(
|
|
53
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
54
|
+
address: TAddress,
|
|
55
|
+
config?: FetchAccountConfig,
|
|
56
|
+
): Promise<MaybeEncodedAccount<TAddress>> {
|
|
57
|
+
return await fetchEncodedAccount<TAddress>(rpc, address, config);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fetch a JSON-parsed sysvar account.
|
|
62
|
+
*
|
|
63
|
+
* Sysvars are special accounts that contain dynamically-updated data about the network cluster, the
|
|
64
|
+
* blockchain history, and the executing transaction.
|
|
65
|
+
*/
|
|
66
|
+
export async function fetchJsonParsedSysvarAccount<TAddress extends SysvarAddress>(
|
|
67
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
68
|
+
address: TAddress,
|
|
69
|
+
config?: FetchAccountConfig,
|
|
70
|
+
): Promise<MaybeAccount<JsonParsedSysvarAccount, TAddress> | MaybeEncodedAccount<TAddress>> {
|
|
71
|
+
return await fetchJsonParsedAccount<JsonParsedSysvarAccount, TAddress>(rpc, address, config);
|
|
72
|
+
}
|