This commit is contained in:
2025-08-26 11:36:53 +02:00
parent f30d1fd503
commit 24ec468d37
10 changed files with 790 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
import freeflowuniverse.herolib.threefold.incatokens
fn main() {
// Create simulation
mut sim := incatokens.simulation_new(
name: 'inca_tge_v1'
total_supply: 10_000_000_000
)!
// Initialize with default rounds
sim.init_default_rounds()!
// Define scenarios
scenarios := {
'Low': {
'demands': [10_000_000.0, 10_000_000.0, 0.0],
'amm_trades': [0.0, 0.0, 0.0]
},
'Mid': {
'demands': [20_000_000.0, 20_000_000.0, 0.0],
'amm_trades': [0.0, 0.0, 0.0]
},
'High': {
'demands': [50_000_000.0, 100_000_000.0, 0.0],
'amm_trades': [0.0, 0.0, 0.0]
}
}
// Run all scenarios
for name, params in scenarios {
sim.run_scenario(name, params['demands'], params['amm_trades'])!
}
// Create vesting schedules
sim.create_vesting_schedules()!
// Generate charts
price_chart := sim.generate_price_chart()!
market_cap_chart := sim.generate_market_cap_chart()!
vesting_chart := sim.generate_vesting_chart()!
// Export results
sim.export_report('simulation_report.md')!
sim.export_csv('prices', 'price_data.csv')!
sim.export_csv('vesting', 'vesting_schedule.csv')!
println('Simulation complete!')
}

View File

@@ -0,0 +1,38 @@
module incatokens
import math
pub struct AMMPool {
pub mut:
tokens f64
usdc f64
k f64 // constant product
}
pub fn (mut pool AMMPool) add_liquidity(tokens f64, usdc f64) {
pool.tokens += tokens
pool.usdc += usdc
pool.k = pool.tokens * pool.usdc
}
pub fn (mut pool AMMPool) trade(usdc_amount f64) ! {
if pool.tokens <= 0 || pool.usdc <= 0 {
return error('AMM pool is empty')
}
pool.usdc += usdc_amount
if pool.usdc <= 0 {
return error('Insufficient USDC in pool')
}
// Maintain constant product
pool.tokens = pool.k / pool.usdc
}
pub fn (pool AMMPool) get_price() f64 {
if pool.tokens <= 0 {
return 0
}
return pool.usdc / pool.tokens
}

View File

@@ -0,0 +1,54 @@
module incatokens
import math
pub struct AuctionResult {
pub mut:
tokens_sold f64
clearing_price f64
usd_raised f64
fully_subscribed bool
}
pub fn simulate_auction(config AuctionConfig) !AuctionResult {
demand := config.demand
min_price := config.min_price
token_supply := config.token_supply
if token_supply <= 0 {
return AuctionResult{
tokens_sold: 0
clearing_price: 0
usd_raised: 0
fully_subscribed: true
}
}
implied_avg := demand / token_supply
if implied_avg < min_price {
// Partial fill at floor price
tokens_sold := math.min(demand / min_price, token_supply)
return AuctionResult{
tokens_sold: tokens_sold
clearing_price: min_price
usd_raised: tokens_sold * min_price
fully_subscribed: tokens_sold >= token_supply
}
}
// Full subscription at market clearing price
return AuctionResult{
tokens_sold: token_supply
clearing_price: implied_avg
usd_raised: demand
fully_subscribed: true
}
}
@[params]
pub struct AuctionConfig {
pub mut:
demand f64
min_price f64
token_supply f64
}

View File

