TransactionsEVM Transactions

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.

construct-evm.ts
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:

build-from-address.ts
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.

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.

EvmTxTypeValueEIP
EvmTxType.Legacy0pre-EIP-2718
EvmTxType.AccessList1EIP-2930
EvmTxType.Eip15592EIP-1559

When type is omitted it defaults to EvmTxType.Eip1559unless 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.

FieldTypeRequiredNotes
tostringyes0x + 40 hex; a mixed-case value must carry a valid EIP-55 checksum, or it is rejected rather than silently corrected
valuebigintnowei; defaults to 0n
datastringnohex bytes; normalized to lowercase 0x-prefixed even length
noncenumbernofinite non-negative integer; auto-filled if omitted
gasLimitbigintnoauto-estimated (with headroom) if omitted
gasPricebigintnolegacy fee; auto-filled for legacy transactions
maxFeePerGasbigintnoEIP-1559 cap; auto-filled
maxPriorityFeePerGasbigintnoEIP-1559 tip; auto-filled
typeEvmTxTypenodefaults to Eip1559 (or Legacy — see above)
chainIdChainIdvia buildTransaction, optionalrequired on the raw constructor; filled from the network by buildTransaction
rpcUrlstringnoper-transaction endpoint override; validated
accessListReadonlyArray<{ address: string; storageKeys: ReadonlyArray<string> }>noEIP-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 feeeth_gasPrice padded by ×1.25.

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 from bigint / safe-integer number / decimal string / hex string, bounded to [0, uint256 max].
  • nonce — finite non-negative integer.
  • When you supply both EIP-1559 caps, maxPriorityFeePerGas must not exceed maxFeePerGas, or the constructor throws PARAMETER_ERROR up 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)

Last updated on