Skip to main content

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 contract
  • beacon_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 pair
  • token_b: The second token in the pair
  • amount_a: The amount of the first token to provide as initial liquidity
  • amount_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 pair
  • alkane_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 pair
  • amount_a_desired, amount_b_desired: Desired amounts to deposit
  • amount_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 pair
  • liquidity: LP tokens to burn
  • amount_a_min, amount_b_min: Minimum amounts to receive
  • deadline: 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 path
  • amount_in: The amount of input tokens to send
  • amount_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 path
  • amount_out: Exact amount of output tokens desired
  • amount_in_max: Maximum input amount willing to pay
  • deadline: 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 path
  • amount_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 pool
  • factory: 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 tokens
  • data: 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 amount
  • reserve_in: Reserve of input token
  • reserve_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 amount
  • reserve_in: Reserve of input token
  • reserve_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 amount
  • path: 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 amount
  • path: 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 pool
  • InsufficientInputAmount: Input amount too small
  • InsufficientOutputAmount: Output amount below minimum
  • InvalidPath: Invalid token path for swap
  • Expired: Transaction deadline exceeded
  • Unauthorized: Caller not authorized for operation

Pool-Specific Errors

  • PoolNotFound: Requested pool doesn't exist
  • InvalidTokenPair: Invalid token combination
  • ReservesEmpty: Pool has no liquidity
  • K: Constant product invariant violated

Factory-Specific Errors

  • PoolExists: Pool already exists for token pair
  • InvalidFactory: Invalid factory configuration
  • FeeTooHigh: 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.