Transfer Between Go Accounts

Overview

You can transfer assets between Go Accounts off chain. Off chain transfers — also known as book transfers — are ledger-based movements that BitGo processes internally with no on-chain transaction involved. Because the transfer stays within BitGo's ledger, it settles instantly, incurs no blockchain fees, and assets never leave BitGo custody.

Book transfers use the same build-authenticate-send flow as on-chain withdrawals, with one key difference: you pass the destination Go Account wallet ID instead of a blockchain address.

📘

Note

Transfers between Go Accounts are off-chain and do not trigger the Transaction Request webhook. Use the Transfer webhook to monitor these transfers.

Prerequisites

1. Build Transaction

Build a transaction from the source Go Account to the destination Go Account. Pass the destination Go Account wallet ID in the walletId field instead of an address.

Endpoint: Build a Transaction

export COIN="<CRYPTO_ASSET_ID>" # Crypto asset IDs in Go Accounts always start with "ofc" (such as ofctbtc)
export WALLET_ID="<SOURCE_GO_ACCOUNT_WALLET_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export AMOUNT="<AMOUNT_IN_BASE_UNITS>"
export DESTINATION_WALLET_ID="<DESTINATION_GO_ACCOUNT_WALLET_ID>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/tx/build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "recipients": [
      {
        "amount": "'"$AMOUNT"'",
        "walletId": "'"$DESTINATION_WALLET_ID"'"
      }
    ]
}'
let params = {
  recipients: [
    {
      amount: 1000,
      walletId: '654ec786c07fe6dc0dcfe03916ec4bb0',
    },
  ],
};
wallet.prebuildTransaction(params).then(function (transaction) {
  console.dir(transaction);
});

Step Result

The response payload contains shortCircuitBlockchainTransfer: true and isIntraJXTransfer: true, confirming that BitGo recognized this as an off-chain book transfer.

{
  "payload": "{\"coin\":\"ofctbtc\",\"recipients\":[{\"address\":\"2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY\",\"amount\":\"10000\"}],\"fromAccount\":\"63ff43c474598902272894f328452ad7\",\"nonce\":\"80bc93b5-eeec-4c11-afca-f6dae4bba2f2\",\"timestamp\":\"2023-09-28T10:55:39.135Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":true,\"isIntraJXTransfer\":true}",
  "feeInfo": { "feeString": "0" },
  "coin": "ofc",
  "token": "ofctbtc"
}

2. Authenticate Transaction

Use your Go Account passphrase to authenticate the transaction. To ensure your passphrase isn't passed over the Internet, you must use BitGo Express in external-signing mode or the JavaScript SDK. If you're using Express in external signing-mode, configure your Go Account passphrase as the WALLET_<walletId>_PASSPHRASE environment variable.

export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export WALLET_ID="<SOURCE_GO_ACCOUNT_WALLET_ID>"
export WALLET_PASSPHRASE="<YOUR_GO_ACCOUNT_PASSPHRASE>"

curl -X POST \
  http://$BITGO_EXPRESS_HOST/api/v2/ofc/signPayload \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
   "walletId": "'"$WALLET_ID"'",
   "walletPassphrase": "'"$WALLET_PASSPHRASE"'",
   "payload": "{\"coin\":\"ofctbtc\",\"recipients\":[{\"address\":\"2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY\",\"amount\":\"10000\"}],\"fromAccount\":\"63ff43c474598902272894f328452ad7\",\"nonce\":\"80bc93b5-eeec-4c11-afca-f6dae4bba2f2\",\"timestamp\":\"2023-09-28T10:55:39.135Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":true,\"isIntraJXTransfer\":true}"
}'
const wallet = await bitgo.coin('ofc').wallets().get({ id: goAccountId });

if (wallet === undefined) {
  throw new Error(`Could not find OFC wallet ${goAccountId}`);
}

const walletPassphrase = "<YOUR_WALLET_PASSPHRASE>";
const tradingAccount = wallet.toTradingAccount();
const stringifiedPayload = JSON.stringify(<payload_returned_by_tx/build_api>);
const signature = await tradingAccount.signPayload({
  payload: stringifiedPayload,
  walletPassphrase,
});

Step Result

