From a65f05a8519b159cf3c8d3bc682cc8ee52331033 Mon Sep 17 00:00:00 2001 From: Lewechi Date: Tue, 31 Mar 2026 17:54:51 +0100 Subject: [PATCH] fix: remove unwanted comments --- .../node_modules/.vite/vitest/results.json | 2 +- .../stellar-address-kit/src/address/detect.ts | 62 +++--------------- .../stellar-address-kit/src/address/errors.ts | 4 ++ .../stellar-address-kit/src/address/parse.ts | 48 ++------------ .../stellar-address-kit/src/muxed/decode.ts | 21 ++++-- .../stellar-address-kit/src/muxed/encode.ts | 24 +++---- .../src/routing/extract.ts | 13 ++-- .../stellar-address-kit/src/routing/memo.ts | 10 +++ .../test/incremental_kernel.Ly9AZGFydD0zLjA= | Bin 4031336 -> 4031656 bytes packages/core-dart/lib/src/address/parse.dart | 3 + .../core-dart/lib/src/routing/extract.dart | 3 + packages/core-dart/test/debug_m.dart | 4 +- .../core-dart/test/muxed_id_range_test.dart | 43 +++++++----- packages/core-go/address/detect.go | 1 + packages/core-go/address/parse.go | 7 +- packages/core-go/routing/extract.go | 4 ++ .../node_modules/.vite/vitest/results.json | 2 +- packages/core-ts/src/address/detect.ts | 62 +++--------------- packages/core-ts/src/address/errors.ts | 4 ++ packages/core-ts/src/address/parse.ts | 48 ++------------ packages/core-ts/src/muxed/decode.ts | 21 ++++-- packages/core-ts/src/muxed/encode.ts | 24 +++---- packages/core-ts/src/routing/extract.ts | 13 ++-- packages/core-ts/src/routing/memo.ts | 10 +++ 24 files changed, 170 insertions(+), 263 deletions(-) diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/node_modules/.vite/vitest/results.json b/node_modules/.pnpm/node_modules/stellar-address-kit/node_modules/.vite/vitest/results.json index 2017d57..aaf2a02 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/node_modules/.vite/vitest/results.json +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/node_modules/.vite/vitest/results.json @@ -1 +1 @@ -{"version":"1.6.1","results":[[":src/muxed/encode.test.ts",{"duration":13,"failed":false}],[":src/address/parse.test.ts",{"duration":20,"failed":false}],[":src/spec/validate.test.ts",{"duration":11,"failed":false}],[":src/spec/detect.test.ts",{"duration":14,"failed":false}],[":src/spec/runner.test.ts",{"duration":21,"failed":false}]]} \ No newline at end of file +{"version":"1.6.1","results":[[":src/test/detect.test.ts",{"duration":24,"failed":false}],[":src/test/validate.test.ts",{"duration":26,"failed":false}],[":src/muxed/encode.test.ts",{"duration":18,"failed":false}],[":src/test/extract.test.ts",{"duration":51,"failed":false}],[":src/address/parse.test.ts",{"duration":45,"failed":false}],[":src/test/integration.test.ts",{"duration":71,"failed":false}],[":src/spec/runner.test.ts",{"duration":55,"failed":false}],[":src/spec/validate.test.ts",{"duration":8,"failed":false}],[":src/test/bigint-edge-cases.test.ts",{"duration":12,"failed":false}],[":src/spec/detect.test.ts",{"duration":13,"failed":false}]]} \ No newline at end of file diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/detect.ts b/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/detect.ts index a30c5a9..adc5166 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/detect.ts +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/detect.ts @@ -5,14 +5,8 @@ const { StrKey } = StellarSdk; const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; /** - * @param input - The Base32-encoded string to decode. - * @returns A Uint8Array containing the decoded binary data. - * - * @throws {Error} If the input contains characters not found in the Base32 alphabet. - * - * @example - * const bytes = decodeBase32("MZXW6==="); // "foo" - * console.log(new TextDecoder().decode(bytes)); // "foo" + * Decodes a Base32-encoded string into binary data. + * @internal */ function decodeBase32(input: string): Uint8Array { const s = input.toUpperCase().replace(/=+$/, ""); @@ -38,23 +32,8 @@ function decodeBase32(input: string): Uint8Array { } /** - * Computes a 16-bit CRC (Cyclic Redundancy Check) for the given byte array. - * - * This implementation uses the CRC-16-CCITT polynomial (0x1021) with: - * - Initial value: 0x0000 - * - No reflection (input or output) - * - No final XOR - * - * The function processes each byte bit-by-bit, updating the CRC value - * using a shift register and polynomial XOR operations. - * - * @param bytes - The input data as a Uint8Array. - * @returns The computed 16-bit CRC value as a number (0–65535). - * - * @example - * const data = new Uint8Array([0x01, 0x02, 0x03]); - * const checksum = crc16(data); - * console.log(checksum); // e.g., 0x6131 + * Computes a 16-bit CRC (CCITT-FALSE) for binary validation. + * @internal */ function crc16(bytes: Uint8Array): number { let crc = 0; @@ -73,45 +52,24 @@ function crc16(bytes: Uint8Array): number { } /** - * Detects the type of a Stellar address. - * - * The function classifies the input string into one of the following: - * - `"G"`: Ed25519 public key (standard account address) - * - `"M"`: Med25519 (muxed) account address - * - `"C"`: Contract address - * - `"invalid"`: Not a valid or recognized address - * - * Detection is performed in two stages: - * 1. Uses official `StrKey` validation methods for known address types. - * 2. Falls back to manual validation for muxed (`"M"`) addresses by: - * - Decoding the Base32 string - * - Verifying structure (length and version byte) - * - Validating the CRC16 checksum - * - * @param address - The address string to evaluate. - * @returns A string indicating the detected address type or `"invalid"` if none match. - * - * @example - * detect("GBRPYHIL2C..."); // "G" - * detect("MA3D5F..."); // "M" - * detect("CA7Q..."); // "C" - * detect("invalid"); // "invalid" + * Identifies the Stellar address kind from a string input. + * Supports G (Ed25519), M (Muxed/SEP-23), and C (Contract) addresses. + * Returns "invalid" if the input does not match a supported format. */ export function detect(address: string): "G" | "M" | "C" | "invalid" { if (!address) return "invalid"; const up = address.toUpperCase(); - // 1. Try standard SDK validation (prioritize these) if (StrKey.isValidEd25519PublicKey(up)) return "G"; if (StrKey.isValidMed25519PublicKey(up)) return "M"; if (StrKey.isValidContract(up)) return "C"; - // 2. Fallback for custom 0x60 muxed addresses try { const prefix = up[0]; if (prefix === "M") { const decoded = decodeBase32(up); - // M-addresses are 43 bytes: 1 (version) + 32 (pubkey) + 8 (id) + 2 (checksum) + // M-addresses (SEP-23) payload consists of 1 version byte (0x60), + // 32-byte pubkey, 8-byte ID, and 2-byte CRC16 checksum. if (decoded.length === 43 && decoded[0] === 0x60) { const data = decoded.slice(0, decoded.length - 2); const checksum = @@ -122,7 +80,7 @@ export function detect(address: string): "G" | "M" | "C" | "invalid" { } } } catch { - // Ignore and proceed to return "invalid" + // Return "invalid" on any decoding failure. } return "invalid"; diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/errors.ts b/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/errors.ts index 49ab564..588cbfb 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/errors.ts +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/errors.ts @@ -8,6 +8,10 @@ export type ErrorCode = | "FEDERATION_ADDRESS_NOT_SUPPORTED" | "UNKNOWN_PREFIX"; +/** + * Represents an error encountered during the parsing of a Stellar address. + * Includes a machine-readable ErrorCode and the original input string. + */ export class AddressParseError extends Error { code: ErrorCode; readonly input: string; diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/parse.ts b/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/parse.ts index 27bb8d5..9158985 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/parse.ts +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/src/address/parse.ts @@ -5,49 +5,11 @@ import { AddressParseError } from "../address/errors"; /** * Parses a Stellar address string and returns a structured result. - * - * Parsing boundaries: - * - **G addresses** (pubkey): 56 characters, base32-encoded ed25519 public key with CRC16 checksum - * - **C addresses** (contract): 64 characters, base64-encoded contract ID - * - **M addresses** (muxed): Starts with "M", contains embedded ed25519 pubkey + muxed account ID - * - * The input is automatically normalized to uppercase before parsing. - * Invalid addresses result in thrown {@link AddressParseError} rather than - * returning an error result, making this the primary validation entrypoint. - * - * @param address - The Stellar address string to parse (G... , C... , or M... prefix) - * @returns A {@link ParseResult} containing the parsed address with kind and warnings - * @throws {AddressParseError} When the address fails validation: - * - `"UNKNOWN_PREFIX"` - Address does not start with G, M, or C - * - `"INVALID_CHECKSUM"` - Base32 decoding or CRC16 checksum validation failed (pubkey/muxed) - * - `"INVALID_LENGTH"` - Address length does not match expected format - * - `"INVALID_BASE32"` - Address contains non-base32 characters (pubkey/muxed) - * - * @example - * ```ts - * // Parse a public key - * const result = parse("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"); - * // => { kind: "G", address: "GA7QYNF7...", warnings: [] } - * - * // Parse a contract address - * const contract = parse("CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"); - * // => { kind: "C", address: "CDLZFC3...", warnings: [] } - * - * // Parse a muxed address - * const muxed = parse("MBMM5N2TPABDA6OZHG5WSF6CXDABPK62L3DCMYC7QEYIIBPL6Q5DGDQ3"); - * // => { kind: "M", address: "...", baseG: "GA7QYNF...", muxedId: 12345n, warnings: [] } - * - * // Handle invalid addresses - * try { - * parse("INVALID"); - * } catch (error) { - * if (error instanceof AddressParseError) { - * console.log(error.code); // "UNKNOWN_PREFIX" - * console.log(error.input); // "INVALID" - * console.log(error.message); // "Invalid address" - * } - * } - * ``` + * Supports G (Public Key), C (Contract), and M (Muxed) addresses. + * + * @param address - The Stellar address string to parse. + * @returns A {@link ParseResult} containing the parsed address components. + * @throws {AddressParseError} If the address prefix is unknown or checksum/length validation fails. */ export function parse(address: string): ParseResult { const up = address.toUpperCase(); diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/decode.ts b/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/decode.ts index c8588b7..c13a389 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/decode.ts +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/decode.ts @@ -2,6 +2,10 @@ import { StrKey } from "@stellar/stellar-sdk"; const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +/** + * Decodes a Base32-encoded string into binary data. + * @internal + */ function decodeBase32(input: string): Uint8Array { const s = input.toUpperCase().replace(/=+$/, ""); const byteCount = Math.floor((s.length * 5) / 8); @@ -24,6 +28,10 @@ function decodeBase32(input: string): Uint8Array { return result; } +/** + * Computes a 16-bit CRC (CCITT-FALSE). + * @internal + */ function crc16(bytes: Uint8Array): number { let crc = 0; for (const byte of bytes) { @@ -35,6 +43,10 @@ function crc16(bytes: Uint8Array): number { return crc & 0xffff; } +/** + * Validates and decodes a Stellar StrKey string. + * @internal + */ function decodeStrKey(address: string): Uint8Array { const up = address.toUpperCase(); const decoded = decodeBase32(up); @@ -50,15 +62,14 @@ function decodeStrKey(address: string): Uint8Array { } /** - * Decodes a muxed Stellar address into its base G address and numeric ID. - * Returns a typed structure to ensure native TypeScript protection and clean destructuring. + * Decodes a muxed Stellar address (SEP-23) into its base account and ID. + * The payload is expected to be [Version(1)] [Pubkey(32)] [ID(8)]. * - * @param mAddress The muxed Stellar address (starts with M). - * @returns An object containing the base G address and the 64-bit ID as a bigint. + * @param mAddress - The muxed address string starting with 'M'. + * @returns Metadata containing the base G address and the 64-bit BigInt ID. */ export function decodeMuxed(mAddress: string): { baseG: string; id: bigint } { const data = decodeStrKey(mAddress); - // Layout for Muxed Address: [Version(1)] [Pubkey(32)] [ID(8)] if (data.length !== 41) throw new Error("invalid payload length"); const pubkey = data.slice(1, 33); diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/encode.ts b/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/encode.ts index f1ee8a6..bc76330 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/encode.ts +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/encode.ts @@ -1,34 +1,34 @@ import { StrKey } from "@stellar/stellar-sdk"; -// Use BigInt literal for the 64-bit unsigned integer maximum +/** + * Maximum value for a 64-bit unsigned integer. + */ const MAX_UINT64 = 18446744073709551615n; /** - * Encodes a muxed Stellar address using a base G address and numeric ID. - * Adheres to BigInt audit requirements to prevent precision loss. + * Encodes a base G address and numeric ID into a muxed Stellar address (SEP-23). + * Uses BigInt for the ID to prevent precision loss and ensures the ID is within uint64 boundaries. + * + * @param baseG - Ed25519 public key string starting with 'G'. + * @param id - The 64-bit routing ID as a BigInt. + * @returns The encoded muxed address string starting with 'M'. + * @throws {TypeError} If ID is not a BigInt. + * @throws {RangeError} If ID is outside the uint64 range [0, 2^64-1]. */ export function encodeMuxed(baseG: string, id: bigint): string { - // 1. Strict Type Enforcement - // Ensure we are working with a BigInt immediately if (typeof id !== "bigint") { throw new TypeError(`ID must be a bigint, received ${typeof id}`); } - // 2. Uint64 Boundary Check - // Using BigInt literals (0n) for comparison if (id < 0n || id > MAX_UINT64) { throw new RangeError(`ID is outside the uint64 range: 0 to ${MAX_UINT64}`); } - // 3. Address Validation if (!StrKey.isValidEd25519PublicKey(baseG)) { throw new Error(`Invalid base G address (Ed25519 public key expected)`); } - // 4. Safe Encoding - // Build the 40-byte med25519 payload directly: - // [ed25519 pubkey (32 bytes)] [uint64 id (8 bytes, big-endian)]. - // This avoids runtime constructor-contract drift in MuxedAccount across SDK versions. + // Build the 40-byte med25519 payload: [pubkey (32 bytes)] [uint64 id (8 bytes, BE)]. const pubkeyBytes = Buffer.from(StrKey.decodeEd25519PublicKey(baseG)); const idBytes = Buffer.alloc(8); idBytes.writeBigUInt64BE(id); diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/extract.ts b/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/extract.ts index 824ecde..eb3d3c2 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/extract.ts +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/extract.ts @@ -33,15 +33,14 @@ function assertRoutableAddress(destination: string): void { } /** - * Extracts routing information from a given destination address and memo. + * Extracts deposit routing information from a Stellar address and memo. * - * @param input - The routing input containing the destination address, memo type, and memo value. - * @returns The extracted routing result including destination base account, routing ID, routing source, and warnings. + * Routing Policy: + * 1. M-addresses: Routing ID is extracted from the address; any memo is ignored for routing. + * 2. G-addresses: Routing ID is extracted from MEMO_ID or numeric MEMO_TEXT if valid. * - * Decision Branches: - * - M-address: The destination is parsed as a muxed account. The routing ID from the M-address takes precedence over any provided memo. - * - G-address with MEMO_ID: The destination is a standard G-address. The routing ID is extracted from the MEMO_ID. - * - G-address with MEMO_TEXT: The destination is a standard G-address. The routing ID is extracted from the MEMO_TEXT if it represents a valid numeric uint64. + * @param input - The destination address and optional memo components. + * @returns A result containing the base account, routing ID, source, and any warnings. */ export function extractRouting(input: RoutingInput): RoutingResult { assertRoutableAddress(input.destination); diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/memo.ts b/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/memo.ts index 0acb6d7..e0c561d 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/memo.ts +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/memo.ts @@ -5,8 +5,18 @@ export type NormalizeResult = { warnings: Warning[]; }; +/** + * Maximum value for a 64-bit unsigned integer (uint64). + */ const UINT64_MAX = BigInt("18446744073709551615"); +/** + * Normalizes a numeric string into a canonical uint64 representation. + * Strips leading zeros and validates that the value is within uint64 boundaries. + * + * @param s - The numeric string to normalize. + * @returns Result containing the normalized string (or null if invalid) and any warnings. + */ export function normalizeMemoTextId(s: string): NormalizeResult { const warnings: Warning[] = []; diff --git a/packages/core-dart/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= b/packages/core-dart/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= index 2f881d8063ff10539aeae1856d73fb8da7b22c23..b6117f8fde8a709bcf7d4b0767a69c5c6dd52f8f 100644 GIT binary patch delta 7329 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 delta 6960 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 Y$qI+AU9x5^o$$x>)V6nd&8>+41Md3?8vp MuxedAddress.encode(baseG: validG, id: BigInt.from(-1)), throwsA(isA()), - reason: 'encode(validG, -1) should throw but returns a String on unfixed code', + reason: + 'encode(validG, -1) should throw but returns a String on unfixed code', ); }); /// Validates: Requirements 1.2 - test('throws StellarAddressException for id = 2^64 (one above uint64Max)', () { + test('throws StellarAddressException for id = 2^64 (one above uint64Max)', + () { expect( () => MuxedAddress.encode( baseG: validG, @@ -51,7 +55,9 @@ void main() { }); /// Validates: Requirements 1.1 - test('throws StellarAddressException for id = -9999999999999999999 (large negative)', () { + test( + 'throws StellarAddressException for id = -9999999999999999999 (large negative)', + () { expect( () => MuxedAddress.encode( baseG: validG, @@ -90,14 +96,14 @@ void main() { // Representative valid ids covering lower boundary, upper boundary, and // several mid-range values. final validIds = [ - BigInt.zero, // lower boundary (req 3.1) - BigInt.one, // just above zero - BigInt.from(1000), // small mid-range - BigInt.parse('9223372036854775807'), // int64 max (mid-range) - BigInt.parse('9223372036854775808'), // int64 max + 1 - BigInt.parse('12345678901234567890'), // large mid-range - uint64Max - BigInt.one, // one below upper boundary - uint64Max, // upper boundary (req 3.3) + BigInt.zero, // lower boundary (req 3.1) + BigInt.one, // just above zero + BigInt.from(1000), // small mid-range + BigInt.parse('9223372036854775807'), // int64 max (mid-range) + BigInt.parse('9223372036854775808'), // int64 max + 1 + BigInt.parse('12345678901234567890'), // large mid-range + uint64Max - BigInt.one, // one below upper boundary + uint64Max, // upper boundary (req 3.3) ]; /// Validates: Requirements 3.1, 3.2, 3.3 @@ -115,24 +121,29 @@ void main() { /// Validates: Requirement 3.4 /// encode with an invalid baseG must throw StellarAddressException for the /// base address, regardless of the id value. - test('encode(invalidG, validId) throws StellarAddressException for invalid base', () { + test( + 'encode(invalidG, validId) throws StellarAddressException for invalid base', + () { const invalidG = 'INVALID_ADDRESS'; expect( () => MuxedAddress.encode(baseG: invalidG, id: BigInt.from(42)), throwsA(isA()), - reason: 'encode with an invalid G-address must throw StellarAddressException', + reason: + 'encode with an invalid G-address must throw StellarAddressException', ); }); /// Validates: Requirement 3.4 — also check with a well-formed but wrong-type address - test('encode(M-address as baseG, validId) throws StellarAddressException', () { + test('encode(M-address as baseG, validId) throws StellarAddressException', + () { // An M-address is not a valid baseG const mAddress = 'MAYCUYT553C5LHVE2XPW5GMEJT4BXGM7AHMJWLAPZP53KJO7EIQACAAAAAAAAAAAAD672'; expect( () => MuxedAddress.encode(baseG: mAddress, id: BigInt.zero), throwsA(isA()), - reason: 'encode with an M-address as baseG must throw StellarAddressException', + reason: + 'encode with an M-address as baseG must throw StellarAddressException', ); }); }); diff --git a/packages/core-go/address/detect.go b/packages/core-go/address/detect.go index b8aadab..338db7f 100644 --- a/packages/core-go/address/detect.go +++ b/packages/core-go/address/detect.go @@ -1,6 +1,7 @@ package address // Detect identifies the AddressKind of a Stellar address string. +// Supported kinds include KindG (Ed25519), KindM (Muxed/SEP-23), and KindC (Contract). func Detect(addr string) (AddressKind, error) { versionByte, _, err := DecodeStrKey(addr) if err != nil { diff --git a/packages/core-go/address/parse.go b/packages/core-go/address/parse.go index 8a3420c..c552f4f 100644 --- a/packages/core-go/address/parse.go +++ b/packages/core-go/address/parse.go @@ -5,10 +5,9 @@ import ( "strings" ) -// Parse parses a Stellar address into an Address struct. -// -// For muxed accounts (M-addresses), BaseG and MuxedID are populated. -// For other kinds, BaseG will be empty and MuxedID will be zero. +// Parse parses a Stellar address string into an Address struct. +// For muxed accounts (KindM), BaseG and MuxedID are populated; for other kinds, +// these fields will be empty/zero. Returns an AddressError if validation fails. func Parse(input string) (*Address, error) { kind, err := Detect(input) if err != nil { diff --git a/packages/core-go/routing/extract.go b/packages/core-go/routing/extract.go index e6b1125..4f7b8b1 100644 --- a/packages/core-go/routing/extract.go +++ b/packages/core-go/routing/extract.go @@ -27,6 +27,10 @@ func normalizeUnsupportedMemoType(memoType string) string { } } +// ExtractRouting identifies the deposit routing destination and identifier from a Stellar +// payment input. It implements the standard priority policy where M-address identifiers +// take precedence over any provided memo. Returns a RoutingResult with the decoded +// state and applicable warnings. func ExtractRouting(input RoutingInput) RoutingResult { if input.SourceAccount != "" { source, err := address.Parse(input.SourceAccount) diff --git a/packages/core-ts/node_modules/.vite/vitest/results.json b/packages/core-ts/node_modules/.vite/vitest/results.json index 2017d57..aaf2a02 100644 --- a/packages/core-ts/node_modules/.vite/vitest/results.json +++ b/packages/core-ts/node_modules/.vite/vitest/results.json @@ -1 +1 @@ -{"version":"1.6.1","results":[[":src/muxed/encode.test.ts",{"duration":13,"failed":false}],[":src/address/parse.test.ts",{"duration":20,"failed":false}],[":src/spec/validate.test.ts",{"duration":11,"failed":false}],[":src/spec/detect.test.ts",{"duration":14,"failed":false}],[":src/spec/runner.test.ts",{"duration":21,"failed":false}]]} \ No newline at end of file +{"version":"1.6.1","results":[[":src/test/detect.test.ts",{"duration":24,"failed":false}],[":src/test/validate.test.ts",{"duration":26,"failed":false}],[":src/muxed/encode.test.ts",{"duration":18,"failed":false}],[":src/test/extract.test.ts",{"duration":51,"failed":false}],[":src/address/parse.test.ts",{"duration":45,"failed":false}],[":src/test/integration.test.ts",{"duration":71,"failed":false}],[":src/spec/runner.test.ts",{"duration":55,"failed":false}],[":src/spec/validate.test.ts",{"duration":8,"failed":false}],[":src/test/bigint-edge-cases.test.ts",{"duration":12,"failed":false}],[":src/spec/detect.test.ts",{"duration":13,"failed":false}]]} \ No newline at end of file diff --git a/packages/core-ts/src/address/detect.ts b/packages/core-ts/src/address/detect.ts index a30c5a9..adc5166 100644 --- a/packages/core-ts/src/address/detect.ts +++ b/packages/core-ts/src/address/detect.ts @@ -5,14 +5,8 @@ const { StrKey } = StellarSdk; const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; /** - * @param input - The Base32-encoded string to decode. - * @returns A Uint8Array containing the decoded binary data. - * - * @throws {Error} If the input contains characters not found in the Base32 alphabet. - * - * @example - * const bytes = decodeBase32("MZXW6==="); // "foo" - * console.log(new TextDecoder().decode(bytes)); // "foo" + * Decodes a Base32-encoded string into binary data. + * @internal */ function decodeBase32(input: string): Uint8Array { const s = input.toUpperCase().replace(/=+$/, ""); @@ -38,23 +32,8 @@ function decodeBase32(input: string): Uint8Array { } /** - * Computes a 16-bit CRC (Cyclic Redundancy Check) for the given byte array. - * - * This implementation uses the CRC-16-CCITT polynomial (0x1021) with: - * - Initial value: 0x0000 - * - No reflection (input or output) - * - No final XOR - * - * The function processes each byte bit-by-bit, updating the CRC value - * using a shift register and polynomial XOR operations. - * - * @param bytes - The input data as a Uint8Array. - * @returns The computed 16-bit CRC value as a number (0–65535). - * - * @example - * const data = new Uint8Array([0x01, 0x02, 0x03]); - * const checksum = crc16(data); - * console.log(checksum); // e.g., 0x6131 + * Computes a 16-bit CRC (CCITT-FALSE) for binary validation. + * @internal */ function crc16(bytes: Uint8Array): number { let crc = 0; @@ -73,45 +52,24 @@ function crc16(bytes: Uint8Array): number { } /** - * Detects the type of a Stellar address. - * - * The function classifies the input string into one of the following: - * - `"G"`: Ed25519 public key (standard account address) - * - `"M"`: Med25519 (muxed) account address - * - `"C"`: Contract address - * - `"invalid"`: Not a valid or recognized address - * - * Detection is performed in two stages: - * 1. Uses official `StrKey` validation methods for known address types. - * 2. Falls back to manual validation for muxed (`"M"`) addresses by: - * - Decoding the Base32 string - * - Verifying structure (length and version byte) - * - Validating the CRC16 checksum - * - * @param address - The address string to evaluate. - * @returns A string indicating the detected address type or `"invalid"` if none match. - * - * @example - * detect("GBRPYHIL2C..."); // "G" - * detect("MA3D5F..."); // "M" - * detect("CA7Q..."); // "C" - * detect("invalid"); // "invalid" + * Identifies the Stellar address kind from a string input. + * Supports G (Ed25519), M (Muxed/SEP-23), and C (Contract) addresses. + * Returns "invalid" if the input does not match a supported format. */ export function detect(address: string): "G" | "M" | "C" | "invalid" { if (!address) return "invalid"; const up = address.toUpperCase(); - // 1. Try standard SDK validation (prioritize these) if (StrKey.isValidEd25519PublicKey(up)) return "G"; if (StrKey.isValidMed25519PublicKey(up)) return "M"; if (StrKey.isValidContract(up)) return "C"; - // 2. Fallback for custom 0x60 muxed addresses try { const prefix = up[0]; if (prefix === "M") { const decoded = decodeBase32(up); - // M-addresses are 43 bytes: 1 (version) + 32 (pubkey) + 8 (id) + 2 (checksum) + // M-addresses (SEP-23) payload consists of 1 version byte (0x60), + // 32-byte pubkey, 8-byte ID, and 2-byte CRC16 checksum. if (decoded.length === 43 && decoded[0] === 0x60) { const data = decoded.slice(0, decoded.length - 2); const checksum = @@ -122,7 +80,7 @@ export function detect(address: string): "G" | "M" | "C" | "invalid" { } } } catch { - // Ignore and proceed to return "invalid" + // Return "invalid" on any decoding failure. } return "invalid"; diff --git a/packages/core-ts/src/address/errors.ts b/packages/core-ts/src/address/errors.ts index 49ab564..588cbfb 100644 --- a/packages/core-ts/src/address/errors.ts +++ b/packages/core-ts/src/address/errors.ts @@ -8,6 +8,10 @@ export type ErrorCode = | "FEDERATION_ADDRESS_NOT_SUPPORTED" | "UNKNOWN_PREFIX"; +/** + * Represents an error encountered during the parsing of a Stellar address. + * Includes a machine-readable ErrorCode and the original input string. + */ export class AddressParseError extends Error { code: ErrorCode; readonly input: string; diff --git a/packages/core-ts/src/address/parse.ts b/packages/core-ts/src/address/parse.ts index 27bb8d5..9158985 100644 --- a/packages/core-ts/src/address/parse.ts +++ b/packages/core-ts/src/address/parse.ts @@ -5,49 +5,11 @@ import { AddressParseError } from "../address/errors"; /** * Parses a Stellar address string and returns a structured result. - * - * Parsing boundaries: - * - **G addresses** (pubkey): 56 characters, base32-encoded ed25519 public key with CRC16 checksum - * - **C addresses** (contract): 64 characters, base64-encoded contract ID - * - **M addresses** (muxed): Starts with "M", contains embedded ed25519 pubkey + muxed account ID - * - * The input is automatically normalized to uppercase before parsing. - * Invalid addresses result in thrown {@link AddressParseError} rather than - * returning an error result, making this the primary validation entrypoint. - * - * @param address - The Stellar address string to parse (G... , C... , or M... prefix) - * @returns A {@link ParseResult} containing the parsed address with kind and warnings - * @throws {AddressParseError} When the address fails validation: - * - `"UNKNOWN_PREFIX"` - Address does not start with G, M, or C - * - `"INVALID_CHECKSUM"` - Base32 decoding or CRC16 checksum validation failed (pubkey/muxed) - * - `"INVALID_LENGTH"` - Address length does not match expected format - * - `"INVALID_BASE32"` - Address contains non-base32 characters (pubkey/muxed) - * - * @example - * ```ts - * // Parse a public key - * const result = parse("GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"); - * // => { kind: "G", address: "GA7QYNF7...", warnings: [] } - * - * // Parse a contract address - * const contract = parse("CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"); - * // => { kind: "C", address: "CDLZFC3...", warnings: [] } - * - * // Parse a muxed address - * const muxed = parse("MBMM5N2TPABDA6OZHG5WSF6CXDABPK62L3DCMYC7QEYIIBPL6Q5DGDQ3"); - * // => { kind: "M", address: "...", baseG: "GA7QYNF...", muxedId: 12345n, warnings: [] } - * - * // Handle invalid addresses - * try { - * parse("INVALID"); - * } catch (error) { - * if (error instanceof AddressParseError) { - * console.log(error.code); // "UNKNOWN_PREFIX" - * console.log(error.input); // "INVALID" - * console.log(error.message); // "Invalid address" - * } - * } - * ``` + * Supports G (Public Key), C (Contract), and M (Muxed) addresses. + * + * @param address - The Stellar address string to parse. + * @returns A {@link ParseResult} containing the parsed address components. + * @throws {AddressParseError} If the address prefix is unknown or checksum/length validation fails. */ export function parse(address: string): ParseResult { const up = address.toUpperCase(); diff --git a/packages/core-ts/src/muxed/decode.ts b/packages/core-ts/src/muxed/decode.ts index c8588b7..c13a389 100644 --- a/packages/core-ts/src/muxed/decode.ts +++ b/packages/core-ts/src/muxed/decode.ts @@ -2,6 +2,10 @@ import { StrKey } from "@stellar/stellar-sdk"; const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +/** + * Decodes a Base32-encoded string into binary data. + * @internal + */ function decodeBase32(input: string): Uint8Array { const s = input.toUpperCase().replace(/=+$/, ""); const byteCount = Math.floor((s.length * 5) / 8); @@ -24,6 +28,10 @@ function decodeBase32(input: string): Uint8Array { return result; } +/** + * Computes a 16-bit CRC (CCITT-FALSE). + * @internal + */ function crc16(bytes: Uint8Array): number { let crc = 0; for (const byte of bytes) { @@ -35,6 +43,10 @@ function crc16(bytes: Uint8Array): number { return crc & 0xffff; } +/** + * Validates and decodes a Stellar StrKey string. + * @internal + */ function decodeStrKey(address: string): Uint8Array { const up = address.toUpperCase(); const decoded = decodeBase32(up); @@ -50,15 +62,14 @@ function decodeStrKey(address: string): Uint8Array { } /** - * Decodes a muxed Stellar address into its base G address and numeric ID. - * Returns a typed structure to ensure native TypeScript protection and clean destructuring. + * Decodes a muxed Stellar address (SEP-23) into its base account and ID. + * The payload is expected to be [Version(1)] [Pubkey(32)] [ID(8)]. * - * @param mAddress The muxed Stellar address (starts with M). - * @returns An object containing the base G address and the 64-bit ID as a bigint. + * @param mAddress - The muxed address string starting with 'M'. + * @returns Metadata containing the base G address and the 64-bit BigInt ID. */ export function decodeMuxed(mAddress: string): { baseG: string; id: bigint } { const data = decodeStrKey(mAddress); - // Layout for Muxed Address: [Version(1)] [Pubkey(32)] [ID(8)] if (data.length !== 41) throw new Error("invalid payload length"); const pubkey = data.slice(1, 33); diff --git a/packages/core-ts/src/muxed/encode.ts b/packages/core-ts/src/muxed/encode.ts index f1ee8a6..bc76330 100644 --- a/packages/core-ts/src/muxed/encode.ts +++ b/packages/core-ts/src/muxed/encode.ts @@ -1,34 +1,34 @@ import { StrKey } from "@stellar/stellar-sdk"; -// Use BigInt literal for the 64-bit unsigned integer maximum +/** + * Maximum value for a 64-bit unsigned integer. + */ const MAX_UINT64 = 18446744073709551615n; /** - * Encodes a muxed Stellar address using a base G address and numeric ID. - * Adheres to BigInt audit requirements to prevent precision loss. + * Encodes a base G address and numeric ID into a muxed Stellar address (SEP-23). + * Uses BigInt for the ID to prevent precision loss and ensures the ID is within uint64 boundaries. + * + * @param baseG - Ed25519 public key string starting with 'G'. + * @param id - The 64-bit routing ID as a BigInt. + * @returns The encoded muxed address string starting with 'M'. + * @throws {TypeError} If ID is not a BigInt. + * @throws {RangeError} If ID is outside the uint64 range [0, 2^64-1]. */ export function encodeMuxed(baseG: string, id: bigint): string { - // 1. Strict Type Enforcement - // Ensure we are working with a BigInt immediately if (typeof id !== "bigint") { throw new TypeError(`ID must be a bigint, received ${typeof id}`); } - // 2. Uint64 Boundary Check - // Using BigInt literals (0n) for comparison if (id < 0n || id > MAX_UINT64) { throw new RangeError(`ID is outside the uint64 range: 0 to ${MAX_UINT64}`); } - // 3. Address Validation if (!StrKey.isValidEd25519PublicKey(baseG)) { throw new Error(`Invalid base G address (Ed25519 public key expected)`); } - // 4. Safe Encoding - // Build the 40-byte med25519 payload directly: - // [ed25519 pubkey (32 bytes)] [uint64 id (8 bytes, big-endian)]. - // This avoids runtime constructor-contract drift in MuxedAccount across SDK versions. + // Build the 40-byte med25519 payload: [pubkey (32 bytes)] [uint64 id (8 bytes, BE)]. const pubkeyBytes = Buffer.from(StrKey.decodeEd25519PublicKey(baseG)); const idBytes = Buffer.alloc(8); idBytes.writeBigUInt64BE(id); diff --git a/packages/core-ts/src/routing/extract.ts b/packages/core-ts/src/routing/extract.ts index 824ecde..eb3d3c2 100644 --- a/packages/core-ts/src/routing/extract.ts +++ b/packages/core-ts/src/routing/extract.ts @@ -33,15 +33,14 @@ function assertRoutableAddress(destination: string): void { } /** - * Extracts routing information from a given destination address and memo. + * Extracts deposit routing information from a Stellar address and memo. * - * @param input - The routing input containing the destination address, memo type, and memo value. - * @returns The extracted routing result including destination base account, routing ID, routing source, and warnings. + * Routing Policy: + * 1. M-addresses: Routing ID is extracted from the address; any memo is ignored for routing. + * 2. G-addresses: Routing ID is extracted from MEMO_ID or numeric MEMO_TEXT if valid. * - * Decision Branches: - * - M-address: The destination is parsed as a muxed account. The routing ID from the M-address takes precedence over any provided memo. - * - G-address with MEMO_ID: The destination is a standard G-address. The routing ID is extracted from the MEMO_ID. - * - G-address with MEMO_TEXT: The destination is a standard G-address. The routing ID is extracted from the MEMO_TEXT if it represents a valid numeric uint64. + * @param input - The destination address and optional memo components. + * @returns A result containing the base account, routing ID, source, and any warnings. */ export function extractRouting(input: RoutingInput): RoutingResult { assertRoutableAddress(input.destination); diff --git a/packages/core-ts/src/routing/memo.ts b/packages/core-ts/src/routing/memo.ts index 0acb6d7..e0c561d 100644 --- a/packages/core-ts/src/routing/memo.ts +++ b/packages/core-ts/src/routing/memo.ts @@ -5,8 +5,18 @@ export type NormalizeResult = { warnings: Warning[]; }; +/** + * Maximum value for a 64-bit unsigned integer (uint64). + */ const UINT64_MAX = BigInt("18446744073709551615"); +/** + * Normalizes a numeric string into a canonical uint64 representation. + * Strips leading zeros and validates that the value is within uint64 boundaries. + * + * @param s - The numeric string to normalize. + * @returns Result containing the normalized string (or null if invalid) and any warnings. + */ export function normalizeMemoTextId(s: string): NormalizeResult { const warnings: Warning[] = [];