Files
solace-bg-dubai/frontend/lib/api/client.ts
T

147 lines
4.8 KiB
TypeScript

/** 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<Record<string, string> | undefined> {
if (!wallet || typeof window === "undefined") return undefined;
const ethereum = (window as Window & { ethereum?: { request: (args: { method: string; params: string[] }) => Promise<string> } }).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<T>(path: string, wallet?: string): Promise<T> {
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<T>;
}
export async function fetchTreasury(wallet: string): Promise<TreasuryRecord | null> {
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<TransactionRecord[]> {
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<TransactionRecord[]> {
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<SubAccountRecord[]> {
const data = await apiFetch<{ subAccounts: SubAccountRecord[] }>(
`/api/treasury/${treasuryId}/sub-accounts`, wallet
);
return data.subAccounts;
}
export async function fetchLedger(treasuryId: string, wallet?: string): Promise<LedgerEntry[]> {
const data = await apiFetch<{ ledger: LedgerEntry[] }>(
`/api/treasury/${treasuryId}/ledger`, wallet
);
return data.ledger;
}
export async function fetchLedgerByWallet(wallet: string): Promise<LedgerEntry[]> {
const data = await apiFetch<{ ledger: LedgerEntry[] }>(
`/api/ledger?wallet=${encodeURIComponent(wallet)}`, wallet
);
return data.ledger;
}
export async function exportTransactionsCsv(treasuryId: string, wallet?: string): Promise<string> {
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();
}