Skip to content

Authorizing an Agent

A one-time onchain transaction from the trader's main wallet that grants a signing key permission to submit trade intents on their behalf.

Skip this page entirely if you are self-signing — when signer == trader, the contract accepts intents without any authorization.

All functions are on the Diamond (0xea1b8E4aB7f14F7dCA68c5B214303B13078FC5ec).

Which route do you need

You are buildingHow the agent gets authorized
A frontend or walletYour app calls authorizeAgent during onboarding — below. Users should never leave your product to set this up.
A bot trading its own walletNothing to do. Self-sign instead.
A bot trading someone else's walletThat trader authorizes your bot's address once. They can do it from the Agent Wallet Manager without you shipping any UI.

Authorize from the app

The LeverUp app has a UI for this, at app.leverup.xyz/agent-wallets. Use it to onboard a counterparty who has no interface of their own, or to inspect and revoke authorizations while debugging your integration.

  1. Open the page and connect the trader's main wallet.
  2. + Add Agent.
  3. Paste the agent address — the public address of the signing key you generated. The private key stays with you; the app never asks for it.
  4. Set a name (unique per trader) and permissionsALL, or tick individual actions.
  5. Authorize, and confirm the one transaction.

The agent then appears as ACTIVE, with Update for name and permissions and Revoke to remove it. Every button maps to a contract call documented on this page, so an authorization made here and one made from your own code are indistinguishable onchain.

Authorize

solidity
function authorizeAgent(address agent, bytes32 name, uint256 permissions) external;
ParameterNotes
agentThe signing key's address. Must not be zero or the trader's own address.
nameA bytes32 label, unique per trader. Must not be zero.
permissionsBitmask — bit n grants action n. type(uint256).max grants everything.
ts
import { stringToHex, maxUint256 } from 'viem'
import { publicClient, walletClient, DIAMOND } from './config'

const AUTHORIZE_ABI = [{
  type: 'function',
  name: 'authorizeAgent',
  stateMutability: 'nonpayable',
  inputs: [
    { name: 'agent', type: 'address' },
    { name: 'name', type: 'bytes32' },
    { name: 'permissions', type: 'uint256' },
  ],
  outputs: [],
}] as const

const hash = await walletClient.writeContract({
  address: DIAMOND,
  abi: AUTHORIZE_ABI,
  functionName: 'authorizeAgent',
  args: [
    agentAddress,
    stringToHex('LeverUp 1CT', { size: 32 }),
    maxUint256,
  ],
})

await publicClient.waitForTransactionReceipt({ hash })

Calling authorizeAgent again for an address that is already authorized updates it — changing the name and/or permissions — rather than creating a duplicate.

A trader can have several agents. name must be unique within a trader's set; reusing one reverts with OneClickV2: name taken.

Choosing permissions

permissions is a uint256 where bit number = action value:

ts
import { maxUint256 } from 'viem'

const openOnly  = 1n << 0n                    // MARKET_OPEN
const tradeOnly = (1n << 0n) | (1n << 1n)     // MARKET_OPEN + MARKET_CLOSE
const everything = maxUint256                 // wildcard

The full bit map is in Actions Reference.

maxUint256 is a wildcard, not a bit pattern

The contract special-cases type(uint256).max and skips per-bit checking entirely. Any other value is checked bit by bit. This means "everything except one action" must be written as an explicit mask, and that mask does not behave as a wildcard:

ts
const noWithdraw = maxUint256 & ~(1n << 6n)   // every action except REMOVE_MARGIN

It also means an explicit mask is a snapshot. Action numbers are appended as the protocol gains operations, and an agent authorized before an action existed does not hold its bit — intents for that action come back skipReason = INVALID until the trader calls updateAgentPermissions. A wildcard agent picks up new actions with no action required.

Query

solidity
function isAgentAuthorized(address trader, address agent) external view returns (bool);
function hasPermission(address trader, address agent, uint8 action) external view returns (bool);
function getAgentAuth(address trader, address agent) external view returns (AgentAuth memory);
function getAgentByName(address trader, bytes32 name) external view returns (address);
function getAgentCount(address trader) external view returns (uint256);
function getAgents(address trader, uint256 offset, uint256 limit) external view returns (AgentAuth[] memory);
function getLastNonce(address trader, address signer) external view returns (uint64);
solidity
struct AgentAuth {
    address agent;
    bytes32 name;
    uint256 permissions;
    uint32 authorizedAt;
}

To check whether 1CT is enabled for a user, read getAgentAuth(user, agentAddress) and treat a non-zero agent field as authorized:

ts
const AGENT_AUTH_ABI = [{
  type: 'function',
  name: 'getAgentAuth',
  stateMutability: 'view',
  inputs: [{ name: 'trader', type: 'address' }, { name: 'agent', type: 'address' }],
  outputs: [{
    type: 'tuple', name: '', components: [
      { name: 'agent', type: 'address' },
      { name: 'name', type: 'bytes32' },
      { name: 'permissions', type: 'uint256' },
      { name: 'authorizedAt', type: 'uint32' },
    ],
  }],
}] as const

const auth = await publicClient.readContract({
  address: DIAMOND,
  abi: AGENT_AUTH_ABI,
  functionName: 'getAgentAuth',
  args: [user, agentAddress],
})

const isEnabled = auth.agent !== '0x0000000000000000000000000000000000000000'

getAgents is paginated — use getAgentCount to size the query.

Update and revoke

solidity
function updateAgentPermissions(address agent, uint256 permissions) external;
function updateAgentName(address agent, bytes32 newName) external;
function revokeAgent(address agent) external;
function revokeAgentByName(bytes32 name) external;
function revokeAllAgents() external;
ts
const REVOKE_ABI = [{
  type: 'function',
  name: 'revokeAgent',
  stateMutability: 'nonpayable',
  inputs: [{ name: 'agent', type: 'address' }],
  outputs: [],
}] as const

const hash = await walletClient.writeContract({
  address: DIAMOND,
  abi: REVOKE_ABI,
  functionName: 'revokeAgent',
  args: [agentAddress],
})
await publicClient.waitForTransactionReceipt({ hash })

WARNING

When resetting a user's 1CT setup, revoke onchain and wait for the receipt before deleting the local key material. The reverse order strands a live authorization on a key nobody holds.

Token approvals

Authorization grants permission to trade, not to move tokens. The trader must still approve the Diamond for:

  1. The collateral token they intend to trade with (USDC, WMON, LVUSD, LVMON).
  2. The execution fee token — see Execution Fee.

Without those approvals, intents are accepted by the endpoint and then skipped onchain with skipReason = "FEE" or a collateral transfer failure.

Next: Actions Reference.

Trading perpetuals involves risk. Nothing here is financial advice.