Overview
API key trading allows you to execute trades on behalf of your main wallet using a separate API key wallet. This keeps your main wallet’s private key secure while enabling programmatic trading with bots and automated systems.
Security Your main wallet’s private key stays offline and secure. Only the API key is exposed to your trading bot.
Multiple Keys Create multiple API keys for different bots or trading strategies while keeping your main wallet secure.
Easy Rotation If an API key is compromised, simply revoke it and create a new one without affecting your main wallet.
Team Collaboration Share API keys with team members or services without sharing your main wallet credentials.
How API Keys Work
Key Concepts:
Main Wallet : Holds your funds and positions (private key stays secure)
API Key Wallet : Signs transactions on behalf of main wallet
Authenticator : On-chain permission that links API key to main wallet
Subaccount : The trading account where positions are held
Setting Up API Key Trading
Create API Key in Web Interface
Go to the LFG web interface to create your API key:
Visit https://lfg.land
Connect your main wallet
Navigate to Settings → API Keys
Click Create New API Key
Download and save the private key - it will only be shown once
Note the authenticator ID displayed
Save the private key immediately! You won’t be able to view it again. Store it securely - this key will be used by your trading bot.
API keys have trading permissions only - they can place and cancel orders but cannot withdraw funds from your account.
Configure Your Bot
Use the private key from the website in your trading bot: import { LocalWallet } from "@oraichain/lfg-client-js" ;
// Use the private key from the website
const apiKeyWallet = await LocalWallet . fromPrivateKey (
process . env . API_KEY_PRIVATE_KEY ! , // Private key from website
"lfg"
);
console . log ( "API Key Address:" , apiKeyWallet . address );
Store the private key in environment variables, never hardcode it in your source code.
Get Authenticator ID
The authenticator ID is shown in the web interface when you create the API key. You can also retrieve it programmatically: const client = await CompositeClient . connect ( Network . staging ());
const auths = await client . getAuthenticators ( MAIN_WALLET_ADDRESS );
// Find your API key's authenticator
const authenticatorId = auths . accountAuthenticators [ 0 ]. id ;
console . log ( "Authenticator ID:" , authenticatorId . toString ());
Set up your bot with the API key configuration:
import {
CompositeClient ,
Network ,
LocalWallet ,
SubaccountInfo ,
OrderSide ,
Order_TimeInForce ,
SelectedGasDenom ,
} from "@oraichain/lfg-client-js" ;
// Configuration
const API_KEY_PRIVATE_KEY = process . env . API_KEY_PRIVATE_KEY ! ;
const MAIN_WALLET_ADDRESS = process . env . MAIN_WALLET_ADDRESS ! ;
const AUTHENTICATOR_ID = process . env . AUTHENTICATOR_ID ! ;
// Create API key wallet
const apiKeyWallet = await LocalWallet . fromPrivateKey (
API_KEY_PRIVATE_KEY ,
"lfg"
);
// Connect to network
const network = Network . staging ();
const client = await CompositeClient . connect ( network );
client . setSelectedGasDenom ( SelectedGasDenom . USDC );
Placing Orders with API Keys
Complete Example
Here’s a complete example of placing an order using an API key:
async function placeOrderWithApiKey () {
try {
// 1. Create API key wallet (main wallet stays offline)
const apiKeyWallet = await LocalWallet . fromPrivateKey (
API_KEY_PRIVATE_KEY ,
"lfg"
);
// 2. Connect to network
const network = Network . staging ();
const client = await CompositeClient . connect ( network );
client . setSelectedGasDenom ( SelectedGasDenom . USDC );
// 3. Get authenticator ID for this API key
const auths = await client . getAuthenticators ( MAIN_WALLET_ADDRESS );
const authenticatorId = auths . accountAuthenticators [ 0 ]. id ;
console . log ( `Using authenticator ID: ${ authenticatorId . toString () } ` );
// 4. Create permissioned subaccount
const subaccount = SubaccountInfo . forPermissionedWallet (
apiKeyWallet , // API key signs the transaction
MAIN_WALLET_ADDRESS , // Trading for main wallet's account
0 , // Subaccount number
[ authenticatorId ] // Authenticator ID from registration
);
// 5. Place order
const currentBlock = await client . validatorClient . get . latestBlockHeight ();
const goodTilBlock = currentBlock + 20 ;
const clientId = Math . floor ( Math . random () * 100000000 );
const tx = await client . placeShortTermOrder (
subaccount ,
"ETH-USD" , // Market
OrderSide . BUY , // Buy order
3800 , // Price: $3800/ETH
0.1 , // Size: 0.1 ETH
clientId ,
goodTilBlock ,
Order_TimeInForce . TIME_IN_FORCE_UNSPECIFIED ,
false
);
console . log ( "✅ Order placed successfully!" );
console . log ( "Transaction hash:" , Buffer . from ( tx . hash ). toString ( "hex" ));
console . log ( "Client ID:" , clientId );
return tx ;
} catch ( error ) {
console . error ( "❌ Order failed:" , error );
throw error ;
}
}
// Execute
placeOrderWithApiKey ();
Key Differences from Regular Trading
The main difference when using API keys is creating the subaccount:
With API Key
Without API Key
// API key signs, main wallet owns
const subaccount = SubaccountInfo . forPermissionedWallet (
apiKeyWallet , // Signer
MAIN_WALLET_ADDRESS , // Owner
0 , // Subaccount number
[ authenticatorId ] // Permission proof
);
// Main wallet signs and owns
const subaccount = SubaccountInfo . forLocalWallet (
mainWallet , // Signer & Owner
0 // Subaccount number
);
Security Best Practices
Never hardcode API keys: // ❌ Bad - Hardcoded
const API_KEY = "0x1234..." ;
// ✅ Good - Environment variable
const API_KEY = process . env . API_KEY_PRIVATE_KEY ;
// ✅ Better - Secret management service
const API_KEY = await secretManager . getSecret ( "api-key" );
Use .env files (add to .gitignore):API_KEY_PRIVATE_KEY = 0x...
MAIN_WALLET_ADDRESS = lfg1...
AUTHENTICATOR_ID = 1
Regularly rotate your API keys:
Generate new API key
Register new key in web interface
Update bot configuration
Test with small orders
Revoke old key
Rotation Schedule:
High-value accounts: Monthly
Production bots: Quarterly
Development: After major changes
API keys have trading-only permissions by design:
Can do : Place and cancel orders
Cannot do : Withdraw funds from your account
Best practice : Create separate API keys for different bots or strategies
This built-in limitation minimizes damage if a key is compromised.
Regularly audit API key usage: async function auditAPIKeyActivity (
client : CompositeClient ,
walletAddress : string
) {
// Get recent orders
const orders = await client . indexerClient . account . getSubaccountOrders (
walletAddress ,
0
);
// Check for unusual activity
const recentOrders = orders . filter (
order => Date . parse ( order . createdAt ) > Date . now () - 86400000
);
if ( recentOrders . length > 1000 ) {
console . warn ( "⚠️ Unusual order volume detected!" );
}
return recentOrders ;
}
Environment Configuration
Production Setup
# Main wallet (public address only - private key stays secure)
MAIN_WALLET_ADDRESS = lfg1...
# API Key for trading
API_KEY_PRIVATE_KEY = 0x...
AUTHENTICATOR_ID = 1
# Network
NETWORK = mainnet
Development Setup
# Test wallet address
MAIN_WALLET_ADDRESS = lfg1...
# Test API key
API_KEY_PRIVATE_KEY = 0x...
AUTHENTICATOR_ID = 1
# Network
NETWORK = staging
API keys only have trading permissions. For withdrawals or other sensitive operations, you must use your main wallet directly.
Troubleshooting
Problem: Error finding authenticator for API keySolutions:
Verify API key is registered in web interface
Check authenticator ID is correct
Ensure API key wallet matches registered public key
Wait a few blocks after registration
// Debug: List all authenticators
const auths = await client . getAuthenticators ( MAIN_WALLET_ADDRESS );
console . log ( "Available authenticators:" ,
auths . accountAuthenticators . map ( a => a . id . toString ())
);
Problem: Transaction rejected due to insufficient permissionsSolutions:
Verify the API key is properly registered
Ensure authenticator is active (not revoked)
Check that you’re using the correct authenticator ID
Re-register API key if needed
API keys only have trading permissions. If you need to withdraw funds, use your main wallet directly.
Problem: Order placed in wrong subaccountSolution: Verify subaccount number in SubaccountInfo:// Correct subaccount
const subaccount = SubaccountInfo . forPermissionedWallet (
apiKeyWallet ,
MAIN_WALLET_ADDRESS ,
0 , // ← Check this matches your target subaccount
[ authenticatorId ]
);
Advanced: Finding the Correct Authenticator
If you have multiple API keys, you need to find the right authenticator:
function hasMatchingPubKey ( config : any , targetPubKey : string ) : boolean {
// Recursive search through authenticator config
if ( Array . isArray ( config )) {
return config . some (( item ) => hasMatchingPubKey ( item , targetPubKey ));
}
if ( typeof config === "object" && config !== null ) {
if (
config . type === "SIGNATURE_VERIFICATION" &&
config . config === targetPubKey
) {
return true ;
}
return Object . values ( config ). some (( value ) =>
hasMatchingPubKey ( value , targetPubKey )
);
}
return false ;
}
async function findAuthenticatorForAPIKey (
client : CompositeClient ,
mainWalletAddress : string ,
apiKeyWallet : LocalWallet
) : Promise < Long | null > {
const auths = await client . getAuthenticators ( mainWalletAddress );
const targetPubKey = apiKeyWallet . pubKey ! . value ;
for ( const auth of auths . accountAuthenticators ) {
try {
const configBytes = Buffer . from ( auth . config as any , "base64" );
const configString = configBytes . toString ( "utf-8" );
const config = JSON . parse ( configString );
if ( hasMatchingPubKey ( config , targetPubKey )) {
return auth . id ;
}
} catch ( error ) {
continue ;
}
}
return null ;
}
// Usage
const authenticatorId = await findAuthenticatorForAPIKey (
client ,
MAIN_WALLET_ADDRESS ,
apiKeyWallet
);
if ( ! authenticatorId ) {
throw new Error ( "No matching authenticator found for this API key" );
}
Next Steps
Always test API key trading on staging network before using on mainnet with real funds!