Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
878e5ed
Add Whitelist program
GuidoDipietro Dec 17, 2025
57af08c
Settler: Intent lifecycle
GuidoDipietro Dec 17, 2025
cc10fad
Settler: Proposal Lifecycle
GuidoDipietro Dec 17, 2025
f5e81b9
Fix tests
GuidoDipietro Dec 17, 2025
2f91061
Merge branch 'solana/1-whitelist' into solana/2-settler-intent-lifecycle
GuidoDipietro Dec 17, 2025
f1545be
Trim tests to this PRs features
GuidoDipietro Dec 17, 2025
9bd0e0e
Rm signatures utils as not relevant in this PR
GuidoDipietro Dec 17, 2025
ea31118
Trim SDK to match this PRs features
GuidoDipietro Dec 17, 2025
0707a85
Trim tests to this PRs features
GuidoDipietro Dec 17, 2025
1b87e10
Merge branch 'solana/2-settler-intent-lifecycle' into solana/3-settle…
GuidoDipietro Dec 17, 2025
3d93df3
EVM: Deploy contracts to Ethereum (#51)
alavarello Dec 17, 2025
a20c769
chore: update readme
facuspagnuolo Dec 18, 2025
804e2a4
Code review: simplify set admin process
GuidoDipietro Dec 30, 2025
a8b019b
Code review: Remove updated_by and last_update from EntityRegistry
GuidoDipietro Dec 30, 2025
eff0a23
Code review: rename Whitelist to Controller/Allowlist
GuidoDipietro Dec 30, 2025
90c6e3c
Code review: rm EntityRegistry status and close when not in allowlist
GuidoDipietro Dec 30, 2025
5cfce2b
Several fixes in Controller code
GuidoDipietro Dec 30, 2025
4282a1f
Code review: address other comments
GuidoDipietro Dec 30, 2025
b918391
Code review: add license
GuidoDipietro Dec 30, 2025
44a93c6
Merge branch 'solana/1-whitelist' into solana/2-settler-intent-lifecycle
GuidoDipietro Dec 30, 2025
e916fa1
Code review: adapt Settler to Controller changes
GuidoDipietro Dec 30, 2025
a1eb61d
Code review: rename controller::GlobalSettings to ControllerSettings,…
GuidoDipietro Dec 30, 2025
5f8eb31
Code review: rm is_paused from SettlerSettings
GuidoDipietro Dec 30, 2025
f42465b
Merge branch 'solana/2-settler-intent-lifecycle' into solana/3-settle…
GuidoDipietro Dec 30, 2025
1039442
Code review: adapt Settler to match Controller changes
GuidoDipietro Dec 30, 2025
435b7dc
Rm unused is_paused flag
GuidoDipietro Dec 30, 2025
5e4eb54
Merge branch 'solana/2-settler-intent-lifecycle' into solana/3-settle…
GuidoDipietro Dec 30, 2025
b638369
Fix lint
GuidoDipietro Dec 30, 2025
3130209
Code review: rename proposal_creator to creator
GuidoDipietro Dec 30, 2025
efbfea9
Fix lint
GuidoDipietro Dec 30, 2025
e1ef9ed
Code review: several comments
GuidoDipietro Jan 5, 2026
c715467
Code review: context() pattern in tests
GuidoDipietro Jan 5, 2026
0d815fa
Merge branch 'solana/1-whitelist' into solana/2-settler-intent-lifecycle
GuidoDipietro Jan 5, 2026
40d2ad6
Fix lint and merge errors
GuidoDipietro Jan 5, 2026
89ddecc
Code review: several comments
GuidoDipietro Jan 5, 2026
38d0f18
Merge branch 'solana/2-settler-intent-lifecycle' into solana/3-settle…
GuidoDipietro Jan 5, 2026
ba1d144
Code review: change crate::controller to controller
GuidoDipietro Jan 5, 2026
0ff6529
(1) Add Controller program (#45)
GuidoDipietro Jan 6, 2026
e690d3c
Merge branch 'solana/settler' into solana/3-settler-proposal-lifecycle
GuidoDipietro Jan 6, 2026
1ac6503
Revert "Merge branch 'solana/settler' into solana/3-settler-proposal-…
GuidoDipietro Jan 6, 2026
9fd1cc8
Merge branch 'solana/settler' into solana/3-settler-proposal-lifecycle
GuidoDipietro Jan 6, 2026
391eb43
Correct controller.test.ts file
GuidoDipietro Jan 6, 2026
ce6dba5
Code review: remove batch capabilities from claim_stale_proposal
GuidoDipietro Jan 7, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use anchor_lang::prelude::*;

use crate::{
errors::SettlerError,
state::{Proposal, ProposalInstruction},
};

#[derive(Accounts)]
#[instruction(more_instructions: Vec<ProposalInstruction>)]
pub struct AddInstructionsToProposal<'info> {
#[account(mut)]
pub creator: Signer<'info>,

#[account(
mut,
realloc = Proposal::extended_size(proposal.to_account_info().data_len(), &more_instructions)?,
realloc::payer = creator,
realloc::zero = true,
has_one = creator @ SettlerError::IncorrectProposalCreator
)]
// Any proposal
pub proposal: Box<Account<'info, Proposal>>,

