FluidRWA Learn
Circle API Tutorial: Create Test Wallets and Transfer Test USDC
Product, network and validation boundary
This lab uses Circle Wallets: Developer-Controlled Wallets, the @circle-fin/developer-controlled-wallets Node.js SDK, two externally owned accounts (EOAs), and Ethereum Sepolia (Circle chain code ETH-SEPOLIA, chain ID 11155111). It is not Circle Mint, CCTP, Circle Payments Network, or the unrelated Circle community platform API. There is no bank payout, mainnet transaction or real-dollar settlement in this exercise.
The target is one test USDC from wallet A to wallet B. Sepolia gas is paid separately in test ETH. Circle's current create-wallet quickstart defaults to Arc Testnet; we deliberately use supported Ethereum Sepolia so the transferred token and gas asset are separate. Never substitute a different chain without rechecking the wallet support, token contract and fee model.
Validation disclosure: the companion example passed local safety tests and a Circle SDK 10.8.1 run against a localhost mock HTTP server, including wallet creation, transfer serialization, entity-secret encryption and status response handling. No authenticated Circle wallet creation, faucet funding or blockchain transfer is claimed here. The acceptance checks below must be completed with your own test account; mock responses are not receipts.
Before you start
You need a Circle developer account with access to Wallets, a TEST API key, a registered entity secret for that environment, Node.js 22.18 or later, npm and a private working folder. Use Circle's official Console and entity-secret setup guide linked below. If onboarding, region restrictions or a faucet prevents progress, stop; no subscription or asset purchase is required by this tutorial.
Generate/register the entity secret through Circle's current Console flow or official SDK setup instructions. Protect the entity secret and recovery file separately. Do not paste either into chat, browser code, an issue, a screenshot or a public repository. The SDK handles entity-secret ciphertext for signing requests; a plaintext secret is not an API ciphertext field.
Use a new test-only key and secret. Never reuse a production key. Run the example on your machine or a trusted server, never in a public web page. Exclude .env, recovery/, node_modules/ and lab-state.json from version control before adding credentials.
Step 1: Prepare an isolated project
Create a new folder outside your application repository, initialize npm and install Circle's developer-controlled SDK. Download the companion circle-wallet-lab.mjs into that folder. The downloadable file is the runnable example, not a fragment requiring you to invent missing helper functions.
Keep the generated package-lock.json for repeatability and record the installed SDK version with npm ls. If the SDK has changed, inspect its current API types before continuing. Do not add these dependencies to a production app just to run a lab.
mkdir circle-wallet-lab
cd circle-wallet-lab
npm init -y
npm install @circle-fin/developer-controlled-wallets@10.8.1
node --version
npm ls @circle-fin/developer-controlled-wallets
node --check circle-wallet-lab.mjsStep 2: Configure secrets without exposing them
In your editor, create .gitignore with the first block below, then .env with your own TEST key and registered entity secret. The displayed values are placeholders, not working credentials. Do not type real secrets in a shell command, where they may enter history. Restrict file access to your user and keep the folder out of cloud-synced/public directories.
The example rejects keys without the TEST_API_KEY: prefix and fixes the network to ETH-SEPOLIA. These are guardrails, not proof that every future SDK or account configuration is safe. Verify the test environment in Console as well. A successful authentication check is not authorization to move real assets.
# .gitignore
.env
recovery/
node_modules/
lab-state.json*
# .env - replace privately, never commit
CIRCLE_API_KEY=TEST_API_KEY:YOUR_ID:YOUR_SECRET
CIRCLE_ENTITY_SECRET=YOUR_REGISTERED_64_HEX_CHARACTER_SECRETStep 3: Create two EOA test wallets once
Run the create command once. It creates a wallet set, then requests count: 2 on ETH-SEPOLIA with accountType: EOA. The local state file keeps request idempotency keys and returned public wallet IDs/addresses. Protect it because public identifiers still reveal operational relationships. It contains no intended credential values.
Expected result: two distinct wallet IDs and addresses on ETH-SEPOLIA. Check the printed addresses against Circle Console. Wallet IDs are API resource identifiers; addresses are destinations on the chain. They are not interchangeable. Label A as sender and B as recipient in your lab notes.
Run only one lab process in this folder at a time. A lock prevents concurrent writes and state is replaced atomically. If the process is forcibly killed, a .lock file may remain: confirm no process is running and reconcile Console before removing only that stale lock, never the state file. If a request times out, re-run with the same saved idempotency keys/body. If Circle reports an idempotency conflict or the retry window has expired, stop and reconcile in Console before another creation.
node --env-file=.env circle-wallet-lab.mjs create
node --env-file=.env circle-wallet-lab.mjs balancesStep 4: Fund A with test USDC and test ETH
Open Circle's faucet from the official link. Select Ethereum Sepolia, not Arc, Base Sepolia or Ethereum mainnet. Paste wallet A's address and request test USDC. If the faucet offers a separate native test ETH request for that network, use it too; otherwise use a Sepolia faucet linked from Ethereum's official network documentation. Never buy tokens or pay a faucet operator. Faucet quotas and eligibility vary.
Compare the USDC token address to Circle's current contract list: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 on Ethereum Sepolia. A token named USDC with another address is not the asset used here. Test USDC is not redeemable for dollars. Test ETH and test USDC have no financial value.
Run balances until A has at least 1 test USDC and enough test ETH for the estimated transfer fee. B can start with zero test ETH because this exercise only receives tokens there. Record both token balances before sending; a delayed API index may lag the explorer. If the relevant token is absent, do not guess a token ID or substitute a ticker.
node --env-file=.env circle-wallet-lab.mjs balancesStep 5: Review and submit exactly one transfer
The send command is deliberately fixed to one USDC, the official Sepolia contract, wallet A and wallet B from your state file. Inspect lab-state.json first and compare both full addresses with your Console records. The SDK request uses a human-readable amount array ["1"], not 1,000,000 base units, with fee level MEDIUM. Fee level is a strategy, not a fixed price.
The command records the intended recipient and idempotency key before sending. Expected result is a Circle transaction ID; that is not yet confirmation. If submission succeeds but the response is lost, rerunning send reuses the same request key and body rather than creating a new payment. Once an ID is stored, send only prints it. Do not edit/delete state or create a fresh idempotency key to fix an ambiguous timeout.
node --env-file=.env circle-wallet-lab.mjs send
node --env-file=.env circle-wallet-lab.mjs statusStep 6: Read status without blindly resending
Status polls the saved transaction ID with a bounded wait. COMPLETE is the successful terminal state used by this exercise; FAILED, CANCELLED and DENIED are terminal failures. Other states remain non-terminal for this check. A local timeout means unknown/pending, not failed. Run status again later; do not run a new payment.
Record the Circle ID, state and any chain hash. An accepted API request, a broadcast transaction, an included block and an API-complete transaction are different observations. If the transaction fails, inspect its documented error and reconcile the hash before considering a new attempt. Do not assume a failed transaction consumed no gas.
Step 7: Reconcile the recipient and sender
Open the hash on the Ethereum Sepolia explorer and confirm the token contract, sender A, recipient B, amount 1 and successful receipt. A USDC contract interaction can show the token contract as the outer destination; inspect its token-transfer event to identify B. Save the chain ID and hash, not just a screenshot of a green status.
Run balances again. With no other transfers or faucet credits during the measurement window, B's USDC should increase by 1 and A's should decrease by 1. A's test ETH decreases separately by the network fee; B's ETH need not change. If the API lags, wait and compare the explorer instead of declaring a mismatch resolved. An API COMPLETE alone is not evidence of fiat redemption or a business ledger posting.
Step 8: Record acceptance tests and an operational handoff
Keep a short run sheet with UTC timestamps, installed SDK version, chain, wallet IDs, public addresses, starting/ending balances, Circle transaction ID, hash and reviewer. Exclude credentials and recovery material. These checks are a buyer's test plan, not claims that FluidRWA executed your account's transfer.
For a safe failure exercise, run the script without credentials or with a non-test prefix in a throwaway environment: it must stop before making SDK calls. Do not deliberately send to a wrong address, and do not clear an ambiguous live request merely to exercise a retry. Duplicate handling should be tested with mocks or a controlled provider sandbox run, not repeated uncontrolled transfers.
| Check | Pass condition |
|---|---|
| Environment guard | Missing/non-test credentials stop locally; all wallet operations use ETH-SEPOLIA. |
| Wallet creation | Two distinct EOA wallet IDs and addresses match Console. |
| Funding | A holds official Sepolia USDC and test ETH; balances captured before sending. |
| Transfer | One saved idempotency key maps to one Circle transaction ID; retries do not create a second intended payment. |
| Completion | COMPLETE plus a successful Sepolia token-transfer event for exactly 1 USDC. |
| Reconciliation | B +1 USDC, A -1 USDC, separate ETH fee, or a documented unresolved indexing difference. |
| Handoff | An operator can resume status from state without credentials in the evidence file. |
Troubleshooting and safe recovery
| Symptom | Check before acting |
|---|---|
| 401/403 | Check test key, account access and product/environment in Console. Do not print an SDK error object that may contain request headers. |
| Entity-secret error | Confirm registration belongs to this account/environment. Follow Circle rotation/recovery guidance; never generate a replacement over the existing secret casually. |
| Insufficient funds | Check official token balance and A's separate test ETH. Wait for faucet confirmation; do not purchase gas. |
| 429 or temporary server error | Respect provider retry instructions. For mutating calls reuse the saved key/body and reconcile uncertain outcomes. |
| Timeout after submission | Retain state, use the stored ID if available, otherwise reconcile by request key/Console. Do not assume no transfer occurred. |
| COMPLETE but balance unchanged | Check exact chain, contract, recipient and transfer event; allow indexing time and record unresolved discrepancies. |
| State file lost or changed | Stop sending. Reconstruct the existing operation from Console and chain records before creating any new transfer. |
Finish and clean up
You are done only after the successful transfer and balance comparison are recorded, or the run sheet explicitly says blocked/unresolved with the reason. A zero balance query or mock response is not an end-to-end pass.
Revoke the lab API key in Console when finished. Retain or securely dispose of the test secret/recovery file according to your policy; do not confuse deleting a local file with deleting on-chain accounts. The lab omits production controls such as access separation, durable transaction storage, signed webhook processing, monitoring and incident response.
FAQs
Does the test USDC have dollar value?
No. Circle states testnet tokens have no financial value and are not backed by real dollars. Do not send mainnet assets to complete the lab.
Why not use Circle Mint or Arc?
This is specifically a developer-controlled Wallets exercise. Arc is the current quickstart default, but this guide deliberately chooses supported ETH-SEPOLIA with test ETH gas. The APIs and fee assumptions must not be mixed.
Was a real Circle transfer tested by FluidRWA?
No authenticated transfer is claimed. Local code and mock checks cannot certify your credentials, faucet availability or on-chain result. Follow the acceptance checklist and record your actual outcome.