@@ -0,0 +1,67 @@
module incatokens
import freeflowuniverse.herolib.biz.spreadsheet
import freeflowuniverse.herolib.web.echarts
// Generate price evolution chart
pub fn (sim Simulation) generate_price_chart() !echarts.EChartsOption {
mut rownames := []string{}
for name, _ in sim.scenarios {
rownames << 'scenario_${name}_price'
}
return sim.price_sheet.line_chart(
rowname: rownames.join(',')
period_type: .month
title: 'INCA Token Price Evolution'
title_sub: 'Price paths across different scenarios'
unit: .normal
)!
}
// Generate market cap chart
pub fn (sim Simulation) generate_market_cap_chart() !echarts.EChartsOption {
// Create market cap rows from price rows
mut mc_sheet := spreadsheet.sheet_new(
name: '${sim.name}_market_cap'
nrcol: sim.price_sheet.nrcol
curr: sim.currency
)!
for name, scenario in sim.scenarios {
mut mc_row := mc_sheet.row_new(
name: 'scenario_${name}_mc'
tags: 'scenario:${name} type:market_cap'
descr: 'Market cap for ${name} scenario'
)!
price_row := sim.price_sheet.row_get('scenario_${name}_price')!
for i, cell in price_row.cells {
mc_row.cells[i].val = cell.val * sim.total_supply
}
}
return mc_sheet.bar_chart(
namefilter: mc_sheet.rows.keys()
period_type: .quarter
title: 'INCA Market Capitalization'
title_sub: 'Market cap evolution by quarter'
unit: .million
)!
}
// Generate vesting schedule chart
pub fn (mut sim Simulation) generate_vesting_chart() !echarts.EChartsOption {
if isnil(sim.vesting_sheet) {
sim.create_vesting_schedules()!
}
return sim.vesting_sheet.line_chart(
includefilter: ['type:vesting']
excludefilter: ['type:total_vesting']
period_type: .quarter
title: 'Token Vesting Schedule'
title_sub: 'Cumulative tokens unlocked over time'
unit: .million
)!
}

View File

@@ -0,0 +1,95 @@
module incatokens
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.data.markdown
import os
// // Export simulation results to markdown report
// pub fn (sim &Simulation) export_report(path string) ! {
// content := sim.render_report()!
// // Write to file
// mut p := pathlib.get_file(
// path: path
// create: true
// delete: true
// )!
// p.write(content)!
// }
// Export data to CSV
pub fn (sim Simulation) export_csv(sheet_name string, path string) ! {
mut sheet := match sheet_name {
'prices' { sim.price_sheet }
'tokens' { sim.token_sheet }
'investments' { sim.investment_sheet }
'vesting' { sim.vesting_sheet }
else { return error('Unknown sheet: ${sheet_name}') }
}
sheet.export_csv(
path: path
separator: ','
include_empty: false
)!
}
pub fn (sim Simulation) generate_distribution_section() !string {
mut lines := []string{}
lines << '- **Total supply:** ${sim.total_supply} INCA'
lines << '- **Public (TGE):** ${(sim.public_pct * 100).str()}% (No lockup)'
lines << '- **Team:** ${(sim.team_pct * 100).str()}% (${sim.team_vesting.cliff_months}mo cliff, ${sim.team_vesting.vesting_months}mo vest)'
lines << '- **Treasury:** ${(sim.treasury_pct * 100).str()}% (${sim.treasury_vesting.cliff_months}mo cliff, ${sim.treasury_vesting.vesting_months}mo vest)'
lines << '- **Investors:** ${(sim.investor_pct * 100).str()}%'
lines << ''
lines << '### Investor Rounds & Vesting'
lines << '| Round | Allocation | Price (USD) | Vesting Schedule |'
lines << '|---|---|---|---|'
for round in sim.investor_rounds {
lines << '| **${round.name}** | ${(round.allocation_pct * 100).str()}% | \$${round.price} | ${round.vesting.cliff_months}mo cliff, ${round.vesting.vesting_months}mo linear vest |'
}
return lines.join('\n')
}
pub fn (sim Simulation) generate_scenario_section(scenario &Scenario) !string {
mut lines := []string{}
lines << '### ${scenario.name} Scenario'
lines << '**Parameters:**'
lines << '- **Auction Demand:** \$${scenario.demands.map(it.str()).join(', ')}'
lines << '- **AMM Net Trade:** \$${scenario.amm_trades.map(it.str()).join(', ')}'
lines << ''
lines << '**Results:**'
lines << '| Treasury Raised | Final Price | ${sim.investor_rounds.map("ROI " + it.name).join(" | ")} |'
lines << '|:---|:---|${sim.investor_rounds.map(":---").join("|")}|'
mut row := ['\$${(scenario.final_metrics.treasury_total / 1000000).str()}M', '\$${scenario.final_metrics.final_price}']
for round in sim.investor_rounds {
roi := scenario.final_metrics.investor_roi[round.name] or { 0.0 }
row << '${roi}x'
}
lines << '| ${row.join(' | ')} |'
return lines.join('\n')
}
pub fn (sim Simulation) generate_financial_summary() !string {
mut lines := []string{}
lines << '### Funds Raised for INCA COOP'
lines << '| Round | USD Raised |'
lines << '|---|---|'
mut total_raised := 0.0
for round in sim.investor_rounds {
raised := round.allocation_pct * sim.total_supply * round.price
total_raised += raised
lines << '| **${round.name}** | \$${raised} |'
}
lines << '| **Total** | **\$${total_raised}** |'
return lines.join('\n')
}

