Overview

Settlements on the Go Network enable you to settle multi-asset trades between Go Accounts, off chain. Assets never leave BitGo cold storage, minimizing counterparty risk, and because settlements are off chain, they're quicker and less expensive than on-chain transactions.

Note: Ticker symbols of assets in Go Accounts use a different naming convention than the ticker symbols used in on-chain wallets. Assets in Go Accounts prepend ofc to the ticker symbol, indicating that the asset is off chain. For example, testnet bitcoin within a Go Account is ofctbc, whereas in other on-chain wallet types it's tbtc4.

Prerequisites

1. Add a Connection

Add connections to Go Accounts you find in the directory by passing the targetListingEntryId. If a Go Account isn't listed in the directory, you can still add connection if you know the wallet ID of the partner's Go Account.

Endpoint: Add a Go Account Connection

export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export LISTING_ENTRY_ID="<LISTING_ENTRY_ID>"
export LOCAL_LISTING_ENTRY_DESCRIPTION="<LOCAL_LISTING_ENTRY_DESCRIPTION>"
export TARGET_LISTING_ENTRY_ID="<TARGET_LISTING_ENTRY_ID>"

curl -X POST \
  https://app.bitgo-test.com/api/address-book/v1/connections \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "listingEntryId": "'"$LISTING_ENTRY_ID"'",
    "localListingEntryDescription": "'"$LOCAL_LISTING_ENTRY_DESCRIPTION"'",
    "targetListingEntryId": "'"$TARGET_LISTING_ENTRY_ID"'"
  }'
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export WALLET_ID="<THEIR_GO_ACCOUNT_WALLET_ID>"

curl -X POST \
  https://app.bitgo-test.com/api/address-book/v1/connections \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{"walletId": "'"$WALLET_ID"'"}'
import { BitGoAPI } from '@bitgo/sdk-api';
import { coins } from '@bitgo/sdk-core';
import { CreateAddressBookConnectionParams } from '@bitgo/sdk-core/src/bitgo/address-book';
import * as dotenv from 'dotenv';

const OFC_WALLET_ID = process.env.OFC_WALLET_ID;

const bitgo = new BitGoAPI({
  accessToken: process.env.TESTNET_ACCESS_TOKEN,
  env: 'test',
  customRootURI: 'https://app.bitgo-test.com',
});

const coin = 'ofc';
bitgo.register(coin, coins.Ofc.createInstance);

async function main() {
  const wallet = await bitgo.coin('ofc').wallets().get({ id: OFC_WALLET_ID });
  const addressBook = wallet.toAddressBook();

  const listing = await addressBook.getListing();
  // Check if the wallet's listing exists and has entries
  if (!listing.listingEntries || listing.listingEntries.length === 0) {
    throw new Error('Wallet with ID ${OFC_WALLET_ID} does not have any address book listing entries.');
  }
  const listingEntry = listing.listingEntries[0];

  const directory = await addressBook.getListingEntryDirectory();
  // Check if the directory has any entries to connect with
  if (!directory.listingEntries || directory.listingEntries.length === 0) {
    throw new Error('The address book directory is empty. No available entries to connect with.');
  }
  const targetListingEntry = directory.listingEntries[0];

  const body: CreateAddressBookConnectionParams = {
    listingEntryId: listingEntry.id, // We now know this exists
    targetListingEntryId: targetListingEntry.id, // We now know this exists
    localListingEntryDescription: 'My description of the connection',
  };

  const connection = await addressBook.createConnection(body);

  console.log('Address Book Connection:', connection);
}

main().catch((e) => console.error(e));
import { BitGoAPI } from '@bitgo/sdk-api';
import { coins } from '@bitgo/sdk-core';
import { CreateAddressBookConnectionParams } from '@bitgo/sdk-core/dist/src/bitgo/address-book';
import * as dotenv from 'dotenv';

dotenv.config();

const OFC_WALLET_ID = process.env.OFC_WALLET_ID;

const bitgo = new BitGoAPI({
  accessToken: process.env.TESTNET_ACCESS_TOKEN,
  env: 'test',
  customRootURI: 'https://app.bitgo-test.com',
});

const coin = 'ofc';
bitgo.register(coin, coins.Ofc.createInstance);

