Appearance
Frontend Integration
Connecting a frontend to Zink uses the familiar SVM app stack: standard client libraries, wallet adapters, and a Zink RPC endpoint. This page covers practical patterns for browser-based, mobile, and desktop applications.
Upstream-compatible
Frontend libraries that speak standard Solana JSON-RPC work with Zink by changing the RPC URL. The JSON-RPC and WebSocket protocols are the same.
Choosing a client library
The Solana TypeScript ecosystem has evolved through several packages. Here is the current landscape:
| Package | Status | Notes |
|---|---|---|
@solana/kit | Current | Modular, tree-shakeable successor to web3.js. Recommended for new projects. |
@solana/web3.js (v2) | Current | Re-export of @solana/kit for backwards-compatible package name. |
@solana/web3.js (v1.x) | Legacy | Widely used. Still works but is in maintenance mode. |
@solana/web3-compat | Bridge | Compatibility layer to ease migration from v1 to v2 APIs. |
@solana/client | Current | Lower-level RPC client used internally by @solana/kit. |
@solana/react-hooks | Current | React bindings for @solana/kit — connection, wallet, account subscriptions. |
Zink recommendation
Use the SDK version recommended for your application stack and deployed program versions. Consistency across the ecosystem reduces integration friction.
Establishing a connection
With @solana/kit (recommended)
typescript
import { createSolanaRpc, createSolanaRpcSubscriptions } from "@solana/kit";
const rpc = createSolanaRpc("https://testnet-rpc.z.ink");
const rpcSubscriptions = createSolanaRpcSubscriptions("wss://testnet-rpc.z.ink");
// Fetch a recent blockhash
const { value: blockhash } = await rpc
.getLatestBlockhash({ commitment: "confirmed" })
.send();With @solana/web3.js v1 (legacy)
typescript
import { Connection } from "@solana/web3.js";
const connection = new Connection("https://testnet-rpc.z.ink", "confirmed");
const blockHeight = await connection.getBlockHeight();Zink-specific
The currently published public endpoint is https://testnet-rpc.z.ink. For subscriptions, use the matching WebSocket host (wss://testnet-rpc.z.ink) or the operator-provided subscription URL for unpublished environments.
Wallet adapter setup
The @solana/wallet-adapter libraries provide a framework-agnostic interface for connecting browser extension wallets (Phantom, Backpack, Solflare, etc.) to your application.
React setup
bash
npm install @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-walletstsx
import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets";
const wallets = [new PhantomWalletAdapter()];
function App() {
return (
<ConnectionProvider endpoint="https://testnet-rpc.z.ink">
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>
{/* Your app */}
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
);
}Wallets connect to whatever RPC endpoint your application specifies — no wallet-side configuration is needed to use Zink. The wallet signs transactions locally; your app submits them to the Zink RPC.
With @solana/react-hooks
If you are building with @solana/kit, the @solana/react-hooks package provides React hooks that integrate directly with the newer API:
tsx
import { SolanaProvider } from "@solana/react-hooks";
function App() {
return (
<SolanaProvider
rpcUrl="https://testnet-rpc.z.ink"
wsUrl="wss://testnet-rpc.z.ink"
>
{/* Your app */}
</SolanaProvider>
);
}Building and sending transactions
Constructing a transaction
typescript
import {
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
compileTransaction,
} from "@solana/kit";
const message = createTransactionMessage({ version: 0 });
const withFeePayer = setTransactionMessageFeePayer(walletAddress, message);
const withBlockhash = setTransactionMessageLifetimeUsingBlockhash(
blockhash,
withFeePayer
);
const withInstruction = appendTransactionMessageInstruction(
myInstruction,
withBlockhash
);
const transaction = compileTransaction(withInstruction);Sending and confirming
typescript
const signature = await rpc.sendTransaction(signedTransaction).send();
// Wait for confirmation
const confirmation = await rpc
.confirmTransaction(signature, { commitment: "confirmed" })
.send();Priority fees
If the Zink cluster is under load, you can attach a priority fee to improve transaction landing rates. Priority fees are set via a Compute Budget instruction:
typescript
import { ComputeBudgetProgram } from "@solana/web3.js";
// Set the price you're willing to pay per compute unit (in micro-lamports)
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 1000,
});
// Optionally cap your compute unit budget
const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 200_000,
});Zink-specific
Fee parameters on Zink may differ from Solana mainnet. Query the cluster's recent priority fee levels with getRecentPrioritizationFees to calibrate your fee strategy. See Fees for details.
Address Lookup Tables
Versioned transactions (v0) support Address Lookup Tables (ALTs) to compress transaction size when referencing many accounts. This is the same mechanism as Solana mainnet:
typescript
import { AddressLookupTableProgram } from "@solana/web3.js";
// Create a lookup table
const [createIx, lookupTableAddress] =
AddressLookupTableProgram.createLookupTable({
authority: wallet.publicKey,
payer: wallet.publicKey,
recentSlot: await connection.getSlot(),
});
// Extend the table with addresses
const extendIx = AddressLookupTableProgram.extendLookupTable({
lookupTable: lookupTableAddress,
authority: wallet.publicKey,
payer: wallet.publicKey,
addresses: [programId, tokenMint, /* ... */],
});Using ALTs is recommended when your transactions reference more than ~20 accounts — common in complex DeFi or game interactions.
Subscribing to account changes
Real-time subscriptions use WebSocket under the hood. This is useful for updating UI when on-chain state changes.
With @solana/kit
typescript
const accountNotifications = await rpcSubscriptions
.accountNotifications(accountAddress, { commitment: "confirmed" })
.subscribe();
for await (const notification of accountNotifications) {
console.log("Account updated:", notification);
}With @solana/web3.js v1
typescript
connection.onAccountChange(accountPublicKey, (accountInfo) => {
console.log("Account data changed:", accountInfo.data);
});Network switching
If your application needs to support multiple clusters, keep published endpoints explicit and inject unpublished ones through environment configuration:
typescript
const CLUSTERS = {
"zink-testnet": "https://testnet-rpc.z.ink",
"solana-mainnet": "https://api.mainnet-beta.solana.com",
} as const;
// For unpublished Zink Mainnet or operator-only environments, load the URL
// from an environment variable instead of hardcoding a guessed hostname.
type ClusterName = keyof typeof CLUSTERS;
function getConnection(cluster: ClusterName) {
return new Connection(CLUSTERS[cluster], "confirmed");
}Wallet adapters handle the endpoint you give them — users do not need to manually switch networks in their wallet.
Error handling patterns
Transactions can fail for many reasons. Common frontend error handling:
typescript
try {
const signature = await sendTransaction(transaction, connection);
await connection.confirmTransaction(signature, "confirmed");
} catch (err) {
if (err instanceof SendTransactionError) {
// Transaction simulation failed — inspect logs
const logs = err.logs;
console.error("Transaction failed:", logs);
}
}Simulation failures include program logs that pinpoint which instruction failed and why. Always surface these logs during development.
Next steps
- SDKs — full SDK reference for TypeScript, Rust, Python, and more
- Fees — understanding compute budgets and priority fees on Zink
- Transactions — transaction anatomy and versioned transaction details
- Clusters & Environments — Zink endpoint URLs