Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2ccf358
liquidation program scaffold
pileks Mar 4, 2026
5487174
scaffolding cleanup
pileks Mar 4, 2026
adcc929
sdk scaffolding
pileks Mar 4, 2026
50c8108
initialize instruction
pileks Mar 4, 2026
56597ac
liquidation initialize sdk
pileks Mar 4, 2026
9ffeded
liquidation init test
pileks Mar 4, 2026
5d362b0
set refund record ix
pileks Mar 4, 2026
16c3af3
set refund record sdk
pileks Mar 4, 2026
4aa2de0
set refund record tests
pileks Mar 4, 2026
de606ce
activate liquidation ix
pileks Mar 4, 2026
2f3ef75
activate liquidation sdk
pileks Mar 4, 2026
c274d71
activate liquidation tests, additional test fixes
pileks Mar 4, 2026
a03c56e
refund ix
pileks Mar 4, 2026
1e4d277
refund sdk
pileks Mar 4, 2026
c94db9c
refund tests
pileks Mar 4, 2026
6a6940b
remove comment numbering.
pileks Mar 4, 2026
69256c5
withdraw remaining quote ix
pileks Mar 4, 2026
fcbb0a0
minor cleanup
pileks Mar 4, 2026
e75bb56
withdraw remaining quote sdk
pileks Mar 4, 2026
05e20b6
withdraw remaining quote tests
pileks Mar 4, 2026
a0891f1
further cleanup
pileks Mar 4, 2026
b2a8b73
update sdk version
pileks Mar 9, 2026
01b12d8
add refund_record address to events
pileks Mar 9, 2026
3d6a2ae
bump SDK version
pileks Mar 9, 2026
233541d
add pda_bump to initialization events
pileks Mar 9, 2026
6505062
bump sdk ver
pileks Mar 9, 2026
79f6596
add launch totals to refund record set event
pileks Mar 9, 2026
221ed7d
bump sdk
pileks Mar 9, 2026
d985bef
add create_key to liquidation initialization event
pileks Mar 9, 2026
919b9a4
v0.7.1-alpha.4
pileks Mar 9, 2026
5ffdf43
add liquidation program to verifiable builds and deploy programs work…
pileks Mar 10, 2026
2eb131f
prevent same base and quote mint on liquidation init, prevent griefin…
pileks Mar 10, 2026
5e9fa39
add proper seeds check
pileks Mar 10, 2026
42b41ab
liquidation - use idiomatic assertions
pileks Mar 10, 2026
b8ffed8
bump sdk ver
pileks Mar 10, 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
20 changes: 20 additions & 0 deletions .github/workflows/deploy-programs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
- price_based_performance_package_v6
- launchpad_v7
- bid_wall
- liquidation
priority-fee:
description: "Priority fee in microlamports"
required: true
Expand All @@ -41,6 +42,25 @@ jobs:
MAINNET_MULTISIG: ${{ secrets.MAINNET_MULTISIG }}
MAINNET_MULTISIG_VAULT: ${{ secrets.MAINNET_MULTISIG_VAULT }}

liquidation:
if: inputs.program == 'liquidation' || inputs.program == 'all'
uses: ./.github/workflows/reusable-build.yaml
with:
program: "liquidation"
override-program-id: "LiQnowFbFQdYyZhF4pUbpsrZCjxRTQ1upKJxZ2VXjde"
network: "mainnet"
deploy: true
upload_idl: true
verify: true
use-squads: true
features: "production"
priority-fee: ${{ inputs.priority-fee }}
secrets:
MAINNET_SOLANA_DEPLOY_URL: ${{ secrets.MAINNET_SOLANA_DEPLOY_URL }}
MAINNET_DEPLOYER_KEYPAIR: ${{ secrets.MAINNET_DEPLOYER_KEYPAIR }}
MAINNET_MULTISIG: ${{ secrets.MAINNET_MULTISIG }}
MAINNET_MULTISIG_VAULT: ${{ secrets.MAINNET_MULTISIG_VAULT }}

