README.md
gno.land/p/moul/x/daily/pullpayment/v0
Pull-payment escrow ledger — New, Credit, CreditMany, Withdraw,
Forfeit, Balance, TotalOwed, TotalWithdrawn, Payees, Iterate,
Consistent, MaxPayees.
1import "gno.land/p/moul/x/daily/pullpayment/v0"
2
3l := pullpayment.New()
4l.CreditMany([]string{"alice", "bob"}, []int64{500, 300})
5l.TotalOwed() // 800 — what the realm must keep in reserve
6
7amt, err := l.Withdraw("bob") // 300; the balance is ALREADY zeroed
8// ...the caller transfers `amt` only now
The classic Solidity answer to reentrancy: never push value to an address, credit it and let the recipient withdraw. A push hands control to the recipient in the middle of your state transition, and a hostile recipient re-enters before you have finished updating.
This package is the bookkeeping half only — it moves no coins. The realm
holding the funds transfers after calling Withdraw, which is exactly the
ordering the pattern demands: checks, effects, then interactions. The balance
is already deleted when control leaves, so a reentrant Withdraw returns
ErrNothing and TotalWithdrawn is not double-counted. That property has its own
test.
Other guarantees, each tested:
CreditManyis all-or-nothing. A batch with one bad entry applies none of itself — a ledger half-agreeing with the funds it guards is worse than a rejected call.- Overflow is refused, not wrapped, on both a single balance and the total.
Consistent()is exported:TotalOwedalways equals the sum of the balances.
Payees comes back sorted, never in map order, so a Render built from it
cannot differ between nodes.
Live demo: r/moul/x/daily/pullpaymentdemo
· render it at /r/moul/x/daily/pullpaymentdemo/v0.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.