// Unified API — Tauri IPC in native builds, HTTP fallback in browser
import { invoke } from '@tauri-apps/api/core';
// API key for backend authentication — embedded in frontend since both run on same machine
const API_KEY = import.meta.env.VITE_API_KEY || '';
// Dynamic API URL: uses same host as the page (works for LAN/Tailscale access)
// When running in Tauri, IPC is used instead of HTTP — this URL is ignored.
function getApiUrl(): string {
if (typeof window === 'undefined') return 'http://127.0.0.1:14195/api';
const host = window.location.hostname;
const protocol = window.location.protocol;
return `${protocol}//${host}:14195/api`;
}
// Common request headers
function getHeaders(): Record<string, string> {
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (API_KEY) headers['X-API-Key'] = API_KEY;
return headers;
}
// Detect Tauri: window.__TAURI__ exists when running in Tauri WebView
function isTauri(): boolean {
return typeof window !== 'undefined' && '__TAURI__' in window;
}
// ================= Core helpers =================
async function proxy(command: string, args: Record<string, unknown>): Promise<any> {
if (isTauri()) {
return invoke('python_proxy', { command, args });
}
const resp = await fetch(`${getApiUrl()}/${command}`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(args),
});
if (!resp.ok) {
const err = await resp.json().catch(() => ({ error: resp.statusText }));
throw new Error(err.error || `API error ${resp.status}`);
}
return resp.json();
}
async function rpc(method: string, params: unknown[] = []): Promise<any> {
if (isTauri()) {
return invoke('rpc_command', { method, params });
}
const resp = await fetch(`${getApiUrl()}/${method}`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ params }),
});
return resp.json();
}
// ================= Wallet operations (via Python proxy) =================
export async function generateSeed(strength: number = 128): Promise<string> {
const data = await proxy('generate_seed', { strength });
return data.seed;
}
export interface WalletInfo {
name: string;
fingerprint: string;
encrypted: boolean;
created: string;
xpub: string;
derivation_path: string;
seed: string;
}
export interface AddressInfo {
address: string;
path: string;
pubkey: string;
privkey_wif: string;
index: number;
internal: boolean;
type: string;
}
export async function createWallet(
seed: string,
walletName: string,
password: string = '',
): Promise<{ wallet: WalletInfo; addresses: AddressInfo[] }> {
const data = await proxy('create_wallet', {
seed,
wallet_name: walletName,
password,
});
return data;
}
export async function deriveAddresses(
wallet: any,
numAddresses: number = 5,
derivationType: string = 'bech32',
password: string = '',
): Promise<AddressInfo[]> {
// H10: Send wallet object (xpub-based) instead of raw mnemonic
const data = await proxy('derive_addresses', {
wallet,
num_addresses: numAddresses,
derivation_type: derivationType,
password,
});
return data.addresses;
}
// ================= Bitcoin RPC (native Rust) =================
export async function getBlockCount(): Promise<number> {
const data = await proxy('get_block_count', {});
return data.block_count;
}
export async function getNetworkInfo(): Promise<any> {
const data = await proxy('get_network_info', {});
return data;
}
export async function getBalance(
addresses: string[],
): Promise<{ balance_btc: number; balance_sats: number }> {
if (isTauri()) {
return invoke('get_balance', { addresses });
}
const resp = await fetch(`${getApiUrl()}/get_balance`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ addresses }),
});
return resp.json();
}
export async function getUtxos(addresses: string[]): Promise<any[]> {
if (isTauri()) {
const data = await invoke('get_utxos', { addresses });
return (data as any).utxos;
}
const resp = await fetch(`${getApiUrl()}/get_utxos`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ addresses }),
});
const data = await resp.json();
return data.utxos;
}
// ================= Transaction history =================
export interface TxInfo {
address: string;
category: string;
amount: number;
confirmations: number;
blockhash?: string;
blockindex?: number;
blocktime?: number;
txid: string;
walletconflicts?: string[];
time?: number;
timereceived?: number;
comment?: string;
}
export async function getTxHistory(
address: string,
count: number = 20,
): Promise<TxInfo[]> {
const data = await proxy('get_tx_history', { address, count });
return data;
}
// ================= Fee estimation =================
export interface FeeEstimate {
blocks: number;
feerate_btc: number | null;
feerate_sats_vb: number;
}
export async function estimateFee(blocks: number = 6): Promise<FeeEstimate> {
const data = await proxy('estimate_fee', { blocks });
return data;
}
// ================= Transaction builder =================
export interface TxOutput {
address: string;
amount: number;
}
export interface CoinSelectResult {
selected: any[];
total_input: number;
}
export interface PsbtResult {
psbt: string;
inputs: number;
outputs: number;
total_input: number;
total_output: number;
fee_btc: number;
change_address: string | null;
change_amount: number;
selected_utxos?: any[];
coin_selection: string;
error?: string;
}
export async function createPsbt(
wallet: any,
outputs: TxOutput[],
feeRate: number = 10.0,
addresses: string[] | null = null,
password: string = '',
coinSelection: string = 'largest',
selectedUtxos: any[] | null = null,
): Promise<PsbtResult> {
return proxy('create_psbt', {
wallet,
outputs,
fee_rate: feeRate,
addresses,
password,
coin_selection: coinSelection,
selected_utxos: selectedUtxos,
});
}
export interface PsbtSigned {
psbt: string;
complete: boolean;
all_signed: boolean;
finalized: boolean;
raw_tx_hex: string | null;
error?: string;
}
export async function signPsbt(
wallet: any,
psbt: string,
password: string = '',
): Promise<PsbtSigned> {
return proxy('sign_psbt', { wallet, psbt: psbt, password });
}
export async function finalizePsbt(psbt: string): Promise<{ raw_tx_hex: string | null; complete: boolean }> {
return proxy('finalize_psbt', { psbt });
}
export async function signAndFinalizePsbt(
wallet: any,
psbt: string,
password: string = '',
): Promise<{ raw_tx_hex: string | null; complete: boolean; error?: string }> {
return proxy('sign_and_finalize_psbt', { wallet, psbt, password });
}
export interface BroadcastResult {
txid: string;
broadcast: boolean;
error?: string;
}
export async function broadcastTx(rawTxHex: string): Promise<BroadcastResult> {
return proxy('broadcast', { raw_tx_hex: rawTxHex });
}
export async function decodePsbt(psbt: string): Promise<any> {
return proxy('decode_psbt', { psbt });
}
// ================= Transaction status =================
export interface TxStatus {
txid: string;
confirmations: number;
blockhash?: string;
blocktime?: number;
hex?: string;
status: 'confirmed' | 'unconfirmed' | 'not_found';
}
export async function getTxStatus(txid: string): Promise<TxStatus> {
if (isTauri()) {
// Use rpc_command directly
const result = await invoke('rpc_command', {
method: 'getrawtransaction',
params: [txid, true],
});
if (!result || result === null) {
return { txid, confirmations: 0, status: 'not_found' };
}
const r = result as any;
return {
txid: r.txid,
confirmations: r.confirmations || 0,
blockhash: r.blockhash,
blocktime: r.blocktime,
hex: r.hex,
status: r.confirmations >= 1 ? 'confirmed' : 'unconfirmed',
};
}
const resp = await fetch(`${getApiUrl()}/rpc_call`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ method: 'getrawtransaction', params: [txid, true] }),
});
const result = await resp.json();
if (!result.result) {
return { txid, confirmations: 0, status: 'not_found' };
}
const r = result.result;
return {
txid: r.txid,
confirmations: r.confirmations || 0,
blockhash: r.blockhash,
blocktime: r.blocktime,
hex: r.hex,
status: r.confirmations >= 1 ? 'confirmed' : 'unconfirmed',
};
}
// ---- Persistence + Labels + UTXO Tree ----
// Unified API call helper for proxy-based commands
async function api({ method, params = {} }: { method: string; params?: Record<string, unknown> }): Promise<any> {
if (isTauri()) {
return invoke('python_proxy', { command: method, args: params });
}
const resp = await fetch(`${getApiUrl()}/${method}`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(params),
});
return resp.json();
}
export interface WalletMeta {
path: string;
name: string;
type: string;
fingerprint: string;
encrypted: boolean;
modified: string;
created: string;
}
export async function saveWallet(wallet: any, walletDir?: string): Promise<any> {
return api({ method: 'save_wallet', params: { wallet, wallet_dir: walletDir } });
}
export async function loadWallet(fpath: string, password?: string): Promise<any> {
return api({ method: 'load_wallet', params: { fpath, password } });
}
export async function listWallets(): Promise<{ wallets: WalletMeta[] }> {
return api({ method: 'list_wallets' });
}
export async function setLabel(wallet: any, kind: string, identifier: string, label: string): Promise<any> {
return api({ method: 'set_label', params: { wallet, kind, identifier, label } });
}
export async function getUtxoTree(utxos: any[], txHistory: any[]): Promise<any> {
return api({ method: 'get_utxo_tree', params: { utxos, tx_history: txHistory } });
}
// ================= Multisig =================
export interface MultisigParticipant {
name: string;
local: boolean;
mnemonic?: string;
xpub?: string;
}
export interface MultisigWallet {
type: 'multisig';
name: string;
m: number;
n: number;
threshold: number;
participants: Array<{
index: number;
name: string;
xpub: string;
local: boolean;
}>;
local_indices: number[];
descriptor: string;
descriptor_hash: string;
address: string;
encrypted: boolean;
created: string;
file_path?: string;
}
export async function createMultisigWallet(
m: number,
n: number,
participants: MultisigParticipant[],
walletName: string,
password: string = '',
): Promise<{ wallet: MultisigWallet; balance: any }> {
return api({ method: 'create_multisig_wallet', params: {
m, n, participants, wallet_name: walletName, password,
}});
}
export async function getMultisigBalance(wallet: MultisigWallet): Promise<any> {
return api({ method: 'get_multisig_balance', params: { wallet } });
}
export async function getMultisigUtxos(wallet: MultisigWallet): Promise<{ utxos: any[] }> {
return api({ method: 'get_multisig_utxos', params: { wallet } });
}
export interface MultisigAddressInfo {
index: number;
address: string;
chain: 'receive' | 'change';
}
export async function getMultisigAddresses(
wallet: MultisigWallet,
count: number = 5,
change: number = 0,
): Promise<{ addresses: MultisigAddressInfo[] }> {
return api({ method: 'get_multisig_addresses', params: { wallet, count, change } });
}
export interface MultisigPsbtResult {
psbt: string;
inputs: number;
outputs: number;
total_input: number;
total_output: number;
fee_btc: number;
change_address: string | null;
error?: string;
}
export async function createMultisigPsbt(
wallet: MultisigWallet,
outputs: TxOutput[],
feeRate: number = 10.0,
utxos: any[] | null = null,
): Promise<MultisigPsbtResult> {
return api({ method: 'create_multisig_psbt', params: {
wallet, outputs, fee_rate: feeRate, utxos,
}});
}
export interface MultisigSignResult {
psbt: string;
signatures_added: number;
error?: string;
}
export async function signMultisigPsbt(
psbt: string,
wallet: MultisigWallet,
password: string = '',
): Promise<MultisigSignResult> {
return api({ method: 'sign_multisig_psbt', params: { psbt, wallet, password } });
}
export async function combinePsbt(psbtHexList: string[]): Promise<{ psbt: string }> {
return api({ method: 'combine_psbt', params: { psbt_hex_list: psbtHexList } });
}
export interface MultisigFinalizeResult {
complete: boolean;
raw_tx_hex: string | null;
error?: string;
}
export async function finalizeMultisigPsbt(psbt: string): Promise<MultisigFinalizeResult> {
return api({ method: 'finalize_multisig_psbt', params: { psbt } });
}