{
  "coin": "ofctbtc",
  "recipients": [
    {
      "address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
      "amount": "10000"
    }
  ],
  "fromAccount": "63ff43c474598902272894f328452ad7",
  "nonce": "80bc93b5-eeec-4c11-afca-f6dae4bba2f2",
  "timestamp": "2023-09-28T10:55:39.135Z",
  "feeString": "0",
  "shortCircuitBlockchainTransfer": true,
  "isIntraJXTransfer": true,
  "payload": "{\"coin\":\"ofctbtc\",\"recipients\":[{\"address\":\"2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY\",\"amount\":\"10000\"}],\"fromAccount\":\"63ff43c474598902272894f328452ad7\",\"nonce\":\"80bc93b5-eeec-4c11-afca-f6dae4bba2f2\",\"timestamp\":\"2023-09-28T10:55:39.135Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":true,\"isIntraJXTransfer\":true}",
  "signature": "1f989926e3fc8ed0138bd0e188e90e2c5321ef86734896886c004005740a005dfd2008142dbc48aee68d1f576c4a9ac558e5b7b4acbd5aa765710ea636aca9d032"
}

3. Send Transaction

Endpoint: Send Half-Signed Transaction

export COIN="<CRYPTO_ASSET_ID>"
export WALLET_ID="<SOURCE_GO_ACCOUNT_WALLET_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/tx/send \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "halfSigned": {
      "coin": "ofctbtc",
      "recipients": [
        {
          "address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
          "amount": "10000"
        }
      ],
      "fromAccount": "63ff43c474598902272894f328452ad7",
      "nonce": "80bc93b5-eeec-4c11-afca-f6dae4bba2f2",
      "timestamp": "2023-09-28T10:55:39.135Z",
      "idfSignedTimestamp": "2023-09-28T10:55:38.732Z",
      "idfVersion": 1,
      "idfUserId": "628ca09d1b78a6022750254f0777561a",
      "feeString": "0",
      "shortCircuitBlockchainTransfer": true,
      "isIntraJXTransfer": true,
      "payload": "{\"coin\":\"ofctbtc\",\"recipients\":[{\"address\":\"2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY\",\"amount\":\"10000\"}],\"fromAccount\":\"63ff43c474598902272894f328452ad7\",\"nonce\":\"80bc93b5-eeec-4c11-afca-f6dae4bba2f2\",\"timestamp\":\"2023-09-28T10:55:39.135Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":true,\"isIntraJXTransfer\":true}",
      "signature": "1f989926e3fc8ed0138bd0e188e90e2c5321ef86734896886c004005740a005dfd2008142dbc48aee68d1f576c4a9ac558e5b7b4acbd5aa765710ea636aca9d032"
    }
  }'

Step Result

The transactionType field confirms that BitGo processed this as a BOOK_TRANSFER.

{
  "transfer": {
    "id": "65155c4a72fddb000774edbee5fa75fd",
    "coin": "ofctbtc",
    "wallet": "63ff43c474598902272894f328452ad7",
    "walletType": "trading",
    "enterprise": "63f31592e277e50837712300495902c7",
    "txid": "ddd6875812fa59f899a68059ff303bfa6d39fc350ee687c407383d0444b43d2b",
    "height": 999999999,
    "heightId": "999999999-65155c4a72fddb000774edbe",
    "date": "2023-09-28T10:58:18.272Z",
    "type": "send",
    "value": -10000,
    "valueString": "-10000",
    "baseValue": -10000,
    "baseValueString": "-10000",
    "feeString": "0",
    "payGoFee": 0,
    "payGoFeeString": "0",
    "state": "signed",
    "instant": false,
    "isReward": false,
    "isFee": false,
    "tags": [
      "63ff43c474598902272894f328452ad7",
      "63f31592e277e50837712300495902c7"
    ],
    "history": [
      {
        "date": "2023-09-28T10:58:18.340Z",
        "action": "signed"
      },
      {
        "date": "2023-09-28T10:58:18.272Z",
        "user": "628ca09d1b78a6022750254f0777561a",
        "action": "created"
      }
    ],
    "signedDate": "2023-09-28T10:58:18.340Z",
    "entries": [
      {
        "address": "63ff43c474598902272894f328452ad7",
        "wallet": "63ff43c474598902272894f328452ad7",
        "value": -10000,
        "valueString": "-10000"
      },
      {
        "address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
        "value": 10000,
        "valueString": "10000"
      }
    ],
    "signedTime": "2023-09-28T10:58:18.340Z",
    "createdTime": "2023-09-28T10:58:18.272Z"
  },
  "txid": "ddd6875812fa59f899a68059ff303bfa6d39fc350ee687c407383d0444b43d2b",
  "tx": {
    "transaction": {
      "coin": "ofctbtc",
      "recipients": [
        {
          "address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
          "amount": "10000"
        }
      ],
      "fromAccount": "63ff43c474598902272894f328452ad7",
      "nonce": "80bc93b5-eeec-4c11-afca-f6dae4bba2f2",
      "timestamp": "2023-09-28T10:55:39.135Z",
      "idfSignedTimestamp": "2023-09-28T10:55:38.732Z",
      "idfVersion": 1,
      "idfUserId": "628ca09d1b78a6022750254f0777561a",
      "feeString": "0",
      "shortCircuitBlockchainTransfer": true,
      "isIntraJXTransfer": true
    },
    "signature": "1f989926e3fc8ed0138bd0e188e90e2c5321ef86734896886c004005740a005dfd2008142dbc48aee68d1f576c4a9ac558e5b7b4acbd5aa765710ea636aca9d032",
    "transactionType": "BOOK_TRANSFER"
  },
  "status": "signed"
}

