@jpool/bond-cli 1.1.0
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/README.md +234 -0
- package/dist/cli.js +316 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# JBond CLI
|
|
2
|
+
|
|
3
|
+
A command-line interface for interacting with the JBond Solana program, which manages validator bond collateral.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
### Initialize Global State
|
|
8
|
+
|
|
9
|
+
Initialize Bond Global State and set reserve address.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm jbond initialize -k <KEYPAIR_PATH> -r <RESERVE_ADDRESS>
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
-k, --keypair <KEYPAIR_PATH> Path to keypair file (default: ~/.config/solana/id.json)
|
|
16
|
+
-r, --reserve <RESERVE_ADDRESS> Reserve address
|
|
17
|
+
-c, --cluster <CLUSTER_NAME> Solana cluster(devnet or mainnet-beta)
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
pnpm jbond initialize -r 61mS9nEir6jx6cvte6NzQpyrFk3Fj4krMNLuHhi4tjJz -c mainnet-beta
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Note**: This command should only be run once by the program authority.
|
|
24
|
+
|
|
25
|
+
### Register Validator
|
|
26
|
+
|
|
27
|
+
Register a new validator and deposit initial collateral.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm jbond register-validator <vote-account> <amount> -k <path>
|
|
31
|
+
|
|
32
|
+
Arguments:
|
|
33
|
+
vote-account Vote account public key
|
|
34
|
+
amount Initial collateral amount in SOL
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
-k, --keypair <KEYPAIR_PATH> Path to keypair file (default: ~/.config/solana/id.json)
|
|
38
|
+
-w, Optional withdraw authority
|
|
39
|
+
-c, --cluster <CLUSTER_NAME> Solana cluster(devnet or mainnet-beta)
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
pnpm jbond register-validator 686JcEJ98r8fMtUiVuKiz4WRoBpJ2Sm9zMhdc2b6H4bu 1.1 -k ~/.config/solana/id.json -c mainnet-beta
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Requirements**:
|
|
46
|
+
|
|
47
|
+
- Minimum collateral amount is determined by the program (typically 1 SOL)
|
|
48
|
+
- The keypair must have sufficient SOL for collateral + transaction fees
|
|
49
|
+
|
|
50
|
+
### Top Up Collateral
|
|
51
|
+
|
|
52
|
+
Add additional collateral to an existing validator bond.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pnpm jbond topup-collateral <vote-account> <amount> -k <path>
|
|
56
|
+
|
|
57
|
+
Arguments:
|
|
58
|
+
vote-account Vote account public key
|
|
59
|
+
amount Amount to add in SOL
|
|
60
|
+
|
|
61
|
+
Options:
|
|
62
|
+
-k, --keypair <KEYPAIR_PATH> Path to keypair file (default: ~/.config/solana/id.json)
|
|
63
|
+
-c, --cluster <CLUSTER_NAME> Solana cluster(devnet or mainnet-beta)
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
pnpm jbond topup-collateral 686JcEJ98r8fMtUiVuKiz4WRoBpJ2Sm9zMhdc2b6H4bu 50 -c mainnet-beta
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Withdraw Collateral
|
|
70
|
+
|
|
71
|
+
Withdraw collateral from validator bond account.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pnpm jbond withdraw-collateral <vote-account> <destination> <amount> [options]
|
|
75
|
+
|
|
76
|
+
Arguments:
|
|
77
|
+
vote-account Vote account public key
|
|
78
|
+
destination Destination address for withdrawn funds
|
|
79
|
+
amount Amount to withdraw in SOL
|
|
80
|
+
|
|
81
|
+
Options:
|
|
82
|
+
-k, --keypair <path> Path to keypair file (default: ~/.config/solana/id.json)
|
|
83
|
+
-c, --cluster <CLUSTER_NAME> Solana cluster(devnet or mainnet-beta)
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
pnpm jbond withdraw-collateral 686JcEJ98r8fMtUiVuKiz4WRoBpJ2Sm9zMhdc2b6H4bu 3K2coMGaZhrSkyF52wUBUXBeRBRpGLnmB3znzLRKjgiP 25 -c mainnet-beta
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Requirements**:
|
|
90
|
+
|
|
91
|
+
Only the validator identity or withdrawal authority can execute withdrawals. The validator bond account must have sufficient collateral
|
|
92
|
+
|
|
93
|
+
### Claim Compensation
|
|
94
|
+
|
|
95
|
+
Claim compensation from a validator's collateral to the reserve (authority only).
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pnpm jbond claim-compensation <vote-account> <amount> -k <path>
|
|
99
|
+
|
|
100
|
+
Arguments:
|
|
101
|
+
vote-account Vote account public key
|
|
102
|
+
amount Amount to claim in SOL
|
|
103
|
+
|
|
104
|
+
Options:
|
|
105
|
+
-k, --keypair <KEYPAIR_PATH> Path to keypair file (default: ~/.config/solana/id.json)
|
|
106
|
+
-c, --cluster <CLUSTER_NAME> Solana cluster(devnet or mainnet-beta)
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
pnpm jbond claim-compensation GHRvDXj9BfACkJ9CoLWbpi2UkMVti9DwXJGsaFT9XDcD 10 -k <path> -c mainnet-beta
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Note**: Only the program authority can execute withdrawals.
|
|
113
|
+
|
|
114
|
+
### Update Global Config
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
pnpm jbond configure [options]
|
|
118
|
+
|
|
119
|
+
Options:
|
|
120
|
+
-k, --keypair <KEYPAIR_PATH> Path to keypair file (default: ~/.config/solana/id.json)
|
|
121
|
+
-a, --new-authority <PUBKEY> New authority public key
|
|
122
|
+
-r, --new-reserve <PUBKEY> New reserve address
|
|
123
|
+
-c, --cluster <CLUSTER_NAME> Solana cluster (devnet or mainnet-beta)
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
|
|
127
|
+
pnpm jbond configure -a 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin -c mainnet-beta
|
|
128
|
+
pnpm jbond configure -r 61mS9nEir6jx6cvte6NzQpyrFk3Fj4krMNLuHhi4tjJz -c mainnet-beta
|
|
129
|
+
pnpm jbond configure \
|
|
130
|
+
-a 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin\
|
|
131
|
+
-r 61mS9nEir6jx6cvte6NzQpyrFk3Fj4krMNLuHhi4tjJz\
|
|
132
|
+
-c mainnet-beta
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Validator Info
|
|
136
|
+
|
|
137
|
+
Display information about a validator bond account.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pnpm jbond validator-info <vote-account> [options]
|
|
141
|
+
|
|
142
|
+
Arguments:
|
|
143
|
+
vote-account Vote account public key
|
|
144
|
+
|
|
145
|
+
Options:
|
|
146
|
+
-k, --keypair <KEYPAIR_PATH> Path to keypair file (default: ~/.config/solana/id.json)
|
|
147
|
+
-v, --validator <pubkey> Validator public key,
|
|
148
|
+
-c, --cluster <CLUSTER_NAME> Solana cluster(devnet or mainnet-beta)
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
pnpm jbond validator-info GHRvDXj9BfACkJ9CoLWbpi2UkMVti9DwXJGsaFT9XDcD -k <path> -c mainnet-beta
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Displays**:
|
|
155
|
+
|
|
156
|
+
- Validator identity
|
|
157
|
+
- Vote account
|
|
158
|
+
- Withdrawal authority
|
|
159
|
+
- Active status
|
|
160
|
+
- Creation timestamp
|
|
161
|
+
|
|
162
|
+
### Global state Info
|
|
163
|
+
|
|
164
|
+
Display information about the global state.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pnpm jbond state [options]
|
|
168
|
+
|
|
169
|
+
Options:
|
|
170
|
+
-k, --keypair <KEYPAIR_PATH> Path to keypair file (default: ~/.config/solana/id.json)
|
|
171
|
+
-c, --cluster <CLUSTER_NAME> Solana cluster(devnet or mainnet-beta)
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
pnpm jbond state -k <path> -c mainnet-beta
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Displays**:
|
|
178
|
+
|
|
179
|
+
- Authority public key
|
|
180
|
+
- Total number of validators
|
|
181
|
+
- Current epoch
|
|
182
|
+
|
|
183
|
+
## Usage Examples
|
|
184
|
+
|
|
185
|
+
### Complete Validator Registration Flow
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# 1. Check current global state
|
|
189
|
+
pnpm jbond global-state-info -c mainnet-beta
|
|
190
|
+
|
|
191
|
+
# 2. Register as a validator with 100 SOL collateral
|
|
192
|
+
pnpm jbond register-validator GHRvDXj9BfACkJ9CoLWbpi2UkMVti9DwXJGsaFT9XDcD 100 -c mainnet-beta
|
|
193
|
+
|
|
194
|
+
# 3. Verify registration
|
|
195
|
+
pnpm jbond validator-info GHRvDXj9BfACkJ9CoLWbpi2UkMVti9DwXJGsaFT9XDcD -c mainnet-beta
|
|
196
|
+
|
|
197
|
+
# 4. Top up with additional 50 SOL
|
|
198
|
+
pnpm jbond topup-collateral GHRvDXj9BfACkJ9CoLWbpi2UkMVti9DwXJGsaFT9XDcD 50 -c mainnet-beta
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Authority Operations
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# As authority, withdraw compensation from underperforming validator
|
|
205
|
+
pnpm jbond claim-compensation GHRvDXj9BfACkJ9CoLWbpi2UkMVti9DwXJGsaFT9XDcD 5 -c mainnet-beta
|
|
206
|
+
|
|
207
|
+
# Check pool statistics
|
|
208
|
+
pnpm jbond global-state-info -c mainnet-beta
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Error Handling
|
|
212
|
+
|
|
213
|
+
Common errors and solutions:
|
|
214
|
+
|
|
215
|
+
### `Failed to load keypair`
|
|
216
|
+
|
|
217
|
+
- Ensure the keypair file exists at the specified path
|
|
218
|
+
- Check file permissions
|
|
219
|
+
- Verify the keypair file is in valid JSON format
|
|
220
|
+
|
|
221
|
+
### `InsufficientCollateral`
|
|
222
|
+
|
|
223
|
+
- The collateral amount is below the minimum requirement
|
|
224
|
+
- Check the program's minimum collateral requirement
|
|
225
|
+
|
|
226
|
+
### `Unauthorized`
|
|
227
|
+
|
|
228
|
+
- Only the authority can withdraw compensations
|
|
229
|
+
- Ensure you're using the correct authority keypair
|
|
230
|
+
|
|
231
|
+
### `Account already in use`
|
|
232
|
+
|
|
233
|
+
- The validator is already registered
|
|
234
|
+
- Use `topup-collateral` to add more funds instead
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/cli.ts
|
|
26
|
+
var import_web32 = require("@solana/web3.js");
|
|
27
|
+
var import_chalk = __toESM(require("chalk"));
|
|
28
|
+
var import_commander = require("commander");
|
|
29
|
+
var import_ora = __toESM(require("ora"));
|
|
30
|
+
|
|
31
|
+
// src/context.ts
|
|
32
|
+
var import_node_buffer = require("buffer");
|
|
33
|
+
var import_node_fs = require("fs");
|
|
34
|
+
var import_node_os = require("os");
|
|
35
|
+
var import_node_path = require("path");
|
|
36
|
+
var import_anchor = require("@coral-xyz/anchor");
|
|
37
|
+
var import_bond_sdk = require("@jpool/bond-sdk");
|
|
38
|
+
var import_web3 = require("@solana/web3.js");
|
|
39
|
+
var DEFAULT_CLUSTER = "devnet";
|
|
40
|
+
var context;
|
|
41
|
+
function useContext() {
|
|
42
|
+
return context;
|
|
43
|
+
}
|
|
44
|
+
function initContext({ cluster, env, keypair }) {
|
|
45
|
+
const opts = import_anchor.AnchorProvider.defaultOptions();
|
|
46
|
+
const connection = new import_web3.Connection(resolveRpcUrl(cluster), opts.commitment);
|
|
47
|
+
const wallet = new import_anchor.Wallet(resolveKeypair(keypair));
|
|
48
|
+
const provider = new import_anchor.AnchorProvider(connection, wallet, opts);
|
|
49
|
+
const client = new import_bond_sdk.JBondClient(provider).env(env);
|
|
50
|
+
return context = { keypair: wallet.payer, provider, client, cluster: cluster ?? DEFAULT_CLUSTER };
|
|
51
|
+
}
|
|
52
|
+
function resolveRpcUrl(cluster) {
|
|
53
|
+
try {
|
|
54
|
+
if (!cluster) {
|
|
55
|
+
throw new Error("No cluster provided");
|
|
56
|
+
}
|
|
57
|
+
return (0, import_web3.clusterApiUrl)(cluster);
|
|
58
|
+
} catch {
|
|
59
|
+
if (cluster && cluster.startsWith("http")) {
|
|
60
|
+
return cluster;
|
|
61
|
+
}
|
|
62
|
+
return (0, import_web3.clusterApiUrl)(DEFAULT_CLUSTER);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function resolveKeypair(path) {
|
|
66
|
+
let buffer;
|
|
67
|
+
if (path) {
|
|
68
|
+
buffer = (0, import_node_fs.readFileSync)(path);
|
|
69
|
+
} else if ((0, import_node_fs.existsSync)("keypair.json")) {
|
|
70
|
+
buffer = (0, import_node_fs.readFileSync)("keypair.json");
|
|
71
|
+
} else if (process.env.CLI_SOLANA_KEYPAIR) {
|
|
72
|
+
buffer = (0, import_node_fs.readFileSync)(process.env.CLI_SOLANA_KEYPAIR);
|
|
73
|
+
} else {
|
|
74
|
+
buffer = (0, import_node_fs.readFileSync)(
|
|
75
|
+
(0, import_node_path.join)((0, import_node_os.homedir)(), ".config", "solana", "id.json").replace(/\\/g, "/")
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return import_web3.Keypair.fromSecretKey(
|
|
79
|
+
import_node_buffer.Buffer.from(JSON.parse(buffer.toString()))
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// package.json
|
|
84
|
+
var version = "1.0.1";
|
|
85
|
+
|
|
86
|
+
// src/cli.ts
|
|
87
|
+
import_commander.program.name("jbond").description("CLI to interact with the JPool Bond program").version(process.env.VERSION ?? version).allowExcessArguments(false).option("-c, --cluster <CLUSTER>", "Solana cluster or RPC URL").option("-k, --keypair <KEYPAIR>", "Filepath to Solana keypair").hook("preAction", async (command) => {
|
|
88
|
+
const opts = command.opts();
|
|
89
|
+
const { provider, client } = initContext(opts);
|
|
90
|
+
console.log(import_chalk.default.dim(`# Version: ${command.version()}`));
|
|
91
|
+
console.log(import_chalk.default.dim(`# Program: ${client.programId}`));
|
|
92
|
+
console.log(import_chalk.default.dim(`# Keypair: ${provider.wallet.publicKey}`));
|
|
93
|
+
console.log(import_chalk.default.dim(`# Rpc Url: ${provider.connection.rpcEndpoint}`));
|
|
94
|
+
console.log("\n");
|
|
95
|
+
}).hook("postAction", (_c) => {
|
|
96
|
+
process.exit();
|
|
97
|
+
});
|
|
98
|
+
import_commander.program.command("info").description("Get global state information").action(async () => {
|
|
99
|
+
const { client, provider } = useContext();
|
|
100
|
+
try {
|
|
101
|
+
const state = await client.getGlobalState();
|
|
102
|
+
if (!state) {
|
|
103
|
+
console.log(import_chalk.default.yellow("Global state not initialized"));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
console.log(import_chalk.default.cyan("Global State Information:"));
|
|
107
|
+
console.log(import_chalk.default.white(` Authority: ${state.authority}`));
|
|
108
|
+
console.log(import_chalk.default.white(` Reserve: ${state.reserve}`));
|
|
109
|
+
console.log(import_chalk.default.white(` Total Validators: ${state.totalValidators}`));
|
|
110
|
+
console.log(import_chalk.default.white(` Total Compensation Amount: ${state.totalCompensationAmount.toString()}`));
|
|
111
|
+
const epochInfo = await provider.connection.getEpochInfo();
|
|
112
|
+
console.log(import_chalk.default.white(` Current Epoch: ${epochInfo.epoch}`));
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error(import_chalk.default.red(`Failed to get global state info: ${error}`));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
import_commander.program.command("initialize").description("Initialize the global state and reserve vault").requiredOption("-r, --reserve <address>", "Reserve vault address").option("-w, --claim-authority <pubkey>", "Claim authority (defaults to signer)").action(async (opts) => {
|
|
119
|
+
const spinner = (0, import_ora.default)("Initializing global state...").start();
|
|
120
|
+
const { client } = useContext();
|
|
121
|
+
const reserve = new import_web32.PublicKey(opts.reserve);
|
|
122
|
+
const authority = opts.claimAuthority ? new import_web32.PublicKey(opts.claimAuthority) : void 0;
|
|
123
|
+
try {
|
|
124
|
+
const tx = await client.initialize({ reserve, authority });
|
|
125
|
+
spinner.succeed(import_chalk.default.green(`Bond program initialized successfully!`));
|
|
126
|
+
console.log(import_chalk.default.gray(`Transaction: ${tx}`));
|
|
127
|
+
console.log(import_chalk.default.gray(`Reserve address: ${reserve.toString()}`));
|
|
128
|
+
} catch (error) {
|
|
129
|
+
spinner.fail(import_chalk.default.red(`Failed to initialize bond program: ${error}`));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
import_commander.program.command("register-validator").description("Register validator and fund initial collateral").argument("<vote-account>", "Vote account public key").argument("<amount>", "Initial collateral amount in SOL").action(async (voteAccountStr, amount) => {
|
|
133
|
+
const spinner = (0, import_ora.default)("Registering validator...").start();
|
|
134
|
+
const { keypair, client } = useContext();
|
|
135
|
+
try {
|
|
136
|
+
const voteAccount = new import_web32.PublicKey(voteAccountStr);
|
|
137
|
+
const initialCollateral = Number.parseFloat(amount);
|
|
138
|
+
if (Number.isNaN(initialCollateral) || initialCollateral <= 0) {
|
|
139
|
+
throw new Error("Invalid collateral amount");
|
|
140
|
+
}
|
|
141
|
+
const tx = await client.registerValidator({
|
|
142
|
+
voteAccount,
|
|
143
|
+
initialCollateral,
|
|
144
|
+
identity: keypair.publicKey
|
|
145
|
+
});
|
|
146
|
+
spinner.succeed(import_chalk.default.green(`Validator registered successfully!`));
|
|
147
|
+
console.log(import_chalk.default.gray(`Transaction: ${tx}`));
|
|
148
|
+
console.log(import_chalk.default.gray(`Initial collateral: ${initialCollateral} SOL`));
|
|
149
|
+
console.log(import_chalk.default.gray(`Validator: ${keypair.publicKey.toString()}`));
|
|
150
|
+
console.log(import_chalk.default.gray(`Vote Account: ${voteAccountStr}`));
|
|
151
|
+
} catch (error) {
|
|
152
|
+
spinner.fail(import_chalk.default.red(`Failed to register validator: ${error}`));
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
import_commander.program.command("topup-collateral").description("Top up collateral for existing validator").argument("<vote-account>", "Vote account public key").argument("<amount>", "Amount to top up in SOL").action(async (voteAccountStr, amount) => {
|
|
156
|
+
const spinner = (0, import_ora.default)("Topping up collateral...").start();
|
|
157
|
+
const { client } = useContext();
|
|
158
|
+
try {
|
|
159
|
+
const voteAccount = new import_web32.PublicKey(voteAccountStr);
|
|
160
|
+
const topUpAmount = Number.parseFloat(amount);
|
|
161
|
+
if (Number.isNaN(topUpAmount) || topUpAmount <= 0) {
|
|
162
|
+
throw new Error("Invalid top-up amount");
|
|
163
|
+
}
|
|
164
|
+
const signature = await client.topUpCollateral({ voteAccount, amount: topUpAmount });
|
|
165
|
+
spinner.succeed(import_chalk.default.green(`Collateral topped up successfully!`));
|
|
166
|
+
console.log(import_chalk.default.gray(`Signature: ${signature}`));
|
|
167
|
+
console.log(import_chalk.default.gray(`Top-up amount: ${topUpAmount} SOL`));
|
|
168
|
+
} catch (error) {
|
|
169
|
+
spinner.fail(import_chalk.default.red(`Failed to top up collateral: ${error}`));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
import_commander.program.command("withdraw-collateral").description("Withdraw collateral from validator bond account").argument("<vote-account>", "Vote account public key").argument("<destination>", "Destination address for withdrawn funds").argument("<amount>", "Amount to withdraw in SOL").action(async (voteAccountStr, destinationStr, amount) => {
|
|
174
|
+
const spinner = (0, import_ora.default)("Withdrawing collateral...").start();
|
|
175
|
+
const { client } = useContext();
|
|
176
|
+
try {
|
|
177
|
+
const voteAccount = new import_web32.PublicKey(voteAccountStr);
|
|
178
|
+
const destination = new import_web32.PublicKey(destinationStr);
|
|
179
|
+
const withdrawAmount = Number.parseFloat(amount);
|
|
180
|
+
if (Number.isNaN(withdrawAmount) || withdrawAmount <= 0) {
|
|
181
|
+
throw new Error("Invalid withdrawal amount");
|
|
182
|
+
}
|
|
183
|
+
const bondAccount = await client.getValidatorBond(voteAccount);
|
|
184
|
+
if (!bondAccount) {
|
|
185
|
+
throw new Error("Validator bond account not found");
|
|
186
|
+
}
|
|
187
|
+
const tx = await client.withdrawCollateral({ voteAccount, destination, amount: withdrawAmount });
|
|
188
|
+
spinner.succeed(import_chalk.default.green(`Collateral withdrawn successfully!`));
|
|
189
|
+
console.log(import_chalk.default.gray(`Transaction: ${tx}`));
|
|
190
|
+
console.log(import_chalk.default.gray(`Amount withdrawn: ${withdrawAmount} SOL`));
|
|
191
|
+
console.log(import_chalk.default.gray(`Destination: ${destinationStr}`));
|
|
192
|
+
} catch (error) {
|
|
193
|
+
spinner.fail(import_chalk.default.red(`Failed to withdraw collateral: ${error}`));
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
import_commander.program.command("claim-compensation").description("Claim compensation from validator to reserve").argument("<vote-account>", "Vote account public key").argument("<amount>", "Amount to withdraw in SOL").action(async (voteAccountStr, amount) => {
|
|
198
|
+
const spinner = (0, import_ora.default)("Claiming compensation...").start();
|
|
199
|
+
const { client } = useContext();
|
|
200
|
+
try {
|
|
201
|
+
const voteAccount = new import_web32.PublicKey(voteAccountStr);
|
|
202
|
+
const withdrawAmount = Number.parseFloat(amount);
|
|
203
|
+
if (Number.isNaN(withdrawAmount) || withdrawAmount <= 0) {
|
|
204
|
+
throw new Error("Invalid withdrawal amount");
|
|
205
|
+
}
|
|
206
|
+
const tx = await client.claimCompensation({
|
|
207
|
+
voteAccount,
|
|
208
|
+
amount: withdrawAmount
|
|
209
|
+
});
|
|
210
|
+
spinner.succeed(import_chalk.default.green(`Compensation withdrawn successfully!`));
|
|
211
|
+
console.log(import_chalk.default.gray(`Transaction: ${tx}`));
|
|
212
|
+
console.log(import_chalk.default.gray(`Amount: ${withdrawAmount} SOL`));
|
|
213
|
+
} catch (error) {
|
|
214
|
+
spinner.fail(import_chalk.default.red(`Failed to withdraw compensation: ${error}`));
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
import_commander.program.command("validator-info").description("Get validator bond account information").argument("<vote-account>", "Vote account public key").option("-v, --validator <pubkey>", "Validator public key (optional, defaults to current keypair)").action(async (voteAccountStr) => {
|
|
219
|
+
const { client } = useContext();
|
|
220
|
+
try {
|
|
221
|
+
const voteAccount = new import_web32.PublicKey(voteAccountStr);
|
|
222
|
+
const [validatorBondAddress] = client.pda.validatorBond(voteAccount);
|
|
223
|
+
console.log(import_chalk.default.gray(`Validator Bond Address: ${validatorBondAddress.toString()}`));
|
|
224
|
+
const state = await client.getValidatorBond(
|
|
225
|
+
voteAccount
|
|
226
|
+
);
|
|
227
|
+
if (!state) {
|
|
228
|
+
console.log(import_chalk.default.yellow("Validator bond account not found"));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
console.log(import_chalk.default.cyan("\nValidator Bond Account Information:"));
|
|
232
|
+
console.log(import_chalk.default.white(` Validator: ${state.identity}`));
|
|
233
|
+
console.log(import_chalk.default.white(` Vote Account: ${state.voteAccount}`));
|
|
234
|
+
if (state.withdrawalAuthority) {
|
|
235
|
+
console.log(import_chalk.default.white(` Withdrawal Authority: ${state.withdrawalAuthority}`));
|
|
236
|
+
} else {
|
|
237
|
+
console.log(import_chalk.default.white(` Withdrawal Authority: Not set (identity only)`));
|
|
238
|
+
}
|
|
239
|
+
console.log(
|
|
240
|
+
import_chalk.default.white(
|
|
241
|
+
` Active: ${state.isActive ? import_chalk.default.green("Yes") : import_chalk.default.red("No")}`
|
|
242
|
+
)
|
|
243
|
+
);
|
|
244
|
+
console.log(import_chalk.default.white(` Created At: ${state.createdAt}`));
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error(import_chalk.default.red(`Failed to get validator info: ${error}`));
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
import_commander.program.command("set-withdraw-authority").description("Set or update withdrawal authority for validator bond").argument("<vote-account>", "Vote account public key").option("-w, --new-authority <pubkey>", "New withdrawal authority (omit to remove authority)").action(async (voteAccountStr, options) => {
|
|
251
|
+
const spinner = (0, import_ora.default)("Setting withdrawal authority...").start();
|
|
252
|
+
const { client, keypair } = useContext();
|
|
253
|
+
try {
|
|
254
|
+
const voteAccount = new import_web32.PublicKey(voteAccountStr);
|
|
255
|
+
const bondAccount = await client.getValidatorBond(voteAccount);
|
|
256
|
+
if (!bondAccount) {
|
|
257
|
+
throw new Error("Validator bond account not found");
|
|
258
|
+
}
|
|
259
|
+
if (!bondAccount.identity.equals(keypair.publicKey)) {
|
|
260
|
+
throw new Error("Signer must be the validator identity");
|
|
261
|
+
}
|
|
262
|
+
const newWithdrawAuthority = options.newAuthority ? new import_web32.PublicKey(options.newAuthority) : null;
|
|
263
|
+
const tx = await client.setWithdrawAuthority({
|
|
264
|
+
voteAccount,
|
|
265
|
+
newWithdrawAuthority
|
|
266
|
+
});
|
|
267
|
+
spinner.succeed(import_chalk.default.green(`Withdrawal authority updated successfully!`));
|
|
268
|
+
console.log(import_chalk.default.gray(`Transaction: ${tx}`));
|
|
269
|
+
if (newWithdrawAuthority) {
|
|
270
|
+
console.log(import_chalk.default.gray(`New withdrawal authority: ${newWithdrawAuthority.toString()}`));
|
|
271
|
+
} else {
|
|
272
|
+
console.log(import_chalk.default.gray(`Withdrawal authority removed (identity only)`));
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
spinner.fail(import_chalk.default.red(`Failed to set withdrawal authority: ${error}`));
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
import_commander.program.command("configure").description("Update program configuration (authority and/or reserve)").option("-a, --new-authority <pubkey>", "New authority address").option("-r, --new-reserve <pubkey>", "New reserve address").action(async (opts) => {
|
|
280
|
+
const spinner = (0, import_ora.default)("Updating program configuration...").start();
|
|
281
|
+
const { client } = useContext();
|
|
282
|
+
try {
|
|
283
|
+
if (!opts.newAuthority && !opts.newReserve) {
|
|
284
|
+
throw new Error("At least one of --new-authority or --new-reserve must be provided");
|
|
285
|
+
}
|
|
286
|
+
const newAuthority = opts.newAuthority ? new import_web32.PublicKey(opts.newAuthority) : void 0;
|
|
287
|
+
const newReserve = opts.newReserve ? new import_web32.PublicKey(opts.newReserve) : void 0;
|
|
288
|
+
const tx = await client.configure({
|
|
289
|
+
newAuthority,
|
|
290
|
+
newReserve
|
|
291
|
+
});
|
|
292
|
+
spinner.succeed(import_chalk.default.green(`Program configuration updated successfully!`));
|
|
293
|
+
console.log(import_chalk.default.gray(`Transaction: ${tx}`));
|
|
294
|
+
if (newAuthority) {
|
|
295
|
+
console.log(import_chalk.default.gray(`New authority: ${newAuthority.toString()}`));
|
|
296
|
+
}
|
|
297
|
+
if (newReserve) {
|
|
298
|
+
console.log(import_chalk.default.gray(`New reserve: ${newReserve.toString()}`));
|
|
299
|
+
}
|
|
300
|
+
} catch (error) {
|
|
301
|
+
spinner.fail(import_chalk.default.red(`Failed to update configuration: ${error}`));
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
import_commander.program.command("*", { isDefault: true, hidden: true }).allowExcessArguments(true).action(() => {
|
|
306
|
+
import_commander.program.help();
|
|
307
|
+
});
|
|
308
|
+
import_commander.program.parseAsync().catch((e) => {
|
|
309
|
+
console.log(import_chalk.default.red(e.message));
|
|
310
|
+
if (e.logs) {
|
|
311
|
+
console.log(
|
|
312
|
+
import_chalk.default.red(JSON.stringify(e.logs, null, 2))
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
process.exit();
|
|
316
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jpool/bond-cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "JBond CLI for interacting with the Solana program",
|
|
5
|
+
"main": "./dist/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"jbond": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"release": "release-it",
|
|
16
|
+
"release:ci": "release-it --ci -VV",
|
|
17
|
+
"release:dry": "release-it --ci --dry-run",
|
|
18
|
+
"release:pre": "release-it --preRelease=next -VV",
|
|
19
|
+
"release:pre:dry": "release-it --preRelease=next --dry-run --ci",
|
|
20
|
+
"postinstall": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@coral-xyz/anchor": "^0.31.1",
|
|
24
|
+
"@jpool/bond-sdk": "workspace:",
|
|
25
|
+
"@solana/web3.js": "^1.98.4",
|
|
26
|
+
"chalk": "^5.6.2",
|
|
27
|
+
"commander": "^14.0.1",
|
|
28
|
+
"dotenv": "^17.2.3",
|
|
29
|
+
"ora": "^9.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"release-it-config": "workspace:",
|
|
33
|
+
"tsup": "^8.5.0"
|
|
34
|
+
}
|
|
35
|
+
}
|