Skip to main content

Overview

DynamicEvmWalletClient::sendTransaction signs an EIP-1559 transaction with the MPC ceremony and broadcasts the result via JSON-RPC in one call. Returns the transaction hash as a 0x-prefixed hex string. sendTransaction is the recommended path for most apps. Reach for signTransaction only if you need the signed RLP out-of-band — co-signing, custom relayers, or assembling the raw tx yourself.

Prerequisites

Sign + Broadcast in One Call

import java.math.BigInteger;
import xyz.dynamic.waas.evm.EvmTransaction;
import xyz.dynamic.waas.evm.opts.SendTransactionOpts;

EvmTransaction tx = EvmTransaction.builder()
    .chainId(11155111L)                                  // Sepolia
    .nonce(BigInteger.ZERO)
    .maxFeePerGas(new BigInteger("10000000000"))
    .maxPriorityFeePerGas(new BigInteger("1000000000"))
    .gasLimit(new BigInteger("21000"))
    .to("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB")
    .value(BigInteger.ZERO)
    .data("0x")
    .build();

String txHash = client.sendTransaction(SendTransactionOpts.builder()
    .transaction(tx)
    .walletProperties(walletProperties)
    .externalServerKeyShares(externalServerKeyShares)
    .rpcUrl("https://sepolia.infura.io/v3/<your-key>")
    .build()
).join();

System.out.println("Submitted tx: " + txHash);
System.out.println("Explorer: https://sepolia.etherscan.io/tx/" + txHash);
A fresh JSON-RPC client is constructed per call. For high-throughput scenarios, compose signTransaction with your own pooled Web3j instance instead.

Broadcast a Pre-Signed Transaction

When the transaction is signed elsewhere — for example on a client via the embedded wallet — and the server only needs to relay it, use the static EvmRpc.sendRawTransaction. It broadcasts an already-signed transaction via eth_sendRawTransaction with no wallet, MPC ceremony, or key shares required — just the serialized signed transaction and an RPC URL:
import xyz.dynamic.waas.evm.EvmRpc;

// `signedRlpHex` is the 0x-prefixed signed transaction produced by the signer —
// e.g. the client's embedded wallet, or signTransaction (see below).
String txHash = EvmRpc.sendRawTransaction(
    signedRlpHex,
    "https://sepolia.infura.io/v3/<your-key>"
).join();

System.out.println("Submitted tx: " + txHash);
This is the building block sendTransaction uses internally after signing. It’s the right fit for a sign-on-client / broadcast-on-server split: the client signs with its embedded wallet and your backend only relays the signed payload to the network.

Building the Nonce

Fetch the current pending nonce from your RPC before each send — concurrent sendTransaction calls against the same wallet without nonce coordination will conflict:
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
import org.web3j.protocol.core.DefaultBlockParameterName;

Web3j w3 = Web3j.build(new HttpService("https://sepolia.infura.io/v3/<your-key>"));
BigInteger nonce = w3
    .ethGetTransactionCount(walletProperties.accountAddress(), DefaultBlockParameterName.PENDING)
    .send()
    .getTransactionCount();

Gas Estimation

EIP-1559 fees vary block to block — query the latest base fee + tip estimate, then bake them into the transaction:
BigInteger baseFee  = w3.ethGetBlockByNumber(DefaultBlockParameterName.PENDING, false)
    .send().getBlock().getBaseFeePerGas();
BigInteger tip      = new BigInteger("1000000000");        // 1 gwei
BigInteger maxFee   = baseFee.multiply(BigInteger.TWO).add(tip);

Common Transaction Types

ETH Transfer

EvmTransaction transfer = EvmTransaction.builder()
    .chainId(1L)
    .nonce(nonce)
    .maxFeePerGas(maxFee)
    .maxPriorityFeePerGas(tip)
    .gasLimit(new BigInteger("21000"))
    .to("0xRecipientAddress")
    .value(new BigInteger("100000000000000000"))           // 0.1 ETH in wei
    .data("0x")
    .build();

Contract Interaction

EvmTransaction call = EvmTransaction.builder()
    .chainId(1L)
    .nonce(nonce)
    .maxFeePerGas(maxFee)
    .maxPriorityFeePerGas(tip)
    .gasLimit(new BigInteger("100000"))
    .to("0xContractAddress")
    .value(BigInteger.ZERO)
    .data("0xFunctionSelector...")
    .build();

Error Handling

import java.util.concurrent.CompletionException;
import xyz.dynamic.waas.core.exceptions.DynamicSdkException;

try {
    String txHash = client.sendTransaction(opts).join();
    log.info("Submitted: {}", txHash);
} catch (CompletionException ce) {
    if (ce.getCause() instanceof DynamicSdkException dse) {
        log.error("Send failed: {}", dse.getMessage(), dse);
    } else {
        throw ce;
    }
}
sendTransaction returns the JSON-RPC error verbatim when the broadcast itself fails (e.g. nonce too low, replacement transaction underpriced) — match on the error message or retry with a refreshed nonce.

Next Steps