You can only create Externally Owned Accounts (EOA) for signing transactions. For details, see the Account Types guide.
This tutorial walks you through creating a developer-controlled wallet to sign transactions on EVM chains.
You can only create Externally Owned Accounts (EOA) for signing transactions. For details, see the Account Types guide.
Before you begin:
If you have created a wallet on an EVM-compatible chain such as Ethereum, Polygon, Avalanche, or Arbitrum, an EVM wallet created under the same wallet set maps to the same address.
In the case where a native wallet is already created, it is important that you don't use the generic EVM wallet to sign transactions in place of the native wallet on those chains.
If you sign with the generic EVM wallet, your transaction may become stuck.
When
creating a developer-controlled wallet,
pass EVM-TESTNET
or EVM
in the blockchains
field. This wallet can be used
to sign transactions on any EVM chain.
The following example code shows how to create a wallet using the Circle Developer SDK.
const response = await circleDeveloperSdk.createWallets({
accountType: "EOA",
blockchains: ["EVM-TESTNET"],
count: 2,
walletSetId: "<wallet-set-id>",
});
Use count
to specify the number of wallets to create, up to a maximum of 200
per API request.
To build a transaction, connect to an Ethereum node and prepare your transaction object as shown in the following example code.
const { createPublicClient, http, parseEther, parseGwei } = require("viem");
// Connect to an Ethereum node
const client = createPublicClient({
chain: "mainnet",
transport: http("https://mainnet.infura.io/v3/YOUR-PROJECT-ID"),
});
async function buildTransactionObject() {
// Sender's address
const senderAddress = "0x1234..."; // Replace with the sender's address
// Recipient's address
const recipientAddress = "0x5678..."; // Replace with the recipient's address
// Amount to send in wei
const amountToSend = parseEther("0.01");
try {
// Get the nonce
const nonce = await client.getTransactionCount({
address: senderAddress,
});
// Estimate gas limit
const estimateGas = await client.estimateGas({
from: senderAddress,
to: recipientAddress,
value: amountToSend,
});
// Get the current gas price
const gasPrice = await client.getGasPrice();
// Calculate max fees with some premium
const maxFeePerGas = (gasPrice * 120n) / 100n; // Add 20% premium
const maxPriorityFeePerGas = parseGwei("2"); // Set fixed priority fee
// Prepare the transaction object
const txObject = {
nonce: nonce,
to: recipientAddress,
value: amountToSend.toString(),
gas: estimateGas.toString(),
maxFeePerGas: maxFeePerGas.toString(),
maxPriorityFeePerGas: maxPriorityFeePerGas.toString(),
chainId: 1,
};
return JSON.stringify(txObject); // Return the transaction object in JSON string
} catch (error) {
console.error("Error building transaction object:", error);
throw error; // Rethrow the error for further handling
}
}
chainId
is a required field that uniquely identifies the blockchain network
to ensure your transaction is processed on the intended chain. Use the
appropriate chain ID for the network on which you want to build transactions.
For EVM-compatible wallets, Circle supports gas fee parameters specified under EIP-1559. The following fields are required when setting gas fees:
maxFeePerGas
represents the maximum total fee per unit of gas.maxPriorityFeePerGas
represents the maximum priority fee per unit of gas.The following example code shows how to sign a transaction.
const responseObject = await circleDeveloperSdk.signTransaction({
walletID: "<wallet-id>",
transaction: txObjectString, // Pass the transaction object
});