# Send and receive Canton assets with the API

This page explains how to send, receive, and manage Canton Coin and CIP-56 tokens. Each operation is a `v0_CreateTransactionOrder` intent with `parameters.type` set to `Canton`, and your policies and approvals apply to it like any other transaction order.

Before you start, complete the account setup. For more information, see [Set up Canton accounts with the API](/products/custody/accounts-and-assets/blockchains/canton/set-up-accounts-api).

## Operations

| Operation | Use it to |
|  --- | --- |
| `NativeTransfer` | Send Canton Coin. |
| `TokenTransfer` | Send a CIP-56 token. |
| `Withdraw` | Cancel an outgoing two-step transfer offer that the receiver hasn't acted on. |
| `Accept` | Accept an incoming two-step transfer offer. |
| `Reject` | Reject an incoming two-step transfer offer and return the assets to the sender. |


To submit an order, follow the dry run, propose, and check steps in [Submit each order](/products/custody/accounts-and-assets/blockchains/canton/set-up-accounts-api#submit-each-order).

## Send assets

If the receiver has an active pre-approval for the asset, the transfer settles directly. Otherwise, it creates a two-step transfer offer, and the Holdings that fund it stay locked until the receiver accepts or rejects it, you withdraw it, or it expires. For background, see [Transfer pre-approvals and two-step transfers](/products/custody/accounts-and-assets/blockchains/canton/concepts#transfer-pre-approvals-and-two-step-transfers).

The dry run and the submission both check that the receiver party exists on the network. If it doesn't, the order fails with an invalid destination error.

### Send Canton Coin

This example sends 25 CC to a counterparty:

```json
{
  "payload": {
    "id": "b4f8d2a6-3c1e-4b9f-a7d5-1e3c5a7f9b2d",
    "accountId": "3f7c1a9e-5b2d-4c8f-a6e1-9d3b7f5c2a8e",
    "ledgerId": "canton-mainnet",
    "parameters": {
      "type": "Canton",
      "operation": {
        "type": "NativeTransfer",
        "destination": {
          "type": "Address",
          "address": "c::1220e2b5a8d1c4f7e0b3a6d9c2f5b8e1a4d7c0f3b6e9a2d5c8f1b4e7a0d3c6f9b2e5"
        },
        "amount": "250000000000",
        "executeBefore": "2026-10-31T17:00:00Z",
        "memo": "INV-2026-0931"
      }
    },
    "description": "Settlement to counterparty",
    "customProperties": {},
    "type": "v0_CreateTransactionOrder"
  }
}
```

### Send a CIP-56 token

A `TokenTransfer` takes the same fields as a `NativeTransfer`, plus the token's `tickerId`. This example sends 1,000 units of a CIP-56 token:

```json
{
  "payload": {
    "id": "d7a3e9c5-2f4b-4d8a-b6e2-8c4a6f2d9b1e",
    "accountId": "3f7c1a9e-5b2d-4c8f-a6e1-9d3b7f5c2a8e",
    "ledgerId": "canton-mainnet",
    "parameters": {
      "type": "Canton",
      "operation": {
        "type": "TokenTransfer",
        "destination": {
          "type": "Address",
          "address": "c::1220e2b5a8d1c4f7e0b3a6d9c2f5b8e1a4d7c0f3b6e9a2d5c8f1b4e7a0d3c6f9b2e5"
        },
        "amount": "10000000000000",
        "tickerId": "8e4b2d6f-1a3c-4f7e-b9d2-6c8a0e2f4b7d",
        "memo": "INV-2026-0932"
      }
    },
    "description": "Token settlement to counterparty",
    "customProperties": {},
    "type": "v0_CreateTransactionOrder"
  }
}
```

| Field | Description |
|  --- | --- |
| `operation.destination` | The receiver. Use an `Address` destination with the receiver's party ID, an `Endpoint` destination, or an `Account` destination for another Ripple Custody account. |
| `operation.amount` | The amount in the smallest unit, as a string. Canton assets use 10 decimal places, so 1 CC is `"10000000000"`. |
| `operation.tickerId` | `TokenTransfer` only. The ticker ID of the CIP-56 token. |
| `operation.executeBefore` | Optional. The deadline, in ISO 8601 format, for the receiver to accept a two-step transfer offer. The default is 30 days after submission. It has no effect when the transfer settles directly. |
| `operation.memo` | Optional. A reference of up to 255 characters that the network stores in the transfer's on-ledger metadata. Both the sender and the receiver can see it. |


Canton orders don't take a `feeStrategy`, and they don't support `maximumFee`. Ripple Custody rejects a Canton order that sets `maximumFee`.

### Transfer deadlines

The `executeBefore` deadline applies only to two-step transfer offers:

- **Canton Coin**: After the deadline passes, the network releases the sender's locked Holdings automatically. You don't need to withdraw the offer.
- **Other CIP-56 tokens**: After the deadline passes, the receiver can't accept the offer. To release the locked Holdings, submit a `Withdraw` order.


A transfer that settles directly through a pre-approval completes when the network commits it and doesn't expire.

### Signing window

Canton accepts a prepared transaction for 24 hours. The window starts when Ripple Custody prepares the transaction, and signing and execution must both happen within it. If the window closes first, the network rejects the transaction. The preparation time is part of the signed transaction, so you can't extend the window: submit the order again, so that it's prepared and signed again.

The signing window is separate from `executeBefore`:

- `executeBefore` is how long the receiver has to accept a two-step transfer offer. It defaults to 30 days, and the sender can set it shorter.
- The signing window is how long Ripple Custody has to get from preparing a transaction to executing it. It applies to every Canton order, including `Accept` and `Reject`.


To accept or reject an offer, the offer must still be before its `executeBefore` deadline. Ripple Custody then prepares the `Accept` or `Reject` transaction, and the 24-hour signing window starts.

### Withdraw a transfer offer

You can withdraw an outgoing two-step transfer offer until the receiver accepts or rejects it. Withdrawing cancels the offer and returns the locked Holdings to your account. You can't withdraw a transfer that settled directly.

Set `contractId` to the contract ID of the offer. You can find it in the `ledgerData` of the outgoing transaction. For more information, see [Find the contract ID of a transfer offer](#find-the-contract-id-of-a-transfer-offer).

```json
{
  "payload": {
    "id": "f1c5a9e3-7b2d-4f6c-8a4e-9d2f6b1c5a8e",
    "accountId": "3f7c1a9e-5b2d-4c8f-a6e1-9d3b7f5c2a8e",
    "ledgerId": "canton-mainnet",
    "parameters": {
      "type": "Canton",
      "operation": {
        "type": "Withdraw",
        "contractId": "00a4c7e1f3b6d9a2c5e8f1b4d7a0c3e6f9b2d5a8c1e4f7b0d3a6c9e2f5b8d1a4c7ca121220f6d9b2e5a8c1f4d7b0e3a6c9f2d5b8e1a4c7f0d3b6e9a2c5f8d1b4e7a0c3"
      }
    },
    "description": "Withdraw expired transfer offer",
    "customProperties": {},
    "type": "v0_CreateTransactionOrder"
  }
}
```

## Receive assets

To receive assets, share the account's party ID with the sender. To get the party ID, see [Step 2: Create the party](/products/custody/accounts-and-assets/blockchains/canton/set-up-accounts-api#step-2-create-the-party).

If the account has a pre-approval for the asset, the transfer settles directly and you don't need to do anything. If it doesn't, the transfer arrives as a two-step offer that you accept or reject before its deadline. For more information, see [Accept or reject an incoming transfer offer](#accept-or-reject-an-incoming-transfer-offer).

In both cases, the incoming assets then enter quarantine, like incoming assets on every other ledger. Your quarantine release policies apply without any Canton-specific change. For more information, see [Receive assets](/products/custody/transactions/send-and-receive/receive-assets-api) and [Transaction screening](/products/custody/compliance/transaction-screening/concept).

Pre-approvals don't bypass quarantine. A pre-approval only means that the network settles the transfer without a signature from the receiver.

### Accept or reject an incoming transfer offer

To accept an offer, submit an `Accept` order with the offer's contract ID. The network moves the assets into your account.

```json
{
  "payload": {
    "id": "a2e6c1f5-8d3b-4a7e-9c5f-3b8d2a6e1c4f",
    "accountId": "3f7c1a9e-5b2d-4c8f-a6e1-9d3b7f5c2a8e",
    "ledgerId": "canton-mainnet",
    "parameters": {
      "type": "Canton",
      "operation": {
        "type": "Accept",
        "contractId": "00b8e2d5a1c4f7b0e3d6a9c2f5e8b1d4a7c0f3e6b9d2a5c8f1e4b7d0a3c6f9e2b5d8ca121220a3f6c9e2b5d8a1f4c7e0b3d6a9f2c5e8b1d4a7f0c3e6b9d2a5f8c1e4b7"
      }
    },
    "description": "Accept incoming Canton Coin offer",
    "customProperties": {},
    "type": "v0_CreateTransactionOrder"
  }
}
```

To reject an offer, submit the same payload with `operation.type` set to `Reject`. The network returns the assets to the sender.

### Find the contract ID of a transfer offer

Ripple Custody records each two-step transfer offer when it detects the offer on the ledger. To find the contract ID:

1. Call the [List transactions](/products/custody/reference/api/openapi/transactions/gettransactions) operation, filtered by the account.
2. Find the transaction for the offer. Its `ledgerTransactionData.ledgerData` object has `type` set to `Canton`.
3. Read the `contractId` field.


The `ledgerData` object also shows the `sender`, `receiver`, `amount`, `instrumentId`, and `executeBefore` deadline of the offer. For all fields, see [Transaction ledger data](/products/custody/accounts-and-assets/blockchains/canton/reference#transaction-ledger-data).

## Balances and transaction history

Use the standard account and transaction operations for Canton accounts:

- To view balances, see [Check account balances](/products/custody/accounts-and-assets/accounts/manage-accounts-api#check-account-balances). An account has a separate balance for Canton Coin and for each CIP-56 token.
- To view history, see [View and audit transactions with the API](/products/custody/transactions/viewing-assets/view-and-audit-api).
- To refresh a balance that you think is out of date, see [Force an account balance update](/products/custody/accounts-and-assets/accounts/manage-accounts-api#force-an-account-balance-update). Ripple Custody rebuilds the balance from the account's current Holdings on the validator.


Keep the following behavior in mind:

- A Canton transaction is final as soon as the network commits it. Canton has no reorganizations.
- Ripple Custody learns a transaction's ledger ID (the Canton update ID) only after the network sequences it. Until then, the transaction has the `Broadcasting` status without a ledger transaction ID.
- A Canton Coin Holding that expires because of holding fees appears as a transfer with no receiver.


Ripple Custody emits the same events and webhooks for Canton transactions as for other ledgers. For more information, see [Events and webhooks](/products/custody/operations-and-maintenance/events-and-webhooks).

## Manage Holdings

Each outgoing transfer spends whole Holdings, up to a limit of 100 per transfer. A transfer that settles directly also reserves its Holdings until it completes. For background, see [Holdings](/products/custody/accounts-and-assets/blockchains/canton/concepts#holdings). Two situations need attention.

### Too many small Holdings

If an account holds many small Holdings, a large transfer can need more Holdings than one transfer can spend and fail, even though the balance covers it.

To fix this, merge the Holdings. Send a self-transfer: a `NativeTransfer` or `TokenTransfer` with the account's own party ID as the destination, for an amount that the Holdings to merge add up to. The network replaces them with one Holding.

### One large Holding

If an account has one large Holding, a second transfer that starts while the first is in progress fails, even though the balance covers both.

To fix this, split the Holding. Send a self-transfer for less than the Holding's amount. The network creates a Holding for the amount that you sent and a change Holding for the rest, so the account can fund two transfers at a time.

## Policy and approval implications

- Every Canton operation, including the setup orders, `Accept`, `Reject`, and `Withdraw`, is a `v0_CreateTransactionOrder` intent. Your transaction order policies apply to all of them. To treat operations differently, for example to require fewer approvals for pre-approval setup than for transfers, write your policies to distinguish them. For more information, see [Policies](/products/custody/governance/policies).
- Approvers review the exact receiver, amount, instrument, and contract ID that the vault signs. The vault refuses to sign a prepared transaction that doesn't match the approved intent. For the checks, see [Architecture](/products/custody/accounts-and-assets/blockchains/canton/architecture-and-responsibilities#architecture).
- Long approval flows don't affect incoming transfers to pre-approved accounts, because the receiver doesn't sign them.
- For a two-step transfer offer, the approval flow for `Accept` or `Reject` must finish before the offer's deadline.