
Use Developer Controlled Wallets
- 606 installs
- 136 repo stars
- Updated June 22, 2026
- circlefin/skills
use-developer-controlled-wallets is a Circle plugin skill that guides developers through creating, funding, and managing developer-controlled custodial wallets via the Circle Wallets SDK, entity secret registration, wall
About
use-developer-controlled-wallets is a circlefin/skills plugin skill ranked 3 on skills.sh with 473 installs for building custodial wallet flows where the application retains programmatic key control through an encrypted entity secret. The skill walks through npm install @circle-fin/developer-controlled-wallets, generating and registering a 32-byte entity secret, initializing initiateDeveloperControlledWalletsClient with CIRCLE_API_KEY and ENTITY_SECRET, and creating wallet sets that can scale to millions of addresses per set. Developers reach for use-developer-controlled-wallets when implementing payouts, treasury movements, subscriptions, or automation that requires EOA or SCA wallet types on supported chains. Reference files cover wallet creation, balance checks, and token transfers with per-request entity secret ciphertext generation. The skill stresses secure secret storage in a secrets manager rather than version control, making it essential during Web3 payment backend integration rather than end-user passkey wallet flows.
- Enables AI agents to programmatically control on-chain and off-chain wallets
- Supports secure funding, balance checks, and transaction signing
- Integrates directly with Circle's developer APIs for USDC and other tokens
- Provides wallet lifecycle commands for creation and management
- Designed for autonomous agent workflows that require payment rails
Use Developer Controlled Wallets by the numbers
- 606 all-time installs (skills.sh)
- Ranked #647 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/circlefin/skills --skill use-developer-controlled-walletsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 606 |
|---|---|
| repo stars | ★ 136 |
| Last updated | June 22, 2026 |
| Repository | circlefin/skills ↗ |
How do you integrate Circle developer-controlled wallets?
Let their AI coding agent safely create, fund, and manage developer-controlled wallets on the Circle platform.
Who is it for?
Backend developers integrating Circle custodial wallets for payouts, treasury automation, or subscription billing with programmatic key custody.
Skip if: Developers building passkey-based user-controlled wallets should skip use-developer-controlled-wallets and use Circle modular wallet skills instead.
When should I use this skill?
Trigger when creating wallet sets, registering entity secrets, calling initiateDeveloperControlledWalletsClient, or transferring tokens on Circle dev-controlled wallets.
What you get
Registered entity secrets, wallet sets, funded wallets, and SDK-driven token transfer flows
- Wallet set configuration
- SDK client initialization code
- Token transfer workflows
By the numbers
- 473 installs on skills.sh
- Rank 3 on skills.sh in circlefin/skills
- Entity secret is a 32-byte cryptographic key
Files
Overview
Developer-controlled wallets let your application create and manage wallets on behalf of end users, with full custody of private keys secured through an encrypted entity secret. Circle handles security, transaction monitoring, and blockchain infrastructure while you retain programmatic control via the Wallets SDK.
Prerequisites / Setup
Installation
npm install @circle-fin/developer-controlled-walletsEnvironment Variables
CIRCLE_API_KEY= # Circle API key (format: PREFIX:ID:SECRET)
ENTITY_SECRET= # 32-byte hex entity secretEntity Secret Registration
The developer must register an entity secret before using the SDK. Direct them to https://developers.circle.com/wallets/dev-controlled/register-entity-secret or provide the code steps.
READ references/register-secret.md for the generation and registration snippets.
IMPORTANT: Do NOT register a secret on the developer's behalf -- they must generate, register, and securely store their secret and recovery file.
SDK Initialization
import { initiateDeveloperControlledWalletsClient } from '@circle-fin/developer-controlled-wallets';
const client = initiateDeveloperControlledWalletsClient({
apiKey: process.env.CIRCLE_API_KEY,
entitySecret: process.env.ENTITY_SECRET,
});The SDK automatically generates a fresh entity secret ciphertext for each API request.
Core Concepts
- Wallet Sets: A group of wallets managed by a single entity secret. Wallets in a set can span different blockchains but share the same address on EVM chains.
- Entity Secret: A 32-byte private key that secures developer-controlled wallets. Generated, encrypted, and registered once. Circle never stores it in plain text.
- Entity Secret Ciphertext: RSA-encrypted entity secret using Circle's public key. Must be unique per API request to prevent replay attacks. The SDK handles this automatically.
- Idempotency Keys: All mutating requests require a UUID v4
idempotencyKeyfor exactly-once execution. - Account Types:
- EOA (Externally Owned Account) -- default choice. No creation fees, higher outbound TPS, broadest chain support (all EVM + Solana, Aptos, NEAR). Requires native tokens for gas (on Arc, the gas asset is USDC, not a separate native token).
- SCA (Smart Contract Account) -- ERC-4337 compliant. Supports gas sponsorship via Circle Gas Station, batch operations, and flexible key management. EVM-only (not available on Solana, Aptos, NEAR). Avoid on Ethereum mainnet due to high gas costs; prefer on L2s.
- Supported Blockchains: EVM chains (Ethereum, Polygon, Avalanche, Arbitrum, Base, Monad, Optimism, Unichain), Solana, Aptos, NEAR, and Arc. See https://developers.circle.com/wallets/account-types for the latest.
Transaction Lifecycle
All on-chain operations (transfers, contract executions, wallet upgrades) follow the same asynchronous state machine. Poll with circleDeveloperSdk.getTransaction({ id }) until a terminal state is reached.
Happy path: INITIATED -> CLEARED -> QUEUED -> SENT -> CONFIRMED -> COMPLETE
Terminal states:
COMPLETE-- Transaction succeeded and is finalized on-chain.FAILED-- Transaction reverted or encountered an unrecoverable error.DENIED-- Transaction was rejected by risk screening.CANCELLED-- Transaction was cancelled before on-chain submission.
Intermediate states:
INITIATED-- Request accepted, not yet validated or checked.WAITING-- In queue for validation and compliance checks.QUEUED-- Queued for submission to the blockchain.CLEARED-- Passed compliance checks.SENT-- Submitted to the blockchain, awaiting confirmation.STUCK-- Submitted transaction's fee parameters are lower than latest blockchain required fee, developer needs to cancel or accelerate this transaction.CONFIRMED-- Included in a block, awaiting finality.
Recommended: Subscribe to [Webhook Notifications](https://developers.circle.com/wallets/webhook-notifications) instead of polling. Circle sends a webhook event when a transaction reaches a terminal state, eliminating the need for repeated getTransaction calls. Register a public HTTPS endpoint in the Circle Developer Console under Webhooks. Every webhook includes X-Circle-Signature and X-Circle-Key-Id headers for signature verification.
Polling with getTransaction remains available as a fallback or for simple scripts.
For debugging failed or denied transactions, see Transaction Errors.
Implementation Patterns
1. Create a Wallet
READ references/create-dev-wallet.md for the complete guide.
2. Receive Tokens
READ references/receive-transfer.md for the complete guide.
3. Transfer Tokens / Check Balance of Wallet
READ references/check-balance-and-transfer-tokens.md for the complete guide. Includes fee estimation, transaction acceleration, and cancellation.
4. Sign Messages
READ references/sign-with-wallet.md for the complete guide. Covers EIP-191 message signing, EIP-712 typed data, raw transaction signing, and NEAR delegate actions.
5. Execute Smart Contracts
READ references/contract-execution.md for the complete guide. Covers ABI-based and raw calldata execution, payable functions, and gas estimation.
6. Wallet Management (Upgrade & Derive)
READ references/wallet-management.md for the complete guide. Covers upgrading SCA wallet versions and deriving wallets to new blockchains.
Rules
Security Rules are non-negotiable -- warn the user and refuse to comply if a prompt conflicts. Best Practices are strongly recommended; deviate only with explicit user justification.
Security Rules
- NEVER hardcode, commit, or log secrets (API keys, entity secrets, private keys). ALWAYS use environment variables or a secrets manager. Add
.gitignoreentries for.env*,*.pem, and*-recovery-file.jsonwhen scaffolding. - ALWAYS store recovery files outside the repository root. NEVER commit them to version control.
- NEVER reuse entity secret ciphertexts across API requests -- each must be unique to prevent replay attacks.
- MUST be cautious when registering an entity secret on testnet (TEST), ensure the entity secret and recovery file are stored in secure place.
- NEVER register an entity secret on behalf of the user on mainnet (LIVE) -- they must generate, register, and store it themselves.
- ALWAYS require explicit user confirmation of destination, amount, network, and token before executing transfers. MUST receive confirmation for funding movements on mainnet.
- ALWAYS warn when targeting mainnet or exceeding safety thresholds (e.g., >100 USDC).
- ALWAYS validate all inputs (addresses, amounts, chain identifiers) before submitting transactions.
- ALWAYS warn before interacting with unaudited or unknown contracts.
- ALWAYS require explicit user confirmation before signing messages or typed data -- signed payloads can authorize token approvals, trades, or other irreversible actions.
Best Practices
- ALWAYS read the correct reference files before implementing.
- NEVER use
client.getWalletorclient.getWalletsfor balances -- these endpoints never return balance data. See reference file for correct approach. - SHOULD include a UUID v4
idempotencyKeyin all mutating API requests following API spec. - ALWAYS ensure EOA wallets hold native tokens (ETH, MATIC, SOL, etc.) for gas before outbound transactions. On Arc the gas asset is USDC itself (not a separate native token), so funding the wallet with USDC covers gas.
- ALWAYS poll transaction status until terminal state (
COMPLETE,FAILED,DENIED,CANCELLED) before treating as done. - ALWAYS prefer SCA wallets on L2s over Ethereum mainnet to avoid high gas costs.
- ALWAYS default to testnet. Require explicit user confirmation before targeting mainnet.
- ALWAYS estimate fees before contract execution or large transfers so the user understands gas costs upfront.
- ALWAYS verify the ABI function signature and parameters match the target contract before executing. Incorrect signatures will revert and waste gas.
- ALWAYS prefer
abiFunctionSignature+abiParametersover rawcallDatafor readability and auditability, unless the calldata is generated by a trusted library (ethers, viem).
Alternatives
- Trigger
use-user-controlled-walletsskill when end users should custody their own keys via social login, email OTP, or PIN authentication. - Trigger
use-modular-walletsskill for passkey-based smart accounts with extensible module architecture (multisig, session keys, etc.).
Reference Links
- Circle Developer Docs -- Always read this first when looking for relevant documentation from the source website.
---
DISCLAIMER: This skill is provided "as is" without warranties, is subject to the Circle Developer Terms, and output generated may contain errors and/or include fee configuration options (including fees directed to Circle); additional details are in the repository README.
Transfer Tokens Across Wallets
Use these TypeScript snippets to find token balances, create a transfer, and poll transaction status.
Get source wallet balances and token address
Use the Get Token Balance for a Wallet API or the SDK method to retrieve balances and the tokenAddress for transfers:
const balanceResponse = await circleDeveloperSdk.getWalletTokenBalance({
id: "<source-wallet-id>",
});
const tokenBalances = balanceResponse.data?.tokenBalances ?? [];Find the token you want to transfer and capture its tokenAddress.
Create transfer transaction
const transferResponse = await circleDeveloperSdk.createTransaction({
walletId: "<source-wallet-id>",
tokenAddress: "<token-address>",
destinationAddress: "<destination-wallet-address>",
amounts: ["0.01"],
fee: {
type: "level",
config: { feeLevel: "MEDIUM" },
},
});
const transactionId = transferResponse.data?.id;Creates an outbound transfer transaction and returns a transaction ID for tracking.
Poll transaction state
const txResponse = await circleDeveloperSdk.getTransaction({
id: "<transaction-id>",
});
const tx = txResponse.data?.transaction;
const state = tx?.state;
const txHash = tx?.txHash;Use state to determine completion and txHash for chain explorer links.
Estimate transfer fees
const feeEstimate = await circleDeveloperSdk.estimateTransferFee({
walletId: "<source-wallet-id>",
tokenAddress: "<token-address>",
destinationAddress: "<destination-wallet-address>",
amounts: ["0.01"],
});
const { low, medium, high } = feeEstimate.data ?? {};
// Each tier includes: gasLimit, gasPrice, maxFee, priorityFee, baseFee, networkFeeEstimate fees before transferring to choose an appropriate fee level or set custom gas parameters.
Accelerate a pending transaction
const accelerateResponse = await circleDeveloperSdk.accelerateTransaction({
id: "<transaction-id>",
});Speeds up a SENT transaction by resubmitting with higher gas. Additional gas fees may be incurred. Only works while the transaction is in the SENT state (still pending on-chain).
Cancel a pending transaction
const cancelResponse = await circleDeveloperSdk.cancelTransaction({
id: "<transaction-id>",
});
const state = cancelResponse.data?.state;Attempts to cancel a transaction. This is best-effort -- cancellation may fail if the blockchain has already processed the original transaction. Gas fees may still be incurred.
Reference Links
Execute a Smart Contract
Use these TypeScript snippets to execute smart contract functions from a developer-controlled wallet.
Execute using ABI function signature
const executionResponse = await circleDeveloperSdk.createContractExecutionTransaction({
walletId: "<source-wallet-id>",
contractAddress: "<contract-address>",
abiFunctionSignature: "transfer(address,uint256)",
abiParameters: ["0xRecipientAddress", "1000000"],
fee: {
type: "level",
config: { feeLevel: "MEDIUM" },
},
});
const transactionId = executionResponse.data?.id;Pass the Solidity function signature and parameters as an array of strings. Supported parameter types: string, integer, boolean, and arrays.
Execute using raw call data
const executionResponse = await circleDeveloperSdk.createContractExecutionTransaction({
walletId: "<source-wallet-id>",
contractAddress: "<contract-address>",
callData: "0xa9059cbb000000000000000000000000...",
fee: {
type: "level",
config: { feeLevel: "MEDIUM" },
},
});
const transactionId = executionResponse.data?.id;Use callData when you have pre-encoded transaction data (e.g., from ethers or viem). callData and abiFunctionSignature are mutually exclusive.
Execute a payable function
const executionResponse = await circleDeveloperSdk.createContractExecutionTransaction({
walletId: "<source-wallet-id>",
contractAddress: "<contract-address>",
abiFunctionSignature: "deposit()",
abiParameters: [],
amount: "0.1", // native token amount (ETH, MATIC, etc.)
fee: {
type: "level",
config: { feeLevel: "MEDIUM" },
},
});
const transactionId = executionResponse.data?.id;Set amount to send native tokens when calling payable functions.
Estimate gas fees before execution
const feeEstimate = await circleDeveloperSdk.estimateContractExecutionFee({
walletId: "<source-wallet-id>",
contractAddress: "<contract-address>",
abiFunctionSignature: "transfer(address,uint256)",
abiParameters: ["0xRecipientAddress", "1000000"],
});
const { low, medium, high } = feeEstimate.data ?? {};
// Each tier includes: gasLimit, gasPrice, maxFee, priorityFee, baseFee, networkFeeEstimate fees before executing to choose an appropriate fee level or set custom gas parameters.
Reference Links
Create Your First Developer-Controlled Wallet
Use these TypeScript snippets to create a wallet set, then create wallets in that set.
Create a wallet set
const walletSetResponse = await circleDeveloperSdk.createWalletSet({
name: "Entity WalletSet A",
});
const walletSetId = walletSetResponse.data?.walletSet?.id;Creates a wallet set and stores walletSetId for the next step.
Create wallets in that wallet set
const walletsResponse = await circleDeveloperSdk.createWallets({
accountType: "SCA",
blockchains: ["MATIC-AMOY"],
count: 2,
walletSetId: "<wallet-set-id>",
});
const wallets = walletsResponse.data?.wallets ?? [];
const sourceWallet = wallets[0];
const destinationWallet = wallets[1];Creates two wallets and captures source/destination wallets for transfer workflows.
Alternate chain/account example
const solWalletResponse = await circleDeveloperSdk.createWallets({
accountType: "EOA",
blockchains: ["SOL-DEVNET"],
count: 1,
walletSetId: "<wallet-set-id>",
});Same flow on a different blockchain/account type.
Reference Links
Receive an Inbound Transfer
Use these TypeScript snippets to get a wallet address, fund it from a faucet/external wallet, then verify inbound transfer state.
Get wallet address for receiving funds
const walletsResponse = await circleDeveloperSdk.getWallets({});
const wallets = walletsResponse.data?.wallets ?? [];
const targetWallet = wallets[0];
const walletId = targetWallet?.id;
const receiveAddress = targetWallet?.address;Use receiveAddress with a testnet faucet (for example, https://faucet.circle.com) or another wallet.
Check inbound transfer state by wallet
const txResponse = await circleDeveloperSdk.listTransactions({
walletIds: ["<wallet-id>"],
});
const inboundTransactions =
txResponse.data?.transactions?.filter((tx) => tx.transactionType === "INBOUND") ?? [];Lists transactions for the wallet so you can confirm inbound transfers and state progression.
Reference Links
Register Your Entity Secret
Use these TypeScript snippets to generate and register your entity secret. Keep the secret and recovery file secure.
Generate an entity secret
import { generateEntitySecret } from "@circle-fin/developer-controlled-wallets";
generateEntitySecret();Generates a 32-byte entity secret for developer-controlled wallet signing.
Register entity secret ciphertext
import { registerEntitySecretCiphertext } from "@circle-fin/developer-controlled-wallets";
import os from "node:os";
import path from "node:path";
const response = await registerEntitySecretCiphertext({
apiKey: process.env.CIRCLE_API_KEY!,
entitySecret: process.env.ENTITY_SECRET!,
recoveryFileDownloadPath: path.join(os.homedir(), ".circle", "recovery-file.json"),
});
console.log(response.data?.recoveryFile);Registers ciphertext with Circle and writes a recovery file to the provided path.
Security notes
- Never commit
ENTITY_SECRETor recovery files. - Store both in secure secret storage.
- SDK methods that require ciphertext will handle generation/rotation for each request.
Reference Links
Sign a Message
Use these TypeScript snippets to sign a message from a developer-controlled wallet.
Sign a message (EIP-191 / blockchain-native)
const signResponse = await circleDeveloperSdk.signMessage({
walletId: "<wallet-id>",
message: "Hello, Circle!",
});
const signature = signResponse.data?.signature;Signs using EIP-191 on EVM chains, or the native signing scheme on Solana and Aptos.
Sign a hex-encoded message
const signResponse = await circleDeveloperSdk.signMessage({
walletId: "<wallet-id>",
message: "0x48656c6c6f",
encodedByHex: true,
});
const signature = signResponse.data?.signature;Set encodedByHex: true when the message is already hex-encoded.
Identify wallet by address instead of ID
const signResponse = await circleDeveloperSdk.signMessage({
walletAddress: "<wallet-address>",
blockchain: "ARC-TESTNET",
message: "Sign ARC-TESTNET message",
});
const signature = signResponse.data?.signature;Provide walletAddress + blockchain instead of walletId when you only have the address.
Other signing operations
The SDK also supports these additional signing methods. They follow the same pattern (provide walletId or walletAddress + blockchain, plus entitySecretCiphertext handled automatically by the SDK):
Sign EIP-712 typed data (EVM-compatible only)
const signResponse = await circleDeveloperSdk.signTypedData({
walletId: "<wallet-id>",
data: JSON.stringify(eip712TypedData), // EIP-712 structured data as string
});
const signature = signResponse.data?.signature;Use for EIP-2612 permit approvals, off-chain order signing (e.g., Seaport), and any protocol requiring typed structured data.
Sign a raw transaction (SOL, NEAR, EVM)
const signResponse = await circleDeveloperSdk.signTransaction({
walletId: "<wallet-id>",
rawTransaction: "<base64-or-hex-encoded-transaction>",
});
const signature = signResponse.data?.signature;
const signedTransaction = signResponse.data?.signedTransaction;Use when you build transactions externally and only need Circle to sign. Accepts base64 (Solana/NEAR) or hex (EVM) encoding. EVM responses also include txHash.
Sign a delegate action (NEAR only)
const signResponse = await circleDeveloperSdk.signDelegateAction({
walletId: "<wallet-id>",
unsignedDelegateAction: "<base64-encoded-delegate-action>",
});
const signature = signResponse.data?.signature;
const signedDelegateAction = signResponse.data?.signedDelegateAction;Use for NEAR meta-transactions where a relayer submits the transaction on behalf of the user.
Reference Links
Wallet Management
Derive a wallet to a new blockchain
const deriveResponse = await circleDeveloperSdk.deriveWallet({
id: "<wallet-id>",
blockchain: "ARB-TESTNET",
metadata: {
name: "Arbitrum Wallet",
refId: "internal-ref-001",
},
});
const derivedWallet = deriveResponse.data?.wallet;Derives an EOA or SCA wallet on a new EVM blockchain from an existing wallet to create the same EVM wallet address. If a wallet already exists at that address on the target chain, its metadata is updated instead.
Update wallet metadata
const updateResponse = await circleDeveloperSdk.updateWallet({
id: "<wallet-id>",
name: "Updated Wallet Name",
refId: "new-ref-id",
});Updates the display name or reference ID of an existing wallet without any on-chain transaction.
Reference Links
Related skills
How it compares
Pick use-developer-controlled-wallets over user-controlled wallet skills when the backend must custody keys for payouts, treasury, or automated operational transfers.
FAQ
What SDK does use-developer-controlled-wallets use?
use-developer-controlled-wallets uses the @circle-fin/developer-controlled-wallets npm package. Developers initialize initiateDeveloperControlledWalletsClient with CIRCLE_API_KEY and a registered ENTITY_SECRET.
When should developers pick developer-controlled wallets?
use-developer-controlled-wallets fits custodial flows like payouts, treasury movements, subscriptions, and backend automation where the application manages wallet keys via an entity secret rather than end-user passkeys.