Inspiration

The idea came from a problem we kept running into ourselves. We would tell friends, “We should get food soon,” or “Let’s study together next week,” and then months would pass without anyone making a plan.

Social media makes it easy to keep up with people from a distance, but maintaining a friendship still takes effort. Someone has to choose a time, decide what to do, and follow through.

That led us to Tomo Together. The name comes from tomodachi, the Japanese word for friend, and “tomorrow,” since tomorrow is often when we promise to see our friends. The app helps turn those promises into actual plans.

What it does

Tomo Together gives users a fun way to stay connected with their friends.

Users add friends who appear as pixel-art animal avatars. Completing hangouts increases a relationship score called “vibe.” Each friend profile shows shared memories, upcoming plans, activity preferences, friendship progress, and the date of the last hangout.

The app also helps groups decide what to do. Users compare activities through quick preference duels. Tomo Together learns from those choices and recommends activities that fit the group’s interests.

After a hangout, someone uploads a photo and the attendees verify that they met using NFC phone taps. The completed hangout is then saved in the Memory Book.

A real-money commitment pool, powered by Unifold

Every user gets a USDC balance inside the app. Adding funds takes one tap: Unifold provisions a personal deposit address on Base, and money sent there from any wallet or exchange appears in the app through a verified webhook. Nobody installs a wallet app, writes down a seed phrase, or buys gas.

When a group plans a hangout, anyone can attach a small stake, say two dollars each. Friends stake when they RSVP. Everyone who shows up gets their stake back, and if someone flakes, the group can split that stake among the people who came. Attendance comes from the same proof the rest of the app runs on: the group photo and pairwise NFC taps.

Winnings build up in the app and cash out to any supported chain and token through Unifold Payout, with Unifold covering the gas. A friend visiting from another country can pull their share to the network they actually use, no local bank account required.

How we built it

  • App: React Native, Expo SDK 57, and TypeScript support the Android and web versions through a shared codebase.

  • Authentication: Auth0 Universal Login uses Native PKCE on mobile and an SPA client on the web. Our API validates RS256 access tokens.

  • Backend: Express and better-sqlite3 manage friendships, hangouts, attendance, photos, preference scores, rewards, avatar customization, and memories.

  • Activity matching: Users select from 70 interests across seven categories and complete head-to-head activity duels. An Elo-style scoring system combines both sources of information to recommend activities for the group.

  • NFC verification: A custom Android HostApduService allows one phone to present a short-lived NFC code. Another phone scans the code using react-native-nfc-manager, confirming that the friends were physically together.

  • No-show settlement: Hangouts use one atomic completion process. The event must have started and a photo must exist before the system confirms attendance, settles stakes, and creates a memory.

  • Real-time world: An authenticated WebSocket broadcasts player positions. Persisted coordinates and React Native Animated values provide smooth movement and camera tracking.

  • Payments (Unifold): A dedicated custody service wraps the Unifold Node SDK. Deposits are provisioned per user through the deposit_addresses API, credited by verified deposit.direct_execution.completed webhooks with a directExecutions.list poll as backup, and cashed out through treasury.outboundTransfers.create with per-attempt idempotency keys. Cash-out chains and tokens come from Unifold's live supported-token catalog, so the options never drift from what Payout can route.

  • Treasury model and ledger: One Unifold treasury account holds the USDC pool on Base. Per-user balances, stakes, and settlements are ledger entries in MongoDB Atlas, which keeps on-chain activity at the edges (deposit in, cash out) and lets a $1.50 flake share settle instantly and gas-free. Shared idempotency references prevent duplicate deposit credits, and withdrawal keys prevent duplicate payouts.

  • Crypto infrastructure: The Unifold service runs on a separate Azure App Service with its own CI/CD workflow, service authentication, and secrets stored in Azure Key Vault.

  • Notifications and audio: Expo Notifications schedules friendship reminders. Expo Audio provides optional background music and remembers the user’s sound setting.

  • Testing: The crypto service has 90 tests covering settlement atomicity, withdrawal idempotency, authentication, routes, and production safety policies.

  • Deployment: Azure App Service hosts the application, Cloudflare manages DNS and TLS, and Terraform defines the infrastructure. GitHub Actions deploys the server, web build, crypto service, and signed Android APK.

How Unifold powers the money layer

Why the app needs real settlement

The flake tax only works if a two dollar promise pays out like real money. Points would be forgettable, and building custody, deposits, gas handling, and cross-chain payouts ourselves was never going to happen in a weekend. Unifold is the settlement layer for the entire feature: it custodies the pool, turns a plain address-and-send deposit into confirmed on-chain USDC, and pays winners out on whatever chain they live on. Every dollar that enters, moves through, or leaves Tomo Together does so via Unifold.

Deposits

Tapping "Add funds" asks our custody service for a personal deposit address. The service calls Unifold's deposit_addresses API with the user's external_user_id, and Unifold returns an address on Base that routes into our project treasury while staying tagged to that user. The user sends USDC from anywhere (Coinbase, MetaMask, another app) and Unifold confirms it on-chain. A deposit.direct_execution.completed webhook credits the balance in real time, and a Refresh button backs it up by paging directExecutions.list for anything the webhook missed. Both paths write the same idempotency reference, deposit:<executionId>, so a deposit can be observed twice and credited exactly once.

One treasury, instant internal settlement

All USDC sits in a single Unifold treasury account. Each user's balance is a ledger claim stored in MongoDB Atlas, so stakes, refunds, and flake redistributions are instant database moves. The chain is touched exactly twice: when money comes in and when it leaves. That design came straight from the economics. A $1.50 flake share could never clear a roughly $3 network minimum on its own, and pooling through the treasury lets the app settle amounts that small in milliseconds, for free.

