Repository Public
1// The signer's own view of THORChain's vault set, used for native cross-chain swaps (packages/core/src/thorchain.ts).2// A THORChain swap pays an Asgard vault whose address only the network's nodes can name. The API proposes one; before signing,3// the signer asks several independently operated THORNodes ITSELF — its own compiled-in list, over TLS, nothing passing4// through the API — and signs only if every operator that answers confirms the vault (checkThorVault). A compromised API5// therefore cannot substitute its own address. Any doubt — too few answers, one dissenting, a halted chain — means no signature.6// Nothing is cached: every signature is checked against the operators' current state.7import { checkThorVault, fetchThorViews, THORNODE_OPERATORS, type ThorGet, type ThorVaultVerdict, type ThorVaultWant } from '@spw/core';89export interface VaultView { /** throws unless the vault is confirmed */ check(want: ThorVaultWant): Promise<ThorVaultVerdict> }1011const TIMEOUT_MS = 7000, MAX_BYTES = 400_000;12const json: ThorGet = async (url) => {13 const r = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS), redirect: 'error', headers: { accept: 'application/json', 'user-agent': 'vault-check', 'x-client-id': 'vault-check' } });14 if (!r.ok) throw new Error(`HTTP ${r.status}`);15 const t = await r.text();16 if (t.length > MAX_BYTES) throw new Error('response too large');17 return JSON.parse(t);18};19/** Hosts this view contacts (documented in docs/swap/providers/thorchain.md; an egress allowlist must include them). */20export const vaultHosts = (): string[] => THORNODE_OPERATORS.map((o) => new URL(o.base).host);2122export function thorVaults(get: ThorGet = json): VaultView {23 return { async check(want) { return checkThorVault(await fetchThorViews(get), want); } };24}25/** For tests and CLI paths: no view, so every cross-chain swap is refused. */26export const noVaults: VaultView = { check: async () => { throw new Error('no vault view configured'); } };27