EVM Transactions
EvmTransaction and the EvmTxType enum — the required chainId (no Ethereum default), EvmTxBuildParams, auto-filled nonce/gas/fees, and 256-bit field validation at assignment time.
EvmTransaction is the concrete EVM transaction. You normally get one from address.buildTransaction(params), which binds the address so the transaction can sign, send, and simulate. You can also construct one directly when you already hold the from address and want full control over every field.
Constructing an EvmTransaction
There are two equivalent constructor forms — an object form and a positional form. Both require chainId.
import { EvmTransaction } from "wative-core";
// Object form
const tx = new EvmTransaction({
from: "0xYourAddress…",
to: "0x1234567890123456789012345678901234567890",
value: 1_000_000_000_000_000n,
chainId: 1,
});
// Positional form: (from, to, value, data, opts)
const tx2 = new EvmTransaction(
"0xYourAddress…",
"0x1234567890123456789012345678901234567890",
1_000_000_000_000_000n,
undefined,
{ chainId: 1, type: 2 },
);Building from an address is the common path, and it is where chainId becomes optional — it is filled in from network.chainId:
const evm = acc.wallets[0].evm; // an EvmSigner
const tx = await evm.buildTransaction({
to: "0x1234567890123456789012345678901234567890",
value: 1_000_000_000_000_000n,
chainId: 1, // optional here — omit to take it from the address's network
nonce: 0,
gasLimit: 21000n,
maxFeePerGas: 50_000_000_000n,
maxPriorityFeePerGas: 1_000_000_000n,
type: 2,
});Required chainId — there is no default
A signed transaction is a bearer instrument: anyone holding the bytes can broadcast them. Guessing the chain would not produce a harmless mistake — it would produce a valid transaction on a chain the caller never named. So a directly-constructed EvmTransaction requires chainId and refuses to default (behavior since 2.4.1):
new EvmTransaction({ from, to, value: 1n });
// PARAMETER_ERROR: EvmTransaction.chainId is required — a transaction must name
// the chain it is signed for, and there is no default.There is no network field on a transaction. To take the chain from an address's network, build with address.buildTransaction({ … }) (which fills chainId from network.chainId) rather than passing a network: property — that property is not part of EvmTxBuildParams and is ignored.
chainId must be a positive, finite, safe integer (1 Ethereum, 8453 Base, 137 Polygon); 0 and 64-bit chain ids are rejected.
EvmTxType
The transaction-type byte is a strict enum. Pass the enum, the number, or the string form ("legacy" / "accessList" / "eip1559") — all normalize to the same value.
EvmTxType | Value | EIP |
|---|---|---|
EvmTxType.Legacy | 0 | pre-EIP-2718 |
EvmTxType.AccessList | 1 | EIP-2930 |
EvmTxType.Eip1559 | 2 | EIP-1559 |
When type is omitted it defaults to EvmTxType.Eip1559 — unless you supplied a legacy gasPrice (and no EIP-1559 fee fields), in which case the transaction is built as Legacy to honor your intent rather than silently dropping gasPrice.
import { EvmTxType } from "wative-core";EvmTxBuildParams
The parameter object accepted by buildTransaction() and the EvmTransaction constructor.
| Field | Type | Required | Notes |
|---|---|---|---|
to | string | yes | 0x + 40 hex; a mixed-case value must carry a valid EIP-55 checksum, or it is rejected rather than silently corrected |
value | bigint | no | wei; defaults to 0n |
data | string | no | hex bytes; normalized to lowercase 0x-prefixed even length |
nonce | number | no | finite non-negative integer; auto-filled if omitted |
gasLimit | bigint | no | auto-estimated (with headroom) if omitted |
gasPrice | bigint | no | legacy fee; auto-filled for legacy transactions |
maxFeePerGas | bigint | no | EIP-1559 cap; auto-filled |
maxPriorityFeePerGas | bigint | no | EIP-1559 tip; auto-filled |
type | EvmTxType | no | defaults to Eip1559 (or Legacy — see above) |
chainId | ChainId | via buildTransaction, optional | required on the raw constructor; filled from the network by buildTransaction |
rpcUrl | string | no | per-transaction endpoint override; validated |
accessList | ReadonlyArray<{ address: string; storageKeys: ReadonlyArray<string> }> | no | EIP-2930/1559 only; storage keys padded to 32 bytes |
Auto-filled nonce, gas, and fees
Any of nonce, gasLimit, and the fee fields you leave out are resolved over RPC at sign time. A value you supply is used verbatim and never adjusted.
- Nonce — from
eth_getTransactionCount(pending), reconciled against a per-(chain, sender)broadcast watermark so two sends in a row cannot collide on one nonce. - Gas limit — from
eth_estimateGas, then multiplied by a 1.2× safety buffer, because state-dependent transactions routinely need more gas at execution than the estimate. - EIP-1559 fees — the tip is the node-suggested priority fee, floored at 1 gwei; the max fee is
baseFee × 2 + tip. The 2× base-fee headroom (roughly six blocks against the +12.5%/block growth ceiling) keeps a transaction includable through a rising-fee window instead of stalling as pending. - Legacy fee —
eth_gasPricepadded by ×1.25.
These anti-pending headroom defaults (current form as of 2.5.1) apply only to a field you omitted. A caller-supplied gasLimit, maxFeePerGas, maxPriorityFeePerGas, or gasPrice is honored exactly as given.
Field validation at assignment time
Every numeric field of an EVM transaction is a uint256 on the wire, so each is bounded to [0, 2²⁵⁶ − 1] when it is assigned — not deferred to sign time, where the failure would name neither the field nor the reason. A value that cannot be encoded is refused in the frame that knows your field name.
value,gasPrice,maxFeePerGas,maxPriorityFeePerGas,gasLimit— coerced frombigint/ safe-integer number / decimal string / hex string, bounded to[0, uint256 max].nonce— finite non-negative integer.- When you supply both EIP-1559 caps,
maxPriorityFeePerGasmust not exceedmaxFeePerGas, or the constructor throwsPARAMETER_ERRORup front (rather than letting the signer reject it opaquely).
new EvmTransaction({ from, to, value: (1n << 256n), chainId: 1 });
// PARAMETER_ERROR: EvmTransaction.value … (out of uint256 range)