
Product
Announcing Socket Fix 2.0
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
@solana-developers/helpers
Advanced tools
The @solana-developers/helpers
package contains Solana helper functions, for use in the browser
and/or node.js, made by the Solana Foundation's
Developer Relations team and contributions from
Anza, Turbin3,
Unboxed Software, and StarAtlas.
[!IMPORTANT]
@solana-developers/helpers
is for Solana web3.js version 1. The updated version of this package which is compatible with Solana web3.js version 2 is calledgill
. Learn more here:
- npm registry - https://www.npmjs.com/package/gill
- source repository - https://github.com/solana-foundation/gill
Make multiple keypairs at once
Get a keypair from a keypair file
Get a keypair from an environment variable
Add a new keypair to an env file
Load or create a keypair and airdrop to it if needed
Make a token mint with metadata
Create multiple accounts with balances of different tokens in a single step
Get the logs for a transaction
Get simulated compute units (CUs) for transaction instructions
Get an airdrop if your balance is below some amount
Resolve a custom error message
Get a Solana Explorer link for a transaction, address, or block
npm i @solana-developers/helpers
PRs are very much welcome! Read the CONTRIBUTING guidelines for the Solana course then send a PR!
Usage:
makeKeypairs(amount);
In some situations - like making tests for your onchain programs - you might need to make lots of
keypairs at once. You can use makeKeypairs()
combined with JS destructuring to quickly create
multiple variables with distinct keypairs.
const [sender, recipient] = makeKeypairs(2);
The makeTokenMint
makes a new token mint. A token mint is effectively the factory that produces
token of a particular type. So if you want to make a new token, this is the right function for you!
Unlike older tools, the function uses Token Extensions Metadata and Metadata Pointer to put all metadata into the Mint Account, without needing an external Metadata account. If you don't know what that means, just know that things are simpler than they used to be!
Parameters
connection
: Connection.mintAuthority
: Keypair of the account that can make new tokens.name
: string, name of the token.symbol
: string, like a ticker symbol. Usually in all-caps.decimals
: number, how many decimal places the new token will have.uri
: string, URI to a JSON file containing at minimum a value for image
.additionalMetadata
: additional metadata as either Record<string, string>
or
Array<[string, string]>
(optional).updateAuthority
: PublicKey (optional) - public key of the account that can update the token.freezeAuthority
: PublicKey (optional) - public key of the freeze account, default to null
const mintAddress = await makeTokenMint(
connection,
mintAuthority,
"Unit test token",
"TEST",
9,
"https://raw.githubusercontent.com/solana-developers/professional-education/main/labs/sample-token-metadata.json",
);
Frequently, tests for onchain programs need to make not just users with SOL, but also token mints
and give each user some balance of each token. To save this boilerplate,
createAccountsMintsAndTokenAccounts()
handles making user keypairs, giving them SOL, making mints,
creating associated token accounts, and minting tokens directly to the associated token accounts.
Eg, to make two new users, and two tokens:
Just run:
const usersMintsAndTokenAccounts = await createAccountsMintsAndTokenAccounts(
[
[1_000_000_000, 0], // User 0 has 1_000_000_000 of token A and 0 of token B
[0, 1_000_000_000], // User 1 has 0 of token A and 1_000_000_000 of token B
],
1 * LAMPORTS_PER_SOL,
connection,
payer,
);
The returned usersMintsAndTokenAccounts
will be an object of the form:
{
users: <Array<Keypair>>
mints: <Array<Keypair>>,
tokenAccounts: <Array<Array><PublicKey>>>
}
tokenAccounts are indexed by the user, then the mint. Eg, the ATA of user[0]
for mint[0]
is
tokenAccounts[0][0]
.
Usage:
getCustomErrorMessage(programErrors, errorMessage);
Sometimes Solana transactions throw an error with a message like:
failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x10
Usage:
getCustomErrorMessage();
Allows you to turn this message into a more readable message from the custom program, like:
This token mint cannot freeze accounts
Just:
Get the errors from the specific program's error.rs
file - for example, there are
the errors for the Token Program
Save the errors into an array
// Token program errors
// https://github.com/solana-labs/solana-program-library/blob/master/token/program/src/error.rs
const tokenProgramErrors = [
"Lamport balance below rent-exempt threshold",
"Insufficient funds",
"Invalid Mint",
"Account not associated with this Mint",
"Owner does not match",
"Fixed supply",
"Already in use",
"Invalid number of provided signers",
"Invalid number of required signers",
"State is unititialized",
"Instruction does not support native tokens",
"Non-native account can only be closed if its balance is zero",
"Invalid instruction",
"State is invalid for requested operation",
"Operation overflowed",
"Account does not support specified authority type",
"This token mint cannot freeze accounts",
"Account is frozen",
"The provided decimals value different from the Mint decimals",
"Instruction does not support non-native tokens",
];
Then run:
const errorMessage = getCustomErrorMessage(
tokenProgramErrors,
"failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x10",
);
And errorMessage
will now be:
"This token mint cannot freeze accounts";
Usage:
airdropIfRequired(connection, publicKey, lamports, maximumBalance);
Request and confirm an airdrop in one step. As soon as the await
returns, the airdropped tokens
will be ready to use, and the new balance of tokens will be returned. The maximumBalance
is used
to avoid errors caused by unnecessarily asking for SOL when there's already enough in the account,
and makes airdropIfRequired()
very handy in scripts that run repeatedly.
To ask for 0.5 SOL, if the balance is below 1 SOL, use:
const newBalance = await airdropIfRequired(
connection,
keypair.publicKey,
0.5 * LAMPORTS_PER_SOL,
1 * LAMPORTS_PER_SOL,
);
Usage:
getExplorerLink(type, identifier, clusterName);
Get an explorer link for an address
, block
or transaction
(tx
works too).
getExplorerLink("address", "dDCQNnDmNbFVi8cQhKAgXhyhXeJ625tvwsunRyRc7c8", "mainnet-beta");
Will return "https://explorer.solana.com/address/dDCQNnDmNbFVi8cQhKAgXhyhXeJ625tvwsunRyRc7c8"
. The
cluster name isn't included since mainnet-beta is the default.
getExplorerLink("address", "dDCQNnDmNbFVi8cQhKAgXhyhXeJ625tvwsunRyRc7c8", "devnet");
Will return
"https://explorer.solana.com/address/dDCQNnDmNbFVi8cQhKAgXhyhXeJ625tvwsunRyRc7c8?cluster=devnet"
getExplorerLink("block", "241889720", "mainnet-beta");
Will return "https://explorer.solana.com/block/241889720"
Usage:
confirmTransaction(connection, transaction);
Confirm a transaction, and also gets the recent blockhash required to confirm it.
await confirmTransaction(connection, transaction);
Usage:
getLogs(connection, transaction);
Get the logs for a transaction signature:
const logs = await getLogs(connection, transaction);
The logs
will be an array of strings, eg:
[
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success",
];
This a good way to assert your onchain programs return particular logs during unit tests.
Usage:
getSimulationComputeUnits(connection, instructions, payer, lookupTables);
Get the compute units required for an array of instructions. Create your instructions:
const sendSol = SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: recipient,
lamports: 1_000_000,
});
Then use getSimulationComputeUnits
to get the number of compute units the instructions will use:
const units = await getSimulationComputeUnits(connection, [sendSol], payer.publicKey);
You can then use ComputeBudgetProgram.setComputeUnitLimit({ units })
as the first instruction in
your transaction. See
How to Request Optimal Compute Budget
for more information on compute units.
addComputeInstructions
Adds compute unit instructions for a transaction if they don't already exist:
const updatedInstructions = await addComputeInstructions(
connection,
instructions,
lookupTables,
payer.publicKey,
10000, // priority fee default 10000 microLamports
{ multiplier: 1.1 }, // compute unit buffer default adds 10%
);
// Returns instructions array with:
// 1. setComputeUnitPrice instruction (if not present)
// 2. setComputeUnitLimit instruction based on simulation (if not present)
// The limit is calculated by simulating the transaction and adding the specified buffer
This function:
Usage:
getKeypairFromFile(filename);
Gets a keypair from a file - the format must be the same as Solana CLI uses, ie, a JSON array of numbers:
To load the default keypair ~/.config/solana/id.json
, just run:
const keyPair = await getKeypairFromFile();
or to load a specific file:
const keyPair = await getKeypairFromFile("somefile.json");
or using home dir expansion:
const keyPair = await getKeypairFromFile("~/code/solana/demos/steve.json");
Usage:
getKeypairFromEnvironment(environmentVariable);
Gets a keypair from a secret key stored in an environment variable. This is typically used to load secret keys from env files.
const keypair = await getKeypairFromEnvironment("SECRET_KEY");
Usage:
addKeypairToEnvFile(keypair, environmentVariable, envFileName);
Saves a keypair to the environment file.
await addKeypairToEnvFile(testKeypair, "SECRET_KEY");
or to specify a file name:
await addKeypairToEnvFile(testKeypair, "SECRET_KEY", ".env.local");
This will also reload the env file.
Usage:
initializeKeypair(connection, options);
Loads in a keypair from the filesystem, or environment and then airdrops to it if needed.
How the keypair is initialized is dependent on the initializeKeypairOptions
:
interface initializeKeypairOptions {
envFileName?: string;
envVariableName?: string;
airdropAmount?: number | null;
minimumBalance?: number;
keypairPath?: string;
}
By default, the keypair will be retrieved from the .env
file. If a .env
file does not exist,
this function will create one with a new keypair under the optional envVariableName
.
To load the keypair from the filesystem, pass in the keypairPath
. When set, loading a keypair from
the filesystem will take precedence over loading from the .env
file.
If airdropAmount
amount is set to something other than null
or 0
, this function will then
check the account's balance. If the balance is below the minimumBalance
, it will airdrop the
account airdropAmount
.
To initialize a keypair from the .env
file, and airdrop it 1 sol if it's beneath 0.5 sol:
const keypair = await initializeKeypair(connection);
To initialize a keypair from the .env
file under a different variable name:
const keypair = await initializeKeypair(connection, {
envVariableName: "TEST_KEYPAIR",
});
To initialize a keypair from the filesystem, and airdrop it 3 sol:
const keypair = await initializeKeypair(connection, {
keypairPath: "~/.config/solana/id.json",
airdropAmount: LAMPORTS_PER_SOL * 3,
});
The default options are as follows:
const DEFAULT_AIRDROP_AMOUNT = 1 * LAMPORTS_PER_SOL;
const DEFAULT_MINIMUM_BALANCE = 0.5 * LAMPORTS_PER_SOL;
const DEFAULT_ENV_KEYPAIR_VARIABLE_NAME = "PRIVATE_KEY";
Secret keys can be read in either the more compact base58 format
(base58.encode(randomKeypair.secretKey);
), like:
# A random secret key for demo purposes
SECRET_KEY=QqKYBnj5mcgUsS4vrCeyMczbTyV1SMrr7SjSAPj7JGFtxfrgD8AWU8NciwHNCbmkscbvj4HdeEen42GDBSHCj1N
Or the longer, 'array of numbers' format JSON.stringify(Object.values(randomKeypair.secretKey));
:
# A random secret key for demo purposes
SECRET_KEY=[112,222,91,246,55,109,221,4,23,148,251,127,212,180,44,249,182,139,18,13,209,208,6,7,193,210,186,249,148,237,237,1,70,118,1,153,238,134,239,75,187,96,101,138,147,130,181,71,22,82,44,217,194,122,59,208,134,119,98,53,136,108,44,105]
We always save keys using the 'array of numbers' format, since most other Solana apps (like the CLI SDK and Rust tools) use the 'array of numbers' format.
To run tests, open a terminal tab, and run:
solana-test-validator
Then in a different tab, run:
npm run test
The tests use the node native test runner.
If you'd like to run a single test, use:
esrun --node-test-name-pattern="getCustomErrorMessage" src/index.test.ts
To just run tests matching the name getCustomErrorMessage
.
sendTransaction
Sends a transaction with compute unit optimization and automatic retries. This function:
const signature = await sendTransaction(connection, transaction, [payer]);
The function is also customizable if you do not like the defaults:
const signature = await sendTransaction(
connection,
transaction,
[payer],
10000, // priority fee in microLamports
{
computeUnitBuffer: { multiplier: 1.1 }, // add 10% buffer to compute units
onStatusUpdate: (status) => console.log(status),
commitment: "confirmed",
maxRetries: 10,
initialDelayMs: 2000,
},
);
The function will:
For RPC providers that support priority fees:
sendVersionedTransaction
Sends a versioned transaction with compute unit optimization and automatic retries.
async function sendVersionedTransaction(
connection: Connection,
instructions: Array<TransactionInstruction>,
signers: Keypair[],
priorityFee: number = 10000,
lookupTables?: Array<AddressLookupTableAccount> | [],
options?: SendTransactionOptions & {
computeUnitBuffer?: ComputeUnitBuffer;
},
): Promise<string>;
Example:
const signature = await sendVersionedTransaction(
connection,
instructions,
[payer],
10000,
lookupTables,
{
computeUnitBuffer: { multiplier: 1.1 },
onStatusUpdate: (status) => console.log(status),
},
);
createLookupTable
Creates a new address lookup table and extends it with additional addresses.
async function createLookupTable(
connection: Connection,
sender: Keypair,
additionalAddresses: PublicKey[],
priorityFee: number = 10000,
): Promise<[PublicKey, AddressLookupTableAccount]>;
Example:
const [lookupTableAddress, lookupTableAccount] = await createLookupTable(connection, payer, [
account1.publicKey,
account2.publicKey,
account3.publicKey,
]);
// Can either cache the lookup table address and lookup table account for reuse, or use them directly
const signature = await sendVersionedTransaction(
connection,
instructions,
[payer],
10000,
[lookupTableAccount],
{
onStatusUpdate: (status) => console.log(status),
},
);
These utilities help with:
Get an IDL from a local file:
const idl = await getIdlByPath("./idl/program.json");
Or fetch it from the chain:
const idl = await getIdlByProgramId(
new PublicKey("verifycLy8mB96wd9wqq3WDXQwM4oU6r42Th37Db9fC"),
connection,
);
Usage:
const idl = await getIdlByProgramId(programId, connection);
const data = await getIdlParsedAccountData(idl, "counter", accountAddress, connection);
// Decoded Data: { count: <BN: 2> }
Fetches and parses an account's data using an Anchor IDL file. This is useful when you need to decode account data from Anchor programs.
Usage:
const idl = await getIdlByPath("./idl/program.json");
const events = await parseAnchorTransactionEvents(idl, signature, connection);
// Events will be an array of:
// {
// name: "GameCreated",
// data: { gameId: "123", player: "..." }
// }
Parses all Anchor events emitted in a transaction. This helps you track and verify program events after transaction execution.
Usage:
const idl = await getIdlByProgramId(programId, connection);
const decoded = await decodeAnchorTransaction(idl, signature, connection);
// Print human-readable format
console.log(decoded.toString());
// Access specific instruction data
decoded.instructions.forEach((ix) => {
console.log(`Instruction: ${ix.name}`);
console.log(`Arguments: ${JSON.stringify(ix.data)}`);
console.log(`Accounts: ${ix.accounts.map((acc) => acc.name).join(", ")}`);
});
Provides detailed decoding of all Anchor instructions in a transaction, including:
2.8.1
NodeWallet
to use the correct Wallet
class exportedArrayBuffer
s and Uint8Array
ssendVersionedTransaction()
to send a versioned transaction with lookup tables. Also adds priority fee support.createLookupTable()
to easily create a lookup table and extend it with additional addressesgetIdlByProgramId()
to fetch an IDL from a program on-chaingetIdlByPath()
to parse an IDL from a local file pathgetIdlParsedAccountData()
to parse account data using an IDLparseAnchorTransactionEvents()
to parse anchor transaction events using an IDLdecodeAnchorTransaction()
to decode a transaction completely using an IDLdecodeAnchorTransaction()
sendTransaction()
to send transactions with compute unit optimization and automatic retries.sendTransactionWithRetry()
as sendTransaction()
is more convenient.prepareTransactionWithCompute()
and sendTransactionWithRetry()
getIdlParsedAccountData()
, parseAnchorTransactionEvents()
and getIdlParsedInstructionData()
createAccountsMintsAndTokenAccounts()
function to use correct commitment and not max
blockhashconfirmTransaction()
to not use correct commitmentmakeTokenMint()
createAccountsMintsAndTokenAccounts()
Improved browser support by only loading node-specific modules when they are needed. Thanks @piotr-layerzero!
getSimulationComputeUnits()
initializeKeypair()
requestAndConfirmAirdropIfRequired()
and requestAndConfirmAirdrop()
with a single function, airdropIfRequired()
. See [README.md]!confirmTransaction()
to throw errors correctly.getLogs()
functiongetExplorerLink()
requestAndConfirmAirdropIfRequired()
helpers
. The old node-helpers
package is marked as deprecated.requestAndConfirmAirdrop()
getCustomErrorMessage()
addKeypairToEnvFile()
Original release.
FAQs
Solana helper functions
The npm package @solana-developers/helpers receives a total of 24,229 weekly downloads. As such, @solana-developers/helpers popularity was classified as popular.
We found that @solana-developers/helpers demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Security News
Socket CEO Feross Aboukhadijeh joins Risky Business Weekly to unpack recent npm phishing attacks, their limited impact, and the risks if attackers get smarter.
Product
Socket’s new Tier 1 Reachability filters out up to 80% of irrelevant CVEs, so security teams can focus on the vulnerabilities that matter.