Skip to content

feat(frontend): let teams stream their own SIP-010 token - #31

Merged
jayteemoney merged 1 commit into
mainfrom
feat/token-discovery-selector
Oct 2, 2026
Merged

jayteemoney merged 1 commit into
mainfrom
feat/token-discovery-selector

Conversation

@jayteemoney

Copy link
Copy Markdown
Owner

Follow-up to #30. The create-stream selector was a hardcoded list of four curated tokens, so a team could not stream its own token without a code change — even though stream-manager.clar is permissionless and #30 already made uncurated tokens safe to resolve.

Why search, not a list

Hiro's token metadata API indexes every SIP-010 token (~5.6k), but a flat dropdown of that is not shippable. symbol=sBTC returns 32 contracts including buttcoin-stxcity and sBTC-mock-vpv-10. Sampling the default listing order showed 0% usable rows in the first few hundred — no symbol, no name, no supply.

So the shape is three entry paths in trust order: curated tokens pinned first, then search over the registry, then a contract id.

Discovery is separate from trust

verifySelection in lib/token-registry.ts is the single gate from a suggestion to a transaction:

  • decimals and assetName are re-proven on-chain by the existing resolver. The registry's values are never used for a post-condition.
  • I verified the registry's decimals against get-decimals on 25 indexed tokens: 25/25 agreed. But "has always agreed" is not a property a payment should depend on, and an indexer is one deploy behind the chain forever.
  • A resolver failure is unverifiable — never a fallback token. Regression-tested against the exact bug fix: resolve token metadata from chain instead of assuming sBTC #30 fixed.

Because the gate is one-directional, the registry can be wrong, stale, or fully down without any path to a misdirected payment.

How users get a contract id

Not by typing it. A team puts /dashboard/create?token=SP….contract-name in its docs or Slack and the recipient clicks it. That resolves from the chain, so it also works for tokens deployed minutes ago that the registry has not indexed, and when the registry is down.

Worth noting: the registry's /search endpoint returns contract_id and token_number with no asset identifier at all, so the asset name for a post-condition cannot come from there even in principle. That path therefore takes only cosmetics from the registry and everything load-bearing from the chain.

Friction: warn, never block

  • An impersonator — any contract claiming a curated symbol — is flagged explicitly, listed below the real one, and shows the canonical contract id alongside it for comparison.
  • Unverified tokens are selectable, since blocking would defeat onboarding, but require an explicit confirm that displays the full principal.
  • The full principal is always visible with a copy button. A mistyped-but-real contract id resolving to a different token is only catchable by seeing the whole string.
  • Zero supply is unselectable. Unknown supply is not — a deep-linked token carries no supply figure, and treating unknown as zero would make every unindexed token unusable, which is exactly the case that path exists to serve.

Verified against mainnet

[curated     ] USDA   asset=usda        dec=6
[curated     ] sBTC   asset=sbtc-token  dec=8
[impersonator] sBTC   asset=sBTC        dec=6   !! impersonates SM3VDXK3…sbtc-token

Disagreement between listing and contract downgrades to unverified and is refused: "Token listing reports 2 decimals but the contract reports 6."

Verification

  • 186 tests pass (43 new, network-free)
  • npx tsc --noEmit clean
  • npm run build clean
  • lint unchanged at the pre-existing 14 errors / 4 warnings

Not addressed here

Multi-asset contracts (sBTC, ALEX) still have no disambiguating read-only function. They work via their curated entries, but a new multi-asset token would need a contract-side identifier. The registry cannot solve this — it indexes only one asset per such contract.

The create-stream selector was a hardcoded list of four curated tokens.
`stream-manager.clar` is permissionless, so a team wanting to stream its
own token had no way to select it without a code change — which blocks
onboarding a protocol that already supports it.

Discovery is now a separate concern from trust:

- `lib/token-registry.ts` queries Hiro's token metadata API, which
  indexes every SIP-010 token (~5.6k). It is used ONLY to find and label
  tokens.
- `verifySelection` is the single gate from a suggestion to a
  transaction. decimals and assetName are re-proven on-chain by the
  existing resolver and the registry's values are never used for a
  post-condition. I checked the registry's decimals against `get-decimals`
  on 25 indexed tokens and they agreed 25/25, but "has always agreed" is
  not a property a payment should depend on, and an indexer is one deploy
  behind the chain forever.
- A resolver failure yields "unverifiable", never a fallback token.

A flat list of the registry would not be shippable: `symbol=sBTC` returns
32 contracts including `buttcoin-stxcity`, so search flags any contract
claiming a curated symbol as an impersonator, shows the full principal
rather than a symbol, and requires an explicit confirm. Verified-but-new
tokens stay frictionless — nothing legitimate is blocked.

Three entry paths, in trust order: curated tokens pinned first, then
search, then a contract id. The contract-id path exists because that is
how users actually obtain one — a team puts `/dashboard/create?token=SP….x`
in its own docs or Slack and nobody types 41 characters. It resolves from
the chain, so it works for tokens the registry has not indexed yet and
when the registry is down; the registry only supplies cosmetics there,
since `/metadata/v1/search` does not return an asset identifier at all.

Adds 43 network-free tests. Verified: 186 pass, tsc clean, build clean,
lint unchanged at the pre-existing 14 errors / 4 warnings.
@vercel

vercel Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
stackstream Ready Ready Preview Oct 2, 2026 12:36pm UTC

@jayteemoney
jayteemoney merged commit 51a5ff4 into main Oct 2, 2026
2 of 4 checks passed

This branch was successfully deployed

1 active deployment
Preview — 26a54d9e Deployed Oct 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant