fix(TokenSelector): correct base32 regex, add component + tests - #300
Open
Joyyyb wants to merge 1 commit into
Open
fix(TokenSelector): correct base32 regex, add component + tests#300Joyyyb wants to merge 1 commit into
Joyyyb wants to merge 1 commit into
Conversation
Closes #[issue]
## Problem
Two related gaps in lib/token-allowance-gateway.ts and the missing
TokenSelector component:
1. Regex bug: both contract-address guards in TokenAllowanceGateway.approve()
used /^C[A-Z0-9]{55}$/. The Stellar StrKey base32 alphabet is A–Z plus
2–7 only (RFC 4648). The digits 0, 1, 8, and 9 can never appear in a
valid encoded address. Using [A-Z0-9] silently accepted addresses that
do not exist on-chain, deferring the error to a confusing RPC failure
rather than giving immediate client-side feedback.
2. G-address scope: the same regex matched G-prefixed (account) addresses
as well as C-prefixed (contract) addresses. A token-contract field
should only accept C-prefixed addresses.
3. No TokenSelector component: the CONTRIBUTING.md checklist and issue
tracker referenced components/TokenSelector.tsx but the file did not
exist, leaving a validated token-contract input field without a
reusable component and without any test coverage for the async
fetch / abort / error paths.
## Root cause
The original regex character class [A-Z0-9] was a naive 'looks like base32'
approximation. Stellar StrKey encoding uses the RFC 4648 base32 alphabet
(A–Z, 2–7); the extra digits (0, 1, 8, 9) are excluded by the spec but were
not excluded by the regex.
## Changes
### lib/token-allowance-gateway.ts
- Line 311: /^C[A-Z0-9]{55}$/ → /^C[A-Z2-7]{55}$/
(token contract address guard in TokenAllowanceGateway.approve)
- Line 321: /^C[A-Z0-9]{55}$/ → /^C[A-Z2-7]{55}$/
(spender contract address guard in TokenAllowanceGateway.approve)
Both guards now correctly:
a) Restrict to C-prefixed (Soroban contract) addresses only.
b) Reject the four invalid base32 digits (0, 1, 8, 9).
### components/TokenSelector.tsx [new file]
Controlled input component for a Soroban token contract address.
- Validation: CONTRACT_ADDRESS_RE = /^C[A-Z2-7]{55}$/ — enforces the
correct base32 alphabet and contract-only prefix at the input layer,
so users get immediate feedback instead of a late RPC error.
- Async resolution: tokenByAddress() is called inside an async effect so
the lookup path is ready for a future upgrade to an RPC call
(e.g. fetching the token symbol/name directly from the contract) without
changing the component interface.
- Abort handling: an AbortController is created per lookup and the previous
controller is aborted before starting a new one, preventing stale results
from overwriting newer input on rapid re-selection.
- States: idle | loading | resolved | unknown | error — each rendered with
appropriate ARIA roles (role="status" for informational, role="alert"
for errors) and aria-invalid/aria-describedby on the input.
- onTokenResolved callback: fires with the TokenMeta object when a known
contract is resolved, or null otherwise.
### components/TokenSelector.test.tsx [new file]
Full unit-test coverage using the project's established pattern
(createRoot + act, vitest + jsdom):
- Format validation: accepts valid C+base32 addresses, rejects wrong
prefix, wrong length, lowercase, and all four invalid base32 digits.
- REGRESSION tests: four dedicated tests that each construct a 56-char
C-address containing 0, 1, 8, or 9 respectively and assert they are
rejected — exactly the class of inputs the old /[A-Z0-9]/ regex accepted.
- Loading state: asserts 'Looking up token…' is shown synchronously before
the microtask resolves.
- Resolved state: asserts USDC symbol + name appear after resolution of the
known testnet USDC contract address.
- Unknown-token state: asserts the 'not in the known token list' message
appears for a valid-format but unrecognised address.
- onTokenResolved callback: asserts it fires with the correct TokenMeta
(or null) and is NOT called for invalid-format addresses.
- Abort / rapid re-selection: changes the address before the microtask
resolves and asserts the final callback reflects only the last address.
- Idle-on-clear: clears the address mid-lookup and asserts no stale state.
- Disabled prop: asserts input.disabled mirrors the prop.
- Accessibility: aria-invalid, role="alert" on format errors,
role="status" on resolution feedback, aria-describedby linkage.
Author
|
Hello Maintainer. Late PR submission, help merge it. Thank you so much |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #224
Problem
Two related gaps in lib/token-allowance-gateway.ts and the missing TokenSelector component:
Regex bug: both contract-address guards in TokenAllowanceGateway.approve() used /^C[A-Z0-9]{55}$/. The Stellar StrKey base32 alphabet is A–Z plus 2–7 only (RFC 4648). The digits 0, 1, 8, and 9 can never appear in a valid encoded address. Using [A-Z0-9] silently accepted addresses that do not exist on-chain, deferring the error to a confusing RPC failure rather than giving immediate client-side feedback.
G-address scope: the same regex matched G-prefixed (account) addresses as well as C-prefixed (contract) addresses. A token-contract field should only accept C-prefixed addresses.
No TokenSelector component: the CONTRIBUTING.md checklist and issue tracker referenced components/TokenSelector.tsx but the file did not exist, leaving a validated token-contract input field without a reusable component and without any test coverage for the async fetch / abort / error paths.
Root cause
The original regex character class [A-Z0-9] was a naive 'looks like base32' approximation. Stellar StrKey encoding uses the RFC 4648 base32 alphabet (A–Z, 2–7); the extra digits (0, 1, 8, 9) are excluded by the spec but were not excluded by the regex.
Changes
lib/token-allowance-gateway.ts
Both guards now correctly:
a) Restrict to C-prefixed (Soroban contract) addresses only. b) Reject the four invalid base32 digits (0, 1, 8, 9).
components/TokenSelector.tsx [new file]
Controlled input component for a Soroban token contract address.
components/TokenSelector.test.tsx [new file]
Full unit-test coverage using the project's established pattern (createRoot + act, vitest + jsdom):
What does this PR do?
Type of change
Related issue
Closes #
Changes
Checklist
npm run typecheck— no errorsnpm run lint— no warningsnpm test— all tests passnpm run build— production build succeedsalert())'use client'added only where genuinely neededCHANGELOG.mdupdated under[Unreleased]Screenshots
Notes for reviewers