# Delegate Ethereum accounts to a smart contract with the API

EIP-7702 lets an Ethereum account adopt the code of a smart contract. After the network installs the delegation, the contract's code runs whenever anyone calls the account, and the account keeps its address and its key. Ripple Custody supports EIP-7702 delegation for Ethereum accounts, so that you can delegate a deposit wallet that holds tokens but no ETH, without funding it first.

A delegation takes two transaction orders from two accounts:

- A **SetDelegation** order from the account you want to delegate, called the authority. The authority signs an authorization that names the delegation contract. The authority pays no fee, so the order uses the `Sponsored` fee strategy and waits until a sponsor broadcasts it.
- A **SubmitAuthorization** order from a funded sponsor account. The sponsor wraps one or more signed authorizations in a single Ethereum transaction, broadcasts it, and pays the gas.


The network installs the delegation when it confirms the sponsor's transaction. Neither order moves assets.

## What this feature covers

Ripple Custody handles the delegation itself: the SetDelegation and SubmitAuthorization orders. You start each order yourself. Ripple Custody doesn't delegate accounts automatically, and it doesn't change existing or new accounts unless you submit a SetDelegation order for them.

What a delegated account can do afterward depends entirely on the contract that you delegate to:

- Ripple Custody doesn't provide a delegation contract. You can delegate to any smart contract that you deploy or choose. To deploy your own contract from Ripple Custody, see [Work with smart contracts](/pt-br/products/custody/transactions/smart-contracts/api).
- If the contract checks signatures, you still sign each call to it. For example, you sign a manifest. For more information, see [Sign and store payloads](/pt-br/products/custody/governance/intents/sign-and-store-payloads). If the contract allows calls from an allowlisted address, calls from that address can run without an extra signature.
- Ripple Custody doesn't yet sponsor ETH or ERC-20 transfers from a delegated account. A sponsor pays only for the delegation transaction itself.
- The delegation sponsor is separate from [Gas Station](/pt-br/products/custody/accounts-and-assets/gas-station/overview). Gas Station funds fees by sending the native token to a sponsored account before it transacts. A delegation sponsor pays the gas for the delegation transaction directly, and no native token moves to the delegated account.


The contract controls a delegated account
Delegating an account gives the delegation contract's code full control over the account and every asset it holds, for as long as the delegation is active. Any address that can call the contract's functions can trigger that control, not only the account owner. A contract that is buggy, badly implemented, or malicious can drain the account.

Delegate only to a contract you trust and that has passed an independent audit. Don't delegate to an unfamiliar or unaudited contract. A delegation doesn't expire and doesn't clear itself after use. Revoke it as soon as you no longer need it. See [Revoke a delegation](#revoke-a-delegation).

Requirements
EIP-7702 delegation requires the following:

