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.
When using
Smart Contract Account (SCA) wallets
with Gateway, you need to authorize an Externally Owned Account (EOA) to sign
burn intents on behalf of the SCA. This is necessary because Gateway requires
ECDSA signatures for static verification, which SCAs cannot provide directly.
This guide demonstrates how to add and remove delegates for Gateway transfers
from SCA wallets using Circle
Developer-controlled wallets.
Prerequisites
Before you begin, ensure that you’ve:
-
Installed Node.js v22+
-
Created a Circle Developer Console account
-
Obtained an API key and
registered your Entity Secret
-
Created an SCA wallet and an EOA wallet via Circle Developer-Controlled
Wallets
- The SCA wallet holds your Gateway USDC deposits
- The EOA wallet will sign burn intents on behalf of the SCA
-
Deposited USDC into the Gateway Wallet from your SCA wallet
-
Created a TypeScript project with the
Developer-Controlled Wallets SDK
installed
-
Set up a
.env file with the following variables:
CIRCLE_API_KEY={YOUR_API_KEY}
CIRCLE_ENTITY_SECRET={YOUR_ENTITY_SECRET}
DEPOSITOR_ADDRESS={YOUR_SCA_WALLET_ADDRESS}
DELEGATE_WALLET_ADDRESS={YOUR_EOA_DELEGATE_ADDRESS}
Add a Delegate
Follow these steps to authorize an EOA delegate to sign burn intents on behalf
of your SCA wallet.
Step 1. Create the add delegate script
Create a new file called add-delegate.ts in the root of your project and add
the following code. This calls the
addDelegate()
method on the Gateway Wallet contract on Arc Testnet.
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
const GATEWAY_WALLET = "0x0077777d7EBA4688BDeF3E311b846F25870A19B9";
const USDC_ADDRESS = "0x3600000000000000000000000000000000000000"; // Arc Testnet
const BLOCKCHAIN = "ARC-TESTNET";
const client = initiateDeveloperControlledWalletsClient({
apiKey: process.env.CIRCLE_API_KEY!,
entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
});
async function waitForTx(txId: string) {
while (true) {
const { data } = await client.getTransaction({ id: txId });
const state = data?.transaction?.state;
if (["COMPLETE", "CONFIRMED"].includes(state!)) return;
if (["FAILED", "DENIED", "CANCELLED"].includes(state!))
throw new Error(`Failed: ${state}`);
await new Promise((resolve) => setTimeout(resolve, 3000));
}
}
async function main() {
console.log(`\n=== Processing ${BLOCKCHAIN} ===`);
console.log(`Adding delegate: ${process.env.DELEGATE_WALLET_ADDRESS}`);
const tx = await client.createContractExecutionTransaction({
walletAddress: process.env.DEPOSITOR_ADDRESS!,
blockchain: BLOCKCHAIN,
contractAddress: GATEWAY_WALLET,
abiFunctionSignature: "addDelegate(address,address)",
abiParameters: [USDC_ADDRESS, process.env.DELEGATE_WALLET_ADDRESS!],
fee: { type: "level", config: { feeLevel: "MEDIUM" } },
});
await waitForTx(tx.data?.id!);
console.log(`Done on ${BLOCKCHAIN}`);
}
main().catch((error) => {
console.error("\nError:", error);
process.exit(1);
});
Step 2. Run the script
Run the script with the following command:
npx tsx --env-file=.env add-delegate.ts
The script output shows “Done on <SELECTED_BLOCKCHAIN>” when the delegate is
successfully authorized. You can now use the delegate to sign burn intents on
behalf of your SCA wallet when making Gateway transfers.
Step 3. Repeat for other chains
To add the delegate on additional chains where the SCA holds Gateway deposits,
repeat Steps 1-2 with different BLOCKCHAIN and USDC_ADDRESS values for your
target chains. Check the
USDC contract addresses for the correct
values.
The delegate authorization remains valid until explicitly removed with the
removeDelegate()
method.
Remove a Delegate
Follow these steps to revoke a delegate’s authorization to sign burn intents on
behalf of your SCA wallet. After removing a delegate, note that:
- The delegate can no longer create new burn intents for the SCA wallet
- Burn intents that were already signed by the delegate remain valid and can
still be executed on-chain
- This ensures that burns may be executed safely even in the event of a
revocation
- The Gateway API reflects revocations as soon as they’re finalized on-chain
This behavior is by design to prevent race conditions where a valid burn intent
becomes unusable mid-flight.
Step 1. Create the remove delegate script
Create a new file called remove-delegate.ts in the root of your project and
add the following code. This is nearly identical to the add delegate script, but
calls
removeDelegate()
instead.
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
const GATEWAY_WALLET = "0x0077777d7EBA4688BDeF3E311b846F25870A19B9";
const USDC_ADDRESS = "0x3600000000000000000000000000000000000000"; // Arc Testnet
const BLOCKCHAIN = "ARC-TESTNET";
const client = initiateDeveloperControlledWalletsClient({
apiKey: process.env.CIRCLE_API_KEY!,
entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
});
async function waitForTx(txId: string) {
while (true) {
const { data } = await client.getTransaction({ id: txId });
const state = data?.transaction?.state;
if (["COMPLETE", "CONFIRMED"].includes(state!)) return;
if (["FAILED", "DENIED", "CANCELLED"].includes(state!))
throw new Error(`Failed: ${state}`);
await new Promise((resolve) => setTimeout(resolve, 3000));
}
}
async function main() {
console.log(`\n=== Processing ${BLOCKCHAIN} ===`);
console.log(`Removing delegate: ${process.env.DELEGATE_WALLET_ADDRESS}`);
const tx = await client.createContractExecutionTransaction({
walletAddress: process.env.DEPOSITOR_ADDRESS!,
blockchain: BLOCKCHAIN,
contractAddress: GATEWAY_WALLET,
abiFunctionSignature: "removeDelegate(address,address)",
abiParameters: [USDC_ADDRESS, process.env.DELEGATE_WALLET_ADDRESS!],
fee: { type: "level", config: { feeLevel: "MEDIUM" } },
});
await waitForTx(tx.data?.id!);
console.log(`Done on ${BLOCKCHAIN}`);
}
main().catch((error) => {
console.error("\nError:", error);
process.exit(1);
});
Step 2. Run the script
Run the script with the following command:
npx tsx --env-file=.env remove-delegate.ts