Read from devnet · 8 holders, 50 transactions

What runs on Solana, step by step

Every step of the bond's life is one Solana transaction. For each: who may send it, which programs ran in which order (a program calling another is indented under it), every account each one used with its role, what the program checks before it acts, and which balances changed. Each example is a real transaction of the 8-holder devnet run.

Every bond movement runs through bond_servicing

One row per step, top to bottom in the bond's order. Columns: who may send the step, then the programs. Arrows: the calls between them in the order the runtime ran them, read from the transactions below; an arrow into bond_servicing is green. In a transfer, the holder calls Token-2022 and Token-2022 calls bond_servicing back: the arrow pointing left. In this run the registrar's wallet paid every fee.

AllPlay walks through the steps: each row lights up and draws its calls in the order they ran. Prev, Next or the slider pick one step; All shows every row.
may send the stepprogramsRegistrarHolderBankAnyonebond_servicingToken-2022SPL Token
1Issuecreate_bondbond time 2026-03-03 12:00transaction
CreateBond
InitializePermanentDelegate
initializeTransferHook
PermissionedBurnExtension
InitializeMint2

bond_servicing writesBond accountHook account list

not drawnSystem program (3)

2Issueregister_holder + placebond time 2026-03-05 12:00 · drawn for BH#1; the transaction did the same for 2 moretransaction
RegisterHolder
Place
MintTo

bond_servicing writesHolder record · BH#1Bond account

not drawnSystem program (2), Associated Token Account program (2), and the 3 calls they made

3Offercreate_offerbond time 2026-03-09 12:00transaction
CreateOffer
initializeAccount3

bond_servicing writesOffer 0 · buyback

not drawnSystem program (2)

4Offerthe bank pays into the offer's vaultbond time 2026-03-16 12:00transaction
mintTo
5Offerconfirm_offer_fundingbond time 2026-03-17 12:00transaction
ConfirmOfferFunding

bond_servicing writesOffer 0 · buyback

6Recordrecord moment: no transactionbond time 2026-09-07 09:00
Record moment 0, 2026-09-07 09:00 bond time: nobody sends anything and no program runs. The next transfer saves the holdings for it (step 7).
7Tradetransfer (Token-2022) -> transfer_hookbond time 2026-09-10 12:00transaction
TransferChecked
TransferHook
8Offersell_to_offerbond time 2026-09-16 12:00transaction
SellToOffer
PermissionedBurnExtension
transferChecked
9Fundopen_event (coupon)bond time 2026-09-21 12:00transaction
OpenEvent
initializeAccount3

bond_servicing writesEvent 0 · coupon

not drawnSystem program (2)

10Fundthe bank pays into the coupon's vaultbond time 2026-09-22 12:00transaction
mintTo
11Fundconfirm_fundingbond time 2026-09-23 12:00transaction
ConfirmFunding

bond_servicing writesEvent 0 · coupon

12Paypay_couponbond time 2026-09-27 12:00 · drawn for BH#1; the transaction did the same for 4 moretransaction
PayCoupon
transferChecked

bond_servicing writesHolder record · BH#1

13Offerchange_tenderbond time 2027-03-16 12:00transaction
ChangeTender

bond_servicing writesOffer 1 · tenderHolder record · BH#1Tender · BH#1

not drawnSystem program (1)

14Offerresolve_tenderbond time 2027-09-18 12:00transaction
ResolveTender
15Offersettle_tenderbond time 2027-09-20 12:00transaction
SettleTender
PermissionedBurnExtension
transferChecked
16Redeemredeem_holderbond time 2028-03-26 12:00 · drawn for BH#1; the transaction did the same for 3 moretransaction
RedeemHolder
PermissionedBurnExtension
transferChecked
0

The parts

walletprogrambond_servicing accounttoken account or mintrolesignswritesHover a name for its address; click it to open it in Solana Explorer.

Walletsthey sign transactions
  • RegistrarRegistrar: the issuer's agent: creates the bond, registers holders, places bonds, opens offers
  • BH#1BH#2BH#3BH#4BH#5BH#6BH#7BH#8Holders: bond holders' wallets; each signs its own transfers, sales and tenders
  • BankBank: the cash token's mint and freeze authority (simulated): credits the vaults
