a
This commit is contained in:
parent
162227dc71
commit
3c9470abba
3 changed files with 912 additions and 0 deletions
|
|
@ -0,0 +1,242 @@
|
||||||
|
# Milestone 1: Pricing Oracle
|
||||||
|
|
||||||
|
> Build decentralized price feeds for SYNOR/USD conversion, enabling stable pricing for L2 services.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Pricing Oracle aggregates price data from multiple sources to provide reliable SYNOR/USD rates for service pricing. This ensures users can estimate costs in familiar fiat terms while paying in SYNOR tokens.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### 1.1 Price Feed Aggregator
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// crates/synor-economics/src/oracle/price_feed.rs
|
||||||
|
|
||||||
|
/// Price source for SYNOR/USD rates
|
||||||
|
pub enum PriceSource {
|
||||||
|
/// On-chain DEX (Synor DEX, Uniswap, etc.)
|
||||||
|
Dex { pool_address: Address, pair: TradingPair },
|
||||||
|
/// Centralized exchange API
|
||||||
|
Cex { exchange: String, api_endpoint: String },
|
||||||
|
/// Chainlink-style oracle
|
||||||
|
Chainlink { feed_address: Address },
|
||||||
|
/// Off-chain oracle (signed by trusted nodes)
|
||||||
|
OffChain { oracle_id: OracleId },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Aggregated price with metadata
|
||||||
|
pub struct AggregatedPrice {
|
||||||
|
/// SYNOR/USD price (6 decimal places)
|
||||||
|
pub price: u64,
|
||||||
|
/// Unix timestamp
|
||||||
|
pub timestamp: u64,
|
||||||
|
/// Sources used
|
||||||
|
pub sources: Vec<PriceSource>,
|
||||||
|
/// Confidence (0-100)
|
||||||
|
pub confidence: u8,
|
||||||
|
/// 24h high
|
||||||
|
pub high_24h: u64,
|
||||||
|
/// 24h low
|
||||||
|
pub low_24h: u64,
|
||||||
|
/// 24h volume
|
||||||
|
pub volume_24h: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PriceFeedAggregator {
|
||||||
|
sources: Vec<PriceSource>,
|
||||||
|
cache: LruCache<TradingPair, AggregatedPrice>,
|
||||||
|
update_interval_ms: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PriceFeedAggregator {
|
||||||
|
/// Get current price with aggregation across sources
|
||||||
|
pub async fn get_price(&self, pair: &TradingPair) -> Result<AggregatedPrice, OracleError> {
|
||||||
|
// Fetch from all sources in parallel
|
||||||
|
let prices = futures::future::join_all(
|
||||||
|
self.sources.iter().map(|s| self.fetch_from_source(s, pair))
|
||||||
|
).await;
|
||||||
|
|
||||||
|
// Filter out failures and outliers
|
||||||
|
let valid_prices = self.filter_outliers(prices);
|
||||||
|
|
||||||
|
// Calculate median price
|
||||||
|
let median = self.calculate_median(&valid_prices);
|
||||||
|
|
||||||
|
// Calculate confidence based on source agreement
|
||||||
|
let confidence = self.calculate_confidence(&valid_prices, median);
|
||||||
|
|
||||||
|
Ok(AggregatedPrice {
|
||||||
|
price: median,
|
||||||
|
timestamp: current_timestamp(),
|
||||||
|
sources: self.sources.clone(),
|
||||||
|
confidence,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Time-Weighted Average Price (TWAP)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// crates/synor-economics/src/oracle/twap.rs
|
||||||
|
|
||||||
|
/// TWAP calculator for stable pricing
|
||||||
|
pub struct TwapCalculator {
|
||||||
|
/// Price observations
|
||||||
|
observations: VecDeque<PriceObservation>,
|
||||||
|
/// TWAP window (e.g., 1 hour)
|
||||||
|
window_seconds: u64,
|
||||||
|
/// Minimum observations required
|
||||||
|
min_observations: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PriceObservation {
|
||||||
|
pub price: u64,
|
||||||
|
pub timestamp: u64,
|
||||||
|
pub cumulative_price: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TwapCalculator {
|
||||||
|
/// Calculate TWAP over the configured window
|
||||||
|
pub fn calculate_twap(&self) -> Option<u64> {
|
||||||
|
if self.observations.len() < self.min_observations {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldest = self.observations.front()?;
|
||||||
|
let newest = self.observations.back()?;
|
||||||
|
|
||||||
|
let time_elapsed = newest.timestamp - oldest.timestamp;
|
||||||
|
if time_elapsed == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cumulative_diff = newest.cumulative_price - oldest.cumulative_price;
|
||||||
|
Some((cumulative_diff / time_elapsed as u128) as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add new price observation
|
||||||
|
pub fn observe(&mut self, price: u64) {
|
||||||
|
let timestamp = current_timestamp();
|
||||||
|
let cumulative = self.observations
|
||||||
|
.back()
|
||||||
|
.map(|o| o.cumulative_price + (price as u128 * (timestamp - o.timestamp) as u128))
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
self.observations.push_back(PriceObservation {
|
||||||
|
price,
|
||||||
|
timestamp,
|
||||||
|
cumulative_price: cumulative,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove old observations outside window
|
||||||
|
let cutoff = timestamp - self.window_seconds;
|
||||||
|
while self.observations.front().map(|o| o.timestamp < cutoff).unwrap_or(false) {
|
||||||
|
self.observations.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Oracle Smart Contract
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// crates/synor-economics/src/oracle/contract.rs
|
||||||
|
|
||||||
|
/// On-chain oracle contract for price storage
|
||||||
|
pub struct OracleContract {
|
||||||
|
/// Current price (6 decimals)
|
||||||
|
pub price: u64,
|
||||||
|
/// Last update timestamp
|
||||||
|
pub updated_at: u64,
|
||||||
|
/// Authorized updaters
|
||||||
|
pub updaters: HashSet<Address>,
|
||||||
|
/// TWAP values
|
||||||
|
pub twap_1h: u64,
|
||||||
|
pub twap_24h: u64,
|
||||||
|
/// Deviation threshold for updates (basis points)
|
||||||
|
pub deviation_threshold_bps: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OracleContract {
|
||||||
|
/// Update price (only authorized updaters)
|
||||||
|
pub fn update_price(
|
||||||
|
&mut self,
|
||||||
|
caller: Address,
|
||||||
|
new_price: u64,
|
||||||
|
sources: Vec<SignedPriceAttestation>,
|
||||||
|
) -> Result<(), OracleError> {
|
||||||
|
// Verify caller is authorized
|
||||||
|
if !self.updaters.contains(&caller) {
|
||||||
|
return Err(OracleError::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify attestations
|
||||||
|
let verified_prices = self.verify_attestations(&sources)?;
|
||||||
|
|
||||||
|
// Check consensus among sources
|
||||||
|
let median = self.calculate_median(&verified_prices);
|
||||||
|
|
||||||
|
// Check deviation from current price
|
||||||
|
let deviation = self.calculate_deviation(self.price, median);
|
||||||
|
if deviation < self.deviation_threshold_bps {
|
||||||
|
return Ok(()); // No significant change
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update price
|
||||||
|
self.price = median;
|
||||||
|
self.updated_at = current_timestamp();
|
||||||
|
|
||||||
|
// Emit event
|
||||||
|
emit_event(PriceUpdatedEvent { price: median, timestamp: self.updated_at });
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] Implement PriceFeedAggregator with multiple source support
|
||||||
|
- [ ] Implement TWAP calculator with configurable windows
|
||||||
|
- [ ] Deploy oracle smart contract to testnet
|
||||||
|
- [ ] Set up price updater nodes
|
||||||
|
- [ ] Integrate with DEX liquidity pools
|
||||||
|
- [ ] Add CEX price feed support (optional)
|
||||||
|
- [ ] Implement price staleness detection
|
||||||
|
- [ ] Add circuit breaker for extreme volatility
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run oracle tests
|
||||||
|
cargo test --package synor-economics --lib oracle
|
||||||
|
|
||||||
|
# Check price feed connectivity
|
||||||
|
synor oracle status
|
||||||
|
|
||||||
|
# Get current SYNOR/USD price
|
||||||
|
synor oracle price SYNOR/USD
|
||||||
|
|
||||||
|
# Check TWAP values
|
||||||
|
synor oracle twap SYNOR/USD --window 1h
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- **Manipulation Resistance**: TWAP smooths out flash loan attacks
|
||||||
|
- **Source Diversity**: Multiple independent sources prevent single point of failure
|
||||||
|
- **Deviation Limits**: Large price swings require multiple confirmations
|
||||||
|
- **Staleness Detection**: Alert when prices haven't updated recently
|
||||||
|
|
@ -0,0 +1,483 @@
|
||||||
|
# Milestone 3: Billing Engine
|
||||||
|
|
||||||
|
> Generate invoices, process payments, and manage account credits.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Billing Engine transforms metered usage into invoices, processes SYNOR payments, and manages prepaid balances and credits.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### 3.1 Invoice Generator
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// crates/synor-economics/src/billing/invoice.rs
|
||||||
|
|
||||||
|
/// Invoice for a billing period
|
||||||
|
pub struct Invoice {
|
||||||
|
/// Invoice ID
|
||||||
|
pub invoice_id: InvoiceId,
|
||||||
|
/// Account ID
|
||||||
|
pub account_id: AccountId,
|
||||||
|
/// Billing period
|
||||||
|
pub period_start: u64,
|
||||||
|
pub period_end: u64,
|
||||||
|
/// Line items
|
||||||
|
pub line_items: Vec<LineItem>,
|
||||||
|
/// Subtotal before discounts
|
||||||
|
pub subtotal_micro_synor: u64,
|
||||||
|
/// Applied discounts
|
||||||
|
pub discounts: Vec<AppliedDiscount>,
|
||||||
|
/// Credits applied
|
||||||
|
pub credits_applied: u64,
|
||||||
|
/// Total due
|
||||||
|
pub total_due_micro_synor: u64,
|
||||||
|
/// Invoice status
|
||||||
|
pub status: InvoiceStatus,
|
||||||
|
/// Due date
|
||||||
|
pub due_date: u64,
|
||||||
|
/// Payment transaction (if paid)
|
||||||
|
pub payment_tx: Option<TxId>,
|
||||||
|
/// Created at
|
||||||
|
pub created_at: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LineItem {
|
||||||
|
pub service: ServiceType,
|
||||||
|
pub resource: ResourceType,
|
||||||
|
pub description: String,
|
||||||
|
pub quantity: u64,
|
||||||
|
pub unit: MeteringUnit,
|
||||||
|
pub unit_price_micro_synor: u64,
|
||||||
|
pub amount_micro_synor: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppliedDiscount {
|
||||||
|
pub discount_type: DiscountType,
|
||||||
|
pub description: String,
|
||||||
|
pub amount_micro_synor: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DiscountType {
|
||||||
|
/// Volume discount based on usage tier
|
||||||
|
Volume { tier: u8 },
|
||||||
|
/// Commitment discount (reserved capacity)
|
||||||
|
Commitment { commitment_id: CommitmentId },
|
||||||
|
/// Promotional credit
|
||||||
|
Promotional { promo_code: String },
|
||||||
|
/// Referral credit
|
||||||
|
Referral { referrer_id: AccountId },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum InvoiceStatus {
|
||||||
|
Draft,
|
||||||
|
Finalized,
|
||||||
|
Sent,
|
||||||
|
Paid,
|
||||||
|
Overdue,
|
||||||
|
Voided,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InvoiceGenerator {
|
||||||
|
metering: UsageAggregator,
|
||||||
|
pricing: PricingEngine,
|
||||||
|
discounts: DiscountEngine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InvoiceGenerator {
|
||||||
|
/// Generate invoice for an account
|
||||||
|
pub async fn generate_invoice(
|
||||||
|
&self,
|
||||||
|
account: &AccountId,
|
||||||
|
period_start: u64,
|
||||||
|
period_end: u64,
|
||||||
|
) -> Result<Invoice, BillingError> {
|
||||||
|
// Get usage summary
|
||||||
|
let usage = self.metering.get_usage_summary(account, period_start, period_end).await?;
|
||||||
|
|
||||||
|
// Convert to line items
|
||||||
|
let line_items = self.usage_to_line_items(&usage);
|
||||||
|
|
||||||
|
// Calculate subtotal
|
||||||
|
let subtotal = line_items.iter().map(|li| li.amount_micro_synor).sum();
|
||||||
|
|
||||||
|
// Apply discounts
|
||||||
|
let discounts = self.discounts.calculate_discounts(account, &line_items).await?;
|
||||||
|
let discount_total: u64 = discounts.iter().map(|d| d.amount_micro_synor).sum();
|
||||||
|
|
||||||
|
// Apply credits
|
||||||
|
let available_credits = self.get_available_credits(account).await?;
|
||||||
|
let credits_applied = std::cmp::min(available_credits, subtotal - discount_total);
|
||||||
|
|
||||||
|
// Calculate total
|
||||||
|
let total_due = subtotal.saturating_sub(discount_total).saturating_sub(credits_applied);
|
||||||
|
|
||||||
|
Ok(Invoice {
|
||||||
|
invoice_id: InvoiceId::new(),
|
||||||
|
account_id: account.clone(),
|
||||||
|
period_start,
|
||||||
|
period_end,
|
||||||
|
line_items,
|
||||||
|
subtotal_micro_synor: subtotal,
|
||||||
|
discounts,
|
||||||
|
credits_applied,
|
||||||
|
total_due_micro_synor: total_due,
|
||||||
|
status: InvoiceStatus::Draft,
|
||||||
|
due_date: period_end + 30 * 24 * 3600, // 30 days
|
||||||
|
payment_tx: None,
|
||||||
|
created_at: current_timestamp(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Payment Processor
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// crates/synor-economics/src/billing/payment.rs
|
||||||
|
|
||||||
|
/// Payment processor for SYNOR token payments
|
||||||
|
pub struct PaymentProcessor {
|
||||||
|
/// Synor blockchain client
|
||||||
|
blockchain: SynorClient,
|
||||||
|
/// Billing contract address
|
||||||
|
billing_contract: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PaymentRequest {
|
||||||
|
pub invoice_id: InvoiceId,
|
||||||
|
pub amount_micro_synor: u64,
|
||||||
|
pub payer: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PaymentResult {
|
||||||
|
pub payment_id: PaymentId,
|
||||||
|
pub tx_id: TxId,
|
||||||
|
pub amount_micro_synor: u64,
|
||||||
|
pub timestamp: u64,
|
||||||
|
pub status: PaymentStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PaymentStatus {
|
||||||
|
Pending,
|
||||||
|
Confirmed,
|
||||||
|
Failed { reason: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaymentProcessor {
|
||||||
|
/// Process payment for an invoice
|
||||||
|
pub async fn process_payment(
|
||||||
|
&self,
|
||||||
|
request: PaymentRequest,
|
||||||
|
) -> Result<PaymentResult, PaymentError> {
|
||||||
|
// Verify invoice exists and is payable
|
||||||
|
let invoice = self.get_invoice(&request.invoice_id).await?;
|
||||||
|
if invoice.status != InvoiceStatus::Finalized && invoice.status != InvoiceStatus::Sent {
|
||||||
|
return Err(PaymentError::InvoiceNotPayable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build payment transaction
|
||||||
|
let tx = self.blockchain.build_transaction(
|
||||||
|
TransactionType::Transfer {
|
||||||
|
to: self.billing_contract,
|
||||||
|
amount: request.amount_micro_synor,
|
||||||
|
memo: format!("Invoice payment: {}", request.invoice_id),
|
||||||
|
},
|
||||||
|
request.payer,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Submit and wait for confirmation
|
||||||
|
let tx_id = self.blockchain.submit_transaction(tx).await?;
|
||||||
|
let confirmation = self.blockchain.wait_for_confirmation(&tx_id).await?;
|
||||||
|
|
||||||
|
// Update invoice status
|
||||||
|
self.mark_invoice_paid(&request.invoice_id, &tx_id).await?;
|
||||||
|
|
||||||
|
Ok(PaymentResult {
|
||||||
|
payment_id: PaymentId::new(),
|
||||||
|
tx_id,
|
||||||
|
amount_micro_synor: request.amount_micro_synor,
|
||||||
|
timestamp: current_timestamp(),
|
||||||
|
status: PaymentStatus::Confirmed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up auto-pay for an account
|
||||||
|
pub async fn setup_auto_pay(
|
||||||
|
&self,
|
||||||
|
account: &AccountId,
|
||||||
|
funding_source: Address,
|
||||||
|
max_amount_per_invoice: u64,
|
||||||
|
) -> Result<AutoPayConfig, PaymentError> {
|
||||||
|
// Verify funding source has sufficient balance/allowance
|
||||||
|
// Store auto-pay configuration
|
||||||
|
Ok(AutoPayConfig {
|
||||||
|
account_id: account.clone(),
|
||||||
|
funding_source,
|
||||||
|
max_amount_per_invoice,
|
||||||
|
enabled: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Credit System
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// crates/synor-economics/src/billing/credit.rs
|
||||||
|
|
||||||
|
/// Account credit management
|
||||||
|
pub struct CreditManager {
|
||||||
|
storage: CreditStorage,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AccountCredit {
|
||||||
|
pub account_id: AccountId,
|
||||||
|
pub balance_micro_synor: u64,
|
||||||
|
pub credits: Vec<CreditEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CreditEntry {
|
||||||
|
pub credit_id: CreditId,
|
||||||
|
pub amount_micro_synor: u64,
|
||||||
|
pub remaining_micro_synor: u64,
|
||||||
|
pub source: CreditSource,
|
||||||
|
pub expires_at: Option<u64>,
|
||||||
|
pub created_at: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CreditSource {
|
||||||
|
/// Prepaid balance deposit
|
||||||
|
Prepaid { tx_id: TxId },
|
||||||
|
/// Promotional credit
|
||||||
|
Promotional { campaign: String },
|
||||||
|
/// Referral bonus
|
||||||
|
Referral { referred_by: AccountId },
|
||||||
|
/// SLA credit (compensation)
|
||||||
|
SlaCredit { incident_id: String },
|
||||||
|
/// Manual adjustment
|
||||||
|
Manual { reason: String, admin: Address },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreditManager {
|
||||||
|
/// Add credits to an account
|
||||||
|
pub async fn add_credits(
|
||||||
|
&self,
|
||||||
|
account: &AccountId,
|
||||||
|
amount: u64,
|
||||||
|
source: CreditSource,
|
||||||
|
expires_at: Option<u64>,
|
||||||
|
) -> Result<CreditEntry, CreditError> {
|
||||||
|
let entry = CreditEntry {
|
||||||
|
credit_id: CreditId::new(),
|
||||||
|
amount_micro_synor: amount,
|
||||||
|
remaining_micro_synor: amount,
|
||||||
|
source,
|
||||||
|
expires_at,
|
||||||
|
created_at: current_timestamp(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.storage.add_credit(account, &entry).await?;
|
||||||
|
Ok(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get available credit balance
|
||||||
|
pub async fn get_balance(&self, account: &AccountId) -> Result<u64, CreditError> {
|
||||||
|
let credits = self.storage.get_credits(account).await?;
|
||||||
|
let now = current_timestamp();
|
||||||
|
|
||||||
|
let balance = credits.iter()
|
||||||
|
.filter(|c| c.expires_at.map(|e| e > now).unwrap_or(true))
|
||||||
|
.map(|c| c.remaining_micro_synor)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
Ok(balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply credits to an invoice
|
||||||
|
pub async fn apply_credits(
|
||||||
|
&self,
|
||||||
|
account: &AccountId,
|
||||||
|
amount: u64,
|
||||||
|
invoice_id: &InvoiceId,
|
||||||
|
) -> Result<u64, CreditError> {
|
||||||
|
let mut credits = self.storage.get_credits(account).await?;
|
||||||
|
let now = current_timestamp();
|
||||||
|
|
||||||
|
// Sort by expiration (FIFO for expiring credits)
|
||||||
|
credits.sort_by_key(|c| c.expires_at.unwrap_or(u64::MAX));
|
||||||
|
|
||||||
|
let mut remaining = amount;
|
||||||
|
let mut applied = 0u64;
|
||||||
|
|
||||||
|
for credit in credits.iter_mut() {
|
||||||
|
if remaining == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip expired credits
|
||||||
|
if credit.expires_at.map(|e| e <= now).unwrap_or(false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let apply_amount = std::cmp::min(credit.remaining_micro_synor, remaining);
|
||||||
|
credit.remaining_micro_synor -= apply_amount;
|
||||||
|
remaining -= apply_amount;
|
||||||
|
applied += apply_amount;
|
||||||
|
|
||||||
|
// Record usage
|
||||||
|
self.storage.record_credit_usage(
|
||||||
|
&credit.credit_id,
|
||||||
|
apply_amount,
|
||||||
|
invoice_id,
|
||||||
|
).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(applied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Prepaid Balance
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// crates/synor-economics/src/billing/prepaid.rs
|
||||||
|
|
||||||
|
/// Prepaid balance management (pay-as-you-go with deposits)
|
||||||
|
pub struct PrepaidManager {
|
||||||
|
credits: CreditManager,
|
||||||
|
blockchain: SynorClient,
|
||||||
|
deposit_address: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrepaidManager {
|
||||||
|
/// Deposit SYNOR to prepaid balance
|
||||||
|
pub async fn deposit(
|
||||||
|
&self,
|
||||||
|
account: &AccountId,
|
||||||
|
amount: u64,
|
||||||
|
from: Address,
|
||||||
|
) -> Result<DepositResult, PrepaidError> {
|
||||||
|
// Build deposit transaction
|
||||||
|
let tx = self.blockchain.build_transaction(
|
||||||
|
TransactionType::Transfer {
|
||||||
|
to: self.deposit_address,
|
||||||
|
amount,
|
||||||
|
memo: format!("Prepaid deposit: {}", account),
|
||||||
|
},
|
||||||
|
from,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Submit and confirm
|
||||||
|
let tx_id = self.blockchain.submit_transaction(tx).await?;
|
||||||
|
self.blockchain.wait_for_confirmation(&tx_id).await?;
|
||||||
|
|
||||||
|
// Add to credits
|
||||||
|
self.credits.add_credits(
|
||||||
|
account,
|
||||||
|
amount,
|
||||||
|
CreditSource::Prepaid { tx_id: tx_id.clone() },
|
||||||
|
None, // Prepaid credits don't expire
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
Ok(DepositResult {
|
||||||
|
tx_id,
|
||||||
|
amount,
|
||||||
|
new_balance: self.credits.get_balance(account).await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if account has sufficient balance
|
||||||
|
pub async fn has_sufficient_balance(
|
||||||
|
&self,
|
||||||
|
account: &AccountId,
|
||||||
|
required: u64,
|
||||||
|
) -> Result<bool, PrepaidError> {
|
||||||
|
let balance = self.credits.get_balance(account).await?;
|
||||||
|
Ok(balance >= required)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deduct from prepaid balance (for real-time billing)
|
||||||
|
pub async fn deduct(
|
||||||
|
&self,
|
||||||
|
account: &AccountId,
|
||||||
|
amount: u64,
|
||||||
|
reason: &str,
|
||||||
|
) -> Result<DeductResult, PrepaidError> {
|
||||||
|
let balance = self.credits.get_balance(account).await?;
|
||||||
|
|
||||||
|
if balance < amount {
|
||||||
|
return Err(PrepaidError::InsufficientBalance {
|
||||||
|
available: balance,
|
||||||
|
required: amount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temporary invoice for the deduction
|
||||||
|
let invoice_id = InvoiceId::new_instant();
|
||||||
|
self.credits.apply_credits(account, amount, &invoice_id).await?;
|
||||||
|
|
||||||
|
Ok(DeductResult {
|
||||||
|
amount_deducted: amount,
|
||||||
|
remaining_balance: self.credits.get_balance(account).await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] Implement Invoice data model and generator
|
||||||
|
- [ ] Build PaymentProcessor for SYNOR payments
|
||||||
|
- [ ] Implement CreditManager for account credits
|
||||||
|
- [ ] Build PrepaidManager for deposit-based billing
|
||||||
|
- [ ] Create billing smart contract
|
||||||
|
- [ ] Implement auto-pay functionality
|
||||||
|
- [ ] Add invoice email/notification system
|
||||||
|
- [ ] Build billing dashboard API
|
||||||
|
- [ ] Implement usage alerts and spending limits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check account balance
|
||||||
|
synor billing balance
|
||||||
|
|
||||||
|
# Get current invoice
|
||||||
|
synor billing invoice --current
|
||||||
|
|
||||||
|
# View invoice history
|
||||||
|
synor billing invoices --limit 10
|
||||||
|
|
||||||
|
# Add prepaid balance
|
||||||
|
synor billing deposit 100 SYNOR
|
||||||
|
|
||||||
|
# Set up auto-pay
|
||||||
|
synor billing auto-pay enable --max 500
|
||||||
|
|
||||||
|
# Export usage report
|
||||||
|
synor billing export --format csv --period 2026-01
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/billing/balance - Get account balance
|
||||||
|
GET /api/v1/billing/invoices - List invoices
|
||||||
|
GET /api/v1/billing/invoices/:id - Get invoice details
|
||||||
|
POST /api/v1/billing/invoices/:id/pay - Pay invoice
|
||||||
|
GET /api/v1/billing/usage - Get usage summary
|
||||||
|
POST /api/v1/billing/deposit - Deposit prepaid balance
|
||||||
|
GET /api/v1/billing/credits - List credits
|
||||||
|
POST /api/v1/billing/auto-pay - Configure auto-pay
|
||||||
|
```
|
||||||
187
docs/PLAN/PHASE12-EconomicsBilling/README.md
Normal file
187
docs/PLAN/PHASE12-EconomicsBilling/README.md
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
# Phase 12: Economics & Billing
|
||||||
|
|
||||||
|
> **Mission**: Build a comprehensive billing infrastructure to monetize Synor's L2 services (Storage, Hosting, Database, Compute) with real-time metering, pricing oracles, and transparent cost management.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
Phase 12 creates the economic backbone for Synor's service ecosystem:
|
||||||
|
- **Real-time Metering**: Track resource usage across all L2 services
|
||||||
|
- **Pricing Oracles**: Dynamic SYNOR/USD price feeds for stable pricing
|
||||||
|
- **Billing Engine**: Usage-based invoicing and payment processing
|
||||||
|
- **Cost Calculator**: CLI and API tools for cost estimation
|
||||||
|
- **Revenue Distribution**: Automatic fee distribution to stakeholders
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ SYNOR ECONOMICS LAYER │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ USER INTERFACE LAYER │ │
|
||||||
|
│ ├──────────────┬──────────────┬──────────────┬──────────────┬────────────┤ │
|
||||||
|
│ │ Billing │ Cost │ Usage │ Payment │ Admin │ │
|
||||||
|
│ │ Dashboard │ Calculator │ Reports │ Portal │ Console │ │
|
||||||
|
│ └──────────────┴──────────────┴──────────────┴──────────────┴────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ BILLING ENGINE LAYER │ │
|
||||||
|
│ ├──────────────┬──────────────┬──────────────┬──────────────┬────────────┤ │
|
||||||
|
│ │ Invoice │ Payment │ Credit │ Prepaid │ SLA │ │
|
||||||
|
│ │ Generator │ Processor │ System │ Balance │ Credits │ │
|
||||||
|
│ └──────────────┴──────────────┴──────────────┴──────────────┴────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ METERING LAYER │ │
|
||||||
|
│ ├──────────────┬──────────────┬──────────────┬──────────────┬────────────┤ │
|
||||||
|
│ │ Storage │ Hosting │ Database │ Compute │ Network │ │
|
||||||
|
│ │ Meter │ Meter │ Meter │ Meter │ Meter │ │
|
||||||
|
│ └──────────────┴──────────────┴──────────────┴──────────────┴────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ PRICING LAYER │ │
|
||||||
|
│ ├──────────────┬──────────────┬──────────────┬──────────────┬────────────┤ │
|
||||||
|
│ │ Price │ Spot │ Reserved │ Discount │ Tiered │ │
|
||||||
|
│ │ Oracle │ Market │ Pricing │ Engine │ Pricing │ │
|
||||||
|
│ └──────────────┴──────────────┴──────────────┴──────────────┴────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ SYNOR L1 BLOCKCHAIN (Smart Contracts) │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Milestones
|
||||||
|
|
||||||
|
| Milestone | Description | Progress |
|
||||||
|
|-----------|-------------|----------|
|
||||||
|
| 1 | Pricing Oracle | 0% |
|
||||||
|
| 2 | Metering Service | 0% |
|
||||||
|
| 3 | Billing Engine | 0% |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fee Distribution Model
|
||||||
|
|
||||||
|
```
|
||||||
|
Transaction Fees:
|
||||||
|
├── 10% → Burn (deflationary)
|
||||||
|
├── 60% → Stakers (rewards)
|
||||||
|
├── 20% → Community Pool (treasury)
|
||||||
|
└── 10% → Miners/Validators
|
||||||
|
|
||||||
|
L2 Service Fees (Storage, Hosting, Database, Compute):
|
||||||
|
├── 70% → Node Operators
|
||||||
|
├── 20% → Protocol Treasury
|
||||||
|
└── 10% → Burn
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Service Pricing Summary
|
||||||
|
|
||||||
|
### Storage L2
|
||||||
|
| Resource | Unit | Price (SYNOR) |
|
||||||
|
|----------|------|---------------|
|
||||||
|
| Storage | GB/month | 0.02 |
|
||||||
|
| Retrieval | GB | 0.01 |
|
||||||
|
| Deal creation | per deal | 0.001 |
|
||||||
|
|
||||||
|
### Hosting
|
||||||
|
| Resource | Unit | Price (SYNOR) |
|
||||||
|
|----------|------|---------------|
|
||||||
|
| Bandwidth | GB | 0.05 |
|
||||||
|
| Custom domain | month | 0.50 |
|
||||||
|
| SSL certificate | month | FREE |
|
||||||
|
|
||||||
|
### Database L2
|
||||||
|
| Resource | Unit | Price (SYNOR) |
|
||||||
|
|----------|------|---------------|
|
||||||
|
| Storage | GB/month | 0.10 |
|
||||||
|
| Queries | 1M | 0.01 |
|
||||||
|
| Vector search | 1M | 0.05 |
|
||||||
|
|
||||||
|
### Compute L2
|
||||||
|
| Resource | Unit | Price (SYNOR) |
|
||||||
|
|----------|------|---------------|
|
||||||
|
| CPU | core/hour | 0.02 |
|
||||||
|
| GPU (RTX 4090) | hour | 0.50 |
|
||||||
|
| Memory | GB/hour | 0.005 |
|
||||||
|
| Serverless | 1M invocations | 0.20 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Timeline
|
||||||
|
|
||||||
|
### Week 1-2: Pricing Oracle
|
||||||
|
- SYNOR/USD price aggregation from DEXes
|
||||||
|
- Time-weighted average pricing (TWAP)
|
||||||
|
- Oracle smart contract deployment
|
||||||
|
|
||||||
|
### Week 3-4: Metering Service
|
||||||
|
- Real-time usage tracking for all L2 services
|
||||||
|
- Event stream processing with Kafka/Redis Streams
|
||||||
|
- Usage aggregation and storage
|
||||||
|
|
||||||
|
### Week 5-6: Billing Engine
|
||||||
|
- Invoice generation from metered usage
|
||||||
|
- Payment processing with SYNOR tokens
|
||||||
|
- Credit system and prepaid balances
|
||||||
|
|
||||||
|
### Week 7-8: Cost Calculator & Dashboard
|
||||||
|
- CLI `synor cost estimate` command
|
||||||
|
- Web dashboard for usage visualization
|
||||||
|
- Cost alerts and budget limits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files to Create
|
||||||
|
|
||||||
|
```
|
||||||
|
crates/synor-economics/
|
||||||
|
├── Cargo.toml
|
||||||
|
├── src/
|
||||||
|
│ ├── lib.rs
|
||||||
|
│ ├── oracle/
|
||||||
|
│ │ ├── mod.rs
|
||||||
|
│ │ ├── price_feed.rs
|
||||||
|
│ │ └── twap.rs
|
||||||
|
│ ├── metering/
|
||||||
|
│ │ ├── mod.rs
|
||||||
|
│ │ ├── storage.rs
|
||||||
|
│ │ ├── hosting.rs
|
||||||
|
│ │ ├── database.rs
|
||||||
|
│ │ └── compute.rs
|
||||||
|
│ ├── billing/
|
||||||
|
│ │ ├── mod.rs
|
||||||
|
│ │ ├── invoice.rs
|
||||||
|
│ │ ├── payment.rs
|
||||||
|
│ │ └── credit.rs
|
||||||
|
│ ├── pricing/
|
||||||
|
│ │ ├── mod.rs
|
||||||
|
│ │ ├── tiers.rs
|
||||||
|
│ │ └── discounts.rs
|
||||||
|
│ └── calculator/
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ └── estimator.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Create [01-Milestone-01-PricingOracle.md](./01-Milestone-01-PricingOracle.md)
|
||||||
|
2. Create [01-Milestone-02-MeteringService.md](./01-Milestone-02-MeteringService.md)
|
||||||
|
3. Create [01-Milestone-03-BillingEngine.md](./01-Milestone-03-BillingEngine.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Created: January 11, 2026*
|
||||||
Loading…
Add table
Reference in a new issue