4. Approve Transaction (Optional)

📘

Note

If you configure an approval requirement for transfers, you can't approve your own transactions — another admin must approve them.

Endpoint: Update Pending Approval

export APPROVAL_ID="<APPROVAL_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export OTP="<OTP>"

curl -X PUT \
  https://app.bitgo-test.com/api/v2/pendingApprovals/$APPROVAL_ID \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "state": "approved",
    "otp": "'"$OTP"'"
}'
const baseCoin = bitgo.coin(initialPendingApproval.coin);
const pendingApproval = await baseCoin.pendingApprovals().get({ id: initialPendingApproval.id });
const result = await pendingApproval.approve(params);

Step Result

Once approved, BitGo processes the book transfer and updates the ledger balances for both Go Accounts.

{
  "id": "65155c4a72fddb000774edbee5fa75fd",
  "coin": "ofctbtc",
  "wallet": "63ff43c474598902272894f328452ad7",
  "enterprise": "63f31592e277e50837712300495902c7",
  "creator": "628ca09d1b78a6022750254f0777561a",
  "createDate": "2023-09-28T10:58:18.272Z",
  "info": {
    "type": "transactionRequest",
    "transactionRequest": {
      "coinSpecific": {
        "ofctbtc": {
          "payload": {
            "coin": "ofctbtc",
            "recipients": [
              {
                "address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
                "amount": "10000"
              }
            ],
            "fromAccount": "63ff43c474598902272894f328452ad7",
            "nonce": "80bc93b5-eeec-4c11-afca-f6dae4bba2f2",
            "timestamp": "2023-09-28T10:55:39.135Z",
            "idfSignedTimestamp": "2023-09-28T10:55:38.732Z",
            "idfVersion": 1,
            "idfUserId": "628ca09d1b78a6022750254f0777561a",
            "feeString": "0",
            "shortCircuitBlockchainTransfer": true,
            "isIntraJXTransfer": true
          },
          "signature": "1f989926e3fc8ed0138bd0e188e90e2c5321ef86734896886c004005740a005dfd2008142dbc48aee68d1f576c4a9ac558e5b7b4acbd5aa765710ea636aca9d032",
          "transactionType": "BOOK_TRANSFER"
        }
      },
      "verificationItems": ["2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY"],
      "videoApprovers": [],
      "requestedAmount": "10000",
      "fee": 0,
      "sourceWallet": "63ff43c474598902272894f328452ad7",
      "recipients": [
        {
          "_id": "65155c4a68fddb000865edbf",
          "address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
          "amount": "10000"
        }
      ]
    }
  },
  "state": "approved",
  "scope": "wallet",
  "userIds": [
    "628ca09d1b78a6022750254f0777561a",
    "5bd8bdb0925d7c9823d4fc823e9e2fd3"
  ],
  "approvalsRequired": 1,
  "resolvers": [
    {
      "user": "5bd8bdb0925d7c9823d4fc823e9e2fd3",
      "date": "2023-09-28T11:15:24.644Z",
      "resolutionType": "pending"
    }
  ]
}

See Also