Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
50669b4
scaffolding
pileks Jan 22, 2026
b48768f
mint governor instructions 1st pass
pileks Jan 22, 2026
36b338d
mint governor client 1st pass
pileks Jan 23, 2026
2255e61
mint authority tests first pass
pileks Jan 23, 2026
0792831
mint governor - cleanup pass
pileks Jan 23, 2026
6225b85
prepare tasks
pileks Jan 29, 2026
1fc23a5
scaffolding
pileks Jan 29, 2026
18ed11d
more scaffolding
pileks Jan 29, 2026
bc13517
scaffolding
pileks Jan 29, 2026
1e159ec
initialize_performance_package ix
pileks Jan 30, 2026
0563620
initialize perf package tests
pileks Jan 30, 2026
c76cc08
pp unlock ix
pileks Jan 30, 2026
f2a8e38
start unlock client/tests
pileks Jan 30, 2026
eabce90
ppv2 - complete unlock ix
pileks Jan 30, 2026
e7f1dfb
ppv2 - complete unlock SDK / tests
pileks Jan 30, 2026
c3f4fba
ppv2 - change authority
pileks Jan 30, 2026
3573dd9
ppv2 - propose change ix
pileks Jan 30, 2026
83c0a37
ppv2 - propose change SDK/tests
pileks Jan 30, 2026
55fabb0
ppv2 - execute change ix
pileks Jan 30, 2026
2b402f5
minor refactor
pileks Jan 30, 2026
58e1fd5
ppv2 - execute change SDK & tests
pileks Jan 30, 2026
6cb58e7
ppv2 - close performance package ix, sdk, tests
pileks Jan 30, 2026
3d23fb4
ppv2 - futarchy twap aggregator support
pileks Jan 30, 2026
0f03dad
Merge remote-tracking branch 'origin/develop' into pileks/met-11-cust…
pileks Jan 31, 2026
3904d21
minor refactor/addition of tests
pileks Jan 31, 2026
3cb4d49
ppv2 - twap-specific test for no decrease in rewards
pileks Jan 31, 2026
5beb60d
Merge remote-tracking branch 'origin/develop' into pileks/met-6-mint-…
pileks Jan 31, 2026
1aab675
Merge branch 'pileks/met-6-mint-governor' into pileks/met-11-custom-p…
pileks Jan 31, 2026
f0a6874
rename client-exported structs
pileks Feb 3, 2026
087e099
Merge remote-tracking branch 'origin/develop' into pileks/met-11-cust…
pileks Feb 3, 2026
a6fbdf5
Merge remote-tracking branch 'origin/develop' into pileks/met-11-cust…
pileks Feb 9, 2026
29246c3
fix .only preventing all tests from running
pileks Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ futarchy = "FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq"
launchpad = "MooNyh4CBUYEKyXVnjGYQ8mEiJDpGvJMdvrZx1iGeHV"
launchpad_v7 = "moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM"
mint_governor = "gvnr27cVeyW3AVf3acL7VCJ5WjGAphytnsgcK1feHyH"
performance_package_v2 = "pPV2pfrxnmstSb9j7kEeCLny5BGj6SNwCWGd6xbGGzz"
price_based_performance_package = "pbPPQH7jyKoSLu8QYs3rSY3YkDRXEBojKbTgnUg7NDS"

[registry]
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions programs/performance_package_v2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "performance_package_v2"
version = "0.7.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "performance_package_v2"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
production = []

[dependencies]
anchor-lang = { version = "0.29.0", features = ["init-if-needed", "event-cpi"] }
anchor-spl = "0.29.0"
solana-security-txt = "1.1.1"
mint_governor = { path = "../mint_governor", features = ["cpi"] }
futarchy = { path = "../futarchy", features = ["cpi"] }
7 changes: 7 additions & 0 deletions programs/performance_package_v2/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use anchor_lang::prelude::*;

