> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Bridge Canton Coin with the Value Transfer API

> How market makers and integrators bridge Canton Coin with the Value Transfer API: prerequisites, endpoint sequence, Canton signing and submission, and errors.

> [!NOTE]
>
> Available when Canton Coin launches on Stargate and the Value Transfer API.
>
> Canton Coin on BSC, HyperEVM, and Solana is bridged CC: an OFT that LayerZero Labs deployed and administers. It is minted when native CC is locked on Canton through the LayerZero CC adapter. On HyperCore, `LZCC` is a spot token linked to the HyperEVM OFT through the Hyperliquid asset bridge. The Global Synchronizer Foundation, Digital Asset, and the Canton Network did not deploy bridged CC and do not administer its contracts. See [Canton Coin Across Chains](https://docs.layerzero.network/v2/developers/canton/canton-coin/overview.md).

The [Value Transfer API](https://docs.layerzero.network/v2/developers/value-transfer-api/overview.md) builds each Canton Coin (CC) transfer for you. You sign it and submit it yourself. On Canton, you submit from your own participant node, or from a node that hosts your party. The API never submits a Canton transaction.

On a send from Canton, the Value Transfer API adds no fee of its own. On a send into Canton, the quote can include the partner or base fees configured for your API key. The quote lists every fee. You also pay the [protocol fee](https://docs.layerzero.network/v2/developers/canton/canton-coin/overview.md#fees). It is the LayerZero messaging fee. On a send from Canton, it also includes the LayerZero request fee on Canton, paid in CC.

> [!WARNING]
>
> **Send into Canton only through the Value Transfer API or Stargate.** The supported paths into Canton are the Value Transfer API, for integrators and market makers, and [Stargate](https://docs.layerzero.network/v2/developers/canton/canton-coin/bridge-with-stargate.md), for end users. Both check that the receiving Canton party is registered before they build a send. An OFT `send()` that you build yourself on BSC, HyperEVM, or Solana bypasses these checks. If the receiving party is not registered in the CC adapter's registry, including because its validator node has not vetted the LayerZero DAR packages, the funds will be stuck on Canton. A malformed `to` value, a fingerprint registered to a different party, or a compose message on the send cannot be fixed by registering. Recovery needs action by the node operator or by LayerZero, is not practical for end users, and LayerZero does not guarantee it. See [Send from Another Chain to Canton](https://docs.layerzero.network/v2/developers/canton/oft/overview.md#send-from-another-chain-to-canton) and the [pre-send checklist](https://docs.layerzero.network/v2/developers/canton/oft/overview.md#pre-send-checklist).

## Prerequisites

1. **A participant node that hosts your party.** Run your own validator node, or host your party on another operator's node. A validator needs a Super Validator sponsor, an egress IP on the Super Validators' allowlist, and traffic to submit transactions. See [Validators, Super Validators, and the Global Synchronizer](https://docs.layerzero.network/v2/developers/canton/technical-overview.md#validators-super-validators-and-the-global-synchronizer).
2. **A Value Transfer API key.** [Contact the LayerZero team](https://layerzeronetwork.typeform.com/to/U9hMgxf1) for a key, and say that you need Canton Coin routes. Send it in the `x-api-key` header on every call, including `/v1/register-canton-party`, as in the [quickstart](https://docs.layerzero.network/v2/developers/value-transfer-api/quickstart.md).
3. **Access to the LayerZero Canton packages.** The DAR packages are private. To get access, [contact the LayerZero team](https://layerzeronetwork.typeform.com/to/U9hMgxf1) with the GitHub handles of the people who will download them.
4. **The packages vetted on every node that hosts your party.** If your party is hosted on more than one participant node, every co-host must upload and vet the same packages. See [LayerZero Packages](https://docs.layerzero.network/v2/developers/canton/technical-overview.md#layerzero-packages).
5. **Funded parties.** Create and fund your Canton parties before you start. A sending party needs CC for the amount plus the protocol fee from `/v1/quotes`. A receiving party needs CC for the registration fee before it can receive any CC.
6. **A registered party for inbound transfers.** Register each party that receives CC from another chain, on Canton, before the first inbound send. See [Register a Receiving Party](#register-a-receiving-party).
7. **Split holdings.** The amount and the fee must come from different holdings, so the sending party needs at least two unlocked Amulet holdings. Their total must cover the amount plus the quoted fee, with some margin: the build re-quotes the fee. The API picks one or more holdings for the fee so that the holdings left over cover the amount. Send from an external party, because the Splice wallet merges the holdings of the parties it manages (see [Holdings](https://docs.layerzero.network/v2/developers/canton/canton-coin/wallet-operators.md#holdings)). To create an external party, see Canton's [external signing guide](https://docs.canton.network/appdev/deep-dives/external-signing). The party's key signs every send. Build and submit immediately after you read the holdings.
8. **Your own signing flow** for Canton, and for Solana if you send from Solana. Solana sends need a signer for Solana v1 transactions. See [Solana Wallets](https://docs.layerzero.network/v2/developers/canton/canton-coin/bridge-with-stargate.md#solana-wallets).

> [!WARNING]
>
> **One receiving party per validator namespace.** The registry stores the 32-byte fingerprint of a party ID, and by default all local parties on one validator share the validator's fingerprint. If two of those parties register, the last one receives every inbound transfer for both. Register only one receiving party per namespace. The Value Transfer API refuses to re-point a namespace that another party holds (the `CantonNamespaceTaken` error; see [Errors](#errors)). A direct `SetPartyId` call does not have this check, so always register through the API.

## Address Formats

| Item | Format |
| - | - |
| Canton party ID | `hint::1220<64 lowercase hex characters>` |
| Native CC on Canton | Use the Canton CC token address from `/v1/tokens`, unchanged. Do not build or parse it yourself. |
| BSC and HyperEVM (bridged CC) | Token `0x7d73496D792FA2C8144788E88217fA4e9a7E18ea`. OFT `0x15eF73ddd62dc6A82C5F556aF441911725A5Bfb0` |
| Solana (bridged CC) | Mint `9fQdbtgJDvBXAX9QefbsSXBiEzMsv79ve4Lj7cbJeh2M`. OFT program `4YSoS7X9AEgrVdemuqxrigF943T66VBknxkfh3Zc4MSy`. OFT store `8HJxcRre8PuwCfrcBwzLeah73PPBRFnntJ2TQsUBXxrY` |

Always pass the full Canton party ID. The API converts it to the 32-byte receiver value for the LayerZero message.

This guide covers Canton mainnet only. It does not cover a testnet route, so test with a small amount on mainnet.

## Endpoint Sequence

Get the Canton chain key (`canton`) from `GET /v1/chains`, and the CC token addresses from `GET /v1/tokens`. Use these values in `/v1/quotes` and `/v1/register-canton-party`.

**1. Get a quote**

Call `POST /v1/quotes` as on any route. Use Canton party IDs as Canton addresses. Before you quote a route into Canton, check the recipient with `POST /v1/register-canton-party` (no `holdings`). `/v1/quotes` does not check the recipient. On a route into Canton from BSC, HyperEVM, or Solana, `/v1/build-user-steps` returns the `CantonPartyNotRegistered` error (HTTP 422; see [Errors](#errors)) if the recipient is not registered. It returns the `CantonNamespaceTaken` error (HTTP 409) if the recipient's fingerprint is registered to a different party. A quote from or into Canton has no `userSteps`. The build step returns them. Quote amounts use base units, as on any route. A transfer carries at most 6 decimal places of CC ([shared decimals](https://docs.layerzero.network/v2/developers/canton/canton-coin/overview.md#token-identifiers)), so a smaller remainder is not sent.

**2. Register the receiving party (once)**

If the recipient is not registered, it registers once per token, on Canton, before the inbound send. `POST /v1/register-canton-party` prepares the registration transaction, and the recipient's own Canton wallet signs it. See [Register a Receiving Party](#register-a-receiving-party). After the registration transaction commits, request the quote again. Skip this step if the party is already registered.

**3. Build the transaction**

Call `POST /v1/build-user-steps` with the `quoteId`. For a Canton source, include the sender's holdings in `data.holdings`. The API does not look up holdings for you. Read them from your node. For a Canton source, the response has one `TRANSACTION` step with `chainType: "CANTON"`.

**4. Sign and submit**

Sign and submit every user step from `/v1/build-user-steps` on the source chain, in order. An ERC-20 source returns an approve step and a send step. For a Canton source, see [Submit on Canton](#submit-on-canton).

**5. Track the transfer**

For every source, including Canton, poll `GET /v1/status/{quoteId}?txHash=<hash>`. For Canton, pass the update ID that your node or wallet returns. The API maps it to the LayerZero transaction that [LayerZero Scan](https://layerzeroscan.com) indexes, and links it in `explorerUrl`. `explorerUrl` is empty until the API has matched the update ID. Searching Scan for your wallet's update ID finds nothing. To track the message in code, query the [LayerZero Scan API](https://docs.layerzero.network/v2/tools/layerzeroscan/api.md) with the transaction hash in `explorerUrl`. For a Canton source, that transaction can hold several messages: select the one whose sender is your party and whose amount matches. Read the warning below before you act on `SUCCEEDED`.

> [!WARNING]
>
> **Check what `SUCCEEDED` means before you act on it.**
>
> * **From Canton:** `/v1/status` can currently return the status of another transfer committed in the same LayerZero batch, and a send that LayerZero rejects can show that other transfer's status. Confirm on [LayerZero Scan](https://layerzeroscan.com) that the message's sender is your party and the amount matches.
> * **Into Canton:** `SUCCEEDED` means that LayerZero delivered the message to the CC adapter. It does not mean that the CC is in the receiver's wallet. Confirm the CC on Canton: check for a new holding, or for a transfer instruction that the receiver must accept within 24 hours if it has no transfer preapproval.

The API rate-limits each key. On HTTP 429, wait and retry. See [Status](https://docs.layerzero.network/v2/developers/value-transfer-api/api-reference/status.md).

### Acceptance, Refunds, and Rejected Sends

* **Inbound CC can wait for acceptance.** Unless the receiving party has a CC transfer preapproval, the CC arrives as a transfer instruction that the receiver must accept within 24 hours. `/v1/status` does not track acceptance. Tell your users to turn on a preapproval (One-step Transfers in Loop) or to accept the instruction.
* **Fee change and refunds can arrive the same way.** On a send from Canton, unused fee and refunds can also arrive as transfer instructions.
* **A send from Canton that VER rejects is not refunded automatically.** After VER processes the request, the `lzowner` party rejects it on the ledger (see [Protocol Overview](https://docs.layerzero.network/v2/developers/canton/protocol-overview.md#step-3-ver-processes-the-request)). The CC that you sent stays with the LayerZero CC adapter until the refund is settled. [Contact the LayerZero team](https://layerzeronetwork.typeform.com/to/U9hMgxf1) with the party ID, the update ID, and the `quoteId`. The LayerZero request fee is not refunded.

LayerZero does not resend an expired instruction automatically. To have the tokens delivered again, [contact the LayerZero team](https://layerzeronetwork.typeform.com/to/U9hMgxf1).

### Build Request for a Canton Source

```json theme={null}
{
  "quoteId": "<quoteId from /v1/quotes>",
  "data": {
    "holdings": [
      {
        "cid": "<holding contract ID>",
        "owner": "<sender party ID>",
        "instrumentId": { "admin": "<admin from fee.instrumentId>", "id": "<id from fee.instrumentId>" },
        "amount": "<amount>"
      }
    ]
  }
}
```

* Pass only unlocked Amulet holdings owned by the sending party. The API drops holdings with another owner or instrument, but it cannot see locks. A locked holding that you pass can be selected, and the transaction then fails on submit.
* Set `instrumentId` to the structured `fee.instrumentId` that `POST /v1/register-canton-party` returns, and pass it through unchanged. Do not derive it from the token address. That call returns `fee` only for an unregistered party. For a registered party, use the instrument ID that your node reports on the Amulet holding.
* Set `amount` to the holding's current amount as a decimal string in CC, as your node reports it, for example `"12.5"`, with at most 10 decimal places. Do not use base units.
* Pass at most 200 holdings. More returns HTTP 400 `Invalid request`.
* A send uses at most 100 holdings across the amount and the fee. If you have many small holdings, merge them: send your CC to yourself from an external party, which consolidates the inputs into one holding plus change.

## Register a Receiving Party

> [!WARNING]
>
> **The receiving party's own Canton wallet must register it.** If you let users send CC from BSC, HyperEVM, or Solana into Canton, your integration must connect each user's Canton wallet. Before the first inbound send, that wallet signs the registration transaction that `POST /v1/register-canton-party` prepares, and the fee is paid in CC from the party's holdings on Canton. The Value Transfer API cannot register a party from the source chain, and no API call registers a party automatically as part of a send. A user whose CC is only on BSC, HyperEVM, or Solana must first get CC into a Canton wallet that can sign, and register there.
>
> Connect the wallet with a CIP-0103 provider, such as `@canton-network/dapp-sdk`, or with Loop's SDK for Loop. Pass the `transaction` from `/v1/register-canton-party` to the wallet's `prepareExecute` unchanged, with your own `commandId`, and wait for the executed event before you quote.

Registration maps your party's fingerprint to your full party ID in the CC adapter's registry. The party signs it with its own wallet or key, in a Canton transaction separate from any send. Register once per party per token. For parties that share a namespace, see the [warning under Prerequisites](#prerequisites).

1. **Check the status and the fee.** Call `POST /v1/register-canton-party` with `partyId` and `token`, without `holdings`.

   * `{"status": "registered", "partyId": "<partyId>"}` means no action is needed.
   * `{"status": "unregistered", "fee": {...}}` gives the fee in CC. If the fee is zero, this response already includes the `transaction`. Go to step 3.

   This call reports only the registration status and the fee. Package vetting, an unknown party, and full registries are reported in step 2. A 409 here means the fingerprint is registered to another party.
2. **Get the registration transaction.** Call the endpoint again with `holdings` that cover the fee. The response includes a `transaction` to sign.
3. **Sign and submit** the transaction as the party, as in [Submit on Canton](#submit-on-canton).

```json theme={null}
{
  "partyId": "<your party ID>",
  "token": "<Canton CC token address from /v1/tokens>",
  "holdings": [
    {
      "cid": "<holding contract ID>",
      "owner": "<your party ID>",
      "instrumentId": { "admin": "<admin from fee.instrumentId>", "id": "<id from fee.instrumentId>" },
      "amount": "<amount>"
    }
  ]
}
```

## Submit on Canton

The Canton transaction is a CIP-0103 `prepareExecute` request. In a build step, it is `transaction.encoded`. In a registration response, it is `transaction`. It has one command (`OApp_LzSend` for a send, `SetPartyId` for a registration), `actAs`, `readAs`, and the `disclosedContracts` that the command needs.

* **Internal party** (your node holds the key): submit the command with its disclosed contracts through your node's Ledger API.
* **External party** (you hold the key): use Canton's [interactive submission](https://docs.canton.network/appdev/deep-dives/external-signing#transaction-submission). Prepare the transaction with the command and its `disclosedContracts`, sign the returned hash with the party key, then execute it. Take the update ID from the completion for your `commandId`. `/v1/status` needs it.

The request has no `commandId`. Set your own unique `commandId` when you submit, and keep the same `commandId` if you resubmit the same send. The request also has no `synchronizerId`. It is optional: set it to the Global Synchronizer ID, or leave it unset and your node picks a synchronizer that it is connected to. Do not change the command or its package ID.

You do not need your own idempotency or deduplication checks on Canton. The transaction spends specific holdings, so only one submission of it can commit. Track each send with `/v1/status` and LayerZero Scan, as in [Endpoint Sequence](#endpoint-sequence). While a send is pending, do not build a replacement from other holdings.

Sign and submit within a few minutes of the build. The transaction carries a timestamp that the API sets when it builds the transaction. The LayerZero contracts reject it once ledger time is more than 10 minutes past that timestamp. If submission fails with a timestamp-in-the-future error, wait a few seconds and resubmit the same transaction. Contract IDs read at build time can also go stale. If submission fails with a stale or archived contract, request a new quote, read your holdings again, and build again. Calling `/v1/build-user-steps` again with the old `quoteId` returns the same transaction. A build that failed is not stored, so you can retry a failed build with the same `quoteId`.

### One Send per Transaction

Each quote gives one command. Submit it as its own Canton transaction. Do not merge commands from several builds into one transaction.

* The API picks holdings the same way each time and does not reserve them. Two builds from the same holdings list pick the same holdings, so the second send fails.
* Transfer tracking expects one LayerZero send per Canton transaction.

To send in parallel from one party, give each build a different set of holdings. This works only for a party whose holdings the Splice wallet automation does not merge, such as an external party. Or send one at a time, and read your holdings again after each send commits. A spent holding returns change with a new contract ID.

### No Native Drop into Canton

Do not set `options.dstNativeDropAmount` on a route into Canton. Leave it at the default of `0`. The fee for a native drop is charged, but the drop is not delivered on Canton.

## Deposit Directly to HyperCore

HyperCore is a destination of the Value Transfer API. You can deposit CC to HyperCore in one transaction from Canton, BSC, Solana, or HyperEVM. Call `POST /v1/quotes` as on any route, with these destination fields:

| Field | Value |
| - | - |
| `dstChainKey` | `hypercore` |
| `dstTokenAddress` | `0x73404db72c0b2613c83f3c529d68639f` (`LZCC` on HyperCore) |
| `dstWalletAddress` | The receiver's EVM address: an EOA that you control, already activated on HyperCore |

An address is activated by its first incoming transfer on HyperCore, which costs 1 USDC. If the address is not activated, the API returns no route. [Activate the address](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/activation-gas-fee) first.

From Canton, BSC, or Solana, the API builds an OFT send to the LayerZero [Hyperliquid Composer](https://docs.layerzero.network/v2/developers/hyperliquid/hyperliquid-concepts.md#11-hyperliquid-composer) on HyperEVM, with the compose message and compose options. The executor delivers the CC to the composer on HyperEVM, and the composer moves it to the receiver on HyperCore. From HyperEVM, the API builds a transfer to the Hyperliquid asset bridge. Call `/v1/build-user-steps` and sign the steps as on any other route.

> [!WARNING]
>
> **If you build or change the transaction yourself.** LayerZero does not support Canton transactions from your own transaction builder, or changes to the encoding of a transaction that the Value Transfer API built. A self-built send must set `to` to the composer `0xccEE9Cad5C58Ad64113629Eb8921c9E948E10A0C`, include compose gas, and carry `composeMsg` = `abi.encode(uint256 minMsgValue, address hyperCoreReceiver)`, exactly 64 bytes. If the receiver is not activated on HyperCore, the composer sends the full amount as CC to the same address on HyperEVM. If the compose message does not decode, the composer holds the CC. Anyone can return it to the sender on the source chain with `refundToSrc(guid)`, paying the LayerZero messaging fee. From Canton, the refund settles only if the sending party is registered. If compose gas is below 150,000 (200,000 with HYPE), the compose reverts and can be retried. For help, [contact the LayerZero team](https://layerzeronetwork.typeform.com/to/U9hMgxf1) with the source transaction hash.

To leave HyperCore, hold at least 0.001 HYPE on HyperCore for gas, and spot-send `LZCC` to the asset bridge `0x20000000000000000000000000000000000004E7`, or quote HyperCore to HyperEVM with the same sender and receiver address. The CC appears at the same address on HyperEVM. Then bridge from HyperEVM.

## Errors

Error responses carry `code` and `message`, and most also carry `data`. Match on the HTTP status and `message`. For HTTP 400 `Invalid request`, read `data.validationErrors`. The names in the first column are labels for this page only. The API does not send them.

| Error | HTTP | Message | What to do |
| - | - | - | - |
| `CantonPartyNotRegistered` | 422 | `Canton party "<partyId>" is not registered for token "<token>". Register via POST /v1/register-canton-party before sending.` | Register the recipient, then quote again |
| `CantonNamespaceTaken` | 409 | `Canton party "<partyId>" cannot be registered: its namespace is already registered to a different party.` | On a registration: use a party with its own namespace. On a send: the recipient's fingerprint belongs to another party. Do not send to it. |
| `CantonHoldingsNotSplit` | 400 | Starts with `Canton Coin balance is held in <n> Amulet(s): the amount being bridged and the protocol fee must come from different ones.` | With one holding, split it. From an external party: transfer CC to yourself, or receive a second transfer of at least the fee, then build again. From a Splice-wallet party: send from an external party instead, because the wallet merges holdings back within about 30 seconds. With several holdings, the API found no set of fee holdings that leaves enough for the amount: receive a second transfer of at least the fee |
| Insufficient balance | 400 | `Insufficient balance to complete transaction` | Pass holdings that cover the amount plus the quoted fee |
| `CantonPackagesNotVetted` | 422 | `The validator hosting Canton party "<partyId>" has not vetted "<packageName>", so its wallet cannot sign this transaction. Ask the wallet operator to vet the LayerZero packages.` | Vet the full [package set](https://docs.layerzero.network/v2/developers/canton/technical-overview.md#layerzero-packages) on every hosting node, not only the named package |
| `CantonPartyUnknown` | 409 | `Canton party "<partyId>" is not known on this network. Check the party id, or allocate it with your validator first.` | Check the party ID |
| `CantonHoldingsRequired` | 400 | `Sender holdings are required for a Canton send` | Pass `data.holdings` |
| `CantonRegistrationFee` | 400 | `Registering Canton party "<partyId>" costs <minFee> Canton Coin, which the holdings supplied do not cover. Fund the party and try again.` | Pass holdings that cover the fee |
| `CantonRegistriesFull` | 409 | `All registries for appUid <appUid> are full (maxEntries=<n>); admin must provision another Registry` | [Contact the LayerZero team](https://layerzeronetwork.typeform.com/to/U9hMgxf1) |
| `CantonRegistryNotFound` | 422 | `No Canton registry for token "<token>": <reason>` | Check that `token` is the Canton CC token address from `/v1/tokens` |
| `CantonValidatorUnavailable` | 503 | `The Canton validator is temporarily unavailable (HTTP <status>). Quoting is unaffected; retry the build shortly.` | Retry the build |
| Invalid party ID | 400 | `Invalid request`. `data.validationErrors[].message` is `Invalid Canton party id (expected hint::1220<64hex>)` | Use the full party ID in lowercase hex |
| Internal error | 500 | `Internal server error` | If the send needs more than 100 holdings, merge small holdings and build again. Otherwise [contact the LayerZero team](https://layerzeronetwork.typeform.com/to/U9hMgxf1) with the `quoteId` |

Submission errors come from your node, not the API. For a stale or archived contract, quote, read your holdings, and build again. For a package that is not vetted, vet the full [package set](https://docs.layerzero.network/v2/developers/canton/technical-overview.md#layerzero-packages) on every hosting node. For insufficient traffic, top up your validator's traffic.

For delivery problems after submission, see [Canton Coin Troubleshooting](https://docs.layerzero.network/v2/developers/canton/canton-coin/troubleshooting.md).
