Overview
The Treasury contract manages the economic pools of the Nexis Network. It receives inflows from agent slashing and early withdrawal penalties, distributes them across three pools (treasury, insurance, rewards), and enables authorized distribution of rewards to agents.
Contract Address (Testnet): 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
Key Features:
Three-pool system: Treasury, Insurance, Rewards
Configurable distribution ratios
Handles slashed stakes and early exit penalties
Reward distribution to agents with REWARDS_ROLE authorization
Pool withdrawal with WITHDRAW_ROLE authorization
Multi-asset support (ETH and ERC20 tokens)
Upgradeable UUPS proxy pattern
Contract Constants
// Roles
bytes32 public constant REWARDS_ROLE = keccak256 ( "REWARDS_ROLE" );
bytes32 public constant WITHDRAW_ROLE = keccak256 ( "WITHDRAW_ROLE" );
bytes32 public constant INFLOW_ROLE = keccak256 ( "INFLOW_ROLE" );
// Constants
uint16 internal constant BPS_DENOMINATOR = 10_000 ; // 100% = 10,000 basis points
address internal constant ETH_ASSET = address ( 0 );
Data Structures
DistributionConfig
struct DistributionConfig {
uint16 treasuryBps; // Treasury share in basis points (0-10000)
uint16 insuranceBps; // Insurance share in basis points
uint16 rewardsBps; // Rewards share in basis points
}
// Note : treasuryBps + insuranceBps + rewardsBps must equal 10,000
Default Distribution:
Treasury: 40% (4,000 bps)
Insurance: 30% (3,000 bps)
Rewards: 30% (3,000 bps)
PoolBalances
struct PoolBalances {
uint256 treasury; // Treasury pool balance
uint256 insurance; // Insurance pool balance
uint256 rewards; // Rewards pool balance
}
Pool Management
poolBalances
Query pool balances for a specific asset.
Asset address (address(0) for ETH, token address for ERC20)
Returns:
PoolBalances - Struct containing balances for all three pools
Example:
import { ethers } from 'ethers' ;
// Query ETH pool balances
const ethBalances = await treasury . poolBalances ( ethers . ZeroAddress );
console . log ( "ETH Pool Balances:" );
console . log ( " Treasury:" , ethers . formatEther ( ethBalances . treasury ), "ETH" );
console . log ( " Insurance:" , ethers . formatEther ( ethBalances . insurance ), "ETH" );
console . log ( " Rewards:" , ethers . formatEther ( ethBalances . rewards ), "ETH" );
// Query ERC20 token pool balances
const usdcAddress = "0x..." ;
const usdcBalances = await treasury . poolBalances ( usdcAddress );
console . log ( " \n USDC Pool Balances:" );
console . log ( " Treasury:" , ethers . formatUnits ( usdcBalances . treasury , 6 ), "USDC" );
console . log ( " Insurance:" , ethers . formatUnits ( usdcBalances . insurance , 6 ), "USDC" );
console . log ( " Rewards:" , ethers . formatUnits ( usdcBalances . rewards , 6 ), "USDC" );
rewardsBalance
Query available rewards balance for a specific asset.
Returns:
uint256 - Available rewards balance
Example:
const rewardsETH = await treasury . rewardsBalance ( ethers . ZeroAddress );
console . log ( "Available rewards:" , ethers . formatEther ( rewardsETH ), "ETH" );
function rewardsBalance ( address asset ) external view returns ( uint256 );
Inflow Handling
These methods are called automatically by the Agents contract when stakes are slashed or early exit penalties are collected.
handleSlash
Process slashed stake from an agent. Only callable by addresses with INFLOW_ROLE (typically the Agents contract).
Agent ID that was slashed
For ETH, must equal amount; for ERC20, must be 0
Distribution:
Splits amount according to configured distribution ratios across three pools.
Events Emitted:
event SlashHandled (
uint256 indexed agentId ,
address indexed asset ,
uint256 amount ,
uint256 treasuryShare ,
uint256 insuranceShare ,
uint256 rewardsShare
);
Example:
// This is typically called by Agents contract, not directly
// But can be called by any address with INFLOW_ROLE
// For ETH
await treasury . handleSlash (
agentId ,
ethers . ZeroAddress ,
ethers . parseEther ( "1.0" ),
{ value: ethers . parseEther ( "1.0" ) }
);
// For ERC20 (token must be transferred first)
const token = new ethers . Contract ( tokenAddress , erc20ABI , signer );
await token . transfer ( treasuryAddress , amount );
await treasury . handleSlash ( agentId , tokenAddress , amount );
function handleSlash (
uint256 agentId ,
address asset ,
uint256 amount
) external payable onlyRole ( INFLOW_ROLE );
handleEarlyExitPenalty
Process early withdrawal penalty from an agent. Only callable by INFLOW_ROLE.
Agent ID that paid penalty
Events Emitted:
event EarlyExitHandled (
uint256 indexed agentId ,
address indexed asset ,
uint256 amount ,
uint256 treasuryShare ,
uint256 insuranceShare ,
uint256 rewardsShare
);
Example:
// Called by Agents contract when agent forces early withdrawal
await treasury . handleEarlyExitPenalty (
agentId ,
ethers . ZeroAddress ,
penaltyAmount ,
{ value: penaltyAmount }
);
function handleEarlyExitPenalty (
uint256 agentId ,
address asset ,
uint256 amount
) external payable onlyRole ( INFLOW_ROLE );
recordRewardDeposit
Record a direct deposit to the rewards pool (e.g., from Tasks contract or external contributions).
For ETH, must equal amount; for ERC20, tokens must be transferred before calling
Events Emitted:
event RewardsDeposited (
address indexed source ,
address indexed asset ,
uint256 amount
);
Example:
JavaScript (ETH)
JavaScript (ERC20)
Python (ETH)
Solidity
// Anyone can deposit rewards
const depositAmount = ethers . parseEther ( "10.0" );
const tx = await treasury . recordRewardDeposit (
ethers . ZeroAddress ,
depositAmount ,
{ value: depositAmount }
);
await tx . wait ();
console . log ( "Deposited 10 ETH to rewards pool" );
Reward Distribution
distributeReward
Distribute rewards from rewards pool to an agent. Only callable by REWARDS_ROLE.
Amount to distribute (must be ≤ rewards balance)
Address to send rewards (address(0) = agent owner)
Human-readable reason for distribution
Requirements:
Agent must be registered
Sufficient balance in rewards pool
Caller must have REWARDS_ROLE
Events Emitted:
event RewardPaid (
uint256 indexed agentId ,
address indexed asset ,
address indexed recipient ,
uint256 amount ,
string reason
);
Errors:
AgentNotKnown(uint256) - Agent not registered
InsufficientRewards(address asset, uint256 requested, uint256 available) - Not enough rewards
Example:
// Distribute rewards to agent owner
await treasury . distributeReward (
agentId ,
ethers . ZeroAddress , // ETH
ethers . parseEther ( "0.5" ),
ethers . ZeroAddress , // Send to agent owner
"Monthly performance bonus for high-quality inferences"
);
// Distribute to specific address
await treasury . distributeReward (
agentId ,
tokenAddress ,
ethers . parseUnits ( "100" , 6 ), // 100 USDC
"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" ,
"Reward for completing 1000 verified tasks"
);
Pool Withdrawals
withdrawTreasury
Withdraw from treasury pool. Only callable by WITHDRAW_ROLE.
Amount to withdraw (must be ≤ treasury balance)
Recipient address (cannot be zero address)
Events Emitted:
event PoolWithdrawn (
bytes32 indexed pool ,
address indexed asset ,
address indexed to ,
uint256 amount
);
Example:
// Withdraw ETH from treasury pool
await treasury . withdrawTreasury (
ethers . ZeroAddress ,
ethers . parseEther ( "100.0" ),
treasuryMultisigAddress
);
// Withdraw USDC from treasury pool
await treasury . withdrawTreasury (
usdcAddress ,
ethers . parseUnits ( "50000" , 6 ),
treasuryMultisigAddress
);
function withdrawTreasury (
address asset ,
uint256 amount ,
address to
) external nonReentrant onlyRole ( WITHDRAW_ROLE );
withdrawInsurance
Withdraw from insurance pool. Only callable by WITHDRAW_ROLE.
Amount to withdraw (must be ≤ insurance balance)
Events Emitted:
event PoolWithdrawn (
bytes32 indexed pool ,
address indexed asset ,
address indexed to ,
uint256 amount
);
Example:
// Withdraw from insurance pool for coverage payout
await treasury . withdrawInsurance (
ethers . ZeroAddress ,
ethers . parseEther ( "10.0" ),
insuranceClaimantAddress
);
function withdrawInsurance (
address asset ,
uint256 amount ,
address to
) external nonReentrant onlyRole ( WITHDRAW_ROLE );
Configuration
setDistribution
Update pool distribution ratios. Only callable by DEFAULT_ADMIN_ROLE.
Treasury share in basis points (0-10000)
Insurance share in basis points (0-10000)
Rewards share in basis points (0-10000)
Requirements:
Sum must equal 10,000 (100%)
Events Emitted:
event DistributionUpdated (
uint16 treasuryBps ,
uint16 insuranceBps ,
uint16 rewardsBps
);
Errors:
InvalidBps() - Sum does not equal 10,000
Example:
// Update distribution to:
// - Treasury: 50%
// - Insurance: 25%
// - Rewards: 25%
await treasury . setDistribution (
5000 , // 50%
2500 , // 25%
2500 // 25%
);
console . log ( "Distribution updated" );
setAgents
Update Agents contract address. Only callable by DEFAULT_ADMIN_ROLE.
New Agents contract address (cannot be zero)
Example:
await treasury . setAgents ( newAgentsAddress );
function setAgents ( address newAgents )
external
onlyRole ( DEFAULT_ADMIN_ROLE );
Query Methods
distribution
DistributionConfig public distribution;
Get current distribution configuration.
Example:
const dist = await treasury . distribution ();
console . log ( "Distribution:" );
console . log ( " Treasury:" , dist . treasuryBps / 100 , "%" );
console . log ( " Insurance:" , dist . insuranceBps / 100 , "%" );
console . log ( " Rewards:" , dist . rewardsBps / 100 , "%" );
agents
IAgentsRegistry public agents;
Get Agents contract address.
Complete Example: Reward Distribution Campaign
Here’s a complete example of distributing rewards to top-performing agents:
import { ethers } from 'ethers' ;
// Setup
const provider = new ethers . JsonRpcProvider ( 'https://testnet-rpc.nex-t1.ai' );
const rewardsManager = new ethers . Wallet ( privateKey , provider );
const treasuryAddress = '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0' ;
const agentsAddress = '0x5FbDB2315678afecb367f032d93F642f64180aa3' ;
const treasury = new ethers . Contract ( treasuryAddress , treasuryABI , rewardsManager );
const agents = new ethers . Contract ( agentsAddress , agentsABI , rewardsManager );
// ========================================
// Step 1: Check available rewards
// ========================================
console . log ( "Step 1: Checking available rewards..." );
const availableRewards = await treasury . rewardsBalance ( ethers . ZeroAddress );
console . log ( "Available ETH rewards:" , ethers . formatEther ( availableRewards ));
if ( availableRewards < ethers . parseEther ( "10.0" )) {
console . log ( "Insufficient rewards, depositing more..." );
await treasury . recordRewardDeposit (
ethers . ZeroAddress ,
ethers . parseEther ( "50.0" ),
{ value: ethers . parseEther ( "50.0" ) }
);
}
// ========================================
// Step 2: Get top agents by reputation
// ========================================
console . log ( " \n Step 2: Identifying top agents..." );
const agentCount = 100 ;
const agentList = await agents . listAgents ( 0 , agentCount );
// Sort by reputation
const topAgents = agentList
. sort (( a , b ) => Number ( b . weightedReputation ) - Number ( a . weightedReputation ))
. slice ( 0 , 10 );
console . log ( "Top 10 agents by reputation:" );
topAgents . forEach (( agent , i ) => {
console . log ( ` ${ i + 1 } . Agent ${ agent . agentId } - Reputation: ${ agent . weightedReputation } ` );
});
// ========================================
// Step 3: Calculate reward distribution
// ========================================
console . log ( " \n Step 3: Calculating rewards..." );
const totalRewardPool = ethers . parseEther ( "10.0" ); // 10 ETH total
const reputationSum = topAgents . reduce (( sum , agent ) =>
sum + Number ( agent . weightedReputation ), 0
);
const distributions = topAgents . map ( agent => ({
agentId: agent . agentId ,
owner: agent . owner ,
reputation: agent . weightedReputation ,
reward: ( totalRewardPool * BigInt ( agent . weightedReputation )) / BigInt ( reputationSum )
}));
// ========================================
// Step 4: Distribute rewards
// ========================================
console . log ( " \n Step 4: Distributing rewards..." );
for ( const dist of distributions ) {
console . log ( ` \n Distributing to agent ${ dist . agentId } ...` );
console . log ( ` Owner: ${ dist . owner } ` );
console . log ( ` Reputation: ${ dist . reputation } ` );
console . log ( ` Reward: ${ ethers . formatEther ( dist . reward ) } ETH` );
const tx = await treasury . distributeReward (
dist . agentId ,
ethers . ZeroAddress ,
dist . reward ,
ethers . ZeroAddress , // Send to agent owner
`Q1 2024 Performance Reward - Top 10 by reputation`
);
await tx . wait ();
console . log ( ` ✓ Distributed!` );
}
// ========================================
// Step 5: Verify final balances
// ========================================
console . log ( " \n\n Step 5: Verifying final state..." );
const finalRewards = await treasury . rewardsBalance ( ethers . ZeroAddress );
console . log ( "Remaining rewards:" , ethers . formatEther ( finalRewards ), "ETH" );
const pools = await treasury . poolBalances ( ethers . ZeroAddress );
console . log ( " \n Pool balances:" );
console . log ( " Treasury:" , ethers . formatEther ( pools . treasury ), "ETH" );
console . log ( " Insurance:" , ethers . formatEther ( pools . insurance ), "ETH" );
console . log ( " Rewards:" , ethers . formatEther ( pools . rewards ), "ETH" );
console . log ( " \n ✅ Reward distribution complete!" );
Events Reference
event DistributionUpdated (
uint16 treasuryBps ,
uint16 insuranceBps ,
uint16 rewardsBps
);
event SlashHandled (
uint256 indexed agentId ,
address indexed asset ,
uint256 amount ,
uint256 treasuryShare ,
uint256 insuranceShare ,
uint256 rewardsShare
);
event EarlyExitHandled (
uint256 indexed agentId ,
address indexed asset ,
uint256 amount ,
uint256 treasuryShare ,
uint256 insuranceShare ,
uint256 rewardsShare
);
event RewardsDeposited (
address indexed source ,
address indexed asset ,
uint256 amount
);
event RewardPaid (
uint256 indexed agentId ,
address indexed asset ,
address indexed recipient ,
uint256 amount ,
string reason
);
event PoolWithdrawn (
bytes32 indexed pool ,
address indexed asset ,
address indexed to ,
uint256 amount
);
Error Reference
error InvalidBps ();
error InvalidAddress ();
error InsufficientRewards ( address asset, uint256 requested, uint256 available);
error AgentNotKnown ( uint256 agentId);
Treasury Economics
Pool Purpose
Treasury Pool Protocol development, operations, grants, and strategic initiatives
Insurance Pool Coverage for black swan events, protocol bugs, or agent failures
Rewards Pool Performance incentives, bounties, and community rewards
Inflow Sources
Source Default Distribution Notes Slashed Stakes 40% / 30% / 30% From agent misbehavior Early Exit Penalties 40% / 30% / 30% From early withdrawals Direct Deposits 0% / 0% / 100% Goes entirely to rewards Task Refunds 0% / 0% / 100% Disputed tasks
Best Practices
Adjust based on protocol needs and growth stage
Early stage: Higher rewards % for agent adoption
Mature stage: Higher treasury % for sustainability
Always maintain adequate insurance reserves
Document clear criteria for rewards
Use transparent, objective metrics (reputation, tasks completed)
Consider time-based campaigns (monthly, quarterly)
Batch distributions to save gas costs
Monitor pool balances regularly
Set thresholds for pool withdrawals
Use multi-sig for WITHDRAW_ROLE
Document all withdrawal purposes
Integration with Other Contracts
The Treasury contract is called by:
Agents Contract :
handleSlash() - When stakes are slashed
handleEarlyExitPenalty() - When early withdrawals occur
Tasks Contract :
recordRewardDeposit() - When disputed tasks are resolved without refund
External Contributors :
recordRewardDeposit() - For direct reward contributions