Skip to content

Transactions & EVM calls ​

BKC has one kind of transaction: a Cosmos transaction, protobuf-encoded, signed with ML-DSA-65 in SIGN_MODE_DIRECT. Native transfers, staking and governance are ordinary Cosmos messages. EVM writes are a BKC message, MsgExecuteEvmCall.

Why there are no Ethereum transactions ​

An Ethereum transaction is authenticated by an ECDSA signature from which the sender is recovered. BKC accounts have no ECDSA key, so no such signature can exist. The node therefore:

  • refuses eth_sendRawTransaction with raw secp256k1 transactions are not supported on this chain; use MsgExecuteEvmCall …;
  • rejects a raw MsgEthereumTx in ABCI with code 1104.

MsgExecuteEvmCall ​

protobuf
// /bkc.bkc.v1.MsgExecuteEvmCall
message MsgExecuteEvmCall {
  string sender                   = 1; // bkc1… — signs the Cosmos tx; its 20 bytes are the EVM `from`
  string to                       = 2; // 0x… target; EMPTY means contract creation
  bytes  data                     = 3; // calldata, or init code when `to` is empty
  string value                    = 4; // wei, base-10
  uint64 gas_limit                = 5; // EVM gas budget
  string max_fee_per_gas          = 6; // wei, base-10
  string max_priority_fee_per_gas = 7; // wei, base-10
  uint64 call_nonce               = 8; // the account's Cosmos sequence before this tx
}

Rules the chain enforces:

  1. One message per transaction. A wrapped call cannot be bundled with a bank send, a staking message or another EVM call (code 1105).
  2. call_nonce is the Cosmos sequence, not an EVM nonce: the handler checks account.sequence == call_nonce + 1 after the ante handler has incremented it (code 1106). Two writes in flight from one account race; the second is rejected. The SDK and Hardhat plugin serialise writes for you.
  3. gas_limit must be non-zero and no larger than the Cosmos transaction's gas limit. The Cosmos gas pays for the ~5.5 KB signed envelope and the EVM execution, so set the Cosmos gas to the EVM budget plus a base (the SDK's cosmosGasForEvmCall() uses a 600,000 base).
  4. max_priority_fee_per_gas ≤ max_fee_per_gas.

Fees are paid once ​

The Cosmos fee (gas × gas price, in bkc) is the only deduction. The EVM fee fields do not charge a second time; they only set what the GASPRICE opcode sees:

text
GASPRICE = min(max_fee_per_gas, BASEFEE + max_priority_fee_per_gas)

EVM gas is metered 1:1 onto the Cosmos gas meter. Because the fee is paid on the Cosmos side, an ethers receipt's fee reads 0; use getEvmTransactionCost(hash, restUrl) from the SDK for what was actually charged. See Gas & fees.

Hashes and receipts ​

Each wrapped call produces two hashes:

HashWhere it comes fromWhere to look it up
Cosmos tx hashSHA-256 of the signed tx bytesCometBFT /tx, REST /cosmos/tx/v1beta1/txs/{hash}
EVM tx hasha deterministic keccak256 over chain id, block, Cosmos hash and the call fieldseth_getTransactionByHash, eth_getTransactionReceipt, the explorer

The Cosmos transaction emits a standard ethereum_tx event carrying the EVM hash (ethereumTxHash), gas used, sender, recipient and any VM error, so indexers such as Blockscout build normal receipts from it. eth_getLogs, eth_getFilterLogs and receipts include wrapped-call logs. eth_subscribe("logs") and eth_newFilter do not — poll eth_getLogs instead.

Contract creation works like Ethereum: the new address is derived from the sender and its EVM nonce, so ContractFactory.deploy() predicts the address that appears in the receipt.

Doing it by hand ​

The SDK exposes the pieces if you need them:

ts
import { makeEvmCallMessage, cosmosGasForEvmCall, findEthereumTxHash } from "@bkc-network-lib/sdk";

and the node binary can send one directly — see Without Hardhat (CLI).

BKC testnet is unaudited. Do not hold value on it.