Skip to main content
In an omnibus setup, three vault types work together:
  • Intermediate vault accounts: Assigned per end-client for deposits. Use the Fireblocks API to generate as many as needed.
  • Omnibus deposits: The central vault where end-client funds are swept and stored.
  • Withdrawal pool: Vault accounts containing funds allocated for end-client withdrawals. More than one may be required due to blockchain limitations.
Follow the right structure for your use case in the Create Direct Custody Wallets article.

Understanding Asset Types

Handling your omnibus account correctly requires a clear understanding of the differences between UTXO and account-based assets. Due to the nature of UTXO-based blockchains, a transaction includes the source address for each end client, unlike account-based transactions which require an intermediary vault account per client.

UTXO Based

Structure

  • In the Omnibus Deposits vault account, you can assign each end client a deposit address (derived from the permanent wallet address of the UTXO asset).
  • When adding an address for an end client in the Omnibus Deposits vault account, use the Create a New Deposit Address of an Asset in a Vault Account endpoint and use the description field in createAddressRequest to associate the end client’s ID. The customerRefId parameter is the ID for AML providers to associate the owner of funds with transactions. Both the name of the vault account and the AML customerRefId fields are propagated to every transaction.

Deposit

Funds are deposited using the following process:
  • The retail platform shares the deposit address with the end client.
  • The end client makes a deposit.
  • The incoming deposit triggers a webhook notification.
  • Your client-facing software automatically notifies the end client that the deposit was received.
  • The deposit appears on the Transaction History page.

Example

const createUTXOWithdrawalVaultAccounts = async (
  assetId: string,
  name: string,
): Promise<Array<{}> | undefined> => {
  const result: Array<{}> = [];

  try {
    const vaultAccount = await fireblocks.vaults.createVaultAccount({
      createVaultAccountRequest: {
        name,
      },
    });

    if (vaultAccount.data) {
      const vaultWallet = await fireblocks.vaults.createVaultAccountAsset({
        vaultAccountId: vaultAccount.data.id as string,
        assetId,
      });

      result.push({
        "Vault Account Name": vaultAccount.data.name,
        "Vault Account ID": vaultAccount.data.id,
        "Asset ID": assetId,
        Address: vaultWallet.data.address,
      });

      console.log(JSON.stringify(result, null, 2));
    }

    return result;
  } catch (error) {
    console.error(error);
  }
};

// Create an omnibus vault account for UTXO based assets
const createOmnibusUTXOAccount = async (
  numOfAddresses: number,
  assetId: string,
): Promise<{} | undefined> => {
  try {
    const myOmnibusVault = await fireblocks.vaults.createVaultAccount({
      createVaultAccountRequest: {
        name: "My Omnibus Vault",
      },
    });

    if (myOmnibusVault.data) {
      const vaultAccountId = myOmnibusVault.data.id as string;

      let result = {};

      await fireblocks.vaults.createVaultAccountAsset({
        vaultAccountId,
        assetId,
      });

      for (let i = 0; i < numOfAddresses; i++) {
        // Generating additional addresses is possible for UTXO based assets only
        await fireblocks.vaults.createVaultAccountAssetAddress({
          assetId,
          vaultAccountId,
          createAddressRequest: {
            description: `UserAddress${i + 1}`,
          },
        });
      }

      const addresses =
        await fireblocks.vaults.getVaultAccountAssetAddressesPaginated({
          vaultAccountId,
          assetId,
        });

      result = {
        "Vault Account Name": myOmnibusVault.data.name,
        "VaultAccount ID": myOmnibusVault.data.id,
        "Asset ID": assetId,
        Addresses: addresses?.data.addresses,
      };

      console.log(JSON.stringify(result, null, 2));

      return result;
    }
  } catch (error) {
    console.error(error);
  }
};
createUTXOWithdrawalVaultAccounts("BTC_TEST", "MyWithdrawalVault");
createOmnibusUTXOAccount(3, "BTC_TEST");
This creates an omnibus vault and a withdrawal vault, then generates a deposit address per end user using a unique customer ID. The function returns a dictionary of the newly created vaults and generated deposit addresses.

Account Based

This section covers account-based assets without a tag or memo. For tag/memo-based assets, see Tag / Memo Based.

Structure

  • The workspace should contain one or more intermediate vault accounts per end client, plus a single Omnibus Deposits vault account.
  • When adding a vault account, use the Create a New Vault Account endpoint and use the name parameter to associate the end client’s ID as a prefix or suffix. The customerRefId parameter is the ID for AML providers to associate the owner of funds with transactions. Both the name of the vault account and the AML customerRefId fields are propagated to every transaction.
  • Due to the nature of account-based blockchains, transactions can only be transferred from one account-based address to another sequentially, unlike UTXO where multiple addresses are included in a single transaction.

Deposit

Funds are deposited using the following process:
  • The end client receives a deposit address.
  • The end client makes a deposit.
  • The incoming deposit triggers a webhook notification.
  • Your client-facing software automatically notifies the end client that the deposit was received.
  • The deposit is swept to the Omnibus Deposits vault account. See Sweeping within an Omnibus Vault structure for the sweeping logic.

Example

Recommended: Set hiddenOnUI: true for end-user vaultsWhen creating end-user vault accounts, set hiddenOnUI: true in the createVaultAccount request. By default it is false, which means all vaults will appear in the Console — not recommended at scale.Transfers to hidden vaults are only visible programmatically, not in the Console UI.
The example below creates:
  1. 5 intermediate vault accounts for 5 end users.
  2. 1 treasury vault.
  3. 3 withdrawal vaults to distribute settlement load.
