Overview

ERC7984 is a token standard for confidential fungible tokens on Ethereum. Unlike ERC20, balances and transfer amounts are encrypted on-chain using Fully Homomorphic Encryption (FHE), powered by the Zama Protocol. Token contracts do not expose ERC20's balanceOf/transfer/approve functions. Instead they expose their own, distinctly named functions — confidentialBalanceOf, confidentialTransfer/confidentialTransferFrom, and a time-limited setOperator in place of approve — whose amounts are opaque encrypted handles (bytes32 pointers) rather than plaintext integers. Only parties holding the right decryption key can learn the actual amount behind a handle.

Note: ERC7984 tokens are supported on multisig and MPC wallets, but not on wallet version 6 or later (v6+). A v6+ wallet cannot enable or hold ERC7984 tokens.

How ERC7984 differs from ERC20

ERC7984 tokens share a wallet with their parent Ethereum wallet the same way ERC20 tokens do (no separate keychains, addresses, or wallet objects). However, the confidentiality model changes several behaviors that developers relying on standard ERC20 flows need to account for:

Behavior ERC20 ERC7984
Balances / transfer amounts Plaintext, readable directly from the contract Encrypted on-chain; BitGo must decrypt them via a viewing key before they can be displayed
Token enablement Not required Requires an on-chain delegation transaction, submitted separately per address, before BitGo can decrypt balances/transfers for that address
Balance & transfer visibility Available immediately once indexed Available asynchronously, after BitGo completes decryption
Consolidation Automatic via forwarders Manual only, and only from receive addresses that have already delegated
Recipients per transaction 1 1
Chains supported Ethereum + most EVM chains BitGo supports Ethereum only, currently

Confidential balances and encrypted amounts

An ERC7984 contract's confidentialBalanceOf and transfer functions return an encrypted handle, not a plaintext value. To show a usable balance or transfer amount, BitGo decrypts these handles using a per-enterprise viewing key. This means:

  • Balances on a wallet's confidential tokens are only meaningful once BitGo has decrypted them; consumers of the API should expect balance/amount fields to populate asynchronously rather than immediately after a transfer is seen on-chain.
  • Decryption is scoped to the enterprise's viewing key — only BitGo can read the actual amounts. The chain itself never reveals plaintext balances.

Token enablement (delegation)

ERC20 tokens require no wallet-level setup before use. ERC7984 tokens do: before BitGo can decrypt balances or transfers for an address, that address's owner must call enableToken to submit an on-chain delegation transaction that grants BitGo's viewing key the right to decrypt that address's encrypted data on the token's ACL contract. This follows the same token enablement pattern used for other enablement-required assets (for example SOL, HBAR, and ALGO) — a normal transaction that must be built, signed, and sent before the token is usable — except that for ERC7984 tokens, "enabling" the token means delegating decryption rights rather than creating an account or trustline.

This is a one-time setup per address, per ERC7984 token. Clients must call enableToken for an address before attempting deposits or transfers of that token to or from it — until that address is enabled, BitGo cannot decrypt its balances or transfer amounts.

Delegation is scoped per address, not per wallet. Enabling the token on a wallet's base address does not delegate decryption rights for its receive addresses — each receive address must have its own delegation transaction submitted before BitGo can decrypt balances or transfers held at that address. This also means consolidation will not pick up a receive address's confidential token balance until that address has delegated; consolidation attempts against a non-delegated receive address will not succeed.

The delegation transaction for an address can only be sent once that address's forwarder contract has been deployed. The forwarder is deployed the first time the address receives any asset — a receive address must have previously received a deposit (ETH or any token) before an enable-token/delegation transaction can be built and sent for it.

If a confidential transfer is received at an address while delegation is not active for it (for example, before the address delegated, or if delegation was later revoked), the transfer cannot be decrypted immediately. It is surfaced as a pending, "awaiting delegation" transaction. To resolve it, the address owner calls enableToken for that address — once the resulting delegation transaction confirms on-chain, BitGo decrypts the stuck transfer and it finalizes normally.

Building confidential transfers

Building, signing, and sending a confidential transfer uses the same API flow as an ERC20 transfer, and, as with ERC20, only a single recipient is supported per transaction. The transfer amount still needs to be encrypted before it's submitted on-chain, but BitGo handles that step internally when building the transaction — there's nothing different an API consumer needs to do to build a confidential transfer.

Consolidation

For ERC20 tokens, deposits are auto-flushed from the receiving address to the wallet's base address with no client action needed. ERC7984 deposits stay on the receiving address until the client explicitly triggers consolidation by calling POST /api/v2/:coin/wallet/:walletId/consolidateAccount/build, and only after that receive address has completed its own delegation transaction — consolidation cannot decrypt or move a confidential balance from an address that has not delegated.

Webhooks

Register a webhook with type: pendingDecryption (via Add Wallet Webhook) on the wallet's base Ethereum coin to be notified when a confidential deposit can't be decrypted because the receiving address hasn't delegated yet. The payload looks like:

{
  "event": "erc7984_deposit_awaiting_delegation",
  "walletId": "<walletId>",
  "coin": "<token coin, e.g. eth:cusdc>",
  "data": {
    "walletName": "<wallet label>",
    "address": "<receive address awaiting delegation>",
    "txHashes": ["<txHash>"],
    "receivedAt": "<ISO 8601 timestamp>"
  }
}

No transfer record exists yet at this point, so calling Get Transfer for a txHash from this payload will 404. Once the address delegates and BitGo completes decryption, the deposit is recorded as a normal transfer and the standard transfer webhook fires — at that point, use Get Transfer with the token's coin name to retrieve the decrypted amount and current state.