pub const PERFORMANCE_PACKAGE_SEED: &[u8] = b"performance_package";
pub const CHANGE_REQUEST_SEED: &[u8] = b"change_request";

#[constant]
pub const MAX_TRANCHES: usize = 10;
58 changes: 58 additions & 0 deletions programs/performance_package_v2/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use anchor_lang::prelude::*;

#[error_code]
pub enum PerformancePackageError {
// Authorization
#[msg("Signer is neither authority nor recipient")]
Unauthorized,
#[msg("Executor is not the opposite party from proposer")]
InvalidExecutor,
#[msg("Signer is not the current authority")]
InvalidAuthority,
#[msg("Signer is not the admin")]
InvalidAdmin,

// Account validation
#[msg("Mint governor does not match the provided mint")]
InvalidMintGovernor,
#[msg("Mint authority does not match expected configuration")]
InvalidMintAuthority,

// State
#[msg("Expected Locked status")]
NotLocked,
#[msg("Expected Unlocking status")]
NotUnlocking,

// Oracle
#[msg("Expected remaining_accounts not provided")]
OracleMissingAccount,
#[msg("Account pubkey doesn't match expected")]
OracleInvalidAccount,
#[msg("Failed to parse account data")]
OracleParseError,
#[msg("Oracle state invalid")]
OracleInvalidState,
#[msg("Minimum duration hasn't passed yet")]
OracleMinDurationNotReached,

// Time
#[msg("Minimum unlock timestamp not yet reached")]
UnlockTimestampNotReached,

// Rewards
#[msg("Math overflow in reward function")]
RewardCalculationOverflow,

// Configuration
#[msg("Tranches should be sorted and non-empty")]
InvalidTranches,
#[msg("Invalid vesting schedule configuration")]
InvalidVestingSchedule,

// Change Requests
#[msg("Missing proposal for execute")]
ChangeRequestNotFound,
#[msg("All optional change fields are None")]
NoChangesProposed,
}
86 changes: 86 additions & 0 deletions programs/performance_package_v2/src/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use anchor_lang::prelude::*;

use crate::{OracleReader, ProposerType, RewardFunction};

/// Common fields included in all events for consistent metadata.
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct CommonFields {
pub slot: u64,
pub unix_timestamp: i64,
pub performance_package_seq_num: u64,
}

/// Emitted by: `initialize_performance_package`
#[event]
pub struct PerformancePackageCreatedEvent {
pub common: CommonFields,
pub performance_package: Pubkey,
pub mint: Pubkey,
pub mint_governor: Pubkey,
pub authority: Pubkey,
pub recipient: Pubkey,
pub create_key: Pubkey,
pub pda_bump: u8,
}

/// Emitted by: `start_unlock`
#[event]
pub struct UnlockStartedEvent {
pub common: CommonFields,
pub performance_package: Pubkey,
pub start_time: i64,
}

/// Emitted by: `complete_unlock`
#[event]
pub struct UnlockCompletedEvent {
pub common: CommonFields,
pub performance_package: Pubkey,
pub oracle_value: u128,
pub recipient: Pubkey,
pub amount_minted: u64,
/// Cumulative after this unlock
pub total_rewards_paid_out: u64,
}

/// Emitted by: `change_authority`
#[event]
pub struct AuthorityChangedEvent {
pub common: CommonFields,
pub performance_package: Pubkey,
pub old_authority: Pubkey,
pub new_authority: Pubkey,
}

/// Emitted by: `propose_change`
#[event]
pub struct ChangeProposedEvent {
pub common: CommonFields,
pub performance_package: Pubkey,
pub change_request: Pubkey,
pub proposer_type: ProposerType,
pub pda_nonce: u32,
pub new_recipient: Option<Pubkey>,
pub new_oracle_reader: Option<OracleReader>,
pub new_reward_function: Option<RewardFunction>,
}