futarchy-v6:
if: inputs.program == 'futarchy_v6' || inputs.program == 'all'
uses: ./.github/workflows/reusable-build.yaml
Expand Down
19 changes: 18 additions & 1 deletion .github/workflows/generate-verifiable-builds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,21 @@ jobs:
uses: EndBug/add-and-commit@v9.1.4
with:
default_author: github_actions
message: 'Update bid_wall verifiable build'
message: 'Update bid_wall verifiable build'
generate-verifiable-liquidation:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: metadaoproject/anchor-verifiable-build@v0.4
with:
program: liquidation
anchor-version: '0.29.0'
solana-cli-version: '1.17.31'
features: 'production'
- run: 'git pull --rebase'
- run: cp target/deploy/liquidation.so ./verifiable-builds
- name: Commit verifiable build back to mainline
uses: EndBug/add-and-commit@v9.1.4
with:
default_author: github_actions
message: 'Update liquidation verifiable build'
1 change: 1 addition & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ conditional_vault = "VLTX1ishMBbcX3rdBWGssxawAo1Q2X2qxYFYqiGodVg"
futarchy = "FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq"
launchpad = "MooNyh4CBUYEKyXVnjGYQ8mEiJDpGvJMdvrZx1iGeHV"
launchpad_v7 = "moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM"
liquidation = "LiQnowFbFQdYyZhF4pUbpsrZCjxRTQ1upKJxZ2VXjde"
mint_governor = "gvnr27cVeyW3AVf3acL7VCJ5WjGAphytnsgcK1feHyH"
performance_package_v2 = "pPV2pfrxnmstSb9j7kEeCLny5BGj6SNwCWGd6xbGGzz"
price_based_performance_package = "pbPPQH7jyKoSLu8QYs3rSY3YkDRXEBojKbTgnUg7NDS"
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ pub recipient_ata: Account<'info, TokenAccount>,
pub funder_token_account: Account<'info, TokenAccount>,
```

### Events
Always use CPI events (`#[event_cpi]` on accounts structs, `emit_cpi!` for emission) rather than regular `emit!`.

