Problem
This codebase has two independent, actively-imported implementations of the same three helpers:
src/utils/stellar.ts (imported by components/ContactBook/AddContact.tsx):
import { StrKey } from '@stellar/stellar-sdk'
export const isValidStellarAddress = (a: string) => StrKey.isValidEd25519PublicKey(a)
export const truncateAddress = (a: string, c = 6) => a.length <= c*2 ? a : a.slice(0,c)+'…'+a.slice(-c)
export const xlmToStroops = (x: number) => BigInt(Math.round(x * 10_000_000))
export const stroopsToXlm = (s: bigint) => Number(s) / 10_000_000
src/lib/stellar.ts (imported by BatchForm.tsx, EscrowForm.tsx, SubscriptionForm.tsx, BatchConfirmModal.tsx, ConfirmEscrowModal.tsx, ConfirmSubscriptionModal.tsx, RequestItem.tsx, and more — this is the one the bulk of the app actually uses):
export function isValidStellarAddress(address: string): boolean {
try {
Keypair.fromPublicKey(address)
return true
} catch {
return false
}
}
export function truncateAddress(address: string, chars = 6): string {
if (!address) return ''
return `${address.slice(0, chars)}...${address.slice(-chars)}`
}
export function formatAmount(amount: string | number, decimals = 2, symbol = ''): string {
const num = typeof amount === 'string' ? parseFloat(amount) : amount
if (isNaN(num)) return '—'
const formatted = new Intl.NumberFormat('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(num)
return symbol ? `${formatted} ${symbol}` : formatted
}
(src/utils/format.ts additionally has a third, differently-signed formatAmount(n: number, d = 2) that only accepts number, not string | number.)
Why it matters — concrete, observable divergences, not just "duplication is bad"
truncateAddress has no short-input guard in lib/stellar.ts. utils/stellar.ts's version explicitly checks a.length <= c*2 and returns the address unmodified if it's too short to usefully truncate. lib/stellar.ts's version has no such check — for any address (or, more realistically, any non-address string accidentally passed through, e.g. a truncated/placeholder value) shorter than 2 * chars, slice(0, chars) and slice(-chars) overlap, producing a garbled, overlapping-substring result (e.g. an 8-character string truncated with chars = 6 prints characters 0–5 and 2–7, silently duplicating the middle).
- Different ellipsis rendering — one uses the
… character, the other uses literal ... — so truncated addresses render inconsistently across the app depending on which import a given component happened to use.
isValidStellarAddress uses different underlying validation. StrKey.isValidEd25519PublicKey (utils) strictly validates a base32-encoded Ed25519 public key. Keypair.fromPublicKey (lib) goes through the SDK's keypair construction path — for most well-formed G... addresses these agree, but they are two independently-maintained code paths that can silently drift apart on SDK upgrades (e.g. future support for M... muxed addresses, or changes in how the SDK surfaces checksum-failure errors) with nothing forcing them to be kept in sync, since neither file references the other.
formatAmount's signature is genuinely different: lib/stellar.ts's version takes an optional trailing symbol argument and accepts string | number; utils/format.ts's only accepts number. A component built against one signature cannot be trivially switched to the other without a call-site rewrite.
Reproduction
grep -rn "from '@/utils/stellar'" src vs grep -rn "from '@/lib/stellar'" src shows both are live, non-dead imports.
- Feed a 6-character string into both
truncateAddress implementations with the default chars = 6 and compare outputs — lib/stellar.ts's version double-renders overlapping characters where utils/stellar.ts's returns the string unchanged.
Suggested fix (flagging as spike — this needs a consolidation decision, not just a patch)
- Pick one canonical location (given
lib/stellar.ts has far more call sites, it's the more likely keeper) and re-export or delete the other, updating the one remaining import site (AddContact.tsx) accordingly.
- Before deleting either, write characterization tests against both implementations' current behavior so the migration doesn't silently change behavior for existing call sites (e.g. if
AddContact.tsx's users are relying on utils/stellar.ts's short-string guard, the consolidated version needs to keep it).
- Decide whether
StrKey.isValidEd25519PublicKey or Keypair.fromPublicKey-based validation is actually the more correct/future-proof choice (e.g. does the codebase need to eventually accept M... muxed addresses? StrKey has separate isValidMed25519PublicKey for that, which neither current implementation exposes).
Testing strategy
- Snapshot both
truncateAddress implementations against a table of inputs (empty string, very short string, exactly 2*chars long, normal 56-char address) and assert the consolidated version matches whichever behavior is chosen as correct for every case, especially the short-string overlap bug.
- Property-test
isValidStellarAddress against a corpus of known-valid and known-invalid Stellar addresses (bad checksum, wrong length, wrong version byte) to confirm the consolidated implementation doesn't regress either former implementation's coverage.
Related issues in this batch
The BatchForm duplicate/self-send issue and the EscrowForm self-escrow issue in this batch both depend on isValidStellarAddress/equality semantics from whichever implementation wins this consolidation.
Problem
This codebase has two independent, actively-imported implementations of the same three helpers:
src/utils/stellar.ts(imported bycomponents/ContactBook/AddContact.tsx):src/lib/stellar.ts(imported byBatchForm.tsx,EscrowForm.tsx,SubscriptionForm.tsx,BatchConfirmModal.tsx,ConfirmEscrowModal.tsx,ConfirmSubscriptionModal.tsx,RequestItem.tsx, and more — this is the one the bulk of the app actually uses):(
src/utils/format.tsadditionally has a third, differently-signedformatAmount(n: number, d = 2)that only acceptsnumber, notstring | number.)Why it matters — concrete, observable divergences, not just "duplication is bad"
truncateAddresshas no short-input guard inlib/stellar.ts.utils/stellar.ts's version explicitly checksa.length <= c*2and returns the address unmodified if it's too short to usefully truncate.lib/stellar.ts's version has no such check — for any address (or, more realistically, any non-address string accidentally passed through, e.g. a truncated/placeholder value) shorter than2 * chars,slice(0, chars)andslice(-chars)overlap, producing a garbled, overlapping-substring result (e.g. an 8-character string truncated withchars = 6prints characters 0–5 and 2–7, silently duplicating the middle).…character, the other uses literal...— so truncated addresses render inconsistently across the app depending on which import a given component happened to use.isValidStellarAddressuses different underlying validation.StrKey.isValidEd25519PublicKey(utils) strictly validates a base32-encoded Ed25519 public key.Keypair.fromPublicKey(lib) goes through the SDK's keypair construction path — for most well-formedG...addresses these agree, but they are two independently-maintained code paths that can silently drift apart on SDK upgrades (e.g. future support forM...muxed addresses, or changes in how the SDK surfaces checksum-failure errors) with nothing forcing them to be kept in sync, since neither file references the other.formatAmount's signature is genuinely different:lib/stellar.ts's version takes an optional trailingsymbolargument and acceptsstring | number;utils/format.ts's only acceptsnumber. A component built against one signature cannot be trivially switched to the other without a call-site rewrite.Reproduction
grep -rn "from '@/utils/stellar'" srcvsgrep -rn "from '@/lib/stellar'" srcshows both are live, non-dead imports.truncateAddressimplementations with the defaultchars = 6and compare outputs —lib/stellar.ts's version double-renders overlapping characters whereutils/stellar.ts's returns the string unchanged.Suggested fix (flagging as
spike— this needs a consolidation decision, not just a patch)lib/stellar.tshas far more call sites, it's the more likely keeper) and re-export or delete the other, updating the one remaining import site (AddContact.tsx) accordingly.AddContact.tsx's users are relying onutils/stellar.ts's short-string guard, the consolidated version needs to keep it).StrKey.isValidEd25519PublicKeyorKeypair.fromPublicKey-based validation is actually the more correct/future-proof choice (e.g. does the codebase need to eventually acceptM...muxed addresses?StrKeyhas separateisValidMed25519PublicKeyfor that, which neither current implementation exposes).Testing strategy
truncateAddressimplementations against a table of inputs (empty string, very short string, exactly2*charslong, normal 56-char address) and assert the consolidated version matches whichever behavior is chosen as correct for every case, especially the short-string overlap bug.isValidStellarAddressagainst a corpus of known-valid and known-invalid Stellar addresses (bad checksum, wrong length, wrong version byte) to confirm the consolidated implementation doesn't regress either former implementation's coverage.Related issues in this batch
The
BatchFormduplicate/self-send issue and theEscrowFormself-escrow issue in this batch both depend onisValidStellarAddress/equality semantics from whichever implementation wins this consolidation.