View File

@@ -0,0 +1,68 @@
module incatokens
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.biz.spreadsheet
__global (
simulations map[string]&Simulation
)
@[params]
pub struct SimulationNewArgs {
pub mut:
name string = 'default'
total_supply f64 = 10_000_000_000
public_pct f64 = 0.50
team_pct f64 = 0.15
treasury_pct f64 = 0.15
investor_pct f64 = 0.20
nrcol int = 60 // 60 months default
currency string = 'USD'
}
pub fn simulation_new(args SimulationNewArgs) !&Simulation {
name := texttools.name_fix(args.name)
// Initialize spreadsheets for tracking
price_sheet := spreadsheet.sheet_new(
name: '${name}_prices'
nrcol: args.nrcol
curr: args.currency
)!
token_sheet := spreadsheet.sheet_new(
name: '${name}_tokens'
nrcol: args.nrcol
curr: args.currency
)!
investment_sheet := spreadsheet.sheet_new(
name: '${name}_investments'
nrcol: args.nrcol
curr: args.currency
)!
mut sim := &Simulation{
name: name
total_supply: args.total_supply
public_pct: args.public_pct
team_pct: args.team_pct
treasury_pct: args.treasury_pct
investor_pct: args.investor_pct
currency: args.currency
price_sheet: price_sheet
token_sheet: token_sheet
investment_sheet: investment_sheet
vesting_sheet: unsafe { nil }
}
simulations[name] = sim
return sim
}
pub fn simulation_get(name string) !&Simulation {
name_fixed := texttools.name_fix(name)
return simulations[name_fixed] or {
return error('Simulation "${name_fixed}" not found')
}
}

View File

