Skip to content
VOUCHVOUCHDocs

Guides

Credit lines on Solana

The worked example of a consumer program: pof-credit opens a credit line against a receipt from pof-gate, pays draws from a pool vault, and stops paying when the line’s proof goes stale.

On this page

#The flow

A credit line from a proofBorrowerthe bound walletRelayerpays feespof-gatepof-creditsubmit_attestationafter the Ed25519 instructionClaimReceipt["receipt", subject]open_line (signs; relayer pays)CPI mark_consumedborrower + [consumer] PDA signconsumed_by = pof-creditCreditLine["line", subject, borrower]draw(1,000 dUSDC)dUSDC from the pool vaultDraws only while the line’s latest proof is fresh (24 h by default, never past its expiry)refresh_line(new receipt)anchor no older than the last
The demo relayer pays every fee and the line's rent, so the borrower only signs. pof-credit consumes the receipt through a CPI that only its own consumer PDA can sign.
  1. Prove, bound to the borrower

    The holder proves at least the pool’s threshold, to the pool’s audience, bound to their Solana wallet.

  2. Attest and record a receipt

    The attestor signs the verdict; a relayer submits it to pof-gate, which creates the receipt.

  3. Open the line

    The borrower signs open_line; the relayer pays the rent. pof-credit checks the receipt and consumes it through pof-gate, so it can never open a second line.

  4. Draw, and refresh

    Draws pay out from the vault up to the limit while the proof is fresh; a new proof refreshes the line.

#Accounts

Program-derived accountspof-gateConfig[config]ClaimReceipt[receipt, subject]consumed · consumed_bypof-creditPool[pool, mint]Vault[vault, pool]CreditLine[line, subject, borrower][consumer] · signs the CPIpof-reserveConfig[config]Feed[feed, audience]total · heights · slotMint[mint, feed] · authority: feedopened againstmint authority
Seeds in brackets. A receipt is keyed by the proof id, so one proof makes one receipt; a credit line is keyed by the receipt and the borrower; a feed is keyed by its audience, and its mint's authority is the feed itself.

#Pool terms

TermSet byDemo value
audienceinit_poolblake2b("pof-credit:usdc-pool-1")
required_zatoshiinit_pool50,000,000,000 (500 ZEC)
line_limitinit_pool25,000 dUSDC
fresh_secsset_freshness86,400 (24 hours, the default)
min_anchor_heightset_freshnessthe oldest anchor accepted

#What open_line and refresh_line check

  • the receipt’s audience is the pool’s (WrongAudience);
  • its claim value is at least the threshold (BelowThreshold);
  • the signer is the receipt’s beneficiary, the bound wallet;
  • it has not been consumed (ReceiptConsumed) and its proof has not expired (ProofExpired);
  • its anchor is no older than the pool’s floor (AnchorTooOld), and, for a refresh, no older than the line’s last.

A line stays drawable until min(now + fresh_secs, proof expiry). After that, draw fails with NeedsFreshProof until a refresh. A holder who has spent the coins cannot make the new proof.

#Try it

/demo runs this whole flow on devnet with a demo relayer: the proof, the attestation, the receipt, the line, a 1,000 dUSDC draw, and the breakers (replay, a stranger’s wallet, a tampered attestation, a tampered proof, a revoked proof).