Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions app/src/components/DelegationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useState } from "react";
import { watchTx } from "@/lib/txWatcher";
import { submitWithToast } from "@/lib/toastStore";
import { CopyButton } from "@/components/CopyButton";
import { rpc } from '@/lib/client-rpc';

interface Delegation {
delegation_id: string;
Expand All @@ -19,17 +20,6 @@ interface Props {

type ActionState = "idle" | "loading" | "success" | "error";

async function rpc<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
const res = await fetch("/api/rpc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? "RPC error");
return data.result as T;
}

/** Get the first unused wallet address, generating a new one only if all are used. */
async function freshAddress(): Promise<string> {
const addresses = await rpc<Array<{ address: string; used: boolean; purpose: string }>>(
Expand Down
42 changes: 7 additions & 35 deletions app/src/components/IssueNFTModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { useState, useEffect } from 'react';
import { submitWithToast } from '@/lib/toastStore';
import { watchTx } from '@/lib/txWatcher';
import { CopyButton } from '@/components/CopyButton';
import { rpc } from '@/lib/client-rpc';
import { toHexField } from '@/lib/token-utils';
import { ModalOverlay } from '@/components/ui/ModalOverlay';

type Mode = 'easy' | 'expert';

Expand All @@ -11,24 +14,6 @@ interface Props {
onIssued?: (tokenId: string) => void;
}

async function rpc<T>(method: string, params: Record<string, unknown>): Promise<T> {
const res = await fetch('/api/rpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? 'RPC error');
return data.result as T;
}

/** Encode a UTF-8 string as a lowercase hex string for the Mintlayer RPC `{ hex }` format. */
function toHexField(str: string): { hex: string } {
const bytes = new TextEncoder().encode(str);
const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
return { hex };
}

/** Strip non-alphanumeric characters and enforce max byte length (chain constraint). */
function sanitize(str: string, maxLen: number): string {
return str.replace(/[^a-zA-Z0-9]/g, '').slice(0, maxLen);
Expand Down Expand Up @@ -182,7 +167,7 @@ export default function IssueNFTModal({ ipfsEnabled, onClose, onIssued }: Props)

if (issuedTokenId) {
return (
<Overlay onClose={onClose}>
<ModalOverlay onClose={onClose}>
<div className="px-6 py-8 text-center">
<div className="w-12 h-12 rounded-full bg-green-900/40 border border-green-700 flex items-center justify-center mx-auto mb-4 text-xl">✓</div>
<h2 className="text-lg font-semibold text-gray-100 mb-2">NFT Issued!</h2>
Expand All @@ -196,12 +181,12 @@ export default function IssueNFTModal({ ipfsEnabled, onClose, onIssued }: Props)
Close
</button>
</div>
</Overlay>
</ModalOverlay>
);
}

return (
<Overlay onClose={onClose}>
<ModalOverlay onClose={onClose}>
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-800 shrink-0">
<h2 className="text-base font-semibold text-gray-100">Mint NFT</h2>
<div className="flex items-center gap-3">
Expand Down Expand Up @@ -414,7 +399,7 @@ export default function IssueNFTModal({ ipfsEnabled, onClose, onIssued }: Props)
</button>
</div>
</form>
</Overlay>
</ModalOverlay>
);
}

Expand All @@ -441,19 +426,6 @@ function ModeToggle({ mode, onChange }: { mode: Mode; onChange: (m: Mode) => voi
);
}

function Overlay({ children, onClose }: { children: React.ReactNode; onClose: () => void }) {
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
onClick={e => e.target === e.currentTarget && onClose()}
>
<div className="w-full max-w-lg rounded-xl bg-gray-900 border border-gray-800 shadow-2xl flex flex-col max-h-[90vh]">
{children}
</div>
</div>
);
}

const inp = 'w-full rounded-lg bg-gray-800 border border-gray-700 text-gray-100 placeholder-gray-600 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-mint-600';
const cancelBtn = 'flex-1 rounded-lg border border-gray-700 px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 transition-colors';
const submitBtn = 'flex-1 rounded-lg bg-mint-700 hover:bg-mint-600 px-4 py-2 text-sm font-semibold text-white transition-colors disabled:opacity-50';
42 changes: 7 additions & 35 deletions app/src/components/IssueTokenModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { useState, useEffect } from 'react';
import { submitWithToast } from '@/lib/toastStore';
import { watchTx } from '@/lib/txWatcher';
import { CopyButton } from '@/components/CopyButton';
import { rpc } from '@/lib/client-rpc';
import { toHexField } from '@/lib/token-utils';
import { ModalOverlay } from '@/components/ui/ModalOverlay';

type SupplyType = 'Fixed' | 'Lockable' | 'Unlimited';
type Mode = 'easy' | 'expert';
Expand All @@ -12,24 +15,6 @@ interface Props {
onIssued?: (tokenId: string) => void;
}

async function rpc<T>(method: string, params: Record<string, unknown>): Promise<T> {
const res = await fetch('/api/rpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? 'RPC error');
return data.result as T;
}

/** Encode a UTF-8 string as a lowercase hex string for the Mintlayer RPC `{ hex }` format. */
function toHexField(str: string): { hex: string } {
const bytes = new TextEncoder().encode(str);
const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
return { hex };
}

async function uploadFile(file: File): Promise<string> {
const form = new FormData();
form.append('file', file);
Expand Down Expand Up @@ -149,7 +134,7 @@ export default function IssueTokenModal({ ipfsEnabled, onClose, onIssued }: Prop

if (issuedTokenId) {
return (
<Overlay onClose={onClose}>
<ModalOverlay onClose={onClose}>
<div className="px-6 py-8 text-center">
<div className="w-12 h-12 rounded-full bg-green-900/40 border border-green-700 flex items-center justify-center mx-auto mb-4 text-xl">✓</div>
<h2 className="text-lg font-semibold text-gray-100 mb-2">Token Issued!</h2>
Expand All @@ -163,12 +148,12 @@ export default function IssueTokenModal({ ipfsEnabled, onClose, onIssued }: Prop
Close
</button>
</div>
</Overlay>
</ModalOverlay>
);
}

return (
<Overlay onClose={onClose}>
<ModalOverlay onClose={onClose}>
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-800 shrink-0">
<h2 className="text-base font-semibold text-gray-100">Issue Fungible Token</h2>
<div className="flex items-center gap-3">
Expand Down Expand Up @@ -357,7 +342,7 @@ export default function IssueTokenModal({ ipfsEnabled, onClose, onIssued }: Prop
</button>
</div>
</form>
</Overlay>
</ModalOverlay>
);
}

Expand All @@ -384,19 +369,6 @@ function ModeToggle({ mode, onChange }: { mode: Mode; onChange: (m: Mode) => voi
);
}

function Overlay({ children, onClose }: { children: React.ReactNode; onClose: () => void }) {
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
onClick={e => e.target === e.currentTarget && onClose()}
>
<div className="w-full max-w-lg rounded-xl bg-gray-900 border border-gray-800 shadow-2xl flex flex-col max-h-[90vh]">
{children}
</div>
</div>
);
}

const input = 'w-full rounded-lg bg-gray-800 border border-gray-700 text-gray-100 placeholder-gray-600 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-mint-600';
const cancelBtn = 'flex-1 rounded-lg border border-gray-700 px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 transition-colors';
const submitBtn = 'flex-1 rounded-lg bg-mint-700 hover:bg-mint-600 px-4 py-2 text-sm font-semibold text-white transition-colors disabled:opacity-50';
12 changes: 1 addition & 11 deletions app/src/components/IssuedTokensPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { hexToText } from '@/lib/token-utils';
import { CopyButton } from '@/components/CopyButton';
import TokenManagePanel from '@/components/TokenManagePanel';
import { TokenIdTooltip } from '@/components/TokenIdTooltip';
import { rpc } from '@/lib/client-rpc';

// ── Types ──────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -56,17 +57,6 @@ function saveStored(list: StoredToken[]) {
localStorage.setItem(LS_KEY, JSON.stringify(deduped));
}

async function rpc<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
const res = await fetch('/api/rpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? 'RPC error');
return data.result as T;
}

function atomsToDecimal(atoms: string, decimals: number): string {
if (decimals === 0) return atoms;
const n = BigInt(atoms);
Expand Down
12 changes: 1 addition & 11 deletions app/src/components/NFTMarketplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { watchTx } from "@/lib/txWatcher";
import { submitWithToast } from "@/lib/toastStore";
import { hexToText } from "@/lib/token-utils";
import type { TokenCurrency, OrderInfo } from "@/lib/wallet-rpc";
import { rpc } from '@/lib/client-rpc';

// ── Types ─────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -43,17 +44,6 @@ function resolveUri(raw: string | null): string | null {
return raw;
}

async function rpc<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
const res = await fetch("/api/rpc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? "RPC error");
return data.result as T;
}

function friendlyError(err: unknown): string {
const raw = (err as Error)?.message ?? String(err);
const lower = raw.toLowerCase();
Expand Down
12 changes: 1 addition & 11 deletions app/src/components/OrderBook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { watchTx } from "@/lib/txWatcher";
import { submitWithToast } from "@/lib/toastStore";
import { CopyButton } from "@/components/CopyButton";
import type { OrderInfo, TokenCurrency } from "@/lib/wallet-rpc";
import { rpc } from '@/lib/client-rpc';

// Raw shape returned by order_list_all_active (flat, no existing_order_data wrapper)
interface ActiveOrderRaw {
Expand Down Expand Up @@ -59,17 +60,6 @@ function saveFavourites(favs: FavouriteEntry[]): void {

// ── RPC helper ────────────────────────────────────────────────────────────────

async function rpc<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
const res = await fetch("/api/rpc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? "RPC error");
return data.result as T;
}

// ── Error translation ─────────────────────────────────────────────────────────

function friendlyError(err: unknown): string {
Expand Down
12 changes: 1 addition & 11 deletions app/src/components/OwnedNFTsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useState, useEffect } from "react";
import { hexToText } from "@/lib/token-utils";
import { CopyButton } from "@/components/CopyButton";
import { rpc } from '@/lib/client-rpc';

interface NFTEntry {
tokenId: string;
Expand All @@ -23,17 +24,6 @@ function resolveUri(raw: string | null): string | null {
return raw;
}

async function rpc<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
const res = await fetch("/api/rpc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? "RPC error");
return data.result as T;
}

// ── Image cell ────────────────────────────────────────────────────────────────

function NFTImageCell({ uri, name }: { uri: string | null; name: string }) {
Expand Down
12 changes: 1 addition & 11 deletions app/src/components/PoolActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,7 @@
import { useState } from "react";
import { watchTx } from "@/lib/txWatcher";
import { submitWithToast } from "@/lib/toastStore";

async function rpc<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
const res = await fetch("/api/rpc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? "RPC error");
return data.result as T;
}
import { rpc } from '@/lib/client-rpc';

async function freshAddress(): Promise<string> {
const addresses = await rpc<Array<{ address: string; used: boolean; purpose: string }>>(
Expand Down
12 changes: 1 addition & 11 deletions app/src/components/ReceiveModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,14 @@

import { useState, useEffect, useCallback } from "react";
import { QRCodeSVG } from "qrcode.react";
import { rpc } from '@/lib/client-rpc';

type State =
| { status: "idle" }
| { status: "loading" }
| { status: "ready"; address: string }
| { status: "error"; message: string };

async function rpc<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
const res = await fetch("/api/rpc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? "RPC error");
return data.result as T;
}

export default function ReceiveModal() {
const [open, setOpen] = useState(false);
const [state, setState] = useState<State>({ status: "idle" });
Expand Down
12 changes: 1 addition & 11 deletions app/src/components/StakingControl.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
"use client";

import { useState } from "react";
import { rpc } from '@/lib/client-rpc';

interface Props {
initialStatus: "Staking" | "NotStaking";
}

async function rpc<T>(method: string, params: Record<string, unknown> = {}): Promise<T> {
const res = await fetch("/api/rpc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ method, params }),
});
const data = await res.json() as { ok: boolean; result?: T; error?: { message: string } };
if (!data.ok) throw new Error(data.error?.message ?? "RPC error");
return data.result as T;
}

export default function StakingControl({ initialStatus }: Props) {
const [status, setStatus] = useState(initialStatus);
const [loading, setLoading] = useState(false);
Expand Down
Loading