Skip to main content
CONFIDENTIAL & PROPRIETARY © 2026 Inkwell Finance, Inc. All Rights Reserved. This document is for informational purposes only and does not constitute legal, tax, or investment advice, nor an offer to sell or a solicitation to buy any security or other financial instrument. Any examples, structures, or flows described here are design intent only and may change.
Pre-alpha. Not deployed, not externally audited. The guardrails below are enforced by the program and covered by its integration tests. The confidentiality they rely on holds only from Encrypt Alpha 1.
Approvers never see a salary. The guardrails let them bound exposure anyway, with public numbers they set themselves.

Run budget

open_pay_run(period, budget) sets a public total ceiling for the run. prepare_pay_run turns it into an encrypted remaining accumulator. Every payment and bonus in the run is checked against it inside the pay graph and subtracted from it on success. A payment that would exceed the remaining budget moves nothing. budget set to u64::MAX (the SDK’s UNLIMITED) means no ceiling.

Salary cap

update_policy(auditor, salary_cap, paused) sets a public per-payment cap. Each run snapshots the cap when it opens. Inside the pay graph, any single payment above the cap moves nothing. This bounds the damage from a bad salary ciphertext. The cap starts at u64::MAX (no cap) when the company is created.

The flagged counter

Each run carries an encrypted flagged counter, starting at 0. The pay graph increments it whenever a payment fails any check:
A flagged payment moves nothing. The count stays encrypted. The authority or the auditor reads it through grant_view (target: run flagged) and fixes shortfalls with a make-up run or a bonus.

Solvency invariant

Every graph conserves value:
The full lifecycle test checks this after a complete cycle: fund, hire, run, withdraw, treasury withdrawal. The FHE graphs are also parity-tested bit for bit against a plaintext shadow model, on an edge grid of roughly 14k cases plus 5k random cases, for conservation, all-or-nothing, no overdraft, cap and flag properties.

Withdrawals survive a pause

employee_withdraw deliberately has no paused or active check. Earned wages belong to the employee and stay withdrawable when the company is paused or the employee is deactivated. Pausing stops new runs and new payments; it does not stop exits.

Other guards

Known limitations

  • Failed payments consume the slot. A payment the graph flags (treasury short, budget exhausted, over cap) still marks the employee paid for that run, because success is encrypted. The authority sees the encrypted flagged count via grant_view and fixes it with a make-up run or a bonus. The sync guard, the no-withdrawal-mid-run rule and the optional operator remove the ways an outsider could trigger this.
  • Without an operator, the crank picks the order. If the treasury is short, whoever cranks decides who is paid first. Set an operator if that matters.
  • Salary and bonus inputs are trusted from the authority. The ingress check can’t tell a fresh CreateInput ciphertext from another program-authorized handle, such as a run accumulator. This is authority-only, so it is not exploitable by outsiders, but the SDK should create fresh inputs.
  • Scratch-ciphertext rent. sync_treasury’s amount ciphertext and the no-payout paid ciphertext are inputs to graphs still pending when the instruction ends, so they aren’t closed. The loss is roughly 0.001 SOL per occurrence, paid by the caller.
  • Ordering. Consecutive graphs on the same ciphertext rely on Encrypt’s executor applying them in order, which is its documented behavior.
Two consequences of the code worth knowing when operating a run:
  • Deactivating an employee mid-run. The run’s headcount is a snapshot taken at open_pay_run, and process_payment rejects inactive employees. If an employee is deactivated before being paid in an open run, the paid count never reaches the headcount, so only the authority can close that run.
  • grant_view is not multisig-shaped. It creates a ciphertext, so it needs a fresh keypair and an Encrypt CPI, unlike the twelve authority instructions. See Multisig integration.
And one that is not a code limitation but governs everything else: Encrypt is pre-alpha and its devnet does no real encryption. See Who sees what.