Docs · Reference · Markdown
Contract (FlyingMoney.sol)
Solidity 0.8.24 with OpenZeppelin 5.1.0. No owner, no admin, no pause, no upgrade, no fee. One settlement token per deployment (the chain's Circle USDC), fixed in the constructor. Addresses for every chain: Deployments and /.well-known/flying-money.json.
Functions
| Function | Who | What |
|---|---|---|
issue(payee, spender, faceValue, expiresAt) → id |
anyone (the funder) | Pulls faceValue USDC and opens a certificate. Lifetime 1 hour to 365 days. spender must differ from the funder and the payee |
topUp(id, amount) |
funder | Adds to the face value |
extend(id, newExpiresAt) |
funder | Moves expiry later (never earlier) |
redeem(id, cumulative, memo, signature) → paid |
anyone | Pays cumulative − redeemed to the payee |
redeemMany(SignedNote[]) → totalPaid |
anyone | Batch redeem; bad notes are skipped with NoteSkipped, not reverted |
reclaim(id) → refunded |
funder, after expiry | Returns the unredeemed remainder and closes the certificate |
getCertificate(id) |
view | The certificate struct |
noteDigest(id, cumulative, memo) |
view | The EIP-712 digest a spender signs |
totalOutstanding() |
view | Σ (faceValue − redeemed) over open certificates |
Spenders are verified with ECDSA only (ECDSA.tryRecover, low-s). There is no ERC-1271: contract signatures can be revoked, so a note accepted offline could stop being valid.
Events
CertificateIssued(id, funder, payee, spender, faceValue, expiresAt) · CertificateToppedUp(id, amount, newFaceValue) · CertificateExtended(id, newExpiresAt) · NoteRedeemed(id, cumulative, paid, memo, redeemer) · NoteSkipped(id, cumulative, reason) · CertificateReclaimed(id, refunded)
NoteSkipped reasons: 1 unknown, 2 closed, 3 expired, 4 exceeds face value, 5 nothing to redeem, 6 bad signature.
Errors
InvalidParams · UnknownCertificate · NotFunder · Expired · NotExpired · Closed · ExceedsFaceValue · ExceedsCap · InvalidSignature · NothingToRedeem · UnsupportedToken
Launch caps
Two immutable caps, set at deployment (0 = unlimited):
maxFaceValue: per-certificate cap. Mainnets: 100 USDC.maxTotalOutstanding: deployment-wide cap. Mainnets: 1,000 USDC.
There is no admin, so caps can never be raised, only superseded by a new deployment. Testnets are uncapped.
Invariants (tested with Foundry, 256 runs × depth 50)
- I1
redeemed ≤ faceValuefor every certificate. - I2 Solvency: the contract's USDC balance equals
totalOutstanding, the sum offaceValue − redeemedover open certificates. I2btotalOutstanding ≤ maxTotalOutstandingwhen the cap is set. - I3 A payee receives exactly the highest valid total redeemed for its certificates.
- I4 A funder never pays out more than the face value it issued.
- I5 No certificate pays anyone other than its payee (redeem) or its funder (reclaim).
- I6 After reclaim, no further transfers happen for that certificate.
- I7
redeemednever decreases.
Unit tests also cover ECDSA-only permanence (a spender address that later gains contract code changes nothing), high-s rejection, domain separation and key isolation. Details and gas numbers: SECURITY.md.