HokusaiAMM Smart Contract
The HokusaiAMM contract implements a Constant Reserve Ratio (CRR) bonding curve for each model token, enabling continuous buying and selling with USDC reserves.
Contract Overview
Purpose: Provide always-available liquidity for model tokens through a deterministic bonding curve mechanism.
Key Features:
- CRR-based buy/sell formulas
- Seven-day buy-only bonding round
- API fee deposit mechanism
- Slippage and deadline protection
- Emergency pause capability
- Governance-controlled parameters
Repository: hokusai-token
Architecture
HokusaiToken (ERC20)
↕
HokusaiAMM ←→ USDC Reserve
↑
|
UsageFeeRouter (deposits API fees)
State Variables
Core Parameters
// Token being traded
IERC20 public immutable token;
// Reserve token (USDC)
IERC20 public immutable reserveToken;
// Constant Reserve Ratio (5% to 50%)
uint256 public reserveRatio;
// Trade fee (0% to 10%)
uint256 public tradeFee;
// Protocol fee (0% to 50% of trade fee)
uint256 public protocolFee;
// End of buy-only period (timestamp)
uint256 public immutable buyOnlyUntil;
// Emergency pause flag
bool public paused;
Constants
uint256 public constant PRECISION = 1e18;
uint256 public constant MAX_RESERVE_RATIO = 50e16; // 50%
uint256 public constant MIN_RESERVE_RATIO = 5e16; // 5%
uint256 public constant MAX_TRADE_FEE = 10e16; // 10%
uint256 public constant MAX_PROTOCOL_FEE = 50e16; // 50%
Core Functions
buy()
Purchase tokens by depositing USDC.
function buy(
uint256 minTokens,
uint256 deadline
) external payable nonReentrant returns (uint256 tokensBought);
Parameters:
minTokens: Minimum tokens expected (slippage protection)deadline: Transaction deadline timestamp
Returns: Number of tokens purchased
Example:
const usdc = await ethers.getContractAt("IERC20", usdcAddress);
const amm = await ethers.getContractAt("HokusaiAMM", ammAddress);
// Approve USDC spending
await usdc.approve(ammAddress, ethers.parseUnits("1000", 6));
// Get quote first
const quote = await amm.getBuyQuote(ethers.parseUnits("1000", 6));
const minTokens = quote * 99n / 100n; // 1% slippage tolerance
// Execute buy
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 min
const tx = await amm.buy(minTokens, deadline, {
value: ethers.parseUnits("1000", 6)
});
await tx.wait();
console.log(`Bought ${ethers.formatUnits(quote, 18)} tokens`);
Formula:
T = S × ((1 + E/R)^w - 1)
Where:
T = Tokens minted
S = Current supply
R = Current reserve
E = USDC deposited (after fees)
w = Reserve ratio
Events Emitted:
event Buy(
address indexed buyer,
uint256 usdcSpent,
uint256 tokensBought,
uint256 tradeFee,
uint256 protocolFee
);
sell()
Sell tokens back for USDC.
function sell(
uint256 tokenAmount,
uint256 minUSDC,
uint256 deadline
) external nonReentrant returns (uint256 usdcReceived);
Parameters:
tokenAmount: Number of tokens to sellminUSDC: Minimum USDC expected (slippage protection)deadline: Transaction deadline timestamp
Returns: USDC amount received
Restrictions:
- ⚠️ Cannot sell during buy-only period (first 7 days)
- Requires token approval first
Example:
const token = await ethers.getContractAt("HokusaiToken", tokenAddress);
const amm = await ethers.getContractAt("HokusaiAMM", ammAddress);
// Check if bonding round is over
const buyOnlyUntil = await amm.buyOnlyUntil();
if (Date.now() / 1000 < buyOnlyUntil) {
throw new Error("Still in bonding round, cannot sell yet");
}
// Approve token spending
const tokenAmount = ethers.parseUnits("1000", 18);
await token.approve(ammAddress, tokenAmount);
// Get quote
const quote = await amm.getSellQuote(tokenAmount);
const minUSDC = quote * 99n / 100n; // 1% slippage
// Execute sell
const deadline = Math.floor(Date.now() / 1000) + 300;
const tx = await amm.sell(tokenAmount, minUSDC, deadline);
await tx.wait();
console.log(`Received ${ethers.formatUnits(quote, 6)} USDC`);
Formula:
F = R × (1 - (1 - T/S)^(1/w))
Where:
F = USDC returned (after fees)
T = Tokens burned
S = Current supply
R = Current reserve
w = Reserve ratio
Events Emitted:
event Sell(
address indexed seller,
uint256 tokensSold,
uint256 usdcReceived,
uint256 tradeFee,
uint256 protocolFee
);
depositFees()
Deposit API usage fees directly to reserves (no token minting).
function depositFees(uint256 amount) external onlyFeeDepositor nonReentrant;
Parameters:
amount: USDC amount to deposit
Access: Only addresses with FEE_DEPOSITOR_ROLE
Effect:
- Increases reserve (R ↑)
- Supply unchanged (S same)
- Spot price increases: P = R / (w × S)
Example:
// Called by UsageFeeRouter contract
const amm = await ethers.getContractAt("HokusaiAMM", ammAddress);
const usdc = await ethers.getContractAt("IERC20", usdcAddress);
// Approve and deposit
const feeAmount = ethers.parseUnits("5000", 6);
await usdc.approve(ammAddress, feeAmount);
await amm.depositFees(feeAmount);
// Price increased without minting tokens
Events Emitted:
event FeesDeposited(
address indexed depositor,
uint256 amount,
uint256 newReserve
);
View Functions
spotPrice()
Get current price per token.
function spotPrice() external view returns (uint256);
Returns: Current price in USDC (scaled by PRECISION)
Formula: P = R / (w × S)
Example:
const price = await amm.spotPrice();
console.log(`Current price: ${ethers.formatUnits(price, 18)} USDC per token`);
getBuyQuote()
Calculate tokens received for a given USDC amount.
function getBuyQuote(uint256 usdcAmount) external view returns (uint256);
Parameters:
usdcAmount: USDC to spend
Returns: Tokens that would be received (after fees)
Example:
const usdcAmount = ethers.parseUnits("1000", 6);
const tokensOut = await amm.getBuyQuote(usdcAmount);
console.log(`1000 USDC → ${ethers.formatUnits(tokensOut, 18)} tokens`);
getSellQuote()
Calculate USDC received for a given token amount.
function getSellQuote(uint256 tokenAmount) external view returns (uint256);
Parameters:
tokenAmount: Tokens to sell
Returns: USDC that would be received (after fees)
Example:
const tokenAmount = ethers.parseUnits("1000", 18);
const usdcOut = await amm.getSellQuote(tokenAmount);
console.log(`1000 tokens → ${ethers.formatUnits(usdcOut, 6)} USDC`);
getReserve()
Get current USDC reserve balance.
function getReserve() external view returns (uint256);
Returns: Current reserve in USDC
getTotalSupply()
Get current token supply.
function getTotalSupply() external view returns (uint256);
Returns: Current circulating supply
isBuyOnlyPeriod()
Check if still in seven-day bonding round.
function isBuyOnlyPeriod() external view returns (bool);
Returns: true if selling is disabled, false if full trading enabled
Example:
const isBuyOnly = await amm.isBuyOnlyPeriod();
if (isBuyOnly) {
console.log("Still in bonding round - buys only");
} else {
console.log("Full trading enabled");
}
Governance Functions
updateParameters()
Update AMM parameters (owner only).
function updateParameters(
uint256 newReserveRatio,
uint256 newTradeFee,
uint256 newProtocolFee
) external onlyOwner;
Parameters:
newReserveRatio: New CRR (5% to 50%)newTradeFee: New trade fee (0% to 10%)newProtocolFee: New protocol fee (0% to 50% of trade fee)
Events Emitted:
event ParametersUpdated(
uint256 reserveRatio,
uint256 tradeFee,
uint256 protocolFee
);
pause() / unpause()
Emergency pause trading.
function pause() external onlyOwner;
function unpause() external onlyOwner;
Effect: Disables buy/sell when paused, re-enables when unpaused
withdrawTreasury()
Withdraw accumulated protocol fees.
function withdrawTreasury(uint256 amount) external onlyOwner;
Parameters:
amount: USDC amount to withdraw from protocol fee balance
Access Control
The contract uses role-based access control:
Roles
bytes32 public constant FEE_DEPOSITOR_ROLE = keccak256("FEE_DEPOSITOR_ROLE");
Owner (Ownable):
- Update parameters
- Pause/unpause
- Withdraw treasury
- Grant/revoke roles
Fee Depositor (role-based):
- Deposit API fees
- Typically the
UsageFeeRoutercontract
Granting Roles
const amm = await ethers.getContractAt("HokusaiAMM", ammAddress);
const usageFeeRouter = "0x...";
// Grant FEE_DEPOSITOR_ROLE to UsageFeeRouter
const role = ethers.keccak256(ethers.toUtf8Bytes("FEE_DEPOSITOR_ROLE"));
await amm.grantRole(role, usageFeeRouter);
Security Features
Reentrancy Protection
All state-changing functions use OpenZeppelin's nonReentrant modifier:
function buy(...) external nonReentrant { ... }
function sell(...) external nonReentrant { ... }
function depositFees(...) external nonReentrant { ... }
Slippage Protection
Both buy and sell require minimum output amounts:
require(tokensOut >= minTokens, "Slippage too high");
require(usdcOut >= minUSDC, "Slippage too high");
Deadline Protection
Transactions expire after deadline:
require(block.timestamp <= deadline, "Transaction expired");
Parameter Bounds
All parameters have strict bounds enforced:
require(
newReserveRatio >= MIN_RESERVE_RATIO &&
newReserveRatio <= MAX_RESERVE_RATIO,
"Invalid reserve ratio"
);
Emergency Pause
Owner can pause in emergencies:
modifier whenNotPaused() {
require(!paused, "Contract paused");
_;
}
Mathematical Implementation
Power Function
The contract implements power(base, exponent) for bonding curve calculations:
function power(uint256 base, uint256 exponent) internal pure returns (uint256);
Uses fixed-point arithmetic with PRECISION = 1e18 for accuracy.
Buy Calculation
function calculateBuy(uint256 usdcIn) internal view returns (uint256) {
uint256 supply = token.totalSupply();
uint256 reserve = getReserve();
// T = S × ((1 + E/R)^w - 1)
uint256 ratio = (usdcIn * PRECISION) / reserve;
uint256 powered = power(PRECISION + ratio, reserveRatio);
uint256 tokensOut = (supply * (powered - PRECISION)) / PRECISION;
return tokensOut;
}
Sell Calculation
function calculateSell(uint256 tokensIn) internal view returns (uint256) {
uint256 supply = token.totalSupply();
uint256 reserve = getReserve();
// F = R × (1 - (1 - T/S)^(1/w))
uint256 ratio = (tokensIn * PRECISION) / supply;
uint256 invRatio = PRECISION - ratio;
uint256 powered = power(invRatio, PRECISION / reserveRatio);
uint256 usdcOut = (reserve * (PRECISION - powered)) / PRECISION;
return usdcOut;
}
Integration Examples
Frontend Integration
import { ethers } from 'ethers';
import HokusaiAMMABI from './abis/HokusaiAMM.json';
class AMMService {
private amm: ethers.Contract;
constructor(provider: ethers.Provider, ammAddress: string) {
this.amm = new ethers.Contract(ammAddress, HokusaiAMMABI, provider);
}
async getPrice(): Promise<number> {
const price = await this.amm.spotPrice();
return parseFloat(ethers.formatUnits(price, 18));
}
async buyTokens(
signer: ethers.Signer,
usdcAmount: string,
slippagePct: number = 1
) {
const amm = this.amm.connect(signer);
const usdcIn = ethers.parseUnits(usdcAmount, 6);
// Get quote
const tokensOut = await amm.getBuyQuote(usdcIn);
const minTokens = tokensOut * BigInt(100 - slippagePct) / 100n;
// Set deadline
const deadline = Math.floor(Date.now() / 1000) + 300;
// Execute
const tx = await amm.buy(minTokens, deadline, { value: usdcIn });
return await tx.wait();
}
async sellTokens(
signer: ethers.Signer,
tokenAmount: string,
slippagePct: number = 1
) {
const amm = this.amm.connect(signer);
const tokensIn = ethers.parseUnits(tokenAmount, 18);
// Check if selling is allowed
const isBuyOnly = await amm.isBuyOnlyPeriod();
if (isBuyOnly) {
throw new Error("Selling not allowed during bonding round");
}
// Get quote
const usdcOut = await amm.getSellQuote(tokensIn);
const minUSDC = usdcOut * BigInt(100 - slippagePct) / 100n;
// Approve tokens first
const token = await ethers.getContractAt(
"IERC20",
await amm.token()
);
await (await token.connect(signer).approve(amm.target, tokensIn)).wait();
// Set deadline
const deadline = Math.floor(Date.now() / 1000) + 300;
// Execute
const tx = await amm.sell(tokensIn, minUSDC, deadline);
return await tx.wait();
}
}
Backend Integration (Fee Deposits)
import { ethers } from 'ethers';
class FeeDepositService {
private router: ethers.Contract;
async depositAPIFees(
modelId: string,
feeAmount: string
) {
const amount = ethers.parseUnits(feeAmount, 6);
// Get AMM address for this model
const ammAddress = await this.getAMMForModel(modelId);
// Approve and deposit
const usdc = await ethers.getContractAt("IERC20", USDC_ADDRESS);
await (await usdc.approve(ammAddress, amount)).wait();
const amm = await ethers.getContractAt("HokusaiAMM", ammAddress);
const tx = await amm.depositFees(amount);
const receipt = await tx.wait();
console.log(`Deposited ${feeAmount} USDC fees to reserve`);
return receipt;
}
}
Common Errors
"Slippage too high"
Cause: Price moved unfavorably between quote and execution
Solution: Increase slippage tolerance or split into smaller trades
"Transaction expired"
Cause: Transaction took too long to mine
Solution: Increase deadline or use higher gas price
"Cannot sell during bonding round"
Cause: Trying to sell before day 7 complete
Solution: Wait until buyOnlyUntil timestamp passes
"Insufficient allowance"
Cause: Token not approved for spending
Solution: Call token.approve(ammAddress, amount) first
"Contract paused"
Cause: Emergency pause activated
Solution: Wait for unpause or check announcements
Testing
Example test suite:
describe("HokusaiAMM", function() {
it("should buy tokens with correct price", async function() {
const usdcAmount = ethers.parseUnits("1000", 6);
const quote = await amm.getBuyQuote(usdcAmount);
await usdc.approve(amm.target, usdcAmount);
await amm.buy(quote * 99n / 100n, deadline);
const balance = await token.balanceOf(buyer.address);
expect(balance).to.be.gte(quote * 99n / 100n);
});
it("should prevent selling during bonding round", async function() {
await expect(
amm.sell(ethers.parseUnits("100", 18), 0, deadline)
).to.be.revertedWith("Selling not allowed during bonding round");
});
it("should increase price when fees deposited", async function() {
const priceBefore = await amm.spotPrice();
const feeAmount = ethers.parseUnits("10000", 6);
await usdc.approve(amm.target, feeAmount);
await amm.depositFees(feeAmount);
const priceAfter = await amm.spotPrice();
expect(priceAfter).to.be.gt(priceBefore);
});
});
Gas Optimization
Typical Gas Costs (estimated):
buy(): ~150k gassell(): ~170k gasdepositFees(): ~80k gasgetBuyQuote(): ~5k gas (view)getSellQuote(): ~5k gas (view)
Optimization Tips:
- Use view functions to calculate quotes off-chain
- Batch approve and trade in same block
- Set reasonable slippage to avoid failed transactions
Related Contracts
- HokusaiToken - ERC20 token implementation
- HokusaiAMMFactory - Pool deployment
- UsageFeeRouter - Fee routing
- TokenManager - Token minting
Next Steps
- AMM Overview - High-level understanding
- Bonding Curve Math - Detailed formulas
- Buy Tokens Guide - Step-by-step buying
- Sell Tokens Guide - Step-by-step selling
For additional support, contact our Support Team or join our Community Forum.