From 6170455a350823ed8320e38034f16c30f89fd034 Mon Sep 17 00:00:00 2001 From: Lukas <64620972+lamentierschweinchen@users.noreply.github.com> Date: Wed, 15 Jul 2026 22:53:07 +0200 Subject: [PATCH 01/13] cookbook design layer: components + swizzle + scoped CSS + llms extension --- .../sdk-js/cookbook-preview-recipe.mdx | 57 + .../sdk-and-tools/sdk-js/cookbook-preview.mdx | 82 + docusaurus.config.js | 2 +- scripts/generate-llms-txt.js | 23 +- sidebars.js | 11 + .../cookbook/DifficultyDots/index.jsx | 29 + .../cookbook/DifficultyDots/styles.module.css | 32 + src/components/cookbook/MetaChip/index.jsx | 17 + .../cookbook/MetaChip/styles.module.css | 31 + src/components/cookbook/RecipeCard/index.jsx | 50 + .../cookbook/RecipeCard/styles.module.css | 65 + src/components/cookbook/RecipeGrid/index.jsx | 15 + .../cookbook/RecipeGrid/styles.module.css | 6 + src/components/cookbook/RecipeMeta/index.jsx | 50 + .../cookbook/RecipeMeta/styles.module.css | 19 + .../cookbook/VerifiedBadge/index.jsx | 40 + .../cookbook/VerifiedBadge/styles.module.css | 43 + src/css/cookbook.css | 54 + src/theme/DocItem/Content/index.js | 32 + static/llms-full.txt | 2490 ++++++++++++++++- static/llms.txt | 4 +- 21 files changed, 3101 insertions(+), 51 deletions(-) create mode 100644 docs/sdk-and-tools/sdk-js/cookbook-preview-recipe.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook-preview.mdx create mode 100644 src/components/cookbook/DifficultyDots/index.jsx create mode 100644 src/components/cookbook/DifficultyDots/styles.module.css create mode 100644 src/components/cookbook/MetaChip/index.jsx create mode 100644 src/components/cookbook/MetaChip/styles.module.css create mode 100644 src/components/cookbook/RecipeCard/index.jsx create mode 100644 src/components/cookbook/RecipeCard/styles.module.css create mode 100644 src/components/cookbook/RecipeGrid/index.jsx create mode 100644 src/components/cookbook/RecipeGrid/styles.module.css create mode 100644 src/components/cookbook/RecipeMeta/index.jsx create mode 100644 src/components/cookbook/RecipeMeta/styles.module.css create mode 100644 src/components/cookbook/VerifiedBadge/index.jsx create mode 100644 src/components/cookbook/VerifiedBadge/styles.module.css create mode 100644 src/css/cookbook.css create mode 100644 src/theme/DocItem/Content/index.js diff --git a/docs/sdk-and-tools/sdk-js/cookbook-preview-recipe.mdx b/docs/sdk-and-tools/sdk-js/cookbook-preview-recipe.mdx new file mode 100644 index 000000000..edd86c198 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook-preview-recipe.mdx @@ -0,0 +1,57 @@ +--- +title: Send EGLD to an address +sidebar_label: Sample recipe — Send EGLD +description: Send a native EGLD transfer with sdk-core's TransfersController against DevnetEntrypoint — the backend/script pattern for when your code holds the key. +difficulty: beginner +est_minutes: 4 +last_validated: "2026-07-07" +sdk_versions: + sdk-core: "^15.4.0" +tags: + - sdk-core + - transaction + - devnet +--- + +:::note Design-layer demo +This page exists to demonstrate the recipe **metadata strip** (above the title): +difficulty dots, estimated time, SDK-version chips, and the teal verified badge — +all read automatically from this page's frontmatter by the `DocItem/Content` +swizzle. The recipe prose itself is authored on a separate track; the short +content below is only here to show code-block and admonition styling inside a +recipe. +::: + +Install and run the recipe project: + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/send-egld +npm install +npm start -- ./wallet.pem erd1... 1.5 +``` + +A native EGLD transfer built with sdk-core's `TransfersController`: + +```ts +const entrypoint = new DevnetEntrypoint(); +const controller = entrypoint.createTransfersController(); + +const tx = await controller.createTransactionForTransfer( + sender, + sender.getNonceThenIncrement(), + { + receiver: new Address(receiverBech32), + nativeAmount: 1_500_000_000_000_000_000n, // 1.5 EGLD (10^18 base units) + } +); + +const hash = await entrypoint.sendTransaction(tx); +``` + +:::tip Verified +The badge above turns teal only while the recipe's nightly CI recheck is green. +If a recheck fails, the frontmatter flips `stale: true` and the same badge +degrades to a neutral "Needs recheck" state — the teal never means anything but +"trustworthy right now". +::: diff --git a/docs/sdk-and-tools/sdk-js/cookbook-preview.mdx b/docs/sdk-and-tools/sdk-js/cookbook-preview.mdx new file mode 100644 index 000000000..96f0e7f0c --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook-preview.mdx @@ -0,0 +1,82 @@ +--- +title: Cookbook (design preview) +sidebar_label: Cookbook (design preview) +description: Design-layer preview of the MultiversX Cookbook recipe system — recipe cards, difficulty, verified badges and SDK-version chips rendered from real frontmatter. +--- + +import RecipeGrid from "@site/src/components/cookbook/RecipeGrid"; + +This page previews the **Cookbook design layer** — the recipe-card index and the +per-recipe metadata strip, built as standard Docusaurus components on the +MultiversX design tokens. Every chip maps to a real frontmatter field +(`difficulty`, `sdk_versions`, `last_validated`, `stale`, `tags`), so +"CI-verified" is the visual system rather than decoration. The teal accent is +reserved for the single load-bearing idea: a recipe is verified. + +Card data below is a representative sample pulled from the verified recipe +sources; wiring the full recipe set into this grid is a later track. + + diff --git a/docusaurus.config.js b/docusaurus.config.js index 4e9fe022b..34032c438 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -76,7 +76,7 @@ const config = { }, blog: false, theme: { - customCss: "./src/css/custom.css", + customCss: ["./src/css/custom.css", "./src/css/cookbook.css"], }, gtag: { trackingID: "G-TW3LCJ0LS7", diff --git a/scripts/generate-llms-txt.js b/scripts/generate-llms-txt.js index 91d56629d..c7fad39bc 100644 --- a/scripts/generate-llms-txt.js +++ b/scripts/generate-llms-txt.js @@ -196,11 +196,18 @@ function parseFrontmatter(mdContent) { title: block.match(/^\s*title:\s*(["']?)(.+?)\1\s*$/m), slug: block.match(/^\s*slug:\s*(["']?)(.+?)\1\s*$/m), description: block.match(/^\s*description:\s*(["']?)([\s\S]*?)\1\s*$/m), + // Cookbook recipe frontmatter — surfaced in the agent-ingestible llms.txt + // so a coding agent sees difficulty + CI-verified date alongside each + // recipe. Optional: only cookbook pages declare these fields. + difficulty: block.match(/^\s*difficulty:\s*(["']?)(.+?)\1\s*$/m), + last_validated: block.match(/^\s*last_validated:\s*(["']?)(.+?)\1\s*$/m), }; if (pairs.id) meta.id = pairs.id[2].trim(); if (pairs.title) meta.title = pairs.title[2].trim(); if (pairs.slug) meta.slug = pairs.slug[2].trim(); if (pairs.description) meta.description = pairs.description[2].trim(); + if (pairs.difficulty) meta.difficulty = pairs.difficulty[2].trim(); + if (pairs.last_validated) meta.lastValidated = pairs.last_validated[2].trim(); meta._fmEnd = end + '\n---'.length; return meta; } @@ -302,7 +309,7 @@ async function getDocMeta(docId) { title = `${humanizeSegmentForTitle(parent)} ${title}`; } } - meta = { ...meta, title, description, filePath, source: fromFm ? 'frontmatter' : (description ? 'content' : 'none') }; + meta = { ...meta, title, description, filePath, source: fromFm ? 'frontmatter' : (description ? 'content' : 'none'), difficulty: fm.difficulty, lastValidated: fm.lastValidated }; } catch { // ignore } @@ -393,7 +400,7 @@ async function main() { const meta = await getDocMeta(id); // eslint-disable-next-line no-await-in-loop const url = await computeUrlForDoc(id, siteUrl); - all.push({ id, title: meta.title, description: meta.description, url, source: meta.source, filePath: meta.filePath }); + all.push({ id, title: meta.title, description: meta.description, url, source: meta.source, filePath: meta.filePath, difficulty: meta.difficulty, lastValidated: meta.lastValidated }); } all.sort((a, b) => a.title.localeCompare(b.title)); for (const e of all) { @@ -402,7 +409,17 @@ async function main() { desc = generatedDescriptionFromPath(e.id, e.filePath, brand); } const clipped = desc.length > 400 ? `${desc.slice(0, 397)}...` : desc; - outputLines.push(`- [${e.title}](${e.url})${clipped ? `: ${clipped}` : ''}`); + // Cookbook pages append a compact, machine-parseable metadata tag so the + // agent surface carries difficulty + CI-verified date. Only pages that + // declare `difficulty` frontmatter are affected; all other entries are + // byte-for-byte unchanged. + let recipeTag = ''; + if (e.difficulty) { + const bits = [e.difficulty]; + if (e.lastValidated) bits.push(`verified ${e.lastValidated}`); + recipeTag = ` [${bits.join(', ')}]`; + } + outputLines.push(`- [${e.title}](${e.url})${clipped ? `: ${clipped}` : ''}${recipeTag}`); // Build full content entry if (e.filePath) { diff --git a/sidebars.js b/sidebars.js index 61e0612fe..ab3d8e3a2 100644 --- a/sidebars.js +++ b/sidebars.js @@ -233,6 +233,17 @@ const sidebars = { "sdk-and-tools/sdk-js/sdk-js-cookbook", ] }, + { + type: "category", + label: "Cookbook (design preview)", + link: { + type: "doc", + id: "sdk-and-tools/sdk-js/cookbook-preview" + }, + items: [ + "sdk-and-tools/sdk-js/cookbook-preview-recipe", + ] + }, "sdk-and-tools/sdk-js/extending-sdk-js", "sdk-and-tools/sdk-js/writing-and-testing-sdk-js-interactions", "sdk-and-tools/sdk-js/sdk-js-signing-providers", diff --git a/src/components/cookbook/DifficultyDots/index.jsx b/src/components/cookbook/DifficultyDots/index.jsx new file mode 100644 index 000000000..136bff297 --- /dev/null +++ b/src/components/cookbook/DifficultyDots/index.jsx @@ -0,0 +1,29 @@ +import React from "react"; +import styles from "./styles.module.css"; + +// Difficulty is drawn as filled dots (1 = beginner, 3 = advanced). Deliberately +// neutral-toned — structure, not accent — so it never competes with the teal +// "verified" signal. +const LEVELS = { beginner: 1, intermediate: 2, advanced: 3 }; + +export default function DifficultyDots({ difficulty }) { + const level = LEVELS[difficulty] ?? 0; + const label = difficulty + ? difficulty.charAt(0).toUpperCase() + difficulty.slice(1) + : "Unknown"; + + return ( + + + ); +} diff --git a/src/components/cookbook/DifficultyDots/styles.module.css b/src/components/cookbook/DifficultyDots/styles.module.css new file mode 100644 index 000000000..43f124ff4 --- /dev/null +++ b/src/components/cookbook/DifficultyDots/styles.module.css @@ -0,0 +1,32 @@ +.wrap { + display: inline-flex; + align-items: center; + gap: 0.4rem; + font-size: 0.8rem; +} + +.dots { + display: inline-flex; + gap: 3px; +} + +.dotOn, +.dotOff { + width: 6px; + height: 6px; + border-radius: 50%; + display: inline-block; +} + +.dotOn { + background: var(--ifm-color-emphasis-800); +} + +.dotOff { + background: var(--ifm-color-emphasis-300); +} + +.label { + color: var(--ifm-color-emphasis-700); + font-weight: 500; +} diff --git a/src/components/cookbook/MetaChip/index.jsx b/src/components/cookbook/MetaChip/index.jsx new file mode 100644 index 000000000..a86b46b0d --- /dev/null +++ b/src/components/cookbook/MetaChip/index.jsx @@ -0,0 +1,17 @@ +import React from "react"; +import styles from "./styles.module.css"; + +// A hairline pill for one piece of recipe metadata (est-time, an SDK version, +// etc.). `mono` renders the value in JetBrains Mono with tabular figures, the +// house treatment for anything version- or number-like. +export default function MetaChip({ label, value, mono = false, title }) { + return ( + + {label && {label}} + {value} + + ); +} diff --git a/src/components/cookbook/MetaChip/styles.module.css b/src/components/cookbook/MetaChip/styles.module.css new file mode 100644 index 000000000..b375ec1cd --- /dev/null +++ b/src/components/cookbook/MetaChip/styles.module.css @@ -0,0 +1,31 @@ +.chip { + display: inline-flex; + align-items: baseline; + gap: 0.35rem; + font-size: 0.76rem; + line-height: 1.4; + padding: 0.22rem 0.5rem; + border-radius: 6px; + border: 1px solid var(--ifm-color-emphasis-200); + background: var(--ifm-color-emphasis-100); + color: var(--ifm-color-emphasis-800); + white-space: nowrap; +} + +.label { + color: var(--ifm-color-emphasis-600); + text-transform: uppercase; + letter-spacing: 0.03em; + font-size: 0.66rem; + font-weight: 600; +} + +.value { + font-weight: 500; +} + +.valueMono { + font-family: var(--cookbook-font-mono); + font-variant-numeric: tabular-nums; + font-weight: 500; +} diff --git a/src/components/cookbook/RecipeCard/index.jsx b/src/components/cookbook/RecipeCard/index.jsx new file mode 100644 index 000000000..aa8cfb53e --- /dev/null +++ b/src/components/cookbook/RecipeCard/index.jsx @@ -0,0 +1,50 @@ +import React from "react"; +import Link from "@docusaurus/Link"; +import DifficultyDots from "@site/src/components/cookbook/DifficultyDots"; +import VerifiedBadge from "@site/src/components/cookbook/VerifiedBadge"; +import styles from "./styles.module.css"; + +// One recipe as a card in the browsable index. Props map to the same recipe +// frontmatter fields the swizzle reads, so a card and its page always agree. +export default function RecipeCard({ recipe = {} }) { + const { + title, + description, + href = "#", + difficulty, + lastValidated, + stale, + tags = [], + sdkVersions, + } = recipe; + + const primaryVersion = + sdkVersions && typeof sdkVersions === "object" + ? Object.entries(sdkVersions).find(([, range]) => Boolean(range)) + : null; + + return ( + +
+ {difficulty && } + +
+ +
{title}
+ {description &&

{description}

} + +
+ {tags.slice(0, 3).map((t) => ( + + {t} + + ))} + {primaryVersion && ( + + {primaryVersion[0]} {primaryVersion[1]} + + )} +
+ + ); +} diff --git a/src/components/cookbook/RecipeCard/styles.module.css b/src/components/cookbook/RecipeCard/styles.module.css new file mode 100644 index 000000000..26fffc2f2 --- /dev/null +++ b/src/components/cookbook/RecipeCard/styles.module.css @@ -0,0 +1,65 @@ +.card { + display: flex; + flex-direction: column; + gap: 0.6rem; + padding: 1rem 1.1rem; + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 10px; + background: var(--ifm-background-color); + text-decoration: none !important; + color: var(--ifm-color-content); + transition: border-color 0.15s ease, transform 0.15s ease; + height: 100%; +} + +.card:hover { + border-color: var(--cookbook-accent); + transform: translateY(-1px); +} + +.head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; +} + +.title { + font-weight: 600; + font-size: 1.02rem; + line-height: 1.3; + color: var(--ifm-heading-color); +} + +.desc { + margin: 0; + font-size: 0.85rem; + line-height: 1.45; + color: var(--ifm-color-emphasis-700); +} + +.foot { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.35rem; + margin-top: auto; + padding-top: 0.4rem; +} + +.tag { + font-size: 0.68rem; + padding: 0.12rem 0.4rem; + border-radius: 4px; + background: var(--ifm-color-emphasis-100); + color: var(--ifm-color-emphasis-700); + border: 1px solid var(--ifm-color-emphasis-200); +} + +.version { + margin-left: auto; + font-family: var(--cookbook-font-mono); + font-size: 0.7rem; + color: var(--ifm-color-emphasis-600); + font-variant-numeric: tabular-nums; +} diff --git a/src/components/cookbook/RecipeGrid/index.jsx b/src/components/cookbook/RecipeGrid/index.jsx new file mode 100644 index 000000000..3cbf6730e --- /dev/null +++ b/src/components/cookbook/RecipeGrid/index.jsx @@ -0,0 +1,15 @@ +import React from "react"; +import RecipeCard from "@site/src/components/cookbook/RecipeCard"; +import styles from "./styles.module.css"; + +// Responsive grid of RecipeCards. Usable directly in MDX: +// +export default function RecipeGrid({ recipes = [] }) { + return ( +
+ {recipes.map((r) => ( + + ))} +
+ ); +} diff --git a/src/components/cookbook/RecipeGrid/styles.module.css b/src/components/cookbook/RecipeGrid/styles.module.css new file mode 100644 index 000000000..9a73fe267 --- /dev/null +++ b/src/components/cookbook/RecipeGrid/styles.module.css @@ -0,0 +1,6 @@ +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 1rem; + margin: 1.5rem 0; +} diff --git a/src/components/cookbook/RecipeMeta/index.jsx b/src/components/cookbook/RecipeMeta/index.jsx new file mode 100644 index 000000000..206a4ecb1 --- /dev/null +++ b/src/components/cookbook/RecipeMeta/index.jsx @@ -0,0 +1,50 @@ +import React from "react"; +import DifficultyDots from "@site/src/components/cookbook/DifficultyDots"; +import VerifiedBadge from "@site/src/components/cookbook/VerifiedBadge"; +import MetaChip from "@site/src/components/cookbook/MetaChip"; +import styles from "./styles.module.css"; + +// The metadata strip rendered above every recipe title by the DocItem/Content +// swizzle. Every item maps to a real frontmatter field — difficulty, +// est_minutes, sdk_versions, last_validated, stale — so the strip reflects the +// product (CI-verified recipes), never decoration. Each field renders only when +// present, so partial frontmatter degrades gracefully. +export default function RecipeMeta({ frontMatter = {} }) { + const { + difficulty, + est_minutes: estMinutes, + last_validated: lastValidated, + stale, + sdk_versions: sdkVersions, + } = frontMatter; + + const versions = + sdkVersions && typeof sdkVersions === "object" + ? Object.entries(sdkVersions).filter(([, range]) => Boolean(range)) + : []; + + return ( +
+ {difficulty && } + + {typeof estMinutes === "number" && ( + + )} + + {versions.map(([name, range]) => ( + + ))} + + {(lastValidated || stale) && ( + + + + )} +
+ ); +} diff --git a/src/components/cookbook/RecipeMeta/styles.module.css b/src/components/cookbook/RecipeMeta/styles.module.css new file mode 100644 index 000000000..076d71a25 --- /dev/null +++ b/src/components/cookbook/RecipeMeta/styles.module.css @@ -0,0 +1,19 @@ +.strip { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.6rem; + margin: 0 0 1.25rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--ifm-color-emphasis-200); +} + +.trailing { + margin-left: auto; +} + +@media (max-width: 600px) { + .trailing { + margin-left: 0; + } +} diff --git a/src/components/cookbook/VerifiedBadge/index.jsx b/src/components/cookbook/VerifiedBadge/index.jsx new file mode 100644 index 000000000..1bf4d483e --- /dev/null +++ b/src/components/cookbook/VerifiedBadge/index.jsx @@ -0,0 +1,40 @@ +import React from "react"; +import styles from "./styles.module.css"; + +// The single load-bearing teal element in the whole design layer: it signals +// that a recipe compiles green / is CI-verified. When the nightly recheck marks +// a recipe stale, it degrades to a neutral-warning state instead of teal, so the +// teal only ever means "trustworthy right now". +export default function VerifiedBadge({ date, stale = false, compact = false }) { + if (stale) { + return ( + + + {compact ? "Stale" : "Needs recheck"} + + ); + } + + return ( + + + {compact || !date ? "Verified" : `Verified ${date}`} + + ); +} diff --git a/src/components/cookbook/VerifiedBadge/styles.module.css b/src/components/cookbook/VerifiedBadge/styles.module.css new file mode 100644 index 000000000..b0fcffd54 --- /dev/null +++ b/src/components/cookbook/VerifiedBadge/styles.module.css @@ -0,0 +1,43 @@ +.badge { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font-size: 0.78rem; + font-weight: 600; + line-height: 1; + padding: 0.28rem 0.5rem; + border-radius: 999px; + font-family: var(--cookbook-font-mono); + font-variant-numeric: tabular-nums; + border: 1px solid transparent; + white-space: nowrap; +} + +.ok { + color: var(--cookbook-accent); + border-color: color-mix(in srgb, var(--cookbook-accent) 40%, transparent); + background: var(--cookbook-accent-soft); +} + +.stale { + color: #8a6d00; + border-color: color-mix(in srgb, var(--cookbook-yellow) 55%, transparent); + background: color-mix(in srgb, var(--cookbook-yellow) 12%, transparent); +} + +html[data-theme="dark"] .stale { + color: var(--cookbook-yellow); +} + +.icon { + display: inline-flex; +} + +.bang { + display: inline-flex; + align-items: center; + justify-content: center; + width: 12px; + height: 12px; + font-weight: 700; +} diff --git a/src/css/cookbook.css b/src/css/cookbook.css new file mode 100644 index 000000000..39091eeba --- /dev/null +++ b/src/css/cookbook.css @@ -0,0 +1,54 @@ +/** + * cookbook.css — MultiversX Cookbook design layer (scoped, additive). + * + * SCOPE DISCIPLINE: every rule here is namespaced under `.cookbook-recipe` + * (added by the DocItem/Content swizzle only on recipe pages) or consumed by + * the CSS-module components under src/components/cookbook/. Nothing here + * restyles global docs chrome (navbar / sidebar / footer / search). The only + * top-level declarations are CSS custom properties, which are inert until + * referenced, so registering this file globally cannot regress other pages. + * + * Tokens are the canonical MvX values (design-reference/MVX-DESIGN-LANGUAGE.md): + * accent teal #23f7dd, near-black grounds, Roobert + JetBrains Mono. Structure + * is driven by Infima neutral variables so components track the light/dark + * toggle automatically; the teal accent is reserved for the single load-bearing + * idea — "this recipe is verified" — never to tint structure. + */ + +:root { + /* MvX brand tokens. */ + --cookbook-teal: #23f7dd; + --cookbook-green: #4ade80; + --cookbook-yellow: #facc15; + + /* Accent is theme-aware: mint collapses to ~1.2:1 on light grounds and is + unreadable as text, so light mode uses the brand system's own accessible + deep-teal ink (5.3:1 AA), not a straight color-invert. */ + --cookbook-accent: #0b6f63; + --cookbook-accent-soft: rgba(11, 111, 99, 0.1); + + /* Roobert is already loaded site-wide (src/css/custom.css @font-face). + JetBrains Mono is not bundled here, so fall back cleanly to system mono. */ + --cookbook-font-brand: "Roobert", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + --cookbook-font-mono: "JetBrains Mono", "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace; +} + +html[data-theme="dark"] { + --cookbook-accent: #23f7dd; + --cookbook-accent-soft: rgba(35, 247, 221, 0.1); +} + +/* ---- Recipe article treatment (scoped: only present on recipe pages) ---- */ +.cookbook-recipe { + font-family: var(--cookbook-font-brand); +} + +.cookbook-recipe code, +.cookbook-recipe kbd, +.cookbook-recipe pre { + font-family: var(--cookbook-font-mono); +} + +.cookbook-recipe :is(h1, h2, h3) { + letter-spacing: -0.01em; +} diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js new file mode 100644 index 000000000..35f1c69a3 --- /dev/null +++ b/src/theme/DocItem/Content/index.js @@ -0,0 +1,32 @@ +import React from "react"; +import Content from "@theme-original/DocItem/Content"; +import { useDoc } from "@docusaurus/plugin-content-docs/client"; +import RecipeMeta from "@site/src/components/cookbook/RecipeMeta"; + +/** + * Wraps the original DocItem/Content. + * + * On cookbook recipe pages — identified by the presence of the recipe-specific + * frontmatter fields (`difficulty` + `sdk_versions`) — it renders the metadata + * strip above the title and scopes the recipe design layer via the + * `.cookbook-recipe` class. Every other doc page is returned untouched (same + * element, same props, no wrapper), so this swizzle cannot change the + * appearance of anything outside the cookbook. + */ +export default function ContentWrapper(props) { + const { frontMatter } = useDoc(); + const isRecipe = Boolean( + frontMatter && frontMatter.difficulty && frontMatter.sdk_versions + ); + + if (!isRecipe) { + return ; + } + + return ( +
+ + +
+ ); +} diff --git a/static/llms-full.txt b/static/llms-full.txt index 537252bb6..dbaf99e30 100644 --- a/static/llms-full.txt +++ b/static/llms-full.txt @@ -4902,11 +4902,92 @@ At the time of writing, the most used constants values for mainnet were: --- +### Cookbook (design preview) + +import RecipeGrid from "@site/src/components/cookbook/RecipeGrid"; + +This page previews the **Cookbook design layer** — the recipe-card index and the +per-recipe metadata strip, built as standard Docusaurus components on the +MultiversX design tokens. Every chip maps to a real frontmatter field +(`difficulty`, `sdk_versions`, `last_validated`, `stale`, `tags`), so +"CI-verified" is the visual system rather than decoration. The teal accent is +reserved for the single load-bearing idea: a recipe is verified. + +Card data below is a representative sample pulled from the verified recipe +sources; wiring the full recipe set into this grid is a later track. + + + +--- + ### Cookbook (v14) ## Overview -This guide walks you through handling common tasks using the MultiversX Javascript SDK (v14, latest stable version). +This guide walks you through handling common tasks using the MultiversX Javascript SDK (v14, old stable version). :::important This cookbook makes use of `sdk-js v14`. In order to migrate from `sdk-js v13.x` to `sdk-js v14`, please also follow [the migration guide](https://github.com/multiversx/mx-sdk-js-core/issues/576). @@ -8348,7 +8429,7 @@ Then, on the receiving side, you can use [`MessageComputer.unpackMessage()`](htt ## Overview -This guide walks you through handling common tasks using the MultiversX Javascript SDK (v14, latest stable version). +This guide walks you through handling common tasks using the MultiversX Javascript SDK (v15, latest stable version). :::important This cookbook makes use of `sdk-js v15`. In order to migrate from `sdk-js v14.x` to `sdk-js v15`, please also follow [the migration guide](https://github.com/multiversx/mx-sdk-js-core/issues/648). @@ -8560,9 +8641,9 @@ When manually instantiating a network provider, you can provide a configuration } ``` -Here you can find a full list of available methods for [`ApiNetworkProvider`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/ApiNetworkProvider.html). +Here you can find a full list of available methods for [`ApiNetworkProvider`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/ApiNetworkProvider.html). -Both `ApiNetworkProvider` and `ProxyNetworkProvider` implement a common interface, which can be found [here](https://multiversx.github.io/mx-sdk-js-core/v14/interfaces/INetworkProvider.html). This allows them to be used interchangeably. +Both `ApiNetworkProvider` and `ProxyNetworkProvider` implement a common interface, which can be found [here](https://multiversx.github.io/mx-sdk-js-core/v15/interfaces/INetworkProvider.html). This allows them to be used interchangeably. The classes returned by the API expose the most commonly used fields directly for convenience. However, each object also contains a `raw` field that stores the original API response, allowing access to additional fields if needed. @@ -9291,10 +9372,10 @@ This allows arguments to be passed as native Javascript values. If the ABI is no ``` :::tip -When creating transactions using [`SmartContractController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/SmartContractController.html) or [`SmartContractTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/SmartContractTransactionsFactory.html), even if the ABI is available and provided, -you can still use [`TypedValue`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/TypedValue.html) objects as arguments for deployments and interactions. +When creating transactions using [`SmartContractController`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/SmartContractController.html) or [`SmartContractTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/SmartContractTransactionsFactory.html), even if the ABI is available and provided, +you can still use [`TypedValue`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/TypedValue.html) objects as arguments for deployments and interactions. -Even further, you can use a mix of [`TypedValue`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/TypedValue.html) objects and plain JavaScript values and objects. For example: +Even further, you can use a mix of [`TypedValue`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/TypedValue.html) objects and plain JavaScript values and objects. For example: ```js let args = [new U32Value(42), "hello", { foo: "bar" }, new TokenIdentifierValue("TEST-abcdef")]; @@ -10121,8 +10202,8 @@ For scripts or quick network interactions, we recommend using the controller. Ho These are just a few examples of what you can do using the token management controller or factory. For a complete list of supported methods, please refer to the autogenerated documentation: -- [`TokenManagementController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/TokenManagementController.html) -- [`TokenManagementTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/TokenManagementTransactionsFactory.html) +- [`TokenManagementController`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/TokenManagementController.html) +- [`TokenManagementTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/TokenManagementTransactionsFactory.html) ### Account management @@ -10369,8 +10450,8 @@ In this section, we'll cover how to: - Undelegate and withdraw funds These operations can be performed using both the controller and the **factory**. For a complete list of supported methods, please refer to the autogenerated documentation: -- [`DelegationController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/DelegationController.html) -- [`DelegationTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/DelegationTransactionsFactory.html) +- [`DelegationController`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/DelegationController.html) +- [`DelegationTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/DelegationTransactionsFactory.html) #### Creating a New Delegation Contract Using the Controller ```js @@ -10990,8 +11071,8 @@ We can deploy a multisig smart contract, add members, propose and execute action The same as the other components, to interact with a multisig smart contract we can use either the MultisigController or the MultisigTransactionsFactory. These operations can be performed using both the **controller** and the **factory**. For a complete list of supported methods, please refer to the autogenerated documentation: -- [`MultisigController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigController.html) -- [`MultisigTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigTransactionsFactory.html) +- [`MultisigController`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/MultisigController.html) +- [`MultisigTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/MultisigTransactionsFactory.html) #### Deploying a Multisig Smart Contract using the controller ```js @@ -11168,8 +11249,8 @@ Let's query the contract to get all board members. We can create transactions for creating a new governance proposal, vote for a proposal or query the governance contract. These operations can be performed using both the **controller** and the **factory**. For a complete list of supported methods, please refer to the autogenerated documentation: -- [`GovernanceController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/GovernanceController.html) -- [`GovernanceTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/GovernanceTransactionsFactory.html) +- [`GovernanceController`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/GovernanceController.html) +- [`GovernanceTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/GovernanceTransactionsFactory.html) #### Creating a new proposal using the controller ```js @@ -11777,7 +11858,7 @@ To prepare a message for transmission, you can use the `MessageComputer.packMess } ``` -Then, on the receiving side, you can use [`MessageComputer.unpackMessage()`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MessageComputer.html#unpackMessage) to reconstruct the message, prior verification: +Then, on the receiving side, you can use [`MessageComputer.unpackMessage()`](https://multiversx.github.io/mx-sdk-js-core/v15/classes/MessageComputer.html#unpackMessage) to reconstruct the message, prior verification: ```js { @@ -19583,6 +19664,14 @@ Proxy/Gateway endpoints are referred as `https://gateway.multiversx.com/....`, w Currently, authentication is not needed to access the API. +## **Rate Limits** + +The public Gateway endpoints use a rate-limiting mechanism to ensure infrastructure stability and fair resource distribution. The limitations are as follows: + +* **gateway.multiversx.com (_Mainnet_):** Maximum of **50 requests / IP / second**. +* **devnet-gateway.multiversx.com (_Devnet_):** Maximum of **50 requests / IP / second**. + + ## **HTTP Response format** Each request against the MultiversX API will resolve to a JSON response having the following structure: @@ -19629,6 +19718,7 @@ In the case of an **error**, the `data` field is unset, the `error` field contai :::important When describing each HTTP endpoint on the following pages, the basic structure of the response is **simplified for brevity,** and, in general, only the actual payload of the response is depicted. ::: +``` --- @@ -23468,12 +23558,18 @@ An API instance can be started with the following behavior: - events notifier: perform various decisions based on incoming logs & events - subscription: used to manage subscriptions, fetch and broadcast data to subscribers -## Rate limiting +### Rate Limits + +Public MultiversX APIs utilize a protective rate-limiting mechanism to ensure network stability. The following limitations apply: + +#### HTTP Requests (REST API) +* **api.multiversx.com (_Mainnet_):** Maximum of **2 requests / IP / second**. +* **devnet-api.multiversx.com (_Devnet_):** Maximum of **5 requests / IP / second**. -Public MultiversX APIs have a rate limit mechanism that brings the following limitations: +--- -- api.multiversx.com (_mainnet_): 2 requests / IP / second -- devnet-api.multiversx.com (_devnet_): 5 requests / IP / second +#### WebSocket Subscriptions +* **Subscription Limit:** A single client can create a maximum of **10 different subscription variants** (active parallel subscriptions). ## Rest API documentation @@ -40004,6 +40100,53 @@ It can also be pre-initialized / initialized with blockchain state from other ne --- +### Send EGLD to an address + +:::note Design-layer demo +This page exists to demonstrate the recipe **metadata strip** (above the title): +difficulty dots, estimated time, SDK-version chips, and the teal verified badge — +all read automatically from this page's frontmatter by the `DocItem/Content` +swizzle. The recipe prose itself is authored on a separate track; the short +content below is only here to show code-block and admonition styling inside a +recipe. +::: + +Install and run the recipe project: + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/send-egld +npm install +npm start -- ./wallet.pem erd1... 1.5 +``` + +A native EGLD transfer built with sdk-core's `TransfersController`: + +```ts +const entrypoint = new DevnetEntrypoint(); +const controller = entrypoint.createTransfersController(); + +const tx = await controller.createTransactionForTransfer( + sender, + sender.getNonceThenIncrement(), + { + receiver: new Address(receiverBech32), + nativeAmount: 1_500_000_000_000_000_000n, // 1.5 EGLD (10^18 base units) + } +); + +const hash = await entrypoint.sendTransaction(tx); +``` + +:::tip Verified +The badge above turns teal only while the recipe's nightly CI recheck is green. +If a recheck fails, the frontmatter flips `stale: true` and the same badge +degrades to a neutral "Needs recheck" state — the teal never means anything but +"trustworthy right now". +::: + +--- + ### Sender ## Overview @@ -51162,7 +51305,7 @@ The node chosen to propose the block for a specific round will: Observe that the loss is 4 times larger than the gain, which means that a proposer must succeed 4 times to gain the points lost for a single missed block. -Rating for proposers is even stricter: there is a compounding penalty rule, which makes the rating of a node drop even faster when it proposes unsuccessfuly. +Rating for proposers is even stricter: there is a compounding penalty rule, which makes the rating of a node drop even faster when it proposes unsuccessfully. The amount of `0.92592` points is deducted from the rating of the proposer on the first unsuccessful proposal, but the second unsuccessful proposal will be penalized by `0.92592 × 1.1`. The third, by `0.92592 × 1.1 × 1.1`. The general formula is: @@ -60291,46 +60434,2301 @@ Also, to ensure maximum protection we strongly recommend you access your account ## Terminology ### Terminology -**Metachain**: the blockchain that runs in a special shard, where the main responsibilities are not processing transactions, -but notarizing and finalizing the processed shard block headers. +A sourced reference for MultiversX terminology. Foundational terms are included on purpose: what is obvious to readers fluent in the docs is not obvious to first-timers. Every entry carries a Source line; features in phased rollout (for example Supernova) are noted as such. + +## Blockchain foundations + +Plain-language entries for readers new to MultiversX or to blockchains. The terms here are general concepts, defined with their MultiversX specifics. The MultiversX-specific machinery follows in later sections. + +### Blockchain + +A shared, append-only ledger maintained by a network of nodes that agree on its contents through a consensus mechanism. + +Context: MultiversX is a layer-1 blockchain that settles its own transactions. Its ledger is partitioned across shards and secured by Secure Proof of Stake. + +See also: [Layer 1](#layer-1-l1), [Shard (Execution Shard)](#shard-execution-shard), [Consensus](#consensus), [Secure Proof of Stake](#secure-proof-of-stake-spos). +Read more: [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding). + +--- + +### Layer 1 (L1) + +A base blockchain that settles transactions on its own network rather than depending on another chain for security. + +Context: MultiversX is a layer-1, in contrast to layer-2 networks that post their data or proofs to a separate base chain. Its scaling comes from sharding rather than from an external settlement layer. + +See also: [Blockchain](#blockchain), [Adaptive State Sharding](#adaptive-state-sharding), [Sovereign Chains](#sovereign-chains). +Read more: [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding). + +--- + +### Block + +A batch of ordered transactions, cryptographically linked to the previous block, that extends the ledger. + +Context: On MultiversX each shard produces its own blocks, one per round, and the metachain notarizes them. A block becomes final once the shard's validators reach the consensus threshold. + +See also: [Round](#round), [Shard (Execution Shard)](#shard-execution-shard), [Metachain](#metachain), [Finality](#finality). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Transaction + +A signed instruction from an account to move value, call a smart contract, or invoke a built-in function. + +Context: A MultiversX transaction carries a sender, receiver, value, data, gas limit, gas price, nonce, and chain ID, plus optional guardian and relayer fields. It is signed by the sender's key and gossiped to the network for inclusion in a block. + +See also: [Account Nonce](#account-nonce), [Gas and Fees](#gas-and-fees), [Built-in Functions](#built-in-functions), [Onchain Guardian](#onchain-guardian). +Read more: [docs.multiversx.com/developers/transactions/tx-overview](https://docs.multiversx.com/developers/transactions/tx-overview/); [github.com/multiversx/mx-sdk-js-core](https://github.com/multiversx/mx-sdk-js-core). + +--- + +### Account + +An entry in the ledger identified by an address, holding a balance, a nonce, and (for contracts) code and storage. + +Context: MultiversX has user accounts, controlled by a key, and smart contract accounts, controlled by code. Both use the same bech32 address format; a contract account additionally holds its deployed code and storage. + +See also: [Bech32 Addressing](#bech32-addressing-erd1), [Account Nonce](#account-nonce), [Smart Contract](#smart-contract), [Account State Trie](#account-state-trie). +Read more: [docs.multiversx.com/developers/account-management](https://docs.multiversx.com/developers/account-management/). + +--- + +### Smart Contract + +A program deployed to the blockchain that runs deterministically when called, holding its own balance and storage. + +Context: On MultiversX, smart contracts compile to WebAssembly and most are written in Rust with the `multiversx-sc` framework. A contract is itself an account, callable by transactions and by other contracts. + +See also: [WASM VM](#wasm-vm-and-wasmer), [multiversx-sc (mx-sdk-rs)](#multiversx-sc-mx-sdk-rs), [Account](#account), [Tx Syntax](#tx-syntax). +Read more: [docs.multiversx.com/developers/overview](https://docs.multiversx.com/developers/overview/). + +--- + +### Consensus + +The process by which the validators of a network agree on the next block and the order of its transactions. + +Context: MultiversX uses Secure Proof of Stake: a proposer is chosen at random each round and the shard's validators sign the block, which becomes final once a two-thirds threshold is reached. + +See also: [Secure Proof of Stake](#secure-proof-of-stake-spos), [Consensus Group](#consensus-group), [Equivalent Consensus Proof](#equivalent-consensus-proof-ecp), [Finality](#finality). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Proof of Stake (PoS) + +A consensus family in which the right to validate is tied to staked tokens rather than to computational work. + +Context: Validators lock EGLD as stake and are selected to produce and sign blocks; misbehavior can cost rewards or stake. MultiversX's variant is Secure Proof of Stake. + +See also: [Secure Proof of Stake](#secure-proof-of-stake-spos), [Staking](#staking), [Validator (node)](#validator-node), [Byzantine Fault Tolerance](#byzantine-fault-tolerance-bft). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Byzantine Fault Tolerance (BFT) + +The property of a consensus protocol that lets the network agree correctly even if some participants fail or act maliciously, up to a threshold. + +Context: MultiversX tolerates faulty or adversarial validators below one-third of a shard's set; a block needs at least two-thirds of signatures to finalize, so a dishonest minority cannot forge agreement. + +See also: [Secure Proof of Stake](#secure-proof-of-stake-spos), [Consensus](#consensus), [Equivalent Consensus Proof](#equivalent-consensus-proof-ecp). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Finality + +The point at which a block and its transactions become irreversible. + +Context: MultiversX has deterministic finality: a block is final the moment its consensus proof is produced, with no probabilistic confirmation window. Finality is distinct from block time, which is how often blocks are produced. + +See also: [Deterministic Finality](#deterministic-finality), [Equivalent Consensus Proof](#equivalent-consensus-proof-ecp), [Block Time](#block-time), [Supernova](#supernova). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Deterministic Finality + +Finality reached at a defined moment, when the consensus threshold is met, rather than becoming more probable as further blocks are added. + +Context: On MultiversX a block is final once its Equivalent Consensus Proof is broadcast, and exactly one valid proof can exist per block, so equivocation is not possible. Probabilistic-finality chains, by contrast, treat a block as final only after enough subsequent blocks. Supernova preserves deterministic finality while cutting the time to reach it. + +See also: [Finality](#finality), [Equivalent Consensus Proof](#equivalent-consensus-proof-ecp), [Supernova](#supernova), [Byzantine Fault Tolerance](#byzantine-fault-tolerance-bft). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Block Time + +The interval between consecutive blocks on a shard. + +Context: MultiversX block time is roughly 6 seconds on mainnet today. The Supernova upgrade is designed to reduce it to a 600-millisecond target; Supernova is pre-mainnet, so this reduction is not yet live. Block time bounds how quickly transactions can be included, and is distinct from finality, which is when a block becomes irreversible. + +See also: [Round](#round), [Finality](#finality), [Supernova](#supernova). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus); MultiversX, "Supernova: decoupling consensus and execution" ([multiversx.com/blog/supernova-decoupling-consensus-and-execution](https://multiversx.com/blog/supernova-decoupling-consensus-and-execution)). + +--- + +### Mempool + +The pool of valid, pending transactions a node holds before they are included in a block. + +Context: Because MultiversX is sharded, each shard's nodes maintain the mempool for the transactions they are responsible for. Supernova (pre-mainnet) is designed to propagate blocks by referencing transactions nodes already hold rather than rebroadcasting full block bodies. + +See also: [Transaction](#transaction), [Shard (Execution Shard)](#shard-execution-shard), [Supernova](#supernova). +Read more: [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding). + +--- + +### Hash + +A fixed-size fingerprint of data produced by a one-way function, used to link blocks, identify transactions, and commit to state. + +Context: MultiversX uses cryptographic hashes throughout: to chain blocks, to derive the randomness for proposer selection, and to compute the transaction hash that identifies a transaction. + +See also: [Block](#block), [Verifiable Random Function](#verifiable-random-function-vrf), [Account State Trie](#account-state-trie). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Account State Trie + +The tree structure in which the protocol stores account balances, nonces, code, and storage, committing the whole state to a single root hash. + +Context: Each shard maintains its own state trie for the accounts it holds. Storing state as a trie lets the protocol prove and verify account data efficiently, and is what state sharding partitions across shards. + +See also: [Account](#account), [Adaptive State Sharding](#adaptive-state-sharding), [Hash](#hash). +Read more: [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding). + +--- + +### Move Balance + +The value-transfer and data-handling part of a transaction's cost, as opposed to smart-contract execution. + +Context: A plain EGLD transfer is a pure move-balance operation: it pays the minimum gas plus a per-data-byte cost and does not invoke the virtual machine. Contract calls add an execution component on top. The distinction is the basis of MultiversX's two-part fee model. + +See also: [Gas and Fees](#gas-and-fees), [Transaction](#transaction), [Smart Contract Results](#smart-contract-results-scrs). +Read more: [docs.multiversx.com/developers/gas-and-fees/overview](https://docs.multiversx.com/developers/gas-and-fees/overview/). + +--- + +### Miniblock + +A container inside a block that holds an ordered list of transactions grouped by their (sender shard, receiver shard) pair. + +Context: A single MultiversX block can contain several miniblocks, one per shard-to-shard direction. The miniblock is the atomic unit of cross-shard execution: either the entire miniblock is processed, or none of its transactions are applied and it is retried in the next round. + +See also: [Block](#block), [Cross-Shard Transaction](#cross-shard-transaction), [Metablock](#metablock), [Cross-Shard Atomicity](#cross-shard-atomicity). +Read more: [docs.multiversx.com/learn/transactions](https://docs.multiversx.com/learn/transactions). + +--- + +### Metablock + +The metachain's own block, which notarizes the finalized shard block headers rather than carrying user transactions. + +Context: When a shard block is final, the metachain records that block's header and miniblock hashes, together with its Equivalent Consensus Proof, in the next metablock. The metablock is what makes a shard block's cross-shard output visible network-wide. + +See also: [Metachain](#metachain), [Notarization](#notarization), [Miniblock](#miniblock), [Hyperblock](#hyperblock). +Read more: [docs.multiversx.com/learn/transactions](https://docs.multiversx.com/learn/transactions). + +--- + +### Genesis Round + +The first round of the first epoch of the chain, containing the network's bootstrapping phase. + +Context: MultiversX organizes time into rounds grouped into epochs; the genesis round is where the chain starts. Round length is fixed (currently 6 seconds) and an epoch is initially sized to last about 24 hours. + +See also: [Round](#round), [Epoch](#epoch), [Block](#block). +Read more: [docs.multiversx.com/learn/chronology](https://docs.multiversx.com/learn/chronology). + +--- + +### Intra-Shard Transaction + +A transaction whose sender and receiver accounts belong to the same shard, processed entirely within that one shard. + +Context: Because both accounts live in the same shard, an intra-shard transaction is executed and finalized in a single shard block, without the metachain-mediated round trip that cross-shard transactions require. + +See also: [Cross-Shard Transaction](#cross-shard-transaction), [Shard (Execution Shard)](#shard-execution-shard), [Bech32 Addressing](#bech32-addressing-erd1). +Read more: [docs.multiversx.com/integrators/faq](https://docs.multiversx.com/integrators/faq/); [docs.multiversx.com/learn/transactions](https://docs.multiversx.com/learn/transactions). + +--- + +### Cross-Shard Transaction + +A transaction whose sender and receiver accounts belong to different shards, executed asynchronously across both shards with metachain notarization in between. + +Context: A cross-shard transaction passes through the sender's shard, metachain notarization, and the receiver's shard. The source shard finalizes and ships a notarized cross-shard miniblock, and the destination shard picks it up and executes its part, so execution is asynchronous rather than atomic across the two shards. + +See also: [Intra-Shard Transaction](#intra-shard-transaction), [Cross-Shard Atomicity](#cross-shard-atomicity), [Miniblock](#miniblock), [Metachain](#metachain). +Read more: [docs.multiversx.com/integrators/faq](https://docs.multiversx.com/integrators/faq/); [docs.multiversx.com/learn/transactions](https://docs.multiversx.com/learn/transactions). + +--- + +### Verifiable Unpredictable Function (VUF) + +A function that produces a random seed which is verifiable and unpredictable, used to derive the randomness that seeds proposer selection each round. + +Context: MultiversX uses a VUF to generate the new random seed carried in each block, and a Verifiable Random Function over that seed to select the block proposer. The VUF output is verifiable and unpredictable, though not uniform, which is why a separate VRF step is applied for selection. + +See also: [Verifiable Random Function (VRF)](#verifiable-random-function-vrf), [Block Proposer (Leader)](#block-proposer-leader), [Secure Proof of Stake (SPoS)](#secure-proof-of-stake-spos). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Mainnet + +The live, production MultiversX network, where transactions move real EGLD and real value. + +Context: MultiversX mainnet launched in July 2020 and runs three execution shards plus the metachain. It is the network real applications and users transact on, as distinct from the public test networks. + +See also: [Testnet](#testnet), [Devnet](#devnet), [EGLD](#egld), [Shard (Execution Shard)](#shard-execution-shard). +Read more: [docs.multiversx.com/learn/EGLD](https://docs.multiversx.com/learn/EGLD/). + +--- + +### Testnet + +A public test network that runs the same MultiversX protocol as mainnet, for testing applications without spending real EGLD. + +Context: Testnet uses valueless test EGLD (xEGLD) obtained from a faucet. It mirrors mainnet behavior so applications can be validated before deployment. + +See also: [Devnet](#devnet), [Mainnet](#mainnet), [Faucet](#faucet), [Chain Simulator](#chain-simulator). +Read more: [docs.multiversx.com/learn/EGLD](https://docs.multiversx.com/learn/EGLD/). + +--- + +### Devnet + +A public development network running the same MultiversX protocol, used to try features and applications earlier than testnet. + +Context: Devnet is the earliest public network for developers and typically carries in-development work; like testnet it uses valueless xEGLD from a faucet. Many applications run on devnet before promoting to mainnet. + +See also: [Testnet](#testnet), [Mainnet](#mainnet), [Faucet](#faucet), [Chain Simulator](#chain-simulator). +Read more: [docs.multiversx.com/learn/EGLD](https://docs.multiversx.com/learn/EGLD/). + +--- + +### Faucet + +A service on the test networks that dispenses valueless test tokens (xEGLD) to an account, so developers can transact without spending real funds. + +Context: The web wallet on devnet and testnet includes a faucet that sends test xEGLD, subject to a rate limit; other community faucets exist. It is the standard way to fund an account for testing. + +See also: [Testnet](#testnet), [Devnet](#devnet), [Web Wallet](#web-wallet), [EGLD](#egld). +Read more: [docs.multiversx.com/wallet/web-wallet](https://docs.multiversx.com/wallet/web-wallet/); [docs.multiversx.com/learn/EGLD](https://docs.multiversx.com/learn/EGLD/). + +--- + +## Protocol and architecture + +### Adaptive State Sharding + +The MultiversX scaling design that splits the network into parallel shards across three dimensions at once (state, transactions, and network) and can change the number of shards in response to load. + +Context: Each shard processes only the transactions whose sender or receiver belongs to it and stores only its own slice of global state, so capacity grows by adding shards rather than by making one chain faster. Mainnet has run a stable configuration of three execution shards plus a coordinating metachain since the July 2020 launch, because it has not been throughput-bound. "Adaptive" refers to the protocol-level ability to split or merge shards; that capability is present but latent at the current configuration. + +See also: [Metachain](#metachain), [Hyperblock](#hyperblock), [Cross-Shard Atomicity](#cross-shard-atomicity), [Secure Proof of Stake](#secure-proof-of-stake-spos). +Read more: [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding); founding whitepaper, *A Highly Scalable Public Blockchain via Adaptive State Sharding and Secure Proof of Stake* ([files.multiversx.com/multiversx-whitepaper.pdf](https://files.multiversx.com/multiversx-whitepaper.pdf)). + +--- + +### Metachain + +The blockchain that runs in a dedicated coordinating shard, whose responsibility is notarizing and finalizing shard block headers rather than processing user transactions. + +Context: Since the Andromeda upgrade a shard block is final within its own shard once more than two-thirds (2/3+1) of that shard's validators have signed it, independent of the metachain. The metachain's role is cross-shard: it includes the header and miniblock hashes of each new shard block in its own block, which is what lets the destination shard process that block's cross-shard output. The metachain also computes validator reshuffling at epoch boundaries and arbitrates cross-shard messaging. + +See also: [Hyperblock](#hyperblock), [Cross-Shard Atomicity](#cross-shard-atomicity), [Validator Reshuffle](#validator-reshuffle-epoch-boundary), [Secure Proof of Stake](#secure-proof-of-stake-spos). +Read more: [docs.multiversx.com/integrators/faq](https://docs.multiversx.com/integrators/faq/). + +--- + +### Hyperblock + +A block-like abstraction that reunites data from all shards from the perspective of a metachain block, and contains only fully executed transactions, meaning transactions executed in both their source and destination shards, where the final step is notarized in the metachain block with that nonce. + +Context: The hyperblock is the unit that represents synchronized cross-shard state at a point in the chain's history. It is distinct from a metachain block (the metachain's own block that notarizes shard headers). A cross-shard transaction's "hyperblock coordinates" are set once it is notarized on both shards with acknowledgment from the metachain. The concept predates the Supernova upgrade. + +See also: [Metachain](#metachain), [Cross-Shard Atomicity](#cross-shard-atomicity), [Supernova](#supernova). +Read more: [docs.multiversx.com/integrators/querying-the-blockchain](https://docs.multiversx.com/integrators/querying-the-blockchain/); [docs.multiversx.com/sdk-and-tools/rest-api/blocks](https://docs.multiversx.com/sdk-and-tools/rest-api/blocks/). + +--- + +### Cross-Shard Atomicity + +Whether a transaction whose sender and receiver live in different shards is all-or-nothing across both. On MultiversX, cross-shard execution is asynchronous rather than atomic in that strict sense: each shard executes its part in its own block, and a step that fails on a later shard does not automatically reverse steps already completed on an earlier one. + +Context: A cross-shard transaction passes through multiple blocks: the source shard finalizes and ships a notarized cross-shard miniblock, and the destination shard picks it up and executes it, mediated by the metachain. Because the parts execute in sequence rather than together, partial outcomes are possible. For example, a swap that completes on the source shard and forwards its result to another shard, where a follow-on step then fails, leaves the original swap in place unless the contract itself undoes it. Composability is therefore structurally asynchronous: a contract calling a contract on another shard receives the outcome in a callback, not as a synchronous return value, and is responsible for any compensating action on failure. This model has held since 2020; the Supernova upgrade compresses the wall-clock latency between the steps without changing it. + +See also: [Metachain](#metachain), [Hyperblock](#hyperblock), [Supernova](#supernova), [Typed Proxies](#typed-proxies). +Read more: [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding); [github.com/multiversx/mx-chain-go](https://github.com/multiversx/mx-chain-go). + +--- + +### Secure Proof of Stake (SPoS) + +MultiversX's Byzantine-fault-tolerant proof-of-stake consensus mechanism, in which validators are assigned to per-shard consensus groups at random and a verifiable random function selects block proposers and signers. + +Context: Eligibility to validate depends on staked EGLD. A verifiable random function selects the block proposer each round, and validator signatures are aggregated with a BLS multi-signature scheme into a single proof. Since the Andromeda upgrade the full shard validator set signs each block, and a block is final once at least two-thirds have signed, giving single-block deterministic finality. SPoS is paired with adaptive state sharding as the two foundational designs from the 2018 whitepaper. + +See also: [Adaptive State Sharding](#adaptive-state-sharding), [Validator Reshuffle](#validator-reshuffle-epoch-boundary), [Metachain](#metachain). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus); founding whitepaper ([files.multiversx.com/multiversx-whitepaper.pdf](https://files.multiversx.com/multiversx-whitepaper.pdf)). + +--- + +### Validator Reshuffle (Epoch Boundary) + +The pseudo-random redistribution of a portion of each shard's validators to other shards at every epoch boundary. + +Context: At each epoch boundary (approximately 24 hours) up to one-third of each shard's validators are moved to new shards, computed by the metachain using its randomness source. The security rationale: if a validator cannot predict which shard it will be in next epoch, an attacker cannot cheaply concentrate a corrupt majority inside a single shard. This mechanism predates Supernova and is unchanged by it. A validator is a node that has staked at least 2500 EGLD and participates in consensus. + +See also: [Secure Proof of Stake](#secure-proof-of-stake-spos), [Metachain](#metachain), [Adaptive State Sharding](#adaptive-state-sharding). +Read more: [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding) (node shuffling). + +--- + +### Onchain Guardian + +An opt-in, protocol-level two-factor authentication standard in which a guarded account requires a second signature from a designated guardian before a transaction is valid. + +Context: When an account enables a Guardian, transactions must carry a guardian co-signature in addition to the owner's signature, and the guardian can be a standard authenticator app (such as Google Authenticator or Authy) or another service. Because the second factor is enforced by the protocol rather than by an application, a leaked seed phrase alone is not sufficient to move funds from a guarded account. The standard shipped to mainnet in July 2023. Guarded transactions cost an additional 50,000 gas. + +See also: [Relayed Transactions](#relayed-transactions-v1--v2--v3), [NativeAuth](#nativeauth). +Read more: [docs.multiversx.com/developers/built-in-functions](https://docs.multiversx.com/developers/built-in-functions/); The Block, "MultiversX introduces on-chain 2FA" (July 14, 2023, [theblock.co/post/239592/multiversx-on-chain-2fa](https://www.theblock.co/post/239592/multiversx-on-chain-2fa)). + +--- + +### Relayed Transactions (V1 / V2 / V3) + +A mechanism that lets one account (the relayer) pay the gas fees for another account's transaction, so the sending account can transact without holding EGLD for fees. + +Context: Versions 1 and 2 wrapped the inner transaction in the relayer's transaction and are being deprecated. Version 3 adds explicit `relayer` and `relayerSignature` fields directly on the transaction. For V3 the relayer field must be set before either party signs, the sender and relayer must be in the same shard, and the transaction costs an additional 50,000 gas. Relayed V3 was introduced in the Spica upgrade. + +See also: [Spica](#spica), [Onchain Guardian](#onchain-guardian). +Read more: [docs.multiversx.com/developers/relayed-transactions](https://docs.multiversx.com/developers/relayed-transactions/); MultiversX SDK reference ([github.com/multiversx/mx-sdk-js-core](https://github.com/multiversx/mx-sdk-js-core)). + +--- + +### WASM VM (and Wasmer) + +The WebAssembly virtual machine in which MultiversX smart contracts execute. Contracts compile to the `wasm32` target and run inside this VM rather than on a chain-specific bytecode interpreter. + +Context: Most contracts are written in Rust against the `multiversx-sc` framework and compiled to optimized WebAssembly. The VM uses a Wasmer-based execution engine to run the WASM modules. ESDT token transfers do not invoke the VM at all, which is part of why token operations are cheaper than contract-based token systems on other chains. The VM identifier for WASM contracts is `0x0500`. + +See also: [ESDT](#esdt-estandard-digital-token), [sc-meta](#sc-meta), [Storage Mappers](#storage-mappers), [Tx Syntax](#tx-syntax). +Read more: [docs.multiversx.com/learn/space-vm](https://docs.multiversx.com/learn/space-vm); [github.com/multiversx/mx-chain-vm-go](https://github.com/multiversx/mx-chain-vm-go). + +--- + +### ESDT (eStandard Digital Token) + +MultiversX's native token standard, used to issue and manage fungible, semi-fungible, and non-fungible tokens at the protocol level without deploying a token-logic smart contract. + +Context: Because token accounting is handled by the protocol, issuing or transferring an ESDT does not require the virtual machine, which lowers cost and complexity relative to contract-based standards. Token identifiers take the form `TICKER-randomhex` for fungible tokens, with a nonce suffix for non-fungible and semi-fungible tokens (`TICKER-randomhex-nonceHex`). A non-fungible token has a nonce greater than zero and a quantity of one; a semi-fungible token has a nonce greater than zero and a quantity greater than one. + +See also: [WASM VM](#wasm-vm-and-wasmer), [EGLD](#egld), [Bech32 Addressing](#bech32-addressing-erd1). +Read more: [docs.multiversx.com/tokens/intro](https://docs.multiversx.com/tokens/intro) (expansion and protocol-level handling); [docs.multiversx.com/tokens/esdt-tokens](https://docs.multiversx.com/tokens/esdt-tokens/). + +--- + +### Bech32 Addressing (erd1…) + +The address format used on MultiversX, in which public keys are encoded as Bech32 strings with the human-readable prefix `erd`. + +Context: A typical address looks like `erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th`. The prefix (the human-readable part, HRP) defaults to `erd`. Addresses are 32-byte public keys; the shard an address belongs to is derived from the last bytes of the public key, which is how the protocol routes a transaction to the correct shard. Smart contract addresses are a recognizable subclass of the same format. + +See also: [ESDT](#esdt-estandard-digital-token), [Adaptive State Sharding](#adaptive-state-sharding), [EGLD](#egld). +Read more: MultiversX SDK `Address` reference ([github.com/multiversx/mx-sdk-js-core](https://github.com/multiversx/mx-sdk-js-core)). + +--- + +### EGLD + +The native token of MultiversX, used to pay transaction fees, secure the network through staking, and fund validator rewards. + +Context: EGLD ("eGold") was introduced at the July 2020 mainnet launch, redenominated from the prior ERD token at 1,000 ERD to 1 EGLD. At launch the project marketed a fixed maximum supply of 31.4 million EGLD. That fixed cap was removed by the Economic Evolution proposal in October 2025, which moved EGLD to a tail-inflation model. One EGLD is divisible to 18 decimals (1 EGLD = 10^18 of its smallest unit). A validator must stake at least 2500 EGLD. + +See also: [Economic Evolution](#economic-evolution), [Staking V5](#staking-v5-and-the-emission-split), [KPI-Gated Emissions](#kpi-gated-emissions), [Secure Proof of Stake](#secure-proof-of-stake-spos). +Read more: CoinDesk, "Elrond Launches Onto Mainnet, Reduces Token Supply by 99%" (July 31, 2020); MultiversX, "The MultiversX Economic Evolution" (Oct 22, 2025, [multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion](https://multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion)). + +--- + +### KPI-Gated Emissions + +An emissions design in which a portion of EGLD's annual issuance is released only if the ecosystem meets defined performance indicators, and is otherwise locked. + +Context: Of the annual emission, the two growth-oriented buckets (the ecosystem growth fund and the user growth dividend, 20% each, so 40% combined) are conditional; the staking-reward and protocol-sustainability buckets are not. Four indicators are evaluated quarterly on three-month rolling averages: the staking ratio (target 65 to 70%), protocol revenue measured through fee burns (target above 10% quarter-on-quarter growth), DeFi activity (aggregate TVL and 24-hour volume targets), and price growth (a market indicator weighted in a composite score). Unmet targets lock the unused tokens at quarter-end, prorated to the share of the target achieved, enforced by smart contract. The distribution model is re-evaluated annually by governance vote. The mechanism is specified in the Economic Evolution framework, which is being rolled out in phases. + +See also: [Economic Evolution](#economic-evolution), [Staking V5](#staking-v5-and-the-emission-split), [EGLD](#egld), [Governance Proposal Process](#governance-proposal-process). +Read more: "A competitive economic framework for MultiversX: toward revenue and reflexive value accrual," MultiversX Agora ([agora.multiversx.com/t/...530](https://agora.multiversx.com/t/a-competitive-economic-framework-for-multiversx-toward-revenue-and-reflexive-value-accrual/530)); MultiversX, "The MultiversX Economic Evolution" (Oct 22, 2025, [multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion](https://multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion)). + +--- + +### Shard (Execution Shard) + +One of the parallel partitions of the network, each processing a subset of transactions and holding a slice of global state. + +Context: Mainnet runs three execution shards plus the coordinating metachain. An account is assigned to a shard by the last bytes of its address, and a transaction is processed by the shard or shards of its sender and receiver. Adding shards is how the network scales throughput. + +See also: [Adaptive State Sharding](#adaptive-state-sharding), [Metachain](#metachain), [Cross-Shard Atomicity](#cross-shard-atomicity), [Bech32 Addressing](#bech32-addressing-erd1). +Read more: [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding). + +--- + +### Shard Split and Merge (Resharding) + +The protocol-level ability to change the number of shards by splitting a shard into two when load rises, or merging shards when it falls, without halting the network. + +Context: This is the "adaptive" part of adaptive state sharding. The machinery exists in the protocol, but mainnet has run a stable configuration of three execution shards plus the metachain since 2020 and has not been throughput-bound, so resharding has not been triggered in production. It is therefore an architectural capability rather than an exercised mainnet behavior. + +See also: [Adaptive State Sharding](#adaptive-state-sharding), [Shard (Execution Shard)](#shard-execution-shard), [Metachain](#metachain), [Validator Reshuffle](#validator-reshuffle-epoch-boundary). +Read more: [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding); founding whitepaper ([files.multiversx.com/multiversx-whitepaper.pdf](https://files.multiversx.com/multiversx-whitepaper.pdf)). + +--- + +### Epoch + +The protocol's day-scale time unit, currently about 24 hours, used for validator reshuffling, staking state changes, and reward computation. + +Context: Many protocol events are denominated in epochs rather than wall-clock time. Validator reshuffling happens at epoch boundaries, the unstake-to-withdraw waiting period is 10 epochs, and a guardian becomes active 20 epochs after it is set. An epoch is divided into many rounds. + +See also: [Round](#round), [Validator Reshuffle](#validator-reshuffle-epoch-boundary), [Unbonding Period](#unbonding-period), [Onchain Guardian](#onchain-guardian). +Read more: [docs.multiversx.com/validators/staking](https://docs.multiversx.com/validators/staking/). + +--- + +### Round + +The fixed time slot in which one block is proposed per shard. + +Context: In each round one validator is selected as proposer and the shard's validators sign the proposed block. Round length sets the block time: roughly 6 seconds today, which the Supernova upgrade (pre-mainnet) is designed to reduce to a 600-millisecond target. Many rounds make up an epoch. + +See also: [Epoch](#epoch), [Secure Proof of Stake](#secure-proof-of-stake-spos), [Supernova](#supernova). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Smart Contract Results (SCRs) + +The result objects the protocol generates from smart-contract execution, used to deliver outcomes, value, and cross-shard effects back to callers and other contracts. + +Context: A single transaction that calls a contract can produce one or more SCRs, including the gas refund and any cross-shard continuation of the call. SCRs are first-class records on the network, visible in explorers, rather than ordinary transactions. They are how MultiversX represents the asynchronous, cross-shard parts of a contract call. + +See also: [Cross-Shard Atomicity](#cross-shard-atomicity), [Built-in Functions](#built-in-functions), [Gas and Fees](#gas-and-fees). +Read more: [docs.multiversx.com/developers/developer-reference/sc-api-functions](https://docs.multiversx.com/developers/developer-reference/sc-api-functions/). + +--- + +### Built-in Functions + +Protocol-side functions that execute without a dedicated smart contract as receiver, handled directly by the protocol. + +Context: Roughly thirty built-in functions cover token and account operations. Examples include ESDTTransfer and MultiESDTNFTTransfer (token transfers), ESDTNFTCreate (mint), ClaimDeveloperRewards, ChangeOwnerAddress, SetUserName (herotag via DNS), and SetGuardian and GuardAccount. Because they run protocol-side, they are cheaper than equivalent contract code and available chain-wide without deploying anything. + +See also: [ESDT](#esdt-estandard-digital-token), [System Smart Contracts](#system-smart-contracts), [Onchain Guardian](#onchain-guardian), [Herotag](#herotag). +Read more: [docs.multiversx.com/developers/built-in-functions](https://docs.multiversx.com/developers/built-in-functions/). + +--- + +### System Smart Contracts + +Protocol-level contracts deployed at fixed, well-known addresses that implement core functions such as staking, ESDT issuance, delegation, and governance. + +Context: Unlike user-deployed contracts, these ship with the protocol and live at reserved addresses. Issuing a token, staking a node, creating a delegation contract, or submitting a governance proposal are all transactions sent to a system smart contract. They are the protocol's own onchain logic. + +See also: [ESDT](#esdt-estandard-digital-token), [Staking](#staking), [Delegation](#delegation), [Governance Proposal Process](#governance-proposal-process). +Read more: [docs.multiversx.com/validators/staking/staking-smart-contract](https://docs.multiversx.com/validators/staking/staking-smart-contract/); [docs.multiversx.com/developers/built-in-functions](https://docs.multiversx.com/developers/built-in-functions/). + +--- + +### Gas and Fees + +MultiversX's transaction-cost model, in which a transaction declares a gas limit and gas price and the fee is computed from two components: moving value and data, and executing contract code. + +Context: A simple EGLD transfer pays only the value-movement component (a minimum gas limit plus a per-data-byte cost). A contract call adds a second component, charged at the gas price times a gas-price modifier that is typically lower. Gas paid beyond what a call actually consumes is refunded. This split is what lets the protocol route most of a contract call's fee to the contract's developer. + +See also: [Developer Revenue Share](#developer-revenue-share), [Smart Contract Results](#smart-contract-results-scrs), [Relayed Transactions](#relayed-transactions-v1--v2--v3). +Read more: [docs.multiversx.com/developers/gas-and-fees/overview](https://docs.multiversx.com/developers/gas-and-fees/overview/). + +--- + +### Gas Refund + +The return of gas a transaction reserved but did not consume, paid back to the sender as part of the transaction's results. + +Context: A caller sets a gas limit up front; if execution uses less, the difference is refunded rather than kept. The refund is delivered as a smart contract result and is visible on the transaction in explorers. This is why setting a generous gas limit on a contract call does not, by itself, cost the full amount. + +See also: [Gas and Fees](#gas-and-fees), [Gas-Price Modifier](#gas-price-modifier), [Smart Contract Results](#smart-contract-results-scrs). +Read more: [docs.multiversx.com/developers/gas-and-fees/overview](https://docs.multiversx.com/developers/gas-and-fees/overview/). + +--- + +### Gas-Price Modifier + +A protocol factor, less than one, applied to the execution portion of a contract call's fee, so the gas spent running contract code is charged at a lower rate than the base gas price. + +Context: A transaction's cost splits into moving value and data (charged at the full gas price) and executing contract code (charged at the gas price times the modifier). The modifier lowers the effective cost of computation and is part of how the fee model keeps contract calls affordable. It applies only to the execution component, not to the value-movement component. + +See also: [Gas and Fees](#gas-and-fees), [Move Balance](#move-balance), [Gas Refund](#gas-refund), [Developer Revenue Share](#developer-revenue-share). +Read more: [docs.multiversx.com/developers/gas-and-fees/overview](https://docs.multiversx.com/developers/gas-and-fees/overview/). + +--- + +### Herotag + +A human-readable name mapped to a MultiversX address through the protocol's onchain Distributed Name Service (DNS), so funds can be sent to a name instead of a full address. + +Context: Instead of an `erd1...` address, a user can register and share a herotag (a short handle). The mapping is stored onchain through DNS smart contracts and resolved by wallets and explorers; the username is set with the SetUserName built-in function. It is MultiversX's naming-service equivalent. + +See also: [Bech32 Addressing](#bech32-addressing-erd1), [Built-in Functions](#built-in-functions). +Source: [docs.multiversx.com/welcome/terminology](https://docs.multiversx.com/welcome/terminology/); xPortal Help Center, "What is a herotag?". + +--- + +### Sovereign Chains + +A framework and SDK for launching application-specific chains that connect to the MultiversX network while remaining independently configurable. + +Context: A Sovereign Chain sets its own parameters (public or private, staking model, gas and fee rules, supported tokens) and runs a consensus configuration that dedicates most of its time to processing for higher throughput on a dedicated workload. It is MultiversX's approach to appchains and interoperability, announced in 2024. + +See also: [Adaptive State Sharding](#adaptive-state-sharding), [ESDT](#esdt-estandard-digital-token). +Read more: [docs.multiversx.com/sovereign/overview](https://docs.multiversx.com/sovereign/overview/); [docs.multiversx.com/sovereign/concept](https://docs.multiversx.com/sovereign/concept/). + +--- + +### SpaceVM + +The name of the MultiversX virtual machine that executes smart contracts, a WebAssembly engine also documented under WASM VM. + +Context: SpaceVM runs contracts compiled to the wasm32 target and is stateless: during execution a contract cannot write directly to storage or the blockchain, and the accumulated changes are applied only at the end, and only on success. Reading global state is permitted at any time. + +See also: [WASM VM (and Wasmer)](#wasm-vm-and-wasmer), [Stateless Execution](#stateless-execution), [Smart Contract](#smart-contract). +Read more: [docs.multiversx.com/learn/space-vm](https://docs.multiversx.com/learn/space-vm). + +--- + +### Stateless Execution + +The MultiversX VM property that a smart contract cannot write directly to storage or the blockchain during execution; changes accumulate in a transient structure and are committed only at the end, and only if execution succeeds. + +Context: This design removes the need for reverting operations: a failed execution leaves global state untouched because nothing was written until success. Reading global state is allowed at any time during execution. + +See also: [SpaceVM](#spacevm), [WASM VM (and Wasmer)](#wasm-vm-and-wasmer), [Smart Contract Results (SCRs)](#smart-contract-results-scrs). +Read more: [docs.multiversx.com/learn/space-vm](https://docs.multiversx.com/learn/space-vm). + +--- + +### Restaking (Sovereign Chains) -**Address**: the public key of a wallet. The MultiversX Address format is bech32, specified by the BIP 0173. -The public key always starts with an `erd1`. e.g.: `erd1sea63y47u569ns3x5mqjf4vnygn9whkk7p6ry4rfpqyd6rd5addqyd9lf2`. +A model in which EGLD holders stake to help secure a sovereign chain and earn additional yield on top of base EGLD staking rewards, without giving up custody. -**Node**: a computer or server, running the MultiversX client and relaying messages received from its peers. +Context: Restaking lets the security of the MultiversX mainchain extend to sovereign chains and lets participants earn extra returns for backing them. It is part of how sovereign chains bootstrap validator participation. -**Validator**: a node on the MultiversX network that staked at least 2500 EGLD, that processes transactions and secures -the network by participating in the consensus mechanism, while earning rewards from the protocol and transaction fees. +See also: [Sovereign Chains](#sovereign-chains), [Dual Staking (Sovereign Chains)](#dual-staking-sovereign-chains), [Staking](#staking), [Delegation](#delegation). +Read more: [docs.multiversx.com/sovereign/restaking](https://docs.multiversx.com/sovereign/restaking/). -**Observer**: a passive member of the network that can act as a read & relay interface. +--- + +### Dual Staking (Sovereign Chains) + +A design in which a validator earns rewards from both the MultiversX mainchain and a sovereign chain at the same time. + +Context: Dual staking aims to let validators contribute to and be rewarded by a sovereign chain while continuing to validate the mainchain, improving participation efficiency across the two. + +See also: [Sovereign Chains](#sovereign-chains), [Restaking (Sovereign Chains)](#restaking-sovereign-chains), [Staking](#staking). +Read more: [docs.multiversx.com/sovereign/dual-staking](https://docs.multiversx.com/sovereign/dual-staking/). + +--- + +### Header Verifier + +A component that verifies mainchain block headers on a sovereign chain, so cross-chain transfers act only on data proven final on the origin chain. + +Context: The header verifier checks proofs of execution and finality before a sovereign chain accepts a cross-chain operation, which is what makes bridging between the mainchain and a sovereign chain secure. + +See also: [Sovereign Chains](#sovereign-chains), [ESDT-Safe](#esdt-safe), [Cross-Shard Message Buffer](#cross-shard-message-buffer). +Read more: [docs.multiversx.com/sovereign/concept](https://docs.multiversx.com/sovereign/concept/). + +--- + +### ESDT-Safe + +The bridge contract that locks or receives tokens on one side of a cross-chain transfer and emits the events that trigger the corresponding action on the other side. + +Context: On a sovereign or bridge setup, the ESDT-Safe contract accepts a token deposit with a destination address and function call, then logs an event that the relaying infrastructure uses to complete the transfer on the other chain. It is paired with a counterpart safe contract on the other side. + +See also: [Sovereign Chains](#sovereign-chains), [Header Verifier](#header-verifier), [Ad-Astra Bridge](#ad-astra-bridge), [Bridged Tokens](#bridged-tokens). +Read more: [docs.multiversx.com/sovereign/concept](https://docs.multiversx.com/sovereign/concept/). + +--- + +### Ad-Astra Bridge + +The MultiversX cross-chain bridge that transfers tokens between MultiversX and EVM-compatible chains such as Ethereum, using safe and bridge contracts on each side operated by a relayer set. + +Context: The bridge is run by ten relayers, five managed by the MultiversX Foundation and five by community validators, which together operate the contracts under a quorum. It moves tokens between ERC20 and ESDT form. + +See also: [Bridged Tokens](#bridged-tokens), [Axelar (Bridge)](#axelar-bridge), [ESDT-Safe](#esdt-safe), [WEGLD (Wrapped EGLD)](#wegld-wrapped-egld). +Read more: [docs.multiversx.com/bridge/architecture](https://docs.multiversx.com/bridge/architecture/). + +--- + +### Axelar (Bridge) + +A second cross-chain path for MultiversX that uses Axelar's verifier network to reach a broader set of chains, requiring a MultiversX observing squad as part of the setup. + +Context: Alongside the Ad-Astra bridge, the Axelar integration lets MultiversX interoperate with the chains Axelar connects, with MultiversX acting as a verifier chain in Axelar's Amplifier model. + +See also: [Ad-Astra Bridge](#ad-astra-bridge), [Bridged Tokens](#bridged-tokens), [Observing Squad](#observing-squad). +Read more: [docs.multiversx.com/bridge/axelar](https://docs.multiversx.com/bridge/axelar/). + +--- + +### Bridged Tokens + +Tokens created on a non-native chain to represent an asset from another chain, kept at parity with the original by the bridge either minting and burning or locking and unlocking. + +Context: Whether a bridged token uses a mint-and-burn or lock-and-unlock mechanism depends on which chain is the token's native chain and whether a mint role is available. On MultiversX these appear as ESDTs that mirror an ERC20 (or the reverse), maintained one-to-one by the bridge. + +See also: [Ad-Astra Bridge](#ad-astra-bridge), [Axelar (Bridge)](#axelar-bridge), [ESDT (eStandard Digital Token)](#esdt-estandard-digital-token), [WEGLD (Wrapped EGLD)](#wegld-wrapped-egld). +Read more: [docs.multiversx.com/bridge/token-types](https://docs.multiversx.com/bridge/token-types/). + +--- + +## Consensus and security + +The mechanics behind Secure Proof of Stake, and the Supernova consensus model layered on top of it. + +### Consensus Group + +The set of validators responsible for proposing and signing a shard's block in a given round. + +Context: Since the Andromeda upgrade the consensus group is the shard's full validator set (about 400 nodes), fixed for the epoch, rather than a small subset re-drawn each round. One member is selected as proposer each round; the rest sign, and a block finalizes once at least two-thirds have signed. + +See also: [Secure Proof of Stake](#secure-proof-of-stake-spos), [Block Proposer](#block-proposer-leader), [Validator Reshuffle](#validator-reshuffle-epoch-boundary), [Equivalent Consensus Proof](#equivalent-consensus-proof-ecp). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Block Proposer (Leader) + +The validator chosen to build and broadcast the block in a given round. + +Context: Selection is random and verifiable: each validator computes a score by hashing its public key with the round's randomness, and the lowest score becomes proposer. The proposer assembles the block and aggregates the other validators' signatures into the consensus proof. + +See also: [Consensus Group](#consensus-group), [Verifiable Random Function](#verifiable-random-function-vrf), [BLS Multi-Signature](#bls-multi-signature), [Equivalent Consensus Proof](#equivalent-consensus-proof-ecp). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### Verifiable Random Function (VRF) + +A function that produces randomness which is unpredictable in advance but verifiable afterward, used to drive proposer and validator selection. + +Context: MultiversX derives each round's randomness from the previous block's seed, so no participant can predict or bias proposer selection, yet anyone can verify the result. The same randomness source drives epoch validator reshuffling. + +See also: [Block Proposer](#block-proposer-leader), [Secure Proof of Stake](#secure-proof-of-stake-spos), [Validator Reshuffle](#validator-reshuffle-epoch-boundary). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- + +### BLS Multi-Signature + +A signature scheme that aggregates many validators' signatures on a block into a single, compact signature. + +Context: MultiversX validators sign the block hash and the proposer aggregates at least two-thirds of the signatures into one 96-byte signature. Aggregation keeps consensus messages small, which is part of how the network reaches finality quickly. + +See also: [Equivalent Consensus Proof](#equivalent-consensus-proof-ecp), [Consensus Group](#consensus-group), [Secure Proof of Stake](#secure-proof-of-stake-spos). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus). + +--- -**Validate**: the act of running a validator node and contributing to the network by relaying and -validating information. +### Equivalent Consensus Proof (ECP) -**Stake**: contribute to the network security by delegating 1 EGLD or more towards a staking provider that operates -validator nodes. +The aggregated BLS signature, from at least two-thirds of a shard's validators, that finalizes a block. -**Delegate**: contribute to the network security by delegating 10 EGLD or more towards the MultiversX Community Delegation -contract. +Context: A block is final the moment its ECP is broadcast, and only one valid ECP can exist for a given block, which makes equivocation impossible. The ECP is what gives MultiversX single-block, deterministic finality, and is what let the Andromeda upgrade remove the old confirmation block. -**Stake rewards**: locked funds for running validator nodes to secure the network and generate income in form of rewards. -Rewards can be claimed or restaked. +See also: [BLS Multi-Signature](#bls-multi-signature), [Deterministic Finality](#deterministic-finality), [Andromeda](#andromeda), [Notarization](#notarization). +Read more: [docs.multiversx.com/learn/consensus](https://docs.multiversx.com/learn/consensus); [docs.multiversx.com/integrators/faq](https://docs.multiversx.com/integrators/faq/). -**Delegate rewards**: locked funds from delegation contracts also generate income in form of rewards. -These rewards can be claimed or redelegated +--- + +### Notarization + +The metachain's recording of a finalized shard block header, which enables cross-shard settlement. + +Context: A shard block is already final within its own shard once more than two-thirds of its validators have signed it (its Equivalent Consensus Proof). Notarization is the separate step in which the metachain includes that block's header and aggregate signature in its own block, making the block visible network-wide so other shards can act on its cross-shard output. + +See also: [Metachain](#metachain), [Hyperblock](#hyperblock), [Cross-Shard Atomicity](#cross-shard-atomicity), [Equivalent Consensus Proof](#equivalent-consensus-proof-ecp). +Read more: [docs.multiversx.com/integrators/faq](https://docs.multiversx.com/integrators/faq/). + +--- + +### Agree-then-Run + +The Supernova consensus model in which validators first agree on the order of transactions and then execute them, rather than agreeing on execution results. + +Context: Validators still agree on the execution results of previous blocks, but the current block's execution result is no longer on the critical path for that block's consensus. Decoupling ordering from execution reduces the required block time, so consensus for one block can proceed while the previous block is still executing. This is what lets Supernova target sub-second blocks while preserving deterministic finality. + +See also: [Pipelined Consensus](#pipelined-consensus), [Partial-Precommit Execution Dispatch](#partial-precommit-execution-dispatch), [Supernova](#supernova), [Deterministic Finality](#deterministic-finality). +Read more: MultiversX, "Supernova: decoupling consensus and execution" ([multiversx.com/blog/supernova-decoupling-consensus-and-execution](https://multiversx.com/blog/supernova-decoupling-consensus-and-execution)). + +--- + +### Pipelined Consensus + +A consensus design in which voting for each block runs in parallel with its own execution, rather than waiting for execution to complete before proceeding. + +Context: Under Supernova, consensus and execution each run on their own pipeline and proceed in parallel. If execution extends past the block slot it overlaps with the next block's consensus, and it does not block it. This removes execution from the critical path of agreement and is the main source of Supernova's block-time reduction. + +See also: [Agree-then-Run](#agree-then-run), [Partial-Precommit Execution Dispatch](#partial-precommit-execution-dispatch), [Supernova](#supernova). +Read more: MultiversX, "Supernova: decoupling consensus and execution" ([multiversx.com/blog/supernova-decoupling-consensus-and-execution](https://multiversx.com/blog/supernova-decoupling-consensus-and-execution)). + +--- + +### Partial-Precommit Execution Dispatch + +The Supernova mechanism by which validators begin executing a block as soon as their local validation passes, before casting their vote. + +Context: The MvX consensus flow has three phases: block broadcast, voting (which follows local validation), and finalization (where a proof is produced). Validators start executing during the voting phase, so state changes are ready by the time finalization completes. Cross-shard transactions produced during this window are accepted only once the originating shard's block is proven final. + +See also: [Pipelined Consensus](#pipelined-consensus), [Cross-Shard Message Buffer](#cross-shard-message-buffer), [Cross-Shard Atomicity](#cross-shard-atomicity), [Supernova](#supernova). +Read more: MultiversX, "Supernova: decoupling consensus and execution" ([multiversx.com/blog/supernova-decoupling-consensus-and-execution](https://multiversx.com/blog/supernova-decoupling-consensus-and-execution)). + +--- + +### Cross-Shard Message Buffer + +The mechanism, mediated by the metachain, that ensures cross-shard transactions are accepted only once the originating shard's execution results are proven final. + +Context: Cross-shard transactions wait for a finality proof from the originating shard before the destination shard acts on them. Block headers may be broadcast early to reduce propagation time, but they are not considered for any operation until a proof exists. This ensures that pipelining does not weaken cross-shard safety. + +See also: [Cross-Shard Atomicity](#cross-shard-atomicity), [Partial-Precommit Execution Dispatch](#partial-precommit-execution-dispatch), [Metachain](#metachain), [Supernova](#supernova). +Read more: MultiversX, "Supernova: decoupling consensus and execution" ([multiversx.com/blog/supernova-decoupling-consensus-and-execution](https://multiversx.com/blog/supernova-decoupling-consensus-and-execution)). + +--- + +## Tokens -**Unstake**: the intention to unlock staked/delegated funds, that will become available after the 10 epoch unbonding period. +These entries detail the token types and controls built on ESDT, which is defined under Protocol and architecture. -**Unbond**: withdraw the funds in the original account after the 10 epoch unbonding period. +### Meta-ESDT -**Staking provider**: node operator that created a staking pool and accepts funds for staking. +A token type that carries per-nonce metadata like an NFT but holds fungible quantities, used for tokens whose units share attributes such as locked or staked positions. -**Staking pool**: a system smart contract that accepts funds for staking. +Context: Technically a special case of the semi-fungible type, a Meta-ESDT behaves like a fungible token with attributes attached at each nonce. MultiversX ecosystem positions such as locked tokens (for example LKMEX and XMEX) are Meta-ESDTs. -**Delegation cap**: the maximum amount of funds accepted by a staking pool. +See also: [ESDT](#esdt-estandard-digital-token), [ESDT Roles](#esdt-roles-special-roles), [Dynamic NFTs](#dynamic-nfts-dynamic-tokens). +Read more: [docs.multiversx.com/tokens/nft-tokens](https://docs.multiversx.com/tokens/nft-tokens/). + +--- + +### Dynamic NFTs (Dynamic Tokens) + +Tokens registered as dynamic so their metadata and attributes can change after issuance rather than being fixed at mint. + +Context: Static NFTs freeze their attributes at creation. A dynamic token can be updated by an address holding the appropriate role, which enables evolving game items, upgradable collectibles, and similar use cases. Dynamic token support was added in the Spica upgrade. + +See also: [ESDT](#esdt-estandard-digital-token), [ESDT Roles](#esdt-roles-special-roles), [Spica](#spica). +Read more: [docs.multiversx.com/tokens/nft-tokens](https://docs.multiversx.com/tokens/nft-tokens/). + +--- + +### ESDT Roles (Special Roles) + +Per-address permissions that authorize specific token operations such as creating, burning, or modifying a token. + +Context: Roles are assigned to addresses by the token manager. Examples include ESDTRoleNFTCreate (mint), ESDTRoleNFTBurn (burn), ESDTRoleNFTAddQuantity (increase SFT supply), and ESDTRoleNFTUpdateAttributes (change attributes). Roles let a project delegate controlled minting and management without giving up ownership of the token. + +See also: [ESDT](#esdt-estandard-digital-token), [Meta-ESDT](#meta-esdt), [Dynamic NFTs](#dynamic-nfts-dynamic-tokens). +Read more: [docs.multiversx.com/tokens/nft-tokens](https://docs.multiversx.com/tokens/nft-tokens/). + +--- + +### NFT Royalties + +A creator royalty set at mint, expressed as a value from 0 to 10000 (0% to 100%) and applied to qualifying transfers. + +Context: The royalty is stored as a property of the token, letting the creator receive a percentage of supported sales. The 0 to 10000 range encodes a percentage with two decimals of precision (for example, 500 is 5%). + +See also: [ESDT](#esdt-estandard-digital-token), [ESDT Roles](#esdt-roles-special-roles). +Read more: [docs.multiversx.com/tokens/nft-tokens](https://docs.multiversx.com/tokens/nft-tokens/). + +--- + +### Fungible Token + +An ESDT whose units are identical and interchangeable, like a currency or a points balance. + +Context: Fungible ESDTs have a nonce of zero and a supply set by the number of decimals chosen at issuance. They are managed at protocol level, so no token contract is deployed. + +See also: [ESDT](#esdt-estandard-digital-token), [Token Identifier](#token-identifier-ticker), [NFT](#nft-non-fungible-token). +Read more: [docs.multiversx.com/tokens/intro](https://docs.multiversx.com/tokens/intro). + +--- + +### NFT (Non-Fungible Token) + +A unique ESDT with a nonce greater than zero and a quantity of one, carrying its own name, attributes, royalties, and URIs. + +Context: MultiversX NFTs are issued and transferred at protocol level rather than through a token contract, and their metadata can be made updatable by issuing them as dynamic tokens. + +See also: [ESDT](#esdt-estandard-digital-token), [SFT](#sft-semi-fungible-token), [Dynamic NFTs](#dynamic-nfts-dynamic-tokens), [NFT Royalties](#nft-royalties). +Read more: [docs.multiversx.com/tokens/nft-tokens](https://docs.multiversx.com/tokens/nft-tokens/). + +--- -**APR**: annual percentage rate. +### SFT (Semi-Fungible Token) -**Service fee**: fee that the service providers are taking from the rewards their staking pools have received. +An ESDT with a nonce greater than zero and a quantity greater than one, behaving like multiple identical copies of the same non-fungible item. + +Context: SFTs suit assets that are unique as a type but issued in editions, such as event tickets or game items. Quantity is increased with the add-quantity role. + +See also: [ESDT](#esdt-estandard-digital-token), [NFT](#nft-non-fungible-token), [Meta-ESDT](#meta-esdt), [ESDT Roles](#esdt-roles-special-roles). +Read more: [docs.multiversx.com/tokens/nft-tokens](https://docs.multiversx.com/tokens/nft-tokens/). + +--- + +### Token Identifier (Ticker) + +The onchain identifier of an ESDT, formed from a human-chosen ticker plus a random suffix, for example `USDC-c76f1f`. + +Context: The random suffix keeps identifiers unique even when tickers collide. Non-fungible and semi-fungible tokens extend the identifier with a nonce (`TICKER-randomhex-nonceHex`) to address an individual token. + +See also: [ESDT](#esdt-estandard-digital-token), [Fungible Token](#fungible-token), [NFT](#nft-non-fungible-token). +Read more: [docs.multiversx.com/tokens/esdt-tokens](https://docs.multiversx.com/tokens/esdt-tokens/). + +--- + +### Liquid Staking (lsEGLD) + +A mechanism that issues a transferable ESDT representing staked EGLD, so the staked position can be used elsewhere while it still earns staking rewards. + +Context: A user stakes EGLD into the liquid-staking contract and receives lsEGLD in return; the token can be used across DeFi while the underlying stake continues to accrue rewards. It addresses the illiquidity of ordinary staking. + +See also: [Staking](#staking), [Delegation](#delegation), [ESDT](#esdt-estandard-digital-token). +Read more: [github.com/multiversx/mx-liquid-staking-sc](https://github.com/multiversx/mx-liquid-staking-sc). + +--- + +### WEGLD (Wrapped EGLD) + +An ESDT that represents EGLD one-to-one, letting the native token be used in contexts that expect a standard token rather than the protocol's native currency. + +Context: EGLD is the native coin and is not itself an ESDT, so smart contracts and DeFi that operate on ESDTs use WEGLD, minted by locking EGLD in a wrapping contract and redeemable one-to-one. It is the MultiversX equivalent of wrapped-native tokens on other chains (such as WETH on Ethereum). + +See also: [EGLD](#egld), [ESDT](#esdt-estandard-digital-token), [Fungible Token](#fungible-token), [Liquid Staking](#liquid-staking-lsegld). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`contracts/core/wegld-swap`); [docs.multiversx.com/tokens/intro](https://docs.multiversx.com/tokens/intro). + +--- + +### Token Properties (canFreeze, canWipe, canPause, ...) + +The configurable flags set on an ESDT at issuance that control which management operations its manager may later perform. + +Context: The properties include canPause (halt all transfers except mint and burn), canFreeze (freeze a specific account's balance), canWipe (wipe a frozen account's tokens), canChangeOwner (transfer token management), canUpgrade (change the properties), canAddSpecialRoles (assign roles), and canCreateMultiShard. On mainnet since epoch 432, the older canMint and canBurn are no longer effective; local mint and burn roles are used instead. + +See also: [ESDT (eStandard Digital Token)](#esdt-estandard-digital-token), [ESDT Roles (Special Roles)](#esdt-roles-special-roles), [Freeze and Wipe](#freeze-and-wipe), [Pause (Token)](#pause-token), [Local Mint and Burn](#local-mint-and-burn). +Read more: [docs.multiversx.com/tokens/fungible-tokens](https://docs.multiversx.com/tokens/fungible-tokens/). + +--- + +### Freeze and Wipe + +Token-manager operations that freeze a specific account's balance of a token (blocking transfers to and from it) and, for an already frozen account, wipe out its tokens, reducing supply. + +Context: Freezing requires the token's canFreeze property, and wiping requires canWipe and a previously frozen account. The pair exists to support regulatory or compliance actions; freezing is reversible, whereas wiping permanently removes the tokens. + +See also: [ESDT (eStandard Digital Token)](#esdt-estandard-digital-token), [Token Properties (canFreeze, canWipe, canPause, ...)](#token-properties-canfreeze-canwipe-canpause-), [Pause (Token)](#pause-token), [ESDT Roles (Special Roles)](#esdt-roles-special-roles). +Read more: [docs.multiversx.com/tokens/fungible-tokens](https://docs.multiversx.com/tokens/fungible-tokens/). + +--- + +### Pause (Token) + +A token-manager operation that prevents all transactions of a token except minting and burning, reversible with an unpause. + +Context: Pausing requires the token's canPause property to be set to true. It halts transfers of the token network-wide until the manager unpauses it. + +See also: [ESDT (eStandard Digital Token)](#esdt-estandard-digital-token), [Token Properties (canFreeze, canWipe, canPause, ...)](#token-properties-canfreeze-canwipe-canpause-), [Freeze and Wipe](#freeze-and-wipe). +Read more: [docs.multiversx.com/tokens/fungible-tokens](https://docs.multiversx.com/tokens/fungible-tokens/). + +--- + +### Local Mint and Burn + +Protocol operations that increase or decrease a token's supply, performed by an address holding the corresponding local role. + +Context: An address with the ESDTRoleLocalMint role can mint new units and one with ESDTRoleLocalBurn can burn units. On mainnet since epoch 432, global mint and burn are disabled, so local mint and burn (via these roles) are the way to change a token's supply. + +See also: [ESDT (eStandard Digital Token)](#esdt-estandard-digital-token), [ESDT Roles (Special Roles)](#esdt-roles-special-roles), [Token Properties (canFreeze, canWipe, canPause, ...)](#token-properties-canfreeze-canwipe-canpause-). +Read more: [docs.multiversx.com/tokens/fungible-tokens](https://docs.multiversx.com/tokens/fungible-tokens/). + +--- + +## Network, nodes, and staking + +### Node + +A computer running the MultiversX client that relays messages with its peers. A node is either a validator or an observer. + +Context: Nodes form the peer-to-peer network that propagates transactions and blocks. Their role depends on whether they have staked to validate or run passively as observers. + +See also: [Validator](#validator-node), [Observer](#observer-node), [Observing Squad](#observing-squad). +Source: [docs.multiversx.com/welcome/terminology](https://docs.multiversx.com/welcome/terminology/). + +--- + +### Validator (node) + +A node that has staked at least 2500 EGLD and participates in consensus, processing transactions and securing the network in exchange for rewards. + +Context: Validators are selected into per-round consensus groups and earn protocol rewards plus a share of fees. Each validator carries a rating, and unreliable validators can be jailed and excluded from consensus. + +See also: [Observer](#observer-node), [Secure Proof of Stake](#secure-proof-of-stake-spos), [Validator Rating](#validator-rating), [Jailing](#jailing), [Staking](#staking). +Read more: [docs.multiversx.com/validators/overview](https://docs.multiversx.com/validators/overview/). + +--- + +### Observer (node) + +A passive node that follows the network and serves as a read-and-relay interface, without participating in consensus or earning rewards. + +Context: Observers are not selected for consensus and do not carry a rating. They are used to read chain state and submit transactions, and a full set of them backs the public API. + +See also: [Node](#node), [Validator](#validator-node), [Observing Squad](#observing-squad). +Read more: [docs.multiversx.com/validators/overview](https://docs.multiversx.com/validators/overview/). + +--- + +### Observing Squad + +A set of observer nodes covering every shard plus the metachain, fronted by a MultiversX Proxy that exposes a single HTTP API over the whole network. + +Context: Because state is sharded, reading the entire network requires an observer in each shard. An observing squad bundles one observer per shard, plus the metachain, with a Proxy instance, giving an application one endpoint that routes each request to the correct shard. It is the standard way to self-host full network access. + +See also: [Observer](#observer-node), [Shard (Execution Shard)](#shard-execution-shard), [Metachain](#metachain). +Read more: [docs.multiversx.com/integrators/observing-squad](https://docs.multiversx.com/integrators/observing-squad/). + +--- + +### Validator Rating + +A per-validator reliability score that rises with successful consensus participation and falls with missed or faulty duties. + +Context: New validators start at a baseline score (50 points) and gain rating for each successful consensus they take part in. Rating reflects reliability and, if it falls too low, leads to jailing. It gives the protocol a continuous, onchain measure of node behavior. + +See also: [Validator](#validator-node), [Jailing](#jailing), [Secure Proof of Stake](#secure-proof-of-stake-spos). +Read more: [docs.multiversx.com/validators/rating](https://docs.multiversx.com/validators/rating/). + +--- + +### Jailing + +The state in which a validator whose rating has fallen too low is excluded from consensus and earns no rewards until it is restored. + +Context: A jailed validator is removed from consensus selection. The operator can return it to the active set by paying a fine and unjailing it. Jailing is the enforcement mechanism behind the rating system. + +See also: [Validator Rating](#validator-rating), [Validator](#validator-node), [Staking](#staking). +Read more: [docs.multiversx.com/validators/rating](https://docs.multiversx.com/validators/rating/). + +--- + +### Staking + +Locking EGLD to run a validator node, currently 2500 EGLD per node, held in a system smart contract. + +Context: Staking makes a node eligible to validate and earn rewards. Stake above the per-node minimum is called topUp and affects how many of an operator's nodes stay active. Withdrawing requires unstaking and then waiting out the unbonding period. + +See also: [Validator](#validator-node), [Delegation](#delegation), [Staking Auction and TopUp](#staking-auction-and-topup), [Unbonding Period](#unbonding-period). +Read more: [docs.multiversx.com/validators/staking](https://docs.multiversx.com/validators/staking/). + +--- + +### Delegation + +Contributing EGLD toward validator nodes operated by someone else, sharing in the rewards without running a node. + +Context: Holders delegate to a staking provider, a custom delegation smart contract that pools participants' funds, operates the nodes, and takes a service fee from the rewards. A provider's pool has a delegation cap, the maximum amount it accepts. Delegation lowers the barrier below the 2500 EGLD per-node minimum. + +See also: [Staking](#staking), [System Smart Contracts](#system-smart-contracts), [Staking Auction and TopUp](#staking-auction-and-topup). +Read more: [docs.multiversx.com/validators/delegation-manager](https://docs.multiversx.com/validators/delegation-manager/). + +--- + +### Staking Auction and TopUp + +The mechanism, introduced with Staking V4, that selects which staked nodes are active based on each operator's stake above the minimum, known as topUp. + +Context: When more nodes are staked than there are available slots, an auction ranks operators by topUp per node and admits as many as their stake supports. This replaced the earlier first-come staking queue and ties active-set membership to economic commitment rather than arrival time. + +See also: [Staking](#staking), [Staking V5](#staking-v5-and-the-emission-split), [Validator](#validator-node). +Read more: [docs.multiversx.com/validators/staking-v4](https://docs.multiversx.com/validators/staking-v4/). + +--- + +### Unbonding Period + +The waiting time between unstaking and being able to withdraw the funds, currently 10 epochs. + +Context: After an operator or delegator unstakes, the funds stay locked for the unbonding period (about 10 days at the current epoch length) before they can be unbonded and withdrawn. The delay protects network security by preventing an instant exit. + +See also: [Staking](#staking), [Delegation](#delegation), [Epoch](#epoch). +Read more: [docs.multiversx.com/validators/staking](https://docs.multiversx.com/validators/staking/). + +--- + +### Unbond + +The action of withdrawing staked or delegated EGLD back to the owner's account after it has been unstaked and the unbonding period has elapsed. + +Context: Unbonding is the final step of exiting stake: an operator or delegator first unstakes, waits out the unbonding period (currently 10 epochs), and then unbonds to receive the funds. It is distinct from the earlier unstake step, which only signals the intent to withdraw. + +See also: [Unstake](#unstake), [Unbonding Period](#unbonding-period), [Staking](#staking), [Delegation](#delegation). +Read more: [docs.multiversx.com/validators/staking/staking-smart-contract](https://docs.multiversx.com/validators/staking/staking-smart-contract/); [docs.multiversx.com/validators/staking](https://docs.multiversx.com/validators/staking/). + +--- + +### Top-up + +The EGLD an operator stakes above the per-node minimum, computed as total staked EGLD minus the minimum times the number of nodes. + +Context: With the base stake fixed at 2500 EGLD per node, top-up is the excess. Top-up per node determines a node's priority in the staking auction and contributes to rewards; nodes with insufficient top-up can be left out of the active set. + +See also: [Staking Auction and TopUp](#staking-auction-and-topup), [Soft Auction](#soft-auction), [Staking](#staking), [Auction List](#auction-list). +Read more: [docs.multiversx.com/validators/staking-v4](https://docs.multiversx.com/validators/staking-v4/). + +--- + +### Auction List + +The set of staked nodes competing, by their top-up per node, to be selected into the active validator set for the coming epoch. + +Context: Introduced with Staking V4, the auction list replaced the earlier first-in first-out staking queue. At each epoch a soft auction selects nodes from the auction list based on top-up; unselected nodes remain in the auction. + +See also: [Soft Auction](#soft-auction), [Top-up](#top-up), [Waiting List](#waiting-list), [Eligible Nodes](#eligible-nodes). +Read more: [docs.multiversx.com/validators/staking-v4](https://docs.multiversx.com/validators/staking-v4/). + +--- + +### Waiting List + +The pool of nodes that have been selected to join but are synchronizing with their assigned shard, before they become eligible for consensus. + +Context: Nodes move from the auction list into the waiting list, spend time resynchronizing with the shard they were assigned, and are then shuffled into the eligible set. The waiting list keeps a node in sync so it is ready before it starts validating. + +See also: [Auction List](#auction-list), [Eligible Nodes](#eligible-nodes), [Validator Reshuffle (Epoch Boundary)](#validator-reshuffle-epoch-boundary), [Soft Auction](#soft-auction). +Read more: [docs.multiversx.com/validators/staking-v4](https://docs.multiversx.com/validators/staking-v4/); [docs.multiversx.com/learn/sharding](https://docs.multiversx.com/learn/sharding). + +--- + +### Eligible Nodes + +The active validators in the consensus set, which produce and sign blocks and earn rewards. + +Context: Only eligible nodes participate in consensus and receive validator rewards, and they are subject to jailing if their rating falls too low. Eligible nodes are drawn from the waiting list and reshuffled across shards at epoch boundaries. + +See also: [Validator (node)](#validator-node), [Waiting List](#waiting-list), [Validator Reshuffle (Epoch Boundary)](#validator-reshuffle-epoch-boundary), [Consensus Group](#consensus-group). +Read more: [docs.multiversx.com/validators/staking-v4](https://docs.multiversx.com/validators/staking-v4/). + +--- + +### Soft Auction + +The Staking V4 selection mechanism that admits nodes from the auction list by finding the top-up threshold that fills the available slots while maximizing the number of qualifying owners and nodes. + +Context: Rather than a strict cutoff that could force operators to unstake nodes, the soft auction adjusts the required top-up per node across a range so as many eligible nodes as possible are selected each epoch. It is designed to be fairer to smaller staking providers. + +See also: [Auction List](#auction-list), [Top-up](#top-up), [Waiting List](#waiting-list), [Staking Auction and TopUp](#staking-auction-and-topup). +Read more: [docs.multiversx.com/validators/staking-v4](https://docs.multiversx.com/validators/staking-v4/). + +--- + +### Slashing + +A penalty in which a validator that seriously misbehaves is fined and loses part or all of its staked EGLD, in addition to having its validator status removed. + +Context: The docs describe stake slashing as reserved for serious offences such as double-signing or producing bad blocks. As of the current docs, slashing for double-signing is noted as a near-future enforcement; day-to-day unreliability is handled by the rating system and jailing rather than by slashing principal. + +See also: [Jailing](#jailing), [Validator Rating](#validator-rating), [Staking](#staking), [Secure Proof of Stake (SPoS)](#secure-proof-of-stake-spos). +Read more: [docs.multiversx.com/validators/overview](https://docs.multiversx.com/validators/overview/). + +--- + +### BLS Key (Validator Key) + +The node-specific key a validator uses to sign blocks and consensus messages, stored in the node's validator key file and distinct from the wallet key that controls funds. + +Context: Each validator node has its own BLS key, whose public key identifies the node in staking transactions and consensus. The private key must never leave the node's host; if it is stolen and used maliciously (for example to double-sign) the associated stake can be slashed. + +See also: [Key Pair (Public and Private Key)](#key-pair-public-and-private-key), [BLS Multi-Signature](#bls-multi-signature), [Multikey Nodes](#multikey-nodes), [Slashing](#slashing). +Read more: [docs.multiversx.com/validators/key-management/validator-keys](https://docs.multiversx.com/validators/key-management/validator-keys/). + +--- + +### Node Redundancy + +A high-availability setup in which one or more standby nodes run alongside a main validator node and take over signing only if the main node goes offline. + +Context: Standby nodes are configured with a redundancy level and stay synchronized with the main node's shard without signing, providing failover to avoid downtime and missed rewards. Two nodes must not share the same redundancy level, or they would sign in parallel and risk double-signing. + +See also: [Multikey Nodes](#multikey-nodes), [BLS Key (Validator Key)](#bls-key-validator-key), [Slashing](#slashing), [Validator (node)](#validator-node). +Read more: [docs.multiversx.com/validators/redundancy](https://docs.multiversx.com/validators/redundancy/). + +--- + +### Multikey Nodes + +A configuration in which a small group of nodes collectively manages many validator BLS keys, with at least one node per shard plus the metachain, instead of running one node per key. + +Context: The nodes share a file listing all managed validator keys and coordinate consensus signing across them, which pools server resources for operators running several keys. It is an optional, more economical setup for medium-to-large staking providers. + +See also: [BLS Key (Validator Key)](#bls-key-validator-key), [Node Redundancy](#node-redundancy), [Validator (node)](#validator-node), [Staking](#staking). +Read more: [docs.multiversx.com/validators/key-management/multikey-nodes](https://docs.multiversx.com/validators/key-management/multikey-nodes/). + +--- + +### Merge Validator + +The operation that converts a standalone staked validator into a node managed by an existing delegation contract, keeping its active slot. + +Context: Merging lets a node operator move a directly staked node under a staking provider's delegation contract without unstaking and re-entering the auction. When the node and the contract have different owners, whitelisting is required first. + +See also: [Delegation Contract](#delegation-contract), [Delegation Manager](#delegation-manager), [Staking](#staking), [Delegation](#delegation). +Read more: [docs.multiversx.com/validators/staking/merge-validator-delegation-sc](https://docs.multiversx.com/validators/staking/merge-validator-delegation-sc/). + +--- + +### Delegation Manager + +The protocol's built-in factory contract, at a fixed metachain address, from which a node operator creates a delegation contract to run a staking provider. + +Context: An operator submits a createNewDelegationContract request to the delegation manager, depositing 1250 EGLD, and receives their own delegation contract. Using the delegation manager is the standard way to set up a staking provider, though a custom smart contract is also possible. + +See also: [Delegation Contract](#delegation-contract), [Staking provider](#staking-provider), [Delegation](#delegation), [System Smart Contracts](#system-smart-contracts). +Read more: [docs.multiversx.com/validators/delegation-manager](https://docs.multiversx.com/validators/delegation-manager/). + +--- + +### Delegation Contract + +The contract that runs an individual staking provider: it tracks the provider's nodes and delegators, distributes rewards, and applies the service fee. + +Context: Created from the delegation manager with an initial 1250 EGLD from the operator (while 2500 EGLD is needed to stake a single node), the delegation contract handles staking, unstaking, and reward accounting for the pool. It also carries the pool's delegation cap and service fee. + +See also: [Delegation Manager](#delegation-manager), [Staking provider](#staking-provider), [Delegation cap](#delegation-cap), [Service Fee](#service-fee). +Read more: [docs.multiversx.com/validators/delegation-manager](https://docs.multiversx.com/validators/delegation-manager/). + +--- + +### Import DB + +A node operation mode that reprocesses an existing node database from genesis, without live network synchronization, to rebuild state or reindex history. + +Context: Import DB lets an operator replay the chain from a stored database, which is useful for reconstructing state, validating history, or populating external systems such as Elasticsearch. It can be faster than syncing from the network. + +See also: [Elastic Indexer](#elastic-indexer), [Observer (node)](#observer-node), [Deep-History Squad](#deep-history-squad). +Read more: [docs.multiversx.com/validators/import-db](https://docs.multiversx.com/validators/import-db/). + +--- + +## Wallets and accounts + +### Wallet + +Software that holds a user's keys and lets them hold assets, sign transactions, and interact with applications. + +Context: MultiversX wallets include xPortal (mobile super-app), the web wallet, browser-extension wallets, and hardware-wallet support. The wallet holds the keys; the assets live onchain. + +See also: [xPortal](#xportal), [Key Pair](#key-pair-public-and-private-key), [Onchain Guardian](#onchain-guardian), [NativeAuth](#nativeauth). +Read more: [docs.multiversx.com/wallet/web-wallet](https://docs.multiversx.com/wallet/web-wallet/). + +--- + +### xPortal + +MultiversX's official non-custodial mobile wallet and financial super-app for holding, sending, staking, and swapping EGLD and ESDTs, with an associated debit card. + +Context: xPortal is the consumer entry point to MultiversX, combining a self-custodial wallet with payments and card features. It uses herotags so users can transact to a name instead of an address. + +See also: [Wallet](#wallet), [Herotag](#herotag), [EGLD](#egld), [ESDT](#esdt-estandard-digital-token). +Read more: [multiversx.com/ecosystem/project/xportal](https://multiversx.com/ecosystem/project/xportal); MultiversX Help Center. + +--- + +### Key Pair (Public and Private Key) + +The cryptographic pair that controls an account: the public key derives the address, and the private key signs transactions. + +Context: Whoever holds the private key controls the account, which is why MultiversX added Guardians as an onchain second factor so a leaked key alone is not enough to move funds. + +See also: [Bech32 Addressing](#bech32-addressing-erd1), [Mnemonic](#mnemonic-seed-phrase), [Message Signing](#message-signing), [Onchain Guardian](#onchain-guardian). +Read more: [docs.multiversx.com/wallet/wallets](https://docs.multiversx.com/wallet/wallets/). + +--- + +### Mnemonic (Seed Phrase) + +A human-readable list of words that encodes the secret from which an account's keys are derived. + +Context: A mnemonic can regenerate the private key and address, so it must be kept secret; anyone with it controls the account. MultiversX wallets generate and restore accounts from a standard mnemonic. + +See also: [Key Pair](#key-pair-public-and-private-key), [Keystore and PEM](#keystore-and-pem), [Onchain Guardian](#onchain-guardian). +Read more: [docs.multiversx.com/wallet/wallets](https://docs.multiversx.com/wallet/wallets/); [github.com/multiversx/mx-sdk-js-core](https://github.com/multiversx/mx-sdk-js-core). + +--- + +### Keystore and PEM + +Two file formats for storing account keys: a keystore is an encrypted JSON file unlocked by a password, while a PEM file stores the key unencrypted. + +Context: Keystore files are for general use because they are password-protected. PEM files are convenient for development and automated testing but offer no protection and should not hold real funds. + +See also: [Key Pair](#key-pair-public-and-private-key), [Mnemonic](#mnemonic-seed-phrase), [Wallet](#wallet). +Read more: [docs.multiversx.com/sdk-and-tools/sdk-py](https://docs.multiversx.com/sdk-and-tools/sdk-py/); [github.com/multiversx/mx-sdk-js-core](https://github.com/multiversx/mx-sdk-js-core). + +--- + +### Message Signing + +Producing a signature over an arbitrary message, rather than a transaction, to prove control of an address without moving funds. + +Context: Signed messages back login schemes such as NativeAuth and let applications verify that a user controls an address. The signature is verified against the account's public key. + +See also: [NativeAuth](#nativeauth), [Key Pair](#key-pair-public-and-private-key), [Bech32 Addressing](#bech32-addressing-erd1). +Read more: [github.com/multiversx/mx-sdk-js-core](https://github.com/multiversx/mx-sdk-js-core); [docs.multiversx.com/sdk-and-tools/sdk-js](https://docs.multiversx.com/sdk-and-tools/sdk-js/). + +--- + +### Web Wallet + +The official browser-based MultiversX wallet, at wallet.multiversx.com, for holding EGLD and tokens, signing transactions, and staking or delegating. + +Context: The web wallet supports login by keystore file, Ledger, xPortal, and PEM, and on the test networks it provides a faucet for test funds. It is a common entry point for users and, via URL hooks, for dApp login flows. + +See also: [Wallet](#wallet), [Wallet Extension (DeFi Wallet)](#wallet-extension-defi-wallet), [xAlias](#xalias), [Keystore and PEM](#keystore-and-pem). +Read more: [docs.multiversx.com/wallet/web-wallet](https://docs.multiversx.com/wallet/web-wallet/). + +--- + +### Wallet Extension (DeFi Wallet) + +The MultiversX browser-extension wallet, for Chrome, Brave, and Firefox, that creates or imports an account from a 24-word secret phrase and connects to dApps. + +Context: The extension holds keys in the browser and signs transactions and messages for web applications, offering a lightweight alternative to the web wallet for day-to-day dApp use. + +See also: [Wallet](#wallet), [Web Wallet](#web-wallet), [Mnemonic (Seed Phrase)](#mnemonic-seed-phrase), [mx-sdk-dapp](#mx-sdk-dapp). +Read more: [docs.multiversx.com/wallet/wallet-extension](https://docs.multiversx.com/wallet/wallet-extension/). + +--- + +### Ledger (Hardware Wallet) + +A hardware wallet device that stores an account's keys offline and signs MultiversX transactions on-device using the MultiversX app. + +Context: With the MultiversX app installed through Ledger Live, a Ledger device keeps the private key isolated from the connected computer, and transactions are confirmed physically on the device. It is recommended for larger holdings. + +See also: [Wallet](#wallet), [Web Wallet](#web-wallet), [Key Pair (Public and Private Key)](#key-pair-public-and-private-key), [Onchain Guardian](#onchain-guardian). +Read more: [docs.multiversx.com/wallet/ledger](https://docs.multiversx.com/wallet/ledger/). + +--- + +### xAlias + +A single sign-on wallet that onboards users through Google Sign-In, creating a self-custody MultiversX account without a seed phrase and convertible to a conventional wallet later. + +Context: xAlias lets non-crypto users start with a familiar Web2 login while remaining self-custodial. For developers, integrating xAlias is identical to integrating the web wallet, since it exposes the same URL hooks and callbacks. + +See also: [Wallet](#wallet), [Web Wallet](#web-wallet), [Mnemonic (Seed Phrase)](#mnemonic-seed-phrase), [NativeAuth](#nativeauth). +Read more: [docs.multiversx.com/wallet/xalias](https://docs.multiversx.com/wallet/xalias/). + +--- + +### WalletConnect + +An open protocol that connects a dApp to a compatible wallet over an encrypted channel, used on MultiversX so a dApp can request transaction and message signatures from a mobile wallet such as xPortal. + +Context: MultiversX exposes WalletConnect JSON-RPC methods for signing, and xPortal implements them, so a user can approve actions on their phone while using a dApp on desktop. It is the vendor-agnostic path for mobile wallet integration. + +See also: [xPortal](#xportal), [Wallet](#wallet), [mx-sdk-dapp](#mx-sdk-dapp), [Message Signing](#message-signing). +Read more: [docs.multiversx.com/integrators/walletconnect-json-rpc-methods](https://docs.multiversx.com/integrators/walletconnect-json-rpc-methods/); [docs.multiversx.com/wallet/xportal](https://docs.multiversx.com/wallet/xportal/). + +--- + +## Ecosystem applications + +These are widely referenced MultiversX-ecosystem products rather than parts of the base protocol. They are included because the names recur in MultiversX materials; the distinction (application, not protocol) is stated in each entry. + +### xExchange + +MultiversX's flagship decentralized exchange, an automated-market-maker DEX for swapping and providing liquidity in EGLD and ESDTs, with its own governance token (MEX). + +Context: xExchange (formerly Maiar Exchange) is an ecosystem application built on MultiversX, not a protocol feature. It uses WEGLD to pair EGLD against ESDTs and offers swaps, liquidity pools, and yield farms. + +See also: [WEGLD](#wegld-wrapped-egld), [ESDT](#esdt-estandard-digital-token), [EGLD](#egld), [Liquid Staking](#liquid-staking-lsegld). +Read more: [multiversx.com/ecosystem](https://multiversx.com/ecosystem); [xexchange.com](https://xexchange.com). + +--- + +### xMoney + +A MultiversX-ecosystem payments product for accepting and making crypto payments, including merchant checkout and card features. + +Context: xMoney (formerly Utrust) is an ecosystem company and product, not a protocol feature. Like xExchange and xPortal, it is an application in the ecosystem rather than part of the base chain. + +See also: [xPortal](#xportal), [EGLD](#egld), [ESDT](#esdt-estandard-digital-token). +Read more: [multiversx.com/ecosystem](https://multiversx.com/ecosystem). + +--- + +## Upgrades + +Named mainnet protocol upgrades are ratified through onchain governance. The entries below are listed in chronological order. + +### Sirius + +The January 2024 upgrade that improved smart-contract upgrade handling, optimized signature verification, and added multi-key support for validators. + +Context: Sirius was the first major release ratified through MultiversX's onchain governance process, passing with 97.89% of voting stake in favor. It established the pattern of governing protocol upgrades by stake-weighted onchain vote. + +See also: [Governance Proposal Process](#governance-proposal-process), [Barnard](#barnard). +Read more: MultiversX State of the Foundation Report 2025 ([files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf](https://files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf)). + +--- + +### Vega + +The April 2024 upgrade (v1.7) that removed the staking-queue waiting period for new validators and introduced a chain-simulator environment for development and testing. + +Context: Removing the staking queue let new validators enter the active set without waiting for a slot to free up. The chain simulator gave developers a local environment that mimics protocol behavior without running a full network. + +See also: [Secure Proof of Stake](#secure-proof-of-stake-spos), [ScenarioWorld](#scenarioworld). +Read more: MultiversX State of the Foundation Report 2025 ([files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf](https://files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf)). + +--- + +### Spica + +The November 2024 upgrade (v1.8) that added relayed transactions V3, dynamic NFT metadata, and onchain passkey authentication. + +Context: Relayed V3 moved fee-relaying to explicit transaction fields. Dynamic NFT metadata allowed token attributes to change after minting. Onchain passkey authentication added a WebAuthn-style login path at the protocol level. + +See also: [Relayed Transactions](#relayed-transactions-v1--v2--v3), [ESDT](#esdt-estandard-digital-token). +Read more: MultiversX State of the Foundation Report 2025 ([files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf](https://files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf)). + +--- + +### Andromeda + +The May 2025 upgrade (v1.9) that restructured consensus, cutting time-to-finality from 12 seconds to 6 seconds and raising per-shard validator capacity. + +Context: Andromeda removed the legacy requirement for a separate confirmation block, so a single block carried finality. This made block time the binding latency constraint and is the predecessor that the Supernova upgrade builds on. + +See also: [Secure Proof of Stake](#secure-proof-of-stake-spos), [Supernova](#supernova). +Read more: MultiversX, "Andromeda" release post ([multiversx.com/blog/andromeda-supernova-highspeed-highways](https://multiversx.com/blog/andromeda-supernova-highspeed-highways)); State of the Foundation Report 2025. + +--- + +### Barnard + +The July 2025 upgrade (v1.10) that moved the governance contracts onchain and added millisecond-level block timestamps available to smart contracts. + +Context: Onchain governance contracts let proposals and voting execute as protocol-native operations. Millisecond timestamps gave contracts finer-grained time information than the prior second-level granularity. + +See also: [Governance Proposal Process](#governance-proposal-process), [Supernova](#supernova). +Read more: MultiversX State of the Foundation Report 2025 ([files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf](https://files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf)). + +--- + +### Staking V5 (and the emission split) + +The first phase of the Economic Evolution changes, live on mainnet since 2 December 2025 (v1.11.1), which revised how staking rewards and new EGLD issuance are distributed. + +Context: Staking V5 implemented the move away from the fixed-supply model toward tail inflation. The annual emission is split 50% to staking rewards, 20% to an ecosystem growth fund, 20% to a user growth dividend, and 10% to protocol sustainability. This split is sometimes misreported as "50/50"; the four-way split above is the figure to use. + +See also: [Economic Evolution](#economic-evolution), [KPI-Gated Emissions](#kpi-gated-emissions), [EGLD](#egld), [Developer Revenue Share](#developer-revenue-share). +Read more: MultiversX, "Release: Staking V5 and New Emissions Model, Phase 1 (v1.11.1)" (Dec 2025, [multiversx.com/release/release-economics-update-phase-1-v1-11-1](https://multiversx.com/release/release-economics-update-phase-1-v1-11-1)); emission split itemized in the economic framework proposal ([agora.multiversx.com/t/...530](https://agora.multiversx.com/t/a-competitive-economic-framework-for-multiversx-toward-revenue-and-reflexive-value-accrual/530)). + +--- + +### Economic Evolution + +The October 2025 governance proposal that dropped EGLD's fixed-supply cap and replaced it with a tail-inflation model. + +Context: Under the new framework, EGLD issuance starts at roughly 8.76% of circulating supply per year and decays toward a floor of 2% to 5%, with the decay tied to ecosystem performance indicators. Base transaction fees are split between smart-contract builders and a permanent burn, starting at 90% to builders and 10% burned and transitioning on a schedule toward a 50/50 split over eight years (see Developer Revenue Share). The proposal passed an onchain governance vote in late October 2025 with 94.55% of voting stake in favor on 41.77% participation. The first phase of protocol changes shipped as Staking V5. + +See also: [Staking V5](#staking-v5-and-the-emission-split), [KPI-Gated Emissions](#kpi-gated-emissions), [EGLD](#egld), [Developer Revenue Share](#developer-revenue-share), [Governance Proposal Process](#governance-proposal-process). +Read more: MultiversX, "The MultiversX Economic Evolution" (Oct 22, 2025, [multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion](https://multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion)); economic framework proposal on Agora ([agora.multiversx.com/t/...530](https://agora.multiversx.com/t/a-competitive-economic-framework-for-multiversx-toward-revenue-and-reflexive-value-accrual/530)). + +--- + +### Supernova + +The MultiversX upgrade that rewrites the consensus and block-propagation pipeline to compress block time from roughly 6 seconds to 600 milliseconds, by overlapping voting with execution and decoupling execution from the consensus critical path. + +Context: Supernova introduces execution dispatch before voting (validators begin executing a block once local validation passes, before casting their vote) and a metachain-mediated cross-shard finality gate that ensures cross-shard transactions are accepted only once the originating shard's block is proven final. Its onchain governance vote (8 to 18 January 2026) passed with 99.64% of voting stake in favor on a 33.63% quorum. Demonstrated figures: the Battle of Nodes public adversarial test (11 to 31 March 2026) sustained 120,000 transactions per second on its final day and processed over a billion transactions across thousands of community-run validators; the design target is 600ms blocks with sub-300ms intra-shard finality (a 100 to 250 millisecond intra-shard production target; a cross-shard transaction requires three rounds (sender's shard, metachain notarization, receiver's shard) and finalizes in approximately 1.8 seconds). As of June 2026 the upgrade has cleared an external security audit and is in final integration and testing toward mainnet; it is not yet live on mainnet. + +See also: [Cross-Shard Atomicity](#cross-shard-atomicity), [Hyperblock](#hyperblock), [Andromeda](#andromeda), [Secure Proof of Stake](#secure-proof-of-stake-spos). +Read more: MultiversX, "Supernova: decoupling consensus and execution" ([multiversx.com/blog/supernova-decoupling-consensus-and-execution](https://multiversx.com/blog/supernova-decoupling-consensus-and-execution)); test figures from xAlliance Substack, "Supernova Engine Room" series and [@MultiversX, March 31, 2026](https://x.com/MultiversX/status/2039002331640180765). + +--- + +## Framework and SDK + +These terms describe the MultiversX developer toolchain: the Rust smart-contract framework (`multiversx-sc`, repository `mx-sdk-rs`) and the dApp/JS SDKs. + +### Storage Mappers + +Typed storage abstractions in the Rust smart-contract framework that present contract state (a single value, a set, a map, a list, a token) through a method on the contract trait, hiding the raw key-value encoding. + +Context: A mapper is declared with a `#[storage_mapper("key")]` annotation and returns a typed handle, for example `SingleValueMapper` for one value or `UnorderedSetMapper` for a set. Adding arguments to the method parameterizes the storage key, so `balance(addr)` resolves to a different slot per address. Different mappers have different storage-cost and access profiles, so the choice of mapper affects gas. The framework documents the per-mapper trade-offs in source. + +See also: [Tx Syntax](#tx-syntax), [Typed Proxies](#typed-proxies), [sc-meta](#sc-meta), [WASM VM](#wasm-vm-and-wasmer). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`framework/base/src/storage/mappers/`); MultiversX docs, storage mappers ([docs.multiversx.com/developers/developer-reference/storage-mappers](https://docs.multiversx.com/developers/developer-reference/storage-mappers/)). + +--- + +### Tx Syntax + +The unified, fluent transaction builder in the Rust framework for every kind of outgoing transaction from a contract: native EGLD transfer, ESDT transfer, cross-contract call, deploy, upgrade, and read-only query. + +Context: A transaction is built by chaining methods, for example `self.tx().to(&recipient).egld(&amount).transfer()`. The builder is type-checked at compile time: combinations that are not valid, such as attaching EGLD on top of an ESDT payment, fail to compile. The same builder expresses sync calls (same-shard), async calls with a callback (cross-shard), and fire-and-forget transfers, differing only in the terminal operation. The design is described in Andrei Marinica's "One Syntax to rule them all" series (2024). + +See also: [Typed Proxies](#typed-proxies), [Storage Mappers](#storage-mappers), [Cross-Shard Atomicity](#cross-shard-atomicity). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`framework/base/src/types/interaction/tx.rs`); [docs.multiversx.com/sdk-and-tools/sdk-rust](https://docs.multiversx.com/sdk-and-tools/sdk-rust/). + +--- + +### Typed Proxies + +Plain Rust structs, generated from a contract's ABI, that let you call that contract (from another contract, a test, or an interactor) with full compile-time argument and return-type checking. + +Context: A proxy is generated by running `sc-meta all proxy` against a path declared in `sc-config.toml`; the framework reads the ABI and writes one method per endpoint. The same proxy is used in three contexts with the same method names: cross-contract calls inside a contract, blackbox tests, and interactor scripts. Proxies are plain structs (not traits), are copyable between crates, and are never hand-edited; they are regenerated when endpoints change. + +See also: [Tx Syntax](#tx-syntax), [sc-meta](#sc-meta), [ScenarioWorld](#scenarioworld). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`framework/meta-lib`); Andrei Marinica, "One Syntax to rule them all," Part 2 (2024). + +--- + +### ScenarioWorld + +The blockchain test harness in the Rust framework's scenario crate, which runs contracts against a Rust implementation of the MultiversX VM for blackbox, whitebox, and JSON-scenario (Mandos) tests. + +Context: A test sets up accounts and balances, deploys contracts, sends transactions, and asserts on results or storage, using the same typed proxies a real caller would. Blackbox tests exercise a contract as the blockchain would; whitebox tests reach into contract internals through a closure; scenario tests replay JSON (`.scen.json`, Mandos) files. The Rust VM runs contracts two ways: through an interpreter (the debugger) or by compiling them and executing through Wasmer (2.2 today, with an experimental Wasmer 6 integration). Blackbox and scenario tests can use either path, and scenario files additionally run against the production Go VM via `mx-scenario-go`, which guards against divergence between the two VMs; whitebox and unit tests use the Rust VM debugger only. Blackbox tests reference the built `.mxsc.json` artifact, so the contract must be built before they run. + +See also: [Typed Proxies](#typed-proxies), [sc-meta](#sc-meta), [Vega](#vega). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`framework/scenario`); [docs.multiversx.com/developers/testing/rust/sc-test-overview](https://docs.multiversx.com/developers/testing/rust/sc-test-overview/). + +--- + +### sc-meta + +The standalone command-line tool for MultiversX smart-contract development: building contracts to WebAssembly, generating proxies, scaffolding from templates, running tests, sending transactions, and managing the framework toolchain. + +Context: Common commands include `sc-meta all build` (compile every contract under the current path to WASM and produce the `.mxsc.json` package), `sc-meta all proxy` (regenerate typed proxies), `sc-meta new --template ` (scaffold a contract), and `sc-meta test`. The build produces an optimized WASM binary plus a package bundling the WASM, the ABI, and metadata. `sc-meta` can also send transactions from the command line, deploying and interacting with contracts directly in the manner of mxpy. `sc-meta` is published as the `multiversx-sc-meta` crate. + +See also: [Typed Proxies](#typed-proxies), [ScenarioWorld](#scenarioworld), [WASM VM](#wasm-vm-and-wasmer), [Storage Mappers](#storage-mappers). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`framework/meta`); [docs.multiversx.com/developers/meta/sc-meta](https://docs.multiversx.com/developers/meta/sc-meta/). + +--- + +### NativeAuth + +A MultiversX-native authentication scheme in which a wallet issues a signed token bound to a specific origin, a recent block hash, and an expiry, which a backend then verifies as proof of wallet control. + +Context: The token encodes the origin, a recent block hash, an expiry in seconds, and optional signed extra information; the user signs it with their wallet, and a server verifies the signature, the origin, and the chain state. Expiry is enforced by distance from the bound block rather than by a server clock, so once the chain passes the bound block plus the expiry, the token is invalid by construction, with no revocation list. It is used for wallet-gated APIs and single sign-on across MultiversX dApps, and is wired into the dApp SDK via `initApp({ dAppConfig: { nativeAuth: true } })`. + +See also: [Onchain Guardian](#onchain-guardian), [Bech32 Addressing](#bech32-addressing-erd1). +Read more: [github.com/multiversx/mx-sdk-dapp](https://github.com/multiversx/mx-sdk-dapp) (`src/services/nativeAuth/`); `@multiversx/sdk-native-auth-client` and `@multiversx/sdk-native-auth-server` packages. + +--- + +### ABI (Application Binary Interface) + +The machine-readable description of a smart contract's endpoints, arguments, return types, events, and custom types, used to encode calls and decode results. + +Context: The ABI is produced when a contract is built. SDKs and the typed-proxy generator read it to convert native values to the contract's binary encoding and back, so callers do not hand-encode arguments. It is the contract's interface contract for tools and other programs. + +See also: [Typed Proxies](#typed-proxies), [Tx Syntax](#tx-syntax), [sc-meta](#sc-meta). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs); [docs.multiversx.com/developers/data/abi](https://docs.multiversx.com/developers/data/abi/). + +--- + +### Managed Types + +The heap-light value types the Rust framework uses inside contracts (such as `BigUint`, `ManagedBuffer`, `ManagedVec`, and `ManagedAddress`) in place of standard-library types. + +Context: Contracts run in a no-std WebAssembly environment without a normal allocator, so the framework provides managed equivalents whose data lives in the VM rather than in contract memory. `BigUint` is an arbitrary-precision unsigned integer; `ManagedBuffer` replaces byte vectors and strings; `ManagedVec` replaces `Vec`. Using them keeps contracts small and gas-efficient. + +See also: [WASM VM](#wasm-vm-and-wasmer), [Storage Mappers](#storage-mappers), [Tx Syntax](#tx-syntax). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`framework/base/src/types`); [docs.multiversx.com/developers/developer-reference](https://docs.multiversx.com/developers/developer-reference/). + +--- + +### mxpy + +The MultiversX command-line tool for deploying and interacting with contracts, for sending transactions, and for performing wallet and account operations. + +Context: mxpy wraps common developer tasks (interacting with contracts, sending transactions, managing wallets, querying the network) in a CLI. + +See also: [sc-meta](#sc-meta), [Typed Proxies](#typed-proxies), [Account Nonce](#account-nonce). +Read more: [docs.multiversx.com/sdk-and-tools/mxpy](https://docs.multiversx.com/sdk-and-tools/mxpy/). + +--- + +### Account Nonce + +A per-account counter that increments with each transaction the account sends, fixing transaction order and preventing replay. + +Context: Every transaction must carry the sender's current nonce, and the network rejects one whose nonce is out of sequence. When sending several transactions in a row, a client fetches the latest nonce from the network and increments it locally. The nonce is account-scoped, not global. + +See also: [Bech32 Addressing](#bech32-addressing-erd1), [Relayed Transactions](#relayed-transactions-v1--v2--v3), [mxpy](#mxpy). +Read more: [docs.multiversx.com/developers/account-management](https://docs.multiversx.com/developers/account-management/); [github.com/multiversx/mx-sdk-js-core](https://github.com/multiversx/mx-sdk-js-core). + +--- + +### multiversx-sc (mx-sdk-rs) + +The official Rust framework for writing, building, and testing MultiversX smart contracts, distributed in the `mx-sdk-rs` repository. + +Context: Also called the SpaceCraft framework, it provides the contract API, managed types, storage mappers, the unified Tx syntax, typed proxies, the build tool `sc-meta`, and a Rust implementation of the VM for testing. Most MultiversX contracts are written with it. + +See also: [Storage Mappers](#storage-mappers), [Tx Syntax](#tx-syntax), [Typed Proxies](#typed-proxies), [sc-meta](#sc-meta), [Managed Types](#managed-types). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs); [docs.multiversx.com/sdk-and-tools/overview](https://docs.multiversx.com/sdk-and-tools/overview/). + +--- + +### mx-sdk-js-core + +The core JavaScript and TypeScript SDK for interacting with MultiversX: accounts, transactions, signing, queries, and smart contract calls. + +Context: It provides the primitives (Address, Transaction, Account, network providers) used by backends, scripts, and higher-level libraries such as the dApp SDK. + +See also: [mx-sdk-dapp](#mx-sdk-dapp), [Account Nonce](#account-nonce), [NativeAuth](#nativeauth). +Read more: [github.com/multiversx/mx-sdk-js-core](https://github.com/multiversx/mx-sdk-js-core); [docs.multiversx.com/sdk-and-tools/sdk-js](https://docs.multiversx.com/sdk-and-tools/sdk-js/). + +--- + +### mx-sdk-dapp + +A library holding the core functional logic for building MultiversX dApps: wallet-provider connections, transaction signing and tracking, and login state. + +Context: It abstracts the supported wallet providers (xPortal/WalletConnect, extension, web wallet, Ledger, passkey) behind one interface and manages the sign, send, and track lifecycle. It is framework-agnostic, with React conveniences. + +See also: [mx-sdk-js-core](#mx-sdk-js-core), [NativeAuth](#nativeauth), [Wallet](#wallet). +Read more: [github.com/multiversx/mx-sdk-dapp](https://github.com/multiversx/mx-sdk-dapp); [docs.multiversx.com/sdk-and-tools/sdk-dapp](https://docs.multiversx.com/sdk-and-tools/sdk-dapp/). + +--- + +### Scenarios (testing) + +A JSON-based test format that describes blockchain state, transactions, and expected results, runnable against both the Rust VM and the production Go VM. + +Context: Scenario files (formerly called Mandos) let the same test verify a contract under the Rust test framework and the Go VM, which guards against divergence between them. The Rust framework also offers blackbox and whitebox test styles over the same engine. + +See also: [ScenarioWorld](#scenarioworld), [sc-meta](#sc-meta), [Chain Simulator](#chain-simulator). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs); [docs.multiversx.com/developers/testing/rust/sc-test-overview](https://docs.multiversx.com/developers/testing/rust/sc-test-overview/). + +--- + +### Chain Simulator + +A local binary that mimics a MultiversX network, exposing the proxy endpoints plus extra controls so developers can test against realistic behavior without a full testnet. + +Context: Introduced with the Vega upgrade, it lets developers drive network state and fast-forward epochs for integration tests. It replicates a local testnet's behavior for faster, deterministic development. + +See also: [ScenarioWorld](#scenarioworld), [MultiversX Proxy](#multiversx-proxy), [Vega](#vega). +Read more: [docs.multiversx.com/sdk-and-tools/chain-simulator](https://docs.multiversx.com/sdk-and-tools/chain-simulator/). + +--- + +### MultiversX Proxy + +A service that sits in front of observer nodes and exposes a single HTTP API, routing each request to the correct shard. + +Context: The proxy is the component an observing squad uses to present the whole sharded network as one endpoint. Public gateways and self-hosted setups both run a proxy. + +See also: [Observing Squad](#observing-squad), [MultiversX API](#multiversx-api), [Observer](#observer-node). +Read more: [docs.multiversx.com/sdk-and-tools/overview](https://docs.multiversx.com/sdk-and-tools/overview/); [docs.multiversx.com/integrators/observing-squad](https://docs.multiversx.com/integrators/observing-squad/). + +--- + +### MultiversX API + +The public HTTP API (`api.multiversx.com`) that serves account, transaction, token, and network data, backed by observers and an indexer. + +Context: It is the most common way applications read MultiversX data and submit transactions, layering a richer, indexed interface (using Elasticsearch) over the lower-level proxy. Network-specific hosts exist for devnet and testnet. + +See also: [MultiversX Proxy](#multiversx-proxy), [Observing Squad](#observing-squad), [NativeAuth](#nativeauth). +Read more: [docs.multiversx.com/sdk-and-tools/rest-api](https://docs.multiversx.com/sdk-and-tools/rest-api/). + +--- + +### Contract Trait (#[contract], #[module]) + +The Rust framework's model in which a smart contract is written as a trait, annotated with `#[multiversx_sc::contract]`, whose methods become the contract's endpoints, views, and storage declarations. + +Context: Annotations on the trait's methods mark behavior: `#[init]` and `#[upgrade]` for lifecycle, `#[endpoint]` and `#[view]` for callable methods, `#[storage_mapper("key")]` for state, `#[payable]` and `#[only_owner]` for guards, and `#[event]` for logs. Reusable logic is factored into `#[multiversx_sc::module]` traits and composed by listing them as supertraits. The build tool reads this trait to generate the WASM entry points and the ABI. + +See also: [multiversx-sc (mx-sdk-rs)](#multiversx-sc-mx-sdk-rs), [Storage Mappers](#storage-mappers), [Reusable Modules](#reusable-modules-multiversx-sc-modules), [ABI](#abi-application-binary-interface), [sc-meta](#sc-meta). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`framework/derive`); [docs.multiversx.com/developers/developer-reference](https://docs.multiversx.com/developers/developer-reference/). + +--- + +### Reusable Modules (multiversx-sc-modules) + +A library of ready-made contract modules in the Rust framework that a contract can inherit to gain common functionality without reimplementing it. + +Context: Modules are trait-based and composed by listing them as supertraits of the contract trait. The `multiversx-sc-modules` crate ships modules for pausing, admin-only access control, governance, staking, ESDT helpers, subscriptions, DNS, token merging, bonding curves, developer-reward claiming, and long-running operations. A contract opts in by adding the module to its trait bounds and gains the module's endpoints and storage. + +See also: [multiversx-sc (mx-sdk-rs)](#multiversx-sc-mx-sdk-rs), [Contract Trait (#[contract], #[module])](#contract-trait-contract-module), [Storage Mappers](#storage-mappers), [Tx Syntax](#tx-syntax). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`contracts/modules`); [docs.multiversx.com/developers/developer-reference](https://docs.multiversx.com/developers/developer-reference/). + +--- + +### Multi-Value Types + +Framework types that let a contract endpoint take a variable or optional number of arguments, or return several values, by mapping a single logical type to multiple serialized argument slots. + +Context: `MultiValueEncoded` carries a variable-length list of arguments or results; `OptionalValue` is a trailing optional argument; `MultiValue2<...>` through `MultiValueN` group a fixed number of values. They exist because a contract call's arguments are a flat list, and these types describe how a Rust signature maps onto that list. + +See also: [multiversx-sc (mx-sdk-rs)](#multiversx-sc-mx-sdk-rs), [Managed Types](#managed-types), [ABI](#abi-application-binary-interface). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`framework/base/src/types`); [docs.multiversx.com/developers/developer-reference](https://docs.multiversx.com/developers/developer-reference/). + +--- + +### Interactors (Snippets) + +The Rust framework's tooling for driving a real network, or the chain simulator, from off-chain code, using the same typed proxies that contracts and tests use. + +Context: Built on the `multiversx-sc-snippets` crate, an interactor sends transactions and queries against a gateway or the chain simulator, so the identical proxy methods serve three settings: cross-contract calls inside a contract, blackbox tests, and live interaction. Interactors are how a project scripts deploys and end-to-end checks against devnet, testnet, or mainnet. + +See also: [Typed Proxies](#typed-proxies), [ScenarioWorld](#scenarioworld), [Chain Simulator](#chain-simulator), [sc-meta](#sc-meta). +Read more: [github.com/multiversx/mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) (`framework/snippets`); [docs.multiversx.com/sdk-and-tools/sdk-rust](https://docs.multiversx.com/sdk-and-tools/sdk-rust/). + +--- + +### Official SDKs + +The set of MultiversX libraries for interacting with the blockchain from different languages and frameworks. + +Context: The docs list sdk-js (JavaScript and TypeScript), sdk-dapp (React and framework-agnostic dApp logic), sdk-py (Python), sdk-nestjs (NestJS microservices), sdk-go (Go), plus community-referenced mxjava (Java) and erdcpp (C++). Each provides wallet, transaction, signing, query, and contract-interaction capabilities in its language. + +See also: [mx-sdk-js-core](#mx-sdk-js-core), [mx-sdk-dapp](#mx-sdk-dapp), [sdk-py](#sdk-py), [sdk-nestjs](#sdk-nestjs), [mxpy](#mxpy). +Read more: [docs.multiversx.com/sdk-and-tools/overview](https://docs.multiversx.com/sdk-and-tools/overview/). + +--- + +### sdk-py + +The official Python SDK for MultiversX, used to create wallets, build and send transactions, and interact with smart contracts and the network. + +Context: sdk-py is the Python counterpart to sdk-js, suited to backend services, scripting, and automation. The mxpy command-line tool builds on the same Python libraries. + +See also: [Official SDKs](#official-sdks), [mxpy](#mxpy), [mx-sdk-js-core](#mx-sdk-js-core). +Read more: [docs.multiversx.com/sdk-and-tools/sdk-py](https://docs.multiversx.com/sdk-and-tools/sdk-py/). + +--- + +### sdk-nestjs + +A MultiversX SDK for the NestJS framework, commonly used across the MultiversX microservice ecosystem. + +Context: sdk-nestjs packages building blocks for backend microservices, including native-auth handling, caching, and monitoring utilities, so services in the ecosystem share common infrastructure. + +See also: [Official SDKs](#official-sdks), [NativeAuth](#nativeauth), [mx-sdk-js-core](#mx-sdk-js-core). +Read more: [docs.multiversx.com/sdk-and-tools/sdk-nestjs](https://docs.multiversx.com/sdk-and-tools/sdk-nestjs/). + +--- + +### Endpoint + +A public method of a smart contract that can be called by a transaction, declared in the Rust framework with the #[endpoint] annotation. + +Context: Endpoints are the contract's callable surface. A method marked #[endpoint] is state-changing; a read-only method is marked #[view]. The build tool exposes annotated methods as the contract's WASM entry points and records them in the ABI. + +See also: [View](#view), [Contract Trait (#[contract], #[module])](#contract-trait-contract-module), [ABI (Application Binary Interface)](#abi-application-binary-interface), [Payable Endpoint](#payable-endpoint). +Read more: [docs.multiversx.com/developers/developer-reference/sc-annotations](https://docs.multiversx.com/developers/developer-reference/sc-annotations/). + +--- + +### View + +A read-only endpoint, declared with the #[view] annotation, intended to query contract state without changing it. + +Context: A view is functionally similar to an endpoint but signals read-only intent and is typically used for queries. Views are recorded in the ABI so SDKs and explorers can call them to read contract state. + +See also: [Endpoint](#endpoint), [ABI (Application Binary Interface)](#abi-application-binary-interface), [Contract Trait (#[contract], #[module])](#contract-trait-contract-module). +Read more: [docs.multiversx.com/developers/developer-reference/sc-annotations](https://docs.multiversx.com/developers/developer-reference/sc-annotations/). + +--- + +### Init and Upgrade + +The lifecycle methods of a contract: #[init] runs once when the contract is deployed, and #[upgrade] runs when the contract's code is replaced. + +Context: The init constructor can take arguments to set up initial state. Since framework v1.6.0 a dedicated #[upgrade] function handles code upgrades separately from init, so migration logic is explicit. Storage persists across an upgrade, so changing the storage layout needs care. + +See also: [Contract Trait (#[contract], #[module])](#contract-trait-contract-module), [Code Metadata](#code-metadata), [sc-meta](#sc-meta). +Read more: [docs.multiversx.com/developers/developer-reference/sc-annotations](https://docs.multiversx.com/developers/developer-reference/sc-annotations/); [docs.multiversx.com/developers/developer-reference/upgrading-smart-contracts](https://docs.multiversx.com/developers/developer-reference/upgrading-smart-contracts/). + +--- + +### Payable Endpoint + +An endpoint marked #[payable] so it can receive a payment (EGLD or ESDT tokens) along with the call. + +Context: Without the payable annotation an endpoint rejects attached value. #[payable("EGLD")] or #[payable("TOKEN-ID")] narrows what is accepted; the contract reads the incoming payment through the call-value API. This is distinct from the code-metadata payable flag, which governs plain transfers to the contract. + +See also: [Endpoint](#endpoint), [Code Metadata](#code-metadata), [ESDT (eStandard Digital Token)](#esdt-estandard-digital-token), [Tx Syntax](#tx-syntax). +Read more: [docs.multiversx.com/developers/developer-reference/sc-payments](https://docs.multiversx.com/developers/developer-reference/sc-payments/). + +--- + +### only_owner + +An annotation that restricts an endpoint so only the contract's owner address may call it. + +Context: Marking a method #[only_owner] adds an ownership check, so administrative endpoints (for example configuration or upgrades) can be limited to the deploying account. It is the framework's built-in access guard. + +See also: [Endpoint](#endpoint), [Contract Trait (#[contract], #[module])](#contract-trait-contract-module), [Reusable Modules (multiversx-sc-modules)](#reusable-modules-multiversx-sc-modules). +Read more: [docs.multiversx.com/developers/developer-reference/sc-annotations](https://docs.multiversx.com/developers/developer-reference/sc-annotations/). + +--- + +### Contract Calls (sync, async, transfer-execute) + +The three ways a contract calls another contract: a synchronous call that runs inline and returns a result, an asynchronous call that runs after the current transaction and can trigger a callback, and a transfer-execute that sends value and calls a function without expecting a result. + +Context: A synchronous call works only when both contracts are in the same shard. An asynchronous call works across shards and reports its outcome to a #[callback]. A transfer-execute is fire-and-forget. The unified Tx syntax expresses all three, differing only in the terminal operation. + +See also: [Callback](#callback), [Tx Syntax](#tx-syntax), [Cross-Shard Atomicity](#cross-shard-atomicity), [Typed Proxies](#typed-proxies). +Read more: [docs.multiversx.com/developers/developer-reference/sc-to-sc-calls](https://docs.multiversx.com/developers/developer-reference/sc-to-sc-calls/). + +--- + +### Callback + +A method marked #[callback] that the framework invokes automatically when an asynchronous contract call completes, receiving the call's result along with any context passed from the call site. + +Context: Because cross-shard calls are asynchronous, a contract cannot read the return value inline; it reacts in a callback once the call finishes. The callback is where a contract handles success or failure of an async call, including any compensating action. + +See also: [Contract Calls (sync, async, transfer-execute)](#contract-calls-sync-async-transfer-execute), [Cross-Shard Atomicity](#cross-shard-atomicity), [Tx Syntax](#tx-syntax). +Read more: [docs.multiversx.com/developers/developer-reference/sc-to-sc-calls](https://docs.multiversx.com/developers/developer-reference/sc-to-sc-calls/). + +--- + +### Code Metadata + +The flags set when a contract is deployed or upgraded that control contract-level properties: whether it is upgradeable, readable by other contracts, payable, and payable by smart contracts. + +Context: Code metadata is fixed at deploy or upgrade time and governs how the contract may be treated afterward, for example whether it can be upgraded later or receive plain transfers. It is separate from per-endpoint annotations such as #[payable]. + +See also: [Init and Upgrade](#init-and-upgrade), [Payable Endpoint](#payable-endpoint), [Contract Trait (#[contract], #[module])](#contract-trait-contract-module). +Read more: [docs.multiversx.com/developers/data/code-metadata](https://docs.multiversx.com/developers/data/code-metadata/). + +--- + +### Serialization (TopEncode / NestedEncode) + +The MultiversX codec that turns contract values into bytes and back, with a top-level encoding for standalone values and a nested encoding for values inside larger structures. + +Context: Top-level encoding (TopEncode/TopDecode) is used where a single value stands alone, such as a call argument or result, and can drop leading zeros because its length is known from context. Nested encoding (NestedEncode/NestedDecode) is used inside collections, tuples, and structs, where each value's length must be encoded so it can be read unambiguously. + +See also: [ABI (Application Binary Interface)](#abi-application-binary-interface), [Managed Types](#managed-types), [Multi-Value Types](#multi-value-types). +Read more: [docs.multiversx.com/developers/data/serialization-overview](https://docs.multiversx.com/developers/data/serialization-overview/). + +--- + +### Managed Decimal + +A fixed-point decimal type in the Rust framework that pairs an integer value with a decimal scale, for representing decimal numbers inside a contract. + +Context: ManagedDecimal is a managed type suited to token amounts and rates, giving decimal arithmetic without floating point. Like other managed types its data lives in the VM rather than in contract memory. + +See also: [Managed Types](#managed-types), [Contract Trait (#[contract], #[module])](#contract-trait-contract-module). +Read more: [docs.multiversx.com/developers/best-practices/managed-decimal](https://docs.multiversx.com/developers/best-practices/managed-decimal/). + +--- + +### Gas Limit + +The maximum amount of gas a transaction may consume, declared by the sender when the transaction is built. + +Context: The gas limit must fall between the network's minimum and maximum, and any gas reserved but not used is refunded to the sender. Setting it too low causes the transaction to run out of gas; setting it generously does not, by itself, cost the full amount because of the refund. + +See also: [Gas and Fees](#gas-and-fees), [Gas Price](#gas-price), [Gas Refund](#gas-refund). +Read more: [docs.multiversx.com/developers/gas-and-fees/overview](https://docs.multiversx.com/developers/gas-and-fees/overview/). + +--- + +### Gas Price + +The price per unit of gas a transaction is willing to pay, declared by the sender and required to meet the network minimum. + +Context: The fee is gas consumed times gas price, with the execution part of a contract call charged at the gas price times a lower modifier. Raising the gas price does not change how much gas a transaction uses, only the rate paid per unit. + +See also: [Gas and Fees](#gas-and-fees), [Gas Limit](#gas-limit), [Gas-Price Modifier](#gas-price-modifier). +Read more: [docs.multiversx.com/developers/gas-and-fees/overview](https://docs.multiversx.com/developers/gas-and-fees/overview/). + +--- + +### Code Hash + +A cryptographic checksum of a deployed contract's bytecode, computed by the network and stored onchain. + +Context: The code hash lets anyone verify a contract's bytecode, and it is the basis for confirming that a reproducible build produced exactly the bytecode that is deployed. Two builds that yield the same code hash contain identical bytecode. + +See also: [Reproducible Build](#reproducible-build), [Smart Contract](#smart-contract), [Contract Address](#contract-address). +Read more: [docs.multiversx.com/developers/reproducible-contract-builds](https://docs.multiversx.com/developers/reproducible-contract-builds/). + +--- + +### Reproducible Build + +A deterministic contract build, run inside a pinned Docker image, that produces the same WebAssembly bytecode from the same source every time. + +Context: By freezing the toolchain (compiler, wasm-opt, and dependency versions) in a versioned image, a reproducible build lets a third party recompile the source and confirm, via the code hash, that it matches the deployed contract. It is how contract bytecode is independently verified. + +See also: [Code Hash](#code-hash), [sc-meta](#sc-meta), [Smart Contract](#smart-contract). +Read more: [docs.multiversx.com/developers/reproducible-contract-builds](https://docs.multiversx.com/developers/reproducible-contract-builds/). + +--- + +### Contract Address + +The bech32 address the network assigns to a smart contract when it is deployed, computed deterministically from the deployer's address and nonce. + +Context: A contract address is a recognizable subclass of the normal address format and identifies the contract's code and storage. Because it is derived from the deployer and nonce, it can be computed before deployment, which lets a deploy flow know the address in advance. + +See also: [Account](#account), [Bech32 Addressing (erd1…)](#bech32-addressing-erd1), [Account Nonce](#account-nonce), [Smart Contract](#smart-contract). +Read more: [docs.multiversx.com/developers/smart-contracts](https://docs.multiversx.com/developers/smart-contracts/). + +--- + +### Blackbox and Whitebox Testing + +Two integration-test styles in the Rust framework: a blackbox test exercises a contract only through its public interface, while a whitebox test reaches into the contract's internals. + +Context: Blackbox tests run against the contract as the blockchain would see it (and scenario files additionally run against the production Go VM), which makes them the most realistic. Whitebox tests run on the Rust VM and can access private functions and internal state for step-by-step debugging. + +See also: [ScenarioWorld](#scenarioworld), [Scenarios (testing)](#scenarios-testing), [Typed Proxies](#typed-proxies), [sc-meta](#sc-meta). +Read more: [docs.multiversx.com/developers/testing/testing-overview](https://docs.multiversx.com/developers/testing/testing-overview/). + +--- + +### Gateway + +The official public MultiversX HTTP API endpoint that fronts an observing squad, routing each request to the correct shard's observer. + +Context: The gateway (gateway.multiversx.com) exposes the proxy's endpoints over the whole sharded network without requiring authentication, so applications can read data and broadcast transactions through one host. The same software can be self-hosted for private infrastructure. + +See also: [MultiversX Proxy](#multiversx-proxy), [Observing Squad](#observing-squad), [MultiversX API](#multiversx-api). +Read more: [docs.multiversx.com/sdk-and-tools/rest-api/gateway-overview](https://docs.multiversx.com/sdk-and-tools/rest-api/gateway-overview/). + +--- + +### Elastic Indexer + +A component that ingests blockchain data from an observer and indexes it into Elasticsearch, enabling historical and search queries over chain data. + +Context: The MultiversX API layers its richer, indexed interface over data held in Elasticsearch, which the elastic indexer populates from the network. Running the indexer is how a self-hosted setup gains historical query capability. + +See also: [MultiversX API](#multiversx-api), [Observer (node)](#observer-node), [Events Notifier](#events-notifier). +Read more: [docs.multiversx.com/sdk-and-tools/elastic-indexer](https://docs.multiversx.com/sdk-and-tools/elastic-indexer/). + +--- + +### Events Notifier + +A service that receives block events from an observer and pushes them to subscribers over a message queue or WebSocket, for real-time event streaming. + +Context: The notifier lets applications react to onchain events as they happen instead of polling the API. It is used to build reactive dApps and integrations that need push-based updates. + +See also: [Elastic Indexer](#elastic-indexer), [MultiversX API](#multiversx-api), [Observer (node)](#observer-node). +Read more: [docs.multiversx.com/sdk-and-tools/notifier](https://docs.multiversx.com/sdk-and-tools/notifier/). + +--- + +### Deep-History Squad + +An observing squad that keeps a non-pruned history of the blockchain, so it can answer queries about an account's state at any past block. + +Context: A standard observing squad retains only recent state, whereas a deep-history squad preserves historical tries, letting an application read balances or storage as they were at an arbitrary block height. It is used where historical state access is required. + +See also: [Observing Squad](#observing-squad), [Snapshotless Observer](#snapshotless-observer), [MultiversX Proxy](#multiversx-proxy). +Read more: [docs.multiversx.com/integrators/deep-history-squad](https://docs.multiversx.com/integrators/deep-history-squad/). + +--- + +### Snapshotless Observer + +An observer running in a mode that skips trie snapshots at epoch boundaries and prunes trie storage, trading historical state access for a smaller footprint. + +Context: By not creating snapshots, a snapshotless observer avoids that CPU and disk overhead and serves current-state queries and transaction broadcasting efficiently. It is the opposite trade-off to a deep-history squad. + +See also: [Observer (node)](#observer-node), [Deep-History Squad](#deep-history-squad), [Observing Squad](#observing-squad). +Read more: [docs.multiversx.com/integrators/snapshotless-observing-squad](https://docs.multiversx.com/integrators/snapshotless-observing-squad/). + +--- + +## Agent stack + +MultiversX's agentic-commerce stack integrates several external standards (MCP, x402, Stripe's MPP, Google's UCP and AP2, the OpenAI/Stripe ACP) and adds protocol-native pieces for agent identity (MX-8004) and a developer hub (Agent Hub). + +### MX-8004 + +MultiversX's onchain agent identity standard, built as a set of four smart contracts covering Identity, Reputation, Validation, and Escrow, and positioned as semantically aligned with Ethereum's ERC-8004 patterns. + +Context: The four-contract framing is a point of difference from ERC-8004's three-contract framing; the Escrow contract is the fourth and is often omitted from third-party coverage. The standard's taxonomy (OASF) is carried in IPFS manifests rather than enforced onchain. MX-8004 is the current and canonical name and stays until further notice. (A rename was explored internally to avoid search-result collision with Ethereum's ERC-8004; it is not active, and all published assets use MX-8004.) + +See also: [MCP Server](#mcp-server-multiversx), [MPP](#mpp-machine-payments-protocol), [Agent Hub](#agent-hub), [x402](#x402-multiversx-adapter). +Read more: MultiversX, "Universal Identity and Trust Layer for Agents" ([multiversx.com/blog/universal-identity-and-trust-layer-for-agents](https://multiversx.com/blog/universal-identity-and-trust-layer-for-agents)). + +--- + +### MCP Server (MultiversX) + +A Model Context Protocol server that exposes MultiversX blockchain operations as tools an AI agent or assistant can call. + +Context: MCP is an open standard for connecting language models to external tools and data; the MultiversX MCP server provides tools for querying accounts, building and sending transactions, and interacting with contracts and tokens. The official MultiversX MCP package exposes 14 tools. A separate community-maintained server (referenced on the MultiversX `/ai` page) exposes 13 tools; the two should not be conflated. The companion `mx-ai-skills` repository provides 25 skills (its README states "19+", which understates the count). + +See also: [Agent Hub](#agent-hub), [MX-8004](#mx-8004), [x402](#x402-multiversx-adapter). +Read more: [github.com/multiversx/mx-mcp](https://github.com/multiversx/mx-mcp); MultiversX `/ai` page ([multiversx.com/ai](https://multiversx.com/ai)); tool and skill counts corroborated by internal review (May 2026). + +--- + +### x402 (MultiversX adapter) + +A MultiversX adapter for the x402 protocol, which uses the HTTP 402 "Payment Required" status code to let a client (including an autonomous agent) pay for a resource inline over HTTP. + +Context: x402 is an open payment standard originating outside MultiversX; the adapter lets an x402 payment settle on MultiversX. It is one of several agentic-commerce standards MultiversX has built integrations for, alongside MCP, Google's UCP and AP2, and the OpenAI/Stripe ACP. The MultiversX advantage in this area is breadth of supported standards rather than dominance of agent transaction volume. + +See also: [MPP](#mpp-machine-payments-protocol), [Agent Hub](#agent-hub), [MCP Server](#mcp-server-multiversx), [MX-8004](#mx-8004). +Read more: MultiversX, "MultiversX speaks Agent" ([multiversx.com/blog/multiversx-speaks-agent](https://multiversx.com/blog/multiversx-speaks-agent)); x402 protocol specification (external). + +--- + +### MPP (Machine Payments Protocol) + +Stripe and Tempo's Machine Payments Protocol, a standard for autonomous machine-to-machine payments, for which MultiversX serves as a settlement venue. + +Context: MPP is authored by Stripe and Tempo, not by MultiversX. The accurate framing is "Stripe's Machine Payments Protocol, settled on MultiversX," rather than "MultiversX MPP." Low-latency settlement is the property that makes a chain suitable for high-frequency machine payments, which is the connection to the Supernova upgrade. + +See also: [x402](#x402-multiversx-adapter), [Supernova](#supernova), [Agent Hub](#agent-hub), [MX-8004](#mx-8004). +Read more: MultiversX, "Stripe's Machine Payments Protocol on MultiversX" ([multiversx.com/blog/stripes-machine-payments-protocol-on-multiversx](https://multiversx.com/blog/stripes-machine-payments-protocol-on-multiversx)). + +--- + +### Agent Hub + +MultiversX's hub for agentic development, bringing together the agent identity standard, the MCP server and skills, the payment-protocol adapters, and an agent explorer, launched in March 2026. + +Context: The Agent Hub is the developer surface for building agents on MultiversX: it gathers the tooling (MCP server, `mx-ai-skills`, `mx-agent-kit`) and the standards (agent identity, MPP, x402, UCP, AP2, ACP) in one place. A devnet Agent Explorer surfaces registered agents. + +See also: [MX-8004](#mx-8004), [MCP Server](#mcp-server-multiversx), [x402](#x402-multiversx-adapter), [MPP](#mpp-machine-payments-protocol). +Read more: MultiversX, "The MultiversX Universal Agentic Commerce Stack" ([multiversx.com/blog/the-multiversx-universal-agentic-commerce-stack](https://multiversx.com/blog/the-multiversx-universal-agentic-commerce-stack)). + +--- + +### UCP (Universal Commerce Protocol) + +An open commerce-orchestration standard from Google and Shopify that coordinates the steps above payment (product discovery, checkout, and post-purchase) for AI-assisted shopping, using lower-level payment and agent protocols underneath. + +Context: UCP sits a layer above settlement: it orchestrates a purchase flow and delegates payment and agent-to-agent messaging to protocols such as AP2, A2A, and x402. Its public specification was published in January 2026 and it is wired into Google's AI shopping surfaces. MultiversX built an integration so a UCP flow can settle onchain; as with the other agentic standards, the MultiversX position is breadth of supported standards rather than control of the standard itself. + +See also: [AP2](#ap2-agent-payments-protocol), [ACP](#acp-agentic-commerce-protocol), [x402 (MultiversX adapter)](#x402-multiversx-adapter), [Agent Hub](#agent-hub). +Read more: Google Developers Blog, "Under the hood: Universal Commerce Protocol (UCP)"; Shopify Engineering, "UCP"; MultiversX agentic-stack coverage ([multiversx.com/blog/the-multiversx-universal-agentic-commerce-stack](https://multiversx.com/blog/the-multiversx-universal-agentic-commerce-stack)). + +--- + +### AP2 (Agent Payments Protocol) + +Google's open protocol for agent-initiated payments, built around cryptographically signed "mandates" that create a non-repudiable record of what a user authorized an agent to buy. + +Context: AP2 chains a user's intent, the resulting cart, and the payment into signed mandates, so an agent's purchase can be proven to match what the user approved. It is rail-agnostic, with crypto settlement handled through an x402 extension, and launched in September 2025 with a broad partner list (including Mastercard, PayPal, American Express, and Coinbase). MultiversX is among the chains that can settle AP2-authorized payments. + +See also: [UCP](#ucp-universal-commerce-protocol), [ACP](#acp-agentic-commerce-protocol), [x402 (MultiversX adapter)](#x402-multiversx-adapter), [MX-8004](#mx-8004), [Agent Hub](#agent-hub). +Read more: Google Cloud Blog, "Announcing the Agent Payments Protocol (AP2)"; MultiversX agentic-stack coverage ([multiversx.com/blog/the-multiversx-universal-agentic-commerce-stack](https://multiversx.com/blog/the-multiversx-universal-agentic-commerce-stack)). + +--- + +### ACP (Agentic Commerce Protocol) + +The Agentic Commerce Protocol from OpenAI and Stripe, a standard for letting an AI assistant complete a purchase (for example, checkout inside ChatGPT), for which MultiversX has built an integration. + +Context: ACP, published in September 2025, covers the checkout handoff between an assistant and a merchant, paired with Stripe's Shared Payment Token. Note a naming collision: "ACP" is also used by Virtuals for a separate Agent Commerce Protocol on Base; the MultiversX agentic stack refers to the OpenAI/Stripe Agentic Commerce Protocol. As with the other standards here, MultiversX provides an adapter rather than authoring the protocol. + +See also: [UCP](#ucp-universal-commerce-protocol), [AP2](#ap2-agent-payments-protocol), [MPP](#mpp-machine-payments-protocol), [x402 (MultiversX adapter)](#x402-multiversx-adapter), [Agent Hub](#agent-hub). +Read more: Stripe newsroom, "Stripe and OpenAI bring Instant Checkout to ChatGPT" (Sept 2025); MultiversX agentic-stack coverage ([multiversx.com/blog/the-multiversx-universal-agentic-commerce-stack](https://multiversx.com/blog/the-multiversx-universal-agentic-commerce-stack)). + +--- + +### mx-agent-kit + +A MultiversX toolkit that exposes blockchain actions (such as building and sending transactions or calling contracts) as functions an AI agent can invoke, so a developer can give an agent the ability to transact on MultiversX. + +Context: The agent kit wraps the SDK's operations as agent-callable actions, alongside the MCP server and mx-ai-skills, and is one of the building blocks gathered under the Agent Hub. Some of the agent tooling is maintained on a core engineer's personal GitHub rather than under the `multiversx` organization, so repository provenance should be confirmed before a specific link is published as official. + +See also: [MCP Server (MultiversX)](#mcp-server-multiversx), [mx-ai-skills](#mx-ai-skills), [Agent Hub](#agent-hub), [mx-sdk-js-core](#mx-sdk-js-core). +Read more: MultiversX `/ai` page ([multiversx.com/ai](https://multiversx.com/ai)); MultiversX agentic-stack coverage. (Provenance: confirm whether the kit is hosted under the `multiversx` org or a core engineer's personal account before publishing a direct link.) + +--- + +### mx-ai-skills + +A MultiversX repository of pre-built "skills" (packaged capabilities) that an AI agent or assistant can load to perform common MultiversX tasks. + +Context: The skills complement the MCP server's tools, giving agents higher-level, ready-made actions. Internal review counts 25 skills in the repository; its README states "19+", which understates the count. It is part of the agent tooling gathered under the Agent Hub. + +See also: [MCP Server (MultiversX)](#mcp-server-multiversx), [mx-agent-kit](#mx-agent-kit), [Agent Hub](#agent-hub). +Read more: MultiversX `/ai` page ([multiversx.com/ai](https://multiversx.com/ai)); skill count corroborated by internal review (May 2026). + +--- + +### Agent Explorer + +A MultiversX explorer, hosted by the Foundation on a devnet backend, for browsing agents registered onchain along with their jobs and verification records. + +Context: The explorer (at `agents.multiversx.com`) reads real onchain data from devnet: registered agents appear as onchain NFT entries with an owner address and transaction hash, and the backend tracks jobs, verifications, and per-protocol support counts. It is a working preview on devnet, not a mainnet product, and should be labeled as such. It surfaces the agents registered under the MX-8004 identity standard. + +See also: [MX-8004](#mx-8004), [MX-8004 Sub-Contracts](#mx-8004-sub-contracts-identity-reputation-validation-escrow), [Agent Hub](#agent-hub), [MCP Server (MultiversX)](#mcp-server-multiversx). +Read more: MultiversX `/ai` page ([multiversx.com/ai](https://multiversx.com/ai)); Agent Explorer ([agents.multiversx.com](https://agents.multiversx.com)), a devnet preview verified by internal review (2026). + +--- + +### MX-8004 Sub-Contracts (Identity, Reputation, Validation, Escrow) + +The four smart contracts that make up the MX-8004 agent-identity standard: Identity (an agent's onchain identifier and metadata), Reputation (feedback and scoring), Validation (verification of an agent's work), and Escrow (holding funds for agent jobs). + +Context: The first three mirror the three registries of Ethereum's ERC-8004 (Identity, Reputation, Validation); MX-8004 adds Escrow as a fourth, which is the main structural difference and is often omitted from third-party coverage. As with MX-8004 generally, the standard is not yet ratified or published at an official standards URL, and parts of the implementation live on a core engineer's personal GitHub rather than under the `multiversx` organization, so it should be described as emerging rather than as a finalized Foundation standard. + +See also: [MX-8004](#mx-8004), [Agent Explorer](#agent-explorer), [Agent Hub](#agent-hub), [Herotag](#herotag). +Read more: MultiversX, "Universal Identity and Trust Layer for Agents" ([multiversx.com/blog/universal-identity-and-trust-layer-for-agents](https://multiversx.com/blog/universal-identity-and-trust-layer-for-agents)); ERC-8004 comparison and ratification status corroborated by internal review (May 2026). + +--- + +## Economics + +### Developer Revenue Share + +The portion of a smart contract's transaction fees routed to the contract's owner, rather than captured by the protocol or paid only to validators. + +Context: Today, the owner of a called smart contract can claim 30% of the fee of each call, through the `ClaimDeveloperRewards` operation. A proposed economic framework would raise the builder share substantially: 90% of base transaction fees to the called contract's author and 10% permanently burned, rebalancing by 5% per year toward a 50/50 builder/burn split over eight years, with multi-contract transactions splitting the share in proportion to the gas each contract used and priority (tip) fees going to validators. That framework is approved in principle but not yet implemented, so the 30% claimable share remains the live figure. + +See also: [Economic Evolution](#economic-evolution), [Staking V5](#staking-v5-and-the-emission-split), [KPI-Gated Emissions](#kpi-gated-emissions), [EGLD](#egld). +Read more (live, 30%): [docs.multiversx.com/developers/built-in-functions](https://docs.multiversx.com/developers/built-in-functions/) (ClaimDeveloperRewards). Read more (proposed, 90%): "A competitive economic framework for MultiversX: toward revenue and reflexive value accrual," MultiversX Agora ([agora.multiversx.com/t/...530](https://agora.multiversx.com/t/a-competitive-economic-framework-for-multiversx-toward-revenue-and-reflexive-value-accrual/530)). + +--- + +### Governance Proposal Process + +MultiversX's onchain, stake-weighted process for ratifying protocol upgrades and parameter changes, in which a proposal passes by a stake-weighted vote subject to a quorum. + +Context: Voting power is proportional to staked EGLD, and proposals (protocol upgrades, economic-model changes) are ratified by an onchain vote. Sirius (late 2023/early 2024) was the first major upgrade passed this way; the governance contracts themselves were moved fully onchain in the Barnard upgrade (July 2025). Recent recorded outcomes include the Economic Evolution vote (94.55% in favor on 41.77% participation, October 2025) and the Supernova vote (99.64% in favor on 33.63% quorum, January 2026). The Foundation reports that all six governance proposals submitted across 2024 and 2025 passed. + +See also: [Economic Evolution](#economic-evolution), [Supernova](#supernova), [Barnard](#barnard), [Sirius](#sirius). +Read more: [docs.multiversx.com/governance/overview](https://docs.multiversx.com/governance/overview/); MultiversX State of the Foundation Report 2025 ([files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf](https://files.multiversx.com/MultiversX-State-of-the-Foundation-Report-2025.pdf)). + +--- + +### Tail Inflation + +An emissions model with no fixed supply cap, in which issuance continues at a declining rate rather than stopping at a maximum supply. + +Context: The Economic Evolution proposal moved EGLD from a fixed cap to tail inflation, starting near 8.76% per year and decaying toward a 2% to 5% floor, with part of issuance gated on ecosystem KPIs. The intent is to keep funding security and growth past the point where the original cap would have been reached. + +See also: [Economic Evolution](#economic-evolution), [KPI-Gated Emissions](#kpi-gated-emissions), [EGLD](#egld), [Fee Burn](#fee-burn). +Read more: MultiversX, "The MultiversX Economic Evolution" ([multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion](https://multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion)); economic framework proposal on Agora ([agora.multiversx.com/t/...530](https://agora.multiversx.com/t/a-competitive-economic-framework-for-multiversx-toward-revenue-and-reflexive-value-accrual/530)). + +--- + +### Fee Burn + +The permanent removal from supply of a portion of transaction fees. + +Context: Under the Economic Evolution framework, 10% of base transaction fees are burned today, rising on a schedule toward 50% over eight years as the developer share falls. Burning offsets issuance and ties supply pressure to network usage. + +See also: [Developer Revenue Share](#developer-revenue-share), [Economic Evolution](#economic-evolution), [Tail Inflation](#tail-inflation), [Gas and Fees](#gas-and-fees). +Read more: economic framework proposal on Agora ([agora.multiversx.com/t/...530](https://agora.multiversx.com/t/a-competitive-economic-framework-for-multiversx-toward-revenue-and-reflexive-value-accrual/530)); MultiversX, "The MultiversX Economic Evolution" ([multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion](https://multiversx.com/blog/the-multiversx-economic-evolution-a-blueprint-for-growth-and-strategic-expansion)). + +--- + +### APR (Annual Percentage Rate) + +The yearly rate of staking rewards, expressed as a percentage of the staked amount. + +Context: A validator's or delegator's APR depends on total network stake, the node's topUp, and the staking provider's service fee. It is the headline yield figure for staking on MultiversX. + +See also: [Staking](#staking), [Delegation](#delegation), [Service Fee](#service-fee), [Staking Auction and TopUp](#staking-auction-and-topup). +Read more: [docs.multiversx.com/economics/staking-providers-apr](https://docs.multiversx.com/economics/staking-providers-apr/). + +--- + +### Service Fee + +The percentage a staking provider takes from the rewards earned on its delegators' stake. + +Context: The provider sets the service fee, and delegators receive rewards net of it. It is how staking providers are compensated for operating nodes. + +See also: [Delegation](#delegation), [Staking](#staking), [APR](#apr-annual-percentage-rate). +Read more: [docs.multiversx.com/validators/delegation-manager](https://docs.multiversx.com/validators/delegation-manager/). + +--- + +### Governance Proposal + +An onchain proposal, identified by a code commit hash, that the community votes on to change the protocol or its parameters. + +Context: Anyone can submit a proposal by paying the proposal fee (currently 500 EGLD) after at least 15 days of public debate on Agora, and setting a voting start epoch. If the proposal passes the fee is refunded; if it fails a portion is kept as a lost-proposal fee; if it is vetoed the fee is slashed. A proposal cannot be duplicated and can be cancelled by the issuer before voting starts. + +See also: [Governance Proposal Process](#governance-proposal-process), [Quorum](#quorum), [Veto](#veto), [Voting Power](#voting-power). +Read more: [docs.multiversx.com/governance/overview](https://docs.multiversx.com/governance/overview/). + +--- + +### Quorum + +The minimum share of total voting power that must participate in a governance vote for the proposal to be valid, currently at least 20%. + +Context: If participation falls below the quorum, the proposal fails regardless of how the votes split. Quorum is a configurable governance parameter that ensures a decision reflects broad participation. + +See also: [Governance Proposal](#governance-proposal), [Voting Power](#voting-power), [Veto](#veto), [Governance Proposal Process](#governance-proposal-process). +Read more: [docs.multiversx.com/governance/overview](https://docs.multiversx.com/governance/overview/). + +--- + +### Veto + +A vote type in MultiversX governance that rejects a proposal, and its threshold: if veto votes exceed 33% of the participating voting power, the proposal is rejected and its fee is slashed. + +Context: Voters choose among Yes, No, Abstain, and Veto. Veto is a minority-protection mechanism, letting a sufficiently large share block a proposal that could harm the network even if a majority is in favor. Acceptance also requires Yes over Yes-plus-No-plus-Veto to reach at least 66.67%. + +See also: [Governance Proposal](#governance-proposal), [Quorum](#quorum), [Voting Power](#voting-power), [Governance Proposal Process](#governance-proposal-process). +Read more: [docs.multiversx.com/governance/overview](https://docs.multiversx.com/governance/overview/). + +--- + +### Voting Power + +A voter's weight in governance, equal to their staked plus delegated EGLD, applied linearly so that each EGLD counts as one unit. + +Context: Voting power is derived from stake, not liquid balance, and the governance contract tracks both used and total available voting power per address. Delegation and liquid-staking contracts can forward their users' voting power. + +See also: [Governance Proposal](#governance-proposal), [Quorum](#quorum), [Veto](#veto), [Staking](#staking). +Read more: [docs.multiversx.com/governance/overview](https://docs.multiversx.com/governance/overview/). + +--- + +## Staking operations + +Practical staking and delegation vocabulary, aligned with the MultiversX docs terminology page. These describe the day-to-day actions a staker or delegator takes. + +### Validate + +Running a validator node and contributing to the network by relaying and validating information. + +Context: Validating requires staking at least 2500 EGLD per node and participating in consensus. It is the direct, node-operating form of securing the network, as opposed to delegating stake to someone else's nodes. + +See also: [Validator (node)](#validator-node), [Staking](#staking), [Delegate](#delegate), [Secure Proof of Stake (SPoS)](#secure-proof-of-stake-spos). +Read more: [docs.multiversx.com/validators/overview](https://docs.multiversx.com/validators/overview/). + +--- + +### Stake + +Contributing to network security by delegating 1 EGLD or more toward a staking provider that operates validator nodes. + +Context: As a day-to-day action for a token holder, staking here means placing EGLD with a provider rather than running a node. The funds accrue rewards, and withdrawing them requires unstaking and then waiting out the unbonding period. + +See also: [Staking](#staking), [Delegate](#delegate), [Stake rewards](#stake-rewards), [Unstake](#unstake). +Read more: [docs.multiversx.com/validators/delegation-manager](https://docs.multiversx.com/validators/delegation-manager/). + +--- + +### Delegate + +Contributing to network security by delegating 10 EGLD or more toward the MultiversX Community Delegation contract. + +Context: Delegating pools a holder's EGLD into a delegation contract that runs the nodes and takes a service fee from the rewards. It lets a holder earn staking rewards without meeting the 2500 EGLD per-node minimum or operating hardware. + +See also: [Delegation](#delegation), [Stake](#stake), [Delegate rewards](#delegate-rewards), [Service Fee](#service-fee). +Read more: [docs.multiversx.com/validators/delegation-manager](https://docs.multiversx.com/validators/delegation-manager/). + +--- + +### Stake rewards + +The income generated by funds locked to run validator nodes, which can be claimed or restaked. + +Context: Rewards accrue to staked EGLD from protocol emission and a share of fees. A staker can withdraw the rewards or compound them back into stake; the yield rate is expressed as an APR that depends on total network stake and top-up. + +See also: [Stake](#stake), [APR (Annual Percentage Rate)](#apr-annual-percentage-rate), [Delegate rewards](#delegate-rewards), [Staking](#staking). +Read more: [docs.multiversx.com/economics/staking-providers-apr](https://docs.multiversx.com/economics/staking-providers-apr/). + +--- + +### Delegate rewards + +The income generated by funds placed in delegation contracts, which can be claimed or redelegated. + +Context: A delegator receives rewards net of the provider's service fee and can either withdraw them or redelegate to compound. This is the delegation counterpart to stake rewards. + +See also: [Delegate](#delegate), [Delegation](#delegation), [Service Fee](#service-fee), [Stake rewards](#stake-rewards). +Read more: [docs.multiversx.com/economics/staking-providers-apr](https://docs.multiversx.com/economics/staking-providers-apr/). + +--- + +### Unstake + +Signaling the intention to unlock staked or delegated funds, which become available after the 10 epoch unbonding period. + +Context: Unstaking starts the exit but does not release funds immediately; the EGLD stays locked for the unbonding period before it can be withdrawn. The final withdrawal step is unbonding. + +See also: [Unbond](#unbond), [Unbonding Period](#unbonding-period), [Stake](#stake), [Delegation](#delegation). +Read more: [docs.multiversx.com/validators/staking](https://docs.multiversx.com/validators/staking/). + +--- + +### Staking provider + +A node operator that created a staking pool and accepts funds for staking. + +Context: A staking provider runs a delegation contract that pools delegators' EGLD, operates the validator nodes, and takes a service fee from the rewards. Holders choose a provider to delegate to based on its fee and reliability. + +See also: [Staking pool](#staking-pool), [Delegation Contract](#delegation-contract), [Delegate](#delegate), [Service Fee](#service-fee). +Read more: [docs.multiversx.com/validators/delegation-manager](https://docs.multiversx.com/validators/delegation-manager/). + +--- + +### Staking pool + +A system smart contract that accepts funds for staking. + +Context: The staking pool is the delegation contract that holds a provider's delegated EGLD and stakes it across the provider's nodes. Its size is bounded by a delegation cap. + +See also: [Staking provider](#staking-provider), [Delegation Contract](#delegation-contract), [Delegation cap](#delegation-cap), [System Smart Contracts](#system-smart-contracts). +Read more: [docs.multiversx.com/validators/delegation-manager](https://docs.multiversx.com/validators/delegation-manager/). + +--- + +### Delegation cap + +The maximum amount of funds a staking pool accepts. + +Context: The delegation cap is set by the pool's owner and limits total delegated EGLD; a cap of zero means unlimited. The initial owner deposit and all delegated funds count toward it. + +See also: [Staking pool](#staking-pool), [Delegation Contract](#delegation-contract), [Delegate](#delegate), [Staking provider](#staking-provider). +Read more: [docs.multiversx.com/validators/delegation-manager](https://docs.multiversx.com/validators/delegation-manager/). + +--- --- diff --git a/static/llms.txt b/static/llms.txt index 9bc16333a..a38a79099 100644 --- a/static/llms.txt +++ b/static/llms.txt @@ -46,6 +46,7 @@ This documentation is organized into major sections. Each section includes tutor - [Concept](https://docs.multiversx.com/developers/testing/scenario/concept): Concept of scenario‑based testing: composing steps to simulate blockchain interactions for contracts. - [Configuration](https://docs.multiversx.com/developers/meta/sc-config): Configure MultiversX smart contract projects: build variants, features, and multi‑contract settings. - [Constants](https://docs.multiversx.com/developers/constants): Protocol constants and defaults referenced throughout MultiversX docs: timings, sizes and network parameters. +- [Cookbook (design preview)](https://docs.multiversx.com/sdk-and-tools/sdk-js/cookbook-preview): Design-layer preview of the MultiversX Cookbook recipe system — recipe cards, difficulty, verified badges and SDK-version chips rendered from real frontmatter. - [Cookbook (v14)](https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-cookbook-v14): sdk‑js Cookbook (v14): common tasks, entrypoints, API/proxy usage and migration notes. - [Cookbook (v15)](https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-cookbook-v15): sdk‑js Cookbook (v15): common tasks, entrypoints, API/proxy usage and migration notes. - [Core Logic](https://docs.multiversx.com/developers/tutorials/crowdfunding/crowdfunding-p2): Tutorial: Core Logic @@ -147,6 +148,7 @@ This documentation is organized into major sections. Each section includes tutor - [sdk-dapp](https://docs.multiversx.com/sdk-and-tools/sdk-dapp/sdk-dapp): React SDK for MultiversX: components, hooks and providers to build dApps quickly and safely. - [sdk-js](https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js): JavaScript/TypeScript SDK: accounts, transactions, signing, smart contracts and utilities. - [SDKs and Tools - Overview](https://docs.multiversx.com/sdk-and-tools/overview): Overview of MultiversX SDKs, tools and APIs for building apps and services. +- [Send EGLD to an address](https://docs.multiversx.com/sdk-and-tools/sdk-js/cookbook-preview-recipe): Send a native EGLD transfer with sdk-core's TransfersController against DevnetEntrypoint — the backend/script pattern for when your code holds the key. [beginner, verified 2026-07-07] - [Sender](https://docs.multiversx.com/developers/transactions/tx-from): Transaction sender field: account requirements, nonce handling and permissions when originating calls. - [Set up a Localnet (mxpy)](https://docs.multiversx.com/developers/setup-local-testnet): Set up a localnet with mxpy: prerequisites, setup/start commands, components and logs. - [Set up a Localnet (raw)](https://docs.multiversx.com/developers/setup-local-testnet-advanced): Manual localnet setup using protocol repositories and scripts: prerequisites, configuration and running nodes. @@ -287,4 +289,4 @@ This documentation is organized into major sections. Each section includes tutor - [xPortal](https://docs.multiversx.com/wallet/xportal): xPortal mobile app: secure wallet, token swaps, payments, missions, AI avatar creation and upcoming debit card features. ## Terminology -- [Terminology](https://docs.multiversx.com/welcome/terminology): Glossary of MultiversX terms: Metachain, addresses, nodes, validators, staking and more. +- [Terminology](https://docs.multiversx.com/welcome/terminology): A glossary of MultiversX terms across blockchain foundations, protocol and architecture, consensus and security, tokens, nodes and staking, wallets, ecosystem applications, upgrades, and the SDK. Every entry is sourced. From f9792b6ba7a0d9f8a2f8274fd20a0052e793b2a0 Mon Sep 17 00:00:00 2001 From: Lukas <64620972+lamentierschweinchen@users.noreply.github.com> Date: Thu, 16 Jul 2026 00:24:11 +0200 Subject: [PATCH 02/13] cookbook: TS verification CI mirroring the Rust tutorial CI + 3 exemplar recipes Add a TypeScript verification harness for the sdk-js cookbook, mirroring the repo's Rust tutorial CI (testing/rust-tutorial-ci.sh + extract-tutorial-code): - testing/cookbook-ts/: a fence extractor (parser.ts + extract.ts) that pulls titled ts/tsx fences from cookbook recipe MDX into a strict TS project, plus CONVENTION.md documenting the authoring convention. - testing/cookbook-ts-ci.sh + .github/workflows/cookbook-ts-ci.yml: extract then tsc --noEmit --strict, the "what is shown is what is compiled" gate. - Three exemplar recipes ported to docs/sdk-and-tools/sdk-js/cookbook/, carrying the meta-strip frontmatter the Track A swizzle reads: a read-side network-providers recipe, a write-side transactions recipe, and a dApp/React sdk-dapp recipe. - sidebars.js: a "Cookbook (recipes)" category. Verified: extractor assembles 10 files across 3 recipes; tsc --noEmit --strict passes; a deliberate type error is rejected (TS2322); Docusaurus build is green with the meta strip server-rendered; markdownlint + codespell pass. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/cookbook-ts-ci.yml | 19 + .../configure-network-provider.mdx | 250 + .../cookbook/transactions/send-egld.mdx | 337 ++ .../wallets/read-connected-account.mdx | 407 ++ sidebars.js | 27 + testing/cookbook-ts-ci.sh | 19 + testing/cookbook-ts/.gitignore | 1 + testing/cookbook-ts/CONVENTION.md | 181 + testing/cookbook-ts/extract.ts | 122 + testing/cookbook-ts/package-lock.json | 569 ++ testing/cookbook-ts/package.json | 18 + testing/cookbook-ts/parser.ts | 150 + testing/cookbook-ts/project/.gitignore | 5 + testing/cookbook-ts/project/package-lock.json | 4750 +++++++++++++++++ testing/cookbook-ts/project/package.json | 27 + testing/cookbook-ts/project/src/scaffold.d.ts | 19 + testing/cookbook-ts/project/tsconfig.json | 41 + testing/cookbook-ts/tsconfig.json | 16 + 18 files changed, 6958 insertions(+) create mode 100644 .github/workflows/cookbook-ts-ci.yml create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/network-providers/configure-network-provider.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/transactions/send-egld.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account.mdx create mode 100755 testing/cookbook-ts-ci.sh create mode 100644 testing/cookbook-ts/.gitignore create mode 100644 testing/cookbook-ts/CONVENTION.md create mode 100644 testing/cookbook-ts/extract.ts create mode 100644 testing/cookbook-ts/package-lock.json create mode 100644 testing/cookbook-ts/package.json create mode 100644 testing/cookbook-ts/parser.ts create mode 100644 testing/cookbook-ts/project/.gitignore create mode 100644 testing/cookbook-ts/project/package-lock.json create mode 100644 testing/cookbook-ts/project/package.json create mode 100644 testing/cookbook-ts/project/src/scaffold.d.ts create mode 100644 testing/cookbook-ts/project/tsconfig.json create mode 100644 testing/cookbook-ts/tsconfig.json diff --git a/.github/workflows/cookbook-ts-ci.yml b/.github/workflows/cookbook-ts-ci.yml new file mode 100644 index 000000000..58346c257 --- /dev/null +++ b/.github/workflows/cookbook-ts-ci.yml @@ -0,0 +1,19 @@ +name: Cookbook TS verification + +on: + push: + branches: + - master + pull_request: + +jobs: + cookbook_ts_test: + name: Cookbook TypeScript recipe test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Extract cookbook recipes and type-check + run: ./testing/cookbook-ts-ci.sh diff --git a/docs/sdk-and-tools/sdk-js/cookbook/network-providers/configure-network-provider.mdx b/docs/sdk-and-tools/sdk-js/cookbook/network-providers/configure-network-provider.mdx new file mode 100644 index 000000000..d56c73c52 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/network-providers/configure-network-provider.mdx @@ -0,0 +1,250 @@ +--- +title: Configure a network provider (Api vs Proxy) +description: Construct an ApiNetworkProvider or ProxyNetworkProvider, tune clientName and timeout, or get one from an entrypoint. Both share one INetworkProvider interface. +difficulty: beginner +est_minutes: 5 +last_validated: "2026-07-16" +sdk_versions: + sdk-core: "^15.4.0" +tags: + - sdk-core + - network-provider + - api + - proxy + - typescript +--- + +Every read-side call in sdk-core (fetching an account, a block, a token +balance, network config) goes through an `INetworkProvider`. Before you can read +anything, you need one. There are two implementations, and this recipe builds +both. + +- **`ApiNetworkProvider`** talks to the MultiversX HTTP API (for example + `api.multiversx.com`), backed by Elasticsearch. It has the richest, indexed + endpoints: token lists per account, transaction history, token metadata. This + is the default choice for most apps and backends. +- **`ProxyNetworkProvider`** talks to a gateway / observing-squad proxy (for + example `gateway.multiversx.com`). It sits closer to the protocol and exposes + a few endpoints the API does not, most notably block-by-nonce. + +Both implementations share one `INetworkProvider` interface, so your code should +depend on `INetworkProvider`, not the concrete class. You can swap one for the +other without touching a call site. + +## Prerequisites + +- Node.js >= 20.13.1. +- Network access. No wallet, no PEM, no EGLD. + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/configure-network-provider +npm install +npm run build +npm start +``` + +## Constructing the providers + +```ts title="src/providers.ts" +// src/providers.ts — the subject of this recipe: how to construct a network +// provider. Everything read-side in sdk-core goes through the same +// `INetworkProvider` interface, and there are two implementations: +// +// - ApiNetworkProvider → the MultiversX HTTP API (e.g. api.multiversx.com), +// backed by Elasticsearch. Richer, indexed endpoints: token lists per +// account, transaction history, token metadata. +// - ProxyNetworkProvider → a gateway / observing-squad proxy (e.g. +// gateway.multiversx.com). Closer to the protocol; exposes a few +// endpoints the API does not (notably block-by-nonce). +// +// Both implement `INetworkProvider`, so application code should depend on the +// interface, not the concrete class: you can swap Api for Proxy without +// touching a call site. + +import { + ApiNetworkProvider, + ProxyNetworkProvider, + DevnetEntrypoint, +} from '@multiversx/sdk-core'; +import type { INetworkProvider, NetworkProviderConfig } from '@multiversx/sdk-core'; + +// A `clientName` is recommended on every provider — it identifies your app in +// the network's request metrics. Omit it and the SDK logs a recommendation to +// the console on every construction. +const CLIENT_NAME = 'mvx-cookbook'; + +/** The API provider — the default choice for most apps and backends. */ +export function createApiProvider(url: string): INetworkProvider { + const config: NetworkProviderConfig = { clientName: CLIENT_NAME }; + return new ApiNetworkProvider(url, config); +} + +/** The Proxy provider — same interface, different backend. */ +export function createProxyProvider(url: string): INetworkProvider { + const config: NetworkProviderConfig = { clientName: CLIENT_NAME }; + return new ProxyNetworkProvider(url, config); +} + +/** + * `NetworkProviderConfig` extends Axios's `AxiosRequestConfig`, so you set the + * request `timeout` (milliseconds), custom `headers`, proxy agents, etc. in the + * same object as `clientName`. + */ +export function createApiProviderWithConfig(url: string): INetworkProvider { + const config: NetworkProviderConfig = { + clientName: CLIENT_NAME, + timeout: 10_000, + }; + return new ApiNetworkProvider(url, config); +} + +/** + * You rarely construct a provider by hand in a script — an entrypoint already + * holds one. `createNetworkProvider()` hands you the exact same + * `INetworkProvider` the controllers and factories use internally. A default + * `DevnetEntrypoint()` gives you an `ApiNetworkProvider` for devnet. + */ +export function providerFromEntrypoint(): INetworkProvider { + return new DevnetEntrypoint().createNetworkProvider(); +} + +/** + * To get a Proxy-backed provider from an entrypoint, pass `kind: 'proxy'` and a + * gateway URL. Everything else about the entrypoint stays the same. + */ +export function proxyProviderFromEntrypoint(gatewayUrl: string): INetworkProvider { + return new DevnetEntrypoint({ url: gatewayUrl, kind: 'proxy' }).createNetworkProvider(); +} +``` + +## Proving each one is live + +```ts title="src/index.ts" +// src/index.ts — constructs each kind of provider and proves it is live by +// calling getNetworkConfig() on it. No wallet, no PEM, no gas — every call +// below is a plain read. +// +// Usage: +// npm run build && npm start + +import { + createApiProvider, + createProxyProvider, + createApiProviderWithConfig, + providerFromEntrypoint, + proxyProviderFromEntrypoint, +} from './providers'; + +const MAINNET_API = 'https://api.multiversx.com'; +const MAINNET_GATEWAY = 'https://gateway.multiversx.com'; +const DEVNET_GATEWAY = 'https://devnet-gateway.multiversx.com'; + +async function main(): Promise { + const api = createApiProvider(MAINNET_API); + const apiConfig = await api.getNetworkConfig(); + console.log(`ApiNetworkProvider (${MAINNET_API})`); + console.log(` live — chainID ${apiConfig.chainID}, minGasPrice ${apiConfig.minGasPrice}`); + + const proxy = createProxyProvider(MAINNET_GATEWAY); + const proxyConfig = await proxy.getNetworkConfig(); + console.log(`ProxyNetworkProvider (${MAINNET_GATEWAY})`); + console.log(` live — chainID ${proxyConfig.chainID}, minGasPrice ${proxyConfig.minGasPrice}`); + + const tuned = createApiProviderWithConfig(MAINNET_API); + const tunedConfig = await tuned.getNetworkConfig(); + console.log('ApiNetworkProvider + custom timeout/config'); + console.log(` live — chainID ${tunedConfig.chainID}`); + + const fromEntry = providerFromEntrypoint(); + const entryConfig = await fromEntry.getNetworkConfig(); + console.log('DevnetEntrypoint().createNetworkProvider()'); + console.log(` live — chainID ${entryConfig.chainID} (devnet)`); + + const proxyFromEntry = proxyProviderFromEntrypoint(DEVNET_GATEWAY); + const proxyEntryConfig = await proxyFromEntry.getNetworkConfig(); + console.log(`DevnetEntrypoint({ kind: 'proxy' }).createNetworkProvider()`); + console.log(` live — chainID ${proxyEntryConfig.chainID} (devnet gateway)`); +} + +main().catch((err: unknown) => { + console.error(err); + process.exitCode = 1; +}); +``` + +## Run it + +```bash +npm start +``` + +Expected output: + +```text +ApiNetworkProvider (https://api.multiversx.com) + live — chainID 1, minGasPrice 1000000000 +ProxyNetworkProvider (https://gateway.multiversx.com) + live — chainID 1, minGasPrice 1000000000 +ApiNetworkProvider + custom timeout/config + live — chainID 1 +DevnetEntrypoint().createNetworkProvider() + live — chainID D (devnet) +DevnetEntrypoint({ kind: 'proxy' }).createNetworkProvider() + live — chainID D (devnet gateway) +``` + +## How it works + +**Depend on the interface, not the class.** Every function in `providers.ts` is +typed to return `INetworkProvider`. `ApiNetworkProvider` and +`ProxyNetworkProvider` both implement it, so a function that accepts an +`INetworkProvider` takes either. This recipe proves it by running the identical +`getNetworkConfig()` call against all five providers and getting the same +`NetworkConfig` shape back. + +**`NetworkProviderConfig` extends Axios's `AxiosRequestConfig`.** The second +constructor argument is where `clientName`, `timeout` (milliseconds), custom +`headers`, and proxy agents all live. It is one object, typed as +`interface NetworkProviderConfig extends AxiosRequestConfig { clientName?: string }`. + +**An entrypoint already holds a provider.** In a real script you rarely `new` a +provider up by hand. `DevnetEntrypoint().createNetworkProvider()` hands you the +exact same `INetworkProvider` the controllers and factories use internally. Pass +`kind: 'proxy'` plus a gateway URL to get a proxy-backed one instead. + +## Pitfalls + +:::note[Pitfall 1: set clientName or the SDK nags you] +Omit `clientName` and the SDK logs a recommendation to the console on every +provider construction ("We recommend providing the `clientName`..."). It is used +for the network's request metrics. This recipe always sets it; the +entrypoint-created providers in `index.ts` do not, which is why you will see +that log line when you run it. +::: + +:::warning[Pitfall 2: Api and Proxy are NOT feature-identical] +They share the `INetworkProvider` interface, but a handful of methods differ. +`ApiNetworkProvider.getBlock(hash)` takes a hash; `ProxyNetworkProvider.getBlock({ shard, blockNonce })` +takes a shard plus nonce. Pagination (`{ from, size }`) is honored by the Api +provider but ignored by the Proxy provider's token methods. Pick the provider +whose backend actually exposes what you need. +::: + +:::note[Pitfall 3: the URL is the network, not a constructor flag] +There is no `network: 'mainnet'` option. Mainnet vs devnet vs testnet is +entirely the URL you pass (`api.multiversx.com` vs `devnet-api.multiversx.com`). +The pre-configured `DevnetEntrypoint` / `MainnetEntrypoint` classes just bake the +right URL and chain ID in for you. +::: + +## See also + +- [Send EGLD to an address](/sdk-and-tools/sdk-js/cookbook/transactions/send-egld) + builds a transfer with an entrypoint that wraps the same provider. +- [Read the connected account with useGetAccount](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account) + is the sdk-dapp counterpart, where the store holds the provider for you. +- [The versioned sdk-js cookbook](/sdk-and-tools/sdk-js/sdk-js-cookbook) covers + the wider read-side API surface. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/transactions/send-egld.mdx b/docs/sdk-and-tools/sdk-js/cookbook/transactions/send-egld.mdx new file mode 100644 index 000000000..1b37b7381 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/transactions/send-egld.mdx @@ -0,0 +1,337 @@ +--- +title: Send EGLD to an address +description: Send a native EGLD transfer with sdk-core's TransfersController against DevnetEntrypoint, the backend/script pattern for when your own code holds the key. +difficulty: beginner +est_minutes: 6 +last_validated: "2026-07-16" +sdk_versions: + sdk-core: "^15.4.0" +tags: + - sdk-core + - transaction + - typescript + - devnet +--- + +Send a native EGLD transfer using sdk-core's `TransfersController` directly +against `DevnetEntrypoint`. No browser, no connected wallet, no sdk-dapp. This is +the pattern for when **your own code holds the private key**: a script, a bot, a +payout job, a backend service. + +If instead you are building a browser dApp where the end user's own wallet should +sign, you want the sdk-dapp flow. Start with +[Read the connected account](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account). +This recipe and that flow build the same kind of transaction two structurally +different ways, for two different trust models. + +## Prerequisites + +- Node.js >= 20.13.1. +- A devnet wallet with some devnet EGLD. If you do not have one, generate a + disposable wallet with only sdk-core: + +```ts +import { Mnemonic, Account } from '@multiversx/sdk-core'; + +const mnemonic = Mnemonic.generate(); +const account = Account.newFromMnemonic(mnemonic.toString()); +account.saveToPem('./wallet.pem'); +console.log('Address:', account.address.toBech32()); +``` + +Then fund the printed address at the [devnet faucet](https://r3d4.fr/faucet) or +the [devnet web wallet](https://devnet-wallet.multiversx.com). `wallet.pem` is +git-ignored, devnet-only, and throwaway. Never reuse a plain PEM like this for +mainnet funds. + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/send-egld +npm install +npm run build +npm start -- ./wallet.pem erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th 0.01 +``` + +## Loading the account + +```ts title="src/account.ts" +// src/account.ts — load a devnet Account from a PEM file and prime its +// local nonce from the network. Framework-agnostic sdk-core, no sdk-dapp +// involved — this is the "your own code holds the keys" pattern (a script, +// a bot, a backend service), not a browser wallet-connected flow. +// +// The sdk-core nonce rule: fetch the account nonce from the network before +// sending transactions, then increment it locally with +// getNonceThenIncrement(). This helper does exactly the fetch half; the +// increment half matters once you send more than one transaction per run. + +import { Account } from '@multiversx/sdk-core'; +import type { DevnetEntrypoint } from '@multiversx/sdk-core'; + +/** + * Loads an Account from a PEM file and sets its local `nonce` from the + * network's current value — the "fetch" half of the fetch-then-increment + * pattern. + * + * `entrypoint.recallAccountNonce(address)` is a thin convenience wrapper + * around `networkProvider.getAccount(address).nonce`, used here instead of + * constructing a network provider by hand. + */ +export async function loadDevnetAccount( + entrypoint: DevnetEntrypoint, + pemPath: string, +): Promise { + const account = await Account.newFromPem(pemPath); + account.nonce = await entrypoint.recallAccountNonce(account.address); + return account; +} +``` + +## Converting the amount + +```ts title="src/amount.ts" +// src/amount.ts — decimal EGLD string <-> smallest-denomination bigint. +// +// EGLD's denomination is 10^18 (1 EGLD = 1000000000000000000). Getting this +// conversion wrong, or doing it with plain JS `Number` math, is a common +// source of bugs. Use BigNumber (already an sdk-core peer dependency) rather +// than floating point, and reject non-integer results outright instead of +// silently truncating them. + +import BigNumber from 'bignumber.js'; + +export const EGLD_DECIMALS = 18; + +/** + * Converts a decimal EGLD amount (e.g. "0.5") to the smallest-denomination + * bigint the network expects (e.g. 500000000000000000n). + * + * Throws if the input has more precision than 18 decimals — silently + * rounding would mean sending a different amount than what was typed. + */ +export function toSmallestDenomination(amountInEgld: string): bigint { + const value = new BigNumber(amountInEgld).multipliedBy( + new BigNumber(10).pow(EGLD_DECIMALS), + ); + if (!value.isFinite() || value.isNegative()) { + throw new Error(`"${amountInEgld}" is not a valid non-negative EGLD amount.`); + } + if (!value.isInteger()) { + throw new Error( + `"${amountInEgld}" EGLD has more precision than the network supports (${EGLD_DECIMALS} decimals).`, + ); + } + return BigInt(value.toFixed(0)); +} +``` + +## Sending + +```ts title="src/sendEgld.ts" +// src/sendEgld.ts — the actual subject of this recipe: sending EGLD via +// sdk-core's TransfersController, not a hand-built Transaction. +// +// Use this pattern when YOUR code holds the private key directly (a script, +// a bot, a backend payout job): sdk-core's purpose-built transfer +// factory/controller does the gas-limit and payload construction for you. +// When the END USER's own wallet should sign in a browser instead, that is +// the sdk-dapp flow, not this one. +// +// How the pieces fit, verified against the installed @multiversx/sdk-core: +// +// entrypoint.createTransfersController() +// -> TransfersController, a thin wrapper: it calls +// TransferTransactionsFactory.createTransactionForNativeTokenTransfer() +// to build the transaction (which computes a correct gasLimit +// itself — minGasLimit + gasLimitPerByte * data.length, per +// TransactionsFactoryConfig — you never need to hardcode 50000 +// yourself), then BaseController.setupAndSignTransaction() sets the +// nonce and SIGNS it. +// controller.createTransactionForNativeTokenTransfer(sender, nonce, options) +// -> returns an ALREADY-SIGNED Transaction, ready for +// entrypoint.sendTransaction(tx). This is the Controller pattern; +// contrast with the Factory pattern, which returns an UNSIGNED +// transaction and leaves nonce/signing to the caller. +// +// One subtlety worth being explicit about: `Account.signTransaction(tx)` +// returns the raw signature bytes (Promise); it does NOT mutate +// `tx` in place. TransfersController assigns `tx.signature` internally, so it +// is a non-issue here — but if you ever build and sign a transfer by hand +// (for example with the Factory pattern), you must assign +// `tx.signature = await account.signTransaction(tx)` yourself. + +import { Address } from '@multiversx/sdk-core'; +import type { Account, DevnetEntrypoint } from '@multiversx/sdk-core'; + +export interface SendEgldInput { + /** Bech32 address of the recipient. */ + receiver: string; + /** Amount in the smallest denomination (10^18 = 1 EGLD) — see src/amount.ts. */ + amountInSmallestDenomination: bigint; +} + +export interface SendEgldOutput { + txHash: string; +} + +/** + * Builds, signs (via TransfersController), and sends a native EGLD + * transfer. `sender.nonce` must already reflect the network's current + * nonce — see src/account.ts's loadDevnetAccount. + */ +export async function sendEgld( + entrypoint: DevnetEntrypoint, + sender: Account, + input: SendEgldInput, +): Promise { + const controller = entrypoint.createTransfersController(); + + const transaction = await controller.createTransactionForNativeTokenTransfer( + sender, + sender.getNonceThenIncrement(), + { + receiver: Address.newFromBech32(input.receiver), + nativeAmount: input.amountInSmallestDenomination, + }, + ); + + const txHash = await entrypoint.sendTransaction(transaction); + return { txHash }; +} +``` + +## Wiring it together + +```ts title="src/index.ts" +// src/index.ts — CLI entry point. Wires together account.ts, amount.ts, +// and sendEgld.ts into a single runnable command. +// +// Usage: +// npm run build && npm start -- [amountInEgld] +// +// Example: +// npm start -- ./wallet.pem erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th 0.01 + +import { DevnetEntrypoint } from '@multiversx/sdk-core'; +import { loadDevnetAccount } from './account'; +import { toSmallestDenomination } from './amount'; +import { sendEgld } from './sendEgld'; + +async function main(): Promise { + const [pemPath, receiver, amountArg] = process.argv.slice(2); + + if (!pemPath || !receiver) { + console.error('Usage: npm start -- [amountInEgld]'); + process.exitCode = 1; + return; + } + + const amountInEgld = amountArg ?? '0.001'; + + // DevnetEntrypoint() with no options defaults to + // https://devnet-api.multiversx.com, chain ID "D". + const entrypoint = new DevnetEntrypoint(); + + const sender = await loadDevnetAccount(entrypoint, pemPath); + console.log(`Sender: ${sender.address.toBech32()} (nonce ${sender.nonce})`); + console.log(`Receiver: ${receiver}`); + console.log(`Amount: ${amountInEgld} EGLD`); + + const { txHash } = await sendEgld(entrypoint, sender, { + receiver, + amountInSmallestDenomination: toSmallestDenomination(amountInEgld), + }); + + console.log(`\nSent. Transaction hash: ${txHash}`); + console.log(`Explorer: https://devnet-explorer.multiversx.com/transactions/${txHash}`); + + // TransactionStatus wraps the raw string status plus boolean helpers. + const outcome = await entrypoint.awaitCompletedTransaction(txHash); + console.log(`Status: ${outcome.status.status} (successful: ${outcome.status.isSuccessful()})`); +} + +main().catch((err: unknown) => { + console.error(err); + process.exitCode = 1; +}); +``` + +## Run it + +```bash +npm start -- ./wallet.pem 0.01 +``` + +Expected output: + +```text +Sender: erd1... (nonce 42) +Receiver: erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th +Amount: 0.01 EGLD + +Sent. Transaction hash: <64-char hex hash> +Explorer: https://devnet-explorer.multiversx.com/transactions/ +Status: success (successful: true) +``` + +## How it works + +**`TransfersController` computes gas for you.** Unlike hand-building a raw +`Transaction`, where you duplicate the network's own gas formula by hand, +`entrypoint.createTransfersController().createTransactionForNativeTokenTransfer(...)` +sets `gasLimit` to `minGasLimit + gasLimitPerByte * data.length` (50,000 plus +1,500 per byte) automatically, via `TransferTransactionsFactory` internally. That +was confirmed against the installed sdk-core source and against a real devnet +request logged while authoring this recipe (`"gasLimit":50000` for a plain, +data-less transfer). + +**The Controller pattern returns an already-signed transaction.** `sender` (an +`Account`) is passed straight into +`createTransactionForNativeTokenTransfer(sender, nonce, options)`. Internally, +`BaseController.setupAndSignTransaction()` sets the nonce and does +`transaction.signature = await sender.signTransaction(transaction)` for you. That +is what makes the transaction returned by the controller ready to send as-is, +unlike the Factory pattern below. + +## Pitfalls + +:::danger[Pitfall 1: signTransaction returns bytes, it does not mutate the tx] +`Account.signTransaction(transaction)` only computes and *returns* the raw +signature bytes (`Promise`); it does not touch +`transaction.signature` itself. The Controller pattern used in this recipe +assigns it internally, so this does not bite here. But if you ever sign a +transaction by hand, you need `tx.signature = await account.signTransaction(tx);` +explicitly. +::: + +:::warning[Pitfall 2: the Factory pattern returns an unsigned transaction] +If you use +`TransferTransactionsFactory.createTransactionForNativeTokenTransfer(senderAddress, options)` +directly instead of `TransfersController`, you get back a transaction with +`nonce: 0n` and no signature. You must set the nonce and sign it yourself before +sending. +::: + +:::warning[Pitfall 3: the nonce trap applies here too] +This recipe's `loadDevnetAccount()` fetches the nonce once per process run, which +is correct for a single send. Running two instances of this CLI back-to-back +without waiting for the first to confirm produces a stale, duplicate nonce for +the second. Use the fetch-once, increment-locally pattern across multiple sends. +::: + +:::note[Pitfall 4: amounts beyond 18 decimals throw, on purpose] +`toSmallestDenomination()` rejects (rather than silently rounds) an input with +more precision than EGLD's 18 decimals can represent. Silently truncating would +mean sending a different amount than what was typed. +::: + +## See also + +- [Configure a network provider](/sdk-and-tools/sdk-js/cookbook/network-providers/configure-network-provider) + builds the read-side provider the entrypoint here wraps. +- [Read the connected account with useGetAccount](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account) + is the browser, connected-wallet counterpart to this backend flow. +- [The versioned sdk-js cookbook](/sdk-and-tools/sdk-js/sdk-js-cookbook) covers + ESDT transfers and the wider transaction API. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account.mdx new file mode 100644 index 000000000..9919ad435 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account.mdx @@ -0,0 +1,407 @@ +--- +title: Read the connected account with useGetAccount +description: Every AccountType field explained, which ones are optional, how to format the balance correctly, and when to reach for useGetAccountInfo instead. +difficulty: beginner +est_minutes: 6 +last_validated: "2026-07-16" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - wallet + - react + - typescript + - devnet +--- + +Once a user is logged in, `useGetAccount()` is how you read their address, +balance, and nonce back out of the sdk-dapp store. This recipe goes field by +field through `AccountType`, shows the two sibling hooks (`useGetAccountInfo()`, +`useGetLatestNonce()`) you will reach for less often, and gets the +balance-formatting pitfall out of the way early. + +## Prerequisites + +- A working sdk-dapp v5 setup (Next.js or Vite). +- A logged-in account on devnet. + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/read-connected-account +npm install +npm run dev +# open https://localhost:5173 +``` + +## The account card + +```tsx title="src/AccountCard.tsx" +// src/AccountCard.tsx — the useGetAccount() field-by-field breakdown. +// +// This is the piece this recipe is actually about. useGetAccount() returns +// AccountType (node_modules/@multiversx/sdk-dapp/out/types/account.types.d.ts) +// — a plain object read straight from the Zustand store sdk-dapp populates +// on login. No network call happens here; the values are whatever the store +// currently holds (refreshed automatically after transactions settle). + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetAccountInfo } from '@multiversx/sdk-dapp/out/react/account/useGetAccountInfo'; +import { useGetLatestNonce } from '@multiversx/sdk-dapp/out/react/account/useGetLatestNonce'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +import { formatAmount } from '@multiversx/sdk-dapp/out/lib/sdkDappUtils'; + +export function AccountCard(): JSX.Element { + // The hook you want almost all of the time. Returns AccountType directly — + // no destructuring through a bigger "everything" object. + const account = useGetAccount(); + + // The latest nonce, alone — a thin convenience wrapper around the same + // store value as account.nonce. Handy when a component only cares about + // the nonce and you don't want to pull in the whole AccountType. + const latestNonce = useGetLatestNonce(); + + // The network config, so we can label the balance with the right symbol + // (EGLD on mainnet, xEGLD on some custom networks) instead of hard-coding it. + const { network } = useGetNetworkConfig(); + + // The lower-level, back-compat-shaped hook. Useful when you also need + // provider-adjacent fields (publicKey, ledgerAccount, walletConnectAccount, + // websocket event snapshots) that AREN'T part of AccountType. Note this is + // NOT the same shape as sdk-dapp v4's useGetAccountInfo() — v5 dropped + // isLoggedIn and tokenLogin from it (those moved to useGetLoginInfo() / + // useGetIsLoggedIn()). + const accountInfo = useGetAccountInfo(); + + return ( +
+

useGetAccount()

+
+
address
+
+ {account.address} +
+ +
balance
+
+ + {formatAmount({ + input: account.balance, + decimals: 18, + digits: 4, + showLastNonZeroDecimal: true, + })}{' '} + {network.egldLabel} + {' '} + (raw: {account.balance}) +
+ +
nonce
+
+ {account.nonce}{' '} + + (useGetLatestNonce() agrees: {latestNonce}) + +
+ +
shard
+
+ {account.shard ?? 'unknown — optional field'} +
+ +
username
+
+ {account.username || '(none set — optional field)'} +
+ +
txCount / scrCount
+
+ + {account.txCount} / {account.scrCount} + +
+ +
isGuarded
+
+ {String(account.isGuarded)} +
+
+ +

useGetAccountInfo() — the fields AccountType doesn't have

+
+
publicKey
+
+ {accountInfo.publicKey} +
+
ledgerAccount
+
+ {accountInfo.ledgerAccount ? 'connected via Ledger' : 'null'} +
+
walletConnectAccount
+
+ {accountInfo.walletConnectAccount ?? 'null'} +
+
+
+ ); +} + +const cardStyle: React.CSSProperties = { + border: '1px solid #444', + borderRadius: '8px', + padding: '1.25rem', + marginTop: '1rem', +}; + +const dlStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'max-content 1fr', + columnGap: '1rem', + rowGap: '0.4rem', + margin: 0, +}; + +const rawStyle: React.CSSProperties = { + color: '#888', + fontSize: '0.85em', +}; +``` + +## The demo page + +```tsx title="src/App.tsx" +// src/App.tsx — demo page: connect, then show every account field. + +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; +import { AccountCard } from './AccountCard'; + +export function App(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + + const handleConnect = (): void => { + void UnlockPanelManager.getInstance().openUnlockPanel(); + }; + + const handleDisconnect = async (): Promise => { + const provider = getAccountProvider(); + await provider.logout(); + }; + + return ( +
+

Reading the connected account

+ + {!isLoggedIn ? ( + + ) : ( + <> + + + + )} +
+ ); +} + +const buttonStyle: React.CSSProperties = { + padding: '0.75rem 1.25rem', + fontSize: '1rem', + cursor: 'pointer', +}; +``` + +## Provider bootstrap + +Every sdk-dapp recipe shares the same init wrapper. `providers.tsx` calls +`initApp()` once and gates rendering until the store is ready; `lib/multiversx.ts` +holds the environment config it passes in. They are shown here so the recipe +compiles as a complete unit, but the subject of this recipe is `AccountCard.tsx` +above. + +```tsx title="src/providers.tsx" +// src/providers.tsx — sdk-dapp v5 init wrapper. +// +// This recipe's subject is AccountCard.tsx, not this file — this is the +// same login bootstrap every recipe in this Cookbook uses. + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { dappConfig } from './lib/multiversx'; + +let initStarted = false; + +export function Providers({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + + UnlockPanelManager.init({ + loginHandler: async (): Promise => { + // eslint-disable-next-line no-console + console.log('Logged in.'); + }, + onClose: async (): Promise => { + // eslint-disable-next-line no-console + console.log('Unlock panel closed.'); + }, + }); + + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return ( +
+ Initializing wallet SDK… +
+ ); + } + + return <>{children}; +} +``` + +```ts title="src/lib/multiversx.ts" +// src/lib/multiversx.ts — environment configuration for sdk-dapp's initApp. +// Same shape as every other Vite recipe in this Cookbook. + +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +const WALLET_CONNECT_PROJECT_ID = + import.meta.env.VITE_WALLETCONNECT_PROJECT_ID ?? 'multiversx-cookbook-demo'; + +export const dappConfig = { + storage: { + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + nativeAuth: true, + theme: ThemesEnum.dark, + providers: { + walletConnect: { + walletConnectV2ProjectId: WALLET_CONNECT_PROJECT_ID, + }, + }, + }, +}; + +export { EnvironmentsEnum }; +``` + +## How it works + +**`useGetAccount()` is a synchronous store read, not a network call.** It returns +`AccountType`, verified field by field against +`node_modules/@multiversx/sdk-dapp/out/types/account.types.d.ts` on the +actually-installed `@multiversx/sdk-dapp@5.6.23`: + +```ts +interface AccountType { + address: string; + balance: string; // smallest denomination, decimal string + nonce: number; + txCount: number; + scrCount: number; + claimableRewards: string; + isGuarded: boolean; + // optional: username, shard, code, ownerAddress, developerReward, + // deployedAt, scamInfo, isUpgradeable, isReadable, isPayable, + // isPayableBySmartContract, assets, and the active/pending guardian fields +} +``` + +The store refreshes this automatically, on initial login and after each tracked +transaction settles. Calling the hook again on every render is fine and expected; +it does not refetch anything by itself. + +**Format the balance, always.** `account.balance` is the raw smallest-unit +string. `formatAmount({ input: account.balance, decimals: 18, digits: 4, showLastNonZeroDecimal: true })` +gives you the human-readable decimal EGLD amount with the precision behavior you +want. + +**`useGetAccountInfo()` still exists in v5, with a narrower shape than v4.** It +dropped `isLoggedIn` and `tokenLogin` (now `useGetIsLoggedIn()` / +`useGetLoginInfo()`), but adds fields `AccountType` does not have: `publicKey`, +`ledgerAccount`, `walletConnectAccount`, `websocketEvent`, `websocketBatchEvent`. +Reach for it only when you need one of those; `useGetAccount()` is the right +default for everything else. + +## Pitfalls + +:::danger[Pitfall 1: never render account.balance directly] +It is the raw smallest-unit string (`"1500000000000000000"`, not `"1.5"`). Always +pass it through `formatAmount()` first. Rendering it raw is the single most common +"why does my balance say a huge number" support question. +::: + +:::warning[Pitfall 2: shard and username are optional] +A freshly created account, or one the API has not fully indexed, may have +`shard: undefined`. Guard with `??` or a conditional. Do not assume every optional +`AccountType` field is always present, as `AccountCard.tsx` does for both. +::: + +:::warning[Pitfall 3: the hook doesn't refetch, refreshAccount() does] +`useGetAccount()` returns whatever the store currently holds. It updates +automatically after login and after tracked transactions settle. If you need to +force a fresh read outside those triggers, call `refreshAccount()` +(`@multiversx/sdk-dapp/out/utils/account/refreshAccount`) instead of expecting the +hook itself to hit the network. +::: + +:::warning[Pitfall 4: getAccount vs getAccountFromApi] +`useGetAccount()` (and its non-React twin `getAccount()`) are free, instant store +reads. `getAccountFromApi` is a *different* function that makes a real network +round-trip on every call. Do not reach for it inside a render loop thinking it is +just a renamed getter. +::: + +## See also + +- [Send EGLD to an address](/sdk-and-tools/sdk-js/cookbook/transactions/send-egld) + is the sdk-core, script-side counterpart, for when your own code holds the key. +- [Configure a network provider](/sdk-and-tools/sdk-js/cookbook/network-providers/configure-network-provider) + shows the read-side provider the sdk-dapp store wraps for you. +- [docs.multiversx.com, sdk-dapp](https://docs.multiversx.com/sdk-and-tools/sdk-dapp) + is the conceptual reference. diff --git a/sidebars.js b/sidebars.js index ab3d8e3a2..a8b8f0b4a 100644 --- a/sidebars.js +++ b/sidebars.js @@ -244,6 +244,33 @@ const sidebars = { "sdk-and-tools/sdk-js/cookbook-preview-recipe", ] }, + { + type: "category", + label: "Cookbook (recipes)", + items: [ + { + type: "category", + label: "Network providers", + items: [ + "sdk-and-tools/sdk-js/cookbook/network-providers/configure-network-provider", + ] + }, + { + type: "category", + label: "Transactions", + items: [ + "sdk-and-tools/sdk-js/cookbook/transactions/send-egld", + ] + }, + { + type: "category", + label: "Wallets", + items: [ + "sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account", + ] + }, + ] + }, "sdk-and-tools/sdk-js/extending-sdk-js", "sdk-and-tools/sdk-js/writing-and-testing-sdk-js-interactions", "sdk-and-tools/sdk-js/sdk-js-signing-providers", diff --git a/testing/cookbook-ts-ci.sh b/testing/cookbook-ts-ci.sh new file mode 100755 index 000000000..742c27917 --- /dev/null +++ b/testing/cookbook-ts-ci.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +## This script assembles the TypeScript from every cookbook recipe page and +## type-checks it under strict settings. It is the TS counterpart of +## rust-tutorial-ci.sh: that one extracts tutorial code into a crate and runs +## `cargo test`; this one extracts titled ts/tsx fences into a project and runs +## `tsc --noEmit --strict`. + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# 1. Extract: titled ts/tsx fences from the recipe MDX -> project/src/recipes/. +cd "$SCRIPT_DIR/cookbook-ts" || exit 1 +npm ci || exit 1 +npm run extract || exit 1 + +# 2. Type-check: strict, no emit, across every extracted recipe at once. +cd "$SCRIPT_DIR/cookbook-ts/project" || exit 1 +npm ci || exit 1 +npm run typecheck || exit 1 diff --git a/testing/cookbook-ts/.gitignore b/testing/cookbook-ts/.gitignore new file mode 100644 index 000000000..07e6e472c --- /dev/null +++ b/testing/cookbook-ts/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/testing/cookbook-ts/CONVENTION.md b/testing/cookbook-ts/CONVENTION.md new file mode 100644 index 000000000..33a36a2bf --- /dev/null +++ b/testing/cookbook-ts/CONVENTION.md @@ -0,0 +1,181 @@ +# Cookbook TypeScript verification: authoring convention + +This is the TypeScript counterpart of the repo's Rust tutorial CI +(`testing/rust-tutorial-ci.sh` + `testing/extract-tutorial-code/`). The Rust CI +extracts the code from a tutorial's fenced blocks into a real crate and runs +`cargo test`. This one extracts the code from cookbook recipe pages into a real +TypeScript project and runs `tsc --noEmit --strict`. Same idea, for TS: the +"Verified" badge on a recipe page means its code actually compiles in CI. + +The one rule to remember: **what is shown is what is compiled.** Every titled +`ts`/`tsx` code fence a reader sees on a recipe page is pulled out and +type-checked. If it compiles, the page is verified; if it does not, CI fails. + +## Where recipes live + +Ported recipes are Docusaurus MDX under: + +```text +docs/sdk-and-tools/sdk-js/cookbook/
/.mdx +``` + +- `
` is a grouping folder (`network-providers`, `transactions`, + `wallets`, `tokens`, ...). It also drives the sidebar sub-category. +- `` is the recipe id and the folder the extractor assembles code into. + Keep slugs unique across the whole cookbook (the extractor namespaces by slug, + not by section). + +The extractor only scans this directory, so nothing else in `docs/` is affected. + +## The fence convention + +A recipe's runnable code is authored as **titled** fenced code blocks: + +````markdown +```ts title="src/providers.ts" +// ... file contents ... +``` +```` + +- **`title=""`** is the file's path inside the recipe, relative to the + recipe root (for example `src/index.ts`, `src/lib/multiversx.ts`). This is the + same `title=` attribute Docusaurus already renders as the code-block filename + label, so the reader sees exactly the file that gets compiled. +- The fence language must be **`ts`** or **`tsx`**. Only those two are compiled. +- A recipe with several files is several titled fences on the page. The + extractor writes each to its path, so relative imports between them + (`import { x } from './providers'`) resolve exactly as they do in the real + recipe project. + +Fences **without** a `title=` (or in any other language: `bash`, `text`, `json`, +an untitled `ts` snippet) are treated as illustrative only. They are shown to the +reader but never extracted or compiled. Use an untitled `ts` fence for a throwaway +snippet you do not want type-checked (for example a one-off "generate a wallet" +aside), and a `text` fence for expected program output. + +`filename="..."` and the bare `` ```foo.ts `` shorthand are also accepted, matching +the Rust parser, but `title="..."` is the house style because Docusaurus renders it. + +### What is shown is what is compiled + +Because only titled fences compile, a recipe must show **every** file needed for +its code to type-check as a closed unit. If `App.tsx` imports `./providers`, then +`providers.tsx` must also appear as a titled fence on the page. Do not hide a +required file. If a file is pure boilerplate the reader does not need to study +(a provider bootstrap, an entry `index.ts`), still show it, under a clearly +labelled section such as "Provider bootstrap" or "Wiring it together". + +The only things the harness supplies that a recipe does not have to show are the +ambient environment types in `project/src/scaffold.d.ts` (see below). + +## Frontmatter + +Recipe pages carry the frontmatter the design-layer meta strip reads (rendered +by the `DocItem/Content` swizzle, which activates only when both `difficulty` and +`sdk_versions` are present): + +```yaml +--- +title: Send EGLD to an address +description: One-sentence summary shown in search and social cards. +difficulty: beginner # beginner | intermediate | advanced +est_minutes: 6 # optional: renders the "EST n min" chip +last_validated: "2026-07-16" # date CI last verified this page (the badge date) +sdk_versions: # object; each entry renders a version chip + sdk-core: "^15.4.0" +tags: + - sdk-core + - transaction + - typescript +# stale: true # optional: flips the badge to "Needs recheck" +--- +``` + +`difficulty` + `sdk_versions` are required for the strip to render at all. +`title`, `description`, `last_validated`, and `tags` are expected on every recipe. +`est_minutes` and `stale` are optional. + +## Running the check + +The whole pipeline is one script, mirroring `rust-tutorial-ci.sh`: + +```bash +./testing/cookbook-ts-ci.sh +``` + +It runs `npm ci` + extract in `testing/cookbook-ts/`, then `npm ci` + `tsc +--noEmit --strict` in `testing/cookbook-ts/project/`. The GitHub workflow +`.github/workflows/cookbook-ts-ci.yml` runs the same script on push / PR. + +To iterate on a single recipe faster, once dependencies are installed: + +```bash +cd testing/cookbook-ts && npm run extract +cd project && npm run typecheck +``` + +## Harness layout + +```text +testing/ + cookbook-ts-ci.sh # runner (mirror of rust-tutorial-ci.sh) + cookbook-ts/ # the extractor (mirror of extract-tutorial-code/) + parser.ts # fence parser (mirror of parser.rs) + extract.ts # assembler (mirror of extract_code.rs) + package.json # tsx + typescript + project/ # the compiled unit (mirror of the crowdfunding crate) + package.json # pinned SDK deps: sdk-core, sdk-dapp, react, ... + tsconfig.json # one strict config for every recipe + src/ + scaffold.d.ts # committed ambient types (import.meta.env) + recipes/ # git-ignored; the extractor fills this each run +.github/workflows/cookbook-ts-ci.yml +``` + +The extracted `src/recipes/**` is git-ignored, exactly as the Rust harness +git-ignores the tutorial code it writes into the crowdfunding crate. Only the +skeleton is committed. + +### Why one tsconfig for everything + +The standalone recipe projects ship two tsconfig shapes: the sdk-core +"backend/script" recipes (CommonJS, Node types) and the sdk-dapp "React" recipes +(JSX, DOM libs, bundler resolution). The harness type-checks with a single +superset config: `module: ESNext`, `moduleResolution: bundler`, `jsx: react-jsx`, +`lib` including DOM, and every strict flag the recipe projects use. Because the +harness only runs `tsc --noEmit` (it never emits or executes), the emit module +format is irrelevant, so one config checks both families. Nothing is checked more +loosely here than in a recipe's own repo. + +### The scaffold ambient types + +`project/src/scaffold.d.ts` declares `import.meta.env` so the sdk-dapp/Vite +recipes type-check without installing a bundler. It is the TS analogue of the +hand-written skeleton files (`meta/`, `sc-config.toml`) the Rust harness assumes +already exist. Recipes never need to show a `vite-env.d.ts`; the harness provides +the environment. + +## Porting a prototype recipe (checklist) + +To convert one runnable recipe project into a compliant Docusaurus page: + +1. Create `docs/sdk-and-tools/sdk-js/cookbook/
/.mdx`. +2. Write the frontmatter above. Set `last_validated` to the date you verify it, + and `sdk_versions` to the versions the recipe's `package.json` actually uses. +3. Convert prose to Docusaurus: paragraphs stay as Markdown; callouts become + admonitions (`:::note[Title]`, `:::warning[Title]`, `:::danger[Title]`), never + raw HTML. +4. Inline each source file the recipe needs as a titled fence + (` ```ts title="src/foo.ts" `), verbatim from the validated source. Include + every file required to compile, even boilerplate. Give command blocks the + `bash` language and expected-output blocks the `text` language. +5. Scrub any internal-only references from comments and prose (these are public + docs). Fix cross-links to real pages (or drop them); `onBrokenLinks` is `log`, + so broken links will not fail the build but should still be avoided. +6. Keep em-dashes sparing. +7. Add the page to `sidebars.js` under "Cookbook (recipes)". +8. Verify: + - `./testing/cookbook-ts-ci.sh` is green (the code compiles). + - `npm run build` is green and the page appears with its meta strip. + - markdownlint (`.markdownlint.jsonc`) and codespell (`.codespell`) pass on + the new file. diff --git a/testing/cookbook-ts/extract.ts b/testing/cookbook-ts/extract.ts new file mode 100644 index 000000000..a8e398ec8 --- /dev/null +++ b/testing/cookbook-ts/extract.ts @@ -0,0 +1,122 @@ +// extract.ts — assemble compilable TypeScript from the cookbook recipe MDX. +// +// TypeScript counterpart of ../extract-tutorial-code/src/extract_code.rs. +// +// The Rust CI extracts a hard-coded set of named blocks from one tutorial into +// one crate, then runs `cargo test`. The cookbook is many small recipes instead +// of one tutorial, so this walks a directory of recipe pages, and for each page +// writes every titled `ts`/`tsx` fence into its own folder inside the harness +// project: +// +// docs/.../cookbook/
/.mdx +// ```ts title="src/foo.ts" -> project/src/recipes//src/foo.ts +// +// Afterwards `tsc --noEmit --strict` in project/ type-checks every recipe at +// once (see ../cookbook-ts-ci.sh). "What is shown is what is compiled": only the +// fences a reader sees on the page are extracted; untitled/illustrative fences +// (bash, output, prose snippets) are ignored. + +import { existsSync } from "node:fs"; +import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises"; +import { dirname, join, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { extractCodeBlocksFromMarkdown } from "./parser.ts"; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = resolve(HERE, "..", ".."); + +/** The one directory that holds ported cookbook recipes (analogous to the + * Rust extractor's hard-coded tutorial path list). */ +const COOKBOOK_DIR = join(REPO_ROOT, "docs/sdk-and-tools/sdk-js/cookbook"); + +/** Where assembled recipes land — git-ignored, wiped and rebuilt each run. */ +const RECIPES_OUT = join(HERE, "project", "src", "recipes"); + +/** Only these fence languages are compiled. */ +const COMPILE_LANGUAGES = new Set(["ts", "tsx"]); + +async function findMdxFiles(dir: string): Promise { + const entries = await readdir(dir, { withFileTypes: true }); + const files: string[] = []; + for (const entry of entries) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...(await findMdxFiles(full))); + } else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) { + files.push(full); + } + } + return files.sort(); +} + +/** Recipe slug = the MDX file's basename (send-egld.mdx -> "send-egld"). */ +function slugFor(mdxPath: string): string { + const base = mdxPath.split("/").at(-1)!; + return base.replace(/\.mdx?$/, ""); +} + +async function main(): Promise { + if (!existsSync(COOKBOOK_DIR)) { + throw new Error(`Cookbook directory not found: ${COOKBOOK_DIR}`); + } + + // Fresh start, so a removed fence never lingers as a stale extracted file. + await rm(RECIPES_OUT, { recursive: true, force: true }); + await mkdir(RECIPES_OUT, { recursive: true }); + + const mdxFiles = await findMdxFiles(COOKBOOK_DIR); + let recipeCount = 0; + let fileCount = 0; + + for (const mdxPath of mdxFiles) { + const slug = slugFor(mdxPath); + const markdown = await readFile(mdxPath, "utf-8"); + const blocks = extractCodeBlocksFromMarkdown(markdown); + + const compilable = blocks.filter( + (b) => b.title && b.language && COMPILE_LANGUAGES.has(b.language), + ); + if (compilable.length === 0) { + continue; + } + + const seen = new Set(); + for (const block of compilable) { + const title = block.title!; + if (seen.has(title)) { + throw new Error( + `Duplicate fence title "${title}" in ${relative(REPO_ROOT, mdxPath)} ` + + `— every titled fence in a recipe must map to a unique file.`, + ); + } + seen.add(title); + + const outPath = join(RECIPES_OUT, slug, title); + await mkdir(dirname(outPath), { recursive: true }); + await writeFile(outPath, block.content, "utf-8"); + fileCount += 1; + console.log( + `Extracted ${relative(REPO_ROOT, mdxPath)} :: ${title} (${block.language})`, + ); + } + recipeCount += 1; + } + + console.log( + `\nAssembled ${fileCount} file(s) across ${recipeCount} recipe(s) into ` + + `${relative(REPO_ROOT, RECIPES_OUT)}`, + ); + + if (fileCount === 0) { + throw new Error( + "No compilable fences found. Expected titled ```ts / ```tsx fences in " + + `${relative(REPO_ROOT, COOKBOOK_DIR)}.`, + ); + } +} + +main().catch((err: unknown) => { + console.error(err); + process.exitCode = 1; +}); diff --git a/testing/cookbook-ts/package-lock.json b/testing/cookbook-ts/package-lock.json new file mode 100644 index 000000000..a9e376d2b --- /dev/null +++ b/testing/cookbook-ts/package-lock.json @@ -0,0 +1,569 @@ +{ + "name": "cookbook-ts-extractor", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cookbook-ts-extractor", + "version": "0.0.0", + "devDependencies": { + "@types/node": "^20.17.6", + "tsx": "^4.19.2", + "typescript": "^5.6.3" + }, + "engines": { + "node": ">=20.13.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/tsx": { + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/testing/cookbook-ts/package.json b/testing/cookbook-ts/package.json new file mode 100644 index 000000000..180dd26ab --- /dev/null +++ b/testing/cookbook-ts/package.json @@ -0,0 +1,18 @@ +{ + "name": "cookbook-ts-extractor", + "version": "0.0.0", + "private": true, + "type": "module", + "description": "Extracts titled ts/tsx fences from the cookbook recipe MDX into the harness project (TypeScript counterpart of extract-tutorial-code).", + "scripts": { + "extract": "tsx extract.ts" + }, + "devDependencies": { + "@types/node": "^20.17.6", + "tsx": "^4.19.2", + "typescript": "^5.6.3" + }, + "engines": { + "node": ">=20.13.1" + } +} diff --git a/testing/cookbook-ts/parser.ts b/testing/cookbook-ts/parser.ts new file mode 100644 index 000000000..ff7c7afa4 --- /dev/null +++ b/testing/cookbook-ts/parser.ts @@ -0,0 +1,150 @@ +// parser.ts — extract fenced code blocks from a Markdown / MDX string. +// +// This is the TypeScript counterpart of ../extract-tutorial-code/src/parser.rs +// (the Rust tutorial CI). It keeps the same idea and the same fence-info +// convention, so a maintainer who knows the Rust one already knows this one: +// +// ```ts title="src/main.ts" -> language "ts", title "src/main.ts" +// ```rust filename="Cargo.toml" -> language "rust", title "Cargo.toml" +// ```Cargo.toml -> title "Cargo.toml", language "toml" +// +// The Rust version leans on the `pulldown-cmark` CommonMark parser. We only need +// fenced code blocks (never indented ones, never inline code) out of our own +// recipe MDX, which is authored to a fixed convention, so a small, dependency- +// free line scanner is enough — and keeps the extractor install-light. + +export interface CodeBlock { + /** The `title=`/`filename=` value, or a bare `foo.ext` info string. */ + title?: string; + /** The first info-string token (e.g. "ts", "tsx", "bash"). */ + language?: string; + /** The verbatim code between the fences (trailing newline preserved). */ + content: string; +} + +/** Matches an opening or closing fence line: 3+ backticks or 3+ tildes. */ +const FENCE_RE = /^(\s*)(`{3,}|~{3,})(.*)$/; + +/** + * Extract every fenced code block from a Markdown / MDX document. + * + * YAML frontmatter (a leading `---` … `---` block) is stripped first so its + * contents can never be mistaken for fences. + */ +export function extractCodeBlocksFromMarkdown(markdown: string): CodeBlock[] { + const lines = stripFrontmatter(markdown).split(/\r?\n/); + const blocks: CodeBlock[] = []; + + let i = 0; + while (i < lines.length) { + const open = lines[i]!.match(FENCE_RE); + if (!open) { + i += 1; + continue; + } + + // An opening fence. Remember its exact marker so we only close on a fence + // of the same character and at least the same length (CommonMark rule). + const fenceChar = open[2]![0]!; + const fenceLen = open[2]!.length; + const info = open[3]!.trim(); + + const contentLines: string[] = []; + i += 1; + while (i < lines.length) { + const close = lines[i]!.match(FENCE_RE); + const isClose = + close !== null && + close[3]!.trim() === "" && + close[2]![0] === fenceChar && + close[2]!.length >= fenceLen; + if (isClose) { + break; + } + contentLines.push(lines[i]!); + i += 1; + } + + const { language, title } = parseCodeBlockInfo(info); + blocks.push({ + ...(title !== undefined ? { title } : {}), + ...(language !== undefined ? { language } : {}), + // Re-join with a trailing newline so extracted files end cleanly. + content: contentLines.length > 0 ? contentLines.join("\n") + "\n" : "", + }); + + i += 1; // step over the closing fence + } + + return blocks; +} + +/** + * Parse a fence info string into { language, title }. + * + * Mirrors `parse_code_block_info` in parser.rs, including the `foo.ext` + * shorthand where the first token is itself a filename. + */ +export function parseCodeBlockInfo(info: string): { + language?: string; + title?: string; +} { + if (info === "") { + return {}; + } + + const parts = info.split(/\s+/); + const first = parts[0]!; + + let language: string | undefined; + let title: string | undefined; + + // A first token that looks like a filename (`foo.ext`) is treated as the + // title, and the extension becomes the language — same as the Rust parser. + if ( + first.includes(".") && + !first.startsWith("title=") && + !first.startsWith("filename=") + ) { + title = first; + const ext = first.split(".").at(-1); + if (ext) { + language = ext; + } + } else { + language = first; + } + + for (const part of parts.slice(1)) { + if (part.startsWith("title=")) { + title = stripQuotes(part.slice("title=".length)); + } else if (part.startsWith("filename=")) { + title = stripQuotes(part.slice("filename=".length)); + } + } + + return { + ...(language !== undefined ? { language } : {}), + ...(title !== undefined ? { title } : {}), + }; +} + +/** Remove a single leading `"`/`'` pair, matching parser.rs's strip_quotes. */ +function stripQuotes(s: string): string { + return s.replace(/^["']/, "").replace(/["']$/, ""); +} + +/** Drop a leading YAML frontmatter block (`---` … `---`) if present. */ +function stripFrontmatter(markdown: string): string { + if (!markdown.startsWith("---")) { + return markdown; + } + const lines = markdown.split(/\r?\n/); + // lines[0] is the opening "---"; find the next closing "---". + for (let i = 1; i < lines.length; i += 1) { + if (lines[i]!.trim() === "---") { + return lines.slice(i + 1).join("\n"); + } + } + return markdown; +} diff --git a/testing/cookbook-ts/project/.gitignore b/testing/cookbook-ts/project/.gitignore new file mode 100644 index 000000000..c943492b6 --- /dev/null +++ b/testing/cookbook-ts/project/.gitignore @@ -0,0 +1,5 @@ +# Assembled by `cookbook-ts-extractor` on every CI run — never committed. +/src/recipes/ + +# npm +/node_modules diff --git a/testing/cookbook-ts/project/package-lock.json b/testing/cookbook-ts/project/package-lock.json new file mode 100644 index 000000000..f2b70f201 --- /dev/null +++ b/testing/cookbook-ts/project/package-lock.json @@ -0,0 +1,4750 @@ +{ + "name": "cookbook-ts-project", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cookbook-ts-project", + "version": "0.0.0", + "dependencies": { + "@multiversx/sdk-core": "^15.4.0", + "@multiversx/sdk-dapp": "^5.6.0", + "axios": "^1.7.9", + "bignumber.js": "^9.1.2", + "protobufjs": "^7.4.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/node": "^20.17.6", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "typescript": "^5.6.3" + }, + "engines": { + "node": ">=20.13.1" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT", + "optional": true + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT", + "optional": true + }, + "node_modules/@ledgerhq/devices": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.16.0.tgz", + "integrity": "sha512-brXLPzkvGM3D5YNsWQ25P5G4SmWdSNBed9W8wKoOIRLGdRfvE+bg9mzFty0iZ+aRLBkLoXwX7xKIL9zUi6LBKQ==", + "license": "Apache-2.0", + "dependencies": { + "semver": "7.7.3" + } + }, + "node_modules/@ledgerhq/errors": { + "version": "6.37.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.37.0.tgz", + "integrity": "sha512-T5yiKI5UX7ugeocdTF3TUsCIN2BH41Bio4ZeN410YFjFOf3es08n/5JyMzzKwzRgP0blG3HfBf7s7vJKqCSAeg==", + "license": "Apache-2.0" + }, + "node_modules/@ledgerhq/hw-transport": { + "version": "6.35.5", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.35.5.tgz", + "integrity": "sha512-P4+wtLewLWgxPtIb90h5kjpzXVlC6f4IBQBmvowVFkInvZt34ffXkX7wa5KfMzu4l3cqCcpNSqtPSCMp+0vuqg==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.16.0", + "@ledgerhq/errors": "^6.37.0", + "@ledgerhq/logs": "^6.17.0", + "events": "^3.3.0" + } + }, + "node_modules/@ledgerhq/hw-transport-web-ble": { + "version": "6.35.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-web-ble/-/hw-transport-web-ble-6.35.0.tgz", + "integrity": "sha512-nc4FAWZ0GtZO9ydDCKlvDzOEVdUXlGL/0nWPIOeCE5xxuXug5iR0uhC4Bl0qwlUCEnhewmXwLJsCFcCdr/N4QQ==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.16.0", + "@ledgerhq/errors": "^6.37.0", + "@ledgerhq/hw-transport": "6.35.5", + "@ledgerhq/logs": "^6.17.0", + "rxjs": "7.8.2" + } + }, + "node_modules/@ledgerhq/hw-transport-webhid": { + "version": "6.36.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-webhid/-/hw-transport-webhid-6.36.0.tgz", + "integrity": "sha512-1mKWm3LyGOgmaYlAiqbmaGoupOZHbj2Kow5sXLxKZzQa4kFvQuUdinYOWhs7T8hqJyAkz9WlHYyKM7aIs8kjNg==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.16.0", + "@ledgerhq/errors": "^6.37.0", + "@ledgerhq/hw-transport": "6.35.5", + "@ledgerhq/logs": "^6.17.0" + } + }, + "node_modules/@ledgerhq/hw-transport-webusb": { + "version": "6.35.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.35.0.tgz", + "integrity": "sha512-8fBNqzQnyR3pKLE2AVxCAc54nHMEKz9/eAYLDENVRuDp+A23lBJYWErxSyoZ6riCM19jIhRvtHjB0o4PXpa2tg==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.16.0", + "@ledgerhq/errors": "^6.37.0", + "@ledgerhq/hw-transport": "6.35.5", + "@ledgerhq/logs": "^6.17.0" + } + }, + "node_modules/@ledgerhq/logs": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.17.0.tgz", + "integrity": "sha512-yra33g5q/AU7+PwAws+GaVpQGUuxnDREjVBnviJjcaJLVKuLzI4pnj8Bd3nY3fypM5k1yZEYKEXfUuGFUjP2+w==", + "license": "Apache-2.0" + }, + "node_modules/@lifeomic/axios-fetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@lifeomic/axios-fetch/-/axios-fetch-3.1.0.tgz", + "integrity": "sha512-C6ceAnh8W19KuekFsCiK1A+AMBirQFTnoEqMZQ7HE6VXJ18zjGofdXxLU8RTo2gZp/yZK5ufmPwIvRtejj1gxg==", + "license": "MIT", + "dependencies": { + "@types/node-fetch": "^2.5.10" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@lit/react": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.8.tgz", + "integrity": "sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==", + "license": "BSD-3-Clause", + "optional": true, + "peerDependencies": { + "@types/react": "17 || 18 || 19" + } + }, + "node_modules/@msgpack/msgpack": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz", + "integrity": "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==", + "license": "ISC", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@multiversx/sdk-bls-wasm": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-bls-wasm/-/sdk-bls-wasm-0.3.5.tgz", + "integrity": "sha512-c0tIdQUnbBLSt6NYU+OpeGPYdL0+GV547HeHT8Xc0BKQ7Cj0v82QUoA2QRtWrR1G4MNZmLsIacZSsf6DrIS2Bw==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/@multiversx/sdk-core": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-core/-/sdk-core-15.4.1.tgz", + "integrity": "sha512-1G/GS7fwED9Pl0NpwbcdXSi+986ey/OUGZycWuDCXPMMWx/qFQ0V5YMEotEqma8UM7uioVW3O5AnI7+EWOmsnQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@multiversx/sdk-transaction-decoder": "1.0.2", + "@noble/ed25519": "1.7.3", + "@noble/hashes": "1.3.0", + "bech32": "1.1.4", + "blake2b": "2.1.3", + "buffer": "6.0.3", + "ed25519-hd-key": "1.1.2", + "ed2curve": "0.3.0", + "json-bigint": "1.0.0", + "keccak": "3.0.2", + "scryptsy": "2.1.0", + "tweetnacl": "1.0.3", + "uuid": "8.3.2" + }, + "optionalDependencies": { + "@multiversx/sdk-bls-wasm": "0.3.5", + "axios": "^1.15.2", + "bip39": "3.1.0" + }, + "peerDependencies": { + "bignumber.js": "^9.0.1", + "protobufjs": "^7.5.6" + } + }, + "node_modules/@multiversx/sdk-dapp": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-dapp/-/sdk-dapp-5.7.1.tgz", + "integrity": "sha512-Tzy5xkKVrwTjylJ31AXVRWPQFtEyb2fyJTUcM8gz8dEqe/VpYH1KzCjbUVZweRcd3K9+/h83R+gT5SLmiPAwOw==", + "license": "MIT", + "dependencies": { + "@lifeomic/axios-fetch": "3.1.0", + "@multiversx/sdk-extension-provider": "5.1.2", + "@multiversx/sdk-hw-provider": "8.2.0", + "@multiversx/sdk-native-auth-client": "2.0.1", + "@multiversx/sdk-wallet-connect-provider": "6.1.5", + "@multiversx/sdk-web-wallet-cross-window-provider": "3.2.2", + "@multiversx/sdk-web-wallet-iframe-provider": "4.0.1", + "@multiversx/sdk-webview-provider": "3.2.7", + "immer": "10.1.1", + "linkifyjs": "4.3.3", + "lodash.isempty": "4.4.0", + "lodash.isequal": "4.5.0", + "lodash.isstring": "4.0.1", + "lodash.startcase": "4.4.0", + "lodash.trimend": "4.18.0", + "lodash.uniq": "4.5.0", + "socket.io-client": "4.8.3", + "zustand": "4.4.7" + }, + "optionalDependencies": { + "@multiversx/sdk-dapp-ui": ">=0.1.24 <0.2.0" + }, + "peerDependencies": { + "@multiversx/sdk-core": "^14.x || ^15.x", + "@multiversx/sdk-dapp-utils": "^3.x", + "axios": ">=1.15.0", + "bignumber.js": "^9.x", + "protobufjs": "^7.5.5" + } + }, + "node_modules/@multiversx/sdk-dapp-ui": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-dapp-ui/-/sdk-dapp-ui-0.1.24.tgz", + "integrity": "sha512-p2Dnqh9CMmlYxSwH4ks+qjqsvzQ6ak8CEIZxAJ+Xj6tYiB8r4sgqamPDkSLsxn9114nxud7bkdw2mvJ+kepRLQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stencil/core": "4.38.1", + "@stencil/react-output-target": "1.2.0", + "@stencil/vue-output-target": "0.11.8", + "classnames": ">=2.5.1", + "lodash.capitalize": "^4.2.1", + "lodash.inrange": "^3.3.0", + "lodash.range": "^3.2.0", + "qrcode": ">=1.5.4" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@multiversx/sdk-dapp-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-dapp-utils/-/sdk-dapp-utils-3.1.0.tgz", + "integrity": "sha512-m3u2ymm+ME/+6Hbu0iNmi+CQbuImNOc8GeX+KtdW0tihQrMwEuWIECj/U5EapEFbGMlNbPb/YmwiMMfQ1bk/fQ==", + "license": "GPL-3.0-or-later", + "peer": true, + "peerDependencies": { + "@multiversx/sdk-core": "^14.0.0 || ^15.0.0", + "bignumber.js": "^9.x" + } + }, + "node_modules/@multiversx/sdk-extension-provider": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-extension-provider/-/sdk-extension-provider-5.1.2.tgz", + "integrity": "sha512-jxu18WKnc8ZLaLdl6ePcLStG2aZB1f9W0m2wPJt2UbHnPMhnk/okoYZYWlHfiF/L8WZGzQn8VXwcmras/vdwlw==", + "license": "MIT", + "peerDependencies": { + "@multiversx/sdk-core": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@multiversx/sdk-hw-provider": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-hw-provider/-/sdk-hw-provider-8.2.0.tgz", + "integrity": "sha512-WSPv72ykmPj/H9swxkD8lBbOSjnFAClGMxKntgDy5L9Uj3nUrEDbJRo0btW9ocJxRCVmkkcNoy2ze/Vp2AZngQ==", + "license": "MIT", + "dependencies": { + "@ledgerhq/devices": "8.16.0", + "@ledgerhq/errors": "6.37.0", + "@ledgerhq/hw-transport": "6.35.5", + "@ledgerhq/hw-transport-web-ble": "6.35.0", + "@ledgerhq/hw-transport-webhid": "6.36.0", + "@ledgerhq/hw-transport-webusb": "6.35.0", + "buffer": "6.0.3", + "platform": "1.3.6" + }, + "peerDependencies": { + "@multiversx/sdk-core": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@multiversx/sdk-native-auth-client": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-native-auth-client/-/sdk-native-auth-client-2.0.1.tgz", + "integrity": "sha512-J4RxpsfNnWBKOPz0vvhnReSf8V6mci11Pe3NT2awhrpBhtAvkSBUGIzgoGVf/eumrhyReRmKyMjo+u4UhGokbg==", + "license": "GPL-3.0-or-later", + "peerDependencies": { + "axios": "^1.10.0" + } + }, + "node_modules/@multiversx/sdk-transaction-decoder": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-transaction-decoder/-/sdk-transaction-decoder-1.0.2.tgz", + "integrity": "sha512-j43QsKquu8N51WLmVlJ7dV2P3A1448R7/ktvl8r3i6wRMpfdtzDPNofTdHmMRT7DdQdvs4+RNgz8hVKL11Etsw==", + "license": "MIT", + "dependencies": { + "bech32": "^2.0.0" + } + }, + "node_modules/@multiversx/sdk-transaction-decoder/node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT" + }, + "node_modules/@multiversx/sdk-wallet-connect-provider": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet-connect-provider/-/sdk-wallet-connect-provider-6.1.5.tgz", + "integrity": "sha512-jDnLoTpr8LA+De2bp5Wv1mFmfguk5cWav9QVCWMZt9SAkYcdoskF8dYJt5Rqs40UhxPS2yy+YrRBN1KWBhhC2w==", + "license": "MIT", + "dependencies": { + "@walletconnect/sign-client": "2.23.9", + "@walletconnect/utils": "2.23.9", + "bech32": "1.1.4" + }, + "peerDependencies": { + "@multiversx/sdk-core": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@multiversx/sdk-web-wallet-cross-window-provider": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-web-wallet-cross-window-provider/-/sdk-web-wallet-cross-window-provider-3.2.2.tgz", + "integrity": "sha512-CBMrf+oNKoBa/YFDTqzItLrERaW4n0pJIlGsP67H0Vr6bUlFLIlc30qsOGvWNoZvMDeDxvhvR7crayypETJSJw==", + "license": "MIT", + "peer": true, + "dependencies": { + "qs": "6.11.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@multiversx/sdk-core": "^14.0.0 || ^15.0.0" + } + }, + "node_modules/@multiversx/sdk-web-wallet-iframe-provider": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-web-wallet-iframe-provider/-/sdk-web-wallet-iframe-provider-4.0.1.tgz", + "integrity": "sha512-zIIKX4G1xYk7XSBQsHuihOag7pUAVGMZ1q3vug9XpwBwcQvgcSwC5RHzrYQusPpVdkTHIn3qa3JHqjwNj1VtGA==", + "license": "MIT", + "dependencies": { + "@types/jest": "^29.5.11", + "@types/qs": "6.9.10", + "qs": "6.11.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@multiversx/sdk-core": "^14.0.0 || ^15.0.0", + "@multiversx/sdk-web-wallet-cross-window-provider": "^3.x" + } + }, + "node_modules/@multiversx/sdk-webview-provider": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-webview-provider/-/sdk-webview-provider-3.2.7.tgz", + "integrity": "sha512-cPDJ4N7i9IDYP+z/M/vXzgjCiAr9JSQof4yp2nzQ67EwPyFPW4fenmHvzyhtD3KYdGc48OmJ0Nk/zv2+nwQnig==", + "license": "MIT", + "peerDependencies": { + "@multiversx/sdk-core": "^14.0.0 || ^15.0.0", + "@multiversx/sdk-web-wallet-cross-window-provider": "^3.x" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.0.tgz", + "integrity": "sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", + "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/ed25519": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", + "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@noble/hashes": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", + "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause" + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "license": "MIT" + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@stencil/core": { + "version": "4.38.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.1.tgz", + "integrity": "sha512-qImplYLSp2wSZJo3oMZ3HrTaI+uULcRB4Knrua7UT9VjN/va+TDfk4JAKwDyDfTDkD2laDPcy6QJP2S3hVxZFQ==", + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" + }, + "optionalDependencies": { + "@rollup/rollup-darwin-arm64": "4.34.9", + "@rollup/rollup-darwin-x64": "4.34.9", + "@rollup/rollup-linux-arm64-gnu": "4.34.9", + "@rollup/rollup-linux-arm64-musl": "4.34.9", + "@rollup/rollup-linux-x64-gnu": "4.34.9", + "@rollup/rollup-linux-x64-musl": "4.34.9", + "@rollup/rollup-win32-arm64-msvc": "4.34.9", + "@rollup/rollup-win32-x64-msvc": "4.34.9" + } + }, + "node_modules/@stencil/react-output-target": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@stencil/react-output-target/-/react-output-target-1.2.0.tgz", + "integrity": "sha512-xDNpWdRg897T3Diy5V2d8dZUdjhc4QJ/5JZdTVyv3/e9UICdJPfCY6eKp/dWWgYlJ9AUE6GLHOI1iePZmLY12A==", + "license": "MIT", + "optional": true, + "dependencies": { + "@lit/react": "^1.0.7", + "html-react-parser": "^5.2.2", + "react-style-stringify": "^1.2.0", + "ts-morph": "^22.0.0" + }, + "peerDependencies": { + "@stencil/core": ">=3 || >= 4.0.0-beta.0 || >= 4.0.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@stencil/core": { + "optional": false + }, + "react": { + "optional": false + }, + "react-dom": { + "optional": false + } + } + }, + "node_modules/@stencil/vue-output-target": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.11.8.tgz", + "integrity": "sha512-R/kQoN15irgL7NJxWaUNSmwDLfoDBZjlYaXNnW3LHlF30TYfyez6pRgD7ZglSSTVktMtCXz6ZPhg0uq59VkhOw==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0", + "vue": "^3.4.38", + "vue-router": "^4.5.0" + }, + "peerDependenciesMeta": { + "@stencil/core": { + "optional": true + }, + "vue": { + "optional": false + }, + "vue-router": { + "optional": true + } + } + }, + "node_modules/@ts-morph/common": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.23.0.tgz", + "integrity": "sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==", + "license": "MIT", + "optional": true, + "dependencies": { + "fast-glob": "^3.3.2", + "minimatch": "^9.0.3", + "mkdirp": "^3.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.9.10", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "license": "MIT", + "optional": true, + "dependencies": { + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "license": "MIT", + "optional": true, + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "vue": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "license": "MIT", + "optional": true + }, + "node_modules/@walletconnect/core": { + "version": "2.23.9", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.23.9.tgz", + "integrity": "sha512-ws4WG8LeagUo2ERRo02HryXRcpwIRmCQ3pHLW5gWbVReLXXIpgk6ZAfID3fEGHevIwwnHSGww+nNhNpdXyiq0g==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/jsonrpc-ws-connection": "1.0.16", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "3.0.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.23.9", + "@walletconnect/utils": "2.23.9", + "@walletconnect/window-getters": "1.0.1", + "es-toolkit": "1.44.0", + "events": "3.3.0", + "uint8arrays": "3.1.1" + }, + "engines": { + "node": ">=18.20.8" + } + }, + "node_modules/@walletconnect/core/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/core/node_modules/unstorage": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/environment": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/environment/-/environment-1.0.1.tgz", + "integrity": "sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/events": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/events/-/events-1.0.1.tgz", + "integrity": "sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==", + "license": "MIT", + "dependencies": { + "keyvaluestorage-interface": "^1.0.0", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/heartbeat": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@walletconnect/heartbeat/-/heartbeat-1.2.2.tgz", + "integrity": "sha512-uASiRmC5MwhuRuf05vq4AT48Pq8RMi876zV8rr8cV969uTOzWdB/k+Lj5yI2PBtB1bGQisGen7MM1GcZlQTBXw==", + "license": "MIT", + "dependencies": { + "@walletconnect/events": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-provider": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.14.tgz", + "integrity": "sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.8", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-types": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.4.tgz", + "integrity": "sha512-P6679fG/M+wuWg9TY8mh6xFSdYnFyFjwFelxyISxMDrlbXokorEVXYOxiqEbrU3x1BmBoCAJJ+vtEaEoMlpCBQ==", + "license": "MIT", + "dependencies": { + "events": "^3.3.0", + "keyvaluestorage-interface": "^1.0.0" + } + }, + "node_modules/@walletconnect/jsonrpc-utils": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.8.tgz", + "integrity": "sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw==", + "license": "MIT", + "dependencies": { + "@walletconnect/environment": "^1.0.1", + "@walletconnect/jsonrpc-types": "^1.0.3", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/jsonrpc-ws-connection": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.16.tgz", + "integrity": "sha512-G81JmsMqh5nJheE1mPst1W0WfVv0SG3N7JggwLLGnI7iuDZJq8cRJvQwLGKHn5H1WTW7DEPCo00zz5w62AbL3Q==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.6", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0", + "ws": "^7.5.1" + } + }, + "node_modules/@walletconnect/logger": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/logger/-/logger-3.0.2.tgz", + "integrity": "sha512-7wR3wAwJTOmX4gbcUZcFMov8fjftY05+5cO/d4cpDD8wDzJ+cIlKdYOXaXfxHLSYeDazMXIsxMYjHYVDfkx+nA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.2", + "pino": "10.0.0" + } + }, + "node_modules/@walletconnect/relay-api": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11.tgz", + "integrity": "sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-types": "^1.0.2" + } + }, + "node_modules/@walletconnect/relay-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.1.0.tgz", + "integrity": "sha512-qFw+a9uRz26jRCDgL7Q5TA9qYIgcNY8jpJzI1zAWNZ8i7mQjaijRnWFKsCHAU9CyGjvt6RKrRXyFtFOpWTVmCQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.0", + "@noble/hashes": "1.7.0", + "@walletconnect/safe-json": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "uint8arrays": "^3.0.0" + } + }, + "node_modules/@walletconnect/relay-auth/node_modules/@noble/hashes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", + "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/safe-json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/safe-json/-/safe-json-1.0.2.tgz", + "integrity": "sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/sign-client": { + "version": "2.23.9", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.23.9.tgz", + "integrity": "sha512-Xj+hw4E6mGRyhCdVOT/RMgnG+up/Y3v0ho5PlkVozvXWeVSqHNh9DmjLuU97a7OACoGd/oHBF6g3NVqD7MgCMQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@walletconnect/core": "2.23.9", + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "3.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.23.9", + "@walletconnect/utils": "2.23.9", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/time/-/time-1.0.2.tgz", + "integrity": "sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/types": { + "version": "2.23.9", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.23.9.tgz", + "integrity": "sha512-IUl1PpD/Dig8IE2OZ9XtjbPohEyOZJ73xs92EDUzoIyzRtfm36g2D340pY3iu3AAdLv1yFiaZafB8Hf8RFze8A==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "3.0.2", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/types/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/types/node_modules/unstorage": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils": { + "version": "2.23.9", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.23.9.tgz", + "integrity": "sha512-C5TltCs8UPypNiteYnKSv8+ZDK2EjVDyXCxN6kA9bkA+j6KGsNIV7l9MUA8WBAvE5Gi5EcBdhD3R9Hpo/1HHqQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@msgpack/msgpack": "3.1.3", + "@noble/ciphers": "1.3.0", + "@noble/curves": "1.9.7", + "@noble/hashes": "1.8.0", + "@scure/base": "1.2.6", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "3.0.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.23.9", + "@walletconnect/window-getters": "1.0.1", + "@walletconnect/window-metadata": "1.0.1", + "blakejs": "1.2.1", + "detect-browser": "5.3.0", + "ox": "0.9.3", + "uint8arrays": "3.1.1" + } + }, + "node_modules/@walletconnect/utils/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils/node_modules/unstorage": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/window-getters": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/window-getters/-/window-getters-1.0.1.tgz", + "integrity": "sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/window-metadata": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/window-metadata/-/window-metadata-1.0.1.tgz", + "integrity": "sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==", + "license": "MIT", + "dependencies": { + "@walletconnect/window-getters": "^1.0.1", + "tslib": "1.14.1" + } + }, + "node_modules/abitype": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.4.tgz", + "integrity": "sha512-dpKH+N27vRjarMVTFFkeY445VTKftzGWpL0FiT7xmVmzQRKazZexzC5uHG0f6XKsVLAuUlndnbGau6lRejClxg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "peer": true, + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "license": "ISC", + "optional": true, + "dependencies": { + "@noble/hashes": "^1.2.0" + } + }, + "node_modules/blake2b": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.3.tgz", + "integrity": "sha512-pkDss4xFVbMb4270aCyGD3qLv92314Et+FsKzilCLxDz5DuZ2/1g3w4nmBbu6nKApPspnjG7JcwTjGZnduB1yg==", + "license": "ISC", + "dependencies": { + "blake2b-wasm": "^1.1.0", + "nanoassert": "^1.0.0" + } + }, + "node_modules/blake2b-wasm": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-1.1.7.tgz", + "integrity": "sha512-oFIHvXhlz/DUgF0kq5B1CqxIDjIJwh9iDeUUGQUcvgiGz7Wdw03McEO7CfLBy7QKGdsydcMCgO9jFNBAFCtFcA==", + "license": "MIT", + "dependencies": { + "nanoassert": "^1.0.0" + } + }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT", + "optional": true + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT", + "optional": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-browser": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", + "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", + "license": "MIT" + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT", + "optional": true + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "optional": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ed25519-hd-key": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ed25519-hd-key/-/ed25519-hd-key-1.1.2.tgz", + "integrity": "sha512-/0y9y6N7vM6Kj5ASr9J9wcMVDTtygxSOvYX+PJiMD7VcxCx2G03V5bLRl8Dug9EgkLFsLhGqBtQWQRcElEeWTA==", + "license": "MIT", + "dependencies": { + "bip39": "3.0.2", + "create-hmac": "1.1.7", + "tweetnacl": "1.0.3" + } + }, + "node_modules/ed25519-hd-key/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", + "license": "MIT" + }, + "node_modules/ed25519-hd-key/node_modules/bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "license": "ISC", + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "node_modules/ed2curve": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.3.0.tgz", + "integrity": "sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ==", + "license": "Unlicense", + "dependencies": { + "tweetnacl": "1.x.x" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/engine.io-client": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", + "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", + "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT", + "optional": true + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "optional": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "optional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "optional": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/hash-base/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hash-base/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hash-base/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/hash-base/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-dom-parser": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-5.1.8.tgz", + "integrity": "sha512-MCIUng//mF2qTtGHXJWr6OLfHWmg3Pm8ezpfiltF83tizPWY17JxT4dRLE8lykJ5bChJELoY3onQKPbufJHxYA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/remarkablemark" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "domhandler": "5.0.3", + "htmlparser2": "10.1.0" + } + }, + "node_modules/html-react-parser": { + "version": "5.2.17", + "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.2.17.tgz", + "integrity": "sha512-m+K/7Moq1jodAB4VL0RXSOmtwLUYoAsikZhwd+hGQe5Vtw2dbWfpFd60poxojMU0Tsh9w59mN1QLEcoHz0Dx9w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/remarkablemark" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/html-react-parser" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "domhandler": "5.0.3", + "html-dom-parser": "5.1.8", + "react-property": "2.0.2", + "style-to-js": "1.1.21" + }, + "peerDependencies": { + "@types/react": "0.14 || 15 || 16 || 17 || 18 || 19", + "react": "0.14 || 15 || 16 || 17 || 18 || 19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/idb-keyval": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.3.0.tgz", + "integrity": "sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT", + "optional": true + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/keccak": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", + "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keyvaluestorage-interface": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", + "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==", + "license": "MIT" + }, + "node_modules/linkifyjs": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz", + "integrity": "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "license": "MIT", + "optional": true + }, + "node_modules/lodash.inrange": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/lodash.inrange/-/lodash.inrange-3.3.6.tgz", + "integrity": "sha512-NqOT/CVclLZKet0ALwg1joQ/XnfxePGf8L9WKvzOcTDAI0axG3Ej24qDGwLk0ejN4AVfFvs6vY5AWBB53o5piw==", + "license": "MIT", + "optional": true + }, + "node_modules/lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.range": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.range/-/lodash.range-3.2.0.tgz", + "integrity": "sha512-Fgkb7SinmuzqgIhNhAElo0BL/R1rHCnhwSZf78omqSwvWqD0kD2ssOAutQonDKH/ldS8BxA72ORYI09qAY9CYg==", + "license": "MIT", + "optional": true + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "license": "MIT" + }, + "node_modules/lodash.trimend": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/lodash.trimend/-/lodash.trimend-4.18.0.tgz", + "integrity": "sha512-8w2M3nZAWLN1OX/6mTPCwRlZiD/LhVyPV9l7DEbkd9wybExvg9AcCjbD19swj6oVzX5hcMZHp3/Y1b4Sl3sHKg==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/nanoassert": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", + "integrity": "sha512-C40jQ3NzfkP53NsO8kEOFd79p4b9kDXQMwgiY1z8ZwrDZgUyom0AHwGegF4Dm99L+YoYhuaB0ceerUcXmqr1rQ==", + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "optional": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "license": "MIT" + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ox": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.3.tgz", + "integrity": "sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.9", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "optional": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT", + "optional": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.6.tgz", + "integrity": "sha512-BT6eelPB1EyGHo8pC0o9Bl6k6SYVhKO1jEbd3lcTrtr7XHdjP8BW1YpfCV3G9Kwkxgattk+S5q2/RvuttCsS1g==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.0.0.tgz", + "integrity": "sha512-eI9pKwWEix40kfvSzqEP6ldqOoBIN7dwD/o91TY5z8vQI12sAffpR/pOqAD1IVVwIVHDpHjkq0joBPdJD0rafA==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "slow-redact": "^0.3.0", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "optional": true, + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz", + "integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==", + "license": "MIT", + "optional": true + }, + "node_modules/react-style-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-style-stringify/-/react-style-stringify-1.2.0.tgz", + "integrity": "sha512-88JZckqgbfXJaGcDQoTFKRmBwHBF0Ddaxz3PL9Q+vywAJruBY+NdN+ZiKSBe7r/pWtjbt0naZdtH5oNI1X3FLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emotion/unitless": "^0.10.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC", + "optional": true + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "optional": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scryptsy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slow-redact": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.2.tgz", + "integrity": "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==", + "license": "MIT" + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "optional": true, + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-morph": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-22.0.0.tgz", + "integrity": "sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ts-morph/common": "~0.23.0", + "code-block-writer": "^13.0.1" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vue": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC", + "optional": true + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "7.5.12", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.12.tgz", + "integrity": "sha512-1xGnbYN3zbog9CwuNDQULNRrTCLIn46/WmpR1f0w6PsCYQHkylZr5vkd6kfMZYV6pRnQkcPNRyiA8LsrNKyhpg==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC", + "optional": true + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/zustand": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.7.tgz", + "integrity": "sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/testing/cookbook-ts/project/package.json b/testing/cookbook-ts/project/package.json new file mode 100644 index 000000000..f9aa576a0 --- /dev/null +++ b/testing/cookbook-ts/project/package.json @@ -0,0 +1,27 @@ +{ + "name": "cookbook-ts-project", + "version": "0.0.0", + "private": true, + "description": "Harness project the cookbook recipe code is extracted into and type-checked (TypeScript counterpart of the crowdfunding crate).", + "scripts": { + "typecheck": "tsc --noEmit --strict" + }, + "dependencies": { + "@multiversx/sdk-core": "^15.4.0", + "@multiversx/sdk-dapp": "^5.6.0", + "axios": "^1.7.9", + "bignumber.js": "^9.1.2", + "protobufjs": "^7.4.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/node": "^20.17.6", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "typescript": "^5.6.3" + }, + "engines": { + "node": ">=20.13.1" + } +} diff --git a/testing/cookbook-ts/project/src/scaffold.d.ts b/testing/cookbook-ts/project/src/scaffold.d.ts new file mode 100644 index 000000000..20cb723f4 --- /dev/null +++ b/testing/cookbook-ts/project/src/scaffold.d.ts @@ -0,0 +1,19 @@ +// scaffold.d.ts — ambient types the harness provides so recipe code type-checks +// without pulling in a bundler. +// +// This is committed (the extractor never touches it) and is the TS analogue of +// the hand-written skeleton files in the crowdfunding crate (meta/, sc-config +// .toml, …) that the Rust extractor assumes already exist. +// +// The sdk-dapp "React" recipes are authored for Vite and read Vite env vars via +// `import.meta.env.VITE_*`. In their own repo a `vite-env.d.ts` referencing +// `vite/client` supplies these types. The harness does not install Vite, so we +// declare just the shape the recipes rely on (VITE_* string vars) here instead. + +interface ImportMetaEnv { + readonly [key: `VITE_${string}`]: string | undefined; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/testing/cookbook-ts/project/tsconfig.json b/testing/cookbook-ts/project/tsconfig.json new file mode 100644 index 000000000..f8f1f54e0 --- /dev/null +++ b/testing/cookbook-ts/project/tsconfig.json @@ -0,0 +1,41 @@ +{ + // Strict type-check config for every extracted cookbook recipe. + // + // It is a superset of the two tsconfig shapes the standalone recipe projects + // ship: the sdk-core "backend/script" recipes (module: commonjs, node types) + // and the sdk-dapp "React" recipes (jsx, DOM libs, bundler resolution). Since + // the harness only runs `tsc --noEmit` (never emits or executes), the emit + // module format is irrelevant — so ESNext + bundler resolution type-checks + // both families with one config. Every strict flag below is carried over + // verbatim from the recipe tsconfigs, so nothing is checked more loosely here + // than in the recipe's own repo. + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + + "allowJs": false, + "noEmit": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "useDefineForClassFields": true, + "skipLibCheck": true, + + // Strictness — identical to the recipe projects' own tsconfig. + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "forceConsistentCasingInFileNames": true + }, + // src/recipes/** is filled in by the extractor; src/scaffold.d.ts is committed. + "include": ["src"] +} diff --git a/testing/cookbook-ts/tsconfig.json b/testing/cookbook-ts/tsconfig.json new file mode 100644 index 000000000..da6ce28ca --- /dev/null +++ b/testing/cookbook-ts/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["extract.ts", "parser.ts"] +} From fb51ec1b4d58fd8ee7580a181e30042980239a33 Mon Sep 17 00:00:00 2001 From: Lukas <64620972+lamentierschweinchen@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:11:41 +0200 Subject: [PATCH 03/13] cookbook: port start-here, migration, and wallets recipes (wave 1) Port 14 recipes onto the proven TS-verification harness + design layer: - start-here (5): vite-react-minimal, nextjs-minimal, local-https-setup, sign-and-send, v4-to-v5-migration - migration (3): dapp-provider-to-initapp, get-account-info-v4-vs-v5, send-transactions-to-transaction-manager - wallets (6): wallet-login-button, defi-extension-login, walletconnect-login, ledger-login, native-auth, typescript-strict-mode-checklist Each page inlines its source verbatim as titled ts/tsx fences (v4 "before" and next/vite config files shown as untitled illustrative fences since they can't compile against the harness deps). Internal-only doc references scrubbed from comments. Sidebar wired under "Cookbook (recipes)". Verified: cookbook-ts extract + tsc --noEmit --strict green (57 files / 16 recipes incl. exemplars); docusaurus build green, all pages generate, no new broken links; markdownlint + codespell clean. Co-Authored-By: Claude Opus 4.8 --- .../migration/dapp-provider-to-initapp.mdx | 352 +++++++++ .../migration/get-account-info-v4-vs-v5.mdx | 224 ++++++ ...nd-transactions-to-transaction-manager.mdx | 275 +++++++ .../cookbook/start-here/local-https-setup.mdx | 405 +++++++++++ .../cookbook/start-here/nextjs-minimal.mdx | 524 ++++++++++++++ .../cookbook/start-here/sign-and-send.mdx | 674 ++++++++++++++++++ .../start-here/v4-to-v5-migration.mdx | 640 +++++++++++++++++ .../start-here/vite-react-minimal.mdx | 589 +++++++++++++++ .../cookbook/wallets/defi-extension-login.mdx | 423 +++++++++++ .../sdk-js/cookbook/wallets/ledger-login.mdx | 420 +++++++++++ .../sdk-js/cookbook/wallets/native-auth.mdx | 470 ++++++++++++ .../typescript-strict-mode-checklist.mdx | 277 +++++++ .../cookbook/wallets/wallet-login-button.mdx | 396 ++++++++++ .../cookbook/wallets/walletconnect-login.mdx | 392 ++++++++++ sidebars.js | 26 + 15 files changed, 6087 insertions(+) create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/wallets/ledger-login.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/wallets/native-auth.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/wallets/typescript-strict-mode-checklist.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button.mdx create mode 100644 docs/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login.mdx diff --git a/docs/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp.mdx b/docs/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp.mdx new file mode 100644 index 000000000..39409383d --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp.mdx @@ -0,0 +1,352 @@ +--- +title: "Migration: DappProvider to initApp, side-by-side" +description: The full-app version of the DappProvider removal, every file that touches it, including the session-restore state v4 quietly handled for you. +difficulty: intermediate +est_minutes: 8 +last_validated: "2026-07-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - migration + - v4-to-v5 + - react + - typescript +--- + +This Cookbook's +[Migrate v4 to v5: hook-by-hook diffs](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration) +shows the `` to `initApp()` change as a single before/after +component. That is the compressed version. This recipe shows the same change at +the scale it actually happens at, a whole entry point plus its provider tree, +because that is where the real migration cost lives: not the line that changed, +but everything that now has to be sequenced around it. + +Come here once you are actually doing the `` removal and want to +see every file that touches it. Start with the hook-by-hook overview first if you +have not already. + +## Prerequisites + +- An existing sdk-dapp v4.x codebase using ``. +- Familiarity with React `useEffect` and component lifecycles. +- Node.js >= 20.13.1. + +## Before (v4): what DappProvider did implicitly + +The v4 entry point wraps the app in a declarative component. These two files are +v4 code and are shown for contrast only; they are not part of the compiled recipe. + +```tsx +// before/App.tsx (v4) +``` + +```tsx +// @ts-nocheck — illustrative v4 code; v4 imports are not installed in this +// recipe (we only install v5 packages — see package.json). This file is +// reference material, not compiled or linted. Excluded via tsconfig.json +// `exclude` and .eslintrc.cjs `ignorePatterns`. +// +// before/App.tsx — the entire v4 app tree, wrapped once at the root. +// +// did five things under the hood that v5 makes explicit +// (see after/Providers.tsx's header comment for the v5 side of each): +// 1. Read `environment` and picked the matching network config. +// 2. Restored a previous session from sessionStorage/localStorage, if any. +// 3. Registered the WebSocket transaction-status listener. +// 4. Made every hook in `hooks/` work anywhere inside the tree. +// 5. Exposed `customNetworkConfig` for WalletConnect's project ID and any +// API-address overrides. +// +// None of this was awaited by the caller — rendered +// synchronously and hooks like useGetIsLoggedIn() simply returned `false` +// (or stale-but-safe defaults) until the async restore finished, then +// re-rendered. There was no explicit "not ready yet" state to handle. + +import { DappProvider } from '@multiversx/sdk-dapp/wrappers'; +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/types'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/hooks'; +import { ExtensionLoginButton } from '@multiversx/sdk-dapp/UI'; + +const customNetworkConfig = { + name: 'customConfig', + walletConnectV2ProjectId: 'multiversx-cookbook-demo', +}; + +function Dashboard() { + // This hook "just works" here because it's rendered inside + // , below in the tree. v4 didn't distinguish + // "provider mounted" from "session restore finished" — both + // logged-out and still-restoring render as isLoggedIn === false. + const isLoggedIn = useGetIsLoggedIn(); + + if (!isLoggedIn) { + return ; + } + + return

Connected.

; +} + +export function App() { + return ( + + + + ); +} +``` + +```tsx +// before/index.tsx (v4) +``` + +```tsx +// @ts-nocheck — illustrative v4 code; see App.tsx's header comment. +// +// before/index.tsx — v4 entry point. +// +// Nothing app-specific happens here beyond the standard React root mount. +// All of the SDK setup is inside because it's a component +// (), not an imperative call — that's the whole point of +// this recipe. Contrast with after/index.tsx, which is almost as short but +// for a different reason: the setup moved into Providers.tsx instead. + +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error('No #root element found in index.html'); +} + +createRoot(rootElement).render( + + + , +); +``` + +## After (v5): initApp made explicit + +The v5 entry point calls `initApp()` inside a `Providers` wrapper, then renders. +These three files compile against the installed v5 SDK. + +```tsx title="after/Providers.tsx" +// after/Providers.tsx — v5 imperative init, side-by-side with before/App.tsx. +// +// Same verified pattern as recipes/vite-react-minimal/src/providers.tsx — +// copied here (not cross-imported; each Cookbook recipe is self-contained) +// because the WHOLE POINT of this recipe is to show this file existing at +// all, where v4 had no equivalent. Read this alongside before/App.tsx: +// every one of 's five under-the-hood jobs (listed in that +// file's header comment) now has an explicit, visible line of code here: +// +// 1. Environment selection -> dappConfig.dAppConfig.environment +// 2. Session restore -> awaited inside initApp(); ready gate +// below makes the "still restoring" +// state explicit, unlike v4. +// 3. WebSocket listener -> registered internally by initApp() +// when a restored session exists +// (during initApp() startup). +// 4. Hooks work anywhere -> still true, but only for descendants +// of , and only after `ready` +// flips true. +// 5. WalletConnect project ID -> dappConfig.dAppConfig.providers.walletConnect +// +// The one thing v4 did NOT make you handle: an explicit "not ready yet" +// render. v5's initApp() is a Promise; until it resolves, hooks return +// defaults that are indistinguishable from "logged out" (same as v4 during +// restore) — but now the developer decides whether to show a loading +// state instead of silently rendering "logged out" for a few hundred ms. +// This recipe chooses to show one (see the `!ready` branch below); +// omitting it is also valid and matches v4's actual behavior more closely. + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +const dappConfig = { + storage: { + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + // theme is a ThemesEnum member, not a bare string literal — see + // node_modules/@multiversx/sdk-dapp/out/methods/initApp/initApp.types.d.ts. + // A `theme: 'dark' as const` cast (what the naive port of the v4 prop + // would look like) does not satisfy InitAppType; this was a real, + // confirmed tsc --strict failure found while verifying this Cookbook's + // start-here recipes. + theme: ThemesEnum.dark, + providers: { + walletConnect: { + walletConnectV2ProjectId: 'multiversx-cookbook-demo', + }, + }, + }, +}; + +// Module-scope flag prevents double-init under React Strict Mode, which +// deliberately double-invokes effects in dev. v4 needed no equivalent +// because was a component, not an effect. +let initStarted = false; + +export function Providers({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return
Initializing…
; + } + return <>{children}; +} +``` + +```tsx title="after/App.tsx" +// after/App.tsx — the v5 half of the side-by-side, structurally identical +// to before/App.tsx's child: read login state, render a login +// button or a connected message. The difference this recipe is actually +// about is entirely in Providers.tsx / index.tsx — this file is here to +// prove the hook still "just works" once it's inside , same +// promise v4 made inside . +// +// For the full connect/disconnect button and account-field walkthrough, +// see the wallets/wallet-login-button and wallets/read-connected-account +// recipes — this file stays intentionally minimal so the provider/init +// contrast above isn't buried under unrelated UI code. + +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; + +export function App(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + + const handleConnect = (): void => { + // openUnlockPanel() returns Promise; this handler is a sync + // onClick, so the promise is explicitly discarded to satisfy + // @typescript-eslint/no-floating-promises. + void UnlockPanelManager.getInstance().openUnlockPanel(); + }; + + if (!isLoggedIn) { + return ( + + ); + } + + return

Connected.

; +} +``` + +```tsx title="after/index.tsx" +// after/index.tsx — v5 entry point. UnlockPanelManager also needs a one- +// time init() call somewhere; the Next.js / Vite minimal recipes do this +// inside Providers.tsx right after initApp() resolves. This recipe's +// Providers.tsx keeps that out to stay focused purely on the initApp() +// side of the story — see wallets/wallet-login-button for the +// UnlockPanelManager.init({ loginHandler, onClose }) call this app would +// need before openUnlockPanel() does anything useful in a real app. + +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; +import { Providers } from './Providers'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error('No #root element found in index.html'); +} + +createRoot(rootElement).render( + + + + + , +); +``` + +## What `` actually did, made explicit + +`` did five things under the hood that v5 makes explicit: + +| v4 (``, implicit) | v5 (`initApp()`, explicit) | +| --- | --- | +| Environment selection | `dappConfig.dAppConfig.environment` | +| Session restore | Awaited inside `initApp()`; this recipe's `ready` gate makes the "still restoring" state visible | +| WebSocket listener | Registered internally by `initApp()` when a restored session exists | +| Hooks work anywhere | Still true, but only for descendants of ``, and only after `ready` flips `true` | +| WalletConnect project ID | `dappConfig.dAppConfig.providers.walletConnect.walletConnectV2ProjectId` | + +The one thing v4 never made you handle explicitly: a distinct "not ready yet" +render. `initApp()` is a Promise; until it resolves, hooks return defaults +indistinguishable from "logged out", same as v4 during restore, but v5 hands you +the choice of showing a loading state instead of letting that ambiguous instant +flash by. + +:::tip[Reused, not reinvented] +`after/Providers.tsx` is copied from the already-verified +[Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal) +pattern, not written fresh for this recipe. That pattern is proven to compile and run. +::: + +## Pitfalls + +:::danger[Pitfall 1: module-scope initApp() calls crash under SSR] +If you lift the `useEffect` body to module scope (or call `initApp()` directly in +a Next.js Server Component), it crashes during server-side rendering. `initApp()` +touches `sessionStorage`, which does not exist on the server. See +[Minimal sdk-dapp v5 in Next.js](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal) +Pitfall 3 for the `"use client"` plus `useEffect` fix. +::: + +:::warning[Pitfall 2: React Strict Mode double-invokes useEffect in dev] +Without the module-scope `initStarted` guard, `initApp()` fires twice on first +mount in development. `initApp()` is idempotent, but the guard avoids a redundant +round of session-restore work. +::: + +:::note[Pitfall 3: v4 hid that restore is async; v5 does not have to] +The `ready` gate in `Providers.tsx` is optional. Skipping it means your app's +first render always shows "logged out", then flips to "logged in" a moment later +for a returning user. Decide deliberately whether that flash is acceptable, rather +than inheriting v4's behavior by default. +::: + +## See also + +- [Migrate v4 to v5: hook-by-hook diffs](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration) + is the compressed six-pair overview this recipe expands on. +- [Migration: useGetAccountInfo v4 vs v5](/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5) + is the next thing that breaks once your provider tree compiles. +- [Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal) + is the target shape `after/` is drawn from directly. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5.mdx b/docs/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5.mdx new file mode 100644 index 000000000..b83cfe7ab --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5.mdx @@ -0,0 +1,224 @@ +--- +title: "Migration: useGetAccountInfo v4 vs v5" +description: The same hook name still resolves in v5 but returns less data, silently. The easiest migration bug to miss because nothing forces a second look at it. +difficulty: intermediate +est_minutes: 6 +last_validated: "2026-07-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - migration + - v4-to-v5 + - react + - typescript +--- + +The +[hook-by-hook migration overview](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration) +shows the v4 shape and the v5-recommended replacement for `useGetAccountInfo`. +This recipe adds the piece that overview leaves out: **`useGetAccountInfo` is not +removed in v5**. The same import name still resolves, still compiles, and still +returns real data. It just returns *less* data, silently, which makes this one of +the easiest migration bugs to miss entirely. + +## Prerequisites + +- An existing sdk-dapp v4.x codebase calling `useGetAccountInfo()`. +- Node.js >= 20.13.1. + +## Before (v4): one hook returned everything + +v4's `useGetAccountInfo()` returned address, balance, nonce, `isLoggedIn`, and +`tokenLogin` from a single call. This is v4 code, shown for contrast only. + +```tsx +// before.tsx (v4) +``` + +```tsx +// @ts-nocheck — illustrative v4 code; v4 imports are not installed. +// +// before.tsx — v4's useGetAccountInfo(): one hook, one big object. +// address + balance/nonce + login flag + auth token, all in one place. + +import { useGetAccountInfo } from '@multiversx/sdk-dapp/hooks'; + +export function AccountSummary() { + const { + address, + account: { balance, nonce }, + isLoggedIn, + tokenLogin, + } = useGetAccountInfo(); + + if (!isLoggedIn) { + return

Not logged in.

; + } + + return ( +
+

Address: {address}

+

Balance: {balance}

+

Nonce: {nonce}

+

Token: {tokenLogin?.nativeAuthToken}

+
+ ); +} +``` + +## After (v5): same name, thinner shape + +The same import name still resolves under its new `out/react/account/...` path, +same call-site shape, but `isLoggedIn` and `tokenLogin` are gone from the return +type. `tsc --strict` only catches this if you actually destructure those fields. + +```tsx title="after-shim.tsx" +// after-shim.tsx — the SAME import name resolves in v5, but the shape it +// returns changed. This is the trap this recipe exists to name: a v4 +// codebase that only bulk-renamed import paths (the "smallest viable +// patch" from the v4-to-v5-migration recipe) will still find +// `useGetAccountInfo` at +// `@multiversx/sdk-dapp/out/react/account/useGetAccountInfo` and will +// still compile a call to it — right up until it reaches for +// `.isLoggedIn` or `.tokenLogin`, which no longer exist on the return +// type. `tsc --strict` catches this the moment you destructure a field +// that's gone; it will NOT catch it if you only ever access `.address` or +// `.account`, which still work identically. That's what makes this a +// sneaky migration bug rather than an obvious one: the hook doesn't +// disappear, so nothing forces you to look at it. +// +// Confirmed against the real, installed +// `@multiversx/sdk-dapp@5.6.23` `.d.ts`: the v5 shape is +// { address, account, publicKey, ledgerAccount, walletConnectAccount, +// websocketEvent, websocketBatchEvent } +// — no `isLoggedIn`, no `tokenLogin`. Both moved to useGetLoginInfo() / +// useGetIsLoggedIn() — see after-recommended.tsx. + +import { useGetAccountInfo } from '@multiversx/sdk-dapp/out/react/account/useGetAccountInfo'; + +export function AccountSummaryShim(): JSX.Element { + const { address, account } = useGetAccountInfo(); + + // The next two lines are what the v4 code looked like. Uncommenting + // either one is a real, reproducible tsc --strict failure against the + // installed v5 types — left here as comments rather than deleted, so + // the failure is visible without needing to break the build to see it: + // + // const { isLoggedIn } = useGetAccountInfo(); + // // error TS2339: Property 'isLoggedIn' does not exist on type + // // 'AccountInfoType'. + // + // const { tokenLogin } = useGetAccountInfo(); + // // error TS2339: Property 'tokenLogin' does not exist on type + // // 'AccountInfoType'. + + return ( +
+

Address: {address}

+

Balance: {account.balance}

+

Nonce: {account.nonce}

+

+ Not shown: login flag and auth token — this shim no longer carries + them. See after-recommended.tsx. +

+
+ ); +} +``` + +## After (v5): the recommended three-hook split + +The recommended replacement: `useGetAccount()` plus `useGetIsLoggedIn()` plus +`useGetLoginInfo()`. + +```tsx title="after-recommended.tsx" +// after-recommended.tsx — the v5-recommended replacement: three hooks, +// one per concern, instead of reaching for the still-exists-but-thinner +// useGetAccountInfo() shim in after-shim.tsx. +// +// Migration rule of thumb: +// address, balance, nonce, shard, username -> useGetAccount() +// isLoggedIn -> useGetIsLoggedIn() +// tokenLogin, providerType, expires, loginMethod -> useGetLoginInfo() + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetLoginInfo } from '@multiversx/sdk-dapp/out/react/loginInfo/useGetLoginInfo'; + +export function AccountSummaryRecommended(): JSX.Element { + const { address, balance, nonce } = useGetAccount(); + const isLoggedIn = useGetIsLoggedIn(); + const { tokenLogin } = useGetLoginInfo(); + + if (!isLoggedIn) { + return

Not logged in.

; + } + + return ( +
+

Address: {address}

+

Balance: {balance}

+

Nonce: {nonce}

+

Token: {tokenLogin?.nativeAuthToken}

+
+ ); +} +``` + +## Why the shim is the dangerous one + +A codebase migrated only via the "smallest viable patch" (bulk find-and-replace +of import paths) keeps calling `useGetAccountInfo()` under its new +`out/react/account/...` path. If that codebase only ever reads `.address` or +`.account`, **it keeps compiling and keeps working**. Nothing forces a second look +at this hook. The bug surfaces only when code reaches for `.isLoggedIn` or +`.tokenLogin`, at which point `tsc --strict` reports a real, specific error (see +the commented-out lines in `after-shim.tsx` for the exact message). + +Confirmed against the real, installed `@multiversx/sdk-dapp@5.6.23` `.d.ts`: the +v5 return type is +`{ address, account, publicKey, ledgerAccount, walletConnectAccount, websocketEvent, websocketBatchEvent }`, +with no `isLoggedIn` and no `tokenLogin`. + +**Practical takeaway:** grep your v4 codebase for every `useGetAccountInfo()` call +site and check what it destructures, rather than trusting that "it still compiles" +means "it still works the same way." + +## How it works + +`useGetAccount()`, `useGetIsLoggedIn()`, and `useGetLoginInfo()` read three +different Zustand store slices, each with a different update cadence: account +balance/nonce change per transaction, the login flag changes twice per session, +and the login info (auth token, provider type) changes only on login. v4's +`useGetAccountInfo()` bundled all three into one subscription, so any change to any +of the three re-rendered every consumer. v5's split means a component that only +cares about the balance does not re-render when the auth token refreshes, a real +performance improvement that falls out of doing the migration properly instead of +leaning on the shim indefinitely. + +## Pitfalls + +:::danger[Pitfall 1: "it still compiles" is not proof the migration is done] +`useGetAccountInfo` surviving the rename means nothing here forces a re-read. +Audit every call site's destructuring, not just whether the import resolves. +::: + +:::warning[Pitfall 2: isLoggedIn and tokenLogin move to different hooks entirely] +There is no `useGetAccountInfo().isLoggedIn` replacement shortcut; you need the +separate `useGetIsLoggedIn()` / `useGetLoginInfo()` calls. +::: + +:::note[Pitfall 3: don't assume every surviving v4 hook kept its full shape] +This Cookbook found the same "same name, thinner shape" pattern is worth checking +case-by-case per hook, not assumed either way. +::: + +## See also + +- [Migrate v4 to v5: hook-by-hook diffs](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration) + is the six-pair overview this recipe expands on. +- [Migration: DappProvider to initApp](/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp) + is the structural change that usually comes first in a real migration. +- [Read the connected account](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account) + is the full `useGetAccount()` field reference. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager.mdx b/docs/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager.mdx new file mode 100644 index 000000000..815da6079 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager.mdx @@ -0,0 +1,275 @@ +--- +title: "Migration: useSendTransactions to TransactionManager.send" +description: The v4 hook this migration is named for does not exist in the real package. This recipe covers what does, batch sends and a v5 nested-array trigger. +difficulty: intermediate +est_minutes: 8 +last_validated: "2026-07-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - migration + - v4-to-v5 + - transaction + - typescript +--- + +## A correction before this recipe starts + +Some migration notes name the v4 hook this recipe replaces as +`useSendTransactions()`. While authoring this recipe we checked the real, +published `@multiversx/sdk-dapp@4.6.4` (the last v4 release, fetched from the npm +registry and unpkg): **no hook by that exact name exists anywhere in it.** The two +real v4 surfaces for sending transactions are: + +- the plain `sendTransactions()` function from + `services/transactions/sendTransactions`, already covered by + [Migrate v4 to v5: hook-by-hook diffs](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration)'s + hook-by-hook pair, for the single-array case. +- `useSendBatchTransactions()`, a dedicated hook for grouped/sequential sends, + which that pair does not cover. + +This recipe covers the second one, in depth, since it maps onto a genuinely +distinct v5 capability (nested-array batch sends). + +## Prerequisites + +- An existing sdk-dapp v4.x codebase using `useSendBatchTransactions()`. +- Node.js >= 20.13.1. +- Familiarity with + [Sign and send a transaction](/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send); + this recipe assumes the single-transaction flow and focuses on what changes for groups. + +## Before (v4): the dedicated batch-sending hook + +This is v4 code, shown for contrast only. + +```tsx +// before.tsx (v4) — useSendBatchTransactions() +``` + +```tsx +// @ts-nocheck — illustrative v4 code; v4 imports are not installed. +// +// before.tsx — v4's dedicated batch-sending hook. +// +// A note on the name of this recipe: some migration notes say +// "useSendTransactions() replaced by +// TransactionManager.getInstance().send()". Checked against the real, +// published @multiversx/sdk-dapp@4.6.4 (the last v4 release, fetched from +// the npm registry / unpkg while authoring this recipe): there is no hook +// literally named useSendTransactions anywhere in that package. The two +// real v4 surfaces for sending transactions are: +// - the plain `sendTransactions()` function from +// `services/transactions/sendTransactions` — already covered by +// the v4-to-v5-migration recipe's migrations/04-send-transactions/ +// pair, for the single-array case. +// - `useSendBatchTransactions()`, shown below — a dedicated HOOK for +// grouped/sequential sends, which pair 04 does not cover at all. +// This recipe covers the batch hook specifically, since it's the one +// real gap pair 04 leaves open, and it maps onto a genuinely distinct v5 +// capability (see after.tsx) rather than repeating pair 04's content. + +import { useSendBatchTransactions } from '@multiversx/sdk-dapp/hooks/transactions/batch/useSendBatchTransactions'; + +export function useBatchStakeFlow() { + const { send, batchId } = useSendBatchTransactions(); + + const submitBatch = async (transactions: unknown[]) => { + const result = await send({ + transactions, + transactionsDisplayInfo: { + processingMessage: 'Processing batch…', + successMessage: 'Batch complete', + errorMessage: 'Batch failed', + }, + }); + + if ('error' in result && result.error) { + throw new Error(result.error); + } + + return result.batchId; + }; + + return { submitBatch, batchId }; +} +``` + +## After (v5): TransactionManager with a nested array + +```tsx title="after.tsx" +// after.tsx — v5's grouped-send path: a nested Transaction[][] fed to the +// SAME TransactionManager.send()/.track() calls the flat, single-group +// case uses (see v4-to-v5-migration/migrations/04-send-transactions/after.tsx +// for that simpler case). +// +// Confirmed directly from the installed +// node_modules/@multiversx/sdk-dapp/out/managers/TransactionManager/TransactionManager.d.ts +// and its compiled .cjs: +// +// send(signedTransactions: Transaction[] | Transaction[][]): +// Promise +// track(sentTransactions: SignedTransactionType[] | SignedTransactionType[][], options?): +// Promise +// +// `isBatchTransaction(t)` (the internal helper send() calls first) is +// exactly `Array.isArray(t[0])` — a NESTED array is what selects batch +// mode. Concretely, from reading the compiled source: +// - Flat Transaction[] -> send() POSTs each transaction individually +// to `${apiAddress}/transactions` (parallel, +// one request per tx). +// - Nested Transaction[][] -> send() POSTs the WHOLE nested structure +// ONCE to `${apiAddress}/batch`, as +// { transactions: [[...], [...]], id: batchId }. +// The outer arrays are what the source calls "sequential" groups +// (`getIsSequential` / `sequentialToFlatArray`) — that naming strongly +// implies the API processes group 1 before group 2, but this recipe only +// verifies the CLIENT-SIDE contract (the type signature, the batch +// trigger, and the endpoint choice) by reading the compiled source; it +// does not independently confirm server-side group ordering against a +// live multi-group broadcast, which would need funded wallets and is out +// of scope here. Treat "sequential" as the SDK's own claim, not this +// recipe's independently-verified one. +// +// track() ALWAYS flattens the structure back down +// (`sequentialToFlatArray`, triggered when every top-level element is +// itself an array) before building the transaction session — so whether +// you sent flat or grouped, you get exactly ONE sessionId covering every +// transaction in the call, not one per group. + +import { Transaction } from '@multiversx/sdk-core'; +import { TransactionManager } from '@multiversx/sdk-dapp/out/managers/TransactionManager/TransactionManager'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; + +/** + * Splits a flat array back into the group sizes it came from. Used below + * to re-nest a signed, flat array of transactions after signing — signing + * itself only accepts a flat Transaction[] (see Pitfall 1), so grouping + * has to happen before signing (to know the sizes) and again after (to + * rebuild the nested shape send()/track() expect for batch mode). + */ +function regroup(flat: T[], groupSizes: number[]): T[][] { + const groups: T[][] = []; + let offset = 0; + for (const size of groupSizes) { + groups.push(flat.slice(offset, offset + size)); + offset += size; + } + return groups; +} + +/** + * Sends `groups` — e.g. [[approveTx1, approveTx2], [claimTx]] — as one + * grouped submission, and tracks all of it under a single sessionId. + * + * Replaces v4's useSendBatchTransactions() hook (see before.tsx). Unlike + * the hook, this is plain async code — usable from anywhere + * getAccountProvider() works (a click handler, a CLI-adjacent browser + * script, a service worker), not tied to a React render. + */ +export async function sendGrouped(groups: Transaction[][]): Promise { + const groupSizes = groups.map((g) => g.length); + const flat = groups.flat(); + + // 1. Sign. signTransactions() only accepts a flat array — see Pitfall 1. + const provider = getAccountProvider(); + const signedFlat = await provider.signTransactions(flat); + + // 2. Re-nest using the ORIGINAL group sizes, now that everything is + // signed. The order signTransactions() returns is stable (one + // signed transaction per input, same index), so slicing by the + // sizes we recorded before flattening reconstructs the same groups. + const signedGroups = regroup(signedFlat, groupSizes); + + // 3. Send. A nested array here (signedGroups is Transaction[][]) makes + // isBatchTransaction() return true, which routes this call to the + // dedicated POST /batch endpoint instead of N individual + // POST /transactions calls. + const txManager = TransactionManager.getInstance(); + const sent = await txManager.send(signedGroups); + + // 4. Track. One call, one sessionId, even though the input was nested — + // track() flattens internally before building the session. + const sessionId = await txManager.track(sent, { + transactionsDisplayInfo: { + processingMessage: 'Processing batch…', + successMessage: 'Batch complete', + errorMessage: 'Batch failed', + }, + }); + + return sessionId; +} +``` + +## The v5 mechanism: a nested array picks batch mode + +Confirmed directly from the installed +`node_modules/@multiversx/sdk-dapp/out/managers/TransactionManager/TransactionManager.d.ts` +and its compiled source: + +```ts +send(signedTransactions: Transaction[] | Transaction[][]): + Promise + +track(sentTransactions: SignedTransactionType[] | SignedTransactionType[][], options?): + Promise +``` + +The internal `isBatchTransaction(t)` check is exactly `Array.isArray(t[0])`. +Passing a **nested** array selects batch mode: + +| Input shape | What `send()` actually does | +| --- | --- | +| Flat `Transaction[]` | POSTs each transaction individually to `${apiAddress}/transactions` (parallel, one request per tx) | +| Nested `Transaction[][]` | POSTs the whole nested structure **once** to `${apiAddress}/batch`, as `{ transactions: [[...], [...]], id: batchId }` | + +The outer groups are what the compiled source's own internal naming calls +"sequential" (`getIsSequential` / `sequentialToFlatArray`), naming that implies the +API processes group 1 before group 2. This recipe verifies the **client-side +contract** (the type signature, the batch trigger, and the endpoint choice) by +reading the compiled source directly; it does not independently confirm +server-side group ordering against a live multi-group broadcast. + +`track()` always flattens the structure back down before building the transaction +session, so whether you sent flat or grouped, you get exactly **one** `sessionId` +covering every transaction in the call, not one per group. + +## Pitfalls + +:::danger[Pitfall 1: signTransactions() only accepts a flat array] +There is no nested-array overload for signing, confirmed from +`node_modules/@multiversx/sdk-dapp/out/providers/DappProvider/DappProvider.d.ts`: +`signTransactions(transactions: Transaction[], options?): Promise`. +This is why `after.tsx` flattens before signing and re-nests afterward, rather +than trying to sign group-by-group. +::: + +:::warning[Pitfall 2: re-nesting depends on signTransactions() preserving order and count] +`regroup()` in `after.tsx` slices the signed, flat result using the group sizes +recorded before flattening. This is correct as long as the signed array has +exactly one entry per input transaction, in the same order. +::: + +:::warning[Pitfall 3: batch mode requires a resolvable logged-in address] +Reading `TransactionManager.cjs`'s `sendSignedBatchTransactions`: it builds the +batch id from `getAccount().address` and returns an explicit error if that is +empty. The flat-array path has no equivalent check at this layer. +::: + +:::note[Pitfall 4: don't treat "sequential" as independently proven here] +See "The v5 mechanism" above; this recipe verified the client contract, not +on-chain group ordering. +::: + +## See also + +- [Migrate v4 to v5: hook-by-hook diffs](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration) + is the six-pair overview, including the flat single-array send/track case. +- [Sign and send a transaction](/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send) + is the canonical single-transaction sign, send, track flow this recipe extends + to groups. +- [Migration: useGetAccountInfo v4 vs v5](/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5) + is another "the old name still resolves, but check the real shape" trap from the + same migration. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup.mdx new file mode 100644 index 000000000..d662316da --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup.mdx @@ -0,0 +1,405 @@ +--- +title: Local HTTPS for dApp dev (mkcert + Vite + Next.js) +description: Wallet providers refuse to connect over plain HTTP. Set up trusted HTTPS for localhost using mkcert with Vite, Next.js, or framework-agnostic plain Node. +difficulty: beginner +est_minutes: 6 +last_validated: "2026-05-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - https + - mkcert + - vite + - nextjs + - wallet +--- + +The first time a builder hits the wallet button over plain HTTP, they get a wall +of silence. The DeFi extension does nothing. Ledger does nothing. Most docs say +"make sure you run your app on https" and stop there. This recipe is the missing how. + +The short version: install mkcert, run `setup.sh`, copy the matching framework +snippet. Five minutes total. The longer version walks the per-framework +configuration, Vite (two options), Next.js (two options), framework-agnostic plain +Node, plus the "I can't install root certs on this corp laptop" Cloudflare Tunnel +escape hatch. + +## Why HTTPS in dev + +Most wallet providers require a secure context to connect: + +| Provider | Works under HTTP? | HTTPS required? | +| --- | --- | --- | +| Extension (DeFi) | No | Yes | +| Ledger | No | Yes | +| WalletConnect / xPortal | Yes | Optional | +| Web Wallet (cross-window) | Yes | Optional | +| Passkey / WebAuthn | No | Yes (browser API requires secure context) | +| InMemory (dev) | Yes | Optional | + +If you only test against xPortal during dev, plain HTTP works. The moment a +teammate tries the DeFi extension, things break, and it does not fail with a useful +message; it just silently does not connect. + +## Prerequisites + +- macOS, Linux, or Windows. +- A package manager (Homebrew on macOS / apt on Linux / Chocolatey on Windows). +- Admin rights for the one-time root cert install. + +## Step 1: Install mkcert and trust the local CA (one-time per machine) + +The bundled `setup.sh` does the install, trust, and cert generation in one shot: + +```bash +#!/usr/bin/env bash +# setup.sh — install mkcert and generate localhost certs for the current +# directory. Idempotent: re-running is safe. +# +# Usage: +# bash setup.sh +# +# What this does: +# 1. Detects OS (macOS / Linux / Windows-via-Git-Bash) and installs +# mkcert via the appropriate package manager if it's not present. +# 2. Runs `mkcert -install` to add the local CA to the OS trust store. +# Requires admin rights on first run; later runs are no-ops. +# 3. Generates cert + key files in the current directory: +# ./localhost+2.pem (cert) +# ./localhost+2-key.pem (key) +# 4. Adds both file globs to .gitignore so the key is never committed. +# +# After running this, configure your dev server to use the cert/key — +# see snippets/vite.config.mkcert.ts or snippets/server.mkcert.js. + +set -euo pipefail + +echo ">>> Local HTTPS setup for dApp dev" +echo + +# ---- 1. Install mkcert ---- +if command -v mkcert >/dev/null 2>&1; then + echo "mkcert already installed: $(mkcert --version 2>&1)" +else + echo "Installing mkcert..." + if [[ "$OSTYPE" == "darwin"* ]]; then + if ! command -v brew >/dev/null 2>&1; then + echo "Homebrew not found. Install from https://brew.sh and re-run." + exit 1 + fi + brew install mkcert nss + elif [[ "$OSTYPE" == "linux"* ]]; then + echo "Linux detected. Install mkcert and libnss3-tools manually:" + echo " Debian/Ubuntu: sudo apt install libnss3-tools && \\" + echo " curl -JLO 'https://dl.filippo.io/mkcert/latest?for=linux/amd64' && \\" + echo " chmod +x mkcert-* && sudo mv mkcert-* /usr/local/bin/mkcert" + echo " Arch: sudo pacman -S mkcert nss" + echo " Fedora: sudo dnf install mkcert nss-tools" + exit 1 + elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then + if ! command -v choco >/dev/null 2>&1; then + echo "Chocolatey not found. Install from https://chocolatey.org and re-run, or install mkcert directly:" + echo " choco install mkcert (PowerShell as admin)" + echo " scoop bucket add extras && scoop install mkcert" + exit 1 + fi + choco install -y mkcert + else + echo "Unsupported OS: $OSTYPE" + echo "Install mkcert manually from https://github.com/FiloSottile/mkcert" + exit 1 + fi +fi + +# ---- 2. Trust the local CA ---- +echo +echo ">>> Installing local CA (requires admin rights on first run)" +mkcert -install + +# ---- 3. Generate the cert ---- +echo +echo ">>> Generating cert for localhost, 127.0.0.1, ::1" +mkcert localhost 127.0.0.1 ::1 + +# ---- 4. .gitignore safety ---- +echo +echo ">>> Adding cert globs to .gitignore" +GITIGNORE=".gitignore" +touch "$GITIGNORE" +for pattern in "localhost*.pem" "localhost*-key.pem"; do + if ! grep -qx "$pattern" "$GITIGNORE"; then + echo "$pattern" >> "$GITIGNORE" + echo " added: $pattern" + else + echo " already present: $pattern" + fi +done + +echo +echo ">>> Done." +echo "Configure your dev server to use ./localhost+2.pem and ./localhost+2-key.pem." +echo "See:" +echo " snippets/vite.config.mkcert.ts (Vite)" +echo " snippets/server.mkcert.js (Next.js)" +echo " snippets/server.plain-node.ts (other)" +``` + +Run it from your project root: + +```bash +bash setup.sh +``` + +It detects the OS, installs mkcert via the appropriate package manager, runs +`mkcert -install` (adds a root CA to your OS trust store, admin rights required +first time), generates `./localhost+2.pem` plus `./localhost+2-key.pem`, and adds +both globs to `.gitignore`. + +## Step 2: Configure your dev server + +### Vite (mkcert): fully trusted, no browser warning (recommended) + +```ts +// snippets/vite.config.mkcert.ts — Vite HTTPS via mkcert-generated certs. +// +// Pros: fully trusted certificate. No browser warning. Same cert can be +// trusted on the phone via mkcert.dev's mobile guide for end-to-end +// trusted dev. +// +// Cons: one-time `mkcert -install` per machine; cert + key files in the +// project root that MUST be gitignored (otherwise you've shipped a key +// that signs as your hostname). +// +// Generation steps (one-time per project): +// mkcert localhost 127.0.0.1 ::1 +// → produces ./localhost+2.pem and ./localhost+2-key.pem +// +// Add both to .gitignore: +// localhost*.pem +// localhost*-key.pem + +import { readFileSync } from 'node:fs'; +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// readFileSync at config-load time: Vite's config is evaluated in Node, +// so this is fine. It runs once at server start, not per request. +const cert = readFileSync('./localhost+2.pem'); +const key = readFileSync('./localhost+2-key.pem'); + +export default defineConfig({ + plugins: [react()], + server: { + https: { cert, key }, + port: 5173, + }, +}); +``` + +### Vite (basic-ssl): zero-config but self-signed + +```bash +npm install --save-dev @vitejs/plugin-basic-ssl +``` + +```ts +// snippets/vite.config.basic-ssl.ts — minimum-config Vite HTTPS via the +// @vitejs/plugin-basic-ssl plugin. +// +// Pros: zero filesystem fingerprint. No certs to generate, manage, or +// gitignore. Good for solo work. +// +// Cons: the cert is self-signed and untrusted. Your browser shows a +// "not private" warning every dev session; the wallet extensions still +// connect, but the warning is annoying and can confuse new contributors +// to your project. +// +// Do NOT also set `server.https` yourself. Two independent reasons: +// 1. Vite's `server.https` type is `https.ServerOptions | undefined` +// (node_modules/vite/dist/node/index.d.ts) — it has never accepted a +// bare `boolean`. `https: true` fails `tsc --strict` with "no overload +// matches this call" on the actually-installed vite@5.4.21. +// 2. The plugin's own README (node_modules/@vitejs/plugin-basic-ssl/README.md) +// shows usage as `plugins: [basicSsl()]` with no `server.https` at all — +// the plugin injects the generated cert into `server.https` itself via +// its Vite `config` hook. Setting it yourself risks the two configs +// merging in an order you don't control. + +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import basicSsl from '@vitejs/plugin-basic-ssl'; + +export default defineConfig({ + plugins: [react(), basicSsl()], + server: { + port: 5173, + }, +}); +``` + +### Next.js (mkcert): fully trusted, via a custom server + +Used by `mx-template-dapp-nextjs/server.js`. + +```js +// snippets/server.mkcert.js — Next.js custom HTTPS dev server with mkcert. +// +// Run with `node server.mkcert.js` instead of `next dev`. +// +// Mirrors the pattern used by mx-template-dapp-nextjs/server.js. +// The Node `https` module wraps the Next.js request handler; mkcert +// supplies the trusted certificate so the browser doesn't show a +// warning. +// +// Generation steps (one-time per project): +// mkcert localhost 127.0.0.1 ::1 +// → produces ./localhost+2.pem and ./localhost+2-key.pem +// +// Add both to .gitignore: +// localhost*.pem +// localhost*-key.pem +// +// This file is .js (CommonJS) deliberately — Next 14 expects a CJS +// custom server. If you'd rather use ESM, rename to .mjs and switch to +// `import` syntax. + +const { createServer } = require('node:https'); +const { parse } = require('node:url'); +const { readFileSync } = require('node:fs'); +const next = require('next'); + +const port = 3000; +const dev = process.env.NODE_ENV !== 'production'; +const app = next({ dev, hostname: 'localhost', port }); +const handle = app.getRequestHandler(); + +const httpsOptions = { + key: readFileSync('./localhost+2-key.pem'), + cert: readFileSync('./localhost+2.pem'), +}; + +void app.prepare().then(() => { + createServer(httpsOptions, (req, res) => { + const parsedUrl = parse(req.url ?? '/', true); + void handle(req, res, parsedUrl); + }).listen(port, () => { + // eslint-disable-next-line no-console + console.log(`> Ready on https://localhost:${port}`); + }); +}); +``` + +Then point your `dev` script at it: + +```json +{ + "scripts": { + "dev": "node server.js", + "build": "next build", + "start": "NODE_ENV=production node server.js" + } +} +``` + +### Next.js (experimental): zero-config, browser warns + +```json +{ + "scripts": { + "dev": "next dev --experimental-https" + } +} +``` + +`npm run dev` starts the server on `https://localhost:3000` with a self-signed cert. + +### Plain Node: Express, Fastify, Koa, or vanilla + +Same pattern, different request handler. + +```ts +// snippets/server.plain-node.ts — framework-agnostic HTTPS dev server. +// +// For the rare reader who isn't on Vite or Next.js — Express, Fastify, +// Koa, or vanilla Node. The pattern is the same: load the cert, hand it +// to https.createServer. The framework's request handler slots in the +// same way the Next.js custom server does. +// +// This snippet is illustrative. In production you'd put a real reverse +// proxy (Cloudflare, NGINX, Caddy) in front of your service — local +// HTTPS only matters for the dev loop. + +import { createServer } from 'node:https'; +import { readFileSync } from 'node:fs'; +import type { IncomingMessage, ServerResponse } from 'node:http'; + +const httpsOptions = { + key: readFileSync('./localhost+2-key.pem'), + cert: readFileSync('./localhost+2.pem'), +}; + +// Replace this with your framework's request handler. For Express: +// const app = express(); +// ... +// const handler = app; +const handler = (_req: IncomingMessage, res: ServerResponse): void => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('hello over https'); +}; + +createServer(httpsOptions, handler).listen(3000, () => { + // eslint-disable-next-line no-console + console.log('> Ready on https://localhost:3000'); +}); +``` + +## Pitfalls + +:::warning[Pitfall 1: mkcert -install fails silently on locked-down corp laptops] +On macOS, IT can lock the System Keychain so `mkcert` can add to `nss` but not +Keychain. Browsers then do not trust the cert. Escape hatch: Cloudflare Tunnel. + +```bash +cloudflared tunnel --url http://localhost:3000 +``` + +A free public HTTPS URL that wallets accept. The tunnel URL changes per session +unless you register a stable hostname through the Cloudflare Zero Trust dashboard. +::: + +:::warning[Pitfall 2: committing the cert key] +The cert and key files are .gitignored, but only after you set them up that way. +`setup.sh` does it; the manual flow does not unless you remember. If you committed +the key, **regenerate** with a fresh `mkcert localhost 127.0.0.1` and rotate. +Anyone with the committed key can MITM your traffic on the local network. +::: + +:::note[Pitfall 3: mkcert's root CA expires after ~3 years] +You will start seeing "not private" warnings on what was a working setup. Run +`mkcert -install` again; it regenerates and re-trusts. +::: + +:::note[Pitfall 4: mobile testing] +Run `mkcert -CAROOT` to find the CA cert location (typically +`~/Library/Application Support/mkcert/rootCA.pem` on macOS), copy it to the iOS / +Android device, and trust manually. mkcert.dev has the per-platform trust +instructions. +::: + +:::note[Pitfall 5: staging is not dev] +The "not private" click-through is fine for `localhost`. Staging deploys should use +a real cert (Let's Encrypt via Caddy / Cloudflare / whatever your hosting provides), +not mkcert. mkcert root CAs are local-machine-only; they do not propagate to +teammates' machines. +::: + +## See also + +- [Minimal sdk-dapp v5 in Next.js](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal) + uses `next dev --experimental-https` by default; switch to mkcert via `server.js` + for fully trusted. +- [Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal) + uses `@vitejs/plugin-basic-ssl` by default; switch to mkcert for fully trusted. +- [Sign and send a transaction](/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send) + is the wallet flow this recipe makes accessible. +- [mkcert](https://github.com/FiloSottile/mkcert) is the upstream tool. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal.mdx new file mode 100644 index 000000000..a017c2dee --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal.mdx @@ -0,0 +1,524 @@ +--- +title: Minimal sdk-dapp v5 app in Next.js (App Router) +description: A working Next.js 14 App Router starter with sdk-dapp v5. Client-only init wrapper, the real next.config.js, login button, account hook, compiles strict. +difficulty: beginner +est_minutes: 10 +last_validated: "2026-04-29" +sdk_versions: + sdk-core: "^15.4.0" + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - nextjs + - react + - typescript + - wallet + - devnet +--- + +This recipe gives you the smallest working sdk-dapp v5 setup in a Next.js 14 App +Router app. It is the path the official sdk-dapp docs describe in passing without +ever showing a complete working file. Use this as your starting point if you are +scaffolding a new dApp on Next.js: clone the `recipes/nextjs-minimal` directory +and rename it. + +This recipe is **not** the right starting point if you are on Vite + React (use +[Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal)) +or if you have an existing v4 codebase to upgrade (use +[Migrate v4 to v5](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration)). + +## Prerequisites + +- Node.js >= 20.13.1 (sdk-dapp's stated minimum). +- pnpm or npm. +- A devnet wallet, either the DeFi browser extension or xPortal (mobile via + WalletConnect). +- Familiarity with the Next.js App Router (`app/` directory). + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/nextjs-minimal +npm install +npm run dev +# open https://localhost:3000 (note the s, see Pitfalls below) +``` + +## package.json: pin the SDK and peer deps + +`bignumber.js` and `protobufjs` are peer dependencies of sdk-core and must be +installed explicitly. The Next.js template installs them as direct deps; we do +the same. + +```json +{ + "name": "cookbook-recipe-nextjs-minimal", + "version": "0.1.0", + "private": true, + "description": "Cookbook recipe — minimal sdk-dapp v5 in Next.js 14 App Router. Compiles strict.", + "scripts": { + "dev": "next dev --experimental-https", + "build": "next build", + "start": "next start", + "typecheck": "tsc --noEmit --strict", + "lint": "next lint --max-warnings=0" + }, + "dependencies": { + "@multiversx/sdk-core": "^15.4.0", + "@multiversx/sdk-dapp": "^5.6.0", + "bignumber.js": "^9.1.2", + "next": "^14.2.18", + "protobufjs": "^7.4.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/node": "^20.17.6", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "eslint": "^8.57.1", + "eslint-config-next": "^14.2.18", + "typescript": "^5.6.3" + }, + "engines": { + "node": ">=20.13.1" + } +} +``` + +## next.config.js: the real config, not the README's + +This matches the actual `next.config.js` shipped in `mx-template-dapp-nextjs`, not +the simplified version in that repo's README. The four entries do four things: +`transpilePackages: ['@multiversx/sdk-dapp-ui']` (sdk-dapp-ui ships untranspiled +web components); `webpack(config) { config.resolve.fallback = { fs: false } }` +(silences the `fs` lookup in transitive deps that do not run server-side anyway); +`config.externals.push('pino-pretty', 'lokijs', 'encoding', ...)` (Node-only code +pulled in via the WalletConnect transitive chain); and the `bufferutil` / +`utf-8-validate` externals (optional WebSocket performance binaries). + +```js +// next.config.js — sdk-dapp v5 + Next.js 14 App Router +// +// This file mirrors the actual configuration shipped in +// https://github.com/multiversx/mx-template-dapp-nextjs/blob/main/next.config.js +// (NOT the simplified version shown in that repo's README). +// +// What each entry does: +// transpilePackages: sdk-dapp-ui ships ESM web components untranspiled. +// Without this, Next.js's default Babel pipeline rejects the syntax. +// webpack().resolve.fallback.fs: false: silences `fs` lookups in +// transitive deps that don't actually run server-side. +// externals: pino-pretty, lokijs, encoding pull in Node-only code via +// the WalletConnect transitive chain. Externalising them avoids +// bundling them and the warnings they produce. +// bufferutil / utf-8-validate: optional WebSocket native acceleration — +// graceful fallback if absent, but webpack tries to bundle them +// unless externalised. + +/** @type {import('next').NextConfig} */ +const nextConfig = { + transpilePackages: ['@multiversx/sdk-dapp-ui'], + webpack: (config) => { + config.resolve.fallback = { ...(config.resolve.fallback || {}), fs: false }; + config.externals.push( + 'pino-pretty', + 'lokijs', + 'encoding', + { + bufferutil: 'bufferutil', + 'utf-8-validate': 'utf-8-validate', + }, + ); + return config; + }, +}; + +module.exports = nextConfig; +``` + +## app/layout.tsx: root layout, just the shell + +`layout.tsx` is a server component (no `"use client"`). It imports ``, +which is the client-only wrapper. + +```tsx +// app/layout.tsx — Next.js 14 App Router root layout. +// +// This is a server component (no "use client" directive). It does nothing +// blockchain-related — it only renders the html/body shell and delegates +// to which is the client-only sdk-dapp init wrapper. +// +// Why this split matters: sdk-dapp's initApp() touches sessionStorage, +// which is undefined during SSR / build. Putting initApp in a server +// component crashes the build. Putting it in a "use client" component that +// runs in useEffect makes it safe. + +import type { Metadata, Viewport } from 'next'; +import type { ReactNode } from 'react'; +import { Providers } from './providers'; + +export const metadata: Metadata = { + title: 'Cookbook recipe — Minimal sdk-dapp v5 in Next.js', + description: + 'A working sdk-dapp v5 + Next.js 14 App Router setup that compiles under TypeScript strict mode.', +}; + +export const viewport: Viewport = { + width: 'device-width', + initialScale: 1, +}; + +export default function RootLayout({ + children, +}: { + children: ReactNode; +}): JSX.Element { + return ( + + + {children} + + + ); +} +``` + +## app/providers.tsx: the client-only init wrapper + +This is the file the docs leave you to invent. Three things happen here: +`"use client"` keeps `initApp()` off the server (it touches `sessionStorage`, +which crashes during SSR); `useEffect` runs `initApp(config)` once on mount and +gates rendering on `ready`; `UnlockPanelManager.init()` sets up the wallet +selection panel, with an **`async`** `onClose` handler (see Pitfall 1). + +```tsx title="app/providers.tsx" +'use client'; + +// app/providers.tsx — client-only sdk-dapp v5 init wrapper. +// +// This component is the file the official docs leave to the reader to +// invent. It does three things: +// +// 1. Marks itself "use client" so it never runs on the server. sdk-dapp's +// initApp reads from sessionStorage, which is undefined during SSR. +// 2. Calls initApp(config) once on mount inside useEffect. Renders a +// lightweight loader until init resolves; then renders children. +// 3. Configures the UnlockPanelManager — the v5 replacement for the +// per-provider login button components from v4. +// +// IMPORTANT: the OnCloseUnlockPanelType requires Promise. The docs +// example uses sync `() => navigate(...)` +// which fails strict-mode compile. We use `async` callbacks. + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { dappConfig } from '../lib/multiversx'; + +// Module-scope flag prevents double-init if React Strict Mode mounts the +// component twice (it does, deliberately, in dev). initApp is idempotent +// in v5 but skipping the second call avoids redundant work. +let initStarted = false; + +export function Providers({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + + // UnlockPanelManager.init must be called AFTER initApp resolves. + // Both callbacks return Promise — see Pitfall 1 on the recipe page. + UnlockPanelManager.init({ + loginHandler: async (): Promise => { + // Replace with your post-login navigation (e.g. router.push). + // For this minimal recipe we just leave the user on /. + // eslint-disable-next-line no-console + console.log('Logged in.'); + }, + onClose: async (): Promise => { + // Called if the user dismisses the unlock panel without logging in. + // eslint-disable-next-line no-console + console.log('Unlock panel closed.'); + }, + }); + + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return ( +
+ Initializing wallet SDK… +
+ ); + } + + return <>{children}; +} +``` + +## app/page.tsx: landing page with login and account info + +`useGetIsLoggedIn()` and `useGetAccount()` are sdk-dapp's React hooks. The +component renders a connect button when logged out and the account address plus +EGLD balance when logged in. + +```tsx title="app/page.tsx" +'use client'; + +// app/page.tsx — landing page. +// +// Two states: +// 1. Logged out → "Connect wallet" button that opens the unlock panel. +// 2. Logged in → address (bech32) and EGLD balance. +// +// All blockchain state comes from sdk-dapp React hooks. These read from a Zustand store that initApp populates, +// so they MUST run inside — they return empty defaults until +// init has resolved. + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; +import { formatAmount } from '@multiversx/sdk-dapp/out/lib/sdkDappUtils'; + +export default function Home(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const account = useGetAccount(); + const { network } = useGetNetworkConfig(); + + // The UnlockPanelManager is a singleton. .getInstance() works once it has + // been initialised inside . Calling .openUnlockPanel() raises + // the wallet selector. + const handleConnect = (): void => { + // openUnlockPanel() returns Promise (see + // node_modules/@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager.d.ts). + // This handler is a sync onClick, so we explicitly discard the promise — + // matching the eslint no-floating-promises gate. + void UnlockPanelManager.getInstance().openUnlockPanel(); + }; + + const handleLogout = async (): Promise => { + const provider = getAccountProvider(); + await provider.logout(); + }; + + return ( +
+

Minimal sdk-dapp v5 in Next.js

+

+ Network: {network.chainId} · API: {network.apiAddress} +

+ + {!isLoggedIn ? ( + + ) : ( +
+

Connected

+

+ Address: {account.address} +

+

+ Balance:{' '} + + {formatAmount({ + input: account.balance, + decimals: 18, + digits: 4, + showLastNonZeroDecimal: true, + })}{' '} + {network.egldLabel} + +

+ +
+ )} +
+ ); +} +``` + +## lib/multiversx.ts: environment config + +Centralized config so swapping environments is a one-line change. Exports +`dappConfig`, consumed by ``. + +```ts title="lib/multiversx.ts" +// lib/multiversx.ts — environment configuration for sdk-dapp's initApp. +// +// Centralising config here means that swapping environments +// (devnet -> testnet -> mainnet) is a one-file change. It also lets us keep +// secret-ish values (WalletConnect project IDs) in a single place and read +// them from environment variables. +// +// The full `dAppConfig` shape accepts more keys than this. We +// only set the keys we actually use; sdk-dapp fills the rest with defaults. + +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +// `WalletConnect` requires a project ID from https://cloud.walletconnect.com. +// Read from env so the same code ships across environments without leaks. +// Fallback is the demo project ID — works in dev but rate-limited; replace it +// with your own before going to production. +const WALLET_CONNECT_PROJECT_ID = + process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID ?? 'multiversx-cookbook-demo'; + +// `dappConfig` is consumed by `app/providers.tsx` -> `initApp(...)`. +export const dappConfig = { + storage: { + // sessionStorage on the client. SSR-safe because only calls + // initApp inside a useEffect. + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + // nativeAuth: true issues a JWT-shaped token on login that backends can + // verify. Set to false for the + // simplest possible flow. + nativeAuth: true, + // Theme is a ThemesEnum member (not a bare string) — sdk-dapp's own + // InitAppType JSDoc example uses `theme: ThemesEnum.light` (see + // node_modules/@multiversx/sdk-dapp/out/methods/initApp/initApp.types.d.ts). + // The runtime value is the literal 'mvx:dark-theme'; the web components + // key off that exact string. + theme: ThemesEnum.dark, + providers: { + walletConnect: { + walletConnectV2ProjectId: WALLET_CONNECT_PROJECT_ID, + }, + }, + }, +}; + +// Re-export EnvironmentsEnum so consumers don't need a deep import. +export { EnvironmentsEnum }; +``` + +## Run it + +```bash +npm run dev +``` + +Then open `https://localhost:3000` (HTTPS, see Pitfall 2). You should see a +"Connect wallet" button; after clicking, the sdk-dapp unlock panel listing your +installed providers; after connecting, the account address (bech32) and the EGLD +balance, formatted via `formatAmount`. + +## How it works + +Three sdk-dapp v5 concepts are in play here. + +**`initApp()` is imperative, not declarative.** Unlike wagmi or +`@solana/wallet-adapter-react`, sdk-dapp's setup is a function call you must +`await` once before any hook works. The `` component wraps that call +so React does not render until it resolves. This is the workaround for the +SSR-unsafe init; the docs show a top-level `initApp(...).then(render)` which works +in Vite but breaks in Next.js. + +**`UnlockPanelManager` replaces v4 login button components.** In v4 you imported +``, ``, and so on, one component +per provider. In v5 you call `UnlockPanelManager.init({ loginHandler })` and then +`.openUnlockPanel()` from any button. The panel itself is a web component rendered +by `@multiversx/sdk-dapp-ui`. + +**Hooks read from a Zustand store.** `useGetAccount()`, `useGetIsLoggedIn()`, and +`useGetNetworkConfig()` are thin wrappers over `useSelector` against a Zustand +store initialized inside `initApp()`. That is why they return empty/default values +until init completes, and why `` gates rendering on the `ready` flag. + +## Pitfalls + +:::warning[Pitfall 1: UnlockPanelManager.init's onClose must be async] +The docs example uses sync callbacks. That fails under TypeScript strict mode with +`error TS2322: Type '() => void' is not assignable to type 'OnCloseUnlockPanelType'`. +The `OnCloseUnlockPanelType` requires `Promise`. Use `async () => { ... }` +or `() => Promise.resolve(...)`. The sample code in `app/providers.tsx` does this +correctly. +::: + +:::warning[Pitfall 2: wallet providers require HTTPS, even in dev] +`npm run dev` defaults to `http://localhost:3000`. The DeFi extension and Ledger +providers refuse to connect over HTTP. WalletConnect (xPortal) **does** work over +HTTP because the connection happens over the WalletConnect relay, not the dApp +origin. See +[Local HTTPS for dApp dev](/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup) +for the mkcert workflow that makes `https://localhost:3000` work. +::: + +:::warning[Pitfall 3: initApp() must run client-side only] +`initApp()` reads from `sessionStorage` during rehydration. On the server (during +Next.js SSR or build), `sessionStorage` is undefined and the call throws. The +`"use client"` plus `useEffect` pattern in `app/providers.tsx` is mandatory; you +cannot put `initApp()` at module scope. +::: + +:::note[Pitfall 4: expect two build warnings] +Building this recipe surfaces two non-blocking warnings: +`Attempted import error: 'firstValueFrom' is not exported from 'rxjs'` and +`Critical dependency: the request of a dependency is an expression`. The first is +the `@ledgerhq/hw-transport-web-ble` package pulling an older `rxjs`; the second is +`protobufjs/inquire` doing a runtime `require` lookup. Neither breaks the build. +::: + +## See also + +- [Minimal sdk-dapp v5 in Vite + React](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal) + is the same recipe, Vite path, no SSR concerns. +- [Local HTTPS for dApp dev](/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup) + is how to make `https://localhost:3000` work for both Next.js and Vite. +- [Sign and send a transaction](/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send) + is the next thing you will do once login works. +- [Migrate v4 to v5](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration) + if you have an existing v4 Next.js codebase. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send.mdx new file mode 100644 index 000000000..c0a1277ed --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send.mdx @@ -0,0 +1,674 @@ +--- +title: Sign and send a transaction (the working path) +description: The canonical sdk-dapp v5 sign, send, and track flow. provider.signTransactions then TransactionManager.send and .track, with proper nonce handling. +difficulty: beginner +est_minutes: 8 +last_validated: "2026-05-07" +sdk_versions: + sdk-core: "^15.4.0" + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - sdk-core + - transaction + - typescript + - devnet +--- + +This is the canonical "I have a wallet, now what?" recipe, the foundation every +other transaction recipe links from. Build a `Transaction` with sdk-core, sign +with the connected provider, send and track with `TransactionManager`. The whole +pattern is three calls. + +In v4, signing was a React hook (`useSignTransactions`). In v5, signing is on the +provider itself (`provider.signTransactions(txs)`), a deliberate change because +the provider already knows the user's signing surface (extension popup, Ledger +USB, WalletConnect mobile prompt). This recipe is the v5-current pattern. + +If you do not yet have a working sdk-dapp v5 setup, start with +[Minimal sdk-dapp v5 in Next.js](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal) +or +[Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal), +both of which end where this recipe begins. + +## Prerequisites + +- A working sdk-dapp v5 setup (Next.js or Vite, see the start-here recipes). +- A logged-in account on devnet. +- A few devnet EGLD ([faucet](https://r3d4.fr/faucet) or + [devnet wallet](https://devnet-wallet.multiversx.com)). + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/sign-and-send +npm install +npm run dev +# open https://localhost:3000 +``` + +The recipe is a Next.js app, but the load-bearing file (`lib/transactions.ts`) is +framework-agnostic. Copy it into a Vite app, a Remix loader, a CLI script, or +anywhere the sdk-dapp store has been initialized. + +## The three-step pattern + +The whole flow lives in `lib/transactions.ts`. Read this once and you have the v5 +transaction pattern. + +```ts title="lib/transactions.ts" +// lib/transactions.ts — the canonical sign / send / track flow for sdk-dapp v5. +// +// This file demonstrates the three-step pattern that every transaction in a +// v5 dApp follows: +// +// 1. BUILD with sdk-core's Transaction constructor. +// Pull sender, nonce, and chainID from the sdk-dapp +// store via getAccount() and getNetworkConfig() — both are non-React +// synchronous reads. +// 2. SIGN via the connected provider. +// In v5 signing is on the provider, not a hook — this is the v4 → v5 +// semantic change documented in the v5 changelog. +// 3. SEND + TRACK with TransactionManager. +// The returned sessionId is the React-hook key for +// useGetPendingTransactions(sessionId). +// +// The function below is intentionally framework-agnostic. It reads from the +// sdk-dapp store directly (no hooks), so you can call it from a React +// onClick, a server action, a CLI script, or anywhere else. + +import { Address, Transaction } from '@multiversx/sdk-core'; +import { GAS_PRICE, GAS_LIMIT } from '@multiversx/sdk-dapp/out/constants/mvx.constants'; +import { TransactionManager } from '@multiversx/sdk-dapp/out/managers/TransactionManager/TransactionManager'; +import { getAccount } from '@multiversx/sdk-dapp/out/methods/account/getAccount'; +import { getNetworkConfig } from '@multiversx/sdk-dapp/out/methods/network/getNetworkConfig'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; +import type { SignedTransactionType } from '@multiversx/sdk-dapp/out/types/transactions.types'; + +/** + * Inputs for an EGLD transfer. Amount is in the smallest denomination — + * 1 EGLD = 10^18. + * + * Use bigint everywhere. Never `Number` — JavaScript loses precision past + * 2^53 and EGLD amounts go higher than that all the time. + */ +export interface SendEgldInput { + /** Bech32 address of the recipient. */ + receiver: string; + /** Amount in the smallest denomination (10^18 = 1 EGLD). */ + amountInSmallestDenomination: bigint; + /** Optional UTF-8 data field. Note: data adds gas. */ + data?: string; +} + +/** + * Output of a successful sign + send + track call. The sessionId is the key + * the React hooks (useGetPendingTransactions, etc.) use to render UI state. + */ +export interface SendEgldOutput { + sessionId: string; + transactionHash: string; +} + +/** + * Build, sign, send, and start tracking an EGLD transfer. + * + * Throws if no account is connected. The caller is responsible for ensuring + * useGetIsLoggedIn() is true before invoking — the function does not + * gracefully wait for login. + */ +export async function sendEgld(input: SendEgldInput): Promise { + const account = getAccount(); + const { network } = getNetworkConfig(); + + if (!account.address) { + throw new Error('No account connected. Call after a successful login.'); + } + + // Step 1 — BUILD. + // Note: account.nonce is the network's last-known nonce. If you sent any + // transactions earlier in this session you must increment locally — see + // Pitfall 1 on this page. + // + // The canonical Transaction constructor shape: value/gasLimit/nonce are + // bigint, data is Uint8Array. + const tx = new Transaction({ + sender: Address.newFromBech32(account.address), + receiver: Address.newFromBech32(input.receiver), + value: input.amountInSmallestDenomination, + gasLimit: BigInt(computeGasLimit(input.data)), + gasPrice: BigInt(GAS_PRICE), + chainID: network.chainId, + nonce: BigInt(account.nonce), + // Version 1 is the unsigned-transaction default; sdk-dapp managers handle + // hash-signing version bumps internally where needed. + version: 1, + // data is omitted via spread when undefined to play nicely with + // exactOptionalPropertyTypes (strict mode rejects passing + // `undefined` to a property that is `Uint8Array` not `Uint8Array | undefined`). + ...(input.data ? { data: new Uint8Array(Buffer.from(input.data)) } : {}), + }); + + // Step 2 — SIGN. + // The provider knows the connected wallet's signing surface (extension + // popup / Ledger USB / WalletConnect mobile prompt). signTransactions + // returns the same array shape it was given, with each tx now carrying a + // .signature field. + const provider = getAccountProvider(); + const [signed] = await provider.signTransactions([tx]); + + if (!signed) { + // Defensive: if the user cancelled the wallet popup, signTransactions + // throws or returns an empty array depending on the provider. + throw new Error('User cancelled signing.'); + } + + // Step 3 — SEND + TRACK. + // TransactionManager.send()'s type signature is shape-symmetric: pass a + // flat Transaction[] (one batch), get a flat SignedTransactionType[] back; + // pass Transaction[][] (multiple batches), get SignedTransactionType[][] + // back (node_modules/@multiversx/sdk-dapp/out/managers/TransactionManager/TransactionManager.d.ts). + // We always pass a single flat batch, so the nested variant cannot occur + // here — narrow explicitly (isFlatSentTransactions below) rather than + // casting, so a real shape change fails loudly instead of miscompiling. + // + // track() takes that SAME array (not a single unwrapped element) — see the + // TransactionManager.d.ts JSDoc example, which calls + // `txManager.track(sentTransactions, {...})` on the whole array. + const txManager = TransactionManager.getInstance(); + const sentTransactions = await txManager.send([signed]); + + if (!isFlatSentTransactions(sentTransactions)) { + throw new Error('Unexpected response shape from TransactionManager.send().'); + } + + const [sent] = sentTransactions; + if (!sent) { + throw new Error('Send failed: TransactionManager returned no transactions.'); + } + + const sessionId = await txManager.track(sentTransactions, { + transactionsDisplayInfo: { + processingMessage: 'Sending EGLD…', + successMessage: 'Transfer complete.', + errorMessage: 'Transfer failed.', + }, + }); + + // `SignedTransactionType` already carries `hash: string` (see + // node_modules/@multiversx/sdk-dapp/out/types/transactions.types.d.ts). + // sdk-dapp's own status-polling joins on this same field internally, so + // it is the authoritative hash — no separate computation needed. + // + // Earlier drafts of this recipe called + // `new TransactionComputer().computeTransactionHash(sent)` and hex-encoded + // the result via `Buffer.from(...)`. Both are wrong for this SDK version: + // computeTransactionHash() expects an sdk-core `Transaction` instance, not + // the `SignedTransactionType` plain object `send()` returns here (that's a + // real tsc --strict error, not just a style nit) — and its return value is + // already a hex `string`, not a `Uint8Array` + // (node_modules/@multiversx/sdk-core/out/core/transactionComputer.js), so + // re-wrapping it in `Buffer.from(str).toString('hex')` would silently + // double-encode it into a wrong, longer hex string. + const transactionHash = sent.hash; + + return { + sessionId, + transactionHash, + }; +} + +/** + * Type guard narrowing TransactionManager.send()'s union return type down to + * the flat (single-batch) variant. We only ever call send() with a single + * flat Transaction[] batch, so this should always hold at runtime; it exists + * so the compiler (not an assertion) enforces that invariant. + */ +function isFlatSentTransactions( + value: SignedTransactionType[] | SignedTransactionType[][], +): value is SignedTransactionType[] { + return value.length === 0 || !Array.isArray(value[0]); +} + +/** + * Compute a reasonable gas limit for a plain or data-bearing EGLD transfer. + * + * The default GAS_LIMIT (50_000) covers the empty-data case. For data + * transactions, the network charges 1500 gas per byte. + */ +function computeGasLimit(data: string | undefined): number { + const baseLimit = GAS_LIMIT; + if (!data) { + return baseLimit; + } + const dataBytes = Buffer.byteLength(data, 'utf8'); + // 1500 gas per byte of data. Add some headroom (10%) so a slight protocol + // change doesn't immediately invalidate every recipe — CI catches drift. + return Math.ceil((baseLimit + dataBytes * 1500) * 1.1); +} +``` + +The three steps: + +1. **Build** with sdk-core. `Transaction` is the value type; + `Address.newFromBech32()` parses the bech32 strings. `getAccount()` and + `getNetworkConfig()` are non-React reads of the sdk-dapp store; they work from + any context as long as `initApp()` has resolved. +2. **Sign** with the provider. `getAccountProvider()` returns the connected + provider; calling `.signTransactions([tx])` opens the wallet UI and resolves + with the same array, now signature-bearing. +3. **Send and track** with `TransactionManager`. The singleton's `.send()` POSTs + to the network; `.track()` starts the WebSocket-or-polling tracker that updates + the Zustand store as the tx transitions pending to success/fail. The returned + `sessionId` is the lookup key for the React hooks. + +## The React hook + +A thin wrapper around `sendEgld()` with status / error state for `onClick` use: + +```ts title="lib/useSendEgld.ts" +// lib/useSendEgld.ts — React hook around the framework-agnostic sendEgld(). +// +// Wraps lib/transactions.ts in a stateful hook so a component can render a +// "Send" button, drive it from local state, and surface in-flight / success / +// error UI. +// +// Why a hook? sendEgld() is callable from anywhere, but most UI calls want: +// - a loading flag while the wallet popup is open +// - the sessionId, so other parts of the page can use +// useGetPendingTransactions(sessionId) to render per-tx state +// - a stable reference to the send function for use in onClick + +import { useCallback, useState } from 'react'; +import { sendEgld, type SendEgldInput, type SendEgldOutput } from './transactions'; + +export interface UseSendEgldState { + status: 'idle' | 'signing' | 'sending' | 'tracking' | 'done' | 'error'; + sessionId: string | null; + transactionHash: string | null; + error: string | null; +} + +const initialState: UseSendEgldState = { + status: 'idle', + sessionId: null, + transactionHash: null, + error: null, +}; + +export interface UseSendEgld { + state: UseSendEgldState; + send: (input: SendEgldInput) => Promise; + reset: () => void; +} + +export function useSendEgld(): UseSendEgld { + const [state, setState] = useState(initialState); + + const send = useCallback( + async (input: SendEgldInput): Promise => { + setState({ ...initialState, status: 'signing' }); + try { + // The status doesn't get a chance to update between signing and + // sending in any visually meaningful way; we keep it simple. + const out = await sendEgld(input); + setState({ + status: 'done', + sessionId: out.sessionId, + transactionHash: out.transactionHash, + error: null, + }); + return out; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + setState({ + status: 'error', + sessionId: null, + transactionHash: null, + error: message, + }); + return null; + } + }, + [], + ); + + const reset = useCallback((): void => { + setState(initialState); + }, []); + + return { state, send, reset }; +} +``` + +## The page + +The form: receiver address, amount in EGLD (decimal, converted internally to the +smallest denomination), submit button. It pulls login state and pending-transaction +count from the sdk-dapp hooks, so the UI reflects the same store the manager writes to. + +```tsx title="app/page.tsx" +'use client'; + +// app/page.tsx — sign + send + track demo page. +// +// Three states: +// 1. Logged out → "Connect wallet" button (delegates to UnlockPanelManager). +// 2. Logged in, idle → form: receiver address + amount in EGLD. +// 3. Logged in, post-send → status display: hash, sessionId, success/error. +// +// The form's amount input is in EGLD (decimal), but lib/transactions.ts +// expects the smallest denomination (10^18). We do the conversion inline — +// see toSmallestDenomination() below — so the user can think in EGLD while +// the SDK still gets a precise bigint. + +import { useState } from 'react'; +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +import { useGetPendingTransactions } from '@multiversx/sdk-dapp/out/react/transactions/useGetPendingTransactions'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; +import { useSendEgld } from '../lib/useSendEgld'; + +export default function SendPage(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const account = useGetAccount(); + const { network } = useGetNetworkConfig(); + const { state, send, reset } = useSendEgld(); + + // useGetPendingTransactions() returns SignedTransactionType[] — a flat + // array of currently-pending transactions, NOT a sessionId-keyed map (see + // node_modules/@multiversx/sdk-dapp/out/react/transactions/useGetPendingTransactions.d.ts). + // If you need session-keyed + // lookups instead, use useGetPendingTransactionsSessions(), which returns + // Record. + const pending = useGetPendingTransactions(); + const pendingCount = pending.length; + + const [receiver, setReceiver] = useState(''); + const [amountEgld, setAmountEgld] = useState('0.001'); + + const handleConnect = (): void => { + // openUnlockPanel() returns Promise (see + // node_modules/@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager.d.ts). + // This handler is a sync onClick, so we explicitly discard the promise — + // matching the eslint no-floating-promises gate. + void UnlockPanelManager.getInstance().openUnlockPanel(); + }; + + const handleLogout = async (): Promise => { + const provider = getAccountProvider(); + await provider.logout(); + }; + + const handleSubmit = async (e: React.FormEvent): Promise => { + e.preventDefault(); + let amountInSmallest: bigint; + try { + amountInSmallest = toSmallestDenomination(amountEgld); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Bad amount:', err); + return; + } + await send({ + receiver, + amountInSmallestDenomination: amountInSmallest, + }); + }; + + return ( +
+

Sign and send a transaction

+

+ Network: {network.chainId} +

+ + {!isLoggedIn ? ( + + ) : ( + <> +

+ Connected as {account.address} +

+ + +
{ + void handleSubmit(e); + }} + style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }} + > + + + +
+ + + + )} +
+ ); +} + +interface ResultProps { + state: ReturnType['state']; + reset: () => void; + pending: number; +} + +function Result({ state, reset, pending }: ResultProps): JSX.Element | null { + if (state.status === 'idle') { + return null; + } + return ( +
+

Result

+

+ Status: {state.status} · Pending in store:{' '} + {pending} +

+ {state.transactionHash && ( +

+ Hash: {state.transactionHash} +

+ )} + {state.sessionId && ( +

+ Session ID: {state.sessionId} +

+ )} + {state.error && ( +

+ Error: {state.error} +

+ )} + +
+ ); +} + +/** + * Convert a decimal EGLD string to the smallest denomination (10^18). + * + * Avoid `parseFloat` + `* 1e18` — it loses precision for any meaningful + * balance. We do the multiplication on the integer part with bigint, then + * pad the fractional part with zeros to 18 digits. + */ +function toSmallestDenomination(decimalEgld: string): bigint { + const trimmed = decimalEgld.trim(); + if (trimmed === '' || !/^\d+(\.\d+)?$/.test(trimmed)) { + throw new Error(`Invalid amount: "${decimalEgld}"`); + } + const dotIndex = trimmed.indexOf('.'); + const integerPart = dotIndex === -1 ? trimmed : trimmed.slice(0, dotIndex); + const rawFraction = dotIndex === -1 ? '' : trimmed.slice(dotIndex + 1); + if (rawFraction.length > 18) { + throw new Error('Amount has more than 18 decimal places.'); + } + const paddedFraction = rawFraction.padEnd(18, '0'); + return BigInt(integerPart) * 10n ** 18n + BigInt(paddedFraction || '0'); +} + +const buttonStyle: React.CSSProperties = { + padding: '0.6rem 1.1rem', + fontSize: '1rem', + cursor: 'pointer', +}; + +const inputStyle: React.CSSProperties = { + display: 'block', + width: '100%', + marginTop: '0.25rem', + padding: '0.5rem', + fontSize: '0.95rem', + fontFamily: 'monospace', +}; +``` + +## How it works + +**Why signing is on the provider, not a hook.** v4 had `useSignTransactions()`, a +hook that paid the cost of being a hook (Rules of Hooks, render-phase identity) +without using any of the benefits. The signing call is fundamentally imperative: +"user clicks Send, wallet pops up". Putting it on the provider makes the call site +honest about that. + +**Why sending is via TransactionManager, not the provider.** Sending is a separate +concern from signing. `TransactionManager` handles batching, gas-bumping retries, +and integrates with the WebSocket-or-polling tracker. The provider is just a +signature surface; the manager is the orchestrator. + +**What `sessionId` is for.** `useGetPendingTransactions()`, +`useGetSuccessfulTransactions()`, and `useGetFailedTransactions()` take **no +arguments**. Each returns the flat `SignedTransactionType[]` for *every* +transaction in that state, not just yours: + +```ts +const pending = useGetPendingTransactions(); // every pending tx, all sessions +const successful = useGetSuccessfulTransactions(); +const failed = useGetFailedTransactions(); +``` + +To look up **one** session (the one `sessionId` from `send()` refers to), use the +`...Sessions()` variants instead. They return +`Record`: + +```ts +const sessions = useGetPendingTransactionsSessions(); +const mySession = sessions[sessionId]; // SessionTransactionType | undefined +``` + +## Pitfalls + +:::warning[Pitfall 1: the nonce trap (silent rejection)] +`getAccount()` returns the network's last-known nonce. If you fire two +transactions in a session, the second is rejected unless you increment the nonce +locally: + +```ts +const nonce = BigInt(getAccount().nonce); +// First transaction with nonce, signed and sent. +// For the second: +const tx2 = new Transaction({ ...rest, nonce: nonce + 1n }); +``` + +The store updates the nonce after a confirmed transaction, but if you fire two +before the first confirms you must manage the nonce yourself. +::: + +:::warning[Pitfall 2: amount is in the smallest denomination (10^18)] +`value: 1n` means **0.000000000000000001** EGLD, not 1 EGLD. The recipe's UI takes +a decimal string and converts via `toSmallestDenomination()`. Avoid +`parseFloat * 1e18`, JavaScript loses precision past 2^53. Use bigint arithmetic. +::: + +:::warning[Pitfall 3: gas limit scales with data] +The default `GAS_LIMIT` (50,000) covers an empty-data transfer. Each byte of +`data` adds 1500 gas. If you hand-roll a `Transaction` and skip the math, the +network rejects with "insufficient gas". The recipe's `computeGasLimit()` does +this, copy it. +::: + +:::warning[Pitfall 4: chain ID mismatch] +The signed transaction's `chainID` must match the network you are sending to. +Pulling from `getNetworkConfig().network.chainId` ensures consistency. Never +hard-code `"D"`, `"T"`, or `"1"`; it makes the recipe environment-bound and +silently breaks when someone switches via a config change. +::: + +:::warning[Pitfall 5: sender mismatch] +`tx.sender` MUST equal the logged-in account's bech32 address. The wallet refuses +to sign otherwise (it would sign with a different key than the tx's sender field, +then the network rejects on mismatch). +::: + +:::warning[Pitfall 6: useGetPendingTransactions() has no per-session overload] +It is tempting to assume `useGetPendingTransactions()` accepts the `sessionId` +from `send()` and filters to just that transaction. It does not; the signature is +`(): SignedTransactionType[]`, no parameters, verified against +`node_modules/@multiversx/sdk-dapp/out/react/transactions/useGetPendingTransactions.d.ts` +on `@multiversx/sdk-dapp@5.6.23`. It returns *every* pending transaction across +every session. For a single session's status, index into +`useGetPendingTransactionsSessions()` by `sessionId` instead. +::: + +## See also + +- [Minimal sdk-dapp v5 in Next.js](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal) + is the wallet-connect setup this recipe builds on. +- [Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal) + is the same pattern, Vite path. +- [Migration: useSendTransactions to TransactionManager.send](/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager) + extends this single-transaction flow to grouped/batch sends. +- [Read the connected account](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account) + is how to read the account this recipe signs for. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration.mdx new file mode 100644 index 000000000..35747a92a --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration.mdx @@ -0,0 +1,640 @@ +--- +title: "Migrate sdk-dapp v4 to v5: hook-by-hook diffs" +description: Side-by-side v4 vs v5 diffs for every removed hook, component, and import path. The shortest viable upgrade path for an existing v4 dApp. +difficulty: intermediate +est_minutes: 12 +last_validated: "2026-05-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - migration + - v4-to-v5 + - typescript + - react +--- + +Many teams have stayed on v4 because the migration cost was unclear. The official +guide is a patch-notes-style doc that lists renames; this recipe shows runnable +diffs for the six load-bearing changes plus the rename table, so you can see the +shape of your migration before you start. + +Effort estimate, very loosely: + +| Codebase size | Estimated effort | +| --- | --- | +| Small dApp: login plus a handful of transactions | Half a day | +| Medium: 20+ pages, custom login UI, multi-step txs | 2 to 3 days | +| Large: heavily customized provider tree, custom hooks | 1 to 2 weeks | + +Most of the time goes into hook renames and the `` to `initApp()` +restructure. The actual code shape is similar; the imports and the +wrapper-vs-imperative shift are what take time. + +In each pair below, the "before (v4)" block is v4 code shown for contrast only. It +imports v4-only paths and is not part of the compiled recipe; the "after (v5)" +block compiles against the installed v5 SDK. + +## The structural change: `` is gone + +v4 wrapped your app in a declarative React component; v5 calls a function before +rendering. + +```tsx +// before (v4): wraps the app +``` + +```tsx +// @ts-nocheck — illustrative v4 code; v4 imports are not installed. +// +// migrations/01-dapp-provider/before.tsx — v4 declarative provider. +// +// In v4 you wrapped the entire app tree with . The provider +// component took the network environment as a prop and handled +// initialisation under the hood. Hooks worked anywhere inside the tree. + +import { DappProvider } from '@multiversx/sdk-dapp/wrappers'; +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/types'; + +const customNetworkConfig = { + walletConnectV2ProjectId: 'multiversx-cookbook-demo', +}; + +export function App() { + return ( + + + + ); +} +``` + +```tsx title="migrations/01-dapp-provider/after.tsx" +// migrations/01-dapp-provider/after.tsx — v5 imperative init. +// +// v5 replaces with an imperative initApp() call. The +// component below mirrors the structure of the Next.js / Vite minimal +// recipes' Providers wrapper: useEffect-driven init, ready-gate, +// children pass-through. +// +// Key changes from v4: +// - wrapping is gone — call initApp(config) instead. +// - The config shape is { storage, dAppConfig: { environment, providers, ... } }. +// - initApp must run AFTER the component mounts. In Next.js this means +// a "use client" component; in Vite there is no SSR concern. +// - Hooks still work, but only after init resolves. + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; + +const dappConfig = { + storage: { + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + providers: { + walletConnect: { + walletConnectV2ProjectId: 'multiversx-cookbook-demo', + }, + }, + }, +}; + +let initStarted = false; + +export function Providers({ children }: { children: ReactNode }): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return
Initializing…
; + } + return <>{children}; +} +``` + +The mechanism changes (`` becomes `await initApp(config)`), where it +runs changes (client-side, in a `useEffect`, behind a `"use client"` directive and +a re-init guard), and SSR behavior changes (v5's `initApp()` hits `sessionStorage` +during rehydration and crashes server-side, so it must be gated). See +[Minimal sdk-dapp v5 in Next.js](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal) +Pitfall 3 for the canonical workaround. + +## `useGetAccountInfo` to `useGetAccount` + `useGetIsLoggedIn` + `useGetLoginInfo` + +v5 splits the v4 monolith into three hooks. Login state and on-chain account state +live in different store slices and have different update cadences. + +```tsx +// before (v4): useGetAccountInfo returned everything +``` + +```tsx +// @ts-nocheck — illustrative v4 code; v4 imports are not installed. +// +// migrations/02-get-account-info/before.tsx — v4 useGetAccountInfo. +// +// In v4, useGetAccountInfo() returned everything: address, balance, +// nonce, plus assorted "loginInfo" fields like isLoggedIn and the +// auth token. One hook, one big object. + +import { useGetAccountInfo } from '@multiversx/sdk-dapp/hooks'; + +export function AccountSummary() { + const { + address, + account: { balance, nonce }, + isLoggedIn, + tokenLogin, + } = useGetAccountInfo(); + + if (!isLoggedIn) { + return

Not logged in.

; + } + + return ( +
+

Address: {address}

+

Balance: {balance}

+

Nonce: {nonce}

+

Token: {tokenLogin?.nativeAuthToken}

+
+ ); +} +``` + +```tsx title="migrations/02-get-account-info/after.tsx" +// migrations/02-get-account-info/after.tsx — v5 split: useGetAccount + useGetLoginInfo. +// +// v5 splits the v4 monolith. The login state and the on-chain account +// state live in different store slices and have different update +// cadences (login info changes rarely; account balance / nonce change +// per transaction), so they got their own hooks. +// +// Migration rule of thumb: +// address, balance, nonce, shard, username → useGetAccount() +// isLoggedIn → useGetIsLoggedIn() +// tokenLogin, providerType, expires, loginMethod → useGetLoginInfo() + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetLoginInfo } from '@multiversx/sdk-dapp/out/react/loginInfo/useGetLoginInfo'; + +export function AccountSummary(): JSX.Element { + const { address, balance, nonce } = useGetAccount(); + const isLoggedIn = useGetIsLoggedIn(); + const { tokenLogin } = useGetLoginInfo(); + + if (!isLoggedIn) { + return

Not logged in.

; + } + + return ( +
+

Address: {address}

+

Balance: {balance}

+

Nonce: {nonce}

+

Token: {tokenLogin?.nativeAuthToken}

+
+ ); +} +``` + +Migration rule of thumb: `address`, `balance`, `nonce`, `shard`, `username` go to +`useGetAccount()`; `isLoggedIn` goes to `useGetIsLoggedIn()`; `tokenLogin`, +`providerType`, `expires`, `loginMethod` go to `useGetLoginInfo()`. See +[Migration: useGetAccountInfo v4 vs v5](/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5) +for why the shim that survives the rename is the dangerous case. + +## `useSignTransactions` to `provider.signTransactions(txs)` + +In v5 signing is on the provider, not a hook. The signing call is fundamentally +imperative ("user clicks Send, wallet pops up"); putting it on the provider makes +the call site honest about that. + +```tsx +// before (v4): useSignTransactions hook +``` + +```tsx +// @ts-nocheck — illustrative v4 code; v4 imports are not installed. +// +// migrations/03-sign-transactions/before.tsx — v4 useSignTransactions. +// +// In v4, signing was a hook. It returned a signTransactions function +// plus a "signing state" object you could destructure to render +// signing-pending UI. + +import { useSignTransactions } from '@multiversx/sdk-dapp/hooks'; +import { Transaction } from '@multiversx/sdk-core'; + +export function useV4SignFlow() { + const { signTransactions } = useSignTransactions(); + + const handleSign = async (txs: Transaction[]) => { + const signedTransactions = await signTransactions(txs); + return signedTransactions; + }; + + return { handleSign }; +} +``` + +```tsx title="migrations/03-sign-transactions/after.tsx" +// migrations/03-sign-transactions/after.tsx — v5 provider.signTransactions. +// +// v5 moves signing onto the provider. The signing call is fundamentally +// imperative — "user clicks Send, wallet pops up". Putting it on the +// provider makes the call site honest about that and avoids paying the +// hook overhead (Rules of Hooks, render-phase identity, etc.) for a +// call that doesn't benefit from being a hook. +// +// Migration: replace useSignTransactions() with getAccountProvider() + +// .signTransactions(). The signature is the same — same input array, +// same return-array shape. + +import { Transaction } from '@multiversx/sdk-core'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; + +// No more hook — the function below is callable from any context, not +// just inside a React component. Useful if you also want to sign from a +// service worker, a CLI, or a server action. +export async function signFlow(txs: Transaction[]): Promise { + const provider = getAccountProvider(); + const signed = await provider.signTransactions(txs); + return signed; +} +``` + +A side benefit: the v5 version is not tied to React. You can call `signFlow(txs)` +from a CLI script, a service worker, or a server action, anywhere +`getAccountProvider()` works. + +## `sendTransactions` to `TransactionManager.send` + `.track` + +v5 splits the v4 monolithic `sendTransactions()` into three steps: sign, send, +track. The split lets you do meaningful things between steps and lets you skip +steps if you only need a subset. + +```tsx +// before (v4): services.sendTransactions +``` + +```tsx +// @ts-nocheck — illustrative v4 code; v4 imports are not installed. +// +// migrations/04-send-transactions/before.tsx — v4 sendTransactions. +// +// In v4, sendTransactions() was a single function exported from the +// services module. It sign+send+track in one shot, returning a +// session id you could pass to other hooks. + +import { sendTransactions } from '@multiversx/sdk-dapp/services'; +import { Transaction } from '@multiversx/sdk-core'; + +export async function v4Send(transactions: Transaction[]) { + const { sessionId, error } = await sendTransactions({ + transactions, + transactionsDisplayInfo: { + processingMessage: 'Processing…', + successMessage: 'Done', + errorMessage: 'Failed', + }, + }); + if (error) { + throw new Error(error); + } + return sessionId; +} +``` + +```tsx title="migrations/04-send-transactions/after.tsx" +// migrations/04-send-transactions/after.tsx — v5 sign + TransactionManager. +// +// v5 splits the v4 monolithic sendTransactions() into three steps: +// 1. provider.signTransactions(txs) — opens the wallet UI +// 2. TransactionManager.send(signed) — POSTs to the network +// 3. TransactionManager.track(sent, opts) — starts the WebSocket / polling tracker +// +// The split lets you do meaningful things between steps (e.g., show a +// "preparing to send" UI while the wallet popup is open, or pre-flight +// validate the signed transactions before posting). It also lets you +// skip steps — e.g., if you sign now and send later from a different +// session, you can do that. +// +// The returned sessionId from .track() is the same string the v4 call +// returned. Existing components that consume sessionId via +// useGetPendingTransactions(sessionId) etc. continue to work. + +import { Transaction } from '@multiversx/sdk-core'; +import { TransactionManager } from '@multiversx/sdk-dapp/out/managers/TransactionManager/TransactionManager'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; + +export async function v5Send(transactions: Transaction[]): Promise { + // 1. Sign on the provider. + const provider = getAccountProvider(); + const signed = await provider.signTransactions(transactions); + + // 2. Send. + const txManager = TransactionManager.getInstance(); + const sent = await txManager.send(signed); + + // 3. Track. The returned sessionId is the lookup key for + // useGetPendingTransactions(sessionId), etc. + const sessionId = await txManager.track(sent, { + transactionsDisplayInfo: { + processingMessage: 'Processing…', + successMessage: 'Done', + errorMessage: 'Failed', + }, + }); + + return sessionId; +} +``` + +The returned `sessionId` from `.track()` is the same string the v4 call returned, +so existing components that consume it keep working. For grouped/batch sends, see +[Migration: useSendTransactions to TransactionManager.send](/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager). + +## Per-provider login buttons to `UnlockPanelManager` + +v4 exported a button component per provider. v5 collapses all of them into a single +panel and a single open call. + +```tsx +// before (v4): plus siblings +``` + +```tsx +// @ts-nocheck — illustrative v4 code; v4 imports are not installed. +// +// migrations/05-login-buttons/before.tsx — v4 per-provider login buttons. +// +// In v4 you imported a button component per provider and rendered them +// all on your login page. Each one was tightly bound to a single +// provider; choosing which to show meant rendering or not rendering +// each component. + +import { + ExtensionLoginButton, + WalletConnectLoginButton, + LedgerLoginButton, + WebWalletLoginButton, +} from '@multiversx/sdk-dapp/UI'; + +export function LoginPage() { + const callbackRoute = '/dashboard'; + + return ( +
+

Login

+ + DeFi extension + + + xPortal + + Ledger + + Web Wallet + +
+ ); +} +``` + +```tsx title="migrations/05-login-buttons/after.tsx" +// migrations/05-login-buttons/after.tsx — v5 UnlockPanelManager. +// +// v5 collapses all per-provider buttons into a single UnlockPanelManager +// that renders a panel listing every available provider. You configure +// which providers to show via `allowedProviders`. The actual panel is a +// web component from @multiversx/sdk-dapp-ui; you don't render it +// yourself. +// +// Migration: replace N per-provider button components with one button +// that calls UnlockPanelManager.openUnlockPanel(). The init() call — +// configuring loginHandler / onClose — happens once at app startup, +// inside the same wrapper as initApp(). +// +// CRITICAL: loginHandler and onClose return Promise. The docs +// example uses sync callbacks; that fails strict-mode compile. Use +// async or () => Promise.resolve(). + +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; + +// At app startup (inside the same Providers wrapper that calls initApp): +export function setupUnlockPanel(): void { + UnlockPanelManager.init({ + loginHandler: async (): Promise => { + // Replace with your post-login navigation, e.g. router.push('/dashboard'). + }, + onClose: async (): Promise => { + // Optional: handle the user dismissing the panel without logging in. + }, + // Restrict which providers appear in the panel. Omit this to show all. + allowedProviders: ['extension', 'walletConnect', 'ledger', 'crossWindow'], + }); +} + +// Anywhere you'd previously have rendered a login button: +export function LoginPage(): JSX.Element { + const handleConnect = (): void => { + // openUnlockPanel() returns Promise; this handler is a sync + // onClick, so discard it explicitly with `void` (eslint + // no-floating-promises). + void UnlockPanelManager.getInstance().openUnlockPanel(); + }; + + return ( +
+

Login

+ +
+ ); +} +``` + +Filter which providers appear in the panel via `allowedProviders` on `init()`. +Omit it to show all. + +## `useGetNotification` to `NotificationsFeedManager` + +v5 ships a pre-built notifications feed UI as a web component. You no longer render +the list yourself. + +```tsx +// before (v4): hook plus hand-rolled list +``` + +```tsx +// @ts-nocheck — illustrative v4 code; v4 imports are not installed. +// +// migrations/06-notifications/before.tsx — v4 useGetNotification. +// +// In v4, you'd render notifications by hand using the data the hook +// returned. The hook gave you the array; you styled and positioned the +// list yourself. + +import { useGetNotification } from '@multiversx/sdk-dapp/hooks'; + +export function NotificationsList() { + const notifications = useGetNotification(); + + return ( +
    + {notifications.map((n) => ( +
  • {n.message}
  • + ))} +
+ ); +} +``` + +```tsx title="migrations/06-notifications/after.tsx" +// migrations/06-notifications/after.tsx — v5 NotificationsFeedManager. +// +// v5 ships a pre-built notifications feed UI as a web component. You no +// longer render the list yourself; the manager + the +// `` element handle it. You only need to call +// .getInstance() to get a reference, and call .open() / .close() / etc. +// to drive the UI. +// +// If you need fine-grained custom rendering (e.g. branding the +// notification card differently), drop down to the store directly via +// useSelector — the data is still there. The manager is the convenience +// layer; bypass it when convenience isn't enough. + +import { NotificationsFeedManager } from '@multiversx/sdk-dapp/out/managers/NotificationsFeedManager/NotificationsFeedManager'; + +export function NotificationsButton(): JSX.Element { + // The manager wires itself to the store automatically once initApp() + // has resolved; no per-component setup is required. If you want to + // register a custom on-notification handler at mount-time, do it in a + // useEffect here. + + const open = (): void => { + // openNotificationsFeed() returns Promise (see + // node_modules/@multiversx/sdk-dapp/out/managers/NotificationsFeedManager/NotificationsFeedManager.d.ts) — + // same floating-promise gate as UnlockPanelManager.openUnlockPanel(). + void NotificationsFeedManager.getInstance().openNotificationsFeed(); + }; + + return ( + + ); +} +``` + +If you need bespoke notification rendering, drop down to the store directly via +`useSelector`; the data is still there. The manager is the convenience layer. + +## The import-path renames + +Most v4 to v5 changes are pure path renames: + +| v4 path | v5 path | Semantic change? | +| --- | --- | --- | +| `/hooks` | `/out/react//...` | Path only | +| `/services` | `/out/methods/...` and `/out/managers/...` | Path only, split | +| `/UI` | `/out/managers/...` (web components) | **UI replaced** | +| `/wrappers` | (gone, see `initApp()`) | **Replaced** | +| `/utils` | `/out/utils/...` | Path only | +| `/types` | `/out/types/...` | Path only | +| `/providers` | `/out/providers/...` | Path only | + +Two semantic gotchas worth flagging loudly: + +1. **`getAccount` is a store read in both versions**, instant return, no network + call. There is **also** a new function called `getAccountFromApi` in v5 that + hits the API. Do not conflate them. +2. **`useGetAccountInfo` is still exported** in v5 for back-compat, but it is a + thinner shim. The recommended v5 path is the three-way split shown above. + +## The smallest viable patch + +The shortest possible diff to make a v4 codebase compile under v5: + +1. Replace `` with the `Providers` wrapper from the Next.js / Vite + minimal recipes. Move the environment plus walletConnect projectId props into + `dappConfig.dAppConfig`. +2. Bulk find-and-replace import paths (`/hooks` to `/out/react//...`, + `/services` to `/out/methods/...` or `/managers/...`, `/types` to + `/out/types/...`, delete `/wrappers` references). +3. Replace `` et al. with one button that calls + `UnlockPanelManager.openUnlockPanel()`. Initialize the manager once at startup. +4. Replace `useSignTransactions` and `sendTransactions` with + `provider.signTransactions(txs)` plus `TransactionManager.send(signed)` plus + `TransactionManager.track(sent, opts)`. +5. Run `tsc --noEmit`. Fix what breaks. Most v4-vs-v5 bugs are caught at + compile-time once you are on v5 deps. + +## Pitfalls + +:::warning[Pitfall 1: getAccount vs getAccountFromApi] +Same prefix, very different semantics. `getAccount()` is a store read (free, +instant); `getAccountFromApi()` makes a network round-trip every call. Search your +codebase for `getAccountFromApi`. If it is used inside a render loop or a +frequently-fired event handler, you have added an unintended network call. +::: + +:::warning[Pitfall 2: Redux DevTools is gone] +v4 used Redux; v5 uses Zustand. Your existing Redux DevTools setup will not surface +state changes. Zustand has its own devtools middleware, but sdk-dapp does not +enable it by default. +::: + +:::warning[Pitfall 3: sessionStorage layout changed] +v4 sessions are not re-readable post-upgrade. Document this in your release notes, +users will be silently logged out on first load after the upgrade. +::: + +:::warning[Pitfall 4: OnCloseUnlockPanelType requires Promise<void>] +The v5 docs example for `UnlockPanelManager.init` uses sync callbacks; that fails +strict-mode compile. Use `async () => {}`. +::: + +:::warning[Pitfall 5: 'use client' boundaries in Next.js] +v4 was loose about SSR; v5 is strict. Any component that calls a sdk-dapp hook (or +`getAccountProvider`, or `getAccount` from the methods directory) must be inside a +`"use client"` tree. +::: + +## See also + +- [Minimal sdk-dapp v5 in Next.js](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal) + is the target shape for Next.js codebases. +- [Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal) + is the target shape for Vite codebases. +- [Sign and send a transaction](/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send) + is the new sign, send, track flow used by every transaction in v5. +- [Migration: DappProvider to initApp](/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp) + is the full-app version of the first change above. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal.mdx new file mode 100644 index 000000000..2c27da6bd --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal.mdx @@ -0,0 +1,589 @@ +--- +title: Minimal sdk-dapp v5 app in Vite + React +description: The smallest working sdk-dapp v5 setup in a fresh Vite + React + TypeScript project. Compiles strict on the first try, with HTTPS dev built in. +difficulty: beginner +est_minutes: 10 +last_validated: "2026-05-07" +sdk_versions: + sdk-core: "^15.4.0" + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - vite + - react + - typescript + - wallet + - devnet +--- + +This recipe gives you the smallest working sdk-dapp v5 setup in a fresh Vite + +React + TypeScript app. Use this if you are scaffolding a new dApp on Vite: clone +the `recipes/vite-react-minimal` directory and rename it. + +This recipe is **not** the right starting point if you are on Next.js (use +[Minimal sdk-dapp v5 in Next.js](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal) +instead, the App Router has SSR concerns this recipe deliberately avoids) or if +you have an existing v4 codebase to upgrade (use +[Migrate v4 to v5](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration)). + +The Vite path is simpler than the Next.js path because there is no SSR. +`initApp()` could run at module scope; we keep it in a `useEffect` only so the +React Strict Mode double-mount does not trigger redundant work in dev. + +## Prerequisites + +- Node.js >= 20.13.1 (sdk-dapp's stated minimum). +- pnpm or npm. +- A devnet wallet, either the DeFi browser extension or xPortal (mobile via + WalletConnect). + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/vite-react-minimal +npm install +npm run dev +# open https://localhost:5173 (note the s, see Pitfall 2) +``` + +## package.json: pin the SDK and peer deps + +`bignumber.js` and `protobufjs` are peer dependencies of sdk-core and must be +installed explicitly. The `@vitejs/plugin-basic-ssl` plugin is the simplest way +to get HTTPS on the Vite dev server. + +```json +{ + "name": "cookbook-recipe-vite-react-minimal", + "version": "0.1.0", + "private": true, + "description": "Cookbook recipe — minimal sdk-dapp v5 in Vite + React + TypeScript. Compiles strict.", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "typecheck": "tsc --noEmit --strict", + "lint": "eslint . --max-warnings=0" + }, + "dependencies": { + "@multiversx/sdk-core": "^15.4.0", + "@multiversx/sdk-dapp": "^5.6.0", + "bignumber.js": "^9.1.2", + "protobufjs": "^7.4.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-basic-ssl": "^1.2.0", + "@vitejs/plugin-react": "^4.3.3", + "eslint": "^8.57.1", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.14", + "typescript": "^5.6.3", + "typescript-eslint": "^8.13.0", + "vite": "^5.4.10" + }, + "engines": { + "node": ">=20.13.1" + } +} +``` + +## vite.config.ts: HTTPS and CommonJS interop + +Two things matter: `basicSsl()` for HTTPS dev, and `optimizeDeps` for sdk-dapp's +CommonJS transitive packages. This exact config was arrived at by actually +running `npm run dev` and `npm run build` and fixing two real failures, not by +reasoning from the docs alone. See Pitfalls 5 and 6 before you trim this file +down for your own project. + +```ts +// vite.config.ts — sdk-dapp v5 + Vite + React. +// +// Two things matter for sdk-dapp to work cleanly under Vite: +// +// 1. HTTPS for the dev server. Most wallet providers (DeFi extension, +// Ledger, Passkey) refuse to talk to http://localhost. We use +// @vitejs/plugin-basic-ssl as a zero-config solution; if you want a +// fully trusted cert (no browser warning every session), see the +// "Local HTTPS for dApp dev" recipe for the mkcert path. +// +// 2. The CommonJS interop plumbing for sdk-dapp's transitive WalletConnect +// and Ledger deps. Unlike Next.js, Vite's default resolver is +// ESM-clean for sdk-dapp 5.x and we don't need the `externals` array +// that recipes/nextjs-minimal/next.config.js carries. If you hit +// "Unexpected token 'export'" or "Cannot use import statement outside +// a module" errors at runtime, narrow the offending package and add +// it to optimizeDeps.include — but see the comment on optimizeDeps +// below before adding '@multiversx/sdk-dapp' itself; that one breaks +// the dev server rather than fixing it. + +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import basicSsl from '@vitejs/plugin-basic-ssl'; + +export default defineConfig({ + plugins: [react(), basicSsl()], + server: { + // Do NOT set `https` here yourself. basicSsl() already injects the + // generated cert into server.https via its own Vite `config` hook — see + // node_modules/@vitejs/plugin-basic-ssl/README.md. It's also not a + // boolean option: Vite's type is `https.ServerOptions | undefined` + // (node_modules/vite/dist/node/index.d.ts), so a literal `https: true` + // fails `tsc --strict` ("no overload matches this call") the moment + // anything actually typechecks this file. See the "Local HTTPS for + // dApp dev" recipe's basic-ssl snippet for the same fix in context. + port: 5173, + }, + // sdk-dapp pulls in a small number of CJS deps that Vite needs to + // pre-bundle. The list is conservative — add to it only if you see + // module-format errors in the browser console. + // + // Do NOT list '@multiversx/sdk-dapp' (the bare package) here. Verified + // against the installed @multiversx/sdk-dapp@5.6.23: its package.json has + // no "main", "module", or "exports" field at all — by design, every import + // is a deep path like '@multiversx/sdk-dapp/out/react/account/useGetAccount' + // Telling Vite to eagerly pre-bundle the bare specifier makes it try to + // resolve a package entry point that doesn't exist, and the dev server + // fails to start outright: + // "Error: Failed to resolve entry for package '@multiversx/sdk-dapp'. + // The package may have incorrect main/module/exports specified in its + // package.json." (reproduced with `npm run dev` before this fix). + // '@multiversx/sdk-core' is a different case — it does ship a real + // `main: "out/index.js"` barrel, so including it here is safe and correct. + optimizeDeps: { + include: ['@multiversx/sdk-core', 'bignumber.js', 'protobufjs'], + // These three deep paths are unresolvable in the actually-installed tree + // (npm install on 2026-07-07 pulled @multiversx/sdk-dapp@5.6.23): three + // of sdk-dapp's Ledger transport strategies + // (hw-transport-webusb, hw-transport-webhid, hw-transport-web-ble) each + // bundle their OWN nested copy of @ledgerhq/devices@8.16.0, and that + // nested copy is missing the hid-framing.js / ble/sendAPDU.js / + // ble/receiveAPDU.js files its sibling packages import — genuinely + // absent from that package version on disk, not just an exports-map + // resolution quirk (the top-level, separately-hoisted + // @ledgerhq/devices@8.0.3 does have them). This is an upstream Ledger + // packaging bug, not a Vite or sdk-dapp config issue; the fix here only + // silences the *bundler* error the same way `vite build`'s own + // suggestion does, not the underlying missing files. + exclude: [ + '@ledgerhq/devices/hid-framing', + '@ledgerhq/devices/ble/sendAPDU', + '@ledgerhq/devices/ble/receiveAPDU', + ], + }, + build: { + rollupOptions: { + // Same root cause as optimizeDeps.exclude above, mirrored for the + // production build (`vite build` uses Rollup, not esbuild, and hits + // the identical unresolvable-import error there independently). + external: [ + '@ledgerhq/devices/hid-framing', + '@ledgerhq/devices/ble/sendAPDU', + '@ledgerhq/devices/ble/receiveAPDU', + ], + }, + }, +}); +``` + +## index.html: the Vite entry + +Single root div plus an ESM script tag. Vite injects the bundle. + +```html + + + + + + Cookbook recipe — Minimal sdk-dapp v5 in Vite + + +
+ + + +``` + +## src/main.tsx: bootstrap + +Classic React 18 `createRoot` plus `StrictMode`. `` wraps the app, the +same pattern as Next.js, just without the layout/page split. + +```tsx title="src/main.tsx" +// src/main.tsx — Vite entry point. +// +// The classic React 18 createRoot + StrictMode setup. wraps +// the entire app — same pattern as Next.js, just without the layout/page +// split that the App Router imposes. +// +// Why StrictMode? It double-mounts components in dev to flush out unsafe +// side effects. The `initStarted` module-scope flag in providers.tsx +// handles the double-mount cleanly; if you remove that flag, you'll see +// initApp() invoked twice in dev. + +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; +import { Providers } from './providers'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error('No #root element found in index.html'); +} + +createRoot(rootElement).render( + + + + + , +); +``` + +## src/providers.tsx: initApp wrapper + +The body is identical to the Next.js recipe's `app/providers.tsx`, minus the +`"use client"` directive. That is the whole story: same code, no SSR concern. + +```tsx title="src/providers.tsx" +// src/providers.tsx — sdk-dapp v5 init wrapper for Vite. +// +// Same conceptual job as recipes/nextjs-minimal/app/providers.tsx, simpler +// implementation: +// +// - No "use client" needed. Vite has no SSR; the entire app is a CSR +// bundle that hydrates in the browser. sessionStorage is always +// defined by the time React mounts. +// - We can call initApp() at module scope if we want, but we keep it in +// a useEffect so the React-Strict-Mode double-mount in dev doesn't +// throw a warning. The module-scope `initStarted` flag matches the +// Next.js recipe exactly. +// +// IMPORTANT: the OnCloseUnlockPanelType requires Promise. The docs +// example uses sync `() => navigate(...)` +// which fails strict-mode compile. We use async callbacks. + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { dappConfig } from './lib/multiversx'; + +// Module-scope flag prevents double-init under React Strict Mode (which +// deliberately mounts useEffect twice in dev to surface unsafe side +// effects). initApp is idempotent in v5 but skipping the second call +// avoids redundant network round-trips. +let initStarted = false; + +export function Providers({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + + // UnlockPanelManager.init must be called AFTER initApp resolves. + // Both callbacks return Promise — see Pitfall 1. + UnlockPanelManager.init({ + loginHandler: async (): Promise => { + // Replace with your post-login navigation (e.g. a router push). + // For this minimal recipe we just leave the user on the home + // page — useGetIsLoggedIn() will flip and the UI re-renders. + // eslint-disable-next-line no-console + console.log('Logged in.'); + }, + onClose: async (): Promise => { + // eslint-disable-next-line no-console + console.log('Unlock panel closed.'); + }, + }); + + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return ( +
+ Initializing wallet SDK… +
+ ); + } + + return <>{children}; +} +``` + +## src/App.tsx: login and account display + +`useGetIsLoggedIn()`, `useGetAccount()`, and `useGetNetworkConfig()` are +sdk-dapp's React hooks. They read from the Zustand store that `initApp()` populates. + +```tsx title="src/App.tsx" +// src/App.tsx — landing component. +// +// Two states: +// 1. Logged out → "Connect wallet" button (opens UnlockPanelManager). +// 2. Logged in → bech32 address + EGLD balance. +// +// All blockchain state comes from sdk-dapp React hooks. These read from the Zustand store that +// initApp populates, so they MUST run inside — they return +// empty defaults until init has resolved. + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; +import { formatAmount } from '@multiversx/sdk-dapp/out/lib/sdkDappUtils'; + +export function App(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const account = useGetAccount(); + const { network } = useGetNetworkConfig(); + + const handleConnect = (): void => { + // openUnlockPanel() returns Promise (see + // node_modules/@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager.d.ts). + // This handler is a sync onClick, so we explicitly discard the promise — + // matching the eslint no-floating-promises gate. + void UnlockPanelManager.getInstance().openUnlockPanel(); + }; + + const handleLogout = async (): Promise => { + const provider = getAccountProvider(); + await provider.logout(); + }; + + return ( +
+

Minimal sdk-dapp v5 in Vite + React

+

+ Network: {network.chainId} · API:{' '} + {network.apiAddress} +

+ + {!isLoggedIn ? ( + + ) : ( +
+

Connected

+

+ Address: {account.address} +

+

+ Balance:{' '} + + {formatAmount({ + input: account.balance, + decimals: 18, + digits: 4, + showLastNonZeroDecimal: true, + })}{' '} + {network.egldLabel} + +

+ +
+ )} +
+ ); +} +``` + +## src/lib/multiversx.ts: environment config + +Environment variables under Vite come from `import.meta.env`, not `process.env`. +The `VITE_` prefix is required to expose a variable to the client bundle (see +Pitfall 4). + +```ts title="src/lib/multiversx.ts" +// src/lib/multiversx.ts — environment configuration for sdk-dapp's initApp. +// +// In Vite, environment variables come from import.meta.env (NOT +// process.env). The VITE_ prefix exposes them to the client bundle; vars +// without the prefix are server-only and never leak. +// +// The full dAppConfig shape accepts more keys than this. +// We only set the keys we actually use; sdk-dapp fills the rest with +// defaults. + +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +// Read from import.meta.env (Vite's runtime environment object). +// Fallback is the demo project ID — works in dev but rate-limited; +// register your own at https://cloud.walletconnect.com. +const WALLET_CONNECT_PROJECT_ID = + import.meta.env.VITE_WALLETCONNECT_PROJECT_ID ?? 'multiversx-cookbook-demo'; + +export const dappConfig = { + storage: { + // Vite always runs in the browser at dev time and in the static-export + // bundle at runtime — sessionStorage is always defined. We don't need + // the SSR-safety tap-dance the Next.js recipe does. + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + nativeAuth: true, + // See node_modules/@multiversx/sdk-dapp/out/methods/initApp/initApp.types.d.ts — + // `theme` is a ThemesEnum member, not a bare string literal. + theme: ThemesEnum.dark, + providers: { + walletConnect: { + walletConnectV2ProjectId: WALLET_CONNECT_PROJECT_ID, + }, + }, + }, +}; + +export { EnvironmentsEnum }; +``` + +## Run it + +```bash +npm run dev +``` + +Then open `https://localhost:5173`. You should see a "Connect wallet" button plus +the active chain ID and API URL; after clicking, the sdk-dapp unlock panel; after +connecting, the bech32 address and EGLD balance. + +## How it works + +This recipe is the Vite parallel to the +[Next.js minimal recipe](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal). +Three observations on the differences: + +**No `"use client"` needed.** Vite has no SSR; the entire app is CSR. +`sessionStorage` is always defined by the time React mounts, so we do not have to +defer `initApp()` to `useEffect` for SSR-safety reasons. We *do* still keep it in +`useEffect`, but only so the module-scope `initStarted` guard can protect against +React Strict Mode's double-mount in dev. + +**HTTPS via plugin, not flag.** Next.js 14 has `next dev --experimental-https`. +Vite uses a plugin (`@vitejs/plugin-basic-ssl`). Both produce the same outcome, a +self-signed cert with the browser warning the first time. For a fully trusted +cert with no warning, see +[Local HTTPS for dApp dev](/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup). + +**Same hooks, same store.** All of sdk-dapp's React hooks work identically across +Vite and Next.js. The hook layer is framework-agnostic; only the bootstrap differs. + +## Pitfalls + +:::warning[Pitfall 1: UnlockPanelManager.init's onClose must be async] +The docs example uses sync callbacks. That fails under TypeScript strict mode +with `error TS2322: Type '() => void' is not assignable to type 'OnCloseUnlockPanelType'`. +The `OnCloseUnlockPanelType` requires `Promise`. Use `async () => { ... }` +or `() => Promise.resolve(...)`. The sample code in `src/providers.tsx` does this +correctly. +::: + +:::warning[Pitfall 2: wallet providers require HTTPS, even in dev] +`@vitejs/plugin-basic-ssl` flips the dev server to HTTPS automatically; you should +see `https://localhost:5173` in the dev-server output. The DeFi extension and +Ledger refuse to connect over HTTP. WalletConnect (xPortal) works over HTTP +because the auth happens off-origin. +::: + +:::warning[Pitfall 3: HMR can re-run initApp] +Vite's hot-module-replacement re-runs modules without a full page reload. Without +the `initStarted` module-scope flag in `src/providers.tsx`, every save triggers +another `initApp()` invocation. The flag in this recipe matches the Next.js one, +keep it. +::: + +:::warning[Pitfall 4: environment variables are import.meta.env, NOT process.env] +Vite injects env vars via `import.meta.env`. The `VITE_` prefix is required for +client-bundled values; `process.env` does not exist in the browser bundle. The +recipe reads `import.meta.env.VITE_WALLETCONNECT_PROJECT_ID`. If you copy the +Next.js recipe's `process.env.NEXT_PUBLIC_*` reads verbatim, you get `undefined` +everywhere. +::: + +:::danger[Pitfall 5: never add '@multiversx/sdk-dapp' itself to optimizeDeps.include] +This breaks the dev server outright. Confirmed by actually running `npm run dev` +against the installed `@multiversx/sdk-dapp@5.6.23`: its `package.json` declares +no `main`, `module`, or `exports` field at all. Every public import is a deep path +like `@multiversx/sdk-dapp/out/react/account/useGetAccount`. Asking Vite to +eagerly pre-bundle the bare package name makes it try to resolve an entry point +that does not exist (`Failed to resolve entry for package "@multiversx/sdk-dapp"`). +`@multiversx/sdk-core` is safe to include, it ships a real `main: "out/index.js"` +barrel. +::: + +:::warning[Pitfall 6: a nested @ledgerhq/devices copy is missing files (upstream bug)] +Also found by actually running `npm run dev` / `npm run build`: three of +sdk-dapp's Ledger transport strategies each bundle their own nested +`@ledgerhq/devices@8.16.0`, and that copy is missing `hid-framing.js` and two +`ble/*` files on disk (the separately hoisted top-level `@ledgerhq/devices@8.0.3` +has them). Left alone, this breaks `npm run dev` and hard-fails `npm run build`. +`vite.config.ts` works around it by externalizing the three specific deep paths in +both `optimizeDeps.exclude` and `build.rollupOptions.external`. This is an upstream +Ledger packaging bug, not a mistake in this config. The DeFi extension, +xPortal/WalletConnect, and web-wallet login paths this recipe demonstrates are +unaffected. +::: + +## See also + +- [Minimal sdk-dapp v5 in Next.js](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal) + is the App Router parallel; choose this if you need SSR. +- [Local HTTPS for dApp dev](/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup) + is the mkcert plus Vite plugin walkthrough for fully trusted certs. +- [Sign and send a transaction](/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send) + is the next thing you will do once login works. +- [Migrate v4 to v5](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration) + if you have an existing v4 Vite codebase. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login.mdx new file mode 100644 index 000000000..cd9a6b843 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login.mdx @@ -0,0 +1,423 @@ +--- +title: Log in with the DeFi Wallet browser extension +description: A dedicated single-provider login button using ProviderFactory directly, with real extension detection and no generic multi-provider picker UI. +difficulty: intermediate +est_minutes: 8 +last_validated: "2026-07-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - wallet + - wallet-extension + - react + - typescript + - devnet +--- + +[Add a wallet login button](/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button) +covers the common case: one button, `UnlockPanelManager` picks the provider. +This recipe is for the other case. You want a single, dedicated button for +exactly one provider, the DeFi Wallet browser extension, with no picker in +between. That means talking to `ProviderFactory` directly instead of +`UnlockPanelManager`. + +Use this when your dApp only supports one wallet, or when you want +provider-specific buttons side by side (a "Connect DeFi Wallet" next to a +"Connect xPortal", see +[Login via xPortal / WalletConnect](/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login) +for that sibling). + +## Prerequisites + +- A working sdk-dapp v5 setup (Next.js or Vite, see the start-here recipes). +- The DeFi Wallet browser extension, to test the connected path. The recipe + handles its absence gracefully (see below) so you can also see that path + without installing anything. + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/defi-extension-login +npm install +npm run dev +# open https://localhost:5173 +``` + +## The button + +```tsx title="src/ExtensionLoginButton.tsx" +// src/ExtensionLoginButton.tsx — a dedicated, single-provider login button. +// +// Unlike "Adding a wallet login button" (which opens a picker listing every +// registered provider via UnlockPanelManager), this component talks to +// ProviderFactory directly for exactly one provider: the DeFi Wallet +// browser extension. Use this pattern when your dApp only supports one +// wallet, or when you want a dedicated "Connect with DeFi Wallet" button +// alongside (not inside) the generic picker. +// +// The full sequence — verified against the actual sdk-dapp source, not +// just its .d.ts files, because the .d.ts alone doesn't say whether extra +// steps are needed after provider.login(): +// +// ProviderFactory.create({ type: ProviderTypeEnum.extension }) +// → internally calls setAccountProvider() for you (see +// node_modules/@multiversx/sdk-dapp/out/providers/ProviderFactory.cjs) +// provider.login() +// → internally dispatches BOTH the login-info store action and the +// account store action (see +// node_modules/@multiversx/sdk-dapp/out/providers/DappProvider/helpers/login/helpers/accountLogin.cjs) +// +// So after `await provider.login()` resolves, useGetIsLoggedIn() and +// useGetAccount() already reflect the new session — no manual store +// wiring needed beyond calling these two functions in order. + +import { useState } from 'react'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { ProviderFactory } from '@multiversx/sdk-dapp/out/providers/ProviderFactory'; +import { ProviderTypeEnum } from '@multiversx/sdk-dapp/out/providers/types/providerFactory.types'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; + +// The DeFi Wallet extension injects `window.multiversxWallet` once it's +// installed and active (`window.elrondWallet` is the deprecated predecessor +// — the SDK still checks both). This is the SAME check +// ExtensionProviderStrategy performs internally +// (node_modules/@multiversx/sdk-extension-provider/out/extensionProvider.js); +// checking it here up-front lets us show an install link instead of a +// cryptic error after the user clicks. The ambient `Window.multiversxWallet` +// type comes from @multiversx/sdk-extension-provider's own `declare global` +// block (its extensionProvider.d.ts) — a transitive dependency of sdk-dapp. +function isExtensionInstalled(): boolean { + return ( + typeof window !== 'undefined' && + Boolean(window.multiversxWallet || window.elrondWallet) + ); +} + +export function ExtensionLoginButton(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const [status, setStatus] = useState<'idle' | 'connecting' | 'error'>('idle'); + const [error, setError] = useState(null); + + const handleConnect = async (): Promise => { + setStatus('connecting'); + setError(null); + try { + const provider = await ProviderFactory.create({ + type: ProviderTypeEnum.extension, + }); + // nativeAuth is enabled in this recipe's dappConfig, so login() + // generates its own native-auth token internally — no `token` arg + // needed here. Pass one only if you're supplying a custom token. + await provider.login(); + setStatus('idle'); + } catch (err) { + // ExtensionProviderStrategy throws "Extension provider is not + // initialised, call init() first" if the extension wasn't detected at + // provider-creation time (see + // node_modules/@multiversx/sdk-extension-provider/out/extensionProvider.js). + // The isExtensionInstalled() pre-check above should catch this case + // before we ever get here, but the catch stays as defense in depth — + // e.g. the user disables the extension between the check and the click. + const message = err instanceof Error ? err.message : String(err); + setError(message); + setStatus('error'); + } + }; + + const handleDisconnect = async (): Promise => { + const provider = getAccountProvider(); + await provider.logout(); + }; + + if (isLoggedIn) { + return ( + + ); + } + + if (!isExtensionInstalled()) { + return ( + + Install DeFi Wallet extension + + ); + } + + return ( + <> + + {status === 'error' && error && ( +

+ Connection failed: {error} +

+ )} + + ); +} + +const buttonStyle: React.CSSProperties = { + padding: '0.75rem 1.25rem', + fontSize: '1rem', + cursor: 'pointer', +}; +``` + +## The Window type declaration + +The extension injects `window.multiversxWallet`, but that global's type does not +arrive through the deep `@multiversx/sdk-dapp/out/...` import paths this recipe +uses. Declare it locally, or `tsc --strict` reports +`Property 'multiversxWallet' does not exist on type 'Window'`: + +```ts title="src/vite-env.d.ts" +// src/vite-env.d.ts — in a real Vite project this file also carries the line +// `/// ` for import.meta.env typing. The +// augmentation below is the part this recipe specifically needs. +// +// @multiversx/sdk-extension-provider declares this same augmentation in its +// own extensionProvider.d.ts (a `declare global { interface Window { ... } }` +// block), but that file only enters a project's compilation if something +// imports a TYPE from that package directly. This project only imports deep +// @multiversx/sdk-dapp/out/... paths, so the augmentation never gets pulled +// in transitively — redeclared here with the identical shape so +// `window.multiversxWallet` / `window.elrondWallet` type-check. Verified +// against node_modules/@multiversx/sdk-extension-provider/out/extensionProvider.d.ts +// on the actually-installed @multiversx/sdk-dapp@5.6.23's dependency tree. +interface Window { + /** @deprecated Use `multiversxWallet` instead. */ + elrondWallet?: { + extensionId: string; + }; + multiversxWallet?: { + extensionId: string; + }; +} +``` + +## The demo page + +```tsx title="src/App.tsx" +// src/App.tsx — demo page for ExtensionLoginButton. + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +import { ExtensionLoginButton } from './ExtensionLoginButton'; + +export function App(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const account = useGetAccount(); + const { network } = useGetNetworkConfig(); + + return ( +
+

Login via the DeFi Wallet extension

+

+ Network: {network.chainId} +

+ + + + {isLoggedIn && ( +

+ Connected as {account.address}. +

+ )} +
+ ); +} +``` + +## Provider bootstrap + +`providers.tsx` calls `initApp()` once and gates rendering until the store is +ready; `lib/multiversx.ts` holds the environment config it passes in. This +recipe does not call `UnlockPanelManager.init()` in the bootstrap, because it +never uses the panel. + +```tsx title="src/providers.tsx" +// src/providers.tsx — sdk-dapp v5 init wrapper. +// +// Deliberately does NOT configure UnlockPanelManager. This recipe bypasses +// the generic multi-provider picker entirely and talks to ProviderFactory +// directly for one specific provider — see src/ExtensionLoginButton.tsx. +// If you want the picker instead, see "Adding a wallet login button." + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { dappConfig } from './lib/multiversx'; + +let initStarted = false; + +export function Providers({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return ( +
+ Initializing wallet SDK… +
+ ); + } + + return <>{children}; +} +``` + +```ts title="src/lib/multiversx.ts" +// src/lib/multiversx.ts — environment configuration for sdk-dapp's initApp. +// Same shape as every other Vite recipe in this Cookbook. + +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +const WALLET_CONNECT_PROJECT_ID = + import.meta.env.VITE_WALLETCONNECT_PROJECT_ID ?? 'multiversx-cookbook-demo'; + +export const dappConfig = { + storage: { + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + nativeAuth: true, + theme: ThemesEnum.dark, + providers: { + walletConnect: { + walletConnectV2ProjectId: WALLET_CONNECT_PROJECT_ID, + }, + }, + }, +}; + +export { EnvironmentsEnum }; +``` + +## How it works + +**`ProviderFactory.create()` already registers the provider.** Verified against +the actual sdk-dapp source +(`node_modules/@multiversx/sdk-dapp/out/providers/ProviderFactory.cjs`), not +inferred from the `.d.ts` alone: `create()`'s last step before returning is +calling `setAccountProvider()` internally. You never call that yourself. + +**`provider.login()` already populates the whole store.** Also verified against +source +(`node_modules/@multiversx/sdk-dapp/out/providers/DappProvider/helpers/login/helpers/accountLogin.cjs`): +it dispatches the login-info store action, fetches and dispatches the account, +registers the websocket listener, and starts transaction tracking, all before +the returned promise resolves. So this really is the complete sequence: + +```ts +const provider = await ProviderFactory.create({ type: ProviderTypeEnum.extension }); +await provider.login(); +// useGetIsLoggedIn() / useGetAccount() already reflect the new session here. +``` + +**Detecting the extension before the click, not after.** +`ExtensionProviderStrategy` checks `window.multiversxWallet || window.elrondWallet` +internally +(`node_modules/@multiversx/sdk-extension-provider/out/extensionProvider.js`). If +neither is present it silently sets an internal flag rather than throwing, and +the throw only happens later, on `login()`. This recipe runs the identical check +in the UI layer first, so a user without the extension sees an install link +instead of a button that is guaranteed to fail. + +## Pitfalls + +:::warning[Pitfall 1: the not-installed error throws from login(), not create()] +`ProviderFactory.create({ type: ProviderTypeEnum.extension })` calls the +strategy's `init()` internally, which just sets a flag to `false` if the +extension is not found. It does not throw, and `create()` still returns a +provider object successfully. The actual throw +(`"Extension provider is not initialised, call init() first"`) only happens on +the next call, e.g. `login()`. Pre-check with `isExtensionInstalled()` (as this +recipe does) rather than relying on `create()` to fail fast. +::: + +:::warning[Pitfall 2: window.multiversxWallet needs a local type declaration] +`@multiversx/sdk-extension-provider` declares this global itself, but that +`.d.ts` only enters your compilation if something imports a *type* from that +package directly. This recipe only imports deep `@multiversx/sdk-dapp/out/...` +paths, so the augmentation never arrives transitively. `src/vite-env.d.ts` +redeclares the identical shape locally. +::: + +:::warning[Pitfall 3: this button only ever shows the extension option] +It is provider-specific by design, but that means a mobile user, or a desktop +user without the extension, hits a dead end unless you also offer another path. +Pair it with +[Login via xPortal / WalletConnect](/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login), +or use +[Add a wallet login button](/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button)'s +picker instead if you would rather the SDK decide which providers to surface. +::: + +:::warning[Pitfall 4: HTTPS required] +The DeFi extension refuses `http://localhost`. See +[Local HTTPS for dApp dev](/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup) +for a fully trusted mkcert alternative to this recipe's self-signed dev certificate. +::: + +## See also + +- [Login via xPortal / WalletConnect](/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login) + is the same `ProviderFactory` pattern, for the mobile-QR provider. +- [Add a wallet login button](/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button) + is the multi-provider picker alternative to this dedicated single-provider button. +- [Read the connected account](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account) + is what to do once `provider.login()` resolves. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/ledger-login.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/ledger-login.mdx new file mode 100644 index 000000000..7f0db5752 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/ledger-login.mdx @@ -0,0 +1,420 @@ +--- +title: Log in with a Ledger hardware wallet +description: A dedicated Ledger login button using ProviderFactory directly, including the anchor element the SDK renders its own device-connect and account-picker UI into. +difficulty: intermediate +est_minutes: 8 +last_validated: "2026-07-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - wallet + - ledger + - react + - typescript + - devnet +--- + +A dedicated, single-provider login button for a Ledger hardware wallet. It is the +same `ProviderFactory` pattern as +[Login via the DeFi extension](/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login) +and +[Login via xPortal / WalletConnect](/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login), +this time for `ProviderTypeEnum.ledger`. Ledger needs the same real DOM anchor +WalletConnect does, but for a different reason: instead of a QR code, the SDK +renders its own device-connect screen, a paginated account picker, and a confirm +screen into it. + +Use this when you want a dedicated "Connect Ledger" button, and when your users +are expected to bring physical hardware wallets. This recipe cannot be exercised +end to end without one, see Pitfall 1. + +## Prerequisites + +- A working sdk-dapp v5 setup (Next.js or Vite, see the start-here recipes). +- A physical Ledger device with the MultiversX app installed and open on it. +- A Chromium-based browser. WebHID/WebUSB, what `@multiversx/sdk-hw-provider` + uses to talk to the device, are not implemented in Firefox or Safari. + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/ledger-login +npm install +npm run dev +# open https://localhost:5173 +``` + +## The button + +```tsx title="src/LedgerLoginButton.tsx" +// src/LedgerLoginButton.tsx — a dedicated Ledger hardware-wallet login +// button with an anchor element for the SDK's own device/account UI. +// +// Same overall pattern as "Login via the DeFi extension" and "Login via +// xPortal / WalletConnect" — ProviderFactory.create() + provider.login() — +// with the anchor requirement specific to Ledger: +// +// ProviderFactory.create({ type: ProviderTypeEnum.ledger, anchor }) needs +// a real HTMLElement. The SDK renders a `` web +// component (from the optional @multiversx/sdk-dapp-ui package — it +// installs automatically as an npm optionalDependency of sdk-dapp, see +// this recipe's README) into that anchor: first a "connecting" screen, +// then a paginated list of the accounts derived from the device (10 per +// page), then a confirm screen while you approve the login on the +// physical device itself. +// +// Everything below is verified against the actually-installed +// @multiversx/sdk-dapp source (not just its .d.ts files), the same standard +// this Cookbook's WalletConnect/DeFi-extension recipes were held to: +// +// ProviderFactory.create({ type: ProviderTypeEnum.ledger, anchor }) +// → constructs `new LedgerProviderStrategy({ anchor })`, then calls +// `LedgerIdleStateManager.getInstance().init()` +// (node_modules/@multiversx/sdk-dapp/out/providers/ProviderFactory.cjs) +// → internally calls setAccountProvider() for you, same as every other +// provider type — no separate call needed. +// strategy.init() +// → connects to the physical device via @multiversx/sdk-hw-provider's +// HWProvider (WebUSB/WebHID under the hood). Because no account is +// logged in yet, this happens eagerly, as part of create() itself — +// not deferred to login() — confirmed from +// .../LedgerProviderStrategy/helpers/getLedgerProvider/getLedgerProvider.cjs: +// `shouldInitProvider = options?.shouldInitProvider || !isLoggedIn`. +// Practically: clicking "Connect Ledger" immediately triggers the +// browser's native device-permission prompt, before any app UI shows. +// provider.login() +// → renders the account list + confirm screen into the anchor via +// LedgerConnectStateManager (confirmed from +// .../LedgerProviderStrategy/helpers/authenticateLedgerAccount/authenticateLedgerAccount.cjs). +// You do NOT pass an addressIndex yourself — the SDK's own UI collects +// it and feeds it back internally. Once the user approves on-device, +// login() resolves the same way it does for every other provider: +// useGetIsLoggedIn() / useGetAccount() are correct immediately after. + +import { useRef, useState } from 'react'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { ProviderFactory } from '@multiversx/sdk-dapp/out/providers/ProviderFactory'; +import { ProviderTypeEnum } from '@multiversx/sdk-dapp/out/providers/types/providerFactory.types'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; + +export function LedgerLoginButton(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const [status, setStatus] = useState<'idle' | 'connecting' | 'error'>('idle'); + const [error, setError] = useState(null); + // The device-connection status, account list, and confirm screen all + // render into this div via a web component the SDK defines at runtime. + // It must exist in the DOM before create() runs — rendered unconditionally + // below, so by the time a click handler fires, anchorRef.current is set. + const anchorRef = useRef(null); + + const handleConnect = async (): Promise => { + if (!anchorRef.current) { + // Should not happen — the anchor div always renders — but keeps the + // types honest under strict null checks rather than asserting `!`. + setError('Ledger anchor not mounted yet.'); + setStatus('error'); + return; + } + + setStatus('connecting'); + setError(null); + try { + // This call itself triggers the browser's WebHID/WebUSB device picker + // — it happens here, not inside login() — see the file header. + const provider = await ProviderFactory.create({ + type: ProviderTypeEnum.ledger, + anchor: anchorRef.current, + }); + // nativeAuth is enabled in this recipe's dappConfig, so login() + // generates its own native-auth token internally, same as every + // other provider. + await provider.login(); + setStatus('idle'); + } catch (err) { + // Two distinct failure classes reach this catch, and this recipe + // cannot tell them apart without a physical device to reproduce + // against — flagged honestly rather than guessed: + // 1. The device/transport layer failing (no device plugged in, + // wrong app open, browser without WebHID/WebUSB support — + // Firefox and Safari do not implement either API). + // 2. The user cancelling from the account list or confirm screen + // rendered inside the anchor. + // sdk-dapp's own internal recovery path (rebuildProvider, used before + // signing) shows a toast reading "Unlock your device & open the + // MultiversX App" for case 1 — a reasonable model for what to tell + // the user here too, but this recipe surfaces the raw message rather + // than pattern-matching on it. + const message = err instanceof Error ? err.message : String(err); + setError(message); + setStatus('error'); + } + }; + + const handleDisconnect = async (): Promise => { + const provider = getAccountProvider(); + await provider.logout(); + }; + + if (isLoggedIn) { + return ( + + ); + } + + return ( +
+ + + {/* The SDK renders its device-connect / account-picker / confirm UI + into this element once ProviderFactory.create() runs. Give it real + dimensions — an empty 0x0 div means that UI has nowhere visible to + draw, the same requirement as the WalletConnect recipe's QR anchor. */} +
+ + {status === 'error' && error && ( +

+ Connection failed: {error} +

+ )} +
+ ); +} + +const buttonStyle: React.CSSProperties = { + padding: '0.75rem 1.25rem', + fontSize: '1rem', + cursor: 'pointer', +}; + +const anchorStyle: React.CSSProperties = { + marginTop: '1rem', + minHeight: '280px', + minWidth: '280px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}; +``` + +## The demo page + +```tsx title="src/App.tsx" +// src/App.tsx — demo page for LedgerLoginButton. + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +import { LedgerLoginButton } from './LedgerLoginButton'; + +export function App(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const account = useGetAccount(); + const { network } = useGetNetworkConfig(); + + return ( +
+

Login via Ledger

+

+ Network: {network.chainId} +

+

+ Requires a physical Ledger device with the MultiversX app installed + and open, plus a Chromium-based browser (Chrome, Edge) — WebHID/WebUSB + are not implemented in Firefox or Safari. +

+ + + + {isLoggedIn && ( +

+ Connected as {account.address}. +

+ )} +
+ ); +} +``` + +## Provider bootstrap + +`providers.tsx` calls `initApp()` once and gates rendering until the store is +ready; `lib/multiversx.ts` holds the environment config it passes in. + +```tsx title="src/providers.tsx" +// src/providers.tsx — sdk-dapp v5 init wrapper. +// +// Deliberately does NOT configure UnlockPanelManager — this recipe bypasses +// the generic multi-provider picker and talks to ProviderFactory directly +// for one specific provider. See src/LedgerLoginButton.tsx. + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { dappConfig } from './lib/multiversx'; + +let initStarted = false; + +export function Providers({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return ( +
+ Initializing wallet SDK… +
+ ); + } + + return <>{children}; +} +``` + +```ts title="src/lib/multiversx.ts" +// src/lib/multiversx.ts — environment configuration for sdk-dapp's initApp. +// Same shape as every other Vite recipe in this Cookbook — see +// vite-react-minimal's README for why the fields are set this way. +// walletConnectV2ProjectId isn't exercised by this recipe (LedgerLoginButton +// talks to ProviderFactory with ProviderTypeEnum.ledger only) but is kept +// here for copy-paste consistency with the rest of the corpus. + +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +const WALLET_CONNECT_PROJECT_ID = + import.meta.env.VITE_WALLETCONNECT_PROJECT_ID ?? 'multiversx-cookbook-demo'; + +export const dappConfig = { + storage: { + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + nativeAuth: true, + // `theme` is a ThemesEnum member (runtime value 'mvx:dark-theme'), not a + // bare string — see node_modules/@multiversx/sdk-dapp/out/methods/initApp/initApp.types.d.ts. + theme: ThemesEnum.dark, + providers: { + walletConnect: { + walletConnectV2ProjectId: WALLET_CONNECT_PROJECT_ID, + }, + }, + }, +}; + +export { EnvironmentsEnum }; +``` + +## How it works + +**The anchor is not optional in practice, the same requirement as the +WalletConnect QR code.** `ProviderFactory.create({ type: ProviderTypeEnum.ledger, anchor })` +passes the anchor straight into `new LedgerProviderStrategy({ anchor })` +(`node_modules/@multiversx/sdk-dapp/out/providers/ProviderFactory.cjs`). The SDK +renders a `` web component into it, from the optional +`@multiversx/sdk-dapp-ui` package, which installs automatically as an +`optionalDependencies` entry of `@multiversx/sdk-dapp` itself; nothing extra to +add to `package.json`. + +**Connecting to the device happens inside `create()`, before `login()` is ever +called.** Because no account is logged in yet on a fresh page load, +`getLedgerProvider()` computes +`shouldInitProvider = options?.shouldInitProvider || !isLoggedIn`, which +evaluates to `true`. That is what triggers the browser's native WebHID/WebUSB +device-permission prompt, as soon as you click "Connect Ledger," before any +account list appears. + +**No `addressIndex` is passed from this component.** The SDK's own UI (rendered +into the anchor) lists the accounts derivable from the device and calls back into +the login flow with the chosen index internally. This component only calls +`provider.login()` with no arguments, the same call shape as every other provider +in this Cookbook. + +## Pitfalls + +:::warning[Pitfall 1: this recipe cannot be verified end-to-end without physical hardware] +Everything above the "physical device" line, the `ProviderFactory` dispatch, the +anchor requirement, the `shouldInitProvider` timing, the account-list hand-off, +is confirmed against the actually-installed `@multiversx/sdk-dapp` source. What a +real device-pairing session looks like in detail (exact error messages on cancel, +exact WebHID permission-prompt wording) is not independently reproduced here. +Treat error messages as unstructured strings to display. +::: + +:::danger[Pitfall 2: Firefox and Safari cannot run this recipe at all] +WebHID and WebUSB are Chromium-only browser APIs. This is a platform limitation, +not something the SDK or this recipe can work around. +::: + +:::warning[Pitfall 3: the Ledger deep-import fix is load-bearing here, not dormant] +The other wallet recipes in this Cookbook carry the same `vite.config.ts` +`optimizeDeps.exclude` / `rollupOptions.external` fix for three `@ledgerhq/devices` +deep-import paths, but only because `ProviderFactory` transitively references the +Ledger strategy. This recipe actually calls into that code path, so dropping the +fix here breaks both `npm run dev` and `npm run build`, not just a path you never +exercise. +::: + +:::note[Pitfall 4: the MultiversX app must be open on the device] +If it is not, or the device goes idle mid-session, sdk-dapp's own recovery path +(used before signing) shows a toast reading "Unlock your device & open the +MultiversX App", confirmed from the compiled source's `rebuildProvider` catch block. +::: + +## See also + +- [Login via the DeFi extension](/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login) + is the same `ProviderFactory` pattern for the browser-extension provider. +- [Login via xPortal / WalletConnect](/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login) + is the same pattern, QR-code anchor instead of a device UI. +- [Add a wallet login button](/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button) + is the multi-provider picker alternative to this dedicated button. +- [Read the connected account](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account) + is what to do once `provider.login()` resolves. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/native-auth.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/native-auth.mdx new file mode 100644 index 000000000..4180febb6 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/native-auth.mdx @@ -0,0 +1,470 @@ +--- +title: Native auth, token issuance, expiry, auto-logout +description: How nativeAuth issues a bearer token on login, why loginExpiresAt is not the token's real expiry, and how LogoutManager schedules the warning toast and logout. +difficulty: intermediate +est_minutes: 8 +last_validated: "2026-07-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - wallet + - native-auth + - react + - typescript + - devnet +--- + +What `nativeAuth` actually gives you once configured: a bearer token issued +automatically on login, an automatic warning toast before it expires, and an +automatic logout when it does, all scheduled by `LogoutManager`, none of it wired +up by hand anywhere in this recipe. + +Use this recipe once you already have login working (see +[Add a wallet login button](/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button)) +and need to understand what to do with the resulting token, or need to control +the auto-logout behavior. Do not use it if you have not enabled `nativeAuth` at +all; the config itself is a one-line `initApp()` option, covered in "Configuring +native auth" below. + +## Prerequisites + +- A working sdk-dapp v5 setup (Next.js or Vite, see the start-here recipes). +- Any wallet provider to log in with. This recipe uses the generic + `UnlockPanelManager` picker. + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/native-auth +npm install +npm run dev +# open https://localhost:5173 +``` + +## Configuring native auth + +```ts title="src/lib/multiversx.ts" +// src/lib/multiversx.ts — environment configuration for sdk-dapp's initApp, +// with an explicit (not just `nativeAuth: true`) native auth config so every +// field is visible and documented in one place. +// +// NativeAuthConfigType (node_modules/@multiversx/sdk-dapp/out/services/nativeAuth/nativeAuth.types.d.ts): +// every field is optional; omitted fields fall back to +// getDefaultNativeAuthConfig() (.../services/nativeAuth/methods/getDefaultNativeAuthConfig.cjs): +// origin -> window.location.origin +// apiAddress -> the configured network's API address +// expirySeconds -> 86400 (24h) +// tokenExpirationToastWarningSeconds -> 300 (5 min) +// +// This recipe deliberately overrides both time values to a couple of +// minutes so the auto-logout warning toast and the actual auto-logout are +// both observable within one `npm run dev` session, instead of requiring a +// 24-hour wait. A real dApp should use the defaults (or its own security +// policy's value) — do not ship NATIVE_AUTH_EXPIRY_SECONDS this short. +export const NATIVE_AUTH_EXPIRY_SECONDS = 120; +export const NATIVE_AUTH_WARNING_SECONDS = 30; + +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +// This recipe uses UnlockPanelManager's generic picker (see providers.tsx), +// which offers every registered provider including WalletConnect — so the +// project ID is still needed here even though the recipe's own point is +// native auth, not any one specific provider. Same demo ID used across the +// rest of the corpus; register your own at https://cloud.walletconnect.com. +const WALLET_CONNECT_PROJECT_ID = + import.meta.env.VITE_WALLETCONNECT_PROJECT_ID ?? 'multiversx-cookbook-demo'; + +export const dappConfig = { + storage: { + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + nativeAuth: { + expirySeconds: NATIVE_AUTH_EXPIRY_SECONDS, + tokenExpirationToastWarningSeconds: NATIVE_AUTH_WARNING_SECONDS, + }, + // `theme` is a ThemesEnum member (runtime value 'mvx:dark-theme'), not a + // bare string — see node_modules/@multiversx/sdk-dapp/out/methods/initApp/initApp.types.d.ts. + theme: ThemesEnum.dark, + providers: { + walletConnect: { + walletConnectV2ProjectId: WALLET_CONNECT_PROJECT_ID, + }, + }, + }, +}; + +export { EnvironmentsEnum }; +``` + +## Reading token and expiry state + +```tsx title="src/NativeAuthPanel.tsx" +// src/NativeAuthPanel.tsx — the actual subject of this recipe: reading +// native-auth token state, and the automatic expiry-warning/auto-logout +// behavior that comes with `nativeAuth` for free. +// +// Three things this component demonstrates, all verified against the +// actually-installed @multiversx/sdk-dapp source (not just its .d.ts files): +// +// 1. TOKEN ISSUANCE. Once nativeAuth is configured (src/lib/multiversx.ts) +// and the user logs in, `useGetLoginInfo().tokenLogin.nativeAuthToken` +// is populated automatically — no extra call needed. `TokenLoginType` +// (node_modules/@multiversx/sdk-dapp/out/types/login.types.d.ts) shows +// the field name; it's a bearer token, safe to attach to `Authorization: +// Bearer ` headers on your own backend's API calls. +// +// 2. EXPIRY IS AUTOMATIC, NOT SOMETHING YOU SCHEDULE YOURSELF. +// `DappProvider.login()` calls `LogoutManager.getInstance().init()` as +// its last step (.../providers/DappProvider/DappProvider.cjs). That +// schedules, purely from the values you passed as `nativeAuth` config: +// - a warning toast (`toastId: 'native-auth-expired'`) at +// `tokenExpirationToastWarningSeconds` before real expiry +// - a "Logging out" toast (`toastId: 'native-auth-logout'`) 3 seconds +// before the actual logout +// - the actual logout — `getAccountProvider().logout()` — at +// `expirySeconds` after login +// (.../managers/LogoutManager/LogoutManager.cjs). None of this is wired +// up in this component; it happens because nativeAuth was configured. +// +// 3. `LogoutManager.getInstance().stop()` is the ONLY public knob. It +// clears all three scheduled timers. There's no public "extend the +// session" call — re-arming means calling `.init()` again, which +// re-reads the CURRENT token's real expiry from the store and +// reschedules from there (it does not mint a new token or change +// `expirySeconds` itself). + +import { useGetLoginInfo } from '@multiversx/sdk-dapp/out/react/loginInfo/useGetLoginInfo'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { LogoutManager } from '@multiversx/sdk-dapp/out/managers/LogoutManager/LogoutManager'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; +import { NATIVE_AUTH_EXPIRY_SECONDS, NATIVE_AUTH_WARNING_SECONDS } from './lib/multiversx'; + +export function NativeAuthPanel(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const { tokenLogin, providerType, loginExpiresAt } = useGetLoginInfo(); + + if (!isLoggedIn) { + return

Log in to see the issued native-auth token and its expiry state.

; + } + + const token = tokenLogin?.nativeAuthToken; + + const handleLogoutNow = async (): Promise => { + await getAccountProvider().logout(); + }; + + const handleStopAutoLogout = (): void => { + LogoutManager.getInstance().stop(); + }; + + const handleRearmAutoLogout = (): void => { + void LogoutManager.getInstance().init(); + }; + + return ( +
+

Native auth state

+
+
providerType
+
+ {providerType ?? 'none'} +
+ +
nativeAuthToken
+
{token ? {truncate(token)} : not issued}
+ +
configured expirySeconds
+
+ {NATIVE_AUTH_EXPIRY_SECONDS} — real per-token TTL, + baked into the token before the wallet signs it + (`services/nativeAuth/nativeAuth.cjs`'s `initialize()` embeds it as + the token string's 3rd dot-separated segment). +
+ +
configured tokenExpirationToastWarningSeconds
+
+ {NATIVE_AUTH_WARNING_SECONDS} — how long before real + expiry the automatic warning toast fires. +
+ +
loginExpiresAt (from useGetLoginInfo)
+
+ {loginExpiresAt ?? 'null'} + {loginExpiresAt !== null && ( + <> + {' '} + ({new Date(loginExpiresAt).toISOString()}) + + )} +
+ Not the same thing as the token's real expiry above.{' '} + This is a separate, generic rolling session ceiling (defaults to + "24 hours from now", already in milliseconds — no ×1000 needed) + maintained by an internal store middleware, unrelated to the + `expirySeconds` value configured for nativeAuth. Confirmed from + `node_modules/@multiversx/sdk-dapp/out/store/middleware/logoutMiddleware.cjs`. + Don't use this field to display "your session expires at X" — it + will disagree with the LogoutManager-driven toasts above whenever + `expirySeconds` isn't ~24h. +
+
+ +
+ + + +
+
+ ); +} + +function truncate(value: string): string { + return value.length > 24 ? `${value.slice(0, 12)}…${value.slice(-8)}` : value; +} + +const buttonStyle: React.CSSProperties = { + padding: '0.5rem 1rem', + fontSize: '0.9rem', + cursor: 'pointer', +}; + +const dlStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'minmax(140px, max-content) 1fr', + columnGap: '1rem', + rowGap: '0.75rem', + fontSize: '0.9rem', +}; +``` + +## The demo page + +```tsx title="src/App.tsx" +// src/App.tsx — demo page. Login UI here is the generic picker (same as +// "Adding a wallet login button") — the point of this recipe is what +// NativeAuthPanel shows once you're in, not how you got there. + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; +import { NativeAuthPanel } from './NativeAuthPanel'; + +export function App(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const account = useGetAccount(); + const { network } = useGetNetworkConfig(); + + const handleConnect = (): void => { + void UnlockPanelManager.getInstance().openUnlockPanel(); + }; + + const handleDisconnect = async (): Promise => { + await getAccountProvider().logout(); + }; + + return ( +
+

Native auth: token issuance, expiry, auto-logout

+

+ Network: {network.chainId} +

+ + {isLoggedIn ? ( + <> +

+ Connected as {account.address}. +

+ + + ) : ( + + )} + + +
+ ); +} +``` + +## Provider bootstrap + +`providers.tsx` calls `initApp()` (with the native-auth config above) once and +gates rendering until the store is ready. + +```tsx title="src/providers.tsx" +// src/providers.tsx — sdk-dapp v5 init wrapper + UnlockPanelManager setup. +// +// Same shape as the "Adding a wallet login button" recipe — this recipe is +// about what happens AFTER login (the native-auth token and its automatic +// expiry handling), not about which login UI you use, so it reuses the +// generic picker rather than a dedicated single-provider button. +// +// initApp(dappConfig) is what actually turns on native auth — see +// src/lib/multiversx.ts for the `nativeAuth` config object. Nothing in this +// file configures LogoutManager directly: DappProvider.login() calls +// `LogoutManager.getInstance().init()` for you as its last step +// (node_modules/@multiversx/sdk-dapp/out/providers/DappProvider/DappProvider.cjs) +// — confirmed from source, not assumed. See src/NativeAuthPanel.tsx for the +// consumer-facing half of this recipe. + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { dappConfig } from './lib/multiversx'; + +let initStarted = false; + +export function Providers({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + + UnlockPanelManager.init({ + loginHandler: async (): Promise => { + // eslint-disable-next-line no-console + console.log('Logged in — native auth token issued.'); + }, + onClose: async (): Promise => { + // eslint-disable-next-line no-console + console.log('Unlock panel closed without logging in.'); + }, + }); + + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return ( +
+ Initializing wallet SDK… +
+ ); + } + + return <>{children}; +} +``` + +## How it works + +**`nativeAuth` accepts `true` (all defaults) or a full config object.** This +recipe passes an explicit object so both timing fields are visible: +`expirySeconds` (default 86400 / 24h) and `tokenExpirationToastWarningSeconds` +(default 300 / 5 min), both confirmed from +`node_modules/@multiversx/sdk-dapp/out/services/nativeAuth/methods/getDefaultNativeAuthConfig.cjs`. +This recipe deliberately overrides both to a couple of minutes so the whole +lifecycle is observable in one dev session. Ship the defaults, or your own +policy's values, not these. + +**`expirySeconds` is a real, per-token TTL, signed by the wallet, not a +client-only timer.** The login-token string sdk-dapp builds before the wallet +signs it is `${origin}.${blockHash}.${expirySeconds}.${extraInfo}` +(`node_modules/@multiversx/sdk-dapp/out/services/nativeAuth/nativeAuth.cjs`'s +`initialize()`). The API validates the signed token against that same embedded +value, so a short `expirySeconds` really does produce a short-lived token. + +**Auto-logout is wired up inside `provider.login()` itself, you never call it.** +`DappProvider.login()`'s last step is `LogoutManager.getInstance().init()`. +`LogoutManager.init()` reads the real token's remaining TTL and schedules a +warning toast, a "Logging out" toast 3 seconds before expiry, and the actual +`getAccountProvider().logout()` call. The only public methods this recipe calls +directly are `.stop()` (cancel all three timers) and `.init()` again (re-arm from +the token's current remaining TTL). + +## Pitfalls + +:::warning[Pitfall 1: the 120-second expirySeconds in this recipe is for demonstration only] +It exists purely so the warning-toast then logout-toast then real-logout sequence +is observable in one sitting. Ship a real value, the 86400-second default, or +whatever your own security policy requires. +::: + +:::danger[Pitfall 2: loginExpiresAt is NOT your native-auth token's expiry] +`useGetLoginInfo().loginExpiresAt` is a separate, generic rolling ~24-hour +session ceiling (already in milliseconds) maintained by an unrelated store +middleware, not the `expirySeconds` you configure for nativeAuth. Displaying it +as "your session expires at X" would be wrong whenever `expirySeconds` is not +~24h. There is currently no public field that returns the real per-token TTL +directly; this recipe shows the *configured* value instead of decoding the token +client-side. +::: + +:::warning[Pitfall 3: there is no public "extend my session" call] +`LogoutManager` only exposes `.stop()` and `.init()`. Calling `.init()` again +reschedules from the *current* token's remaining TTL. It does not mint a new token +or push the expiry further out. Extending a session for real means logging in again. +::: + +:::note[Pitfall 4: .stop() only cancels the client-side timers] +The token still expires server-side at its real TTL. Any authenticated API call +made after that point fails regardless of whether `LogoutManager` is running. +`.stop()` just means the SDK will not proactively force a client-side logout for you. +::: + +## See also + +- [Add a wallet login button](/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button) + is the generic picker this recipe's login UI reuses. +- [Read the connected account](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account) + is the account-level counterpart to this recipe's login-info focus. +- [Login via the DeFi extension](/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login) + is a single-provider login flow that also issues a native-auth token the same way. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/typescript-strict-mode-checklist.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/typescript-strict-mode-checklist.mdx new file mode 100644 index 000000000..c6ecb302d --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/typescript-strict-mode-checklist.mdx @@ -0,0 +1,277 @@ +--- +title: TypeScript strict-mode checklist for sdk-dapp consumers +description: Four real bugs found while verifying cookbook recipes against installed sdk-dapp, plus the tsconfig flags and ESLint rules that catch them. +difficulty: intermediate +est_minutes: 7 +last_validated: "2026-07-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - typescript + - react +--- + +Every item below is a real bug this Cookbook's own build found while verifying its +recipes against actually-installed `@multiversx/sdk-dapp`, not a hypothetical list. +Each is the concrete, runnable version of a class of failure: docs examples that +do not survive `tsc --strict`. + +## Prerequisites + +- A TypeScript project consuming `@multiversx/sdk-dapp` v5. +- `strict: true` in `tsconfig.json`. + +## The tsconfig + +| Flag | Why it matters here | +| --- | --- | +| `strict: true` | Umbrella flag; without it, most of the bugs below compile silently. | +| `strictNullChecks` | Catches `account.balance` used before confirming `account` is not `null`/`undefined`, common when reading store state before `initApp()` resolves. | +| `noUncheckedIndexedAccess` | Array/object index access returns `T \| undefined` instead of `T`. Catches assuming `sentTransactions[0]` exists after a `.send()` call, see Item 4. | +| `exactOptionalPropertyTypes` | Distinguishes "property omitted" from "property explicitly `undefined`" in config objects like `dAppConfig`. | +| `noImplicitAny` | Without it, an unresolved sdk-dapp import path silently degrades to `any`, and every bug on this page stops being a compile error. | +| `noUnusedLocals` / `noUnusedParameters` | Catches leftover v4 imports after a migration pass. | + +## The checklist + +### 1. theme is a ThemesEnum member, not a string literal + +```ts title="src/theme.ts" +// src/theme.ts — Checklist item 1: `theme` is an enum member, not a string. +// +// The naive port of sdk-dapp's own prose docs (which show `theme: 'dark'` +// without importing anything) fails strict-mode compile. Confirmed +// against the real, installed +// node_modules/@multiversx/sdk-dapp/out/methods/initApp/initApp.types.d.ts +// while verifying this Cookbook's start-here recipes (nextjs-minimal, +// sign-and-send, vite-react-minimal all had this bug). + +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +// WRONG — fails tsc --strict: +// +// const dappConfig = { +// dAppConfig: { +// environment: EnvironmentsEnum.devnet, +// theme: 'dark' as const, +// }, +// }; +// +// error TS2322: Type '"dark"' is not assignable to type +// '`${ThemesEnum}`'. + +// RIGHT: +export const dappConfig = { + dAppConfig: { + environment: EnvironmentsEnum.devnet, + theme: ThemesEnum.dark, + }, +}; +``` + +Found in three of this Cookbook's own recipes during verification (`nextjs-minimal`, +`sign-and-send`, `vite-react-minimal`) before being fixed. + +### 2. UnlockPanelManager callbacks must return Promise<void> + +```ts title="src/unlockPanel.ts" +// src/unlockPanel.ts — Checklist item 2: UnlockPanelManager callbacks must +// return Promise, not void. +// +// A real bug this Cookbook hit. The docs' own example for +// UnlockPanelManager.init uses plain synchronous callbacks; that shape +// does not satisfy the real installed type. Confirmed from +// node_modules/@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager.types.d.ts. + +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; + +// WRONG — fails tsc --strict: +// +// UnlockPanelManager.init({ +// loginHandler: () => { +// console.log('logged in'); +// }, +// }); +// +// error TS2322: Type '() => void' is not assignable to type +// 'OnProviderLoginType'. +// Type 'void' is not assignable to type 'Promise'. + +// RIGHT — async, even if the body has nothing to await: +export function initUnlockPanel(): void { + UnlockPanelManager.init({ + loginHandler: async (): Promise => { + // eslint-disable-next-line no-console + console.log('logged in'); + }, + onClose: async (): Promise => { + // eslint-disable-next-line no-console + console.log('panel closed'); + }, + }); +} +``` + +This is the reason `OnCloseUnlockPanelType` shows up by name in this Cookbook's +migration recipes. + +### 3. @typescript-eslint/no-floating-promises must actually be wired, and actually run + +```ts title="src/floatingPromise.ts" +// src/floatingPromise.ts — Checklist item 3: the ESLint rule that catches +// what tsc alone does not, PLUS the meta-bug of the rule silently not +// running at all. +// +// UnlockPanelManager.getInstance().openUnlockPanel() returns Promise +// (confirmed from the real .d.ts). Calling it from a synchronous onClick +// handler without `void` or `await` is a floating promise: if it rejects +// (e.g. the user's environment has no injected wallet), the rejection is +// silently swallowed — no error boundary, no console warning by default. +// tsc --strict does NOT flag this on its own; a floating Promise is +// still assignable to a `void`-returning callback signature. This is +// exactly why @typescript-eslint/no-floating-promises exists as a +// SEPARATE gate from tsc. +// +// Validation performed while authoring this recipe: temporarily removed +// the `void` operator below and re-ran `npm run lint` against this +// exact .eslintrc.cjs. It failed with: +// error Promises must be awaited, end with a call to .catch, end with +// a call to .then with a rejection handler or be explicitly marked as +// ignored with the `void` operator @typescript-eslint/no-floating-promises +// — confirming this recipe's own lint config does NOT have the +// silent-no-op bug it warns about (see .eslintrc.cjs's header comment). +// Restored the `void` operator afterward; this file lints clean. + +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; + +export function handleConnectClick(): void { + // WRONG — a real, reproducible eslint error under this exact config: + // + // UnlockPanelManager.getInstance().openUnlockPanel(); + // + // RIGHT — explicitly discard the promise (there's nothing to await from + // a sync onClick handler here): + void UnlockPanelManager.getInstance().openUnlockPanel(); +} +``` + +Two distinct failure modes, both real: the rule silently does nothing if +`parserOptions.project` is not set (this Cookbook's own `nextjs-minimal` recipe +shipped exactly this bug once), and, once wired, it catches real bugs `tsc` alone +does not, since a floating `Promise` is still assignable everywhere a +`void`-returning callback is expected. + +### 4. Don't assume a .send() result is the class instance you signed + +```ts title="src/transactionHash.ts" +// src/transactionHash.ts — Checklist item 4: don't assume a helper class +// accepts the object shape a hand-signed value happens to look like, AND +// don't assume a union return type narrows just because your own input +// didn't. +// +// TransactionManager.getInstance().send() returns SignedTransactionType[] +// (a plain object shape: `{ ...Transaction fields, hash, status }`), +// NOT sdk-core Transaction class instances. Passing that return value +// into TransactionComputer.computeTransactionHash() — which expects a +// real Transaction instance with its class methods — is a real, +// reproducible tsc --strict failure, confirmed while authoring this +// Cookbook's sign-and-send recipe. The fix isn't a cast: SignedTransactionType already +// carries `hash: string` directly, computed server-side, so there's +// nothing to recompute client-side at all. +// +// A second, related trap this file's own first draft hit while being +// verified for this Cookbook: send()'s declared return type is +// `SignedTransactionType[] | SignedTransactionType[][]` — a union, +// UNCONDITIONALLY, even when the input you passed was a plain flat +// `Transaction[]` (see the +// send-transactions-to-transaction-manager recipe for why the return +// type is a union at all: send() also accepts a nested batch input). +// There is no overload that narrows the return based on which arm of the +// input union you passed, so `const [first] = sent; first.hash` fails — +// `first`'s type includes `SignedTransactionType[]` (the nested-batch +// member), which has no `.hash`. Narrow explicitly at runtime instead of +// casting blindly. + +import { TransactionManager } from '@multiversx/sdk-dapp/out/managers/TransactionManager/TransactionManager'; +import type { SignedTransactionType } from '@multiversx/sdk-dapp/out/types/transactions.types'; +import { TransactionComputer, type Transaction } from '@multiversx/sdk-core'; + +/** + * Narrows send()'s always-a-union return type down to the flat shape, + * using the same Array.isArray check the SDK's own internal + * isBatchTransaction() helper uses — not a blind `as` cast. + */ +function assertFlatResult( + sent: SignedTransactionType[] | SignedTransactionType[][], +): SignedTransactionType[] { + if (sent.length > 0 && Array.isArray(sent[0])) { + throw new Error( + 'Expected a flat transaction result; got a nested batch result instead.', + ); + } + return sent as SignedTransactionType[]; +} + +export async function sendAndGetHash(signed: Transaction[]): Promise { + const txManager = TransactionManager.getInstance(); + + // WRONG — fails tsc --strict, even though `signed` above is flat: + // + // const sent = await txManager.send(signed); + // const [first] = sent; + // return first.hash; + // + // error TS2339: Property 'hash' does not exist on type + // 'SignedTransactionType | SignedTransactionType[]'. + + // RIGHT — narrow explicitly, then read the hash the network already + // computed; no TransactionComputer call needed: + const flatSent = assertFlatResult(await txManager.send(signed)); + const [first] = flatSent; + if (!first) { + throw new Error('No transaction was sent.'); + } + return first.hash; +} + +// TransactionComputer.computeTransactionHash IS the right tool — just for +// a different input: an sdk-core Transaction instance you haven't sent +// yet (e.g. to display a hash before broadcasting). Kept here to show +// the type it actually wants, not to suggest it's unused: +export function hashBeforeSending(tx: Transaction): string { + const computer = new TransactionComputer(); + return computer.computeTransactionHash(tx); +} +``` + +Two stacked traps: `TransactionManager.getInstance().send()` returns +`SignedTransactionType[]` (plain objects with `hash` already on them), not +`Transaction` class instances, so `TransactionComputer.computeTransactionHash()` +rejects it. And `send()`'s declared return type is a union +(`SignedTransactionType[] | SignedTransactionType[][]`) **even when your input was +flat** (see +[Migration: grouped/batch sends](/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager)), +so destructuring the first element needs an explicit `Array.isArray` narrow, not a +blind cast. + +## How to use this checklist on an existing codebase + +1. Turn on every flag in the table above, one at a time if the codebase is large. +2. Fix in the order `tsc --noEmit --strict` reports them; resist the urge to + blanket-suppress with `// @ts-ignore`. +3. Add the two `@typescript-eslint` promise rules last, once the codebase compiles. +4. Confirm the ESLint rules are actually running (Item 3) before trusting a clean + `eslint` pass. + +## See also + +- [Minimal sdk-dapp v5 in Next.js](/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal) + and + [Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal) + are where Items 1 to 3 were first found and fixed. +- [Sign and send a transaction](/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send) + is where Item 4 was first found and fixed. +- [Migrate v4 to v5: hook-by-hook diffs](/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration) + references Item 2 by name (`OnCloseUnlockPanelType`). diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button.mdx new file mode 100644 index 000000000..9c0fc3a1e --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button.mdx @@ -0,0 +1,396 @@ +--- +title: Add a wallet login button +description: Configure UnlockPanelManager once at startup, then add a reusable connect and disconnect button that works with every registered wallet provider. +difficulty: beginner +est_minutes: 6 +last_validated: "2026-07-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - wallet + - vite + - react + - typescript + - devnet +--- + +Every dApp needs a "Connect wallet" button. In sdk-dapp v5 that button does not +render a picker itself. It calls `UnlockPanelManager.getInstance().openUnlockPanel()`, +and a pre-built web component, configured once at app startup, does the rest. +This recipe shows the full pattern: configure the panel, build one reusable +button component, and understand what each `UnlockPanelManager.init()` option does. + +This recipe assumes you already have a working sdk-dapp v5 + Vite setup. If not, +start with +[Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal) +first. If you want a login button for one *specific* provider (skip the picker +entirely, e.g. a "Connect DeFi Wallet" button with no other options shown), see +[Login via the DeFi extension](/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login) +or +[Login via xPortal / WalletConnect](/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login) +instead, which use the lower-level `ProviderFactory` directly. + +## Prerequisites + +- A working sdk-dapp v5 setup (Next.js or Vite, see the start-here recipes). +- A devnet wallet to test with (DeFi extension or xPortal). + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/wallet-login-button +npm install +npm run dev +# open https://localhost:5173 +``` + +## The reusable button + +Zero props. Every instance reads the same sdk-dapp store, so dropping +`` in five different places in your tree keeps all five in sync +automatically: + +```tsx title="src/LoginButton.tsx" +// src/LoginButton.tsx — the reusable connect/disconnect button. +// +// This is the piece you copy into your own dApp: a single component that +// renders "Connect wallet" when logged out and "Disconnect" when logged in. +// It has zero props because it reads everything it needs from the sdk-dapp +// store (via hooks) and the UnlockPanelManager / provider singletons — drop +// it anywhere in the tree, as many times as you want, and every instance +// stays in sync automatically. + +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; + +export function LoginButton(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + + const handleConnect = (): void => { + // The panel singleton was configured once in (src/providers.tsx). + // openUnlockPanel() returns Promise; this is a sync onClick, so we + // discard it explicitly with `void` (eslint no-floating-promises). + void UnlockPanelManager.getInstance().openUnlockPanel(); + }; + + const handleDisconnect = async (): Promise => { + const provider = getAccountProvider(); + await provider.logout(); + }; + + if (isLoggedIn) { + return ( + + ); + } + + return ( + + ); +} + +const buttonStyle: React.CSSProperties = { + padding: '0.75rem 1.25rem', + fontSize: '1rem', + cursor: 'pointer', +}; +``` + +## The demo page + +```tsx title="src/App.tsx" +// src/App.tsx — demo page for the LoginButton component. +// +// Deliberately thin: this recipe's job is the button + the UnlockPanelManager +// setup behind it, not the full account display (see the "Reading the +// connected account" recipe for the useGetAccount() deep dive). + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +import { LoginButton } from './LoginButton'; + +export function App(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const account = useGetAccount(); + const { network } = useGetNetworkConfig(); + + return ( +
+

Wallet login button

+

+ Network: {network.chainId} +

+ + + + {isLoggedIn && ( +

+ Connected as {account.address}. See{' '} + + Reading the connected account + {' '} + for the full field breakdown. +

+ )} + +

+ Drop <LoginButton /> anywhere in your component + tree — it needs no props and stays in sync with every other instance + automatically, because all of them read the same sdk-dapp store. +

+
+ ); +} +``` + +## The panel setup + +`UnlockPanelManager.init()` runs exactly once, at app startup, in the same +wrapper that calls `initApp()`. `loginHandler` and `onClose` both return +`Promise`, not `void`. The SDK's own type declarations require it (see +Pitfall 1), even though some example code you will find floating around uses a +sync arrow function. + +```tsx title="src/providers.tsx" +// src/providers.tsx — sdk-dapp v5 init wrapper + UnlockPanelManager setup. +// +// This file is the actual subject of this recipe. Two things happen here, +// in order: +// +// 1. initApp(dappConfig) — boots the SDK (store, network config, native +// auth, provider factory). Must resolve before anything else touches +// the store. +// 2. UnlockPanelManager.init({...}) — configures the (singleton) unlock +// panel: which callback runs after login, which runs if the user +// closes the panel without logging in, and which providers to show. +// This call happens ONCE, here, at app startup — not per-button. Every +// "Connect wallet" button anywhere in the app just calls +// UnlockPanelManager.getInstance().openUnlockPanel(). + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager'; +import { dappConfig } from './lib/multiversx'; + +// Module-scope flag prevents double-init under React Strict Mode (which +// deliberately mounts effects twice in dev). Matches every other recipe in +// this Cookbook — see Pitfall 3. +let initStarted = false; + +export function Providers({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + + UnlockPanelManager.init({ + // loginHandler accepts TWO different shapes (both documented in + // node_modules/@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager.types.d.ts): + // + // 1) A zero-arg callback — the SDK has already run the full + // provider-create + login sequence for you; you just react to + // "login succeeded" (e.g. navigate away). This is what almost + // every dApp wants, and what this recipe uses. + // + // 2) A `({ type, anchor }) => Promise` function — you take + // over the ENTIRE login sequence yourself, including calling + // ProviderFactory.create({ type, anchor }) and provider.login() + // by hand. Use this only if you need to intercept or customize + // that sequence (custom loading UI per provider, analytics + // hooks, etc.) — see the "Login via the DeFi extension" and + // "Login via xPortal / WalletConnect" recipes for the + // lower-level ProviderFactory pattern this shape wraps. + // + // IMPORTANT: both shapes must return Promise, not void — the + // docs' own sync example fails strict-mode compile. See Pitfall 1. + loginHandler: async (): Promise => { + // Replace with your post-login navigation (e.g. router.push). + // eslint-disable-next-line no-console + console.log('Logged in.'); + }, + // Called if the user dismisses the panel without completing login. + onClose: async (): Promise => { + // eslint-disable-next-line no-console + console.log('Unlock panel closed without logging in.'); + }, + // Optional: restrict + reorder which providers the panel shows. + // Omit this key entirely to show every registered provider (the + // default, and what this recipe does). Example from the SDK's own + // JSDoc: `allowedProviders: [ProviderTypeEnum.walletConnect, 'inMemoryProvider']`. + // allowedProviders: [ProviderTypeEnum.extension, ProviderTypeEnum.walletConnect], + }); + + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return ( +
+ Initializing wallet SDK… +
+ ); + } + + return <>{children}; +} +``` + +`providers.tsx` imports the shared environment config that every recipe in this +Cookbook passes to `initApp()`: + +```ts title="src/lib/multiversx.ts" +// src/lib/multiversx.ts — environment configuration for sdk-dapp's initApp. +// +// The full dAppConfig shape accepts more keys than this; we only set the +// keys this recipe actually uses. + +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +// Read from import.meta.env (Vite's runtime environment object) — NOT +// process.env, which does not exist in the browser bundle. Fallback is the +// demo project ID; register your own at https://cloud.walletconnect.com. +const WALLET_CONNECT_PROJECT_ID = + import.meta.env.VITE_WALLETCONNECT_PROJECT_ID ?? 'multiversx-cookbook-demo'; + +export const dappConfig = { + storage: { + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + nativeAuth: true, + // `theme` is a ThemesEnum member (runtime value 'mvx:dark-theme'), not a + // bare string — see node_modules/@multiversx/sdk-dapp/out/methods/initApp/initApp.types.d.ts. + theme: ThemesEnum.dark, + providers: { + walletConnect: { + walletConnectV2ProjectId: WALLET_CONNECT_PROJECT_ID, + }, + }, + }, +}; + +export { EnvironmentsEnum }; +``` + +## How it works + +**Configure once, use everywhere.** `UnlockPanelManager` is a singleton. +`.init({...})` sets its `loginHandler`, `onClose`, and (optionally) +`allowedProviders`. Call it once, at startup. Every button anywhere in the app +calls `.getInstance().openUnlockPanel()`, which just raises the +already-configured panel. + +**`loginHandler` has two shapes.** Per +`node_modules/@multiversx/sdk-dapp/out/managers/UnlockPanelManager/UnlockPanelManager.types.d.ts`: + +```ts +// 1) Zero-arg callback: the SDK already ran the full login sequence. +loginHandler: () => { navigate('/dashboard'); }; + +// 2) Full control: you run the login sequence yourself. +loginHandler: async ({ type, anchor }) => { + const provider = await ProviderFactory.create({ type, anchor }); + await provider?.login(); + navigate('/dashboard'); +}; +``` + +This recipe uses shape 1, the common case. Shape 2 is the same `ProviderFactory` +pattern the dedicated single-provider recipes +([DeFi extension](/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login), +[xPortal/WalletConnect](/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login)) +use directly, without going through the panel at all. + +**`allowedProviders` restricts and reorders the picker.** Omit it to show every +registered provider (this recipe's default). Pass +`[ProviderTypeEnum.extension, ProviderTypeEnum.walletConnect]` to show only those +two, in that order. Its type also accepts custom provider-name strings, for apps +that registered a custom `IProvider`. + +## Pitfalls + +:::warning[Pitfall 1: loginHandler and onClose must return Promise<void>] +A sync callback fails under TypeScript strict mode: + +```text +error TS2322: Type '() => void' is not assignable to type 'OnCloseUnlockPanelType'. + Type 'void' is not assignable to type 'Promise'. +``` + +Use `async () => { ... }` for both callbacks. `src/providers.tsx` does this +correctly. +::: + +:::warning[Pitfall 2: UnlockPanelManager.init() runs once, not per button] +Calling `.init()` a second time from another component overwrites the first +configuration (last call wins). It does not merge or stack. Configure it exactly +once, in the same wrapper that calls `initApp()`. Every button elsewhere only +ever calls `.getInstance().openUnlockPanel()`. +::: + +:::warning[Pitfall 3: don't render login buttons before init resolves] +`UnlockPanelManager.getInstance()` before `.init()` has run returns a panel with +no configured `loginHandler`. This recipe's `ready` gate in `src/providers.tsx` +prevents children, and therefore any ``, from rendering until +`initApp()` and `UnlockPanelManager.init()` have both completed. Don't remove +that gate. +::: + +:::warning[Pitfall 4: HTTPS required for most wallets] +The DeFi extension and Ledger refuse `http://localhost`. This recipe's dev server +uses `@vitejs/plugin-basic-ssl`. See +[Local HTTPS for dApp dev](/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup) +for a fully trusted mkcert alternative. +::: + +## See also + +- [Login via the DeFi extension](/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login) + is a dedicated single-provider button using `ProviderFactory` directly, no picker. +- [Login via xPortal / WalletConnect](/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login) + is the same lower-level pattern, for the mobile-QR provider. +- [Read the connected account](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account) + is what to do with `useGetAccount()` once a user is connected. +- [Minimal sdk-dapp v5 in Vite](/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal) + is the base setup this recipe builds on. diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login.mdx new file mode 100644 index 000000000..0d4a9e611 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login.mdx @@ -0,0 +1,392 @@ +--- +title: Log in with xPortal via WalletConnect +description: A dedicated xPortal login button using ProviderFactory and WalletConnect v2 directly, including the QR-code anchor element and project ID setup. +difficulty: intermediate +est_minutes: 8 +last_validated: "2026-07-07" +sdk_versions: + sdk-dapp: "^5.6.0" +tags: + - sdk-dapp + - wallet + - walletconnect + - react + - typescript + - devnet +--- + +The sibling to +[Login via the DeFi extension](/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login): +a dedicated, single-provider button, this time for xPortal via WalletConnect v2. +Same `ProviderFactory` pattern, with two things specific to WalletConnect: a +`walletConnectV2ProjectId` configured up front, and a real DOM anchor for the QR +code to render into. + +Use this when you want a dedicated "Connect xPortal" button next to (or instead +of) +[Add a wallet login button](/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button)'s +generic picker. + +## Prerequisites + +- A working sdk-dapp v5 setup (Next.js or Vite, see the start-here recipes). +- A WalletConnect v2 project ID from + [cloud.walletconnect.com](https://cloud.walletconnect.com). The recipe ships + with a shared, rate-limited demo ID so it runs out of the box, but replace it + before shipping. +- The xPortal mobile app, to actually scan the QR code and test the connected path. + +## Install + +```bash +git clone https://github.com/multiversx/cookbook.git +cd cookbook/recipes/walletconnect-login +npm install +npm run dev +# open https://localhost:5173 +``` + +## Configuring the project ID + +```ts title="src/lib/multiversx.ts" +// src/lib/multiversx.ts — environment configuration for sdk-dapp's initApp. +// +// walletConnectV2ProjectId here is not optional in practice: ProviderFactory +// throws `WalletConnectV2Error.invalidConfig` ("Invalid WalletConnect +// setup") if you try to create a walletConnect provider without one +// configured (verified in +// node_modules/@multiversx/sdk-dapp/out/providers/ProviderFactory.cjs). +// Register a real project ID at https://cloud.walletconnect.com before +// shipping — the demo one here is shared and rate-limited. + +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { ThemesEnum } from '@multiversx/sdk-dapp/out/types/theme.types'; + +const WALLET_CONNECT_PROJECT_ID = + import.meta.env.VITE_WALLETCONNECT_PROJECT_ID ?? 'multiversx-cookbook-demo'; + +export const dappConfig = { + storage: { + getStorageCallback: (): Storage => sessionStorage, + }, + dAppConfig: { + environment: EnvironmentsEnum.devnet, + nativeAuth: true, + theme: ThemesEnum.dark, + providers: { + walletConnect: { + walletConnectV2ProjectId: WALLET_CONNECT_PROJECT_ID, + }, + }, + }, +}; + +export { EnvironmentsEnum }; +``` + +## The button + +```tsx title="src/WalletConnectLoginButton.tsx" +// src/WalletConnectLoginButton.tsx — a dedicated xPortal / WalletConnect +// login button with a QR-code anchor. +// +// Same overall pattern as the "Login via the DeFi extension" recipe — +// ProviderFactory.create() + provider.login() — with two differences +// specific to WalletConnect: +// +// 1. It needs an `anchor`: an HTMLElement the SDK renders the QR code +// (desktop) / deep-link prompt (mobile) into. Without one, there's +// nowhere for the user to actually see the code to scan. +// 2. It needs `walletConnectV2ProjectId` configured in initApp()'s +// dAppConfig — see src/lib/multiversx.ts. Omitting it makes +// ProviderFactory.create() throw immediately (Pitfall 1). +// +// The rest of the sequence is identical to the DeFi extension recipe and +// is verified the same way — against the actual sdk-dapp source, not just +// its .d.ts files: +// +// ProviderFactory.create({ type, anchor }) +// → internally calls setAccountProvider() for you (see +// node_modules/@multiversx/sdk-dapp/out/providers/ProviderFactory.cjs) +// provider.login() +// → internally dispatches both the login-info and account store +// actions (see +// node_modules/@multiversx/sdk-dapp/out/providers/DappProvider/helpers/login/helpers/accountLogin.cjs) + +import { useRef, useState } from 'react'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { ProviderFactory } from '@multiversx/sdk-dapp/out/providers/ProviderFactory'; +import { ProviderTypeEnum } from '@multiversx/sdk-dapp/out/providers/types/providerFactory.types'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; + +export function WalletConnectLoginButton(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const [status, setStatus] = useState<'idle' | 'connecting' | 'error'>('idle'); + const [error, setError] = useState(null); + // The QR code / deep-link UI renders into this div. It must exist in the + // DOM before ProviderFactory.create() runs — it's rendered unconditionally + // below, so by the time a click handler fires, anchorRef.current is set. + const anchorRef = useRef(null); + + const handleConnect = async (): Promise => { + if (!anchorRef.current) { + // Should not happen — the anchor div always renders — but keeps the + // types honest under strict null checks rather than asserting `!`. + setError('QR anchor not mounted yet.'); + setStatus('error'); + return; + } + + setStatus('connecting'); + setError(null); + try { + const provider = await ProviderFactory.create({ + type: ProviderTypeEnum.walletConnect, + anchor: anchorRef.current, + }); + // nativeAuth is enabled in this recipe's dappConfig, so login() + // generates its own native-auth token internally. + await provider.login(); + setStatus('idle'); + } catch (err) { + // If walletConnectV2ProjectId isn't configured, this throws + // "Invalid WalletConnect setup" (WalletConnectV2Error.invalidConfig, + // node_modules/@multiversx/sdk-dapp/out/providers/strategies/WalletConnectProviderStrategy/types/walletConnect.types.d.ts) + // — before the QR code ever renders. That specific case is confirmed + // from source. What happens if the user closes the QR modal without + // scanning is NOT independently confirmed here — the strategy's + // compiled source logs a `WalletConnectV2Error.userRejected` label in + // a catch block around the approval flow, but tracing exactly what + // (if anything) it re-throws to this call site would need a live + // WalletConnect session to observe, not just reading the bundle. + // Don't assume the caught message always matches one of the + // WalletConnectV2Error enum strings — display it, but treat it as + // unstructured for now. + const message = err instanceof Error ? err.message : String(err); + setError(message); + setStatus('error'); + } + }; + + const handleDisconnect = async (): Promise => { + const provider = getAccountProvider(); + await provider.logout(); + }; + + if (isLoggedIn) { + return ( + + ); + } + + return ( +
+ + + {/* The SDK renders the QR code / deep-link prompt into this element + once ProviderFactory.create() runs. Give it real dimensions — + an empty 0x0 div means the QR code has nowhere visible to draw. */} +
+ + {status === 'error' && error && ( +

+ Connection failed: {error} +

+ )} +
+ ); +} + +const buttonStyle: React.CSSProperties = { + padding: '0.75rem 1.25rem', + fontSize: '1rem', + cursor: 'pointer', +}; + +const anchorStyle: React.CSSProperties = { + marginTop: '1rem', + minHeight: '280px', + minWidth: '280px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}; +``` + +## The demo page + +```tsx title="src/App.tsx" +// src/App.tsx — demo page for WalletConnectLoginButton. + +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +import { WalletConnectLoginButton } from './WalletConnectLoginButton'; + +export function App(): JSX.Element { + const isLoggedIn = useGetIsLoggedIn(); + const account = useGetAccount(); + const { network } = useGetNetworkConfig(); + + return ( +
+

Login via xPortal / WalletConnect

+

+ Network: {network.chainId} +

+ + + + {isLoggedIn && ( +

+ Connected as {account.address}. +

+ )} +
+ ); +} +``` + +## Provider bootstrap + +`providers.tsx` calls `initApp()` once and gates rendering until the store is ready. + +```tsx title="src/providers.tsx" +// src/providers.tsx — sdk-dapp v5 init wrapper. +// +// Deliberately does NOT configure UnlockPanelManager — this recipe bypasses +// the generic multi-provider picker and talks to ProviderFactory directly +// for one specific provider. See src/WalletConnectLoginButton.tsx. + +import { useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import { dappConfig } from './lib/multiversx'; + +let initStarted = false; + +export function Providers({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [ready, setReady] = useState(false); + + useEffect(() => { + if (initStarted) { + setReady(true); + return; + } + initStarted = true; + + let cancelled = false; + void initApp(dappConfig).then(() => { + if (cancelled) return; + setReady(true); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!ready) { + return ( +
+ Initializing wallet SDK… +
+ ); + } + + return <>{children}; +} +``` + +## How it works + +**The anchor is not optional in practice.** +`ProviderFactory.create({ type: ProviderTypeEnum.walletConnect, anchor })` needs a +real `HTMLElement` to draw the QR code (desktop) or deep-link prompt (mobile +browser) into, verified from `WalletConnectProviderStrategyConfigType`, which +extends `WalletConnectConfig` with `anchor?: HTMLElement`. This recipe uses a +`useRef` div rendered unconditionally, so it exists before any +click handler runs. + +**The project ID gate happens before the QR code ever renders.** +`ProviderFactory.create()` reads `walletConnectV2ProjectId` from the store (the +same config object passed to `initApp()`) and throws `"Invalid WalletConnect setup"` +immediately if it is missing, confirmed directly from the compiled source +(`node_modules/@multiversx/sdk-dapp/out/providers/ProviderFactory.cjs`): +`if (!config?.walletConnectV2ProjectId) throw new Error(WalletConnectV2Error.invalidConfig)`. + +**Everything after `create()` matches the DeFi extension recipe exactly.** +`ProviderFactory.create()` calls `setAccountProvider()` internally; +`provider.login()` dispatches both the login-info and account store actions +internally. Once `await provider.login()` resolves, `useGetIsLoggedIn()` and +`useGetAccount()` already reflect the session. + +## Pitfalls + +:::danger[Pitfall 1: missing walletConnectV2ProjectId fails immediately] +`ProviderFactory.create({ type: ProviderTypeEnum.walletConnect })` throws +`"Invalid WalletConnect setup"` (`WalletConnectV2Error.invalidConfig`) if +`dAppConfig.providers.walletConnect.walletConnectV2ProjectId` was not set in +`initApp()`. Register a real project ID at +[cloud.walletconnect.com](https://cloud.walletconnect.com) before shipping. The +shared demo ID here is rate-limited and meant for local development only. +::: + +:::warning[Pitfall 2: give the anchor real dimensions] +An anchor `
` with no width or height renders a QR code with nowhere visible +to draw. This recipe's anchor style sets an explicit `min-height`/`min-width`. +Don't drop that when you copy the pattern into your own layout. +::: + +:::warning[Pitfall 3: the closed-modal error path is not independently confirmed here] +The compiled strategy source references a `WalletConnectV2Error.userRejected` +label inside a `catch` block around its approval flow, but confirming exactly what +(if anything) propagates to your own `catch` when the user closes the modal +without scanning would need a live WalletConnect session to observe, not just +reading the minified bundle. Display whatever you get, do not pattern-match on it. +::: + +:::warning[Pitfall 4: this button only ever offers WalletConnect] +Pair it with +[Login via the DeFi extension](/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login) +for a desktop-first option, or use +[Add a wallet login button](/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button)'s +picker if you would rather let the SDK decide which providers to surface. +::: + +## See also + +- [Login via the DeFi extension](/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login) + is the same `ProviderFactory` pattern for the browser-extension provider. +- [Add a wallet login button](/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button) + is the multi-provider picker alternative to this dedicated button. +- [Read the connected account](/sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account) + is what to do once `provider.login()` resolves. diff --git a/sidebars.js b/sidebars.js index a8b8f0b4a..cc384e2e5 100644 --- a/sidebars.js +++ b/sidebars.js @@ -248,6 +248,17 @@ const sidebars = { type: "category", label: "Cookbook (recipes)", items: [ + { + type: "category", + label: "Start here", + items: [ + "sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal", + "sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal", + "sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup", + "sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send", + "sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration", + ] + }, { type: "category", label: "Network providers", @@ -267,6 +278,21 @@ const sidebars = { label: "Wallets", items: [ "sdk-and-tools/sdk-js/cookbook/wallets/read-connected-account", + "sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button", + "sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login", + "sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login", + "sdk-and-tools/sdk-js/cookbook/wallets/ledger-login", + "sdk-and-tools/sdk-js/cookbook/wallets/native-auth", + "sdk-and-tools/sdk-js/cookbook/wallets/typescript-strict-mode-checklist", + ] + }, + { + type: "category", + label: "Migration", + items: [ + "sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp", + "sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5", + "sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager", ] }, ] From 388ad1bb696bb6072c3ccbe62864976ec880d30e Mon Sep 17 00:00:00 2001 From: Lukas <64620972+lamentierschweinchen@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:14:06 +0200 Subject: [PATCH 04/13] cookbook: normalize last_validated to CI-verify date (uniform badges) --- .../sdk-js/cookbook/migration/dapp-provider-to-initapp.mdx | 2 +- .../sdk-js/cookbook/migration/get-account-info-v4-vs-v5.mdx | 2 +- .../migration/send-transactions-to-transaction-manager.mdx | 2 +- .../sdk-js/cookbook/start-here/local-https-setup.mdx | 2 +- .../sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal.mdx | 2 +- docs/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send.mdx | 2 +- .../sdk-js/cookbook/start-here/v4-to-v5-migration.mdx | 2 +- .../sdk-js/cookbook/start-here/vite-react-minimal.mdx | 2 +- .../sdk-js/cookbook/wallets/defi-extension-login.mdx | 2 +- docs/sdk-and-tools/sdk-js/cookbook/wallets/ledger-login.mdx | 2 +- docs/sdk-and-tools/sdk-js/cookbook/wallets/native-auth.mdx | 2 +- .../cookbook/wallets/typescript-strict-mode-checklist.mdx | 2 +- .../sdk-js/cookbook/wallets/wallet-login-button.mdx | 2 +- .../sdk-js/cookbook/wallets/walletconnect-login.mdx | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp.mdx b/docs/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp.mdx index 39409383d..451b462f2 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/migration/dapp-provider-to-initapp.mdx @@ -3,7 +3,7 @@ title: "Migration: DappProvider to initApp, side-by-side" description: The full-app version of the DappProvider removal, every file that touches it, including the session-restore state v4 quietly handled for you. difficulty: intermediate est_minutes: 8 -last_validated: "2026-07-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5.mdx b/docs/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5.mdx index b83cfe7ab..431e2a769 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/migration/get-account-info-v4-vs-v5.mdx @@ -3,7 +3,7 @@ title: "Migration: useGetAccountInfo v4 vs v5" description: The same hook name still resolves in v5 but returns less data, silently. The easiest migration bug to miss because nothing forces a second look at it. difficulty: intermediate est_minutes: 6 -last_validated: "2026-07-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager.mdx b/docs/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager.mdx index 815da6079..0612ebd1b 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/migration/send-transactions-to-transaction-manager.mdx @@ -3,7 +3,7 @@ title: "Migration: useSendTransactions to TransactionManager.send" description: The v4 hook this migration is named for does not exist in the real package. This recipe covers what does, batch sends and a v5 nested-array trigger. difficulty: intermediate est_minutes: 8 -last_validated: "2026-07-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup.mdx index d662316da..856d291af 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/local-https-setup.mdx @@ -3,7 +3,7 @@ title: Local HTTPS for dApp dev (mkcert + Vite + Next.js) description: Wallet providers refuse to connect over plain HTTP. Set up trusted HTTPS for localhost using mkcert with Vite, Next.js, or framework-agnostic plain Node. difficulty: beginner est_minutes: 6 -last_validated: "2026-05-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal.mdx index a017c2dee..5b37bcb4d 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/nextjs-minimal.mdx @@ -3,7 +3,7 @@ title: Minimal sdk-dapp v5 app in Next.js (App Router) description: A working Next.js 14 App Router starter with sdk-dapp v5. Client-only init wrapper, the real next.config.js, login button, account hook, compiles strict. difficulty: beginner est_minutes: 10 -last_validated: "2026-04-29" +last_validated: "2026-07-16" sdk_versions: sdk-core: "^15.4.0" sdk-dapp: "^5.6.0" diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send.mdx index c0a1277ed..6b8101b25 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/sign-and-send.mdx @@ -3,7 +3,7 @@ title: Sign and send a transaction (the working path) description: The canonical sdk-dapp v5 sign, send, and track flow. provider.signTransactions then TransactionManager.send and .track, with proper nonce handling. difficulty: beginner est_minutes: 8 -last_validated: "2026-05-07" +last_validated: "2026-07-16" sdk_versions: sdk-core: "^15.4.0" sdk-dapp: "^5.6.0" diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration.mdx index 35747a92a..1e3e4e282 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/v4-to-v5-migration.mdx @@ -3,7 +3,7 @@ title: "Migrate sdk-dapp v4 to v5: hook-by-hook diffs" description: Side-by-side v4 vs v5 diffs for every removed hook, component, and import path. The shortest viable upgrade path for an existing v4 dApp. difficulty: intermediate est_minutes: 12 -last_validated: "2026-05-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal.mdx b/docs/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal.mdx index 2c27da6bd..ca9407c1d 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/start-here/vite-react-minimal.mdx @@ -3,7 +3,7 @@ title: Minimal sdk-dapp v5 app in Vite + React description: The smallest working sdk-dapp v5 setup in a fresh Vite + React + TypeScript project. Compiles strict on the first try, with HTTPS dev built in. difficulty: beginner est_minutes: 10 -last_validated: "2026-05-07" +last_validated: "2026-07-16" sdk_versions: sdk-core: "^15.4.0" sdk-dapp: "^5.6.0" diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login.mdx index cd9a6b843..bc1c29095 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/defi-extension-login.mdx @@ -3,7 +3,7 @@ title: Log in with the DeFi Wallet browser extension description: A dedicated single-provider login button using ProviderFactory directly, with real extension detection and no generic multi-provider picker UI. difficulty: intermediate est_minutes: 8 -last_validated: "2026-07-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/ledger-login.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/ledger-login.mdx index 7f0db5752..4aa976aa2 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/wallets/ledger-login.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/ledger-login.mdx @@ -3,7 +3,7 @@ title: Log in with a Ledger hardware wallet description: A dedicated Ledger login button using ProviderFactory directly, including the anchor element the SDK renders its own device-connect and account-picker UI into. difficulty: intermediate est_minutes: 8 -last_validated: "2026-07-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/native-auth.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/native-auth.mdx index 4180febb6..cd1a380b9 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/wallets/native-auth.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/native-auth.mdx @@ -3,7 +3,7 @@ title: Native auth, token issuance, expiry, auto-logout description: How nativeAuth issues a bearer token on login, why loginExpiresAt is not the token's real expiry, and how LogoutManager schedules the warning toast and logout. difficulty: intermediate est_minutes: 8 -last_validated: "2026-07-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/typescript-strict-mode-checklist.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/typescript-strict-mode-checklist.mdx index c6ecb302d..8a66bd142 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/wallets/typescript-strict-mode-checklist.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/typescript-strict-mode-checklist.mdx @@ -3,7 +3,7 @@ title: TypeScript strict-mode checklist for sdk-dapp consumers description: Four real bugs found while verifying cookbook recipes against installed sdk-dapp, plus the tsconfig flags and ESLint rules that catch them. difficulty: intermediate est_minutes: 7 -last_validated: "2026-07-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button.mdx index 9c0fc3a1e..723deb02c 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/wallet-login-button.mdx @@ -3,7 +3,7 @@ title: Add a wallet login button description: Configure UnlockPanelManager once at startup, then add a reusable connect and disconnect button that works with every registered wallet provider. difficulty: beginner est_minutes: 6 -last_validated: "2026-07-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: diff --git a/docs/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login.mdx b/docs/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login.mdx index 0d4a9e611..03a579efa 100644 --- a/docs/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login.mdx +++ b/docs/sdk-and-tools/sdk-js/cookbook/wallets/walletconnect-login.mdx @@ -3,7 +3,7 @@ title: Log in with xPortal via WalletConnect description: A dedicated xPortal login button using ProviderFactory and WalletConnect v2 directly, including the QR-code anchor element and project ID setup. difficulty: intermediate est_minutes: 8 -last_validated: "2026-07-07" +last_validated: "2026-07-16" sdk_versions: sdk-dapp: "^5.6.0" tags: From ab6a9d4bf72045db5feb40e012ca7b56ca4506ae Mon Sep 17 00:00:00 2001 From: Lukas <64620972+lamentierschweinchen@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:39:30 +0200 Subject: [PATCH 05/13] cookbook: port accounts and signing recipes (wave 2) Port the entire prototype `accounts` section (14 recipes) onto the proven TS-verification template, under docs/.../cookbook/accounts/: - Key management: generate-mnemonic-derive-keys, account-from-keys, keystore-save-load, pem-save-load - Addresses & state: address-utilities, fetch-account-state, manage-nonces - Signing: sign-verify-message, sign-verify-transaction, hash-signing-transaction - Guardians & relayed: set-guardian, guard-unguard-account, apply-guardian-to-transaction, relayed-v3-transaction Each page carries the meta-strip frontmatter (difficulty + sdk_versions), last_validated normalized to 2026-07-16, Docusaurus admonitions in place of Starlight