Technical Deep Dive
ULayer's StableSwap algorithm combines constant sum (x + y = k) and constant product (x * y = k) formulas:
Where:
| A Value | Curve Behavior | Slippage | Suitable For |
|---|---|---|---|
| 1-10 | Close to constant product | High | Volatile stablecoins |
| 50-100 | Medium curvature | Medium | Standard stablecoins |
| 200-500 | Close to constant sum | Low | Highly stable stablecoins |
| 1000+ | Almost linear | Very low | Perfectly pegged stablecoins |
ULayer implements a dynamic amplification coefficient that automatically adjusts based on market conditions:
func calculateDynamicA(priceVolatility, deviationFromPeg) {
// Base amplification coefficient
baseA := 200
// Volatility penalty - reduce A when volatility is high
volatilityFactor := max(0.1, 1 - priceVolatility * 10)
// Peg deviation penalty - reduce A when stablecoins deviate from peg
deviationFactor := max(0.1, 1 - deviationFromPeg * 20)
// Calculate final amplification coefficient
dynamicA := baseA * volatilityFactor * deviationFactor
// Ensure A is within reasonable range (10 to 1000)
return min(max(dynamicA, 10), 1000)
}
Multiple stablecoins pegged to the same currency:
Original + yield-bearing versions:
Same stablecoin across different chains:
Stablecoins and their collateral assets:
function createPool(
string memory name,
string memory symbol,
address[] memory coins,
uint256 amplifier,
uint256 fee,
uint256 adminFee
) external onlyOwner returns (address) {
// Validate parameters
require(coins.length >= 2 && coins.length <= 8, "Invalid number of coins");
require(amplifier >= 10, "Amplifier too low");
require(fee <= 100000000, "Fee too high"); // max 0.1%
require(adminFee <= 10000000000, "Admin fee too high"); // max 10%
// Create pool
StableSwapPool pool = new StableSwapPool(
name,
symbol,
coins,
amplifier == 0 ? defaultAmplifier : amplifier,
fee == 0 ? defaultFee : fee,
adminFee == 0 ? defaultAdminFee : adminFee,
msg.sender
);
// Register pool
address poolAddress = address(pool);
isPool[poolAddress] = true;
allPools.push(poolAddress);
emit PoolCreated(
poolAddress,
coins,
amplifier,
fee,
adminFee
);
return poolAddress;
}
contract StableSwapPool is ERC20, ReentrancyGuard, AccessControl {
// Constants
uint256 private constant FEE_DENOMINATOR = 10**10;
uint256 private constant PRECISION = 10**18;
// Roles
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant FEE_MANAGER_ROLE = keccak256("FEE_MANAGER_ROLE");
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
// State variables
address[] public coins; // Tokens in pool
uint256[] private balances; // Token balances
uint256 public amplifier; // A parameter
uint256 public fee; // Exchange fee rate
uint256 public adminFee; // Admin fee rate
uint256 private constant_d; // Calculated invariant D
IDynamicFeeModel public feeModel; // Dynamic fee model
IGaslessRelayer public relayer; // Gasless relayer
}
function exchange(
uint256 i, // input token index
uint256 j, // output token index
uint256 dx, // input amount
uint256 minDy // min output amount
) external nonReentrant returns (uint256) {
require(i != j, "Same token");
require(i < coins.length && j < coins.length, "Invalid index");
// Transfer input token
IERC20(coins[i]).transferFrom(msg.sender, address(this), dx);
// Calculate output amount
uint256 dy = _calculateSwap(i, j, dx);
// Calculate fees
uint256 dyFee = (dy * fee) / FEE_DENOMINATOR;
uint256 dyAdminFee = (dyFee * adminFee) / FEE_DENOMINATOR;
// Update pool state
balances[i] += dx;
balances[j] -= (dy - dyFee);
// Verify slippage limit
require(dy - dyFee >= minDy, "Exchange below minimum");
// Transfer output token
IERC20(coins[j]).transfer(msg.sender, dy - dyFee);
return dy - dyFee;
}
function gaslessExchange(
uint256 i,
uint256 j,
uint256 dx,
uint256 minDy,
address user,
uint256 relayerFee,
bytes memory signature
) external nonReentrant onlyRole(RELAYER_ROLE) returns (uint256) {
// Verify signature
require(
relayer.verifyExchangeSignature(
user, address(this), i, j, dx, minDy,
relayerFee, signature
),
"Invalid signature"
);
// Process exchange (similar to standard exchange)
// ...
// Pay relayer fee
IERC20(coins[j]).transfer(msg.sender, relayerFee);
// Transfer remainder to user
IERC20(coins[j]).transfer(
user, dy - dyFee - relayerFee
);
return dy - dyFee - relayerFee;
}
function _calculateSwap(
uint256 i,
uint256 j,
uint256 dx
) internal view returns (uint256) {
uint256[] memory xp = _getXp();
uint256 x = xp[i] + (dx * PRECISION /
_getTokenPrecision(i));
uint256 y = _getY(i, j, x, xp);
uint256 dy = (xp[j] - y) * _getTokenPrecision(j) /
PRECISION;
return dy;
}
function _getY(
uint256 i,
uint256 j,
uint256 x,
uint256[] memory xp
) internal view returns (uint256) {
uint256 amp = amplifier;
uint256 d = constant_d;
if (d == 0) {
d = _getD(xp);
}
uint256 n = xp.length;
uint256 s = 0;
uint256 c = d;
for (uint256 k = 0; k < n; k++) {
if (k == i) {
s += x;
c = c * d / (x * n);
} else if (k != j) {
s += xp[k];
c = c * d / (xp[k] * n);
}
}
c = c * d / (amp * n**n);
uint256 b = s + d / (amp * n**n);
uint256 y = d;
for (uint256 k = 0; k < 255; k++) {
uint256 y_prev = y;
y = (y*y + c) / (2*y + b - d);
if (y > y_prev && y - y_prev <= 1) break;
if (y_prev > y && y_prev - y <= 1) break;
}
return y;
}
function _getD(uint256[] memory xp) internal view returns (uint256) {
uint256 amp = amplifier;
uint256 s = 0;
for (uint256 i = 0; i < xp.length; i++) {
s += xp[i];
}
if (s == 0) return 0;
uint256 n = xp.length;
uint256 d = s;
uint256 ann = amp * n**n;
for (uint256 i = 0; i < 255; i++) {
uint256 d_prev = d;
uint256 prod = d;
for (uint256 j = 0; j < n; j++) {
prod = prod * d / (xp[j] * n);
}
d = (ann * s + prod * n) * d /
((ann - 1) * d + (n + 1) * prod);
if (d > d_prev) {
if (d - d_prev <= 1) break;
} else {
if (d_prev - d <= 1) break;
}
}
return d;
}
function _getXp() internal view returns (uint256[] memory) {
uint256[] memory xp = new uint256[](coins.length);
for (uint256 i = 0; i < coins.length; i++) {
xp[i] = balances[i] * PRECISION /
_getTokenPrecision(i);
}
return xp;
}
function _getTokenPrecision(uint256 i) internal view returns (uint256) {
return 10**uint256(ERC20(coins[i]).decimals());
}
contract DynamicFeeModel is IDynamicFeeModel, Ownable {
// Constants
uint256 private constant FEE_PRECISION = 10**10;
// State variables
IOracleProvider public oracle;
uint256 public baseFeeBps = 4000000; // 0.04%
uint256 public maxFeeBps = 100000000; // 0.1%
uint256 public volatilityMultiplier = 5;
uint256 public imbalanceMultiplier = 10;
// Calculate dynamic fee
function calculateFee(
address pool,
uint256[] memory balances,
uint256 amplifier,
uint256 defaultFee
) external view override returns (uint256) {
if (address(oracle) == address(0)) {
return defaultFee;
}
// Calculate pool imbalance
uint256 imbalance = _calculateImbalance(balances);
// Get price volatility
uint256 volatility = oracle.get24hVolatility(pool);
// Calculate dynamic fee
uint256 fee = baseFeeBps;
// Add imbalance fee
fee += imbalance * imbalanceMultiplier;
// Add volatility fee
fee += volatility * volatilityMultiplier;
// Adjust based on amplifier (higher A = lower fee)
if (amplifier > 200) {
uint256 ampDiscount = (amplifier - 200) * baseFeeBps / 1800; // up to 50% discount
if (fee > ampDiscount) {
fee -= ampDiscount;
}
}
// Cap at maximum fee
if (fee > maxFeeBps) {
fee = maxFeeBps;
}
return fee;
}
}
contract GaslessRelayer is IGaslessRelayer, Ownable {
using ECDSA for bytes32;
// State variables
mapping(address => bool) public authorizedRelayers;
mapping(address => mapping(address => uint256)) public userNonces;
uint256 public minStake = 1 ether;
uint256 public maxRelayerFeeBps = 1000; // 10%
// Register as relayer
function registerAsRelayer() external payable {
require(msg.value >= minStake, "Insufficient stake");
authorizedRelayers[msg.sender] = true;
emit RelayerRegistered(msg.sender, msg.value);
}
// Verify exchange signature
function verifyExchangeSignature(
address user,
address pool,
uint256 i,
uint256 j,
uint256 dx,
uint256 minDy,
uint256 relayerFee,
bytes memory signature
) external view override returns (bool) {
// Verify relayer fee
require(relayerFee <= dx * maxRelayerFeeBps / 10000,
"Relayer fee too high");
// Build message hash
bytes32 messageHash = keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
keccak256(
abi.encode(
pool,
i,
j,
dx,
minDy,
relayerFee,
userNonces[user][pool],
block.chainid
)
)
)
);
// Verify signature
address signer = messageHash.recover(signature);
return signer == user;
}
// Relay exchange transaction
function relayExchange(
address pool,
uint256 i,
uint256 j,
uint256 dx,
uint256 minDy,
uint256 relayerFee,
address user,
bytes memory signature
) external onlyRelayer returns (uint256) {
uint256 gasStart = gasleft();
// Execute exchange
bool success = IStableSwapPool(pool).gaslessExchange(
i, j, dx, minDy, user, relayerFee, signature
);
require(success, "Exchange failed");
// Increment nonce
userNonces[user][pool]++;
// Calculate gas used
uint256 gasUsed = gasStart - gasleft();
emit RelayExecuted(user, msg.sender, pool, gasUsed);
return gasUsed;
}
}
| Trade Size | Uniswap v2 | Curve Finance | ULayer (A=200) | ULayer (A=500) |
|---|---|---|---|---|
| 0.1% of pool | ~0.2% | ~0.0005% | ~0.0002% | ~0.00008% |
| 1% of pool | ~2% | ~0.005% | ~0.002% | ~0.0008% |
| 5% of pool | ~10% | ~0.1% | ~0.05% | ~0.02% |
| 10% of pool | ~20% | ~0.4% | ~0.2% | ~0.08% |
| Operation | Unoptimized | Optimized | Savings |
|---|---|---|---|
| Swap | ~180,000 | ~120,000 | 33% |
| Add Liquidity | ~240,000 | ~160,000 | 33% |
| Remove Liquidity | ~200,000 | ~130,000 | 35% |