Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ Following is the help message.
```bash
Usage: @blessnetwork/blesscontract blesstoken [options] <wallets> <mint> <mintAuthority>

blesstoken: initial blesstoken registration
blesstoken: initial blesstoken token mint and disptch bless token to wallet1-5 by rules.
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated help text includes a typo: "disptch" should be "dispatch" (and consider aligning the CLI command description/help output accordingly so README and --help stay consistent).

Suggested change
blesstoken: initial blesstoken token mint and disptch bless token to wallet1-5 by rules.
blesstoken: initial blesstoken token mint and dispatch bless token to wallet1-5 by rules.

Copilot uses AI. Check for mistakes.

Arguments:
wallets wallets: wallets is the wallet 1-5 that distrubuted the bless token by the contract, value should be base58 sperate by `,`.
Expand All @@ -208,7 +208,22 @@ Arguments:

Options:
--cluster <cluster> solana cluster: mainnet, testnet, devnet, localnet, <custom>
--programId <programId> Program ID: Specify the program ID when working on devnet, testnet, or localnet; it will not work on mainnet.
--payer <payer> the default payer: ~/.config/solana/id.json
-h, --help display help for command

```

Execute the following command to get the blesstoken-disable-mint help message.

```bash
npx @blessnetwork/blesscontract blesstoken-disable-mint --help
```

Execute the following command to disable mint authority.

```bash
npx @blessnetwork/blesscontract blesstoken-disable-mint <mint>
```