const createAccountBasedVaultAccounts = async (
  vaultAccountNamePrefix: string,
  numOfVaultAccounts: number,
  assetId: string,
  hiddenOnUI: boolean,
  endUserReferences?: string[],
): Promise<Array<{}> | undefined> => {
  try {
    let vaultAccount: FireblocksResponse<VaultAccount>;
    let results: Array<{}> = [];

    for (let i = 0; i < numOfVaultAccounts; i++) {
      if (
        endUserReferences &&
        endUserReferences.length !== numOfVaultAccounts
      ) {
        throw new Error(
          "Number of Vault Accounts does not equal to the number of end user references",
        );
      }

      vaultAccount = await fireblocks.vaults.createVaultAccount({
        createVaultAccountRequest: {
          name: endUserReferences
            ? vaultAccountNamePrefix + "_" + endUserReferences[i]
            : vaultAccountNamePrefix + "_Vault" + String(i + 1),
          hiddenOnUI,
        },
      });

      const vaultAccountId = vaultAccount.data?.id as string;

      const vaultWallet = await fireblocks.vaults.createVaultAccountAsset({
        assetId,
        vaultAccountId,
      });

      const singleVaultResult = {
        "Vault Account": vaultAccount.data?.name,
        "Vault Account ID": vaultAccountId,
        "Asset ID": assetId,
        Address: vaultWallet.data?.address,
      };

      results.push(singleVaultResult);
      console.log(
        `Created Vault Account:\n ${JSON.stringify(singleVaultResult, null, 2)}`,
      );
    }

    return results;
  } catch (error) {
    console.error(error);
  }
};

createAccountBasedVaultAccounts("Deposits", 5, "ETH_TEST5", true, [
  "UserA",
  "UserB",
  "UserC",
  "UserD",
  "UserE",
]);
createAccountBasedVaultAccounts("Treasury", 1, "ETH_TEST5", false);
createAccountBasedVaultAccounts("Withdrawal_Pool", 3, "ETH_TEST5", false);
The function accepts a name prefix, account count, and optionally internalCustRefIds and hiddenOnUI. It runs three times: once for end-user vaults, once for the treasury vault, and once for withdrawal vaults.

Tag / Memo Based

Tag and memo-based assets share a single wallet address per omnibus vault, with a unique tag or memo identifying each end client. The tag/memo name varies by blockchain.

Structure

  • In the Omnibus Deposits vault account, you can assign each end client a tag or memo.
  • When adding an address for an end client in the Omnibus Deposits vault account, use the Create a New Deposit Address of an Asset in a Vault Account endpoint and use the description field in createAddressRequest to associate the end client’s ID. The customerRefId parameter is the ID for AML providers to associate the owner of funds with transactions. Both the name of the vault account and the AML customerRefId fields are propagated to every transaction.

Deposit

Funds are deposited using the following process:
  • The end client receives a deposit address and a tag or memo.
  • The end client makes a deposit using both the address and the tag.
  • The incoming deposit triggers a webhook notification.
  • Your client-facing software automatically notifies the end client that the deposit was received, provided they included the tag or memo.
  • All funds are held in the same vault account; different customer balances are tracked in your internal ledger.

Example

const createTagWithdrawalVaultAccounts = async (
  assetId: string,
  name: string,
): Promise<Array<{}> | undefined> => {
  const result: Array<{}> = [];

  try {
    const vaultAccount = await fireblocks.vaults.createVaultAccount({
      createVaultAccountRequest: {
        name,
      },
    });

    if (vaultAccount.data) {
      const vaultWallet = await fireblocks.vaults.createVaultAccountAsset({
        vaultAccountId: vaultAccount.data.id as string,
        assetId,
      });

      result.push({
        "Vault Account Name": vaultAccount.data.name,
        "Vault Account ID": vaultAccount.data.id,
        "Asset ID": assetId,
        Address: vaultWallet.data.address,
      });

      console.log(JSON.stringify(result, null, 2));
    }

    return result;
  } catch (error) {
    console.error(error);
  }
};

// Create an omnibus vault account for Tag/Memo based assets
const createTagOmnibusAccount = async (
  numOfAddresses: number,
  assetId: string,
): Promise<{} | undefined> => {
  try {
    const myOmnibusVault = await fireblocks.vaults.createVaultAccount({
      createVaultAccountRequest: {
        name: "My Omnibus Vault",
      },
    });

    if (myOmnibusVault.data) {
      const vaultAccountId = myOmnibusVault.data.id as string;

      let result = {};

      await fireblocks.vaults.createVaultAccountAsset({
        vaultAccountId,
        assetId,
      });

      for (let i = 0; i < numOfAddresses; i++) {
        // For Tag/Memo based assets, the address of the wallet is always the same but a new Memo/Tag is generated upon each user
        await fireblocks.vaults.createVaultAccountAssetAddress({
          assetId,
          vaultAccountId,
          createAddressRequest: {
            description: `UserAddress${i + 1}`,
          },
        });
      }

      const addresses =
        await fireblocks.vaults.getVaultAccountAssetAddressesPaginated({
          vaultAccountId,
          assetId,
        });

      result = {
        "Vault Account Name": myOmnibusVault.data.name,
        "VaultAccount ID": myOmnibusVault.data.id,
        "Asset ID": assetId,
        Addresses: addresses?.data.addresses,
      };

      console.log(JSON.stringify(result, null, 2));

      return result;
    }
  } catch (error) {
    console.error(error);
  }
};

createTagWithdrawalVaultAccounts("XLM_TEST", "Withdrawal");
createTagOmnibusAccount(2, "XLM_TEST");
This creates an omnibus vault and a withdrawal vault, then generates a unique deposit identifier per end user. The function returns a dictionary of vault IDs and deposit address mappings.