// The only file in the webview that talks to Rust. One function per Tauri // command, each typed with the ts-rs output in ./generated, and one place // where a Rust `WalletError` becomes a thrown `WalletError`. Screens never // call `invoke` themselves; CI counts the occurrences in this directory. import { invoke } from '@tauri-apps/api/core' import type { AccountBalance } from './generated/AccountBalance' import type { AppInfo } from './generated/AppInfo' import type { ChainId } from './generated/ChainId' import type { ChainProfile } from './generated/ChainProfile' import type { ChainStatus } from './generated/ChainStatus' import type { PendingReversible } from './generated/PendingReversible' import type { PreparedTransferInfo } from './generated/PreparedTransferInfo' import type { CreationStart } from './generated/CreationStart' import type { SessionStatusInfo } from './generated/SessionStatusInfo' import type { Settings } from './generated/Settings' import type { AssetInfo } from './generated/AssetInfo' import type { HistoryCursor } from './generated/HistoryCursor' import type { Order } from './generated/Order' import type { SwapAsk } from './generated/SwapAsk' import type { SwapPreview } from './generated/SwapPreview' import type { HistoryPage } from './generated/HistoryPage' import type { SignatureScheme } from './generated/SignatureScheme' import type { WalletSummary } from './generated/WalletSummary' import type { WormholeSummary } from './generated/WormholeSummary' import type { WalletError as WalletErrorShape } from './generated/WalletError' export class WalletError extends Error { readonly kind: WalletErrorShape['kind'] readonly detail: string constructor(shape: WalletErrorShape) { const detail = 'detail' in shape ? shape.detail : '' super(detail ? `${shape.kind}: ${detail}` : shape.kind) this.name = 'WalletError' this.kind = shape.kind this.detail = detail } } function isWalletErrorShape(e: unknown): e is WalletErrorShape { return ( typeof e === 'object' && e !== null && 'kind' in e && typeof (e as { kind: unknown }).kind === 'string' ) } // Rust commands return `Result`; Tauri rejects the promise // with the serialised error. Anything that is not a WalletError is a bug on // one side of the boundary and is rethrown as-is so it is loud. async function command(name: string, args?: Record): Promise { try { return await invoke(name, args) } catch (e) { if (isWalletErrorShape(e)) throw new WalletError(e) throw e } } export function appInfo(): Promise { return command('app_info') } export function walletsList(): Promise { return command('wallets_list') } // Creation is three steps so the phrase is shown once and then gone: begin // returns the words and the challenge, confirm writes the wallet if the // challenge passes, cancel forgets the phrase. export function walletCreateBegin(scheme: SignatureScheme): Promise { return command('wallet_create_begin', { scheme }) } export function walletCreateConfirm( name: string, password: string, answers: string[], ): Promise { return command('wallet_create_confirm', { name, password, answers }) } export function walletCreateCancel(): Promise { return command('wallet_create_cancel') } export function walletImportPhrase( name: string, password: string, phrase: string, scheme: SignatureScheme, ): Promise { return command('wallet_import_phrase', { name, password, phrase, scheme }) } export function walletImportFile(path: string, password: string): Promise { return command('wallet_import_file', { path, password }) } export function checkphrase(address: string): Promise { return command('checkphrase', { address }) } // The session: one wallet open at a time, locked again after idling. The // Rust side emits `session:locked` when the idle rule fires. export function unlock(name: string, password: string): Promise { return command('unlock', { name, password }) } export function lock(): Promise { return command('lock') } export function sessionStatus(): Promise { return command('session_status') } export function chainsList(): Promise { return command('chains_list') } export function chainConnect(chain: ChainId): Promise { return command('chain_connect', { chain }) } export function chainStatus(chain: ChainId): Promise { return command('chain_status', { chain }) } // Balances are read from finalized state. `balancesWatch` makes Rust re-read // them on every finalized head and emit `balances:updated`. export function balances(chain: ChainId): Promise { return command('balances', { chain }) } export function balancesWatch(chain: ChainId): Promise { return command('balances_watch', { chain }) } // Sending is two steps so the review screen shows what Rust decoded from the // bytes: prepare returns the summary and fee under an id, submit signs and // sends that id. Progress arrives as `tx:status` events. export function transferPrepare( chain: ChainId, from: string, to: string, amount: string, reversibleFor?: number, ): Promise { return command('transfer_prepare', { chain, from, to, amount, reversibleFor: reversibleFor ?? null, }) } export function transferSubmit(id: string): Promise { return command('transfer_submit', { id }) } export function transferDiscard(id: string): Promise { return command('transfer_discard', { id }) } export function reversiblePending(chain: ChainId, address: string): Promise { return command('reversible_pending', { chain, address }) } export function reversibleCancelPrepare( chain: ChainId, from: string, txId: string, ): Promise { return command('reversible_cancel_prepare', { chain, from, txId }) } export function settingsGet(): Promise { return command('settings_get') } export function settingsSet(settings: Settings): Promise { return command('settings_set', { settings }) } // History: one page per call, from the indexer or, when it cannot be // reached, from the local cache with `from_cache` set. export function historyPage( chain: ChainId, address: string, before?: HistoryCursor | null, ): Promise { return command('history_page', { chain, address, before: before ?? null }) } // The wormhole address behind the wallet's mining rewards and what the // zk-tree holds for it. Scans on the Rust side; can take a while the first // time on a long tree. export function wormholeSummary(chain: ChainId): Promise { return command('wormhole_summary', { chain }) } // Swaps, in preview: the assets the provider and the registry agree on, a // dry quote, an order (a wet quote persisted with its deposit address), // and the list. Rust follows open orders and emits `orders:updated`. export function swapAssets(): Promise { return command('swap_assets') } export function swapPreview(ask: SwapAsk): Promise { return command('swap_preview', { ask }) } export function swapOrderCreate(ask: SwapAsk): Promise { return command('swap_order_create', { ask }) } export function ordersList(): Promise { return command('orders_list') }