> ## 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.

# Sign transactions

> Sign transactions with developer-controlled wallets on EVM, Solana, or NEAR when you manage broadcasting yourself.

Signing APIs let you build a transaction in your application and have Circle
sign it. You then broadcast the signed transaction using your own node provider.
Use this flow when Circle doesn't provide full
[blockchain infrastructure](/wallets/blockchain-infrastructure) for your
blockchain, or when you prefer to manage broadcasting yourself. For an overview,
see [How signing APIs work](/wallets/signing-apis).

## Prerequisites

Before you begin, make sure you have:

* Obtained an [API key](/api-reference/keys) from the
  [Circle Console](https://console.circle.com/).
* Registered an [entity secret](/wallets/dev-controlled/register-entity-secret).
* Created a
  [developer-controlled wallet](/wallets/dev-controlled/create-your-first-wallet).
* Set up access to a node or RPC provider for your blockchain.
* Installed [Node.js 22+](https://nodejs.org/)

<Note>Only `EOA` wallets are supported for this signing flow.</Note>

<Tabs>
  <Tab title="EVM">
    ## Step 1. Set up your project

    ### 1.1. Create the project and install dependencies

    ```shell theme={null}
    # Create the project directory and initialize Node.js
    mkdir sign-transactions
    cd sign-transactions
    npm init -y

    # Set up module type and a script for EVM signing
    npm pkg set type=module
    npm pkg set scripts.sign-evm="tsx --env-file=.env sign-evm.ts"

    # Install runtime dependencies
    npm install @circle-fin/developer-controlled-wallets viem

    # Install dev dependencies
    npm install --save-dev tsx typescript @types/node
    ```

    ### 1.2. Configure TypeScript (optional)

    <Tip>
      This step is optional. It helps prevent missing types in your IDE or editor.
    </Tip>

    Create a `tsconfig.json` file:

    ```shell theme={null}
    npx tsc --init
    ```

    Then, update the `tsconfig.json` file:

    ```shell theme={null}
    cat <<'EOF' > tsconfig.json
    {
      "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "strict": true,
        "types": ["node"]
      }
    }
    EOF
    ```

    ### 1.3. Set environment variables

    Create a `.env` file in the project directory:

    ```text .env theme={null}
    CIRCLE_API_KEY=YOUR_API_KEY
    CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
    ```

    * `CIRCLE_API_KEY` is your Circle Developer API key.
    * `CIRCLE_ENTITY_SECRET` is your registered entity secret.

    <Tip>
      Open `.env` in your editor rather than writing values with shell commands, and
      add `.env` to your `.gitignore`. This prevents credentials from leaking into
      your shell history or version control.
    </Tip>

    The `npm run start` command loads variables from `.env` using Node.js native
    env-file support.

    ## Step 2. Create a signing wallet

    ```ts theme={null}
    const response = await client.createWallets({
      accountType: "EOA",
      blockchains: ["EVM-TESTNET"],
      count: 1,
      walletSetId: "YOUR_WALLET_SET_ID",
    });
    ```

    <Note>Use `count` to create up to 200 wallets per request.</Note>

    If you already created a native wallet on Ethereum, Polygon, Avalanche,
    Arbitrum, or another EVM-compatible chain, a generic `EVM` wallet under the same
    wallet set maps to the same address. Do not use the generic `EVM` wallet in
    place of the native wallet on those chains, or transactions may become stuck.

    ## Step 3. Build and sign a transaction

    ```ts sign-evm.ts theme={null}
    import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
    import { arcTestnet } from "viem/chains";

    // Initialize the wallets client
    const client = initiateDeveloperControlledWalletsClient({
      apiKey: process.env.CIRCLE_API_KEY!,
      entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
    });

    async function main() {
      // Set signing inputs
      const walletId = "YOUR_WALLET_ID";
      const walletAddress = "YOUR_WALLET_ADDRESS";

      // Build an EVM transaction object for a signing test
      const txObject = {
        chainId: arcTestnet.id,
        nonce: "0",
        to: walletAddress,
        value: "0",
        gas: "21000",
        maxFeePerGas: "41500000000",
        maxPriorityFeePerGas: "1500000000",
      };

      // Sign the transaction
      const response = await client.signTransaction({
        walletId,
        transaction: JSON.stringify(txObject),
      });

      console.log(response.data);
    }

    main().catch((err) => {
      console.error("Error:", err.message || err);
      process.exit(1);
    });
    ```

    The transaction object must match the Ethereum JSON-RPC transaction shape. Use
    your own tooling to prepare it before calling `signTransaction()`. Include the
    correct `chainId` for your network. For supported `EVM-TESTNET` chains, see
    [Chain IDs for Signing Transactions](/wallets/sign-tx-chain-id). When building
    EIP-1559 transactions, include both `maxFeePerGas` and `maxPriorityFeePerGas`.

    ## Step 4. Review the response

    The signing response includes:

    * `signature`
    * `signedTransaction`
    * `txHash`

    You can use `signedTransaction` with your own broadcast flow or chain tooling.
  </Tab>

  <Tab title="Solana">
    ## Step 1. Set up your project

    ### 1.1. Create the project and install dependencies

    ```shell theme={null}
    # Create the project directory and initialize Node.js
    mkdir sign-transactions
    cd sign-transactions
    npm init -y

    # Set up module type and a script for Solana signing
    npm pkg set type=module
    npm pkg set scripts.sign-solana="tsx --env-file=.env sign-solana.ts"

    # Install runtime dependencies
    npm install @circle-fin/developer-controlled-wallets @solana/web3.js

    # Install dev dependencies
    npm install --save-dev tsx typescript @types/node
    ```

    ### 1.2. Configure TypeScript (optional)

    <Tip>
      This step is optional. It helps prevent missing types in your IDE or editor.
    </Tip>

    Create a `tsconfig.json` file:

    ```shell theme={null}
    npx tsc --init
    ```

    Then, update the `tsconfig.json` file:

    ```shell theme={null}
    cat <<'EOF' > tsconfig.json
    {
      "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "strict": true,
        "types": ["node"]
      }
    }
    EOF
    ```

    ### 1.3. Set environment variables

    Create a `.env` file in the project directory:

    ```text .env theme={null}
    CIRCLE_API_KEY=YOUR_API_KEY
    CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
    ```

    * `CIRCLE_API_KEY` is your Circle Developer API key.
    * `CIRCLE_ENTITY_SECRET` is your registered entity secret.

    <Tip>
      Open `.env` in your editor rather than writing values with shell commands, and
      add `.env` to your `.gitignore`. This prevents credentials from leaking into
      your shell history or version control.
    </Tip>

    The `npm run` command in this tutorial loads variables from `.env` using Node.js
    native env-file support.

    ## Step 2. Create a signing wallet

    ```ts theme={null}
    const response = await client.createWallets({
      accountType: "EOA",
      blockchains: ["SOL-DEVNET"],
      count: 1,
      walletSetId: "YOUR_WALLET_SET_ID",
    });
    ```

    <Note>Use `count` to create up to 200 wallets per request.</Note>

    Fund the `SOL-DEVNET` wallet with devnet `SOL` before broadcasting signed
    transactions, or the account will not have the balance needed to pay fees
    onchain.

    ## Step 3. Build and sign a transaction

    ```ts sign-solana.ts theme={null}
    import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
    import {
      Connection,
      PublicKey,
      SystemProgram,
      Transaction,
      clusterApiUrl,
    } from "@solana/web3.js";

    // Initialize the wallets client
    const client = initiateDeveloperControlledWalletsClient({
      apiKey: process.env.CIRCLE_API_KEY!,
      entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
    });

    async function main() {
      // Set transaction inputs
      const walletId = "YOUR_SOLANA_WALLET_ID";
      const senderAddress = "YOUR_WALLET_ADDRESS";
      const recipientAddress = senderAddress;

      // Connect to the Solana RPC provider
      const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

      // Build the Solana transaction for a signing test
      const instruction = SystemProgram.transfer({
        fromPubkey: new PublicKey(senderAddress),
        toPubkey: new PublicKey(recipientAddress),
        lamports: 0,
      });

      const { blockhash } = await connection.getLatestBlockhash();
      const transaction = new Transaction().add(instruction);
      transaction.recentBlockhash = blockhash;
      transaction.feePayer = new PublicKey(senderAddress);

      const rawTransaction = transaction.serialize({
        requireAllSignatures: false,
        verifySignatures: false,
      });

      // Sign the raw transaction
      const response = await client.signTransaction({
        walletId,
        rawTransaction: rawTransaction.toString("base64"),
      });

      // Broadcast the signed transaction
      const signedTransaction = Buffer.from(
        response.data?.signedTransaction ?? "",
        "base64",
      );

      const txid = await connection.sendRawTransaction(signedTransaction);

      console.log(response.data);
      console.log("Broadcast transaction ID:", txid);
    }

    main().catch((err) => {
      console.error("Error:", err.message || err);
      process.exit(1);
    });
    ```

    Base64 encode the raw transaction before you send it to `signTransaction()`. The
    signing request can succeed before the account is funded, but the broadcast step
    can still fail if the wallet has not received devnet `SOL`.

    ## Step 4. Review the response

    The signing response includes:

    * `signature`
    * `signedTransaction`

    If you broadcast the signed payload in the same script, you should also see the
    broadcast transaction ID.
  </Tab>

  <Tab title="NEAR">
    ## Step 1. Set up your project

    ### 1.1. Create the project and install dependencies

    ```shell theme={null}
    # Create the project directory and initialize Node.js
    mkdir sign-transactions
    cd sign-transactions
    npm init -y

    # Set up module type and a script for NEAR signing
    npm pkg set type=module
    npm pkg set scripts.sign-near="tsx --env-file=.env sign-near.ts"

    # Install runtime dependencies
    npm install @circle-fin/developer-controlled-wallets near-api-js

    # Install dev dependencies
    npm install --save-dev tsx typescript @types/node
    ```

    ### 1.2. Configure TypeScript (optional)

    <Tip>
      This step is optional. It helps prevent missing types in your IDE or editor.
    </Tip>

    Create a `tsconfig.json` file:

    ```shell theme={null}
    npx tsc --init
    ```

    Then, update the `tsconfig.json` file:

    ```shell theme={null}
    cat <<'EOF' > tsconfig.json
    {
      "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "strict": true,
        "types": ["node"]
      }
    }
    EOF
    ```

    ### 1.3. Set environment variables

    Create a `.env` file in the project directory:

    ```text .env theme={null}
    CIRCLE_API_KEY=YOUR_API_KEY
    CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
    ```

    * `CIRCLE_API_KEY` is your Circle Developer API key.
    * `CIRCLE_ENTITY_SECRET` is your registered entity secret.

    <Tip>
      Open `.env` in your editor rather than writing values with shell commands, and
      add `.env` to your `.gitignore`. This prevents credentials from leaking into
      your shell history or version control.
    </Tip>

    The `npm run` command in this tutorial loads variables from `.env` using Node.js
    native env-file support.

    ## Step 2. Create a signing wallet

    ```ts theme={null}
    const response = await client.createWallets({
      accountType: "EOA",
      blockchains: ["NEAR-TESTNET"],
      count: 1,
      walletSetId: "YOUR_WALLET_SET_ID",
    });
    ```

    <Note>Use `count` to create up to 200 wallets per request.</Note>

    Fund the `senderAccount` NEAR testnet account before broadcasting signed
    transactions, or the account will not have enough `NEAR` to pay network fees.

    Use these values from the wallet creation response in the next step:

    * `walletId` = the wallet `id`
    * `walletPublicKey` = the wallet `initialPublicKey`
    * `address` = the wallet's implicit account ID onchain

    For the signing script, `senderAccount` is the NEAR testnet account where you
    added the wallet `initialPublicKey` as a full-access key. It is not the Circle
    wallet `address` unless you specifically set up and fund that implicit account
    for signing.

    Before you continue, create or use an existing NEAR testnet account and add the
    wallet `initialPublicKey` as an access key on that account. Then use that
    account ID as `senderAccount` in the signing step. For setup guidance, see
    [Create an Account](https://docs.near.org/getting-started/create-account) and
    [NEAR CLI](https://docs.near.org/tools/cli).

    ## Step 3. Build and sign a transaction

    ```ts sign-near.ts theme={null}
    import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
    import {
      Account,
      JsonRpcProvider,
      nearToYocto,
      actions,
      encodeTransaction,
    } from "near-api-js";

    // Initialize the wallets client
    const client = initiateDeveloperControlledWalletsClient({
      apiKey: process.env.CIRCLE_API_KEY!,
      entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
    });

    const NEAR_CONFIG = {
      networkId: "testnet",
      nodeUrl: "https://rpc.testnet.near.org",
    };

    async function main() {
      // Set transaction inputs
      const walletId = "YOUR_NEAR_WALLET_ID";
      const walletPublicKey = "YOUR_NEAR_INITIAL_PUBLIC_KEY";
      const senderAccount = "YOUR_NEAR_TESTNET_ACCOUNT";
      const recipientAccount = senderAccount;

      // Connect to the NEAR RPC provider
      const provider = new JsonRpcProvider({ url: NEAR_CONFIG.nodeUrl });
      const sender = new Account(senderAccount, provider);

      const transactionActions = [actions.transfer(nearToYocto("0"))];

      // Build and serialize the NEAR transaction for a signing test
      const transaction = await sender.createTransaction({
        receiverId: recipientAccount,
        actions: transactionActions,
        publicKey: walletPublicKey,
      });

      const rawTransaction = Buffer.from(encodeTransaction(transaction)).toString(
        "base64",
      );

      // Sign the raw transaction
      const response = await client.signTransaction({
        walletId,
        rawTransaction,
      });

      console.log(response.data);
    }

    main().catch((err) => {
      console.error("Error:", err.message || err);
      process.exit(1);
    });
    ```

    Base64 encode the raw transaction before you send it to `signTransaction()`.

    For example, if the create-wallet response includes:

    ```text theme={null}
    {
      id: "40313ab7-9abd-57f3-a067-617df1530383",
      address: "8c18a0fb0717bc82f6cc7ca019e2cbd92ca5cb0767f47ef2cb2a70a0d561ad88",
      initialPublicKey: "ARsrrXKzD5C9Bj9BxEzTQNNsBUGk35FM3vyUL5PbgwLj"
    }
    ```

    and you add `initialPublicKey` to `my-circle-signer.testnet`, then set:

    ```ts theme={null}
    const walletId = "40313ab7-9abd-57f3-a067-617df1530383";
    const walletPublicKey = "ARsrrXKzD5C9Bj9BxEzTQNNsBUGk35FM3vyUL5PbgwLj";
    const senderAccount = "my-circle-signer.testnet";
    ```

    ## Step 4. Review the response

    The signing response includes:

    * `signature`
    * `signedTransaction`
    * `txHash`

    Broadcast that signed payload through your own RPC or node provider.
  </Tab>
</Tabs>
