mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
Merge kms-nonce-bind-2026-04-28
# Conflicts: # CHANGELOG.md
This commit is contained in:
+53
-158
@@ -1,72 +1,28 @@
|
||||
import { Router, Route, Switch, Link, useLocation, Redirect } from 'wouter'
|
||||
import { Router, Route, Switch, Link, useLocation } from 'wouter'
|
||||
import { useHashLocation } from 'wouter/use-hash-location'
|
||||
import { auth } from './lib/api'
|
||||
import { Login } from './pages/Login'
|
||||
import { Dashboard } from './pages/Dashboard'
|
||||
import { Operations } from './pages/Operations'
|
||||
import { Sign } from './pages/Sign'
|
||||
import { Wallets } from './pages/Wallets'
|
||||
import { Vaults } from './pages/Vaults'
|
||||
import { Policies } from './pages/Policies'
|
||||
import { Whitelist } from './pages/Whitelist'
|
||||
import { Settlements } from './pages/Settlements'
|
||||
import { Bridge } from './pages/Bridge'
|
||||
import { Payments } from './pages/Payments'
|
||||
import { Audit } from './pages/Audit'
|
||||
import { Devices } from './pages/Devices'
|
||||
import { Webhooks } from './pages/Webhooks'
|
||||
import { APIKeys } from './pages/APIKeys'
|
||||
import { Users } from './pages/Users'
|
||||
import { Settings } from './pages/Settings'
|
||||
|
||||
type NavItem = { href: string; label: string }
|
||||
type NavGroup = { title: string; items: NavItem[] }
|
||||
const navItems = [
|
||||
{ href: '/', label: 'Dashboard' },
|
||||
{ href: '/wallets', label: 'Wallets' },
|
||||
{ href: '/vaults', label: 'Vaults' },
|
||||
{ href: '/policies', label: 'Policies' },
|
||||
{ href: '/settlements', label: 'Settlements' },
|
||||
{ href: '/bridge', label: 'Bridge' },
|
||||
{ href: '/payments', label: 'Payments' },
|
||||
{ href: '/audit', label: 'Audit' },
|
||||
{ href: '/settings', label: 'Settings' },
|
||||
] as const
|
||||
|
||||
const groups: NavGroup[] = [
|
||||
{
|
||||
title: 'Treasury',
|
||||
items: [
|
||||
{ href: '/', label: 'Dashboard' },
|
||||
{ href: '/operations', label: 'Operations' },
|
||||
{ href: '/sign', label: 'New transaction' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Wallets',
|
||||
items: [
|
||||
{ href: '/wallets', label: 'Wallets' },
|
||||
{ href: '/vaults', label: 'Vaults' },
|
||||
{ href: '/whitelist', label: 'Allowlist' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Governance',
|
||||
items: [
|
||||
{ href: '/policies', label: 'Policies' },
|
||||
{ href: '/users', label: 'Users' },
|
||||
{ href: '/devices', label: 'Devices' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Pipeline',
|
||||
items: [
|
||||
{ href: '/settlements', label: 'Settlements' },
|
||||
{ href: '/bridge', label: 'Bridge' },
|
||||
{ href: '/payments', label: 'Payments' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
items: [
|
||||
{ href: '/audit', label: 'Audit log' },
|
||||
{ href: '/api-keys', label: 'API keys' },
|
||||
{ href: '/webhooks', label: 'Webhooks' },
|
||||
{ href: '/settings', label: 'Settings' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function NavLink({ href, label }: NavItem) {
|
||||
function NavLink({ href, label }: { href: string; label: string }) {
|
||||
const [location] = useLocation()
|
||||
const active = href === '/' ? location === '/' : location.startsWith(href)
|
||||
return (
|
||||
@@ -74,12 +30,12 @@ function NavLink({ href, label }: NavItem) {
|
||||
href={href}
|
||||
style={{
|
||||
display: 'block',
|
||||
padding: '5px 12px',
|
||||
padding: '6px 12px',
|
||||
borderRadius: 6,
|
||||
color: active ? '#fff' : '#a3a3a3',
|
||||
background: active ? '#262626' : 'transparent',
|
||||
textDecoration: 'none',
|
||||
fontSize: 13,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
@@ -87,108 +43,47 @@ function NavLink({ href, label }: NavItem) {
|
||||
)
|
||||
}
|
||||
|
||||
function Sidebar() {
|
||||
const me = auth.user()
|
||||
function logout() {
|
||||
auth.clear()
|
||||
window.location.hash = '#/login'
|
||||
}
|
||||
return (
|
||||
<aside
|
||||
style={{
|
||||
width: 220,
|
||||
borderRight: '1px solid #262626',
|
||||
padding: 16,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 12,
|
||||
flexShrink: 0,
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, fontSize: 16, color: '#fff' }}>MPC Admin</div>
|
||||
{me && (
|
||||
<div style={{ fontSize: 11, color: '#737373', marginTop: 2 }}>
|
||||
{me.email} · {me.role}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{groups.map((g) => (
|
||||
<nav key={g.title} style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<div style={{
|
||||
fontSize: 10,
|
||||
color: '#525252',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.08em',
|
||||
padding: '4px 12px',
|
||||
}}>
|
||||
{g.title}
|
||||
</div>
|
||||
{g.items.map((n) => <NavLink key={n.href} {...n} />)}
|
||||
</nav>
|
||||
))}
|
||||
<button
|
||||
onClick={logout}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
background: 'transparent',
|
||||
border: '1px solid #262626',
|
||||
borderRadius: 6,
|
||||
padding: '6px 12px',
|
||||
color: '#a3a3a3',
|
||||
fontSize: 12,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
function Protected({ children }: { children: React.ReactNode }) {
|
||||
const tok = auth.token()
|
||||
if (!tok) return <Redirect to="/login" />
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<Router hook={useHashLocation}>
|
||||
<Switch>
|
||||
<Route path="/login" component={Login} />
|
||||
<Route>
|
||||
<Protected>
|
||||
<div style={{ display: 'flex', height: '100vh', background: '#000', color: '#e5e5e5' }}>
|
||||
<Sidebar />
|
||||
<main style={{ flex: 1, overflow: 'auto', padding: 24 }}>
|
||||
<Switch>
|
||||
<Route path="/" component={Dashboard} />
|
||||
<Route path="/operations" component={Operations} />
|
||||
<Route path="/sign" component={Sign} />
|
||||
<Route path="/wallets" component={Wallets} />
|
||||
<Route path="/vaults" component={Vaults} />
|
||||
<Route path="/whitelist" component={Whitelist} />
|
||||
<Route path="/policies" component={Policies} />
|
||||
<Route path="/users" component={Users} />
|
||||
<Route path="/devices" component={Devices} />
|
||||
<Route path="/settlements" component={Settlements} />
|
||||
<Route path="/bridge" component={Bridge} />
|
||||
<Route path="/payments" component={Payments} />
|
||||
<Route path="/audit" component={Audit} />
|
||||
<Route path="/api-keys" component={APIKeys} />
|
||||
<Route path="/webhooks" component={Webhooks} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route>
|
||||
<div style={{ color: '#737373' }}>Not found</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
</main>
|
||||
</div>
|
||||
</Protected>
|
||||
</Route>
|
||||
</Switch>
|
||||
<div style={{ display: 'flex', height: '100vh', background: '#000', color: '#e5e5e5' }}>
|
||||
<aside
|
||||
style={{
|
||||
width: 200,
|
||||
borderRight: '1px solid #262626',
|
||||
padding: 16,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontWeight: 600, fontSize: 16, marginBottom: 16, color: '#fff' }}>
|
||||
MPC Admin
|
||||
</div>
|
||||
<nav style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{navItems.map((n) => (
|
||||
<NavLink key={n.href} href={n.href} label={n.label} />
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
<main style={{ flex: 1, overflow: 'auto', padding: 24 }}>
|
||||
<Switch>
|
||||
<Route path="/" component={Dashboard} />
|
||||
<Route path="/wallets" component={Wallets} />
|
||||
<Route path="/vaults" component={Vaults} />
|
||||
<Route path="/policies" component={Policies} />
|
||||
<Route path="/settlements" component={Settlements} />
|
||||
<Route path="/bridge" component={Bridge} />
|
||||
<Route path="/payments" component={Payments} />
|
||||
<Route path="/audit" component={Audit} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route>
|
||||
<div style={{ color: '#737373' }}>Not found</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
</main>
|
||||
</div>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,30 +28,6 @@ export function useTransactions(filters?: { status?: string; chain?: string }) {
|
||||
})
|
||||
}
|
||||
|
||||
export function useOperations(filters?: { status?: string; walletId?: string; page?: number; perPage?: number }) {
|
||||
return useQuery({
|
||||
queryKey: ['mpc', 'operations', filters],
|
||||
queryFn: () => api.listOperations(filters),
|
||||
refetchInterval: 5000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useOperation(operationId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['mpc', 'operation', operationId],
|
||||
queryFn: () => api.getOperation(operationId),
|
||||
enabled: !!operationId,
|
||||
})
|
||||
}
|
||||
|
||||
export function useSessions(walletId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['mpc', 'sessions', walletId],
|
||||
queryFn: () => api.listSessions(walletId),
|
||||
enabled: !!walletId,
|
||||
})
|
||||
}
|
||||
|
||||
export function usePolicies() {
|
||||
return useQuery({ queryKey: ['mpc', 'policies'], queryFn: api.listPolicies })
|
||||
}
|
||||
@@ -86,23 +62,3 @@ export function useWalletBackups(walletId: string) {
|
||||
enabled: !!walletId,
|
||||
})
|
||||
}
|
||||
|
||||
export function useUsers() {
|
||||
return useQuery({ queryKey: ['mpc', 'users'], queryFn: api.listUsers })
|
||||
}
|
||||
|
||||
export function useAPIKeys() {
|
||||
return useQuery({ queryKey: ['mpc', 'api-keys'], queryFn: api.listAPIKeys })
|
||||
}
|
||||
|
||||
export function useWebhooks() {
|
||||
return useQuery({ queryKey: ['mpc', 'webhooks'], queryFn: api.listWebhooks })
|
||||
}
|
||||
|
||||
export function useWhitelist() {
|
||||
return useQuery({ queryKey: ['mpc', 'whitelist'], queryFn: api.listWhitelist })
|
||||
}
|
||||
|
||||
export function useWebAuthnCredentials() {
|
||||
return useQuery({ queryKey: ['mpc', 'webauthn'], queryFn: api.listWebAuthnCredentials })
|
||||
}
|
||||
|
||||
+43
-298
@@ -1,48 +1,24 @@
|
||||
// Typed API client for MPC backend.
|
||||
//
|
||||
// Two surfaces: the spec'd Liquidity MPC namespace at /v1/mpc/* and the
|
||||
// platform-internal /v1/* (auth, users, webhooks, api-keys, whitelist,
|
||||
// webauthn). Everything authenticates with the bearer JWT in localStorage.
|
||||
// Typed API client for MPC backend — all paths relative to /v1/mpc
|
||||
|
||||
const ROOT = '/v1'
|
||||
const MPC = '/v1/mpc'
|
||||
const BASE = '/v1/mpc'
|
||||
|
||||
export class APIError extends Error {
|
||||
constructor(public status: number, public body: { error?: string }) {
|
||||
super(body.error ?? `HTTP ${status}`)
|
||||
constructor(public status: number, public body: { error: string }) {
|
||||
super(body.error)
|
||||
this.name = 'APIError'
|
||||
}
|
||||
}
|
||||
|
||||
const TOKEN_KEY = 'mpc_token'
|
||||
const REFRESH_KEY = 'mpc_refresh'
|
||||
const USER_KEY = 'mpc_user'
|
||||
|
||||
export const auth = {
|
||||
token: (): string | null => localStorage.getItem(TOKEN_KEY),
|
||||
refresh: (): string | null => localStorage.getItem(REFRESH_KEY),
|
||||
user: (): { id: string; org_id: string; role: string; email: string } | null => {
|
||||
const raw = localStorage.getItem(USER_KEY)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
},
|
||||
set: (token: string, refresh: string, user: { id: string; org_id: string; role: string; email: string }) => {
|
||||
localStorage.setItem(TOKEN_KEY, token)
|
||||
localStorage.setItem(REFRESH_KEY, refresh)
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(user))
|
||||
},
|
||||
clear: () => {
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
localStorage.removeItem(REFRESH_KEY)
|
||||
localStorage.removeItem(USER_KEY)
|
||||
},
|
||||
function token(): string | null {
|
||||
return localStorage.getItem('mpc_token')
|
||||
}
|
||||
|
||||
async function request<T>(method: string, fullPath: string, body?: unknown): Promise<T> {
|
||||
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
||||
const t = auth.token()
|
||||
const t = token()
|
||||
if (t) headers['Authorization'] = `Bearer ${t}`
|
||||
|
||||
const res = await fetch(fullPath, {
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
@@ -50,26 +26,17 @@ async function request<T>(method: string, fullPath: string, body?: unknown): Pro
|
||||
|
||||
if (res.status === 204) return undefined as T
|
||||
|
||||
// 401: token dead, force re-login
|
||||
if (res.status === 401 && fullPath !== `${ROOT}/auth/login`) {
|
||||
auth.clear()
|
||||
if (typeof window !== 'undefined') window.location.hash = '#/login'
|
||||
}
|
||||
|
||||
const text = await res.text()
|
||||
const data = text ? JSON.parse(text) : {}
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw new APIError(res.status, data)
|
||||
return data as T
|
||||
}
|
||||
|
||||
const get = <T>(p: string) => request<T>('GET', p)
|
||||
const post = <T>(p: string, b?: unknown) => request<T>('POST', p, b)
|
||||
const patch = <T>(p: string, b?: unknown) => request<T>('PATCH', p, b)
|
||||
const del = <T>(p: string) => request<T>('DELETE', p)
|
||||
function get<T>(path: string): Promise<T> { return request<T>('GET', path) }
|
||||
function post<T>(path: string, body?: unknown): Promise<T> { return request<T>('POST', path, body) }
|
||||
function patch<T>(path: string, body?: unknown): Promise<T> { return request<T>('PATCH', path, body) }
|
||||
function del<T>(path: string): Promise<T> { return request<T>('DELETE', path) }
|
||||
|
||||
// =================================================================
|
||||
// Types — matching Go backend models
|
||||
// =================================================================
|
||||
// --- Types (matching Go backend models) ---
|
||||
|
||||
export interface ClusterStatus {
|
||||
node_id: string
|
||||
@@ -131,33 +98,6 @@ export interface Transaction {
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface OperationApproval {
|
||||
approverId: string
|
||||
approvedAt: string
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface Operation {
|
||||
operationId: string
|
||||
walletId: string
|
||||
kind: string
|
||||
payload?: Record<string, unknown>
|
||||
status: string
|
||||
approvers?: string[]
|
||||
approvals?: OperationApproval[]
|
||||
rejectionReason?: string
|
||||
result?: Record<string, string>
|
||||
createdAt: string
|
||||
completedAt?: string
|
||||
}
|
||||
|
||||
export interface OperationsPage {
|
||||
items: Operation[]
|
||||
page: number
|
||||
perPage: number
|
||||
totalItems: number
|
||||
}
|
||||
|
||||
export interface Policy {
|
||||
id: string
|
||||
org_id: string
|
||||
@@ -233,151 +173,35 @@ export interface BridgeConfig {
|
||||
withdrawalsEnabled: boolean
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string
|
||||
createdAt: string
|
||||
orgId: string
|
||||
email: string
|
||||
role: string
|
||||
}
|
||||
|
||||
export interface APIKey {
|
||||
id: string
|
||||
name: string
|
||||
keyPrefix: string
|
||||
permissions: string[]
|
||||
expiresAt?: string | null
|
||||
lastUsedAt?: string | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface APIKeyCreated {
|
||||
id: string
|
||||
name: string
|
||||
key: string
|
||||
key_prefix: string
|
||||
permissions: string[]
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface Webhook {
|
||||
id: string
|
||||
org_id: string
|
||||
url: string
|
||||
secret: string
|
||||
events: string[]
|
||||
enabled: boolean
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface WhitelistEntry {
|
||||
id: string
|
||||
org_id: string
|
||||
vault_id?: string | null
|
||||
address: string
|
||||
chain: string
|
||||
label?: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface WebAuthnCredential {
|
||||
id: string
|
||||
device_name?: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
sessionId: string
|
||||
walletId: string
|
||||
grantedTo?: string
|
||||
scopes?: string[]
|
||||
valueLimit?: string
|
||||
operationLimit?: number
|
||||
status: string
|
||||
createdAt: string
|
||||
expiresAt: string
|
||||
revokedAt?: string
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
user_id: string
|
||||
org_id: string
|
||||
role: string
|
||||
email?: string
|
||||
access_token: string
|
||||
refresh_token: string
|
||||
mfa_required?: boolean
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// API methods
|
||||
// =================================================================
|
||||
// --- API methods ---
|
||||
|
||||
export const api = {
|
||||
// -------- Auth (no /mpc prefix; raw v1) --------
|
||||
login: (email: string, password: string, mfa_code?: string) =>
|
||||
post<LoginResponse>(`${ROOT}/auth/login`, { email, password, mfa_code }),
|
||||
refresh: (refresh_token: string) =>
|
||||
post<{ access_token: string; refresh_token: string }>(`${ROOT}/auth/refresh`, { refresh_token }),
|
||||
register: (email: string, password: string, org_name: string) =>
|
||||
post<LoginResponse>(`${ROOT}/auth/register`, { email, password, org_name }),
|
||||
// Status
|
||||
getStatus: () => get<ClusterStatus>('/status'),
|
||||
getInfo: () => get<InfoResponse>('/info'),
|
||||
|
||||
// -------- Status --------
|
||||
getStatus: () => get<ClusterStatus>(`${MPC}/status`),
|
||||
getInfo: () => get<InfoResponse>(`${MPC}/info`),
|
||||
// Vaults
|
||||
listVaults: () => get<Vault[]>('/vaults'),
|
||||
createVault: (req: { name: string; description?: string }) => post<Vault>('/vaults', req),
|
||||
deleteVault: (id: string) => del(`/vaults/${id}`),
|
||||
|
||||
// -------- Vaults --------
|
||||
listVaults: () => get<Vault[]>(`${MPC}/vaults`),
|
||||
createVault: (req: { name: string; description?: string }) =>
|
||||
post<Vault>(`${MPC}/vaults`, req),
|
||||
deleteVault: (id: string) => del<void>(`${MPC}/vaults/${id}`),
|
||||
|
||||
// -------- Wallets --------
|
||||
listWallets: (vaultId: string) => get<Wallet[]>(`${MPC}/vaults/${vaultId}/wallets`),
|
||||
// Wallets
|
||||
listWallets: (vaultId: string) => get<Wallet[]>(`/vaults/${vaultId}/wallets`),
|
||||
createWallet: (vaultId: string, req?: { name?: string; key_type?: string; protocol?: string }) =>
|
||||
post<Wallet>(`${MPC}/vaults/${vaultId}/wallets`, req),
|
||||
getWalletBackups: (walletId: string) =>
|
||||
get<WalletBackup[]>(`${MPC}/wallets/${walletId}/backup`),
|
||||
post<Wallet>(`/vaults/${vaultId}/wallets`, req),
|
||||
getWalletBackups: (walletId: string) => get<WalletBackup[]>(`/wallets/${walletId}/backup`),
|
||||
|
||||
// -------- Transactions / Operations --------
|
||||
// Transactions
|
||||
listTransactions: (filters?: { status?: string; chain?: string }) => {
|
||||
const params = new URLSearchParams()
|
||||
if (filters?.status) params.set('status', filters.status)
|
||||
if (filters?.chain) params.set('chain', filters.chain)
|
||||
const qs = params.toString()
|
||||
return get<Transaction[]>(`${MPC}/transactions${qs ? `?${qs}` : ''}`)
|
||||
return get<Transaction[]>(`/transactions${qs ? `?${qs}` : ''}`)
|
||||
},
|
||||
|
||||
listOperations: (filters?: { status?: string; walletId?: string; page?: number; perPage?: number }) => {
|
||||
const params = new URLSearchParams()
|
||||
if (filters?.status) params.set('status', filters.status)
|
||||
if (filters?.walletId) params.set('walletId', filters.walletId)
|
||||
if (filters?.page) params.set('page', String(filters.page))
|
||||
if (filters?.perPage) params.set('perPage', String(filters.perPage))
|
||||
const qs = params.toString()
|
||||
return get<OperationsPage>(`${MPC}/operations${qs ? `?${qs}` : ''}`)
|
||||
},
|
||||
getOperation: (id: string) => get<Operation>(`${MPC}/operations/${id}`),
|
||||
approveOperation: (id: string, notes?: string) =>
|
||||
post<Operation>(`${MPC}/operations/${id}/approve`, { notes: notes ?? '' }),
|
||||
rejectOperation: (id: string, reason: string) =>
|
||||
post<Operation>(`${MPC}/operations/${id}/reject`, { reason }),
|
||||
|
||||
// -------- Sessions (per-wallet signing grants) --------
|
||||
listSessions: (walletId: string) =>
|
||||
get<{ items: Session[] }>(`${MPC}/wallets/${walletId}/sessions`),
|
||||
createSession: (walletId: string, req: {
|
||||
grantedTo?: string
|
||||
scopes: string[]
|
||||
valueLimit?: string
|
||||
operationLimit?: number
|
||||
expiresAt: string
|
||||
}) => post<Session>(`${MPC}/wallets/${walletId}/sessions`, req),
|
||||
revokeSession: (walletId: string, sessionId: string) =>
|
||||
del<void>(`${MPC}/wallets/${walletId}/sessions/${sessionId}`),
|
||||
|
||||
// -------- Policies --------
|
||||
listPolicies: () => get<Policy[]>(`${MPC}/policies`),
|
||||
// Policies
|
||||
listPolicies: () => get<Policy[]>('/policies'),
|
||||
createPolicy: (req: {
|
||||
name: string
|
||||
action: string
|
||||
@@ -385,104 +209,25 @@ export const api = {
|
||||
conditions: Record<string, unknown>
|
||||
required_approvers: number
|
||||
approver_roles: string[]
|
||||
}) => post<Policy>(`${MPC}/policies`, req),
|
||||
updatePolicy: (id: string, req: { enabled?: boolean }) =>
|
||||
patch<Policy>(`${MPC}/policies/${id}`, req),
|
||||
deletePolicy: (id: string) => del<void>(`${MPC}/policies/${id}`),
|
||||
}) => post<Policy>('/policies', req),
|
||||
updatePolicy: (id: string, req: { enabled?: boolean }) => patch<Policy>(`/policies/${id}`, req),
|
||||
deletePolicy: (id: string) => del(`/policies/${id}`),
|
||||
|
||||
// -------- Settlements --------
|
||||
// Settlements
|
||||
listSettlements: (filters?: { status?: string }) => {
|
||||
const params = new URLSearchParams()
|
||||
if (filters?.status) params.set('status', filters.status)
|
||||
const qs = params.toString()
|
||||
return get<Settlement[]>(`${MPC}/settlements${qs ? `?${qs}` : ''}`)
|
||||
return get<Settlement[]>(`/settlements${qs ? `?${qs}` : ''}`)
|
||||
},
|
||||
|
||||
// -------- Bridge --------
|
||||
getBridgeConfig: () => get<BridgeConfig>(`${MPC}/bridge/config`),
|
||||
listBridgeNetworks: () => get<BridgeNetwork[]>(`${MPC}/bridge/networks`),
|
||||
// Bridge
|
||||
getBridgeConfig: () => get<BridgeConfig>('/bridge/config'),
|
||||
listBridgeNetworks: () => get<BridgeNetwork[]>('/bridge/networks'),
|
||||
|
||||
// -------- Payments --------
|
||||
listPaymentRequests: () => get<PaymentRequest[]>(`${MPC}/payment-requests`),
|
||||
// Payments
|
||||
listPaymentRequests: () => get<PaymentRequest[]>('/payment-requests'),
|
||||
|
||||
// -------- Audit --------
|
||||
listAudit: () => get<AuditEntry[]>(`${MPC}/audit`),
|
||||
|
||||
// -------- Users --------
|
||||
listUsers: () => get<User[]>(`${ROOT}/users`),
|
||||
inviteUser: (req: { email: string; password: string; role?: string }) =>
|
||||
post<User>(`${ROOT}/users`, req),
|
||||
updateUser: (id: string, req: { role?: string }) =>
|
||||
patch<User>(`${ROOT}/users/${id}`, req),
|
||||
deleteUser: (id: string) => del<void>(`${ROOT}/users/${id}`),
|
||||
|
||||
// -------- API Keys --------
|
||||
listAPIKeys: () => get<APIKey[]>(`${ROOT}/api-keys`),
|
||||
createAPIKey: (req: { name: string; permissions?: string[] }) =>
|
||||
post<APIKeyCreated>(`${ROOT}/api-keys`, req),
|
||||
deleteAPIKey: (id: string) => del<void>(`${ROOT}/api-keys/${id}`),
|
||||
|
||||
// -------- Webhooks --------
|
||||
listWebhooks: () => get<Webhook[]>(`${ROOT}/webhooks`),
|
||||
createWebhook: (req: { url: string; events: string[]; secret: string }) =>
|
||||
post<Webhook>(`${ROOT}/webhooks`, req),
|
||||
updateWebhook: (id: string, req: { url?: string; events?: string[]; enabled?: boolean }) =>
|
||||
patch<Webhook>(`${ROOT}/webhooks/${id}`, req),
|
||||
deleteWebhook: (id: string) => del<void>(`${ROOT}/webhooks/${id}`),
|
||||
testWebhook: (id: string) =>
|
||||
post<{ status: string }>(`${ROOT}/webhooks/${id}/test`),
|
||||
|
||||
// -------- Whitelist (allowlist for destinations) --------
|
||||
listWhitelist: () => get<WhitelistEntry[]>(`${ROOT}/whitelist`),
|
||||
addWhitelist: (req: { address: string; chain: string; label?: string; vault_id?: string }) =>
|
||||
post<WhitelistEntry>(`${ROOT}/whitelist`, req),
|
||||
deleteWhitelist: (id: string) => del<void>(`${ROOT}/whitelist/${id}`),
|
||||
|
||||
// -------- WebAuthn --------
|
||||
listWebAuthnCredentials: () => get<WebAuthnCredential[]>(`${ROOT}/webauthn/credentials`),
|
||||
deleteWebAuthnCredential: (id: string) => del<void>(`${ROOT}/webauthn/credentials/${id}`),
|
||||
webAuthnRegisterBegin: () =>
|
||||
post<WebAuthnRegisterOptions>(`${ROOT}/webauthn/register/begin`),
|
||||
webAuthnRegisterComplete: (body: WebAuthnRegisterComplete) =>
|
||||
post<{ status: string; credential_id: string }>(`${ROOT}/webauthn/register/complete`, body),
|
||||
webAuthnVerify: (body: WebAuthnVerify) =>
|
||||
post<{ status: string; biometric: boolean; signing_started: boolean; approvals: number; required: number }>(
|
||||
`${ROOT}/webauthn/verify`, body,
|
||||
),
|
||||
}
|
||||
|
||||
// WebAuthn ceremony types — server-shaped, not browser-shaped.
|
||||
export interface WebAuthnRegisterOptions {
|
||||
challenge: string
|
||||
rp: { id: string; name: string }
|
||||
user: { id: string; name: string; displayName: string }
|
||||
pubKeyCredParams: { type: string; alg: number }[]
|
||||
timeout: number
|
||||
attestation: string
|
||||
authenticatorSelection: {
|
||||
authenticatorAttachment: string
|
||||
userVerification: string
|
||||
residentKey: string
|
||||
}
|
||||
credential_id: string
|
||||
}
|
||||
|
||||
export interface WebAuthnRegisterComplete {
|
||||
credential_id: string
|
||||
id: string
|
||||
rawId: string
|
||||
type: string
|
||||
response: {
|
||||
attestationObject: string
|
||||
clientDataJSON: string
|
||||
}
|
||||
device_name: string
|
||||
}
|
||||
|
||||
export interface WebAuthnVerify {
|
||||
transaction_id: string
|
||||
credential_id: string
|
||||
authenticator_data: string
|
||||
client_data_json: string
|
||||
signature: string
|
||||
// Audit
|
||||
listAudit: () => get<AuditEntry[]>('/audit'),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user