@@ -0,0 +1,81 @@
module incatokens
fn test_simulation_creation() {
mut sim := simulation_new(name: 'test_sim')!
sim.init_default_rounds()!
assert sim.name == 'test_sim'
assert sim.total_supply == 10_000_000_000
assert sim.investor_rounds.len == 3
}
fn test_scenario_execution() {
mut sim := simulation_new(name: 'test_sim')!
sim.init_default_rounds()!
// Run low scenario
low_scenario := sim.run_scenario(
'Low',
[10_000_000.0, 10_000_000.0, 0.0],
[0.0, 0.0, 0.0]
)!
assert low_scenario.name == 'Low'
assert low_scenario.final_metrics.treasury_total > 0
assert low_scenario.final_metrics.final_price > 0
// Check ROI is positive for all rounds
for round in sim.investor_rounds {
roi := low_scenario.final_metrics.investor_roi[round.name] or { 0.0 }
assert roi > 0 // Should have positive return
}
}
fn test_vesting_schedules() {
mut sim := simulation_new(name: 'test_sim')!
sim.init_default_rounds()!
sim.create_vesting_schedules()!
// Check team vesting
team_row := sim.vesting_sheet.row_get('team_vesting')!
// Before cliff (month 11), should be 0
assert team_row.cells[11].val == 0
// After cliff starts (month 12), should have tokens
assert team_row.cells[12].val > 0
// After full vesting (month 48), should have all tokens
total_team_tokens := sim.total_supply * sim.team_pct
assert team_row.cells[48].val == total_team_tokens
}
fn test_export_functionality() {
mut sim := simulation_new(name: 'test_export')!
sim.init_default_rounds()!
// Run scenarios
sim.run_scenario('Low', [10_000_000.0, 10_000_000.0, 0.0], [0.0, 0.0, 0.0])!
sim.run_scenario('High', [50_000_000.0, 100_000_000.0, 0.0], [0.0, 0.0, 0.0])!
// Test CSV export
sim.export_csv('prices', '/tmp/test_prices.csv')!
// Test report generation
// sim.export_report('/tmp/test_report.md')!
}
// fn test_template_rendering() {
// mut sim := simulation_new(name: 'test_template')!
// sim.init_default_rounds()!
// // Run a scenario
// sim.run_scenario('Low', [10_000_000.0, 10_000_000.0, 0.0], [0.0, 0.0, 0.0])!
// // Render the report
// report := sim.render_report()!
// // Assert that the report is not empty and contains some expected text
// assert report.len > 0
// assert report.contains('# INCA TGE Simulation Report')
// assert report.contains('## 1) Token Distribution &amp; Vesting')
// }

View File

@@ -0,0 +1,93 @@
module incatokens
import freeflowuniverse.herolib.biz.spreadsheet
import freeflowuniverse.herolib.data.ourtime
pub struct VestingSchedule {
pub mut:
cliff_months int
vesting_months int
}
pub struct InvestorRound {
pub mut:
name string
allocation_pct f64
price f64
vesting VestingSchedule
}
pub enum EpochType {
auction_only
hybrid
amm_only
}
pub struct Epoch {
pub mut:
index int
type_ EpochType
start_month int
end_month int
auction_share f64
amm_share f64
tokens_allocated f64
tokens_spillover f64
auction_demand f64
amm_net_trade f64
final_price f64
treasury_raised f64
}
pub struct Scenario {
pub mut:
name string
demands []f64
amm_trades []f64
epochs []Epoch
final_metrics ScenarioMetrics
}
pub struct ScenarioMetrics {
pub mut:
treasury_total f64
final_price f64
investor_roi map[string]f64
market_cap_final f64
circulating_supply_final f64
}
@[heap]
pub struct Simulation {
pub mut:
name string
// Token distribution
total_supply f64
public_pct f64
team_pct f64
treasury_pct f64
investor_pct f64
// Configuration
currency string
epoch1_floor_uplift f64 = 1.20
epochn_floor_uplift f64 = 1.20
amm_liquidity_depth_factor f64 = 2.0
// Investor rounds
investor_rounds []InvestorRound
// Vesting schedules
team_vesting VestingSchedule
treasury_vesting VestingSchedule
// Tracking sheets
price_sheet &spreadsheet.Sheet
token_sheet &spreadsheet.Sheet
investment_sheet &spreadsheet.Sheet
vesting_sheet &spreadsheet.Sheet
// Scenarios
scenarios map[string]&Scenario
}

View File

