Send an Outbound Transfer

This guide outlines how to initiate a currency transfer out of a previously created user-controlled wallet. If you have not yet created a user-controlled wallet, go to this guide. If you do not have any tokens in your wallet, go to the inbound transfer guide.

The following steps utilize Circle’s sample applications in combination with API requests that can be done via Circle's API references or cURL requests. cURL request will be provided inline, while API references will be linked from the API endpoint code text. You can find instructions on using it in the testing via the reference pages guide.

1. Run Sample App

Once you have the iOS or Android sample app setup locally you will then

  1. Run the simulator
  2. Acquire an App ID from the developer console wallet configurator
  3. Add the App ID to the sample app

2. Acquire a Session Token

You will start by making a request to POST /users/token using a previously created userId. The userToken is a 60-minute session token to initiate requests requiring a user challenge (PIN code entry). After 60 minutes, the session expires, and a new userToken must be generated via the same endpoint.

From this response, you will acquire the encryptionKey and userToken which you should provide in the respective sample app fields. Additionally, you will use the userToken in Step 3.

curl --request POST \
     --url 'https://api.circle.com/v1/w3s/users/token' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --data '
{
  "userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463"
}
'
{
  "data": {
    "userToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCC9.eyJkZXZlbG9wZXJFbnRpdHlFbnZpcm9ubWVudCI6IlRFU1QiLCJlbnRpdHlJZCI6IjRlMDdhOGM5LTIxOTAtNDVlNC1hNjc0LWQyMGFkNjg4MWI3YyIsImV4cCI6MTY5MDU1MjcwNywiaWF0IjoxNjkwNTQ5MTA3LCJpbnRlcm5hbFVzZXJJZCI6ImQ2ZjkzODliLWQ5MzUtNWFlYy1iOTVhLWNjNTk1NjA2YWM5NiIsImlzcyI6Imh0dHBzOi8vcHJvZ3JhbW1hYmxlLXdhbGxldC5jaXJjbGUuY29tIiwianRpIjoiMmE0YmJlMzAtZTdkZi00YmM2LThiODMtNTk0NGUyMzE2ODlkIiwic3ViIjoiZXh0X3VzZXJfaWRfOSJ9.dhfByhxZFbJx0XWlzxneadT4RQWdnxLu3FSN9ln65hCDOfavaTL1sc4h-jUR8i4zMmfdURw3FFcQIdSbm-BUg6M7FP_fp-cs9xBbNmRZa31gMd1aKdcajJ9SvlVrfUowYfGXM3VcNF8rtTFtW-gk1-KzU4u10U35XXbbMcW1moxE0Rqx_fKotDgk2VdITuuds5d5TiQzAXECqeCOCtNoDKktMkglltbnLxOaRl2ReZjGt-ctD2V0DbYNO4T_ndPSUDI6qD7dXQRed5uDcezJYoha3Qj3tFGBglEnox2Y6DWTbllqjwmfTGrU8Pr0yz4jQz7suGwmiCzHPxcpYxMzYQ",
    "encryptionKey": "Tlcyxz7Ts9ztRLQq5+pic0MIETblYimOo2d7idV/UFM="
  }
}

3. Estimate gas

3.a Get the wallets ID

Make request to GET /wallets passing in the wallets userToken to get the walletId.

curl --request GET \
		 --url 'https://api.circle.com/v1/w3s/wallets?pageSize=10' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'X-User-Token: <USER_TOKEN>'
{
  "data": {
    "wallets": [
      {
        "id": "01899cf2-d415-7052-a207-f9862157e546",
        "state": "LIVE",
        "walletSetId": "01899cf2-d407-7f89-b4d9-84d63573f138",
        "custodyType": "ENDUSER",
        "userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
        "address": "0x075e62c80e55d024cfd8fd4e3d1184834461db57",
        "addressIndex": 0,
        "blockchain": "ETH-GOERLI",
        "accountType": "EOA",
        "updateDate": "2023-07-28T14:41:47Z",
        "createDate": "2023-07-28T14:41:47Z"
      }
    ]
  }
}

3.b Check the wallet balance and acquire the token ID

Make a request to GET /wallet/{id}/balances to check the balance of tokens and acquire the tokenId you intend to transfer.

curl --request GET \
		 --url 'https://api.circle.com/v1/w3s/wallets/{id}/balances' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'X-User-Token: <USER_TOKEN>'
{
  "data": {
    "tokenBalances": [
      {
        "token": {
          "id": "3495b830-d0f9-524b-89c0-4408274fed6e",
          "blockchain": "ETH-GOERLI",
          "tokenAddress": "",
          "standard": "",
          "name": "Ethereum-Goerli",
          "symbol": "ETH-GOERLI",
          "decimals": 18,
          "isNative": true,
          "updateDate": "2023-06-29T08:02:50Z",
          "createDate": "2023-06-29T08:02:50Z"
        },
        "amount": "0.0788",
        "updateDate": "2023-07-28T19:07:34Z"
      }
    ]
  }
}

3.c Estimate the cost of transferring the token

Make a request to POST transactions/transfer/estimateFee to estimate the fees for transferring the token.

curl --request POST \
		 --url 'https://api.circle.com/v1/w3s/transactions/transfer/estimateFee' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'X-User-Token: <USER_TOKEN>' \
     --data '{
  "amounts": [".0001"],
  "destinationAddress": "0xEb9614D6d001391e22dDbbEA7571e9823A469c1f",
  "tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e",    
  "walletId": "01899cf2-d415-7052-a207-f9862157e546"
}
{
  "data": {
    "low": {
      "gasLimit": "21000",
      "baseFee": "2.456220277",
      "priorityFee": "1.022783914",
      "maxFee": "5.935224468"
    },
    "medium": {
      "gasLimit": "21000",
      "baseFee": "2.456220277",
      "priorityFee": "2.655282857",
      "maxFee": "7.567723411"
    },
    "high": {
      "gasLimit": "21000",
      "baseFee": "2.456220277",
      "priorityFee": "15.986229693",
      "maxFee": "20.898670247"
    }
  }
}

