Skip to content

UnityNodes/Cairn

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cairn

An onchain safe-send allowlist for your wallets, on Monad. Send to the right wallet, every time.

If you build on crypto you juggle a dozen wallets (a deployer, an agent key, a cold wallet, throwaways), and you send between them by copy-pasting addresses you read as 0x1234…abcd. Attackers exploit exactly that: they generate a look-alike address that shares your first and last characters and seed it into your history, so you paste the wrong one and the money is gone. Address poisoning has racked up 270M+ attempts and $83M+ in confirmed losses (USENIX Security '25 study); one trader lost $50M to a single look-alike.

Cairn turns your own wallets into an onchain allowlist that fixes both problems at once. You label your addresses (name, role, tags, note); each label lives in a Monad contract under your own namespace, portable and verifiable, not stuck in a local file. Then you send by name, not by pasting hex, and if you do paste an address, Cairn gives you a hard GREEN / RED / GREY verdict: green if it is a wallet you verified, red with a character-by-character diff if it is a look-alike of one, grey if it is new. A companion registry lets anyone flag a scam or poison address so the whole community's warnings travel with the address.

Each labeled wallet is a marker. A pile of markers is your cairn: the stones you leave so you never lose your way.

Cairn dashboard: labeled wallets with live balances

Live

Both contracts are deployed and verified on Monad mainnet (Sourcify exact_match), so anyone can read exactly what runs. Reading is free and needs no wallet; writing a label or a flag costs mainnet gas.

How it works

Contract (contracts/). Cairn.sol is a per-user registry with no admin. Every write is namespaced to msg.sender, so you can only ever change your own markers, and anyone can read anyone's set (that is what lets a dashboard show a public, verifiable list). A marker holds wallet, label, a Role enum, tags, note, and updatedAt. Core calls:

  • mark(wallet, label, role, tags, note) add or update a marker
  • unmark(wallet) remove one (O(1) swap and pop)
  • markMany(...) import a whole set in one transaction
  • markersOf(owner) / markerOf(owner, wallet) / count(owner) reads

dApp (web/). Next.js 14 with wagmi v2 and viem. Connect an injected wallet, read your markers with markersOf, batch every wallet's balance through Multicall3 in one round trip and refresh live, then mark / unmark straight from the cards. States are real: empty, hero, wrong-network, loading skeletons. No fake buttons, no placeholder rows, every control issues a real transaction or a real read.

Companion registry (CairnShield.sol). A second, permissionless contract for community attestations: anyone can attest(subject, kind, stance, label) to flag an address (Scam, Poison) or vouch for it (Deployer, Merchant, Exchange, Contract, Person), and revoke their own. subjectSummary and isFlagged aggregate the signal in a single read (they iterate that address's attesters, so cost grows with how many people weighed in). The send flow and the poison check both consume it live, so a flag one person raises protects everyone.

Read it in three lines. Cairn's labels are a public primitive, not a walled app. Any dApp can read the community safety signal for an address:

import { readCairnLabel } from "@/lib/cairnLabels";
const label = await readCairnLabel("0xabc...");
if (label.flagged) warnUser();

Because the markers are onchain, they are shareable. Every set has a public read-only link, /?owner=0x..., that shows anyone's labeled wallets with live balances, no wallet needed. That is the difference from a notes app: the labels live on the same chain as the wallets, so anyone can read and verify them.

Features

  • Safe send. Pick a wallet by name to send MON. Paste an address and get a GREEN / RED / GREY verdict: red blocks the send and diffs the look-alike against your real wallet character by character; grey (a new address) makes you type its last 4 to confirm, or mark and label it first.
  • Poison check, no wallet needed. Paste any address before you pay it and get a 0-100 risk score plus a shareable card. Drop the link in a group chat: "verify before you pay me."
  • Community flags. Flag a scam or poison address, or vouch a legit one, onchain via CairnShield. The send flow and poison check read those flags live.
  • Label onchain. mark a wallet with a name, role, tags, and note; markMany imports a whole set in one transaction.
  • Live balances. Every wallet's MON balance batched through Multicall3 and refreshed live, so you see what is funded at a glance.
  • Public, shareable, browsable. Share a read-only link to your set, or open Explore to browse featured public cairns.

A deliberate tradeoff, stated plainly: markers are public onchain, which is exactly what makes them portable, verifiable, and shareable. The flip side is that a labeled set links your wallets together in public, so treat a cairn like a profile you choose to publish and label only what you would show a teammate. Private-by-default labels via client-side encryption are the natural next step.

Design

A ring of worn index cards on a workshop bench: cool dark slate, one oxidized-teal accent, monospace for anything a machine produced (addresses, balances, tags). The marker card is the signature object, a tactile luggage tag with a single punched-hole notch. Role is always shown as text, never color alone (WCAG AA on the dark surface). See PRODUCT.md and DESIGN.md.

Run it locally

Contract

cd contracts
forge build
forge test

Forty-seven tests cover both contracts: unit, fuzz over arbitrary inputs, and invariant suites (count matches the list, index integrity after swap-and-pop, no duplicate entries, attestation summary math). A committed .gas-snapshot tracks gas per call. Compiler 0.8.28, evm_version = prague.

dApp

cd web
npm install
npm run dev

Open the printed URL, connect an injected wallet (MetaMask, Rabby) set to Monad mainnet, and add your first marker. No environment variables: both chains and both contract addresses are pinned in web/lib/chain.ts.

Stack

Solidity 0.8.28 / Foundry / Monad mainnet, Next.js 14 / wagmi v2 / viem / Tailwind, Multicall3 for batched live balances.

Why it fits the track

The brief is an onchain thing that solves a personal problem. This is the exact problem I hit every week running many wallets, and the fix is onchain on purpose: the labels live on the same chain as the wallets they describe, so they travel with me and anyone can verify them. Practical over fancy.


Built for the Monad Spark hackathon, July 2026.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors