Migrating from web3.js v1 to v2

Connection is gone, everything is tree-shakable functions, and the send path is the part you rewrite. What changes and whether it is worth it.

BoltTx Team··8 min read
solanaweb3jssdkmigrationtypescripttransaction-landing

The v2 rewrite of the JavaScript SDK is not an upgrade in the usual sense. ★Almost every symbol you import today either moved, changed shape, or no longer exists.★

The parts that matter most for a trading bot are the send path and the number types.

The Shape Change

v1 gives you an object with methods. v2 gives you functions you compose.

// ★v1: one object does everything.★
const connection = new Connection(url, "confirmed");
const balance = await connection.getBalance(pubkey);

// ★v2: composed functions, tree-shakable.★
import { createSolanaRpc, address } from "@solana/web3.js";

const rpc = createSolanaRpc(url);
const { value: balance } = await rpc.getBalance(address(addr)).send();

★Note the .send().★ In v2 an RPC call builds a request object first and executes only when you send it. That is what allows per-call transport configuration, and it is the single most common source of "my call returns nothing" during a migration — a request that is never sent looks like a call that returned undefined.

The practical payoff is bundle size. A frontend importing three functions ships three functions instead of the whole library, which matters for a web app and matters very little for a backend bot.

bigint Replaces number

★This is the change most likely to introduce a silent bug.★

// v1: number — loses precision above 2^53
const lamports: number = await connection.getBalance(pubkey);

// ★v2: bigint — exact★
const { value } = await rpc.getBalance(address(addr)).send();
const lamports: bigint = value;

The v2 choice is correct — lamport amounts and u64 token balances genuinely exceed what a JavaScript number represents exactly. But mixing the two throws rather than coercing:

lamports - 5000        // ★TypeError: cannot mix BigInt and other types★
lamports - 5000n       // ★correct★

★Migrating arithmetic is where the real work is, and the compiler catches most of it.★ What it does not catch is a Number(bigint) conversion you add to silence an error — that reintroduces exactly the precision loss v2 was designed to remove.

If your SDK version is settled and transactions still miss, a free BoltTx key is one line to test the submission path.

The Send Path Rewrite

// ★v1★
const sig = await connection.sendRawTransaction(tx.serialize(), {
  skipPreflight: true,
  maxRetries: 0,
});

// ★v2★
import {
  createSolanaRpc,
  getSignatureFromTransaction,
  sendAndConfirmTransactionFactory,
} from "@solana/web3.js";

const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
await sendAndConfirm(signedTx, { commitment: "confirmed", skipPreflight: true });

★The factory pattern is the structural difference.★ v2 wants you to build a sender once, with its dependencies wired in, rather than calling a method on a connection each time.

Two things worth knowing before you adopt the built-in confirmation:

It needs rpcSubscriptions. Confirmation in v2 is subscription-based rather than polling-based, so you configure a WebSocket endpoint alongside the HTTP one.

It makes the retry decision for you. ★For a trading bot the retry loop is strategy, not plumbing★ — resend until blockhash expiry, no backoff on inclusion, terminal state resolution. If you have tuned that, keep your own loop and use v2 only for the send primitive.

Signatures Are Known Before Sending

One genuine improvement:

const sig = getSignatureFromTransaction(signedTx);   // ★no network call★

The signature is deterministic from the signed bytes, and v2 exposes that directly. A lost response after sending is no longer ambiguous — you already hold the signature to check, which removes the most common path to a duplicate execution.

This was always possible in v1 by base58-encoding the first signature; v2 just makes it obvious.

What Stays Identical

★Nothing about the chain changed.★

★A transaction built by either SDK produces identical bytes and an identical signature.★ That is what makes a gradual migration safe: v1 and v2 code can run side by side against the same wallet, and a transaction signed by one can be resent by the other.

Whether to Migrate

Worth it:

Not urgent:

★The ecosystem constraint is usually the deciding factor.★ Anchor clients, wallet adapters, and DEX SDKs migrated on their own schedules, and a project pinned to one that still expects v1 types will spend more effort on adapters than the migration saves.