async function main() {
  const wallet = await bitgo.coin(coin).wallets().get({ id: OFC_WALLET_ID });
  const addressBook = wallet.toAddressBook();

  const listing = await addressBook.getListing();
    
  if (!listing.listingEntries || listing.listingEntries.length === 0) {
    throw new Error('Wallet with ID ${OFC_WALLET_ID} does not have any address book listing entries.');
  }
  const listingEntry = listing.listingEntries[0];
  
  const body: CreateAddressBookConnectionParams = {
    listingEntryId: listingEntry.id, // my listing entry id for the wallet
    walletId: '', // ofc wallet with whom I was to add to the address book.
    localListingName: 'Manual Wallet Name',
    localListingEntryDescription: 'My description of the connection',
  };

  const connection = await addressBook.createConnection(body);

  console.log('Address Book Connection:', connection);
}

main().catch((e) => console.error(e));

Step Result

{
  "connections": [
    {
      "ownerListingEntry": {
        "listing": {
          "id": "string",
          "name": "string",
          "description": "string",
          "editable": true
        },
        "id": "string",
        "walletId": "string",
        "coin": "string",
        "type": "GO_ACCOUNT",
        "description": "string",
        "discoverable": true,
        "featured": true,
        "createdAt": "string",
        "updatedAt": "string"
      },
      "targetListingEntry": {
        "listing": {
          "id": "string",
          "name": "string",
          "description": "string",
          "editable": true
        },
        "id": "string",
        "walletId": "string",
        "coin": "string",
        "type": "GO_ACCOUNT",
        "description": "string",
        "discoverable": true,
        "featured": true,
        "createdAt": "string",
        "updatedAt": "string"
      },
      "id": "string", // this is the addressBookConnectionId for settlements
      "type": "DVP",
      "status": "PENDING_DEACTIVATION",
      "label": "string",
      "description": "string",
      "createdBy": "string",
      "updatedBy": "string",
      "createdAt": "string",
      "updatedAt": "string" // this is the addressBookConnectionUpdatedAt for settlements
    }
  ]
}

2. Create Settlement

The addressBookConnectionId and addressBookConnectionUpdatedAt timestamp identify your counterparty for the settlement. If you need to find these values, you can call the Lists counterparty Go Account connections endpoint.

Endpoint: Create settlement

export ENTERPRISE_ID="<YOUR_ENTERPRISE_ID>"
export ACCOUNT_ID="<YOUR_ACCOUNT_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export CONNECTION_ID="<CONNECTION_ID>"
export CONNECTION_UPDATED_AT="<CONNECTION_UPDATED_AT>"
export COUNTERPARTY_ACCOUNT_ID="<COUNTERPARTY_ACCOUNT_ID>"
export CURRENCY="<SENDING_ASSET>"
export QUANTITY="<SENDING_QUANTITY>"

curl -X POST \
  https://app.bitgo-test.com/api/clearing/v1/enterprise/$ENTERPRISE_ID/account/$ACCOUNT_ID/settlement \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "addressBookConnectionId": "'"$CONNECTION_ID"'",
    "addressBookConnectionUpdatedAt": "'"$CONNECTION_UPDATED_AT"'",
    "assetTransfers": [
      {
        "currency": "'"$CURRENCY"'",
        "quantity": "'"$QUANTITY"'"
      }
    ]
  }'

Step Result