pub system_program: Program<'info, System>,
}

pub fn add_instructions_to_proposal(
ctx: Context<AddInstructionsToProposal>,
more_instructions: Vec<ProposalInstruction>,
finalize: bool,
) -> Result<()> {
let now = Clock::get()?.unix_timestamp as u64;
let proposal = &mut ctx.accounts.proposal;

require!(proposal.deadline > now, SettlerError::ProposalIsExpired);
require!(!proposal.is_final, SettlerError::ProposalIsFinal);

proposal.instructions.extend_from_slice(&more_instructions);

if finalize {
proposal.is_final = true;
}

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,11 @@ pub struct ClaimStaleIntent<'info> {
mut,
close = creator,
has_one = creator @ SettlerError::IncorrectIntentCreator,
constraint = Clock::get()?.unix_timestamp as u64 > intent.deadline @ SettlerError::IntentNotYetExpired
)]
pub intent: Box<Account<'info, Intent>>,
}

pub fn claim_stale_intent(ctx: Context<ClaimStaleIntent>) -> Result<()> {
let now = Clock::get()?.unix_timestamp as u64;

require!(
ctx.accounts.intent.deadline < now,
SettlerError::IntentNotYetExpired
);

pub fn claim_stale_intent(_ctx: Context<ClaimStaleIntent>) -> Result<()> {
Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use anchor_lang::prelude::*;

use crate::{errors::SettlerError, state::Proposal};

#[derive(Accounts)]
pub struct ClaimStaleProposal<'info> {
#[account(mut)]
pub creator: Signer<'info>,

#[account(
mut,
close = creator,
has_one = creator @ SettlerError::IncorrectProposalCreator,
constraint = Clock::get()?.unix_timestamp as u64 > proposal.deadline @ SettlerError::ProposalNotYetExpired
)]
pub proposal: Box<Account<'info, Proposal>>,
}

pub fn claim_stale_proposal(_ctx: Context<ClaimStaleProposal>) -> Result<()> {
Ok(())
}
95 changes: 95 additions & 0 deletions packages/svm/programs/settler/src/instructions/create_proposal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use anchor_lang::prelude::*;

use crate::{
controller::{self, accounts::EntityRegistry, types::EntityType},
errors::SettlerError,
state::{Intent, Proposal, ProposalInstruction},
types::TokenFee,
};

#[derive(Accounts)]
#[instruction(instructions: Vec<ProposalInstruction>, fees: Vec<TokenFee>,)]
pub struct CreateProposal<'info> {
#[account(mut)]
pub solver: Signer<'info>,

#[account(
seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()],
bump = solver_registry.bump,
seeds::program = controller::ID,
)]
pub solver_registry: Box<Account<'info, EntityRegistry>>,

/// Any intent
pub intent: Box<Account<'info, Intent>>,

#[account(
seeds = [b"fulfilled-intent", intent.hash.as_ref()],
bump
)]
/// This PDA must be uninitialized (checked by SystemAccount type)
pub fulfilled_intent: SystemAccount<'info>,

#[account(
init,
seeds = [b"proposal", intent.key().as_ref(), solver.key().as_ref()],
bump,
payer = solver,
space = Proposal::total_size(&instructions, fees.len())?
)]
pub proposal: Box<Account<'info, Proposal>>,

pub system_program: Program<'info, System>,
}

pub fn create_proposal(
ctx: Context<CreateProposal>,
instructions: Vec<ProposalInstruction>,
fees: Vec<TokenFee>,
deadline: u64,
is_final: bool,
) -> Result<()> {
let now = Clock::get()?.unix_timestamp as u64;
let intent = &ctx.accounts.intent;

require!(deadline > now, SettlerError::DeadlineIsInThePast);
require!(intent.deadline > now, SettlerError::IntentIsExpired);
require!(
deadline <= intent.deadline,
SettlerError::ProposalDeadlineExceedsIntentDeadline
);
require!(
intent.validators.len() >= intent.min_validations as usize,
SettlerError::InsufficientIntentValidations
);
require!(intent.is_final, SettlerError::IntentIsNotFinal);
require!(
fees.len() == intent.max_fees.len(),
SettlerError::InvalidFeeMint
);

fees.iter()
.zip(&intent.max_fees)
.try_for_each(|(fee, max_fee)| {
require_keys_eq!(fee.mint, max_fee.mint, SettlerError::InvalidFeeMint);
require_gte!(
max_fee.amount,
fee.amount,
SettlerError::FeeAmountExceedsMaxFee
);
Ok(())
})?;

let proposal = &mut ctx.accounts.proposal;

proposal.intent = intent.key();
proposal.creator = ctx.accounts.solver.key();
proposal.deadline = deadline;
proposal.is_final = is_final;
proposal.is_signed = false;
proposal.instructions = instructions;
proposal.fees = fees;
proposal.bump = ctx.bumps.proposal;

Ok(())
}
84 changes: 84 additions & 0 deletions packages/svm/programs/settler/src/instructions/execute_proposal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use anchor_lang::prelude::*;