4. Initiate Blockchain Transfer

Make a request to POST /user/transactions/transfer to initiate a blockchain transfer from a specifiedwalletId to a specified blockchain addressdestinationAddress. This call returns a challengeId, which is used within the sample app to have the user enter their PIN code to authorize the transfer.

curl --request POST \
		 --url 'https://api.circle.com/v1/w3s/user/transactions/transfer' \
     --header 'Content-Type: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'X-User-Token: <USER_TOKEN>' \
     --data '{
  "userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
  "idempotencyKey": "607a0972-17f9-4d56-8ca3-a0e94adc3210",
  "amounts": [".0001"],
  "destinationAddress": "0x6E5eAf34c73D1CD0be4e24f923b97CF38e10d1f3",
  "tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e", 
  "walletId": "01899cf2-d415-7052-a207-f9862157e546",
  "feeLevel": "MEDIUM"
}'
{
  "data": {
    "challengeId": "0d1b5f41-1381-50af-983b-f54691415158"
  }
}

5. Authorize transfer from the sample app

Using the sample application, enter the userToken and secretKey returned from Step 2. Also, enter the challengeId returned from Step 4. 

At this point, you should be ready to execute your first transfer through the sample app. Click Execute in the sample app to continue. 

The sample application takes you through the authentication process, which includes the user entering their PIN code to authorize the transfer. 

6. Check the Transfer Status

As a transfer's state changes and ultimately completes, Circle sends notifications to a subscribed endpoint. You can find a list of all possible states in the Asynchronous States and Statuses guide. The Webhook notification will be similar to the one below.

{
  "subscriptionId": "d4c07d5f-f05f-4fe4-853d-4dd434806dfb",
  "notificationId": "acab8c14-92ae-481a-8335-6eb5271da014",
  "notificationType": "transactions.outbound",
  "notification": {
    "id": "720a46f2-35f8-55b0-845b-63fafca29580",
    "blockchain": "ETH-GOERLI",
    "walletId": "01899cf2-d415-7052-a207-f9862157e546",
    "tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e",
    "userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
    "destinationAddress": "0xeb9614d6d001391e22ddbbea7571e9823a469c1f",
    "amounts": [
      "0.0001"
    ],
    "nftTokenIds": [],
    "state": "CONFIRMED",
    "errorReason": "",
    "transactionType": "OUTBOUND",
    "createDate": "2023-07-28T19:57:34Z",
    "updateDate": "2023-07-28T19:57:50Z"
  },
  "timestamp": "2023-07-28T19:57:50.746029415Z",
  "version": 2
}

Alternatively, you can poll GET /transactions using the userId or userToken associated with your user. 

curl --request GET \
     --url 'https://api.circle.com/v1/w3s/transactions' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'X-User-Token: <USER_TOKEN>'
{
  "data": {
    "transactions": [
      {
        "id": "720a46f2-35f8-55b0-845b-63fafca29580",
        "blockchain": "ETH-GOERLI",
        "tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e",
        "walletId": "01899cf2-d415-7052-a207-f9862157e546",
        "sourceAddress": "0x075e62c80e55d024cfd8fd4e3d1184834461db57",
        "destinationAddress": "0xeb9614d6d001391e22ddbbea7571e9823a469c1f",
        "transactionType": "OUTBOUND",
        "custodyType": "ENDUSER",
        "state": "CONFIRMED",
        "amounts": [
          "0.0001"
        ],
        "nfts": null,
        "txHash": "0x26e84f1e5b91b44946171de0133a42136e01ad113c389018e87afae706732430",
        "blockHash": "0x64cca66b1eb813d69f89b6cbde0d88aca710b32aa8f41eaf7a5c820543ce49c4",
        "blockHeight": 9424101,
        "networkFee": "",
        "firstConfirmDate": "2023-07-28T19:57:36Z",
        "operation": "TRANSFER",
        "userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
        "abiParameters": null,
        "createDate": "2023-07-28T19:57:34Z",
        "updateDate": "2023-07-28T19:57:50Z"
      },
      {
        "id": "97d22a88-6d25-5947-a7b6-61b3dc668057",
        "blockchain": "ETH-GOERLI",
        "tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e",
        "walletId": "01899cf2-d415-7052-a207-f9862157e546",
        "sourceAddress": "0x6e5eaf34c73d1cd0be4e24f923b97cf38e10d1f3",
        "destinationAddress": "0x075e62c80e55d024cfd8fd4e3d1184834461db57",
        "transactionType": "INBOUND",
        "custodyType": "ENDUSER",
        "state": "COMPLETE",
        "amounts": [
          "0.0788"
        ],
        "nfts": null,
        "txHash": "0xdd2f81a78605dcad759265c703fb2b4c507c5ea100319338422714bfcde77225",
        "blockHash": "0x4df6092fdb868331614771ff11944b43051cf6ed1067f8cfa55e9d40ef61426b",
        "blockHeight": 9423950,
        "networkFee": "0.000069299258595",
        "firstConfirmDate": "2023-07-28T19:07:24Z",
        "operation": "TRANSFER",
        "userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
        "abiParameters": null,
        "createDate": "2023-07-28T19:07:34Z",
        "updateDate": "2023-07-28T19:13:37Z"
      }
    ]
  }
}

What’s Next

Congratulations! You’ve received your first transaction to your user-controlled wallet. To learn how wallet recovery works, go to