/// Emitted by: `execute_change`
#[event]
pub struct ChangeExecutedEvent {
pub common: CommonFields,
pub performance_package: Pubkey,
pub executed_by: Pubkey,
pub new_recipient: Option<Pubkey>,
pub new_oracle_reader: Option<OracleReader>,
pub new_reward_function: Option<RewardFunction>,
}

/// Emitted by: `close_performance_package`
#[event]
pub struct PerformancePackageClosedEvent {
pub common: CommonFields,
pub performance_package: Pubkey,
/// Final cumulative amount paid
pub total_rewards_paid_out: u64,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use anchor_lang::prelude::*;

use crate::{
AuthorityChangedEvent, CommonFields, PerformancePackage, PerformancePackageError,
PERFORMANCE_PACKAGE_SEED,
};

#[derive(Accounts)]
pub struct ChangeAuthority<'info> {
#[account(
mut,
seeds = [PERFORMANCE_PACKAGE_SEED, performance_package.create_key.as_ref()],
bump = performance_package.bump,
has_one = authority @ PerformancePackageError::InvalidAuthority
)]
pub performance_package: Account<'info, PerformancePackage>,

/// Must be the current authority of the performance package
pub authority: Signer<'info>,

/// The new authority address
/// CHECK: Can be any valid pubkey
pub new_authority: UncheckedAccount<'info>,
}

impl ChangeAuthority<'_> {
pub fn handle(ctx: Context<Self>) -> Result<()> {
let pp = &mut ctx.accounts.performance_package;

let new_authority = ctx.accounts.new_authority.key();

// Update authority
pp.authority = new_authority;

// Increment sequence number
pp.seq_num += 1;

let clock = Clock::get()?;

emit!(AuthorityChangedEvent {
common: CommonFields {
slot: clock.slot,
unix_timestamp: clock.unix_timestamp,
performance_package_seq_num: pp.seq_num,
},
performance_package: pp.key(),
old_authority: ctx.accounts.authority.key(),
new_authority,
});

Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use anchor_lang::prelude::*;

use crate::{
CommonFields, PackageStatus, PerformancePackage, PerformancePackageClosedEvent,
PerformancePackageError, PERFORMANCE_PACKAGE_SEED,
};

pub mod admin {
use anchor_lang::prelude::declare_id;

// MetaDAO operational multisig
declare_id!("6awyHMshBGVjJ3ozdSJdyyDE1CTAXUwrpNMaRGMsb4sf");
}

#[derive(Accounts)]
pub struct ClosePerformancePackage<'info> {
#[account(
mut,
seeds = [PERFORMANCE_PACKAGE_SEED, performance_package.create_key.as_ref()],
bump = performance_package.bump,
close = rent_destination
)]
pub performance_package: Account<'info, PerformancePackage>,

pub admin: Signer<'info>,

/// CHECK: Receives closed account rent
#[account(mut)]
pub rent_destination: UncheckedAccount<'info>,
}

impl ClosePerformancePackage<'_> {
pub fn validate(&self) -> Result<()> {
#[cfg(feature = "production")]
require_keys_eq!(
self.admin.key(),
admin::ID,
PerformancePackageError::InvalidAdmin
);

// Status must be Locked (cannot close while unlocking)
require!(
self.performance_package.status == PackageStatus::Locked,
PerformancePackageError::NotLocked
);

Ok(())
}

pub fn handle(ctx: Context<Self>) -> Result<()> {
let pp = &ctx.accounts.performance_package;
let clock = Clock::get()?;

emit!(PerformancePackageClosedEvent {
common: CommonFields {
slot: clock.slot,
unix_timestamp: clock.unix_timestamp,
performance_package_seq_num: pp.seq_num,
},
performance_package: pp.key(),
total_rewards_paid_out: pp.total_rewards_paid_out,
});

// The performance_package account is closed automatically via the `close = rent_destination` constraint

Ok(())
}
}
Loading
Loading