use crate::{
controller::{self, accounts::EntityRegistry, types::EntityType},
errors::SettlerError,
state::{FulfilledIntent, Intent, Proposal},
types::IntentEvent,
};

#[derive(Accounts)]
pub struct ExecuteProposal<'info> {
#[account(mut)]
pub solver: Signer<'info>,

#[account(
seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()],
bump = solver_registry.bump,
seeds::program = controller::ID,
)]
pub solver_registry: Box<Account<'info, EntityRegistry>>,

/// CHECK: account defined in proposal
#[account(mut)]
pub proposal_creator: UncheckedAccount<'info>,

#[account(
mut,
has_one = intent @ SettlerError::IncorrectIntentForProposal,
constraint = proposal.creator == proposal_creator.key() @ SettlerError::IncorrectProposalCreator,
constraint = proposal.is_signed @ SettlerError::ProposalIsNotSigned,
close = proposal_creator
)]
pub proposal: Box<Account<'info, Proposal>>,

/// CHECK: account defined in intent
#[account(mut)]
pub intent_creator: UncheckedAccount<'info>,

#[account(
mut,
constraint = intent.creator == intent_creator.key() @ SettlerError::IncorrectIntentCreator,
close = intent_creator
)]
pub intent: Box<Account<'info, Intent>>,

#[account(
init,
seeds = [b"fulfilled-intent", intent.hash.as_ref()],
bump,
space = 8 + FulfilledIntent::INIT_SPACE,
payer = solver
)]
pub fulfilled_intent: Box<Account<'info, FulfilledIntent>>,

pub system_program: Program<'info, System>,
}

pub fn execute_proposal(ctx: Context<ExecuteProposal>) -> Result<()> {
let now = Clock::get()?.unix_timestamp as u64;
let proposal = &ctx.accounts.proposal;
let intent = &ctx.accounts.intent;

require!(proposal.deadline > now, SettlerError::ProposalIsExpired);

// TODO: Execute proposal

// TODO: Validate execution

// TODO: Emit events
intent.events.iter().for_each(|event| {
emit!(IntentEventEvent {
event: event.clone()
})
});

// TODO: Pay fees to Solver

Ok(())
}

#[event]
pub struct IntentEventEvent {
event: IntentEvent,
}
8 changes: 8 additions & 0 deletions packages/svm/programs/settler/src/instructions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
pub mod add_instructions_to_proposal;
pub mod claim_stale_intent;
pub mod claim_stale_proposal;
pub mod create_intent;
pub mod create_proposal;
pub mod execute_proposal;
pub mod extend_intent;
pub mod initialize;

pub use add_instructions_to_proposal::*;
pub use claim_stale_intent::*;
pub use claim_stale_proposal::*;
pub use create_intent::*;
pub use create_proposal::*;
pub use execute_proposal::*;
pub use extend_intent::*;
pub use initialize::*;
30 changes: 29 additions & 1 deletion packages/svm/programs/settler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,30 @@ pub mod state;
pub mod types;
pub mod utils;

use crate::{instructions::*, types::*};
use crate::{instructions::*, state::*, types::*};

#[program]
pub mod settler {
use super::*;

pub fn add_instructions_to_proposal(
ctx: Context<AddInstructionsToProposal>,
more_instructions: Vec<ProposalInstruction>,
finalize: bool,
) -> Result<()> {
instructions::add_instructions_to_proposal(ctx, more_instructions, finalize)
}

pub fn claim_stale_intent(ctx: Context<ClaimStaleIntent>) -> Result<()> {
instructions::claim_stale_intent(ctx)
}

pub fn claim_stale_proposal<'info>(
ctx: Context<'_, '_, 'info, 'info, ClaimStaleProposal<'info>>,
) -> Result<()> {
instructions::claim_stale_proposal(ctx)
}

pub fn create_intent(
ctx: Context<CreateIntent>,
intent_hash: [u8; 32],
Expand Down Expand Up @@ -48,6 +62,20 @@ pub mod settler {
)
}

pub fn create_proposal(
ctx: Context<CreateProposal>,
instructions: Vec<ProposalInstruction>,
fees: Vec<TokenFee>,
deadline: u64,
is_final: bool,
) -> Result<()> {
instructions::create_proposal(ctx, instructions, fees, deadline, is_final)
}

pub fn execute_proposal(ctx: Context<ExecuteProposal>) -> Result<()> {
instructions::execute_proposal(ctx)
}

pub fn extend_intent(
ctx: Context<ExtendIntent>,
more_data: Option<Vec<u8>>,
Expand Down
2 changes: 2 additions & 0 deletions packages/svm/programs/settler/src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pub mod fulfilled_intent;
pub mod intent;
pub mod proposal;
pub mod settler_settings;

pub use fulfilled_intent::*;
pub use intent::*;
pub use proposal::*;
pub use settler_settings::*;
Loading