Skip to main content
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:
    .env
    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.
add-delegate.ts
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.
remove-delegate.ts
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