diff --git a/example-apps/dashmint-lab/index.html b/example-apps/dashmint-lab/index.html
index c4277a3..2cf7c8d 100644
--- a/example-apps/dashmint-lab/index.html
+++ b/example-apps/dashmint-lab/index.html
@@ -5,6 +5,12 @@
DashMint Lab — Dash Platform NFTs
+
+
+
diff --git a/example-apps/dashmint-lab/src/App.tsx b/example-apps/dashmint-lab/src/App.tsx
index 149323f..ac80077 100644
--- a/example-apps/dashmint-lab/src/App.tsx
+++ b/example-apps/dashmint-lab/src/App.tsx
@@ -67,8 +67,12 @@ function App() {
}, [refreshBalance]);
// Auto-connect in browse-only mode so read tabs work without login.
+ // Defer to the next frame so the shell paints before the SDK chunk
+ // (~8MB WASM) starts downloading.
useEffect(() => {
- if (status === "idle") void browseOnly();
+ if (status !== "idle") return;
+ const raf = requestAnimationFrame(() => void browseOnly());
+ return () => cancelAnimationFrame(raf);
}, [status, browseOnly]);
// Default sub-tab: "My" when logged in, otherwise "All".
diff --git a/example-apps/dashmint-lab/src/dash/contract.ts b/example-apps/dashmint-lab/src/dash/contract.ts
index ccb2867..d7c79aa 100644
--- a/example-apps/dashmint-lab/src/dash/contract.ts
+++ b/example-apps/dashmint-lab/src/dash/contract.ts
@@ -1,5 +1,5 @@
/**
- * NFT card data contract schema + ensureContract().
+ * NFT card data contract schema + registerContract / ensureContract.
*
* WHAT: A Dash Platform "data contract" defines the schema for documents.
* This one describes a single document type (`card`) with four fields
@@ -11,13 +11,26 @@
* tradeMode: 1 — documents can be priced and purchased (0 to disable)
* creationRestrictionMode: 1 — (1 - only the contract owner can mint; 0 - anyone can mint)
*
+ * Storage helpers (loadStoredContractId, saveContractId, …) and the owner
+ * lookup live in contractStorage.ts so they can be imported without
+ * pulling the @dashevo/evo-sdk runtime into the entry bundle.
+ *
* SDK methods: new DataContract({ ... }), sdk.contracts.publish(...)
*/
import { DataContract } from "@dashevo/evo-sdk";
+import { loadStoredContractId, saveContractId } from "./contractStorage";
import type { Logger } from "./logger";
import type { DashKeyManager, DashSdk } from "./types";
+export {
+ DEFAULT_CONTRACT_ID,
+ clearStoredContractId,
+ fetchContractOwnerId,
+ loadStoredContractId,
+ saveContractId,
+} from "./contractStorage";
+
export const CARD_SCHEMAS = {
card: {
type: "object",
@@ -62,49 +75,6 @@ export const CARD_SCHEMAS = {
},
} as const;
-/**
- * Fetch the owner identity ID for a given data contract.
- *
- * SDK method: sdk.contracts.fetch(...)
- */
-export async function fetchContractOwnerId({
- sdk,
- contractId,
-}: {
- sdk: DashSdk;
- contractId: string;
-}): Promise {
- const contract = await sdk.contracts.fetch(contractId);
- if (!contract) return null;
- const json =
- typeof contract.toJSON === "function" ? contract.toJSON() : contract;
- const ownerId = json.$ownerId ?? json.ownerId ?? null;
- return ownerId ? String(ownerId) : null;
-}
-
-const STORAGE_KEY = "dashmint-lab.contractId";
-
-/**
- * Default contract ID baked into the tutorial so browse-only mode works
- * on a fresh machine without any setup. Comes from the original
- * HTML tutorial's pre-deployed testnet contract. Users can override it
- * in the Settings modal or register their own.
- */
-export const DEFAULT_CONTRACT_ID =
- "4eJR4pgV9mQdyoodfTTwFUp3SYBRJbUrJ5X1ViN2zBhY";
-
-export function loadStoredContractId(): string | null {
- return localStorage.getItem(STORAGE_KEY) ?? DEFAULT_CONTRACT_ID;
-}
-
-export function saveContractId(id: string): void {
- localStorage.setItem(STORAGE_KEY, id);
-}
-
-export function clearStoredContractId(): void {
- localStorage.removeItem(STORAGE_KEY);
-}
-
/**
* Register a fresh NFT card data contract on Platform and persist its ID.
*
diff --git a/example-apps/dashmint-lab/src/dash/contractStorage.ts b/example-apps/dashmint-lab/src/dash/contractStorage.ts
new file mode 100644
index 0000000..449e380
--- /dev/null
+++ b/example-apps/dashmint-lab/src/dash/contractStorage.ts
@@ -0,0 +1,46 @@
+/**
+ * Contract ID persistence + owner lookup. Split from contract.ts so the
+ * session bootstrap can import these helpers without dragging the
+ * @dashevo/evo-sdk module (and its WASM bundle) into the entry chunk.
+ *
+ * SDK method (fetchContractOwnerId): sdk.contracts.fetch(...)
+ */
+import type { DashSdk } from "./types";
+
+const STORAGE_KEY = "dashmint-lab.contractId";
+
+/**
+ * Default contract ID baked into the tutorial so browse-only mode works
+ * on a fresh machine without any setup. Comes from the original
+ * HTML tutorial's pre-deployed testnet contract. Users can override it
+ * in the Settings modal or register their own.
+ */
+export const DEFAULT_CONTRACT_ID =
+ "4eJR4pgV9mQdyoodfTTwFUp3SYBRJbUrJ5X1ViN2zBhY";
+
+export function loadStoredContractId(): string | null {
+ return localStorage.getItem(STORAGE_KEY) ?? DEFAULT_CONTRACT_ID;
+}
+
+export function saveContractId(id: string): void {
+ localStorage.setItem(STORAGE_KEY, id);
+}
+
+export function clearStoredContractId(): void {
+ localStorage.removeItem(STORAGE_KEY);
+}
+
+export async function fetchContractOwnerId({
+ sdk,
+ contractId,
+}: {
+ sdk: DashSdk;
+ contractId: string;
+}): Promise {
+ const contract = await sdk.contracts.fetch(contractId);
+ if (!contract) return null;
+ const json =
+ typeof contract.toJSON === "function" ? contract.toJSON() : contract;
+ const ownerId = json.$ownerId ?? json.ownerId ?? null;
+ return ownerId ? String(ownerId) : null;
+}
diff --git a/example-apps/dashmint-lab/src/session/SessionContext.tsx b/example-apps/dashmint-lab/src/session/SessionContext.tsx
index 7f74078..cd1f5f1 100644
--- a/example-apps/dashmint-lab/src/session/SessionContext.tsx
+++ b/example-apps/dashmint-lab/src/session/SessionContext.tsx
@@ -24,17 +24,38 @@ import {
type ReactNode,
} from "react";
-import { createClient } from "../dash/client";
-import { IdentityKeyManager } from "../dash/keyManager";
import {
clearStoredContractId,
fetchContractOwnerId,
loadStoredContractId,
saveContractId,
-} from "../dash/contract";
+} from "../dash/contractStorage";
import { errorMessage, type Logger } from "../dash/logger";
import type { DashKeyManager, DashSdk } from "../dash/types";
+// The SDK + IdentityKeyManager pull in @dashevo/evo-sdk (and its ~8MB WASM
+// bundle), so we load them lazily on first use to keep the app shell off
+// the critical path. Cached after first call.
+let sdkModulePromise: Promise<{
+ createClient: (network: string) => Promise;
+ IdentityKeyManager: typeof import("../../../../setupDashClient-core.mjs").IdentityKeyManager;
+}> | null = null;
+function loadSdkModule() {
+ if (!sdkModulePromise) {
+ sdkModulePromise = import("../../../../setupDashClient-core.mjs").catch(
+ (err) => {
+ // Clear the cache on failure so a subsequent connect/login retry
+ // can re-attempt the import (e.g., after a transient chunk fetch
+ // failure). Without this, every retry would await the same
+ // rejected promise and fail immediately.
+ sdkModulePromise = null;
+ throw err;
+ },
+ );
+ }
+ return sdkModulePromise;
+}
+
export type SessionStatus =
| "idle"
| "connecting"
@@ -162,6 +183,7 @@ export function SessionProvider({ children }: { children: ReactNode }) {
setStatus("connecting");
setError(null);
log("Connecting to Dash Platform testnet…");
+ const { createClient } = await loadSdkModule();
const connected = await createClient("testnet");
log("Connected to testnet.", "info");
setSdk(connected);
@@ -176,6 +198,7 @@ export function SessionProvider({ children }: { children: ReactNode }) {
try {
const connected = sdk ?? (await connect());
log("Deriving identity keys from mnemonic…");
+ const { IdentityKeyManager } = await loadSdkModule();
const km = await IdentityKeyManager.create({
sdk: connected,
mnemonic: trimmed,
diff --git a/example-apps/dashmint-lab/src/styles/globals.css b/example-apps/dashmint-lab/src/styles/globals.css
index ff30c04..551994f 100644
--- a/example-apps/dashmint-lab/src/styles/globals.css
+++ b/example-apps/dashmint-lab/src/styles/globals.css
@@ -1,4 +1,6 @@
-@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap");
+/* Google Fonts (Inter + JetBrains Mono) loaded via in index.html
+ * with rel="preconnect" so the font fetch parallelizes with JS download
+ * instead of being serialized behind this stylesheet. */
@import "tailwindcss";
/*
diff --git a/example-apps/dashmint-lab/test/SessionContext.test.tsx b/example-apps/dashmint-lab/test/SessionContext.test.tsx
index 238c5db..3faba2f 100644
--- a/example-apps/dashmint-lab/test/SessionContext.test.tsx
+++ b/example-apps/dashmint-lab/test/SessionContext.test.tsx
@@ -34,17 +34,16 @@ const {
mockToastError: vi.fn(),
}));
-vi.mock("../src/dash/client", () => ({
+// SessionContext dynamic-imports the SDK core module directly (not via the
+// app's client.ts/keyManager.ts wrappers), so mock that module instead.
+vi.mock("../../../setupDashClient-core.mjs", () => ({
createClient: mockCreateClient,
-}));
-
-vi.mock("../src/dash/keyManager", () => ({
IdentityKeyManager: {
create: mockIdentityKeyManagerCreate,
},
}));
-vi.mock("../src/dash/contract", () => ({
+vi.mock("../src/dash/contractStorage", () => ({
DEFAULT_CONTRACT_ID: "default-contract-id",
fetchContractOwnerId: mockFetchContractOwnerId,
loadStoredContractId: mockLoadStoredContractId,
diff --git a/example-apps/dashmint-lab/vite.config.ts b/example-apps/dashmint-lab/vite.config.ts
index 274d206..aaabc8e 100644
--- a/example-apps/dashmint-lab/vite.config.ts
+++ b/example-apps/dashmint-lab/vite.config.ts
@@ -23,6 +23,18 @@ export default defineConfig({
},
},
plugins: [react(), tailwindcss()],
+ build: {
+ // Vite auto-injects for every dynamic-import
+ // chunk it discovers at build time. For the ~8MB Evo SDK + WASM chunk
+ // this defeats the whole point of dynamic-importing it: the browser
+ // races to fetch the SDK in parallel with the entry chunk, blocking
+ // FCP. Strip the SDK preload so it only fetches when SessionContext
+ // actually triggers the dynamic import.
+ modulePreload: {
+ resolveDependencies: (_filename, deps) =>
+ deps.filter((d) => !d.includes("evo-sdk")),
+ },
+ },
test: {
environment: "node",
include: ["test/**/*.test.{ts,tsx}"],