EvmProvider¶
EvmProvider is a complete implementation of CoinProvider for Ethereum-compatible blockchains (EVM), such as Ethereum, BNB Smart Chain, Polygon, and others.
Overview¶
This provider enables:
- Wallet generation and transaction signing
- Value conversion between human and unit amounts
- Token contract interactions
- Transaction history fetching
- Real-time or polling-based event listening
It uses the Secp256k1 curve and supports the standard Ethereum transaction flow.
Chain Separation¶
Each EVM provider is initialized with a specific chainId that identifies the target network.
Example chains:
EvmProvider provider = EvmProvider.newBuilder()
.chainId(ChainID.ETHEREUM) // 1
.build();
A helper class ChainID provides predefined constants for popular networks:
ChainID.ETHEREUM // 1
ChainID.BNB // 56
ChainID.POLYGON // 137
...
Wallet¶
Wallets are created using private keys or key managers. Each wallet exposes the standard CryptoWallet interface.
EvmWallet wallet = provider.createWallet(myKeyManager);
Transactions¶
Wallets expose a .transaction(...) method which returns an OutgoingTransaction. Internally, it delegates to the TransactionService.
This service supports both simple native transfers and token transfers.
Native Transfer¶
wallet.transaction("0xRecipient", new Amount("0.01", AmountUnit.HUMAN)).send();
Token Transfer¶
wallet.transaction("0xRecipient", "0xTokenContract", new Amount("100", AmountUnit.HUMAN)).send();
If the
INCLUDE_FEEflag is passed, the transaction amount is automatically reduced to account for the gas fee. Otherwise, the full amount is sent, and the sender pays the fee separately.
Gas Estimation and Fee Deduction¶
The TransactionService will automatically estimate the gas and calculate the fee if not provided. When INCLUDE_FEE is used, it adjusts the value accordingly:
if ((flags & Flag.INCLUDE_FEE) != 0) {
BigInteger fee = gasPrice * estimatedGas;
value = value - fee;
}
Token Transfers¶
When sending tokens (like USDT, USDC), the TransactionService encodes the standard ERC-20 transfer(address,uint256) function and sets it as the input in the transaction.
Gas and nonce are still fetched from the node.
Event Listening¶
EvmProvider uses two components for event tracking:
EventClient— provides historical transaction fetches (EventFetcher).ListenerProvider— provides real-time or polling-based listeners (TransactionListener).
EventClient Implementations¶
Finja provides built-in EventClient implementations:
- BlockBookEventClient – fetches data from a BlockBook-compatible explorer.
- EtherScanEventClient – fetches data from etherscan.io using an API key.
Example:
var client = new EtherScanEventClient();
provider = EvmProvider.newBuilder()
.eventClient(client)
...
.build();
ListenerProvider Implementations¶
There are two types of listeners:
- EvmNodeListenerProvider – uses the node's WebSocket/RPC
eth_subscribesupport. Requires a node that supports subscriptions. - EvmPollingListenerProvider – periodically fetches event data using the provided
EventClientand simulates real-time behavior.
var polling = new EvmPollingListenerProvider(10, new PollingLimiterConfig());
provider = EvmProvider.newBuilder().listenerProvider(polling).build();
Polling-based listeners internally use
provider.eventFetcher()to access the historical event source.
> Chain ID is included in meta of TxnEvent to allow filtering by chain.¶
Multi-send contract¶
To enable mass transfers on any EVM network, deploy the following contract (compatible with both native and token transfers):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MultiSend is ReentrancyGuard {
using Address for address payable;
using SafeERC20 for IERC20;
function send(address[] calldata recipients, uint256[] calldata amounts) external payable nonReentrant {
require(recipients.length == amounts.length, "MultiSend: Recipients and amounts arrays length mismatch");
uint256 totalAmount = 0;
for (uint256 i = 0; i < amounts.length; i++) {
require(recipients[i] != address(0), "MultiSend: Cannot send to zero address");
totalAmount += amounts[i];
}
require(msg.value == totalAmount, "MultiSend: Incorrect Ether value sent");
for (uint256 i = 0; i < recipients.length; i++) {
payable(recipients[i]).sendValue(amounts[i]);
}
}
function sendToken(address token, address[] calldata recipients, uint256[] calldata amounts) external nonReentrant {
require(recipients.length == amounts.length, "MultiSend: Recipients and amounts arrays length mismatch");
uint256 totalAmount = 0;
IERC20 erc20 = IERC20(token);
for (uint256 i = 0; i < amounts.length; i++) {
require(recipients[i] != address(0), "MultiSend: Cannot send to zero address");
totalAmount += amounts[i];
}
require(erc20.allowance(msg.sender, address(this)) >= totalAmount, "MultiSend: Not enough allowance");
for (uint256 i = 0; i < recipients.length; i++) {
erc20.safeTransferFrom(msg.sender, recipients[i], amounts[i]);
}
}
}
After deployment:
provider.registerContract(new EthMultiTransferContract("contractAddress"));
Contract Registry¶
NOTE: Working with smart-contracts is in beta and may change in future releases.
EvmProvider supports a lightweight contract registry:
provider.registerContract(new MyTokenContract());
MyTokenContract token = provider.findContract(MyTokenContract.class);
This makes it easy to store references to known tokens or apps and interact with them later.