1. Prepare the Mint Authory Keypair.
Expand Down
10 changes: 0 additions & 10 deletions command/bles_meta_create.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const blessMetaCreateCommand = new Command("create")
"signer: the signer is the payer of the bless meta, default: " +
WALLET_PATH,
)
.option("--multisig <multisig>", "multisig: the multisig of the bless meta")
.option(
"--squads <true/false>",
"squads: if squads true, use squads to signature, default is false.",
Expand Down Expand Up @@ -62,15 +61,6 @@ blessMetaCreateCommand
const state =
await client.blessTokenClient.getBlessTokenMetaState(mintPubkey);
if (options.squads) {
if (options.multisig == null) {
console.log(chalk.red("multisig is required."));
process.exit(1);
}
const multisigPda = new PublicKey(options.multisig);
if (options.admin == null) {
console.log(chalk.red("admin is required."));
process.exit(1);
}
const adminPubkey = new PublicKey(options.admin);
if (state.admin.toBase58() != adminPubkey.toBase58()) {
console.log(
Expand Down
119 changes: 119 additions & 0 deletions command/blesstoken_disable_mint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const { Command, Argument } = require("commander");
const {
getBlsContractClient,
getPath,
readKeypair,
bs58Message,
} = require("./utils");
const { WALLET_PATH } = require("../lib/constants");
const chalk = require("chalk");
const { PublicKey } = require("@solana/web3.js");
const BLESS_CONTRACT_STATE_SEED = "bless_contract_state";

const blessTokenDisableMintCommand = new Command("blesstoken-disable-mint")
.alias("blesstoken_disable_mint")
.alias("disable-mint")
.alias("disable_mint")
.option(
"--cluster <cluster>",
"solana cluster: mainnet, testnet, devnet, localnet, <custom>",
)
.option(
"--signer <signer>",
"the signer is the payer/admin of bless token meta: " + WALLET_PATH,
)
.option(
"--programId <programId>",
"Program ID: Specify the program ID when working on devnet, testnet, or localnet; it will not work on mainnet.",
)
.option(
"--squads <true/false>",
"squads: if squads true, use squads to signature, default is false.",
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--squads <true/false> is parsed by commander as a string, so --squads false results in options.squads === 'false' (truthy) and the Squads branch will run anyway. Consider changing this to a boolean flag (--squads) or providing a coercion function so only 'true' enables Squads mode.

Suggested change
"squads: if squads true, use squads to signature, default is false.",
"squads: if squads true, use squads to signature, default is false.",
(value) => value === "true",
false,

Copilot uses AI. Check for mistakes.
)
.option("--admin <admin>", "admin pubkey when using squads mode.")
.description("disable-mint: disable bless token mint authority.");

const mint = new Argument(
"mint",
"mint: the mint is the mint token base58 value ",
);
mint.required = true;

blessTokenDisableMintCommand.addArgument(mint).action(async (mint, options) => {
options.cluster = options.cluster || "localnet";
options.signer = options.signer || getPath(WALLET_PATH);
try {
const keypair = readKeypair(options.signer);
let mintPubkey = null;
try {
mintPubkey = new PublicKey(mint);
} catch (e) {
console.log(chalk.red("invaild mint parameter: " + e));
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in error message: "invaild" should be "invalid".

Suggested change
console.log(chalk.red("invaild mint parameter: " + e));
console.log(chalk.red("invalid mint parameter: " + e));

Copilot uses AI. Check for mistakes.
process.exit(1);
}
const client = getBlsContractClient(
options.cluster,
keypair,
options.programId,
);
const [blessStatePda] = PublicKey.findProgramAddressSync(
[Buffer.from(BLESS_CONTRACT_STATE_SEED), mintPubkey.toBuffer()],
client.programId,
);
Comment on lines +59 to +62
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blessStatePda is computed unconditionally but only used in the non-Squads path. Consider moving the PDA derivation into the else branch to avoid extra work and reduce confusion about which inputs are required for Squads mode.

Copilot uses AI. Check for mistakes.
const state = await client.blessTokenClient.getBlessTokenMetaState(mintPubkey);

if (options.squads) {
if (options.admin == null) {
console.log(chalk.red("admin is required."));
process.exit(1);
}
const admin = new PublicKey(options.admin);
if (state.admin.toBase58() != admin.toBase58()) {
console.log(
chalk.red(
"disable mint is denied, admin is not matched, the state admin is " +
state.admin.toBase58(),
),
);
process.exit(1);
}
const tx = await client.blessTokenClient.getDisableMintTx(
mintPubkey,
{ signer: admin },
);
const itx = await bs58Message(
client.connection,
tx.instructions,
keypair,
);
console.log("bless token disable mint transaction created: \n" + itx);
} else {
if (state.admin.toBase58() != keypair.publicKey.toBase58()) {
console.log(
chalk.red(
"disable mint is denied, admin is not matched, the state admin is " +
state.admin.toBase58(),
),
);
process.exit(1);
}

await client.blessTokenClient.disableMint(
mintPubkey,
blessStatePda,
{
signer: keypair.publicKey,
signerKeypair: [keypair],
},
);
console.log(chalk.green("disable mint success."));
process.exit(0);
}
} catch (e) {
console.log(e);
console.log(chalk.red("disable mint fail: " + e));
process.exit(1);
}
});

module.exports = blessTokenDisableMintCommand;
60 changes: 60 additions & 0 deletions command/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ function encodeInstruction(data) {
dataLayout.addVariant(3, BufferLayout.struct([]), "Upgrade");
dataLayout.addVariant(4, BufferLayout.struct([]), "SetAuthority");
dataLayout.addVariant(5, BufferLayout.struct([]), "Close");
const extendProgram = BufferLayout.struct([
BufferLayout.u32("additional_bytes"),
]);
dataLayout.addVariant(6, extendProgram, "ExtendProgram");
const extendProgramChecked = BufferLayout.struct([
BufferLayout.u32("additional_bytes"),
]);
dataLayout.addVariant(9, extendProgramChecked, "ExtendProgramChecked");

// UpgradeableLoaderInstruction tag + offset + chunk length + chunk data
const instructionBuffer = Buffer.alloc(4 + 4 + 8 + Loader.chunkSize);
Expand Down Expand Up @@ -174,6 +182,58 @@ class Loader {
});
}

static async extendProgramInstruction(program, payer, additionalBytes) {
const [programDataKey, _nonce] = PublicKey.findProgramAddressSync(
[program.toBuffer()],
UPGRADEABLE_BPF_LOADER_PROGRAM_ID,
);
return new TransactionInstruction({
keys: [
{ pubkey: programDataKey, isSigner: false, isWritable: true },
{ pubkey: program, isSigner: false, isWritable: true },
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
{ pubkey: payer, isSigner: true, isWritable: true },
],
programId: UPGRADEABLE_BPF_LOADER_PROGRAM_ID,
data: encodeInstruction({
ExtendProgram: { additional_bytes: additionalBytes },
}),
});
}

static async extendProgramCheckedInstruction(
program,
authority,
payer,
additionalBytes,
) {
const [programDataKey, _nonce] = PublicKey.findProgramAddressSync(
[program.toBuffer()],
UPGRADEABLE_BPF_LOADER_PROGRAM_ID,
);
return new TransactionInstruction({
keys: [
{ pubkey: programDataKey, isSigner: false, isWritable: true },
{ pubkey: program, isSigner: false, isWritable: true },
{ pubkey: authority, isSigner: true, isWritable: false },
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
{ pubkey: payer, isSigner: true, isWritable: true },
],
programId: UPGRADEABLE_BPF_LOADER_PROGRAM_ID,
data: encodeInstruction({
ExtendProgramChecked: { additional_bytes: additionalBytes },
}),
});
}

static async getDeployBufferTransaction(
connection,
payer,
Expand Down
147 changes: 147 additions & 0 deletions command/prg_extend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const { Command, Argument } = require("commander");
const { getPath, readKeypair, getConnection, bs58Message } = require("./utils");
const { WALLET_PATH } = require("../lib/constants");
const Loader = require("./loader");
const chalk = require("chalk");
const { bs58 } = require("@coral-xyz/anchor/dist/cjs/utils/bytes");
const { PublicKey, Transaction, TransactionInstruction, SystemProgram } = require("@solana/web3.js");

const extendCommand = new Command("extend")
.option(
"--cluster <cluster>",
"solana cluster: mainnet, testnet, devnet, localnet, <custom>",
)
.option("--payer <payer>", "the default payer: " + WALLET_PATH)
.option(
"--authority <authority>",
"the authority keypair file (default: " + WALLET_PATH + ")",
)
.option(
"--squads <true/false>",
"squads: if squads true, create a tx message for squads signing, default is false.",
)
.option(
"--admin <admin>",
"admin: payer pubkey when using squads mode.",
)
.description("extend: extend an upgradeable program data account");

const programId = new Argument(
"programId",
"programId: the program id (base58 value)",
);
const additionalBytes = new Argument(
"additionalBytes",
"additionalBytes: extra bytes to add to the ProgramData account",
);

extendCommand
.addArgument(programId)
.addArgument(additionalBytes)
.action(async (programId, additionalBytes, options) => {
options.payer = options.payer || getPath(WALLET_PATH);
options.authority = options.authority || getPath(WALLET_PATH);
options.cluster = options.cluster || "localnet";
try {
const payerKeypair = readKeypair(options.payer);
const authorityKeypair = readKeypair(options.authority);
const programIdPK = new PublicKey(programId);
const additionalBytesNum = Number(additionalBytes);
if (
!Number.isInteger(additionalBytesNum) ||
additionalBytesNum <= 0 ||
additionalBytesNum > 0xffffffff
) {
throw new Error("additionalBytes must be an integer in [1, 4294967295]");
}

const connection = getConnection(options.cluster);
const useSquads =
options.squads === true ||
options.squads === "true" ||
options.squads === 1 ||
options.squads === "1";
if (useSquads) {
const programAccount = await connection.getAccountInfo(programIdPK);
if (!programAccount) {
throw new Error(`Program account not found: ${programId}`);
}
const programDataAddress = new PublicKey(
programAccount.data.subarray(4, 36),
);
const PROGRAMDATA_HEADER_SIZE = 45;
const programDataAccount = await connection.getAccountInfo(
programDataAddress,
)
if (!programDataAccount) {
throw new Error(
`ProgramData account not found: ${programDataAddress.toBase58()}`,
);
}

const data = Buffer.alloc(8);
data.writeUInt32LE(6, 0); // ExtendProgram discriminator
data.writeUInt32LE(Math.floor(additionalBytes * 1.05), 4);
const BPF_LOADER_UPGRADEABLE = new PublicKey(
"BPFLoaderUpgradeab1e11111111111111111111111",
);
const admin = new PublicKey(options.admin);
const ix = new TransactionInstruction({
programId: BPF_LOADER_UPGRADEABLE,
keys: [
{ pubkey: programDataAddress, isSigner: false, isWritable: true },
{
pubkey: new PublicKey(programId),
isSigner: false,
isWritable: true,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
{ pubkey: admin, isSigner: true, isWritable: true },
],
data,
});
const tx = new Transaction().add(ix);

tx.feePayer = admin;
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
const serialized = tx.serialize({ verifySignatures: false });
console.log("Transaction:", {
b64: serialized.toString("base64"),
b58: bs58.encode(serialized),
});
return
}

const extendIx = await Loader.extendProgramCheckedInstruction(
programIdPK,
authorityKeypair.publicKey,
payerKeypair.publicKey,
additionalBytesNum,
);
const transaction = new Transaction();
transaction.instructions = [extendIx];

transaction.recentBlockhash = (
await connection.getLatestBlockhash()
).blockhash;
transaction.feePayer = payerKeypair.publicKey;
if (
payerKeypair.publicKey.toBase58() === authorityKeypair.publicKey.toBase58()
) {
transaction.sign(payerKeypair);
} else {
transaction.sign(payerKeypair, authorityKeypair);
}

const tx = await connection.sendRawTransaction(transaction.serialize());
console.log(chalk.green("program extend success: " + tx));
} catch (e) {
console.log(chalk.red("program extend fail: " + e));
}
});

module.exports = extendCommand;
Loading