{"id":51529455,"url":"https://github.com/bh2smith/tool-circles","last_synced_at":"2026-07-09T01:01:43.053Z","repository":{"id":364151271,"uuid":"1265817786","full_name":"bh2smith/tool-circles","owner":"bh2smith","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-11T20:31:01.000Z","size":103,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T22:11:47.098Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://tool-circles.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bh2smith.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T05:26:38.000Z","updated_at":"2026-06-11T20:26:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bh2smith/tool-circles","commit_stats":null,"previous_names":["bh2smith/tool-circles"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/bh2smith/tool-circles","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bh2smith%2Ftool-circles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bh2smith%2Ftool-circles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bh2smith%2Ftool-circles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bh2smith%2Ftool-circles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bh2smith","download_url":"https://codeload.github.com/bh2smith/tool-circles/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bh2smith%2Ftool-circles/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35282899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-09T01:01:42.073Z","updated_at":"2026-07-09T01:01:42.992Z","avatar_url":"https://github.com/bh2smith.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Circles Tools\n\nA standalone [Circles](https://aboutcircles.com) **mini app** — a browser toolbox\nof CirclesTools utilities. Opened inside the Circles wallet, the connected avatar\n_is_ the Safe, so transactions execute **as the avatar** (`msg.sender == avatar`)\nvia [`@aboutcircles/miniapp-sdk`](https://docs.aboutcircles.com)'s\n`sendTransactions` — no private key in the app.\n\nTools so far:\n\n- **Replenish CRC** — a self→self transitive transfer constrained to settle in\n  your own personal token, which converts foreign CRC you hold back into fresh\n  personal CRC. Browser port of\n  [koeppelmann/CirclesTools `replenishMyCRC.html`](https://github.com/koeppelmann/CirclesTools).\n- **Trust Score** — look up any account's Backers trust score (a sybil-resistance\n  proxy), with a hop-distance breakdown, and prune your own trust list by\n  scoring everyone you trust and batch-untrusting weakly-connected accounts.\n- **Payment Doctor** — diagnose a CRC payment corridor: the max amount that can\n  route from sender to recipient right now, and which direct trusts from the\n  recipient would widen it. Read-only; not a port — new to this toolbox.\n- **CRC Converter** — convert between demurraged CRC and static (inflationary)\n  ERC20 units at any date, delegating the math to the Hub's own `Demurrage`\n  helpers via `eth_call`. Port of `crcConverter.html`.\n- **Mint CRC** — claim your accrued personal CRC (1/hour, capped two weeks\n  after the last mint), with an accrual-cap countdown and mint history. Not a\n  port — new to this toolbox.\n- **Portfolio** — every CRC holding grouped by issuer, with demurrage\n  projections (+30 d / +1 y) and dead-issuer flags (stopped / idle /\n  untrusted). Not a port — new to this toolbox.\n- **Invite** — invite someone into Circles (trust-then-register), track\n  outstanding invites, and list everyone who registered through you. Not a\n  port — new to this toolbox.\n- **Token Holders** — holder distribution of any Circles token (personal or\n  group), with concentration stats and CSV export. Port of\n  `tokenDistributionChecker.html`.\n\n## How Replenish works\n\n1. **Find path** — `circlesV2_findPath` (POST `https://rpc.aboutcircles.com/`)\n   with `Source == Sink == avatar`, `WithWrap: true`, and `ToTokens: [avatar]`.\n   `ToTokens:[self]` forces the pathfinder to route foreign CRC back into your\n   token; without it a self→self query is degenerate and returns 0.\n2. **Build flow matrix** — port of `generateFlowMatrixParams()`: ascending\n   `_flowVertices`, per-edge `streamSinkId`, one `_streams` entry, and\n   big-endian `uint16` `_packedCoordinates`. See `src/lib/contract.ts`.\n3. **Handle wrapped balances** — `operateFlowMatrix` only moves ERC1155 Circles,\n   whose `tokenOwner` must be a registered avatar. But with `WithWrap: true` the\n   pathfinder also routes any ERC20-_wrapped_ foreign CRC you hold, reporting\n   those edges' `tokenOwner` as the **wrapper contract** (not an avatar) — which\n   the Hub rejects with code 36 ([#1](https://github.com/bh2smith/tool-circles/issues/1)).\n   So, like `@circles-sdk`, `buildReplenishBatch` unwraps each wrapper the path\n   spends (full static balance for inflationary, exact amount for demurraged),\n   rewrites those edges' `tokenOwner` to the underlying avatar, and re-wraps any\n   inflationary leftover — yielding a batch of `[unwrap…, operateFlowMatrix,\nre-wrap…]` (just `operateFlowMatrix` when no wrapped balance is on the path).\n4. **Submit** — the batch above, sent by the avatar via `sendTransactions`. The\n   v2 Hub is `0xc12C1E50ABB450d6205Ea2C3Fa861b3B834d13e8`.\n\n## How Trust Score works\n\nThe **Backers trust score** is a BFS over the Circles trust graph: starting from\nthe target avatar, it counts how many [Backers group](https://app.gnosis.io)\nmembers are reachable within 5 hops, weighted by a per-hop decay\n(1.0 / 0.9 / 0.8 / 0.6 / 0.4 / 0.2). It is **incoming-based** — it counts who\ntrusts _you_, transitively — which is what makes it sybil-resistant: you can't\ninflate your own score by trusting others.\n\n- **Score** — POST `…/scoring/relative_trustscore` with\n  `target_set_name: \"all_backers\"`, proxied through `/api/trust-score` so the call\n  is same-origin (no CORS dependence on the analytics host — the likely reason the\n  original [`trustScoreExplorer.html`](https://aboutcircles.github.io/CirclesTools/trustScoreExplorer.html)\n  fails) and the target set is pinned server-side.\n- **Prune** — list the avatar's active outgoing trusts (`circles_query` on\n  `TrustRelations`), score each trustee, then batch-untrust the weak ones with\n  `Hub.trust(trustee, 0)` (expiry 0 = revoke, matching `@circles-sdk`'s untrust).\n  This is the only trust lever the avatar controls; it curates who you vouch for\n  and your token exposure, but does **not** change your own (incoming) score.\n\n## How Payment Doctor works\n\nTransitive payments fail opaquely: the wallet caps the amount at the corridor's\nmax flow with no explanation. The doctor probes\n`circlesV2_findPath(Source: sender, Sink: recipient)` with a huge `TargetFlow`\n(and **no** `ToTokens` pin — any token the recipient accepts may settle) to get\nthe true max flow, then explains it by diffing the sender's holdings\n(`circles_getTokenBalances`, grouped by token owner, v2 only) against the\nrecipient's outgoing trusts:\n\n- tokens the recipient trusts route **directly**;\n- the rest only route while intermediaries have liquidity — each direct trust\n  from the recipient is a guaranteed unlock, up to the amount held. The\n  sender's own token is called out specially, since it's usually the largest\n  holding.\n\n## How the daily-driver tools work\n\n- **Mint** — one multicall on the Hub (`calculateIssuance(avatar)`, `isHuman`,\n  `stopped`) gives the claimable amount and the accrual window:\n  `calculateIssuance` returns `(issuance, startPeriod, endPeriod)` where\n  `startPeriod` is the last mint and `endPeriod` the last completed hour, so\n  `endPeriod − startPeriod` vs `MAX_CLAIM_DURATION` (2 weeks) drives the\n  cap-progress bar. Mint = `Hub.personalMint()` via `sendTransactions`;\n  history = `circles_query` on `CrcV2.PersonalMint`.\n- **Portfolio** — `circles_getTokenBalances` grouped by `tokenOwner` (it\n  carries demurraged + static balances per row, so no math client-side).\n  Projections scale each issuer's **static** balance by the Hub's own\n  conversion factors fetched for `today+30` / `today+365` (see\n  `src/lib/demurrage.ts`). Dead-issuer signals are batched: `Hub.stopped`\n  multicall, issuer types + active incoming-trust counts via two `In`-filtered\n  `circles_query` calls, and last-`PersonalMint` lookups (capped + chunked).\n- **Invite** — there is no `inviteHuman` on the v2 Hub. Inviting = trusting an\n  _unregistered_ address (`Hub.trust`); when the invitee calls\n  `registerHuman(inviter, …)` from their own wallet, **96 CRC is burned from\n  the inviter's personal tokens at that moment** (so the page checks your\n  own-token balance and warns). Outstanding invites = outgoing trusts whose\n  trustee has no `Hub.avatars` entry; successful ones =\n  `V_CrcV2.Avatars where invitedBy == you`.\n- **Token Holders** — one `circles_query` on\n  `V_CrcV2.BalancesByAccountAndToken` filtered by token address (a personal or\n  group token's address _is_ the issuing avatar's address). Note: ERC20-wrapped\n  balances live under their wrapper's address, not the avatar's.\n\n## Stack\n\nNext.js 16 (App Router) · React 19 · Tailwind 4 · viem · `@aboutcircles/miniapp-sdk`.\nRun with [bun](https://bun.sh).\n\n```bash\nbun install\nbun run dev          # http://localhost:3000\nbun run build        # production build + typecheck\nbun run lint\nbun run fmt:check\n```\n\n## Layout\n\n```\nsrc/\n  app/\n    layout.tsx            # dark shell + \u003cToolNav/\u003e\n    page.tsx              # tool index (cards from the registry)\n    replenish/page.tsx    # Replenish CRC tool (client)\n    trust/page.tsx        # Trust Score evaluator + trust-list pruning (client)\n    doctor/page.tsx       # Payment Doctor corridor diagnosis (client)\n    converter/page.tsx    # CRC Converter demurraged \u003c-\u003e static calculator (client)\n    mint/page.tsx         # Mint CRC claimable dashboard (client)\n    portfolio/page.tsx    # Portfolio holdings-by-issuer inspector (client)\n    invite/page.tsx       # Invite flow + outstanding/successful invites (client)\n    holders/page.tsx      # Token Holders distribution (client)\n    api/trust-score/route.ts  # server proxy to the analytics scoring endpoint\n  components/             # ToolNav, ToolCard, ConnectGate, Toast, AddressField\n  lib/\n    circles.ts            # miniapp-sdk wiring: wallet subscribe, sendTransactions, profiles\n    contract.ts           # findPath + flow-matrix encoders + trust/untrust encoders\n    query.ts              # generic circles_query client (the indexer)\n    trust.ts              # score lookups, outgoing-trust listing\n    doctor.ts             # corridor probe: general findPath, holdings-by-owner, trust diff\n    demurrage.ts          # day index + Hub demurrage conversion factors\n    mint.ts               # calculateIssuance/personalMint + mint history\n    portfolio.ts          # holdings-by-issuer, projections, dead-issuer signals\n    invite.ts             # invitees/pending queries + personal-balance check\n    distribution.ts       # token-holder distribution query + CSV\n    format.ts             # shared CRC/address/duration formatting\n    tools.ts              # tool registry — add a tool = one entry + one page\nreference/               # not compiled into the app\n    replenish-crc.ts     # the original headless / private-key port (prose reference)\n    parity.test.ts       # byte-for-byte encoding cross-check vs the reference HTML\n```\n\n## Verification\n\n- `bun run reference/parity.test.ts` — asserts `buildFlowMatrix` matches the\n  original HTML's `generateFlowMatrixParams` byte-for-byte (the key encoding check).\n- `bun run build \u0026\u0026 bun run lint` — typecheck + lint.\n\n## Registration \u0026 deploy (the one external step)\n\nDeploy to Vercel (new project), then register the slug in the Circles mini-app\nregistry — the same mechanism `word-circles` uses — and update\n`CIRCLES_MINIAPP_URL` in `src/lib/circles.ts` to match. Confirm the registration\npath with the Circles team. No backend or env is required: Replenish is fully\nclient-side against the public RPC.\n\n## Adding tools\n\nA tool is one entry in `src/lib/tools.ts` + one page under `src/app/\u003cid\u003e/`.\nThe registry already lists the roadmap (rendered as \"soon\" cards), distilled\nfrom a survey of both CirclesTools repos and the Circles RPC surface:\n\n- **Wave 2 — diagnostics**: Profile Checker (`profileChecker.html`, helper\n  contract `0x6885E3e0…`), Profile History (`profileHistory.html`, NameRegistry\n  `updateMetadataDigest` restore), Safe Inspector (`SafeViewer.html`, state\n  half), plus a Sankey path visualization for Payment Doctor\n  (`trustPathViz.html`).\n- **Wave 3 — groups**: Group Checker (GroupInfoGetter `0x3cd1c2be…`), Group\n  Buy (`findPath` with `ToTokens:[group]` over the existing flow-matrix\n  engine — the original `groupPurchaseHelper.html` swaps the static/demurraged\n  wrappers at line 1263; don't copy that), Group Creator (modernized to\n  BaseGroupFactory `0xD0B5Bd99…`), Group Management (batched\n  `trustBatchWithConditions`).\n- **Wave 4 — economy/social**: Backing Factory (`0xecEd9123…`, CowSwap\n  post-hook appData — verify the program is still live first), Lottery\n  participate (`LotteryFactory 0x4B146798…`), Marketplace Explorer\n  (`market-api.aboutcircles.com`), Trust Graph visualizer (`trustViz.html`).\n- **Skipped on purpose**: `createLegacyCirclesSafe.html` (legacy v1 Safe\n  deployer), `rpcQueryView.html` as a whole (developer IDE — its method\n  catalog is still the best machine-readable RPC docs), `priceInsights.html`\n  (unofficial third-party analytics API).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbh2smith%2Ftool-circles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbh2smith%2Ftool-circles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbh2smith%2Ftool-circles/lists"}