@rhea-finance/confidential-swap 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/index.cjs +7 -2
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.js +7 -2
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.cjs +11 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/adapters/index.cjs
CHANGED
|
@@ -98,11 +98,16 @@ function deriveConfidentialAccountId(kind, address) {
|
|
|
98
98
|
if (kind === "solana") {
|
|
99
99
|
const bytes2 = import_base.base58.decode(account);
|
|
100
100
|
assert(bytes2.length === 32, "Invalid Solana public key.");
|
|
101
|
-
return import_base.
|
|
101
|
+
return import_base.base58.encode(bytes2);
|
|
102
102
|
}
|
|
103
103
|
assert(kind === "tron", "Unsupported linking wallet kind.");
|
|
104
104
|
const bytes = (0, import_base.base58check)(import_sha2562.sha256).decode(account);
|
|
105
105
|
assert(bytes.length === 21 && bytes[0] === 65, "Invalid Tron address.");
|
|
106
|
+
return (0, import_base.base58check)(import_sha2562.sha256).encode(bytes);
|
|
107
|
+
}
|
|
108
|
+
function tronRecoveredAddressHex(address) {
|
|
109
|
+
const account = deriveConfidentialAccountId("tron", address);
|
|
110
|
+
const bytes = (0, import_base.base58check)(import_sha2562.sha256).decode(account);
|
|
106
111
|
return `0x${import_base.base16.encode(bytes.slice(1)).toLowerCase()}`;
|
|
107
112
|
}
|
|
108
113
|
function identityKey(identity) {
|
|
@@ -207,7 +212,7 @@ function verifyWalletSignature(data, identity) {
|
|
|
207
212
|
const sig = import_secp256k1.secp256k1.Signature.fromCompact(bytes.slice(0, 64)).addRecoveryBit(bytes[64] - 27);
|
|
208
213
|
const publicKey = sig.recoverPublicKey(personalMessageHash(data.payload, identity.kind)).toRawBytes(false);
|
|
209
214
|
const address = `0x${import_base2.base16.encode((0, import_sha3.keccak_256)(publicKey.slice(1)).slice(-20)).toLowerCase()}`;
|
|
210
|
-
valid = address ===
|
|
215
|
+
valid = identity.kind === "tron" ? address === tronRecoveredAddressHex(identity.accountId) : address === deriveConfidentialAccountId("evm", identity.accountId);
|
|
211
216
|
}
|
|
212
217
|
} catch {
|
|
213
218
|
valid = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/index.ts","../../src/errors.ts","../../src/identity.ts","../../src/utils.ts","../../src/signatures.ts","../../src/validation.ts"],"sourcesContent":["import { base58 } from \"@scure/base\";\nimport { assert, ConfidentialSwapError, isUserRejectedError, responseAssert, throwIfAborted, userRejectedError } from \"../errors\";\nimport { sameLinking } from \"../identity\";\nimport { decodeEd25519, decodeNep413Nonce, normalizeSecpSignature, toSignatureBytes } from \"../signatures\";\nimport { clone } from \"../utils\";\nimport { validateSigned } from \"../validation\";\nimport type { HttpConfidentialApi } from \"../api\";\nimport type { LinkingIdentity, SignedData, SigningAdapter, UnsignedData } from \"../types\";\n\nexport interface EvmMessageSigner { getAddress(): Promise<string>; signMessage(message: string): Promise<string> }\nexport function createEvmSigningAdapter(getSigner: () => Promise<EvmMessageSigner> | EvmMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"evm\", accountId: await (await getSigner()).getAddress() }),\n sign: async (data) => {\n assert(data.standard === \"erc191\", \"Expected ERC-191 payload.\");\n return { ...data, signature: normalizeSecpSignature(await (await getSigner()).signMessage(data.payload)) };\n },\n });\n}\nexport interface SolanaMessageSigner { publicKey: { toBase58(): string }; signMessage(message: Uint8Array): Promise<unknown> }\nexport function createSolanaSigningAdapter(getWallet: () => SolanaMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"solana\", accountId: getWallet().publicKey.toBase58(), publicKey: `ed25519:${getWallet().publicKey.toBase58()}` }),\n sign: async (data) => {\n assert(data.standard === \"raw_ed25519\", \"Expected raw Ed25519 payload.\");\n const wallet = getWallet();\n const signature = toSignatureBytes(await wallet.signMessage(new TextEncoder().encode(data.payload)));\n return { ...data, public_key: `ed25519:${wallet.publicKey.toBase58()}`, signature: `ed25519:${base58.encode(decodeEd25519(signature, 64))}` };\n },\n });\n}\nexport interface TronMessageSigner { getAddress(): Promise<string> | string; signMessageV2(message: string): Promise<string> }\nexport function createTronSigningAdapter(getWallet: () => TronMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"tron\", accountId: await getWallet().getAddress() }),\n sign: async (data) => {\n assert(data.standard === \"tip191\", \"Expected TIP-191 payload.\");\n return { ...data, signature: normalizeSecpSignature(await getWallet().signMessageV2(data.payload)) };\n },\n });\n}\nexport interface NearMessageSigner {\n getIdentity(): Promise<{ accountId: string; publicKey: string }>;\n signMessage(params: { message: string; recipient: string; nonce: Uint8Array; callbackUrl?: string }): Promise<{ accountId: string; publicKey: string; signature: string }>;\n /** Explicit user-approved NEAR transaction: add_public_key on the configured Intents contract. */\n registerPublicKey?(accountId: string, publicKey: string, contract: string, signal?: AbortSignal): Promise<void>;\n}\nexport function createNearSigningAdapter(wallet: NearMessageSigner, api: Pick<HttpConfidentialApi, \"viewIntents\" | \"intentsContract\">): SigningAdapter {\n const getIdentity = async (): Promise<LinkingIdentity> => {\n const identity = await wallet.getIdentity();\n return { kind: \"near\", accountId: identity.accountId, publicKey: `ed25519:${base58.encode(decodeEd25519(identity.publicKey, 32))}` };\n };\n return createCheckedAdapter({\n getIdentity,\n prepare: async (signal) => {\n const identity = await getIdentity();\n if (/^[0-9a-f]{64}$/.test(identity.accountId)) {\n const keyHex = Array.from(decodeEd25519(identity.publicKey!, 32), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n assert(keyHex === identity.accountId, \"Implicit NEAR account does not match its signing public key.\");\n } else {\n const registered = await api.viewIntents<boolean>(\"has_public_key\", { account_id: identity.accountId, public_key: identity.publicKey }, signal);\n responseAssert(typeof registered === \"boolean\", \"Invalid key registration response.\");\n if (!registered) {\n assert(wallet.registerPublicKey, \"NEAR key registration requires an explicit registerPublicKey callback.\");\n throwIfAborted(signal);\n await wallet.registerPublicKey(identity.accountId, identity.publicKey!, api.intentsContract, signal);\n responseAssert(await api.viewIntents<boolean>(\"has_public_key\", { account_id: identity.accountId, public_key: identity.publicKey }, signal) === true, \"NEAR signing key registration is not confirmed.\");\n }\n }\n },\n sign: async (data) => {\n assert(data.standard === \"nep413\", \"Expected NEP-413 payload.\");\n const result = await wallet.signMessage({ ...data.payload, nonce: decodeNep413Nonce(data.payload.nonce) });\n responseAssert(result.accountId === (await getIdentity()).accountId, \"NEAR wallet returned a different account.\");\n return { ...data, public_key: `ed25519:${base58.encode(decodeEd25519(result.publicKey, 32))}`, signature: `ed25519:${base58.encode(decodeEd25519(result.signature, 64))}` };\n },\n });\n}\nexport function createCheckedAdapter(adapter: SigningAdapter): SigningAdapter {\n return {\n getIdentity: () => adapter.getIdentity(),\n prepare: adapter.prepare?.bind(adapter),\n sign: async (data: UnsignedData, signal?: AbortSignal): Promise<SignedData> => {\n throwIfAborted(signal);\n const identity = await adapter.getIdentity();\n let signed: SignedData;\n try {\n signed = await adapter.sign(clone(data), signal);\n } catch (error) {\n if (isUserRejectedError(error)) throw userRejectedError();\n throw error;\n }\n throwIfAborted(signal);\n if (!sameLinking(identity, await adapter.getIdentity())) throw new ConfidentialSwapError(\"WALLET_CHANGED\", \"Wallet identity changed while signing.\");\n validateSigned(data, signed, identity);\n return signed;\n },\n };\n}\n","export type ErrorCode =\n | \"INVALID_INPUT\" | \"INVALID_RESPONSE\" | \"HTTP_ERROR\" | \"UNAUTHORIZED\"\n | \"WALLET_CHANGED\" | \"MISSING_WALLET\" | \"QUOTE_EXPIRED\" | \"BUSY\"\n | \"CONFIRMATION_REQUIRED\" | \"USER_REJECTED\" | \"TIMEOUT\" | \"ABORTED\" | \"TERMINAL_FAILURE\";\n\nexport class ConfidentialSwapError extends Error {\n readonly name = \"ConfidentialSwapError\";\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly status?: number,\n readonly executionId?: string,\n ) { super(message); }\n}\n\nexport function assert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_INPUT\", message);\n}\n\nexport function responseAssert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_RESPONSE\", message);\n}\n\nexport function isUserRejectedError(error: unknown): boolean {\n if (error instanceof ConfidentialSwapError) return error.code === \"USER_REJECTED\";\n if (!error || typeof error !== \"object\") return false;\n const value = error as { code?: unknown; message?: unknown; error?: unknown };\n const code = String(value.code ?? \"\").toUpperCase();\n const message = String(value.message ?? value.error ?? \"\");\n return (\n code === \"4001\" ||\n code === \"ACTION_REJECTED\" ||\n /user.*(reject|cancel|den(?:y|ied))|(?:reject|cancel|den(?:y|ied)).*user/i.test(message)\n );\n}\n\nexport function userRejectedError(executionId?: string): ConfidentialSwapError {\n return new ConfidentialSwapError(\n \"USER_REJECTED\",\n \"User rejected transaction.\",\n undefined,\n executionId,\n );\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted)\n throw new ConfidentialSwapError(\"ABORTED\", \"Trade cancelled.\");\n}\n","import { base16, base58, base58check } from \"@scure/base\";\nimport { sha256 } from \"@noble/hashes/sha256\";\nimport { assert } from \"./errors\";\nimport { text } from \"./utils\";\nimport type { FundingIdentity, LinkingIdentity, LinkingKind, SigningStandard } from \"./types\";\n\nexport const CONFIDENTIAL_LINKING_KINDS = [\"near\", \"evm\", \"solana\", \"tron\"] as const;\nexport const standards: Record<LinkingKind, SigningStandard> = { near: \"nep413\", evm: \"erc191\", solana: \"raw_ed25519\", tron: \"tip191\" };\nexport function deriveConfidentialAccountId(kind: LinkingKind, address: string): string {\n const account = text(address, \"Wallet account\");\n if (kind === \"near\") {\n assert(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(account) && account.length >= 2 && account.length <= 64, \"Invalid NEAR account.\");\n return account;\n }\n if (kind === \"evm\") { assert(/^0x[0-9a-f]{40}$/i.test(account), \"Invalid EVM address.\"); return account.toLowerCase(); }\n if (kind === \"solana\") {\n const bytes = base58.decode(account);\n assert(bytes.length === 32, \"Invalid Solana public key.\");\n return base16.encode(bytes).toLowerCase();\n }\n assert(kind === \"tron\", \"Unsupported linking wallet kind.\");\n const bytes = base58check(sha256).decode(account);\n assert(bytes.length === 21 && bytes[0] === 0x41, \"Invalid Tron address.\");\n return `0x${base16.encode(bytes.slice(1)).toLowerCase()}`;\n}\nexport function identityKey(identity: LinkingIdentity): string {\n // Session / linking identity is account-scoped — do not include publicKey.\n return `${identity.kind}:${deriveConfidentialAccountId(identity.kind, identity.accountId)}`;\n}\nexport function sameLinking(a: LinkingIdentity, b: LinkingIdentity): boolean { return identityKey(a) === identityKey(b); }\nexport function sameFunding(a: FundingIdentity, b: FundingIdentity): boolean {\n return a.kind === b.kind && a.chain === b.chain && (a.kind === \"evm\" ? a.accountId.toLowerCase() === b.accountId.toLowerCase() : a.accountId === b.accountId);\n}\nexport function resolveLinkingWallet(input: { sourceKind?: string; wallets: readonly LinkingIdentity[]; preferred?: LinkingIdentity }): LinkingIdentity | null {\n const wallets = input.wallets.filter((w) => CONFIDENTIAL_LINKING_KINDS.includes(w.kind) && w.accountId.trim());\n if (input.preferred) return wallets.find((w) => sameLinking(w, input.preferred!)) ?? null;\n return wallets.find((w) => w.kind === input.sourceKind) ?? CONFIDENTIAL_LINKING_KINDS.map((kind) => wallets.find((w) => w.kind === kind)).find(Boolean) ?? null;\n}\n","import { sha256 } from \"@noble/hashes/sha256\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\nimport { assert, throwIfAborted } from \"./errors\";\nimport type { Balance, BalanceSnapshotEntry, QuotePlan } from \"./types\";\n\nexport function raw(value: unknown, field = \"Amount\", allowZero = false): bigint {\n assert(typeof value === \"string\" && /^(0|[1-9]\\d*)$/.test(value), `${field} must be a canonical integer string.`);\n const result = BigInt(value);\n assert(allowZero || result > 0n, `${field} must be positive.`);\n return result;\n}\n/** Rescale a raw integer between token decimals (floor when shrinking). */\nexport function rescaleRawAmount(amount: string, fromDecimals: number, toDecimals: number): string {\n assert(Number.isInteger(fromDecimals) && fromDecimals >= 0 && fromDecimals <= 255, \"Invalid source decimals.\");\n assert(Number.isInteger(toDecimals) && toDecimals >= 0 && toDecimals <= 255, \"Invalid target decimals.\");\n const value = raw(amount);\n if (fromDecimals === toDecimals) return value.toString();\n if (toDecimals > fromDecimals) {\n return (value * 10n ** BigInt(toDecimals - fromDecimals)).toString();\n }\n const scaled = value / 10n ** BigInt(fromDecimals - toDecimals);\n assert(scaled > 0n, \"Amount too small after decimal conversion.\");\n return scaled.toString();\n}\nexport function text(value: unknown, field: string): string {\n assert(typeof value === \"string\" && value.trim().length > 0, `${field} is required.`);\n return value.trim();\n}\nexport function clone<T>(value: T): T { return structuredClone(value); }\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(\",\")}}`;\n }\n assert(value === null || [\"string\", \"boolean\", \"number\"].includes(typeof value), \"Plan data must be JSON-serializable.\");\n if (typeof value === \"number\") assert(Number.isFinite(value), \"Plan numbers must be finite.\");\n return JSON.stringify(value);\n}\nexport function planDigest(plan: Omit<QuotePlan, \"digest\"> | QuotePlan): string {\n const { digest: _digest, ...body } = plan as QuotePlan;\n return bytesToHex(sha256(new TextEncoder().encode(canonical(body))));\n}\nexport function randomFraction(): number {\n return crypto.getRandomValues(new Uint32Array(1))[0]! / 0xffffffff;\n}\nexport function splitConfidentialRawAmount(totalRaw: string, count: number, values?: readonly number[]): string[] {\n assert(Number.isInteger(count) && count >= 1 && count <= 10, \"A batch requires 1 to 10 recipients.\");\n const total = raw(totalRaw);\n assert(total >= BigInt(count), \"Amount too small for every recipient.\");\n const minimum = total * 80n / (100n * BigInt(count));\n const lowBound = minimum > 0n ? minimum : 1n;\n const denominator = 100n * BigInt(count);\n const maximum = (total * 120n + denominator - 1n) / denominator;\n const highBound = maximum > lowBound ? maximum : lowBound;\n const shares: bigint[] = [];\n let remaining = total;\n for (let i = 0; i < count - 1; i++) {\n const left = BigInt(count - i - 1);\n const low = remaining - highBound * left > lowBound ? remaining - highBound * left : lowBound;\n const high = remaining - lowBound * left < highBound ? remaining - lowBound * left : highBound;\n assert(low <= high, \"Amount cannot be split safely.\");\n const fraction = values?.[i] ?? randomFraction();\n assert(Number.isFinite(fraction) && fraction >= 0 && fraction <= 1, \"Random fractions must be within [0, 1].\");\n const share = low + (high - low) * BigInt(Math.floor(fraction * 1_000_000)) / 1_000_000n;\n shares.push(share);\n remaining -= share;\n }\n assert(remaining >= lowBound && remaining <= highBound, \"Final share outside safe bounds.\");\n return [...shares, remaining].map(String);\n}\n/**\n * Quote / route assetIds (especially `1cs_v1:…`) may differ from the tokenId\n * returned by `/account/balances`. Native assets often settle as\n * `nep141:<symbol>.omft.near` (ZEC/BTC/XRP/SOL/…), not the per-chain 1cs id.\n */\nfunction balanceLookupIds(assetId: string, symbol?: string): string[] {\n const ids = [assetId];\n const near1cs = /^1cs_v1:near:(nep141:.+)$/i.exec(assetId);\n if (near1cs) ids.push(near1cs[1]!);\n const nep = /^(nep141:.+)$/i.exec(assetId);\n if (nep) ids.push(`1cs_v1:near:${nep[1]}`);\n const sym = symbol?.trim().toLowerCase();\n if (sym && /^[a-z0-9]+$/i.test(sym)) {\n const omft = `nep141:${sym}.omft.near`;\n ids.push(omft, `1cs_v1:near:${omft}`);\n }\n return [...new Set(ids)];\n}\n\nexport function availableForAsset(\n balances: readonly Balance[],\n assetId: string,\n symbol?: string,\n): string {\n for (const id of balanceLookupIds(assetId, symbol)) {\n const exact = balances.find((b) => b.tokenId === id);\n const entry =\n exact ??\n balances.find((b) => b.tokenId.startsWith(\"imt:\") && b.tokenId.endsWith(`:${id}`));\n if (entry) return raw(entry.available ?? \"0\", \"Available balance\", true).toString();\n }\n return \"0\";\n}\n\n/** Strip `imt:<account>:` so the same asset compares across API shapes. */\nexport function balanceCanonicalKey(tokenId: string): string {\n const imt = /^imt:[^:]+:(.+)$/.exec(tokenId);\n return imt?.[1] ?? tokenId;\n}\n\nexport function snapshotBalances(balances: readonly Balance[]): BalanceSnapshotEntry[] {\n return balances.map((b) => ({\n tokenId: b.tokenId,\n available: raw(b.available ?? \"0\", \"Available balance\", true).toString(),\n }));\n}\n\nexport interface CreditedBalanceChange {\n tokenId: string;\n baseline: string;\n available: string;\n delta: string;\n}\n\n/**\n * Find which account token increased by at least `credited` vs the baseline snapshot.\n * Prefers an exact delta match, then the largest qualifying increase.\n */\nexport function findCreditedBalanceChange(\n baseline: readonly BalanceSnapshotEntry[] | undefined,\n current: readonly Balance[],\n credited: string,\n): CreditedBalanceChange | null {\n const creditedBn = raw(credited);\n if (creditedBn <= 0n) return null;\n\n const baseByKey = new Map<string, bigint>();\n for (const row of baseline ?? []) {\n const key = balanceCanonicalKey(row.tokenId);\n const amount = raw(row.available, \"Baseline\", true);\n const prev = baseByKey.get(key);\n if (prev === undefined || amount > prev) baseByKey.set(key, amount);\n }\n\n const increases: CreditedBalanceChange[] = [];\n const seen = new Set<string>();\n for (const row of current) {\n const key = balanceCanonicalKey(row.tokenId);\n if (seen.has(key)) continue;\n seen.add(key);\n const availableBn = raw(row.available ?? \"0\", \"Available\", true);\n const baselineBn = baseByKey.get(key) ?? 0n;\n const delta = availableBn - baselineBn;\n if (delta >= creditedBn) {\n increases.push({\n tokenId: row.tokenId.startsWith(\"imt:\") ? key : row.tokenId,\n baseline: baselineBn.toString(),\n available: availableBn.toString(),\n delta: delta.toString(),\n });\n }\n }\n if (increases.length === 0) return null;\n const exact = increases.find((row) => row.delta === credited);\n if (exact) return exact;\n return increases.reduce((best, row) =>\n BigInt(row.delta) > BigInt(best.delta) ? row : best,\n );\n}\nexport async function delay(ms: number, signal?: AbortSignal): Promise<void> {\n throwIfAborted(signal);\n await new Promise<void>((resolve, reject) => {\n const finish = () => { signal?.removeEventListener(\"abort\", abort); resolve(); };\n const timer = setTimeout(finish, ms);\n const abort = () => { clearTimeout(timer); signal?.removeEventListener(\"abort\", abort); try { throwIfAborted(signal); } catch (e) { reject(e); } };\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n","import { ed25519 } from \"@noble/curves/ed25519\";\nimport { secp256k1 } from \"@noble/curves/secp256k1\";\nimport { sha256 } from \"@noble/hashes/sha256\";\nimport { keccak_256 } from \"@noble/hashes/sha3\";\nimport { concatBytes } from \"@noble/hashes/utils\";\nimport { base16, base58, base64, base64nopad, base64urlnopad } from \"@scure/base\";\nimport { responseAssert } from \"./errors\";\nimport { deriveConfidentialAccountId } from \"./identity\";\nimport type { LinkingIdentity, SignedData, UnsignedData } from \"./types\";\n\nconst encode = (s: string) => new TextEncoder().encode(s);\nfunction u32(value: number): Uint8Array { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, value, true); return b; }\nfunction borshString(s: string): Uint8Array { const b = encode(s); return concatBytes(u32(b.length), b); }\nexport function decodeNep413Nonce(value: string): Uint8Array {\n responseAssert(typeof value === \"string\", \"Invalid NEP-413 nonce.\");\n const normalized = value.trim();\n let nonce: Uint8Array;\n try { nonce = base64.decode(normalized); }\n catch {\n const unpadded = normalized.replace(/=+$/, \"\");\n try { nonce = base64urlnopad.decode(unpadded); }\n catch {\n try { nonce = base64nopad.decode(unpadded); }\n catch { responseAssert(false, \"Invalid NEP-413 nonce encoding.\"); }\n }\n }\n responseAssert(nonce.length === 32, \"The NEP-413 nonce must be 32 bytes.\");\n return nonce;\n}\nexport function nep413Hash(payload: Extract<UnsignedData, { standard: \"nep413\" }>[\"payload\"]): Uint8Array {\n const nonce = decodeNep413Nonce(payload.nonce);\n return sha256(concatBytes(u32(2 ** 31 + 413), borshString(payload.message), nonce, borshString(payload.recipient), payload.callbackUrl === undefined ? new Uint8Array([0]) : concatBytes(new Uint8Array([1]), borshString(payload.callbackUrl))));\n}\nexport function personalMessageHash(message: string, kind: \"evm\" | \"tron\"): Uint8Array {\n const bytes = encode(message);\n return keccak_256(concatBytes(encode(`\\x19${kind === \"evm\" ? \"Ethereum\" : \"TRON\"} Signed Message:\\n${bytes.length}`), bytes));\n}\nexport function toSignatureBytes(value: unknown): Uint8Array {\n if (value instanceof Uint8Array) return value;\n if (value && typeof value === \"object\" && \"signature\" in value) {\n return toSignatureBytes((value as { signature: unknown }).signature);\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n }\n throw new Error(\"The wallet returned an invalid Ed25519 signature.\");\n}\nexport function decodeEd25519(value: string | Uint8Array, length: number): Uint8Array {\n if (value instanceof Uint8Array) { responseAssert(value.length === length, \"Invalid Ed25519 byte length.\"); return new Uint8Array(value); }\n const raw = value.replace(/^ed25519:/, \"\");\n for (const decoder of [() => base58.decode(raw), () => base64.decode(raw), () => base16.decode(raw.replace(/^0x/, \"\").toUpperCase())]) {\n try { const b = decoder(); if (b.length === length) return b; } catch { /* Wallets use base58, base64, or hex. */ }\n }\n responseAssert(false, \"Invalid Ed25519 encoding.\");\n}\nexport function normalizeSecpSignature(value: string): string {\n const hex = value.replace(/^0x/i, \"\");\n responseAssert(/^[0-9a-f]{130}$/i.test(hex), \"Expected a 65-byte signature.\");\n const bytes = base16.decode(hex.toUpperCase());\n const v = bytes[64]!;\n responseAssert([0, 1, 27, 28].includes(v), \"Invalid recovery id.\");\n bytes[64] = v < 27 ? v + 27 : v;\n return `0x${base16.encode(bytes).toLowerCase()}`;\n}\nexport function verifyWalletSignature(data: SignedData, identity: LinkingIdentity): void {\n let valid = false;\n try {\n if (identity.kind === \"near\" && data.standard === \"nep413\") {\n const key = decodeEd25519(identity.publicKey ?? \"\", 32);\n valid = ed25519.verify(decodeEd25519(data.signature, 64), nep413Hash(data.payload), key);\n if (/^[0-9a-f]{64}$/.test(identity.accountId)) valid = valid && base16.encode(key).toLowerCase() === identity.accountId;\n } else if (identity.kind === \"solana\" && data.standard === \"raw_ed25519\") {\n valid = ed25519.verify(decodeEd25519(data.signature, 64), encode(data.payload), base58.decode(identity.accountId));\n } else if ((identity.kind === \"evm\" || identity.kind === \"tron\") && typeof data.payload === \"string\") {\n const bytes = base16.decode(normalizeSecpSignature(data.signature).slice(2).toUpperCase());\n const sig = secp256k1.Signature.fromCompact(bytes.slice(0, 64)).addRecoveryBit(bytes[64]! - 27);\n const publicKey = sig.recoverPublicKey(personalMessageHash(data.payload, identity.kind)).toRawBytes(false);\n const address = `0x${base16.encode(keccak_256(publicKey.slice(1)).slice(-20)).toLowerCase()}`;\n valid = address === deriveConfidentialAccountId(identity.kind, identity.accountId);\n }\n } catch { valid = false; }\n responseAssert(valid, \"Signature does not verify against the frozen wallet identity.\");\n}\n","import { base58, base64 } from \"@scure/base\";\nimport { assert, responseAssert } from \"./errors\";\nimport { deriveConfidentialAccountId, standards } from \"./identity\";\nimport { normalizeChain } from \"./routing\";\nimport { raw, text } from \"./utils\";\nimport { decodeNep413Nonce, verifyWalletSignature } from \"./signatures\";\nimport type { LinkingIdentity, QuoteResponse, SignedData, Token, UnsignedData } from \"./types\";\n\nconst evmChains = new Set([\"eth\", \"arb\", \"avax\", \"op\", \"pol\", \"bsc\", \"base\", \"gnosis\", \"aurora\", \"bera\", \"berachain\", \"monad\", \"hypercore\", \"hyperliquid\", \"linea\", \"scroll\", \"zksync\", \"mantle\", \"celo\", \"sonic\"]);\nexport type AddressValidator = (address: string, token: Token) => boolean;\nexport function validateRecipients(addresses: readonly string[], token: Token, custom?: AddressValidator): string[] {\n assert(addresses.length >= 1 && addresses.length <= 10, \"A batch requires 1 to 10 recipients.\");\n const chain = normalizeChain(token.blockchain);\n const evm = evmChains.has(chain) || /^\\d+$/.test(token.swapChain ?? chain);\n const seen = new Set<string>();\n return addresses.map((value) => {\n const address = text(value, \"Recipient address\");\n let valid = false;\n try {\n if (custom) valid = custom(address, token);\n else if (evm) valid = /^0x[0-9a-f]{40}$/i.test(address);\n else if (chain === \"near\") { deriveConfidentialAccountId(\"near\", address); valid = true; }\n else if (chain === \"sol\") valid = base58.decode(address).length === 32;\n else if (chain === \"tron\") { deriveConfidentialAccountId(\"tron\", address); valid = true; }\n else if (chain === \"aptos\") valid = /^0x[0-9a-f]{1,64}$/i.test(address);\n else if (chain === \"sui\") valid = /^0x[0-9a-f]{64}$/i.test(address);\n else assert(false, \"Supply an address validator for this destination chain (including BTC/Zcash).\");\n } catch (error) { if (!custom && !evm && ![\"near\", \"sol\", \"tron\", \"aptos\", \"sui\"].includes(chain)) throw error; }\n assert(valid, \"Invalid recipient address for the destination chain.\");\n const key = evm || [\"aptos\", \"sui\"].includes(chain) ? address.toLowerCase() : address;\n assert(!seen.has(key), \"Duplicate recipient address.\");\n seen.add(key);\n return address;\n });\n}\nexport function validateToken(token: Token): void {\n text(token.assetId, \"Asset id\"); text(token.blockchain, \"Blockchain\"); text(token.symbol, \"Symbol\");\n assert(Number.isInteger(token.decimals) && token.decimals >= 0 && token.decimals <= 255, \"Invalid token decimals.\");\n}\nexport function validateQuote(q: QuoteResponse, requireDeposit: boolean): void {\n responseAssert(q && typeof q.quote === \"object\" && q.quote, \"Missing quote.\");\n raw(q.quote.amountIn, \"Quoted input\"); raw(q.quote.amountOut, \"Quoted output\");\n if (q.quote.minAmountOut !== undefined) assert(raw(q.quote.minAmountOut) <= raw(q.quote.amountOut), \"Minimum output exceeds quoted output.\");\n if (q.quote.minAmountIn !== undefined) raw(q.quote.minAmountIn);\n if (q.quoteRequest?.appFees !== undefined) {\n raw(q.quoteRequest.amount, \"Quote request amount\");\n responseAssert(Array.isArray(q.quoteRequest.appFees), \"Invalid quote app fees.\");\n for (const fee of q.quoteRequest.appFees) {\n responseAssert(fee && Number.isInteger(fee.fee) && fee.fee >= 0, \"Invalid quote app fee rate.\");\n }\n }\n if (requireDeposit) text(q.quote.depositAddress, \"Deposit address\");\n}\nexport function payloadMessage(data: UnsignedData): Record<string, unknown> {\n let message: unknown;\n try { message = JSON.parse(typeof data.payload === \"string\" ? data.payload : data.payload.message); }\n catch { responseAssert(false, \"Invalid intent JSON.\"); }\n responseAssert(message && typeof message === \"object\" && !Array.isArray(message), \"Invalid intent payload.\");\n return message as Record<string, unknown>;\n}\nexport function validatePayload(data: UnsignedData, identity: LinkingIdentity, signerId: string, contract: string, now: number, purpose: \"authorization\" | \"withdrawal\" = \"authorization\"): Record<string, unknown> {\n responseAssert(data.standard === standards[identity.kind], \"Signing standard does not match wallet.\");\n const msg = payloadMessage(data);\n responseAssert(msg.signer_id === signerId, \"Intent signer does not match the frozen identity.\");\n const deadline = Date.parse(String(msg.deadline ?? \"\"));\n responseAssert(Number.isFinite(deadline) && deadline > now, \"Intent deadline expired or invalid.\");\n responseAssert(Array.isArray(msg.intents), \"Intent list is missing.\");\n if (data.standard === \"nep413\") {\n text(data.payload.recipient, \"NEP-413 recipient\");\n // Generated withdrawals carry their own signed recipient; only login binds the configured verifier.\n if (purpose === \"authorization\") responseAssert(data.payload.recipient === contract, \"Invalid NEP-413 authorization verifier.\");\n decodeNep413Nonce(data.payload.nonce);\n } else if (purpose === \"authorization\") {\n responseAssert(msg.verifying_contract === contract && typeof msg.nonce === \"string\" && base64.decode(msg.nonce).length === 32, \"Invalid verifier or nonce.\");\n }\n return msg;\n}\nexport function validateSigned(unsigned: UnsignedData, signed: SignedData, identity: LinkingIdentity): void {\n responseAssert(signed.standard === unsigned.standard && JSON.stringify(signed.payload) === JSON.stringify(unsigned.payload), \"Wallet changed the payload.\");\n text(signed.signature, \"Signature\");\n if (identity.kind === \"near\" || identity.kind === \"solana\") {\n const expected = identity.publicKey ?? (identity.kind === \"solana\" ? `ed25519:${identity.accountId}` : \"\");\n responseAssert(expected && signed.public_key === expected, \"Signing public key changed.\");\n }\n verifyWalletSignature(signed, identity);\n}\n"],"mappings":"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","names":["import_base","import_sha256","bytes","import_sha256","import_utils","import_base","raw","import_base"]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/index.ts","../../src/errors.ts","../../src/identity.ts","../../src/utils.ts","../../src/signatures.ts","../../src/validation.ts"],"sourcesContent":["import { base58 } from \"@scure/base\";\nimport { assert, ConfidentialSwapError, isUserRejectedError, responseAssert, throwIfAborted, userRejectedError } from \"../errors\";\nimport { sameLinking } from \"../identity\";\nimport { decodeEd25519, decodeNep413Nonce, normalizeSecpSignature, toSignatureBytes } from \"../signatures\";\nimport { clone } from \"../utils\";\nimport { validateSigned } from \"../validation\";\nimport type { HttpConfidentialApi } from \"../api\";\nimport type { LinkingIdentity, SignedData, SigningAdapter, UnsignedData } from \"../types\";\n\nexport interface EvmMessageSigner { getAddress(): Promise<string>; signMessage(message: string): Promise<string> }\nexport function createEvmSigningAdapter(getSigner: () => Promise<EvmMessageSigner> | EvmMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"evm\", accountId: await (await getSigner()).getAddress() }),\n sign: async (data) => {\n assert(data.standard === \"erc191\", \"Expected ERC-191 payload.\");\n return { ...data, signature: normalizeSecpSignature(await (await getSigner()).signMessage(data.payload)) };\n },\n });\n}\nexport interface SolanaMessageSigner { publicKey: { toBase58(): string }; signMessage(message: Uint8Array): Promise<unknown> }\nexport function createSolanaSigningAdapter(getWallet: () => SolanaMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"solana\", accountId: getWallet().publicKey.toBase58(), publicKey: `ed25519:${getWallet().publicKey.toBase58()}` }),\n sign: async (data) => {\n assert(data.standard === \"raw_ed25519\", \"Expected raw Ed25519 payload.\");\n const wallet = getWallet();\n const signature = toSignatureBytes(await wallet.signMessage(new TextEncoder().encode(data.payload)));\n return { ...data, public_key: `ed25519:${wallet.publicKey.toBase58()}`, signature: `ed25519:${base58.encode(decodeEd25519(signature, 64))}` };\n },\n });\n}\nexport interface TronMessageSigner { getAddress(): Promise<string> | string; signMessageV2(message: string): Promise<string> }\nexport function createTronSigningAdapter(getWallet: () => TronMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"tron\", accountId: await getWallet().getAddress() }),\n sign: async (data) => {\n assert(data.standard === \"tip191\", \"Expected TIP-191 payload.\");\n return { ...data, signature: normalizeSecpSignature(await getWallet().signMessageV2(data.payload)) };\n },\n });\n}\nexport interface NearMessageSigner {\n getIdentity(): Promise<{ accountId: string; publicKey: string }>;\n signMessage(params: { message: string; recipient: string; nonce: Uint8Array; callbackUrl?: string }): Promise<{ accountId: string; publicKey: string; signature: string }>;\n /** Explicit user-approved NEAR transaction: add_public_key on the configured Intents contract. */\n registerPublicKey?(accountId: string, publicKey: string, contract: string, signal?: AbortSignal): Promise<void>;\n}\nexport function createNearSigningAdapter(wallet: NearMessageSigner, api: Pick<HttpConfidentialApi, \"viewIntents\" | \"intentsContract\">): SigningAdapter {\n const getIdentity = async (): Promise<LinkingIdentity> => {\n const identity = await wallet.getIdentity();\n return { kind: \"near\", accountId: identity.accountId, publicKey: `ed25519:${base58.encode(decodeEd25519(identity.publicKey, 32))}` };\n };\n return createCheckedAdapter({\n getIdentity,\n prepare: async (signal) => {\n const identity = await getIdentity();\n if (/^[0-9a-f]{64}$/.test(identity.accountId)) {\n const keyHex = Array.from(decodeEd25519(identity.publicKey!, 32), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n assert(keyHex === identity.accountId, \"Implicit NEAR account does not match its signing public key.\");\n } else {\n const registered = await api.viewIntents<boolean>(\"has_public_key\", { account_id: identity.accountId, public_key: identity.publicKey }, signal);\n responseAssert(typeof registered === \"boolean\", \"Invalid key registration response.\");\n if (!registered) {\n assert(wallet.registerPublicKey, \"NEAR key registration requires an explicit registerPublicKey callback.\");\n throwIfAborted(signal);\n await wallet.registerPublicKey(identity.accountId, identity.publicKey!, api.intentsContract, signal);\n responseAssert(await api.viewIntents<boolean>(\"has_public_key\", { account_id: identity.accountId, public_key: identity.publicKey }, signal) === true, \"NEAR signing key registration is not confirmed.\");\n }\n }\n },\n sign: async (data) => {\n assert(data.standard === \"nep413\", \"Expected NEP-413 payload.\");\n const result = await wallet.signMessage({ ...data.payload, nonce: decodeNep413Nonce(data.payload.nonce) });\n responseAssert(result.accountId === (await getIdentity()).accountId, \"NEAR wallet returned a different account.\");\n return { ...data, public_key: `ed25519:${base58.encode(decodeEd25519(result.publicKey, 32))}`, signature: `ed25519:${base58.encode(decodeEd25519(result.signature, 64))}` };\n },\n });\n}\nexport function createCheckedAdapter(adapter: SigningAdapter): SigningAdapter {\n return {\n getIdentity: () => adapter.getIdentity(),\n prepare: adapter.prepare?.bind(adapter),\n sign: async (data: UnsignedData, signal?: AbortSignal): Promise<SignedData> => {\n throwIfAborted(signal);\n const identity = await adapter.getIdentity();\n let signed: SignedData;\n try {\n signed = await adapter.sign(clone(data), signal);\n } catch (error) {\n if (isUserRejectedError(error)) throw userRejectedError();\n throw error;\n }\n throwIfAborted(signal);\n if (!sameLinking(identity, await adapter.getIdentity())) throw new ConfidentialSwapError(\"WALLET_CHANGED\", \"Wallet identity changed while signing.\");\n validateSigned(data, signed, identity);\n return signed;\n },\n };\n}\n","export type ErrorCode =\n | \"INVALID_INPUT\" | \"INVALID_RESPONSE\" | \"HTTP_ERROR\" | \"UNAUTHORIZED\"\n | \"WALLET_CHANGED\" | \"MISSING_WALLET\" | \"QUOTE_EXPIRED\" | \"BUSY\"\n | \"CONFIRMATION_REQUIRED\" | \"USER_REJECTED\" | \"TIMEOUT\" | \"ABORTED\" | \"TERMINAL_FAILURE\";\n\nexport class ConfidentialSwapError extends Error {\n readonly name = \"ConfidentialSwapError\";\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly status?: number,\n readonly executionId?: string,\n ) { super(message); }\n}\n\nexport function assert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_INPUT\", message);\n}\n\nexport function responseAssert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_RESPONSE\", message);\n}\n\nexport function isUserRejectedError(error: unknown): boolean {\n if (error instanceof ConfidentialSwapError) return error.code === \"USER_REJECTED\";\n if (!error || typeof error !== \"object\") return false;\n const value = error as { code?: unknown; message?: unknown; error?: unknown };\n const code = String(value.code ?? \"\").toUpperCase();\n const message = String(value.message ?? value.error ?? \"\");\n return (\n code === \"4001\" ||\n code === \"ACTION_REJECTED\" ||\n /user.*(reject|cancel|den(?:y|ied))|(?:reject|cancel|den(?:y|ied)).*user/i.test(message)\n );\n}\n\nexport function userRejectedError(executionId?: string): ConfidentialSwapError {\n return new ConfidentialSwapError(\n \"USER_REJECTED\",\n \"User rejected transaction.\",\n undefined,\n executionId,\n );\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted)\n throw new ConfidentialSwapError(\"ABORTED\", \"Trade cancelled.\");\n}\n","import { base16, base58, base58check } from \"@scure/base\";\nimport { sha256 } from \"@noble/hashes/sha256\";\nimport { assert } from \"./errors\";\nimport { text } from \"./utils\";\nimport type { FundingIdentity, LinkingIdentity, LinkingKind, SigningStandard } from \"./types\";\n\nexport const CONFIDENTIAL_LINKING_KINDS = [\"near\", \"evm\", \"solana\", \"tron\"] as const;\nexport const standards: Record<LinkingKind, SigningStandard> = { near: \"nep413\", evm: \"erc191\", solana: \"raw_ed25519\", tron: \"tip191\" };\nexport function deriveConfidentialAccountId(kind: LinkingKind, address: string): string {\n const account = text(address, \"Wallet account\");\n if (kind === \"near\") {\n assert(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(account) && account.length >= 2 && account.length <= 64, \"Invalid NEAR account.\");\n return account;\n }\n if (kind === \"evm\") { assert(/^0x[0-9a-f]{40}$/i.test(account), \"Invalid EVM address.\"); return account.toLowerCase(); }\n if (kind === \"solana\") {\n const bytes = base58.decode(account);\n assert(bytes.length === 32, \"Invalid Solana public key.\");\n // Keep native Base58 for signer_id / withdraw / orders (not hex).\n return base58.encode(bytes);\n }\n assert(kind === \"tron\", \"Unsupported linking wallet kind.\");\n const bytes = base58check(sha256).decode(account);\n assert(bytes.length === 21 && bytes[0] === 0x41, \"Invalid Tron address.\");\n // Keep the native Base58 form for signer_id / withdraw / orders. Intents and\n // indexers expect T..., not the eth-style 0x payload used only for tip191\n // signature recovery.\n return base58check(sha256).encode(bytes);\n}\n/** Eth-style 20-byte hex for Tron tip191 recovery checks (not the wire signer_id). */\nexport function tronRecoveredAddressHex(address: string): string {\n const account = deriveConfidentialAccountId(\"tron\", address);\n const bytes = base58check(sha256).decode(account);\n return `0x${base16.encode(bytes.slice(1)).toLowerCase()}`;\n}\nexport function identityKey(identity: LinkingIdentity): string {\n // Session / linking identity is account-scoped — do not include publicKey.\n return `${identity.kind}:${deriveConfidentialAccountId(identity.kind, identity.accountId)}`;\n}\nexport function sameLinking(a: LinkingIdentity, b: LinkingIdentity): boolean { return identityKey(a) === identityKey(b); }\nexport function sameFunding(a: FundingIdentity, b: FundingIdentity): boolean {\n return a.kind === b.kind && a.chain === b.chain && (a.kind === \"evm\" ? a.accountId.toLowerCase() === b.accountId.toLowerCase() : a.accountId === b.accountId);\n}\nexport function resolveLinkingWallet(input: { sourceKind?: string; wallets: readonly LinkingIdentity[]; preferred?: LinkingIdentity }): LinkingIdentity | null {\n const wallets = input.wallets.filter((w) => CONFIDENTIAL_LINKING_KINDS.includes(w.kind) && w.accountId.trim());\n if (input.preferred) return wallets.find((w) => sameLinking(w, input.preferred!)) ?? null;\n return wallets.find((w) => w.kind === input.sourceKind) ?? CONFIDENTIAL_LINKING_KINDS.map((kind) => wallets.find((w) => w.kind === kind)).find(Boolean) ?? null;\n}\n","import { sha256 } from \"@noble/hashes/sha256\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\nimport { assert, throwIfAborted } from \"./errors\";\nimport type { Balance, BalanceSnapshotEntry, QuotePlan } from \"./types\";\n\nexport function raw(value: unknown, field = \"Amount\", allowZero = false): bigint {\n assert(typeof value === \"string\" && /^(0|[1-9]\\d*)$/.test(value), `${field} must be a canonical integer string.`);\n const result = BigInt(value);\n assert(allowZero || result > 0n, `${field} must be positive.`);\n return result;\n}\n/** Rescale a raw integer between token decimals (floor when shrinking). */\nexport function rescaleRawAmount(amount: string, fromDecimals: number, toDecimals: number): string {\n assert(Number.isInteger(fromDecimals) && fromDecimals >= 0 && fromDecimals <= 255, \"Invalid source decimals.\");\n assert(Number.isInteger(toDecimals) && toDecimals >= 0 && toDecimals <= 255, \"Invalid target decimals.\");\n const value = raw(amount);\n if (fromDecimals === toDecimals) return value.toString();\n if (toDecimals > fromDecimals) {\n return (value * 10n ** BigInt(toDecimals - fromDecimals)).toString();\n }\n const scaled = value / 10n ** BigInt(fromDecimals - toDecimals);\n assert(scaled > 0n, \"Amount too small after decimal conversion.\");\n return scaled.toString();\n}\nexport function text(value: unknown, field: string): string {\n assert(typeof value === \"string\" && value.trim().length > 0, `${field} is required.`);\n return value.trim();\n}\nexport function clone<T>(value: T): T { return structuredClone(value); }\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(\",\")}}`;\n }\n assert(value === null || [\"string\", \"boolean\", \"number\"].includes(typeof value), \"Plan data must be JSON-serializable.\");\n if (typeof value === \"number\") assert(Number.isFinite(value), \"Plan numbers must be finite.\");\n return JSON.stringify(value);\n}\nexport function planDigest(plan: Omit<QuotePlan, \"digest\"> | QuotePlan): string {\n const { digest: _digest, ...body } = plan as QuotePlan;\n return bytesToHex(sha256(new TextEncoder().encode(canonical(body))));\n}\nexport function randomFraction(): number {\n return crypto.getRandomValues(new Uint32Array(1))[0]! / 0xffffffff;\n}\nexport function splitConfidentialRawAmount(totalRaw: string, count: number, values?: readonly number[]): string[] {\n assert(Number.isInteger(count) && count >= 1 && count <= 10, \"A batch requires 1 to 10 recipients.\");\n const total = raw(totalRaw);\n assert(total >= BigInt(count), \"Amount too small for every recipient.\");\n const minimum = total * 80n / (100n * BigInt(count));\n const lowBound = minimum > 0n ? minimum : 1n;\n const denominator = 100n * BigInt(count);\n const maximum = (total * 120n + denominator - 1n) / denominator;\n const highBound = maximum > lowBound ? maximum : lowBound;\n const shares: bigint[] = [];\n let remaining = total;\n for (let i = 0; i < count - 1; i++) {\n const left = BigInt(count - i - 1);\n const low = remaining - highBound * left > lowBound ? remaining - highBound * left : lowBound;\n const high = remaining - lowBound * left < highBound ? remaining - lowBound * left : highBound;\n assert(low <= high, \"Amount cannot be split safely.\");\n const fraction = values?.[i] ?? randomFraction();\n assert(Number.isFinite(fraction) && fraction >= 0 && fraction <= 1, \"Random fractions must be within [0, 1].\");\n const share = low + (high - low) * BigInt(Math.floor(fraction * 1_000_000)) / 1_000_000n;\n shares.push(share);\n remaining -= share;\n }\n assert(remaining >= lowBound && remaining <= highBound, \"Final share outside safe bounds.\");\n return [...shares, remaining].map(String);\n}\n/**\n * Quote / route assetIds (especially `1cs_v1:…`) may differ from the tokenId\n * returned by `/account/balances`. Native assets often settle as\n * `nep141:<symbol>.omft.near` (ZEC/BTC/XRP/SOL/…), not the per-chain 1cs id.\n */\nfunction balanceLookupIds(assetId: string, symbol?: string): string[] {\n const ids = [assetId];\n const near1cs = /^1cs_v1:near:(nep141:.+)$/i.exec(assetId);\n if (near1cs) ids.push(near1cs[1]!);\n const nep = /^(nep141:.+)$/i.exec(assetId);\n if (nep) ids.push(`1cs_v1:near:${nep[1]}`);\n const sym = symbol?.trim().toLowerCase();\n if (sym && /^[a-z0-9]+$/i.test(sym)) {\n const omft = `nep141:${sym}.omft.near`;\n ids.push(omft, `1cs_v1:near:${omft}`);\n }\n return [...new Set(ids)];\n}\n\nexport function availableForAsset(\n balances: readonly Balance[],\n assetId: string,\n symbol?: string,\n): string {\n for (const id of balanceLookupIds(assetId, symbol)) {\n const exact = balances.find((b) => b.tokenId === id);\n const entry =\n exact ??\n balances.find((b) => b.tokenId.startsWith(\"imt:\") && b.tokenId.endsWith(`:${id}`));\n if (entry) return raw(entry.available ?? \"0\", \"Available balance\", true).toString();\n }\n return \"0\";\n}\n\n/** Strip `imt:<account>:` so the same asset compares across API shapes. */\nexport function balanceCanonicalKey(tokenId: string): string {\n const imt = /^imt:[^:]+:(.+)$/.exec(tokenId);\n return imt?.[1] ?? tokenId;\n}\n\nexport function snapshotBalances(balances: readonly Balance[]): BalanceSnapshotEntry[] {\n return balances.map((b) => ({\n tokenId: b.tokenId,\n available: raw(b.available ?? \"0\", \"Available balance\", true).toString(),\n }));\n}\n\nexport interface CreditedBalanceChange {\n tokenId: string;\n baseline: string;\n available: string;\n delta: string;\n}\n\n/**\n * Find which account token increased by at least `credited` vs the baseline snapshot.\n * Prefers an exact delta match, then the largest qualifying increase.\n */\nexport function findCreditedBalanceChange(\n baseline: readonly BalanceSnapshotEntry[] | undefined,\n current: readonly Balance[],\n credited: string,\n): CreditedBalanceChange | null {\n const creditedBn = raw(credited);\n if (creditedBn <= 0n) return null;\n\n const baseByKey = new Map<string, bigint>();\n for (const row of baseline ?? []) {\n const key = balanceCanonicalKey(row.tokenId);\n const amount = raw(row.available, \"Baseline\", true);\n const prev = baseByKey.get(key);\n if (prev === undefined || amount > prev) baseByKey.set(key, amount);\n }\n\n const increases: CreditedBalanceChange[] = [];\n const seen = new Set<string>();\n for (const row of current) {\n const key = balanceCanonicalKey(row.tokenId);\n if (seen.has(key)) continue;\n seen.add(key);\n const availableBn = raw(row.available ?? \"0\", \"Available\", true);\n const baselineBn = baseByKey.get(key) ?? 0n;\n const delta = availableBn - baselineBn;\n if (delta >= creditedBn) {\n increases.push({\n tokenId: row.tokenId.startsWith(\"imt:\") ? key : row.tokenId,\n baseline: baselineBn.toString(),\n available: availableBn.toString(),\n delta: delta.toString(),\n });\n }\n }\n if (increases.length === 0) return null;\n const exact = increases.find((row) => row.delta === credited);\n if (exact) return exact;\n return increases.reduce((best, row) =>\n BigInt(row.delta) > BigInt(best.delta) ? row : best,\n );\n}\nexport async function delay(ms: number, signal?: AbortSignal): Promise<void> {\n throwIfAborted(signal);\n await new Promise<void>((resolve, reject) => {\n const finish = () => { signal?.removeEventListener(\"abort\", abort); resolve(); };\n const timer = setTimeout(finish, ms);\n const abort = () => { clearTimeout(timer); signal?.removeEventListener(\"abort\", abort); try { throwIfAborted(signal); } catch (e) { reject(e); } };\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n","import { ed25519 } from \"@noble/curves/ed25519\";\nimport { secp256k1 } from \"@noble/curves/secp256k1\";\nimport { sha256 } from \"@noble/hashes/sha256\";\nimport { keccak_256 } from \"@noble/hashes/sha3\";\nimport { concatBytes } from \"@noble/hashes/utils\";\nimport { base16, base58, base64, base64nopad, base64urlnopad } from \"@scure/base\";\nimport { responseAssert } from \"./errors\";\nimport { deriveConfidentialAccountId, tronRecoveredAddressHex } from \"./identity\";\nimport type { LinkingIdentity, SignedData, UnsignedData } from \"./types\";\n\nconst encode = (s: string) => new TextEncoder().encode(s);\nfunction u32(value: number): Uint8Array { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, value, true); return b; }\nfunction borshString(s: string): Uint8Array { const b = encode(s); return concatBytes(u32(b.length), b); }\nexport function decodeNep413Nonce(value: string): Uint8Array {\n responseAssert(typeof value === \"string\", \"Invalid NEP-413 nonce.\");\n const normalized = value.trim();\n let nonce: Uint8Array;\n try { nonce = base64.decode(normalized); }\n catch {\n const unpadded = normalized.replace(/=+$/, \"\");\n try { nonce = base64urlnopad.decode(unpadded); }\n catch {\n try { nonce = base64nopad.decode(unpadded); }\n catch { responseAssert(false, \"Invalid NEP-413 nonce encoding.\"); }\n }\n }\n responseAssert(nonce.length === 32, \"The NEP-413 nonce must be 32 bytes.\");\n return nonce;\n}\nexport function nep413Hash(payload: Extract<UnsignedData, { standard: \"nep413\" }>[\"payload\"]): Uint8Array {\n const nonce = decodeNep413Nonce(payload.nonce);\n return sha256(concatBytes(u32(2 ** 31 + 413), borshString(payload.message), nonce, borshString(payload.recipient), payload.callbackUrl === undefined ? new Uint8Array([0]) : concatBytes(new Uint8Array([1]), borshString(payload.callbackUrl))));\n}\nexport function personalMessageHash(message: string, kind: \"evm\" | \"tron\"): Uint8Array {\n const bytes = encode(message);\n return keccak_256(concatBytes(encode(`\\x19${kind === \"evm\" ? \"Ethereum\" : \"TRON\"} Signed Message:\\n${bytes.length}`), bytes));\n}\nexport function toSignatureBytes(value: unknown): Uint8Array {\n if (value instanceof Uint8Array) return value;\n if (value && typeof value === \"object\" && \"signature\" in value) {\n return toSignatureBytes((value as { signature: unknown }).signature);\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n }\n throw new Error(\"The wallet returned an invalid Ed25519 signature.\");\n}\nexport function decodeEd25519(value: string | Uint8Array, length: number): Uint8Array {\n if (value instanceof Uint8Array) { responseAssert(value.length === length, \"Invalid Ed25519 byte length.\"); return new Uint8Array(value); }\n const raw = value.replace(/^ed25519:/, \"\");\n for (const decoder of [() => base58.decode(raw), () => base64.decode(raw), () => base16.decode(raw.replace(/^0x/, \"\").toUpperCase())]) {\n try { const b = decoder(); if (b.length === length) return b; } catch { /* Wallets use base58, base64, or hex. */ }\n }\n responseAssert(false, \"Invalid Ed25519 encoding.\");\n}\nexport function normalizeSecpSignature(value: string): string {\n const hex = value.replace(/^0x/i, \"\");\n responseAssert(/^[0-9a-f]{130}$/i.test(hex), \"Expected a 65-byte signature.\");\n const bytes = base16.decode(hex.toUpperCase());\n const v = bytes[64]!;\n responseAssert([0, 1, 27, 28].includes(v), \"Invalid recovery id.\");\n bytes[64] = v < 27 ? v + 27 : v;\n return `0x${base16.encode(bytes).toLowerCase()}`;\n}\nexport function verifyWalletSignature(data: SignedData, identity: LinkingIdentity): void {\n let valid = false;\n try {\n if (identity.kind === \"near\" && data.standard === \"nep413\") {\n const key = decodeEd25519(identity.publicKey ?? \"\", 32);\n valid = ed25519.verify(decodeEd25519(data.signature, 64), nep413Hash(data.payload), key);\n if (/^[0-9a-f]{64}$/.test(identity.accountId)) valid = valid && base16.encode(key).toLowerCase() === identity.accountId;\n } else if (identity.kind === \"solana\" && data.standard === \"raw_ed25519\") {\n valid = ed25519.verify(decodeEd25519(data.signature, 64), encode(data.payload), base58.decode(identity.accountId));\n } else if ((identity.kind === \"evm\" || identity.kind === \"tron\") && typeof data.payload === \"string\") {\n const bytes = base16.decode(normalizeSecpSignature(data.signature).slice(2).toUpperCase());\n const sig = secp256k1.Signature.fromCompact(bytes.slice(0, 64)).addRecoveryBit(bytes[64]! - 27);\n const publicKey = sig.recoverPublicKey(personalMessageHash(data.payload, identity.kind)).toRawBytes(false);\n const address = `0x${base16.encode(keccak_256(publicKey.slice(1)).slice(-20)).toLowerCase()}`;\n valid =\n identity.kind === \"tron\"\n ? address === tronRecoveredAddressHex(identity.accountId)\n : address === deriveConfidentialAccountId(\"evm\", identity.accountId);\n }\n } catch { valid = false; }\n responseAssert(valid, \"Signature does not verify against the frozen wallet identity.\");\n}\n","import { base58, base64 } from \"@scure/base\";\nimport { assert, responseAssert } from \"./errors\";\nimport { deriveConfidentialAccountId, standards } from \"./identity\";\nimport { normalizeChain } from \"./routing\";\nimport { raw, text } from \"./utils\";\nimport { decodeNep413Nonce, verifyWalletSignature } from \"./signatures\";\nimport type { LinkingIdentity, QuoteResponse, SignedData, Token, UnsignedData } from \"./types\";\n\nconst evmChains = new Set([\"eth\", \"arb\", \"avax\", \"op\", \"pol\", \"bsc\", \"base\", \"gnosis\", \"aurora\", \"bera\", \"berachain\", \"monad\", \"hypercore\", \"hyperliquid\", \"linea\", \"scroll\", \"zksync\", \"mantle\", \"celo\", \"sonic\"]);\nexport type AddressValidator = (address: string, token: Token) => boolean;\nexport function validateRecipients(addresses: readonly string[], token: Token, custom?: AddressValidator): string[] {\n assert(addresses.length >= 1 && addresses.length <= 10, \"A batch requires 1 to 10 recipients.\");\n const chain = normalizeChain(token.blockchain);\n const evm = evmChains.has(chain) || /^\\d+$/.test(token.swapChain ?? chain);\n const seen = new Set<string>();\n return addresses.map((value) => {\n const address = text(value, \"Recipient address\");\n let valid = false;\n try {\n if (custom) valid = custom(address, token);\n else if (evm) valid = /^0x[0-9a-f]{40}$/i.test(address);\n else if (chain === \"near\") { deriveConfidentialAccountId(\"near\", address); valid = true; }\n else if (chain === \"sol\") valid = base58.decode(address).length === 32;\n else if (chain === \"tron\") { deriveConfidentialAccountId(\"tron\", address); valid = true; }\n else if (chain === \"aptos\") valid = /^0x[0-9a-f]{1,64}$/i.test(address);\n else if (chain === \"sui\") valid = /^0x[0-9a-f]{64}$/i.test(address);\n else assert(false, \"Supply an address validator for this destination chain (including BTC/Zcash).\");\n } catch (error) { if (!custom && !evm && ![\"near\", \"sol\", \"tron\", \"aptos\", \"sui\"].includes(chain)) throw error; }\n assert(valid, \"Invalid recipient address for the destination chain.\");\n const key = evm || [\"aptos\", \"sui\"].includes(chain) ? address.toLowerCase() : address;\n assert(!seen.has(key), \"Duplicate recipient address.\");\n seen.add(key);\n return address;\n });\n}\nexport function validateToken(token: Token): void {\n text(token.assetId, \"Asset id\"); text(token.blockchain, \"Blockchain\"); text(token.symbol, \"Symbol\");\n assert(Number.isInteger(token.decimals) && token.decimals >= 0 && token.decimals <= 255, \"Invalid token decimals.\");\n}\nexport function validateQuote(q: QuoteResponse, requireDeposit: boolean): void {\n responseAssert(q && typeof q.quote === \"object\" && q.quote, \"Missing quote.\");\n raw(q.quote.amountIn, \"Quoted input\"); raw(q.quote.amountOut, \"Quoted output\");\n if (q.quote.minAmountOut !== undefined) assert(raw(q.quote.minAmountOut) <= raw(q.quote.amountOut), \"Minimum output exceeds quoted output.\");\n if (q.quote.minAmountIn !== undefined) raw(q.quote.minAmountIn);\n if (q.quoteRequest?.appFees !== undefined) {\n raw(q.quoteRequest.amount, \"Quote request amount\");\n responseAssert(Array.isArray(q.quoteRequest.appFees), \"Invalid quote app fees.\");\n for (const fee of q.quoteRequest.appFees) {\n responseAssert(fee && Number.isInteger(fee.fee) && fee.fee >= 0, \"Invalid quote app fee rate.\");\n }\n }\n if (requireDeposit) text(q.quote.depositAddress, \"Deposit address\");\n}\nexport function payloadMessage(data: UnsignedData): Record<string, unknown> {\n let message: unknown;\n try { message = JSON.parse(typeof data.payload === \"string\" ? data.payload : data.payload.message); }\n catch { responseAssert(false, \"Invalid intent JSON.\"); }\n responseAssert(message && typeof message === \"object\" && !Array.isArray(message), \"Invalid intent payload.\");\n return message as Record<string, unknown>;\n}\nexport function validatePayload(data: UnsignedData, identity: LinkingIdentity, signerId: string, contract: string, now: number, purpose: \"authorization\" | \"withdrawal\" = \"authorization\"): Record<string, unknown> {\n responseAssert(data.standard === standards[identity.kind], \"Signing standard does not match wallet.\");\n const msg = payloadMessage(data);\n responseAssert(msg.signer_id === signerId, \"Intent signer does not match the frozen identity.\");\n const deadline = Date.parse(String(msg.deadline ?? \"\"));\n responseAssert(Number.isFinite(deadline) && deadline > now, \"Intent deadline expired or invalid.\");\n responseAssert(Array.isArray(msg.intents), \"Intent list is missing.\");\n if (data.standard === \"nep413\") {\n text(data.payload.recipient, \"NEP-413 recipient\");\n // Generated withdrawals carry their own signed recipient; only login binds the configured verifier.\n if (purpose === \"authorization\") responseAssert(data.payload.recipient === contract, \"Invalid NEP-413 authorization verifier.\");\n decodeNep413Nonce(data.payload.nonce);\n } else if (purpose === \"authorization\") {\n responseAssert(msg.verifying_contract === contract && typeof msg.nonce === \"string\" && base64.decode(msg.nonce).length === 32, \"Invalid verifier or nonce.\");\n }\n return msg;\n}\nexport function validateSigned(unsigned: UnsignedData, signed: SignedData, identity: LinkingIdentity): void {\n responseAssert(signed.standard === unsigned.standard && JSON.stringify(signed.payload) === JSON.stringify(unsigned.payload), \"Wallet changed the payload.\");\n text(signed.signature, \"Signature\");\n if (identity.kind === \"near\" || identity.kind === \"solana\") {\n const expected = identity.publicKey ?? (identity.kind === \"solana\" ? `ed25519:${identity.accountId}` : \"\");\n responseAssert(expected && signed.public_key === expected, \"Signing public key changed.\");\n }\n verifyWalletSignature(signed, identity);\n}\n"],"mappings":"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","names":["import_base","import_sha256","bytes","import_sha256","import_utils","import_base","raw","import_base"]}
|
package/dist/adapters/index.js
CHANGED
|
@@ -70,11 +70,16 @@ function deriveConfidentialAccountId(kind, address) {
|
|
|
70
70
|
if (kind === "solana") {
|
|
71
71
|
const bytes2 = base58.decode(account);
|
|
72
72
|
assert(bytes2.length === 32, "Invalid Solana public key.");
|
|
73
|
-
return
|
|
73
|
+
return base58.encode(bytes2);
|
|
74
74
|
}
|
|
75
75
|
assert(kind === "tron", "Unsupported linking wallet kind.");
|
|
76
76
|
const bytes = base58check(sha2562).decode(account);
|
|
77
77
|
assert(bytes.length === 21 && bytes[0] === 65, "Invalid Tron address.");
|
|
78
|
+
return base58check(sha2562).encode(bytes);
|
|
79
|
+
}
|
|
80
|
+
function tronRecoveredAddressHex(address) {
|
|
81
|
+
const account = deriveConfidentialAccountId("tron", address);
|
|
82
|
+
const bytes = base58check(sha2562).decode(account);
|
|
78
83
|
return `0x${base16.encode(bytes.slice(1)).toLowerCase()}`;
|
|
79
84
|
}
|
|
80
85
|
function identityKey(identity) {
|
|
@@ -179,7 +184,7 @@ function verifyWalletSignature(data, identity) {
|
|
|
179
184
|
const sig = secp256k1.Signature.fromCompact(bytes.slice(0, 64)).addRecoveryBit(bytes[64] - 27);
|
|
180
185
|
const publicKey = sig.recoverPublicKey(personalMessageHash(data.payload, identity.kind)).toRawBytes(false);
|
|
181
186
|
const address = `0x${base162.encode(keccak_256(publicKey.slice(1)).slice(-20)).toLowerCase()}`;
|
|
182
|
-
valid = address ===
|
|
187
|
+
valid = identity.kind === "tron" ? address === tronRecoveredAddressHex(identity.accountId) : address === deriveConfidentialAccountId("evm", identity.accountId);
|
|
183
188
|
}
|
|
184
189
|
} catch {
|
|
185
190
|
valid = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/index.ts","../../src/errors.ts","../../src/identity.ts","../../src/utils.ts","../../src/signatures.ts","../../src/validation.ts"],"sourcesContent":["import { base58 } from \"@scure/base\";\nimport { assert, ConfidentialSwapError, isUserRejectedError, responseAssert, throwIfAborted, userRejectedError } from \"../errors\";\nimport { sameLinking } from \"../identity\";\nimport { decodeEd25519, decodeNep413Nonce, normalizeSecpSignature, toSignatureBytes } from \"../signatures\";\nimport { clone } from \"../utils\";\nimport { validateSigned } from \"../validation\";\nimport type { HttpConfidentialApi } from \"../api\";\nimport type { LinkingIdentity, SignedData, SigningAdapter, UnsignedData } from \"../types\";\n\nexport interface EvmMessageSigner { getAddress(): Promise<string>; signMessage(message: string): Promise<string> }\nexport function createEvmSigningAdapter(getSigner: () => Promise<EvmMessageSigner> | EvmMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"evm\", accountId: await (await getSigner()).getAddress() }),\n sign: async (data) => {\n assert(data.standard === \"erc191\", \"Expected ERC-191 payload.\");\n return { ...data, signature: normalizeSecpSignature(await (await getSigner()).signMessage(data.payload)) };\n },\n });\n}\nexport interface SolanaMessageSigner { publicKey: { toBase58(): string }; signMessage(message: Uint8Array): Promise<unknown> }\nexport function createSolanaSigningAdapter(getWallet: () => SolanaMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"solana\", accountId: getWallet().publicKey.toBase58(), publicKey: `ed25519:${getWallet().publicKey.toBase58()}` }),\n sign: async (data) => {\n assert(data.standard === \"raw_ed25519\", \"Expected raw Ed25519 payload.\");\n const wallet = getWallet();\n const signature = toSignatureBytes(await wallet.signMessage(new TextEncoder().encode(data.payload)));\n return { ...data, public_key: `ed25519:${wallet.publicKey.toBase58()}`, signature: `ed25519:${base58.encode(decodeEd25519(signature, 64))}` };\n },\n });\n}\nexport interface TronMessageSigner { getAddress(): Promise<string> | string; signMessageV2(message: string): Promise<string> }\nexport function createTronSigningAdapter(getWallet: () => TronMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"tron\", accountId: await getWallet().getAddress() }),\n sign: async (data) => {\n assert(data.standard === \"tip191\", \"Expected TIP-191 payload.\");\n return { ...data, signature: normalizeSecpSignature(await getWallet().signMessageV2(data.payload)) };\n },\n });\n}\nexport interface NearMessageSigner {\n getIdentity(): Promise<{ accountId: string; publicKey: string }>;\n signMessage(params: { message: string; recipient: string; nonce: Uint8Array; callbackUrl?: string }): Promise<{ accountId: string; publicKey: string; signature: string }>;\n /** Explicit user-approved NEAR transaction: add_public_key on the configured Intents contract. */\n registerPublicKey?(accountId: string, publicKey: string, contract: string, signal?: AbortSignal): Promise<void>;\n}\nexport function createNearSigningAdapter(wallet: NearMessageSigner, api: Pick<HttpConfidentialApi, \"viewIntents\" | \"intentsContract\">): SigningAdapter {\n const getIdentity = async (): Promise<LinkingIdentity> => {\n const identity = await wallet.getIdentity();\n return { kind: \"near\", accountId: identity.accountId, publicKey: `ed25519:${base58.encode(decodeEd25519(identity.publicKey, 32))}` };\n };\n return createCheckedAdapter({\n getIdentity,\n prepare: async (signal) => {\n const identity = await getIdentity();\n if (/^[0-9a-f]{64}$/.test(identity.accountId)) {\n const keyHex = Array.from(decodeEd25519(identity.publicKey!, 32), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n assert(keyHex === identity.accountId, \"Implicit NEAR account does not match its signing public key.\");\n } else {\n const registered = await api.viewIntents<boolean>(\"has_public_key\", { account_id: identity.accountId, public_key: identity.publicKey }, signal);\n responseAssert(typeof registered === \"boolean\", \"Invalid key registration response.\");\n if (!registered) {\n assert(wallet.registerPublicKey, \"NEAR key registration requires an explicit registerPublicKey callback.\");\n throwIfAborted(signal);\n await wallet.registerPublicKey(identity.accountId, identity.publicKey!, api.intentsContract, signal);\n responseAssert(await api.viewIntents<boolean>(\"has_public_key\", { account_id: identity.accountId, public_key: identity.publicKey }, signal) === true, \"NEAR signing key registration is not confirmed.\");\n }\n }\n },\n sign: async (data) => {\n assert(data.standard === \"nep413\", \"Expected NEP-413 payload.\");\n const result = await wallet.signMessage({ ...data.payload, nonce: decodeNep413Nonce(data.payload.nonce) });\n responseAssert(result.accountId === (await getIdentity()).accountId, \"NEAR wallet returned a different account.\");\n return { ...data, public_key: `ed25519:${base58.encode(decodeEd25519(result.publicKey, 32))}`, signature: `ed25519:${base58.encode(decodeEd25519(result.signature, 64))}` };\n },\n });\n}\nexport function createCheckedAdapter(adapter: SigningAdapter): SigningAdapter {\n return {\n getIdentity: () => adapter.getIdentity(),\n prepare: adapter.prepare?.bind(adapter),\n sign: async (data: UnsignedData, signal?: AbortSignal): Promise<SignedData> => {\n throwIfAborted(signal);\n const identity = await adapter.getIdentity();\n let signed: SignedData;\n try {\n signed = await adapter.sign(clone(data), signal);\n } catch (error) {\n if (isUserRejectedError(error)) throw userRejectedError();\n throw error;\n }\n throwIfAborted(signal);\n if (!sameLinking(identity, await adapter.getIdentity())) throw new ConfidentialSwapError(\"WALLET_CHANGED\", \"Wallet identity changed while signing.\");\n validateSigned(data, signed, identity);\n return signed;\n },\n };\n}\n","export type ErrorCode =\n | \"INVALID_INPUT\" | \"INVALID_RESPONSE\" | \"HTTP_ERROR\" | \"UNAUTHORIZED\"\n | \"WALLET_CHANGED\" | \"MISSING_WALLET\" | \"QUOTE_EXPIRED\" | \"BUSY\"\n | \"CONFIRMATION_REQUIRED\" | \"USER_REJECTED\" | \"TIMEOUT\" | \"ABORTED\" | \"TERMINAL_FAILURE\";\n\nexport class ConfidentialSwapError extends Error {\n readonly name = \"ConfidentialSwapError\";\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly status?: number,\n readonly executionId?: string,\n ) { super(message); }\n}\n\nexport function assert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_INPUT\", message);\n}\n\nexport function responseAssert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_RESPONSE\", message);\n}\n\nexport function isUserRejectedError(error: unknown): boolean {\n if (error instanceof ConfidentialSwapError) return error.code === \"USER_REJECTED\";\n if (!error || typeof error !== \"object\") return false;\n const value = error as { code?: unknown; message?: unknown; error?: unknown };\n const code = String(value.code ?? \"\").toUpperCase();\n const message = String(value.message ?? value.error ?? \"\");\n return (\n code === \"4001\" ||\n code === \"ACTION_REJECTED\" ||\n /user.*(reject|cancel|den(?:y|ied))|(?:reject|cancel|den(?:y|ied)).*user/i.test(message)\n );\n}\n\nexport function userRejectedError(executionId?: string): ConfidentialSwapError {\n return new ConfidentialSwapError(\n \"USER_REJECTED\",\n \"User rejected transaction.\",\n undefined,\n executionId,\n );\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted)\n throw new ConfidentialSwapError(\"ABORTED\", \"Trade cancelled.\");\n}\n","import { base16, base58, base58check } from \"@scure/base\";\nimport { sha256 } from \"@noble/hashes/sha256\";\nimport { assert } from \"./errors\";\nimport { text } from \"./utils\";\nimport type { FundingIdentity, LinkingIdentity, LinkingKind, SigningStandard } from \"./types\";\n\nexport const CONFIDENTIAL_LINKING_KINDS = [\"near\", \"evm\", \"solana\", \"tron\"] as const;\nexport const standards: Record<LinkingKind, SigningStandard> = { near: \"nep413\", evm: \"erc191\", solana: \"raw_ed25519\", tron: \"tip191\" };\nexport function deriveConfidentialAccountId(kind: LinkingKind, address: string): string {\n const account = text(address, \"Wallet account\");\n if (kind === \"near\") {\n assert(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(account) && account.length >= 2 && account.length <= 64, \"Invalid NEAR account.\");\n return account;\n }\n if (kind === \"evm\") { assert(/^0x[0-9a-f]{40}$/i.test(account), \"Invalid EVM address.\"); return account.toLowerCase(); }\n if (kind === \"solana\") {\n const bytes = base58.decode(account);\n assert(bytes.length === 32, \"Invalid Solana public key.\");\n return base16.encode(bytes).toLowerCase();\n }\n assert(kind === \"tron\", \"Unsupported linking wallet kind.\");\n const bytes = base58check(sha256).decode(account);\n assert(bytes.length === 21 && bytes[0] === 0x41, \"Invalid Tron address.\");\n return `0x${base16.encode(bytes.slice(1)).toLowerCase()}`;\n}\nexport function identityKey(identity: LinkingIdentity): string {\n // Session / linking identity is account-scoped — do not include publicKey.\n return `${identity.kind}:${deriveConfidentialAccountId(identity.kind, identity.accountId)}`;\n}\nexport function sameLinking(a: LinkingIdentity, b: LinkingIdentity): boolean { return identityKey(a) === identityKey(b); }\nexport function sameFunding(a: FundingIdentity, b: FundingIdentity): boolean {\n return a.kind === b.kind && a.chain === b.chain && (a.kind === \"evm\" ? a.accountId.toLowerCase() === b.accountId.toLowerCase() : a.accountId === b.accountId);\n}\nexport function resolveLinkingWallet(input: { sourceKind?: string; wallets: readonly LinkingIdentity[]; preferred?: LinkingIdentity }): LinkingIdentity | null {\n const wallets = input.wallets.filter((w) => CONFIDENTIAL_LINKING_KINDS.includes(w.kind) && w.accountId.trim());\n if (input.preferred) return wallets.find((w) => sameLinking(w, input.preferred!)) ?? null;\n return wallets.find((w) => w.kind === input.sourceKind) ?? CONFIDENTIAL_LINKING_KINDS.map((kind) => wallets.find((w) => w.kind === kind)).find(Boolean) ?? null;\n}\n","import { sha256 } from \"@noble/hashes/sha256\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\nimport { assert, throwIfAborted } from \"./errors\";\nimport type { Balance, BalanceSnapshotEntry, QuotePlan } from \"./types\";\n\nexport function raw(value: unknown, field = \"Amount\", allowZero = false): bigint {\n assert(typeof value === \"string\" && /^(0|[1-9]\\d*)$/.test(value), `${field} must be a canonical integer string.`);\n const result = BigInt(value);\n assert(allowZero || result > 0n, `${field} must be positive.`);\n return result;\n}\n/** Rescale a raw integer between token decimals (floor when shrinking). */\nexport function rescaleRawAmount(amount: string, fromDecimals: number, toDecimals: number): string {\n assert(Number.isInteger(fromDecimals) && fromDecimals >= 0 && fromDecimals <= 255, \"Invalid source decimals.\");\n assert(Number.isInteger(toDecimals) && toDecimals >= 0 && toDecimals <= 255, \"Invalid target decimals.\");\n const value = raw(amount);\n if (fromDecimals === toDecimals) return value.toString();\n if (toDecimals > fromDecimals) {\n return (value * 10n ** BigInt(toDecimals - fromDecimals)).toString();\n }\n const scaled = value / 10n ** BigInt(fromDecimals - toDecimals);\n assert(scaled > 0n, \"Amount too small after decimal conversion.\");\n return scaled.toString();\n}\nexport function text(value: unknown, field: string): string {\n assert(typeof value === \"string\" && value.trim().length > 0, `${field} is required.`);\n return value.trim();\n}\nexport function clone<T>(value: T): T { return structuredClone(value); }\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(\",\")}}`;\n }\n assert(value === null || [\"string\", \"boolean\", \"number\"].includes(typeof value), \"Plan data must be JSON-serializable.\");\n if (typeof value === \"number\") assert(Number.isFinite(value), \"Plan numbers must be finite.\");\n return JSON.stringify(value);\n}\nexport function planDigest(plan: Omit<QuotePlan, \"digest\"> | QuotePlan): string {\n const { digest: _digest, ...body } = plan as QuotePlan;\n return bytesToHex(sha256(new TextEncoder().encode(canonical(body))));\n}\nexport function randomFraction(): number {\n return crypto.getRandomValues(new Uint32Array(1))[0]! / 0xffffffff;\n}\nexport function splitConfidentialRawAmount(totalRaw: string, count: number, values?: readonly number[]): string[] {\n assert(Number.isInteger(count) && count >= 1 && count <= 10, \"A batch requires 1 to 10 recipients.\");\n const total = raw(totalRaw);\n assert(total >= BigInt(count), \"Amount too small for every recipient.\");\n const minimum = total * 80n / (100n * BigInt(count));\n const lowBound = minimum > 0n ? minimum : 1n;\n const denominator = 100n * BigInt(count);\n const maximum = (total * 120n + denominator - 1n) / denominator;\n const highBound = maximum > lowBound ? maximum : lowBound;\n const shares: bigint[] = [];\n let remaining = total;\n for (let i = 0; i < count - 1; i++) {\n const left = BigInt(count - i - 1);\n const low = remaining - highBound * left > lowBound ? remaining - highBound * left : lowBound;\n const high = remaining - lowBound * left < highBound ? remaining - lowBound * left : highBound;\n assert(low <= high, \"Amount cannot be split safely.\");\n const fraction = values?.[i] ?? randomFraction();\n assert(Number.isFinite(fraction) && fraction >= 0 && fraction <= 1, \"Random fractions must be within [0, 1].\");\n const share = low + (high - low) * BigInt(Math.floor(fraction * 1_000_000)) / 1_000_000n;\n shares.push(share);\n remaining -= share;\n }\n assert(remaining >= lowBound && remaining <= highBound, \"Final share outside safe bounds.\");\n return [...shares, remaining].map(String);\n}\n/**\n * Quote / route assetIds (especially `1cs_v1:…`) may differ from the tokenId\n * returned by `/account/balances`. Native assets often settle as\n * `nep141:<symbol>.omft.near` (ZEC/BTC/XRP/SOL/…), not the per-chain 1cs id.\n */\nfunction balanceLookupIds(assetId: string, symbol?: string): string[] {\n const ids = [assetId];\n const near1cs = /^1cs_v1:near:(nep141:.+)$/i.exec(assetId);\n if (near1cs) ids.push(near1cs[1]!);\n const nep = /^(nep141:.+)$/i.exec(assetId);\n if (nep) ids.push(`1cs_v1:near:${nep[1]}`);\n const sym = symbol?.trim().toLowerCase();\n if (sym && /^[a-z0-9]+$/i.test(sym)) {\n const omft = `nep141:${sym}.omft.near`;\n ids.push(omft, `1cs_v1:near:${omft}`);\n }\n return [...new Set(ids)];\n}\n\nexport function availableForAsset(\n balances: readonly Balance[],\n assetId: string,\n symbol?: string,\n): string {\n for (const id of balanceLookupIds(assetId, symbol)) {\n const exact = balances.find((b) => b.tokenId === id);\n const entry =\n exact ??\n balances.find((b) => b.tokenId.startsWith(\"imt:\") && b.tokenId.endsWith(`:${id}`));\n if (entry) return raw(entry.available ?? \"0\", \"Available balance\", true).toString();\n }\n return \"0\";\n}\n\n/** Strip `imt:<account>:` so the same asset compares across API shapes. */\nexport function balanceCanonicalKey(tokenId: string): string {\n const imt = /^imt:[^:]+:(.+)$/.exec(tokenId);\n return imt?.[1] ?? tokenId;\n}\n\nexport function snapshotBalances(balances: readonly Balance[]): BalanceSnapshotEntry[] {\n return balances.map((b) => ({\n tokenId: b.tokenId,\n available: raw(b.available ?? \"0\", \"Available balance\", true).toString(),\n }));\n}\n\nexport interface CreditedBalanceChange {\n tokenId: string;\n baseline: string;\n available: string;\n delta: string;\n}\n\n/**\n * Find which account token increased by at least `credited` vs the baseline snapshot.\n * Prefers an exact delta match, then the largest qualifying increase.\n */\nexport function findCreditedBalanceChange(\n baseline: readonly BalanceSnapshotEntry[] | undefined,\n current: readonly Balance[],\n credited: string,\n): CreditedBalanceChange | null {\n const creditedBn = raw(credited);\n if (creditedBn <= 0n) return null;\n\n const baseByKey = new Map<string, bigint>();\n for (const row of baseline ?? []) {\n const key = balanceCanonicalKey(row.tokenId);\n const amount = raw(row.available, \"Baseline\", true);\n const prev = baseByKey.get(key);\n if (prev === undefined || amount > prev) baseByKey.set(key, amount);\n }\n\n const increases: CreditedBalanceChange[] = [];\n const seen = new Set<string>();\n for (const row of current) {\n const key = balanceCanonicalKey(row.tokenId);\n if (seen.has(key)) continue;\n seen.add(key);\n const availableBn = raw(row.available ?? \"0\", \"Available\", true);\n const baselineBn = baseByKey.get(key) ?? 0n;\n const delta = availableBn - baselineBn;\n if (delta >= creditedBn) {\n increases.push({\n tokenId: row.tokenId.startsWith(\"imt:\") ? key : row.tokenId,\n baseline: baselineBn.toString(),\n available: availableBn.toString(),\n delta: delta.toString(),\n });\n }\n }\n if (increases.length === 0) return null;\n const exact = increases.find((row) => row.delta === credited);\n if (exact) return exact;\n return increases.reduce((best, row) =>\n BigInt(row.delta) > BigInt(best.delta) ? row : best,\n );\n}\nexport async function delay(ms: number, signal?: AbortSignal): Promise<void> {\n throwIfAborted(signal);\n await new Promise<void>((resolve, reject) => {\n const finish = () => { signal?.removeEventListener(\"abort\", abort); resolve(); };\n const timer = setTimeout(finish, ms);\n const abort = () => { clearTimeout(timer); signal?.removeEventListener(\"abort\", abort); try { throwIfAborted(signal); } catch (e) { reject(e); } };\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n","import { ed25519 } from \"@noble/curves/ed25519\";\nimport { secp256k1 } from \"@noble/curves/secp256k1\";\nimport { sha256 } from \"@noble/hashes/sha256\";\nimport { keccak_256 } from \"@noble/hashes/sha3\";\nimport { concatBytes } from \"@noble/hashes/utils\";\nimport { base16, base58, base64, base64nopad, base64urlnopad } from \"@scure/base\";\nimport { responseAssert } from \"./errors\";\nimport { deriveConfidentialAccountId } from \"./identity\";\nimport type { LinkingIdentity, SignedData, UnsignedData } from \"./types\";\n\nconst encode = (s: string) => new TextEncoder().encode(s);\nfunction u32(value: number): Uint8Array { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, value, true); return b; }\nfunction borshString(s: string): Uint8Array { const b = encode(s); return concatBytes(u32(b.length), b); }\nexport function decodeNep413Nonce(value: string): Uint8Array {\n responseAssert(typeof value === \"string\", \"Invalid NEP-413 nonce.\");\n const normalized = value.trim();\n let nonce: Uint8Array;\n try { nonce = base64.decode(normalized); }\n catch {\n const unpadded = normalized.replace(/=+$/, \"\");\n try { nonce = base64urlnopad.decode(unpadded); }\n catch {\n try { nonce = base64nopad.decode(unpadded); }\n catch { responseAssert(false, \"Invalid NEP-413 nonce encoding.\"); }\n }\n }\n responseAssert(nonce.length === 32, \"The NEP-413 nonce must be 32 bytes.\");\n return nonce;\n}\nexport function nep413Hash(payload: Extract<UnsignedData, { standard: \"nep413\" }>[\"payload\"]): Uint8Array {\n const nonce = decodeNep413Nonce(payload.nonce);\n return sha256(concatBytes(u32(2 ** 31 + 413), borshString(payload.message), nonce, borshString(payload.recipient), payload.callbackUrl === undefined ? new Uint8Array([0]) : concatBytes(new Uint8Array([1]), borshString(payload.callbackUrl))));\n}\nexport function personalMessageHash(message: string, kind: \"evm\" | \"tron\"): Uint8Array {\n const bytes = encode(message);\n return keccak_256(concatBytes(encode(`\\x19${kind === \"evm\" ? \"Ethereum\" : \"TRON\"} Signed Message:\\n${bytes.length}`), bytes));\n}\nexport function toSignatureBytes(value: unknown): Uint8Array {\n if (value instanceof Uint8Array) return value;\n if (value && typeof value === \"object\" && \"signature\" in value) {\n return toSignatureBytes((value as { signature: unknown }).signature);\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n }\n throw new Error(\"The wallet returned an invalid Ed25519 signature.\");\n}\nexport function decodeEd25519(value: string | Uint8Array, length: number): Uint8Array {\n if (value instanceof Uint8Array) { responseAssert(value.length === length, \"Invalid Ed25519 byte length.\"); return new Uint8Array(value); }\n const raw = value.replace(/^ed25519:/, \"\");\n for (const decoder of [() => base58.decode(raw), () => base64.decode(raw), () => base16.decode(raw.replace(/^0x/, \"\").toUpperCase())]) {\n try { const b = decoder(); if (b.length === length) return b; } catch { /* Wallets use base58, base64, or hex. */ }\n }\n responseAssert(false, \"Invalid Ed25519 encoding.\");\n}\nexport function normalizeSecpSignature(value: string): string {\n const hex = value.replace(/^0x/i, \"\");\n responseAssert(/^[0-9a-f]{130}$/i.test(hex), \"Expected a 65-byte signature.\");\n const bytes = base16.decode(hex.toUpperCase());\n const v = bytes[64]!;\n responseAssert([0, 1, 27, 28].includes(v), \"Invalid recovery id.\");\n bytes[64] = v < 27 ? v + 27 : v;\n return `0x${base16.encode(bytes).toLowerCase()}`;\n}\nexport function verifyWalletSignature(data: SignedData, identity: LinkingIdentity): void {\n let valid = false;\n try {\n if (identity.kind === \"near\" && data.standard === \"nep413\") {\n const key = decodeEd25519(identity.publicKey ?? \"\", 32);\n valid = ed25519.verify(decodeEd25519(data.signature, 64), nep413Hash(data.payload), key);\n if (/^[0-9a-f]{64}$/.test(identity.accountId)) valid = valid && base16.encode(key).toLowerCase() === identity.accountId;\n } else if (identity.kind === \"solana\" && data.standard === \"raw_ed25519\") {\n valid = ed25519.verify(decodeEd25519(data.signature, 64), encode(data.payload), base58.decode(identity.accountId));\n } else if ((identity.kind === \"evm\" || identity.kind === \"tron\") && typeof data.payload === \"string\") {\n const bytes = base16.decode(normalizeSecpSignature(data.signature).slice(2).toUpperCase());\n const sig = secp256k1.Signature.fromCompact(bytes.slice(0, 64)).addRecoveryBit(bytes[64]! - 27);\n const publicKey = sig.recoverPublicKey(personalMessageHash(data.payload, identity.kind)).toRawBytes(false);\n const address = `0x${base16.encode(keccak_256(publicKey.slice(1)).slice(-20)).toLowerCase()}`;\n valid = address === deriveConfidentialAccountId(identity.kind, identity.accountId);\n }\n } catch { valid = false; }\n responseAssert(valid, \"Signature does not verify against the frozen wallet identity.\");\n}\n","import { base58, base64 } from \"@scure/base\";\nimport { assert, responseAssert } from \"./errors\";\nimport { deriveConfidentialAccountId, standards } from \"./identity\";\nimport { normalizeChain } from \"./routing\";\nimport { raw, text } from \"./utils\";\nimport { decodeNep413Nonce, verifyWalletSignature } from \"./signatures\";\nimport type { LinkingIdentity, QuoteResponse, SignedData, Token, UnsignedData } from \"./types\";\n\nconst evmChains = new Set([\"eth\", \"arb\", \"avax\", \"op\", \"pol\", \"bsc\", \"base\", \"gnosis\", \"aurora\", \"bera\", \"berachain\", \"monad\", \"hypercore\", \"hyperliquid\", \"linea\", \"scroll\", \"zksync\", \"mantle\", \"celo\", \"sonic\"]);\nexport type AddressValidator = (address: string, token: Token) => boolean;\nexport function validateRecipients(addresses: readonly string[], token: Token, custom?: AddressValidator): string[] {\n assert(addresses.length >= 1 && addresses.length <= 10, \"A batch requires 1 to 10 recipients.\");\n const chain = normalizeChain(token.blockchain);\n const evm = evmChains.has(chain) || /^\\d+$/.test(token.swapChain ?? chain);\n const seen = new Set<string>();\n return addresses.map((value) => {\n const address = text(value, \"Recipient address\");\n let valid = false;\n try {\n if (custom) valid = custom(address, token);\n else if (evm) valid = /^0x[0-9a-f]{40}$/i.test(address);\n else if (chain === \"near\") { deriveConfidentialAccountId(\"near\", address); valid = true; }\n else if (chain === \"sol\") valid = base58.decode(address).length === 32;\n else if (chain === \"tron\") { deriveConfidentialAccountId(\"tron\", address); valid = true; }\n else if (chain === \"aptos\") valid = /^0x[0-9a-f]{1,64}$/i.test(address);\n else if (chain === \"sui\") valid = /^0x[0-9a-f]{64}$/i.test(address);\n else assert(false, \"Supply an address validator for this destination chain (including BTC/Zcash).\");\n } catch (error) { if (!custom && !evm && ![\"near\", \"sol\", \"tron\", \"aptos\", \"sui\"].includes(chain)) throw error; }\n assert(valid, \"Invalid recipient address for the destination chain.\");\n const key = evm || [\"aptos\", \"sui\"].includes(chain) ? address.toLowerCase() : address;\n assert(!seen.has(key), \"Duplicate recipient address.\");\n seen.add(key);\n return address;\n });\n}\nexport function validateToken(token: Token): void {\n text(token.assetId, \"Asset id\"); text(token.blockchain, \"Blockchain\"); text(token.symbol, \"Symbol\");\n assert(Number.isInteger(token.decimals) && token.decimals >= 0 && token.decimals <= 255, \"Invalid token decimals.\");\n}\nexport function validateQuote(q: QuoteResponse, requireDeposit: boolean): void {\n responseAssert(q && typeof q.quote === \"object\" && q.quote, \"Missing quote.\");\n raw(q.quote.amountIn, \"Quoted input\"); raw(q.quote.amountOut, \"Quoted output\");\n if (q.quote.minAmountOut !== undefined) assert(raw(q.quote.minAmountOut) <= raw(q.quote.amountOut), \"Minimum output exceeds quoted output.\");\n if (q.quote.minAmountIn !== undefined) raw(q.quote.minAmountIn);\n if (q.quoteRequest?.appFees !== undefined) {\n raw(q.quoteRequest.amount, \"Quote request amount\");\n responseAssert(Array.isArray(q.quoteRequest.appFees), \"Invalid quote app fees.\");\n for (const fee of q.quoteRequest.appFees) {\n responseAssert(fee && Number.isInteger(fee.fee) && fee.fee >= 0, \"Invalid quote app fee rate.\");\n }\n }\n if (requireDeposit) text(q.quote.depositAddress, \"Deposit address\");\n}\nexport function payloadMessage(data: UnsignedData): Record<string, unknown> {\n let message: unknown;\n try { message = JSON.parse(typeof data.payload === \"string\" ? data.payload : data.payload.message); }\n catch { responseAssert(false, \"Invalid intent JSON.\"); }\n responseAssert(message && typeof message === \"object\" && !Array.isArray(message), \"Invalid intent payload.\");\n return message as Record<string, unknown>;\n}\nexport function validatePayload(data: UnsignedData, identity: LinkingIdentity, signerId: string, contract: string, now: number, purpose: \"authorization\" | \"withdrawal\" = \"authorization\"): Record<string, unknown> {\n responseAssert(data.standard === standards[identity.kind], \"Signing standard does not match wallet.\");\n const msg = payloadMessage(data);\n responseAssert(msg.signer_id === signerId, \"Intent signer does not match the frozen identity.\");\n const deadline = Date.parse(String(msg.deadline ?? \"\"));\n responseAssert(Number.isFinite(deadline) && deadline > now, \"Intent deadline expired or invalid.\");\n responseAssert(Array.isArray(msg.intents), \"Intent list is missing.\");\n if (data.standard === \"nep413\") {\n text(data.payload.recipient, \"NEP-413 recipient\");\n // Generated withdrawals carry their own signed recipient; only login binds the configured verifier.\n if (purpose === \"authorization\") responseAssert(data.payload.recipient === contract, \"Invalid NEP-413 authorization verifier.\");\n decodeNep413Nonce(data.payload.nonce);\n } else if (purpose === \"authorization\") {\n responseAssert(msg.verifying_contract === contract && typeof msg.nonce === \"string\" && base64.decode(msg.nonce).length === 32, \"Invalid verifier or nonce.\");\n }\n return msg;\n}\nexport function validateSigned(unsigned: UnsignedData, signed: SignedData, identity: LinkingIdentity): void {\n responseAssert(signed.standard === unsigned.standard && JSON.stringify(signed.payload) === JSON.stringify(unsigned.payload), \"Wallet changed the payload.\");\n text(signed.signature, \"Signature\");\n if (identity.kind === \"near\" || identity.kind === \"solana\") {\n const expected = identity.publicKey ?? (identity.kind === \"solana\" ? `ed25519:${identity.accountId}` : \"\");\n responseAssert(expected && signed.public_key === expected, \"Signing public key changed.\");\n }\n verifyWalletSignature(signed, identity);\n}\n"],"mappings":"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","names":["base58","sha256","bytes","sha256","sha256","base16","base58","sha256","raw","base58","base16","base58","base64","base58"]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/index.ts","../../src/errors.ts","../../src/identity.ts","../../src/utils.ts","../../src/signatures.ts","../../src/validation.ts"],"sourcesContent":["import { base58 } from \"@scure/base\";\nimport { assert, ConfidentialSwapError, isUserRejectedError, responseAssert, throwIfAborted, userRejectedError } from \"../errors\";\nimport { sameLinking } from \"../identity\";\nimport { decodeEd25519, decodeNep413Nonce, normalizeSecpSignature, toSignatureBytes } from \"../signatures\";\nimport { clone } from \"../utils\";\nimport { validateSigned } from \"../validation\";\nimport type { HttpConfidentialApi } from \"../api\";\nimport type { LinkingIdentity, SignedData, SigningAdapter, UnsignedData } from \"../types\";\n\nexport interface EvmMessageSigner { getAddress(): Promise<string>; signMessage(message: string): Promise<string> }\nexport function createEvmSigningAdapter(getSigner: () => Promise<EvmMessageSigner> | EvmMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"evm\", accountId: await (await getSigner()).getAddress() }),\n sign: async (data) => {\n assert(data.standard === \"erc191\", \"Expected ERC-191 payload.\");\n return { ...data, signature: normalizeSecpSignature(await (await getSigner()).signMessage(data.payload)) };\n },\n });\n}\nexport interface SolanaMessageSigner { publicKey: { toBase58(): string }; signMessage(message: Uint8Array): Promise<unknown> }\nexport function createSolanaSigningAdapter(getWallet: () => SolanaMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"solana\", accountId: getWallet().publicKey.toBase58(), publicKey: `ed25519:${getWallet().publicKey.toBase58()}` }),\n sign: async (data) => {\n assert(data.standard === \"raw_ed25519\", \"Expected raw Ed25519 payload.\");\n const wallet = getWallet();\n const signature = toSignatureBytes(await wallet.signMessage(new TextEncoder().encode(data.payload)));\n return { ...data, public_key: `ed25519:${wallet.publicKey.toBase58()}`, signature: `ed25519:${base58.encode(decodeEd25519(signature, 64))}` };\n },\n });\n}\nexport interface TronMessageSigner { getAddress(): Promise<string> | string; signMessageV2(message: string): Promise<string> }\nexport function createTronSigningAdapter(getWallet: () => TronMessageSigner): SigningAdapter {\n return createCheckedAdapter({\n getIdentity: async () => ({ kind: \"tron\", accountId: await getWallet().getAddress() }),\n sign: async (data) => {\n assert(data.standard === \"tip191\", \"Expected TIP-191 payload.\");\n return { ...data, signature: normalizeSecpSignature(await getWallet().signMessageV2(data.payload)) };\n },\n });\n}\nexport interface NearMessageSigner {\n getIdentity(): Promise<{ accountId: string; publicKey: string }>;\n signMessage(params: { message: string; recipient: string; nonce: Uint8Array; callbackUrl?: string }): Promise<{ accountId: string; publicKey: string; signature: string }>;\n /** Explicit user-approved NEAR transaction: add_public_key on the configured Intents contract. */\n registerPublicKey?(accountId: string, publicKey: string, contract: string, signal?: AbortSignal): Promise<void>;\n}\nexport function createNearSigningAdapter(wallet: NearMessageSigner, api: Pick<HttpConfidentialApi, \"viewIntents\" | \"intentsContract\">): SigningAdapter {\n const getIdentity = async (): Promise<LinkingIdentity> => {\n const identity = await wallet.getIdentity();\n return { kind: \"near\", accountId: identity.accountId, publicKey: `ed25519:${base58.encode(decodeEd25519(identity.publicKey, 32))}` };\n };\n return createCheckedAdapter({\n getIdentity,\n prepare: async (signal) => {\n const identity = await getIdentity();\n if (/^[0-9a-f]{64}$/.test(identity.accountId)) {\n const keyHex = Array.from(decodeEd25519(identity.publicKey!, 32), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n assert(keyHex === identity.accountId, \"Implicit NEAR account does not match its signing public key.\");\n } else {\n const registered = await api.viewIntents<boolean>(\"has_public_key\", { account_id: identity.accountId, public_key: identity.publicKey }, signal);\n responseAssert(typeof registered === \"boolean\", \"Invalid key registration response.\");\n if (!registered) {\n assert(wallet.registerPublicKey, \"NEAR key registration requires an explicit registerPublicKey callback.\");\n throwIfAborted(signal);\n await wallet.registerPublicKey(identity.accountId, identity.publicKey!, api.intentsContract, signal);\n responseAssert(await api.viewIntents<boolean>(\"has_public_key\", { account_id: identity.accountId, public_key: identity.publicKey }, signal) === true, \"NEAR signing key registration is not confirmed.\");\n }\n }\n },\n sign: async (data) => {\n assert(data.standard === \"nep413\", \"Expected NEP-413 payload.\");\n const result = await wallet.signMessage({ ...data.payload, nonce: decodeNep413Nonce(data.payload.nonce) });\n responseAssert(result.accountId === (await getIdentity()).accountId, \"NEAR wallet returned a different account.\");\n return { ...data, public_key: `ed25519:${base58.encode(decodeEd25519(result.publicKey, 32))}`, signature: `ed25519:${base58.encode(decodeEd25519(result.signature, 64))}` };\n },\n });\n}\nexport function createCheckedAdapter(adapter: SigningAdapter): SigningAdapter {\n return {\n getIdentity: () => adapter.getIdentity(),\n prepare: adapter.prepare?.bind(adapter),\n sign: async (data: UnsignedData, signal?: AbortSignal): Promise<SignedData> => {\n throwIfAborted(signal);\n const identity = await adapter.getIdentity();\n let signed: SignedData;\n try {\n signed = await adapter.sign(clone(data), signal);\n } catch (error) {\n if (isUserRejectedError(error)) throw userRejectedError();\n throw error;\n }\n throwIfAborted(signal);\n if (!sameLinking(identity, await adapter.getIdentity())) throw new ConfidentialSwapError(\"WALLET_CHANGED\", \"Wallet identity changed while signing.\");\n validateSigned(data, signed, identity);\n return signed;\n },\n };\n}\n","export type ErrorCode =\n | \"INVALID_INPUT\" | \"INVALID_RESPONSE\" | \"HTTP_ERROR\" | \"UNAUTHORIZED\"\n | \"WALLET_CHANGED\" | \"MISSING_WALLET\" | \"QUOTE_EXPIRED\" | \"BUSY\"\n | \"CONFIRMATION_REQUIRED\" | \"USER_REJECTED\" | \"TIMEOUT\" | \"ABORTED\" | \"TERMINAL_FAILURE\";\n\nexport class ConfidentialSwapError extends Error {\n readonly name = \"ConfidentialSwapError\";\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly status?: number,\n readonly executionId?: string,\n ) { super(message); }\n}\n\nexport function assert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_INPUT\", message);\n}\n\nexport function responseAssert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_RESPONSE\", message);\n}\n\nexport function isUserRejectedError(error: unknown): boolean {\n if (error instanceof ConfidentialSwapError) return error.code === \"USER_REJECTED\";\n if (!error || typeof error !== \"object\") return false;\n const value = error as { code?: unknown; message?: unknown; error?: unknown };\n const code = String(value.code ?? \"\").toUpperCase();\n const message = String(value.message ?? value.error ?? \"\");\n return (\n code === \"4001\" ||\n code === \"ACTION_REJECTED\" ||\n /user.*(reject|cancel|den(?:y|ied))|(?:reject|cancel|den(?:y|ied)).*user/i.test(message)\n );\n}\n\nexport function userRejectedError(executionId?: string): ConfidentialSwapError {\n return new ConfidentialSwapError(\n \"USER_REJECTED\",\n \"User rejected transaction.\",\n undefined,\n executionId,\n );\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted)\n throw new ConfidentialSwapError(\"ABORTED\", \"Trade cancelled.\");\n}\n","import { base16, base58, base58check } from \"@scure/base\";\nimport { sha256 } from \"@noble/hashes/sha256\";\nimport { assert } from \"./errors\";\nimport { text } from \"./utils\";\nimport type { FundingIdentity, LinkingIdentity, LinkingKind, SigningStandard } from \"./types\";\n\nexport const CONFIDENTIAL_LINKING_KINDS = [\"near\", \"evm\", \"solana\", \"tron\"] as const;\nexport const standards: Record<LinkingKind, SigningStandard> = { near: \"nep413\", evm: \"erc191\", solana: \"raw_ed25519\", tron: \"tip191\" };\nexport function deriveConfidentialAccountId(kind: LinkingKind, address: string): string {\n const account = text(address, \"Wallet account\");\n if (kind === \"near\") {\n assert(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(account) && account.length >= 2 && account.length <= 64, \"Invalid NEAR account.\");\n return account;\n }\n if (kind === \"evm\") { assert(/^0x[0-9a-f]{40}$/i.test(account), \"Invalid EVM address.\"); return account.toLowerCase(); }\n if (kind === \"solana\") {\n const bytes = base58.decode(account);\n assert(bytes.length === 32, \"Invalid Solana public key.\");\n // Keep native Base58 for signer_id / withdraw / orders (not hex).\n return base58.encode(bytes);\n }\n assert(kind === \"tron\", \"Unsupported linking wallet kind.\");\n const bytes = base58check(sha256).decode(account);\n assert(bytes.length === 21 && bytes[0] === 0x41, \"Invalid Tron address.\");\n // Keep the native Base58 form for signer_id / withdraw / orders. Intents and\n // indexers expect T..., not the eth-style 0x payload used only for tip191\n // signature recovery.\n return base58check(sha256).encode(bytes);\n}\n/** Eth-style 20-byte hex for Tron tip191 recovery checks (not the wire signer_id). */\nexport function tronRecoveredAddressHex(address: string): string {\n const account = deriveConfidentialAccountId(\"tron\", address);\n const bytes = base58check(sha256).decode(account);\n return `0x${base16.encode(bytes.slice(1)).toLowerCase()}`;\n}\nexport function identityKey(identity: LinkingIdentity): string {\n // Session / linking identity is account-scoped — do not include publicKey.\n return `${identity.kind}:${deriveConfidentialAccountId(identity.kind, identity.accountId)}`;\n}\nexport function sameLinking(a: LinkingIdentity, b: LinkingIdentity): boolean { return identityKey(a) === identityKey(b); }\nexport function sameFunding(a: FundingIdentity, b: FundingIdentity): boolean {\n return a.kind === b.kind && a.chain === b.chain && (a.kind === \"evm\" ? a.accountId.toLowerCase() === b.accountId.toLowerCase() : a.accountId === b.accountId);\n}\nexport function resolveLinkingWallet(input: { sourceKind?: string; wallets: readonly LinkingIdentity[]; preferred?: LinkingIdentity }): LinkingIdentity | null {\n const wallets = input.wallets.filter((w) => CONFIDENTIAL_LINKING_KINDS.includes(w.kind) && w.accountId.trim());\n if (input.preferred) return wallets.find((w) => sameLinking(w, input.preferred!)) ?? null;\n return wallets.find((w) => w.kind === input.sourceKind) ?? CONFIDENTIAL_LINKING_KINDS.map((kind) => wallets.find((w) => w.kind === kind)).find(Boolean) ?? null;\n}\n","import { sha256 } from \"@noble/hashes/sha256\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\nimport { assert, throwIfAborted } from \"./errors\";\nimport type { Balance, BalanceSnapshotEntry, QuotePlan } from \"./types\";\n\nexport function raw(value: unknown, field = \"Amount\", allowZero = false): bigint {\n assert(typeof value === \"string\" && /^(0|[1-9]\\d*)$/.test(value), `${field} must be a canonical integer string.`);\n const result = BigInt(value);\n assert(allowZero || result > 0n, `${field} must be positive.`);\n return result;\n}\n/** Rescale a raw integer between token decimals (floor when shrinking). */\nexport function rescaleRawAmount(amount: string, fromDecimals: number, toDecimals: number): string {\n assert(Number.isInteger(fromDecimals) && fromDecimals >= 0 && fromDecimals <= 255, \"Invalid source decimals.\");\n assert(Number.isInteger(toDecimals) && toDecimals >= 0 && toDecimals <= 255, \"Invalid target decimals.\");\n const value = raw(amount);\n if (fromDecimals === toDecimals) return value.toString();\n if (toDecimals > fromDecimals) {\n return (value * 10n ** BigInt(toDecimals - fromDecimals)).toString();\n }\n const scaled = value / 10n ** BigInt(fromDecimals - toDecimals);\n assert(scaled > 0n, \"Amount too small after decimal conversion.\");\n return scaled.toString();\n}\nexport function text(value: unknown, field: string): string {\n assert(typeof value === \"string\" && value.trim().length > 0, `${field} is required.`);\n return value.trim();\n}\nexport function clone<T>(value: T): T { return structuredClone(value); }\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(\",\")}}`;\n }\n assert(value === null || [\"string\", \"boolean\", \"number\"].includes(typeof value), \"Plan data must be JSON-serializable.\");\n if (typeof value === \"number\") assert(Number.isFinite(value), \"Plan numbers must be finite.\");\n return JSON.stringify(value);\n}\nexport function planDigest(plan: Omit<QuotePlan, \"digest\"> | QuotePlan): string {\n const { digest: _digest, ...body } = plan as QuotePlan;\n return bytesToHex(sha256(new TextEncoder().encode(canonical(body))));\n}\nexport function randomFraction(): number {\n return crypto.getRandomValues(new Uint32Array(1))[0]! / 0xffffffff;\n}\nexport function splitConfidentialRawAmount(totalRaw: string, count: number, values?: readonly number[]): string[] {\n assert(Number.isInteger(count) && count >= 1 && count <= 10, \"A batch requires 1 to 10 recipients.\");\n const total = raw(totalRaw);\n assert(total >= BigInt(count), \"Amount too small for every recipient.\");\n const minimum = total * 80n / (100n * BigInt(count));\n const lowBound = minimum > 0n ? minimum : 1n;\n const denominator = 100n * BigInt(count);\n const maximum = (total * 120n + denominator - 1n) / denominator;\n const highBound = maximum > lowBound ? maximum : lowBound;\n const shares: bigint[] = [];\n let remaining = total;\n for (let i = 0; i < count - 1; i++) {\n const left = BigInt(count - i - 1);\n const low = remaining - highBound * left > lowBound ? remaining - highBound * left : lowBound;\n const high = remaining - lowBound * left < highBound ? remaining - lowBound * left : highBound;\n assert(low <= high, \"Amount cannot be split safely.\");\n const fraction = values?.[i] ?? randomFraction();\n assert(Number.isFinite(fraction) && fraction >= 0 && fraction <= 1, \"Random fractions must be within [0, 1].\");\n const share = low + (high - low) * BigInt(Math.floor(fraction * 1_000_000)) / 1_000_000n;\n shares.push(share);\n remaining -= share;\n }\n assert(remaining >= lowBound && remaining <= highBound, \"Final share outside safe bounds.\");\n return [...shares, remaining].map(String);\n}\n/**\n * Quote / route assetIds (especially `1cs_v1:…`) may differ from the tokenId\n * returned by `/account/balances`. Native assets often settle as\n * `nep141:<symbol>.omft.near` (ZEC/BTC/XRP/SOL/…), not the per-chain 1cs id.\n */\nfunction balanceLookupIds(assetId: string, symbol?: string): string[] {\n const ids = [assetId];\n const near1cs = /^1cs_v1:near:(nep141:.+)$/i.exec(assetId);\n if (near1cs) ids.push(near1cs[1]!);\n const nep = /^(nep141:.+)$/i.exec(assetId);\n if (nep) ids.push(`1cs_v1:near:${nep[1]}`);\n const sym = symbol?.trim().toLowerCase();\n if (sym && /^[a-z0-9]+$/i.test(sym)) {\n const omft = `nep141:${sym}.omft.near`;\n ids.push(omft, `1cs_v1:near:${omft}`);\n }\n return [...new Set(ids)];\n}\n\nexport function availableForAsset(\n balances: readonly Balance[],\n assetId: string,\n symbol?: string,\n): string {\n for (const id of balanceLookupIds(assetId, symbol)) {\n const exact = balances.find((b) => b.tokenId === id);\n const entry =\n exact ??\n balances.find((b) => b.tokenId.startsWith(\"imt:\") && b.tokenId.endsWith(`:${id}`));\n if (entry) return raw(entry.available ?? \"0\", \"Available balance\", true).toString();\n }\n return \"0\";\n}\n\n/** Strip `imt:<account>:` so the same asset compares across API shapes. */\nexport function balanceCanonicalKey(tokenId: string): string {\n const imt = /^imt:[^:]+:(.+)$/.exec(tokenId);\n return imt?.[1] ?? tokenId;\n}\n\nexport function snapshotBalances(balances: readonly Balance[]): BalanceSnapshotEntry[] {\n return balances.map((b) => ({\n tokenId: b.tokenId,\n available: raw(b.available ?? \"0\", \"Available balance\", true).toString(),\n }));\n}\n\nexport interface CreditedBalanceChange {\n tokenId: string;\n baseline: string;\n available: string;\n delta: string;\n}\n\n/**\n * Find which account token increased by at least `credited` vs the baseline snapshot.\n * Prefers an exact delta match, then the largest qualifying increase.\n */\nexport function findCreditedBalanceChange(\n baseline: readonly BalanceSnapshotEntry[] | undefined,\n current: readonly Balance[],\n credited: string,\n): CreditedBalanceChange | null {\n const creditedBn = raw(credited);\n if (creditedBn <= 0n) return null;\n\n const baseByKey = new Map<string, bigint>();\n for (const row of baseline ?? []) {\n const key = balanceCanonicalKey(row.tokenId);\n const amount = raw(row.available, \"Baseline\", true);\n const prev = baseByKey.get(key);\n if (prev === undefined || amount > prev) baseByKey.set(key, amount);\n }\n\n const increases: CreditedBalanceChange[] = [];\n const seen = new Set<string>();\n for (const row of current) {\n const key = balanceCanonicalKey(row.tokenId);\n if (seen.has(key)) continue;\n seen.add(key);\n const availableBn = raw(row.available ?? \"0\", \"Available\", true);\n const baselineBn = baseByKey.get(key) ?? 0n;\n const delta = availableBn - baselineBn;\n if (delta >= creditedBn) {\n increases.push({\n tokenId: row.tokenId.startsWith(\"imt:\") ? key : row.tokenId,\n baseline: baselineBn.toString(),\n available: availableBn.toString(),\n delta: delta.toString(),\n });\n }\n }\n if (increases.length === 0) return null;\n const exact = increases.find((row) => row.delta === credited);\n if (exact) return exact;\n return increases.reduce((best, row) =>\n BigInt(row.delta) > BigInt(best.delta) ? row : best,\n );\n}\nexport async function delay(ms: number, signal?: AbortSignal): Promise<void> {\n throwIfAborted(signal);\n await new Promise<void>((resolve, reject) => {\n const finish = () => { signal?.removeEventListener(\"abort\", abort); resolve(); };\n const timer = setTimeout(finish, ms);\n const abort = () => { clearTimeout(timer); signal?.removeEventListener(\"abort\", abort); try { throwIfAborted(signal); } catch (e) { reject(e); } };\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n","import { ed25519 } from \"@noble/curves/ed25519\";\nimport { secp256k1 } from \"@noble/curves/secp256k1\";\nimport { sha256 } from \"@noble/hashes/sha256\";\nimport { keccak_256 } from \"@noble/hashes/sha3\";\nimport { concatBytes } from \"@noble/hashes/utils\";\nimport { base16, base58, base64, base64nopad, base64urlnopad } from \"@scure/base\";\nimport { responseAssert } from \"./errors\";\nimport { deriveConfidentialAccountId, tronRecoveredAddressHex } from \"./identity\";\nimport type { LinkingIdentity, SignedData, UnsignedData } from \"./types\";\n\nconst encode = (s: string) => new TextEncoder().encode(s);\nfunction u32(value: number): Uint8Array { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, value, true); return b; }\nfunction borshString(s: string): Uint8Array { const b = encode(s); return concatBytes(u32(b.length), b); }\nexport function decodeNep413Nonce(value: string): Uint8Array {\n responseAssert(typeof value === \"string\", \"Invalid NEP-413 nonce.\");\n const normalized = value.trim();\n let nonce: Uint8Array;\n try { nonce = base64.decode(normalized); }\n catch {\n const unpadded = normalized.replace(/=+$/, \"\");\n try { nonce = base64urlnopad.decode(unpadded); }\n catch {\n try { nonce = base64nopad.decode(unpadded); }\n catch { responseAssert(false, \"Invalid NEP-413 nonce encoding.\"); }\n }\n }\n responseAssert(nonce.length === 32, \"The NEP-413 nonce must be 32 bytes.\");\n return nonce;\n}\nexport function nep413Hash(payload: Extract<UnsignedData, { standard: \"nep413\" }>[\"payload\"]): Uint8Array {\n const nonce = decodeNep413Nonce(payload.nonce);\n return sha256(concatBytes(u32(2 ** 31 + 413), borshString(payload.message), nonce, borshString(payload.recipient), payload.callbackUrl === undefined ? new Uint8Array([0]) : concatBytes(new Uint8Array([1]), borshString(payload.callbackUrl))));\n}\nexport function personalMessageHash(message: string, kind: \"evm\" | \"tron\"): Uint8Array {\n const bytes = encode(message);\n return keccak_256(concatBytes(encode(`\\x19${kind === \"evm\" ? \"Ethereum\" : \"TRON\"} Signed Message:\\n${bytes.length}`), bytes));\n}\nexport function toSignatureBytes(value: unknown): Uint8Array {\n if (value instanceof Uint8Array) return value;\n if (value && typeof value === \"object\" && \"signature\" in value) {\n return toSignatureBytes((value as { signature: unknown }).signature);\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n }\n throw new Error(\"The wallet returned an invalid Ed25519 signature.\");\n}\nexport function decodeEd25519(value: string | Uint8Array, length: number): Uint8Array {\n if (value instanceof Uint8Array) { responseAssert(value.length === length, \"Invalid Ed25519 byte length.\"); return new Uint8Array(value); }\n const raw = value.replace(/^ed25519:/, \"\");\n for (const decoder of [() => base58.decode(raw), () => base64.decode(raw), () => base16.decode(raw.replace(/^0x/, \"\").toUpperCase())]) {\n try { const b = decoder(); if (b.length === length) return b; } catch { /* Wallets use base58, base64, or hex. */ }\n }\n responseAssert(false, \"Invalid Ed25519 encoding.\");\n}\nexport function normalizeSecpSignature(value: string): string {\n const hex = value.replace(/^0x/i, \"\");\n responseAssert(/^[0-9a-f]{130}$/i.test(hex), \"Expected a 65-byte signature.\");\n const bytes = base16.decode(hex.toUpperCase());\n const v = bytes[64]!;\n responseAssert([0, 1, 27, 28].includes(v), \"Invalid recovery id.\");\n bytes[64] = v < 27 ? v + 27 : v;\n return `0x${base16.encode(bytes).toLowerCase()}`;\n}\nexport function verifyWalletSignature(data: SignedData, identity: LinkingIdentity): void {\n let valid = false;\n try {\n if (identity.kind === \"near\" && data.standard === \"nep413\") {\n const key = decodeEd25519(identity.publicKey ?? \"\", 32);\n valid = ed25519.verify(decodeEd25519(data.signature, 64), nep413Hash(data.payload), key);\n if (/^[0-9a-f]{64}$/.test(identity.accountId)) valid = valid && base16.encode(key).toLowerCase() === identity.accountId;\n } else if (identity.kind === \"solana\" && data.standard === \"raw_ed25519\") {\n valid = ed25519.verify(decodeEd25519(data.signature, 64), encode(data.payload), base58.decode(identity.accountId));\n } else if ((identity.kind === \"evm\" || identity.kind === \"tron\") && typeof data.payload === \"string\") {\n const bytes = base16.decode(normalizeSecpSignature(data.signature).slice(2).toUpperCase());\n const sig = secp256k1.Signature.fromCompact(bytes.slice(0, 64)).addRecoveryBit(bytes[64]! - 27);\n const publicKey = sig.recoverPublicKey(personalMessageHash(data.payload, identity.kind)).toRawBytes(false);\n const address = `0x${base16.encode(keccak_256(publicKey.slice(1)).slice(-20)).toLowerCase()}`;\n valid =\n identity.kind === \"tron\"\n ? address === tronRecoveredAddressHex(identity.accountId)\n : address === deriveConfidentialAccountId(\"evm\", identity.accountId);\n }\n } catch { valid = false; }\n responseAssert(valid, \"Signature does not verify against the frozen wallet identity.\");\n}\n","import { base58, base64 } from \"@scure/base\";\nimport { assert, responseAssert } from \"./errors\";\nimport { deriveConfidentialAccountId, standards } from \"./identity\";\nimport { normalizeChain } from \"./routing\";\nimport { raw, text } from \"./utils\";\nimport { decodeNep413Nonce, verifyWalletSignature } from \"./signatures\";\nimport type { LinkingIdentity, QuoteResponse, SignedData, Token, UnsignedData } from \"./types\";\n\nconst evmChains = new Set([\"eth\", \"arb\", \"avax\", \"op\", \"pol\", \"bsc\", \"base\", \"gnosis\", \"aurora\", \"bera\", \"berachain\", \"monad\", \"hypercore\", \"hyperliquid\", \"linea\", \"scroll\", \"zksync\", \"mantle\", \"celo\", \"sonic\"]);\nexport type AddressValidator = (address: string, token: Token) => boolean;\nexport function validateRecipients(addresses: readonly string[], token: Token, custom?: AddressValidator): string[] {\n assert(addresses.length >= 1 && addresses.length <= 10, \"A batch requires 1 to 10 recipients.\");\n const chain = normalizeChain(token.blockchain);\n const evm = evmChains.has(chain) || /^\\d+$/.test(token.swapChain ?? chain);\n const seen = new Set<string>();\n return addresses.map((value) => {\n const address = text(value, \"Recipient address\");\n let valid = false;\n try {\n if (custom) valid = custom(address, token);\n else if (evm) valid = /^0x[0-9a-f]{40}$/i.test(address);\n else if (chain === \"near\") { deriveConfidentialAccountId(\"near\", address); valid = true; }\n else if (chain === \"sol\") valid = base58.decode(address).length === 32;\n else if (chain === \"tron\") { deriveConfidentialAccountId(\"tron\", address); valid = true; }\n else if (chain === \"aptos\") valid = /^0x[0-9a-f]{1,64}$/i.test(address);\n else if (chain === \"sui\") valid = /^0x[0-9a-f]{64}$/i.test(address);\n else assert(false, \"Supply an address validator for this destination chain (including BTC/Zcash).\");\n } catch (error) { if (!custom && !evm && ![\"near\", \"sol\", \"tron\", \"aptos\", \"sui\"].includes(chain)) throw error; }\n assert(valid, \"Invalid recipient address for the destination chain.\");\n const key = evm || [\"aptos\", \"sui\"].includes(chain) ? address.toLowerCase() : address;\n assert(!seen.has(key), \"Duplicate recipient address.\");\n seen.add(key);\n return address;\n });\n}\nexport function validateToken(token: Token): void {\n text(token.assetId, \"Asset id\"); text(token.blockchain, \"Blockchain\"); text(token.symbol, \"Symbol\");\n assert(Number.isInteger(token.decimals) && token.decimals >= 0 && token.decimals <= 255, \"Invalid token decimals.\");\n}\nexport function validateQuote(q: QuoteResponse, requireDeposit: boolean): void {\n responseAssert(q && typeof q.quote === \"object\" && q.quote, \"Missing quote.\");\n raw(q.quote.amountIn, \"Quoted input\"); raw(q.quote.amountOut, \"Quoted output\");\n if (q.quote.minAmountOut !== undefined) assert(raw(q.quote.minAmountOut) <= raw(q.quote.amountOut), \"Minimum output exceeds quoted output.\");\n if (q.quote.minAmountIn !== undefined) raw(q.quote.minAmountIn);\n if (q.quoteRequest?.appFees !== undefined) {\n raw(q.quoteRequest.amount, \"Quote request amount\");\n responseAssert(Array.isArray(q.quoteRequest.appFees), \"Invalid quote app fees.\");\n for (const fee of q.quoteRequest.appFees) {\n responseAssert(fee && Number.isInteger(fee.fee) && fee.fee >= 0, \"Invalid quote app fee rate.\");\n }\n }\n if (requireDeposit) text(q.quote.depositAddress, \"Deposit address\");\n}\nexport function payloadMessage(data: UnsignedData): Record<string, unknown> {\n let message: unknown;\n try { message = JSON.parse(typeof data.payload === \"string\" ? data.payload : data.payload.message); }\n catch { responseAssert(false, \"Invalid intent JSON.\"); }\n responseAssert(message && typeof message === \"object\" && !Array.isArray(message), \"Invalid intent payload.\");\n return message as Record<string, unknown>;\n}\nexport function validatePayload(data: UnsignedData, identity: LinkingIdentity, signerId: string, contract: string, now: number, purpose: \"authorization\" | \"withdrawal\" = \"authorization\"): Record<string, unknown> {\n responseAssert(data.standard === standards[identity.kind], \"Signing standard does not match wallet.\");\n const msg = payloadMessage(data);\n responseAssert(msg.signer_id === signerId, \"Intent signer does not match the frozen identity.\");\n const deadline = Date.parse(String(msg.deadline ?? \"\"));\n responseAssert(Number.isFinite(deadline) && deadline > now, \"Intent deadline expired or invalid.\");\n responseAssert(Array.isArray(msg.intents), \"Intent list is missing.\");\n if (data.standard === \"nep413\") {\n text(data.payload.recipient, \"NEP-413 recipient\");\n // Generated withdrawals carry their own signed recipient; only login binds the configured verifier.\n if (purpose === \"authorization\") responseAssert(data.payload.recipient === contract, \"Invalid NEP-413 authorization verifier.\");\n decodeNep413Nonce(data.payload.nonce);\n } else if (purpose === \"authorization\") {\n responseAssert(msg.verifying_contract === contract && typeof msg.nonce === \"string\" && base64.decode(msg.nonce).length === 32, \"Invalid verifier or nonce.\");\n }\n return msg;\n}\nexport function validateSigned(unsigned: UnsignedData, signed: SignedData, identity: LinkingIdentity): void {\n responseAssert(signed.standard === unsigned.standard && JSON.stringify(signed.payload) === JSON.stringify(unsigned.payload), \"Wallet changed the payload.\");\n text(signed.signature, \"Signature\");\n if (identity.kind === \"near\" || identity.kind === \"solana\") {\n const expected = identity.publicKey ?? (identity.kind === \"solana\" ? `ed25519:${identity.accountId}` : \"\");\n responseAssert(expected && signed.public_key === expected, \"Signing public key changed.\");\n }\n verifyWalletSignature(signed, identity);\n}\n"],"mappings":"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","names":["base58","sha256","bytes","sha256","sha256","base16","base58","sha256","raw","base58","base16","base58","base64","base58"]}
|
package/dist/index.cjs
CHANGED
|
@@ -54,6 +54,7 @@ __export(index_exports, {
|
|
|
54
54
|
standards: () => standards,
|
|
55
55
|
successes: () => successes,
|
|
56
56
|
throwIfAborted: () => throwIfAborted,
|
|
57
|
+
tronRecoveredAddressHex: () => tronRecoveredAddressHex,
|
|
57
58
|
userRejectedError: () => userRejectedError,
|
|
58
59
|
validateRecipients: () => validateRecipients,
|
|
59
60
|
withdrawalProgress: () => withdrawalProgress
|
|
@@ -264,11 +265,16 @@ function deriveConfidentialAccountId(kind, address) {
|
|
|
264
265
|
if (kind === "solana") {
|
|
265
266
|
const bytes2 = import_base.base58.decode(account);
|
|
266
267
|
assert(bytes2.length === 32, "Invalid Solana public key.");
|
|
267
|
-
return import_base.
|
|
268
|
+
return import_base.base58.encode(bytes2);
|
|
268
269
|
}
|
|
269
270
|
assert(kind === "tron", "Unsupported linking wallet kind.");
|
|
270
271
|
const bytes = (0, import_base.base58check)(import_sha2562.sha256).decode(account);
|
|
271
272
|
assert(bytes.length === 21 && bytes[0] === 65, "Invalid Tron address.");
|
|
273
|
+
return (0, import_base.base58check)(import_sha2562.sha256).encode(bytes);
|
|
274
|
+
}
|
|
275
|
+
function tronRecoveredAddressHex(address) {
|
|
276
|
+
const account = deriveConfidentialAccountId("tron", address);
|
|
277
|
+
const bytes = (0, import_base.base58check)(import_sha2562.sha256).decode(account);
|
|
272
278
|
return `0x${import_base.base16.encode(bytes.slice(1)).toLowerCase()}`;
|
|
273
279
|
}
|
|
274
280
|
function identityKey(identity) {
|
|
@@ -501,7 +507,7 @@ function verifyWalletSignature(data, identity) {
|
|
|
501
507
|
const sig = import_secp256k1.secp256k1.Signature.fromCompact(bytes.slice(0, 64)).addRecoveryBit(bytes[64] - 27);
|
|
502
508
|
const publicKey = sig.recoverPublicKey(personalMessageHash(data.payload, identity.kind)).toRawBytes(false);
|
|
503
509
|
const address = `0x${import_base2.base16.encode((0, import_sha3.keccak_256)(publicKey.slice(1)).slice(-20)).toLowerCase()}`;
|
|
504
|
-
valid = address ===
|
|
510
|
+
valid = identity.kind === "tron" ? address === tronRecoveredAddressHex(identity.accountId) : address === deriveConfidentialAccountId("evm", identity.accountId);
|
|
505
511
|
}
|
|
506
512
|
} catch {
|
|
507
513
|
valid = false;
|
|
@@ -601,10 +607,10 @@ function validateSigned(unsigned, signed, identity) {
|
|
|
601
607
|
|
|
602
608
|
// src/api.ts
|
|
603
609
|
function toMultiPayload(data) {
|
|
604
|
-
if (data.standard !== "tip191") return data;
|
|
610
|
+
if (data.standard !== "tip191" && data.standard !== "erc191") return data;
|
|
605
611
|
if (data.signature.startsWith("secp256k1:")) {
|
|
606
612
|
const bytes2 = import_base4.base58.decode(data.signature.slice("secp256k1:".length));
|
|
607
|
-
responseAssert(bytes2.length === 65 && bytes2[64] <= 1, "Invalid
|
|
613
|
+
responseAssert(bytes2.length === 65 && bytes2[64] <= 1, "Invalid secp256k1 signature.");
|
|
608
614
|
return data;
|
|
609
615
|
}
|
|
610
616
|
const bytes = import_base4.base16.decode(normalizeSecpSignature(data.signature).slice(2).toUpperCase());
|
|
@@ -2123,6 +2129,7 @@ function createConfidentialSwapClient(config) {
|
|
|
2123
2129
|
standards,
|
|
2124
2130
|
successes,
|
|
2125
2131
|
throwIfAborted,
|
|
2132
|
+
tronRecoveredAddressHex,
|
|
2126
2133
|
userRejectedError,
|
|
2127
2134
|
validateRecipients,
|
|
2128
2135
|
withdrawalProgress
|