Skip to content

Harden token resolution, clear lint, and gate CI on typecheck, lint and build - #33

Merged
jayteemoney merged 6 commits into
mainfrom
fix/token-resolution-hardening
Oct 6, 2026
Merged

jayteemoney merged 6 commits into
mainfrom
fix/token-resolution-hardening

Conversation

@jayteemoney

Copy link
Copy Markdown
Owner

What this fixes

From the Oct 2 code review.

  • Testnet: contract ids from both networks are valid. The resolver, the curated list and the token search are scoped to the network the app runs on. Before this, a testnet build (the default when NEXT_PUBLIC_NETWORK is unset) could not resolve its own mock token.
  • Token resolution cache: successful lookups are still cached for good, because decimals, symbol and asset name never change. Failed lookups now expire after 60s, so one 429 no longer marks a valid token unverifiable until a cold start. Concurrent lookups share one request. The API routes now use the same resolver instead of their own copy.
  • Amounts: the create, top-up and claim forms refuse more decimal places than the token supports. They used to drop the extra digits silently.
  • Lint: 18 problems down to 0. any is gone at the Clarity JSON boundaries, and useStreamProgress no longer reads the clock during render or sets state inside effects.
  • Contract fuzz tests: every test now uses the printed seed. The "claimable frozen during each pause" test was flaky because it asserted a freeze after pause-stream had correctly refused (ERR-STREAM-ENDED). The contract is unchanged.
  • OpenClaw: SKILL.md no longer tells the assistant that every token has 8 decimals or that blocks take 10 minutes. A new parity test pins the Express service and the hosted API to the same output.
  • CI: adds a frontend job (tsc, lint, webpack build) and a service typecheck.
  • Housekeeping: the literal NUL byte in token-metadata.test.ts is replaced, so git shows the file as text again.

Verified locally

  • npx vitest run: 206 passed (was 191)
  • Frontend tsc, npm run lint (0 problems), and npm run build (webpack, mainnet): clean
  • Service tsc: clean
  • Contract suite run 10 more times with random seeds: 10/10 passed. Two previously failing seeds replayed and passed.

CI will not start until the GitHub billing lock on the account is cleared.

…t misses

The contract-id check accepted only SP/SM principals, so on a testnet build
(the default when NEXT_PUBLIC_NETWORK is unset) the mock token could never
resolve and every stream showed as unverifiable. Ids from both networks are
now well formed; contractIdNetwork() says which network one belongs to, and
the resolver, the curated list and the token search are scoped to the
network the app runs on. Pasting a contract from the other network now says
so instead of failing as "could not verify".

The resolver cached failures for the life of the instance, so one 429 or
timeout from the node marked a valid token unverifiable until a cold start.
Hits are still cached forever (decimals, symbol and asset name are fixed at
deploy); misses now expire after 60s, and concurrent lookups share one
request. The API routes had a second, separately cached copy of the decimals
and symbol readers; they now use the same resolver.

Also removes verifySelection's two unused parameters, corrects its comment
about mismatched listings (it refuses, it does not hand back chain values),
and replaces the literal NUL byte in token-metadata.test.ts with "\0" so git
stops treating the file as binary and PR diffs show it.
toRawAmount truncates extra fractional digits so it never sends more than
was typed, but doing that silently still sends less than was typed. The
create, top-up and claim forms now stop and say how many decimal places the
token supports.
Replaces the remaining `any` types at the Clarity JSON and API boundaries
with narrow types, types the wallet call options (getNetwork now returns the
"mainnet" | "testnet" literal), and drops unused imports.

useStreamProgress read Date.now() during render and set state synchronously
inside effects. It now derives the value: the animation frame is tagged with
the snapshot it extrapolates from and only shown while it matches, so a
stale frame can never overwrite a new snapshot.

eslint: 18 problems (14 errors, 4 warnings) -> 0.
Three property tests drew from Math.random instead of the seeded RNG, so a
failure printed a FUZZ_SEED that could not reproduce it. All now use the seed.

"multi-cycle pause, claimable frozen" failed for durations of 10-13 and
20-23 blocks. Each pause pushes accrual past end-block, and pause-stream is
refused once end-block passes (ERR-STREAM-ENDED, by design). The test
ignored the refusal and asserted a freeze that never started. It now stops
the cycle on that refusal, the same guard the sibling "returns to ACTIVE"
test already had. Contract unchanged.
SKILL.md told the assistant every amount has 8 decimals, the same mistake
that showed a 1.2 USDA deposit as 0.012, and that blocks take 10 minutes
(about 5 seconds since Nakamoto). It now uses the tokenDecimals field the API
returns.

The read API exists as Next route handlers and as the Express service that
self-hosted skills run. A parity test pins their formatting helpers to the
same output so the two copies cannot drift.
CI ran only the root vitest suite, so a type error, a lint error or a broken
production build in either app could merge. Adds a frontend job (tsc, lint,
webpack build) and a service typecheck, installs both apps' dependencies for
the root suite (it imports their sources), and cancels superseded runs.
@vercel

vercel Bot commented Oct 5, 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 5, 2026 6:36am UTC

@jayteemoney
jayteemoney merged commit 9fa7ea0 into main Oct 6, 2026
2 of 5 checks passed

This branch was successfully deployed

1 active deployment
Preview — 86a86a63 Deployed Oct 5, 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