/** Same-origin in production (nginx /api/ → backend). Local dev uses localhost:3001. */ function apiUrl(path: string): string { const normalized = path.startsWith("/") ? path : `/${path}`; const configured = (process.env.NEXT_PUBLIC_API_URL ?? "").trim(); if (typeof window !== "undefined") { // Ignore localhost baked in at build time — browser must hit same-origin /api/. if (configured && !/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?/i.test(configured)) { return `${configured.replace(/\/$/, "")}${normalized}`; } return normalized; } if (configured) { return `${configured.replace(/\/$/, "")}${normalized}`; } if (process.env.NODE_ENV === "production") { return normalized; } return `http://localhost:3001${normalized}`; } export interface TreasuryRecord { id: string; organizationId: string; chainId: number; mainWallet: string; label: string | null; createdAt: string; updatedAt: string; } export interface TransactionRecord { id: string; treasuryId: string; proposalId: number; walletAddress: string; to: string; value: string; token: string | null; data: string | null; status: string; proposer: string; createdAt: string; executedAt: string | null; approvalCount?: number; } export interface SubAccountRecord { id: string; treasuryId: string; address: string; label: string | null; metadataHash: string | null; createdAt: string; } export interface LedgerEntry { id: string; kind: "deposit" | "transfer_out"; txHash: string; from: string; to: string; value: string; token: string | null; tokenSymbol: string | null; timestamp: string; blockNumber: number; } async function signedHeaders(path: string, wallet?: string, method = "GET"): Promise | undefined> { if (!wallet || typeof window === "undefined") return undefined; const ethereum = (window as Window & { ethereum?: { request: (args: { method: string; params: string[] }) => Promise } }).ethereum; if (!ethereum) return undefined; const domain = window.location.host; const nonce = crypto.randomUUID(); const message = `Solace Treasury API authorization\nDomain: ${domain}\nChain ID: 138\nWallet: ${wallet.toLowerCase()}\nMethod: ${method}\nPath: ${path}\nTimestamp: ${Date.now()}\nNonce: ${nonce}`; const signature = await ethereum.request({ method: "personal_sign", params: [message, wallet] }); return { "X-Wallet-Address": wallet, "X-Wallet-Message": message, "X-Wallet-Signature": signature }; } async function apiFetch(path: string, wallet?: string): Promise { const res = await fetch(apiUrl(path), { cache: "no-store", headers: await signedHeaders(path, wallet) }); if (!res.ok) { throw new Error(`API ${path} failed: ${res.status}`); } return res.json() as Promise; } export async function fetchTreasury(wallet: string): Promise { const data = await apiFetch<{ treasury: TreasuryRecord | null }>( `/api/treasury?wallet=${encodeURIComponent(wallet)}` ); return data.treasury; } export async function fetchTransactions( treasuryId: string, status?: string, wallet?: string ): Promise { const query = status ? `?status=${encodeURIComponent(status)}` : ""; const data = await apiFetch<{ proposals: TransactionRecord[] }>( `/api/transactions/${treasuryId}${query}`, wallet ); return data.proposals; } export async function fetchPendingProposals(treasuryId: string, wallet?: string): Promise { const data = await apiFetch<{ proposals: TransactionRecord[] }>( `/api/proposals/${treasuryId}?status=pending&includeApprovals=1`, wallet ); return data.proposals; } export async function fetchSubAccounts(treasuryId: string, wallet?: string): Promise { const data = await apiFetch<{ subAccounts: SubAccountRecord[] }>( `/api/treasury/${treasuryId}/sub-accounts`, wallet ); return data.subAccounts; } export async function fetchLedger(treasuryId: string, wallet?: string): Promise { const data = await apiFetch<{ ledger: LedgerEntry[] }>( `/api/treasury/${treasuryId}/ledger`, wallet ); return data.ledger; } export async function fetchLedgerByWallet(wallet: string): Promise { const data = await apiFetch<{ ledger: LedgerEntry[] }>( `/api/ledger?wallet=${encodeURIComponent(wallet)}`, wallet ); return data.ledger; } export async function exportTransactionsCsv(treasuryId: string, wallet?: string): Promise { const path = `/api/transactions/export?treasuryId=${encodeURIComponent(treasuryId)}&format=csv`; const res = await fetch( apiUrl(path), { headers: await signedHeaders(path, wallet) } ); if (!res.ok) { throw new Error(`CSV export failed: ${res.status}`); } return res.text(); }