ethers signer
You write ethers. BkcSigner returns an ordinary ethers v6 Signer; Contract, ContractFactory, tx.wait() and queryFilter behave as on any EVM chain.
Create a signer
import { BkcSigner, GasPrice } from "@bkc-network-lib/sdk";
const endpoints = {
evmRpcUrl: "https://rpc.bkc.network", // reads
cosmosRpcUrl: "https://comet-rpc.bkc.network", // writes are broadcast here
gasPrice: GasPrice.fromString("5000000bkc"),
};
const signer = await BkcSigner.fromMnemonic(process.env.MNEMONIC!, endpoints);
// or: await BkcSigner.fromSeed(seedBytes, endpoints) // 32-byte ML-DSA-65 seed
// or: await BkcSigner.fromWallet(offlineDirectSigner, endpoints)Always pass gasPrice
Without it the SDK falls back to bkcChain.gasPrice, 66,666,666,667 bkc per gas — about 13,000× the chain minimum. Transactions still succeed, but each one costs that much more (a native send ≈ 0.017 BKC instead of ≈ 0.0000013 BKC).
Deploy, write, read
import { ContractFactory, Contract } from "@bkc-network-lib/sdk";
const factory = new ContractFactory(abi, bytecode, signer);
const contract = await factory.deploy();
await contract.waitForDeployment();
await (await contract.set(42)).wait(); // a write: MsgExecuteEvmCall, signed with ML-DSA-65
await contract.get(); // a read: eth_call, no signing → 42nNative transfers are writes too:
const tx = await signer.sendTransaction({ to: "0x…", value: 10n ** 18n }); // 1 BKC
await tx.wait();What works
Measured against the live chain:
| Works unchanged | Cannot work (throws with an explanation) |
|---|---|
getNetwork, getBlockNumber, getBalance, getBlock | signer.signMessage, signTypedData — secp256k1 only |
ContractFactory.deploy(), contract.method(), estimateGas | eth_sendRawTransaction |
tx.wait(), getTransaction, receipt log decoding | queryFilter with no block range — the node caps a log query at 10,000 blocks |
queryFilter(filter, fromBlock, toBlock) | |
signer.sendTransaction({ to, value }) |
ethers nests the explanation one level down, at error.error.message; error.shortMessage only says "could not coalesce error".
Replacements
| Instead of | Use | You get |
|---|---|---|
receipt.fee (always 0) | getEvmTransactionCost(hash, restUrl) | the fee actually charged, the Cosmos hash and gas |
queryFilter with no range | getLogsPaged(provider, filter, { maxWindows }) | logs walked backwards one 10,000-block window at a time |
signer.signMessage | signMessage(wallet, text) | an ADR-36 document signed with ML-DSA-65 |
import { getEvmTransactionCost, getLogsPaged, signMessage, verifyMessage } from "@bkc-network-lib/sdk";
const cost = await getEvmTransactionCost(receipt.hash, "https://api.bkc.network");
// getLogsPaged takes an EIP-1193 provider and a plain { address, topics } filter
import { JsonRpcProvider, id } from "ethers";
const rpc = new JsonRpcProvider("https://rpc.bkc.network");
const eip1193 = { request: ({ method, params }) => rpc.send(method, params ?? []) };
const { logs, reachedBlock } = await getLogsPaged(
eip1193,
{ address: tokenAddress, topics: [id("Transfer(address,address,uint256)")] },
{ maxWindows: 5 }, // 5 × 10,000 blocks, newest first
);
// reachedBlock: the oldest block searched — continue from there if you need more historyConcurrency
call_nonce is the account's Cosmos sequence, and the chain requires it to match exactly. Two writes sent at once from the same signer would collide; send them one after another (await tx.wait() between) or use separate accounts.
Lower-level pieces
For custom tooling: makeEvmCallMessage, withGasHeadroom, cosmosGasForEvmCall, findEthereumTxHash, toEvmAddress, tryToEvmAddress, and createBkcEip1193Provider() — a raw EIP-1193 provider for libraries that wire their own stack (viem, wallets).
