The hardest call
Ship 1.0 without sync.
I built shared balances over iCloud: invites, confirmed payments between two phones, push updates. But I had never tested it with two real accounts on two real phones, and a money app that syncs wrong is worse than one that doesn’t sync. Turning it on would also have brought Sign in with Apple, which means in-app account deletion and a different privacy disclosure.
So it sits behind one compile-time flag, connections = false, and 1.0 shipped local-only.
Cost: you can’t share a balance with a friend’s phone yet. That’s the first thing I’d build next, after real two-device testing.
Money
Decimal, never Double.
Floating point can’t hold cents exactly. Every amount is a Decimal, rounded to cents only at the edges.
Cost: more careful code everywhere money moves.
Payments
“Oldest” means the date on the card.
A payment pays off the oldest debts first, by the date you see, not when it was typed in. One comparator, used by every payment path.
Cost: rewiring every path to share it.
Receipt photos
Ask before anything leaves.
Apple requires permission before personal data goes to a third-party AI. The consent screen names Google Gemini and says what is sent. It also says Google doesn’t train on the photo, which is only true because the account is on paid billing. If that ever changes, the sentence comes out.
The API token
Assume it’s public.
A token inside an app binary can be read out in seconds. So instead of hiding it, the backend rate-limits every address before checking the token, caps image size, and sits under a budget cap on the billing account.