Skip to content

Conversation

@GuidoDipietro
Copy link
Member

@GuidoDipietro GuidoDipietro commented Dec 17, 2025

Adds Settler program, with instructions pertinent to the lifecycle of an Intent PDA.

Includes Rust program, Typescript SDK, and Typescript tests.


Instructions

Following are instructions for creation, extension, and deletion of an Intent.
Additionally, the initialize and set_paused_state instructions were added to manage the Settler program's global state.

initialize

Sets global state, defining the Whitelist program to be used and the paused state.
This program doesn't have an admin.

set_paused_state

Pauses or unpauses program. The admin of the defined Whitelist program can execute this action.

create_intent

Initializes an Intent PDA with all its fields.
NOTE: Some of these fields might not be necessary to have on-chain (or a hash might be enough). I have simply added them all until the Settler's first iteration is done and I can remove what is not necessary, as it is easier to remove than to add here. For now, we are assuming that we need all the fields.
Technical debt: Check that the Intent hash is correct.

extend_intent

Adds more data, max_fees, or events to an Intent PDA in case we ran out of space in the create_intent call.
Solana transactions have a limited size of 1232 bytes, so we can only pass in that many bytes (even less since there's other overhead) as instruction parameters. For this reason, Intents have an intermediate state with is_final=false where they can still be extended by the intent_creator with more data added in further transactions, non-atomically.

claim_stale_intent

If an Intent expired (deadline is in the past), the intent_creator can close the PDA and reclaim the rent, as this account is now useless.

State

Intent

PDA defining an Intent with all its fields.
NOTE: This account will be reduced in a future iteration, with fields that aren't necessary on-chain being removed.

FulfilledIntent

A minimal on-chain proof that an Intent existed and was executed, to save as much rent as possible.
When an Intent is executed, it is closed, rent goes back to the creator, and this PDA is initialized in its place.

SettlerSettings

Global state of the program.


NOTE: Some files might have content from future PRs given this series of PRs come from a larger PR that was split (#41). I have tried to keep this to a minimum but bear this in mind.

@GuidoDipietro GuidoDipietro self-assigned this Dec 18, 2025
@GuidoDipietro GuidoDipietro marked this pull request as ready for review December 18, 2025 16:39
@GuidoDipietro GuidoDipietro changed the title Settler: Intent lifecycle (2) Settler: Intent lifecycle Dec 18, 2025
Copy link
Member

@PedroAraoz PedroAraoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would update the imports to make them a bit shorter and reduce verbosity
from: Box<Account<'info, crate::whitelist::accounts::GlobalSettings>>,
to: Box<Account<'info, WhitelistSettings>>,
the same with the errors as we discussed offline.

@@ -0,0 +1,4 @@
pub const DEPLOYER_KEY: &'static str = env!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub const DEPLOYER_KEY: &'static str = env!(
pub const DEPLOYER_KEY: &str = env!(

#[account(
seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()],
bump = solver_registry.bump,
seeds::program = crate::controller::ID
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
seeds::program = crate::controller::ID
seeds::program = controller::ID


let settler_settings = &mut ctx.accounts.settler_settings;

settler_settings.controller_program = crate::controller::ID;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
settler_settings.controller_program = crate::controller::ID;
settler_settings.controller_program = controller::ID;

bump
)]
/// This PDA must be uninitialized
pub fulfilled_intent: SystemAccount<'info>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we doing something with this account?

Comment on lines +24 to +33
export function generateIntentHash(): string {
return Buffer.from(Array.from({ length: INTENT_HASH_LENGTH }, () => Math.floor(Math.random() * 256))).toString('hex')
}

/**
* Generate a random 32-byte hex string for nonce
*/
export function generateNonce(): string {
return Buffer.from(Array.from({ length: NONCE_LENGTH }, () => Math.floor(Math.random() * 256))).toString('hex')
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use randomHex() from the sdk

Comment on lines +90 to +140
/**
* Create a validated intent (with validations set to meet min_validations requirement)
*/
export async function createValidatedIntent(
solverSdk: SettlerSDK,
solverProvider: LiteSVMProvider,
client: LiteSVM,
options: {
intentHash?: string
minValidations?: number
isFinal?: boolean
deadline?: number
} = {}
): Promise<string> {
const intentHash = await createTestIntent(solverSdk, solverProvider, {
...options,
isFinal: options.isFinal ?? true,
})

// Set validations to meet min_validations requirement
const intentKey = solverSdk.getIntentKey(intentHash)
const intentAccount = client.getAccount(intentKey)
if (intentAccount) {
const intentData = Buffer.from(intentAccount.data)
// validations is at offset: 8 (disc) + 1 (op) + 32 (user) + 32 (intent_creator) + 32 (intent_hash) + 32 (nonce) + 8 (deadline) + 2 (min_validations) = 147
// validations is u16, so 2 bytes
const minValidations = options.minValidations ?? DEFAULT_MIN_VALIDATIONS
intentData.writeUInt16LE(minValidations, 147)
client.setAccount(intentKey, {
...intentAccount,
data: intentData,
})
}

return intentHash
}

/**
* Creates an allowlisted entity (validator, axia, or solver)
*/
export async function createAllowlistedEntity(
controllerSdk: ControllerSDK,
provider: LiteSVMProvider,
entityType: EntityType,
entityKeypair?: Keypair
): Promise<Keypair> {
const entity = entityKeypair || Keypair.generate()
const allowlistIx = await controllerSdk.createEntityRegistryIx(entityType, entity.publicKey)
await makeTxSignAndSend(provider, allowlistIx)
return entity
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* Create a validated intent (with validations set to meet min_validations requirement)
*/
export async function createValidatedIntent(
solverSdk: SettlerSDK,
solverProvider: LiteSVMProvider,
client: LiteSVM,
options: {
intentHash?: string
minValidations?: number
isFinal?: boolean
deadline?: number
} = {}
): Promise<string> {
const intentHash = await createTestIntent(solverSdk, solverProvider, {
...options,
isFinal: options.isFinal ?? true,
})
// Set validations to meet min_validations requirement
const intentKey = solverSdk.getIntentKey(intentHash)
const intentAccount = client.getAccount(intentKey)
if (intentAccount) {
const intentData = Buffer.from(intentAccount.data)
// validations is at offset: 8 (disc) + 1 (op) + 32 (user) + 32 (intent_creator) + 32 (intent_hash) + 32 (nonce) + 8 (deadline) + 2 (min_validations) = 147
// validations is u16, so 2 bytes
const minValidations = options.minValidations ?? DEFAULT_MIN_VALIDATIONS
intentData.writeUInt16LE(minValidations, 147)
client.setAccount(intentKey, {
...intentAccount,
data: intentData,
})
}
return intentHash
}
/**
* Creates an allowlisted entity (validator, axia, or solver)
*/
export async function createAllowlistedEntity(
controllerSdk: ControllerSDK,
provider: LiteSVMProvider,
entityType: EntityType,
entityKeypair?: Keypair
): Promise<Keypair> {
const entity = entityKeypair || Keypair.generate()
const allowlistIx = await controllerSdk.createEntityRegistryIx(entityType, entity.publicKey)
await makeTxSignAndSend(provider, allowlistIx)
return entity
}

If you don't mind deleting this as it is not used yet. I want to review it when it's used

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants