Sign transactions with developer-controlled wallets on EVM, Solana, or NEAR when you manage broadcasting yourself.
Signing APIs let you build a transaction in your application and have Circle
sign it. You then broadcast the signed transaction using your own node provider.
Use this flow when Circle doesn’t provide full
blockchain infrastructure for your
blockchain, or when you prefer to manage broadcasting yourself. For an overview,
see How signing APIs work.
# Create the project directory and initialize Node.jsmkdir sign-transactionscd sign-transactionsnpm init -y# Set up module type and a script for EVM signingnpm pkg set type=modulenpm pkg set scripts.sign-evm="node --env-file=.env sign-evm.ts"# Install runtime dependenciesnpm install @circle-fin/developer-controlled-wallets viem# Install dev dependenciesnpm install --save-dev typescript @types/node
CIRCLE_ENTITY_SECRET is your registered entity secret.
Open .env in your editor rather than writing values with shell commands, and
add .env to your .gitignore. This prevents credentials from leaking into
your shell history or version control.
The npm run start command loads variables from .env using Node.js native
env-file support.
Use count to create up to 200 wallets per request.
If you already created a native wallet on Ethereum, Polygon, Avalanche,
Arbitrum, or another EVM-compatible chain, a generic EVM wallet under the same
wallet set maps to the same address. Do not use the generic EVM wallet in
place of the native wallet on those chains, or transactions may become stuck.
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";import { arcTestnet } from "viem/chains";// Initialize the wallets clientconst client = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!,});async function main() { // Set signing inputs const walletId = "YOUR_WALLET_ID"; const walletAddress = "YOUR_WALLET_ADDRESS"; // Build an EVM transaction object for a signing test const txObject = { chainId: arcTestnet.id, nonce: "0", to: walletAddress, value: "0", gas: "21000", maxFeePerGas: "41500000000", maxPriorityFeePerGas: "1500000000", }; // Sign the transaction const response = await client.signTransaction({ walletId, transaction: JSON.stringify(txObject), }); console.log(response.data);}main().catch((err) => { console.error("Error:", err.message || err); process.exit(1);});
The transaction object must match the Ethereum JSON-RPC transaction shape. Use
your own tooling to prepare it before calling signTransaction(). Include the
correct chainId for your network. For supported EVM-TESTNET chains, see
Chain IDs for Signing Transactions. When building
EIP-1559 transactions, include both maxFeePerGas and maxPriorityFeePerGas.
# Create the project directory and initialize Node.jsmkdir sign-transactionscd sign-transactionsnpm init -y# Set up module type and a script for Solana signingnpm pkg set type=modulenpm pkg set scripts.sign-solana="node --env-file=.env sign-solana.ts"# Install runtime dependenciesnpm install @circle-fin/developer-controlled-wallets @solana/web3.js# Install dev dependenciesnpm install --save-dev typescript @types/node
CIRCLE_ENTITY_SECRET is your registered entity secret.
Open .env in your editor rather than writing values with shell commands, and
add .env to your .gitignore. This prevents credentials from leaking into
your shell history or version control.
The npm run command in this tutorial loads variables from .env using Node.js
native env-file support.
Use count to create up to 200 wallets per request.
Fund the SOL-DEVNET wallet with devnet SOL before broadcasting signed
transactions, or the account will not have the balance needed to pay fees
onchain.
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";import { Connection, PublicKey, SystemProgram, Transaction, clusterApiUrl,} from "@solana/web3.js";// Initialize the wallets clientconst client = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET!,});async function main() { // Set transaction inputs const walletId = "YOUR_SOLANA_WALLET_ID"; const senderAddress = "YOUR_WALLET_ADDRESS"; const recipientAddress = senderAddress; // Connect to the Solana RPC provider const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); // Build the Solana transaction for a signing test const instruction = SystemProgram.transfer({ fromPubkey: new PublicKey(senderAddress), toPubkey: new PublicKey(recipientAddress), lamports: 0, }); const { blockhash } = await connection.getLatestBlockhash(); const transaction = new Transaction().add(instruction); transaction.recentBlockhash = blockhash; transaction.feePayer = new PublicKey(senderAddress); const rawTransaction = transaction.serialize({ requireAllSignatures: false, verifySignatures: false, }); // Sign the raw transaction const response = await client.signTransaction({ walletId, rawTransaction: rawTransaction.toString("base64"), }); // Broadcast the signed transaction const signedTransaction = Buffer.from( response.data?.signedTransaction ?? "", "base64", ); const txid = await connection.sendRawTransaction(signedTransaction); console.log(response.data); console.log("Broadcast transaction ID:", txid);}main().catch((err) => { console.error("Error:", err.message || err); process.exit(1);});
Base64 encode the raw transaction before you send it to signTransaction(). The
signing request can succeed before the account is funded, but the broadcast step
can still fail if the wallet has not received devnet SOL.
# Create the project directory and initialize Node.jsmkdir sign-transactionscd sign-transactionsnpm init -y# Set up module type and a script for NEAR signingnpm pkg set type=modulenpm pkg set scripts.sign-near="node --env-file=.env sign-near.ts"# Install runtime dependenciesnpm install @circle-fin/developer-controlled-wallets near-api-js# Install dev dependenciesnpm install --save-dev typescript @types/node
CIRCLE_ENTITY_SECRET is your registered entity secret.
Open .env in your editor rather than writing values with shell commands, and
add .env to your .gitignore. This prevents credentials from leaking into
your shell history or version control.
The npm run command in this tutorial loads variables from .env using Node.js
native env-file support.
Use count to create up to 200 wallets per request.
Fund the senderAccount NEAR testnet account before broadcasting signed
transactions, or the account will not have enough NEAR to pay network fees.Use these values from the wallet creation response in the next step:
walletId = the wallet id
walletPublicKey = the wallet initialPublicKey
address = the wallet’s implicit account ID onchain
For the signing script, senderAccount is the NEAR testnet account where you
added the wallet initialPublicKey as a full-access key. It is not the Circle
wallet address unless you specifically set up and fund that implicit account
for signing.Before you continue, either create or use an existing NEAR testnet account and
add the wallet initialPublicKey as an access key on that account. Then use
that account ID as senderAccount in the signing step. For setup guidance, see
Create an Account and
NEAR CLI.