Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/core-ts/node_modules/.vite/vitest/results.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 8 additions & 75 deletions packages/core-ts/src/muxed/decode.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,20 @@
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);
const result = new Uint8Array(byteCount);
let buffer = 0;
let bitsLeft = 0;
let byteIndex = 0;
for (const ch of s) {
const value = BASE32_CHARS.indexOf(ch);
if (value === -1) throw new Error(`Invalid base32 character: ${ch}`);
buffer = (buffer << 5) | value;
bitsLeft += 5;
if (bitsLeft >= 8) {
if (byteIndex < byteCount) {
result[byteIndex++] = (buffer >> (bitsLeft - 8)) & 0xff;
}
bitsLeft -= 8;
}
}
return result;
}

/**
* Computes a 16-bit CRC (CCITT-FALSE).
* @internal
*/
function crc16(bytes: Uint8Array): number {
let crc = 0;
for (const byte of bytes) {
crc ^= byte << 8;
for (let i = 0; i < 8; i++) {
crc = crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1;
}
}
return crc & 0xffff;
}

/**
* Validates and decodes a Stellar StrKey string.
* @internal
*/
function decodeStrKey(address: string): Uint8Array {
const up = address.toUpperCase();
const decoded = decodeBase32(up);
if (decoded.length < 3) throw new Error("invalid encoded string");

const data = decoded.slice(0, decoded.length - 2);
const checksum =
decoded[decoded.length - 2] | (decoded[decoded.length - 1] << 8);
const computed = crc16(data);
if (computed !== checksum) throw new Error("invalid checksum");

return data;
}
import { MuxedAccount } from "@stellar/stellar-sdk";

/**
* 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)].
* Uses the official Stellar SDK MuxedAccount parser to ensure specification compliance.
*
* @param mAddress - The muxed address string starting with 'M'.
* @returns Metadata containing the base G address and the 64-bit BigInt ID.
* @throws {Error} If the address is not a valid muxed address.
*/
export function decodeMuxed(mAddress: string): { baseG: string; id: bigint } {
const data = decodeStrKey(mAddress);
if (data.length !== 41) throw new Error("invalid payload length");

const pubkey = data.slice(1, 33);
const idBytes = data.slice(33, 41);

let id = 0n;
for (const byte of idBytes) {
id = (id << 8n) + BigInt(byte);
}
// MuxedAccount.fromAddress requires a sequence number. Since this is an
// offline decoding of the address parts, we provide a placeholder.
const muxed = MuxedAccount.fromAddress(mAddress, "0");

return {
baseG: StrKey.encodeEd25519PublicKey(Buffer.from(pubkey)),
id: id,
baseG: muxed.baseAccount().accountId(),
id: BigInt(muxed.id()),
};
}
10 changes: 6 additions & 4 deletions packages/core-ts/src/routing/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ export function extractRouting(input: RoutingInput): RoutingResult {
const warnings: Warning[] = [...parsed.warnings];

warnings.push({
code: "CONTRACT_SENDER_DETECTED",
severity: "warn",
message:
"Contract address detected. Contract addresses cannot be used as transaction senders.",
code: "INVALID_DESTINATION",
severity: "error",
message: "C address is not a valid destination",
context: {
destinationKind: "C",
},
});

return {
Expand Down
Loading