Skip to content

circle-ccooper/stablecoin-workshop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Circle Stablecoin Workshop

Hands-on workshop for developers new to building with stablecoins.

In about 30 minutes you'll use Circle's TypeScript SDKs to:

  1. Embed wallets in a backend app — no seed phrases, no browser wallets
  2. Receive USDC and EURC on Arc Testnet
  3. Send stablecoins between wallets you control
  4. Bridge USDC across chains: Arc → Base Sepolia or Arc → Solana Devnet

Each step is one short TypeScript file in src/. Read the file header, run the script, then move on.


Why Arc Testnet?

Arc is Circle's EVM-compatible Layer-1 designed for stablecoin finance. The thing that matters for this workshop:

USDC is the native gas token on Arc.

That means your wallet only needs USDC — no separate ETH, MATIC, or SOL — to do everything in this workshop: send transfers and bridge across chains. It also makes the developer experience radically simpler when you start building real apps. One asset, one balance to monitor, one fee to estimate.


What you'll build

                     ┌──────────────────────────────────┐
                     │   Your wallet set on Circle      │
                     │   (apiKey + entitySecret)        │
                     └──────────────────────────────────┘
                                    │
                ┌───────────────────┼───────────────────┐
                │                   │                   │
        Wallet A (Arc)      Wallet B (Arc)     Wallet C (Base / Sol)
        sender              receiver           bridge destination
                │                   ▲                   ▲
                │  step 4: send     │                   │
                ├───── 1 USDC ──────┘                   │
                │                                       │
                │  step 5: bridge (CCTP + Forwarder)    │
                └─────── 1 USDC ────────────────────────┘

All wallets live in one wallet set, all signing flows through the same API key + entity secret.


Prerequisites

  • Node.js 20+ and npm
  • A Circle Developer account: https://console.circle.com
  • 5–10 minutes to register an entity secret (one-time setup)

That's it. No Hardhat, no Anchor, no private keys, no faucets you have to chase across Discord.


Setup (one-time)

1. Install dependencies

npm install

2. Get a Circle API key

  1. Sign in at https://console.circle.com
  2. Switch the environment toggle to Testnet (top-right)
  3. Go to API KeysCreate a key
  4. Copy the key (starts with TEST_API_KEY:…)

3. Create your .env

cp .env.example .env

Open .env and paste in your CIRCLE_API_KEY. Leave CIRCLE_ENTITY_SECRET=your_32_byte_hex_entity_secret_here as-is for now — the next step will fill it in for you.

4. Generate and register an entity secret

The entity secret is a 32-byte key Circle uses to encrypt every signing request your app makes. You generate it locally; Circle never sees the raw value, only an encrypted ciphertext that proves you possess it.

Skip this step if you already registered an entity secret for your Circle account (e.g. in the Circle Developer Console quickstart or another project). Just paste your existing CIRCLE_ENTITY_SECRET into .env and continue to step 1.

Otherwise, run:

npm run 0-register-entity-secret

This script will:

  1. Generate a fresh 32-byte hex secret using Node's crypto
  2. Register its ciphertext with Circle via the SDK
  3. Save Circle's recovery file (recovery_file_*.dat) to the project root
  4. Write CIRCLE_ENTITY_SECRET=… into your .env file

Important: Move the recovery file to a password manager or offline storage. It's the only way to rotate the entity secret if it's ever lost. Circle does not store your entity secret and cannot recover it for you.


The workshop (five steps)

Run each script in order. Each one prints a clear "Next:" line at the end.

Step Command What it does
0 npm run 0-register-entity-secret (Prereq) Generate + register an entity secret if you don't have one yet
1 npm run 1-create-wallets Create two SCA wallets on Arc Testnet
2 npm run 2-fund-wallet Receive USDC + EURC from the Circle faucet
3 npm run 3-setup-monitoring Filter wallet balances to USDC + EURC
4 npm run 4-transfer Send 1 USDC between two wallets you own
5 npm run 5-bridge Bridge 1 USDC from Arc → Base Sepolia (CCTP + Forwarder)

Two ways to run step 5:

# Default: bridge to Base Sepolia
npm run 5-bridge

# Bridge to Solana Devnet instead
BRIDGE_DESTINATION=solana npm run 5-bridge

State is persisted to wallet-state.json between scripts, so destination wallets are created once and reused on reruns.


What each file demonstrates

src/
├── client.ts                   ← shared SDK client, token addresses, state file
├── log.ts                      ← shared console formatting (banners, sections, kv)
├── 0-register-entity-secret.ts ← generateEntitySecret + registerEntitySecretCiphertext
├── 1-create-wallets.ts         ← client.createWalletSet + client.createWallets
├── 2-fund-wallet.ts            ← reading wallet balances via REST
├── 3-setup-monitoring.ts       ← client.updateMonitoredTokensScope + createMonitoredTokens
├── 4-transfer.ts               ← client.createTransaction + polling for confirmation
└── 5-bridge.ts                 ← AppKit + Circle Wallets adapter + CCTP Forwarding Service

Each script is short (under 150 lines) and the SDK call you care about is always near the bottom, after a header that explains the concept.


Key SDKs at a glance

Package What it does
@circle-fin/developer-controlled-wallets Create + manage wallets, sign transactions, read balances
@circle-fin/app-kit High-level bridge operations across chains
@circle-fin/adapter-circle-wallets Lets App Kit sign using your developer-controlled wallets

You never touch viem, ethers, or web3.js directly. You also never touch a private key — Circle holds them in HSMs and signs on your behalf when you call the SDK.


Troubleshooting

unable to get local issuer certificate The npm scripts already prefix NODE_TLS_REJECT_UNAUTHORIZED=0 to handle corporate proxies / SSL inspection. If you're running tsx directly, set that env var yourself.

wallet-state.json not found Run step 1 first: npm run 1-create-wallets.

Token not found in wallet balance after 8 attempts (step 3) The Circle faucet hasn't landed your EURC yet. Re-run step 2 and wait until both USDC and EURC show up before running step 3.

Bridge fee estimation fails The script falls back to bridging the net amount as-is — your recipient may receive slightly less than 1 USDC after fees. Re-run a few minutes later if you want the exact-amount behaviour.

Bridge to Solana ends with mint [error] Solana mints require the recipient to have a USDC Associated Token Account (ATA) — without one, there is nowhere on-chain for the bridged USDC to land. A freshly-created Circle Solana wallet does not have an ATA yet. Fund the address with a small amount of USDC from a faucet first (the faucet will create the ATA for you) and then re-run the bridge: https://faucet.circle.com → select Solana Devnet.

Want to start over?

npm run reset    # removes wallet-state.json

Your wallets stay in Circle's database — only the local pointer file is removed. To fully clean up, archive the wallet set in the Circle Console.


Going further

After the workshop:

  • Try mainnet by swapping TEST_API_KEY:… for a LIVE_API_KEY:… and adjusting the chain identifiers (ARC instead of ARC-TESTNET, BASE instead of BASE-SEPOLIA, etc.)
  • Add a third wallet to the set and chain transfers (A → B → C)
  • Sponsor gas with Circle's Gas Station so users don't need a USDC balance to send their first transaction
  • Build a webhook listener for transactions.outbound.confirmed events so your app reacts to on-chain state without polling

Documentation:

About

Hands-on workshop for developers new to building with stablecoins.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors