# Send assets

You can send assets from a Ripple Custody account to one of the following destinations:

- Other Ripple Custody accounts: With an internal account ID or ledger address.
- External accounts: With a ledger address.
- Endpoint addresses: With an endpoint ID.


The assets you can send are blockchain-dependent, and include assets such as native cryptocurrency, non-fungible tokens (NFT), and other tokens, including tokens hosted by a smart contract.

The Ripple Custody API requires you to specify currency amounts in the smallest denomination of the currency. For more information about how to calculate the smallest denomination, see [Currency details by ledger](/products/custody/reference/supported-ledgers#currency-details-by-ledger).

## Overview

The steps below describe how to send assets with a transfer order, which is the standard way of sending assets in Ripple Custody. Transfer orders provide a way to send assets to any ledger using the same format and minimal information.

You can use a transfer order if:

- You want to send assets to a destination account, and you have a validated asset that is a native asset or a token.
- You want to send assets to a destination account via a smart contract that hosts tokens and includes a `transfer` method, and you have a validated asset that is a smart contract address.


You can only use transfer orders for smart contracts in the limited scenario described above. For all other smart contract transactions, you need a transaction order. For more information, see [Work with smart contracts](/products/custody/transactions/smart-contracts/api).

- You want to use a priority fee strategy instead of a custom fee strategy. For more details, see [Fee strategy types](/products/custody/transactions/reference#fee-strategy-types).


For all other asset transfer transactions, you need to use a transaction order.

[Learn more about transfer orders and transaction orders](/products/custody/transactions/initiation#key-concepts)

### Transfer order details

All transfer orders include the following basic details:

- One or more destinations: External accounts, other Ripple Custody accounts, or endpoint addresses.
- An asset: Native cryptocurrency, token, or smart contract.
- An amount: The amount of assets to send to each destination. For native cryptocurrency, the amount is in the smallest denomination of the ledger. For more information, see [Currency details by ledger](/products/custody/reference/supported-ledgers#currency-details-by-ledger).
- A priority: The priority level for the transaction.


## Prerequisites

To send assets, you need the following:

| Prerequisite | Additional information |
|  --- | --- |
| A source account on the ledger to which you want to send assets | [View account details](/products/custody/accounts-and-assets/accounts/manage-accounts-api#view-account-details) |
| Enough funds in the account to cover the transaction fees and the amount | [Check account balances](/products/custody/accounts-and-assets/accounts/manage-accounts-api#check-account-balances) |
| Details of the asset destination:For another Ripple Custody account, an account ID or address.For an external account, an account address.For an endpoint, an endpoint ID. | [View account details](/products/custody/accounts-and-assets/accounts/manage-accounts-api#view-account-details) [View endpoint details](/products/custody/accounts-and-assets/endpoints/manage-endpoints#view-endpoint-details-with-the-api) |
| An allowlisted token corresponding to the asset to send (native currency, token, or smart contract) | [View tokens](/products/custody/accounts-and-assets/tokenization/token-management/api/view-tokens)[Allowlist tokens](/products/custody/accounts-and-assets/tokenization/token-management/api/allowlist-token) |
| The following new IDs, in a standard UUID format:A transfer order IDAn intent ID |  |


### Ledger-specific prerequisites

In addition to the prerequisites above, the following ledgers require you to perform some additional tasks for specific asset sending operations:

- XRPL: To send fungible tokens, [Create a trust line between XRPL accounts](/products/custody/accounts-and-assets/tokenization/ledger-specific/xrpl-api#create-a-trust-line).
- Hedera: To send native currency or tokens, [Initialize Hedera accounts and associate tokens](/products/custody/accounts-and-assets/accounts/ledger-specific-settings/hedera-api).
- Stellar: To send native currency, [Initialize Stellar accounts](/products/custody/accounts-and-assets/accounts/ledger-specific-settings/stellar-api).
- Tezos: To send native currency, [Reveal Tezos accounts](/products/custody/accounts-and-assets/accounts/ledger-specific-settings/tezos-api).


## Send the assets

System change process
All new requests to change the system state follow the same process. To familiarize yourself with this process first, see [Manage intents and approvals](/products/custody/governance/intents/manage-intents-and-approvals).

To send assets, prepare a transfer order intent with a payload similar to the following example. Before you sign and submit the intent, dry run the request body to validate the payload and estimate transaction fees.

### Payload example

The following example shows an asset transfer to three different destinations:


```json
"payload": {
   "id": "a5a0df1a-7fb4-43bc-baf9-006a60990de1",
   "accountId": "b82a94ea-78f3-4fd4-8790-d8e4d6958986",
   "tickerId": "d5184973-7b97-467a-9003-079b05485c58",
   "outputs": [
      {
         "destination": {
            "accountId": "917e4e1a-eb19-4031-8885-e778e401809e",
            "type": "Account"
         },
         "amount": "1000",
         "paysFee": false
      },
      {
         "destination": {
            "address": "2ND6nZtyiVsVGsDcFWw8ZLixm2CCsPK29eB",
            "type": "Address"
         },
         "amount": "1000"
      }
      {
         "destination": {
            "endpointId": "917e4e1a-eb19-4031-8885-e778e401809e",
            "type": "Endpoint"
         },
         "amount": "1000"
      },
   ],
   "feeStrategy": {
      "priority": "Low",
      "type": "Priority"
   },
   "maximumFee": "20000",
   "description": "transfer order example",
   "customProperties": {},
   "type": "v0_CreateTransferOrder"
}
```

The format of the payload is not dependent on the ledger where the transaction is broadcast. Ripple Custody takes care of constructing the specific transaction order based on the `tickerId`.

Fields of the `payload` block to note are as follows:

| Field | Description |
|  --- | --- |
| `id` | Newly generated UUID string that uniquely identifies the transaction order underlying the transfer order. |
| `accountId` | The source account from which the transaction is to be initiated. |
| `tickerId` | The asset to use. This can be a native asset, token, or smart contract. Only allowlisted tokens can be specified. For details of how to allowlist a token, see [Allowlist tokens](/products/custody/accounts-and-assets/tokenization/token-management/api/allowlist-token). |
| `outputs` | One or more transactions for the asset type associated with the `tickerId`. |
| `outputs.destination` | One of the following three destination types:`Account`: ID of another Ripple Custody account. Ripple Custody automatically resolves the ledger address of the destination account using the ID you provide.`Address`: An address in an accepted format for the ledger.`Endpoint`: ID of an endpoint. Ripple Custody automatically resolves the ledger address of the destination endpoint using the ID you provide.The destination can be different for each output. For protocols supporting multiple addresses per account, and when the address needs to be resolved by Ripple Custody, no guarantee is provided regarding which account address the resolution leads to. |
| `outputs.amount` | Amount expressed as an integer. The value depends on the asset type: Native currency: Must be the amount of currency, in its smallest denomination. For more information on how to calculate this, see [Currency details by ledger](/products/custody/reference/supported-ledgers#currency-details-by-ledger).NFTs: Must be `1`. |
| `outputs.paysFee` | Boolean to express whether the given output will pay for the fees. The main use for this field is to let you sweep a UTXO-based account. This parameter is only supported for ledgers where the underlying transaction order supports it, such as Bitcoin and Cardano. You can only assign this parameter to the first `outputs.destination` and is `false` by default. |
| `feeStrategy` | Strategy used to calculate the fees for the transfer order: `type`: Only type `Priority` is available for transfer orders.`priority` Priority of the transaction: `Low`, `Medium`, or `High`. A higher priority means higher fees, which incentivizes miners to pick up the transaction faster. If you are not concerned about processing times, set this to `Low` to pay lower fees.For more information about how Ripple Custody calculates fees based on the priority, see [Fee strategy and maximum fee](/products/custody/transactions/reference#fee-strategy-and-maximum-fee). |
| `maximumFee` | Maximum fee the user is willing to pay to execute the transaction, expressed in the smallest non divisible unit of the native ticker of the ledger where the transfer will be broadcast.For more information, see [Fee strategy and maximum fee](/products/custody/transactions/reference#fee-strategy-and-maximum-fee). |
| `preferredAddressForChange` | Optional hint for Bitcoin-type transfer orders. Set to `PrimaryAddress` to send UTXO change to the source account's primary address. For more information, see [UTXO change address whitelisting](/products/custody/transactions/send-and-receive/utxo-change-address-whitelisting). |
| `type` | `v0_CreateTransferOrder` |


### Dry run the intent

Dry runs simulate intent submission so you can validate the transfer order payload before signing, proposing, and approving it.

Use a dry run to check for issues such as:

- An intent with the same UUID already exists.
- The destination address is invalid.
- The source account has insufficient balance or fees.
- The account, token, or ledger setup is not compatible with the transaction.
- The transaction requires additional ledger-specific setup.


For transaction intents, the dry run can also return blockchain-specific fee information, such as Bitcoin transaction size, Ethereum gas estimates, XRPL minimum cost in drops, or Hedera token fee information.

To dry run the intent:

1. Prepare the request body in the format shown in [Dry run and signature request body](/products/custody/governance/intents/intent-request-structure#dry-run-and-signature-request-body).
2. Call the [Perform a dry run for an intent](/products/custody/reference/api/openapi/intents/intentdryrun) operation.
3. Review the result:
  - For transaction intents, check the `hint` field.
  - For other intent validation errors, check the `errors` field.



```sh
curl -X POST "${CUSTODY_API_URL}/v1/intents/dry-run" \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d @intent-dry-run.json
```

If the dry run returns an error or an unexpected fee estimate, update the payload and dry run it again before signing and submitting the intent.

For general intent workflow guidance, see [Manage intents and approvals](/products/custody/governance/intents/manage-intents-and-approvals#dry-run-an-intent-with-the-api).

### Submit the intent

After the dry run returns the expected result, sign and submit the intent. For the standard intent workflow, see [Manage intents and approvals](/products/custody/governance/intents/manage-intents-and-approvals).

When the transfer order is successfully approved, Ripple Custody does the following:

- Creates a transaction order.
- Creates a transaction, to broadcast the details on the blockchain.
- Creates one or more transfers, to represent the separate amounts associated with the transaction, such as the cryptocurrency amount and the fees.


Remember!
Transfer orders are a wrapper for transaction orders, and Ripple Custody does not persist the transfer order in the system. When you create a transfer order, Ripple Custody creates an underlying transaction order with the ID that you provide. You need to use the transaction order to track the operation.