@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.
Files changed (3) hide show
  1. package/README.md +234 -0
  2. package/dist/cli.js +316 -0
  3. 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
+ }