Ssoftalpha0

solana-web3-to-kit

Migrates @solana/web3.js v1 to @solana/kit (the new functional, tree-shakeable Solana SDK).

solanaweb3.js@solana/web3.js@solana/kitmigrationblockchain
Public
2 executions

Run locally

npx codemod solana-web3-to-kit

solana-web3-to-kit

A Codemod workflow that migrates @solana/web3.js v1 codebases to @solana/kit — the new functional, tree-shakeable Solana SDK.

Built with jssg (deterministic AST rewrites) plus an AI step for structural edge cases.

Why

@solana/web3.js v1 is a class-based, monolithic SDK. @solana/kit (formerly @solana/web3.js v2) is a functional, tree-shakeable rewrite with bigint numerics, async signers, and a pipe-based transaction builder. The migration changes hundreds of call-sites in any non-trivial dApp. This codemod automates the deterministic 80–95% so that engineers only review the structural edges.

Coverage on a real repo

Validated end-to-end on solana-developers/helpers — a 2,000-LOC Solana TypeScript utility library actively used in tutorials.

PatternRemaining
new PublicKey(x)1 (JSDoc comment — correctly skipped)
Keypair.generate()0
Keypair.fromSecretKey(x)0
SystemProgram.transfer({...})0
SystemProgram.programId0
clusterApiUrl(x)0
PublicKey.findProgramAddressSync(...)0
new Connection(...)0

100% coverage on every deterministic pattern. Zero false positives. 12 files changed, 223 insertions, 273 deletions.

What it rewrites (deterministically)

Calls and constructors

v1 patternkit pattern
new PublicKey(x)address(x)
Keypair.generate()await generateKeyPairSigner()
Keypair.fromSecretKey(x)await createKeyPairSignerFromBytes(x)
const [pda, bump] = PublicKey.findProgramAddressSync(seeds, prog)const { address: pda, bump } = await getProgramDerivedAddress({ programAddress: prog, seeds })
clusterApiUrl('devnet')'https://api.devnet.solana.com' (and testnet, mainnet-beta)
SystemProgram.transfer({ fromPubkey, toPubkey, lamports })getTransferSolInstruction({ source, destination, amount }) (all shorthand variants)
SystemProgram.programIdSYSTEM_PROGRAM_ADDRESS
new Connection(url, commitment?)createSolanaRpc(url) (commitment dropped — kit applies per-call)

RPC method propagation<rpcVar>.<method>(...)<rpcVar>.<method>(...).send() for ~40 whitelisted RPC methods (getBalance, getLatestBlockhash, requestAirdrop, sendTransaction, etc.), where <rpcVar> is discovered via:

  • variable declarations: const conn = new Connection(...) / const conn: Connection
  • function parameters: (connection: Connection, ...)
  • class properties: constructor(public conn: Connection) (resolves this.conn.<method>(...) too)
  • never via name-baseline guesses (avoids the wallet-adapter false-positive class of bug)

Signer property renames<signerVar>.publicKey<signerVar>.address, <signerVar>.secretKey<signerVar>.privateKey, where <signerVar> is discovered from:

  • direct declarations: const kp = Keypair.generate() / Keypair.fromSecretKey(x)
  • destructured arrays: const [a, b] = [Keypair.generate(), Keypair.generate()]
  • typed declarations and parameters: : Keypair
  • class properties via this.kp.publicKey resolution
  • never on arbitrary .publicKey access (so wallet-adapter wallet.publicKey is safely preserved)

Type annotationsKeypairKeyPairSigner, PublicKeyAddress, ConnectionRpc<SolanaRpcApi>. Matches type_identifier AST nodes only, never the value-position usages.

Imports — every rewrite adds the new specifiers to @solana/kit / @solana-program/system and merges idempotently with any pre-existing import from the same module. The original @solana/web3.js import is left intact for the AI step / human to clean up unused specifiers.

Safety guarantees

  1. Gated on @solana/web3.js import. The codemod first scans for import {...} from '@solana/web3.js' and only rewrites identifiers actually imported from there. A local class named SystemProgram or PublicKey is left untouched. (See tests/11-no-solana-imports/.)
  2. No-op on unrelated files. Files that don't import from @solana/web3.js produce zero edits. (See tests/08-no-op/.)
  3. AST-precise patterns. Matches use tree-sitter, not regex — patterns inside comments, strings, or template literals are correctly skipped. (See idl.ts:53 JSDoc case in the real-repo validation.)
  4. Deterministic and idempotent. Running twice produces the same result.

What the AI step covers (edge cases)

The deterministic step leaves these to the AI step (workflow.yamlai):

  • {value} unwrap on RPC results that the deterministic .send() propagation cannot infer ((await rpc.getBalance(addr).send()).value)
  • new Transaction().add(ix) + sendAndConfirmTransaction → full pipe(createTransactionMessage, ...) rewrite + factory hoisting
  • BN arithmetic → native bigint operators
  • connection.onAccountChange(...) callback → accountNotifications().subscribe() AsyncIterable + AbortController
  • pk.toBase58() / .equals() removal on receivers we can't statically prove are addresses/signers
  • BufferUint8Array + encoder swaps
  • TransactionInstruction literal field renames (isSigner/isWritableAccountRole)
  • Async propagation: marking enclosing functions async when newly-async calls are introduced
  • Removing now-unused specifiers from @solana/web3.js imports

The AI step uses Anthropic Claude with the bash, glob, and str_replace_based_edit_tool tools, scoped to TypeScript files only.

Layout

text

Usage

bash

Development

bash

Scoring methodology

Following the hackathon evaluation formula
100 × (1 − ((FP × wFP) + (FN × wFN)) ÷ (N × (wFP + wFN))):

  • N (total real patterns in solana-developers/helpers): 60
  • FP (incorrect changes): 0
  • FN (missed real patterns): 0 (the 1 unmatched case is a JSDoc comment, not a real call site)
  • Score: 100

License

MIT

Before

This is one example from the codemod's test cases. The codemod may handle many more cases.

Ready to contribute?

Build your own codemod and share it with the community.