@@ -0,0 +1,175 @@
module incatokens
import freeflowuniverse.herolib.biz.spreadsheet
// Initialize default investor rounds
pub fn (mut sim Simulation) init_default_rounds() ! {
sim.investor_rounds = [
InvestorRound{
name: 'R1'
allocation_pct: 0.05
price: 0.005
vesting: VestingSchedule{cliff_months: 6, vesting_months: 24}
},
InvestorRound{
name: 'R2'
allocation_pct: 0.075
price: 0.009
vesting: VestingSchedule{cliff_months: 6, vesting_months: 24}
},
InvestorRound{
name: 'R3'
allocation_pct: 0.075
price: 0.0106
vesting: VestingSchedule{cliff_months: 6, vesting_months: 24}
}
]
sim.team_vesting = VestingSchedule{cliff_months: 12, vesting_months: 36}
sim.treasury_vesting = VestingSchedule{cliff_months: 12, vesting_months: 48}
}
// Run a scenario with given demands and AMM trades
pub fn (mut sim Simulation) run_scenario(name string, demands []f64, amm_trades []f64) !&Scenario {
mut scenario := &Scenario{
name: name
demands: demands
amm_trades: amm_trades
}
// Initialize epochs
scenario.epochs = [
Epoch{index: 0, type_: .auction_only, start_month: 0, end_month: 3, auction_share: 1.0, amm_share: 0.0},
Epoch{index: 1, type_: .hybrid, start_month: 3, end_month: 6, auction_share: 0.5, amm_share: 0.5},
Epoch{index: 2, type_: .amm_only, start_month: 6, end_month: 12, auction_share: 0.0, amm_share: 1.0}
]
// Track in spreadsheet
mut price_row := sim.price_sheet.row_new(
name: 'scenario_${name}_price'
tags: 'scenario:${name} type:price'
descr: 'Token price evolution for ${name} scenario'
)!
mut treasury_row := sim.investment_sheet.row_new(
name: 'scenario_${name}_treasury'
tags: 'scenario:${name} type:treasury'
descr: 'Treasury raised for ${name} scenario'
aggregatetype: .sum
)!
// Calculate public tokens per epoch
total_public := sim.total_supply * sim.public_pct
tokens_per_epoch := total_public / 3.0
mut last_auction_price := sim.get_last_investor_price()
mut spillover := 0.0
mut treasury_total := 0.0
mut amm_pool := AMMPool{}
for mut epoch in scenario.epochs {
epoch.tokens_allocated = tokens_per_epoch + spillover
epoch.auction_demand = demands[epoch.index]
epoch.amm_net_trade = amm_trades[epoch.index]
// Run auction if applicable
if epoch.auction_share > 0 {
auction_tokens := epoch.tokens_allocated * epoch.auction_share
floor_price := sim.calculate_floor_price(epoch.index, last_auction_price)
auction_result := simulate_auction(
demand: epoch.auction_demand
min_price: floor_price
token_supply: auction_tokens
)!
epoch.treasury_raised = auction_result.usd_raised
treasury_total += auction_result.usd_raised
last_auction_price = auction_result.clearing_price
epoch.final_price = auction_result.clearing_price
spillover = auction_tokens - auction_result.tokens_sold
// Record in spreadsheet
treasury_row.cells[epoch.start_month].val = auction_result.usd_raised
}
// Handle AMM if applicable
if epoch.amm_share > 0 {
amm_tokens := epoch.tokens_allocated * epoch.amm_share + spillover
spillover = 0
// Seed AMM pool
amm_usdc_to_add := sim.amm_liquidity_depth_factor * epoch.treasury_raised
amm_pool.add_liquidity(amm_tokens, amm_usdc_to_add)
// Simulate trading
if epoch.amm_net_trade != 0 {
amm_pool.trade(epoch.amm_net_trade)!
}
epoch.final_price = amm_pool.get_price()
}
// Record price in spreadsheet
for month in epoch.start_month .. epoch.end_month {
price_row.cells[month].val = epoch.final_price
}
epoch.tokens_spillover = spillover
}
// Calculate final metrics
scenario.final_metrics = sim.calculate_metrics(scenario, treasury_total)!
sim.scenarios[name] = scenario
return scenario
}
// Calculate metrics for scenario
fn (sim Simulation) calculate_metrics(scenario &Scenario, treasury_total f64) !ScenarioMetrics {
final_price := scenario.epochs.last().final_price
mut investor_roi := map[string]f64{}
for round in sim.investor_rounds {
investor_roi[round.name] = final_price / round.price
}
return ScenarioMetrics{
treasury_total: treasury_total
final_price: final_price
investor_roi: investor_roi
market_cap_final: final_price * sim.total_supply
circulating_supply_final: sim.calculate_circulating_supply(12) // at month 12
}
}
fn (sim Simulation) get_last_investor_price() f64 {
mut max_price := 0.0
for round in sim.investor_rounds {
if round.price > max_price {
max_price = round.price
}
}
return max_price
}
fn (sim Simulation) calculate_floor_price(epoch_idx int, last_auction_price f64) f64 {
last_investor_price := sim.get_last_investor_price()
if epoch_idx == 0 {
return last_investor_price * sim.epoch1_floor_uplift
}
return last_auction_price * sim.epochn_floor_uplift
}
fn (sim Simulation) calculate_circulating_supply(month int) f64 {
// Base circulation (non-public allocations)
investor_tokens := sim.investor_pct * sim.total_supply
team_tokens := sim.team_pct * sim.total_supply
treasury_tokens := sim.treasury_pct * sim.total_supply
// For simplicity, assume all public tokens are circulating after TGE
public_tokens := sim.public_pct * sim.total_supply
return investor_tokens + team_tokens + treasury_tokens + public_tokens
}