Programsthey run instructions
  • bond_servicingbond_servicing: this program: the bond's rules
  • Token-2022Token-2022: runs the bond token; calls bond_servicing on every bond transfer
  • SPL TokenSPL Token: runs the cash token
  • Associated Token Account programAssociated Token Account program: opens a wallet's token account for a mint
  • System programSystem program: creates accounts and moves SOL for their rent
  • Compute Budget programCompute Budget program: sets the transaction's compute limit; no part of the bond's logic
  • Memo programMemo program: adds a memo to a payment when the receiving cash account requires one
bond_servicing accountsonly the program writes them; addresses derived from seeds
Tokensmints and token accounts
1 create_bond2 register_holder + place3 create_offer4 the bank pays into the offer's vault5 confirm_offer_funding6 record moment: no transaction7 transfer (Token-2022) -> transfer_hook8 sell_to_offer9 open_event (coupon)10 the bank pays into the coupon's vault11 confirm_funding12 pay_coupon13 change_tender14 resolve_tender15 settle_tender16 redeem_holder
1Issue

create_bond

may be sent by: Registrar

The registrar creates the bond in one transaction: the bond token, the bond account that holds the terms and the time table, and the list of accounts the transfer hook needs. The program makes the bond account the token's mint authority, permanent delegate and burn authority, sets itself as the transfer hook, and gives the token no freeze authority.

bond time 2026-03-03 12:00chain time 2026-10-10 18:22:54 UTCslot 509,629,460fee 10,000 lamports, paid by Registrarother signers: Bond token (mint)59,688 compute units

Checksrefused with this error if not met
  • InvalidTermsface value, issue size, rate, redemption price, period and time compression above zero; record and payment times inside a day; payments a year x months a period = 12
  • InvalidSchedulepayment and record days match the schedule computed from issue and maturity: business days, payment at most 14 days from the period end, record moments in order and not after payment, at most 40 periods
  • UnsupportedCashTokenthe cash token is SPL Token, or Token-2022 without a transfer fee, transfer hook, non-transferability or permanent delegate
  • account existsthe bond account and the hook list are new accounts at their seeds
Balancestoken accounts that changed

No token balance changed: only program accounts were written.

2Issue

register_holder + place

may be sent by: Registrar

The registrar admits a wallet (KYC is simulated) and places bonds with it: a holder record is created for the wallet, its bond account is opened, and the program mints the bonds into it, signing as mint authority. The holder record and the bond's supply count the new bonds.

bond time 2026-03-05 12:00chain time 2026-10-10 18:22:56 UTCslot 509,629,467fee 5,000 lamports, paid by Registrarother signers: none164,546 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1bond_servicingRegisterHolder12,796 CU
    • HolderRegistered: BH#1
  2. 2System programcreateAccount
  3. 1Associated Token Account programcreateIdempotent21,779 CU
  4. 2Token-2022GetAccountDataSize1,609 CU
  5. 2System programcreateAccount
  6. 2Token-2022InitializeImmutableOwner734 CU
  7. 2Token-2022InitializeAccount33,634 CU
  8. 1Associated Token Account programcreateIdempotent4,437 CU
  9. 1bond_servicingPlace27,505 CU
    • BondsPlaced: 10 bonds to BH#1
  10. 2Token-2022MintTo2,288 CU

The same transaction also ran RegisterHolder + Place for BH#2; RegisterHolder for BH#3; only BH#1's RegisterHolder + Place is drawn. Balances are the whole transaction's.

Checksrefused with this error if not met
  • ConstraintHasOneonly the bond's registrar registers and places
  • account existsone holder record per wallet and bond, at ["holder", bond token, wallet]
  • PlacementClosedplacement only before the first record moment
  • ExceedsIssueSizesupply + new bonds within the issue size from the terms
  • IssueTooLargeevery coupon and the principal of the new supply fit in a token amount
Balancestoken accounts that changed
AccountBeforeAfterChange
BH#2 · bonds05+5
BH#1 · bonds010+10
3Offer

create_offer

may be sent by: Registrar

The registrar opens a buyback (first come) with a price, a maximum and a window. The program creates the offer and a vault for its cash, a cash account whose authority is the bond account.

bond time 2026-03-09 12:00chain time 2026-10-10 18:23:00 UTCslot 509,629,484fee 5,000 lamports, paid by Registrarother signers: none24,177 compute units

Checksrefused with this error if not met
  • ConstraintHasOneonly the bond's registrar opens offers
  • InvalidOfferprice and maximum above zero; the window opens before it closes and closes by the final record moment; price x maximum fits
Balancestoken accounts that changed

No token balance changed: only program accounts were written.

4Offer

the bank pays into the offer's vault

