For the complete documentation index, see llms.txt. This page is also available as Markdown.
Common Patterns
The SendParam in the following pattern are based on LayerZero patterns extensions: https://docs.layerzero.network/v2/developers/evm/oft/oft-patterns-extensions
Integration Patterns
Pattern 1: Same-Chain Deposit (Hub Only)
Fastest and cheapest—no LayerZero fees.
// Direct deposit on hub chainuint256 usdtAmount =1000*1e6;uint256 minShares =990*1e18;IERC20(usdt).approve(depositPipe, usdtAmount);uint256 shares =IDepositPipe(depositPipe).deposit( usdtAmount,msg.sender,msg.sender, minShares);// Shares minted instantly
Note: The deposit function signatures are:
deposit(uint256 assets, address receiver) - ERC4626 standard
deposit(uint256 assets, address receiver, address controller) - With controller
deposit(uint256 assets, address receiver, uint256 minShares) - With slippage protection
deposit(uint256 assets, address receiver, address controller, uint256 minShares) - Full control
The mint function signatures are:
mint(uint256 shares, address receiver) - ERC4626 standard
mint(uint256 shares, address receiver, address controller) - With controller
mint(uint256 shares, address receiver, uint256 maxAssets) - With slippage protection
mint(uint256 shares, address receiver, address controller, uint256 maxAssets) - Full control
Pattern 2: Cross-Chain Deposit (Spoke → Hub)
Deposit from any spoke, receive shares on hub.
Pattern 3: Cross-Chain Deposit (Spoke → Hub → Different Spoke)
Deposit on one spoke, receive shares on another spoke.
Pattern 4: Same-Chain Instant Redemption
Fastest redemption with highest fee.
Alternative: Withdraw specific asset amount
Note:
Instant redemption (redeem) applies a fee (instantRedeemFeeBps) which is deducted from the assets received. The fee stays with the liquidity provider.
withdraw calculates the required shares based on the net asset amount (after fees), while redeem burns a specific number of shares.
// Redeem shares for USDT on hub
uint256 shares = 1000 * 1e18;
IShareManager(shareManager).approve(redemptionPipe, shares);
uint256 usdtReceived = IRedemptionPipe(redemptionPipe).redeem(
shares,
msg.sender,
msg.sender
);
// USDT received instantly (after fee)
// Withdraw specific amount of assets (net, after fees)
uint256 desiredAssets = 1000 * 1e6; // Want 1000 USDT net
IShareManager(shareManager).approve(redemptionPipe, type(uint256).max);
uint256 sharesBurned = IRedemptionPipe(redemptionPipe).withdraw(
desiredAssets,
msg.sender,
msg.sender
);
// Shares burned, assets received (after fee)
// On Ethereum, redeem shares for USDT on hub
uint256 shares = 1000 * 1e18;
uint32 hubEid = 30167; // HyperEVM
// Build compose message for redemption
// ACTION_REDEEM_SHARES params: (address receiver, SendParam, uint256 minMsgValue, uint256 minAssets, bytes32 feeRefundRecipient, uint32 originEid)
bytes memory composeMsg = abi.encode(
uint8(2), // ACTION_REDEEM_SHARES
abi.encode(
msg.sender, // receiver (USDT recipient if same chain)
SendParam({
dstEid: 0, // 0 = same chain, non-zero = cross-chain
to: addressToBytes32(msg.sender), // Asset recipient if cross-chain
amountLD: 0, // Will be set by composer
minAmountLD: 990 * 1e6, // Min USDT (slippage protection)
extraOptions: "",
composeMsg: "",
oftCmd: ""
}),
uint256(0), // minMsgValue (native fee for compose)
990 * 1e6, // minAssets (slippage protection)
addressToBytes32(msg.sender), // feeRefundRecipient
getSourceEid() // originEid (Ethereum
)
)
);
SendParam memory sendParam = SendParam({
dstEid: hubEid,
to: addressToBytes32(composer),
amountLD: shares,
minAmountLD: shares,
extraOptions: OptionsBuilder.newOptions().addExecutorLzComposeOption(0, 700_000, 0),
composeMsg: composeMsg,
oftCmd: ""
});
MessagingFee memory fee = IOFT(shareOFT).quoteSend(sendParam, false);
IShareManager(shareERC20).approve(shareOFT, shares);
IOFT(shareOFT).send{value: fee.nativeFee}(sendParam, fee, msg.sender);
// USDT received on hub in ~1-3 minutes
// Transfer shares from Arbitrum to Ethereum (no deposit/redeem)
uint256 shares = 1000 * 1e18;
uint32 ethereumEid = 30101;
SendParam memory sendParam = SendParam({
dstEid: ethereumEid,
to: addressToBytes32(msg.sender),
amountLD: shares,
minAmountLD: shares,
extraOptions: OptionsBuilder.newOptions().addExecutorLzReceiveOption(200_000, 0),
composeMsg: "", // No compose message
oftCmd: ""
});
MessagingFee memory fee = IOFT(shareOFT).quoteSend(sendParam, false);
IShareManager(shareERC20).approve(shareOFT, shares);
IOFT(shareOFT).send{value: fee.nativeFee}(sendParam, fee, msg.sender);
// Shares arrive on Ethereum in ~1-3 minutes
// Deposit and send shares cross-chain in one call
uint256 usdtAmount = 1000 * 1e6;
uint32 ethereumEid = 30101;
SendParam memory shareSendParam = SendParam({
dstEid: ethereumEid,
to: addressToBytes32(msg.sender),
amountLD: 0, // Will be set by composer
minAmountLD: 990 * 1e18,
extraOptions: OptionsBuilder.newOptions().addExecutorLzReceiveOption(200_000, 0),
composeMsg: "",
oftCmd: ""
});
IERC20(usdt).approve(composer, usdtAmount);
MessagingFee memory fee = IOFT(shareOFT).quoteSend(shareSendParam, false);
IOVaultComposerMulti(composer).depositAssetAndSend{value: fee.nativeFee}(
usdt,
usdtAmount,
shareSendParam,
msg.sender // refund address
);
// Redeem and send assets cross-chain in one call
uint256 shares = 1000 * 1e18;
SendParam memory assetSendParam = SendParam({
dstEid: ethereumEid,
to: addressToBytes32(msg.sender),
amountLD: 0, // Will be set by composer
minAmountLD: 990 * 1e6,
extraOptions: OptionsBuilder.newOptions().addExecutorLzReceiveOption(200_000, 0),
composeMsg: "",
oftCmd: ""
});
IShareManager(shareERC20).approve(composer, shares);
MessagingFee memory fee = IOFT(underlyingAssetOFT).quoteSend(assetSendParam, false);
IOVaultComposerMulti(composer).redeemAndSend{value: fee.nativeFee}(
shares,
assetSendParam,
msg.sender // refund address
);
// Request standard redemption
uint256 shares = 1000 * 1e18;
// Transfer shares to redemption pipe (they will be held in custody)
IShareManager(shareManager).transfer(redemptionPipe, shares);
uint256 requestId = IRedemptionPipe(redemptionPipe).requestRedeem(
shares,
msg.sender, // receiver
msg.sender, // controller
msg.sender // owner
);
// Later, a FULFILL_MANAGER_ROLE will fulfill the request
// Users receive assets after fulfillment (no fees)
// Get current NAV (18 decimals)
uint256 nav = INAVOracle(navOracle).getNAV();
// Get total share supply (18 decimals)
uint256 supply = IShareManager(shareManager).totalSupply();
// Calculate share price (18 decimals)
uint256 sharePrice = (nav * 1e18) / supply;
// Calculate user's USD value
uint256 userShares = IShareManager(shareManager).balanceOf(user);
uint256 userValue = (userShares * nav) / supply;
// Normalize asset amount to 18 decimals
function normalizeToDecimals18(uint256 amount, uint8 decimals)
internal
pure
returns (uint256)
{
if (decimals == 18) return amount;
if (decimals < 18) {
return amount * (10 ** (18 - decimals));
} else {
return amount / (10 ** (decimals - 18));
}
}
// Denormalize from 18 decimals to asset decimals
function normalizeFromDecimals18(uint256 amount18, uint8 decimals)
internal
pure
returns (uint256)
{
if (decimals == 18) return amount18;
if (decimals < 18) {
return amount18 / (10 ** (18 - decimals));
} else {
return amount18 * (10 ** (decimals - 18));
}
}
// User sets operator
IShareManager(shareManager).setOperator(operatorContract, true);
// Operator can now act on behalf of user
// Check if user is blacklisted
bool isBlacklisted = IShareManager(shareManager).isBlacklisted(user);
// If blacklisted, operations will revert with:
// "ShareManager: receiver address is blacklisted"
// "ShareManager: sender address is blacklisted"
// Check if contract is paused
bool isPaused = Pausable(depositPipe).paused();
// If paused, operations will revert with:
// "Pausable: paused"
abi.encode(
address targetAsset, // Asset to deposit on hub
bytes32 receiver, // Share recipient if same chain (bytes32(0) if cross-chain)
SendParam shareSendParam, // Share distribution (dstEid=0 for same chain)
uint256 minMsgValue, // Minimum native fee for compose operation
bytes32 feeRefundRecipient, // Fee refund recipient (bytes32(0) = depositor)
uint32 originEid // Source chain for multi-hop refunds
)
abi.encode(
address receiver, // Asset recipient if same chain
SendParam assetSendParam, // Asset distribution (dstEid=0 for same chain)
uint256 minMsgValue, // Minimum native fee for compose operation
uint256 minAssets, // Minimum assets (slippage protection)
bytes32 feeRefundRecipient, // Fee refund recipient (bytes32(0) = redeemer)
uint32 originEid // Source chain for multi-hop refunds
)
// Convert address to bytes32 for LayerZero
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
// Convert bytes32 to address
function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
return address(uint160(uint256(_b)));
}