The Problem
Payroll’s broken: Workers wait.
Cash sits idle.
Traditional payroll has a structural mismatch. Employees accrue salary continuously, but payment arrives once a month. Meanwhile, expenses don't wait for payday.
For the employer, idle payroll capital earns almost nothing. Banks offer 0.5–2% APY on business accounts. Large institutions have the infrastructure to capture yield on idle capital. Most startups don't. The money just sits there.
Atlas Payroll was built to fix both problems simultaneously.
The Solution
Stream salary per second.
Yield on everything else.
Atlas routes idle payroll capital into Kamino Finance, a Solana-native lending protocol, earning yield on funds between disbursement cycles.
Simultaneously, employees accrue salary every second and can withdraw earned income at any point, with no loans, no overdrafts, no waiting.
If you earn $5,000 per month and need cash on March 13th, you can withdraw what you've earned so far, approximately $2,094 rather than taking a payday loan. Meanwhile, the remaining $2,906 is compounding at 13.35% APY inside Kamino instead of sitting idle in a bank account.
System Architecture
System Flow.
The protocol's financial logic splits into three independent sub-systems: operator capital management (deposit, withdraw), employee salary mechanics (accrue, claim, offboard), and the rebalance loop (Kamino → Safety Vault). Each is an isolated instruction set, they share state through the ProtocolVault account but cannot corrupt each other's invariants.
1 — Capital Provisioning (Deposit & Stake)
Deposit flow — liability checkpointed first, USDC routed to Kamino via CPI, kTokens minted to Protocol_ATA and recorded on ProtocolVault
2 — Keeper Rebalance Circuit (Yield Extraction & Liquidity Buffering)
Rebalance flow — Keeper triggers only when target > safety_amount; redeems kTokens via CPI, distributes bounty and fee, updates the vault buffer
3 — Employee Claim Flow (JIT Withdrawal)
Claim flow — liability checkpointed globally, claimable computed per employee; JIT Kamino redemption fires only when safety_amount is insufficient
What It Delivers
Shipped
Technical Decisions
Key Decisions and Solutions
Each decision below solved a constraint that didn't have an obvious answer.
Protocol Design · Salary Accrual · On-chain Accounting
Employee salary accrues continuously. The protocol tracks it through two fields on ProtocolVault: global_rate (the sum of all active per-second salary rates) and liability_timestamp (the last time the liability was checkpointed). Current total liability is derived on demand:
// checkpoint: update liability_timestamp = now
The critical constraint: always call update_liability() before mutating global_rate — specifically, adding or removing an employee.
Suppose the global rate is 10 USDC/sec and you add an employee who earns 2 USDC/sec. If you update the rate to 12 before checkpointing, then the next liability calculation will use 12 USDC/sec all the way back to liability_timestamp — retroactively overcounting what the protocol owes by 2 USDC/sec × elapsed time. The employee didn't start earning until now, but the arithmetic acts like they were earning since the last checkpoint.
The fix is simple, checkpoint first, mutate second. Every instruction that touches global_rate calls update_liability() as its very first line.
Solana SVM · Compute Units · bytemuck · Zero-Copy Deserialisation
To calculate how much USDC the protocol's kUSDC holdings are worth, we need to read Kamino's Reserve account — a large, complex struct that contains the full state of the lending market. It holds liquidity totals, collateral supply figures, interest rate curves, and a dozen other fields.
The standard Anchor approach — #[account] deserialisation — copies the entire struct from on-chain bytes into Solana's heap. For a 3.5+ KB struct, this is expensive in compute units and risks breaching Solana's heap limit, Especially in instructions that already perform multiple CPIs and token transfers.
#[zero_copy] with bytemuck avoids the copy entirely. Instead of deserialising the account into a Rust struct on the heap, it reinterprets the raw byte slice as a typed reference in-place. No allocation, no copy — the data is accessed directly from the account's memory as a Ref<Reserve>. Compute units for struct access drop to near zero.
The constraint bytemuck demands every field in the struct must have a known, fixed size with guaranteed alignment. This is where the u128 problem surfaces (see decision 3).
Fixed-Point Arithmetic · bytemuck · Kamino Finance internals
Two separate problems here that are easy to conflate — both require precise understanding of how Kamino stores its numbers.
The u128 alignment problem. Kamino's Reserve struct stores several liquidity fields as u128 — 16-byte unsigned integers. bytemuck requires that all fields in a zero-copy struct have alignment guarantees matching their size. u128 requires 16-byte alignment. Solana account data begins with an 8-byte discriminator, meaning subsequent fields are aligned on 8-byte boundaries, not 16. Using u128 directly in the bytemuck struct causes runtime panic.
The fix: split each u128 field into two u64 values in the struct definition, then reconstruct the full u128 at runtime when the value is needed. Both halves are 8-byte aligned. The reconstruction is one arithmetic operation.
The WAD problem. Kamino stores its liquidity values in a u68.60 fixed-point format, not the common 1e18 WAD used in most DeFi protocols. The scale factor is 2^60 = 1,152,921,504,606,846,976. Using the wrong scale factor — say, treating a value as if it were scaled by 10^18 — produces exchange rate calculations that are off by roughly 10%.
total_pool_usdc = raw_value / 10^18
// CORRECT — Kamino's actual scale factor
total_pool_usdc = raw_value / 2^60 // 1,152,921,504,606,846,976
Integer Arithmetic · Token Redemption · Solana SVM
When an employee claims salary or the keeper triggers a rebalance, the protocol needs to burn kUSDC tokens from Kamino to receive USDC in return. The calculation converts a required USDC amount into the equivalent kUSDC to burn:
The question is: which direction do we round the result?
Solana's SVM uses integer arithmetic exclusively. Division truncates toward zero by default. If we use floor division and the result has a fractional component, we burn slightly fewer kUSDC than required and Kamino returns slightly less USDC than we asked for. The employee's claim comes up short. In a protocol where every USDC unit matters, this is a silent underpayment.
Ceiling division guarantees the protocol always burns at least enough kUSDC to receive the required USDC. The employee is never shorted. The cost is that the protocol occasionally burns one extra unit of kUSDC — a negligible overpayment measured in fractions of a cent — but the employee always receives exactly what they're owed.
ktoken_to_burn = (required_usdc × total_ktoken + total_pool_usdc - 1) / total_pool_usdc
Mechanism Design · Off-chain Agents · Protocol Economics
The safety vault needs to be replenished after employee claims draw it down. Something has to trigger the rebalance instruction. The simplest approach is a centralised bot controlled by Stellus — but that reintroduces a centralised dependency and single point of failure into a protocol designed to be permissionless.
The alternative is a permissionless Keeper: any wallet can call the rebalance instruction, and any successfull wallet earns a fixed $1 USDC bounty per call, plus the protocol levies a 0.5% tax on the total rebalanced amount for its treasury.
Two guards protect the system from keeper abuse. The first fires before any Kamino interaction: if the vault is already adequately funded, or if the available yield is smaller than the combined bounty + tax, the instruction returns silently with no state change. A keeper calling at the wrong time wastes only their own transaction fee. The second guard fires after the redemption: if the actual USDC received from Kamino falls below the cost threshold (due to rounding), the instruction also returns silently.
The economic logic: keepers are incentivised to watch for underfunded vaults and call rebalance quickly — they earn $1 per call. The protocol is incentivised to set the bounty high enough to attract keepers but low enough to not erode yield. Neither party has an incentive to behave maliciously, because the guards ensure any abusive call costs the caller a transaction fee and returns nothing.
Testing Strategy
Testing against the real Kamino market
The test suite uses Surfpool to clone mainnet state into a local validator — every test runs against the exact on-chain data that production users would hit.