Pulling The Trigger
Inside initialize_intent.rs: How CHAKRA Locks Funds And Starts The Clock
Welcome to Day 7.
Day 4 was state.rs, the containers. Day 5 was errors.rs, the ten promises. Day 6 was events.rs, the voice that talks to the Sentinel Network without any middleman.
All three of those files have one thing in common. They cannot do anything by themselves. A struct cannot run. An error cannot fire on its own. An event cannot emit itself.
Something has to actually call them. Something has to be the first thing that runs when a real user does a real thing.
That something is initialize_intent.rs
This is the file that starts everything. Literally everything you have read about so far in this series, the escrow, the timeout, the event the Sentinel Nodes are listening for, all of it begins the moment this single instruction is called.
Part 1: The Accounts Struct
rust
#[derive(Accounts)]
#[instruction(target_chain_id: u64, nonce: u64)]
pub struct InitializeIntent<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init,
payer = user,
space = EscrowState::LEN,
seeds = [b"escrow", user.key().as_ref(), &target_chain_id.to_le_bytes(), &nonce.to_le_bytes()],
bump
)]
pub escrow_account: Account<'info, EscrowState>,
pub system_program: Program<'info, System>,
}Three accounts. That is it.
user is the Signer. This is whoever is initiating the cross chain intent. They have to sign because they are about to lose lamports from their wallet, and Solana will never let anyone move your lamports without your signature.
escrow_account is the star of the show. Look at the seeds. b"escrow" plus the user's pubkey plus the target_chain_id plus the nonce, all combined. This is the exact same EscrowState struct we spent all of Day 4 designing byte by byte. space = EscrowState::LEN is literally that 211 byte calculation from Day 4 finally being put to use.
system_program is there because we are about to do a CPI, and I will explain what that means in Part 4.
One small detail before we move on. Notice target_chain_id.to_le_bytes() and nonce.to_le_bytes(). LE means little endian. Keep that in your head. Hold onto it. When we get to submit_proof.rs later in this series you are going to see the exact same numbers encoded as BE, big endian, and the difference is not a mistake. It is on purpose. I will explain why when we get there.
Part 2: The Three Gatekeepers
Before anything gets written, before any lamport moves, the function runs three checks.
rust
require!(amount > 0, ChakraError::MathError);
require!(timeout_slots >= 150, ChakraError::TimeoutTooShort);
require!(timeout_slots <= 216000, ChakraError::TimeoutTooLong);You already met all three of these errors back on Day 5. amount > 0 stops someone from creating a pointless empty intent. timeout_slots >= 150 stops someone from setting a timeout so short the Sentinel Network physically cannot finish in time. timeout_slots <= 216000 stops someone from locking an account open for years.
This is what I mean when I say errors.rs is not a side file. It is the actual logic that runs inside the actual instruction. Day 5 was not theory. Day 5 was a preview of this exact moment.
If any of these three checks fail, the entire transaction reverts. The escrow account never even gets created. No partial state. Nothing half done. Solana either does the whole thing or none of it.
Part 3: Filling In The Blueprint
Once the gatekeepers pass, we start writing into the escrow account that Anchor just created for us.
rust
escrow.owner = ctx.accounts.user.key();
escrow.target_chain_id = target_chain_id;
escrow.nonce = nonce;
escrow.amount = amount;
escrow.start_slot = clock.slot;
escrow.timeout_slot = clock.slot.checked_add(timeout_slots).ok_or(ChakraError::MathError)?;
escrow.is_finalized = false;
escrow.is_cancelled = false;
escrow.source_chain = source_chain;
escrow.target_chain = target_chain;
escrow.target_address = target_address;
escrow.bump = ctx.bumps.escrow_account;Every single line here is one of the fields we designed in Day 4. owner, target_chain_id, nonce, amount, you have already read the reasoning for every one of these in the 60 Lines post. This is just the moment they actually get a value for the first time.
One line worth slowing down on. timeout_slot is not the timeout duration. It is current slot plus the duration. And look, it is wrapped in checked_add. If somehow adding the duration to the current slot would overflow a u64, instead of wrapping around to some tiny number in the past, the whole thing fails with MathError. This is the exact scenario I talked about on Day 5 when explaining why MathError exists. Today you are seeing the actual line of code where that protection lives.
Part 4: The Transfer, And What CPI Even Means
rust
let cpi_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.user.to_account_info(),
to: escrow_info,
},
);
transfer(cpi_context, amount)?;CPI stands for Cross Program Invocation. It basically means one program calling another program. Here, the CHAKRA program is calling the Solana System Program, which is the program responsible for moving lamports around.
Why can CHAKRA not just move the lamports itself directly. Because moving SOL is the System Program's job, and on Solana, programs do not bypass each other's responsibilities. CHAKRA says please, System Program, move this much from the user's account to the escrow account, and the System Program does it, because the user already signed this transaction.
The escrow account itself never signs anything here. It is on the receiving end. A PDA can receive lamports passively like this even though it has no private key, because receiving does not require a signature, only sending does.
After this line runs, the user's wallet balance just dropped by exactly amount, and the escrow PDA's balance went up by the same amount. The money is now sitting inside a Program Derived Address that only the CHAKRA program controls.
Part 5: The Signal
rust
emit!(ControlIntent {
owner: ctx.accounts.user.key(),
target_chain_id,
nonce,
amount,
source_chain,
target_chain,
target_address,
escrow_pda: escrow_key,
timeout_slot: escrow.timeout_slot,
});This is the very last line. This is Day 6 coming full circle. The exact ControlIntent struct we walked through field by field yesterday gets emitted right here, at the very end of this function, only after everything else has already succeeded.
Order matters here. The checks ran first. The escrow got written second. The funds moved third. The signal goes out last. By the time the Sentinel Nodes hear this event, the escrow PDA on chain already has the funds sitting in it, already has all the correct fields, ready for the Sentinel to fetch and cross check.
Part 6: What This One Function Actually Proves
Step back for a second. This single function, from top to bottom, is the entire user facing promise of CHAKRA in miniature.
You give it an amount and a destination. It checks your inputs are sane. It locks your funds in a place only the program controls. It writes down everything anyone will ever need to know about this intent. And then it shouts about it to the world, in a way that cannot be faked, because the shout only happens after the lock already happened on chain.
Nothing here asks you to trust me, Maha, or trust some company, or trust a multisig of strangers. It asks you to trust Solana's runtime, which enforces that this function either runs completely or not at all.
In part 8
we look at the file on the opposite end of the spectrum. initialize_intent.rs is the longest instruction in the program. Next file, cancel_intent.rs, might be the shortest piece of meaningful code I have ever shipped. And I want to talk about why being short is actually the entire point of that file.
See y'all for Part 8.
Here are some resources if u want to dig deeper

Written by Maha.. thankyou


