678 lines
19 KiB
Markdown
678 lines
19 KiB
Markdown
# Balancer V3 & Gyroscope: Integration Reference
|
||
|
||
**Companion to**: `BALANCER_V3_GYROSCOPE_RESEARCH.md`
|
||
**Purpose**: Concrete contract addresses, code snippets, and deployment examples
|
||
|
||
---
|
||
|
||
## Part 1: Balancer V3 Contract Addresses
|
||
|
||
### Ethereum Mainnet
|
||
|
||
```
|
||
Vault (Core): 0xBA12222222228d8Ba445958a75a0704d566BF2C8
|
||
WeightedPoolFactory: 0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc
|
||
StablePoolFactory: 0xf3de90B4dDD9b3ba9d2ab9BbBe6EfFcf52F1a1fB
|
||
Router (EntryPoint): 0xBA12222222228d8Ba445958a75a0704d566BF2C8
|
||
ProtocolFeeController: 0xF4c873f02aF4aE79d01aF27Cfd8F25FCC22d9D47
|
||
```
|
||
|
||
**Key Insight**: The vault address is the same for all operations. Always target `0xBA12...` for swaps/joins/exits.
|
||
|
||
### Arbitrum
|
||
|
||
```
|
||
Vault: 0xBA12222222228d8Ba445958a75a0704d566BF2C8
|
||
WeightedPoolFactory: 0x86F5453991fBBFF1eDF663108f302a1eB4FdDf10
|
||
StablePoolFactory: 0x6B7b84d4bffad1c5C1eC8b3d8f1F5c81c5f1D2a4
|
||
GyroECLPFactory: 0x65755d39C36... (check Balancer UI for current)
|
||
```
|
||
|
||
### Base
|
||
|
||
```
|
||
Vault: 0xBA12222222228d8Ba445958a75a0704d566BF2C8
|
||
WeightedPoolFactory: 0xf3de90B4dDD9b3ba9d2ab9BbBe6EfFcf52F1a1fB
|
||
StablePoolFactory: 0x6B7b84d4bffad1c5C1eC8b3d8f1F5c81c5f1D2a4
|
||
USDC/sUSDS Boosted Pool: 0x... (look up on Balancer UI)
|
||
```
|
||
|
||
**Note**: Factory addresses change across deployments. **Always verify on [Balancer Deployments](https://github.com/balancer/balancer-deployments) repo** or Balancer UI for current addresses.
|
||
|
||
---
|
||
|
||
## Part 2: Python → Solidity Translation Guide
|
||
|
||
### E-CLP Parameters
|
||
|
||
**Python** (`elliptical_clp.py:33-43`):
|
||
```python
|
||
@dataclass
|
||
class ECLPParams:
|
||
alpha: float # Lower price bound
|
||
beta: float # Upper price bound
|
||
c: float # cos(-φ) rotation
|
||
s: float # sin(-φ) rotation
|
||
lam: float # λ ≥ 1, stretching factor
|
||
```
|
||
|
||
**Solidity** (FixedPoint 18 decimals):
|
||
```solidity
|
||
struct ECLPParams {
|
||
uint256 alpha; // e.g., 0.95e18 (95% price)
|
||
uint256 beta; // e.g., 1.05e18 (105% price)
|
||
uint256 c; // e.g., 0.707107e18 (cos 45°)
|
||
uint256 s; // e.g., 0.707107e18 (sin 45°)
|
||
uint256 lam; // e.g., 2.0e18 (2x stretch)
|
||
}
|
||
|
||
// Validation
|
||
require(alpha < beta, "α must be < β");
|
||
require(lam >= 1e18, "λ must be ≥ 1");
|
||
uint256 norm = sqrt(c*c + s*s);
|
||
require(abs(norm - 1e18) < 1e8, "(c,s) must be unit");
|
||
```
|
||
|
||
### Invariant Computation
|
||
|
||
**Python** (`elliptical_clp.py:106-150`):
|
||
```python
|
||
def compute_invariant(x: float, y: float, p: ECLPParams, dp: ECLPDerivedParams) -> float:
|
||
A = make_eclp_A(p.c, p.s, p.lam)
|
||
|
||
AChi_x = dp.w / p.lam + dp.z
|
||
AChi_y = p.lam * dp.u + dp.v
|
||
AChi = np.array([AChi_x, AChi_y])
|
||
AChi_sq = AChi @ AChi
|
||
|
||
t = np.array([x, y])
|
||
At = A @ t
|
||
At_sq = At @ At
|
||
|
||
AtAChi = At @ AChi
|
||
denom = AChi_sq - 1.0
|
||
|
||
discriminant = AtAChi**2 - denom * At_sq
|
||
r = (AtAChi + np.sqrt(discriminant)) / denom
|
||
return r
|
||
```
|
||
|
||
**Solidity** (PRBMath fixed-point):
|
||
```solidity
|
||
import { PRBMath } from "@prb/math/PRBMath.sol";
|
||
|
||
function computeInvariant(
|
||
uint256 x,
|
||
uint256 y,
|
||
ECLPParams memory params,
|
||
ECLPDerivedParams memory derived
|
||
) internal pure returns (uint256 invariant) {
|
||
// Compute A-matrix
|
||
(uint256 a00, uint256 a01, uint256 a10, uint256 a11) = _computeAMatrix(
|
||
params.c, params.s, params.lam
|
||
);
|
||
|
||
// AChi components
|
||
uint256 AChi_x = PRBMath.mulDiv(derived.w, 1e18, params.lam) + derived.z;
|
||
uint256 AChi_y = PRBMath.mulDiv(params.lam, derived.u, 1e18) + derived.v;
|
||
|
||
uint256 AChi_sq = PRBMath.mulDiv(AChi_x, AChi_x, 1e18) +
|
||
PRBMath.mulDiv(AChi_y, AChi_y, 1e18);
|
||
|
||
// At = A @ (x, y)
|
||
uint256 At_x = PRBMath.mulDiv(a00, x, 1e18) + PRBMath.mulDiv(a01, y, 1e18);
|
||
uint256 At_y = PRBMath.mulDiv(a10, x, 1e18) + PRBMath.mulDiv(a11, y, 1e18);
|
||
|
||
uint256 At_sq = PRBMath.mulDiv(At_x, At_x, 1e18) +
|
||
PRBMath.mulDiv(At_y, At_y, 1e18);
|
||
|
||
// AtAChi = At · AChi
|
||
uint256 AtAChi = PRBMath.mulDiv(At_x, AChi_x, 1e18) +
|
||
PRBMath.mulDiv(At_y, AChi_y, 1e18);
|
||
|
||
// Quadratic: denom * r^2 - 2*AtAChi*r + At_sq = 0
|
||
int256 denom = int256(AChi_sq) - int256(1e18);
|
||
|
||
if (denom == 0) {
|
||
// Degenerate: circle case λ=1
|
||
return _sqrt(At_sq);
|
||
}
|
||
|
||
// discriminant = AtAChi^2 - denom * At_sq
|
||
uint256 disc = PRBMath.mulDiv(AtAChi, AtAChi, 1e18) -
|
||
PRBMath.mulDiv(uint256(abs(denom)), At_sq, 1e18);
|
||
|
||
// r = (AtAChi + sqrt(disc)) / denom
|
||
uint256 sqrtDisc = _sqrt(disc);
|
||
invariant = PRBMath.mulDiv(AtAChi + sqrtDisc, 1e18, uint256(abs(denom)));
|
||
|
||
return invariant;
|
||
}
|
||
|
||
function _computeAMatrix(
|
||
uint256 c, uint256 s, uint256 lam
|
||
) internal pure returns (uint256 a00, uint256 a01, uint256 a10, uint256 a11)
|
||
{
|
||
// A = [[c²/λ + s², (1-c²)(1-1/λ²)],
|
||
// [(1-c²)(1-1/λ²), s²/λ + c²]]
|
||
// Simplified from GyroECLPMath.sol
|
||
|
||
uint256 lam_sq = PRBMath.mulDiv(lam, lam, 1e18);
|
||
uint256 lam_inv = PRBMath.mulDiv(1e18, 1e18, lam); // 1/λ
|
||
|
||
uint256 c_sq = PRBMath.mulDiv(c, c, 1e18);
|
||
uint256 s_sq = PRBMath.mulDiv(s, s, 1e18);
|
||
|
||
a00 = PRBMath.mulDiv(c_sq, lam_inv, 1e18) + s_sq;
|
||
a11 = PRBMath.mulDiv(s_sq, lam_inv, 1e18) + c_sq;
|
||
|
||
uint256 term = (1e18 - c_sq) * (1e18 - lam_inv) / 1e18;
|
||
a01 = term;
|
||
a10 = term;
|
||
}
|
||
|
||
function _sqrt(uint256 x) internal pure returns (uint256) {
|
||
if (x == 0) return 0;
|
||
return PRBMath.sqrt(x);
|
||
}
|
||
```
|
||
|
||
### Swap Out Given In
|
||
|
||
**Python** (`elliptical_clp.py:172-189`):
|
||
```python
|
||
def calc_out_given_in(
|
||
x: float, y: float, p: ECLPParams, dp: ECLPDerivedParams,
|
||
amount_in: float, token_in: int = 0,
|
||
) -> float:
|
||
r = compute_invariant(x, y, p, dp)
|
||
|
||
if token_in == 0:
|
||
new_x = x + amount_in
|
||
new_y = _calc_y_given_x(new_x, r, p, dp)
|
||
return y - new_y
|
||
else:
|
||
new_y = y + amount_in
|
||
new_x = _calc_x_given_y(new_y, r, p, dp)
|
||
return x - new_x
|
||
```
|
||
|
||
**Solidity** (for `IBasePool.onSwap`):
|
||
```solidity
|
||
function onSwap(PoolSwapParams calldata params)
|
||
external
|
||
view
|
||
override
|
||
returns (uint256 amountCalculated)
|
||
{
|
||
uint256[] memory balances = params.balances;
|
||
uint256 x = balances[0]; // Token 0 balance
|
||
uint256 y = balances[1]; // Token 1 balance
|
||
|
||
uint256 invariant = computeInvariant(x, y, eclpParams, eclpDerived);
|
||
|
||
if (params.kind == SwapKind.EXACT_IN) {
|
||
if (params.tokenInIndex == 0) {
|
||
uint256 new_x = x + params.amountIn;
|
||
uint256 new_y = _solveYGivenX(new_x, invariant);
|
||
amountCalculated = y > new_y ? y - new_y : 0;
|
||
} else {
|
||
uint256 new_y = y + params.amountIn;
|
||
uint256 new_x = _solveXGivenY(new_y, invariant);
|
||
amountCalculated = x > new_x ? x - new_x : 0;
|
||
}
|
||
} else {
|
||
// EXACT_OUT: solve for input required
|
||
if (params.tokenOutIndex == 0) {
|
||
uint256 new_x = x - params.amountOut;
|
||
uint256 new_y = _solveYGivenX(new_x, invariant);
|
||
amountCalculated = new_y > y ? new_y - y : 0;
|
||
} else {
|
||
uint256 new_y = y - params.amountOut;
|
||
uint256 new_x = _solveXGivenY(new_y, invariant);
|
||
amountCalculated = new_x > x ? new_x - x : 0;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Part 3: P-AMM (Redemption Curve) Hook Example
|
||
|
||
### Simplified Hook Implementation
|
||
|
||
```solidity
|
||
pragma solidity ^0.8.24;
|
||
|
||
import { IHooks } from "@balancer-labs/v3-vault/contracts/interfaces/IHooks.sol";
|
||
|
||
contract SimpleRedemptionHook is IHooks {
|
||
struct PAMMParams {
|
||
uint256 alpha_bar; // Curvature: 10.0e18
|
||
uint256 xu_bar; // No-discount threshold: 0.8e18
|
||
uint256 theta_bar; // Floor rate: 0.5e18
|
||
uint256 outflow_memory; // Decay: 0.999e18
|
||
}
|
||
|
||
struct PAMMState {
|
||
uint256 reserve_value; // Total USD in reserve
|
||
uint256 myco_supply; // Total MYCO issued
|
||
uint256 cumulative_redeemed;
|
||
uint256 last_redemption_time;
|
||
}
|
||
|
||
PAMMParams public params;
|
||
PAMMState public state;
|
||
address public bondedPool;
|
||
address public governance;
|
||
|
||
event RedemptionRateUpdated(uint256 newRate, uint256 timestamp);
|
||
|
||
modifier onlyGovernance() {
|
||
require(msg.sender == governance, "Not governance");
|
||
_;
|
||
}
|
||
|
||
function initialize(
|
||
uint256 reserve,
|
||
uint256 supply,
|
||
address pool,
|
||
address gov
|
||
) external {
|
||
state = PAMMState(reserve, supply, 0, block.timestamp);
|
||
params = PAMMParams(10.0e18, 0.8e18, 0.5e18, 0.999e18);
|
||
bondedPool = pool;
|
||
governance = gov;
|
||
}
|
||
|
||
function computeRedemptionRate(uint256 redemption_amount)
|
||
public
|
||
view
|
||
returns (uint256)
|
||
{
|
||
if (state.myco_supply == 0) return 1e18;
|
||
|
||
uint256 ba = (state.reserve_value * 1e18) / state.myco_supply;
|
||
|
||
// Fully backed
|
||
if (ba >= 1e18) {
|
||
return 1e18;
|
||
}
|
||
|
||
// No backing
|
||
if (ba == 0) {
|
||
return params.theta_bar;
|
||
}
|
||
|
||
// Compute alpha (curvature)
|
||
uint256 delta = 1e18 - ba;
|
||
uint256 alpha = (params.alpha_bar * 1e18) / state.myco_supply;
|
||
if (delta > 0) {
|
||
uint256 min_alpha = 2 * delta;
|
||
alpha = alpha > min_alpha ? alpha : min_alpha;
|
||
}
|
||
|
||
// Compute xu (no-discount zone)
|
||
uint256 xu = params.xu_bar;
|
||
if (alpha > 0 && delta > 0) {
|
||
// xu_from_delta = 1.0 - sqrt(2*delta/alpha)
|
||
uint256 ratio = (2 * delta * 1e18) / alpha;
|
||
uint256 sqrt_ratio = _sqrt(ratio);
|
||
xu = (1e18 - sqrt_ratio) < params.xu_bar
|
||
? (1e18 - sqrt_ratio)
|
||
: params.xu_bar;
|
||
}
|
||
|
||
// Current redemption level
|
||
uint256 x = ((state.cumulative_redeemed + redemption_amount / 2) * 1e18) /
|
||
state.myco_supply;
|
||
|
||
// Determine zone
|
||
if (x <= xu) {
|
||
// No discount
|
||
return ba;
|
||
}
|
||
|
||
// Parabolic discount (simplified)
|
||
// rate = ba - x + alpha * (x - xu)^2 / 2
|
||
uint256 discount = (alpha * (x - xu) * (x - xu)) / (2e18);
|
||
uint256 rate = ba > x ? ba - x : 0;
|
||
rate = rate + discount / 1e18;
|
||
rate = rate < params.theta_bar ? params.theta_bar : rate;
|
||
return rate > 1e18 ? 1e18 : rate;
|
||
}
|
||
|
||
// Hook interface implementations
|
||
|
||
function onRegister(
|
||
address pool,
|
||
address factory,
|
||
bytes memory userData
|
||
) external returns (bytes4) {
|
||
require(pool == bondedPool, "Unknown pool");
|
||
return IHooks.onRegister.selector;
|
||
}
|
||
|
||
function beforeSwap(BeforeSwapParams calldata params)
|
||
external
|
||
override
|
||
returns (bytes memory hookData)
|
||
{
|
||
// If swapping OUT of MYCO (redemption), apply discount
|
||
uint256 redemptionRate = computeRedemptionRate(params.amount);
|
||
return abi.encode(redemptionRate);
|
||
}
|
||
|
||
function afterSwap(AfterSwapParams calldata params)
|
||
external
|
||
override
|
||
{
|
||
// Update state: flow tracking, backing ratio
|
||
uint256 time_diff = block.timestamp - state.last_redemption_time;
|
||
uint256 decay = (params.outflowMemory ** time_diff) / 1e18;
|
||
state.cumulative_redeemed = (state.cumulative_redeemed * decay) / 1e18;
|
||
state.last_redemption_time = block.timestamp;
|
||
|
||
emit RedemptionRateUpdated(
|
||
computeRedemptionRate(params.amountOut),
|
||
block.timestamp
|
||
);
|
||
}
|
||
|
||
function beforeAddLiquidity(BeforeAddLiquidityParams calldata)
|
||
external
|
||
pure
|
||
override
|
||
returns (bytes memory)
|
||
{
|
||
return "";
|
||
}
|
||
|
||
function afterAddLiquidity(AfterAddLiquidityParams calldata)
|
||
external
|
||
pure
|
||
override
|
||
{}
|
||
|
||
function beforeRemoveLiquidity(BeforeRemoveLiquidityParams calldata)
|
||
external
|
||
pure
|
||
override
|
||
returns (bytes memory)
|
||
{
|
||
return "";
|
||
}
|
||
|
||
function afterRemoveLiquidity(AfterRemoveLiquidityParams calldata)
|
||
external
|
||
pure
|
||
override
|
||
{}
|
||
|
||
// Governance functions
|
||
function updateReserve(uint256 newValue) external onlyGovernance {
|
||
state.reserve_value = newValue;
|
||
}
|
||
|
||
function updateParams(
|
||
uint256 alpha,
|
||
uint256 xu,
|
||
uint256 theta
|
||
) external onlyGovernance {
|
||
params.alpha_bar = alpha;
|
||
params.xu_bar = xu;
|
||
params.theta_bar = theta;
|
||
}
|
||
|
||
// Helper
|
||
function _sqrt(uint256 x) internal pure returns (uint256) {
|
||
if (x == 0) return 0;
|
||
uint256 z = (x + 1) / 2;
|
||
uint256 y = x;
|
||
while (z < y) {
|
||
y = z;
|
||
z = (x / z + z) / 2;
|
||
}
|
||
return y;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Part 4: Pool Factory Example
|
||
|
||
```solidity
|
||
pragma solidity ^0.8.24;
|
||
|
||
import { BasePoolFactory } from "@balancer-labs/v3-pool-utils/contracts/BasePoolFactory.sol";
|
||
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
|
||
import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol";
|
||
import "./ECLPBondingCurvePool.sol";
|
||
|
||
contract ECLPBondingCurveFactory is BasePoolFactory {
|
||
event PoolCreated(
|
||
address indexed pool,
|
||
string name,
|
||
address[] tokens,
|
||
uint256[] params
|
||
);
|
||
|
||
constructor(IVault vault) BasePoolFactory(vault) {}
|
||
|
||
function create(
|
||
string memory name,
|
||
string memory symbol,
|
||
address[] calldata tokens,
|
||
uint256 swapFeePercentage,
|
||
address hooksContract,
|
||
bytes calldata eclpParamsEncoded
|
||
) external returns (address) {
|
||
require(tokens.length == 2, "Must be 2-token pool");
|
||
|
||
// Decode E-CLP params
|
||
(uint256 alpha, uint256 beta, uint256 c, uint256 s, uint256 lam) = abi.decode(
|
||
eclpParamsEncoded,
|
||
(uint256, uint256, uint256, uint256, uint256)
|
||
);
|
||
|
||
bytes memory creationCode = type(ECLPBondingCurvePool).creationCode;
|
||
bytes memory constructorArgs = abi.encode(
|
||
address(_vault),
|
||
name,
|
||
symbol,
|
||
tokens,
|
||
swapFeePercentage,
|
||
hooksContract,
|
||
alpha,
|
||
beta,
|
||
c,
|
||
s,
|
||
lam
|
||
);
|
||
|
||
address pool = _create(creationCode, constructorArgs);
|
||
|
||
emit PoolCreated(
|
||
pool,
|
||
name,
|
||
tokens,
|
||
abi.decode(eclpParamsEncoded, (uint256[]))
|
||
);
|
||
|
||
return pool;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Part 5: Testing (Foundry)
|
||
|
||
### Test E-CLP Invariant Preservation
|
||
|
||
```solidity
|
||
pragma solidity ^0.8.24;
|
||
|
||
import "forge-std/Test.sol";
|
||
import "../src/ECLPBondingCurvePool.sol";
|
||
|
||
contract ECLPBondingCurvePoolTest is Test {
|
||
ECLPBondingCurvePool pool;
|
||
|
||
function setUp() public {
|
||
// Deploy pool with test params
|
||
// α = 0.95, β = 1.05, (c,s) = (0.707, 0.707), λ = 1.5
|
||
}
|
||
|
||
function testInvariantPreservationOnSwap() public {
|
||
uint256 x = 1000e18;
|
||
uint256 y = 1000e18;
|
||
uint256 invariant1 = pool.computeInvariant(x, y);
|
||
|
||
uint256 amountIn = 100e18;
|
||
uint256 amountOut = pool.onSwap(
|
||
PoolSwapParams({
|
||
kind: SwapKind.EXACT_IN,
|
||
tokenInIndex: 0,
|
||
tokenOutIndex: 1,
|
||
amount: amountIn,
|
||
balances: [x + amountIn, y - amountOut],
|
||
// ... other params
|
||
})
|
||
);
|
||
|
||
uint256 newX = x + amountIn;
|
||
uint256 newY = y - amountOut;
|
||
uint256 invariant2 = pool.computeInvariant(newX, newY);
|
||
|
||
// Invariant should be approximately preserved (allow small rounding error)
|
||
assertApproxEqRel(invariant1, invariant2, 1e15); // 0.0001% tolerance
|
||
}
|
||
|
||
function testSpotPriceMonotonic() public {
|
||
// Increasing X should increase Y price (spot price of Y in X)
|
||
uint256 price1 = pool.spotPrice(1000e18, 1000e18);
|
||
uint256 price2 = pool.spotPrice(2000e18, 500e18);
|
||
|
||
// With asymmetric ellipse, price moves smoothly
|
||
assertTrue(price2 < price1); // Y becomes cheaper as X increases
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Part 6: Deployment Checklist
|
||
|
||
### Pre-Launch (Testnet)
|
||
|
||
```
|
||
[ ] Deploy factory on testnet
|
||
[ ] Create sample pool via factory
|
||
[ ] Register pool with vault
|
||
[ ] Link hooks contract
|
||
[ ] Run full test suite (Foundry)
|
||
[ ] Compare gas to reference pools (WeightedPool, StablePool, GyroECLP)
|
||
[ ] Run fuzz tests (Echidna or Foundry)
|
||
[ ] Verify against Python reference implementation
|
||
[ ] Integration test with Balancer UI (swaps, joins, exits)
|
||
```
|
||
|
||
### Pre-Launch (Mainnet)
|
||
|
||
```
|
||
[ ] Security audit (Trail of Bits, OpenZeppelin, etc.)
|
||
[ ] Bug bounty program ($50K+ coverage)
|
||
[ ] Community review period (1+ week)
|
||
[ ] Governance vote (if DAO-controlled)
|
||
[ ] Reserve initialization (fund vaults)
|
||
[ ] Set conservative swap fee (e.g., 0.5%)
|
||
[ ] Low TVL cap (e.g., $100K initial)
|
||
```
|
||
|
||
### Launch Day
|
||
|
||
```
|
||
[ ] Deploy factory + pools
|
||
[ ] Mint initial liquidity
|
||
[ ] Announce on Balancer Discord/Forums
|
||
[ ] Enable on Balancer UI
|
||
[ ] Monitor TVL, gas, price stability
|
||
[ ] Be ready to pause if needed (governance power)
|
||
```
|
||
|
||
---
|
||
|
||
## Part 7: Example Deployments
|
||
|
||
### Community Treasury (Weighted Pool)
|
||
|
||
```javascript
|
||
// Using Balancer SDK
|
||
|
||
const MYCO = "0x..."; // Your token
|
||
const USDC = "0x..."; // Stablecoin
|
||
|
||
const factory = IWeightedPoolFactory("0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc");
|
||
|
||
const pool = factory.create(
|
||
"MYCO Treasury LP",
|
||
"MYCO-LP",
|
||
[MYCO, USDC],
|
||
["600000000000000000", "400000000000000000"], // 60%, 40%
|
||
"5000000000000000", // 0.5% swap fee
|
||
"0x0000000000000000000000000000000000000000" // no hooks
|
||
);
|
||
|
||
// Mint initial liquidity
|
||
await vault.joinPool(pool, MYCO, USDC, amounts);
|
||
```
|
||
|
||
### Token Launch (E-CLP + P-AMM Hook)
|
||
|
||
```javascript
|
||
const LAUNCH_TOKEN = "0x...";
|
||
const STABLECOIN = "0x...";
|
||
|
||
// Deploy hook first
|
||
const pammHook = new SimpleRedemptionHook.deploy(
|
||
/* reserve, supply, pool, governance */
|
||
);
|
||
|
||
// Deploy E-CLP via factory
|
||
const eclpFactory = ECLPBondingCurveFactory("0x...");
|
||
|
||
const params = {
|
||
alpha: "950000000000000000", // 0.95
|
||
beta: "1050000000000000000", // 1.05
|
||
c: "707107000000000000", // cos(45°)
|
||
s: "707107000000000000", // sin(45°)
|
||
lam: "2000000000000000000" // 2.0 (asymmetric)
|
||
};
|
||
|
||
const pool = eclpFactory.create(
|
||
"LAUNCH E-CLP",
|
||
"LAUNCH-LP",
|
||
[LAUNCH_TOKEN, STABLECOIN],
|
||
"10000000000000000", // 1% launch fee (higher for illiquid)
|
||
pammHook.address,
|
||
abi.encode(params)
|
||
);
|
||
|
||
// Seed initial liquidity
|
||
// Ratio biased toward stablecoin (due to λ=2 stretch)
|
||
```
|
||
|
||
---
|
||
|
||
## References & Links
|
||
|
||
- [Balancer V3 Docs - Custom Pools](https://docs.balancer.fi/build/build-an-amm/create-custom-amm-with-novel-invariant.html)
|
||
- [Balancer V3 GitHub - Pool Examples](https://github.com/balancer/balancer-v3-monorepo/tree/main/pkg/pool-stable/contracts)
|
||
- [GyroECLPPool.sol Source](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-gyro/contracts/GyroECLPPool.sol)
|
||
- [Hooks Balancer Directory](https://hooks.balancer.fi/)
|
||
- [PRBMath Library](https://github.com/paulrberg/prb-math)
|
||
|