This repository is a fork of Aave Protocol V2 that has been customized to deploy the Pyraxe lending market on the Sepolia testnet. This document describes all the modifications, configurations, and additional logic that were implemented to enable this deployment.
- Overview
- Key Modifications
- Market Configuration
- Additional Contracts
- Sepolia Network Configuration
- Deployment Scripts
- Deployment Process
- Contract Addresses
- Adding a New Reserve Token
- Verification
This fork extends the Aave Protocol V2 codebase to support:
- Pyraxe Market: A custom lending market configuration
- Sepolia Testnet: Full deployment support for Ethereum Sepolia testnet
- Pyth Fallback Oracle: Enhanced oracle infrastructure using Pyth Network as a fallback price feed
- Fee Collector: Custom protocol fee collection contract with role-based access control
A new market configuration was added at markets/pyraxe/:
markets/pyraxe/index.ts: Main Pyraxe market configurationmarkets/pyraxe/commons.ts: Common configuration parameters including Pyth Oracle and Fee Collector settingsmarkets/pyraxe/reservesConfigs.ts: Reserve asset configurations
The Pyraxe market is configured with a subset of assets for Sepolia:
- WBTC (Wrapped Bitcoin)
- WETH (Wrapped Ethereum)
- USDT (Tether)
The Pyraxe market was registered in the configuration system:
File: helpers/configuration.ts
export enum ConfigNames {
Commons = 'Commons',
Aave = 'Aave',
Matic = 'Matic',
Amm = 'Amm',
Avalanche = 'Avalanche',
Pyraxe = 'Pyraxe', // Added
}New interfaces were added to support Pyth Oracle and Fee Collector:
File: helpers/types.ts
export interface IPythOracleParams {
[network: string]: {
pythAddress: tEthereumAddress;
initialStaleTime: number;
};
}
export interface IFeeCollectorParams {
[network: string]: {
admin: tEthereumAddress;
guardian: tEthereumAddress;
withdrawer: tEthereumAddress;
};
}Location: markets/pyraxe/index.ts
export const PyraxeConfig: IAaveConfiguration = {
...CommonsConfig,
MarketId: 'Pyraxe market',
ProviderId: 1,
ReservesConfig: {
USDT: strategyUSDT,
WBTC: strategyWBTC,
WETH: strategyWETH,
},
// ... additional configuration
};Location: markets/pyraxe/index.ts
The following asset addresses are configured for Sepolia:
[eEthereumNetwork.sepolia]: {
WBTC: '0x29f2D40B0605204364af54EC677bD022dA425d03',
WETH: '0xC558DBdd856501FCd9aaF1E62eae57A9F0629a3c',
USDT: '0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0',
}Location: markets/pyraxe/commons.ts
PythOracle: {
[eEthereumNetwork.sepolia]: {
pythAddress: '0xDd24F84d36BF92C65F92307595335bdFab5Bbd21',
initialStaleTime: 120, // 120 seconds staleness check
},
}Location: markets/pyraxe/commons.ts
FeeCollector: {
[eEthereumNetwork.sepolia]: {
admin: '0xB33Ab79988Bcd11Ff0E98Dd7813f0a6a4555C91C',
guardian: '0xB33Ab79988Bcd11Ff0E98Dd7813f0a6a4555C91C',
withdrawer: '0xB33Ab79988Bcd11Ff0E98Dd7813f0a6a4555C91C',
},
}Location: contracts/pyraxe/contracts/PythFallbackOracle.sol
A custom fallback oracle implementation that integrates with Pyth Network for price feeds.
- Staleness Protection: Configurable staleness time (default: 120 seconds)
- Price Feed Mapping: Flexible asset-to-asset price conversion using Pyth feed IDs
- Precision Handling: Proper conversion of Pyth's exponential price format to target asset decimals
- Admin Controls: Owner can set price feeds and adjust staleness parameters
The Pyth Oracle is deployed during the oracle deployment phase and set as the fallback oracle for the AaveOracle contract.
Location: contracts/pyraxe/contracts/FeeCollector.sol
A custom protocol fee collection contract with enhanced security features.
- Role-Based Access Control: Three distinct roles (Admin, Guardian, Withdrawer)
- Two-Step Withdrawal: 24-hour cooldown period for withdrawals
- Guardian Veto: Guardian can cancel withdrawal requests during cooldown
- Security Model: Prevents single-point-of-failure attacks
- Admin: Manages access control (typically a multisig)
- Withdrawer: Can initiate and claim withdrawals after cooldown
- Guardian: Can veto withdrawal requests during cooldown period
The Fee Collector is deployed during the initialization phase and used as the treasury address for reserve factors.
File: hardhat.config.ts
Sepolia network was added to the Hardhat configuration:
networks: {
sepolia: getCommonNetworkConfig(eEthereumNetwork.sepolia, 11155111),
// ... other networks
}File: helpers/types.ts
Sepolia was added to the network enum:
export enum eEthereumNetwork {
// ... other networks
sepolia = 'sepolia',
}File: helper-hardhat-config.ts
Sepolia RPC URL and gas settings were configured:
export const NETWORKS_RPC_URL: iParamsPerNetwork<string> = {
// ... other networks
[eEthereumNetwork.sepolia]: process.env.SEPOLIA_RPC_URL || '',
};
export const NETWORKS_DEFAULT_GAS: iParamsPerNetwork<number | 'auto'> = {
// ... other networks
[eEthereumNetwork.sepolia]: 'auto',
};The deployment follows a sequential execution pattern in the tasks/full/ directory:
File: tasks/full/0_address_provider_registry.ts
- Deploys the LendingPoolAddressesProviderRegistry
- Task:
full:deploy-address-provider-registry
File: tasks/full/1_address_provider.ts
- Deploys the LendingPoolAddressesProvider
- Sets pool admin and emergency admin
- Registers with the provider registry
- Task:
full:deploy-address-provider
File: tasks/full/2_lending_pool.ts
- Deploys the LendingPool implementation
- Deploys the LendingPoolConfigurator
- Sets up proxy contracts
- Task:
full:deploy-lending-pool
File: tasks/full/3_oracles.ts
- Custom Addition: Deploys Pyth Fallback Oracle
- Deploys AaveOracle with Pyth as fallback
- Deploys LendingRateOracle
- Registers oracles with AddressesProvider
- Task:
full:deploy-oracles
Key Modification: The oracle deployment task was modified to:
- Deploy the Pyth Fallback Oracle using the custom task
deploy-pyraxe-pyth-oracle - Use the deployed Pyth Oracle address as the fallback oracle for AaveOracle
File: tasks/full/4_data-provider.ts
- Deploys AaveProtocolDataProvider
- Task:
full:data-provider
File: tasks/full/5-deploy-wethGateWay.ts
- Deploys WETHGateway
- Authorizes gateway with lending pool
- Task:
full-deploy-weth-gateway
File: tasks/full/6-initialize.ts
- Custom Addition: Deploys Fee Collector
- Initializes reserve assets (aTokens, debt tokens)
- Configures reserve parameters
- Deploys CollateralManager
- Task:
full:initialize-lending-pool
Key Modification: The initialization task was modified to:
- Deploy the Fee Collector using the custom task
deploy-pyraxe-feecollector - Use the Fee Collector address as the treasury address for reserve factors
File: tasks/deployments/pyraxe-deployments.ts
Two custom tasks were added:
task('deploy-pyraxe-pyth-oracle', 'Deploys the Pyraxe Pyth Fallback Oracle')
.addFlag('verify', 'Verify contract at Etherscan')
.addParam('pool', 'Pool configuration name');This task:
- Loads Pyth Oracle configuration from pool config
- Deploys PythFallbackOracle contract
task('deploy-pyraxe-feecollector', 'Deploys the Pyraxe Fee Collector')
.addFlag('verify', 'Verify contract at Etherscan')
.addParam('pool', 'Pool configuration name');This task:
- Loads Fee Collector configuration from pool config
- Deploys FeeCollector contract with admin, guardian, and withdrawer roles
File: tasks/helpers/contracts-deployments.ts
Added deployment helper functions:
export const deployPythOracle = async (
args: (string | number)[],
verify?: boolean
): Promise<PythFallbackOracle>
export const deployFeeCollector = async (
args: string[],
verify?: boolean
): Promise<FeeCollector>
export const getPythOracle = async (address?: tEthereumAddress)
export const getFeeCollector = async (address?: tEthereumAddress)Set up environment variables in .env:
MNEMONIC=
ALCHEMY_KEY=
ETHERSCAN_KEY=docker-compose builddocker-compose up -ddocker compose exec contracts-env bashCompile contracts
npm run compileDeploy Address Provider Registry
npx hardhat full:deploy-address-provider-registry --network sepolia --pool PyraxeDeploy Address Provider
npx hardhat full:deploy-address-provider --network sepolia --pool PyraxeDeploy Lending Pool
npx hardhat full:deploy-lending-pool --network sepolia --pool PyraxeDeploy Oracles
npx hardhat full:deploy-oracles --network sepolia --pool PyraxeDeploy Data Provider
npx hardhat full:data-provider --network sepoliaDeploy WETH Gateway
npx hardhat full-deploy-weth-gateway --network sepolia --pool PyraxeInitialize Lending Pool
npx hardhat full:initialize-lending-pool --network sepolia --pool PyraxeDeploy Ui Pool Data Provider
npx hardhat deploy-UiPoolDataProviderV2V3 --network sepoliaDeploy Ui Incentive Data Provider
npx hardhat deploy-UiIncentiveDataProviderV2V3 --network sepoliaAfter deployment, contract addresses are stored in the deployment-contracts.json file. You can find all deployed contract addresses there. Key contracts include:
- LendingPoolAddressesProvider: Core address registry
- LendingPool: Main lending pool contract
- LendingPoolConfigurator: Configuration contract (proxy)
- AaveOracle: Price oracle with Pyth fallback
- PythFallbackOracle: Pyth Network integration
- FeeCollector: Protocol fee collection (treasury)
- WETHGateway: Wrapped ETH gateway
- UiPoolDataProvider: UI data provider contract
- aTokens: Interest-bearing tokens for each reserve
- Debt Tokens: Stable and variable debt tokens
Note: All contract addresses can be found in
deployment-contracts.jsonafter deployment, or by checking the deployment console output.
This guide demonstrates how to add a new token (using LINK as an example) to the Pyraxe lending pool after the initial deployment. This process involves 5 sequential steps that must be completed in order.
Before starting, ensure you have:
- ✅ All core contracts deployed (from the deployment steps above)
- ✅ Access to Etherscan for the Sepolia network
- ✅ The following contract addresses from
deployment-contracts.json:LendingPoolConfigurator(proxy address)LendingPoolAddressesProviderAaveOracleUiPoolDataProviderFeeCollector(treasury address)
- ✅ Token implementation addresses (can reuse from existing reserves like WETH):
ATokenimplementationStableDebtTokenimplementationVariableDebtTokenimplementation
- ✅ Chainlink price feed address for the token (e.g., LINK/ETH feed)
This step creates the aToken, stable debt token, and variable debt token proxies for your new asset.
- Navigate to Etherscan (Sepolia) and search for the LendingPoolConfigurator address
- Go to the Contract tab
- Click More Options → Is this a proxy? → Verify
- Select Write as Proxy (this is required because
LendingPoolConfiguratoris a proxy contract)
Important: Always interact with the proxy contract, not the implementation. Calling functions directly on the implementation will revert for security reasons.
Navigate to the Write Contract section and call the batchInitReserve function with the following parameters:
| Parameter | Type | Description | Example Value (LINK) |
|---|---|---|---|
| aTokenImpl | address |
Logic contract for aTokens (reuse from WETH) | 0xE66a8df05aBf119c8dBc08AaA6a38A46e948A62f |
| stableDebtTokenImpl | address |
Logic for stable debt tokens (reuse) | 0xe5E43f6327f08879bbf2302f17302A65702B4E2d |
| variableDebtTokenImpl | address |
Logic for variable debt tokens (reuse) | 0xe68451E184e3C10f95663130D600fc4b3C176a2D |
| underlyingAssetDecimals | uint8 |
Decimals of the token | 18 |
| interestRateStrategyAddress | address |
Strategy contract (reuse WETH strategy or deploy new) | 0x8877E27C326ae220089a322F56F2Fdde38a2d2b5 |
| underlyingAsset | address |
The token address to list | 0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5 |
| treasury | address |
Fee collector address | 0x7530E19c719B63aaD05068CfF2593BF93cACdDc6 |
| incentivesController | address |
Reward distributor (use zero address if none) | 0x0000000000000000000000000000000000000000 |
| aTokenName | string |
Name for the receipt token | "Pyraxe Interest Bearing LINK" |
| aTokenSymbol | string |
Symbol for receipt token | "mtLINK" |
| variableDebtTokenName | string |
Name for variable debt token | "Pyraxe Variable Debt LINK" |
| variableDebtTokenSymbol | string |
Symbol for variable debt token | "variableDebtmtLINK" |
| stableDebtTokenName | string |
Name for stable debt token | "Pyraxe Stable Debt LINK" |
| stableDebtTokenSymbol | string |
Symbol for stable debt token | "stableDebtmtLINK" |
| params | bytes |
Extra initialization data (usually empty) | 0x |
For the LINK token example, the complete input array would be:
[
[
"0xE66a8df05aBf119c8dBc08AaA6a38A46e948A62f",
"0xe5E43f6327f08879bbf2302f17302A65702B4E2d",
"0xe68451E184e3C10f95663130D600fc4b3C176a2D",
"18",
"0x8877E27C326ae220089a322F56F2Fdde38a2d2b5",
"0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5",
"0x7530E19c719B63aaD05068CfF2593BF93cACdDc6",
"0x0000000000000000000000000000000000000000",
"ChainLink",
"Pyraxe Interest Bearing LINK",
"mtLINK",
"Pyraxe Variable Debt LINK",
"variableDebtmtLINK",
"Pyraxe Stable Debt LINK",
"stableDebtmtLINK",
"0x"
]
]Note: The function accepts an array of arrays, allowing you to initialize multiple reserves in a single transaction. For a single token, wrap the parameters in an additional array.
After the transaction is confirmed:
- Go to the UiPoolDataProvider contract on Etherscan
- Call
getReservesList(address provider)with the LendingPoolAddressesProvider address - Verify that your new token address appears in the returned list
- Compare the token details to ensure everything was set correctly
Critical: This step must be completed before any deposits. Without a price feed, deposits will fail or health factors will be incorrect.
- Locate the AaveOracle contract address from
deployment-contracts.json - Navigate to the contract on Etherscan
- Go to the Write Contract section
Call the setAssetSources function with:
- assets (array):
[LINK_TOKEN_ADDRESS] - sources (array):
[CHAINLINK_PRICE_FEED_ADDRESS]
Example for LINK:
- assets:
["0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5"] - sources:
["CHAINLINK_LINK_ETH_FEED_ADDRESS"]
Note: The price feed should be the Chainlink aggregator for the token/ETH pair (e.g., LINK/ETH). Find the correct Chainlink feed address for Sepolia testnet.
After setting the price source, you can verify by calling getAssetPrice(address asset) on the AaveOracle contract with your token address. It should return a non-zero price.
This step defines the risk parameters for using the token as collateral.
On the LendingPoolConfigurator contract (Write as Proxy), call:
Function: configureReserveAsCollateral(address asset, uint256 ltv, uint256 liquidationThreshold, uint256 liquidationBonus)
Parameters:
| Parameter | Type | Description | Example Value (LINK) |
|---|---|---|---|
| asset | address |
The token address | 0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5 |
| ltv | uint256 |
Loan-to-Value ratio (in basis points) | 7000 (70.00%) |
| liquidationThreshold | uint256 |
Liquidation threshold (in basis points) | 7500 (75.00%) |
| liquidationBonus | uint256 |
Liquidation bonus (in basis points) | 10500 (105.00%) |
Parameter Explanations:
- LTV (7000): Maximum percentage of collateral value that can be borrowed (70%)
- Liquidation Threshold (7500): If debt reaches 75% of collateral value, the position becomes liquidatable
- Liquidation Bonus (10500): Liquidators receive a 5% bonus (use 10500, not 500)
Important: Values are in basis points (1 basis point = 0.01%). For example, 7000 = 70.00%.
After completing the previous steps, users can deposit the token, but borrowing must be explicitly enabled.
On the LendingPoolConfigurator contract (Write as Proxy), call:
Function: enableBorrowingOnReserve(address asset, bool stableBorrowRateEnabled)
Parameters:
| Parameter | Type | Description | Example Value (LINK) |
|---|---|---|---|
| asset | address |
The token address | 0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5 |
| stableBorrowRateEnabled | bool |
Enable stable borrowing rate | false |
Note: Setting
stableBorrowRateEnabledtofalsemeans only variable rate borrowing is available, which is standard for most volatile assets.
The reserve factor determines what percentage of interest paid by borrowers goes to the protocol treasury versus liquidity providers.
On the LendingPoolConfigurator contract (Write as Proxy), call:
Function: setReserveFactor(address asset, uint256 reserveFactor)
Parameters:
| Parameter | Type | Description | Example Value (LINK) |
|---|---|---|---|
| asset | address |
The token address | 0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5 |
| reserveFactor | uint256 |
Reserve factor (in basis points) | 1000 (10.00%) |
Common Reserve Factor Values:
- Stablecoins:
1000(10%) - Volatile assets:
1000-2000(10-20%) - High-risk assets:
2000-3500(20-35%)
Note: The reserve factor is in basis points. 1000 = 10.00% of interest goes to the treasury.
After completing all 5 steps, verify the following:
- Reserve appears in
getReservesList()from UiPoolDataProvider - Price oracle returns a valid price for the asset
- Collateral parameters are set correctly (LTV, liquidation threshold, bonus)
- Borrowing is enabled
- Reserve factor is set
- Users can deposit the token
- Users can borrow the token (if enabled)
- Token can be used as collateral
Issue: Transaction reverts when calling batchInitReserve
- Solution: Ensure you're calling through the proxy contract, not the implementation
Issue: Deposits fail or health factors are incorrect
- Solution: Verify the price oracle is configured (Step 2)
Issue: Token cannot be used as collateral
- Solution: Verify collateral parameters are set (Step 3)
Issue: Borrowing is not available
- Solution: Verify borrowing is enabled (Step 4)
After deployment, verify contracts on Etherscan to ensure transparency and security.
# Verify all contracts
npx hardhat --network sepolia verify:general --all --pool Pyraxe
# Verify tokens (aTokens and debt tokens)
npx hardhat --network sepolia verify:tokens --pool Pyraxe- Navigate to Etherscan Sepolia
- Search for each deployed contract address
- Click Contract → Verify and Publish
- Fill in the contract details and verify
- LendingPoolAddressesProvider
- LendingPool
- LendingPoolConfigurator
- AaveOracle
- PythFallbackOracle
- FeeCollector
- WETHGateway
- UiPoolDataProvider
- All aTokens and debt tokens