Documentation Index
Fetch the complete documentation index at: https://developers.circle.com/llms.txt
Use this file to discover all available pages before exploring further.
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.
Prerequisites
Before you begin, make sure you have:
Only EOA wallets are supported for this signing flow.
Step 1. Set up your project
1.1. Create the project and install dependencies
# Create the project directory and initialize Node.js
mkdir sign-transactions
cd sign-transactions
npm init -y
# Set up module type and a script for EVM signing
npm pkg set type=module
npm pkg set scripts.sign-evm="tsx --env-file=.env sign-evm.ts"
# Install runtime dependencies
npm install @circle-fin/developer-controlled-wallets viem
# Install dev dependencies
npm install --save-dev tsx typescript @types/node
This step is optional. It helps prevent missing types in your IDE or editor.
Create a tsconfig.json file:Then, update the tsconfig.json file:cat <<'EOF' > tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"types": ["node"]
}
}
EOF
1.3. Set environment variables
Create a .env file in the project directory:CIRCLE_API_KEY=YOUR_API_KEY
CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
CIRCLE_API_KEY is your Circle Developer API key.
CIRCLE_ENTITY_SECRET is your registered entity secret.
The npm run command in this tutorial loads variables from .env using Node.js
native env-file support.Step 2. Create a signing wallet
const response = await client.createWallets({
accountType: "EOA",
blockchains: ["EVM-TESTNET"],
count: 1,
walletSetId: "YOUR_WALLET_SET_ID",
});
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.Step 3. Build and sign a transaction
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
import { arcTestnet } from "viem/chains";
// Initialize the wallets client
const 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.Step 4. Review the response
The signing response includes:
signature
signedTransaction
txHash
You can use signedTransaction with your own broadcast flow or chain tooling.Step 1. Set up your project
1.1. Create the project and install dependencies
# Create the project directory and initialize Node.js
mkdir sign-transactions
cd sign-transactions
npm init -y
# Set up module type and a script for Solana signing
npm pkg set type=module
npm pkg set scripts.sign-solana="tsx --env-file=.env sign-solana.ts"
# Install runtime dependencies
npm install @circle-fin/developer-controlled-wallets @solana/web3.js
# Install dev dependencies
npm install --save-dev tsx typescript @types/node
This step is optional. It helps prevent missing types in your IDE or editor.
Create a tsconfig.json file:Then, update the tsconfig.json file:cat <<'EOF' > tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"types": ["node"]
}
}
EOF
1.3. Set environment variables
Create a .env file in the project directory:CIRCLE_API_KEY=YOUR_API_KEY
CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
CIRCLE_API_KEY is your Circle Developer API key.
CIRCLE_ENTITY_SECRET is your registered entity secret.
The npm run command in this tutorial loads variables from .env using Node.js
native env-file support.Step 2. Create a signing wallet
const response = await client.createWallets({
accountType: "EOA",
blockchains: ["SOL-DEVNET"],
count: 1,
walletSetId: "YOUR_WALLET_SET_ID",
});
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.Step 3. Build and sign a transaction
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
import {
Connection,
PublicKey,
SystemProgram,
Transaction,
clusterApiUrl,
} from "@solana/web3.js";
// Initialize the wallets client
const 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.Step 4. Review the response
The signing response includes:
signature
signedTransaction
If you broadcast the signed payload in the same script, you should also see the
broadcast transaction ID.Step 1. Set up your project
1.1. Create the project and install dependencies
# Create the project directory and initialize Node.js
mkdir sign-transactions
cd sign-transactions
npm init -y
# Set up module type and a script for NEAR signing
npm pkg set type=module
npm pkg set scripts.sign-near="tsx --env-file=.env sign-near.ts"
# Install runtime dependencies
npm install @circle-fin/developer-controlled-wallets near-api-js
# Install dev dependencies
npm install --save-dev tsx typescript @types/node
This step is optional. It helps prevent missing types in your IDE or editor.
Create a tsconfig.json file:Then, update the tsconfig.json file:cat <<'EOF' > tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"types": ["node"]
}
}
EOF
1.3. Set environment variables
Create a .env file in the project directory:CIRCLE_API_KEY=YOUR_API_KEY
CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
CIRCLE_API_KEY is your Circle Developer API key.
CIRCLE_ENTITY_SECRET is your registered entity secret.
The npm run command in this tutorial loads variables from .env using Node.js
native env-file support.Step 2. Create a signing wallet
const response = await client.createWallets({
accountType: "EOA",
blockchains: ["NEAR-TESTNET"],
count: 1,
walletSetId: "YOUR_WALLET_SET_ID",
});
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, 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.Step 3. Build and sign a transaction
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
import {
Account,
JsonRpcProvider,
nearToYocto,
actions,
encodeTransaction,
} from "near-api-js";
// Initialize the wallets client
const client = initiateDeveloperControlledWalletsClient({
apiKey: process.env.CIRCLE_API_KEY!,
entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
});
const NEAR_CONFIG = {
networkId: "testnet",
nodeUrl: "https://rpc.testnet.near.org",
};
async function main() {
// Set transaction inputs
const walletId = "YOUR_NEAR_WALLET_ID";
const walletPublicKey = "YOUR_NEAR_INITIAL_PUBLIC_KEY";
const senderAccount = "YOUR_NEAR_TESTNET_ACCOUNT";
const recipientAccount = senderAccount;
// Connect to the NEAR RPC provider
const provider = new JsonRpcProvider({ url: NEAR_CONFIG.nodeUrl });
const sender = new Account(senderAccount, provider);
const transactionActions = [actions.transfer(nearToYocto("0"))];
// Build and serialize the NEAR transaction for a signing test
const transaction = await sender.createTransaction({
receiverId: recipientAccount,
actions: transactionActions,
publicKey: walletPublicKey,
});
const rawTransaction = Buffer.from(encodeTransaction(transaction)).toString(
"base64",
);
// Sign the raw transaction
const response = await client.signTransaction({
walletId,
rawTransaction,
});
console.log(response.data);
}
main().catch((err) => {
console.error("Error:", err.message || err);
process.exit(1);
});
Base64 encode the raw transaction before you send it to signTransaction().For example, if the create-wallet response includes:{
id: "40313ab7-9abd-57f3-a067-617df1530383",
address: "8c18a0fb0717bc82f6cc7ca019e2cbd92ca5cb0767f47ef2cb2a70a0d561ad88",
initialPublicKey: "ARsrrXKzD5C9Bj9BxEzTQNNsBUGk35FM3vyUL5PbgwLj"
}
and you add initialPublicKey to my-circle-signer.testnet, then set:const walletId = "40313ab7-9abd-57f3-a067-617df1530383";
const walletPublicKey = "ARsrrXKzD5C9Bj9BxEzTQNNsBUGk35FM3vyUL5PbgwLj";
const senderAccount = "my-circle-signer.testnet";
Step 4. Review the response
The signing response includes:
signature
signedTransaction
txHash
Broadcast that signed payload through your own RPC or node provider.