Staking and settlement

Creating a staked hangout registers an event with the custody service and stakes the host up front. Friends stake when they RSVP. The NFC taps and group photo that complete a hangout double as the attendance oracle: present stakers are checked in, and one atomic settlement returns stakes to attendees, splits forfeited stakes among them, and records each person's payout. The math is conservation-checked (total paid out must equal total staked) and every credit carries a settle:<event>:<user> reference, so a crash and retry can never pay anyone twice.

Cash-outs, cross-chain

Withdrawals call treasury.outboundTransfers.create with a fresh idempotency key per attempt. Unifold routes the transfer to the user's chosen chain and token and pays the gas. The destination menu is built from Unifold's live supported-token catalog at request time, which keeps the options in lockstep with what Payout can actually route. Status lands via treasury.outbound_transfer.completed and .failed webhooks, and a failed transfer refunds the user's balance exactly once.

Reliability around the money

The Unifold secret key never touches the main app. A dedicated custody service on its own Azure App Service holds it (through Key Vault), and the main API reaches that service over an authenticated internal channel. Webhooks are verified with unifold.webhooks.constructEvent, HMAC over the raw request body, and the endpoint itself was registered programmatically with webhookEndpoints.create. The custody service carries 90 tests covering settlement atomicity, withdrawal idempotency, webhook verification with real signatures, and production safety policies, all compiled against the SDK's TypeScript types.

Stablecoins as a first crypto experience

We think this is what a genuine on-ramp looks like. For most people, crypto starts with an exchange chart or a seed phrase and ends there. In Tomo Together, someone's first stablecoin balance exists because a friend skipped brunch. They fund it by sending to a plain address, they never see gas or network fees, and when they cash out they pick a chain from a menu. Unifold absorbs custody, confirmation, routing, and fees, which leaves only the fun part: money that moves between friends instantly, across any bank, app, or country.

Building on Unifold

The SDK was one of the smoothest parts of the weekend. The treasury surface (accounts, outbound transfers, direct executions) maps cleanly onto a custody architecture, idempotency keys are first-class on transfers, and webhook verification works the way Stripe taught everyone to expect. The documentation at docs.unifold.io was specific enough that we could wire deposits, payouts, and webhook handling directly against the documented shapes, and the published TypeScript types caught our mistakes at compile time instead of at settlement time.

Challenges we ran into

The hardest part was connecting activity in the app with something that happened in person. A simple confirmation button would have been easy to fake, so we created a process involving photo uploads, pairwise NFC taps, temporary verification codes, and backend attendance checks.

The money layer had constraints of its own. Network minimums sit near $3 while a flake share can be $1.50, so settling each event on-chain was ruled out early. We chose treasury custody with an internal ledger, which turned our hardest problem into bookkeeping: two independent crediting paths (webhook and poll) that must never double-credit, withdrawals that must debit exactly once through retries and timeouts, and refunds that fire exactly once when a transfer fails. Unifold's idempotency keys and signed webhooks supplied the primitives, and the test suite around them is where much of our backend time went.

We also spent time getting the tone right. Friendship scores, reminders, and staking could easily make the app feel demanding. We designed the interface and language to feel lighthearted and optional, especially for users who are busy or cannot meet often.

Synchronizing the shared world created another challenge. Player positions had to move smoothly on each device while remaining consistent across connected users.

Accomplishments that we're proud of

We are proud of how much of the main experience works from beginning to end. Users can add a friend, plan a hangout, verify attendance, receive rewards, save a memory, and increase their vibe score.

The full money loop runs end to end: deposit USDC to a Unifold-provisioned address, watch the balance appear, stake it on a hangout, prove attendance over NFC, and cash winnings out cross-chain. The user never sees a wallet, a gas fee, or a network picker along the way, and 90 tests on the custody service stand behind that flow. The stake itself stays optional, a playful layer of accountability that groups opt into together.

The NFC attendance system is one of our favorite features. It connects progress in the app to an in-person interaction and gives Tomo Together something that a typical planning app does not have.

What we learned

This project taught us that every feature needs to support the same core experience. Adding another system only helped when it made planning, attending, or remembering a hangout easier.

Building on Unifold reshaped how we think about stablecoins in consumer apps. Treated as infrastructure, USDC behaves like a faster shared balance that happens to settle on-chain, and Unifold's custody, webhook, and payout primitives let a hackathon team stand up a small bank in a weekend. The takeaway we keep returning to: the way to bring new people into crypto is to hide it inside something they already want to do with their friends.

We learned a great deal about React Native development, NFC communication, WebSockets, backend state management, payment reliability, and interactions that move between a phone and the physical world.

We also learned how carefully social features need to be designed. Friendship can become uncomfortable when an app treats it like a competition or obligation. Tomo Together works best when its reminders and rewards feel encouraging, flexible, and easy to ignore when life gets busy.

What's next for Tomo Together

We want to improve activity recommendations by considering location, budget, weather, availability, and shared interests.

We would also like to expand the shared world with seasonal events, multiplayer activities, rare cosmetics, group quests, and collaborative spaces in the yard.

On the money side, we want to bring Unifold's client Deposit SDK into the app for a fully in-app funding flow, open cash-outs to more of the chains in the supported-token catalog, and add group treasuries where a friend circle pools a season of flake taxes toward a shared goal, like a trip paid for by everyone who didn't show up. Alongside that, we want clearer group rules, safer defaults, and more flexible options for small, voluntary stakes.

Our long-term goal is for Tomo Together to reward time spent with friends in person and give people a reason to close the feed, make a plan, and show up.

Share this project:

Updates