Okay, look. Every other tutorial on Solana SDKs jumps straight into "install this, run that" without telling you why Gill even exists. You end up copying code, it half works on devnet, then bombs on mainnet because you don't get the basics like blockhashes or signers. Frustrating as hell. I've wasted hours debugging that crap. The thing is, Gill isn't just another wrapper-it's Web3.js v2 but way less painful for real tasks like minting tokens or transfers. It cuts the boilerplate while letting you drop low level if you need to. Sound familiar? That's what we're fixing here. No fluff. Just steps that actually work.
So, fire up your terminal. Make a folder.
mkdir gill solana fun && cd gill solana fun
Init it quick.
npm init -y
Now grab Gill and the basics. I usually use pnpm 'cause it's faster, but npm's fine too.
pnpm install gill esrun
pnpm install -D @types/node typescript ts node
esrun? It's for running TypeScript files without compiling every time. Life saver. Create your first file: touch index.ts. Boom. You're set. Fees on Solana? Like ~0.000005 SOL per signature. Devnet's free to test.
Why does this matter? Old guides make you install 10 packages for token stuff. Gill bundles Token-2022, Compute Budget, all that. One import, done.
Most noobs mess up RPC connections. Use crappy endpoints that rate limit or go down. Stick to devnet first. In index.ts, start here:
import { createSolanaClient } from "gill"; const { rpc, sendAndConfirmTransaction } = createSolanaClient({ urlOrMoniker: "devnet",
});
That's it. No pipe chains, no separate subscriptions. rpc for queries, sendAndConfirmTransaction handles sending + confirming. Test it:
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
console.log("Latest blockhash:", latestBlockhash.blockhash);
Run with npx esrun index.ts. See a hash? Good. Now you're talking to Solana. Mainnet? Swap "devnet" to "mainnet". But don't. Not yet.
In my experience, forgetting the value: destructuring trips everyone up. It's how Gill cleans up Web3.js responses. Clean code, less bugs.
Okay, you need a keypair. Generate one? Or load from file. I usually keep a dev wallet in ~/.config/solana/id.json from solana keygen new. Load it like this:
import { getBase58Codec, createKeyPairSignerFromBytes } from "gill"; const keypairBase58 = "YOURBASE58SECRET_HERE"; // Or read from file
const keypairBytes = getBase58Codec().encode(keypairBase58);
const feePayer = await createKeyPairSignerFromBytes(keypairBytes);
console.log("Fee payer address:", feePayer.address);
Never hardcode mainnet keys, duh. For devnet, airdrop SOL:
// Use solana airdrop CLI: solana airdrop 2 <your address> --url devnet
Fee payer's your signer for tx fees. ~0.000005 SOL per tx, remember? Generate fresh? const mint = await generateKeyPairSigner();. Easy.
Here's where Gill shines. Web3.js v2? 50 lines of extensions, pointers, rent calc. Gill? One builder. Let's make "OPOS" token, 2 decimals, metadata.
import { buildCreateTokenTransaction, TOKEN2022PROGRAMADDRESS } from "gill/programs/token"; const mint = await generateKeyPairSigner(); const createTokenTx = await buildCreateTokenTransaction({ feePayer, latestBlockhash, mint, metadata: { isMutable: true, name: "Only Possible On Solana", symbol: "OPOS", uri: "https://raw.githubusercontent.com/solana developers/opos asset/main/assets/Climate/metadata.json", }, decimals: 2, tokenProgram: TOKEN2022PROGRAMADDRESS
});
Sign it.
import { signTransactionMessageWithSigners, getSignatureFromTransaction, getExplorerLink } from "gill"; const signedTx = await signTransactionMessageWithSigners(createTokenTx, [feePayer]);
const sig = getSignatureFromTransaction(signedTx);
console.log(getExplorerLink({ transaction: sig, cluster: "devnet" })); await sendAndConfirmTransaction(signedTx);
Run it. Check the explorer link. Token created! One tx, metadata included. Without Gill? See that huge code block in other guides? Nightmare.
Potential issue: "Insufficient funds". Airdrop more SOL. Or "Invalid blockhash"-grab fresh one before each tx.
Full code. Add to your file:
import { buildMintTokensTransaction, getAssociatedTokenAddress } from "gill/programs/token";
import { address } from "gill"; // For Address type const destWallet = address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC mint, or yours
const destATA = await getAssociatedTokenAddress({ mint: mint.address, owner: destWallet, programId: TOKEN2022PROGRAMADDRESS }); const mintTx = await buildMintTokensTransaction({ feePayer, latestBlockhash, mint: mint.address, tokenOwner: feePayer.address, // Mint authority dest: destATA, amount: 100n * 10n BigInt(2), // 100 tokens, 2 decimals tokenProgram: TOKEN2022PROGRAMADDRESS
}); const signedMint = await signTransactionMessageWithSigners(mintTx, [feePayer]);
const mintSig = getSignatureFromTransaction(signedMint);
console.log(getExplorerLink({ transaction: mintSig, cluster: "devnet" }));
await sendAndConfirmTransaction(signedMint);
Decimals trip people. 2 decimals? Multiply by 100. BigInt 'cause SOL numbers are huge. Minted to non owned ATA? It creates it auto. Magic.
| Problem | Why? | Fix |
|---|---|---|
| ATA doesn't exist | No token account for that mint/owner | Gill builder creates it if you're minting enough |
| "Invalid account owner" | Wrong program ID | Use TOKEN2022PROGRAM_ADDRESS for Token-2022 |
| Tx too big | Missing compute budget | Gill adds it auto in most builders |
| 0 tokens minted | Amount 0 or wrong decimals | Double check BigInt calc |
Debug tip: Enable Gill's debug mode. createSolanaClient({ debug: true }). Logs everything. Browser console too.
Similar vibe. Build, sign, send.
import { buildTransferTokensTransaction } from "gill/programs/token"; const sourceATA = await getAssociatedTokenAddress({ mint: mint.address, owner: feePayer.address, programId: TOKEN2022PROGRAM_ADDRESS });
const destATA2 = await getAssociatedTokenAddress({ mint: mint.address, owner: address("SomeOtherAddressHere"), programId: TOKEN2022PROGRAMADDRESS }); const transferTx = await buildTransferTokensTransaction({ feePayer, latestBlockhash, source: sourceATA, dest: destATA2, owner: feePayer.address, amount: 50n * 10n BigInt(2), // 50 tokens tokenProgram: TOKEN2022PROGRAMADDRESS
}); const signedTransfer = await signTransactionMessageWithSigners(transferTx, [feePayer]);
console.log(getExplorerLink({ transaction: getSignatureFromTransaction(signedTransfer), cluster: "devnet" }));
await sendAndConfirmTransaction(signedTransfer);
Your full index.ts now does create → mint → transfer. Three explorer links. Pretty much a mini dapp.
What's next? Fees stay low, ~0.000005 SOL each. Batch 'em? Use Versioned Transactions, Gill supports.
Building frontend? @gillsdk/react. Install it. Hooks like:
useBalance(address): Lamports to SOL.useLatestBlockhash(): Fresh hash.useTokenAccount(mint, owner): ATA balance.useSignatureStatuses(sigs): Confirmations.Example component? Wrap your app in their provider. Hook grabs wallet balance, button mints on click. No state management hell.
import { useBalance, useTokenMint } from "@gillsdk/react"; function TokenBalance() { const { data: balance } = useBalance(wallet.address); const { data: mintInfo } = useTokenMint(mintAddress); return <p>Balance: {balance?.lamports / 1e9} SOL</p>;
}
Still beta ish, but works great. For Node/backends, pure Gill.
Sometimes you need custom. Gill's got escape hatches. Add memos:
import { getAddMemoInstruction } from "gill/programs/memo"; const memoIx = getAddMemoInstruction({ memo: "Built with Gill!" });
Pipe it into a tx message. Or compute units: getSetComputeUnitLimitInstruction({ units: 300_000 });. Gill programs/ has all: system, token22, metaplex metadata.
Migration from Web3.js/Kit? Swap imports to "gill", uninstall @solana program/*, done. 5 mins.
Tx fails with "Program Error: 0x1"? Invalid keys. Check addresses.
"Blockhash not found"? Always fetch latest before building.
Rate limited? Use Helius or QuickNode RPC. Devnet's public ones suck under load.
Explorer links wrong cluster? Add cluster: "devnet" to getExplorerLink.
Big tx? Gill auto adds compute budget, but tweak if needed: pass computeUnits: 1400000 to builder.
Honestly, enable debug everywhere. const client = createSolanaClient({ debug: true });. Prints tx breakdowns. Saved my ass debugging a failed mint last week.
Multiple signers? Pass array to signTransactionMessageWithSigners([feePayer, otherSigner]).
Priority fees? getSetComputeUnitPriceInstruction({ microLamports: 1000n });. ~0.001 SOL extra for speed on mainnet.
Subscriptions? rpcSubscriptions from client. Listen to account changes.
Node server? Same code. Serverless? Works in Vercel.
I usually wrap builders in functions: async function mintToUser(user: Address, amount: bigint) { .. }. Reusable AF.
| Classic Token | Token-2022 (Gill Default) | |
|---|---|---|
| Extensions | No | Metadata, transfer fees, etc. |
| Program ID | TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA | TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb |
| Fees | Standard | Supports 0.3% transfer fees native |
| Gill Support | Yes | Full builders |
Use 2022 for new tokens. Metadata baked in, no extra programs.
const kp = await generateKeyPairSigner();import { getBase58Codec } from "gill"; const secret = getBase58Codec().encode(await kp.signerKeypair.secretKey); fs.writeFileSync('dev.json', JSON.stringify(Array.from(secret)));Don't commit secrets. Use .env or files.