A reasonable middle path: keep v1 where it works, write new modules against v2, and let the boundary be a serialized transaction — which both versions produce identically.

What Landing Looks Like

Real transactions through our delivery nodes: median confirmation 336ms — under one slot.

★No SDK version changes that number.★ Serialization and signing are microseconds against network time measured in slots, so a migration is a code quality decision rather than a performance one.

Where BoltTx Fits

We accept the bytes either SDK produces, because they are the same bytes.

Submissions route through our own delivery nodes in four regions with stake-weighted routing and no public mempool exposure, so a transaction is not observable in transit before it lands. ★Pointing v1 and v2 code at the same endpoint during a gradual migration is safe — identical bytes produce identical signatures, and a signature is included at most once.★

You sign locally. We never hold funds, never sign, and never modify transaction contents. The tip travels inside the transaction, paid on chain from your own wallet, and reverts with the transaction if it fails, because that is how Solana handles atomic transactions. You pay only on transactions that reach the chain.

Get a free API key. No monthly fee:

// v1
const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

// v2
const rpc = createSolanaRpc("https://la.bolttx.io/?api-key=YOUR_KEY");

FAQ

What is the difference between web3.js v1 and v2? v1 exposes a Connection object with methods; v2 exposes composable, tree-shakable functions with no central object. Numbers also become bigint instead of number.

Do I have to migrate to web3.js v2? No. v1 still works and nothing about the chain changed. Migrate for bundle size on frontends or for precision correctness, not because a backend bot requires it.

Why does my v2 RPC call return nothing? You probably forgot .send(). In v2 a call builds a request object and executes only when sent, so an unsent request looks like a call that returned undefined.

Why do I get a BigInt type error after migrating? Because v2 returns bigint and JavaScript refuses to mix it with number. Use bigint literals such as 5000n, and avoid Number() conversions that reintroduce precision loss.

Is bigint actually better than number? Yes for lamports and token amounts, which genuinely exceed what a number represents exactly. The precision bug it prevents only appears on large balances, which is why it survives testing.

How does sending differ in v2? You build a sender with a factory and its dependencies rather than calling a method on a connection. The built-in confirmation is subscription-based and needs an rpcSubscriptions endpoint.

Should I use v2's built-in confirmation? Not if you have a tuned retry loop. For a trading bot the retry behaviour is strategy — resend until expiry, no backoff on inclusion — so keep your loop and use v2 for the send primitive.

Can I get a signature before sending in v2? Yes, with getSignatureFromTransaction, which needs no network call. That removes the ambiguity of a lost response, since you already hold the signature to check.

Do v1 and v2 produce different transactions? No. Both produce identical bytes and identical signatures for the same instructions, which is what makes side-by-side operation and gradual migration safe.

Can I run v1 and v2 in the same codebase? Yes. Let the boundary be a serialized transaction, since both versions produce it identically. That allows new modules on v2 without rewriting working v1 code.

Does v2 make my transactions land faster? No. Serialization and signing are microseconds against network time measured in slots. Landing speed comes from fees, routing, and retry behaviour, none of which the SDK changes.

What breaks most often during migration? Arithmetic mixing bigint and number, and forgetting .send(). The compiler catches the first category, which is why the migration is tedious rather than risky.

Is Anchor compatible with web3.js v2? It depends on the version you use, and ecosystem libraries migrated on their own schedules. A dependency still expecting v1 types is usually the reason a migration is deferred.

How do addresses differ in v2? PublicKey is replaced by an address() helper producing a branded string type. It is lighter, and it means address handling is a type-level concern rather than a class instance.

What is the benefit of tree-shaking here? A frontend importing three functions ships three functions rather than the whole library. It is a real gain for web apps and close to irrelevant for a backend bot.

Should new projects start on v2? Generally yes, provided the libraries you depend on support it. Starting there avoids a migration later, but a dependency that still requires v1 outweighs that benefit.

← Back to all posts