{
  "approvalRequests": [
    {
      "createdAt": "2019-08-24",
      "updatedAt": "2019-08-24",
      "id": "23d3e6c5-72d2-41e8-9e79-8d7e09a19b74",
      "accountId": "yourAccountId",
      "status": "acknowledged",
      "payload": "{\"nonce\":\"216c7312-5af8-463a-963d-8a91535b7ed5\",\"version\":\"3.0.0\",\"signerAccount\":\"yourAccountId\",\"settlementTransfers\":[{\"id\":\"b5f1ab55-b510-4bf6-92cd-214a7e23321\",\"sourceTradingAccountId\":\"yourAccountId\",\"destinationTradingAccountId\":\"counterpartiesAccountId\",\"currency\":\"$CURRENCY\",\"quantity\":\"$QUANTITY\"}]}",
    },
    {
      "createdAt": "2019-08-24",
      "updatedAt": "2019-08-24",
      "id": "54586c2b-adfd-40bd-8115-ea3a37f3f4df",
      "accountId": "counterpartiesAccountId",
      "status": "initialized",
      "payload":  "{\"nonce\":\"d51a9909-0b25-4ebc-b864-eba6e76e77d1\",\"version\":\"3.0.0\",\"signerAccount\":\"counterpartiesAccountId\",\"settlementTransfers\":[{\"id\":\"b5f1ab55-b510-4bf6-92cd-214a7e23321\",\"sourceTradingAccountId\":\"yourAccountId\",\"destinationTradingAccountId\":\"counterpartiesAccountId\",\"currency\":\"$CURRENCY\",\"quantity\":\"$QUANTITY\"}]}",
    }
  ],
  "settlementTransfers": [
    {
      "id": "b5f1ab55-b510-4bf6-92cd-214a7e23321",
      "sourceTradingAccountId": "yourAccountId",
      "destinationTradingAccountId": "counterpartiesAccountId",
      "currency":"$CURRENCY",
      "quantity":"$QUANTITY"
    }
  ],
  "requesterAccountName": "string",
  "finalizedAt": "2019-08-24",
  "createdAt": "2019-08-24",
  "updatedAt": "2019-08-24",
  "id": "string", // settlementId for signing
  "externalId": "string",
  "notation": "string",
  "requesterAccountId": "string",
  "status": "canceled",
  "type": "direct"
}

3. Sign Settlement (Optional)

If you're sending assets in the settlement, you must sign the settlement to confirm the transaction. However, if you're only receiving assets in a settlement, signing is optional.

3.1 Apply Signature

import { BitGoAPI } from '@bitgo/sdk-api';
import { coins } from '@bitgo/sdk-core';

const OFC_WALLET_ID = process.env.OFC_WALLET_ID;
const payload = process.env.PAYLOAD;
const walletPassphrase = process.env.OFC_WALLET_PASSPHRASE;

const bitgo = new BitGoAPI({
  accessToken: process.env.TESTNET_ACCESS_TOKEN,
  env: 'test',
});

const coin = 'ofc';
bitgo.register(coin, coins.Ofc.createInstance);

async function main() {
  const wallet = await bitgo.coin('ofc').wallets().get({ id: OFC_WALLET_ID });

  console.log('Wallet:', wallet);

  const tradingAccount = wallet.toTradingAccount();

  const signature = tradingAccount.signPayload({payload, walletPassphrase});

  console.log('Signature:', signature);
}

main().catch((e) => console.error(e));

Substep Result

"1f2f62b22832940d82819eccc9e2fbcb5339ae863bc9fba4d6cc62f01550fc325a7e007fb8b753d0e862145e716d4a6054f75c2da2b7c2ac5d41679683a4996349"

3.2 Submit Signed Settlement

Endpoint: Sign settlement

export ENTERPRISE_ID="<YOUR_ENTERPRISE_ID>"
export ACCOUNT_ID="<YOUR_ACCOUNT_ID>"
export SETTLEMENT_ID="<SETTLEMENT_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export PAYLOAD="<SETTLEMENT_PAYLOAD>"
export SIGNATURE="<YOUR_PUBLIC_KEY>"

curl -X POST \
  https://app.bitgo-test.com/api/clearing/v1/enterprise/$ENTERPRISE_ID/account/$ACCOUNT_ID/settlement/$SETTLEMENT_ID/signing \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "payload": "'"$PAYLOAD"'",
    "signature": "'"$SIGNATURE"'"
  }'

Substep Result

Signing a settlement places the assets in your Go Account into a held state, preventing them from use in other transactions. If your Go Account has an insufficient balance to cover the settlement, then the request is rejected.

Note: If the signing fails for any reason, the settlement cancels.

{}

4. Approve Settlement (Optional)

Note: If you configure an approval requirement for settlements, you can't approve a settlement you initiated - another admin must always approve it.

Endpoint: Update Pending Approval

export APPROVAL_ID="<APPROVAL_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export OTP="<YOUR_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 = this.bitgoSDK.coin(initialPendingApproval.coin);
const pendingApproval = await baseCoin.pendingApprovals().get({ id: initialPendingApproval.id });
const result = await pendingApproval.approve(params);