- **Pectra.** The network must have the Pectra hard fork active. EIP-7702 transactions are a Pectra feature. A network that hasn't activated Pectra can't install the delegation. It rejects the EIP-7702 transaction outright.
- **Vault image 1.35.0 or later.** An earlier Vault image can sign the authorization but not the sponsor's EIP-7702 transaction. It rejects the SubmitAuthorization signing request without reporting an error to the caller. Confirm the deployed Vault image version before you rely on this feature.
- **Unified Indexer Service (UIS) v2**, introduced with [ledger accounting refactor phase 2](/pt-br/products/custody/support/change-history/v140#ledger-accounting-refactor-phase-2). SaaS deployments run UIS v2 from version 1.40. On-premises deployments run it starting in version 1.43. An environment that hasn't migrated rejects Ethereum orders that carry an `operation`. For more information, see [Unified Indexer Service](/pt-br/products/custody/overview/architecture/unified-indexer-service).


## Delegation roles

A delegation involves three roles:

| Role | Description | Signs |
|  --- | --- | --- |
| Authority | The account to delegate. Typically a deposit wallet that holds tokens but no ETH. You set it with the top-level `accountId` of the SetDelegation order. | The authorization, in the SetDelegation order |
| Sponsor | A funded Ripple Custody account on the same Ethereum ledger. It broadcasts the authorization on chain and pays the gas. You set it with the top-level `accountId` of the SubmitAuthorization order. It must be a different account from the authority. | The on-chain transaction, in the SubmitAuthorization order |
| Delegation contract | The smart contract whose code the authority adopts. You set it with `operation.delegationContract` in the SetDelegation order. | Never |


The lifecycle of a delegation is as follows:

1. **Authorization.** You create a SetDelegation order from the authority. After approval, Ripple Custody reserves the authority's nonce and the vault signs the authorization. The order then waits in the `Broadcasting` status. Nothing is on chain yet. The transaction has no hash and no `ledgerTransactionData` block while it waits.
2. **Submission.** You create a SubmitAuthorization order from the sponsor that references one or more waiting SetDelegation orders. Ripple Custody resolves each signed authorization, verifies its signature again, builds a single EIP-7702 transaction that carries all of them, signs it with the sponsor's key, and broadcasts it.
3. **Confirmation.** When the network confirms the sponsor's transaction, Ripple Custody checks whether the network applied each authorization, by comparing the authority's on-chain nonce with the nonce it reserved. Both orders finish in the `Completed` status. The transaction's `ledgerTransactionData.failure` field reports the outcome of each delegation, installed or skipped. See [Check the delegation outcome](#check-the-delegation-outcome).


Both orders go through the standard intent approval flow. You can scope a policy to delegation orders with a condition on `parameters.operation.type` equal to `SetDelegation` or `SubmitAuthorization`.

### Account requirements

| Account | Requirement |
|  --- | --- |
| Authority | An Ethereum account in Ripple Custody. It needs no ETH. |
| Sponsor | Any Ethereum account on the same ledger as the authority, with enough ETH to pay the gas for the EIP-7702 transaction, other than the authority itself. An account can't sponsor its own delegation. Ripple Custody rejects a SubmitAuthorization order whose sponsor is also the authority of a referenced SetDelegation order. |


## Delegate an account

### Prerequisites

To delegate an Ethereum account, you need the following:

| Prerequisite | Additional information |
|  --- | --- |
| The account ID of the Ethereum account to delegate. It needs no funding. | [View account details](/pt-br/products/custody/accounts-and-assets/accounts/manage-accounts-api#view-account-details) |
| The address of an audited smart contract that you trust, deployed on the same network | [What this feature covers](#what-this-feature-covers) |
| If this is a multi-ledger account, the ledger ID | [List ledgers](/pt-br/products/custody/reference/api/openapi/ledgers/getledgers) |
| A deployment that runs the Unified Indexer Service v2 and Vault image 1.35.0 or later, on a network with Pectra active | [Unified Indexer Service](/pt-br/products/custody/overview/architecture/unified-indexer-service) |
| The following new IDs, in a standard UUID format:A transaction order IDAn intent ID |  |


### Create the SetDelegation 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](/pt-br/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](/pt-br/products/custody/governance/intents/intent-request-structure#user-signed-proposal-request-body).
2. Call the [Perform a dry run for an intent](/pt-br/products/custody/reference/api/openapi/intents/intentdryrun) operation, with the `request` field excluded. For more information, see [Dry run intents](/pt-br/products/custody/governance/intents/manage-intents-and-approvals#dry-run-an-intent-with-the-api).
This step is optional, but we recommend it. A dry run reveals a rejected payload, for example a SetDelegation order that sets a `destination`, before you submit it.
3. Sign the request body and call the [Propose an intent](/pt-br/products/custody/reference/api/openapi/intents/createintent) operation. For more information, see [Propose an intent to create an entity](/pt-br/products/custody/governance/intents/manage-intents-and-approvals#submit-an-intent-with-the-api).
4. Check the update. For more information, see [Check updates](/pt-br/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:

- Reserves the account's next nonce and signs the authorization with the account's key.
- Creates a transaction that waits in the `Broadcasting` status until a SubmitAuthorization order broadcasts it. While it waits, the transaction has no `ledgerTransactionData` block.


It doesn't broadcast anything on chain and creates no fee transfer. The account's balances don't change.

A SetDelegation order doesn't expire on chain. The signed authorization stays valid until another transaction consumes the account's nonce. However, sponsor the order promptly. An order that no SubmitAuthorization references for about two days can no longer complete on its own, and Support has to release the account's nonce reservation. 

#### Payload example

This example delegates an account to a delegation contract:

```json
{
    "payload": {
        "id": "7d1f7a0e-2d3b-4a2e-9d7e-3f2b9c1a5e10",
        "accountId": "bc9a3d6d-a6e5-4c1c-9058-40ba7ee9ead0",
        "ledgerId": "ethereum-mainnet",
        "parameters": {
            "amount": "0",
            "operation": {
                "delegationContract": "0x1234567890abcdef1234567890abcdef12345678",
                "type": "SetDelegation"
            },
            "feeStrategy": {
                "type": "Sponsored"
            },
            "type": "Ethereum"
        },
        "description": "Delegate deposit wallet to the delegation contract",
        "customProperties": {},
        "type": "v0_CreateTransactionOrder"
    }
}
```

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

| Field | Description |
|  --- | --- |
| `accountId` | Internal account ID of the Ethereum account to delegate. This account signs the authorization. |
| `ledgerId` | ID of the Ethereum ledger, for example `ethereum-mainnet`. Include it for multi-ledger accounts. |
| `parameters.amount` | `0`. A SetDelegation order moves no assets. |
| `parameters.operation.delegationContract` | Address of the delegation contract. To revoke an existing delegation, use the zero address. See [Revoke a delegation](#revoke-a-delegation). |
| `parameters.operation.type` | `SetDelegation` |
| `parameters.feeStrategy` | `Sponsored`. A SetDelegation order must use this strategy, because a sponsor pays the gas. Don't set `maximumFee`. The strategy accepts an optional `sponsor` field, the internal account ID of the account that you intend to broadcast the delegation from. Ripple Custody records the value but doesn't enforce it. Any account other than the authority can submit the SubmitAuthorization order. Ripple plans to use the field in a future Gas Station integration, to pick the sponsor. |
| `parameters.type` | Ledger type, in this case `Ethereum`. |
| `type` | `v0_CreateTransactionOrder` |


Don't include `destination` or `data`. Ripple Custody rejects a SetDelegation order that sets either field. It also rejects an Ethereum order that combines the `Sponsored` fee strategy with any operation other than `SetDelegation`, and rejects `Sponsored` on any other ledger.

## Submit the authorization

### Prerequisites

To broadcast one or more delegations, you need the following:

| Prerequisite | Additional information |
|  --- | --- |
| A funded Ethereum account in the same domain to act as the sponsor | [Delegation roles](#delegation-roles) |
| Enough ETH in the sponsor account to cover the transaction fee. Ripple Custody quarantines incoming deposits, and you can't spend them until you release them. | [Check account balances](/pt-br/products/custody/accounts-and-assets/accounts/manage-accounts-api#check-account-balances)[Release quarantined assets](/pt-br/products/custody/transactions/send-and-receive/receive-assets-api#release-quarantined-assets) |
| The transaction order IDs of one or more SetDelegation orders that are waiting for a sponsor, all on the same ledger as the sponsor | [View the status of a transaction order](/pt-br/products/custody/transactions/viewing-assets/view-and-audit-api#view-the-status-of-a-transaction-order) |
| The following new IDs, in a standard UUID format:A transaction order IDAn intent ID |  |


### Create the SubmitAuthorization order

Follow the same steps as in [Create the SetDelegation order](#create-the-setdelegation-order), with a `payload` block similar to the example shown in [SubmitAuthorization payload example](#submitauthorization-payload-example).

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

- Creates a transaction, to broadcast an EIP-7702 transaction from the sponsor that carries every referenced authorization.
- Creates one or more transfers, to represent the fees paid by the sponsor.
- Records the hash of the sponsor's transaction on each referenced SetDelegation order, in `ledgerTransactionData.sponsoringTransactionHash`.


#### SubmitAuthorization payload example

This example broadcasts the delegations of two accounts in a single transaction:

```json
{
    "payload": {
        "id": "c4a2e7b1-8f6d-4c3a-b5e9-1d0f2a3b4c5d",
        "accountId": "5e8f1a2b-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
        "ledgerId": "ethereum-mainnet",
        "parameters": {
            "amount": "0",
            "operation": {
                "authorizations": [
                    {
                        "transactionOrderId": "7d1f7a0e-2d3b-4a2e-9d7e-3f2b9c1a5e10"
                    },
                    {
                        "transactionOrderId": "9b3c5d7e-1f2a-4b6c-8d9e-0a1b2c3d4e5f"
                    }
                ],
                "type": "SubmitAuthorization"
            },
            "feeStrategy": {
                "priority": "Medium",
                "type": "Priority"
            },
            "maximumFee": "2187025000000000",
            "type": "Ethereum"
        },
        "description": "Broadcast deposit wallet delegations",
        "customProperties": {},
        "type": "v0_CreateTransactionOrder"
    }
}
```

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

| Field | Description |
|  --- | --- |
| `accountId` | Internal account ID of the sponsor. This account broadcasts the transaction and pays the gas. |
| `ledgerId` | ID of the Ethereum ledger. It must match the ledger of the referenced SetDelegation orders. |
| `parameters.amount` | `0`. |
| `parameters.operation.authorizations` | One entry per delegation to broadcast, each with the `transactionOrderId` of a SetDelegation order that is waiting for a sponsor. Don't include the signed authorization itself. Ripple Custody resolves it from the referenced order and verifies its signature again. |
| `parameters.operation.type` | `SubmitAuthorization` |
| `parameters.feeStrategy` | The sponsor's normal fee strategy, such as `Priority` or a specified rate. Don't use `Sponsored`. For more information, see [Fee strategy and maximum fee](/pt-br/products/custody/transactions/reference#fee-strategy-and-maximum-fee). |
| `parameters.maximumFee` | The maximum fee, in Wei. An EIP-7702 transaction costs more gas than a plain transfer, and the cost grows with the number of authorizations. |
| `parameters.type` | Ledger type, in this case `Ethereum`. |
| `type` | `v0_CreateTransactionOrder` |


The order is all or nothing. If any referenced SetDelegation order is missing, already completed, or fails signature verification, the SubmitAuthorization order fails before Ripple Custody broadcasts anything, and the network installs no delegation.

## Check the delegation outcome

When the network confirms the sponsor's transaction, both the SubmitAuthorization order and every referenced SetDelegation order reach the `Completed` status, whether or not the network installed each delegation. The `Completed` status means that Ripple Custody finished processing the order. It doesn't mean the delegation is in place.

To check the outcome of a delegation:

1. Get the transaction for the SetDelegation order, as described in [View transactions for a transaction order](/pt-br/products/custody/transactions/viewing-assets/view-and-audit-api#view-transactions-for-a-transaction-order).
2. Read the `ledgerTransactionData` block. It appears on the transaction only after Ripple Custody broadcasts the sponsor's transaction:
| Field | Value | Meaning |
|  --- | --- | --- |
| `failure` | `null` | The network installed the delegation. The account's code now points to the delegation contract. |
| `failure` | `FailedOnChain` | The network skipped the authorization, for example because another transaction consumed the account's nonce first. The network didn't install the delegation. To retry, create a new SetDelegation order. Resubmitting the same order in another SubmitAuthorization doesn't work. |
| `sponsoringTransactionHash` | The hash of the sponsor's transaction | Use it in a blockchain explorer to view the transaction that installed the delegation. |
| `ledgerTransactionId` | A composite identifier in the form `<sponsorTransactionHash>:<authorityAddress>:<nonce>` | Identifies this authorization within the sponsor's transaction. Only the first component is a transaction hash. Don't use the whole value in a blockchain explorer, and don't assume that every `ledgerTransactionId` is a plain hash if your integration parses this field. |


For example, a SetDelegation order for the authority `0x22d2d1c346784c9453d0818ef0a5ca56a62ddcb1` at nonce 2, installed by the sponsor's transaction `0x8601b0008f6165cf30d550eabdc7e117590674679032e9fc0c0a297d8fbea802`, has the following identifiers:

```json
"ledgerTransactionData": {
    "ledgerStatus": "Confirmed",
    "failure": null,
    "ledgerTransactionId": "0x8601b0008f6165cf30d550eabdc7e117590674679032e9fc0c0a297d8fbea802:0x22d2d1c346784c9453d0818ef0a5ca56a62ddcb1:2",
    "sponsoringTransactionHash": "0x8601b0008f6165cf30d550eabdc7e117590674679032e9fc0c0a297d8fbea802"
}
```

The outcome of each delegation is independent of the others in the same SubmitAuthorization order, and independent of whether the sponsor's transaction itself executed successfully. Per EIP-7702, the network processes the authorization list before it executes the transaction and doesn't roll the delegations back if the execution reverts.

To confirm the delegation on chain independently, look up the authority's address in a blockchain explorer or query its code with `eth_getCode`. A delegated account's code is the EIP-7702 designator `0xef0100` followed by the address of the delegation contract. Etherscan shows a **Delegated to** tag on the address page, and lists every authorization the sponsor's transaction carried in the transaction's **Authorizations** tab. A revoked account has empty code again.

## Revoke a delegation

A delegation stays active until you revoke it. To revoke it, create a new SetDelegation order from the delegated account with `delegationContract` set to the zero address, then broadcast it with a SubmitAuthorization order as before:

```json
"operation": {
    "delegationContract": "0x0000000000000000000000000000000000000000",
    "type": "SetDelegation"
}
```

Per EIP-7702, an authorization for the zero address clears the account's code, so the account behaves as a regular account again.

To move an account to a different contract, create a SetDelegation order for the new contract. You don't need to revoke the old delegation first. The new delegation replaces it.

## Troubleshooting

| Symptom | Likely cause | Resolution |
|  --- | --- | --- |
| The dry run or the proposal of a SetDelegation order fails with a validation error. | The payload sets `destination` or `data`, or uses a fee strategy other than `Sponsored`. | Remove `destination`, `data`, and `maximumFee`, and set `feeStrategy.type` to `Sponsored`. |
| Ripple Custody rejects an order with the `Sponsored` fee strategy. | `Sponsored` is valid only on Ethereum, and only for `SetDelegation` orders. | Use the account's normal fee strategy for any other operation. |
| Ripple Custody rejects a SetDelegation order when it prepares the transaction, with an error that the network isn't supported. | The network hasn't activated the Pectra hard fork, or the network configuration turns off EIP-7702 support. | Use a network with Pectra active. On-premises operators can check the EIP-7702 setting of the network.  |
| A SetDelegation order stays in `Broadcasting` and its transaction has no `ledgerTransactionData` block. | No SubmitAuthorization order has referenced it yet. | Create a SubmitAuthorization order from a sponsor that references the order. If you no longer want the delegation, or the order has waited for more than about two days, contact Support to release the account's nonce reservation. |
| A SubmitAuthorization order fails before broadcast. | One of the referenced SetDelegation orders doesn't exist, isn't waiting for a sponsor, or failed signature verification. The order is all or nothing. | Check each referenced order ID and status, remove the invalid entries, and create a new SubmitAuthorization order. |
| Ripple Custody rejects a SubmitAuthorization order because the sponsor is also the authority. | An account can't sponsor its own delegation. | Create the SubmitAuthorization order from a different, funded account. |
| A SubmitAuthorization order fails to broadcast or expires, and the referenced SetDelegation orders stay in `Broadcasting`. | The sponsor's transaction never landed on chain. No transaction consumed the nonces, so the signed authorizations are still valid. | Create a new SubmitAuthorization order that references the same SetDelegation orders. If you no longer want the delegations, contact Support to release the nonce reservations. |
| A SubmitAuthorization order stays in `Prepared` and is never signed. | The Vault image is older than 1.35.0 and can't sign EIP-7702 transactions. It rejects the request without reporting an error. | Upgrade the Vault image to 1.35.0 or later. |
| Ripple Custody rejects an Ethereum order that carries an `operation`. | The environment hasn't migrated to UIS v2 and ledger accounting refactor phase 2. | Wait for the migration. SaaS deployments run UIS v2 from version 1.40. |
| A SetDelegation order is `Completed` but `failure` is `FailedOnChain`. | The network skipped the authorization. | Create a new SetDelegation order and broadcast it with a new SubmitAuthorization order. |


## Related topics

- [Fee strategy and maximum fee](/pt-br/products/custody/transactions/reference#fee-strategy-and-maximum-fee)
- [View transactions](/pt-br/products/custody/transactions/viewing-assets/view-and-audit-api)
- [Gas Station](/pt-br/products/custody/accounts-and-assets/gas-station/overview)
- [Omnibus deposits](/pt-br/products/custody/accounts-and-assets/omnibus/deposits)
- [Work with smart contracts](/pt-br/products/custody/transactions/smart-contracts/api)
- [Supported ledgers](/pt-br/products/custody/reference/supported-ledgers)