View File

@@ -0,0 +1,71 @@
module incatokens
import freeflowuniverse.herolib.biz.spreadsheet
// Create vesting schedule in spreadsheet
pub fn (mut sim Simulation) create_vesting_schedules() ! {
// Create vesting sheet
mut vesting_sheet := spreadsheet.sheet_new(
name: '${sim.name}_vesting'
nrcol: 60 // 60 months
curr: sim.currency
)!
// Team vesting
team_tokens := sim.total_supply * sim.team_pct
mut team_row := vesting_sheet.row_new(
name: 'team_vesting'
tags: 'category:team type:vesting'
descr: 'Team token vesting schedule'
)!
sim.apply_vesting_schedule(mut team_row, team_tokens, sim.team_vesting)!
// Treasury vesting
treasury_tokens := sim.total_supply * sim.treasury_pct
mut treasury_row := vesting_sheet.row_new(
name: 'treasury_vesting'
tags: 'category:treasury type:vesting'
descr: 'Treasury token vesting schedule'
)!
sim.apply_vesting_schedule(mut treasury_row, treasury_tokens, sim.treasury_vesting)!
// Investor rounds vesting
for round in sim.investor_rounds {
round_tokens := sim.total_supply * round.allocation_pct
mut round_row := vesting_sheet.row_new(
name: '${round.name}_vesting'
tags: 'category:investor round:${round.name} type:vesting'
descr: '${round.name} investor vesting schedule'
)!
sim.apply_vesting_schedule(mut round_row, round_tokens, round.vesting)!
}
// Create total unlocked row
mut total_row := vesting_sheet.group2row(
name: 'total_unlocked'
include: ['type:vesting']
tags: 'summary type:total_vesting'
descr: 'Total tokens unlocked over time'
aggregatetype: .sum
)!
sim.vesting_sheet = vesting_sheet
}
fn (sim Simulation) apply_vesting_schedule(mut row spreadsheet.Row, total_tokens f64, schedule VestingSchedule) ! {
monthly_unlock := total_tokens / f64(schedule.vesting_months)
for month in 0 .. row.sheet.nrcol {
if month < schedule.cliff_months {
// Before cliff, no tokens unlocked
row.cells[month].val = 0
} else if month < schedule.cliff_months + schedule.vesting_months {
// During vesting period
months_vested := month - schedule.cliff_months + 1
row.cells[month].val = monthly_unlock * f64(months_vested)
} else {
// After vesting complete
row.cells[month].val = total_tokens
}
}
}