From cc34e1f8e35ba12b855ee223edf3bd46165dce56 Mon Sep 17 00:00:00 2001 From: Galmanus Date: Tue, 25 Aug 2026 07:02:51 -0300 Subject: [PATCH] refactor(core): centralize Stellar address strkey in one module (#148) The public-key regex /^G[A-Z2-7]{55}$/ was duplicated in resolve-method-args, resolve-source-address and recover-deploy-contract-id, and validate-source-shape imported isLikelyPublicKeySource from the deploy-recovery module just to borrow it. Introduce stellar-cli/strkey.ts as the single source (STELLAR_ADDRESS_REGEX + isLikelyPublicKeySource); the three consumers import from it, recover-deploy re-exports for back-compat, and validate-source-shape no longer couples to the recovery module. Public re-export via index.ts is unchanged. tsc + tests pass. --- .../core/src/contracts/resolve-method-args.ts | 3 +-- .../src/contracts/resolve-source-address.ts | 3 +-- .../src/contracts/validate-source-shape.ts | 2 +- .../stellar-cli/recover-deploy-contract-id.ts | 6 +++--- packages/core/src/stellar-cli/strkey.test.ts | 19 +++++++++++++++++++ packages/core/src/stellar-cli/strkey.ts | 13 +++++++++++++ 6 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 packages/core/src/stellar-cli/strkey.test.ts create mode 100644 packages/core/src/stellar-cli/strkey.ts diff --git a/packages/core/src/contracts/resolve-method-args.ts b/packages/core/src/contracts/resolve-method-args.ts index c6d1d618..840bfedc 100644 --- a/packages/core/src/contracts/resolve-method-args.ts +++ b/packages/core/src/contracts/resolve-method-args.ts @@ -2,8 +2,7 @@ import { CaatingaError, CaatingaErrorCode } from "../errors/CaatingaError.js"; import { formatNamedCliArgs } from "./format-cli-args.js"; import { resolveSourceAddress } from "./resolve-source-address.js"; import type { DeployArgValue } from "./resolve-deploy-args.js"; - -const STELLAR_ADDRESS_REGEX = /^G[A-Z2-7]{55}$/; +import { STELLAR_ADDRESS_REGEX } from "../stellar-cli/strkey.js"; export type ResolveMethodArgsOptions = { args: Record; diff --git a/packages/core/src/contracts/resolve-source-address.ts b/packages/core/src/contracts/resolve-source-address.ts index bb42c762..845790e1 100644 --- a/packages/core/src/contracts/resolve-source-address.ts +++ b/packages/core/src/contracts/resolve-source-address.ts @@ -2,8 +2,7 @@ import { CaatingaError, CaatingaErrorCode } from "../errors/CaatingaError.js"; import { checkBinary } from "../shell/check-binary.js"; import { runCommand } from "../shell/run-command.js"; import { assertSafeSourceAccount } from "./source-account.js"; - -const STELLAR_ADDRESS_REGEX = /^G[A-Z2-7]{55}$/; +import { STELLAR_ADDRESS_REGEX } from "../stellar-cli/strkey.js"; export async function resolveSourceAddress(options: { source: string; diff --git a/packages/core/src/contracts/validate-source-shape.ts b/packages/core/src/contracts/validate-source-shape.ts index abaa0dd3..66dda2b1 100644 --- a/packages/core/src/contracts/validate-source-shape.ts +++ b/packages/core/src/contracts/validate-source-shape.ts @@ -1,5 +1,5 @@ import { CaatingaError, CaatingaErrorCode } from "../errors/CaatingaError.js"; -import { isLikelyPublicKeySource } from "../stellar-cli/recover-deploy-contract-id.js"; +import { isLikelyPublicKeySource } from "../stellar-cli/strkey.js"; export function validateSourceShape(source: string): CaatingaError | undefined { if (source.startsWith("S")) { diff --git a/packages/core/src/stellar-cli/recover-deploy-contract-id.ts b/packages/core/src/stellar-cli/recover-deploy-contract-id.ts index 61a98d3e..411145ad 100644 --- a/packages/core/src/stellar-cli/recover-deploy-contract-id.ts +++ b/packages/core/src/stellar-cli/recover-deploy-contract-id.ts @@ -24,9 +24,9 @@ type HorizonOperationsResponse = { }; }; -export function isLikelyPublicKeySource(source: string): boolean { - return /^G[A-Z2-7]{55}$/.test(source); -} +// Re-exported from the shared strkey module (#148); kept here for back-compat +// with existing importers of this path. +export { isLikelyPublicKeySource } from "./strkey.js"; export function decimalSaltToHex(salt: string): string { return BigInt(salt).toString(16).padStart(64, "0"); diff --git a/packages/core/src/stellar-cli/strkey.test.ts b/packages/core/src/stellar-cli/strkey.test.ts new file mode 100644 index 00000000..113d9436 --- /dev/null +++ b/packages/core/src/stellar-cli/strkey.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vitest"; +import { STELLAR_ADDRESS_REGEX, isLikelyPublicKeySource } from "./strkey.js"; + +describe("strkey", () => { + const validKey = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"; + + it("should_match_a_valid_public_key", () => { + expect(STELLAR_ADDRESS_REGEX.test(validKey)).toBe(true); + expect(isLikelyPublicKeySource(validKey)).toBe(true); + }); + + it("should_reject_named_aliases_and_malformed_keys", () => { + expect(isLikelyPublicKeySource("alice")).toBe(false); + expect(isLikelyPublicKeySource(validKey.slice(0, -1))).toBe(false); // too short + expect(isLikelyPublicKeySource(`${validKey}A`)).toBe(false); // too long + expect(isLikelyPublicKeySource(validKey.replace("G", "M"))).toBe(false); // wrong version byte + expect(isLikelyPublicKeySource(validKey.replace(/.$/, "1"))).toBe(false); // 1 not in base32 alphabet + }); +}); diff --git a/packages/core/src/stellar-cli/strkey.ts b/packages/core/src/stellar-cli/strkey.ts new file mode 100644 index 00000000..9dfc7734 --- /dev/null +++ b/packages/core/src/stellar-cli/strkey.ts @@ -0,0 +1,13 @@ +// Single source of truth for the Stellar account (public-key) strkey shape. +// Previously this regex was copy-pasted in three places (resolve-method-args, +// resolve-source-address, recover-deploy-contract-id) and source validation +// reached into the deploy-recovery module just to borrow it (#148). Centralizing +// it here removes the divergence risk and the cross-module coupling. + +/** Stellar account (public-key) strkey: `G` followed by 55 base32 `[A-Z2-7]` chars. */ +export const STELLAR_ADDRESS_REGEX = /^G[A-Z2-7]{55}$/; + +/** True when `source` is a raw Stellar public key rather than a named alias. */ +export function isLikelyPublicKeySource(source: string): boolean { + return STELLAR_ADDRESS_REGEX.test(source); +}