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

# Manage Token Delegations

> Learn how to approve, revoke, and manage token delegations using Portal's Web SDK.

Portal's Web SDK provides token delegation capabilities through the `portal.delegations` API. This enables approving token spending, revoking approvals, checking delegation status, and transferring tokens as a delegate on both EVM and Solana chains.

## Overview

The delegations functionality allows you to:

* **Approve** other addresses to spend tokens on behalf of your wallet
* **Revoke** existing delegations to remove spending permissions
* **Check status** of active delegations and balances
* **Transfer tokens** as a delegate from another address
* **Approve, revoke, and transfer in one step** using the high-level submit helpers (`approveAndSubmit`, `revokeAndSubmit`, `transferAndSubmit`)

## Prerequisites

Before using delegation operations, ensure you have:

* A properly initialized Portal client
* An active wallet with tokens on the target network (see [Create a wallet](./create-a-wallet))
* Understanding of [token delegations concepts](/resources/delegations)

<Warning>
  Delegations apply to ERC-20 tokens (EVM) and SPL Tokens (Solana) only. Native assets like ETH, MON, and SOL cannot be delegated — they have no on-chain `approve` / `transferFrom` (or SPL delegate) semantics. Calls using a native asset identifier will be rejected. See [Delegations](/resources/delegations#what-are-token-delegations) for the protocol-level reason and workarounds.
</Warning>

***

## High-Level Methods

These methods provide auto-submit behavior. For most use cases, these helpers are the recommended starting point. They call the matching delegation API method, then sign and broadcast each returned transaction in order — all in a single call.

The low-level methods (`approve`, `revoke`, `transferFrom`) only build delegation transactions via the API. You receive EVM transaction objects or Solana-encoded payloads and must call `portal.request` with the correct method yourself (`eth_sendTransaction` or `sol_signAndSendTransaction`). The high-level methods handle that signing and submission step for you, resolving to transaction hashes only — they do not wait for on-chain confirmation.

When you use the standard `Portal` client, `portal.delegations` is automatically wired with a default `signAndSendTransaction` implementation that picks `eth_sendTransaction` vs `sol_signAndSendTransaction` based on whether the chain id **starts with** `solana`.

The signer is resolved in priority order:

1. **Per-call override** — `options.signAndSendTransaction` when provided
2. **Instance-level default** — configured on `Portal` (automatic when you construct `Portal`)
3. **Error** — thrown if neither is available: `[Delegations] No signer configured. Call setSignAndSendTransaction() on the instance or pass signAndSendTransaction in options.`

<Note>
  TypeScript request and options shapes ([`ApproveDelegationRequest`](/sdks/web/reference#approve-delegation-request), [`RevokeDelegationRequest`](/sdks/web/reference#revoke-delegation-request), [`TransferFromRequest`](/sdks/web/reference#transfer-from-request), [`DelegationSubmitOptions`](/sdks/web/reference#delegation-submit-options), [`DelegationSubmitProgress`](/sdks/web/reference#delegation-submit-progress)) are listed in [Delegations — Web SDK reference](/sdks/web/reference#delegations). Types are exported from `@portal-hq/web`.
</Note>

### `approveAndSubmit`

#### Overview

Calls `approve`, then signs and sends every transaction in the response. Use it when you want a single step from approval intent to submitted transactions instead of manually calling `portal.request` after `approve`.

#### Example

```typescript theme={null}
// Using Portal — no signAndSendTransaction needed (auto-configured)
const { hashes } = await portal.delegations.approveAndSubmit(
  {
    chain: 'eip155:11155111',
    token: 'USDC',
    delegateAddress: '0xb52a818536341003c9d923103abd3659c27e5a2b',
    amount: '10',
  },
  {
    onProgress: (event) => console.log(event),
  },
)

console.log('Submitted:', hashes)
```

#### Parameters

| Name                             | Type              | Required | Description                                                                          |
| -------------------------------- | ----------------- | -------- | ------------------------------------------------------------------------------------ |
| `params`                         | `object`          | Yes      | Same shape as `approve`.                                                             |
| `params.chain`                   | `string`          | Yes      | Chain CAIP ID (EVM `eip155:…` or Solana `solana:…`).                                 |
| `params.token`                   | `string`          | Yes      | Token symbol for the delegations API.                                                |
| `params.delegateAddress`         | `string`          | Yes      | Address allowed to spend on your behalf.                                             |
| `params.amount`                  | `string`          | Yes      | Approval amount as a positive decimal string.                                        |
| `options`                        | `object`          | No       | Optional callbacks and configuration.                                                |
| `options.signAndSendTransaction` | `function`        | No       | Per-call signer override. Auto-configured by `Portal` when using the default client. |
| `options.onProgress`             | `(event) => void` | No       | See **Progress events** below.                                                       |

**Progress events** (when `onProgress` is set): the callback is invoked **twice per transaction** — once with `step: 'signing'` (fields: `index`, `total`) before signing begins, and once with `step: 'submitted'` (fields: `index`, `total`, `hash`) after `signAndSendTransaction` resolves. For a batch of *n* transactions, `onProgress` is called *2n* times in total.

#### Returns

* **`{ hashes: string[] }`** — One entry per submitted transaction, in the same order as `transactions` (EVM) or `encodedTransactions` (Solana) from the API. Each value is the hash returned by `signAndSendTransaction` for that item.

#### Notes / Gotchas

* Transactions are submitted **sequentially** (not batched in one RPC call). If the API returns multiple payloads, the second is only sent after the first send resolves.
* The method returns as soon as transactions are **submitted**; waiting for confirmations is your app's responsibility.
* Each step triggers a **wallet / signing** flow through the Portal provider.
* For Solana responses, encoded payloads are used when `transactions` is missing or empty (same selection rules as the implementation).
* If the API response includes no `transactions` and no `encodedTransactions`, the method throws: `No transactions in delegation response.`

### `revokeAndSubmit`

#### Overview

Calls `revoke`, then signs and sends each returned transaction. Use it to remove spending permission and broadcast the revocation in one flow.

#### Example

```typescript theme={null}
const { hashes } = await portal.delegations.revokeAndSubmit(
  {
    chain: 'eip155:11155111',
    token: 'USDC',
    delegateAddress: '0xb52a818536341003c9d923103abd3659c27e5a2b',
  },
  {
    onProgress: (event) => console.log(event),
  },
)
```

#### Parameters

| Name                     | Type     | Required | Description                                                          |
| ------------------------ | -------- | -------- | -------------------------------------------------------------------- |
| `params`                 | `object` | Yes      | Same shape as `revoke`.                                              |
| `params.chain`           | `string` | Yes      | Chain CAIP ID.                                                       |
| `params.token`           | `string` | Yes      | Token symbol.                                                        |
| `params.delegateAddress` | `string` | Yes      | Delegate address whose approval you revoke.                          |
| `options`                | `object` | No       | Same as `approveAndSubmit` (`signAndSendTransaction`, `onProgress`). |

#### Returns

* **`{ hashes: string[] }`** — Same semantics as `approveAndSubmit`: ordered hashes from each `signAndSendTransaction` call.

#### Notes / Gotchas

* Same execution, signing, confirmation, chain-routing, and empty-response behavior as `approveAndSubmit`.

### `transferAndSubmit`

#### Overview

Calls `transferFrom`, then signs and sends each returned transaction. Use it when your wallet is the delegate moving funds from an owner you were approved for.

#### Example

```typescript theme={null}
const { hashes } = await portal.delegations.transferAndSubmit(
  {
    chain: 'eip155:11155111',
    token: 'USDC',
    fromAddress: '0x03c66353df426e18e6e7866fa9e2e73ef6833500',
    toAddress: '0xdFd8302f44727A6348F702fF7B594f127dE3A902',
    amount: '0.0001',
  },
  {
    onProgress: (event) => console.log(event),
  },
)
```

#### Parameters

| Name                 | Type     | Required | Description                                                          |
| -------------------- | -------- | -------- | -------------------------------------------------------------------- |
| `params`             | `object` | Yes      | Same shape as `transferFrom`.                                        |
| `params.chain`       | `string` | Yes      | Chain CAIP ID.                                                       |
| `params.token`       | `string` | Yes      | Token symbol.                                                        |
| `params.fromAddress` | `string` | Yes      | Owner who delegated to your wallet.                                  |
| `params.toAddress`   | `string` | Yes      | Recipient of the transfer.                                           |
| `params.amount`      | `string` | Yes      | Amount to move.                                                      |
| `options`            | `object` | No       | Same as `approveAndSubmit` (`signAndSendTransaction`, `onProgress`). |

#### Returns

* **`{ hashes: string[] }`** — Same ordering rules as the other submit helpers.

#### Notes / Gotchas

* Same sequential submit, no confirmation wait, signing, chain routing, and empty-response error as above.
* Unlike `transferFrom`, **`transferAndSubmit` does not return API `metadata`** (for example `TransferAsDelegateMetadata`); only `{ hashes }` is exposed after broadcast.

### Overriding the transaction sender

When using `Portal`, the default `signAndSendTransaction` is automatically configured, so you don't need to provide it. For custom signing logic (for example a different submission path), pass `signAndSendTransaction` in the options:

```typescript theme={null}
await portal.delegations.approveAndSubmit(
  {
    chain: 'eip155:11155111',
    token: 'USDC',
    delegateAddress: '0xb52a818536341003c9d923103abd3659c27e5a2b',
    amount: '10',
  },
  {
    signAndSendTransaction: async (tx, chainId) => {
      const method = chainId.startsWith('solana')
        ? 'sol_signAndSendTransaction'
        : 'eth_sendTransaction'
      const hash = await portal.request({
        chainId,
        method,
        params: [tx],
      })
      if (typeof hash !== 'string' || hash.trim().length === 0) {
        throw new Error('No transaction hash returned from provider')
      }
      return hash
    },
    onProgress: (event) => console.log(event),
  },
)
```

The payload `tx` is the same shape the default signer passes through: an EVM transaction object from `transactions[]`, or a Solana payload from `encodedTransactions[]`.

***

## Low-level methods

The following methods return raw API responses — transaction objects for EVM or base64-encoded payloads for Solana — which you sign and broadcast yourself via `portal.request`. Use these when you need full control over the signing or submission step.

## Approving Delegations

Use `approve` to grant another address permission to spend tokens on your behalf. This method works for both EVM and Solana chains.

EVM Approval

```typescript theme={null}
async function approveEVMDelegation(portal: Portal) {
  const tx = await portal.delegations.approve({
    chain: 'eip155:11155111', // Sepolia testnet
    token: 'USDC',
    delegateAddress: '0xb52a818536341003c9d923103abd3659c27e5a2b',
    amount: '10',
  })

  console.log('Approve response:', tx)
  
  // Sign and send the transaction
  const hash = await portal.request({
    chainId: 'eip155:11155111',
    method: 'eth_sendTransaction',
    params: [tx.transactions[0]]
  })
  
  console.log('Transaction hash:', hash)
  return hash
}
```

Solana Approval

```typescript theme={null}
async function approveSolanaDelegation(portal: Portal) {
  const tx = await portal.delegations.approve({
    chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', // Solana Devnet
    token: 'USDC',
    delegateAddress: '7EYg9HUZBoCeCfWbcj3EFNX5Ecgjn9FTY2UhnRny5NYv',
    amount: '10',
  })

  console.log('Approve response:', tx)
  
  // Sign and send the transaction
  const hash = await portal.request({
    chainId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
    method: 'sol_signAndSendTransaction',
    params: [tx.encodedTransactions[0]]
  })
  
  console.log('Transaction hash:', hash)
  return hash
}
```

***

## Checking Delegation Status

Use `getStatus` to check current delegations and token balances for a specific delegate address.

EVM Status Check

```typescript theme={null}
async function getEVMDelegationStatus(portal: Portal) {
  const status = await portal.delegations.getStatus({
    chain: 'eip155:11155111',
    token: 'USDC',
    delegateAddress: '0xb52a818536341003c9d923103abd3659c27e5a2b',
  })

  console.log('Delegation status:', status)
  console.log('Balance:', status.balance)
  console.log('Delegations:', status.delegations)
}
```

Solana Status Check

```typescript theme={null}
async function getSolanaDelegationStatus(portal: Portal) {
  const status = await portal.delegations.getStatus({
    chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
    token: 'USDC',
    delegateAddress: '7EYg9HUZBoCeCfWbcj3EFNX5Ecgjn9FTY2UhnRny5NYv',
  })

  console.log('Delegation status:', status)
}
```

***

## Revoking Delegations

Use `revoke` to remove spending permissions from a delegate address.

EVM Revoke

```typescript theme={null}
async function revokeEVMDelegation(portal: Portal) {
  const tx = await portal.delegations.revoke({
    chain: 'eip155:11155111',
    token: 'USDC',
    delegateAddress: '0xb52a818536341003c9d923103abd3659c27e5a2b',
  })

  console.log('Revoke response:', tx)
  
  const hash = await portal.request({
    chainId: 'eip155:11155111',
    method: 'eth_sendTransaction',
    params: [tx.transactions[0]]
  })
  
  console.log('Transaction hash:', hash)
  return hash
}
```

Solana Revoke

```typescript theme={null}
async function revokeSolanaDelegation(portal: Portal) {
  const tx = await portal.delegations.revoke({
    chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
    token: 'USDC',
    delegateAddress: '7EYg9HUZBoCeCfWbcj3EFNX5Ecgjn9FTY2UhnRny5NYv',
  })

  console.log('Revoke response:', tx)
  
  const hash = await portal.request({
    chainId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
    method: 'sol_signAndSendTransaction',
    params: [tx.encodedTransactions[0]]
  })
  
  console.log('Transaction hash:', hash)
  return hash
}
```

<Warning>
  Always revoke unused delegations after completing operations to minimize security risks.
</Warning>

***

## Transferring as a Delegate

Use `transferFrom` to transfer tokens from another address that has delegated spending permission to you.

EVM Transfer From

```typescript theme={null}
async function transferFromEVM(portal: Portal) {
  const tx = await portal.delegations.transferFrom({
    chain: 'eip155:11155111',
    token: 'USDC',
    fromAddress: '0x03c66353df426e18e6e7866fa9e2e73ef6833500', // Token owner
    toAddress: '0xdFd8302f44727A6348F702fF7B594f127dE3A902', // Recipient
    amount: '0.0001',
  })

  console.log('TransferFrom response:', tx)
  
  const hash = await portal.request({
    chainId: 'eip155:11155111',
    method: 'eth_sendTransaction',
    params: [tx.transactions[0]]
  })
  
  console.log('Transaction hash:', hash)
  return hash
}
```

Solana Transfer From

```typescript theme={null}
async function transferFromSolana(portal: Portal) {
  const tx = await portal.delegations.transferFrom({
    chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
    token: 'USDC',
    fromAddress: 'GoFBWzCVxSEGYxgSAmdm2itS3EUbYmLpgzEcQ4J3WnsN', // Token owner
    toAddress: 'GPsPXxoQA51aTJJkNHtFDFYui5hN5UxcFPnheJEHa5Du', // Recipient
    amount: '0.0001',
  })

  console.log('TransferFrom response:', tx)
  
  const hash = await portal.request({
    chainId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
    method: 'sol_signAndSendTransaction',
    params: [tx.encodedTransactions[0]]
  })
  
  console.log('Transaction hash:', hash)
  return hash
}
```

<Note>
  **Delegation Roles**: `fromAddress` is the token owner who approved the delegation. Your wallet (the delegate) signs the transaction to transfer tokens from the owner to the `toAddress` recipient.
</Note>

***

## Supported Networks

Delegations work on all Portal-supported EVM and Solana chains:

* **EVM**: Ethereum, Polygon, Base, Arbitrum, Optimism, Monad, and all other EVM-compatible chains
* **Solana**: Solana Mainnet and Devnet

For a complete list, see [Blockchain Support](/resources/blockchain-support).

***

## Next Steps

* Learn about [signing transactions](./sign-a-transaction)
* Explore [Portal API methods](./portal-api-methods)
* Review [delegation concepts](/resources/delegations)
* Check out [wallet lifecycle management](./manage-wallet-lifecycle-states)