may be sent by: Bank

Not a bond_servicing instruction. The bank, the cash token's mint authority, credits the vault: an SPL Token MintTo. In real markets this is the issuer's payment to its paying agent; here the bank stands for it.

bond time 2026-03-16 12:00chain time 2026-10-10 18:23:07 UTCslot 509,629,515fee 10,000 lamports, paid by Registrarother signers: Bank120 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1SPL TokenmintTo120 CU
Checksrefused with this error if not met
  • owner does not matchSPL Token: only the cash token's mint authority mints
Balancestoken accounts that changed
AccountBeforeAfterChange
Vault · offer 0 (buyback)0.003,960.00+3,960.00
5Offer

confirm_offer_funding

may be sent by: Anyone

Anyone confirms that the vault holds price x maximum. Only then may holders sell or tender.

bond time 2026-03-17 12:00chain time 2026-10-10 18:23:08 UTCslot 509,629,519fee 5,000 lamports, paid by Registrarother signers: none5,109 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1bond_servicingConfirmOfferFunding5,109 CU
    • OfferFunded: offer 0, vault 3,960.00 of 3,960.00
Checksrefused with this error if not met
  • AlreadyFundednot confirmed before
  • InsufficientFundingthe vault holds at least price x maximum
Balancestoken accounts that changed

No token balance changed: only program accounts were written.

6Record

record moment: no transaction

may be sent by: Nobody

Nothing is sent at a record moment, and no program runs: a Solana program runs only inside a transaction someone sends. This bond's record moments are 2026-09-07 09:00, 2027-03-09 09:00, 2027-09-06 09:00, 2028-03-06 09:00 (bond time, UTC+5). Each holder record keeps a position history: the quantity now, live_from (how many record moments had passed at its last change) and at[e], the quantity saved for record moment e. The next change to a holding, a transfer, a sale or a redemption, first copies the current quantity into at[] for every record moment passed since live_from. Between two changes a holding is constant, so that copy is exactly the holding at those moments, however late anyone looks.

Measuredrecord moment 0 at chain time 18:26:01 UTC; the first transfer after it (step 7) logged
  • PositionsSaved: BH#1 held 10 at record moment 0
  • PositionsSaved: BH#3 held 0 at record moment 0

A coupon is paid on at_moment(e): at[e] if the holding changed after moment e, else the quantity now. A holder that never trades needs no transaction at all.

7Trade

transfer (Token-2022) -> transfer_hook

may be sent by: Holder

A holder sends bonds with Token-2022's TransferChecked. Token-2022 moves the balances, then calls bond_servicing's transfer hook with the accounts listed at bond creation. The hook keeps each holder's quantity for every record moment passed since its last change (PositionsSaved), then records the new quantities. This is how the register knows who held what at 09:00 without anyone taking a list.

bond time 2026-09-10 12:00chain time 2026-10-10 18:26:05 UTCslot 509,630,261fee 10,000 lamports, paid by Registrarother signers: BH#151,264 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1Token-2022TransferChecked51,264 CU
  2. 2bond_servicingTransferHook28,786 CU
    • PositionsSaved: BH#1 held 10 at record moment 0
    • PositionsSaved: BH#3 held 0 at record moment 0
Checksrefused with this error if not met
  • NotTransferringruns only inside a real Token-2022 transfer
  • NotAssociatedTokenAccountboth sides are the holders' associated bond accounts
  • AccountOwnedByWrongProgramboth wallets have holder records (are registered)
  • TransfersClosedAtMaturitybefore the final record moment
  • PositionMismatcheach holder record agrees with its balance before the transfer
  • BondsLockedthe seller keeps at least its tendered bonds
Balancestoken accounts that changed
AccountBeforeAfterChange
BH#3 · bonds03+3
BH#1 · bonds107-3
8Offer

sell_to_offer

may be sent by: Holder

A holder sells bonds to the buyback. In one transaction the program burns them (Token-2022 permissioned burn, the bond account signing as burn authority), pays price x quantity from the offer's vault (SPL Token TransferChecked, the bond account signing as the vault's authority), and takes them off the holder's record and the supply, saving both histories first.

bond time 2026-09-16 12:00chain time 2026-10-10 18:26:11 UTCslot 509,630,285fee 10,000 lamports, paid by Registrarother signers: BH#245,211 compute units

