Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Solana development skill covering @solana/kit v5, Anchor programs, LiteSVM testing, and security patterns.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/kit/gotchas.md
1---2title: Common Gotchas3description: Common type errors and runtime pitfalls with @solana/kit and their fixes, including signer types, lifetime assertions, plugin ordering, and account existence.4---56# Solana Kit Gotchas78Common type errors and runtime pitfalls with their fixes.910## Plugin Client Gotchas1112### Plugin Ordering — Type Error1314**Cause:** Plugins installed before their dependencies. `solanaRpc` / `solanaLocalRpc` / `solanaDevnetRpc` / `litesvm` all require a `payer` to be installed first; low-level `rpcTransactionPlanner` / `rpcTransactionPlanExecutor` require `rpc` and `payer`.1516```ts17// ❌ Type error — solanaRpc requires payer18createClient()19.use(solanaRpc({ rpcUrl: url }))20.use(signer(mySigner));2122// ✅ Fix: signer first (sets payer + identity), then RPC bundle23createClient()24.use(signer(mySigner))25.use(solanaRpc({ rpcUrl: url }));26```2728### Forgetting to `await` Async Client2930**Cause:** Some plugins (e.g., `signerFromFile`, `generatedSigner`, `generatedSignerWithSol`) are async, and `.use()` automatically threads the promise through the chain.3132```ts33// ❌ Runtime error — client is a Promise, not a client34const client = createClient()35.use(signerFromFile('./id.json'))36.use(solanaLocalRpc());37client.sendTransaction([ix]); // TypeError: not a function3839// ✅ Fix: await the client40const client = await createClient()41.use(signerFromFile('./id.json'))42.use(solanaLocalRpc());43await client.sendTransaction([ix]);44```4546---4748## Type Errors4950### `IInstruction` does not exist5152**Cause:** Using old type name from legacy web3.js.5354```ts55// ❌ Type error56import { IInstruction } from '@solana/kit';5758// ✅ Fix: Use Instruction59import type { Instruction } from '@solana/kit';60```6162### "Transaction message must be signed"6364**Cause:** Trying to send unsigned message (manual pipeline only).6566```ts67// ✅ Fix: Assert fully signed68import { assertTransactionMessageIsFullySigned } from '@solana/transaction-messages';69assertTransactionMessageIsFullySigned(message);70```7172### "Missing blockhash lifetime"7374**Cause:** Message missing lifetime before signing/sending (manual pipeline only).7576```ts77// ✅ Fix: Assert lifetime exists78import { assertTransactionMessageHasBlockhashLifetime } from '@solana/transaction-messages';79assertTransactionMessageHasBlockhashLifetime(message);80```8182### `signAndSendTransactionMessageWithSigners` type error8384**Cause:** Fee payer set as address, not signer.8586```ts87// ❌ Type error — fee payer is address only88setTransactionMessageFeePayer(address, message);8990// ✅ Fix: Use signer version91setTransactionMessageFeePayerSigner(signer, message);92```9394### Wrong signer type for wallet9596**Cause:** Using `TransactionSigner` for wallet that needs to send.9798```ts99// Wallets that submit transactions need TransactionSendingSigner100type TransactionSendingSigner = {101signAndSendTransactions(txs): Promise<SignatureBytes[]>;102};103```104105### Missing Lifetime Type Assertion106107**Cause:** `sendAndConfirm` requires typed lifetime assertion (manual pipeline only).108109```ts110// ❌ Type error: Property '"__transactionWithBlockhashLifetime"' is missing111const signed = await signTransactionMessageWithSigners(message);112await sendAndConfirm(signed, { commitment: 'confirmed' });113114// ✅ Fix: Assert lifetime + size types115assertIsTransactionWithBlockhashLifetime(signed);116assertIsTransactionWithinSizeLimit(signed);117await sendAndConfirm(signed, { commitment: 'confirmed' });118```119120### Missing `TransactionWithinSizeLimit`121122**Cause:** Recent Kit versions require size assertion for send factories.123124```ts125// ✅ Fix: Add size assertion126import { assertIsTransactionWithinSizeLimit } from '@solana/kit';127assertIsTransactionWithinSizeLimit(signed);128```129130### RPC URL String vs Cluster Wrapper131132**Cause:** Using `devnet()`/`mainnet()` wrappers when raw URL string expected.133134```ts135// ❌ May cause issues136import { devnet } from '@solana/rpc-types';137const rpc = createSolanaRpc(devnet('https://my-custom-endpoint.com'));138139// ✅ Simple: use raw URL strings directly140const rpc = createSolanaRpc('https://api.devnet.solana.com');141```142143---144145## Runtime Errors146147### "Account does not exist"148149**Cause:** Decoding account that may not exist.150151```ts152// ❌ Runtime error if account missing153const account = await fetchEncodedAccount(rpc, address);154const decoded = decodeAccount(account, decoder);155156// ✅ Fix: Assert existence first157const account = await fetchEncodedAccount(rpc, address);158assertAccountExists(account);159const decoded = decodeAccount(account, decoder);160```161162### Blockhash expired after CU estimation163164**Cause:** Simulation takes time, blockhash ages out. Only applies to manual pipeline — plugin clients handle this automatically.165166```ts167// ❌ Blockhash may expire168let message = pipe(...blockhash...);169message = await estimateAndUpdateCU(message);170await signAndSendTransactionMessageWithSigners(message);171172// ✅ Fix: Refresh blockhash AFTER estimation173let message = pipe(...blockhash...);174message = await estimateAndUpdateCU(message);175const { value: freshBlockhash } = await rpc.getLatestBlockhash().send();176message = setTransactionMessageLifetimeUsingBlockhash(freshBlockhash, message);177await signAndSendTransactionMessageWithSigners(message);178```179180### Simulation fails with "account not found"181182**Cause:** Account doesn't exist yet (e.g., PDA not initialized).183184```ts185const account = await fetchEncodedAccount(rpc, address);186if (!account.exists) {187// Handle missing account — may need to create it first188}189```190191---192193## Quick Reference194195| Gotcha | Fix |196|--------|-----|197| Plugin ordering type error | Install dependencies before dependents (`signer()` before `solanaRpc`/`litesvm`) |198| Forgot to `await` async client | `const client = await createClient().use(signerFromFile(...)).use(solanaLocalRpc())` |199| `IInstruction` doesn't exist | Use `Instruction` from `@solana/kit` |200| "Transaction message must be signed" | `assertTransactionMessageIsFullySigned(msg)` |201| "Missing blockhash lifetime" | `assertTransactionMessageHasBlockhashLifetime(msg)` |202| Blockhash expired after CU estimation | Refresh blockhash AFTER `estimateAndUpdateCU()` |203| `signAndSendTransactionMessageWithSigners` type error | Use `setTransactionMessageFeePayerSigner` (not address) |204| Account doesn't exist runtime error | `assertAccountExists(account)` before decode |205| Wrong signer type for wallet | Use `TransactionSendingSigner` for wallets |206| Missing lifetime type on send | `assertIsTransactionWithBlockhashLifetime(signed)` |207| Missing size type on send | `assertIsTransactionWithinSizeLimit(signed)` |208| Durable nonce send type error | `assertIsTransactionWithDurableNonceLifetime(signed)` |209| `lifetimeConstraint` lost after deserialize | Re-attach `lifetimeConstraint` metadata manually |210| RPC URL wrapper issues | Use raw URL strings instead of `devnet()`/`mainnet()` |211