Skip to main content

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.

Portal’s React Native 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:
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 for the protocol-level reason and workarounds.

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 PortalRequestMethod yourself. 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 (via the setSignAndSendTransaction setter) that picks PortalRequestMethod.EthSendTransaction vs PortalRequestMethod.SolSignAndSendTransaction based on the chain’s CAIP-2 namespace. The signer is resolved in priority order:
  1. Per-call overrideoptions.signAndSendTransaction when provided
  2. Instance-level default — configured via setSignAndSendTransaction() (Portal sets this automatically)
  3. Error — thrown if neither is available: [Delegations] No signer configured. Call setSignAndSendTransaction() on the instance or pass signAndSendTransaction in options.

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

// 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

NameTypeRequiredDescription
paramsobjectYesSame shape as approve.
params.chainstringYesChain CAIP ID (EVM eip155:… or Solana solana:…).
params.tokenstringYesToken symbol for the delegations API.
params.delegateAddressstringYesAddress allowed to spend on your behalf.
params.amountstringYesApproval amount as a positive decimal string.
optionsobjectNoOptional callbacks and configuration.
options.signAndSendTransactionfunctionNoPer-call signer override. Auto-configured by Portal via setSignAndSendTransaction.
options.onProgress(event) => voidNoSee 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 signAndSend resolves.
  • The method returns as soon as transactions are submitted; waiting for confirmations is your app’s responsibility.
  • Important: If subsequent operations depend on these transactions being mined, use portal.waitForConfirmation(txHash, chainId) to ensure the transaction is confirmed before proceeding. Unconfirmed transactions may cause dependent operations to fail.
  • Each step triggers a wallet / signing flow through portal.request.
  • 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

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

Parameters

NameTypeRequiredDescription
paramsobjectYesSame shape as revoke.
params.chainstringYesChain CAIP ID.
params.tokenstringYesToken symbol.
params.delegateAddressstringYesDelegate address whose approval you revoke.
optionsobjectNoSame 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

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

Parameters

NameTypeRequiredDescription
paramsobjectYesSame shape as transferFrom.
params.chainstringYesChain CAIP ID.
params.tokenstringYesToken symbol.
params.fromAddressstringYesOwner who delegated to your wallet.
params.toAddressstringYesRecipient of the transfer.
params.amountstringYesAmount to move.
optionsobjectNoSame 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 via setSignAndSendTransaction(), so you don’t need to provide it. For custom signing logic (e.g., using a different wallet provider), pass signAndSendTransaction in the options:
await portal.delegations.approveAndSubmit(
  {
    chain: 'eip155:11155111',
    token: 'USDC',
    delegateAddress: '0xb52a818536341003c9d923103abd3659c27e5a2b',
    amount: '10',
  },
  {
    signAndSendTransaction: async (tx, chainId) => {
      // Custom signing logic here
      console.log('Signing tx for chain:', chainId)
      return myCustomSigner(tx, chainId)
    },
    onProgress: (event) => console.log(event),
  },
)
If you construct Delegations standalone (without Portal), you must call setSignAndSendTransaction() after construction, or provide it per-call in options:
import { Delegations } from '@portal-hq/core'

const delegations = new Delegations({ api: myPortalApi })

// Option 1: Configure once via setter (recommended for standalone use)
delegations.setSignAndSendTransaction((tx, chainId) => myCustomSigner(tx, chainId))

// Now all calls use the configured signer
await delegations.approveAndSubmit({ chain, token, delegateAddress, amount })

// Option 2: Pass per-call (useful for testing or one-off custom signing)
await delegations.approveAndSubmit(
  { chain, token, delegateAddress, amount },
  { signAndSendTransaction: myCustomSigner },
)

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
import { PortalRequestMethod } from '@portal-hq/core'

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
  await portal.request(PortalRequestMethod.EthSendTransaction, [tx.transactions[0]], 'eip155:11155111')
}
Solana Approval
import { PortalRequestMethod } from '@portal-hq/core'

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
  await portal.request(PortalRequestMethod.SolSignAndSendTransaction, [{ transaction: tx.encodedTransactions[0] }], 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1')
}

Checking Delegation Status

Use getStatus to check current delegations and token balances for a specific delegate address. EVM Status Check
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
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
import { PortalRequestMethod } from '@portal-hq/core'

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

  console.log('Revoke response:', tx)
  await portal.request(PortalRequestMethod.EthSendTransaction, [tx.transactions[0]], 'eip155:11155111')
}
Solana Revoke
import { PortalRequestMethod } from '@portal-hq/core'

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

  console.log('Revoke response:', tx)
  await portal.request(PortalRequestMethod.SolSignAndSendTransaction, [{ transaction: tx.encodedTransactions[0] }], 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1')
}
Always revoke unused delegations after completing operations to minimize security risks.

Transferring as a Delegate

Use transferFrom to transfer tokens from another address that has delegated spending permission to you. EVM Transfer From
import { PortalRequestMethod } from '@portal-hq/core'

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)
  await portal.request(PortalRequestMethod.EthSendTransaction, [tx.transactions[0]], 'eip155:11155111')
}
Solana Transfer From
import { PortalRequestMethod } from '@portal-hq/core'

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)
  await portal.request(PortalRequestMethod.SolSignAndSendTransaction, [{ transaction: tx.encodedTransactions[0] }], 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1')
}
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.

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.

Next Steps