API Reference
Complete reference for all Oyl AMM smart contract functions, including parameters, return values, and usage examples.
Factory Contract API
Pool Management
InitFactory
Initializes the factory contract with required parameters.
#[opcode(0)]
InitFactory {
pool_factory_id: u128,
beacon_id: AlkaneId,
}
Parameters:
pool_factory_id
: The ID of the pool factory contractbeacon_id
: The ID of the beacon contract
Access: Admin only
CreateNewPool
Creates a new trading pair pool.
#[opcode(1)]
CreateNewPool {
token_a: AlkaneId,
token_b: AlkaneId,
amount_a: u128,
amount_b: u128,
}
Parameters:
token_a
: The first token in the pairtoken_b
: The second token in the pairamount_a
: The amount of the first token to provide as initial liquidityamount_b
: The amount of the second token to provide as initial liquidity
Returns: Pool ID of the newly created pool
Usage:
let response = factory.call(AMMFactoryMessage::CreateNewPool)?;
let pool_id = response.into();
FindExistingPoolId
Finds the pool ID for a given token pair.
#[opcode(2)]
FindExistingPoolId {
alkane_a: AlkaneId, // First token
alkane_b: AlkaneId, // Second token
}
Parameters:
alkane_a
: First token in the pairalkane_b
: Second token in the pair
Returns: Pool ID if exists, creates new pool if not
Usage:
let pool_id = factory.call(AMMFactoryMessage::FindExistingPoolId {
alkane_a: token_a_id,
alkane_b: token_b_id,
})?;
GetAllPools
Returns a list of all existing pools.
#[opcode(3)]
#[returns(Vec<u8>)]
GetAllPools
Returns: Serialized list of all pool IDs Example script to decode the output
function parseHexData(hex) {
if (hex.startsWith("0x")) {
hex = hex.slice(2);
}
const buf = Buffer.from(hex, "hex");
// 获取 pool 数量(前 16 字节,小端,取最低字节)
const poolCount = buf.readUIntLE(0, 1);
const pools = [];
let offset = 16;
for (let i = 0; i < poolCount; i++) {
// 读取 block(16 字节,小端)
const blockLE = buf.slice(offset, offset + 16);
const blockLEBigInt = bufferToBigIntLE(blockLE);
offset += 16;
// 读取 tx(16 字节,小端)
const txLE = buf.slice(offset, offset + 16);
const txBigInt = bufferToBigIntLE(txLE);
offset += 16;
pools.push({
block: blockLEBigInt.toString(),
tx: txBigInt.toString(),
});
}
return {
poolCount,
pools,
};
}
function bufferToBigIntLE(buffer) {
let result = 0n;
for (let i = buffer.length - 1; i >= 0; i--) {
result = (result << 8n) + BigInt(buffer[i]);
}
return result;
}
GetNumPools
Returns the total number of pools.
#[opcode(4)]
#[returns(Vec<u8>)]
GetNumPools
Returns: Total number of pools as bytes
Configuration
SetPoolFactoryId
Updates the pool factory ID (admin only).
#[opcode(7)]
SetPoolFactoryId {
pool_factory_id: u128
}
Parameters:
pool_factory_id
: New pool factory ID
Access: Admin only
Fee Management
CollectFees
Collects protocol fees from a specific pool.
#[opcode(10)]
CollectFees {
pool_id: AlkaneId
}
Parameters:
pool_id
: Pool to collect fees from
Access: Admin only
Liquidity Operations
AddLiquidity
Adds liquidity to a trading pair.
#[opcode(11)]
AddLiquidity {
token_a: AlkaneId, // First token
token_b: AlkaneId, // Second token
amount_a_desired: u128, // Desired amount of token A
amount_b_desired: u128, // Desired amount of token B
amount_a_min: u128, // Minimum amount of token A
amount_b_min: u128, // Minimum amount of token B
deadline: u128, // Transaction deadline in block height
}
Parameters:
token_a
,token_b
: Token pairamount_a_desired
,amount_b_desired
: Desired amounts to depositamount_a_min
,amount_b_min
: Minimum amounts (slippage protection)deadline
: Transaction must execute before this block height
Returns: Amounts deposited and LP tokens minted
Burn
Removes liquidity from a trading pair.
#[opcode(12)]
Burn {
token_a: AlkaneId, // First token
token_b: AlkaneId, // Second token
liquidity: u128, // Amount of LP tokens to burn
amount_a_min: u128, // Minimum amount of token A to receive
amount_b_min: u128, // Minimum amount of token B to receive
deadline: u128, // Transaction deadline in block height
}
Parameters:
token_a
,token_b
: Token pairliquidity
: LP tokens to burnamount_a_min
,amount_b_min
: Minimum amounts to receivedeadline
: Transaction deadline in block height
Returns: Actual amounts received
Trading Operations
SwapExactTokensForTokens
Swaps an exact amount of input tokens for output tokens.
#[opcode(13)]
SwapExactTokensForTokens {
path: Vec<AlkaneId>, // Token path for the swap
amount_in: u128, // The amount of input tokens to send
amount_out_min: u128, // Minimum output amount
deadline: u128, // Transaction deadline in block height
}
Parameters:
path
: Array of token IDs defining the swap pathamount_in
: The amount of input tokens to sendamount_out_min
: Minimum output amount (slippage protection)deadline
: Transaction deadline in block height
Returns: Array of amounts for each step in the path
SwapTokensForExactTokens
Swaps tokens to get an exact amount of output tokens.
#[opcode(14)]
SwapTokensForExactTokens {
path: Vec<AlkaneId>, // Token path for the swap
amount_out: u128, // Exact output amount desired
amount_in_max: u128, // Maximum input amount
deadline: u128, // Transaction deadline in block height
}
Parameters:
path
: Array of token IDs defining the swap pathamount_out
: Exact amount of output tokens desiredamount_in_max
: Maximum input amount willing to paydeadline
: Transaction deadline in block height
SwapExactTokensForTokensImplicit
Swaps an exact amount of input tokens for output tokens, where the input amount is implicitly determined by the value of the alkanes sent with the call.
#[opcode(29)]
SwapExactTokensForTokensImplicit {
path: Vec<AlkaneId>,
amount_out_min: u128,
deadline: u128, // Transaction deadline in block height
}
Parameters:
path
: Array of token IDs defining the swap pathamount_out_min
: Minimum output amount (slippage protection)deadline
: Transaction deadline in block height
Returns: Array of amounts for each step in the path
Pool Contract API
Initialization
InitPool
Initializes a new pool with token pair information.
#[opcode(0)]
InitPool {
alkane_a: AlkaneId, // First token in the pair
alkane_b: AlkaneId, // Second token in the pair
factory: AlkaneId, // Factory contract address
}
Parameters:
alkane_a
,alkane_b
: Token pair for this poolfactory
: Factory contract that created this pool
Access: Factory contract only
Liquidity Management
AddLiquidity
Adds liquidity to the pool.
#[opcode(1)]
AddLiquidity
Note: This is typically called through the factory contract, not directly.
Burn
Removes liquidity from the pool.
#[opcode(2)]
Burn
Note: This is typically called through the factory contract, not directly.
Trading
Swap
Executes a token swap (low-level function).
#[opcode(3)]
Swap {
amount_0_out: u128, // Amount of token0 to send out
amount_1_out: u128, // Amount of token1 to send out
to: AlkaneId, // Recipient address
data: Vec<u128>, // Additional data (for flash swaps)
}
Parameters:
amount_0_out
,amount_1_out
: Amounts to send (one should be 0)to
: Recipient of the output tokensdata
: Callback data for flash swaps (empty for regular swaps)
Warning: This is a low-level function. Use factory swap functions instead.
Information Queries
GetReserves
Returns the current token reserves.
#[opcode(97)]
#[returns(u128, u128)]
GetReserves
Returns: (reserve0, reserve1)
- current reserves of both tokens
Usage:
let (reserve_a, reserve_b) = pool.call(AMMPoolMessage::GetReserves)?;
GetPriceCumulativeLast
Returns cumulative prices for oracle functionality.
#[opcode(98)]
#[returns(u128, u128)]
GetPriceCumulativeLast
Returns: (price0_cumulative, price1_cumulative)
- cumulative prices
Returns cumulative prices used for TWAP calculations. Note that the price is returned with 128 bits for the integer and 128 bits for the decimal.
use ruint::Uint;
pub type U256 = Uint<256, 4>;
// Create a storage wrapper for U256 to implement ByteView
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct StorableU256(pub U256);
impl ByteView for StorableU256 {
fn from_bytes(v: Vec<u8>) -> Self {
assert!(v.len() == 32, "Expected a byte vector of length 32.");
// Convert bytes to U256 using from_le_bytes
let mut bytes_array = [0u8; 32];
bytes_array.copy_from_slice(&v);
StorableU256(U256::from_le_bytes(bytes_array))
}
fn to_bytes(&self) -> Vec<u8> {
self.0.to_le_bytes::<32>().to_vec()
}
fn maximum() -> Self {
StorableU256(U256::MAX)
}
fn zero() -> Self {
StorableU256(U256::ZERO)
}
}
impl From<U256> for StorableU256 {
fn from(value: U256) -> Self {
StorableU256(value)
}
}
impl From<StorableU256> for U256 {
fn from(value: StorableU256) -> Self {
value.0
}
}
// assuming data is the response from the rpc from calling GetPriceCumulativeLast
let p0: U256 = StorableU256::from_bytes(data[0..32].to_vec()).into();
let p1: U256 = StorableU256::from_bytes(data[32..64].to_vec()).into();
println!(
"{:?}.{:?}",
p0 >> U256::from(PRECISION), // integer portion
p0 & U256::from(u128::MAX) // decimal portion
);
println!(
"{:?}.{:?}",
p1 >> U256::from(PRECISION), // integer portion
p1 & U256::from(u128::MAX) // decimal portion
);
GetName
Returns the pool's name.
#[opcode(99)]
#[returns(String)]
GetName
Returns: Pool name as string
PoolDetails
Returns comprehensive pool information.
#[opcode(999)]
#[returns(Vec<u8>)]
PoolDetails
Returns: Serialized pool information including tokens, reserves, and metadata
Example script to decode the output
#[derive(Default)]
pub struct PoolInfo {
pub token_a: AlkaneId,
pub token_b: AlkaneId,
pub reserve_a: u128,
pub reserve_b: u128,
pub total_supply: u128,
pub pool_name: String,
}
impl PoolInfo {
pub fn try_to_vec(&self) -> Vec<u8> {
let mut bytes = Vec::new();
// token_a: 32 bytes (16 bytes block + 16 bytes tx)
bytes.extend_from_slice(&self.token_a.block.to_le_bytes());
bytes.extend_from_slice(&self.token_a.tx.to_le_bytes());
// token_b: 32 bytes (16 bytes block + 16 bytes tx)
bytes.extend_from_slice(&self.token_b.block.to_le_bytes());
bytes.extend_from_slice(&self.token_b.tx.to_le_bytes());
// reserves and total_supply: 16 bytes each
bytes.extend_from_slice(&self.reserve_a.to_le_bytes());
bytes.extend_from_slice(&self.reserve_b.to_le_bytes());
bytes.extend_from_slice(&self.total_supply.to_le_bytes());
// Add the pool name
let name_bytes = self.pool_name.as_bytes();
// Add the length of the name as a u32
bytes.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
// Add the name bytes
bytes.extend_from_slice(name_bytes);
bytes
}
pub fn from_vec(bytes: &[u8]) -> Result<Self> {
// Minimum size: 32 (token_a) + 32 (token_b) + 16 (reserve_a) + 16 (reserve_b) + 16 (total_supply) + 4 (name_length) = 116 bytes
if bytes.len() < 116 {
return Err(anyhow!("Invalid bytes length for PoolInfo"));
}
let mut offset = 0;
// Read token_a (32 bytes: 16 bytes block + 16 bytes tx)
let token_a_block = u128::from_le_bytes(bytes[offset..offset + 16].try_into()?);
offset += 16;
let token_a_tx = u128::from_le_bytes(bytes[offset..offset + 16].try_into()?);
offset += 16;
// Read token_b (32 bytes: 16 bytes block + 16 bytes tx)
let token_b_block = u128::from_le_bytes(bytes[offset..offset + 16].try_into()?);
offset += 16;
let token_b_tx = u128::from_le_bytes(bytes[offset..offset + 16].try_into()?);
offset += 16;
// Read reserve_a (16 bytes for u128)
let reserve_a = u128::from_le_bytes(bytes[offset..offset + 16].try_into()?);
offset += 16;
// Read reserve_b (16 bytes for u128)
let reserve_b = u128::from_le_bytes(bytes[offset..offset + 16].try_into()?);
offset += 16;
// Read total_supply (16 bytes for u128)
let total_supply = u128::from_le_bytes(bytes[offset..offset + 16].try_into()?);
offset += 16;
// Read pool_name length (4 bytes for u32)
let name_length = u32::from_le_bytes(bytes[offset..offset + 4].try_into()?) as usize;
offset += 4;
// Check if we have enough bytes for the name
if bytes.len() < offset + name_length {
return Err(anyhow!("Invalid bytes length for pool_name"));
}
// Read pool_name
let pool_name = String::from_utf8(bytes[offset..offset + name_length].to_vec())?;
Ok(PoolInfo {
token_a: AlkaneId {
block: token_a_block,
tx: token_a_tx,
},
token_b: AlkaneId {
block: token_b_block,
tx: token_b_tx,
},
reserve_a,
reserve_b,
total_supply,
pool_name,
})
}
}
fn _get_pool_info(&self, pool: AlkaneId) -> Result<PoolInfo> {
let cellpack = Cellpack {
target: pool,
inputs: vec![999],
};
let response = self.call(&cellpack, &AlkaneTransferParcel(vec![]), self.fuel())?;
Ok(PoolInfo::from_vec(&response.data)?)
}
Utility Functions
CollectFees
Collects accumulated fees from the pool.
#[opcode(10)]
CollectFees {}
Access: Admin only
ForwardIncoming
Handles incoming token transfers.
#[opcode(50)]
ForwardIncoming
Note: Internal function for token transfer handling.
Library Functions
Swap Calculations
get_amount_out
Calculates output amount for a given input.
fn get_amount_out(
amount_in: u128,
reserve_in: u128,
reserve_out: u128
) -> u128
Parameters:
amount_in
: Input token amountreserve_in
: Reserve of input tokenreserve_out
: Reserve of output token
Returns: Output amount after fees
get_amount_in
Calculates required input for a desired output.
fn get_amount_in(
amount_out: u128,
reserve_in: u128,
reserve_out: u128
) -> u128
Parameters:
amount_out
: Desired output amountreserve_in
: Reserve of input tokenreserve_out
: Reserve of output token
Returns: Required input amount
get_amounts_out
Calculates outputs for a multi-hop swap path.
fn get_amounts_out(
amount_in: u128,
path: Vec<AlkaneId>
) -> Vec<u128>
Parameters:
amount_in
: Initial input amountpath
: Array of token IDs for the swap path
Returns: Array of amounts for each step in the path
get_amounts_in
Calculates inputs for a multi-hop swap path.
fn get_amounts_in(
amount_out: u128,
path: Vec<AlkaneId>
) -> Vec<u128>
Parameters:
amount_out
: Final desired output amountpath
: Array of token IDs for the swap path
Returns: Array of amounts for each step in the path
Error Codes
Common Errors
InsufficientLiquidity
: Not enough liquidity in poolInsufficientInputAmount
: Input amount too smallInsufficientOutputAmount
: Output amount below minimumInvalidPath
: Invalid token path for swapExpired
: Transaction deadline exceededUnauthorized
: Caller not authorized for operation
Pool-Specific Errors
PoolNotFound
: Requested pool doesn't existInvalidTokenPair
: Invalid token combinationReservesEmpty
: Pool has no liquidityK
: Constant product invariant violated
Factory-Specific Errors
PoolExists
: Pool already exists for token pairInvalidFactory
: Invalid factory configurationFeeTooHigh
: Fee exceeds maximum allowed
Usage Examples
Basic Swap
// Swap 100 Token A for Token B with 1% slippage tolerance
let path = vec![token_a_id, token_b_id];
let min_output = expected_output * 99 / 100; // 1% slippage
let result = factory.call(AMMFactoryMessage::SwapExactTokensForTokens {
path,
amount_out_min: min_output,
deadline: current_time + 300, // 5 minutes
})?;
Add Liquidity
// Add liquidity with 0.5% slippage tolerance
let min_a = amount_a_desired * 995 / 1000;
let min_b = amount_b_desired * 995 / 1000;
let result = factory.call(AMMFactoryMessage::AddLiquidity {
token_a: token_a_id,
token_b: token_b_id,
amount_a_desired,
amount_b_desired,
amount_a_min: min_a,
amount_b_min: min_b,
deadline: current_time + 600, // 10 minutes
})?;
Query Pool Information
// Get current reserves
let (reserve_a, reserve_b) = pool.call(AMMPoolMessage::GetReserves)?;
// Calculate current price
let price = reserve_b * PRECISION / reserve_a;
// Get pool details
let details = pool.call(AMMPoolMessage::PoolDetails)?;
This API reference provides complete documentation for integrating with the Oyl AMM protocol.