Step Result

{
    "id": "65529448bd87efe59c3b0156ddfce867",
    "coin": "tbtc4",
    "wallet": "654ec786c07fe8dc0dcfe03916ec5bb0",
    "enterprise": "62c5ae8174ac860007aff138a2d74df7",
    "creator": "62ab90e06dfda30007974f0a52a12995",
    "createDate": "2023-11-13T21:25:28.022Z",
    "info": {
        "type": "transactionRequest",
        "transactionRequest": {
            "requestedAmount": "10000",
            "fee": 20451,
            "sourceWallet": "654ec786c07fe8dc0dcfe03916ec5bb0",
            "policyUniqueId": "654ec786c07fe8dc0dcfe03f",
            "recipients": [
                {
                    "address": "2N1UvKhtSHLUa9YomSH7n9YMFCkMG2pbmsQ",
                    "amount": "10000",
                    "_id": "65529448bd87efe59c3b0158"
                }
            ],
            "coinSpecific": {
                "tbtc4": {
                    "txHex": "010000000001010e4d3af014f9efe311062965d561b67f78a1759e7016605cd506ddd7041762d50000000023220020510ded26d712922bbb61bc68ef6766f836a03527820cbdc8b1551914eb467dafffffffff02102700000000000017a9145a581567fd2a630e61e34a696ab3bb887972886d87ad0f010000000000225120850d0ab466d15cb1565dd528d4d9709f3e46f41d41fe6d94aa01378e626983990500483045022100db45a8d94ee2144f7e29baa855d94a2bf0120707a7ab2fc93734ed94af972c460220558d00b91275aafc7805dfaaf9ab796adbe9f4e66dc467f7eb93b790671a323201000069522103c10ac628c880629ed0fd2a0563a898f4882baca45e15668a4d3064cf1ea379882103e56f84be4460080618ef869bb7b07096880760f748ba23efab533c2359f923bd21020ae81372264b5eac5c9dc7fe0b9a32bad00771c3a2c71f6e2c971823c3182ed653ae00000000"
                }
            },
            "validTransaction": "010000000001010e4d3af014f9efe311062965d561b67f78a1759e7016605cd506ddd7041762d50000000023220020510ded26d712922bbb61bc68ef6766f836a03527820cbdc8b1551914eb467dafffffffff02102700000000000017a9145a581567fd2a630e61e34a696ab3bb887972886d87ad0f010000000000225120850d0ab466d15cb1565dd528d4d9709f3e46f41d41fe6d94aa01378e626983990400483045022100db45a8d94ee2144f7e29baa855d94a2bf0120707a7ab2fc93734ed94af972c460220558d00b91275aafc7805dfaaf9ab796adbe9f4e66dc467f7eb93b790671a323201473044022049e20c0073f42c9636408efc6c833ebf0e1a8ef13e8cb818dde2f4e2af7f7b7f022000cb3a321d65605b9d51d7f87e34306169607646c8d54a44011b021eff3dbe500169522103c10ac628c880629ed0fd2a0563a898f4882baca45e15668a4d3064cf1ea379882103e56f84be4460080618ef869bb7b07096880760f748ba23efab533c2359f923bd21020ae81372264b5eac5c9dc7fe0b9a32bad00771c3a2c71f6e2c971823c3182ed653ae00000000",
            "validTransactionHash": "5ea8d2b93997ed9fa3597a2f3113817c8216f573a0278c2533bb5db50fdb0dff"
        }
    },
    "state": "approved",
    "scope": "wallet",
    "userIds": [
        "62ab90e06dfda30007974f0a52a12995",
        "621d08a634ad8a0007fcddffd7c429cc"
    ],
    "approvalsRequired": 1,
    "singleRunResults": [
        {
            "ruleId": "Custody Enterprise Transaction ID Verification",
            "triggered": false,
            "_id": "65529448bd87efe59c3b0157"
        }
    ],
    "resolvers": [
        {
            "user": "621d08a634ad8a0007fcddffd7c429cc",
            "date": "2023-11-13T21:44:27.794Z",
            "resolutionType": "pending"
        }
    ]
}

See Also