Checksrefused with this error if not met
  • ConstraintRawthe signer is the holder whose bonds are sold
  • WrongOfferKinda first-come buyback
  • OfferNotFundedthe offer is confirmed funded
  • OfferNotOpeninside the window
  • ExceedsOfferquantity above zero and the offer's total within its maximum
  • InsufficientUnlockednot more than the holder's untendered bonds
  • PositionMismatchthe holder record agrees with its bond balance
Balancestoken accounts that changed
AccountBeforeAfterChange
BH#2 · bonds53-2
BH#2 · cash59,590.0061,570.00+1,980.00
Vault · offer 0 (buyback)3,960.001,980.00-1,980.00
9Fund

open_event (coupon)

may be sent by: Anyone

After a record moment anyone opens its coupon. The program reads the supply at that moment from the bond's supply history, fixes the budget (coupon on the whole supply, rounded down once) and creates the event and its own vault. No holder is counted.

bond time 2026-09-21 12:00chain time 2026-10-10 18:26:16 UTCslot 509,630,305fee 5,000 lamports, paid by Registrarother signers: none18,896 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1bond_servicingOpenEvent18,896 CU
    • EventOpened: event 0, supply at the record moment 33, budget 1,650.00
  2. 2System programcreateAccount
  3. 2System programcreateAccount
  4. 2SPL TokeninitializeAccount3233 CU
Checksrefused with this error if not met
  • RecordMomentNotReachedthe coupon's record moment has passed
  • account existsone event per index, at ["event", bond, index], with its vault at ["vault", event]
Balancestoken accounts that changed

No token balance changed: only program accounts were written.

10Fund

the bank pays into the coupon's vault

may be sent by: Bank

Not a bond_servicing instruction: the bank credits the coupon's vault with the budget (SPL Token MintTo). In real markets, the issuer pays the coupon to the paying agent.

bond time 2026-09-22 12:00chain time 2026-10-10 18:26:17 UTCslot 509,630,310fee 10,000 lamports, paid by Registrarother signers: Bank120 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1SPL TokenmintTo120 CU
Checksrefused with this error if not met
  • owner does not matchSPL Token: only the cash token's mint authority mints
Balancestoken accounts that changed
AccountBeforeAfterChange
Vault · coupon 00.001,650.00+1,650.00
11Fund

confirm_funding

may be sent by: Anyone

Anyone confirms that the vault covers the budget. Payment starts only then, so no holder is paid from an event that cannot pay all of them.

bond time 2026-09-23 12:00chain time 2026-10-10 18:26:18 UTCslot 509,630,315fee 5,000 lamports, paid by Registrarother signers: none4,830 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1bond_servicingConfirmFunding4,830 CU
    • EventFunded: event 0, vault 1,650.00 of budget 1,650.00
Checksrefused with this error if not met
  • AlreadyFundednot confirmed before
  • InsufficientFundingthe vault holds at least the budget
Balancestoken accounts that changed

No token balance changed: only program accounts were written.

12Pay

pay_coupon

may be sent by: Anyone

Anyone pays a holder its coupon. The amount comes from the holder's own record (its quantity at the record moment) and the terms; the cash goes from the event's vault to the holder's cash account (created if missing), and the holder's paid bit is set in the same transaction. One transaction can pay several holders, one instruction each.

bond time 2026-09-27 12:00chain time 2026-10-10 18:26:22 UTCslot 509,630,330fee 5,000 lamports, paid by Registrarother signers: none119,365 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1bond_servicingPayCoupon22,374 CU
    • CouponPaid: BH#1, coupon 0, on 10 bonds, 500.00
  2. 2SPL TokentransferChecked105 CU

The same transaction also ran PayCoupon for BH#2, BH#4, BH#5, BH#7; only BH#1's PayCoupon is drawn. Balances are the whole transaction's.

Checksrefused with this error if not met
  • WrongEventKinda coupon event
  • NotFundedthe event is confirmed funded
  • PaymentNotOpenbond time has reached the payment time
  • AlreadyPaidthe holder's paid bit for this coupon is not set
  • NothingToPaythe position at the record moment gives a coupon above zero
  • ConstraintHasOnethe holder record belongs to this wallet and bond; the vault is the event's
Balancestoken accounts that changed
AccountBeforeAfterChange
BH#1 · cash87,650.0088,150.00+500.00
Vault · coupon 01,650.000.00-1,650.00
BH#2 · cash61,570.0061,820.00+250.00
BH#5 · cash26,250.0026,450.00+200.00
BH#4 · cash27,600.0028,000.00+400.00
BH#7 · cash0.00300.00+300.00
13Offer

