Skip to content

viem

Konekt implements the EIP-1193 request interface expected by viem’s custom() transport. This lets a viem wallet client sign messages and submit transactions through an approved WalletConnect session.

This integration is for EVM networks. Use Konekt directly, or the Solana and CosmJS bridges, for other namespaces.

Terminal window
pnpm add konekt viem

You also need a WalletConnect project ID and an EVM JSON-RPC URL.

Configure the Konekt provider before creating viem clients:

import { Provider } from "konekt";
import { evm } from "konekt/eip155";
import { http as konektHttp } from "konekt/http";
import { mainnet } from "viem/chains";
const rpcUrl = "https://ethereum.example-rpc.com";
const provider = await Provider.init({
projectId: "YOUR_PROJECT_ID",
metadata: {
name: "My app",
description: "Connect to My app",
url: window.location.origin,
icons: [new URL("/icon.png", window.location.origin).href],
},
chains: [
evm(mainnet, {
read: konektHttp(rpcUrl),
}),
],
});
provider.on("display_uri", (uri) => {
// Render the URI as a QR code, or use WalletModal from konekt-ui.
});
provider.on("request_sent", ({ url }) => {
if (url) window.location.assign(url);
});
if (!provider.connected) {
await provider.connect();
}

The read transport handles JSON-RPC reads sent through the provider. Wallet methods such as personal_sign and eth_sendTransaction still go to the connected wallet.

This snippet omits the pairing UI and cancellation for brevity. See Wallet UI for rendering the URI, aborting an attempt, and separating a user cancellation from a real failure.

Wrap the connected provider with custom():

import {
createPublicClient,
createWalletClient,
custom,
parseEther,
verifyMessage,
} from "viem";
import { mainnet } from "viem/chains";
const transport = custom(provider);
const walletClient = createWalletClient({
chain: mainnet,
transport,
});
const publicClient = createPublicClient({
chain: mainnet,
transport,
});
const [account] = await walletClient.getAddresses();
if (!account) throw new Error("The wallet did not approve an account");

Both clients use the Konekt provider:

  • wallet actions are routed to the WalletConnect session;
  • public JSON-RPC actions use the read transport configured on evm().
const message = "Sign in to My app";
const signature = await walletClient.signMessage({
account,
message,
});
const valid = await verifyMessage({
address: account,
message,
signature,
});

signMessage() sends personal_sign to the wallet. The request_sent listener can return the user to a mobile wallet while the request is pending.

const hash = await walletClient.sendTransaction({
account,
to: "0x000000000000000000000000000000000000dEaD",
value: parseEther("0.001"),
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log(receipt.status);

The wallet approves and broadcasts the transaction. Receipt polling is a read and therefore uses the configured JSON-RPC transport.

It is also valid—and common—to keep public reads outside Konekt:

import { createPublicClient, http as viemHttp } from "viem";
import { mainnet } from "viem/chains";
const httpPublicClient = createPublicClient({
chain: mainnet,
transport: viemHttp(rpcUrl),
});

Keep the same walletClient as above and use this client in place of the earlier publicClient. With this arrangement:

  • walletClient uses WalletConnect through Konekt;
  • httpPublicClient reads directly through viem;
  • the Konekt chain does not need read unless other code sends reads through provider.request(), so you can drop konektHttp from evm() entirely.

Alias the two http imports when you use both konekt/http and viem’s http() in one module.

Give each network its own Konekt read transport, and pass the result as the chains option of the Provider.init() call above:

import { base, mainnet } from "viem/chains";
const mainnetRpcUrl = "https://ethereum.example-rpc.com";
const baseRpcUrl = "https://base.example-rpc.com";
const chains = [
evm(mainnet.id, { read: konektHttp(mainnetRpcUrl) }),
evm(base.id, { read: konektHttp(baseRpcUrl) }),
];

A chain without its own read cannot serve JSON-RPC reads, so configure one per network you read from.

The standard custom(provider) transport uses the provider’s active EVM chain. Switch the wallet before using a viem client configured for another chain:

await walletClient.switchChain({ id: base.id });
const baseWalletClient = createWalletClient({
chain: base,
transport: custom(provider),
});

A viem client’s chain option describes the network but does not switch the Konekt provider by itself.

For a read-only client that must always target one configured network without changing the active chain, wrap Konekt’s per-request target:

import type { RequestArguments } from "konekt";
const baseTransport = custom({
request: (args: RequestArguments) => provider.request(args, `eip155:${base.id}`),
});
const basePublicClient = createPublicClient({
chain: base,
transport: baseTransport,
});

Viem clients do not subscribe to provider state automatically. Listen to provider events when your application stores the active account or chain:

provider.on("accountsChanged", (accounts) => {
const [next] = accounts;
if (next) updateSelectedAccount(next);
else clearWalletState();
});
provider.on("chainChanged", (chainId) => {
// Number() reads both "0x1" and the "1" some wallets send.
updateSelectedChain(Number(chainId));
});
provider.on("disconnect", () => {
clearWalletState();
});

Framework integrations such as wagmi already maintain this reactive state. Use the wagmi integration when building a React application around wagmi hooks. Use the ethers integration when the rest of the app is on ethers v6.