diff --git a/src/app/dashboard/wallets/[id]/addresses/page.tsx b/src/app/dashboard/wallets/[id]/addresses/page.tsx
index 5d0b17a..8d3ce94 100644
--- a/src/app/dashboard/wallets/[id]/addresses/page.tsx
+++ b/src/app/dashboard/wallets/[id]/addresses/page.tsx
@@ -1,22 +1,22 @@
"use client";
-import { use, useEffect, useState } from "react";
+import { use, useCallback, useEffect, useState } from "react";
import Link from "next/link";
import { useAuth } from "@/lib/useAuth";
import { useWallet } from "@/lib/useWallet";
import {
- listAddresses,
- listRecentAddresses,
+ listAddressesPage,
createAddress,
type Address,
} from "@/lib/wallets";
import { Modal, CopyField } from "@/components/dashboard/Modal";
import { Stat, ActionButton, Panel, Empty } from "@/components/dashboard/WalletUI";
import { PageSpinner } from "@/components/OctoSpinner";
+import { Pagination } from "@/components/dashboard/Pagination";
import { AddressSearchBox } from "@/components/addresses/AddressSearchBox";
import { filterAddressesByRef } from "@/lib/addressSearch";
import { NewAddressModal } from "@/components/addresses/CustomerReferenceField";
-import { formatStroops, sumStroops } from "@/lib/amount";
+import { formatStroops } from "@/lib/amount";
// Dynamic render so the strict nonce CSP (src/proxy.ts) applies — matches the other
// /dashboard/wallets/:id/* pages, which all read wallet-scoped data.
@@ -40,35 +40,50 @@ export default function AddressesPage({
const [selected, setSelected] = useState
(null);
const [error, setError] = useState(null);
+ // `cursors[i]` is the `before` cursor for page i+1; the API has no offsets, so Prev is a pop.
+ const [cursors, setCursors] = useState<(string | null)[]>([null]);
+ const [pageIndex, setPageIndex] = useState(0);
+ const [nextCursor, setNextCursor] = useState(null);
+
+ const load = useCallback(
+ (before: string | null) => {
+ if (!token) return;
+ setRefreshing(true);
+ listAddressesPage(token, id, { before })
+ .then((page) => {
+ setAddresses(page.data);
+ setNextCursor(page.next_cursor);
+ setError(null);
+ })
+ .catch((e) => {
+ setError(e instanceof Error ? e.message : "Could not load addresses.");
+ })
+ .finally(() => setRefreshing(false));
+ },
+ [token, id],
+ );
+
function refresh() {
- if (!token) return;
- setRefreshing(true);
- listRecentAddresses(token, id)
- .then((a) => {
- setAddresses(a);
- setError(null);
- })
- .catch((e) =>
- setError(e instanceof Error ? e.message : "Could not load addresses."),
- )
- .finally(() => setRefreshing(false));
+ load(cursors[pageIndex] ?? null);
}
useEffect(() => {
- if (!token) return;
- const controller = new AbortController();
- listAddresses(token, id, { signal: controller.signal })
- .then((a) => {
- setAddresses(a);
- setError(null);
- })
- .catch((e) => {
- if (e instanceof DOMException && e.name === "AbortError") return;
- setAddresses([]);
- setError(e instanceof Error ? e.message : "Could not load addresses.");
- });
- return () => controller.abort();
- }, [token, id]);
+ load(null);
+ }, [load]);
+
+ function goNext() {
+ if (!nextCursor) return;
+ setCursors((c) => [...c.slice(0, pageIndex + 1), nextCursor]);
+ setPageIndex((i) => i + 1);
+ load(nextCursor);
+ }
+
+ function goPrev() {
+ if (pageIndex === 0) return;
+ const target = cursors[pageIndex - 1];
+ setPageIndex((i) => i - 1);
+ load(target ?? null);
+ }
async function handleNewAddress(customerRef?: string) {
if (!token) return;
@@ -84,8 +99,6 @@ export default function AddressesPage({
}
}
- const totalReceived = sumStroops(addresses.map((a) => a.received_stroops));
-
if (loading || !user) {
return (
@@ -112,13 +125,8 @@ export default function AddressesPage({
{/* stat cards */}
-
-
-
+
+
)}
+
0}
+ hasNext={nextCursor !== null}
+ loading={refreshing}
+ onPrev={goPrev}
+ onNext={goNext}
+ />
@@ -212,9 +228,7 @@ function AddressRow({
{address.memo_id} |
- {address.received_stroops > 0
- ? `${formatStroops(address.received_stroops)} XLM`
- : "$0"}
+ {formatStroops(address.received_stroops)}
|
);
@@ -233,7 +247,10 @@ function AddressDetail({
Total received
- {formatStroops(address.received_stroops)} XLM
+ {formatStroops(address.received_stroops)}
+
+
+ Deposits across all assets; see Transactions for per-asset amounts.
diff --git a/src/app/dashboard/wallets/[id]/api/page.tsx b/src/app/dashboard/wallets/[id]/api/page.tsx
index 2760c77..5bc5188 100644
--- a/src/app/dashboard/wallets/[id]/api/page.tsx
+++ b/src/app/dashboard/wallets/[id]/api/page.tsx
@@ -11,7 +11,8 @@ import {
import { PageSpinner } from "@/components/OctoSpinner";
import { CopyButton } from "@/components/CopyButton";
import { Modal } from "@/components/dashboard/Modal";
-import { ApiError } from "@/lib/api";
+import { ApiError, API_URL } from "@/lib/api";
+import { Code } from "@/components/docs/DocsUI";
import { toast } from "sonner";
import { WalletPageShell } from "@/components/dashboard/WalletPageShell";
@@ -183,17 +184,12 @@ export default function DevelopersPage({
{/* quickstart */}
{keyInfo?.configured && (
-
-
- Quickstart — generate a deposit address
-
-
- {`curl -X POST \\
- ${apiBase()}/v1/wallets/${id}/addresses \\
- -H "authorization: Bearer " \\
+
+ {`curl -X POST \\
+ ${API_URL}/v1/wallets/${id}/addresses \\
+ -H "authorization: Bearer " \\
-H "content-type: application/json" \\
- -d '{"customer_ref":"user_123"}'`}
-
+ -d '{"customer_ref":"user_123"}'`}
)}
@@ -277,10 +273,6 @@ function RegenerateKeyModal({
);
}
-function apiBase() {
- return process.env.NEXT_PUBLIC_OCTO_API_URL ?? "http://localhost:8080";
-}
-
function maskFrom(key: string) {
const prefix = key.split("_").slice(0, 3).join("_") + "_";
return prefix + "•".repeat(Math.max(0, key.length - prefix.length));
diff --git a/src/app/dashboard/wallets/[id]/assets/page.tsx b/src/app/dashboard/wallets/[id]/assets/page.tsx
index b59aa55..2f9b98f 100644
--- a/src/app/dashboard/wallets/[id]/assets/page.tsx
+++ b/src/app/dashboard/wallets/[id]/assets/page.tsx
@@ -106,15 +106,14 @@ export default function AssetsPage({
{/* stat cards */}
-