Kite leverages the speed and elegance of @solana/kit
(previously known as @solana/web3.js
version 2) but allows you to complete most Solana tasks in a single step. Since Kite uses @solana/kit
for the heavy lifting, Kite is fully compatible with @solana/kit
. If you decide you no longer need Kite, you can easily remove it and use plain @solana/kit
.
Users of Cursor, VSCode, Sublime and other popular editors will see TSDoc comments with parameters, return types, and usage examples right in their editor.
Tip
Kite is the @solana/kit
update of @solana-developers/helpers
, the most popular high-level library for @solana/web3.js version 1, by the original author. The kite
package includes updated versions of most of the original helpers, including contributions from Helius, the Solana Foundation, Anza, Turbin3, Unboxed Software, and StarAtlas. The ones we haven't added yet should be there soon.
Kite works both in the browser and node.js, is small, and has minimal dependencies. It also works with Anchor.
Kite includes functions for:
- Create a new wallet
- Create multiple wallets
- Load a wallet from a file
- Load a wallet from an environment variable
- Create a new token
- Get token account address
- Get token mint information
- Get token account balance
- 🆕 Check if token account is closed
- Mint tokens to a wallet
- Transfer tokens between wallets
- Send a transaction from multiple instructions
- Send and confirm a transaction you're already created
- Check if a transaction is confirmed
- Get transaction logs
- 🆕 Get a PDA from seeds
We'll be adding more functions over time. You're welcome to suggest a new function or read the CONTRIBUTING guidelines and send a PR.
Solana itself is named after a beach. Kite is a high-level framework, so what is high above a beach? Kites! 🪁😃
Coincidentally, a couple of weeks after Kite's release, Solana web3.js version 2 was renamed kit
. So Kite now flies on top of Kit.
Also coincidentally, a Kite is a type of Edwards curve!
Yes. Here's a full Anchor demo token Swap app using Kite, Kit and Codama.
npm i solana-kite
To start Kite, you need to connect to a Solana RPC - RPCs are how your code communicates with the Solana blockchain.
To use the local cluster (ie, solana-test-validator
running on your machine):
import { connect } from "solana-kite";
const connection = connect();
You can also specify a cluster name. The connection object defaults to localnet
but any of the following cluster names are supported:
mainnet-beta
(ormainnet
) - Main Solana network where transactions involving real value occur.testnet
- Used to test future versions of the Solana blockchain.devnet
- Development network for testing with fake SOL. This is where Solana apps developers typically deploy first.helius-mainnet
,helius-testnet
, orhelius-devnet
- Helius-operated RPC nodes with additional features.
const connection = connect("helius-devnet");
The Helius names require the environment variable HELIUS_API_KEY
to be set in your environment. You can get an API key from Helius.
You can also specify an arbitrary RPC URL and RPC subscription URL:
const connection = connect("https://mainnet.example.com/", "wss://mainnet.example.com/");
After you've made a connection Kite is ready to use. You don't need to set up any factories, they're already configured. A connection
has the following functions ready out of the box:
Like
initializeKeypair
from@solana/helpers
Creates a new Solana wallet (more specifically a KeyPairSigner
).
If you like, the wallet will have a prefix/suffix of your choice, the wallet will have a SOL balance ready to spend, and the keypair will be saved to a file for you to use later.
Returns: Promise<KeyPairSigner>
const wallet = await connection.createWallet({
prefix: "be", // Optional: Generate address starting with these characters
suffix: "en", // Optional: Generate address ending with these characters
envFileName: ".env", // Optional: Save private key to this .env file
envVariableName: "PRIVATE_KEY", // Optional: Environment variable name to store the key
airdropAmount: 1_000_000_000n, // Optional: Amount of test SOL to request from faucet
});
All options are optional:
prefix
:string | null
- Prefix for wallet addresssuffix
:string | null
- Suffix for wallet addressenvFileName
:string | null
- Path to .env file to save keypairenvVariableName
:string
- Name of environment variable to store keypair (default: "PRIVATE_KEY")airdropAmount
:Lamports | null
- Amount of SOL to airdrop (default: 1 SOL)
Create a basic wallet:
const wallet = await connection.createWallet();
Create a wallet with a specific prefix and suffix:
const wallet = await connection.createWallet({
prefix: "COOL",
suffix: "WALLET",
});
Create a wallet and save it to an environment file:
const wallet = await connection.createWallet({
envFileName: ".env",
envVariableName: "MY_WALLET_KEY",
});
Create a wallet with a custom airdrop amount:
const wallet = await connection.createWallet({
airdropAmount: lamports(2n * SOL),
});
Like
getKeypairFromFile
from@solana/helpers
Loads a wallet (more specifically a KeyPairSigner
) from a file. The file should be in the same format as files created by the solana-keygen
command.
Returns: Promise<KeyPairSigner>
const wallet = await connection.loadWalletFromFile(keyPairPath);
keyPairPath
:string
- Path to load keypair from file
Like
getKeypairFromEnvironment
from@solana/helpers
Loads a wallet (more specifically a KeyPairSigner
) from an environment variable. The keypair should be in the same 'array of numbers' format as used by solana-keygen
.
Returns: Promise<KeyPairSigner>
const wallet = await connection.loadWalletFromEnvironment(envVariableName);
envVariableName
:string
- Name of environment variable containing the keypair (default: "PRIVATE_KEY")
Like
sendTransaction
from@solana/helpers
Sends a transaction and waits for confirmation.
Returns: Promise<void>
await connection.sendAndConfirmTransaction(transaction, options);
transaction
:VersionedTransaction
- Transaction to sendoptions
:Object
(optional)commitment
:Commitment
- Desired confirmation levelskipPreflight
:boolean
- Whether to skip preflight transaction checks
Gets the SOL balance of an account in lamports (1 SOL = 1,000,000,000 lamports). Lamports are the smallest unit of SOL, similar to how cents are the smallest unit of dollars.
Returns: Promise<Lamports>
- The balance in lamports as a bigint
const balance = await connection.getLamportBalance(address, commitment);
address
:string
- Address to check balance forcommitment
:Commitment
(optional) - Desired commitment level. Can be"processed"
,"confirmed"
(default), or"finalized"
.
// Get balance in lamports
const balanceInLamports = await connection.getLamportBalance("GkFTrgp8FcCgkCZeKreKKVHLyzGV6eqBpDHxRzg1brRn");
// Convert to SOL
const balanceInSOL = Number(balanceInLamports) / 1_000_000_000;
console.log(`Balance: ${balanceInSOL} SOL`);
- Throws if the address is invalid
- Throws if the RPC connection fails
Get a link to view an address, transaction, or token on Solana Explorer. The link will automatically use your RPC.
Returns: string
- Explorer URL
Get a link to view an address:
const addressLink = connection.getExplorerLink("address", "GkFTrgp8FcCgkCZeKreKKVHLyzGV6eqBpDHxRzg1brRn");
Get a link to view a transaction:
const transactionLink = connection.getExplorerLink(
"transaction",
"5rUQ2tX8bRzB2qJWnrBhHYgHsafpqVZwGwxVrtyYFZXJZs6yBVwerZHGbwsrDHKbRtKpxnWoHKmBgqYXVbU5TrHe",
);
Or if you like abbreviations:
const transactionLink = connection.getExplorerLink(
"tx",
"5rUQ2tX8bRzB2qJWnrBhHYgHsafpqVZwGwxVrtyYFZXJZs6yBVwerZHGbwsrDHKbRtKpxnWoHKmBgqYXVbU5TrHe",
);
Get a link to view a block:
const blockLink = connection.getExplorerLink("block", "180392470");
linkType
:"transaction" | "tx" | "address" | "block"
- Type of entity to link toid
:string
- The address, signature, or block to link to
Checks the confirmation status of a recent transaction.
Returns: Promise<boolean>
const confirmed = await connection.getRecentSignatureConfirmation(signature);
signature
:string
- The signature of the transaction to check
Like
airdropIfRequired
from@solana/helpers
Airdrops SOL to an address if its balance is below the specified threshold.
Returns: Promise<string | null>
- Transaction signature if airdrop occurred, null if no airdrop was needed
const signature = await connection.airdropIfRequired(address, airdropAmount, minimumBalance);
address
:Address
- Address to check balance and potentially airdrop toairdropAmount
:Lamports
- Amount of lamports to airdrop if neededminimumBalance
:Lamports
- Minimum balance threshold that triggers airdrop
Retrieves logs for a transaction.
Returns: Promise<Array<string>>
const logs = await connection.getLogs(signature);
signature
:string
- Transaction signature to get logs for
Transfers SOL from one wallet to another.
Returns: Promise<Signature>
const signature = await connection.transferLamports({
source: senderWallet,
destination: recipientAddress,
amount: 1_000_000_000n,
skipPreflight: true,
maximumClientSideRetries: 0,
});
source
:KeyPairSigner
- The wallet to send SOL fromdestination
:Address
- The wallet to send SOL toamount
:Lamports
- Amount of lamports to sendskipPreflight
:boolean
(optional) - Whether to skip preflight checks (default: true)maximumClientSideRetries
:number
(optional) - Maximum number of times to retry sending the transaction (default: 0)abortSignal
:AbortSignal | null
(optional) - Signal to abort the transaction (default: null)
Creates a new SPL token mint with specified parameters.
Returns: Promise<Address>
mintAuthority
:KeyPairSigner
- Authority that can mint new tokensdecimals
:number
- Number of decimal places for the tokenname
:string
- Name of the tokensymbol
:string
- Symbol of the tokenuri
:string
- URI pointing to the token's metadata (eg: "https://arweave.net/abc123")additionalMetadata
:Record<string, string> | Map<string, string>
(optional) - Additional metadata fields
Create a token with additional metadata:
const mintAddress = await connection.createTokenMint({
mintAuthority: wallet,
decimals: 9,
name: "My Token",
symbol: "TKN",
uri: "https://example.com/token-metadata.json",
additionalMetadata: {
description: "A sample token",
website: "https://example.com",
},
});
A metadata.json
file, and any images inside, should be hosted at a decentralized storage service. The file itself is at minimum:
{
"image": "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/CompressedCoil/image.png"
}
Images should be square, and either 512x512 or 1024x1024 pixels, and less than 100kb if possible.
Gets the associated token account address for a given wallet and token mint.
Returns: Promise<Address>
const tokenAccountAddress = await connection.getTokenAccountAddress(wallet, mint, useTokenExtensions);
wallet
:Address
- The wallet address to get the token account formint
:Address
- The token mint addressuseTokenExtensions
:boolean
(optional) - Whether to use Token Extensions program (default: false)
Get a token account address for a token made with the classic token program:
const tokenAccountAddress = await connection.getTokenAccountAddress(
"GkFTrgp8FcCgkCZeKreKKVHLyzGV6eqBpDHxRzg1brRn",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
);
Get a token account address for a Token Extensions token:
const tokenAccountAddress = await connection.getTokenAccountAddress(
"GkFTrgp8FcCgkCZeKreKKVHLyzGV6eqBpDHxRzg1brRn",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
true,
);
Mints tokens from a token mint to a destination account. The mint authority must sign the transaction.
Returns: Promise<Signature>
const signature = await connection.mintTokens(
mintAddress, // address of the token mint
mintAuthority, // signer with authority to mint tokens
amount, // amount of tokens to mint
destination, // address to receive the tokens
);
mintAddress
:Address
- Address of the token mintmintAuthority
:KeyPairSigner
- Signer with authority to mint tokensamount
:bigint
- Amount of tokens to mint (in base units)destination
:Address
- Address to receive the minted tokens
// Create a new token mint
const mintAuthority = await connection.createWallet({
airdropAmount: lamports(1n * SOL),
});
const mintAddress = await connection.createTokenMint(
mintAuthority,
9, // decimals
"My Token",
"TKN",
"https://example.com/token.json",
);
// Mint 100 tokens to the mint authority's account
const signature = await connection.mintTokens(mintAddress, mintAuthority, 100n, mintAuthority.address);
Transfers tokens from one account to another. The sender must sign the transaction.
Returns: Promise<Signature>
const signature = await connection.transferTokens({
sender: senderWallet,
destination: recipientAddress,
mintAddress: tokenMint,
amount: 1_000_000n,
maximumClientSideRetries: 0,
});
sender
:KeyPairSigner
- Signer that owns the tokens and will sign the transactiondestination
:Address
- Address to receive the tokensmintAddress
:Address
- Address of the token mintamount
:bigint
- Amount of tokens to transfer (in base units)maximumClientSideRetries
:number
(optional) - Maximum number of times to retry sending the transaction (default: 0)abortSignal
:AbortSignal | null
(optional) - Signal to abort the transaction (default: null)
// Create wallets for sender and recipient
const [sender, recipient] = await Promise.all([
connection.createWallet({
airdropAmount: lamports(1n * SOL),
}),
connection.createWallet({
airdropAmount: lamports(1n * SOL),
}),
]);
// Create a new token mint
const mintAddress = await connection.createTokenMint(
sender, // sender will be the mint authority
9, // decimals
"My Token",
"TKN",
"https://example.com/token.json",
);
// Mint some tokens to the sender's account
await connection.mintTokens(mintAddress, sender, 100n, sender.address);
// Transfer 50 tokens from sender to recipient with retries
const signature = await connection.transferTokens({
sender,
destination: recipient.address,
mintAddress,
amount: 50n,
maximumClientSideRetries: 3,
});
Like
sendTransactionWithSigners
from@solana/helpers
Sends a transaction containing one or more instructions. The transaction will be signed by the fee payer.
Returns: Promise<Signature>
- The unique transaction signature that can be used to look up the transaction
const signature = await connection.sendTransactionFromInstructions({
feePayer: wallet,
instructions: [instruction1, instruction2],
commitment: "confirmed",
skipPreflight: true,
maximumClientSideRetries: 0,
abortSignal: null,
});
feePayer
:KeyPairSigner
- The account that will pay for the transaction's feesinstructions
:Array<IInstruction>
- Array of instructions to include in the transactioncommitment
:Commitment
(optional) - Desired commitment level. Can be"processed"
,"confirmed"
(default), or"finalized"
.skipPreflight
:boolean
(optional) - Whether to skip preflight transaction checks. Enable to reduce latency, disable for more safety (default: true)maximumClientSideRetries
:number
(optional) - Maximum number of times to retry if the transaction fails. Useful for handling temporary network issues (default: 0)abortSignal
:AbortSignal | null
(optional) - Signal to abort the transaction. Use this to implement timeouts or cancel pending transactions (default: null)
Here's an example of sending a transaction with a SOL transfer instruction:
const feePayer = await connection.createWallet({
airdropAmount: lamports(1n * SOL)
});
const recipient = await generateKeyPairSigner();
// Create an instruction to transfer SOL
const transferInstruction = getTransferSolInstruction({
amount: lamports(0.1n * SOL),
destination: recipient.address,
source: feePayer
});
// Send the transaction with the transfer instruction
const signature = await connection.sendTransactionFromInstructions({
feePayer,
instructions: [transferInstruction],
maximumClientSideRetries: 3
});
console.log(`Transaction successful: ${signature}`);
console.log(`Explorer link: ${connection.getExplorerLink("tx", signature)}`);
You can also send multiple instructions in a single transaction:
// Create instructions to transfer SOL to multiple recipients
const transferInstructions = recipients.map(recipient =>
getTransferSolInstruction({
amount: lamports(0.1n * SOL),
destination: recipient.address,
source: feePayer
})
);
// Send all transfers in one transaction
const signature = await connection.sendTransactionFromInstructions({
feePayer,
instructions: transferInstructions,
commitment: "confirmed"
});
The function will automatically:
- Get a recent blockhash
- Set the fee payer
- Add compute budget instructions if needed
- Sign the transaction with the fee payer
- Send and confirm the transaction
- Retry the transaction if requested and needed
- Throws if any instruction is invalid
- Throws if the fee payer lacks sufficient SOL
- Throws if the transaction exceeds the maximum size
- Throws if the transaction is not confirmed within the timeout period
- Throws if the RPC connection fails
Gets information about a token mint, including its decimals, authority, and supply.
Returns: Promise<Mint | null>
const mint = await connection.getMint(mintAddress, commitment);
mintAddress
:Address
- Address of the token mint to get information forcommitment
:Commitment
(optional) - Desired confirmation level (default: "confirmed")
// Get information about the USDC mint
const usdcMint = await connection.getMint("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
console.log(`Decimals: ${usdcMint.data.decimals}`);
console.log(`Mint authority: ${usdcMint.data.mintAuthority}`);
console.log(`Supply: ${usdcMint.data.supply}`);
The mint information includes:
decimals
: Number of decimal places for the tokenmintAuthority
: Public key of the account allowed to mint new tokenssupply
: Current total supply of the token- Other metadata if the token uses Token Extensions
Like
makeWallets
from@solana/helpers
Creates multiple Solana wallets with the same options. Returns an array of wallet promises that can be awaited in parallel.
Returns: Array<Promise<KeyPairSigner>>
const wallets = await connection.createWallets(amount, options);
amount
:number
- Number of wallets to createoptions
: Same options ascreateWallet
:prefix
:string | null
- Prefix for wallet addressessuffix
:string | null
- Suffix for wallet addressesenvFileName
:string | null
- Path to .env file to save keypairsenvVariableName
:string
- Name of environment variable to store keypairsairdropAmount
:Lamports | null
- Amount of SOL to airdrop to each wallet
Create 3 wallets with airdrops:
const [wallet1, wallet2, wallet3] = await connection.createWallets(3, {
airdropAmount: lamports(1n * SOL),
});
If you need to create multiple wallets with different options, you can do this with:
const [sender, recipient] = await Promise.all([
connection.createWallet({
airdropAmount: lamports(1n * SOL),
}),
connection.createWallet({
airdropAmount: lamports(1n * SOL),
}),
]);
Gets the balance of tokens in a token account. You can either provide a token account address directly, or provide a wallet address and a mint address to derive the token account address.
Returns: Promise<{amount: BigInt, decimals: number, uiAmount: number | null, uiAmountString: string}>
const balance = await connection.getTokenAccountBalance({
tokenAccount, // Optional: Direct token account address to check
wallet, // Optional: Wallet address (required if tokenAccount not provided)
mint, // Optional: Token mint address (required if tokenAccount not provided)
useTokenExtensions, // Optional: Use Token-2022 program instead of Token program
});
params
:Object
- Parameters for getting token balancetokenAccount
:Address
(optional) - Direct token account address to check balance forwallet
:Address
(optional) - Wallet address (required if tokenAccount not provided)mint
:Address
(optional) - Token mint address (required if tokenAccount not provided)useTokenExtensions
:boolean
(optional) - Use Token-2022 program instead of Token program (default: false)
Get a token balance using wallet or PDA address, and mint addresses.
// Get USDC balance
const balance = await connection.getTokenAccountBalance({
wallet: "GkFTrgp8FcCgkCZeKreKKVHLyzGV6eqBpDHxRzg1brRn", // wallet or PDA address
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
});
console.log(`Balance: ${balance.uiAmount} ${balance.symbol}`);
Get a token balance using direct token account address:
const balance = await connection.getTokenAccountBalance({
tokenAccount: "4MD31b2GFAWVDYQT8KG7E5GcZiFyy4MpDUt4BcyEdJRP",
});
Get a Token-2022 token balance:
const balance = await connection.getTokenAccountBalance({
wallet: "GkFTrgp8FcCgkCZeKreKKVHLyzGV6eqBpDHxRzg1brRn",
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
useTokenExtensions: true,
});
The balance includes:
amount
: Raw token amount as a BigInt (in base units)decimals
: Number of decimal places for the tokenuiAmount
: Formatted amount with decimalsuiAmountString
: String representation of the UI amount
- Throws if neither
tokenAccount
nor bothwallet
andmint
are provided - Throws if the token account doesn't exist
- Throws if there's an error retrieving the balance
Checks if a token account is closed or doesn't exist. A token account can be specified directly or derived from a wallet and mint address.
Returns: Promise<boolean>
const isClosed = await connection.checkTokenAccountIsClosed(params);
params
:Object
- Parameters for checking token accounttokenAccount
:Address
(optional) - Direct token account address to checkwallet
:Address
(optional) - Wallet address (required if tokenAccount not provided)mint
:Address
(optional) - Token mint address (required if tokenAccount not provided)useTokenExtensions
:boolean
(optional) - Use Token-2022 program instead of Token program (default: false)
Check if a token account is closed using direct token account address:
const tokenAccount = "4MD31b2GFAWVDYQT8KG7E5GcZiFyy4MpDUt4BcyEdJRP";
const isClosed = await connection.checkTokenAccountIsClosed({ tokenAccount });
console.log(`Token account is ${isClosed ? "closed" : "open"}`);
Check if a token account is closed using wallet and mint:
const isClosed = await connection.checkTokenAccountIsClosed({
wallet: "GkFTrgp8FcCgkCZeKreKKVHLyzGV6eqBpDHxRzg1brRn",
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
useTokenExtensions: true,
});
console.log(`Token account is ${isClosed ? "closed" : "open"}`);
Gets a Program Derived Address (PDA) and its bump seed from a program address and seeds. Automatically handles encoding of different seed types.
Returns: Promise<{pda: Address, bump: number}>
const { pda, bump } = await connection.getPDAAndBump(programAddress, seeds);
programAddress
:Address
- The program address to derive the PDA fromseeds
:Array<String | Address | BigInt>
- Array of seeds to derive the PDA. Can include:- Strings (encoded as UTF-8)
- Addresses (encoded as base58)
- BigInts (encoded as 8-byte little-endian)
const programAddress = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as Address;
const seeds = [
"offer", // string seed
aliceAddress, // address seed
420n, // bigint seed
];
const { pda, bump } = await connection.getPDAAndBump(programAddress, seeds);
console.log("PDA:", pda.toString());
console.log("Bump seed:", bump);
Since this is a library, all functions in the codebase must include TSDoc comments that describe their purpose, parameters, and return types. I appreciate TSDoc can be annoying and reptitive, but we use TSdoc because VSCode and other IDEs show these comments when the function is hovered. See existing functions for examples of the required documentation format.
Make the TSDoc comments actually useful. Eg, don't tell people that
mintAuthority
is 'the mint authority'. Tell them thatmintAuthority
is 'the account that is allowed to use the token mint'.
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:
npx tsx --test --no-warnings src/tests/keypair.test.ts
We use --no-warnings
to avoid ExperimentalWarning: The Ed25519 Web Crypto API algorithm is an experimental feature
which is pretty boring once you've read it for the 50th time.
To just run tests matching the name connect
:
npx tsx --test --no-warnings --test-name-pattern="connect"