### Require Macros
When writing validation checks, prefer specific require macros over generic `require!`:
1. `require_keys_eq!` - when comparing two `Pubkey` values
Expand Down Expand Up @@ -289,3 +292,4 @@ External programs required for tests. These are pre-compiled `.so` files in `tes
| conditional_vault | v0.4 | `VLTX1ishMBbcX3rdBWGssxawAo1Q2X2qxYFYqiGodVg` |
| price_based_performance_package | v0.6.0 | `pbPPQH7jyKoSLu8QYs3rSY3YkDRXEBojKbTgnUg7NDS` |
| mint_governor | v0.7.0 | `gvnr27cVeyW3AVf3acL7VCJ5WjGAphytnsgcK1feHyH` |
| liquidation | v0.1.0 | `LiQnowFbFQdYyZhF4pUbpsrZCjxRTQ1upKJxZ2VXjde` |
9 changes: 9 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Programs for unruggable capital formation and market-driven governance.
| ----------------- | ---- | -------------------------------------------- |
| launchpad | v0.7.0 | moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM |
| bid_wall | v0.7.0 | WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx |
| liquidation | v0.1.0 | LiQnowFbFQdYyZhF4pUbpsrZCjxRTQ1upKJxZ2VXjde |
| futarchy | v0.6.0 | FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq |
| launchpad | v0.6.0 | MooNyh4CBUYEKyXVnjGYQ8mEiJDpGvJMdvrZx1iGeHV |
| price_based_performance_package | v0.6.0 | pbPPQH7jyKoSLu8QYs3rSY3YkDRXEBojKbTgnUg7NDS |
Expand Down
22 changes: 22 additions & 0 deletions programs/liquidation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "liquidation"
version = "0.1.0"
description = "Manages the orderly liquidation of a project's treasury back to token holders."
edition = "2021"

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

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

[dependencies]
anchor-lang = { version = "0.29.0", features = ["event-cpi", "init-if-needed"] }
anchor-spl = "0.29.0"
solana-security-txt = "1.1.1"
2 changes: 2 additions & 0 deletions programs/liquidation/Xargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []
27 changes: 27 additions & 0 deletions programs/liquidation/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use super::*;

#[error_code]
pub enum LiquidationError {
#[msg("Refunding is not enabled")]
RefundingNotEnabled,
#[msg("Liquidation is already activated")]
AlreadyActivated,
#[msg("No quote tokens to fund")]
NothingToFund,
#[msg("No base tokens assigned")]
NoBaseAssigned,
#[msg("Refund window has expired")]
RefundWindowExpired,
#[msg("Refund window has not expired")]
RefundWindowNotExpired,
#[msg("Duration must be greater than zero")]
InvalidDuration,
#[msg("Nothing to refund")]
NothingToRefund,
#[msg("Invalid allocation")]
InvalidAllocation,
#[msg("Invalid authority")]
InvalidAuthority,
#[msg("Invalid mint")]
InvalidMint,
}
74 changes: 74 additions & 0 deletions programs/liquidation/src/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use anchor_lang::prelude::*;

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct CommonFields {
pub slot: u64,
pub unix_timestamp: i64,
pub liquidation_seq_num: u64,
}

impl CommonFields {
pub fn new(clock: &Clock, liquidation_seq_num: u64) -> Self {
Self {
slot: clock.slot,
unix_timestamp: clock.unix_timestamp,
liquidation_seq_num,
}
}
}

#[event]
pub struct LiquidationCreatedEvent {
pub common: CommonFields,
pub liquidation: Pubkey,
pub create_key: Pubkey,
pub record_authority: Pubkey,
pub liquidation_authority: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub duration_seconds: u32,
pub pda_bump: u8,
}

#[event]
pub struct LiquidationActivatedEvent {
pub common: CommonFields,
pub liquidation: Pubkey,
pub total_quote_funded: u64,
pub started_at: i64,
}

#[event]
pub struct RefundRecordSetEvent {
pub common: CommonFields,
pub liquidation: Pubkey,
pub refund_record: Pubkey,
pub recipient: Pubkey,
pub base_assigned: u64,
pub quote_refundable: u64,
pub liquidation_total_base_assigned: u64,
pub liquidation_total_quote_refundable: u64,
pub pda_bump: u8,
}

#[event]
pub struct RefundEvent {
pub common: CommonFields,
pub liquidation: Pubkey,
pub refund_record: Pubkey,
pub recipient: Pubkey,
pub base_burned: u64,
pub quote_refunded: u64,
pub post_record_base_burned: u64,
pub post_record_quote_refunded: u64,
pub post_liquidation_total_base_burned: u64,
pub post_liquidation_total_quote_refunded: u64,
}

#[event]
pub struct WithdrawRemainingQuoteEvent {
pub common: CommonFields,
pub liquidation: Pubkey,
pub liquidation_authority: Pubkey,
pub amount: u64,
}
99 changes: 99 additions & 0 deletions programs/liquidation/src/instructions/activate_liquidation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Mint, Token, TokenAccount, Transfer};

use crate::{
error::LiquidationError,
events::{CommonFields, LiquidationActivatedEvent},
state::Liquidation,
};

#[event_cpi]
#[derive(Accounts)]
pub struct ActivateLiquidation<'info> {
pub liquidation_authority: Signer<'info>,

#[account(
mut,
has_one = liquidation_authority @ LiquidationError::InvalidAuthority,
has_one = quote_mint @ LiquidationError::InvalidMint,
)]
pub liquidation: Account<'info, Liquidation>,

#[account(
mut,
token::mint = quote_mint,
token::authority = liquidation_authority,
)]
pub liquidation_authority_quote_account: Account<'info, TokenAccount>,

#[account(
mut,
associated_token::mint = quote_mint,
associated_token::authority = liquidation,
)]
pub liquidation_quote_vault: Account<'info, TokenAccount>,

pub quote_mint: Account<'info, Mint>,

pub token_program: Program<'info, Token>,
}

impl ActivateLiquidation<'_> {
pub fn validate(&self) -> Result<()> {
require!(
!self.liquidation.is_refunding,
LiquidationError::AlreadyActivated
);

require_gt!(
self.liquidation.total_base_assigned,
0,
LiquidationError::NoBaseAssigned
);

require_gt!(
self.liquidation.total_quote_refundable,
0,
LiquidationError::NothingToFund
);

Ok(())
}

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

// Transfer total_quote_refundable from authority to vault
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx
.accounts
.liquidation_authority_quote_account
.to_account_info(),
to: ctx.accounts.liquidation_quote_vault.to_account_info(),
authority: ctx.accounts.liquidation_authority.to_account_info(),
},
),
liquidation.total_quote_refundable,
)?;

// Permanently enable refunding
liquidation.started_at = clock.unix_timestamp;
liquidation.is_refunding = true;

// Emit event
liquidation.seq_num += 1;

emit_cpi!(LiquidationActivatedEvent {
common: CommonFields::new(&clock, liquidation.seq_num),
liquidation: liquidation.key(),
total_quote_funded: liquidation.total_quote_refundable,
started_at: liquidation.started_at,
});

Ok(())
}
}
Loading
Loading