181 lines
5.0 KiB
TypeScript
181 lines
5.0 KiB
TypeScript
import { ref } from 'vue';
|
|
import { csrfFetch } from '@/utils/csrfFetch';
|
|
|
|
export interface VaultBalances {
|
|
balance: string;
|
|
vault_balance: string;
|
|
vault_balances: Record<string, string>;
|
|
currency: string | null;
|
|
}
|
|
|
|
export function useVault() {
|
|
const loading = ref(false);
|
|
const error = ref<string | null>(null);
|
|
const balances = ref<VaultBalances | null>(null);
|
|
const transfers = ref<any>(null);
|
|
const pinRequired = ref(false);
|
|
const lockedUntil = ref<string | null>(null);
|
|
|
|
const sessionPin = ref<string | null>(null);
|
|
|
|
async function load(perPage = 10) {
|
|
loading.value = true;
|
|
error.value = null;
|
|
try {
|
|
const resp = await fetch(`/api/wallet/vault?per_page=${perPage}`, {
|
|
headers: { 'Accept': 'application/json' }
|
|
});
|
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
const json = await resp.json();
|
|
balances.value = {
|
|
balance: json.balance,
|
|
vault_balance: json.vault_balance,
|
|
vault_balances: json.vault_balances ?? { BTX: json.vault_balance },
|
|
currency: json.currency,
|
|
};
|
|
transfers.value = json.transfers;
|
|
} catch (e: any) {
|
|
error.value = e?.message || 'Failed to load vault';
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
function uuidv4(): string {
|
|
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
|
return (crypto as any).randomUUID();
|
|
}
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
const r = (Math.random() * 16) | 0;
|
|
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
return v.toString(16);
|
|
});
|
|
}
|
|
|
|
async function deposit(amount: string, currency = 'BTX', pin?: string) {
|
|
return doAction('/api/wallet/vault/deposit', amount, currency, pin);
|
|
}
|
|
|
|
async function withdraw(amount: string, currency = 'BTX', pin?: string) {
|
|
return doAction('/api/wallet/vault/withdraw', amount, currency, pin);
|
|
}
|
|
|
|
async function doAction(url: string, amount: string, currency: string, pin?: string) {
|
|
loading.value = true;
|
|
error.value = null;
|
|
pinRequired.value = false;
|
|
lockedUntil.value = null;
|
|
|
|
try {
|
|
const usePin = pin ?? sessionPin.value;
|
|
if (!usePin) {
|
|
pinRequired.value = true;
|
|
throw new Error('PIN required');
|
|
}
|
|
|
|
const body = { amount, currency, pin: usePin, idempotency_key: uuidv4() };
|
|
const resp = await csrfFetch(url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (resp.status === 423) {
|
|
const j = await resp.json().catch(() => ({}));
|
|
pinRequired.value = true;
|
|
lockedUntil.value = j?.locked_until || null;
|
|
throw new Error(j?.message || 'PIN required');
|
|
}
|
|
|
|
if (!resp.ok) {
|
|
const j = await resp.json().catch(() => ({}));
|
|
throw new Error(j?.message || `HTTP ${resp.status}`);
|
|
}
|
|
|
|
const json = await resp.json();
|
|
|
|
if (json?.balances) {
|
|
balances.value = {
|
|
balance: json.balances.balance,
|
|
vault_balance: json.balances.vault_balance,
|
|
vault_balances: json.balances.vault_balances ?? { BTX: json.balances.vault_balance },
|
|
currency: balances.value?.currency || 'BTX',
|
|
};
|
|
} else {
|
|
await load();
|
|
}
|
|
return json;
|
|
} catch (e: any) {
|
|
error.value = e?.message || 'Vault action failed';
|
|
throw e;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
async function verifyPin(pin: string) {
|
|
error.value = null;
|
|
try {
|
|
const resp = await csrfFetch('/api/wallet/vault/pin/verify', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
body: JSON.stringify({ pin }),
|
|
});
|
|
|
|
if (!resp.ok) {
|
|
const j = await resp.json().catch(() => ({}));
|
|
if (resp.status === 423) lockedUntil.value = j?.locked_until || null;
|
|
throw new Error(j?.message || `HTTP ${resp.status}`);
|
|
}
|
|
|
|
sessionPin.value = pin;
|
|
pinRequired.value = false;
|
|
lockedUntil.value = null;
|
|
|
|
return await resp.json();
|
|
} catch (e: any) {
|
|
error.value = e?.message || 'PIN verification failed';
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
function clearSessionPin() {
|
|
sessionPin.value = null;
|
|
}
|
|
|
|
async function setPin(pin: string, current_pin?: string) {
|
|
error.value = null;
|
|
const payload: any = { pin };
|
|
if (current_pin) payload.current_pin = current_pin;
|
|
|
|
const resp = await csrfFetch('/api/wallet/vault/pin/set', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!resp.ok) {
|
|
const j = await resp.json().catch(() => ({}));
|
|
throw new Error(j?.message || `HTTP ${resp.status}`);
|
|
}
|
|
|
|
sessionPin.value = pin;
|
|
return await resp.json();
|
|
}
|
|
|
|
return {
|
|
loading,
|
|
error,
|
|
balances,
|
|
transfers,
|
|
pinRequired,
|
|
lockedUntil,
|
|
load,
|
|
deposit,
|
|
withdraw,
|
|
verifyPin,
|
|
setPin,
|
|
clearSessionPin,
|
|
};
|
|
}
|