change_tender

may be sent by: Holder

A holder tenders bonds into the tender offer. The bonds do not move: the holder record locks them, so a coupon whose record moment falls in the window is still paid on them. The tender is numbered in the order it was made.

bond time 2027-03-16 12:00chain time 2026-10-10 18:29:12 UTCslot 509,631,035fee 10,000 lamports, paid by Registrarother signers: BH#126,527 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1bond_servicingChangeTender26,527 CU
    • TenderChanged: BH#1 tenders 6; offer total 6
  2. 2System programcreateAccount
Checksrefused with this error if not met
  • ConstraintHasOnethe signer owns the holder record
  • WrongOfferKinda pro-rata tender offer
  • OfferNotFundedthe offer is confirmed funded
  • OfferNotOpeninside the window
  • ExceedsOfferquantity above zero; a withdrawal not more than tendered
  • InsufficientUnlockednot more than the holder's untendered bonds
Balancestoken accounts that changed

No token balance changed: only program accounts were written.

14Offer

resolve_tender

may be sent by: Anyone

After the window anyone resolves the tenders, in the order they were made. If the offer is oversubscribed, each takes the pro-rata share of the running total, rounded down, minus what the earlier ones took, so the offer buys exactly its maximum. The rejected part is unlocked. No cash or token account is involved.

bond time 2027-09-18 12:00chain time 2026-10-10 18:32:18 UTCslot 509,631,799fee 5,000 lamports, paid by Registrarother signers: none16,467 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1bond_servicingResolveTender16,467 CU
    • TenderResolved: BH#1, 3 accepted, 3 released
Checksrefused with this error if not met
  • WrongOfferKinda pro-rata tender offer
  • OfferStillOpenthe window has closed
  • TenderOutOfOrderthis tender's number is the next to resolve
Balancestoken accounts that changed

No token balance changed: only program accounts were written.

15Offer

settle_tender

may be sent by: Anyone

Anyone settles a resolved tender: the accepted bonds are burned and paid for in one transaction, as in the buyback, and released from the lock.

bond time 2027-09-20 12:00chain time 2026-10-10 18:32:20 UTCslot 509,631,807fee 5,000 lamports, paid by Registrarother signers: none52,631 compute units

Checksrefused with this error if not met
  • WrongOfferKinda pro-rata tender offer
  • OfferStillOpenthe window has closed
  • AlreadySettlednot settled before
  • PositionMismatchthe holder record agrees with its bond balance
Balancestoken accounts that changed
AccountBeforeAfterChange
Vault · offer 1 (tender)6,000.003,000.00-3,000.00
BH#1 · cash88,950.0091,950.00+3,000.00
BH#1 · bonds85-3
16Redeem

redeem_holder

may be sent by: Anyone

At maturity, after the redemption is opened and funded like a coupon, anyone redeems a holder: the program burns its bonds and pays the principal from the redemption vault in one transaction, so no bond is burned unpaid and no principal is paid for a live bond.

bond time 2028-03-26 12:00chain time 2026-10-10 18:35:28 UTCslot 509,632,592fee 5,000 lamports, paid by Registrarother signers: none157,953 compute units

Executionprograms in the order they ran; indented = called by the one above
  1. 1bond_servicingRedeemHolder40,611 CU
    • PositionsSaved: BH#1 held 5 at record moment 3
    • BondsRedeemed: BH#1, 5 burned, 5,000.00 principal
  2. 2Token-2022PermissionedBurnExtension2,878 CU
  3. 2SPL TokentransferChecked105 CU

The same transaction also ran RedeemHolder for BH#2, BH#3, BH#4; only BH#1's RedeemHolder is drawn. Balances are the whole transaction's.

Checksrefused with this error if not met
  • WrongEventKindthe redemption event
  • NotFundedthe redemption is confirmed funded
  • PaymentNotOpenbond time has reached the payment time
  • PositionMismatchthe holder record agrees with its bond balance
  • NothingToPaythe holder has untendered bonds
Balancestoken accounts that changed
AccountBeforeAfterChange
BH#2 · bonds20-2
BH#3 · bonds10-1
BH#3 · cash15,800.0016,800.00+1,000.00
BH#1 · cash92,200.0097,200.00+5,000.00
BH#1 · bonds50-5
BH#2 · cash62,170.0064,170.00+2,000.00
BH#4 · cash28,200.0032,200.00+4,000.00
BH#4 · bonds40-4
Vault · redemption23,000.0011,000.00-12,000.00