One-Click Trading API Documentation
This documentation describes how to interact with the One-Click Trading (1CT) service. This service allows for low-latency trade execution by relaying pre-signed EIP-712 messages to the blockchain.
Base URL: https://oneclick-01-keeper.leverup.xyz
Overview
The One-Click Trading flow consists of:
- Delegation: The user delegates trading authority to a session wallet (1CT Wallet) on-chain via the
OneClickAgentcontract. - Signing: The client signs trade instructions using the session wallet's private key (EIP-712).
- Relaying: The client submits the signed instruction and trade data to this API.
- Execution: The server validates the signature and submits the transaction to the blockchain.
Prerequisites
- Wallet Connection: The user must have their main wallet connected.
- Allowlist: Currently, the user must be on the allowlist to access this feature.
- ERC20 Only: One-Click Trading only supports ERC20 tokens as collateral.
- If you wish to use native MON, you must first wrap it into WMON.
- Token Approval: The user must approve the LeverUp contract to spend their ERC20 collateral tokens (e.g., USDC, WMON, LVMON) before attempting to open a position.
Authentication & Security
All trade requests must be signed using EIP-712. The signer address must be authorized as a "Trader Agent" for the user on the OneClickAgent contract.
EIP-712 Domain Separator
All signatures use the following domain separator:
{
"chainId": <Current_Chain_ID>,
"verifyingContract": "<OneClickAgent_Contract_Address>"
}- chainId: The integer ID of the network.
- Monad Mainnet:
143 - Monad Testnet:
10143
- Monad Mainnet:
- verifyingContract: The address of the
OneClickAgentcontract on that chain.
Contract Addresses
For contract addresses (OneClickAgent, LeverUp, Tokens), please refer to the Contract Addresses page.
Pyth Price Feeds & Update Data
When opening a position, you must provide Pyth Update Data (pythUpdateData) in the request body. This data proves the current price on-chain.
1. Get Price IDs
You need the Pyth Price Feed ID for:
- The Trading Pair (e.g., BTC/USD).
- The Collateral Token (if applicable).
You can find the full list of supported pairs and their Pyth IDs in the Supported Pairs reference page.
2. Fetch Update Data (Hermes API)
Call the Pyth Hermes API (e.g., https://hermes.pyth.network) to get the signed VAA (Verification Across Arbitrary Chain) data.
Request:GET https://hermes.pyth.network/v2/updates/price/latest?ids[]=<ID_1>&ids[]=<ID_2>&...
Example (BTC/USD & USDC Collateral):
curl "https://hermes.pyth.network/v2/updates/price/latest?ids[]=0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43&ids[]=0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a"Response: The API returns a JSON object containing a binary field. This binary string (hex) is what you pass as pythUpdateData in the send-open-position request.
{
"binary": {
"data": ["<HEX_STRING_1>"]
},
...
}Endpoints
1. Open Position
Submits a request to open a new market position.
Endpoint: POST /v1/trading/send-open-position
Query Parameters:
blockchain(required): The target blockchain (e.g.,MONAD).
Request Body (application/json):
{
"openData": {
"pairBase": "0x...", // Pair base address (string)
"isLong": true, // Direction (boolean)
"tokenIn": "0x...", // token in address (string)
"lvToken": "0x...", // collateral token address(LVUSD/LVMON) (string)
"amountIn": "1000...", // Amount in(tokenIn) (wei, as string)
"qty": "5000...", // Position size (wei, as string)
"price": "2000...", // Execution price (wei, as string)
"stopLoss": "0", // Stop loss price (wei, as string)
"takeProfit": "0", // Take profit price (wei, as string)
"broker": "0" // Broker ID (string)
},
"trader": "0x...", // The actual user's address (string)
"salt": "0x...", // Unique 32-byte salt (string)
"deadline": 1715000000, // Unix timestamp expiry (integer)
"signature": "0x...", // EIP-712 Signature (string)
"pythUpdateData": ["0x..."] // Array of Pyth price update data (array of strings)
}EIP-712 Type Definition (OneClickOpenPosition):
const types = {
OneClickOpenPosition: [
{ type: 'address', name: 'pairBase' },
{ type: 'bool', name: 'isLong' },
{ type: 'address', name: 'tokenIn' },
{ type: 'address', name: 'lvToken' },
{ type: 'uint96', name: 'amountIn' },
{ type: 'uint128', name: 'qty' },
{ type: 'uint128', name: 'price' },
{ type: 'uint128', name: 'stopLoss' },
{ type: 'uint128', name: 'takeProfit' },
{ type: 'uint24', name: 'broker' },
{ type: 'address', name: 'trader' },
{ type: 'bytes32', name: 'salt' },
{ type: 'uint128', name: 'deadline' }
]
};2. Close Position
Submits a request to close an existing position.
Endpoint: POST /v1/trading/send-close-position
Query Parameters:
blockchain(required): The target blockchain (e.g.,MONAD).
Request Body (application/json):
{
"positionHash": "0x...", // Unique identifier of the position (string)
"deadline": 1715000000, // Unix timestamp expiry (integer)
"signature": "0x..." // EIP-712 Signature (string)
}EIP-712 Type Definition (OneClickClosePosition):
const types = {
OneClickClosePosition: [
{ type: 'bytes32', name: 'positionHash' },
{ type: 'uint128', name: 'deadline' }
]
};3. Check Execution Status
Polls the status of a submitted trade using the input hash returned from the open/close endpoints.
Endpoint: GET /v1/trading/{input_hash}/status
Path Parameters:
input_hash: The hash returned by thesend-open-positionorsend-close-positionendpoint.
Response:
{
"executed": true, // Whether the trade has been processed
"success": true, // Whether the trade was successful
"txnHash": "0x...", // The transaction hash on-chain
"reason": null // Error reason if failed (or null)
}Signing Examples
Generating a Signature (TypeScript / Viem)
Below are complete examples for generating EIP-712 signatures for both Opening and Closing positions using viem.
1. Sign OneClick Open Position
import { createWalletClient, http, keccak256, stringToHex, toHex } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { monad } from 'viem/chains';
// 1. Setup Client & Account (The Session Key)
// Ideally, this private key is generated locally in the browser and stored securely.
const account = privateKeyToAccount('0xYOUR_SESSION_PRIVATE_KEY');
const client = createWalletClient({
account,
chain: monad, // Use monadTestnet for Testnet (Chain ID 10143)
transport: http()
});
// 2. Define Domain
const domain = {
name: 'OneClickAgent', // Verify if your contract uses a name/version in domain separator
version: '1',
chainId: 143, // Monad Mainnet ID (Use 10143 for Testnet)
verifyingContract: '0x1567FB09f13653f63A047B4F11Fb11fD1D7567d0' // Mainnet OneClickAgent
} as const;
// 3. Define Types
const types = {
OneClickOpenDataInput: [
{ name: 'openData', type: 'OpenDataInput' },
{ name: 'trader', type: 'address' },
{ name: 'salt', type: 'bytes32' },
{ name: 'deadline', type: 'uint128' }
],
OpenDataInput: [
{ name: 'pairBase', type: 'address' },
{ name: 'isLong', type: 'bool' },
{ name: 'tokenIn', type: 'address' },
{ name: 'lvToken', type: 'address' },
{ name: 'amountIn', type: 'uint96' },
{ name: 'qty', type: 'uint128' },
{ name: 'price', type: 'uint128' },
{ name: 'stopLoss', type: 'uint128' },
{ name: 'takeProfit', type: 'uint128' },
{ name: 'broker', type: 'uint24' }
]
} as const;
// 4. Prepare Message
const salt = keccak256(stringToHex(`leverup-1ct-open:${Date.now()}`));
const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); // 5 minutes expiry
const message = {
openData: {
pairBase: '0x...', // Trading Pair Base Address
isLong: true,
tokenIn: '0x...', // Collateral Token Address
lvToken: '0x...', // Corresponding LV Token
amountIn: 1000000000n, // 1000 USDC (6 decimals)
qty: 50000000000n, // Position Size in Base Asset (10 decimals)
price: 60000000000000000000000n, // Execution Price (18 decimals)
stopLoss: 0n,
takeProfit: 0n,
broker: 0
},
trader: '0xUserRealAddress...', // The user who authorized the session key
salt: salt,
deadline: deadline
};
// 5. Sign Data
const signature = await client.signTypedData({
domain,
primaryType: 'OneClickOpenDataInput',
types,
message
});
console.log('Open Position Signature:', signature);2. Sign OneClick Close Position
// ... (Client setup same as above) ...
// 3. Define Types for Close
const closeTypes = {
OneClickCloseDataInput: [
{ name: 'positionHash', type: 'bytes32' },
{ name: 'deadline', type: 'uint128' }
]
} as const;
// 4. Prepare Message
const closeMessage = {
positionHash: '0x...', // The hash of the position you want to close
deadline: BigInt(Math.floor(Date.now() / 1000) + 300) // 5 minutes expiry
};
// 5. Sign Data
const closeSignature = await client.signTypedData({
domain,
primaryType: 'OneClickCloseDataInput',
types: closeTypes,
message: closeMessage
});
console.log('Close Position Signature:', closeSignature);Note on Domain Separator
Ensure you use the correct chainId and verifyingContract address for the network you are targeting (Mainnet vs Testnet). The name and version fields in the domain separator should match what is defined in the OneClickAgent smart contract. If the contract does not check for name/version, you can omit them, but chainId and verifyingContract are mandatory.