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 building | How the agent gets authorized |
|---|---|
| A frontend or wallet | Your app calls authorizeAgent during onboarding — below. Users should never leave your product to set this up. |
| A bot trading its own wallet | Nothing to do. Self-sign instead. |
| A bot trading someone else's wallet | That 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.
- Open the page and connect the trader's main wallet.
- + Add Agent.
- 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.
- Set a name (unique per trader) and permissions —
ALL, or tick individual actions. - 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
function authorizeAgent(address agent, bytes32 name, uint256 permissions) external;| Parameter | Notes |
|---|---|
agent | The signing key's address. Must not be zero or the trader's own address. |
name | A bytes32 label, unique per trader. Must not be zero. |
permissions | Bitmask — bit n grants action n. type(uint256).max grants everything. |
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:
import { maxUint256 } from 'viem'
const openOnly = 1n << 0n // MARKET_OPEN
const tradeOnly = (1n << 0n) | (1n << 1n) // MARKET_OPEN + MARKET_CLOSE
const everything = maxUint256 // wildcardThe 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:
const noWithdraw = maxUint256 & ~(1n << 6n) // every action except REMOVE_MARGINIt 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
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);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:
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
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;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:
- The collateral token they intend to trade with (USDC, WMON, LVUSD, LVMON).
- 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.