# Set up Solana durable nonce accounts with the API

Before you can send assets from a Solana account in a cold vault, you need a durable nonce account whose nonce authority is that cold vault account. A hot vault account creates the nonce account and delegates the nonce authority to the cold vault account.

Solana transactions normally reference a recent blockhash that expires in roughly 90 seconds. The network rejects a signature produced after the blockhash expires. A cold vault signing round trip takes longer than that, so a transaction signed from a cold vault must reference a **durable nonce** instead. A durable nonce is a stored value that stays valid until a transaction uses it.

Creating a nonce account is itself a Solana transaction that needs a signature within the 90-second window. A cold vault can't provide one in time. Instead, a hot vault account creates and funds the nonce account and names the cold vault account as the **nonce authority**, the account that must sign every later use of the nonce. After this one-time setup, the cold vault uses the nonce account on its own. The hot vault plays no further part.

In earlier releases, a nonce account created through Ripple Custody always belonged to the account that paid for it, so a cold vault could only use a nonce account created with external Solana tooling. Delegating the nonce authority removes that dependency.

Requires the Unified Indexer Service v2
Delegated nonce authority requires the Unified Indexer Service (UIS) v2, introduced with [ledger accounting refactor phase 2](/products/custody/support/change-history/v140#ledger-accounting-refactor-phase-2). SaaS deployments run UIS v2 from version 1.40. On-premises deployments receive it in the next LTS release. For more information, see [Unified Indexer Service](/products/custody/overview/architecture/unified-indexer-service).

If you don't specify a nonce authority, the account that pays for the nonce account creation becomes the authority. This matches the behavior of earlier releases.

## Durable nonce accounts and the nonce authority

A durable nonce account involves three roles:

| Role | Description | Signs |
|  --- | --- | --- |
| Fee payer | Pays the rent to create the nonce account, or pays the fee of a transaction that uses the nonce. You set it with the top-level `accountId` field of the transaction order. | Always |
| Nonce account | The on-chain account that stores the durable nonce. It holds only its rent-exempt balance and never sends assets. | At creation only |
| Nonce authority | The account that owns the nonce. | Whenever a transaction uses the nonce |


The lifecycle of a durable nonce account is as follows:

1. **Creation.** A hot vault account creates the nonce account and names the nonce authority in the same transaction. The transaction records the authority on chain as data. The authority doesn't sign, so creating a nonce account owned by a cold vault account needs no cold vault round trip.
2. **Use.** When a transaction references the nonce account, Ripple Custody adds an `advanceNonce` instruction ahead of the transaction's own instructions. The nonce authority signs the `advanceNonce` instruction, and the fee payer signs the transaction. If the same account fills both roles, the transaction carries one signature. If they are different accounts, it carries two.
3. **Advance.** Each use replaces the stored nonce with a fresh value. The authority never changes.


The Solana protocol fixes the nonce authority at creation. You can't reassign it later. To move ownership, create a new nonce account.

Each nonce account supports one in-flight transaction at a time. Ripple Custody reserves the nonce account when it prepares a transaction order and releases it when the transaction completes. A second order that references the same nonce account stays in the `Preparing` status until the first one completes. To run several cold vault transactions in parallel, create one nonce account per concurrent transaction. Nonce accounts are reusable indefinitely, so a pool of 10 nonce accounts supports 10 concurrent transactions for as long as each account stays rent-exempt.

### Account requirements

Ripple Custody rejects, at the dry run or proposal stage, an intent that breaks the following rules:

| Account | At creation | When a transaction uses the nonce |
|  --- | --- | --- |
| Fee payer | A hot vault account with enough SOL for the rent-exempt deposit of the nonce account, about 2,039,280 lamports (0.00204 SOL), plus the transaction fee. Any funded hot vault account qualifies. | Any account in the same vault as the nonce authority. In a cold vault workflow, a cold vault account. |
| Nonce account | A hot vault account in the same vault as the fee payer. It needs no funding. Both accounts sign the creation transaction, so both must be hot. | Reference it as a transaction destination of type `Account`. |
| Nonce authority | Any Ripple Custody account, in any vault, including a cold vault. You can also specify an account alias (endpoint) or an external Solana address. | A Ripple Custody account ID, in the same vault as the fee payer. Ripple Custody rejects accounts in another vault, external-provider accounts, and locked accounts. |


External addresses as nonce authority
You can name an external Solana address as the nonce authority when you create a nonce account. However, Ripple Custody can only use a nonce whose authority is an account it holds the key for. You can't use or close a nonce account delegated to an external address through Ripple Custody, and the rent deposit stays locked in the account.

## Create the nonce account

### Prerequisites

To create a durable nonce account with a delegated authority, you need the following:

| Prerequisite | Additional information |
|  --- | --- |
| A hot vault account on a Solana ledger from which to initiate the transaction | [View account details](/products/custody/accounts-and-assets/accounts/manage-accounts-api#view-account-details) |
| Enough funds in the account to cover the rent-exempt deposit of the nonce account, about 2,039,280 lamports (0.00204 SOL), and the transaction fees. Ripple Custody quarantines incoming deposits, and you can't spend them until you release them. | [Check account balances](/products/custody/accounts-and-assets/accounts/manage-accounts-api#check-account-balances)[Release quarantined assets](/products/custody/transactions/send-and-receive/receive-assets-api#release-quarantined-assets) |
| A second Solana account in the same hot vault to become the nonce account. This account needs no funding. | [View account details](/products/custody/accounts-and-assets/accounts/manage-accounts-api#view-account-details) |
| The account ID of the Solana account in the cold vault to act as the nonce authority. This account needs no funding to receive the delegation. | [Cold vaults](/products/custody/identity-and-access/vault-management/cold-vaults) |
| If this is a multi-ledger account, the ledger ID | [List ledgers](/products/custody/reference/api/openapi/ledgers/getledgers) |
| A deployment that runs the Unified Indexer Service v2 | [Unified Indexer Service](/products/custody/overview/architecture/unified-indexer-service) |
| The following new IDs, in a standard UUID format:A transaction order IDAn intent ID |  |


### Create the transaction order

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 create the transaction order:

1. Prepare the request body in the standard intent proposal format, with a `payload` block similar to the example shown in [Payload example](#payload-example). For more information, see [User-signed proposal request body](/products/custody/governance/intents/intent-request-structure#user-signed-proposal-request-body).
2. Call the [Perform a dry run for an intent](/products/custody/reference/api/openapi/intents/intentdryrun) operation, with the `request` field excluded. For more information, see [Dry run intents](/products/custody/governance/intents/manage-intents-and-approvals#dry-run-an-intent-with-the-api).
This step is optional, but highly recommended. A dry run can reveal errors or unintended consequences of the intent, as well as estimate the fees incurred by the transaction.
You can repeat this step more than once to obtain the required result.
3. Sign the request body and call the [Propose an intent](/products/custody/reference/api/openapi/intents/createintent) operation. For more information, see [Propose an intent to create an entity](/products/custody/governance/intents/manage-intents-and-approvals#submit-an-intent-with-the-api).
4. Check the update. For more information, see [Check updates](/products/custody/governance/intents/manage-intents-and-approvals#check-state-with-the-api).


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

- Creates a transaction, to create the nonce account on the blockchain and record the delegated authority.
- Creates one or more transfers, to represent the separate amounts associated with the transaction, such as the rent and the fees.


The creation transaction carries two signatures, from the fee payer and from the new nonce account. Both are hot vault accounts, so the creation needs no cold vault round trip. Delegating the authority adds no fee, because the authority is data in the creation transaction rather than a signer.

The creation transaction also moves the rent-exempt deposit, about 2,039,280 lamports (0.00204 SOL), from the fee payer into the nonce account. Set `maximumFee` on the creation order to cover this deposit as well as the 10,000 lamports for the two signatures. The deposit stays locked in the nonce account for as long as the account exists.

To confirm the delegation, look up the nonce account in a Solana explorer. Its `authority` field shows the address of the cold vault account, not the address of the account that paid for it.

#### Payload example

This example creates a durable nonce account and delegates the nonce authority to a cold vault account:

```json
{
    "payload": {
        "id": "953d75b2-e384-46e9-8a72-a1ce753a50e2",
        "accountId": "b82a94ea-78f3-4fd4-8790-d8e4d6958986",
        "ledgerId": "solana-mainnet",
        "parameters": {
            "operation": {
                "createOnAccount": "1f77dbb8-1fd6-4f7c-b839-9d51c31be2c7",
                "nonceAuthority": {
                    "accountId": "6de35f06-3b46-4a5c-9c67-1b1c2f31a4d8",
                    "type": "Account"
                },
                "type": "CreateNonceAccount"
            },
            "feeStrategy": {
                "priority": "Low",
                "type": "Priority"
            },
            "maximumFee": "10000000",
            "memos": [],
            "nonceAccount": null,
            "type": "Solana"
        },
        "description": "Solana nonce account with cold vault authority",
        "customProperties": {},
        "type": "v0_CreateTransactionOrder"
    }
}
```

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

| Field | Description |
|  --- | --- |
| `accountId` | Internal account ID of the hot vault Solana account that pays the rent and the fees. This account doesn't become the nonce authority unless you omit `operation.nonceAuthority`. |
| `ledgerId` | ID of the Solana ledger, for example `solana-mainnet`. Include it for multi-ledger accounts. |
| `operation.createOnAccount` | Internal account ID of the account that becomes the durable nonce account. It must be in the same vault as the fee payer. |
| `operation.nonceAuthority` | Optional. The account to delegate the nonce authority to:`type` is `Account`, `Endpoint`, or `Address`.`accountId`, `endpointId`, or `address`, matching the type.Use `Account` with the account ID of the cold vault account for any nonce account you intend to use through Ripple Custody. If you omit this field, the fee payer becomes the authority. |
| `operation.type` | `CreateNonceAccount` |
| `parameters.feeStrategy` | The fee strategy to use for the transaction. For Solana, the Priority strategy applies. For more information, see [Fee strategy and maximum fee](/products/custody/transactions/reference#fee-strategy-and-maximum-fee). |
| `parameters.maximumFee` | The maximum fee, in lamports. For a nonce account creation, it must cover the rent-exempt deposit as well as the signatures. For more information, see [Fee strategy and maximum fee](/products/custody/transactions/reference#fee-strategy-and-maximum-fee). |
| `parameters.memos` | Solana memos to include. Use an empty array when there are no memos. |
| `parameters.nonceAccount` | `null`. This transaction creates a nonce account rather than using one. |
| `parameters.type` | Ledger type, in this case `Solana`. |
| `type` | `v0_CreateTransactionOrder` |


## Send a transaction that uses the nonce

To send assets from the cold vault account, create a transaction order that references the nonce account and the nonce authority. Follow the same steps as in [Create the transaction order](#create-the-transaction-order), with a `payload` block similar to the example shown in [Transfer payload example](#transfer-payload-example).

### Transfer payload example

This example sends SOL from a cold vault account, using a nonce owned by a different account in the same cold vault:

```json
{
    "payload": {
        "id": "af1c2b9e-4c2b-45f5-9f76-2f2f4f7f9f10",
        "accountId": "9c1de1a2-58e3-41c8-8f4d-7f3f0b6f2b55",
        "ledgerId": "solana-mainnet",
        "parameters": {
            "operation": {
                "outputs": [
                    {
                        "destination": {
                            "address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
                            "type": "Address"
                        },
                        "amount": "1000000"
                    }
                ],
                "type": "Transfer"
            },
            "feeStrategy": {
                "priority": "Low",
                "type": "Priority"
            },
            "maximumFee": "10000000",
            "memos": [],
            "nonceAccount": {
                "accountId": "1f77dbb8-1fd6-4f7c-b839-9d51c31be2c7",
                "type": "Account"
            },
            "nonceAuthority": "6de35f06-3b46-4a5c-9c67-1b1c2f31a4d8",
            "type": "Solana"
        },
        "description": "Cold vault transfer with durable nonce",
        "customProperties": {},
        "type": "v0_CreateTransactionOrder"
    }
}
```

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

| Field | Description |
|  --- | --- |
| `accountId` | Internal account ID of the account that sends the assets and pays the fee. In a cold vault workflow, this is a cold vault account. |
| `operation.outputs` | Details of the transfer:`destination`: The recipient, as a transaction destination.`amount`: Amount to send, in lamports. |
| `operation.type` | `Transfer` |
| `parameters.maximumFee` | The maximum fee, in lamports. Set it high enough to cover every signature. For more information, see [Signatures and fees](#signatures-and-fees). |
| `parameters.nonceAccount` | The durable nonce account to use, as a transaction destination of type `Account`. |
| `parameters.nonceAuthority` | Optional. Internal account ID of the nonce authority, as a plain string. It must be a Ripple Custody account in the same vault as the fee payer. If you omit this field, the fee payer acts as the authority, which only works for a nonce account the fee payer owns. |
| `parameters.type` | Ledger type, in this case `Solana`. |
| `type` | `v0_CreateTransactionOrder` |


The two payloads use different shapes for the nonce authority. At creation, `operation.nonceAuthority` is a transaction destination object with a `type` field. When a transaction uses the nonce, `parameters.nonceAuthority` is a plain account ID string, because the authority must sign and so must resolve to a Ripple Custody account.

### Signatures and fees

For a transaction that uses the nonce, the number of signatures, and therefore the base fee, depends on whether the fee payer and the nonce authority are the same account:

| Fee payer and nonce authority | Signatures | Base fee |
|  --- | --- | --- |
| Same account | 1 | 5,000 lamports |
| Different accounts | 2 | 10,000 lamports |


Set `maximumFee` to cover both signatures when the authority is a different account. If it doesn't, the dry run or the proposal fails with an error similar to the following:

```text
simulated fee 10000 lamports exceeds caller maxFee 5000
```

### Cold vault signing

When the fee payer or the nonce authority is a cold vault account, the transaction order waits in the `Prepared` status until you complete a cold vault signing round trip: export the pending operations, sign them on the cold vault, and import the signed operations. Both signatures return in the same round trip when the fee payer and the authority are in the same cold vault. For the procedure, see [Process cold vault operations with the API](/products/custody/identity-and-access/vault-management/cold-vault-setup-api#process-accounts-transactions-or-manifests).

After you import the signed operations, Ripple Custody broadcasts the transaction. The confirmed transaction contains an `advanceNonce` instruction ahead of the transfer, and the nonce account shows a new stored nonce with the same authority.

## Troubleshooting

| Symptom | Likely cause | Resolution |
|  --- | --- | --- |
| An order stays in `Preparing` for a long time. | Another transaction that references the same nonce account is still in flight. | Wait for the earlier transaction to complete, or use a different nonce account. Create more nonce accounts to raise the number of concurrent transactions. |
| The dry run fails with a fee that exceeds `maximumFee`. | The fee payer and the nonce authority are different accounts, so the transaction needs two signatures. | Raise `maximumFee` to cover both signatures. |
| Ripple Custody rejects the intent because of the nonce authority. | The authority is in a different vault from the fee payer, is an external-provider account, is locked, or isn't a Ripple Custody account. | Use a nonce account whose authority is an account in the same vault as the fee payer. |
| Creating the nonce account fails with insufficient funds although the balance looks healthy. | The SOL on the fee-paying account is still in quarantine. | Release the quarantined deposit and retry. |
| A cold vault order stays in `Prepared`. | You haven't completed a cold vault round trip. | Export the pending operations, sign them on the cold vault, and import the signed operations. |


A dry run doesn't detect that an in-flight transaction has already reserved the nonce account. Ripple Custody accepts the order, and the order waits in `Preparing` until the nonce account becomes free.

## Related topics

- [Cold vaults](/products/custody/identity-and-access/vault-management/cold-vaults)
- [Process cold vault operations with the API](/products/custody/identity-and-access/vault-management/cold-vault-setup-api)
- [Send assets with the API](/products/custody/transactions/send-and-receive/send-assets-api)
- [Transaction reference](/products/custody/transactions/reference)
- [Supported ledgers](/products/custody/reference/supported-ledgers)