Cobo - Wallet Custody Integration (Waas 2.0)

Available To

Free for the following exchange plans:

  • On-Premise Plans:

    • Enterprise Unlimited

circle-info

This is a wallet custody plugin and is only available on On-Premise (Unlimited) plans. It is not offered on Cloud plans because custody must be controlled by the exchange operator's own infrastructure and Cobo API credentials.

What Is It?

The Cobo Plugin connects a HollaEx exchange to Cobo Wallet-as-a-Service 2.0arrow-up-right using a single shared Custodial Asset Wallet as the custody backend.

It exposes server-side routes only:

  • Generates per-user deposit addresses inside the shared Cobo wallet.

  • Credits are deposited when Cobo emits a transactions.* webhook, and the canonical state is reconfirmed via a signed GET /v2/transactions/{id} API call.

  • Dispatches pending exchange withdrawals to Cobo every minute via POST /v2/transactions/transfer.

  • Approves only those Cobo callback requests whose request_id matches a pending withdrawal it previously dispatched (auto-deny otherwise).

Who Needs It?

This plugin is suitable for exchange operators that:

  • Use Cobo as their custody provider.

  • Want a single shared Custodial Asset Wallet with per-user deposit addresses across many chains.

  • Want pending exchange withdrawals submitted to Cobo automatically.

  • Need defense-in-depth on deposit credits: every webhook is re-verified against Cobo's authenticated API before any mint is created or updated.

How to Use It?

Install the plugin from the Plugins section inside the Operator Control, then configure the plugin meta with your Cobo API credentials and shared wallet ID.

1. Generate a Cobo API key pair

Cobo authenticates every API request with an Ed25519 key pair.

  1. Generate a key pair using either the Cobo CLIarrow-up-right, OpenSSL, or any Ed25519 library. The output you need is two 32-byte hex strings:

    • API key - the public key in hex (64 hex characters).

    • API secret - the private key in hex (64 hex characters).

  2. Register the public key on Cobo Portal under API Management -> Register an API key, scoped at minimum to:

    • wallet.read, wallet.create_address

    • transaction.read, transaction.withdraw

    • webhook.read, webhook.edit, callback.read

2. Create a shared Custodial Asset Wallet

  1. In Cobo Portal, create a new wallet:

    • wallet_type: Custodial

    • wallet_subtype: Asset

  2. Copy the wallet ID. This is the single shared wallet that will hold per-user deposit addresses for every supported chain. The plugin does not create one wallet per exchange user.

3. Configure the plugin

Open the plugin in the Operator Control and set the following fields:

Field
Required
Default
Description

api_url

yes

https://api.dev.cobo.com/v2

Cobo WaaS 2.0 base URL. Use https://api.cobo.com/v2 for production.

api_key

yes

empty

Your Cobo API public key (hex, 64 chars).

api_secret

yes

empty

Your Cobo API private key (hex, 64 chars). Used to sign every outbound API request.

wallet_id

yes

empty

ID of the shared Custodial Asset Wallet.

webhook_public_key

yes

dev key

Cobo's Ed25519 public key (hex) used to verify inbound webhooks and callbacks. Production: 8d4a482641adb2a34b726f05827dba9a9653e5857469b8749052bf4458a86729. Development: a04ea1d5fa8da71f1dcfccf972b9c4eba0a2d8aba1f6da26f49977b08a0d2718.

The plugin will not initialize unless all required values are present.

4. Register the webhook endpoint on Cobo Portal

In Cobo Portal -> Developers -> Webhook Endpoints, register:

Subscribe, at minimum, to:

  • transactions.created

  • transactions.updated

Cobo signs every event with Biz-Resp-Signature and Biz-Timestamp request headers. The plugin verifies the signature and then re-fetches the canonical transaction via GET /v2/transactions/{id} so the on-disk state, not the webhook body, is what drives the deposit credit or withdrawal status update. Forged or stale webhooks are dropped.

5. Register the callback endpoint on Cobo Portal

Cobo asks an external endpoint to approve every initiated withdrawal before it is signed and broadcast. In Cobo Portal -> Developers -> Callback Endpoints, register:

The plugin replies in plain text with ok or deny:

  • ok only when the callback's request_id matches a pending exchange withdrawal that the plugin previously dispatched and is in waiting=true, status=false, dismissed=false, rejected=false state.

  • deny for everything else, including missing/invalid signature, unknown request_id, or burns that are no longer in the dispatched state.

6. User-facing address creation

Authenticated users request a deposit address with:

The plugin:

  1. Returns 400 if the user already has a wallet for this currency+network.

  2. Reuses any existing same-network address the user already has, if one exists.

  3. Otherwise calls POST /v2/wallets/{wallet_id}/addresses with { chain_id, count: 1 } and registers the returned address with toolsLib.wallet.createUserWalletByKitId.

7. Withdrawal dispatch

Two cron jobs run every minute:

  • markPendingWithdrawalsProcessing flips eligible pending burns to processing: true. A burn is eligible when it is not completed, dismissed, rejected, waiting, processing, or on hold, and its currency maps to a Cobo chain.

  • dispatchPendingWithdrawals picks up processing burns, marks them waiting: true, processing: false, then calls POST /v2/transactions/transfer with:

    • request_id set to the exchange's burn transaction_id (used for callback matching and webhook reconciliation).

    • source: { source_type: "Asset", wallet_id }.

    • token_id resolved via GET /v2/wallets/{wallet_id}/tokens?chain_ids=... (cached in memory).

    • destination.account_output: { address, amount }.

Final completion is recorded later by the webhook (which re-verifies via the Cobo API) and writes status: true plus the on-chain hash to the exchange burn record.

To trigger dispatch manually as an admin:

Health check

Supported networks

Supported chains are loaded from Cobo at startup via GET /v2/wallets/chains?wallet_type=Custodial&wallet_subtype=Asset and cached in memory. The plugin maps Cobo chain names to exchange currency symbols. Built-in mappings include Bitcoin, Bitcoin Cash, Litecoin, Dogecoin, Dash, Ethereum, Tron, Solana, Polygon, Avalanche C-Chain, Arbitrum, Optimism, Base, BNB Smart Chain, XRP, Cosmos, Algorand, Cardano, Polkadot, Stellar, Tezos, TON, NEAR, Aptos, and Sui. Other chains returned by Cobo are best-effort matched by name.

Assets without a mapped Cobo chain are skipped by the withdrawal dispatcher and cannot derive deposit addresses until the mapping is added.


Defense-in-Depth

Every completed credit deposit, and withdrawal, goes through two checks before the kit ledger is touched:

  1. The inbound webhook signature is verified with Cobo's published Ed25519 public key.

  2. The plugin then makes its own signed GET /v2/transactions/{id} call and uses that response (status, amount, destination address, transaction hash, request_id) to decide what to credit or update.

Even if the webhook signature were ever bypassed or the public key were misconfigured, no funds move unless Cobo's authenticated API confirms the same transaction.


Benefits for HollaEx Operators

The Cobo plugin lets an exchange keep custody operations inside Cobo while preserving the normal HollaEx wallet flow for users. Deposit addresses are derived from a shared Cobo Custodial Asset Wallet; deposit credits depend on signed Cobo webhooks and Cobo API confirmation, outgoing withdrawals are queued and dispatched on a cron, and Cobo callbacks for those withdrawals are auto-approved only when they match a known pending exchange burn. This keeps custody integration centralized, auditable, and aligned with the exchange's existing pending transaction workflow.

Last updated