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 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:
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 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 overrideoptions.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.
TypeScript request and options shapes (ApproveDelegationRequest, RevokeDelegationRequest, TransferFromRequest, DelegationSubmitOptions, DelegationSubmitProgress) are listed in Delegations — Web SDK reference. Types are exported from @portal-hq/web.

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 when using the default client.
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 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

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, so you don’t need to provide it. For custom signing logic (for example a different submission path), pass signAndSendTransaction in the options:
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
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
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
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
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
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
}
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
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
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
}
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