diff --git a/.agents/skills/oneshot-embedded-wallet/SKILL.md b/.agents/skills/oneshot-embedded-wallet/SKILL.md
new file mode 100644
index 0000000..baf2d5c
--- /dev/null
+++ b/.agents/skills/oneshot-embedded-wallet/SKILL.md
@@ -0,0 +1,239 @@
+---
+name: oneshot-embedded-wallet
+description: >-
+ Integrate the 1Shot embedded wallet (OWS Host Layer) with @1shotapi/ows-provider.
+ Use when embedding wallet.1shotapi.com, wiring OWSProxy, EIP-1193, credentials,
+ or custom RPC such as setStyle / focusWallet / addAsset for theming, host-driven focus mode, and tracked assets on the 1Shot Branding Layer.
+license: MIT
+metadata:
+ author: 1Shot-API
+ version: "0.1.0"
+ repository: https://github.com/1Shot-API/embedded-wallet
+---
+
+# 1Shot Embedded Wallet (Host integration)
+
+Teach an agent how to embed the **1Shot Wallet** Branding Layer from a Host Layer app using `@1shotapi/ows-provider`.
+
+```
+Host (your dapp) @1shotapi/ows-provider → OWSProxy
+ └── Branding iframe https://wallet.1shotapi.com/
+ └── Signing https://wallet.1shotapi.com/signer/ (same origin)
+```
+
+## Install
+
+```bash
+npm install @1shotapi/ows-provider @1shotapi/ows-types
+```
+
+## Minimal setup
+
+```typescript
+import { OWSProxy } from "@1shotapi/ows-provider";
+
+const WALLET_URL = "https://wallet.1shotapi.com/";
+
+const container = document.getElementById("wallet-container")!;
+const proxy = await OWSProxy.create(container, WALLET_URL);
+
+// Optional: theme / copy before showing the flyout
+await proxy.rpc("setStyle", {
+ copy: { productName: "Acme Wallet", tagline: "Powered by 1Shot" },
+ theme: { primary: "oklch(0.45 0.18 250)" },
+});
+
+proxy.showWallet();
+
+// EIP-1193
+const accounts = await proxy.ethereum.request({ method: "eth_requestAccounts" });
+```
+
+### Local / HTTPS notes
+
+- Passkeys require a **secure-context ancestor chain**. The Host page must be HTTPS (or `localhost`) when the wallet iframe is HTTPS.
+- Dev wallet URL: your ngrok or local Vite origin root (e.g. `https://….ngrok-free.app/`).
+- Production wallet URL: **`https://wallet.1shotapi.com/`** (Signing Layer at `/signer/` on the same origin — do not embed `/signer/` from the host).
+
+## Custom RPC — `setStyle`
+
+1Shot-specific method registered on the Branding Layer. Call via:
+
+```typescript
+await proxy.rpc("setStyle", options);
+```
+
+`options` is a partial merge (safe to call repeatedly):
+
+| Field | Type | Purpose |
+|-------|------|---------|
+| `theme.primary` | string (CSS color) | `--primary` |
+| `theme.primaryForeground` | string | `--primary-foreground` |
+| `theme.background` / `foreground` | string | page colors |
+| `theme.muted` / `mutedForeground` | string | secondary text |
+| `theme.border` / `accent` / `accentForeground` | string | chrome |
+| `theme.radius` | string | `--radius` (e.g. `"0.625rem"`) |
+| `theme.fontSans` | string | `--font-sans` |
+| `copy.productName` | string | titles / chrome |
+| `copy.tagline` | string | supporting line |
+| `copy.connect.title` | string | connect modal title |
+| `copy.connect.body` | string | connect modal body |
+| `copy.connect.rejectLabel` | string | Reject button |
+| `copy.connect.continueLabel` | string | Continue button |
+| `copy.walletSetup.title` | string | setup modal title |
+| `copy.walletSetup.body` | string | setup modal body |
+| `copy.walletSetup.cancelLabel` | string | Cancel button |
+| `copy.walletSetup.loginLabel` | string | Login with passkey |
+| `copy.walletSetup.createLabel` | string | Create account |
+| `copy.passkeyName.title` | string | passkey name modal title |
+| `copy.passkeyName.body` | string | passkey name modal body |
+| `copy.passkeyName.fieldLabel` | string | input label |
+| `copy.passkeyName.placeholder` | string | input placeholder |
+| `copy.passkeyName.emptyError` | string | empty-name validation error |
+| `copy.passkeyName.cancelLabel` | string | Cancel button |
+| `copy.passkeyName.continueLabel` | string | Continue button |
+| `copy.personalSign.title` | string | personal_sign modal title |
+| `copy.personalSign.accountLabel` | string | Account field label |
+| `copy.personalSign.messageLabel` | string | Message field label |
+| `copy.personalSign.rejectLabel` | string | Reject button |
+| `copy.personalSign.signLabel` | string | Sign button |
+| `copy.typedData.title` | string | EIP-712 modal title |
+| `copy.typedData.accountLabel` | string | Account field label |
+| `copy.typedData.primaryTypeLabel` | string | Primary type label |
+| `copy.typedData.domainLabel` | string | Domain label |
+| `copy.typedData.messageLabel` | string | Message label |
+| `copy.typedData.rejectLabel` | string | Reject button |
+| `copy.typedData.signLabel` | string | Sign button |
+| `copy.credentialOffer.title` | string | offer modal title |
+| `copy.credentialOffer.body` | string | supports `{issuerName}` `{issuerId}` |
+| `copy.credentialOffer.offeredHeading` | string | offered list heading |
+| `copy.credentialOffer.passkeyNote` | string | passkey hint |
+| `copy.credentialOffer.rejectLabel` | string | Reject button |
+| `copy.credentialOffer.acceptLabel` | string | Accept button |
+| `copy.credentialPresentation.title` | string | presentation modal title |
+| `copy.credentialPresentation.body` | string | supports `{verifierName}` `{verifierId}` |
+| `copy.credentialPresentation.credentialDetail` | string | supports `{credentialType}` `{credentialIssuer}` |
+| `copy.credentialPresentation.claimsHeading` | string | claims list heading |
+| `copy.credentialPresentation.passkeyNote` | string | passkey hint |
+| `copy.credentialPresentation.rejectLabel` | string | Reject button |
+| `copy.credentialPresentation.shareLabel` | string | Share button |
+| `copy.credentials.tabLabel` | string | Credentials tab label |
+| `copy.credentials.emptyCountLabel` | string | zero-count summary |
+| `copy.credentials.countLabel` | string | supports `{count}` |
+| `copy.credentials.refreshLabel` | string | Refresh button |
+| `copy.credentials.loadingBody` | string | loading state |
+| `copy.credentials.emptyBody` | string | empty-state text |
+| `copy.credentials.loadFailedError` | string | list load failure |
+| `copy.credentials.refreshFailedError` | string | relayer refresh failure |
+| `copy.credentials.notFoundError` | string | detail not in cache |
+| `copy.credentials.openFailedError` | string | detail open failure |
+| `copy.credentials.typeColumn` | string | Type column header |
+| `copy.credentials.issuerColumn` | string | Issuer column header |
+| `copy.credentials.issuedColumn` | string | Issued column header |
+| `copy.credentials.viewLabel` | string | View button |
+| `copy.credentials.detailFallbackTitle` | string | detail title fallback |
+| `copy.credentials.detailDescription` | string | detail dialog description |
+| `copy.credentials.issuerLabel` | string | Issuer field label |
+| `copy.credentials.formatLabel` | string | Format field label |
+| `copy.credentials.issuedLabel` | string | Issued field label |
+| `copy.credentials.validUntilLabel` | string | Valid until label |
+| `copy.credentials.idLabel` | string | Id field label |
+| `copy.credentials.claimsHeading` | string | Claims section heading |
+| `copy.credentials.claimsLoading` | string | claims loading text |
+| `copy.credentials.claimsEmpty` | string | no claims text |
+| `copy.credentials.closeLabel` | string | Close button |
+| `copy.createBackup.title` | string | create backup modal title |
+| `copy.createBackup.body` | string | supports `{minLength}` |
+| `copy.createBackup.passphrasePrompt` | string | Signing Layer passphrase label (`{minLength}`) |
+| `copy.createBackup.continueLabel` | string | Signing Layer continue |
+| `copy.createBackup.cancelLabel` | string | Cancel button |
+| `copy.createBackup.closeLabel` | string | Close button |
+| `copy.createBackup.copyLabel` | string | Copy button |
+| `copy.createBackup.copiedLabel` | string | after successful copy |
+| `copy.createBackup.copyFailedLabel` | string | copy failure |
+| `copy.createBackup.doneLabel` | string | Done button |
+| `copy.createBackup.encryptedLabel` | string | result ciphertext label |
+| `copy.createBackup.passwordTooShortError` | string | short passphrase error |
+| `copy.createBackup.cancelledError` | string | passkey cancelled |
+| `copy.createBackup.failedError` | string | generic failure |
+| `copy.restoreBackup.title` | string | restore backup modal title |
+| `copy.restoreBackup.body` | string | restore prompt body |
+| `copy.restoreBackup.passphraseLabel` | string | Signing Layer passphrase label |
+| `copy.restoreBackup.restoreLabel` | string | Signing Layer restore button |
+| `copy.restoreBackup.cancelLabel` | string | Cancel button |
+| `copy.restoreBackup.closeLabel` | string | Close button |
+| `copy.restoreBackup.doneLabel` | string | Done button |
+| `copy.restoreBackup.successBody` | string | success message |
+| `copy.restoreBackup.decryptFailedError` | string | bad passphrase error |
+| `copy.restoreBackup.cancelledError` | string | passkey cancelled |
+| `copy.restoreBackup.failedError` | string | generic failure |
+| `dark` | boolean | toggles `html.dark` |
+
+Returns `{ ok: true, productName: string }` with the resolved product name after merge.
+
+Unknown keys are rejected (Zod `.strict()`).
+
+See also [README.md](../../README.md) in this repository.
+
+## Custom RPC — `focusWallet` / `unfocusWallet`
+
+Host-controlled shell modes. Callers (not end users) switch between **General** (multi-chain tabs) and **Focused** (single chain + asset detail view).
+
+```typescript
+// Lock to one chain + ERC-20 (or other) asset
+await proxy.rpc("focusWallet", {
+ chainId: "0x4cef52", // Arc Testnet
+ assetAddress: "0x3600000000000000000000000000000000000000", // USDC
+});
+proxy.showWallet();
+
+// Restore general mode (keeps the current chain)
+await proxy.rpc("unfocusWallet");
+```
+
+| Method | Params | Effect |
+|--------|--------|--------|
+| `focusWallet` | `{ chainId: \`0x…\`, assetAddress: \`0x…\` }` | Switches active chain, sets focused asset, shows Asset Details shell |
+| `unfocusWallet` | none | Clears focus; returns to network selector + tabs |
+
+`focusWallet` returns `{ ok: true, mode: "focused", chainId, assetAddress }`.
+`unfocusWallet` returns `{ ok: true, mode: "general" }`.
+
+Unlike `addAsset`, **`focusWallet` does not ask the user for confirmation** — hosts may temporarily lock the shell to any asset.
+
+## Custom RPC — `addAsset`
+
+Propose a tracked **ERC-20** for the Balances tab. The wallet resolves the token (known catalog, or on-chain `getCode` + `name`/`symbol`/`decimals`) **before** showing the confirm modal. Non-ERC-20 addresses are rejected. **Always requires user confirmation** (Reject / Add). On approval the asset is persisted; on rejection the RPC throws a user-rejected error.
+
+```typescript
+await proxy.rpc("addAsset", {
+ chainId: "0x4cef52", // Arc Testnet
+ assetAddress: "0x3600000000000000000000000000000000000000", // USDC
+});
+proxy.showWallet();
+```
+
+| Method | Params | Effect |
+|--------|--------|--------|
+| `addAsset` | `{ chainId: \`0x…\`, assetAddress: \`0x…\` }` | Probes ERC-20, shows confirm modal; on accept, adds to tracked assets |
+
+Returns `{ ok: true, chainId, assetAddress }` when the user accepts.
+
+Users can also add assets from the Balances tab without a host RPC. The Balances list shows tracked assets for the currently selected network only (USDC is always tracked per supported chain).
+
+## Other Host APIs
+
+| API | Use |
+|-----|-----|
+| `proxy.ethereum.request(...)` | EIP-1193 (accounts, sign, chain, …) |
+| `proxy.credentials.*` | OID4 offer / present (when enabled in wallet) |
+| `proxy.showWallet()` / `hideWallet()` | Host-driven flyout without an EIP-1193 call |
+| `proxy.rpc(method, params)` | Custom Branding RPC (`setStyle`, `focusWallet`, `unfocusWallet`, `addAsset`, …) |
+
+## Hard rules
+
+- Never embed the Signing Layer iframe from the Host — always Host → Branding → Signing.
+- Prefer the published wallet URL in production; point at a local Branding origin only while developing this repo.
+- Theme with `setStyle`; do not ask integrators to fork CSS for basic brand colors / product name.
+- Use `focusWallet` / `unfocusWallet` for host-driven single-asset flows; do not expose mode switching in the wallet UI.
+- Use `addAsset` when the host wants a lasting Balances entry; expect a confirm modal (contrast with `focusWallet`).
diff --git a/.agents/skills/ows-branding-layer/SKILL.md b/.agents/skills/ows-branding-layer/SKILL.md
new file mode 100644
index 0000000..c34a9f8
--- /dev/null
+++ b/.agents/skills/ows-branding-layer/SKILL.md
@@ -0,0 +1,179 @@
+---
+name: ows-branding-layer
+description: >-
+ Build an Open Wallet Standard (OWS) Branding Layer with @1shotapi/ows-wallet-utils,
+ ows-signer-utils, ows-types, and optional ows-oid4. Use when scaffolding a branding
+ wallet, wiring OWSWallet/OWSSigner, RpcHelper/SignHelper/CredentialsHelper, passkey
+ unlock, custom host RPC, signing consent, recovery overlay, credentials, or display
+ shell in a separate repo.
+license: MIT
+metadata:
+ author: 1Shot-API
+ version: "0.3.0"
+ repository: https://github.com/1Shot-API/open-wallet
+---
+
+# OWS Branding Layer
+
+Teach an agent how to scaffold and extend an **OWS Branding Layer** in any repository.
+
+UI, flow ownership, and display queuing stay **app-owned**. Published packages provide thin protocol helpers (`RpcHelper`, `SignHelper`, `CredentialsHelper`, `overlaySignerIframe`) — not a module/registry runtime.
+
+When a local clone of [open-wallet](https://github.com/1Shot-API/open-wallet) is available (multi-root workspace), prefer grepping `examples/general-wallet` over guessing from this skill alone.
+
+## Install this skill (consumer repo)
+
+```bash
+npx skills add 1Shot-API/open-wallet@ows-branding-layer
+# or: npx skills add https://github.com/1Shot-API/open-wallet --skill ows-branding-layer
+# or from a sibling clone: npx skills add ../open-wallet --skill ows-branding-layer
+```
+
+Then invoke `/ows-branding-layer` or ask to build a branding wallet.
+
+## Architecture (non-negotiable)
+
+```
+Host Layer @1shotapi/ows-provider EIP-1193 / credentials proxy
+ └── Branding THIS APP UX + Postmate child + helpers
+ └── Signing @1shotapi/ows-signer WebAuthn PRF custody (nested iframe)
+```
+
+- Host **never** embeds the Signing Layer. Always Host → Branding → Signing.
+- Signing Layer accepts `postMessage` only from `window.parent` when `window.parent !== window.top`.
+- Passkeys require a **secure context on the entire ancestor chain** — Host and Branding/Signer must be HTTPS (or `localhost` / `*.localhost`).
+- `allowLocalAccess` is a **host** `OWSProxy` option (LAN OID4 from a public branding origin). Do not put it on branding APIs.
+
+Details: [references/architecture.md](references/architecture.md)
+
+## Packages
+
+| Package | Role |
+|---------|------|
+| `@1shotapi/ows-types` | Branded primitives, errors, credential types, EIP-1193 tables, `CredentialCryptoUtils` / `PresentationUtils` / `ProofUtils` |
+| `@1shotapi/ows-wallet-utils` | Branding ↔ Host (`OWSWallet`, `RpcHelper`, `requestDisplay`) |
+| `@1shotapi/ows-signer-utils` | Branding ↔ Signing (`OWSSigner`, `SignHelper`, `overlaySignerIframe`) |
+| `@1shotapi/ows-oid4` | Optional credentials (`CredentialsHelper`, HTTP OID4VCI/OID4VP clients) |
+| `@1shotapi/ows-signer` | Plain JS Signing Layer sources (serve as static `/signer/`) |
+
+```bash
+npm install \
+ @1shotapi/ows-types \
+ @1shotapi/ows-wallet-utils \
+ @1shotapi/ows-signer-utils \
+ @1shotapi/ows-oid4 \
+ viem
+# zod is transitive via ows-wallet-utils — add a direct dep only for custom registerRpc schemas
+# Serve/copy ows-signer HTML+JS from the same origin as branding (rpId = signer hostname)
+```
+
+Details: [references/packages.md](references/packages.md)
+
+## Task checklist
+
+Copy and track progress. Order matches a typical first branding app; specialized wallets may skip EIP-1193 and lead with custom host RPC. The reference wallet (`examples/general-wallet`) is **EIP-1193-first**.
+
+```
+Branding Layer Progress:
+- [ ] 1. Scaffold — packages + serve Signing Layer on same origin at /signer/
+- [ ] 2. Unlock — passkey create/login (`awaitSignerReady` vs `ensureReady`)
+- [ ] 3. Display shell — requestDisplay / hide + app-owned modal queue
+- [ ] 4. Host RPC — EIP-1193 (RpcHelper + account connect) and/or custom registerRpc
+- [ ] 5. Signing consent — SignHelper + app approval UI
+- [ ] 6. Recovery overlay — create/restore with overlaySignerIframe (no reparent)
+- [ ] 7. Credentials — CredentialsHelper.register + consent UI (optional)
+```
+
+Task details + exact example paths: [references/tasks.md](references/tasks.md)
+
+### Minimal boot sequence
+
+Stock Postmate parents only retry the handshake briefly after branding iframe `load`. **Register `Postmate.Model` via `wallet.start()` before the nested Signing Layer finishes loading.** Use a deferred signer proxy so helpers can register with an `OWSSigner`-shaped object immediately.
+
+Canonical implementation: `examples/general-wallet/src/wallet/WalletProvider.tsx` (`createDeferredSigner` + `boot`).
+
+```typescript
+import { OWSSigner, SignHelper } from "@1shotapi/ows-signer-utils";
+import { OWSWallet, RpcHelper } from "@1shotapi/ows-wallet-utils";
+// Optional credentials:
+// import { CredentialsHelper } from "@1shotapi/ows-oid4";
+
+const wallet = OWSWallet.prepare({ debug: false });
+
+const signerPromise = OWSSigner.create(signerContainer, signerUrl, {
+ hidden: true,
+ credentialId: loadCredentialId(),
+});
+// Proxy that throws until signerPromise resolves — do NOT await signer before start().
+const signer = createDeferredSigner(() => signerPromise); // copy pattern from WalletProvider
+
+// Register before start (order in general-wallet):
+// account connect → RpcHelper → SignHelper handlers → CredentialsHelper.register()
+const rpcHelper = new RpcHelper(providers, wallet, signer, { defaultChainId });
+
+const signHelper = new SignHelper(signer, wallet, {
+ // Setup-only when no credential; signing ceremony unlocks when credential exists
+ ensureReady: ensureOnboardedForSigning,
+ onAuthenticated: markUnlockedAndRefreshAddresses,
+ getChainId: () => rpcHelper.getChainId(),
+ requestPersonalSignApproval,
+ requestSignTypedDataApproval,
+ approveAndSignTransaction, // branding: consent + prepare + sign + broadcast
+});
+for (const [method, handler] of Object.entries(signHelper.handlers)) {
+ wallet.registerEip1193(method, handler);
+}
+
+// Optional:
+// new CredentialsHelper(wallet, signer, { … }).register();
+// Note arg order: CredentialsHelper(wallet, signer) vs SignHelper(signer, wallet)
+
+void wallet.start(); // registers Model immediately — do not await nested signer first
+void signerPromise; // background load; UI can paint
+```
+
+Prefer **`OWSWallet.prepare()` → register handlers → `start()`**. Do not use a module install runtime.
+
+Split readiness in your app:
+
+| API | Meaning |
+|-----|---------|
+| `awaitSignerReady()` | Nested Signing Layer iframe + `OWSSigner` loaded |
+| `ensureReady()` | Signer loaded **and** unlocked / onboarded (passkey) — for connect, credentials, recovery create |
+| `ensureOnboardedForSigning()` | Setup/login **only if no credential id**; otherwise no-op — for `SignHelper` signed actions |
+| `onAuthenticated` | After successful sign / send: set unlocked + refresh addresses |
+
+For custom RPCs such as `eth_sendTransaction` or future ERC-7710 sends, use the same **centralized signed-action gate**: setup-only before consent when the credential is missing; one passkey ceremony for the sign itself when the credential is known.
+
+Restore-backup must use **`awaitSignerReady` only** — calling `ensureReady` first can force setup/login before recovery.
+
+## Hard rules (from OWS)
+
+1. **Branded types** from `@1shotapi/ows-types` — use constructors (`EVMAccountAddress(...)`), never `as` casts.
+2. **No deprecations** during pre-1.0 — rename and update all call sites.
+3. Prefer **methods on objects** over free helpers when logic belongs to one class.
+4. Set iframe `allow` for WebAuthn/clipboard **before** navigation (`OWSSigner` / host `OWSProxy` already do this).
+5. Do not vendor Postmate — use the `postmate` package (transitive).
+6. Do **not** reparent the signer iframe for passphrase UI — use `overlaySignerIframe`.
+7. Prefer Vite `/signer/` at **origin root** even if the branding app uses a subpath `base` (e.g. `/wallet/`).
+
+## Reference implementation
+
+Canonical React + Vite + Tailwind demo: `examples/general-wallet` in [1Shot-API/open-wallet](https://github.com/1Shot-API/open-wallet).
+
+| Concern | Start here |
+|---------|------------|
+| Boot / deferred signer | `src/wallet/WalletProvider.tsx` |
+| Account connect | `src/ows/registerAccountConnect.ts` |
+| Sign consent | `src/ows/registerApprovalSigning.ts`, `src/components/modals/SignModals.tsx` |
+| Credentials | `src/ows/registerCredentialsProvider.ts`, `src/components/modals/CredentialModals.tsx` |
+| Recovery | `src/components/modals/BackupModals.tsx` |
+| Serve signer | `vite.config.ts`, `scripts/copy-signer.mjs`, `signer-static/index.html` |
+
+## Out of scope for this skill
+
+- Host Layer apps (`ows-provider`) — separate concern (`allowLocalAccess`, flyout chrome)
+- Implementing or modifying `ows-signer` internals
+- Publishing npm packages / changesets
+- Shared React/Tailwind UI kits (apps own their UI)
+- Calling `PresentationUtils` / SD-JWT low-level APIs unless you bypass `CredentialsHelper`
diff --git a/.agents/skills/ows-branding-layer/references/architecture.md b/.agents/skills/ows-branding-layer/references/architecture.md
new file mode 100644
index 0000000..6498f17
--- /dev/null
+++ b/.agents/skills/ows-branding-layer/references/architecture.md
@@ -0,0 +1,53 @@
+# OWS Branding Layer architecture notes
+
+## Three layers
+
+| Layer | Runs where | Trust / UX |
+|-------|------------|------------|
+| Host | Integrator dapp | EIP-1193 / credentials proxy; must be secure context if it embeds HTTPS wallet |
+| Branding | Wallet origin (your product) | Branding, consent, app-owned UI + SDK helpers |
+| Signing | Nested iframe (prefer same origin as branding) | Passkeys / PRF; minimal attack surface |
+
+## Origins and `rpId`
+
+- Signing Layer `rpId` is always `window.location.hostname` of the signer document.
+- Prefer **same origin** for branding + signer (shared passkey namespace).
+- Reference layout: branding UI may live under a path (`/wallet/`) while signer document is at `/signer/` on the **same origin**.
+- Canonical CDN / on-chain gateway is an alternative shared `rpId` for all integrators.
+
+## Secure contexts
+
+Browsers require the **entire iframe ancestor chain** to be secure for WebAuthn. Therefore:
+
+- Production hosts: HTTPS
+- Local custom hosts (`ows-host.com`): HTTPS via mkcert (see `examples/host` in open-wallet)
+- `http://localhost` remains a special-case secure context
+- Branding demos often use ngrok HTTPS (`examples/general-wallet/scripts/dev.mjs`)
+
+## Display protocol
+
+Branding:
+
+1. `const display = await wallet.requestDisplay({ width, height })`
+2. Run WebAuthn / approval UI inside the branding iframe
+3. `await display.hide()` (or `wallet.requestHide()`)
+
+Host `OWSProxy` shows a lower-right opaque flyout (no modal backdrop). Protocol events include `ows:requestDisplay`, `ows:requestHide`, and `ows:releaseDisplay` (session `release()` / hide). Apps should not invent parallel display protocols.
+
+Display **queuing** (serializing concurrent modals) is app-owned — see `WalletProvider` modal queue in general-wallet.
+
+## Host handshake vs nested signer
+
+Stock Postmate child handshake windows are short. Host-side `OWSProxy` extends the handshake timeout for slow tunnels, but branding must still **`wallet.start()` before awaiting the nested Signing Layer** so the parent finds a Model. Deferred signer proxies keep handlers registerable immediately.
+
+## Host-only: Local Network Access
+
+When a public branding origin (e.g. ngrok) fetches private/loopback OID4 endpoints, Chrome may require the host iframe `allow` attribute. That is configured on **`OWSProxy.create(..., { allowLocalAccess: true })`**, not on branding wallet APIs.
+
+## Integrity (future)
+
+Signed-manifest / verify-before-execute for `ows-signer` is planned (see `packages/ows-signer/docs/trusted-loader-plan.md`). Not required for a first branding scaffold.
+
+## Reference demo
+
+`examples/general-wallet` + `examples/host` (and credential issuer/verifier hosts) in the open-wallet monorepo demonstrate Host → Branding → Signing with HTTPS passkeys.
diff --git a/.agents/skills/ows-branding-layer/references/packages.md b/.agents/skills/ows-branding-layer/references/packages.md
new file mode 100644
index 0000000..7f74748
--- /dev/null
+++ b/.agents/skills/ows-branding-layer/references/packages.md
@@ -0,0 +1,70 @@
+# OWS packages for a Branding Layer
+
+## Install
+
+Aligned with `examples/general-wallet/package.json`:
+
+```bash
+npm install \
+ @1shotapi/ows-types \
+ @1shotapi/ows-wallet-utils \
+ @1shotapi/ows-signer-utils \
+ @1shotapi/ows-oid4 \
+ viem
+```
+
+Notes:
+
+- `viem` is a peer of `ows-signer-utils` (EVM helpers).
+- `postmate` and `zod` are **transitive** via `ows-wallet-utils` — do not vendor Postmate; add a direct `zod` dep only if you author custom `registerRpc` schemas.
+- `@1shotapi/ows-signer` is plain JS sources. Prefer **copy or static-serve** into your app (no build step). Same origin as branding so `rpId === location.hostname`. Consuming it solely as an npm import of TS modules is not the reference pattern.
+- `@1shotapi/ows-provider` is for **host** apps only — omit from a branding-only package unless the same origin also serves a demo host.
+
+Until packages are on npm, depend via workspace, `file:`, or git:
+
+```bash
+# example — sibling clone
+npm install ../open-wallet/packages/ows-types
+```
+
+## Package roles
+
+| Package | Import surface | Branding uses it for |
+|---------|----------------|----------------------|
+| `ows-types` | primitives, errors, credentials, EIP-1193 tables, `CredentialCryptoUtils`, `PresentationUtils`, `ProofUtils` | Branded values, holder signer bridge, shared errors |
+| `ows-wallet-utils` | `OWSWallet`, `RpcHelper`, display child client | Postmate child, EIP-1193 / custom RPC registration, reads/chain, `requestDisplay` |
+| `ows-signer-utils` | `OWSSigner`, `SignHelper`, `overlaySignerIframe`, `evm.*` | Nested signer iframe, consent→sign wiring, digests → signatures |
+| `ows-oid4` | `CredentialsHelper`, `HttpOid4vciClient`, `HttpOid4vpClient`, … | Optional OID4 accept/present orchestration |
+| `ows-signer` | static files | Custody kernel under `/signer/` |
+
+There is **no** branding-core / registry package. App-local UI and wiring live in your repo (see `examples/general-wallet/src/ows/`).
+
+### Constructor arg order (easy to swap)
+
+| Helper | Order |
+|--------|-------|
+| `SignHelper` | `(signer, wallet, options)` |
+| `CredentialsHelper` | `(wallet, signer, options)` |
+
+## Serving the Signing Layer
+
+Mirror `examples/general-wallet`:
+
+- Branding app may use Vite `base: "/wallet/"` for the UI.
+- Signer is served at **`/signer/` outside that base** via middleware so WebAuthn + nest stay same-origin (`vite.config.ts` `serveSignerPlugin`).
+- HTML shell: `signer-static/index.html`; JS from package `src/`.
+- Prod: `scripts/copy-signer.mjs` → `dist/signer/`.
+
+```typescript
+const signerUrl = new URL("/signer/", window.location.origin).href;
+```
+
+## Credentials stack (optional)
+
+Prefer `CredentialsHelper` + HTTP clients from **`@1shotapi/ows-oid4`**. Demo-only stores / trust / keys live under `examples/shared` in open-wallet (not published) — product apps supply their own `ICredentialRepository`, trust registry, and OID4 clients.
+
+Holder signing: omit `holderSigner` to use the default OWS Ed25519 bridge (`CredentialCryptoUtils.createOwsEd25519HolderSigner` in `ows-types`), or pass your own `IHolderSigner`.
+
+## Host apps (not branding)
+
+Hosts use `@1shotapi/ows-provider` (`OWSProxy`) only. Host-only options include `allowLocalAccess` (iframe permissions for Local Network Access when branding on a public origin fetches `127.0.0.1` / private LAN OID4 endpoints).
diff --git a/.agents/skills/ows-branding-layer/references/tasks.md b/.agents/skills/ows-branding-layer/references/tasks.md
new file mode 100644
index 0000000..dd8c8c6
--- /dev/null
+++ b/.agents/skills/ows-branding-layer/references/tasks.md
@@ -0,0 +1,160 @@
+# Branding Layer tasks
+
+Task-oriented guidance aligned with `examples/general-wallet`. Specialize freely; production branding apps may lead with **custom host RPC** and skip MetaMask-shaped EIP-1193. The reference wallet is **EIP-1193-first**.
+
+Paths below are under `examples/general-wallet/` unless noted.
+
+## 1. Scaffold
+
+1. Install packages (see [packages.md](packages.md)).
+2. Serve `@1shotapi/ows-signer` as static **`/signer/` on the same origin** as branding (`rpId === location.hostname`).
+ - Dev: Vite middleware maps `/signer/` → `signer-static/index.html` and `/signer/src/` → package `src/` (**outside** Vite `base`, e.g. `/wallet/`). See `vite.config.ts`.
+ - Prod: `scripts/copy-signer.mjs` into `dist/signer/`.
+3. Signer URL: `new URL("/signer/", window.location.origin).href` (origin root, not under app `base`).
+4. Create a hidden signer host (`src/components/SignerHost.tsx`), then **start Postmate before awaiting nested signer load**:
+
+```typescript
+const wallet = OWSWallet.prepare();
+const signerPromise = OWSSigner.create(container, signerUrl, {
+ hidden: true,
+ credentialId: loadCredentialId(),
+});
+const signer = createDeferredSigner(() => signerPromise); // WalletProvider.tsx
+// …register handlers (ensureReady awaits signerPromise + unlock)…
+void wallet.start(); // registers Model immediately
+void signerPromise; // background — do not block UI/paint on this
+```
+
+Also see `scripts/dev.mjs` (ngrok HTTPS for passkeys). Do not run leftover `--no-tunnel` processes on the same port while hosts expect a tunnel URL.
+
+## 2. Unlock (`awaitSignerReady` vs `ensureReady`)
+
+App-owned passkey create / login. Split readiness:
+
+| Helper | Waits for |
+|--------|-----------|
+| `awaitSignerReady()` | Signing Layer iframe + `OWSSigner` instance |
+| `ensureReady()` | Signer ready **plus** unlocked / wallet created |
+
+- Persist credential id + cached addresses in app storage (`src/storage.ts`).
+- Gate signing and account-connect behind `ensureReady()`.
+- Embedded first-run UI when `window.parent !== window.top` is optional — `OnboardingPanel.tsx` / `WalletProvider.tsx` `runSetupFlow`.
+- Host-driven setup: wrap with `requestDisplay` + setup modals (`SetupModals.tsx`).
+
+No published SDK for setup dialogs; keep UI local.
+
+## 3. Display shell
+
+Before WebAuthn or consent UI in a cross-origin host embed:
+
+```typescript
+const display = await wallet.requestDisplay({ width, height });
+try {
+ // dialogs / passkey / overlay
+} finally {
+ await display.hide();
+}
+```
+
+Also available: `wallet.requestHide()`. Host wire event `ows:releaseDisplay` is reached via `DisplaySession.release()` / hide paths — branding apps should prefer `display.hide()`.
+
+Host `OWSProxy` shows a lower-right opaque flyout (no modal backdrop).
+
+**App concerns (not SDK):**
+
+- **Modal queue** — serialize concurrent dialogs (`WalletProvider` `pushModal` / `ModalHost.tsx`).
+- Nested `requestDisplay` may reuse an active session (depth). Helpers (`SignHelper`, `CredentialsHelper`) already call `requestDisplay` — **do not double-wrap** those handler paths with another outer display.
+
+## 4. Host RPC (primary for specialized wallets) + EIP-1193
+
+**Specialized wallets:** register custom methods the host calls via `proxy.rpc` / your protocol (`wallet.registerRpc`).
+
+**Reference wallet path (EIP-1193):**
+
+1. `src/ows/registerAccountConnect.ts` — `eth_accounts` / `eth_requestAccounts` (cached addresses; connect consent + `ensureReady`).
+2. `RpcHelper` for JSON-RPC reads / `wallet_switchEthereumChain` (`src/ows/demoChains.ts`, construct in `WalletProvider.tsx`).
+3. `SignHelper` for `personal_sign` / typed data (task 5).
+
+```typescript
+new RpcHelper(
+ new Map([[chainId, rpcUrl] /* … */]),
+ wallet,
+ signer, // optional; unused for reads today
+ { defaultChainId },
+);
+```
+
+Call after `prepare()`, before `start()`. Zod EIP-1193 / credential wire schemas live in `ows-wallet-utils` (transitive).
+
+## 5. Signing consent
+
+Headless wiring in `SignHelper` (`@1shotapi/ows-signer-utils`):
+
+```
+requestDisplay → consent UI → ensureReady → signer.evm.signMessage | signTypedData → hide
+```
+
+Registers: `personal_sign`, `eth_signTypedData`, `eth_signTypedData_v3`, `eth_signTypedData_v4`, `eth_sendTransaction`. Reject with `OwsUserRejectedError`.
+
+Create `RpcHelper` before `SignHelper`. Pass `getChainId` from RpcHelper. Branding implements `approveAndSignTransaction` (consent + prepare + sign + broadcast; use exported `prepareEvmTransaction`). Use a **setup-only** `ensureReady` plus `onAuthenticated` after message/typed-data ceremonies (send auth side effects live in branding's approve callback).
+
+```typescript
+const signHelper = new SignHelper(signer, wallet, {
+ ensureReady: ensureOnboardedForSigning,
+ onAuthenticated,
+ getChainId: () => rpcHelper.getChainId(),
+ requestPersonalSignApproval, // PersonalSignApprovalRequest → boolean
+ requestSignTypedDataApproval, // SignTypedDataApprovalRequest → boolean
+ approveAndSignTransaction, // SendTransactionApprovalRequest → EVMTransactionHash
+});
+for (const [method, handler] of Object.entries(signHelper.handlers)) {
+ wallet.registerEip1193(method, handler);
+}
+```
+
+Reference: `src/ows/registerApprovalSigning.ts`, `src/components/modals/SignModals.tsx`, `src/wallet/withWalletReady.ts` (embedded-wallet).
+
+## 6. Recovery overlay
+
+Create / restore encrypted backup — **never reparent** the signer iframe.
+
+Reference: `src/components/modals/BackupModals.tsx`, called from `WalletProvider` `openCreateBackup` / `openRestoreBackup`.
+
+1. Outer `wallet.requestDisplay` for the dialog shell (WalletProvider wrappers).
+2. `overlaySignerIframe(iframe, slot, { homeContainer })` so the passphrase UI appears over a slot.
+3. **Double `requestAnimationFrame`** after overlay before calling signer RPCs (layout must settle).
+4. Create: `ensureReady()` then `signer.createRecoveryData(passwordText, buttonText, minPasswordLength)`.
+5. Restore: **`awaitSignerReady()` only** (not `ensureReady`) then `signer.recoverKey(encrypted, passwordText, buttonText)`.
+6. Always restore overlay styles in `finally` / abort cleanup; then `display.hide()`.
+
+Distinct from `prepareSignerIframeForWebAuthn` (1×1 invisible passkey focus used **inside** `OWSSigner`).
+
+## 7. Credentials (optional)
+
+Prefer `CredentialsHelper` from `@1shotapi/ows-oid4` (not exported from `ows-wallet-utils`):
+
+```typescript
+new CredentialsHelper(wallet, signer, {
+ repository,
+ oid4vci,
+ oid4vp,
+ trust,
+ ensureReady,
+ requestCredentialOfferApproval,
+ requestCredentialPresentationApproval,
+ // holderSigner optional — defaults via CredentialCryptoUtils.createOwsEd25519HolderSigner
+}).register();
+```
+
+Constructor arg order: **`(wallet, signer, options)`** — opposite of `SignHelper(signer, wallet, …)`.
+
+Reference stack:
+
+- Wiring: `src/ows/registerCredentialsProvider.ts`
+- Consent UI: `src/components/modals/CredentialModals.tsx`
+- Demo repositories / trust / HTTP clients fixtures: `examples/shared` (alias `@ows-shared` in the monorepo — not published)
+- Wire methods: `credentials.acceptOffer` / `present` / `list` / `delete`
+
+Do **not** call `PresentationUtils` / low-level SD-JWT helpers unless you bypass `CredentialsHelper`. Holder KB JWT lives in `CredentialCryptoUtils.createOwsEd25519HolderSigner` (`@1shotapi/ows-types`).
+
+Host demos: `examples/credential-issuer`, `examples/credential-verifier` (use `ows-provider`; `allowLocalAccess` is host-only).
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..5631ebb
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,8 @@
+node_modules
+dist
+.env
+.git
+.agents
+*.md
+.DS_Store
+.dockerignore
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..e2aca9f
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,21 @@
+# Ngrok (required for HTTPS passkey testing with a host dapp)
+# https://dashboard.ngrok.com/get-started/your-authtoken
+NGROK_AUTHTOKEN=
+
+# Optional reserved domain (hostname only or full URL)
+# https://dashboard.ngrok.com/domains
+# NGROK_DOMAIN=your-name.ngrok-free.app
+
+# Optional Vite port for the Branding Layer (default 5174)
+# PORT=5174
+# WALLET_PORT=5174
+
+# Test Host Layer (`npm run dev:host`, default port 5173)
+# HOST_PORT=5173
+# Explicit Branding iframe URL (overrides NGROK_DOMAIN / localhost)
+# WALLET_IFRAME_URL=https://your-name.ngrok-free.app/
+
+# Optional HTTPS for the test host (passkeys when wallet is on HTTPS/ngrok)
+# HOST_HTTPS=1
+# HOST_SSL_CERT=host/certs/dev-cert.pem
+# HOST_SSL_KEY=host/certs/dev-key.pem
diff --git a/.github/workflows/Deploy Dev.yaml b/.github/workflows/Deploy Dev.yaml
new file mode 100644
index 0000000..51525b1
--- /dev/null
+++ b/.github/workflows/Deploy Dev.yaml
@@ -0,0 +1,135 @@
+name: 1Shot Wallet - Build, Push, and Deploy to Dev
+
+on:
+ push:
+ branches:
+ - develop
+
+env:
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
+ PRODUCT_NAME: oneshot
+ GITHUB_SHA: ${{ github.sha }}
+ GITHUB_REF: ${{ github.ref }}
+ REGISTRY_HOSTNAME: gcr.io
+ GAR_LOCATION: us-central1
+ GKE_ZONE: us-central1-a
+ PROJECT_ID: shot-dev
+ GCP_CREDENTIALS: ${{ secrets.DEV_GCP_CREDENTIALS }}
+ GOOGLE_APPLICATION_CREDENTIALS: ~/.gcp/credentials.json
+ GOOGLE_PROJECT: shot-dev
+ GOOGLE_REGION: us-central1
+ GOOGLE_ZONE: us-central1-a
+ BUILD_ENV: develop
+ SSH_KEY: ${{ secrets.DEV_SSH_KEY }}
+ # GCE instance name in GOOGLE_ZONE (single dev VM)
+ DEV_VM_INSTANCE: dev-vm
+
+concurrency:
+ group: deploy-${{ github.ref_name }}
+ cancel-in-progress: false
+
+jobs:
+ wallet-dockerize:
+ name: Dockerize Code
+ runs-on:
+ labels: [ubuntu-latest]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+ - uses: dorny/paths-filter@v4
+ id: changes
+ with:
+ filters: |
+ packages:
+ - '**'
+ # Setup gcloud CLI
+ - id: "auth"
+ uses: "google-github-actions/auth@v3"
+ with:
+ credentials_json: "${{ secrets.DEV_GCP_CREDENTIALS }}"
+ - name: "Set up Cloud SDK"
+ uses: "google-github-actions/setup-gcloud@v3"
+ - name: Docker configuration
+ #if: steps.changes.outputs.packages == 'true'
+ run: |-
+ gcloud --quiet auth configure-docker $GAR_LOCATION-docker.pkg.dev
+
+ - name: Build Wallet
+ run: |-
+ docker build \
+ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:$GITHUB_SHA" \
+ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:${GITHUB_REF##*/}" \
+ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:latest" \
+ --build-arg PRODUCT_NAME="$PRODUCT_NAME" \
+ --build-arg GITHUB_SHA="$GITHUB_SHA" \
+ --build-arg GITHUB_REF="$GITHUB_REF" \
+ --build-arg BASE_TAG="$GITHUB_SHA" \
+ -f ./Dockerfile \
+ .
+
+ # Push the Docker image to Google Artifact Registry
+ - name: Publish Wallet
+ run: |-
+ docker push "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet" --all-tags
+
+ google-deployment:
+ name: Deploy to Google
+ runs-on:
+ labels: [ubuntu-latest]
+ if: ${{ success() && contains(join(needs.*.result, ','), 'success') }}
+ needs: [wallet-dockerize]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+
+ - id: "auth"
+ uses: "google-github-actions/auth@v3"
+ with:
+ credentials_json: "${{ secrets.DEV_GCP_CREDENTIALS }}"
+ - name: "Set up Cloud SDK"
+ uses: "google-github-actions/setup-gcloud@v3"
+ # SSH into the VM and run the deploy script
+ - name: Set up SSH key
+ run: |
+ mkdir -p ~/.ssh
+ echo "$SSH_KEY" > ~/.ssh/id_ed25519
+ chmod 600 ~/.ssh/id_ed25519
+
+ - name: Pull and Run Latest
+ run: |
+ set -euo pipefail
+ ZONE="${GOOGLE_ZONE}"
+ VM="${DEV_VM_INSTANCE}"
+ EXTERNAL_IP=$(gcloud compute instances describe "$VM" --zone="$ZONE" --project="$PROJECT_ID" \
+ --format='get(networkInterfaces[0].accessConfigs[0].natIP)')
+ EXTERNAL_IP="${EXTERNAL_IP//$'\r'/}"
+ EXTERNAL_IP="${EXTERNAL_IP//[[:space:]]/}"
+ if [[ -z "${EXTERNAL_IP}" ]]; then
+ echo "::error::VM '$VM' in zone '$ZONE' has no external NAT IP (networkInterfaces[0].accessConfigs[0].natIP is empty). Attach a public IP or change the deploy method (e.g. IAP)."
+ exit 1
+ fi
+ echo "==================== Deploying wallet to $VM ($EXTERNAL_IP) ===================="
+ ssh-keyscan -t ed25519 -H "$EXTERNAL_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
+ ssh-keyscan -H "$EXTERNAL_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
+ ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 "github@${EXTERNAL_IP}" bash -s << 'EOF'
+ cd /home/github
+ echo "----------------------------------------------------------------------------------------"
+ echo "----------------------- Start of Wallet Deployment Script ------------------------------"
+ echo "----------------------------------------------------------------------------------------"
+
+ echo "---------------------------- Docker Deployment ---------------------------------------"
+ # Pull the latest images
+ docker-compose pull
+
+ # Stop necessary services
+ docker stop wallet || true
+ docker rm -f wallet || true
+
+ # Reup everything
+ docker-compose up -d
+
+ # Prune dead resources
+ docker system prune -f
+
+ # If the downtime is too much in the future, https://github.com/wowu/docker-rollout?tab=readme-ov-file#%EF%B8%8F-caveats
+ EOF
diff --git a/.github/workflows/Deploy Prod.yaml b/.github/workflows/Deploy Prod.yaml
new file mode 100644
index 0000000..0ba27c3
--- /dev/null
+++ b/.github/workflows/Deploy Prod.yaml
@@ -0,0 +1,113 @@
+name: 1Shot Wallet - Build, Push, and Deploy to Production
+
+on:
+ push:
+ branches:
+ - main
+
+env:
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
+ PRODUCT_NAME: oneshot
+ GITHUB_SHA: ${{ github.sha }}
+ GITHUB_REF: ${{ github.ref }}
+ REGISTRY_HOSTNAME: gcr.io
+ GAR_LOCATION: us-central1
+ GKE_ZONE: us-central1-a
+ PROJECT_ID: shot-prod-493103
+ GCP_CREDENTIALS: ${{ secrets.PROD_GCP_CREDENTIALS }}
+ GOOGLE_APPLICATION_CREDENTIALS: ~/.gcp/credentials.json
+ GOOGLE_PROJECT: shot-prod-493103
+ GOOGLE_REGION: us-central1
+ GOOGLE_ZONE: us-central1-a
+ BUILD_ENV: develop
+ SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
+
+concurrency:
+ group: deploy-${{ github.ref_name }}
+ cancel-in-progress: false
+
+jobs:
+ wallet-dockerize:
+ name: Dockerize Code
+ runs-on:
+ labels: [ubuntu-latest]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+ - uses: dorny/paths-filter@v4
+ id: changes
+ with:
+ filters: |
+ packages:
+ - '**'
+ # Setup gcloud CLI
+ - id: "auth"
+ uses: "google-github-actions/auth@v3"
+ with:
+ credentials_json: "${{ secrets.PROD_GCP_CREDENTIALS }}"
+ - name: "Set up Cloud SDK"
+ uses: "google-github-actions/setup-gcloud@v3"
+ - name: Docker configuration
+ #if: steps.changes.outputs.packages == 'true'
+ run: |-
+ gcloud --quiet auth configure-docker $GAR_LOCATION-docker.pkg.dev
+
+ - name: Build Wallet
+ run: |-
+ docker build \
+ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:$GITHUB_SHA" \
+ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:${GITHUB_REF##*/}" \
+ --tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:latest" \
+ --build-arg PRODUCT_NAME="$PRODUCT_NAME" \
+ --build-arg GITHUB_SHA="$GITHUB_SHA" \
+ --build-arg GITHUB_REF="$GITHUB_REF" \
+ --build-arg BASE_TAG="$GITHUB_SHA" \
+ -f ./Dockerfile \
+ .
+
+ # Push the Docker image to Google Artifact Registry
+ - name: Publish Wallet
+ run: |-
+ docker push "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet" --all-tags
+
+ google-deployment:
+ name: Deploy to Google
+ runs-on:
+ labels: [ubuntu-latest]
+ if: ${{ success() && contains(join(needs.*.result, ','), 'success') }}
+ needs: [wallet-dockerize]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+
+ - id: "auth"
+ uses: "google-github-actions/auth@v3"
+ with:
+ credentials_json: "${{ secrets.PROD_GCP_CREDENTIALS }}"
+ - name: "Set up Cloud SDK"
+ uses: "google-github-actions/setup-gcloud@v3"
+ # SSH into the VM and run the deploy script
+ - name: Set up SSH key
+ run: |
+ mkdir -p ~/.ssh
+ echo "$SSH_KEY" > ~/.ssh/id_ed25519
+ chmod 600 ~/.ssh/id_ed25519
+
+ - name: Pull and Run Latest
+ run: |
+ set -euo pipefail
+ ZONE="${GOOGLE_ZONE}"
+ for VM in prod-vm prod-vm-2; do
+ EXTERNAL_IP=$(gcloud compute instances describe "$VM" --zone="$ZONE" --project="$PROJECT_ID" \
+ --format='get(networkInterfaces[0].accessConfigs[0].natIP)')
+ EXTERNAL_IP="${EXTERNAL_IP//$'\r'/}"
+ EXTERNAL_IP="${EXTERNAL_IP//[[:space:]]/}"
+ if [[ -z "${EXTERNAL_IP}" ]]; then
+ echo "::error::VM '$VM' in zone '$ZONE' has no external NAT IP (networkInterfaces[0].accessConfigs[0].natIP is empty). Attach a public IP or change the deploy method (e.g. IAP)."
+ exit 1
+ fi
+ echo "==================== Deploying wallet to $VM ($EXTERNAL_IP) ===================="
+ ssh-keyscan -t ed25519 -H "$EXTERNAL_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
+ ssh-keyscan -H "$EXTERNAL_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
+ ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 "github@${EXTERNAL_IP}" 'bash -s' < infra/scripts/deploy-prod-wallet-vm-remote.sh
+ done
diff --git a/.gitignore b/.gitignore
index 872d5f6..a8299ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -141,3 +141,12 @@ dist
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vite/
+
+# Local HTTPS certs (mkcert) for host tester
+host/certs/*.pem
+
+# Deploy SSH keypairs (never commit)
+oneshot-wallet-*-deploy
+oneshot-wallet-*-deploy.pub
+*-deploy
+*-deploy.pub
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..885bf23
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,52 @@
+# Agent instructions — 1Shot Embedded Wallet
+
+Branding Layer for the 1Shot Wallet (`wallet.1shotapi.com`). Host → Branding → Signing; never embed `/signer/` from the Host.
+
+## Compatibility policy (until further notice)
+
+Prefer **clean code over backwards compatibility**. Do not add legacy redirects, `@deprecated` aliases, dual APIs, compatibility shims, or parallel code paths “just in case.” When a pattern is renamed or superseded, update all call sites in this repo (and docs/skills) to the new shape — breaking changes are expected and welcome during active development.
+
+## Layout
+
+| Path | Content |
+|------|---------|
+| `/` | Branding Layer (React SPA) |
+| `/signer/` | Signing Layer (`@1shotapi/ows-signer`) |
+| `src/lib/types/primitives/` | Wallet-local branded types (one file each) |
+| `src/lib/types/enum/` | Domain enums (`EAssetType`, `EWalletEventKind`, …) |
+| `src/lib/types/business/` | Domain DTOs (e.g. `KnownAsset`, `TrackedAsset`) |
+| `src/lib/types/events/` | Domain event classes (one file each) |
+| `src/lib/interfaces/{business,data,utils}/` | Layer interfaces |
+| `src/lib/implementations/{business,data,utils}/` | Layer implementations |
+| `src/assets/` | Static media only (SVGs, images) |
+
+Test Host Layer: `host/` (`npm run dev:host`). Style via Host RPC `setStyle`, not in-wallet debug knobs.
+
+### Form validation UX
+
+Primary submit actions (e.g. Send in `TransferTokensModal`) stay **disabled until every required field is valid**. Do not leave the button enabled and only reject on click. Empty fields show no error text; invalid non-empty input shows inline errors; the CTA enables only when the whole form is ready.
+
+### Passkey ceremony overlays
+
+Every WebAuthn prompt (Signing Layer PRF via `OWSSigner`, or Relayer `createRelayerAssertion`) must show the non-interactive `PasskeyPromptModal` for the duration of the ceremony—no Continue/Done. New ceremony entry points go through `withPasskeyPrompt` or the wrapped signer from `wrapSignerWithPasskeyPrompts` (applied once after `OWSSigner.create` in `WalletProvider`). Copy lives under `style.copy.passkeyPrompt`.
+
+### User-facing copy (`setStyle`)
+
+When adding or changing UI strings:
+
+1. Wire them through `style.copy` (defaults, types, and `registerSetStyle` Zod schema) so hosts can override via `setStyle`.
+2. Expose the same keys in **both** WalletConfigurator playgrounds: `host/` in this repo and `app/playground/` in **1Shot-API-Website-New** (`styleForm.ts` + `WalletConfigurator.tsx`).
+
+### Domain layers (assets example)
+
+- **Utils:** `IBlockchainProvider` / `AddressUtils` (from `@1shotapi/ows-wallet-utils`) / `DemoChainsBlockchainProvider`, `IEventBus` / `EventBus`, `ITransactionUtils` / `TransactionUtils`
+- **Data:** `IKnownAssetRepository`, `ITrackedAssetRepository`, `IOneshotRelayerRepository` (`src/lib`) and their implementations
+- **Business:** services that orchestrate domain logic (add as needed)
+
+`IOneshotRelayerRepository.sendTransaction` owns prepare + passkey sign + broadcast (interim: `eth_sendRawTransaction`). Host EIP-1193 sends go SignHelper → branding `approveAndSignTransaction` (ConfirmTransfer / SendTransaction consent) → relayer. In-wallet Send uses `TransferTokensModal` → `WalletProvider.sendTransaction` → relayer, then `SentTransactionModal` (hash + explorer link). Host-driven sends do not show that confirmation — the host surfaces the hash itself.
+
+## Branded types
+
+Prefer branded primitives from `@1shotapi/ows-types` when a shared type already exists. For **wallet-local** branded types (e.g. `TrackedAssetId`), put each brand in its **own file** under `src/lib/types/primitives/` (type alias + `make()` constructor, same pattern as `ows-types` primitives) — do not declare brands inline in DTO modules.
+
+**Trust brands after construction.** Validate shape (`string` / `number` / `boolean`, regex, etc.) **before** wrapping with a branded constructor (`EVMAccountAddress(...)`, `makeTrackedAssetId(...)`, …). Once a value is branded, compare and pass it as that type — do **not** coerce with `String(...)`, `Number(...)`, or similar for identity checks (`===`) or Map/Set keys. Coercion hides type changes (e.g. id becoming a `number`) and creates runtime bugs that are hard to track down. Brand subtypes of `string`/`number` remain assignable to the underlying primitive where an API truly needs it (display, JSON fields typed as `string`).
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..b2f1ac9
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,26 @@
+# Build static Branding Layer + Signing Layer assets
+FROM node:22-alpine AS build
+
+WORKDIR /app
+
+COPY package.json package-lock.json ./
+COPY host/package.json ./host/
+RUN npm ci
+
+COPY index.html vite.config.ts tsconfig.json tsconfig.node.json components.json ./
+COPY src ./src
+COPY scripts ./scripts
+COPY signer-static ./signer-static
+
+RUN npm run build
+
+# Serve with nginx
+FROM nginx:1.27-alpine
+
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+COPY --from=build /app/dist/ /usr/share/nginx/html/
+
+EXPOSE 80
+
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD wget -qO- http://127.0.0.1/ >/dev/null || exit 1
diff --git a/README.md b/README.md
index 85eabd8..a406460 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,116 @@
# embedded-wallet
-1Shot API's free, permissionless embedded wallet. Includes passkey based verification and OIDv4 credential managment for shared KYC
+
+1Shot API's free, permissionless embedded wallet. Includes passkey-based verification and OID4 credential management for shared KYC.
+
+This repo is the **OWS Branding Layer** for the 1Shot Wallet — a frontend-only static app (React + Vite + Tailwind + shadcn/ui) hosted at `wallet.1shotapi.com`.
+
+```
+Host Layer (integrator dapp)
+ └── This app / Branding Layer (@1shotapi/ows-wallet-utils)
+ └── /signer/ Signing Layer (@1shotapi/ows-signer)
+```
+
+## Stack
+
+| Path | Content |
+|------|---------|
+| `/` | React Branding Layer (Vite bundle) |
+| `/signer/` | Static `@1shotapi/ows-signer` ES modules |
+
+Production deliverable: a static **nginx** Docker image (no server-side runtime).
+
+## Setup
+
+```bash
+npm install
+cp .env.example .env # set NGROK_AUTHTOKEN (and optional NGROK_DOMAIN)
+```
+
+## Develop
+
+```bash
+npm run dev # Branding Layer + ngrok HTTPS tunnel
+npm run dev:local # Branding Layer, local HTTP only
+npm run dev:host # Test Host Layer (setStyle knobs + EIP-1193)
+```
+
+| Service | Local URL |
+|---------|-----------|
+| Branding (`/`) | http://localhost:5174/ |
+| Signing (`/signer/`) | http://localhost:5174/signer/ |
+| Test host | http://localhost:5173 |
+
+Passkeys need HTTPS — use the printed ngrok wallet URL as the host iframe source (`NGROK_DOMAIN` in `.env` is picked up by `dev:host`).
+
+Style testing: use the **Style (setStyle RPC)** panel on the test host (`host/`), not in-wallet debug UI. See [host/README.md](host/README.md).
+
+## Host integration
+
+Production iframe URL: **`https://wallet.1shotapi.com/`**
+
+```bash
+npm install @1shotapi/ows-provider
+```
+
+```typescript
+import { OWSProxy } from "@1shotapi/ows-provider";
+
+const proxy = await OWSProxy.create(container, "https://wallet.1shotapi.com/");
+
+await proxy.rpc("setStyle", {
+ copy: { productName: "Acme Wallet", tagline: "Powered by 1Shot" },
+ theme: { primary: "oklch(0.45 0.18 250)" },
+});
+
+proxy.showWallet();
+```
+
+### `setStyle` (custom RPC)
+
+Additive merge of theme CSS variables + copy. Safe to call repeatedly. Schema is Zod-strict (unknown keys rejected). Full field list: [skills/oneshot-embedded-wallet/SKILL.md](skills/oneshot-embedded-wallet/SKILL.md).
+
+### Agent skill
+
+Integrators / coding agents:
+
+```bash
+npx skills add 1Shot-API/embedded-wallet@oneshot-embedded-wallet
+# or from a sibling clone:
+npx skills add ../embedded-wallet --skill oneshot-embedded-wallet
+```
+
+Source: [skills/oneshot-embedded-wallet](skills/oneshot-embedded-wallet/).
+
+## Build
+
+```bash
+npm run build # dist/ (branding) + dist/signer
+npm run preview # preview production wallet build
+```
+
+## Docker
+
+```bash
+docker build -t oneshot-wallet .
+docker run --rm -p 8080:80 oneshot-wallet
+# open http://localhost:8080/
+# signer at http://localhost:8080/signer/
+```
+
+## Scripts
+
+| Script | Purpose |
+|--------|---------|
+| `dev` / `dev:local` | Dev server (± ngrok) |
+| `dev:host` | Test Host Layer |
+| `build` | Typecheck + Vite build + copy signer from `node_modules` |
+| `clean` | Remove `dist/` |
+| `lint` | `tsc --noEmit` |
+
+## Refactor roadmap
+
+See [roadmap.md](roadmap.md) (ShadCN + customization phases).
+
+## Status
+
+Functional Branding Layer (passkey unlock, EIP-1193, signing consent, recovery, credentials) with Phase 0–1 style foundations (`setStyle` + StyleProvider + shell). ShadCN UI migration in progress.
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..5c23ec4
--- /dev/null
+++ b/components.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "radix-nova",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/index.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "iconLibrary": "lucide",
+ "rtl": false,
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "menuColor": "default",
+ "menuAccent": "subtle",
+ "registries": {}
+}
diff --git a/host/README.md b/host/README.md
new file mode 100644
index 0000000..1f1be8c
--- /dev/null
+++ b/host/README.md
@@ -0,0 +1,36 @@
+# Test Host Layer
+
+Local Host Layer for exercising Host ↔ Branding communication against this repo’s wallet (served at domain root).
+
+Adapted from OWS [`examples/host`](https://github.com/1Shot-API/open-wallet/tree/main/examples/host), plus a **Style** panel that calls `proxy.rpc("setStyle", options)`.
+
+## Run
+
+From the repo root (after `npm install`):
+
+```bash
+# Terminal 1 — Branding Layer (prefer ngrok for passkeys)
+npm run dev
+
+# Terminal 2 — this host
+npm run dev:host
+```
+
+Open the printed host URL (default `http://localhost:5173`). The iframe URL comes from:
+
+1. `WALLET_IFRAME_URL` if set
+2. else `https://{NGROK_DOMAIN}/` when `NGROK_DOMAIN` is set in root `.env`
+3. else `http://localhost:5174/`
+
+## HTTPS (passkeys)
+
+If the wallet iframe is HTTPS (ngrok), the host page should also be HTTPS for a secure-context ancestor chain:
+
+```bash
+mkcert -install
+mkcert -cert-file host/certs/dev-cert.pem \
+ -key-file host/certs/dev-key.pem \
+ localhost 127.0.0.1
+```
+
+Certs present under `host/certs/` enable HTTPS automatically; or set `HOST_HTTPS=1`.
diff --git a/host/certs/.gitkeep b/host/certs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/host/components.json b/host/components.json
new file mode 100644
index 0000000..5c23ec4
--- /dev/null
+++ b/host/components.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "radix-nova",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/index.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "iconLibrary": "lucide",
+ "rtl": false,
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "menuColor": "default",
+ "menuAccent": "subtle",
+ "registries": {}
+}
diff --git a/host/index.html b/host/index.html
new file mode 100644
index 0000000..ba81e23
--- /dev/null
+++ b/host/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+ 1Shot Wallet — Host Playground
+
+
+
+
+
+
diff --git a/host/package.json b/host/package.json
new file mode 100644
index 0000000..6289a1a
--- /dev/null
+++ b/host/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "@1shotapi/oneshot-wallet-host",
+ "version": "0.0.0",
+ "private": true,
+ "description": "Local Host Layer for testing 1Shot Wallet Branding (setStyle + EIP-1193)",
+ "type": "module",
+ "scripts": {
+ "dev": "node scripts/dev.mjs",
+ "build": "tsc -p tsconfig.json --noEmit && vite build",
+ "clean": "node scripts/clean.mjs",
+ "lint": "tsc -p tsconfig.json --noEmit",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@1shotapi/ows-provider": "^0.2.1",
+ "@1shotapi/ows-types": "^0.1.3",
+ "@fontsource-variable/geist": "^5.2.9",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^1.24.0",
+ "radix-ui": "^1.6.2",
+ "react": "^19.2.7",
+ "react-colorful": "^5.8.0",
+ "react-dom": "^19.2.7",
+ "shadcn": "^4.13.0",
+ "tailwind-merge": "^3.6.0",
+ "viem": "^2.55.2"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.3.2",
+ "@types/node": "^22.20.1",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^4.7.0",
+ "dotenv": "^16.4.7",
+ "tailwindcss": "^4.3.2",
+ "tw-animate-css": "^1.4.0",
+ "typescript": "~5.8.0",
+ "vite": "^7.0.4"
+ }
+}
diff --git a/host/public/1Shot-Icon-New.svg b/host/public/1Shot-Icon-New.svg
new file mode 100644
index 0000000..d577373
--- /dev/null
+++ b/host/public/1Shot-Icon-New.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/host/public/favicon.svg b/host/public/favicon.svg
new file mode 100644
index 0000000..cd4a6f0
--- /dev/null
+++ b/host/public/favicon.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/host/scripts/clean.mjs b/host/scripts/clean.mjs
new file mode 100644
index 0000000..512e5e6
--- /dev/null
+++ b/host/scripts/clean.mjs
@@ -0,0 +1,7 @@
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const dist = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../dist");
+fs.rmSync(dist, { recursive: true, force: true });
+console.log("Removed host/dist/");
diff --git a/host/scripts/dev.mjs b/host/scripts/dev.mjs
new file mode 100644
index 0000000..420266c
--- /dev/null
+++ b/host/scripts/dev.mjs
@@ -0,0 +1,46 @@
+/**
+ * Start Host Layer Vite (port 5173 by default).
+ * Loads NGROK_DOMAIN / WALLET_IFRAME_URL from repo root .env.
+ */
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { createServer } from "vite";
+import dotenv from "dotenv";
+import { resolveHttpsOptions, walletIframeUrl } from "../wallet-url.mjs";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const repoRoot = path.resolve(__dirname, "../..");
+dotenv.config({ path: path.join(repoRoot, ".env") });
+
+const preferredPort = Number(process.env.HOST_PORT ?? process.env.PORT ?? 5173);
+
+try {
+ const viteServer = await createServer({
+ configFile: path.join(__dirname, "../vite.config.mjs"),
+ server: {
+ port: preferredPort,
+ strictPort: true,
+ host: "0.0.0.0",
+ },
+ });
+ await viteServer.listen();
+
+ const https = resolveHttpsOptions({
+ certsDir: path.join(__dirname, "../certs"),
+ });
+ const scheme = https ? "https" : "http";
+ const address = viteServer.httpServer?.address();
+ const port =
+ address && typeof address === "object" ? address.port : preferredPort;
+
+ console.log(`1Shot Wallet test host: ${scheme}://localhost:${port}`);
+ console.log(` Branding Layer iframe: ${walletIframeUrl()}`);
+ if (scheme === "http") {
+ console.log(
+ " Tip: for HTTPS wallet (ngrok) + passkeys, serve this host over HTTPS (host/certs + HOST_HTTPS=1).",
+ );
+ }
+} catch (error) {
+ console.error("Failed to start host:", error);
+ process.exit(1);
+}
diff --git a/host/src/App.tsx b/host/src/App.tsx
new file mode 100644
index 0000000..d609a0d
--- /dev/null
+++ b/host/src/App.tsx
@@ -0,0 +1,658 @@
+import { useCallback, useEffect, useRef, useState } from "react";
+import {
+ EWalletPresentationMode,
+ OWSProxy,
+} from "@1shotapi/ows-provider";
+import {
+ EVMAccountAddress,
+ EVMChainId,
+ HexString,
+ type EVMTransactionHash,
+} from "@1shotapi/ows-types";
+import {
+ createPublicClient,
+ custom,
+ encodeFunctionData,
+ erc20Abi,
+ formatUnits,
+ getAddress,
+ isAddress,
+ parseUnits,
+ type Address,
+ type Hex,
+} from "viem";
+import { AppHeader } from "./components/AppHeader";
+import { AppSidebar, type HostMode } from "./components/AppSidebar";
+import { DesignPanel } from "./components/DesignPanel";
+import { TestPanel } from "./components/TestPanel";
+import {
+ HOST_CHAINS,
+ FOCUS_USDC_ARC,
+ FOCUS_USDT_BASE,
+ hostChainMeta,
+ type UsdcMode,
+} from "./components/WalletActions";
+import { SidebarInset, SidebarProvider } from "./components/ui/sidebar";
+import { TooltipProvider } from "./components/ui/tooltip";
+
+const USDC_DECIMALS = 6;
+
+function normalizeChainIdHex(value: string): EVMChainId {
+ return EVMChainId(`0x${BigInt(value).toString(16)}`);
+}
+
+/** MetaMask-like branding panel size (also default in ows-provider). */
+const WALLET_SIZE_X = 360;
+const WALLET_SIZE_Y = 600;
+
+export function App() {
+ const flyoutContainerRef = useRef(null);
+ const proxyRef = useRef(null);
+ /** Last setStyle payload from Design mode — re-applied after Test recreate. */
+ const lastStyleRef = useRef | null>(null);
+ const [previewMount, setPreviewMount] = useState(null);
+
+ const [mode, setMode] = useState("test");
+ const [ready, setReady] = useState(false);
+ const [busy, setBusy] = useState(false);
+ const [chainId, setChainId] = useState(HOST_CHAINS[0].value);
+ const [message, setMessage] = useState("Hello from 1Shot Wallet");
+ const [usdcMode, setUsdcMode] = useState("balance");
+ const [usdcDestination, setUsdcDestination] = useState("");
+ const [usdcAmount, setUsdcAmount] = useState("");
+ const [status, setStatus] = useState("Connecting to wallet…");
+ const [statusIsError, setStatusIsError] = useState(false);
+ const [signature, setSignature] = useState(null);
+ const [usdcOutput, setUsdcOutput] = useState(null);
+ const [txHash, setTxHash] = useState(null);
+ const [txExplorerUrl, setTxExplorerUrl] = useState(null);
+ const [walletVisible, setWalletVisible] = useState(false);
+
+ const reportStatus = useCallback((next: string, isError = false) => {
+ setStatus(next);
+ setStatusIsError(isError);
+ }, []);
+
+ const clearUsdcOutputs = useCallback(() => {
+ setUsdcOutput(null);
+ setTxHash(null);
+ setTxExplorerUrl(null);
+ }, []);
+
+ const refreshChainFromWallet = useCallback(
+ async (proxy: OWSProxy): Promise => {
+ const next = normalizeChainIdHex(
+ await proxy.ethereum.request({ method: "eth_chainId" }),
+ );
+ setChainId(String(next));
+ return next;
+ },
+ [],
+ );
+
+ // Presentation is create-time only. Switching Test ↔ Design destroys and
+ // recreates the proxy against the right container (reparenting breaks Postmate).
+ useEffect(() => {
+ if (mode === "design" && !previewMount) {
+ return;
+ }
+
+ const container =
+ mode === "design" ? previewMount : flyoutContainerRef.current;
+ if (!container) {
+ return;
+ }
+
+ let cancelled = false;
+ setReady(false);
+ setWalletVisible(false);
+ reportStatus("Connecting to wallet…");
+
+ console.info(
+ "[oneshot-wallet-host] creating Branding Layer proxy",
+ mode,
+ __WALLET_IFRAME_URL__,
+ );
+
+ void (async () => {
+ proxyRef.current?.destroy();
+ proxyRef.current = null;
+ container.replaceChildren();
+
+ try {
+ const proxy = await OWSProxy.create(container, __WALLET_IFRAME_URL__, {
+ walletSizeX: WALLET_SIZE_X,
+ walletSizeY: WALLET_SIZE_Y,
+ presentationMode:
+ mode === "design"
+ ? EWalletPresentationMode.Inline
+ : EWalletPresentationMode.Flyout,
+ });
+ if (cancelled) {
+ proxy.destroy();
+ return;
+ }
+ proxyRef.current = proxy;
+
+ if (lastStyleRef.current) {
+ await proxy.rpc("setStyle", lastStyleRef.current);
+ }
+
+ if (cancelled) {
+ proxy.destroy();
+ proxyRef.current = null;
+ return;
+ }
+
+ setReady(true);
+ try {
+ const connectedChain = await refreshChainFromWallet(proxy);
+ reportStatus(
+ mode === "design"
+ ? `Design preview connected on ${connectedChain}. Apply setStyle to refresh.`
+ : `Wallet connected on ${connectedChain}. Enter a message and click Sign.`,
+ );
+ } catch (error) {
+ reportStatus(
+ error instanceof Error ? error.message : "Failed to read chain id",
+ true,
+ );
+ }
+ } catch (error) {
+ if (cancelled) return;
+ console.error("[oneshot-wallet-host] failed to start", error);
+ reportStatus(
+ error instanceof Error
+ ? error.message
+ : "Failed to connect to wallet",
+ true,
+ );
+ }
+ })();
+
+ return () => {
+ cancelled = true;
+ proxyRef.current?.destroy();
+ proxyRef.current = null;
+ };
+ }, [mode, previewMount, refreshChainFromWallet, reportStatus]);
+
+ // Keep the Show/Hide label in sync when the wallet closes itself (× / menu).
+ useEffect(() => {
+ if (mode !== "test") {
+ return;
+ }
+ const container = flyoutContainerRef.current;
+ if (!container) {
+ return;
+ }
+
+ const syncVisibility = () => {
+ const width = container.offsetWidth;
+ const height = container.offsetHeight;
+ // Full flyout ≈ wallet size; hidden is 0×0; RPC passthrough is 1×1.
+ setWalletVisible(width > 32 && height > 32);
+ };
+
+ syncVisibility();
+ const observer = new ResizeObserver(syncVisibility);
+ observer.observe(container);
+ return () => observer.disconnect();
+ }, [mode, ready]);
+
+ const resolveAccount = async (
+ proxy: OWSProxy,
+ ): Promise => {
+ let accounts = await proxy.ethereum.request({ method: "eth_accounts" });
+ if (accounts.length === 0) {
+ accounts = await proxy.ethereum.request({
+ method: "eth_requestAccounts",
+ });
+ }
+ const account = accounts[0];
+ if (!account) {
+ throw new Error("No account returned from wallet");
+ }
+ return account;
+ };
+
+ const handleChainChange = (next: string) => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ const selected = EVMChainId(next as `0x${string}`);
+ setBusy(true);
+ clearUsdcOutputs();
+ void (async () => {
+ try {
+ await proxy.ethereum.request({
+ method: "wallet_switchEthereumChain",
+ params: [{ chainId: selected }],
+ });
+ setChainId(String(selected));
+ reportStatus(`Switched to ${selected}`);
+ } catch (error) {
+ await refreshChainFromWallet(proxy).catch(() => undefined);
+ reportStatus(
+ error instanceof Error ? error.message : "Chain switch failed",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const handleRefreshChain = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ setBusy(true);
+ void (async () => {
+ try {
+ const next = await refreshChainFromWallet(proxy);
+ reportStatus(`Chain synced: ${next}`);
+ } catch (error) {
+ reportStatus(
+ error instanceof Error ? error.message : "Failed to refresh chain",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const handleUsdcModeChange = (next: UsdcMode) => {
+ setUsdcMode(next);
+ clearUsdcOutputs();
+ };
+
+ const handleSign = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ const trimmed = message.trim();
+ if (!trimmed) {
+ reportStatus("Enter a message to sign.", true);
+ return;
+ }
+
+ setBusy(true);
+ setSignature(null);
+ reportStatus("Requesting accounts…");
+
+ void (async () => {
+ try {
+ const account = await resolveAccount(proxy);
+ reportStatus("Approve the passkey prompt to sign…");
+ const nextSignature = await proxy.ethereum.request({
+ method: "personal_sign",
+ params: [trimmed, account],
+ });
+ setSignature(nextSignature);
+ reportStatus(`Signed as ${account}`);
+ } catch (error) {
+ reportStatus(
+ error instanceof Error ? error.message : "Signing failed",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const handleCheckUsdcBalance = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ const meta = hostChainMeta(chainId);
+ if (!meta) {
+ reportStatus("USDC is not configured for this chain.", true);
+ clearUsdcOutputs();
+ return;
+ }
+
+ setBusy(true);
+ clearUsdcOutputs();
+ reportStatus("Reading USDC balance…");
+
+ void (async () => {
+ try {
+ const token = getAddress(meta.usdc) as Address;
+ const account = await resolveAccount(proxy);
+ const owner = getAddress(account) as Address;
+ const client = createPublicClient({
+ transport: custom(proxy.ethereum),
+ });
+
+ const [name, symbol, balance, decimals] = await Promise.all([
+ client.readContract({
+ address: token,
+ abi: erc20Abi,
+ functionName: "name",
+ }),
+ client.readContract({
+ address: token,
+ abi: erc20Abi,
+ functionName: "symbol",
+ }),
+ client.readContract({
+ address: token,
+ abi: erc20Abi,
+ functionName: "balanceOf",
+ args: [owner],
+ }),
+ client.readContract({
+ address: token,
+ abi: erc20Abi,
+ functionName: "decimals",
+ }),
+ ]);
+
+ setUsdcOutput(
+ [
+ `Contract: ${token}`,
+ `Account: ${owner}`,
+ `Name: ${name}`,
+ `Symbol: ${symbol}`,
+ `Balance: ${formatUnits(balance, decimals)} ${symbol}`,
+ `Raw: ${balance.toString()}`,
+ ].join("\n"),
+ );
+ reportStatus(
+ `Balance for ${symbol}: ${formatUnits(balance, decimals)}`,
+ );
+ } catch (error) {
+ reportStatus(
+ error instanceof Error
+ ? error.message
+ : "Failed to read USDC balance",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const handleSendUsdc = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ const meta = hostChainMeta(chainId);
+ if (!meta) {
+ reportStatus("USDC is not configured for this chain.", true);
+ clearUsdcOutputs();
+ return;
+ }
+
+ const destinationRaw = usdcDestination.trim();
+ if (!isAddress(destinationRaw)) {
+ reportStatus("Enter a valid destination address.", true);
+ clearUsdcOutputs();
+ return;
+ }
+
+ let amount: bigint;
+ try {
+ amount = parseUnits(usdcAmount.trim(), USDC_DECIMALS);
+ } catch {
+ reportStatus("Enter a valid USDC amount.", true);
+ clearUsdcOutputs();
+ return;
+ }
+ if (amount <= 0n) {
+ reportStatus("Amount must be greater than zero.", true);
+ clearUsdcOutputs();
+ return;
+ }
+
+ setBusy(true);
+ clearUsdcOutputs();
+ reportStatus("Preparing USDC transfer…");
+
+ void (async () => {
+ try {
+ const account = await resolveAccount(proxy);
+ const to = getAddress(destinationRaw) as Address;
+ const data = encodeFunctionData({
+ abi: erc20Abi,
+ functionName: "transfer",
+ args: [to, amount],
+ }) as Hex;
+ const activeChainId = EVMChainId(chainId as `0x${string}`);
+
+ reportStatus("Approve the transaction in the wallet…");
+ const hash = (await proxy.ethereum.request({
+ method: "eth_sendTransaction",
+ params: [
+ {
+ from: account,
+ to: EVMAccountAddress(meta.usdc as `0x${string}`),
+ data: HexString(data),
+ value: HexString("0x0"),
+ chainId: activeChainId,
+ },
+ ],
+ })) as EVMTransactionHash;
+
+ setUsdcOutput(`Transaction hash:\n${hash}`);
+ setTxHash(hash);
+ setTxExplorerUrl(`${meta.blockExplorerUrl}/tx/${hash}`);
+ reportStatus(`USDC sent. Hash ${hash}`);
+ } catch (error) {
+ reportStatus(
+ error instanceof Error ? error.message : "Failed to send USDC",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const handleUsdcAction = () => {
+ if (usdcMode === "send") {
+ handleSendUsdc();
+ } else {
+ handleCheckUsdcBalance();
+ }
+ };
+
+ const handleToggleWallet = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+
+ if (walletVisible) {
+ proxy.hideWallet();
+ setWalletVisible(false);
+ reportStatus("Wallet panel hidden.");
+ return;
+ }
+
+ proxy.showWallet();
+ setWalletVisible(true);
+ reportStatus("Wallet panel shown. Use Hide Wallet or the wallet menu to close.");
+ };
+
+ const handleApplyStyle = async (options: Record) => {
+ lastStyleRef.current = options;
+ const proxy = proxyRef.current;
+ if (!proxy) {
+ throw new Error("Wallet not connected");
+ }
+ await proxy.rpc("setStyle", options);
+ };
+
+ const handleFocusUsdcArc = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ setBusy(true);
+ reportStatus("Focusing wallet on Arc USDC…");
+ void (async () => {
+ try {
+ await proxy.rpc("focusWallet", {
+ chainId: FOCUS_USDC_ARC.chainId,
+ assetAddress: FOCUS_USDC_ARC.assetAddress,
+ });
+ setChainId(FOCUS_USDC_ARC.chainId);
+ proxy.showWallet();
+ setWalletVisible(true);
+ reportStatus("Wallet focused on USDC (Arc Testnet).");
+ } catch (error) {
+ reportStatus(
+ error instanceof Error ? error.message : "focusWallet failed",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const handleFocusUsdtBase = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ setBusy(true);
+ reportStatus("Focusing wallet on Base USDT…");
+ void (async () => {
+ try {
+ await proxy.rpc("focusWallet", {
+ chainId: FOCUS_USDT_BASE.chainId,
+ assetAddress: FOCUS_USDT_BASE.assetAddress,
+ });
+ setChainId(FOCUS_USDT_BASE.chainId);
+ proxy.showWallet();
+ setWalletVisible(true);
+ reportStatus("Wallet focused on Tether (Base).");
+ } catch (error) {
+ reportStatus(
+ error instanceof Error ? error.message : "focusWallet failed",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const handleUnfocusWallet = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ setBusy(true);
+ reportStatus("Restoring general wallet mode…");
+ void (async () => {
+ try {
+ await proxy.rpc("unfocusWallet");
+ reportStatus("Wallet restored to general mode.");
+ } catch (error) {
+ reportStatus(
+ error instanceof Error ? error.message : "unfocusWallet failed",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const handleAddUsdcArc = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ setBusy(true);
+ reportStatus("Requesting add Arc USDC…");
+ void (async () => {
+ try {
+ await proxy.rpc("addAsset", {
+ chainId: FOCUS_USDC_ARC.chainId,
+ assetAddress: FOCUS_USDC_ARC.assetAddress,
+ });
+ proxy.showWallet();
+ setWalletVisible(true);
+ reportStatus("Arc USDC added to tracked assets.");
+ } catch (error) {
+ reportStatus(
+ error instanceof Error ? error.message : "addAsset failed",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const handleAddUsdtBase = () => {
+ const proxy = proxyRef.current;
+ if (!proxy) return;
+ setBusy(true);
+ reportStatus("Requesting add Base USDT…");
+ void (async () => {
+ try {
+ await proxy.rpc("addAsset", {
+ chainId: FOCUS_USDT_BASE.chainId,
+ assetAddress: FOCUS_USDT_BASE.assetAddress,
+ });
+ proxy.showWallet();
+ setWalletVisible(true);
+ reportStatus("Base USDT added to tracked assets.");
+ } catch (error) {
+ reportStatus(
+ error instanceof Error ? error.message : "addAsset failed",
+ true,
+ );
+ } finally {
+ setBusy(false);
+ }
+ })();
+ };
+
+ const walletActionProps = {
+ ready,
+ busy,
+ chainId,
+ message,
+ usdcMode,
+ usdcDestination,
+ usdcAmount,
+ status,
+ statusIsError,
+ signature,
+ usdcOutput,
+ txHash,
+ txExplorerUrl,
+ onChainChange: handleChainChange,
+ onRefreshChain: handleRefreshChain,
+ onMessageChange: setMessage,
+ onUsdcModeChange: handleUsdcModeChange,
+ onUsdcDestinationChange: setUsdcDestination,
+ onUsdcAmountChange: setUsdcAmount,
+ onSign: handleSign,
+ walletVisible,
+ onToggleWallet: handleToggleWallet,
+ onUsdcAction: handleUsdcAction,
+ onFocusUsdcArc: handleFocusUsdcArc,
+ onFocusUsdtBase: handleFocusUsdtBase,
+ onUnfocusWallet: handleUnfocusWallet,
+ onAddUsdcArc: handleAddUsdcArc,
+ onAddUsdtBase: handleAddUsdtBase,
+ };
+
+ return (
+
+
+
+
+
+ {mode === "test" ? (
+
+ ) : (
+
+ )}
+
+ {/* Flyout create() target — never reparented; Test mode only. */}
+
+
+
+ );
+}
diff --git a/host/src/components/AppHeader.tsx b/host/src/components/AppHeader.tsx
new file mode 100644
index 0000000..e45b5e9
--- /dev/null
+++ b/host/src/components/AppHeader.tsx
@@ -0,0 +1,36 @@
+import { SidebarTrigger } from "@/components/ui/sidebar";
+import { Separator } from "@/components/ui/separator";
+
+export function AppHeader() {
+ return (
+
+ );
+}
diff --git a/host/src/components/AppSidebar.tsx b/host/src/components/AppSidebar.tsx
new file mode 100644
index 0000000..b7da61c
--- /dev/null
+++ b/host/src/components/AppSidebar.tsx
@@ -0,0 +1,84 @@
+import { FlaskConicalIcon, PaletteIcon } from "lucide-react";
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarRail,
+} from "@/components/ui/sidebar";
+
+export type HostMode = "test" | "design";
+
+export interface IAppSidebarProps {
+ mode: HostMode;
+ onModeChange: (mode: HostMode) => void;
+}
+
+const NAV_ITEMS: {
+ mode: HostMode;
+ label: string;
+ description: string;
+ icon: typeof FlaskConicalIcon;
+}[] = [
+ {
+ mode: "test",
+ label: "Test",
+ description: "Sign, switch chain, USDC balance and send",
+ icon: FlaskConicalIcon,
+ },
+ {
+ mode: "design",
+ label: "Design",
+ description: "Live setStyle playground",
+ icon: PaletteIcon,
+ },
+];
+
+export function AppSidebar({ mode, onModeChange }: IAppSidebarProps) {
+ return (
+
+
+
+ Host
+
+
+
+
+ Modes
+
+
+ {NAV_ITEMS.map((item) => (
+
+ onModeChange(item.mode)}
+ >
+
+ {item.label}
+
+
+ ))}
+
+
+
+
+ Hint
+
+
+ {mode === "test"
+ ? "Wallet stays hidden. Use actions below to exercise EIP-1193."
+ : "Configurator on the left; wallet flyout stays open on the right."}
+
+
+
+
+
+
+ );
+}
diff --git a/host/src/components/ColorPickerField.tsx b/host/src/components/ColorPickerField.tsx
new file mode 100644
index 0000000..6aa69b0
--- /dev/null
+++ b/host/src/components/ColorPickerField.tsx
@@ -0,0 +1,79 @@
+import { HexColorPicker } from "react-colorful";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+
+function normalizeHex(value: string): string | null {
+ const trimmed = value.trim();
+ if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();
+ if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {
+ const [, r, g, b] = trimmed;
+ return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();
+ }
+ return null;
+}
+
+export interface IColorPickerFieldProps {
+ id: string;
+ label: string;
+ value: string;
+ onChange: (value: string) => void;
+}
+
+/**
+ * Hex color field with a Popover + react-colorful canvas
+ * (shadcn.io color-picker alternative for our host ShadCN setup).
+ */
+export function ColorPickerField({
+ id,
+ label,
+ value,
+ onChange,
+}: IColorPickerFieldProps) {
+ const hex = normalizeHex(value) ?? "#000000";
+
+ return (
+
+
{label}
+
+
+
+
+
+
+
+
+ {
+ onChange(next);
+ }}
+ />
+
+
+
onChange(event.target.value)}
+ />
+
+
+ );
+}
diff --git a/host/src/components/DesignPanel.tsx b/host/src/components/DesignPanel.tsx
new file mode 100644
index 0000000..c95101b
--- /dev/null
+++ b/host/src/components/DesignPanel.tsx
@@ -0,0 +1,82 @@
+import type { RefCallback } from "react";
+import { WalletConfigurator } from "./WalletConfigurator";
+
+export interface IDesignPanelProps {
+ ready: boolean;
+ onApplyStyle: (options: Record) => Promise;
+ /** Create() container for inline OWSProxy (never reparent the iframe). */
+ previewMountRef: RefCallback;
+}
+
+/**
+ * Design mode: configurator (left) + live inline wallet preview (right).
+ * Host destroys/recreates the proxy into this mount with presentationMode=inline.
+ */
+export function DesignPanel({
+ ready,
+ onApplyStyle,
+ previewMountRef,
+}: IDesignPanelProps) {
+ return (
+
+
+
+
+
+
+
+ Preview
+
+
+ Fresh inline proxy in this slot. Styles are re-applied via{" "}
+ setStyle when you return to Test.
+
+
+
+ Design mode
+
+
+
+
+
+
+
+
+
+ Host canvas · inline presentation
+
+
+
+
+
+
+ {!ready ? (
+
+ Connecting to wallet…
+
+ ) : null}
+
+
+
+
+
+ );
+}
diff --git a/host/src/components/TestPanel.tsx b/host/src/components/TestPanel.tsx
new file mode 100644
index 0000000..27cae33
--- /dev/null
+++ b/host/src/components/TestPanel.tsx
@@ -0,0 +1,28 @@
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { WalletActions, type IWalletActionsProps } from "./WalletActions";
+
+/** Test mode: EIP-1193 actions with the branding wallet hidden. */
+export function TestPanel(props: IWalletActionsProps) {
+ return (
+
+
+
+ Test
+
+ Exercise connect, personal_sign, chain switch, and ERC-20 reads.
+ The wallet flyout stays hidden until an action needs it.
+
+
+
+
+
+
+
+ );
+}
diff --git a/host/src/components/WalletActions.tsx b/host/src/components/WalletActions.tsx
new file mode 100644
index 0000000..0f76771
--- /dev/null
+++ b/host/src/components/WalletActions.tsx
@@ -0,0 +1,349 @@
+import { RefreshCwIcon } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Textarea } from "@/components/ui/textarea";
+
+export const HOST_CHAINS = [
+ {
+ value: "0x4cef52",
+ label: "Arc Testnet",
+ usdc: "0x3600000000000000000000000000000000000000",
+ blockExplorerUrl: "https://testnet.arcscan.app",
+ },
+ {
+ value: "0xaa36a7",
+ label: "Sepolia",
+ usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
+ blockExplorerUrl: "https://sepolia.etherscan.io",
+ },
+ {
+ value: "0x14a34",
+ label: "Base Sepolia",
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
+ blockExplorerUrl: "https://sepolia.basescan.org",
+ },
+] as const;
+
+/** Focus demo: Arc Testnet USDC. */
+export const FOCUS_USDC_ARC = {
+ chainId: "0x4cef52",
+ assetAddress: "0x3600000000000000000000000000000000000000",
+ label: "USDC (Arc)",
+} as const;
+
+/** Focus demo: Base mainnet USDT. */
+export const FOCUS_USDT_BASE = {
+ chainId: "0x2105",
+ assetAddress: "0xfde4c96c8593536e31f229ea8f37b2ada2699bb2",
+ label: "Tether (Base)",
+} as const;
+
+export type UsdcMode = "balance" | "send";
+
+export function hostChainMeta(chainId: string) {
+ return HOST_CHAINS.find((chain) => chain.value === chainId) ?? null;
+}
+
+export interface IWalletActionsProps {
+ ready: boolean;
+ busy: boolean;
+ chainId: string;
+ message: string;
+ usdcMode: UsdcMode;
+ usdcDestination: string;
+ usdcAmount: string;
+ status: string;
+ statusIsError: boolean;
+ signature: string | null;
+ usdcOutput: string | null;
+ txHash: string | null;
+ txExplorerUrl: string | null;
+ onChainChange: (chainId: string) => void;
+ onRefreshChain: () => void;
+ onMessageChange: (message: string) => void;
+ onUsdcModeChange: (mode: UsdcMode) => void;
+ onUsdcDestinationChange: (address: string) => void;
+ onUsdcAmountChange: (amount: string) => void;
+ onSign: () => void;
+ walletVisible: boolean;
+ onToggleWallet: () => void;
+ onUsdcAction: () => void;
+ onFocusUsdcArc: () => void;
+ onFocusUsdtBase: () => void;
+ onUnfocusWallet: () => void;
+ onAddUsdcArc: () => void;
+ onAddUsdtBase: () => void;
+}
+
+export function WalletActions({
+ ready,
+ busy,
+ chainId,
+ message,
+ usdcMode,
+ usdcDestination,
+ usdcAmount,
+ status,
+ statusIsError,
+ signature,
+ usdcOutput,
+ txHash,
+ txExplorerUrl,
+ onChainChange,
+ onRefreshChain,
+ onMessageChange,
+ onUsdcModeChange,
+ onUsdcDestinationChange,
+ onUsdcAmountChange,
+ onSign,
+ walletVisible,
+ onToggleWallet,
+ onUsdcAction,
+ onFocusUsdcArc,
+ onFocusUsdtBase,
+ onUnfocusWallet,
+ onAddUsdcArc,
+ onAddUsdtBase,
+}: IWalletActionsProps) {
+ const meta = hostChainMeta(chainId);
+
+ return (
+
+
+
+ Chain
+
+ {
+ if (value) onChainChange(value);
+ }}
+ >
+
+
+
+
+ {HOST_CHAINS.map((chain) => (
+
+ {chain.label}
+
+ ))}
+ {!HOST_CHAINS.some((chain) => chain.value === chainId) &&
+ chainId ? (
+ {chainId}
+ ) : null}
+
+
+
+
+
+
+
+
+ Message to sign (EIP-191)
+
+
+
+
+ Sign
+
+
+ {walletVisible ? "Hide Wallet" : "Show Wallet"}
+
+
+
+
+ {status}
+
+
+ {signature ? (
+
+ {signature}
+
+ ) : null}
+
+
+
+
+
USDC
+
{
+ if (value === "balance" || value === "send") {
+ onUsdcModeChange(value);
+ }
+ }}
+ >
+
+
+
+
+ Check Balance
+ Send USDC
+
+
+
+ {meta ? `USDC: ${meta.usdc}` : "USDC: unsupported chain"}
+
+
+
+ {usdcMode === "send" ? (
+ <>
+
+ Destination
+
+ onUsdcDestinationChange(event.target.value)
+ }
+ />
+
+
+ Amount (USDC)
+ onUsdcAmountChange(event.target.value)}
+ />
+
+ >
+ ) : null}
+
+
+
+ {usdcMode === "send" ? "Send USDC" : "Check Balance"}
+
+
+
+ {usdcOutput ? (
+
+ {usdcOutput}
+
+ ) : null}
+
+ {txHash && txExplorerUrl ? (
+
+
+ View on explorer
+
+
+ ) : null}
+
+
+
+
+
Focus mode
+
+ Host-controlled shell: lock the wallet to one chain + asset, or restore
+ general mode.
+
+
+
+ Focus USDC (Arc)
+
+
+ Focus Tether (Base)
+
+
+ Unfocus
+
+
+
+
+
+
Add asset
+
+ Propose a tracked asset via addAsset (user must confirm
+ in the wallet).
+
+
+
+ Add Arc USDC
+
+
+ Add Base USDT
+
+
+
+
+ );
+}
diff --git a/host/src/components/WalletConfigurator.tsx b/host/src/components/WalletConfigurator.tsx
new file mode 100644
index 0000000..cc0678f
--- /dev/null
+++ b/host/src/components/WalletConfigurator.tsx
@@ -0,0 +1,810 @@
+import { useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import { Textarea } from "@/components/ui/textarea";
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@/components/ui/accordion";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { ColorPickerField } from "./ColorPickerField";
+import {
+ ACME_PRESET,
+ DEFAULTS_PRESET,
+ OCEAN_PRESET,
+ buildSetStylePayload,
+ type IStyleFormState,
+} from "../styleForm";
+
+export interface IWalletConfiguratorProps {
+ /** When false, apply/presets are disabled (wallet not connected yet). */
+ ready: boolean;
+ onApply: (options: Record) => Promise;
+}
+
+function TextField({
+ id,
+ label,
+ value,
+ onChange,
+ mono = false,
+}: {
+ id: string;
+ label: string;
+ value: string;
+ onChange: (value: string) => void;
+ mono?: boolean;
+}) {
+ return (
+
+ {label}
+ onChange(event.target.value)}
+ />
+
+ );
+}
+
+function BodyField({
+ id,
+ label,
+ value,
+ onChange,
+}: {
+ id: string;
+ label: string;
+ value: string;
+ onChange: (value: string) => void;
+}) {
+ return (
+
+ {label}
+
+ );
+}
+
+/**
+ * Host-side style playground for `proxy.rpc("setStyle", …)`.
+ * Tabs: Basic (identity), Style (theme colors), Text (modal copy).
+ */
+export function WalletConfigurator({
+ ready,
+ onApply,
+}: IWalletConfiguratorProps) {
+ const [form, setForm] = useState(ACME_PRESET);
+ const [status, setStatus] = useState("");
+ const [isError, setIsError] = useState(false);
+ const [busy, setBusy] = useState(false);
+
+ const patch = (
+ key: K,
+ value: IStyleFormState[K],
+ ) => {
+ setForm((current) => ({ ...current, [key]: value }));
+ };
+
+ const apply = async (next?: IStyleFormState) => {
+ const payloadForm = next ?? form;
+ if (next) setForm(next);
+ setBusy(true);
+ setIsError(false);
+ setStatus("Calling setStyle…");
+ try {
+ await onApply(buildSetStylePayload(payloadForm));
+ setStatus("setStyle applied.");
+ } catch (error) {
+ setIsError(true);
+ setStatus(error instanceof Error ? error.message : "setStyle failed");
+ } finally {
+ setBusy(false);
+ }
+ };
+
+ return (
+
+
+
+ Basic
+ Style
+ Text
+
+
+
+ patch("logoUrl", value)}
+ />
+ patch("productName", value)}
+ />
+ patch("tagline", value)}
+ />
+
+
+
+ patch("primary", value)}
+ />
+ patch("primaryForeground", value)}
+ />
+ patch("background", value)}
+ />
+ patch("foreground", value)}
+ />
+ patch("muted", value)}
+ />
+ patch("mutedForeground", value)}
+ />
+ patch("border", value)}
+ />
+ patch("accent", value)}
+ />
+ patch("accentForeground", value)}
+ />
+ patch("radius", value)}
+ />
+ patch("fontSans", value)}
+ />
+
+
+ Dark mode
+
+ patch("dark", checked)}
+ />
+
+
+
+
+
+
+ Connect
+
+ patch("connectTitle", value)}
+ />
+ patch("connectBody", value)}
+ />
+ patch("connectContinue", value)}
+ />
+ patch("connectReject", value)}
+ />
+
+
+
+
+ Wallet setup
+
+ patch("setupTitle", value)}
+ />
+ patch("setupBody", value)}
+ />
+ patch("setupCreate", value)}
+ />
+ patch("setupLogin", value)}
+ />
+ patch("setupCancel", value)}
+ />
+
+
+
+
+ Passkey name
+
+ patch("passkeyTitle", value)}
+ />
+ patch("passkeyBody", value)}
+ />
+ patch("passkeyContinue", value)}
+ />
+ patch("passkeyCancel", value)}
+ />
+
+
+
+
+ Personal sign
+
+ patch("signTitle", value)}
+ />
+ patch("signLabel", value)}
+ />
+ patch("signReject", value)}
+ />
+
+
+
+
+ Typed data
+
+ patch("typedTitle", value)}
+ />
+ patch("typedSignLabel", value)}
+ />
+ patch("typedReject", value)}
+ />
+
+
+
+
+ Send transaction
+
+ patch("txTitle", value)}
+ />
+ patch("txSignLabel", value)}
+ />
+ patch("txReject", value)}
+ />
+
+
+
+
+ Credential offer
+
+ patch("credOfferTitle", value)}
+ />
+ patch("credOfferBody", value)}
+ />
+ patch("credOfferAccept", value)}
+ />
+ patch("credOfferReject", value)}
+ />
+
+
+
+
+ Credential presentation
+
+ patch("credPresentTitle", value)}
+ />
+ patch("credPresentBody", value)}
+ />
+ patch("credPresentShare", value)}
+ />
+ patch("credPresentReject", value)}
+ />
+
+
+
+
+ Credentials tab
+
+ patch("credTabLabel", value)}
+ />
+ patch("credEmptyCount", value)}
+ />
+ patch("credCountLabel", value)}
+ />
+ patch("credEmptyBody", value)}
+ />
+ patch("credRefresh", value)}
+ />
+ patch("credView", value)}
+ />
+ patch("credDetailDescription", value)}
+ />
+ patch("credClaimsHeading", value)}
+ />
+ patch("credClose", value)}
+ />
+
+
+
+
+ Balances / Receive
+
+ patch("balTabLabel", value)}
+ />
+ patch("receiveLabel", value)}
+ />
+ patch("receiveTitle", value)}
+ />
+ patch("receiveBody", value)}
+ />
+ patch("receiveAddressLabel", value)}
+ />
+ patch("receiveQrAlt", value)}
+ />
+ patch("receiveCopyLabel", value)}
+ />
+ patch("receiveCopiedLabel", value)}
+ />
+ patch("receiveCopyFailedLabel", value)}
+ />
+ patch("receiveCloseLabel", value)}
+ />
+ patch("sendLabel", value)}
+ />
+ patch("confirmTransferTitle", value)}
+ />
+ patch("confirmTransferBody", value)}
+ />
+ patch("confirmTransferConfirm", value)}
+ />
+ patch("confirmTransferReject", value)}
+ />
+ patch("transferTokensTitle", value)}
+ />
+ patch("transferTokensSend", value)}
+ />
+ patch("transferTokensCancel", value)}
+ />
+ patch("transferTokensSentTitle", value)}
+ />
+
+ patch("transferTokensViewExplorer", value)
+ }
+ />
+ patch("transferTokensDone", value)}
+ />
+
+ patch("passkeyPromptUnlockTitle", value)
+ }
+ />
+
+ patch("passkeyPromptCreateTitle", value)
+ }
+ />
+ patch("passkeyPromptSignTitle", value)}
+ />
+
+ patch("passkeyPromptEncryptTitle", value)
+ }
+ />
+
+ patch("passkeyPromptDecryptTitle", value)
+ }
+ />
+
+ patch("passkeyPromptRelayerTitle", value)
+ }
+ />
+
+ patch("passkeyPromptBackupTitle", value)
+ }
+ />
+
+
+
+
+ Create backup
+
+ patch("backupTitle", value)}
+ />
+ patch("backupBody", value)}
+ />
+ patch("backupContinue", value)}
+ />
+ patch("backupCancel", value)}
+ />
+
+
+
+
+ Restore backup
+
+ patch("restoreTitle", value)}
+ />
+ patch("restoreBody", value)}
+ />
+ patch("restoreLabel", value)}
+ />
+ patch("restoreCancel", value)}
+ />
+
+
+
+
+
+
+
+ {
+ void apply();
+ }}
+ >
+ Apply setStyle
+
+ {
+ void apply(OCEAN_PRESET);
+ }}
+ >
+ Preset: ocean
+
+ {
+ void apply(DEFAULTS_PRESET);
+ }}
+ >
+ Preset: defaults
+
+
+
+
+ {status}
+
+
+ );
+}
diff --git a/host/src/components/ui/accordion.tsx b/host/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..fcfee5c
--- /dev/null
+++ b/host/src/components/ui/accordion.tsx
@@ -0,0 +1,79 @@
+import * as React from "react"
+import { Accordion as AccordionPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+
+function Accordion({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/host/src/components/ui/button.tsx b/host/src/components/ui/button.tsx
new file mode 100644
index 0000000..75b8c3d
--- /dev/null
+++ b/host/src/components/ui/button.tsx
@@ -0,0 +1,67 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
+ outline:
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
+ ghost:
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
+ destructive:
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default:
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
+ icon: "size-8",
+ "icon-xs":
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
+ "icon-sm":
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
+ "icon-lg": "size-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant = "default",
+ size = "default",
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot.Root : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/host/src/components/ui/card.tsx b/host/src/components/ui/card.tsx
new file mode 100644
index 0000000..4458dae
--- /dev/null
+++ b/host/src/components/ui/card.tsx
@@ -0,0 +1,103 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Card({
+ className,
+ size = "default",
+ ...props
+}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
+ return (
+ img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+}
diff --git a/host/src/components/ui/input.tsx b/host/src/components/ui/input.tsx
new file mode 100644
index 0000000..d763cd9
--- /dev/null
+++ b/host/src/components/ui/input.tsx
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/host/src/components/ui/label.tsx b/host/src/components/ui/label.tsx
new file mode 100644
index 0000000..f752f82
--- /dev/null
+++ b/host/src/components/ui/label.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+import { Label as LabelPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps
) {
+ return (
+
+ )
+}
+
+export { Label }
diff --git a/host/src/components/ui/popover.tsx b/host/src/components/ui/popover.tsx
new file mode 100644
index 0000000..0ad11df
--- /dev/null
+++ b/host/src/components/ui/popover.tsx
@@ -0,0 +1,87 @@
+import * as React from "react"
+import { Popover as PopoverPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Popover({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function PopoverTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function PopoverContent({
+ className,
+ align = "center",
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function PopoverAnchor({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
+ return (
+
+ )
+}
+
+function PopoverDescription({
+ className,
+ ...props
+}: React.ComponentProps<"p">) {
+ return (
+
+ )
+}
+
+export {
+ Popover,
+ PopoverAnchor,
+ PopoverContent,
+ PopoverDescription,
+ PopoverHeader,
+ PopoverTitle,
+ PopoverTrigger,
+}
diff --git a/host/src/components/ui/select.tsx b/host/src/components/ui/select.tsx
new file mode 100644
index 0000000..8333850
--- /dev/null
+++ b/host/src/components/ui/select.tsx
@@ -0,0 +1,190 @@
+import * as React from "react"
+import { Select as SelectPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "item-aligned",
+ align = "center",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/host/src/components/ui/separator.tsx b/host/src/components/ui/separator.tsx
new file mode 100644
index 0000000..d457090
--- /dev/null
+++ b/host/src/components/ui/separator.tsx
@@ -0,0 +1,28 @@
+"use client"
+
+import * as React from "react"
+import { Separator as SeparatorPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/host/src/components/ui/sheet.tsx b/host/src/components/ui/sheet.tsx
new file mode 100644
index 0000000..49a6af2
--- /dev/null
+++ b/host/src/components/ui/sheet.tsx
@@ -0,0 +1,145 @@
+import * as React from "react"
+import { Dialog as SheetPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { XIcon } from "lucide-react"
+
+function Sheet({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SheetTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetContent({
+ className,
+ children,
+ side = "right",
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ side?: "top" | "right" | "bottom" | "left"
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+
+ Close
+
+
+ )}
+
+
+ )
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Sheet,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/host/src/components/ui/sidebar.tsx b/host/src/components/ui/sidebar.tsx
new file mode 100644
index 0000000..ea78b50
--- /dev/null
+++ b/host/src/components/ui/sidebar.tsx
@@ -0,0 +1,702 @@
+"use client"
+
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "radix-ui"
+
+import { useIsMobile } from "@/hooks/use-mobile"
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Separator } from "@/components/ui/separator"
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetHeader,
+ SheetTitle,
+} from "@/components/ui/sheet"
+import { Skeleton } from "@/components/ui/skeleton"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+import { PanelLeftIcon } from "lucide-react"
+
+const SIDEBAR_COOKIE_NAME = "sidebar_state"
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = "16rem"
+const SIDEBAR_WIDTH_MOBILE = "18rem"
+const SIDEBAR_WIDTH_ICON = "3rem"
+const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+
+type SidebarContextProps = {
+ state: "expanded" | "collapsed"
+ open: boolean
+ setOpen: (open: boolean) => void
+ openMobile: boolean
+ setOpenMobile: (open: boolean) => void
+ isMobile: boolean
+ toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext(null)
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext)
+ if (!context) {
+ throw new Error("useSidebar must be used within a SidebarProvider.")
+ }
+
+ return context
+}
+
+function SidebarProvider({
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ defaultOpen?: boolean
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+}) {
+ const isMobile = useIsMobile()
+ const [openMobile, setOpenMobile] = React.useState(false)
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen)
+ const open = openProp ?? _open
+ const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === "function" ? value(open) : value
+ if (setOpenProp) {
+ setOpenProp(openState)
+ } else {
+ _setOpen(openState)
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+ },
+ [setOpenProp, open]
+ )
+
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
+ }, [isMobile, setOpen, setOpenMobile])
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
+ (event.metaKey || event.ctrlKey)
+ ) {
+ event.preventDefault()
+ toggleSidebar()
+ }
+ }
+
+ window.addEventListener("keydown", handleKeyDown)
+ return () => window.removeEventListener("keydown", handleKeyDown)
+ }, [toggleSidebar])
+
+ // We add a state so that we can do data-state="expanded" or "collapsed".
+ // This makes it easier to style the sidebar with Tailwind classes.
+ const state = open ? "expanded" : "collapsed"
+
+ const contextValue = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ )
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+function Sidebar({
+ side = "left",
+ variant = "sidebar",
+ collapsible = "offcanvas",
+ className,
+ children,
+ dir,
+ ...props
+}: React.ComponentProps<"div"> & {
+ side?: "left" | "right"
+ variant?: "sidebar" | "floating" | "inset"
+ collapsible?: "offcanvas" | "icon" | "none"
+}) {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+ if (collapsible === "none") {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (isMobile) {
+ return (
+
+
+
+ Sidebar
+ Displays the mobile sidebar.
+
+ {children}
+
+
+ )
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ )
+}
+
+function SidebarTrigger({
+ className,
+ onClick,
+ ...props
+}: React.ComponentProps) {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+ {
+ onClick?.(event)
+ toggleSidebar()
+ }}
+ {...props}
+ >
+
+ Toggle Sidebar
+
+ )
+}
+
+function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+
+ )
+}
+
+function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
+ return (
+
+ )
+}
+
+function SidebarInput({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarGroupLabel({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"div"> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot.Root : "div"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function SidebarGroupAction({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot.Root : "button"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function SidebarGroupContent({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+const sidebarMenuButtonVariants = cva(
+ "peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
+ {
+ variants: {
+ variant: {
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+ outline:
+ "bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
+ },
+ size: {
+ default: "h-8 text-sm",
+ sm: "h-7 text-xs",
+ lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function SidebarMenuButton({
+ asChild = false,
+ isActive = false,
+ variant = "default",
+ size = "default",
+ tooltip,
+ className,
+ ...props
+}: React.ComponentProps<"button"> & {
+ asChild?: boolean
+ isActive?: boolean
+ tooltip?: string | React.ComponentProps
+} & VariantProps) {
+ const Comp = asChild ? Slot.Root : "button"
+ const { isMobile, state } = useSidebar()
+
+ const button = (
+
+ )
+
+ if (!tooltip) {
+ return button
+ }
+
+ if (typeof tooltip === "string") {
+ tooltip = {
+ children: tooltip,
+ }
+ }
+
+ return (
+
+ {button}
+
+
+ )
+}
+
+function SidebarMenuAction({
+ className,
+ asChild = false,
+ showOnHover = false,
+ ...props
+}: React.ComponentProps<"button"> & {
+ asChild?: boolean
+ showOnHover?: boolean
+}) {
+ const Comp = asChild ? Slot.Root : "button"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ showOnHover &&
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function SidebarMenuBadge({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSkeleton({
+ className,
+ showIcon = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showIcon?: boolean
+}) {
+ // Random width between 50 to 90%.
+ const [width] = React.useState(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`
+ })
+
+ return (
+
+ {showIcon && (
+
+ )}
+
+
+ )
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSubItem({
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSubButton({
+ asChild = false,
+ size = "md",
+ isActive = false,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean
+ size?: "sm" | "md"
+ isActive?: boolean
+}) {
+ const Comp = asChild ? Slot.Root : "a"
+
+ return (
+ span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ useSidebar,
+}
diff --git a/host/src/components/ui/skeleton.tsx b/host/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..0118624
--- /dev/null
+++ b/host/src/components/ui/skeleton.tsx
@@ -0,0 +1,13 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/host/src/components/ui/switch.tsx b/host/src/components/ui/switch.tsx
new file mode 100644
index 0000000..877dbc8
--- /dev/null
+++ b/host/src/components/ui/switch.tsx
@@ -0,0 +1,31 @@
+import * as React from "react"
+import { Switch as SwitchPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Switch({
+ className,
+ size = "default",
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+
+
+ )
+}
+
+export { Switch }
diff --git a/host/src/components/ui/tabs.tsx b/host/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..72465b2
--- /dev/null
+++ b/host/src/components/ui/tabs.tsx
@@ -0,0 +1,88 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Tabs as TabsPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Tabs({
+ className,
+ orientation = "horizontal",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+const tabsListVariants = cva(
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
+ {
+ variants: {
+ variant: {
+ default: "bg-muted",
+ line: "gap-1 bg-transparent",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function TabsList({
+ className,
+ variant = "default",
+ ...props
+}: React.ComponentProps &
+ VariantProps) {
+ return (
+
+ )
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
diff --git a/host/src/components/ui/textarea.tsx b/host/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..04d27f7
--- /dev/null
+++ b/host/src/components/ui/textarea.tsx
@@ -0,0 +1,18 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ )
+}
+
+export { Textarea }
diff --git a/host/src/components/ui/tooltip.tsx b/host/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..bb1ea52
--- /dev/null
+++ b/host/src/components/ui/tooltip.tsx
@@ -0,0 +1,57 @@
+"use client"
+
+import * as React from "react"
+import { Tooltip as TooltipPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+
+export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
diff --git a/host/src/env.d.ts b/host/src/env.d.ts
new file mode 100644
index 0000000..e822314
--- /dev/null
+++ b/host/src/env.d.ts
@@ -0,0 +1,3 @@
+///
+
+declare const __WALLET_IFRAME_URL__: string;
diff --git a/host/src/hooks/use-mobile.ts b/host/src/hooks/use-mobile.ts
new file mode 100644
index 0000000..2b0fe1d
--- /dev/null
+++ b/host/src/hooks/use-mobile.ts
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined)
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ }
+ mql.addEventListener("change", onChange)
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ return () => mql.removeEventListener("change", onChange)
+ }, [])
+
+ return !!isMobile
+}
diff --git a/host/src/index.css b/host/src/index.css
new file mode 100644
index 0000000..98cfe03
--- /dev/null
+++ b/host/src/index.css
@@ -0,0 +1,148 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+@import "shadcn/tailwind.css";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --font-heading: "Manrope", "Inter", ui-sans-serif, system-ui, sans-serif;
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
+ --color-chart-5: var(--chart-5);
+ --color-chart-4: var(--chart-4);
+ --color-chart-3: var(--chart-3);
+ --color-chart-2: var(--chart-2);
+ --color-chart-1: var(--chart-1);
+ --color-ring: var(--ring);
+ --color-input: var(--input);
+ --color-border: var(--border);
+ --color-destructive: var(--destructive);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-accent: var(--accent);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-muted: var(--muted);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-secondary: var(--secondary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary: var(--primary);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-popover: var(--popover);
+ --color-card-foreground: var(--card-foreground);
+ --color-card: var(--card);
+ --color-foreground: var(--foreground);
+ --color-background: var(--background);
+ --radius-sm: calc(var(--radius) * 0.6);
+ --radius-md: calc(var(--radius) * 0.8);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) * 1.4);
+ --radius-2xl: calc(var(--radius) * 1.8);
+ --radius-3xl: calc(var(--radius) * 2.2);
+ --radius-4xl: calc(var(--radius) * 2.6);
+}
+
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ /* 1Shot brand (from 1shotapi.com) */
+ --primary: #239aaa;
+ --primary-foreground: #ffffff;
+ --secondary: #f1f5f9;
+ --secondary-foreground: #1e293b;
+ --muted: #f8fafc;
+ --muted-foreground: #64748b;
+ --accent: #e0f2f4;
+ --accent-foreground: #1d7f8d;
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: #e2e8f0;
+ --input: #e2e8f0;
+ --ring: #239aaa;
+ --chart-1: oklch(0.87 0 0);
+ --chart-2: oklch(0.556 0 0);
+ --chart-3: oklch(0.439 0 0);
+ --chart-4: oklch(0.371 0 0);
+ --chart-5: oklch(0.269 0 0);
+ --radius: 0.625rem;
+ --sidebar: #ffffff;
+ --sidebar-foreground: #1e293b;
+ --sidebar-primary: #239aaa;
+ --sidebar-primary-foreground: #ffffff;
+ --sidebar-accent: #e0f2f4;
+ --sidebar-accent-foreground: #1d7f8d;
+ --sidebar-border: #e2e8f0;
+ --sidebar-ring: #239aaa;
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.87 0 0);
+ --chart-2: oklch(0.556 0 0);
+ --chart-3: oklch(0.439 0 0);
+ --chart-4: oklch(0.371 0 0);
+ --chart-5: oklch(0.269 0 0);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ html,
+ body,
+ #root {
+ margin: 0;
+ min-height: 100%;
+ }
+ body {
+ @apply bg-muted text-foreground antialiased;
+ }
+ html {
+ @apply font-sans;
+ }
+}
+
+@layer utilities {
+ .font-heading {
+ font-family: var(--font-heading);
+ }
+
+ .glass-panel {
+ background: rgba(255, 255, 255, 0.8);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ }
+}
diff --git a/host/src/lib/utils.ts b/host/src/lib/utils.ts
new file mode 100644
index 0000000..a5ef193
--- /dev/null
+++ b/host/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/host/src/main.tsx b/host/src/main.tsx
new file mode 100644
index 0000000..b3f554a
--- /dev/null
+++ b/host/src/main.tsx
@@ -0,0 +1,11 @@
+import { createRoot } from "react-dom/client";
+import { App } from "./App";
+import "./index.css";
+
+const root = document.getElementById("root");
+if (!root) {
+ throw new Error("#root not found");
+}
+
+// No StrictMode: OWSProxy.create is a one-shot iframe handshake.
+createRoot(root).render( );
diff --git a/host/src/styleForm.ts b/host/src/styleForm.ts
new file mode 100644
index 0000000..4a13df0
--- /dev/null
+++ b/host/src/styleForm.ts
@@ -0,0 +1,529 @@
+/** Flat form state for the host style playground. */
+export interface IStyleFormState {
+ // Basic
+ logoUrl: string;
+ productName: string;
+ tagline: string;
+
+ // Style (colors + chrome)
+ primary: string;
+ primaryForeground: string;
+ background: string;
+ foreground: string;
+ muted: string;
+ mutedForeground: string;
+ border: string;
+ accent: string;
+ accentForeground: string;
+ radius: string;
+ fontSans: string;
+ dark: boolean;
+
+ // Text — Connect
+ connectTitle: string;
+ connectBody: string;
+ connectContinue: string;
+ connectReject: string;
+
+ // Text — Wallet setup
+ setupTitle: string;
+ setupBody: string;
+ setupCreate: string;
+ setupLogin: string;
+ setupCancel: string;
+
+ // Text — Passkey name
+ passkeyTitle: string;
+ passkeyBody: string;
+ passkeyContinue: string;
+ passkeyCancel: string;
+
+ // Text — Personal sign
+ signTitle: string;
+ signLabel: string;
+ signReject: string;
+
+ // Text — Typed data
+ typedTitle: string;
+ typedSignLabel: string;
+ typedReject: string;
+
+ // Text — Send transaction
+ txTitle: string;
+ txSignLabel: string;
+ txReject: string;
+
+ // Text — Credential offer
+ credOfferTitle: string;
+ credOfferBody: string;
+ credOfferAccept: string;
+ credOfferReject: string;
+
+ // Text — Credential presentation
+ credPresentTitle: string;
+ credPresentBody: string;
+ credPresentShare: string;
+ credPresentReject: string;
+
+ // Text — Credentials tab
+ credTabLabel: string;
+ credEmptyCount: string;
+ credCountLabel: string;
+ credEmptyBody: string;
+ credRefresh: string;
+ credView: string;
+ credDetailDescription: string;
+ credClaimsHeading: string;
+ credClose: string;
+
+ // Text — Balances / Receive
+ balTabLabel: string;
+ receiveLabel: string;
+ receiveTitle: string;
+ receiveBody: string;
+ receiveAddressLabel: string;
+ receiveQrAlt: string;
+ receiveCopyLabel: string;
+ receiveCopiedLabel: string;
+ receiveCopyFailedLabel: string;
+ receiveCloseLabel: string;
+ sendLabel: string;
+
+ // Text — Confirm transfer (host ERC-20)
+ confirmTransferTitle: string;
+ confirmTransferBody: string;
+ confirmTransferConfirm: string;
+ confirmTransferReject: string;
+
+ // Text — Transfer tokens (in-wallet send)
+ transferTokensTitle: string;
+ transferTokensSend: string;
+ transferTokensCancel: string;
+ transferTokensSentTitle: string;
+ transferTokensViewExplorer: string;
+ transferTokensDone: string;
+
+ // Text — Passkey ceremony overlays
+ passkeyPromptUnlockTitle: string;
+ passkeyPromptCreateTitle: string;
+ passkeyPromptSignTitle: string;
+ passkeyPromptEncryptTitle: string;
+ passkeyPromptDecryptTitle: string;
+ passkeyPromptRelayerTitle: string;
+ passkeyPromptBackupTitle: string;
+
+ // Text — Create backup
+ backupTitle: string;
+ backupBody: string;
+ backupContinue: string;
+ backupCancel: string;
+
+ // Text — Restore backup
+ restoreTitle: string;
+ restoreBody: string;
+ restoreLabel: string;
+ restoreCancel: string;
+}
+
+export const ACME_PRESET: IStyleFormState = {
+ logoUrl: "",
+ productName: "Acme Wallet",
+ tagline: "Powered by 1Shot",
+ primary: "#3b6ef5",
+ primaryForeground: "#ffffff",
+ background: "#ffffff",
+ foreground: "#171717",
+ muted: "#f5f5f5",
+ mutedForeground: "#737373",
+ border: "#e5e5e5",
+ accent: "#eff6ff",
+ accentForeground: "#1e3a8a",
+ radius: "0.625rem",
+ fontSans: "",
+ dark: false,
+ connectTitle: "Connect to Acme",
+ connectBody: "Acme is requesting your wallet address.",
+ connectContinue: "Allow",
+ connectReject: "Reject",
+ setupTitle: "Welcome to Acme",
+ setupBody: "Create or restore your Acme passkey wallet.",
+ setupCreate: "Get started",
+ setupLogin: "Log in",
+ setupCancel: "Cancel",
+ passkeyTitle: "Name this passkey",
+ passkeyBody: "Choose a name for this wallet passkey.",
+ passkeyContinue: "Save name",
+ passkeyCancel: "Cancel",
+ signTitle: "Approve signature",
+ signLabel: "Sign",
+ signReject: "Reject",
+ typedTitle: "Approve typed data",
+ typedSignLabel: "Sign",
+ typedReject: "Reject",
+ txTitle: "Approve transaction",
+ txSignLabel: "Sign",
+ txReject: "Reject",
+ credOfferTitle: "Accept this credential?",
+ credOfferBody: "Review the offer before accepting.",
+ credOfferAccept: "Accept",
+ credOfferReject: "Reject",
+ credPresentTitle: "Share this credential?",
+ credPresentBody: "A verifier is requesting a presentation.",
+ credPresentShare: "Share",
+ credPresentReject: "Reject",
+ credTabLabel: "Credentials",
+ credEmptyCount: "No credentials stored yet.",
+ credCountLabel: "{count} credential(s)",
+ credEmptyBody:
+ "Accept an offer or refresh from the relayer to sync credentials.",
+ credRefresh: "Refresh",
+ credView: "View",
+ credDetailDescription: "Full credential details and claims.",
+ credClaimsHeading: "Claims",
+ credClose: "Close",
+ balTabLabel: "Balances",
+ receiveLabel: "Receive",
+ receiveTitle: "Receive",
+ receiveBody: "Scan this QR code or copy your {chainLabel} address.",
+ receiveAddressLabel: "Address",
+ receiveQrAlt: "{chainLabel} wallet address",
+ receiveCopyLabel: "Copy address",
+ receiveCopiedLabel: "Address copied",
+ receiveCopyFailedLabel: "Copy failed",
+ receiveCloseLabel: "Close",
+ sendLabel: "Send",
+ confirmTransferTitle: "Confirm transfer",
+ confirmTransferBody:
+ "{domain} wants to send {amount} {tokenSymbol} ({tokenName}) to {receiver} on {chainName}.",
+ confirmTransferConfirm: "Confirm",
+ confirmTransferReject: "Reject",
+ transferTokensTitle: "Send",
+ transferTokensSend: "Send",
+ transferTokensCancel: "Cancel",
+ transferTokensSentTitle: "Transaction sent",
+ transferTokensViewExplorer: "View on explorer",
+ transferTokensDone: "Done",
+ passkeyPromptUnlockTitle: "Unlock with passkey",
+ passkeyPromptCreateTitle: "Create passkey",
+ passkeyPromptSignTitle: "Confirm with passkey",
+ passkeyPromptEncryptTitle: "Encrypt with passkey",
+ passkeyPromptDecryptTitle: "Decrypt with passkey",
+ passkeyPromptRelayerTitle: "Authenticate with passkey",
+ passkeyPromptBackupTitle: "Confirm backup",
+ backupTitle: "Create a backup",
+ backupBody: "Encrypt a recovery blob with a passphrase.",
+ backupContinue: "Continue",
+ backupCancel: "Cancel",
+ restoreTitle: "Restore wallet",
+ restoreBody: "Paste a backup and enter your passphrase.",
+ restoreLabel: "Restore",
+ restoreCancel: "Cancel",
+};
+
+export const OCEAN_PRESET: IStyleFormState = {
+ ...ACME_PRESET,
+ productName: "Ocean Wallet",
+ tagline: "Host setStyle preset",
+ primary: "#0e7490",
+ primaryForeground: "#ffffff",
+ background: "#f0f9ff",
+ foreground: "#164e63",
+ muted: "#e0f2fe",
+ mutedForeground: "#0e7490",
+ border: "#bae6fd",
+ accent: "#cffafe",
+ accentForeground: "#155e75",
+ radius: "0.75rem",
+ connectTitle: "Connect to Ocean",
+ setupTitle: "Welcome aboard",
+ setupCreate: "Create Ocean account",
+ passkeyTitle: "Name your Ocean passkey",
+ signTitle: "Sign with Ocean",
+ signLabel: "Approve",
+ typedTitle: "Ocean typed data",
+ txTitle: "Ocean transaction",
+ credOfferTitle: "Accept Ocean credential?",
+ credPresentTitle: "Share with verifier?",
+ balTabLabel: "Balances",
+ receiveTitle: "Receive to Ocean",
+ receiveBody: "Scan or copy your {chainLabel} Ocean address.",
+ backupTitle: "Backup Ocean keys",
+ restoreTitle: "Restore Ocean wallet",
+};
+
+export const DEFAULTS_PRESET: IStyleFormState = {
+ ...ACME_PRESET,
+ productName: "1Shot Wallet",
+ tagline: "Passkey-secured embedded wallet",
+ primary: "#171717",
+ primaryForeground: "#fafafa",
+ background: "#ffffff",
+ foreground: "#171717",
+ muted: "#f5f5f5",
+ mutedForeground: "#737373",
+ border: "#e5e5e5",
+ accent: "#f5f5f5",
+ accentForeground: "#171717",
+ radius: "0.625rem",
+ connectTitle: "Connect wallet",
+ connectBody: "",
+ connectContinue: "Continue",
+ setupTitle: "Set up your wallet",
+ setupBody: "",
+ setupCreate: "Create account",
+ passkeyTitle: "Name your passkey",
+ passkeyBody: "",
+ passkeyContinue: "Continue",
+ signTitle: "Sign message",
+ signLabel: "Sign",
+ typedTitle: "Sign typed data",
+ typedSignLabel: "Sign",
+ txTitle: "Send transaction",
+ txSignLabel: "Sign",
+ credOfferTitle: "Accept credential offer?",
+ credOfferBody: "",
+ credPresentTitle: "Share credential?",
+ credPresentBody: "",
+ credTabLabel: "Credentials",
+ credEmptyCount: "No credentials stored yet.",
+ credCountLabel: "{count} credential(s)",
+ credEmptyBody:
+ "Accept an offer or refresh from the relayer to sync credentials.",
+ credRefresh: "Refresh",
+ credView: "View",
+ credDetailDescription: "Full credential details and claims.",
+ credClaimsHeading: "Claims",
+ credClose: "Close",
+ balTabLabel: "Balances",
+ receiveLabel: "Receive",
+ receiveTitle: "Receive",
+ receiveBody: "Scan this QR code or copy your {chainLabel} address.",
+ receiveAddressLabel: "Address",
+ receiveQrAlt: "{chainLabel} wallet address",
+ receiveCopyLabel: "Copy address",
+ receiveCopiedLabel: "Address copied",
+ receiveCopyFailedLabel: "Copy failed",
+ receiveCloseLabel: "Close",
+ sendLabel: "Send",
+ confirmTransferTitle: "Confirm transfer",
+ confirmTransferBody:
+ "{domain} wants to send {amount} {tokenSymbol} ({tokenName}) to {receiver} on {chainName}.",
+ confirmTransferConfirm: "Confirm",
+ confirmTransferReject: "Reject",
+ transferTokensTitle: "Send",
+ transferTokensSend: "Send",
+ transferTokensCancel: "Cancel",
+ transferTokensSentTitle: "Transaction sent",
+ transferTokensViewExplorer: "View on explorer",
+ transferTokensDone: "Done",
+ passkeyPromptUnlockTitle: "Unlock with passkey",
+ passkeyPromptCreateTitle: "Create passkey",
+ passkeyPromptSignTitle: "Confirm with passkey",
+ passkeyPromptEncryptTitle: "Encrypt with passkey",
+ passkeyPromptDecryptTitle: "Decrypt with passkey",
+ passkeyPromptRelayerTitle: "Authenticate with passkey",
+ passkeyPromptBackupTitle: "Confirm backup",
+ backupTitle: "Create backup",
+ backupBody: "",
+ restoreTitle: "Restore backup",
+ restoreBody: "",
+};
+
+function put(
+ target: Record,
+ key: string,
+ value: string,
+): void {
+ const trimmed = value.trim();
+ if (trimmed) target[key] = trimmed;
+}
+
+/** Build a `setStyle` RPC payload from the flat form (omit empty theme/copy keys). */
+export function buildSetStylePayload(
+ form: IStyleFormState,
+): Record {
+ const theme: Record = {};
+ put(theme, "primary", form.primary);
+ put(theme, "primaryForeground", form.primaryForeground);
+ put(theme, "background", form.background);
+ put(theme, "foreground", form.foreground);
+ put(theme, "muted", form.muted);
+ put(theme, "mutedForeground", form.mutedForeground);
+ put(theme, "border", form.border);
+ put(theme, "accent", form.accent);
+ put(theme, "accentForeground", form.accentForeground);
+ put(theme, "radius", form.radius);
+ put(theme, "fontSans", form.fontSans);
+
+ const copy: Record = {};
+ put(copy as Record, "productName", form.productName);
+ put(copy as Record, "tagline", form.tagline);
+ put(copy as Record, "logoUrl", form.logoUrl);
+
+ const connect: Record = {};
+ put(connect, "title", form.connectTitle);
+ put(connect, "body", form.connectBody);
+ put(connect, "continueLabel", form.connectContinue);
+ put(connect, "rejectLabel", form.connectReject);
+ if (Object.keys(connect).length > 0) copy.connect = connect;
+
+ const walletSetup: Record = {};
+ put(walletSetup, "title", form.setupTitle);
+ put(walletSetup, "body", form.setupBody);
+ put(walletSetup, "createLabel", form.setupCreate);
+ put(walletSetup, "loginLabel", form.setupLogin);
+ put(walletSetup, "cancelLabel", form.setupCancel);
+ if (Object.keys(walletSetup).length > 0) copy.walletSetup = walletSetup;
+
+ const passkeyName: Record = {};
+ put(passkeyName, "title", form.passkeyTitle);
+ put(passkeyName, "body", form.passkeyBody);
+ put(passkeyName, "continueLabel", form.passkeyContinue);
+ put(passkeyName, "cancelLabel", form.passkeyCancel);
+ if (Object.keys(passkeyName).length > 0) copy.passkeyName = passkeyName;
+
+ const personalSign: Record = {};
+ put(personalSign, "title", form.signTitle);
+ put(personalSign, "signLabel", form.signLabel);
+ put(personalSign, "rejectLabel", form.signReject);
+ if (Object.keys(personalSign).length > 0) copy.personalSign = personalSign;
+
+ const typedData: Record = {};
+ put(typedData, "title", form.typedTitle);
+ put(typedData, "signLabel", form.typedSignLabel);
+ put(typedData, "rejectLabel", form.typedReject);
+ if (Object.keys(typedData).length > 0) copy.typedData = typedData;
+
+ const sendTransaction: Record = {};
+ put(sendTransaction, "title", form.txTitle);
+ put(sendTransaction, "signLabel", form.txSignLabel);
+ put(sendTransaction, "rejectLabel", form.txReject);
+ if (Object.keys(sendTransaction).length > 0) {
+ copy.sendTransaction = sendTransaction;
+ }
+
+ const confirmTransfer: Record = {};
+ put(confirmTransfer, "title", form.confirmTransferTitle);
+ put(confirmTransfer, "body", form.confirmTransferBody);
+ put(confirmTransfer, "confirmLabel", form.confirmTransferConfirm);
+ put(confirmTransfer, "rejectLabel", form.confirmTransferReject);
+ if (Object.keys(confirmTransfer).length > 0) {
+ copy.confirmTransfer = confirmTransfer;
+ }
+
+ const transferTokens: Record = {};
+ put(transferTokens, "title", form.transferTokensTitle);
+ put(transferTokens, "sendLabel", form.transferTokensSend);
+ put(transferTokens, "cancelLabel", form.transferTokensCancel);
+ put(transferTokens, "sentTitle", form.transferTokensSentTitle);
+ put(transferTokens, "viewOnExplorerLabel", form.transferTokensViewExplorer);
+ put(transferTokens, "doneLabel", form.transferTokensDone);
+ if (Object.keys(transferTokens).length > 0) {
+ copy.transferTokens = transferTokens;
+ }
+
+ const passkeyPrompt: Record> = {};
+ const unlock: Record = {};
+ put(unlock, "title", form.passkeyPromptUnlockTitle);
+ if (Object.keys(unlock).length > 0) {
+ passkeyPrompt.unlock = unlock;
+ }
+ const create: Record = {};
+ put(create, "title", form.passkeyPromptCreateTitle);
+ if (Object.keys(create).length > 0) {
+ passkeyPrompt.create = create;
+ }
+ const sign: Record = {};
+ put(sign, "title", form.passkeyPromptSignTitle);
+ if (Object.keys(sign).length > 0) {
+ passkeyPrompt.sign = sign;
+ }
+ const encrypt: Record = {};
+ put(encrypt, "title", form.passkeyPromptEncryptTitle);
+ if (Object.keys(encrypt).length > 0) {
+ passkeyPrompt.encrypt = encrypt;
+ }
+ const decrypt: Record = {};
+ put(decrypt, "title", form.passkeyPromptDecryptTitle);
+ if (Object.keys(decrypt).length > 0) {
+ passkeyPrompt.decrypt = decrypt;
+ }
+ const relayerAuth: Record = {};
+ put(relayerAuth, "title", form.passkeyPromptRelayerTitle);
+ if (Object.keys(relayerAuth).length > 0) {
+ passkeyPrompt.relayerAuth = relayerAuth;
+ }
+ const backup: Record = {};
+ put(backup, "title", form.passkeyPromptBackupTitle);
+ if (Object.keys(backup).length > 0) {
+ passkeyPrompt.backup = backup;
+ }
+ if (Object.keys(passkeyPrompt).length > 0) {
+ copy.passkeyPrompt = passkeyPrompt;
+ }
+
+ const credentialOffer: Record = {};
+ put(credentialOffer, "title", form.credOfferTitle);
+ put(credentialOffer, "body", form.credOfferBody);
+ put(credentialOffer, "acceptLabel", form.credOfferAccept);
+ put(credentialOffer, "rejectLabel", form.credOfferReject);
+ if (Object.keys(credentialOffer).length > 0) {
+ copy.credentialOffer = credentialOffer;
+ }
+
+ const credentialPresentation: Record = {};
+ put(credentialPresentation, "title", form.credPresentTitle);
+ put(credentialPresentation, "body", form.credPresentBody);
+ put(credentialPresentation, "shareLabel", form.credPresentShare);
+ put(credentialPresentation, "rejectLabel", form.credPresentReject);
+ if (Object.keys(credentialPresentation).length > 0) {
+ copy.credentialPresentation = credentialPresentation;
+ }
+
+ const credentials: Record = {};
+ put(credentials, "tabLabel", form.credTabLabel);
+ put(credentials, "emptyCountLabel", form.credEmptyCount);
+ put(credentials, "countLabel", form.credCountLabel);
+ put(credentials, "emptyBody", form.credEmptyBody);
+ put(credentials, "refreshLabel", form.credRefresh);
+ put(credentials, "viewLabel", form.credView);
+ put(credentials, "detailDescription", form.credDetailDescription);
+ put(credentials, "claimsHeading", form.credClaimsHeading);
+ put(credentials, "closeLabel", form.credClose);
+ if (Object.keys(credentials).length > 0) copy.credentials = credentials;
+
+ const balances: Record = {};
+ put(balances, "tabLabel", form.balTabLabel);
+ put(balances, "receiveLabel", form.receiveLabel);
+ put(balances, "receiveTitle", form.receiveTitle);
+ put(balances, "receiveBody", form.receiveBody);
+ put(balances, "receiveAddressLabel", form.receiveAddressLabel);
+ put(balances, "receiveQrAlt", form.receiveQrAlt);
+ put(balances, "receiveCopyLabel", form.receiveCopyLabel);
+ put(balances, "receiveCopiedLabel", form.receiveCopiedLabel);
+ put(balances, "receiveCopyFailedLabel", form.receiveCopyFailedLabel);
+ put(balances, "receiveCloseLabel", form.receiveCloseLabel);
+ put(balances, "sendLabel", form.sendLabel);
+ if (Object.keys(balances).length > 0) copy.balances = balances;
+
+ const createBackup: Record = {};
+ put(createBackup, "title", form.backupTitle);
+ put(createBackup, "body", form.backupBody);
+ put(createBackup, "continueLabel", form.backupContinue);
+ put(createBackup, "cancelLabel", form.backupCancel);
+ if (Object.keys(createBackup).length > 0) copy.createBackup = createBackup;
+
+ const restoreBackup: Record = {};
+ put(restoreBackup, "title", form.restoreTitle);
+ put(restoreBackup, "body", form.restoreBody);
+ put(restoreBackup, "restoreLabel", form.restoreLabel);
+ put(restoreBackup, "cancelLabel", form.restoreCancel);
+ if (Object.keys(restoreBackup).length > 0) {
+ copy.restoreBackup = restoreBackup;
+ }
+
+ const payload: Record = { dark: form.dark };
+ if (Object.keys(theme).length > 0) payload.theme = theme;
+ if (Object.keys(copy).length > 0) payload.copy = copy;
+ return payload;
+}
diff --git a/host/tsconfig.json b/host/tsconfig.json
new file mode 100644
index 0000000..e11ae76
--- /dev/null
+++ b/host/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "types": ["vite/client"],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["src/**/*", "vite.config.mjs"]
+}
diff --git a/host/vite.config.mjs b/host/vite.config.mjs
new file mode 100644
index 0000000..7086795
--- /dev/null
+++ b/host/vite.config.mjs
@@ -0,0 +1,44 @@
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import dotenv from "dotenv";
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
+import { resolveHttpsOptions, walletIframeUrl } from "./wallet-url.mjs";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+dotenv.config({ path: path.join(__dirname, "../.env") });
+
+const https = resolveHttpsOptions({
+ certsDir: path.resolve(__dirname, "certs"),
+});
+
+export default defineConfig({
+ define: {
+ __WALLET_IFRAME_URL__: JSON.stringify(walletIframeUrl()),
+ },
+ plugins: [react(), tailwindcss()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
+ server: {
+ port: Number(process.env.HOST_PORT ?? process.env.PORT ?? 5173),
+ host: "0.0.0.0",
+ allowedHosts: true,
+ strictPort: true,
+ ...(https ? { https } : {}),
+ },
+ preview: {
+ port: Number(process.env.HOST_PORT ?? process.env.PORT ?? 5173),
+ host: "0.0.0.0",
+ strictPort: true,
+ ...(https ? { https } : {}),
+ },
+ build: {
+ outDir: "dist",
+ emptyOutDir: true,
+ sourcemap: true,
+ },
+});
diff --git a/host/wallet-url.mjs b/host/wallet-url.mjs
new file mode 100644
index 0000000..3c269bc
--- /dev/null
+++ b/host/wallet-url.mjs
@@ -0,0 +1,67 @@
+/**
+ * Wallet iframe URL + optional mkcert HTTPS for the Host Layer tester.
+ */
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+export function normalizeNgrokDomain(value) {
+ const raw = value?.trim();
+ if (!raw) return undefined;
+ try {
+ const url = raw.includes("://") ? raw : `https://${raw}`;
+ return new URL(url).hostname;
+ } catch {
+ return raw.replace(/^https?:\/\//, "").replace(/\/+$/, "");
+ }
+}
+
+/** Branding Layer iframe URL. Prefer WALLET_IFRAME_URL, then NGROK_DOMAIN, else local wallet. */
+export function walletIframeUrl() {
+ const explicit = process.env.WALLET_IFRAME_URL?.trim();
+ if (explicit) {
+ return explicit.endsWith("/") ? explicit : `${explicit}/`;
+ }
+ const domain = normalizeNgrokDomain(process.env.NGROK_DOMAIN);
+ if (domain) {
+ return `https://${domain}/`;
+ }
+ const walletPort = process.env.WALLET_PORT?.trim() || "5174";
+ return `http://localhost:${walletPort}/`;
+}
+
+/**
+ * Optional HTTPS when HOST_HTTPS=1 or host/certs/dev-*.pem exist.
+ * Needed when the wallet iframe is on HTTPS (ngrok) for passkey ancestor checks.
+ */
+export function resolveHttpsOptions({ certsDir }) {
+ const flag = process.env.HOST_HTTPS?.trim().toLowerCase();
+ const forceOn = flag === "1" || flag === "true" || flag === "yes";
+ const forceOff = flag === "0" || flag === "false" || flag === "no";
+
+ const certPath = process.env.HOST_SSL_CERT?.trim()
+ ? path.resolve(process.env.HOST_SSL_CERT.trim())
+ : path.join(certsDir, "dev-cert.pem");
+ const keyPath = process.env.HOST_SSL_KEY?.trim()
+ ? path.resolve(process.env.HOST_SSL_KEY.trim())
+ : path.join(certsDir, "dev-key.pem");
+
+ const certsPresent = fs.existsSync(certPath) && fs.existsSync(keyPath);
+ if (forceOff) return undefined;
+ if (!forceOn && !certsPresent) return undefined;
+ if (!certsPresent) {
+ throw new Error(
+ `HOST_HTTPS is set but cert/key missing under ${certsDir}.\n` +
+ `Generate: mkcert -install && mkcert -cert-file host/certs/dev-cert.pem -key-file host/certs/dev-key.pem localhost 127.0.0.1`,
+ );
+ }
+ return {
+ key: fs.readFileSync(keyPath),
+ cert: fs.readFileSync(certPath),
+ };
+}
+
+// silence unused in some tooling
+void __dirname;
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..5ee0887
--- /dev/null
+++ b/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ 1Shot Wallet
+
+
+
+
+
+
diff --git a/infra/scripts/deploy-prod-wallet-vm-remote.sh b/infra/scripts/deploy-prod-wallet-vm-remote.sh
new file mode 100644
index 0000000..619983d
--- /dev/null
+++ b/infra/scripts/deploy-prod-wallet-vm-remote.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# Remote commands executed on each production GCE instance (via SSH stdin).
+# See: .github/workflows/Deploy Prod.yaml
+set -euo pipefail
+
+cd /home/github
+echo "----------------------------------------------------------------------------------------"
+echo "----------------------- Start of Wallet Deployment Script ------------------------------"
+echo "----------------------------------------------------------------------------------------"
+
+echo "---------------------------- Docker Deployment ---------------------------------------"
+docker-compose pull
+
+docker stop wallet || true
+docker rm -f wallet || true
+
+docker-compose up -d
+
+docker system prune -f
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..56e4e73
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,22 @@
+server {
+ listen 80;
+ server_name _;
+ root /usr/share/nginx/html;
+
+ # Keep Location headers relative so published host ports (e.g. :8080) survive redirects.
+ absolute_redirect off;
+
+ # Signing Layer — unbundled ES modules (same origin as branding)
+ location /signer/ {
+ try_files $uri $uri/ /signer/index.html;
+ }
+
+ location = /signer {
+ return 301 /signer/;
+ }
+
+ # Branding Layer SPA at domain root (/signer/ above takes precedence)
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..a2a9c13
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,8710 @@
+{
+ "name": "@1shotapi/embedded-wallet",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@1shotapi/embedded-wallet",
+ "version": "0.0.0",
+ "workspaces": [
+ "host"
+ ],
+ "dependencies": {
+ "@1shotapi/ows-oid4": "^0.1.1",
+ "@1shotapi/ows-signer": "^0.2.1",
+ "@1shotapi/ows-signer-utils": "^0.2.0",
+ "@1shotapi/ows-types": "^0.1.3",
+ "@1shotapi/ows-wallet-utils": "^0.1.2",
+ "@fontsource-variable/geist": "^5.2.9",
+ "@simplewebauthn/browser": "^13.3.0",
+ "@tanstack/react-table": "^8.21.3",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.525.0",
+ "qrcode": "^1.5.4",
+ "radix-ui": "^1.6.2",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "shadcn": "^4.13.0",
+ "tailwind-merge": "^3.3.1",
+ "ts-brand": "^0.2.0",
+ "viem": "^2.55.2",
+ "zod": "^4.4.3",
+ "zustand": "^5.0.14"
+ },
+ "devDependencies": {
+ "@ngrok/ngrok": "^1.7.0",
+ "@tailwindcss/vite": "^4.1.11",
+ "@types/node": "^22.15.0",
+ "@types/qrcode": "^1.5.6",
+ "@types/react": "^19.1.8",
+ "@types/react-dom": "^19.1.6",
+ "@vitejs/plugin-react": "^4.6.0",
+ "dotenv": "^16.4.7",
+ "tailwindcss": "^4.1.11",
+ "tw-animate-css": "^1.3.5",
+ "typescript": "~5.8.0",
+ "vite": "^7.0.4"
+ }
+ },
+ "host": {
+ "name": "@1shotapi/oneshot-wallet-host",
+ "version": "0.0.0",
+ "dependencies": {
+ "@1shotapi/ows-provider": "^0.2.1",
+ "@1shotapi/ows-types": "^0.1.3",
+ "@fontsource-variable/geist": "^5.2.9",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^1.24.0",
+ "radix-ui": "^1.6.2",
+ "react": "^19.2.7",
+ "react-colorful": "^5.8.0",
+ "react-dom": "^19.2.7",
+ "shadcn": "^4.13.0",
+ "tailwind-merge": "^3.6.0",
+ "viem": "^2.55.2"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.3.2",
+ "@types/node": "^22.20.1",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^4.7.0",
+ "dotenv": "^16.4.7",
+ "tailwindcss": "^4.3.2",
+ "tw-animate-css": "^1.4.0",
+ "typescript": "~5.8.0",
+ "vite": "^7.0.4"
+ }
+ },
+ "host/node_modules/lucide-react": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.24.0.tgz",
+ "integrity": "sha512-YT6mBD8lGKkg4nM39enlm94/sfJIiW0YKUT60fBy4YK8tai31ylg1VhGNWxkpSKHo9UagfnZqwIff3HTDQwXeA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@1shotapi/oneshot-wallet-host": {
+ "resolved": "host",
+ "link": true
+ },
+ "node_modules/@1shotapi/ows-oid4": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@1shotapi/ows-oid4/-/ows-oid4-0.1.1.tgz",
+ "integrity": "sha512-3Vy1sG48h8zENWDGQIHQx6LFaf74HhVFKd5vCmhiOH0lPAhVnSPlMJDHEii9kvt6tayBbmKob6IDHfsail97Rw==",
+ "license": "MIT",
+ "dependencies": {
+ "@1shotapi/ows-types": "*",
+ "jose": "^6.0.11"
+ }
+ },
+ "node_modules/@1shotapi/ows-provider": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@1shotapi/ows-provider/-/ows-provider-0.2.1.tgz",
+ "integrity": "sha512-O/F1iiyBidbbV/hf1025sSut5h7Yn7MGZ/pxC1SVdB7HuBVcl/afZd3JDnW/bEK49VobT/8vWRgo4iQ24eGGnQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@1shotapi/ows-types": "*",
+ "@sd-jwt/sd-jwt-vc": "^0.20.0",
+ "postmate": "^1.5.2"
+ }
+ },
+ "node_modules/@1shotapi/ows-signer": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@1shotapi/ows-signer/-/ows-signer-0.2.1.tgz",
+ "integrity": "sha512-GMWfkOu+fSGDGEjPUwmM4gtWy+BLssXrIxbnUITNzavaZXL7o4NtAQoGX+yZ/fAKZSbPLSsd/ljJ3oVqnm7EPg==",
+ "license": "MIT"
+ },
+ "node_modules/@1shotapi/ows-signer-utils": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@1shotapi/ows-signer-utils/-/ows-signer-utils-0.2.0.tgz",
+ "integrity": "sha512-KtM9/Oq3YQVqrmO7FWZRWWw620b3H6msw4ZmCmqNCaB9VVQZfrTvEg9QMkDRYY4H90tlvQiEFg8NUlErE/yYNw==",
+ "license": "MIT",
+ "dependencies": {
+ "@1shotapi/ows-types": "*",
+ "@scure/base": "^1.2.5"
+ },
+ "peerDependencies": {
+ "viem": "^2.0.0"
+ }
+ },
+ "node_modules/@1shotapi/ows-types": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@1shotapi/ows-types/-/ows-types-0.1.3.tgz",
+ "integrity": "sha512-IQVmxue0hFjv1JYQPpBwb5nylql+J+kBEQLOcaoXkfpIQD72x1zsbqhAAdh21aYnzIgwSodUVbmqATQ0bhSCGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sd-jwt/core": "^0.20.0",
+ "@sd-jwt/sd-jwt-vc": "^0.20.0",
+ "ts-brand": "^0.2.0"
+ }
+ },
+ "node_modules/@1shotapi/ows-wallet-utils": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@1shotapi/ows-wallet-utils/-/ows-wallet-utils-0.1.2.tgz",
+ "integrity": "sha512-vnu7QbLJgvRoxZdmTwPE/7znbwB5Y3de6PuOBsBx/UpVIg5jnGBPPiyh3k0m+2XK0vcrvHaWXeKh9VkTGAaNCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@1shotapi/ows-types": "*",
+ "postmate": "^1.5.2",
+ "zod": "^4.4.0"
+ },
+ "peerDependencies": {
+ "viem": "^2.0.0"
+ }
+ },
+ "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/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz",
+ "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz",
+ "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.29.7",
+ "@babel/helper-member-expression-to-functions": "^7.29.7",
+ "@babel/helper-optimise-call-expression": "^7.29.7",
+ "@babel/helper-replace-supers": "^7.29.7",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz",
+ "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz",
+ "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz",
+ "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.29.7",
+ "@babel/helper-optimise-call-expression": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz",
+ "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "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",
+ "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/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "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",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz",
+ "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz",
+ "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-commonjs": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz",
+ "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typescript": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz",
+ "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.29.7",
+ "@babel/helper-create-class-features-plugin": "^7.29.7",
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
+ "@babel/plugin-syntax-typescript": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-typescript": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz",
+ "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "@babel/plugin-syntax-jsx": "^7.29.7",
+ "@babel/plugin-transform-modules-commonjs": "^7.29.7",
+ "@babel/plugin-transform-typescript": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.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",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@cbor-extract/cbor-extract-darwin-arm64": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.2.tgz",
+ "integrity": "sha512-ZKZ/F8US7JR92J4DMct6cLW/Y66o2K576+zjlEN/MevH70bFIsB10wkZEQPLzl2oNh2SMGy55xpJ9JoBRl5DOA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@cbor-extract/cbor-extract-darwin-x64": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.2.tgz",
+ "integrity": "sha512-32b1mgc+P61Js+KW9VZv/c+xRw5EfmOcPx990JbCBSkYJFY0l25VinvyyWfl+3KjibQmAcYwmyzKF9J4DyKP/Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@cbor-extract/cbor-extract-linux-arm": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.2.tgz",
+ "integrity": "sha512-tNg0za41TpQfkhWjptD+0gSD2fggMiDCSacuIeELyb2xZhr7PrhPe5h66Jc67B/5dmpIhI2QOUtv4SBsricyYQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@cbor-extract/cbor-extract-linux-arm64": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.2.tgz",
+ "integrity": "sha512-wfqgzqCAy/Vn8i6WVIh7qZd0DdBFaWBjPdB6ma+Wihcjv0gHqD/mw3ouVv7kbbUNrab6dKEx/w3xQZEdeXIlzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@cbor-extract/cbor-extract-linux-x64": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.2.tgz",
+ "integrity": "sha512-rpiLnVEsqtPJ+mXTdx1rfz4RtUGYIUg2rUAZgd1KjiC1SehYUSkJN7Yh+aVfSjvCGtVP0/bfkQkXpPXKbmSUaA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@cbor-extract/cbor-extract-win32-x64": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.2.tgz",
+ "integrity": "sha512-dI+9P7cfWxkTQ+oE+7Aa6onEn92PHgfWXZivjNheCRmTBDBf2fx6RyTi0cmgpYLnD1KLZK9ZYrMxaPZ4oiXhGA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@dotenvx/dotenvx": {
+ "version": "1.75.1",
+ "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz",
+ "integrity": "sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@dotenvx/primitives": "^0.8.0",
+ "commander": "^11.1.0",
+ "conf": "^10.2.0",
+ "dotenv": "^17.2.1",
+ "enquirer": "^2.4.1",
+ "env-paths": "^2.2.1",
+ "execa": "^5.1.1",
+ "fdir": "^6.2.0",
+ "ignore": "^5.3.0",
+ "object-treeify": "1.1.33",
+ "open": "^8.4.2",
+ "picomatch": "^4.0.4",
+ "systeminformation": "^5.22.11",
+ "undici": "^7.11.0",
+ "which": "^4.0.0",
+ "yocto-spinner": "^1.1.0"
+ },
+ "bin": {
+ "dotenvx": "src/cli/dotenvx.js"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/dotenv": {
+ "version": "17.4.2",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+ "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@dotenvx/primitives": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@dotenvx/primitives/-/primitives-0.8.0.tgz",
+ "integrity": "sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==",
+ "license": "BSD-3-Clause"
+ },
+ "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/@floating-ui/core": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
+ "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.12"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
+ "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.8.0",
+ "@floating-ui/utils": "^0.2.12"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz",
+ "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.8.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
+ "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
+ "license": "MIT"
+ },
+ "node_modules/@fontsource-variable/geist": {
+ "version": "5.2.9",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.2.9.tgz",
+ "integrity": "sha512-TP+QSBG3wxKGPE33CbMy/L0Nu3qvJ6Fy81Yc4LnQ95xH+i+cfEp8fyU8/kfV14YwszxIFPhnoMTbjL71waVpyQ==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.14",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
+ "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.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"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.29.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
+ "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@hono/node-server": "^1.19.9",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.2.1",
+ "hono": "^4.11.4",
+ "jose": "^6.1.3",
+ "json-schema-typed": "^8.0.2",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.25 || ^4.0",
+ "zod-to-json-schema": "^3.25.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/@ngrok/ngrok": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok/-/ngrok-1.7.0.tgz",
+ "integrity": "sha512-P06o9TpxrJbiRbHQkiwy/rUrlXRupc+Z8KT4MiJfmcdWxvIdzjCaJOdnNkcOTs6DMyzIOefG5tvk/HLdtjqr0g==",
+ "dev": true,
+ "license": "(MIT OR Apache-2.0)",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@ngrok/ngrok-android-arm64": "1.7.0",
+ "@ngrok/ngrok-darwin-arm64": "1.7.0",
+ "@ngrok/ngrok-darwin-universal": "1.7.0",
+ "@ngrok/ngrok-darwin-x64": "1.7.0",
+ "@ngrok/ngrok-freebsd-x64": "1.7.0",
+ "@ngrok/ngrok-linux-arm-gnueabihf": "1.7.0",
+ "@ngrok/ngrok-linux-arm64-gnu": "1.7.0",
+ "@ngrok/ngrok-linux-arm64-musl": "1.7.0",
+ "@ngrok/ngrok-linux-x64-gnu": "1.7.0",
+ "@ngrok/ngrok-linux-x64-musl": "1.7.0",
+ "@ngrok/ngrok-win32-arm64-msvc": "1.7.0",
+ "@ngrok/ngrok-win32-ia32-msvc": "1.7.0",
+ "@ngrok/ngrok-win32-x64-msvc": "1.7.0"
+ }
+ },
+ "node_modules/@ngrok/ngrok-android-arm64": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-android-arm64/-/ngrok-android-arm64-1.7.0.tgz",
+ "integrity": "sha512-8tco3ID6noSaNy+CMS7ewqPoIkIM6XO5COCzsUp3Wv3XEbMSyn65RN6cflX2JdqLfUCHcMyD0ahr9IEiHwqmbQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-darwin-arm64": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-arm64/-/ngrok-darwin-arm64-1.7.0.tgz",
+ "integrity": "sha512-+dmJSOzSO+MNDVrPOca2yYDP1W3KfP4qOlAkarIeFRIfqonQwq3QCBmcR7HAlZocLsSqEwyG6KP4RRvAuT0WGQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-darwin-universal": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-universal/-/ngrok-darwin-universal-1.7.0.tgz",
+ "integrity": "sha512-fDEfewyE2pWGFBhOSwQZObeHUkc65U1l+3HIgSOe094TMHsqmyJD0KTCgW9KSn0VP4OvDZbAISi1T3nvqgZYhQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-darwin-x64": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-x64/-/ngrok-darwin-x64-1.7.0.tgz",
+ "integrity": "sha512-+fwMi5uHd9G8BS42MMa9ye6exI5lwTcjUO6Ut497Vu0qgLONdVRenRqnEePV+Q3KtQR7NjqkMnomVfkr9MBjtw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-freebsd-x64": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-freebsd-x64/-/ngrok-freebsd-x64-1.7.0.tgz",
+ "integrity": "sha512-2OGgbrjy3yLRrqAz5N6hlUKIWIXSpR5RjQa2chtZMsSbszQ6c9dI+uVQfOKAeo05tHMUgrYAZ7FocC+ig0dzdQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-linux-arm-gnueabihf": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm-gnueabihf/-/ngrok-linux-arm-gnueabihf-1.7.0.tgz",
+ "integrity": "sha512-SN9YIfEQiR9xN90QVNvdgvAemqMLoFVSeTWZs779145hQMhvF9Qd9rnWi6J+2uNNK10OczdV1oc/nq1es7u/3g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-linux-arm64-gnu": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-gnu/-/ngrok-linux-arm64-gnu-1.7.0.tgz",
+ "integrity": "sha512-KDMgzPKFU2kbpVSaA2RZBBia5IPdJEe063YlyVFnSMJmPYWCUnMwdybBsucXfV9u1Lw/ZjKTKotIlbTWGn3HGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-linux-arm64-musl": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-musl/-/ngrok-linux-arm64-musl-1.7.0.tgz",
+ "integrity": "sha512-e66vUdVrBlQ0lT9ZdamB4U604zt5Gualt8/WVcUGzbu8s5LajWd6g/mzZCUjK4UepjvMpfgmCp1/+rX7Rk8d5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-linux-x64-gnu": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-gnu/-/ngrok-linux-x64-gnu-1.7.0.tgz",
+ "integrity": "sha512-M6gF0DyOEFqXLfWxObfL3bxYZ4+PnKBHuyLVaqNfFN9Y5utY2mdPOn5422Ppbk4XoIK5/YkuhRqPJl/9FivKEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-linux-x64-musl": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-musl/-/ngrok-linux-x64-musl-1.7.0.tgz",
+ "integrity": "sha512-4Ijm0dKeoyzZTMaYxR2EiNjtlK81ebflg/WYIO1XtleFrVy4UJEGnxtxEidYoT4BfCqi4uvXiK2Mx216xXKvog==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-win32-arm64-msvc": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-arm64-msvc/-/ngrok-win32-arm64-msvc-1.7.0.tgz",
+ "integrity": "sha512-u7qyWIJI2/YG1HTBnHwUR1+Z2tyGfAsUAItJK/+N1G0FeWJhIWQvSIFJHlaPy4oW1Dc8mSDBX9qvVsiQgLaRFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-win32-ia32-msvc": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-ia32-msvc/-/ngrok-win32-ia32-msvc-1.7.0.tgz",
+ "integrity": "sha512-/UdYUsLNv/Q8j9YJsyIfq/jLCoD8WP+NidouucTUzSoDtmOsXBBT3itLrmPiZTEdEgKiFYLuC1Zon8XQQvbVLA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@ngrok/ngrok-win32-x64-msvc": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-x64-msvc/-/ngrok-win32-x64-msvc-1.7.0.tgz",
+ "integrity": "sha512-UFJg/duEWzZlLkEs61Gz6/5nYhGaKI62I8dvUGdBR3NCtIMagehnFaFxmnXZldyHmCM8U0aCIFNpWRaKcrQkoA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "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.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/@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/@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",
+ "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",
+ "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",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@owf/identity-common": {
+ "version": "0.1.0-alpha-20260422101556",
+ "resolved": "https://registry.npmjs.org/@owf/identity-common/-/identity-common-0.1.0-alpha-20260422101556.tgz",
+ "integrity": "sha512-0CJhMHXWf+Dn0MpZCeachjFjMjBI2uCJl56Po3dnGiJDis5wVkfWPeCdPJZ2h4fBWwXV0+Owa8nJRxXeruRwrA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@owf/token-status-list": {
+ "version": "0.1.0-alpha-20260422101556",
+ "resolved": "https://registry.npmjs.org/@owf/token-status-list/-/token-status-list-0.1.0-alpha-20260422101556.tgz",
+ "integrity": "sha512-cwZHQPqBaXt3pTbllI6GLafE+Ws8HfEYoOgmpiQiK1sFElkF07mFZt1FTt1YElbMWtEV6YLTh+MjEWdJdRNklA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@owf/identity-common": "0.1.0-alpha-20260422101556",
+ "cbor-x": "^1.6.4",
+ "pako": "^2.1.0"
+ }
+ },
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz",
+ "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz",
+ "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-accessible-icon": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.11.tgz",
+ "integrity": "sha512-HQDOFTKwSnmUij6l54wYJJtxTAnxI71+YJLOrjm2ladFB8HAV5Jt7hwaZPhWTGBkYoW4+ZAOfNZrLDh/qvxSYA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-visually-hidden": "1.2.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-accordion": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.16.tgz",
+ "integrity": "sha512-BpZJNmetujnGgUI6OX0jEhEmlA46WPqgub8Rv09Kyquwd0cc1ndMKpiPYCjmBU6KSSRPAMtgLpEoZSG/tdNIWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-collapsible": "1.1.16",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-alert-dialog": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.19.tgz",
+ "integrity": "sha512-FA7n1f6D/DwGE0+AWxiY5LacNbbExQuEgMubeG06idEaH+mSLuf9dp/qBNqOnvbTQ+4gZ2ue1RATF1Ub91Mg5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-dialog": "1.1.19",
+ "@radix-ui/react-primitive": "2.1.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.11.tgz",
+ "integrity": "sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-aspect-ratio": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.11.tgz",
+ "integrity": "sha512-IUAhIVpBUvP5NNICjlaB1OFmtRLGqQqTF3ZOSGPoq3XeLXRFtHiWTRxSVEULgOd9GQR2c7tsYqDnhUennapZnw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-avatar": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.2.tgz",
+ "integrity": "sha512-sST0qh8GzOB7besQ3tMLWLyngnRuSk0gc/Hm+667KYKQFCt6Y6ZXv25WlqM7dIDK54ULCh5+CHmk4LIolzfz+A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-is-hydrated": "0.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.7.tgz",
+ "integrity": "sha512-JroKHfQBfh+fDuzpPsBC+pESkhuq8ql4hljTguz8MWnS35cISr3d/Jhl9kYrB44FlDtxCArYdDvTx+BSsJ64rQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible": {
+ "version": "1.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.16.tgz",
+ "integrity": "sha512-opfXRe6nnzyGmCDPx+l1Aqo/RbqWtQal2FnsBqF9hhePp6j0LsRoBaRxcMOlTv+uYTJVtWYZKg9t9wTe+BA/ZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz",
+ "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-slot": "1.3.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz",
+ "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz",
+ "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.3.tgz",
+ "integrity": "sha512-PS+gKE0z2prJ74Y0sM+brAGK4mYOHIR7TlcV5EJgUQ6E0xMvyswkK2X4yRqyganrzsRL+WCSKAPu0NQITICRWg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-menu": "2.1.20",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.19.tgz",
+ "integrity": "sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-dismissable-layer": "1.1.15",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.12",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-portal": "1.1.13",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz",
+ "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz",
+ "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-effect-event": "0.0.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.20",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.20.tgz",
+ "integrity": "sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-menu": "2.1.20",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz",
+ "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz",
+ "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-callback-ref": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-form": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.12.tgz",
+ "integrity": "sha512-JTX94E4LDL91rzLg7X0mHPdxr0A8JEdVwZEmeOwZJSMDHCGW5DFtSlTSJozUyUs807IQmnvbfzKZFVCK5DmkqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-label": "2.1.11",
+ "@radix-ui/react-primitive": "2.1.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-hover-card": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.19.tgz",
+ "integrity": "sha512-2KTgMLQtKvicznQgbindEI2RZ3QbDIwU5gabjUPwFJsormjGDz+rUvO4NANmYwzEEpTcTONUt33vBHIfTIVSfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-dismissable-layer": "1.1.15",
+ "@radix-ui/react-popper": "1.3.3",
+ "@radix-ui/react-portal": "1.1.13",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz",
+ "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.11.tgz",
+ "integrity": "sha512-3PKvDDxOn62k0oV1n4QtNtD2vpu+zYjXR7ojLBPaO6SPvhy53yg0vAmgNeBQeJW5rV3dffoRG+HYfLBZuzw0CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.20",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.20.tgz",
+ "integrity": "sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.15",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.12",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-popper": "1.3.3",
+ "@radix-ui/react-portal": "1.1.13",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-roving-focus": "1.1.15",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menubar": {
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.20.tgz",
+ "integrity": "sha512-gzFZvybgmwYsFBWDqanycIoEYnhyk8MMnuLamdFVHUZYGp4COM+sqXiwbnn0VMWqGLeeU7GV7jm+dXRa+Wufag==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-menu": "2.1.20",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-roving-focus": "1.1.15",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-navigation-menu": {
+ "version": "1.2.18",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.18.tgz",
+ "integrity": "sha512-K9HiuxZ6xCwSaHcIuUpxyhy4w5gpwzWjh9dHTSbMN3Ix4qAyVObS9RlU3zMycb0PO3v9Tpk0BXMwWvXOUbVXew==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.15",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-visually-hidden": "1.2.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-one-time-password-field": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.12.tgz",
+ "integrity": "sha512-nQLu5OAcORDQp1EHAv6k3mJGV1hjMTw2NTGVAsGE1g/mWeNqAd1R5jyaAs3U+A8ZD/W8XNPY2yKT0ZdQnqo3NA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.2",
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-roving-focus": "1.1.15",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-effect-event": "0.0.3",
+ "@radix-ui/react-use-is-hydrated": "0.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-password-toggle-field": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.7.tgz",
+ "integrity": "sha512-gB1Mr8vzdv1XzDjrtJTXmL0JORRs1B4g7ngUs0F+H2VvMOwXTZMTmLCl0wZZ3m7ylX8TssI7NCvgiSHmLuTm/A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-effect-event": "0.0.3",
+ "@radix-ui/react-use-is-hydrated": "0.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.19.tgz",
+ "integrity": "sha512-jkrTdQVxnIB8fpn0NyyxW9CTB5aCXZZelVz5z+Xmii6g5WxMqS3fInNslZ63puP39+Puu4jYohUK31y3dT87gQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-dismissable-layer": "1.1.15",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.12",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-popper": "1.3.3",
+ "@radix-ui/react-portal": "1.1.13",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.3.tgz",
+ "integrity": "sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.11",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-rect": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2",
+ "@radix-ui/rect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz",
+ "integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz",
+ "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz",
+ "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.3.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.12.tgz",
+ "integrity": "sha512-ZPHyI0JyzoH/rP0tq2uRaIZTj/4s8+kAbqPz+e2N8+ejHvwPJ889dHhqn+vh7PNvNeq+boAoH9yzqeoShzwF2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-primitive": "2.1.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.3.tgz",
+ "integrity": "sha512-WwZFjWV4s3aC1QtR3k04R+oANHtX2q6fgKlc7MCEiDNlnTxCZ3H8k3mHtEgVlOejystwk1WQgarQhNOQZ2bK1g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-roving-focus": "1.1.15",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz",
+ "integrity": "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-is-hydrated": "0.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.14",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.14.tgz",
+ "integrity": "sha512-bBODCWZK7JTbQLHs0uIP4f73wIWatakK4OS33UzkR1x897wu0PuO658a3f+6P2GEGyDzGYMuHRatMVoAk9WZTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.2",
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.3.tgz",
+ "integrity": "sha512-L5RQTXz6Anxsf9CCv+pTgiAsUpyVj7rJxsGtmhFaEOJ++cVfXucv4qWfsIO0AIB4NAhi3yovWGVMKKS1Xf1Wrg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.2",
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.15",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.12",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-popper": "1.3.3",
+ "@radix-ui/react-portal": "1.1.13",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-visually-hidden": "1.2.7",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.11.tgz",
+ "integrity": "sha512-jRhe86+8PF7VZ1u14eOWVOuh2BuAhALg/FT1VcMC4OHedMTRUazDnDlKTt+yxo5cRNKHMfmvZ4sSQtWDeMV4CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slider": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.3.tgz",
+ "integrity": "sha512-CWVVj+XaTom0SKCqw1EUgb0NuiLwS+N3OFG73mVEezKEjgNIvZiu0EevMelSSU+CbX3owbqJweG2gPU31WGC5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.2",
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz",
+ "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.3.tgz",
+ "integrity": "sha512-1+mlB4/lxJfk5tgJ4g+R5mUCbRpPE1T9+UsEyeLYbGgMtwiMgmuTnfKz4Mw1nHALHjuwyxw4MLd4cSHn6pNSlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-previous": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.17",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.17.tgz",
+ "integrity": "sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-roving-focus": "1.1.15",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.19.tgz",
+ "integrity": "sha512-SxfVZfVOibWKWdkf0Xx1awW2d09fQu4V4PXDY1j5hi4MVf7MWdJZqTBJMa1KWtOr1S6GGtCk02nniZ0Iia+dHw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-dismissable-layer": "1.1.15",
+ "@radix-ui/react-portal": "1.1.13",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-visually-hidden": "1.2.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.14.tgz",
+ "integrity": "sha512-QI/hB65XKWACA66P64A+aHxtLUgHJeJLkaQa+awUNXT6T3swndtY5DojeHA+vldrTspMTtFBd7HfZ9QGbM1Qrw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle-group": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.15.tgz",
+ "integrity": "sha512-gIC5Q+Xljg7lmUdzSuDoy0t97yZn1sZl00Ra37ZvKrYdWnQLU6sWLd09yG8cIB9jUAlQfHgJ2ACAG00MFwsqSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-roving-focus": "1.1.15",
+ "@radix-ui/react-toggle": "1.1.14",
+ "@radix-ui/react-use-controllable-state": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toolbar": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.15.tgz",
+ "integrity": "sha512-t/iEuVjUnXXtrsGK40AA43uIx37sn3AqZ7oAVnPICK6lFJP6dzMzWR3U9b6eCfFjb6wtSEqkJ9Rn9xDjiOx20g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-roving-focus": "1.1.15",
+ "@radix-ui/react-separator": "1.1.11",
+ "@radix-ui/react-toggle-group": "1.1.15"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.2.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.12.tgz",
+ "integrity": "sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-dismissable-layer": "1.1.15",
+ "@radix-ui/react-id": "1.1.2",
+ "@radix-ui/react-popper": "1.3.3",
+ "@radix-ui/react-portal": "1.1.13",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-visually-hidden": "1.2.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz",
+ "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz",
+ "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.3",
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz",
+ "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.3.tgz",
+ "integrity": "sha512-3wEkMiPHXha/2VadZ68rYBcmYnPINVGl4Y3gtcM7fKRjANk0OscK+cdqBgUWdozb7YJxsh0vefM7vgAMHXOjqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-is-hydrated": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz",
+ "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz",
+ "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz",
+ "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz",
+ "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.7.tgz",
+ "integrity": "sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz",
+ "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==",
+ "license": "MIT"
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "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/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/@sd-jwt/core": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@sd-jwt/core/-/core-0.20.0.tgz",
+ "integrity": "sha512-thTj5xtKqOvqnULELJGvCa64Hf+Hf7v0Dlao6mTh198rmZcyFCBop3vT1ShgWMhg5cL04ofXeiEhnQR10/8wjA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@owf/identity-common": "^0.1.0-alpha-20260312123226"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@sd-jwt/sd-jwt-vc": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@sd-jwt/sd-jwt-vc/-/sd-jwt-vc-0.20.0.tgz",
+ "integrity": "sha512-V+pwRt4nRPDPnrHPRCbZubM3jL1Yn2DOT/AM/N/AawbFLWxsIf2HM7ioTrhpXRFC2kVFnWFmgNcq4mXXAzOBCA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@owf/token-status-list": "^0.1.0-alpha-20260312123226",
+ "@sd-jwt/core": "0.20.0",
+ "zod": "^4.3.5"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@sec-ant/readable-stream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
+ "license": "MIT"
+ },
+ "node_modules/@simplewebauthn/browser": {
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.3.0.tgz",
+ "integrity": "sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ==",
+ "license": "MIT"
+ },
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+ "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz",
+ "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "5.21.6",
+ "jiti": "^2.7.0",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.3.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz",
+ "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.3.2",
+ "@tailwindcss/oxide-darwin-arm64": "4.3.2",
+ "@tailwindcss/oxide-darwin-x64": "4.3.2",
+ "@tailwindcss/oxide-freebsd-x64": "4.3.2",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.3.2",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.3.2",
+ "@tailwindcss/oxide-linux-x64-musl": "4.3.2",
+ "@tailwindcss/oxide-wasm32-wasi": "4.3.2",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.3.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz",
+ "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz",
+ "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz",
+ "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz",
+ "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz",
+ "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz",
+ "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz",
+ "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz",
+ "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz",
+ "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz",
+ "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.11.1",
+ "@emnapi/runtime": "^1.11.1",
+ "@emnapi/wasi-threads": "^1.2.2",
+ "@napi-rs/wasm-runtime": "^1.1.4",
+ "@tybys/wasm-util": "^0.10.2",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz",
+ "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz",
+ "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz",
+ "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.3.2",
+ "@tailwindcss/oxide": "4.3.2",
+ "tailwindcss": "4.3.2"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/@tanstack/react-table": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz",
+ "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/table-core": "8.21.3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/@tanstack/table-core": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
+ "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@ts-morph/common": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz",
+ "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-glob": "^3.3.3",
+ "minimatch": "^10.0.1",
+ "path-browserify": "^1.0.1"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.20.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
+ "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/qrcode": {
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
+ "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.17",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
+ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/validate-npm-package-name": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz",
+ "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==",
+ "license": "MIT"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/abitype": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz",
+ "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==",
+ "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/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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",
+ "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/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ast-types": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz",
+ "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/atomically": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
+ "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.43",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz",
+ "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
+ "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "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/browserslist": {
+ "version": "4.28.6",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz",
+ "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.42",
+ "caniuse-lite": "^1.0.30001803",
+ "electron-to-chromium": "^1.5.389",
+ "node-releases": "^2.0.51",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bundle-name": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
+ "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "run-applescript": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001805",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz",
+ "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/cbor-extract": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.2.tgz",
+ "integrity": "sha512-hlSxxI9XO2yQfe9g6msd3g4xCfDqK5T5P0fRMLuaLHhxn4ViPrm+a+MUfhrvH2W962RGxcBwEGzLQyjbDG1gng==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "node-gyp-build-optional-packages": "5.1.1"
+ },
+ "bin": {
+ "download-cbor-prebuilds": "bin/download-prebuilds.js"
+ },
+ "optionalDependencies": {
+ "@cbor-extract/cbor-extract-darwin-arm64": "2.2.2",
+ "@cbor-extract/cbor-extract-darwin-x64": "2.2.2",
+ "@cbor-extract/cbor-extract-linux-arm": "2.2.2",
+ "@cbor-extract/cbor-extract-linux-arm64": "2.2.2",
+ "@cbor-extract/cbor-extract-linux-x64": "2.2.2",
+ "@cbor-extract/cbor-extract-win32-x64": "2.2.2"
+ }
+ },
+ "node_modules/cbor-x": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.4.tgz",
+ "integrity": "sha512-UGKHjp6RHC6QuZ2yy5LCKm7MojM4716DwoSaqwQpaH4DvZvbBTGcoDNTiG9Y2lByXZYFEs9WRkS5tLl96IrF1Q==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "cbor-extract": "^2.2.2"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/cliui/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"
+ },
+ "node_modules/cliui/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",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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"
+ },
+ "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/commander": {
+ "version": "14.0.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
+ "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/conf": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz",
+ "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.6.3",
+ "ajv-formats": "^2.1.1",
+ "atomically": "^1.7.0",
+ "debounce-fn": "^4.0.0",
+ "dot-prop": "^6.0.1",
+ "env-paths": "^2.2.1",
+ "json-schema-typed": "^7.0.3",
+ "onetime": "^5.1.2",
+ "pkg-up": "^3.1.0",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/conf/node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/conf/node_modules/json-schema-typed": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz",
+ "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/conf/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cosmiconfig": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz",
+ "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==",
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.1",
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cross-spawn/node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/cross-spawn/node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/debounce-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz",
+ "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/dedent": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz",
+ "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/default-browser": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
+ "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
+ "license": "MIT",
+ "dependencies": {
+ "bundle-name": "^4.1.0",
+ "default-browser-id": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser-id": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
+ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
+ "node_modules/diff": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
+ "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "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"
+ },
+ "node_modules/dot-prop": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
+ "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
+ "license": "MIT",
+ "dependencies": {
+ "is-obj": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "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/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.391",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.391.tgz",
+ "integrity": "sha512-YmCu4856jkgKT1Nh6fwRdeVrM6Ydf/fBnq51tpmSfX+jOcUMTxh31yH6hjKScRenhB2oDSvA9oooxcpjogPeig==",
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.21.6",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz",
+ "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/enquirer": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
+ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-colors": "^4.1.1",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.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/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/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz",
+ "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz",
+ "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/merge-streams": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "figures": "^6.1.0",
+ "get-stream": "^9.0.0",
+ "human-signals": "^8.0.1",
+ "is-plain-obj": "^4.1.0",
+ "is-stream": "^4.0.1",
+ "npm-run-path": "^6.0.0",
+ "pretty-ms": "^9.2.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^4.0.0",
+ "yoctocolors": "^2.1.1"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.5.2",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz",
+ "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.2.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "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",
+ "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/fast-uri": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
+ "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "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",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/figures": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
+ "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-unicode-supported": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.3.6",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz",
+ "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "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/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/fuzzysort": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz",
+ "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==",
+ "license": "MIT"
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/get-own-enumerable-keys": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz",
+ "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/get-stream": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
+ "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sec-ant/readable-stream": "^0.4.1",
+ "is-stream": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
+ "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/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/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/hono": {
+ "version": "4.12.30",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.30.tgz",
+ "integrity": "sha512-emn+JoJjrN9YTpRDS5it/UI2SO9BAE37T6I3d963RxcZ81G9A4pr2SZTEiiaiKbzx+NKRg5BZ89fCL7gCJCUog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
+ "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
+ "node_modules/is-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
+ "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",
+ "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",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-in-ssh": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
+ "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^3.0.0"
+ },
+ "bin": {
+ "is-inside-container": "cli.js"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-interactive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
+ "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-regexp": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz",
+ "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
+ "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+ "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
+ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-inside-container": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
+ "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/isows": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz",
+ "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/wevm"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "ws": "*"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/jose": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
+ "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "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/js-yaml": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
+ "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
+ "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/log-symbols": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
+ "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.3.0",
+ "is-unicode-supported": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/is-unicode-supported": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
+ "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.525.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz",
+ "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "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/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "license": "MIT"
+ },
+ "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",
+ "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/micromatch/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/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
+ "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/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",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-gyp-build-optional-packages": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz",
+ "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^2.0.1"
+ },
+ "bin": {
+ "node-gyp-build-optional-packages": "bin.js",
+ "node-gyp-build-optional-packages-optional": "optional.js",
+ "node-gyp-build-optional-packages-test": "build-test.js"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.51",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz",
+ "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
+ "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0",
+ "unicorn-magic": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "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/object-treeify": {
+ "version": "1.1.33",
+ "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz",
+ "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/onetime/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/open": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz",
+ "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
+ "license": "MIT",
+ "dependencies": {
+ "default-browser": "^5.4.0",
+ "define-lazy-prop": "^3.0.0",
+ "is-in-ssh": "^1.0.0",
+ "is-inside-container": "^1.0.0",
+ "powershell-utils": "^0.1.0",
+ "wsl-utils": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz",
+ "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.3.0",
+ "cli-cursor": "^5.0.0",
+ "cli-spinners": "^2.9.2",
+ "is-interactive": "^2.0.0",
+ "is-unicode-supported": "^2.0.0",
+ "log-symbols": "^6.0.0",
+ "stdin-discarder": "^0.2.2",
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ora/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/ox": {
+ "version": "0.14.30",
+ "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.30.tgz",
+ "integrity": "sha512-LI11uu+8iiM1B3CLckgd++YF1a0A2k5wDoM9ZeQMiL21BOzQs6L//BLS6hb1HSEKCyycdDIQLsVQx9MjpcC0hA==",
+ "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.2.3",
+ "eventemitter3": "5.0.1"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "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",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pako": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz",
+ "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-ms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
+ "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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"
+ },
+ "node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/pkg-up": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
+ "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "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",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz",
+ "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==",
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postmate": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/postmate/-/postmate-1.5.2.tgz",
+ "integrity": "sha512-EHLlEmrUA/hALls49oBrtE7BzDXXjB9EiO4MZpsoO3R/jRuBmD+2WKQuYAbeuVEpTzrPpUTT79z2cz4qaFgPRg==",
+ "license": "MIT"
+ },
+ "node_modules/powershell-utils": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
+ "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pretty-ms": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz",
+ "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==",
+ "license": "MIT",
+ "dependencies": {
+ "parse-ms": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/prompts/node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qrcode": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+ "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+ "license": "MIT",
+ "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.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "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"
+ },
+ "node_modules/radix-ui": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.2.tgz",
+ "integrity": "sha512-OwYUjzMwiInCUxgAWpPsavXC3Kh4iyi/49uU1/qZTG3RQDlvegyk1GOMiGvSkjua1RDb3JD3fo3eroL9FV4GQw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.5",
+ "@radix-ui/react-accessible-icon": "1.1.11",
+ "@radix-ui/react-accordion": "1.2.16",
+ "@radix-ui/react-alert-dialog": "1.1.19",
+ "@radix-ui/react-arrow": "1.1.11",
+ "@radix-ui/react-aspect-ratio": "1.1.11",
+ "@radix-ui/react-avatar": "1.2.2",
+ "@radix-ui/react-checkbox": "1.3.7",
+ "@radix-ui/react-collapsible": "1.1.16",
+ "@radix-ui/react-collection": "1.1.12",
+ "@radix-ui/react-compose-refs": "1.1.3",
+ "@radix-ui/react-context": "1.2.0",
+ "@radix-ui/react-context-menu": "2.3.3",
+ "@radix-ui/react-dialog": "1.1.19",
+ "@radix-ui/react-direction": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.15",
+ "@radix-ui/react-dropdown-menu": "2.1.20",
+ "@radix-ui/react-focus-guards": "1.1.4",
+ "@radix-ui/react-focus-scope": "1.1.12",
+ "@radix-ui/react-form": "0.1.12",
+ "@radix-ui/react-hover-card": "1.1.19",
+ "@radix-ui/react-label": "2.1.11",
+ "@radix-ui/react-menu": "2.1.20",
+ "@radix-ui/react-menubar": "1.1.20",
+ "@radix-ui/react-navigation-menu": "1.2.18",
+ "@radix-ui/react-one-time-password-field": "0.1.12",
+ "@radix-ui/react-password-toggle-field": "0.1.7",
+ "@radix-ui/react-popover": "1.1.19",
+ "@radix-ui/react-popper": "1.3.3",
+ "@radix-ui/react-portal": "1.1.13",
+ "@radix-ui/react-presence": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.7",
+ "@radix-ui/react-progress": "1.1.12",
+ "@radix-ui/react-radio-group": "1.4.3",
+ "@radix-ui/react-roving-focus": "1.1.15",
+ "@radix-ui/react-scroll-area": "1.2.14",
+ "@radix-ui/react-select": "2.3.3",
+ "@radix-ui/react-separator": "1.1.11",
+ "@radix-ui/react-slider": "1.4.3",
+ "@radix-ui/react-slot": "1.3.0",
+ "@radix-ui/react-switch": "1.3.3",
+ "@radix-ui/react-tabs": "1.1.17",
+ "@radix-ui/react-toast": "1.2.19",
+ "@radix-ui/react-toggle": "1.1.14",
+ "@radix-ui/react-toggle-group": "1.1.15",
+ "@radix-ui/react-toolbar": "1.1.15",
+ "@radix-ui/react-tooltip": "1.2.12",
+ "@radix-ui/react-use-callback-ref": "1.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.3",
+ "@radix-ui/react-use-effect-event": "0.0.3",
+ "@radix-ui/react-use-escape-keydown": "1.1.3",
+ "@radix-ui/react-use-is-hydrated": "0.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.2",
+ "@radix-ui/react-use-size": "1.1.2",
+ "@radix-ui/react-visually-hidden": "1.2.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
+ "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-colorful": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.8.0.tgz",
+ "integrity": "sha512-Wy9OzPfjSN9bF12OB8N7UQvlsZ0I+7wHxpN+bV5BjNQGxOj6IiwkRjevJK9yOBjJWGQvAaf1OXtn8rUeEatAng==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
+ "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.7"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/recast": {
+ "version": "0.23.12",
+ "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.12.tgz",
+ "integrity": "sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA==",
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.16.1",
+ "esprima": "~4.0.0",
+ "source-map": "~0.6.1",
+ "tiny-invariant": "^1.3.3",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "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",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "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"
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-function": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.62.2",
+ "@rollup/rollup-android-arm64": "4.62.2",
+ "@rollup/rollup-darwin-arm64": "4.62.2",
+ "@rollup/rollup-darwin-x64": "4.62.2",
+ "@rollup/rollup-freebsd-arm64": "4.62.2",
+ "@rollup/rollup-freebsd-x64": "4.62.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-musl": "4.62.2",
+ "@rollup/rollup-openbsd-x64": "4.62.2",
+ "@rollup/rollup-openharmony-arm64": "4.62.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/run-applescript": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
+ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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"
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shadcn": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.13.0.tgz",
+ "integrity": "sha512-5fuJ4jI/GcPeA/iTL4cJivCZuYQGXz/N3bIzyd+Gd/FM6xUCy2MxGG+LaDQuw2cjNy9zGPSFPTEmI048UwPTZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/parser": "^7.28.0",
+ "@babel/plugin-transform-typescript": "^7.28.0",
+ "@babel/preset-typescript": "^7.27.1",
+ "@dotenvx/dotenvx": "^1.48.4",
+ "@modelcontextprotocol/sdk": "^1.26.0",
+ "@types/validate-npm-package-name": "^4.0.2",
+ "browserslist": "^4.26.2",
+ "commander": "^14.0.0",
+ "cosmiconfig": "^9.0.0",
+ "dedent": "^1.6.0",
+ "deepmerge": "^4.3.1",
+ "diff": "^8.0.2",
+ "execa": "^9.6.0",
+ "fast-glob": "^3.3.3",
+ "fs-extra": "^11.3.1",
+ "fuzzysort": "^3.1.0",
+ "kleur": "^4.1.5",
+ "open": "^11.0.0",
+ "ora": "^8.2.0",
+ "postcss": "^8.5.6",
+ "postcss-selector-parser": "^7.1.0",
+ "prompts": "^2.4.2",
+ "recast": "^0.23.11",
+ "stringify-object": "^5.0.0",
+ "tailwind-merge": "^3.0.1",
+ "ts-morph": "^26.0.0",
+ "tsconfig-paths": "^4.2.0",
+ "undici": "^7.27.2",
+ "validate-npm-package-name": "^7.0.1",
+ "zod": "^3.24.1",
+ "zod-to-json-schema": "^3.24.6"
+ },
+ "bin": {
+ "shadcn": "dist/index.js"
+ },
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "node_modules/shadcn/node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "license": "MIT"
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.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",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/stdin-discarder": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
+ "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/stringify-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz",
+ "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "get-own-enumerable-keys": "^1.0.0",
+ "is-obj": "^3.0.0",
+ "is-regexp": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/yeoman/stringify-object?sponsor=1"
+ }
+ },
+ "node_modules/stringify-object/node_modules/is-obj": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz",
+ "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
+ "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/systeminformation": {
+ "version": "5.31.17",
+ "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.17.tgz",
+ "integrity": "sha512-TvFA9iwDWlMjqZVlKIJ0Cy+Zgm9ttlMx0SMRwJDMNKyhlEKWBMb3+WRwDi/3dvHdWbexpos4Osp4U49p5WjB5g==",
+ "license": "MIT",
+ "os": [
+ "darwin",
+ "linux",
+ "win32",
+ "freebsd",
+ "openbsd",
+ "netbsd",
+ "sunos",
+ "android"
+ ],
+ "bin": {
+ "systeminformation": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "Buy me a coffee",
+ "url": "https://www.buymeacoffee.com/systeminfo"
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz",
+ "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz",
+ "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+ "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "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/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/ts-brand": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/ts-brand/-/ts-brand-0.2.0.tgz",
+ "integrity": "sha512-H5uo7OqMvd91D2EefFmltBP9oeNInNzWLAZUSt6coGDn8b814Eis6SnEtzyXORr9ccEb38PfzyiRVDacdkycSQ==",
+ "license": "MIT"
+ },
+ "node_modules/ts-morph": {
+ "version": "26.0.0",
+ "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz",
+ "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@ts-morph/common": "~0.27.0",
+ "code-block-writer": "^13.0.3"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
+ "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+ "license": "MIT",
+ "dependencies": {
+ "json5": "^2.2.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/tw-animate-css": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
+ "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
+ "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "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"
+ },
+ "node_modules/unicorn-magic": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+ "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "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/validate-npm-package-name": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz",
+ "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==",
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/viem": {
+ "version": "2.55.2",
+ "resolved": "https://registry.npmjs.org/viem/-/viem-2.55.2.tgz",
+ "integrity": "sha512-XlJeyNAZ96dQfOHlxLTK1FKgtWw/TtxENKNMBSBgxqALjiWiBWrFmSSzwwMivryKnBwkbt5E+90jSCLnVEilLA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/wevm"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@noble/curves": "1.9.1",
+ "@noble/hashes": "1.8.0",
+ "@scure/bip32": "1.7.0",
+ "@scure/bip39": "1.6.0",
+ "abitype": "1.2.3",
+ "isows": "1.0.7",
+ "ox": "0.14.30",
+ "ws": "8.21.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.4"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
+ "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
+ "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^3.1.1"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^16.13.0 || >=18.0.0"
+ }
+ },
+ "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"
+ },
+ "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",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/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"
+ },
+ "node_modules/wrap-ansi/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",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
+ "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/wsl-utils": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz",
+ "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-wsl": "^3.1.0",
+ "powershell-utils": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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"
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "license": "ISC"
+ },
+ "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",
+ "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",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/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"
+ },
+ "node_modules/yargs/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",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yocto-spinner": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.1.tgz",
+ "integrity": "sha512-9cbFWLhbiZp+820O4pkHGNncI7+MrUGzBOjw8NMG+ewsY+aG0DdEXnr19Smxao32YOjLZRMdn1UtaxcrXOYOIg==",
+ "license": "MIT",
+ "dependencies": {
+ "yoctocolors": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=18.19"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yoctocolors": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",
+ "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.2",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
+ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25.28 || ^4"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.14",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz",
+ "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..a4c2321
--- /dev/null
+++ b/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "@1shotapi/embedded-wallet",
+ "version": "0.0.0",
+ "private": true,
+ "description": "1Shot Wallet — OWS Branding Layer (React + Vite + Tailwind)",
+ "type": "module",
+ "workspaces": [
+ "host"
+ ],
+ "scripts": {
+ "dev": "node scripts/dev.mjs",
+ "dev:local": "node scripts/dev.mjs --no-tunnel",
+ "dev:host": "npm run dev -w @1shotapi/oneshot-wallet-host",
+ "build": "tsc -p tsconfig.json --noEmit && vite build && node scripts/copy-signer.mjs",
+ "build:host": "npm run build -w @1shotapi/oneshot-wallet-host",
+ "clean": "node scripts/clean.mjs && npm run clean -w @1shotapi/oneshot-wallet-host",
+ "lint": "tsc -p tsconfig.json --noEmit",
+ "preview": "vite preview",
+ "dockerize": "docker build -t oneshot-wallet ."
+ },
+ "dependencies": {
+ "@1shotapi/ows-oid4": "^0.1.1",
+ "@1shotapi/ows-signer": "^0.2.1",
+ "@1shotapi/ows-signer-utils": "^0.2.0",
+ "@1shotapi/ows-types": "^0.1.3",
+ "@1shotapi/ows-wallet-utils": "^0.1.2",
+ "@fontsource-variable/geist": "^5.2.9",
+ "@simplewebauthn/browser": "^13.3.0",
+ "@tanstack/react-table": "^8.21.3",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.525.0",
+ "qrcode": "^1.5.4",
+ "radix-ui": "^1.6.2",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "shadcn": "^4.13.0",
+ "tailwind-merge": "^3.3.1",
+ "ts-brand": "^0.2.0",
+ "viem": "^2.55.2",
+ "zod": "^4.4.3",
+ "zustand": "^5.0.14"
+ },
+ "devDependencies": {
+ "@ngrok/ngrok": "^1.7.0",
+ "@tailwindcss/vite": "^4.1.11",
+ "@types/node": "^22.15.0",
+ "@types/qrcode": "^1.5.6",
+ "@types/react": "^19.1.8",
+ "@types/react-dom": "^19.1.6",
+ "@vitejs/plugin-react": "^4.6.0",
+ "dotenv": "^16.4.7",
+ "tailwindcss": "^4.1.11",
+ "tw-animate-css": "^1.3.5",
+ "typescript": "~5.8.0",
+ "vite": "^7.0.4"
+ }
+}
diff --git a/roadmap.md b/roadmap.md
new file mode 100644
index 0000000..faf848e
--- /dev/null
+++ b/roadmap.md
@@ -0,0 +1,225 @@
+# 1Shot Wallet — ShadCN + customization roadmap
+
+Phased refactor of the Branding Layer UI onto ShadCN, with host-driven theming via `setStyle`, and cleaner state boundaries. Work proceeds **one phase (or one modal) per query**.
+
+## Goals
+
+1. **Integrator customization** — Host calls `proxy.rpc("setStyle", options)` before / during display; wallet applies CSS tokens + copy overrides. Re-callable for the future customization tester site.
+2. **ShadCN UI** — Replace bespoke Tailwind shells/modals with tokenized shadcn/ui components.
+3. **State that scales** — Theme in Context; wallet session / modal queue in Zustand; server data (later) via TanStack Query.
+
+## Architecture (locked)
+
+```
+Host OWSProxy
+ └── rpc("setStyle", IStyleOptions) # branding-app registerRpc
+ └── StyleProvider (React Context)
+ ├── CSS variables on document / root
+ └── copy / labels object
+
+Wallet session + modal queue → Zustand stores
+Durable keys / addresses → localStorage (existing storage.ts)
+Relayer / credential fetch → TanStack Query (later phase)
+```
+
+### UI ground rules
+
+- No hardcoded brand colors — use semantic tokens (`bg-primary`, `text-muted-foreground`, etc.).
+- User-visible copy comes from StyleContext defaults / overrides, not scattered string literals.
+- `setStyle` merges into current style state (additive, re-applicable).
+- Defaults = 1Shot branding when host never calls `setStyle`.
+- Style state stays separate from unlock / chain / modal runtime state.
+
+---
+
+## Inventory — current UI surfaces
+
+### Shell (non-modal)
+
+| Surface | File | Notes |
+|---------|------|--------|
+| App shell | `src/App.tsx` | Loading / onboarding / main switch |
+| Embedded chrome | `src/components/WalletChrome.tsx` | Title + hide |
+| Onboarding | `src/components/OnboardingPanel.tsx` | Login vs create |
+| Main panel | `src/components/MainPanel.tsx` | Status, addresses, chain, actions |
+| Modal shell | `src/components/Modal.tsx` | Custom dialog — replace with shadcn Dialog |
+| Modal router | `src/components/ModalHost.tsx` | Switches on `activeModal.kind` |
+| Signer host | `src/components/SignerHost.tsx` | Keep minimal; no visual design |
+
+### Modals (`activeModal.kind`)
+
+| Kind | Component file | Purpose |
+|------|----------------|---------|
+| `walletSetup` | `SetupModals.tsx` → `WalletSetupModal` | Login / create / cancel |
+| `passkeyName` | `SetupModals.tsx` → `PasskeyNameModal` | Name for new passkey |
+| `connect` | `SetupModals.tsx` → `ConnectModal` | Account connect consent |
+| `personalSign` | `SignModals.tsx` → `PersonalSignModal` | EIP-191 consent |
+| `typedData` | `SignModals.tsx` → `TypedDataModal` | EIP-712 consent |
+| `credentialOffer` | `CredentialModals.tsx` → `CredentialOfferModal` | OID4VCI accept |
+| `credentialPresentation` | `CredentialModals.tsx` → `CredentialPresentationModal` | OID4VP disclose |
+| `credentialList` | _(removed)_ | Replaced by Credentials tab |
+| `credentials` | `CredentialsTab.tsx` + `CredentialDetailDialog.tsx` | Stored credentials table + detail |
+| `createBackup` | `BackupModals.tsx` → `CreateBackupModal` | Recovery create + overlay |
+| `restoreBackup` | `BackupModals.tsx` → `RestoreBackupModal` | Recovery restore + overlay |
+
+---
+
+## Phases
+
+### Phase 0 — Foundations (style + RPC, no visual redesign)
+
+**Outcome:** Host can call `setStyle`; CSS vars + copy map exist; shadcn theme CSS restored alongside wallet layout.
+
+- [x] Define `IStyleOptions` (theme tokens + copy/labels + feature flags as needed).
+- [x] Defaults module (`DEFAULT_STYLE` / 1Shot branding).
+- [x] `applyStyleToDocument(options)` → set CSS variables on `document.documentElement` (or `#root`).
+- [x] `StyleProvider` + `useStyle()` context.
+- [x] Register `wallet.registerRpc("setStyle", …)` **before** `wallet.start()` (merge + apply).
+- [x] Restore / merge shadcn theme into `src/index.css` (css variables + Tailwind) without breaking `/signer/` host styles.
+- [x] Smoke: call `setStyle` from a temporary button or host console; primary/background tokens update.
+- [x] Bootstrap Host integrator skill (`skills/oneshot-embedded-wallet`) documenting `setStyle` + wallet URL.
+
+**Exit criteria:** Defaults render; RPC path works; no modal UI rewritten yet.
+
+---
+
+### Phase 1 — Shell on ShadCN + tokens
+
+**Outcome:** App chrome uses Button / layout primitives; reads brand name / labels from StyleContext. Host test app exercises `setStyle` over Host ↔ Branding RPC (not an in-wallet debug button).
+
+- [x] Wire `StyleProvider` around app in `main.tsx`.
+- [x] Refactor `WalletChrome` → tokens + styled title from copy map.
+- [x] Refactor `OnboardingPanel` → shadcn Button / typography.
+- [x] Refactor `MainPanel` → shadcn Button, Select (chain).
+- [x] Replace shell ad-hoc `` styles with `@/components/ui/button`.
+- [x] Ensure shell respects `setStyle` colors and product name.
+- [x] Add `host/` test Host Layer (from OWS `examples/host`) with setStyle knobs; `npm run dev:host`.
+
+**Exit criteria:** Standalone + embedded shell looks like a coherent 1Shot product; theme knobs on the host exercise host↔branding communication.
+
+---
+
+### Phase 2 — Modal system (Dialog primitive)
+
+**Outcome:** One shadcn Dialog-based `Modal` / `AppDialog` used by all kinds; ModalHost unchanged in routing.
+
+- [x] `npx shadcn add dialog` (and Input / Label / Textarea as needed).
+- [x] Replace `src/components/Modal.tsx` with shadcn Dialog wrapper (actions, title, description slots).
+- [x] Keep `ModalHost` switch; each modal migrates in later phases.
+- [x] Accessibility: focus trap, Escape → reject/cancel where appropriate.
+
+**Exit criteria:** At least one modal (prefer `connect`) uses the new Dialog shell end-to-end.
+
+---
+
+### Phase 3 — Setup & connect modals
+
+Migrate one kind at a time:
+
+1. [x] `connect` — `copy.connect.*` via StyleContext; Dialog + Button tokens
+2. [x] `walletSetup` — `copy.walletSetup.*` via StyleContext; Dialog + Button tokens
+3. [x] `passkeyName` — `copy.passkeyName.*` via StyleContext; Dialog + Button tokens
+
+Copy via StyleContext (titles, button labels). Tokens only for colors.
+
+---
+
+### Phase 4 — Signing modals
+
+1. [x] `personalSign` — `copy.personalSign.*` via StyleContext; token-styled detail blocks
+2. [x] `typedData` — `copy.typedData.*` via StyleContext; token-styled detail blocks
+
+Preserve request detail display; avoid overstuffing the first viewport of the flyout.
+
+---
+
+### Phase 5 — Credential modals
+
+1. [x] `credentialOffer` — `copy.credentialOffer.*` via StyleContext (`{issuerName}` / `{issuerId}` templates)
+2. [x] `credentialPresentation` — `copy.credentialPresentation.*` via StyleContext
+3. [x] `credentials` — `copy.credentials.*` via StyleContext (tab + detail dialog)
+
+Keep OID4 wiring in `registerCredentialsProvider` / WalletProvider; UI-only change.
+
+---
+
+### Phase 6 — Backup / recovery modals
+
+1. [x] `createBackup` — `copy.createBackup.*` via StyleContext; `overlaySignerIframe` + `waitForSignerSlot` unchanged
+2. [x] `restoreBackup` — `copy.restoreBackup.*` via StyleContext; Signing Layer labels from copy
+
+**Do not** reparent the signer iframe — keep `overlaySignerIframe`. Passphrase UI can sit in Dialog; signer overlay behavior stays as today.
+
+---
+
+### Phase 7 — Zustand session + modal queue
+
+**Outcome:** `WalletProvider` sheds UI state into stores; OWS handlers call stores from outside React.
+
+- [x] Add `zustand`.
+- [x] `useWalletSessionStore` — ready, unlocked, addresses, chain, credentialCount, bootError, walletCreated.
+- [x] `useModalStore` — queue, push/pop, `activeModal`.
+- [x] Thin `WalletProvider` (boot, refs, `ensureReady`, register handlers) that syncs into stores.
+- [x] Update consumers (`useWallet` → store selectors where appropriate).
+
+**Exit criteria:** Opening a modal does not force unrelated shell re-renders; handlers remain correct.
+
+---
+
+### Phase 8 — Polish + customization tester prep
+
+**Outcome:** Host tester is a professional playground for `setStyle`; wallet docs catch up.
+
+- [ ] Document `IStyleOptions` and example host `setStyle` call in README.
+- [x] Expand knobs as needed (logo URL, colors, radius, font; showBackup later).
+- [x] Host tester: React + ShadCN shell; style knobs encapsulated in `WalletConfigurator` (organization TBD).
+- [x] Host layout v1: 1Shot-branded header + Sidebar (Test / Design); Design embeds wallet inline with configurator.
+- [x] Wallet presentation: MetaMask-like 360×600; create-time `presentationMode`; Test/Design destroy+recreate proxy (no reparent).
+- [x] Reorganize `WalletConfigurator` (Basic / Style / Text tabs; color picker + accordion).
+- [ ] Dark mode strategy if we support host toggle via `setStyle`.
+
+**Exit criteria:** Host looks intentional; `setStyle` knobs are maintainable as we grow the wallet UI.
+---
+
+### Later (out of this refactor)
+
+- TanStack Query for relayer credential blob / balances.
+- Full host-side customization tester site (separate app using `ows-provider`).
+- Production 1Shot visual identity beyond shadcn nova defaults (brand fonts/imagery rules).
+
+---
+
+## Suggested shadcn components (add as phases need them)
+
+| Component | Used by |
+|-----------|---------|
+| `button` | Shell, all modals (already present) |
+| `dialog` | Modal system |
+| `input` / `label` | passkey name, backup passphrase |
+| `textarea` | typed data / backup paste |
+| `select` | chain switch |
+| `separator` | Main panel sections |
+| `badge` / `scroll-area` | credential list (optional) |
+
+---
+
+## Working agreement
+
+- One phase or one modal per chat query unless explicitly batched.
+- Prefer grepping `examples/general-wallet` + this roadmap over reinventing flows.
+- Keep OWS boot order: register handlers (including `setStyle`) → `wallet.start()` → deferred signer.
+- No deprecation shims — update call sites when renaming.
+
+## Progress
+
+| Phase | Status |
+|-------|--------|
+| 0 Foundations | done |
+| 1 Shell | pending |
+| 2 Modal system | pending |
+| 3 Setup modals | pending |
+| 4 Sign modals | pending |
+| 5 Credential modals | pending |
+| 6 Backup modals | pending |
+| 7 Zustand | pending |
+| 8 Polish | pending |
diff --git a/scripts/clean.mjs b/scripts/clean.mjs
new file mode 100644
index 0000000..26d0615
--- /dev/null
+++ b/scripts/clean.mjs
@@ -0,0 +1,8 @@
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const dist = path.resolve(__dirname, "../dist");
+fs.rmSync(dist, { recursive: true, force: true });
+console.log("Removed dist/");
diff --git a/scripts/copy-signer.mjs b/scripts/copy-signer.mjs
new file mode 100644
index 0000000..1e4a909
--- /dev/null
+++ b/scripts/copy-signer.mjs
@@ -0,0 +1,26 @@
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const repoRoot = path.resolve(__dirname, "..");
+const signerPkgSrc = path.join(
+ repoRoot,
+ "node_modules/@1shotapi/ows-signer/src",
+);
+const signerPublic = path.join(repoRoot, "signer-static/index.html");
+const outSigner = path.join(repoRoot, "dist/signer");
+
+if (!fs.existsSync(signerPkgSrc)) {
+ console.error(
+ "node_modules/@1shotapi/ows-signer/src missing. Run: npm install",
+ );
+ process.exit(1);
+}
+
+fs.rmSync(outSigner, { recursive: true, force: true });
+fs.mkdirSync(path.join(outSigner, "src"), { recursive: true });
+fs.cpSync(signerPkgSrc, path.join(outSigner, "src"), { recursive: true });
+fs.copyFileSync(signerPublic, path.join(outSigner, "index.html"));
+
+console.log("Copied Signing Layer to dist/signer/");
diff --git a/scripts/dev.mjs b/scripts/dev.mjs
new file mode 100644
index 0000000..5f7aa37
--- /dev/null
+++ b/scripts/dev.mjs
@@ -0,0 +1,141 @@
+/**
+ * Start Vite for Branding Layer + Signing Layer, optionally expose via ngrok.
+ * Loads NGROK_AUTHTOKEN from repo root .env (copy from .env.example).
+ *
+ * Usage:
+ * node scripts/dev.mjs # dev server + ngrok tunnel
+ * node scripts/dev.mjs --no-tunnel # local HTTP only
+ */
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { createServer } from "vite";
+import dotenv from "dotenv";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const repoRoot = path.resolve(__dirname, "..");
+dotenv.config({ path: path.join(repoRoot, ".env") });
+
+const preferredPort = Number(process.env.PORT ?? 5174);
+const noTunnel = process.argv.includes("--no-tunnel");
+let viteServer;
+let ngrokListener;
+let shuttingDown = false;
+/** Port Vite actually bound (must match ngrok `addr`). */
+let listenPort = preferredPort;
+
+async function startDevServer() {
+ viteServer = await createServer({
+ configFile: path.join(__dirname, "../vite.config.ts"),
+ server: {
+ port: preferredPort,
+ strictPort: true,
+ host: "0.0.0.0",
+ },
+ });
+ await viteServer.listen();
+
+ const address = viteServer.httpServer?.address();
+ if (address && typeof address === "object") {
+ listenPort = address.port;
+ } else {
+ listenPort = preferredPort;
+ }
+}
+
+async function startTunnel() {
+ const ngrok = (await import("@ngrok/ngrok")).default;
+ const domain = normalizeNgrokDomain(process.env.NGROK_DOMAIN);
+ const options = {
+ addr: listenPort,
+ authtoken_from_env: true,
+ };
+ if (domain) {
+ options.domain = domain;
+ }
+ ngrokListener = await ngrok.forward(options);
+ return ngrokListener.url();
+}
+
+/** Hostname only — accepts `immune-sheep-light.ngrok-free.app` or a full URL. */
+function normalizeNgrokDomain(value) {
+ const raw = value?.trim();
+ if (!raw) return undefined;
+ try {
+ const url = raw.includes("://") ? raw : `https://${raw}`;
+ return new URL(url).hostname;
+ } catch {
+ return raw.replace(/^https?:\/\//, "").replace(/\/+$/, "");
+ }
+}
+
+function printUrls(tunnelUrl) {
+ const localWallet = `http://localhost:${listenPort}/`;
+ const localSigner = `http://localhost:${listenPort}/signer/`;
+
+ console.log(`1Shot Wallet dev server: http://localhost:${listenPort}`);
+ console.log(` Branding Layer (local): ${localWallet}`);
+ console.log(` Signing Layer (local): ${localSigner}`);
+
+ if (tunnelUrl) {
+ const walletUrl = new URL("/", tunnelUrl).href;
+ const signerUrl = new URL("/signer/", tunnelUrl).href;
+ console.log(` Branding Layer (ngrok): ${walletUrl}`);
+ console.log(` Signing Layer (ngrok): ${signerUrl}`);
+ console.log(` Host env: WALLET_IFRAME_URL=${walletUrl}`);
+ }
+}
+
+async function shutdown(signal) {
+ if (shuttingDown) return;
+ shuttingDown = true;
+
+ try {
+ if (ngrokListener) {
+ await ngrokListener.close();
+ }
+ if (viteServer) {
+ await viteServer.close();
+ }
+ } catch (error) {
+ console.error(`Failed to shut down (${signal}):`, error);
+ process.exit(1);
+ return;
+ }
+
+ process.exit(0);
+}
+
+process.on("SIGINT", () => {
+ void shutdown("SIGINT");
+});
+process.on("SIGTERM", () => {
+ void shutdown("SIGTERM");
+});
+
+try {
+ await startDevServer();
+
+ let tunnelUrl;
+ if (!noTunnel) {
+ if (!process.env.NGROK_AUTHTOKEN) {
+ console.warn(
+ "NGROK_AUTHTOKEN not set — running local only. Copy .env.example to .env at repo root.",
+ );
+ } else {
+ tunnelUrl = await startTunnel();
+ }
+ }
+
+ printUrls(tunnelUrl);
+} catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ if (/Port .* is already in use|EADDRINUSE/i.test(message)) {
+ console.error(
+ `Port ${preferredPort} is already in use. Stop the other wallet process ` +
+ `(or anything on that port), then retry. A leftover \`dev:local\` / ` +
+ `\`--no-tunnel\` process will steal ngrok traffic and break the host handshake.`,
+ );
+ }
+ console.error("Failed to start wallet dev server:", error);
+ process.exit(1);
+}
diff --git a/signer-static/index.html b/signer-static/index.html
new file mode 100644
index 0000000..d7fdea8
--- /dev/null
+++ b/signer-static/index.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+ OWS Custody Signer
+
+
+
+
+
+
+
diff --git a/skills-lock.json b/skills-lock.json
new file mode 100644
index 0000000..f89bf95
--- /dev/null
+++ b/skills-lock.json
@@ -0,0 +1,10 @@
+{
+ "version": 1,
+ "skills": {
+ "ows-branding-layer": {
+ "source": "C:\\Users\\charl\\Source\\prf-wallet",
+ "sourceType": "local",
+ "computedHash": "b6e8aadef89264b05a76c525163272bebbadda53e64f005994fa742ea4d42063"
+ }
+ }
+}
diff --git a/skills/oneshot-embedded-wallet/SKILL.md b/skills/oneshot-embedded-wallet/SKILL.md
new file mode 100644
index 0000000..73b8728
--- /dev/null
+++ b/skills/oneshot-embedded-wallet/SKILL.md
@@ -0,0 +1,191 @@
+---
+name: oneshot-embedded-wallet
+description: >-
+ Integrate the 1Shot embedded wallet (OWS Host Layer) with @1shotapi/ows-provider.
+ Use when embedding wallet.1shotapi.com, wiring OWSProxy, EIP-1193, credentials,
+ or custom RPC such as setStyle for theming the 1Shot Branding Layer.
+license: MIT
+metadata:
+ author: 1Shot-API
+ version: "0.1.0"
+ repository: https://github.com/1Shot-API/embedded-wallet
+---
+
+# 1Shot Embedded Wallet (Host integration)
+
+Teach an agent how to embed the **1Shot Wallet** Branding Layer from a Host Layer app using `@1shotapi/ows-provider`.
+
+```
+Host (your dapp) @1shotapi/ows-provider → OWSProxy
+ └── Branding iframe https://wallet.1shotapi.com/
+ └── Signing https://wallet.1shotapi.com/signer/ (same origin)
+```
+
+## Install
+
+```bash
+npm install @1shotapi/ows-provider @1shotapi/ows-types
+```
+
+## Minimal setup
+
+```typescript
+import { OWSProxy } from "@1shotapi/ows-provider";
+
+const WALLET_URL = "https://wallet.1shotapi.com/";
+
+const container = document.getElementById("wallet-container")!;
+const proxy = await OWSProxy.create(container, WALLET_URL);
+
+// Optional: theme / copy before showing the flyout
+await proxy.rpc("setStyle", {
+ copy: { productName: "Acme Wallet", tagline: "Powered by 1Shot" },
+ theme: { primary: "oklch(0.45 0.18 250)" },
+});
+
+proxy.showWallet();
+
+// EIP-1193
+const accounts = await proxy.ethereum.request({ method: "eth_requestAccounts" });
+```
+
+### Local / HTTPS notes
+
+- Passkeys require a **secure-context ancestor chain**. The Host page must be HTTPS (or `localhost`) when the wallet iframe is HTTPS.
+- Dev wallet URL: your ngrok or local Vite origin root (e.g. `https://….ngrok-free.app/`).
+- Production wallet URL: **`https://wallet.1shotapi.com/`** (Signing Layer at `/signer/` on the same origin — do not embed `/signer/` from the host).
+
+## Custom RPC — `setStyle`
+
+1Shot-specific method registered on the Branding Layer. Call via:
+
+```typescript
+await proxy.rpc("setStyle", options);
+```
+
+`options` is a partial merge (safe to call repeatedly):
+
+| Field | Type | Purpose |
+|-------|------|---------|
+| `theme.primary` | string (CSS color) | `--primary` |
+| `theme.primaryForeground` | string | `--primary-foreground` |
+| `theme.background` / `foreground` | string | page colors |
+| `theme.muted` / `mutedForeground` | string | secondary text |
+| `theme.border` / `accent` / `accentForeground` | string | chrome |
+| `theme.radius` | string | `--radius` (e.g. `"0.625rem"`) |
+| `theme.fontSans` | string | `--font-sans` |
+| `copy.productName` | string | titles / chrome |
+| `copy.tagline` | string | supporting line |
+| `copy.connect.title` | string | connect modal title |
+| `copy.connect.body` | string | connect modal body |
+| `copy.connect.rejectLabel` | string | Reject button |
+| `copy.connect.continueLabel` | string | Continue button |
+| `copy.walletSetup.title` | string | setup modal title |
+| `copy.walletSetup.body` | string | setup modal body |
+| `copy.walletSetup.cancelLabel` | string | Cancel button |
+| `copy.walletSetup.loginLabel` | string | Login with passkey |
+| `copy.walletSetup.createLabel` | string | Create account |
+| `copy.passkeyName.title` | string | passkey name modal title |
+| `copy.passkeyName.body` | string | passkey name modal body |
+| `copy.passkeyName.fieldLabel` | string | input label |
+| `copy.passkeyName.placeholder` | string | input placeholder |
+| `copy.passkeyName.emptyError` | string | empty-name validation error |
+| `copy.passkeyName.cancelLabel` | string | Cancel button |
+| `copy.passkeyName.continueLabel` | string | Continue button |
+| `copy.personalSign.title` | string | personal_sign modal title |
+| `copy.personalSign.accountLabel` | string | Account field label |
+| `copy.personalSign.messageLabel` | string | Message field label |
+| `copy.personalSign.rejectLabel` | string | Reject button |
+| `copy.personalSign.signLabel` | string | Sign button |
+| `copy.typedData.title` | string | EIP-712 modal title |
+| `copy.typedData.accountLabel` | string | Account field label |
+| `copy.typedData.primaryTypeLabel` | string | Primary type label |
+| `copy.typedData.domainLabel` | string | Domain label |
+| `copy.typedData.messageLabel` | string | Message label |
+| `copy.typedData.rejectLabel` | string | Reject button |
+| `copy.typedData.signLabel` | string | Sign button |
+| `copy.credentialOffer.title` | string | offer modal title |
+| `copy.credentialOffer.body` | string | supports `{issuerName}` `{issuerId}` |
+| `copy.credentialOffer.offeredHeading` | string | offered list heading |
+| `copy.credentialOffer.passkeyNote` | string | passkey hint |
+| `copy.credentialOffer.rejectLabel` | string | Reject button |
+| `copy.credentialOffer.acceptLabel` | string | Accept button |
+| `copy.credentialPresentation.title` | string | presentation modal title |
+| `copy.credentialPresentation.body` | string | supports `{verifierName}` `{verifierId}` |
+| `copy.credentialPresentation.credentialDetail` | string | supports `{credentialType}` `{credentialIssuer}` |
+| `copy.credentialPresentation.claimsHeading` | string | claims list heading |
+| `copy.credentialPresentation.passkeyNote` | string | passkey hint |
+| `copy.credentialPresentation.rejectLabel` | string | Reject button |
+| `copy.credentialPresentation.shareLabel` | string | Share button |
+| `copy.credentials.tabLabel` | string | Credentials tab label |
+| `copy.credentials.emptyCountLabel` | string | zero-count summary |
+| `copy.credentials.countLabel` | string | supports `{count}` |
+| `copy.credentials.refreshLabel` | string | Refresh button |
+| `copy.credentials.loadingBody` | string | loading state |
+| `copy.credentials.emptyBody` | string | empty-state text |
+| `copy.credentials.loadFailedError` | string | list load failure |
+| `copy.credentials.refreshFailedError` | string | relayer refresh failure |
+| `copy.credentials.notFoundError` | string | detail not in cache |
+| `copy.credentials.openFailedError` | string | detail open failure |
+| `copy.credentials.typeColumn` | string | Type column header |
+| `copy.credentials.issuerColumn` | string | Issuer column header |
+| `copy.credentials.issuedColumn` | string | Issued column header |
+| `copy.credentials.viewLabel` | string | View button |
+| `copy.credentials.detailFallbackTitle` | string | detail title fallback |
+| `copy.credentials.detailDescription` | string | detail dialog description |
+| `copy.credentials.issuerLabel` | string | Issuer field label |
+| `copy.credentials.formatLabel` | string | Format field label |
+| `copy.credentials.issuedLabel` | string | Issued field label |
+| `copy.credentials.validUntilLabel` | string | Valid until label |
+| `copy.credentials.idLabel` | string | Id field label |
+| `copy.credentials.claimsHeading` | string | Claims section heading |
+| `copy.credentials.claimsLoading` | string | claims loading text |
+| `copy.credentials.claimsEmpty` | string | no claims text |
+| `copy.credentials.closeLabel` | string | Close button |
+| `copy.createBackup.title` | string | create backup modal title |
+| `copy.createBackup.body` | string | supports `{minLength}` |
+| `copy.createBackup.passphrasePrompt` | string | Signing Layer passphrase label (`{minLength}`) |
+| `copy.createBackup.continueLabel` | string | Signing Layer continue |
+| `copy.createBackup.cancelLabel` | string | Cancel button |
+| `copy.createBackup.closeLabel` | string | Close button |
+| `copy.createBackup.copyLabel` | string | Copy button |
+| `copy.createBackup.copiedLabel` | string | after successful copy |
+| `copy.createBackup.copyFailedLabel` | string | copy failure |
+| `copy.createBackup.doneLabel` | string | Done button |
+| `copy.createBackup.encryptedLabel` | string | result ciphertext label |
+| `copy.createBackup.passwordTooShortError` | string | short passphrase error |
+| `copy.createBackup.cancelledError` | string | passkey cancelled |
+| `copy.createBackup.failedError` | string | generic failure |
+| `copy.restoreBackup.title` | string | restore backup modal title |
+| `copy.restoreBackup.body` | string | restore prompt body |
+| `copy.restoreBackup.passphraseLabel` | string | Signing Layer passphrase label |
+| `copy.restoreBackup.restoreLabel` | string | Signing Layer restore button |
+| `copy.restoreBackup.cancelLabel` | string | Cancel button |
+| `copy.restoreBackup.closeLabel` | string | Close button |
+| `copy.restoreBackup.doneLabel` | string | Done button |
+| `copy.restoreBackup.successBody` | string | success message |
+| `copy.restoreBackup.decryptFailedError` | string | bad passphrase error |
+| `copy.restoreBackup.cancelledError` | string | passkey cancelled |
+| `copy.restoreBackup.failedError` | string | generic failure |
+| `dark` | boolean | toggles `html.dark` |
+
+Returns `{ ok: true, productName: string }` with the resolved product name after merge.
+
+Unknown keys are rejected (Zod `.strict()`).
+
+See also [README.md](../../README.md) in this repository.
+
+## Other Host APIs
+
+| API | Use |
+|-----|-----|
+| `proxy.ethereum.request(...)` | EIP-1193 (accounts, sign, chain, …) |
+| `proxy.credentials.*` | OID4 offer / present (when enabled in wallet) |
+| `proxy.showWallet()` / `hideWallet()` | Host-driven flyout without an EIP-1193 call |
+| `proxy.rpc(method, params)` | Custom Branding RPC (`setStyle`, …) |
+
+## Hard rules
+
+- Never embed the Signing Layer iframe from the Host — always Host → Branding → Signing.
+- Prefer the published wallet URL in production; point at a local Branding origin only while developing this repo.
+- Theme with `setStyle`; do not ask integrators to fork CSS for basic brand colors / product name.
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..e5e2023
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,55 @@
+import { useShallow } from "zustand/react/shallow";
+import { useWalletSessionStore } from "./wallet/sessionStore";
+import { WalletChrome } from "./components/WalletChrome";
+import { OnboardingPanel } from "./components/OnboardingPanel";
+import { MainPanel } from "./components/MainPanel";
+import { SignerHost } from "./components/SignerHost";
+import { ModalHost } from "./components/ModalHost";
+import { PasskeyPromptModal } from "./components/modals/PasskeyPromptModal";
+
+export function App() {
+ const { bootError, ready, unlocked, walletCreated, embedded } =
+ useWalletSessionStore(
+ useShallow((state) => ({
+ bootError: state.bootError,
+ ready: state.ready,
+ unlocked: state.unlocked,
+ walletCreated: state.walletCreated,
+ embedded: state.embedded,
+ })),
+ );
+
+ const showOnboarding = embedded && !walletCreated && !unlocked;
+
+ return (
+
+ {embedded ?
: null}
+
+
+ {bootError ? (
+
+ Failed to start: {bootError}
+
+ ) : !ready ? (
+
+ Loading…
+
+ ) : showOnboarding ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/src/assets/1Shot-Icon-New.svg b/src/assets/1Shot-Icon-New.svg
new file mode 100644
index 0000000..f91a366
--- /dev/null
+++ b/src/assets/1Shot-Icon-New.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/AddressInput.tsx b/src/components/AddressInput.tsx
new file mode 100644
index 0000000..02c3e0e
--- /dev/null
+++ b/src/components/AddressInput.tsx
@@ -0,0 +1,225 @@
+import { useEffect, useId, useState } from "react";
+import { ScanLineIcon } from "lucide-react";
+import { AddressUtils } from "@1shotapi/ows-wallet-utils";
+import {
+ EChainTechnology,
+ type BitcoinAccountAddress,
+ type EVMAccountAddress,
+ type EVMChainId,
+ type SolanaAccountAddress,
+} from "@1shotapi/ows-types";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+
+type BarcodeDetectorLike = {
+ detect: (
+ source: ImageBitmapSource,
+ ) => Promise>;
+};
+
+declare global {
+ interface Window {
+ BarcodeDetector?: new (options?: {
+ formats?: string[];
+ }) => BarcodeDetectorLike;
+ }
+}
+
+export type AddressInputValue =
+ | EVMAccountAddress
+ | SolanaAccountAddress
+ | BitcoinAccountAddress
+ | null;
+
+export interface IAddressInputProps {
+ technology: T;
+ chainId: EVMChainId;
+ addressUtils: AddressUtils;
+ value: string;
+ onChange: (value: string) => void;
+ onValidated: (address: AddressInputValue) => void;
+ label: string;
+ placeholder?: string;
+ scanQrLabel?: string;
+ invalidAddressError: string;
+ disabled?: boolean;
+ className?: string;
+}
+
+async function cameraPermissionGranted(): Promise {
+ try {
+ if (!navigator.permissions?.query) {
+ return false;
+ }
+ const status = await navigator.permissions.query({
+ name: "camera" as PermissionName,
+ });
+ return status.state === "granted";
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Address field with ENS/Base58/Bitcoin validation via AddressUtils.
+ * QR scan is shown only when camera permission is already granted.
+ */
+export function AddressInput({
+ technology,
+ chainId,
+ addressUtils,
+ value,
+ onChange,
+ onValidated,
+ label,
+ placeholder,
+ scanQrLabel = "Scan QR",
+ invalidAddressError,
+ disabled,
+ className,
+}: IAddressInputProps) {
+ const id = useId();
+ const [error, setError] = useState(null);
+ const [canScan, setCanScan] = useState(false);
+ const [scanning, setScanning] = useState(false);
+
+ useEffect(() => {
+ let cancelled = false;
+ void cameraPermissionGranted().then((granted) => {
+ if (!cancelled) {
+ setCanScan(
+ granted &&
+ typeof window !== "undefined" &&
+ typeof window.BarcodeDetector === "function",
+ );
+ }
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ useEffect(() => {
+ let cancelled = false;
+ const trimmed = value.trim();
+ if (!trimmed) {
+ setError(null);
+ onValidated(null);
+ return;
+ }
+
+ const timer = window.setTimeout(() => {
+ void (async () => {
+ try {
+ let address: AddressInputValue = null;
+ switch (technology) {
+ case EChainTechnology.Evm:
+ address = await addressUtils.validateEVMAddress(trimmed, chainId);
+ break;
+ case EChainTechnology.Solana:
+ address = addressUtils.validateSolanaAddress(trimmed);
+ break;
+ case EChainTechnology.Bitcoin:
+ address = addressUtils.validateBitcoinAddress(trimmed);
+ break;
+ }
+ if (!cancelled) {
+ setError(null);
+ onValidated(address);
+ }
+ } catch {
+ if (!cancelled) {
+ setError(invalidAddressError);
+ onValidated(null);
+ }
+ }
+ })();
+ }, 300);
+
+ return () => {
+ cancelled = true;
+ window.clearTimeout(timer);
+ };
+ }, [
+ addressUtils,
+ chainId,
+ invalidAddressError,
+ onValidated,
+ technology,
+ value,
+ ]);
+
+ async function scanQr(): Promise {
+ if (!window.BarcodeDetector) {
+ return;
+ }
+ setScanning(true);
+ let stream: MediaStream | null = null;
+ try {
+ stream = await navigator.mediaDevices.getUserMedia({
+ video: { facingMode: "environment" },
+ });
+ const video = document.createElement("video");
+ video.srcObject = stream;
+ video.playsInline = true;
+ await video.play();
+ const detector = new window.BarcodeDetector({
+ formats: ["qr_code"],
+ });
+ const deadline = Date.now() + 12_000;
+ while (Date.now() < deadline) {
+ const codes = await detector.detect(video);
+ const raw = codes[0]?.rawValue?.trim();
+ if (raw) {
+ onChange(raw.replace(/^ethereum:/i, "").split("@")[0] ?? raw);
+ break;
+ }
+ await new Promise((resolve) => setTimeout(resolve, 250));
+ }
+ } catch {
+ // Permission revoked or scan cancelled — leave field unchanged.
+ } finally {
+ stream?.getTracks().forEach((track) => track.stop());
+ setScanning(false);
+ }
+ }
+
+ return (
+
+
+ {label}
+
+
+ onChange(event.target.value)}
+ aria-invalid={Boolean(error)}
+ className="min-w-0 flex-1 font-mono text-sm"
+ />
+ {canScan ? (
+ void scanQr()}
+ >
+
+
+ ) : null}
+
+ {error ? (
+
+ {error}
+
+ ) : null}
+
+ );
+}
diff --git a/src/components/AssetDetails.tsx b/src/components/AssetDetails.tsx
new file mode 100644
index 0000000..f6455b7
--- /dev/null
+++ b/src/components/AssetDetails.tsx
@@ -0,0 +1,269 @@
+import { useEffect, useState } from "react";
+import type { ReactNode } from "react";
+import { useShallow } from "zustand/react/shallow";
+import {
+ ArrowDownLeftIcon,
+ ArrowUpRightIcon,
+ PlusIcon,
+ QrCodeIcon,
+ SendIcon,
+ ShoppingBagIcon,
+} from "lucide-react";
+import { AddressUtils } from "@1shotapi/ows-wallet-utils";
+import type { TrackedAsset } from "../lib/types/business";
+import { EAssetType } from "../lib/types/enum";
+import type { EVMChainId } from "@1shotapi/ows-types";
+import { DEMO_CHAINS } from "../ows/demoChains";
+import { DemoChainsBlockchainProvider } from "../lib/implementations/utils";
+import { useStyle } from "../style";
+import { useWallet } from "../wallet/WalletProvider";
+import { resolveActiveAddress } from "../wallet/activeAddress";
+import { useWalletSessionStore } from "../wallet/sessionStore";
+import { BalanceDisplay } from "./BalanceDisplay";
+import { ReceiveModal } from "./modals/ReceiveModal";
+import { PurchaseComingSoonModal } from "./modals/PurchaseComingSoonModal";
+import { TransferTokensModal } from "./modals/TransferTokensModal";
+
+const addressUtils = new AddressUtils(new DemoChainsBlockchainProvider());
+
+type ActivityKind = "received" | "sent" | "purchase";
+
+interface IMockActivity {
+ kind: ActivityKind;
+ title: string;
+ when: string;
+ amount: string;
+ positive: boolean;
+}
+
+const MOCK_ACTIVITY: IMockActivity[] = [
+ {
+ kind: "received",
+ title: "Received from 0x…4a2b",
+ when: "Today, 2:45 PM",
+ amount: "+10.00",
+ positive: true,
+ },
+ {
+ kind: "sent",
+ title: "Sent to alex.eth",
+ when: "Yesterday, 11:12 AM",
+ amount: "-5.00",
+ positive: false,
+ },
+ {
+ kind: "purchase",
+ title: "Purchase from Uniswap",
+ when: "Oct 24, 09:30 AM",
+ amount: "+50.00",
+ positive: true,
+ },
+];
+
+function chainLabel(chainId: EVMChainId): string {
+ return (
+ DEMO_CHAINS.find((chain) => chain.chainId === chainId)?.label ?? chainId
+ );
+}
+
+function ActivityIcon({ kind }: { kind: ActivityKind }) {
+ const className = "size-4 text-muted-foreground";
+ switch (kind) {
+ case "received":
+ return ;
+ case "sent":
+ return ;
+ case "purchase":
+ return ;
+ }
+}
+
+export interface IAssetDetailsProps {
+ asset: TrackedAsset;
+}
+
+/**
+ * Shared focused-asset / asset-detail shell.
+ * Balance is live; recent activity remains mock for now.
+ */
+export function AssetDetails({ asset }: IAssetDetailsProps) {
+ const { style } = useStyle();
+ const { balances: copy } = style.copy;
+ const { requestBalanceRefresh } = useWallet();
+ const { evmAddress, solanaAddress } = useWalletSessionStore(
+ useShallow((state) => ({
+ evmAddress: state.evmAddress,
+ solanaAddress: state.solanaAddress,
+ })),
+ );
+ const [receiveOpen, setReceiveOpen] = useState(false);
+ const [sendOpen, setSendOpen] = useState(false);
+ const [purchaseOpen, setPurchaseOpen] = useState(false);
+
+ useEffect(() => {
+ requestBalanceRefresh(asset.id);
+ }, [asset.id, requestBalanceRefresh]);
+
+ const network = chainLabel(asset.chainId);
+ const active = resolveActiveAddress({
+ chainId: asset.chainId,
+ evmAddress,
+ solanaAddress,
+ });
+ const canSend = asset.type === EAssetType.Erc20;
+
+ return (
+
+
+
+
+ setPurchaseOpen(true)}
+ >
+
+
+ setSendOpen(true)}
+ >
+
+
+ setReceiveOpen(true)}
+ >
+
+
+
+
+
+
+
+ Recent Activity
+
+
+ View all
+
+
+
+
+
+ {receiveOpen ? (
+
setReceiveOpen(false)}
+ />
+ ) : null}
+ {sendOpen ? (
+ setSendOpen(false)}
+ onSuccess={() => {
+ requestBalanceRefresh(asset.id);
+ }}
+ />
+ ) : null}
+ {purchaseOpen ? (
+ setPurchaseOpen(false)} />
+ ) : null}
+
+ );
+}
+
+function ActionButton({
+ label,
+ variant,
+ children,
+ disabled = false,
+ onClick,
+}: {
+ label: string;
+ variant: "primary" | "outline";
+ children: ReactNode;
+ disabled?: boolean;
+ onClick?: () => void;
+}) {
+ return (
+
+
+ {children}
+
+
+ {label}
+
+
+ );
+}
diff --git a/src/components/BalanceDisplay.tsx b/src/components/BalanceDisplay.tsx
new file mode 100644
index 0000000..0ce8261
--- /dev/null
+++ b/src/components/BalanceDisplay.tsx
@@ -0,0 +1,59 @@
+import { useCallback, useEffect, useState } from "react";
+import { formatUnits } from "viem";
+import { cn } from "@/lib/utils";
+import type { TrackedAssetId } from "../lib/types/primitives";
+import { useBalanceUpdated } from "../wallet/useWalletEvent";
+
+export interface IBalanceDisplayProps {
+ trackedAssetId: TrackedAssetId;
+ balance: bigint | null;
+ decimals: number;
+ className?: string;
+}
+
+function formatBalance(balance: bigint | null, decimals: number): string {
+ if (balance === null) return "—";
+ try {
+ return formatUnits(balance, decimals);
+ } catch {
+ return "—";
+ }
+}
+
+/**
+ * Formats a raw token balance and stays live via BalanceUpdated events.
+ */
+export function BalanceDisplay({
+ trackedAssetId,
+ balance: initialBalance,
+ decimals: initialDecimals,
+ className,
+}: IBalanceDisplayProps) {
+ const [balance, setBalance] = useState(initialBalance);
+ const [decimals, setDecimals] = useState(initialDecimals);
+
+ useEffect(() => {
+ setBalance(initialBalance);
+ setDecimals(initialDecimals);
+ }, [initialBalance, initialDecimals, trackedAssetId]);
+
+ useBalanceUpdated(
+ useCallback(
+ (event) => {
+ const next = event.assets.find(
+ (asset) => asset.id === trackedAssetId,
+ );
+ if (!next) return;
+ setBalance(next.balance);
+ setDecimals(next.decimals);
+ },
+ [trackedAssetId],
+ ),
+ );
+
+ return (
+
+ {formatBalance(balance, decimals)}
+
+ );
+}
diff --git a/src/components/BrandLogo.tsx b/src/components/BrandLogo.tsx
new file mode 100644
index 0000000..d22da75
--- /dev/null
+++ b/src/components/BrandLogo.tsx
@@ -0,0 +1,25 @@
+import { cn } from "@/lib/utils";
+import defaultLogoUrl from "../assets/1Shot-Icon-New.svg";
+
+export interface IBrandLogoProps {
+ /** Host `setStyle` logo URL; falls back to the bundled 1Shot icon. */
+ logoUrl?: string;
+ className?: string;
+ alt?: string;
+}
+
+export function BrandLogo({
+ logoUrl,
+ className,
+ alt = "",
+}: IBrandLogoProps) {
+ const src = logoUrl?.trim() ? logoUrl : defaultLogoUrl;
+
+ return (
+
+ );
+}
diff --git a/src/components/CopyableText.tsx b/src/components/CopyableText.tsx
new file mode 100644
index 0000000..05f5c5e
--- /dev/null
+++ b/src/components/CopyableText.tsx
@@ -0,0 +1,110 @@
+import { useEffect, useRef, useState } from "react";
+import { CheckIcon, CopyIcon } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { copyText } from "@/lib/clipboard";
+import { cn } from "@/lib/utils";
+
+type CopyState = "idle" | "copied" | "failed";
+
+const TRUNCATE_CHARS = 5;
+const FEEDBACK_MS = 1500;
+
+export interface ICopyableTextProps {
+ /** Full value to display (and copy). */
+ text: string;
+ /**
+ * When set, show the first and last 5 characters with an ellipsis between.
+ * The full `text` is still copied.
+ */
+ truncate?: boolean;
+ className?: string;
+ disabled?: boolean;
+ copyLabel?: string;
+ copiedLabel?: string;
+ copyFailedLabel?: string;
+}
+
+function formatTruncated(text: string): string {
+ if (text.length <= TRUNCATE_CHARS * 2 + 1) return text;
+ return `${text.slice(0, TRUNCATE_CHARS)}…${text.slice(-TRUNCATE_CHARS)}`;
+}
+
+/**
+ * Mono text with an inline copy control and brief success/failure feedback.
+ * Use `truncate` for compact address rows; omit it for full wrapped blocks (e.g. backup).
+ */
+export function CopyableText({
+ text,
+ truncate = false,
+ className,
+ disabled = false,
+ copyLabel = "Copy",
+ copiedLabel = "Copied",
+ copyFailedLabel = "Copy failed",
+}: ICopyableTextProps) {
+ const [copyState, setCopyState] = useState("idle");
+ const resetRef = useRef | null>(null);
+ const canCopy = Boolean(text) && text !== "—" && !disabled;
+
+ useEffect(() => {
+ return () => {
+ if (resetRef.current) clearTimeout(resetRef.current);
+ };
+ }, []);
+
+ const feedbackLabel =
+ copyState === "copied"
+ ? copiedLabel
+ : copyState === "failed"
+ ? copyFailedLabel
+ : copyLabel;
+
+ const onCopy = () => {
+ if (!canCopy) return;
+ void copyText(text).then((ok) => {
+ setCopyState(ok ? "copied" : "failed");
+ if (resetRef.current) clearTimeout(resetRef.current);
+ resetRef.current = setTimeout(() => setCopyState("idle"), FEEDBACK_MS);
+ });
+ };
+
+ const display = truncate ? formatTruncated(text) : text;
+
+ return (
+
+
+ {display}
+
+
+ {copyState === "copied" ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/src/components/MainPanel.tsx b/src/components/MainPanel.tsx
new file mode 100644
index 0000000..35372b6
--- /dev/null
+++ b/src/components/MainPanel.tsx
@@ -0,0 +1,158 @@
+import { useEffect, useState } from "react";
+import { useShallow } from "zustand/react/shallow";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import type { TrackedAsset } from "../lib/types/business";
+import { useWallet } from "../wallet/WalletProvider";
+import {
+ EWalletMode,
+ useWalletSessionStore,
+} from "../wallet/sessionStore";
+import { resolveActiveAddress } from "../wallet/activeAddress";
+import { useStyle } from "../style";
+import { AssetDetails } from "./AssetDetails";
+import { CopyableText } from "./CopyableText";
+import { BalancesTab } from "./balances/BalancesTab";
+import { CredentialsTab } from "./credentials/CredentialsTab";
+
+function FocusedAssetPanel() {
+ const { resolveTrackedAsset } = useWallet();
+ const { chainId, focusedAssetAddress } = useWalletSessionStore(
+ useShallow((state) => ({
+ chainId: state.chainId,
+ focusedAssetAddress: state.focusedAssetAddress,
+ })),
+ );
+ const [asset, setAsset] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (!focusedAssetAddress) return;
+ let cancelled = false;
+ setError(null);
+ void resolveTrackedAsset(chainId, focusedAssetAddress)
+ .then((resolved) => {
+ if (!cancelled) setAsset(resolved);
+ })
+ .catch((err: unknown) => {
+ if (!cancelled) {
+ setAsset(null);
+ setError(err instanceof Error ? err.message : "Failed to load asset");
+ }
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [chainId, focusedAssetAddress, resolveTrackedAsset]);
+
+ if (error) {
+ return {error}
;
+ }
+ if (!asset) {
+ return Loading…
;
+ }
+ return ;
+}
+
+/**
+ * Main unlocked shell: General (network + tabs) or Focused (single asset).
+ */
+export function MainPanel() {
+ const { style } = useStyle();
+ const { chains, switchChain } = useWallet();
+ const {
+ evmAddress,
+ solanaAddress,
+ chainId,
+ mode,
+ focusedAssetAddress,
+ } = useWalletSessionStore(
+ useShallow((state) => ({
+ evmAddress: state.evmAddress,
+ solanaAddress: state.solanaAddress,
+ chainId: state.chainId,
+ mode: state.mode,
+ focusedAssetAddress: state.focusedAssetAddress,
+ })),
+ );
+
+ if (mode === EWalletMode.Focused && focusedAssetAddress) {
+ return ;
+ }
+
+ const active = resolveActiveAddress({
+ chainId,
+ evmAddress,
+ solanaAddress,
+ });
+ const hasAddress = Boolean(active.address && active.address !== "—");
+
+ return (
+
+
+
+
+ Network
+
+ {
+ if (value) void switchChain(value);
+ }}
+ >
+
+
+
+
+ {chains.map((chain) => (
+
+ {chain.label}
+
+ ))}
+
+
+
+
+
+
+ {active.label} address
+
+
+
+
+
+
+
+
+ {style.copy.balances.tabLabel}
+
+
+ {style.copy.credentials.tabLabel}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx
new file mode 100644
index 0000000..df98c40
--- /dev/null
+++ b/src/components/Modal.tsx
@@ -0,0 +1,108 @@
+import type { ReactNode } from "react";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { cn } from "@/lib/utils";
+
+export type ModalAction = {
+ label: string;
+ onClick: () => void;
+ variant?: "primary" | "secondary";
+ autoFocus?: boolean;
+ disabled?: boolean;
+};
+
+export type ModalProps = {
+ title: string;
+ children: ReactNode;
+ actions?: ModalAction[];
+ /** Escape / overlay dismiss. Omit to lock the dialog until an action. */
+ onBackdropDismiss?: () => void;
+ /** Extra content after children (e.g. signer slot). */
+ footer?: ReactNode;
+ wide?: boolean;
+ /** Extra classes on DialogContent (e.g. higher z-index for overlays). */
+ contentClassName?: string;
+};
+
+/**
+ * Wallet modal shell — controlled Dialog always mounted `open` while ModalHost
+ * keeps this tree rendered. Escape / overlay dismiss call `onBackdropDismiss` when set.
+ */
+export function Modal({
+ title,
+ children,
+ actions,
+ onBackdropDismiss,
+ footer,
+ wide,
+ contentClassName,
+}: ModalProps) {
+ return (
+ {
+ if (!nextOpen) {
+ onBackdropDismiss?.();
+ }
+ }}
+ >
+ {
+ if (!onBackdropDismiss) {
+ event.preventDefault();
+ }
+ }}
+ onEscapeKeyDown={(event) => {
+ if (!onBackdropDismiss) {
+ event.preventDefault();
+ }
+ }}
+ >
+
+
+ {title}
+
+
+
+
+ {children}
+
+
+ {footer}
+
+ {actions && actions.length > 0 ? (
+
+ {actions.map((action) => (
+
+ {action.label}
+
+ ))}
+
+ ) : null}
+
+
+ );
+}
diff --git a/src/components/ModalHost.tsx b/src/components/ModalHost.tsx
new file mode 100644
index 0000000..4f0dc3b
--- /dev/null
+++ b/src/components/ModalHost.tsx
@@ -0,0 +1,98 @@
+import { useModalStore } from "../wallet/modalStore";
+import {
+ ConnectModal,
+ PasskeyNameModal,
+ WalletSetupModal,
+} from "./modals/SetupModals";
+import {
+ PersonalSignModal,
+ SendTransactionModal,
+ ConfirmTransferModal,
+ TypedDataModal,
+} from "./modals/SignModals";
+import {
+ CredentialOfferModal,
+ CredentialPresentationModal,
+} from "./modals/CredentialModals";
+import { CreateBackupModal, RestoreBackupModal } from "./modals/BackupModals";
+import { AddAssetModal } from "./modals/AddAssetModal";
+
+export function ModalHost() {
+ const activeModal = useModalStore((state) => state.activeModal);
+ if (!activeModal) return null;
+
+ switch (activeModal.kind) {
+ case "walletSetup":
+ return ;
+ case "passkeyName":
+ return ;
+ case "connect":
+ return ;
+ case "personalSign":
+ return (
+
+ );
+ case "typedData":
+ return (
+
+ );
+ case "sendTransaction":
+ return (
+
+ );
+ case "confirmTransfer":
+ return (
+
+ );
+ case "credentialOffer":
+ return (
+
+ );
+ case "credentialPresentation":
+ return (
+
+ );
+ case "addAsset":
+ return (
+
+ );
+ case "createBackup":
+ return (
+
+ );
+ case "restoreBackup":
+ return (
+
+ );
+ default:
+ return null;
+ }
+}
diff --git a/src/components/OnboardingPanel.tsx b/src/components/OnboardingPanel.tsx
new file mode 100644
index 0000000..010c3ea
--- /dev/null
+++ b/src/components/OnboardingPanel.tsx
@@ -0,0 +1,75 @@
+import { Button } from "@/components/ui/button";
+import { useWallet } from "../wallet/WalletProvider";
+import { useStyle } from "../style";
+import { BrandLogo } from "./BrandLogo";
+
+function taglineLines(tagline: string): string[] {
+ return tagline
+ .split(/\n+/)
+ .map((line) => line.trim())
+ .filter(Boolean);
+}
+
+export function OnboardingPanel() {
+ const { loginWithPasskey, createNewWalletFromUi } = useWallet();
+ const { style } = useStyle();
+ const { productName, tagline, logoUrl, walletSetup } = style.copy;
+ const lines = taglineLines(tagline);
+
+ return (
+
+
+
+
+
+ {productName}
+
+
+
+ {lines.map((line) => (
+
+ {line}
+
+ ))}
+
+
+
+ {
+ void loginWithPasskey().catch((error: unknown) => {
+ console.error("[wallet-setup] embedded login failed", error);
+ });
+ }}
+ >
+ {walletSetup.loginLabel}
+
+ {
+ void createNewWalletFromUi().catch((error: unknown) => {
+ console.error("[wallet-setup] embedded create failed", error);
+ });
+ }}
+ >
+ {walletSetup.createLabel}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/QRCode.tsx b/src/components/QRCode.tsx
new file mode 100644
index 0000000..552290b
--- /dev/null
+++ b/src/components/QRCode.tsx
@@ -0,0 +1,96 @@
+import { useEffect, useState } from "react";
+import QRCodeLib from "qrcode";
+import { cn } from "@/lib/utils";
+
+export interface IQRCodeProps {
+ /** Payload encoded in the QR (e.g. a wallet address). */
+ value: string;
+ /** Rendered square size in CSS pixels. */
+ size?: number;
+ className?: string;
+ /** Accessible label for the generated image. */
+ alt?: string;
+}
+
+/**
+ * Renders a QR code for `value`. All QR encoding lives in this module.
+ */
+export function QRCode({
+ value,
+ size = 192,
+ className,
+ alt = "QR code",
+}: IQRCodeProps) {
+ const [dataUrl, setDataUrl] = useState(null);
+ const [error, setError] = useState(false);
+
+ useEffect(() => {
+ if (!value) {
+ setDataUrl(null);
+ setError(false);
+ return;
+ }
+
+ let cancelled = false;
+ setError(false);
+
+ void QRCodeLib.toDataURL(value, {
+ width: size,
+ margin: 1,
+ errorCorrectionLevel: "M",
+ color: {
+ dark: "#000000",
+ light: "#ffffff",
+ },
+ })
+ .then((url) => {
+ if (!cancelled) setDataUrl(url);
+ })
+ .catch(() => {
+ if (!cancelled) {
+ setDataUrl(null);
+ setError(true);
+ }
+ });
+
+ return () => {
+ cancelled = true;
+ };
+ }, [value, size]);
+
+ if (!value || error) {
+ return (
+
+ —
+
+ );
+ }
+
+ if (!dataUrl) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/SignerHost.tsx b/src/components/SignerHost.tsx
new file mode 100644
index 0000000..2bc1eda
--- /dev/null
+++ b/src/components/SignerHost.tsx
@@ -0,0 +1,7 @@
+import { useWallet } from "../wallet/WalletProvider";
+
+/** Hidden mount point for the Signing Layer iframe. */
+export function SignerHost() {
+ const { signerContainerRef } = useWallet();
+ return
;
+}
diff --git a/src/components/TokenAmountInput.tsx b/src/components/TokenAmountInput.tsx
new file mode 100644
index 0000000..f2c84ef
--- /dev/null
+++ b/src/components/TokenAmountInput.tsx
@@ -0,0 +1,58 @@
+import { useId } from "react";
+import { Input } from "@/components/ui/input";
+import { cn } from "@/lib/utils";
+
+export interface ITokenAmountInputProps {
+ value: string;
+ onChange: (value: string) => void;
+ label: string;
+ placeholder?: string;
+ symbol?: string;
+ disabled?: boolean;
+ error?: string | null;
+ className?: string;
+}
+
+/** Decimal amount field for token sends (string in / out; parse at submit). */
+export function TokenAmountInput({
+ value,
+ onChange,
+ label,
+ placeholder = "0.0",
+ symbol,
+ disabled,
+ error,
+ className,
+}: ITokenAmountInputProps) {
+ const id = useId();
+ return (
+
+
+ {label}
+
+
+ onChange(event.target.value)}
+ aria-invalid={Boolean(error)}
+ className={cn(symbol && "pr-14")}
+ />
+ {symbol ? (
+
+ {symbol}
+
+ ) : null}
+
+ {error ? (
+
+ {error}
+
+ ) : null}
+
+ );
+}
diff --git a/src/components/WalletChrome.tsx b/src/components/WalletChrome.tsx
new file mode 100644
index 0000000..9db44c5
--- /dev/null
+++ b/src/components/WalletChrome.tsx
@@ -0,0 +1,86 @@
+import { MenuIcon } from "lucide-react";
+import { useStyle } from "../style";
+import { hasBackup } from "../storage";
+import { useWallet } from "../wallet/WalletProvider";
+import { useWalletSessionStore } from "../wallet/sessionStore";
+import {
+ Menubar,
+ MenubarContent,
+ MenubarItem,
+ MenubarMenu,
+ MenubarSeparator,
+ MenubarTrigger,
+} from "@/components/ui/menubar";
+
+/**
+ * Top shell bar: brand + lock status + menu (backup / close).
+ * Uses shadcn Menubar — https://ui.shadcn.com/docs/components/base/menubar
+ */
+export function WalletChrome() {
+ const { requestHide, openCreateBackup, openRestoreBackup } = useWallet();
+ const { style } = useStyle();
+ const unlocked = useWalletSessionStore((state) => state.unlocked);
+ const canRestore = hasBackup();
+
+ return (
+
+ );
+}
diff --git a/src/components/balances/AssetList.tsx b/src/components/balances/AssetList.tsx
new file mode 100644
index 0000000..eb5354a
--- /dev/null
+++ b/src/components/balances/AssetList.tsx
@@ -0,0 +1,238 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+import {
+ flexRender,
+ getCoreRowModel,
+ getPaginationRowModel,
+ useReactTable,
+ type ColumnDef,
+} from "@tanstack/react-table";
+import { RefreshCwIcon } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { TrackedAsset } from "../../lib/types/business";
+import { useStyle } from "../../style";
+import { useWallet } from "../../wallet/WalletProvider";
+import { useWalletSessionStore } from "../../wallet/sessionStore";
+import { BalanceDisplay } from "../BalanceDisplay";
+
+const PAGE_SIZE = 5;
+
+export function AssetList({
+ onView,
+}: {
+ onView: (asset: TrackedAsset) => void;
+}) {
+ const { style } = useStyle();
+ const { balances: copy } = style.copy;
+ const { listTrackedAssets, requestBalanceRefresh } = useWallet();
+ const trackedAssetCount = useWalletSessionStore(
+ (state) => state.trackedAssetCount,
+ );
+ const chainId = useWalletSessionStore((state) => state.chainId);
+ const evmAddress = useWalletSessionStore((state) => state.evmAddress);
+
+ const [rows, setRows] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const reload = useCallback(async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const tracked = (await listTrackedAssets()).filter(
+ (asset) => asset.chainId === chainId,
+ );
+ setRows(tracked);
+ } catch (err: unknown) {
+ setError(err instanceof Error ? err.message : copy.loadFailedError);
+ } finally {
+ setLoading(false);
+ }
+ }, [listTrackedAssets, chainId, copy.loadFailedError]);
+
+ useEffect(() => {
+ void reload();
+ }, [reload, trackedAssetCount, evmAddress]);
+
+ const columns = useMemo[]>(
+ () => [
+ {
+ id: "asset",
+ header: copy.assetColumn,
+ cell: ({ row }) => (
+
+
+ $
+
+
{row.original.symbol}
+
+ ),
+ },
+ {
+ id: "balance",
+ header: copy.balanceColumn,
+ cell: ({ row }) => (
+
+ ),
+ },
+ {
+ id: "actions",
+ header: "",
+ cell: ({ row }) => (
+ onView(row.original)}
+ >
+ {copy.viewLabel}
+
+ ),
+ },
+ ],
+ [onView, copy.assetColumn, copy.balanceColumn, copy.viewLabel],
+ );
+
+ const table = useReactTable({
+ data: rows,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getRowId: (row) => row.id,
+ initialState: {
+ pagination: { pageSize: PAGE_SIZE },
+ },
+ });
+
+ const pageCount = table.getPageCount();
+ const pageIndex = table.getState().pagination.pageIndex;
+
+ return (
+
+
+
+ {rows.length === 0
+ ? copy.emptyCountLabel
+ : copy.countLabel.replace("{count}", String(rows.length))}
+
+
requestBalanceRefresh()}
+ aria-label="Refresh balances"
+ >
+
+
+
+
+ {error ? (
+
{error}
+ ) : null}
+
+ {loading ? (
+
{copy.loadingBody}
+ ) : rows.length === 0 ? (
+
{copy.emptyBody}
+ ) : (
+ <>
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext(),
+ )}
+
+ ))}
+
+ ))}
+
+
+
+ {pageCount > 1 ? (
+
+
+
+ {
+ event.preventDefault();
+ table.previousPage();
+ }}
+ />
+
+
+
+ {pageIndex + 1} / {pageCount}
+
+
+
+ {
+ event.preventDefault();
+ table.nextPage();
+ }}
+ />
+
+
+
+ ) : null}
+ >
+ )}
+
+ );
+}
diff --git a/src/components/balances/BalancesTab.tsx b/src/components/balances/BalancesTab.tsx
new file mode 100644
index 0000000..08c75d7
--- /dev/null
+++ b/src/components/balances/BalancesTab.tsx
@@ -0,0 +1,154 @@
+import { useEffect, useState } from "react";
+import { EVMAccountAddress } from "@1shotapi/ows-types";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import type { TrackedAsset } from "../../lib/types/business";
+import { useStyle } from "../../style";
+import { useWallet } from "../../wallet/WalletProvider";
+import { useWalletSessionStore } from "../../wallet/sessionStore";
+import { AssetDetails } from "../AssetDetails";
+import { AssetList } from "./AssetList";
+
+const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
+
+export function BalancesTab() {
+ const { style } = useStyle();
+ const { balances: copy } = style.copy;
+ const { addTrackedAsset } = useWallet();
+ const chainId = useWalletSessionStore((state) => state.chainId);
+
+ const [selected, setSelected] = useState(null);
+ const [addOpen, setAddOpen] = useState(false);
+ const [addressInput, setAddressInput] = useState("");
+ const [addError, setAddError] = useState(null);
+ const [adding, setAdding] = useState(false);
+
+ useEffect(() => {
+ setSelected(null);
+ }, [chainId]);
+
+ if (selected) {
+ return (
+
+
+ setSelected(null)}
+ >
+ {copy.closeLabel}
+
+
+
+
+ );
+ }
+
+ const onSubmitAdd = async () => {
+ const trimmed = addressInput.trim();
+ if (!ADDRESS_RE.test(trimmed)) {
+ setAddError(copy.invalidAddressError);
+ return;
+ }
+ setAdding(true);
+ setAddError(null);
+ try {
+ await addTrackedAsset(
+ chainId,
+ EVMAccountAddress(trimmed as `0x${string}`),
+ );
+ setAddOpen(false);
+ setAddressInput("");
+ } catch (err: unknown) {
+ setAddError(err instanceof Error ? err.message : copy.addFailedError);
+ } finally {
+ setAdding(false);
+ }
+ };
+
+ return (
+
+
+
+
{
+ setAddError(null);
+ setAddressInput("");
+ setAddOpen(true);
+ }}
+ >
+ {copy.addLabel}
+
+
+
{
+ setAddOpen(open);
+ if (!open) {
+ setAddError(null);
+ setAddressInput("");
+ }
+ }}
+ >
+
+
+ {copy.addDialogTitle}
+ {copy.addDialogBody}
+
+
+
{copy.addressLabel}
+
setAddressInput(event.target.value)}
+ onKeyDown={(event) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ void onSubmitAdd();
+ }
+ }}
+ />
+ {addError ? (
+
{addError}
+ ) : null}
+
+
+ setAddOpen(false)}
+ >
+ {copy.addDialogCancelLabel}
+
+ {
+ void onSubmitAdd();
+ }}
+ >
+ {copy.addDialogSubmitLabel}
+
+
+
+
+
+ );
+}
diff --git a/src/components/credentials/CredentialDetailDialog.tsx b/src/components/credentials/CredentialDetailDialog.tsx
new file mode 100644
index 0000000..9d37df0
--- /dev/null
+++ b/src/components/credentials/CredentialDetailDialog.tsx
@@ -0,0 +1,176 @@
+import { useEffect, useState } from "react";
+import { PresentationUtils, type StoredCredential } from "@1shotapi/ows-types";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { useStyle } from "../../style";
+
+function formatClaimValue(value: unknown): string {
+ if (value === null || value === undefined) return "—";
+ if (typeof value === "string") return value;
+ if (typeof value === "number" || typeof value === "boolean") {
+ return String(value);
+ }
+ try {
+ return JSON.stringify(value);
+ } catch {
+ return String(value);
+ }
+}
+
+async function resolveClaims(
+ credential: StoredCredential,
+): Promise> {
+ const format = credential.format;
+ if (format.includes("sd-jwt") || credential.payload.includes("~")) {
+ try {
+ return await PresentationUtils.unpackSubject(credential.payload);
+ } catch (error: unknown) {
+ console.warn("[credentials] unpackSubject failed", error);
+ }
+ }
+
+ const subject = credential.semantic?.credentialSubject;
+ if (subject && typeof subject === "object") {
+ return subject;
+ }
+
+ try {
+ const parsed: unknown = JSON.parse(credential.payload);
+ if (parsed && typeof parsed === "object") {
+ return parsed as Record;
+ }
+ } catch {
+ // not JSON
+ }
+
+ return { payload: credential.payload };
+}
+
+export function CredentialDetailDialog({
+ credential,
+ open,
+ onOpenChange,
+}: {
+ credential: StoredCredential | null;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}) {
+ const { style } = useStyle();
+ const { credentials: copy } = style.copy;
+ const [claims, setClaims] = useState | null>(null);
+
+ useEffect(() => {
+ if (!credential || !open) {
+ setClaims(null);
+ return;
+ }
+
+ let cancelled = false;
+ void resolveClaims(credential).then((next) => {
+ if (!cancelled) setClaims(next);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [credential, open]);
+
+ return (
+
+
+
+
+ {credential?.type.join(", ") ?? copy.detailFallbackTitle}
+
+ {copy.detailDescription}
+
+
+ {credential ? (
+
+
+
+
+ {copy.issuerLabel}
+
+ {credential.issuer}
+
+
+
+ {copy.formatLabel}
+
+ {credential.format}
+
+
+
+ {copy.issuedLabel}
+
+ {credential.issuedAt}
+
+ {credential.validUntil ? (
+
+
+ {copy.validUntilLabel}
+
+
+ {credential.validUntil}
+
+
+ ) : null}
+
+
+ {copy.idLabel}
+
+
+ {credential.credentialId}
+
+
+
+
+
+
+ {copy.claimsHeading}
+
+ {claims === null ? (
+
+ {copy.claimsLoading}
+
+ ) : Object.keys(claims).length === 0 ? (
+
+ {copy.claimsEmpty}
+
+ ) : (
+
+ )}
+
+
+ ) : null}
+
+
+ onOpenChange(false)}>
+ {copy.closeLabel}
+
+
+
+
+ );
+}
diff --git a/src/components/credentials/CredentialsTab.tsx b/src/components/credentials/CredentialsTab.tsx
new file mode 100644
index 0000000..71cc06d
--- /dev/null
+++ b/src/components/credentials/CredentialsTab.tsx
@@ -0,0 +1,301 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+import {
+ flexRender,
+ getCoreRowModel,
+ getPaginationRowModel,
+ useReactTable,
+ type ColumnDef,
+} from "@tanstack/react-table";
+import type { CredentialId, CredentialSummary, StoredCredential } from "@1shotapi/ows-types";
+import { RefreshCwIcon } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { useStyle } from "../../style";
+import { useWallet } from "../../wallet/WalletProvider";
+import { useWalletSessionStore } from "../../wallet/sessionStore";
+import { CredentialDetailDialog } from "./CredentialDetailDialog";
+
+const PAGE_SIZE = 5;
+
+function fillTemplate(
+ template: string,
+ vars: Record,
+): string {
+ return template.replace(/\{(\w+)\}/g, (_, key: string) => vars[key] ?? "");
+}
+
+export function CredentialsTab() {
+ const { style } = useStyle();
+ const { credentials: copy } = style.copy;
+ const {
+ listCredentials,
+ getCredential,
+ refreshCredentialsFromRelayer,
+ refreshCredentialCount,
+ } = useWallet();
+ const credentialCount = useWalletSessionStore(
+ (state) => state.credentialCount,
+ );
+
+ const [rows, setRows] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [refreshing, setRefreshing] = useState(false);
+ const [error, setError] = useState(null);
+ const [detail, setDetail] = useState(null);
+ const [detailOpen, setDetailOpen] = useState(false);
+
+ const reload = useCallback(async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const listed = await listCredentials();
+ setRows(listed);
+ await refreshCredentialCount();
+ } catch (err: unknown) {
+ setError(
+ err instanceof Error ? err.message : copy.loadFailedError,
+ );
+ } finally {
+ setLoading(false);
+ }
+ }, [listCredentials, refreshCredentialCount, copy.loadFailedError]);
+
+ // Reload when store count changes (e.g. recover after discoverable login).
+ useEffect(() => {
+ void reload();
+ }, [reload, credentialCount]);
+
+ const onRefresh = async () => {
+ setRefreshing(true);
+ setError(null);
+ try {
+ await refreshCredentialsFromRelayer();
+ await reload();
+ } catch (err: unknown) {
+ setError(
+ err instanceof Error ? err.message : copy.refreshFailedError,
+ );
+ } finally {
+ setRefreshing(false);
+ }
+ };
+
+ const onView = useCallback(
+ async (credentialId: CredentialId) => {
+ setError(null);
+ try {
+ const full = await getCredential(credentialId);
+ if (!full) {
+ setError(copy.notFoundError);
+ return;
+ }
+ setDetail(full);
+ setDetailOpen(true);
+ } catch (err: unknown) {
+ setError(
+ err instanceof Error ? err.message : copy.openFailedError,
+ );
+ }
+ },
+ [getCredential, copy.notFoundError, copy.openFailedError],
+ );
+
+ const columns = useMemo[]>(
+ () => [
+ {
+ id: "type",
+ header: copy.typeColumn,
+ cell: ({ row }) => (
+ {row.original.type.join(", ")}
+ ),
+ },
+ {
+ id: "issuer",
+ header: copy.issuerColumn,
+ cell: ({ row }) => (
+
+ {row.original.issuer}
+
+ ),
+ },
+ {
+ id: "issued",
+ header: copy.issuedColumn,
+ cell: ({ row }) => (
+
+ {row.original.issuedAt}
+
+ ),
+ },
+ {
+ id: "actions",
+ header: "",
+ cell: ({ row }) => (
+ {
+ void onView(row.original.credentialId);
+ }}
+ >
+ {copy.viewLabel}
+
+ ),
+ },
+ ],
+ [
+ onView,
+ copy.typeColumn,
+ copy.issuerColumn,
+ copy.issuedColumn,
+ copy.viewLabel,
+ ],
+ );
+
+ const table = useReactTable({
+ data: rows,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ initialState: {
+ pagination: { pageSize: PAGE_SIZE },
+ },
+ });
+
+ const pageCount = table.getPageCount();
+ const pageIndex = table.getState().pagination.pageIndex;
+
+ return (
+
+
+
+ {rows.length === 0
+ ? copy.emptyCountLabel
+ : fillTemplate(copy.countLabel, { count: String(rows.length) })}
+
+
{
+ void onRefresh();
+ }}
+ >
+
+ {copy.refreshLabel}
+
+
+
+ {error ? (
+
+ {error}
+
+ ) : null}
+
+ {loading ? (
+
{copy.loadingBody}
+ ) : rows.length === 0 ? (
+
{copy.emptyBody}
+ ) : (
+ <>
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext(),
+ )}
+
+ ))}
+
+ ))}
+
+
+
+ {pageCount > 1 ? (
+
+
+
+ {
+ event.preventDefault();
+ table.previousPage();
+ }}
+ />
+
+
+
+ {pageIndex + 1} / {pageCount}
+
+
+
+ {
+ event.preventDefault();
+ table.nextPage();
+ }}
+ />
+
+
+
+ ) : null}
+ >
+ )}
+
+
+
+ );
+}
diff --git a/src/components/modals/AddAssetModal.tsx b/src/components/modals/AddAssetModal.tsx
new file mode 100644
index 0000000..c097b41
--- /dev/null
+++ b/src/components/modals/AddAssetModal.tsx
@@ -0,0 +1,79 @@
+import type { EVMChainId } from "@1shotapi/ows-types";
+import { Modal } from "../Modal";
+import { useStyle } from "../../style";
+import { DEMO_CHAINS } from "../../ows/demoChains";
+import type { IAddAssetApprovalRequest } from "../../wallet/registerAddAsset";
+import { CopyableText } from "../CopyableText";
+
+function chainLabel(chainId: EVMChainId): string {
+ return (
+ DEMO_CHAINS.find((chain) => chain.chainId === chainId)?.label ?? chainId
+ );
+}
+
+export function AddAssetModal({
+ request,
+ onResolve,
+}: {
+ request: IAddAssetApprovalRequest;
+ onResolve: (approved: boolean) => void;
+}) {
+ const { style } = useStyle();
+ const { balances: copy } = style.copy;
+ const displayName = request.assetSymbol || request.assetName;
+ const network = chainLabel(request.chainId);
+
+ return (
+ onResolve(false)}
+ actions={[
+ {
+ label: copy.addConfirmRejectLabel,
+ variant: "secondary",
+ onClick: () => onResolve(false),
+ },
+ {
+ label: copy.addConfirmAcceptLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve(true),
+ },
+ ]}
+ >
+
+ {copy.addConfirmBody
+ .replace("{assetName}", displayName)
+ .replace("{chainLabel}", network)}
+
+
+
+
+ {copy.assetColumn}
+
+ {displayName}
+
+
+
+ {copy.chainColumn}
+
+ {network}
+
+
+
+ Address
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/modals/BackupModals.tsx b/src/components/modals/BackupModals.tsx
new file mode 100644
index 0000000..378c03e
--- /dev/null
+++ b/src/components/modals/BackupModals.tsx
@@ -0,0 +1,401 @@
+import { useEffect, useRef, useState } from "react";
+import { overlaySignerIframe } from "@1shotapi/ows-signer-utils";
+import type { RecoveryDataCreatedData } from "@1shotapi/ows-types";
+import {
+ useStyle,
+ type IStyleCopyCreateBackup,
+ type IStyleCopyRestoreBackup,
+} from "../../style";
+import { CopyableText } from "../CopyableText";
+import { Modal } from "../Modal";
+import { useWallet } from "../../wallet/WalletProvider";
+
+const DEFAULT_MIN_PASSWORD_LENGTH = 12;
+
+const SIGNER_SLOT_CLASS =
+ "border-border bg-muted/40 mb-4 min-h-28 overflow-hidden rounded-md border";
+
+function waitForPaint(): Promise {
+ return new Promise((resolve) => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => resolve());
+ });
+ });
+}
+
+/**
+ * Dialog portals content; refs on the footer slot are not always set on the
+ * first effect tick. Wait a few frames before treating them as missing.
+ */
+async function waitForSignerSlot(
+ getHome: () => HTMLElement | null,
+ getSlot: () => HTMLElement | null,
+ isAborted: () => boolean,
+): Promise<{ home: HTMLElement; slot: HTMLElement } | null> {
+ for (let attempt = 0; attempt < 60; attempt++) {
+ if (isAborted()) return null;
+ const home = getHome();
+ const slot = getSlot();
+ if (home && slot) {
+ return { home, slot };
+ }
+ await waitForPaint();
+ }
+ if (isAborted()) return null;
+ const home = getHome();
+ const slot = getSlot();
+ if (home && slot) {
+ return { home, slot };
+ }
+ return null;
+}
+
+function fillTemplate(
+ template: string,
+ vars: Record,
+): string {
+ return template.replace(/\{(\w+)\}/g, (_match, key: string) => vars[key] ?? "");
+}
+
+function formatBackupError(
+ error: unknown,
+ copy: IStyleCopyCreateBackup,
+): string {
+ if (error instanceof Error) {
+ const message = error.message;
+ if (message.includes("passwordTooShort")) {
+ return copy.passwordTooShortError;
+ }
+ if (message.includes("NotAllowed") || message.includes("not allowed")) {
+ return copy.cancelledError;
+ }
+ return message || copy.failedError;
+ }
+ return copy.failedError;
+}
+
+function formatRestoreError(
+ error: unknown,
+ copy: IStyleCopyRestoreBackup,
+): string {
+ if (error instanceof Error) {
+ const message = error.message;
+ if (
+ message.includes("decryptionFailed") ||
+ message.includes("decrypt") ||
+ message.includes("OperationError")
+ ) {
+ return copy.decryptFailedError;
+ }
+ if (message.includes("NotAllowed") || message.includes("not allowed")) {
+ return copy.cancelledError;
+ }
+ return message || copy.failedError;
+ }
+ return copy.failedError;
+}
+
+export function CreateBackupModal({
+ onResolve,
+ onReject,
+}: {
+ onResolve: () => void;
+ onReject: (error: unknown) => void;
+}) {
+ const { getSigner, signerContainerRef, ensureReady, persistBackup } =
+ useWallet();
+ const { style } = useStyle();
+ const createBackup = style.copy.createBackup;
+ const minLengthVars = {
+ minLength: String(DEFAULT_MIN_PASSWORD_LENGTH),
+ };
+ const signerSlotRef = useRef(null);
+ const [phase, setPhase] = useState<"prompt" | "result" | "error">("prompt");
+ const [error, setError] = useState(null);
+ const [result, setResult] = useState(null);
+ const abortedRef = useRef(false);
+ const restoreOverlayRef = useRef<(() => void) | null>(null);
+
+ useEffect(() => {
+ abortedRef.current = false;
+
+ void (async () => {
+ try {
+ const nodes = await waitForSignerSlot(
+ () => signerContainerRef.current,
+ () => signerSlotRef.current,
+ () => abortedRef.current,
+ );
+ if (abortedRef.current) return;
+ if (!nodes) {
+ onReject(new Error("Signer not ready for backup"));
+ return;
+ }
+ const { home, slot } = nodes;
+
+ // Unlock is usually a no-op here (create only when unlocked); this still
+ // waits for Signing Layer load before getSigner().
+ await ensureReady();
+ if (abortedRef.current) return;
+
+ const signer = getSigner();
+ if (!signer) {
+ throw new Error("Signer not ready for backup");
+ }
+
+ const iframe = home.querySelector("iframe");
+ if (!(iframe instanceof HTMLIFrameElement)) {
+ throw new Error("Signer iframe not found in signerContainer");
+ }
+
+ restoreOverlayRef.current = overlaySignerIframe(iframe, slot, {
+ homeContainer: home,
+ });
+ await waitForPaint();
+
+ const created = await signer.createRecoveryData(
+ fillTemplate(createBackup.passphrasePrompt, minLengthVars),
+ createBackup.continueLabel,
+ DEFAULT_MIN_PASSWORD_LENGTH,
+ );
+
+ if (abortedRef.current) return;
+
+ restoreOverlayRef.current?.();
+ restoreOverlayRef.current = null;
+
+ await persistBackup(created.encryptedPrivateKey);
+ if (abortedRef.current) return;
+
+ setResult(created);
+ setPhase("result");
+ } catch (err) {
+ if (abortedRef.current) return;
+ restoreOverlayRef.current?.();
+ restoreOverlayRef.current = null;
+ setError(formatBackupError(err, createBackup));
+ setPhase("error");
+ }
+ })().catch((err: unknown) => {
+ if (abortedRef.current) return;
+ restoreOverlayRef.current?.();
+ restoreOverlayRef.current = null;
+ onReject(err);
+ });
+
+ return () => {
+ abortedRef.current = true;
+ restoreOverlayRef.current?.();
+ restoreOverlayRef.current = null;
+ };
+ // Overlay/signing path runs once per modal open. Copy is snapshotted from
+ // StyleContext at mount; do not re-run when setStyle patches arrive mid-flow.
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- intentional
+ }, [
+ ensureReady,
+ getSigner,
+ onReject,
+ persistBackup,
+ signerContainerRef,
+ ]);
+
+ if (phase === "result" && result) {
+ return (
+
+
+ {createBackup.encryptedLabel}
+
+
+
+ );
+ }
+
+ return (
+
+ ) : null
+ }
+ actions={
+ phase === "error" || phase === "prompt"
+ ? [
+ {
+ label:
+ phase === "error"
+ ? createBackup.closeLabel
+ : createBackup.cancelLabel,
+ variant: "secondary",
+ autoFocus: phase === "error",
+ onClick: onResolve,
+ },
+ ]
+ : undefined
+ }
+ >
+ {phase === "prompt" ? (
+
+ {fillTemplate(createBackup.body, minLengthVars)}
+
+ ) : null}
+ {error ? (
+ {error}
+ ) : null}
+
+ );
+}
+
+export function RestoreBackupModal({
+ encryptedPrivateKey,
+ onResolve,
+ onReject,
+}: {
+ encryptedPrivateKey: string;
+ onResolve: (restored: boolean) => void;
+ onReject: (error: unknown) => void;
+}) {
+ const { getSigner, signerContainerRef, awaitSignerReady } = useWallet();
+ const { style } = useStyle();
+ const restoreBackup = style.copy.restoreBackup;
+ const signerSlotRef = useRef(null);
+ const [phase, setPhase] = useState<"prompt" | "done" | "error">("prompt");
+ const [error, setError] = useState(null);
+ const abortedRef = useRef(false);
+ const restoreOverlayRef = useRef<(() => void) | null>(null);
+
+ useEffect(() => {
+ abortedRef.current = false;
+
+ void (async () => {
+ try {
+ const nodes = await waitForSignerSlot(
+ () => signerContainerRef.current,
+ () => signerSlotRef.current,
+ () => abortedRef.current,
+ );
+ if (abortedRef.current) return;
+ if (!nodes) {
+ onReject(new Error("Signer not ready for restore"));
+ return;
+ }
+ const { home, slot } = nodes;
+
+ // Restore is offered while locked — wait for Signing Layer only.
+ // Do not call ensureReady() (that would unlock / run setup first).
+ await awaitSignerReady();
+ if (abortedRef.current) return;
+
+ const signer = getSigner();
+ if (!signer) {
+ throw new Error("Signer not ready for restore");
+ }
+
+ const iframe = home.querySelector("iframe");
+ if (!(iframe instanceof HTMLIFrameElement)) {
+ throw new Error("Signer iframe not found in signerContainer");
+ }
+
+ restoreOverlayRef.current = overlaySignerIframe(iframe, slot, {
+ homeContainer: home,
+ });
+ await waitForPaint();
+ await signer.recoverKey(
+ encryptedPrivateKey,
+ restoreBackup.passphraseLabel,
+ restoreBackup.restoreLabel,
+ );
+ if (abortedRef.current) return;
+ restoreOverlayRef.current?.();
+ restoreOverlayRef.current = null;
+ setPhase("done");
+ } catch (err) {
+ if (abortedRef.current) return;
+ restoreOverlayRef.current?.();
+ restoreOverlayRef.current = null;
+ setError(formatRestoreError(err, restoreBackup));
+ setPhase("error");
+ }
+ })().catch((err: unknown) => {
+ if (abortedRef.current) return;
+ restoreOverlayRef.current?.();
+ restoreOverlayRef.current = null;
+ onReject(err);
+ });
+
+ return () => {
+ abortedRef.current = true;
+ restoreOverlayRef.current?.();
+ restoreOverlayRef.current = null;
+ };
+ // Overlay/signing path runs once per modal open. Copy is snapshotted from
+ // StyleContext at mount; do not re-run when setStyle patches arrive mid-flow.
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- intentional
+ }, [
+ awaitSignerReady,
+ encryptedPrivateKey,
+ getSigner,
+ onReject,
+ signerContainerRef,
+ ]);
+
+ return (
+ onResolve(false) : undefined
+ }
+ footer={
+ phase === "prompt" ? (
+
+ ) : null
+ }
+ actions={
+ phase === "done"
+ ? [
+ {
+ label: restoreBackup.doneLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve(true),
+ },
+ ]
+ : [
+ {
+ label:
+ phase === "error"
+ ? restoreBackup.closeLabel
+ : restoreBackup.cancelLabel,
+ variant: "secondary",
+ autoFocus: phase === "error",
+ onClick: () => onResolve(false),
+ },
+ ]
+ }
+ >
+ {phase === "prompt" ? {restoreBackup.body}
: null}
+ {phase === "done" ? (
+ {restoreBackup.successBody}
+ ) : null}
+ {error ? (
+ {error}
+ ) : null}
+
+ );
+}
diff --git a/src/components/modals/CredentialModals.tsx b/src/components/modals/CredentialModals.tsx
new file mode 100644
index 0000000..4bbc5b6
--- /dev/null
+++ b/src/components/modals/CredentialModals.tsx
@@ -0,0 +1,122 @@
+import type {
+ CredentialOfferApprovalRequest,
+ CredentialPresentationApprovalRequest,
+} from "@1shotapi/ows-types";
+import { useStyle } from "../../style";
+import { Modal } from "../Modal";
+
+export function CredentialOfferModal({
+ request,
+ onResolve,
+}: {
+ request: CredentialOfferApprovalRequest;
+ onResolve: (approved: boolean) => void;
+}) {
+ const { style } = useStyle();
+ const { credentialOffer } = style.copy;
+
+ return (
+ onResolve(false)}
+ actions={[
+ {
+ label: credentialOffer.rejectLabel,
+ variant: "secondary",
+ onClick: () => onResolve(false),
+ },
+ {
+ label: credentialOffer.acceptLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve(true),
+ },
+ ]}
+ >
+
+ {fillTemplate(credentialOffer.body, {
+ issuerName: request.issuerName,
+ issuerId: request.issuerId,
+ })}
+
+ {credentialOffer.offeredHeading}
+
+ {request.offeredCredentials.map((credential) => {
+ const scope = credential.scope ? ` — ${credential.scope}` : "";
+ return (
+
+ {credential.configurationId} ({credential.format})
+ {scope}
+
+ );
+ })}
+
+
+ {credentialOffer.passkeyNote}
+
+
+ );
+}
+
+export function CredentialPresentationModal({
+ request,
+ onResolve,
+}: {
+ request: CredentialPresentationApprovalRequest;
+ onResolve: (approved: boolean) => void;
+}) {
+ const { style } = useStyle();
+ const { credentialPresentation } = style.copy;
+
+ return (
+ onResolve(false)}
+ actions={[
+ {
+ label: credentialPresentation.rejectLabel,
+ variant: "secondary",
+ onClick: () => onResolve(false),
+ },
+ {
+ label: credentialPresentation.shareLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve(true),
+ },
+ ]}
+ >
+
+ {fillTemplate(credentialPresentation.body, {
+ verifierName: request.verifierName,
+ verifierId: request.verifierId,
+ })}
+
+
+ {fillTemplate(credentialPresentation.credentialDetail, {
+ credentialType: request.credentialType,
+ credentialIssuer: request.credentialIssuer,
+ })}
+
+
+ {credentialPresentation.claimsHeading}
+
+
+ {request.requestedClaims.map((claim) => (
+ {claim}
+ ))}
+
+
+ {credentialPresentation.passkeyNote}
+
+
+ );
+}
+
+/** Replace `{name}` tokens; unknown keys become empty strings. */
+function fillTemplate(
+ template: string,
+ vars: Record,
+): string {
+ return template.replace(/\{(\w+)\}/g, (_match, key: string) => vars[key] ?? "");
+}
diff --git a/src/components/modals/PasskeyPromptModal.tsx b/src/components/modals/PasskeyPromptModal.tsx
new file mode 100644
index 0000000..bcfcb92
--- /dev/null
+++ b/src/components/modals/PasskeyPromptModal.tsx
@@ -0,0 +1,48 @@
+import { usePasskeyPromptStore } from "../../wallet/passkeyPromptStore";
+import { EPasskeyPromptReason } from "../../lib/types/enum";
+import type { IStyleCopyPasskeyPrompt } from "../../style";
+import { useStyle } from "../../style";
+import { Modal } from "../Modal";
+
+function copyForReason(
+ reason: EPasskeyPromptReason,
+ prompts: IStyleCopyPasskeyPrompt,
+): { title: string; body: string } {
+ switch (reason) {
+ case EPasskeyPromptReason.Unlock:
+ return prompts.unlock;
+ case EPasskeyPromptReason.Create:
+ return prompts.create;
+ case EPasskeyPromptReason.Sign:
+ return prompts.sign;
+ case EPasskeyPromptReason.Encrypt:
+ return prompts.encrypt;
+ case EPasskeyPromptReason.Decrypt:
+ return prompts.decrypt;
+ case EPasskeyPromptReason.RelayerAuth:
+ return prompts.relayerAuth;
+ case EPasskeyPromptReason.Backup:
+ return prompts.backup;
+ }
+}
+
+/**
+ * Non-interactive explanation while a WebAuthn ceremony is in flight.
+ * No Continue/Done — dismisses when {@link withPasskeyPrompt} finishes.
+ */
+export function PasskeyPromptModal() {
+ const reason = usePasskeyPromptStore((state) => state.activeReason);
+ const { style } = useStyle();
+
+ if (!reason) {
+ return null;
+ }
+
+ const copy = copyForReason(reason, style.copy.passkeyPrompt);
+
+ return (
+
+ {copy.body}
+
+ );
+}
diff --git a/src/components/modals/PurchaseComingSoonModal.tsx b/src/components/modals/PurchaseComingSoonModal.tsx
new file mode 100644
index 0000000..f56ecb2
--- /dev/null
+++ b/src/components/modals/PurchaseComingSoonModal.tsx
@@ -0,0 +1,29 @@
+import { Modal } from "../Modal";
+
+export interface IPurchaseComingSoonModalProps {
+ onClose: () => void;
+}
+
+/** Placeholder until an onramp provider is integrated. */
+export function PurchaseComingSoonModal({
+ onClose,
+}: IPurchaseComingSoonModalProps) {
+ return (
+
+
+ Purchase capabilities coming soon
+
+
+ );
+}
diff --git a/src/components/modals/ReceiveModal.tsx b/src/components/modals/ReceiveModal.tsx
new file mode 100644
index 0000000..d6385f2
--- /dev/null
+++ b/src/components/modals/ReceiveModal.tsx
@@ -0,0 +1,61 @@
+import { Modal } from "../Modal";
+import { QRCode } from "../QRCode";
+import { CopyableText } from "../CopyableText";
+import { useStyle } from "../../style";
+
+export interface IReceiveModalProps {
+ address: string;
+ chainLabel: string;
+ onClose: () => void;
+}
+
+/**
+ * Show the wallet address for the current chain as a QR + full copyable text.
+ */
+export function ReceiveModal({
+ address,
+ chainLabel,
+ onClose,
+}: IReceiveModalProps) {
+ const { style } = useStyle();
+ const { balances: copy } = style.copy;
+ const hasAddress = Boolean(address) && address !== "—";
+
+ return (
+
+
+ {copy.receiveBody.replace("{chainLabel}", chainLabel)}
+
+
+
+
+
+ {copy.receiveAddressLabel}
+
+
+
+
+
+ );
+}
diff --git a/src/components/modals/SentTransactionModal.tsx b/src/components/modals/SentTransactionModal.tsx
new file mode 100644
index 0000000..bb7f7f7
--- /dev/null
+++ b/src/components/modals/SentTransactionModal.tsx
@@ -0,0 +1,68 @@
+import type { EVMChainId, EVMTransactionHash } from "@1shotapi/ows-types";
+import { demoTxExplorerUrl } from "../../ows/demoChains";
+import { useStyle } from "../../style";
+import { CopyableText } from "../CopyableText";
+import { Modal } from "../Modal";
+
+export interface ISentTransactionModalProps {
+ chainId: EVMChainId;
+ transactionHash: EVMTransactionHash;
+ onClose: () => void;
+}
+
+/**
+ * Confirmation after an in-wallet send. Host EIP-1193 sends do not use this —
+ * they surface the hash in the host app instead.
+ */
+export function SentTransactionModal({
+ chainId,
+ transactionHash,
+ onClose,
+}: ISentTransactionModalProps) {
+ const { style } = useStyle();
+ const copy = style.copy.transferTokens;
+ const explorerUrl = demoTxExplorerUrl(chainId, transactionHash);
+
+ return (
+
+ {copy.sentBody}
+
+
+ );
+}
diff --git a/src/components/modals/SetupModals.tsx b/src/components/modals/SetupModals.tsx
new file mode 100644
index 0000000..2000343
--- /dev/null
+++ b/src/components/modals/SetupModals.tsx
@@ -0,0 +1,139 @@
+import { useState } from "react";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { useStyle } from "../../style";
+import { Modal } from "../Modal";
+import type { WalletSetupChoice } from "../../wallet/modalTypes";
+
+export function WalletSetupModal({
+ onResolve,
+}: {
+ onResolve: (choice: WalletSetupChoice) => void;
+}) {
+ const { style } = useStyle();
+ const { walletSetup } = style.copy;
+
+ return (
+ onResolve("cancel")}
+ actions={[
+ {
+ label: walletSetup.cancelLabel,
+ variant: "secondary",
+ onClick: () => onResolve("cancel"),
+ },
+ {
+ label: walletSetup.loginLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve("login"),
+ },
+ {
+ label: walletSetup.createLabel,
+ variant: "primary",
+ onClick: () => onResolve("create"),
+ },
+ ]}
+ >
+ {walletSetup.body}
+
+ );
+}
+
+export function PasskeyNameModal({
+ onResolve,
+}: {
+ onResolve: (name: string | null) => void;
+}) {
+ const { style } = useStyle();
+ const { passkeyName } = style.copy;
+ const [name, setName] = useState("");
+ const [error, setError] = useState(null);
+
+ const submit = () => {
+ const trimmed = name.trim();
+ if (!trimmed) {
+ setError(passkeyName.emptyError);
+ return;
+ }
+ onResolve(trimmed);
+ };
+
+ return (
+ onResolve(null)}
+ actions={[
+ {
+ label: passkeyName.cancelLabel,
+ variant: "secondary",
+ onClick: () => onResolve(null),
+ },
+ {
+ label: passkeyName.continueLabel,
+ variant: "primary",
+ onClick: submit,
+ },
+ ]}
+ >
+ {passkeyName.body}
+
+ {passkeyName.fieldLabel}
+ {
+ setName(event.target.value);
+ setError(null);
+ }}
+ onKeyDown={(event) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ submit();
+ }
+ }}
+ />
+
+ {error ? (
+ {error}
+ ) : null}
+
+ );
+}
+
+export function ConnectModal({
+ onResolve,
+}: {
+ onResolve: (approved: boolean) => void;
+}) {
+ const { style } = useStyle();
+ const { connect } = style.copy;
+
+ return (
+ onResolve(false)}
+ actions={[
+ {
+ label: connect.rejectLabel,
+ variant: "secondary",
+ onClick: () => onResolve(false),
+ },
+ {
+ label: connect.continueLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve(true),
+ },
+ ]}
+ >
+ {connect.body}
+
+ );
+}
diff --git a/src/components/modals/SignModals.tsx b/src/components/modals/SignModals.tsx
new file mode 100644
index 0000000..62da2fb
--- /dev/null
+++ b/src/components/modals/SignModals.tsx
@@ -0,0 +1,245 @@
+import type {
+ PersonalSignApprovalRequest,
+ SendTransactionApprovalRequest,
+ SignTypedDataApprovalRequest,
+} from "@1shotapi/ows-signer-utils";
+import { ConversionUtils, HexString } from "@1shotapi/ows-types";
+import { useStyle } from "../../style";
+import { Modal } from "../Modal";
+
+export function PersonalSignModal({
+ request,
+ onResolve,
+}: {
+ request: PersonalSignApprovalRequest;
+ onResolve: (approved: boolean) => void;
+}) {
+ const { style } = useStyle();
+ const { personalSign } = style.copy;
+
+ return (
+ onResolve(false)}
+ actions={[
+ {
+ label: personalSign.rejectLabel,
+ variant: "secondary",
+ onClick: () => onResolve(false),
+ },
+ {
+ label: personalSign.signLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve(true),
+ },
+ ]}
+ >
+ {personalSign.accountLabel}
+ {request.address}
+ {personalSign.messageLabel}
+
+
+ );
+}
+
+export function TypedDataModal({
+ request,
+ onResolve,
+}: {
+ request: SignTypedDataApprovalRequest;
+ onResolve: (approved: boolean) => void;
+}) {
+ const { style } = useStyle();
+ const { typedData: copy } = style.copy;
+ const { typedData } = request;
+
+ return (
+ onResolve(false)}
+ actions={[
+ {
+ label: copy.rejectLabel,
+ variant: "secondary",
+ onClick: () => onResolve(false),
+ },
+ {
+ label: copy.signLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve(true),
+ },
+ ]}
+ >
+ {copy.accountLabel}
+ {request.address}
+
+
+
+
+ );
+}
+
+export function SendTransactionModal({
+ request,
+ onResolve,
+}: {
+ request: SendTransactionApprovalRequest;
+ onResolve: (approved: boolean) => void;
+}) {
+ const { style } = useStyle();
+ const { sendTransaction: copy } = style.copy;
+
+ return (
+ onResolve(false)}
+ actions={[
+ {
+ label: copy.rejectLabel,
+ variant: "secondary",
+ onClick: () => onResolve(false),
+ },
+ {
+ label: copy.signLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve(true),
+ },
+ ]}
+ >
+ {copy.accountLabel}
+ {request.address}
+
+
+
+
+
+ );
+}
+
+export function ConfirmTransferModal({
+ request,
+ onResolve,
+}: {
+ request: {
+ domain: string;
+ amount: string;
+ tokenName: string;
+ tokenSymbol: string;
+ receiver: string;
+ chainName: string;
+ };
+ onResolve: (approved: boolean) => void;
+}) {
+ const { style } = useStyle();
+ const { confirmTransfer: copy } = style.copy;
+ const body = copy.body
+ .replace("{domain}", request.domain)
+ .replace("{amount}", request.amount)
+ .replace("{tokenName}", request.tokenName)
+ .replace("{tokenSymbol}", request.tokenSymbol)
+ .replace("{receiver}", request.receiver)
+ .replace("{chainName}", request.chainName);
+
+ return (
+ onResolve(false)}
+ actions={[
+ {
+ label: copy.rejectLabel,
+ variant: "secondary",
+ onClick: () => onResolve(false),
+ },
+ {
+ label: copy.confirmLabel,
+ variant: "primary",
+ autoFocus: true,
+ onClick: () => onResolve(true),
+ },
+ ]}
+ >
+ {body}
+
+
+
+
+
+
+
+ );
+}
+
+function FieldLabel({ children }: { children: string }) {
+ return (
+
+ {children}
+
+ );
+}
+
+function DetailBlock({ content }: { content: string }) {
+ return {content} ;
+}
+
+function LabeledBlock({ label, content }: { label: string; content: string }) {
+ return (
+
+ {label}
+
+
+ );
+}
+
+function formatJson(value: unknown): string {
+ try {
+ return JSON.stringify(
+ value,
+ (_key, v) => (typeof v === "bigint" ? v.toString() : v),
+ 2,
+ );
+ } catch {
+ return String(value);
+ }
+}
+
+function formatMessageForDisplay(message: string): string {
+ if (message.startsWith("0x") && message.length > 2) {
+ try {
+ const bytes = ConversionUtils.hexToBytes(
+ HexString(message as `0x${string}`),
+ );
+ const decoded = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
+ if (isMostlyPrintable(decoded)) {
+ return decoded;
+ }
+ } catch {
+ // fall through
+ }
+ }
+ return message;
+}
+
+function isMostlyPrintable(text: string): boolean {
+ if (!text.trim()) return false;
+ let printable = 0;
+ for (const char of text) {
+ const code = char.codePointAt(0) ?? 0;
+ if (code >= 32 && code !== 127) printable++;
+ }
+ return printable / text.length >= 0.85;
+}
diff --git a/src/components/modals/TransferTokensModal.tsx b/src/components/modals/TransferTokensModal.tsx
new file mode 100644
index 0000000..6b47c77
--- /dev/null
+++ b/src/components/modals/TransferTokensModal.tsx
@@ -0,0 +1,222 @@
+import { useCallback, useMemo, useState } from "react";
+import {
+ encodeFunctionData,
+ erc20Abi,
+ parseUnits,
+ type Hex,
+} from "viem";
+import { AddressUtils } from "@1shotapi/ows-wallet-utils";
+import {
+ EChainTechnology,
+ HexString,
+ type EVMAccountAddress,
+ type EVMTransactionHash,
+} from "@1shotapi/ows-types";
+import type { TrackedAsset } from "../../lib/types/business";
+import { EAssetType } from "../../lib/types/enum";
+import { useStyle } from "../../style";
+import { chainTechnologyFor } from "../../wallet/activeAddress";
+import { useWallet } from "../../wallet/WalletProvider";
+import { Modal } from "../Modal";
+import {
+ AddressInput,
+ type AddressInputValue,
+} from "../AddressInput";
+import { SentTransactionModal } from "./SentTransactionModal";
+import { TokenAmountInput } from "../TokenAmountInput";
+
+export interface ITransferTokensModalProps {
+ asset: TrackedAsset;
+ addressUtils: AddressUtils;
+ onClose: () => void;
+ onSuccess: (hash: EVMTransactionHash) => void;
+}
+
+function amountValidationError(
+ raw: string,
+ decimals: number,
+ balance: bigint | null,
+ copy: {
+ invalidAmountError: string;
+ insufficientBalanceError: string;
+ },
+): string | null {
+ const trimmed = raw.trim();
+ if (!trimmed) {
+ return null;
+ }
+ let parsed: bigint;
+ try {
+ parsed = parseUnits(trimmed, decimals);
+ } catch {
+ return copy.invalidAmountError;
+ }
+ if (parsed <= 0n) {
+ return copy.invalidAmountError;
+ }
+ if (balance !== null && parsed > balance) {
+ return copy.insufficientBalanceError;
+ }
+ return null;
+}
+
+/**
+ * User-initiated ERC-20 send. Collects recipient/amount, then submits via the
+ * relayer (`sendTransaction`) — not through host EIP-1193 consent.
+ */
+export function TransferTokensModal({
+ asset,
+ addressUtils,
+ onClose,
+ onSuccess,
+}: ITransferTokensModalProps) {
+ const { style } = useStyle();
+ const copy = style.copy.transferTokens;
+ const { switchChain, sendTransaction } = useWallet();
+ const [amount, setAmount] = useState("");
+ const [recipientText, setRecipientText] = useState("");
+ const [recipient, setRecipient] = useState(null);
+ const [submitError, setSubmitError] = useState(null);
+ const [busy, setBusy] = useState(false);
+ const [sentHash, setSentHash] = useState(null);
+
+ const technology = useMemo(
+ () => chainTechnologyFor(asset.chainId),
+ [asset.chainId],
+ );
+
+ const amountError = useMemo(
+ () =>
+ amountValidationError(amount, asset.decimals, asset.balance, copy),
+ [amount, asset.balance, asset.decimals, copy],
+ );
+
+ const canSubmit = useMemo(() => {
+ if (busy || asset.type !== EAssetType.Erc20) {
+ return false;
+ }
+ if (technology !== EChainTechnology.Evm || !recipient) {
+ return false;
+ }
+ if (!amount.trim() || amountError) {
+ return false;
+ }
+ return true;
+ }, [
+ amount,
+ amountError,
+ asset.type,
+ busy,
+ recipient,
+ technology,
+ ]);
+
+ const onValidated = useCallback((address: AddressInputValue) => {
+ setRecipient(address);
+ }, []);
+
+ const onAmountChange = useCallback((next: string) => {
+ setAmount(next);
+ setSubmitError(null);
+ }, []);
+
+ async function handleSend(): Promise {
+ setSubmitError(null);
+
+ if (!canSubmit || !recipient) {
+ return;
+ }
+
+ let parsed: bigint;
+ try {
+ parsed = parseUnits(amount.trim(), asset.decimals);
+ } catch {
+ return;
+ }
+
+ setBusy(true);
+ try {
+ await switchChain(asset.chainId);
+ const data = HexString(
+ encodeFunctionData({
+ abi: erc20Abi,
+ functionName: "transfer",
+ args: [recipient as EVMAccountAddress, parsed],
+ }) as Hex,
+ );
+ const hash = await sendTransaction(asset.chainId, asset.address, data);
+ onSuccess(hash);
+ setSentHash(hash);
+ } catch (error: unknown) {
+ console.error("[oneshot-wallet] transfer failed", error);
+ setSubmitError(
+ error instanceof Error ? error.message : copy.sendFailedError,
+ );
+ } finally {
+ setBusy(false);
+ }
+ }
+
+ if (sentHash) {
+ return (
+
+ );
+ }
+
+ return (
+ void handleSend(),
+ },
+ ]}
+ >
+ {copy.body}
+
+
+
+ {submitError ? (
+
+ {submitError}
+
+ ) : null}
+
+
+ );
+}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..75b8c3d
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,67 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
+ outline:
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
+ ghost:
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
+ destructive:
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default:
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
+ icon: "size-8",
+ "icon-xs":
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
+ "icon-sm":
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
+ "icon-lg": "size-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant = "default",
+ size = "default",
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot.Root : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..29201e1
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,168 @@
+"use client"
+
+import * as React from "react"
+import { Dialog as DialogPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { XIcon } from "lucide-react"
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+
+ Close
+
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({
+ className,
+ showCloseButton = false,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+ {children}
+ {showCloseButton && (
+
+ Close
+
+ )}
+
+ )
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
new file mode 100644
index 0000000..d763cd9
--- /dev/null
+++ b/src/components/ui/input.tsx
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
new file mode 100644
index 0000000..f752f82
--- /dev/null
+++ b/src/components/ui/label.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+import { Label as LabelPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Label }
diff --git a/src/components/ui/menubar.tsx b/src/components/ui/menubar.tsx
new file mode 100644
index 0000000..f25ff82
--- /dev/null
+++ b/src/components/ui/menubar.tsx
@@ -0,0 +1,278 @@
+import * as React from "react"
+import { Menubar as MenubarPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { CheckIcon, ChevronRightIcon } from "lucide-react"
+
+function Menubar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function MenubarMenu({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function MenubarGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function MenubarPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function MenubarRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function MenubarTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function MenubarContent({
+ className,
+ align = "start",
+ alignOffset = -4,
+ sideOffset = 8,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function MenubarItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+ variant?: "default" | "destructive"
+}) {
+ return (
+
+ )
+}
+
+function MenubarCheckboxItem({
+ className,
+ children,
+ checked,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function MenubarRadioItem({
+ className,
+ children,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function MenubarLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function MenubarSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function MenubarShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function MenubarSub({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function MenubarSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function MenubarSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Menubar,
+ MenubarPortal,
+ MenubarMenu,
+ MenubarTrigger,
+ MenubarContent,
+ MenubarGroup,
+ MenubarSeparator,
+ MenubarLabel,
+ MenubarItem,
+ MenubarShortcut,
+ MenubarCheckboxItem,
+ MenubarRadioGroup,
+ MenubarRadioItem,
+ MenubarSub,
+ MenubarSubTrigger,
+ MenubarSubContent,
+}
diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx
new file mode 100644
index 0000000..96fdae7
--- /dev/null
+++ b/src/components/ui/pagination.tsx
@@ -0,0 +1,129 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
+
+function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
+ return (
+
+ )
+}
+
+function PaginationContent({
+ className,
+ ...props
+}: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function PaginationItem({ ...props }: React.ComponentProps<"li">) {
+ return
+}
+
+type PaginationLinkProps = {
+ isActive?: boolean
+} & Pick, "size"> &
+ React.ComponentProps<"a">
+
+function PaginationLink({
+ className,
+ isActive,
+ size = "icon",
+ ...props
+}: PaginationLinkProps) {
+ return (
+
+
+
+ )
+}
+
+function PaginationPrevious({
+ className,
+ text = "Previous",
+ ...props
+}: React.ComponentProps & { text?: string }) {
+ return (
+
+
+ {text}
+
+ )
+}
+
+function PaginationNext({
+ className,
+ text = "Next",
+ ...props
+}: React.ComponentProps & { text?: string }) {
+ return (
+
+ {text}
+
+
+ )
+}
+
+function PaginationEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More pages
+
+ )
+}
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationEllipsis,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+}
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
new file mode 100644
index 0000000..8333850
--- /dev/null
+++ b/src/components/ui/select.tsx
@@ -0,0 +1,190 @@
+import * as React from "react"
+import { Select as SelectPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "item-aligned",
+ align = "center",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
new file mode 100644
index 0000000..ac9585e
--- /dev/null
+++ b/src/components/ui/table.tsx
@@ -0,0 +1,114 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Table({ className, ...props }: React.ComponentProps<"table">) {
+ return (
+
+ )
+}
+
+function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
+ return (
+
+ )
+}
+
+function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
+ return (
+
+ )
+}
+
+function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
+ return (
+ tr]:last:border-b-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+ return (
+
+ )
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+ return (
+
+ )
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+
+ )
+}
+
+function TableCaption({
+ className,
+ ...props
+}: React.ComponentProps<"caption">) {
+ return (
+
+ )
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..05f469f
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,90 @@
+"use client"
+
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Tabs as TabsPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Tabs({
+ className,
+ orientation = "horizontal",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+const tabsListVariants = cva(
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
+ {
+ variants: {
+ variant: {
+ default: "bg-muted",
+ line: "gap-1 bg-transparent",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function TabsList({
+ className,
+ variant = "default",
+ ...props
+}: React.ComponentProps &
+ VariantProps) {
+ return (
+
+ )
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..04d27f7
--- /dev/null
+++ b/src/components/ui/textarea.tsx
@@ -0,0 +1,18 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ )
+}
+
+export { Textarea }
diff --git a/src/credentials/CachedRelayerCredentialRepository.ts b/src/credentials/CachedRelayerCredentialRepository.ts
new file mode 100644
index 0000000..f039f36
--- /dev/null
+++ b/src/credentials/CachedRelayerCredentialRepository.ts
@@ -0,0 +1,314 @@
+import type { OWSSigner } from "@1shotapi/ows-signer-utils";
+import {
+ AES256CipherText,
+ type COSEPublicKey,
+ type CredentialFilter,
+ type CredentialId,
+ type CredentialSummary,
+ type ICredentialRepository,
+ type StoredCredential,
+} from "@1shotapi/ows-types";
+import type { RelayerCredentialsClient } from "../relayer/RelayerCredentialsClient";
+import { createRelayerAssertion } from "../relayer/webauthnAuth";
+import { loadCredentialId } from "../storage";
+
+export const OWS_CREDENTIALS_STORAGE_KEY = "ows.credentials.v2";
+
+/** Minimal sync key/value API (localStorage or test double). */
+export type CredentialStorageBackend = {
+ getItem(key: string): string | null;
+ setItem(key: string, value: string): void;
+ removeItem(key: string): void;
+};
+
+type StoredBlob = {
+ credentials: Record;
+ revoked: string[];
+ blobIds: Record;
+};
+
+export interface ICachedRelayerCredentialRepositoryDeps {
+ client: RelayerCredentialsClient;
+ getSigner: () => OWSSigner;
+ storageKey?: string;
+ storage?: CredentialStorageBackend;
+}
+
+function summarize(
+ credentials: Iterable,
+ filter?: CredentialFilter,
+): CredentialSummary[] {
+ const filtered = [...credentials].filter((c) => {
+ if (filter?.type && !c.type.includes(filter.type)) {
+ return false;
+ }
+ if (filter?.issuer && c.issuer !== filter.issuer) {
+ return false;
+ }
+ return true;
+ });
+
+ return filtered.map((c) => ({
+ credentialId: c.credentialId,
+ type: c.type,
+ issuer: c.issuer,
+ format: c.format,
+ issuedAt: c.issuedAt,
+ validUntil: c.validUntil,
+ }));
+}
+
+function isStoredCredential(value: unknown): value is StoredCredential {
+ if (!value || typeof value !== "object") return false;
+ const record = value as Record;
+ return (
+ typeof record.credentialId === "string" &&
+ typeof record.payload === "string" &&
+ typeof record.issuer === "string" &&
+ Array.isArray(record.type)
+ );
+}
+
+/**
+ * Local plaintext cache + encrypted official copies on the 1Shot Relayer.
+ * `list`/`get` read the cache; `store`/`delete`/`revoke` sync to the relayer;
+ * `refreshFromRelayer` replaces the cache from recover.
+ */
+export class CachedRelayerCredentialRepository implements ICredentialRepository {
+ private readonly client: RelayerCredentialsClient;
+ private readonly getSigner: () => OWSSigner;
+ private readonly storageKey: string;
+ private readonly storage: CredentialStorageBackend;
+
+ constructor(deps: ICachedRelayerCredentialRepositoryDeps) {
+ this.client = deps.client;
+ this.getSigner = deps.getSigner;
+ this.storageKey = deps.storageKey ?? OWS_CREDENTIALS_STORAGE_KEY;
+ this.storage =
+ deps.storage ??
+ (typeof localStorage !== "undefined"
+ ? localStorage
+ : createMemoryStorageBackend());
+ }
+
+ async store(credential: StoredCredential): Promise {
+ const blob = this.readBlob();
+ const previousBlobId = blob.blobIds[credential.credentialId];
+ blob.credentials[credential.credentialId] = credential;
+ blob.revoked = blob.revoked.filter((id) => id !== credential.credentialId);
+ this.writeBlob(blob);
+
+ try {
+ const signer = this.getSigner();
+ const [ciphertext] = await signer.encryptAES256([
+ JSON.stringify(credential),
+ ]);
+ if (!ciphertext) {
+ throw new Error("encryptAES256 returned no ciphertext");
+ }
+
+ if (previousBlobId) {
+ await this.deleteRelayerBlob(previousBlobId);
+ }
+
+ const assertion = await this.assert();
+ const { id } = await this.client.storeCredential({
+ ...assertion,
+ ciphertext: String(ciphertext),
+ });
+
+ const next = this.readBlob();
+ next.blobIds[credential.credentialId] = id;
+ this.writeBlob(next);
+ } catch (error: unknown) {
+ // Keep the local cache so OID4 accept still succeeds; Refresh can sync later.
+ console.warn(
+ "[credentials] local store ok; relayer upload failed",
+ error,
+ );
+ }
+ }
+
+ async get(credentialId: CredentialId): Promise {
+ const blob = this.readBlob();
+ if (blob.revoked.includes(credentialId)) {
+ return undefined;
+ }
+ return blob.credentials[credentialId];
+ }
+
+ async list(filter?: CredentialFilter): Promise {
+ const blob = this.readBlob();
+ const active = Object.values(blob.credentials).filter(
+ (c) => !blob.revoked.includes(c.credentialId),
+ );
+ return summarize(active, filter);
+ }
+
+ async delete(credentialId: CredentialId): Promise {
+ const blob = this.readBlob();
+ const blobId = blob.blobIds[credentialId];
+ delete blob.credentials[credentialId];
+ delete blob.blobIds[credentialId];
+ blob.revoked = blob.revoked.filter((id) => id !== credentialId);
+ this.writeBlob(blob);
+
+ if (blobId) {
+ try {
+ await this.deleteRelayerBlob(blobId);
+ } catch (error: unknown) {
+ console.warn(
+ "[credentials] failed to delete credential blob on relayer",
+ error,
+ );
+ }
+ }
+ }
+
+ async revoke(credentialId: CredentialId): Promise {
+ const blob = this.readBlob();
+ if (blob.credentials[credentialId] && !blob.revoked.includes(credentialId)) {
+ blob.revoked.push(credentialId);
+ this.writeBlob(blob);
+ }
+
+ const blobId = blob.blobIds[credentialId];
+ if (blobId) {
+ try {
+ await this.deleteRelayerBlob(blobId);
+ const next = this.readBlob();
+ delete next.blobIds[credentialId];
+ this.writeBlob(next);
+ } catch (error: unknown) {
+ console.warn(
+ "[credentials] failed to revoke credential blob on relayer",
+ error,
+ );
+ }
+ }
+ }
+
+ /**
+ * Register this wallet's WebAuthn passkey with the relayer.
+ * Call once at account creation while `cosePublicKey` is still available.
+ * Later store/recover/delete only need `credentialId` + assertion.
+ */
+ async registerPasskey(publicKey: COSEPublicKey): Promise {
+ const assertion = await this.assert();
+ await this.client.registerPasskey({ ...assertion, publicKey });
+ }
+
+ /**
+ * Pull recover blobs from the relayer, decrypt, and replace the local cache.
+ * Requires the passkey to already be registered (done at wallet create).
+ */
+ async refreshFromRelayer(): Promise {
+ const assertion = await this.assert();
+ const { credentials: remote } =
+ await this.client.recoverCredentials(assertion);
+
+ if (remote.length === 0) {
+ this.writeBlob({ credentials: {}, revoked: [], blobIds: {} });
+ return;
+ }
+
+ const signer = this.getSigner();
+ const ciphertexts = remote.map((item) =>
+ AES256CipherText(item.ciphertext),
+ );
+ const plaintexts = await signer.decryptAES256(ciphertexts);
+
+ const credentials: Record = {};
+ const blobIds: Record = {};
+
+ for (let i = 0; i < remote.length; i += 1) {
+ const item = remote[i]!;
+ const raw = plaintexts[i];
+ if (typeof raw !== "string") continue;
+ try {
+ const parsed: unknown = JSON.parse(raw);
+ if (!isStoredCredential(parsed)) {
+ console.warn(
+ "[credentials] skipping undecryptable/invalid recovered blob",
+ item.id,
+ );
+ continue;
+ }
+ credentials[parsed.credentialId] = parsed;
+ blobIds[parsed.credentialId] = item.id;
+ } catch (error: unknown) {
+ console.warn(
+ "[credentials] failed to parse recovered credential",
+ item.id,
+ error,
+ );
+ }
+ }
+
+ this.writeBlob({ credentials, revoked: [], blobIds });
+ }
+
+ private async assert() {
+ const credentialId = loadCredentialId();
+ if (!credentialId) {
+ throw new Error("WebAuthn credential id missing");
+ }
+ return createRelayerAssertion(this.client, credentialId);
+ }
+
+ private async deleteRelayerBlob(blobId: string): Promise {
+ const assertion = await this.assert();
+ await this.client.deleteCredentials({
+ ...assertion,
+ credentialBlobId: blobId,
+ });
+ }
+
+ private readBlob(): StoredBlob {
+ const raw = this.storage.getItem(this.storageKey);
+ if (!raw) {
+ return { credentials: {}, revoked: [], blobIds: {} };
+ }
+
+ try {
+ const parsed = JSON.parse(raw) as StoredBlob;
+ return {
+ credentials:
+ parsed && typeof parsed.credentials === "object"
+ ? (parsed.credentials ?? {})
+ : {},
+ revoked: Array.isArray(parsed.revoked) ? parsed.revoked : [],
+ blobIds:
+ parsed && typeof parsed.blobIds === "object"
+ ? (parsed.blobIds ?? {})
+ : {},
+ };
+ } catch {
+ return { credentials: {}, revoked: [], blobIds: {} };
+ }
+ }
+
+ private writeBlob(blob: StoredBlob): void {
+ const credCount = Object.keys(blob.credentials).length;
+ const blobCount = Object.keys(blob.blobIds).length;
+ if (credCount === 0 && blob.revoked.length === 0 && blobCount === 0) {
+ this.storage.removeItem(this.storageKey);
+ return;
+ }
+ this.storage.setItem(this.storageKey, JSON.stringify(blob));
+ }
+}
+
+/** In-memory backend for Node tests and SSR fallbacks. */
+export function createMemoryStorageBackend(): CredentialStorageBackend {
+ const data = new Map();
+ return {
+ getItem: (key) => data.get(key) ?? null,
+ setItem: (key, value) => {
+ data.set(key, value);
+ },
+ removeItem: (key) => {
+ data.delete(key);
+ },
+ };
+}
diff --git a/src/demo/demo-keys.ts b/src/demo/demo-keys.ts
new file mode 100644
index 0000000..7261bb3
--- /dev/null
+++ b/src/demo/demo-keys.ts
@@ -0,0 +1,10 @@
+/**
+ * MOCK demo Ed25519 holder key — TEST ONLY. Do not use in production.
+ * Used by DemoWalletAttestationProvider until a wallet-backed IHolderSigner is wired.
+ */
+export const DEMO_HOLDER_PRIVATE_JWK: JsonWebKey = {
+ crv: "Ed25519",
+ d: "G8_8M9RjEA5L5NIeAoB24C9yOb8KPuRoh9y7SidoXJA",
+ x: "3dmWOAMTtkMxm88aJ9QhlK5SWimNXt6-WTsI4eFHwC8",
+ kty: "OKP",
+};
diff --git a/src/demo/in-memory-trust-registry.ts b/src/demo/in-memory-trust-registry.ts
new file mode 100644
index 0000000..49a23b2
--- /dev/null
+++ b/src/demo/in-memory-trust-registry.ts
@@ -0,0 +1,79 @@
+import {
+ CredentialIssuer,
+ type IIssuerTrustRegistry,
+ type IssuerTrustMetadata,
+} from "@1shotapi/ows-types";
+
+/** MOCK KYC issuer — demo trust entry until production allow-lists land. */
+export const MOCK_KYC_ISSUER_ID = CredentialIssuer(
+ "https://kyc.demo.issuer.example",
+);
+
+const MOCK_ISSUER_TRUST: IssuerTrustMetadata = {
+ issuerId: MOCK_KYC_ISSUER_ID,
+ name: "Demo KYC Issuer",
+ assuranceLevels: ["substantial", "high"],
+ jurisdictions: ["US", "GB"],
+};
+
+/**
+ * Hostnames trusted for demo OID4VCI offers (any path under the origin).
+ * Includes local OWS demos and the 1Shot marketing playground issuer.
+ */
+const TRUSTED_DEMO_HOSTNAMES = new Set([
+ "localhost",
+ "127.0.0.1",
+ "ows-host.com",
+ "ows-issuer.com",
+ "ows-verifier.com",
+ "1shotapi.com",
+ "www.1shotapi.com",
+]);
+
+/** True for local Vite / mkcert demos and the 1Shot site playground issuer. */
+export function isTrustedDemoIssuerOrigin(issuerId: string): boolean {
+ try {
+ const url = new URL(issuerId);
+ return (
+ TRUSTED_DEMO_HOSTNAMES.has(url.hostname) ||
+ url.hostname.endsWith(".ows-host.com") ||
+ url.hostname.endsWith(".ows-issuer.com") ||
+ url.hostname.endsWith(".ows-verifier.com") ||
+ url.hostname.endsWith(".1shotapi.com")
+ );
+ } catch {
+ return false;
+ }
+}
+
+export class InMemoryIssuerTrustRegistry implements IIssuerTrustRegistry {
+ constructor(
+ private readonly entries: IssuerTrustMetadata[] = [MOCK_ISSUER_TRUST],
+ ) {}
+
+ async isTrustedIssuer(issuerId: CredentialIssuer): Promise {
+ const id = String(issuerId);
+ if (isTrustedDemoIssuerOrigin(id)) return true;
+ return this.entries.some((e) => e.issuerId === issuerId);
+ }
+
+ async resolveIssuer(
+ issuerId: CredentialIssuer,
+ ): Promise {
+ const found = this.entries.find((e) => e.issuerId === issuerId);
+ if (found) return found;
+ if (isTrustedDemoIssuerOrigin(String(issuerId))) {
+ return {
+ issuerId,
+ name: "Demo KYC Issuer",
+ assuranceLevels: ["substantial", "high"],
+ jurisdictions: ["US", "GB"],
+ };
+ }
+ return undefined;
+ }
+
+ async listIssuers(): Promise {
+ return [...this.entries];
+ }
+}
diff --git a/src/demo/local-storage-store.ts b/src/demo/local-storage-store.ts
new file mode 100644
index 0000000..0b217ae
--- /dev/null
+++ b/src/demo/local-storage-store.ts
@@ -0,0 +1,159 @@
+import type { CredentialId } from "@1shotapi/ows-types";
+import type {
+ ICredentialRepository,
+ StoredCredential,
+ CredentialFilter,
+ CredentialSummary,
+} from "@1shotapi/ows-types";
+
+/** localStorage key for demo credentials (shared across wallet iframe loads on one origin). */
+export const OWS_MOCK_CREDENTIALS_STORAGE_KEY = "ows.mock.credentials.v1";
+
+/** Minimal sync key/value API (localStorage or test double). */
+export type CredentialStorageBackend = {
+ getItem(key: string): string | null;
+ setItem(key: string, value: string): void;
+ removeItem(key: string): void;
+};
+
+type StoredBlob = {
+ credentials: Record;
+ revoked: string[];
+};
+
+function summarize(
+ credentials: Iterable,
+ filter?: CredentialFilter,
+): CredentialSummary[] {
+ const filtered = [...credentials].filter((c) => {
+ if (filter?.type && !c.type.includes(filter.type)) {
+ return false;
+ }
+ if (filter?.issuer && c.issuer !== filter.issuer) {
+ return false;
+ }
+ return true;
+ });
+
+ return filtered.map((c) => ({
+ credentialId: c.credentialId,
+ type: c.type,
+ issuer: c.issuer,
+ format: c.format,
+ issuedAt: c.issuedAt,
+ validUntil: c.validUntil,
+ }));
+}
+
+/** Persists credentials in localStorage (plaintext — demo / bootstrap only). */
+export class LocalStorageCredentialRepository implements ICredentialRepository {
+ private readonly storageKey: string;
+ private readonly storage: CredentialStorageBackend;
+
+ constructor(
+ options: {
+ storageKey?: string;
+ storage?: CredentialStorageBackend;
+ } = {},
+ ) {
+ this.storageKey =
+ options.storageKey ?? OWS_MOCK_CREDENTIALS_STORAGE_KEY;
+ this.storage =
+ options.storage ??
+ (typeof localStorage !== "undefined"
+ ? localStorage
+ : createMemoryStorageBackend());
+ }
+
+ async store(credential: StoredCredential): Promise {
+ const blob = this.readBlob();
+ blob.credentials[credential.credentialId] = credential;
+ blob.revoked = blob.revoked.filter((id) => id !== credential.credentialId);
+ this.writeBlob(blob);
+ }
+
+ async get(credentialId: CredentialId): Promise {
+ const blob = this.readBlob();
+ if (blob.revoked.includes(credentialId)) {
+ return undefined;
+ }
+ return blob.credentials[credentialId];
+ }
+
+ async list(filter?: CredentialFilter): Promise {
+ const blob = this.readBlob();
+ const active = Object.values(blob.credentials).filter(
+ (c) => !blob.revoked.includes(c.credentialId),
+ );
+ return summarize(active, filter);
+ }
+
+ async delete(credentialId: CredentialId): Promise {
+ const blob = this.readBlob();
+ delete blob.credentials[credentialId];
+ this.writeBlob(blob);
+ }
+
+ async revoke(credentialId: CredentialId): Promise {
+ const blob = this.readBlob();
+ if (blob.credentials[credentialId] && !blob.revoked.includes(credentialId)) {
+ blob.revoked.push(credentialId);
+ this.writeBlob(blob);
+ }
+ }
+
+ private readBlob(): StoredBlob {
+ const raw = this.storage.getItem(this.storageKey);
+ if (!raw) {
+ return { credentials: {}, revoked: [] };
+ }
+
+ try {
+ const parsed = JSON.parse(raw) as
+ | StoredBlob
+ | Record;
+ if (
+ parsed &&
+ typeof parsed === "object" &&
+ "credentials" in parsed &&
+ typeof (parsed as StoredBlob).credentials === "object"
+ ) {
+ const blob = parsed as StoredBlob;
+ return {
+ credentials: blob.credentials ?? {},
+ revoked: Array.isArray(blob.revoked) ? blob.revoked : [],
+ };
+ }
+ return {
+ credentials: parsed as Record,
+ revoked: [],
+ };
+ } catch {
+ return { credentials: {}, revoked: [] };
+ }
+ }
+
+ private writeBlob(blob: StoredBlob): void {
+ const credCount = Object.keys(blob.credentials).length;
+ if (credCount === 0 && blob.revoked.length === 0) {
+ this.storage.removeItem(this.storageKey);
+ return;
+ }
+
+ this.storage.setItem(this.storageKey, JSON.stringify(blob));
+ }
+}
+
+/** In-memory backend for Node tests and SSR fallbacks. */
+export function createMemoryStorageBackend(): CredentialStorageBackend {
+ const data = new Map();
+ return {
+ getItem: (key) => data.get(key) ?? null,
+ setItem: (key, value) => {
+ data.set(key, value);
+ },
+ removeItem: (key) => {
+ data.delete(key);
+ },
+ };
+}
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..1b2619d
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,160 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+@import "shadcn/tailwind.css";
+@import "@fontsource-variable/geist";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --font-heading: var(--font-sans);
+ --font-sans: "Geist Variable", ui-sans-serif, system-ui, sans-serif;
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
+ --color-chart-5: var(--chart-5);
+ --color-chart-4: var(--chart-4);
+ --color-chart-3: var(--chart-3);
+ --color-chart-2: var(--chart-2);
+ --color-chart-1: var(--chart-1);
+ --color-ring: var(--ring);
+ --color-input: var(--input);
+ --color-border: var(--border);
+ --color-destructive: var(--destructive);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-accent: var(--accent);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-muted: var(--muted);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-secondary: var(--secondary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary: var(--primary);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-popover: var(--popover);
+ --color-card-foreground: var(--card-foreground);
+ --color-card: var(--card);
+ --color-foreground: var(--foreground);
+ --color-background: var(--background);
+ --radius-sm: calc(var(--radius) * 0.6);
+ --radius-md: calc(var(--radius) * 0.8);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) * 1.4);
+ --radius-2xl: calc(var(--radius) * 1.8);
+ --radius-3xl: calc(var(--radius) * 2.2);
+ --radius-4xl: calc(var(--radius) * 2.6);
+}
+
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: #2499a9;
+ --primary-foreground: #ffffff;
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.87 0 0);
+ --chart-2: oklch(0.556 0 0);
+ --chart-3: oklch(0.439 0 0);
+ --chart-4: oklch(0.371 0 0);
+ --chart-5: oklch(0.269 0 0);
+ --radius: 0.625rem;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.87 0 0);
+ --chart-2: oklch(0.556 0 0);
+ --chart-3: oklch(0.439 0 0);
+ --chart-4: oklch(0.371 0 0);
+ --chart-5: oklch(0.269 0 0);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ html,
+ body,
+ #root {
+ margin: 0;
+ height: 100%;
+ min-height: 100%;
+ /* Match host requestDisplay(~420px): never grow past the iframe. */
+ max-width: 100%;
+ overflow-x: hidden;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+ html {
+ @apply font-sans;
+ }
+}
+
+@layer components {
+ /**
+ * Mono detail blocks (tx data, backup ciphertext, etc.).
+ * `break-all` + `min-w-0` so unbroken hex wraps at the wallet design width
+ * instead of forcing horizontal scroll inside modals.
+ */
+ .wallet-detail-block {
+ @apply border-border bg-muted/40 m-0 max-h-40 max-w-full min-w-0 overflow-x-hidden overflow-y-auto break-all whitespace-pre-wrap rounded-md border p-3 font-mono text-[0.85rem];
+ }
+}
+
+/* Nested Signing Layer iframe host — visually hidden, not display:none (WebAuthn). */
+#signer-container {
+ position: absolute;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+ clip-path: inset(50%);
+}
diff --git a/src/lib/clipboard.ts b/src/lib/clipboard.ts
new file mode 100644
index 0000000..b3b52b9
--- /dev/null
+++ b/src/lib/clipboard.ts
@@ -0,0 +1,40 @@
+/**
+ * Copy text to the clipboard with a textarea fallback for cross-origin iframes
+ * where `navigator.clipboard` is missing or Permissions Policy blocks it.
+ */
+export async function copyText(text: string): Promise {
+ try {
+ if (navigator.clipboard?.writeText) {
+ await navigator.clipboard.writeText(text);
+ return true;
+ }
+ } catch {
+ // fall through to execCommand
+ }
+
+ try {
+ const textarea = document.createElement("textarea");
+ textarea.value = text;
+ textarea.setAttribute("readonly", "");
+ textarea.style.position = "fixed";
+ textarea.style.top = "0";
+ textarea.style.left = "0";
+ textarea.style.width = "1px";
+ textarea.style.height = "1px";
+ textarea.style.padding = "0";
+ textarea.style.border = "none";
+ textarea.style.outline = "none";
+ textarea.style.boxShadow = "none";
+ textarea.style.background = "transparent";
+ textarea.style.opacity = "0";
+ document.body.appendChild(textarea);
+ textarea.focus();
+ textarea.select();
+ textarea.setSelectionRange(0, text.length);
+ const ok = document.execCommand("copy");
+ document.body.removeChild(textarea);
+ return ok;
+ } catch {
+ return false;
+ }
+}
diff --git a/src/lib/implementations/data/HardcodedKnownAssetRepository.ts b/src/lib/implementations/data/HardcodedKnownAssetRepository.ts
new file mode 100644
index 0000000..5e036d2
--- /dev/null
+++ b/src/lib/implementations/data/HardcodedKnownAssetRepository.ts
@@ -0,0 +1,156 @@
+import { erc20Abi } from "viem";
+import {
+ EVMAccountAddress,
+ EVMChainId,
+ type EVMAccountAddress as EVMAccountAddressType,
+ type EVMChainId as EVMChainIdType,
+} from "@1shotapi/ows-types";
+import type { IBlockchainProvider } from "@1shotapi/ows-wallet-utils";
+import type { IKnownAssetRepository } from "../../interfaces/data/IKnownAssetRepository";
+import {
+ KnownAsset,
+ NewTrackedAsset,
+} from "../../types/business";
+import { EAssetType } from "../../types/enum";
+import { makeTrackedAssetId } from "@/lib/types/primitives";
+
+/** Seeded known ERC-20s for demos (USDC on every DEMO_CHAINS network + Base USDT). */
+const SEEDED_KNOWN: readonly KnownAsset[] = [
+ new KnownAsset(
+ EVMChainId("0x4cef52"),
+ EVMAccountAddress("0x3600000000000000000000000000000000000000"),
+ EAssetType.Erc20,
+ "USDC",
+ "USDC",
+ 6,
+ ),
+ new KnownAsset(
+ EVMChainId("0xaa36a7"),
+ EVMAccountAddress("0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"),
+ EAssetType.Erc20,
+ "USDC",
+ "USDC",
+ 6,
+ ),
+ new KnownAsset(
+ EVMChainId("0x14a34"),
+ EVMAccountAddress("0x036CbD53842c5426634e7929541eC2318f3dCF7e"),
+ EAssetType.Erc20,
+ "USDC",
+ "USDC",
+ 6,
+ ),
+ new KnownAsset(
+ EVMChainId("0x2105"),
+ EVMAccountAddress("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"),
+ EAssetType.Erc20,
+ "USDC",
+ "USDC",
+ 6,
+ ),
+ new KnownAsset(
+ EVMChainId("0x2105"),
+ EVMAccountAddress("0xfde4c96c8593536e31f229ea8f37b2ada2699bb2"),
+ EAssetType.Erc20,
+ "USDT",
+ "USDT",
+ 6,
+ ),
+];
+
+const BY_KEY = new Map(
+ SEEDED_KNOWN.map((asset) => [
+ makeTrackedAssetId(asset.chainId, asset.address),
+ asset,
+ ]),
+);
+
+/**
+ * USDC on every supported demo chain — always shown in Balances (not removable).
+ */
+export const DEFAULT_TRACKED_USDC: readonly NewTrackedAsset[] =
+ SEEDED_KNOWN.filter((asset) => asset.symbol === "USDC").map((asset) =>
+ NewTrackedAsset.fromKnown(asset),
+ );
+
+const DEFAULT_TRACKED_USDC_KEYS = new Set(
+ DEFAULT_TRACKED_USDC.map((asset) =>
+ makeTrackedAssetId(asset.chainId, asset.address),
+ ),
+);
+
+export function isDefaultTrackedUsdc(
+ chainId: EVMChainIdType,
+ address: EVMAccountAddressType,
+): boolean {
+ return DEFAULT_TRACKED_USDC_KEYS.has(makeTrackedAssetId(chainId, address));
+}
+
+export class HardcodedKnownAssetRepository implements IKnownAssetRepository {
+ constructor(private readonly blockchain: IBlockchainProvider) {}
+
+ async getKnownAsset(
+ chainId: EVMChainIdType,
+ address: EVMAccountAddressType,
+ ): Promise {
+ return BY_KEY.get(makeTrackedAssetId(chainId, address)) ?? null;
+ }
+
+ async resolveForTracking(
+ chainId: EVMChainIdType,
+ address: EVMAccountAddressType,
+ owner: EVMAccountAddressType,
+ ): Promise {
+ const known = await this.getKnownAsset(chainId, address);
+ if (known?.type === EAssetType.Erc20) {
+ return NewTrackedAsset.fromKnown(known);
+ }
+
+ const client = this.blockchain.getPublicClient(chainId);
+ const code = await client.getCode({ address: address });
+ if (!code || code === "0x") {
+ throw new Error("Address is not a contract");
+ }
+
+ try {
+ const [name, symbol, decimals] = await Promise.all([
+ client.readContract({
+ address: address,
+ abi: erc20Abi,
+ functionName: "name",
+ }),
+ client.readContract({
+ address: address,
+ abi: erc20Abi,
+ functionName: "symbol",
+ }),
+ client.readContract({
+ address: address,
+ abi: erc20Abi,
+ functionName: "decimals",
+ }),
+ ]);
+
+ // Validate ERC-20 with a balanceOf probe (reverts → not ERC-20).
+ await client.readContract({
+ address: address,
+ abi: erc20Abi,
+ functionName: "balanceOf",
+ args: [owner],
+ });
+
+ return new NewTrackedAsset(
+ chainId,
+ address,
+ EAssetType.Erc20,
+ name,
+ symbol,
+ decimals,
+ );
+ } catch (error: unknown) {
+ const message =
+ error instanceof Error ? error.message : "ERC-20 probe failed";
+ throw new Error(`Not an ERC-20 token: ${message}`);
+ }
+ }
+}
diff --git a/src/lib/implementations/data/LocalStorageTrackedAssetRepository.ts b/src/lib/implementations/data/LocalStorageTrackedAssetRepository.ts
new file mode 100644
index 0000000..4da892e
--- /dev/null
+++ b/src/lib/implementations/data/LocalStorageTrackedAssetRepository.ts
@@ -0,0 +1,265 @@
+import { erc20Abi, type Address } from "viem";
+import {
+ EVMAccountAddress,
+ EVMChainId,
+ type EVMAccountAddress as EVMAccountAddressType,
+ type EVMChainId as EVMChainIdType,
+} from "@1shotapi/ows-types";
+import type { IBlockchainProvider } from "@1shotapi/ows-wallet-utils";
+import {
+ createMemoryStorageBackend,
+ type CredentialStorageBackend,
+} from "../../../demo/local-storage-store";
+import type { IEventBus } from "../../interfaces/utils/IEventBus";
+import type { ITrackedAssetRepository } from "../../interfaces/data/ITrackedAssetRepository";
+import {
+ DEFAULT_TRACKED_USDC,
+ isDefaultTrackedUsdc,
+} from "./HardcodedKnownAssetRepository";
+import { NewTrackedAsset, TrackedAsset } from "../../types/business";
+import { EAssetType } from "../../types/enum";
+import { BalanceUpdatedEvent } from "../../types/events";
+import {
+ makeTrackedAssetId,
+ type TrackedAssetId,
+} from "../../types/primitives";
+
+/** localStorage key for user-tracked assets (defaults like USDC are merged in). */
+export const OWS_TRACKED_ASSETS_STORAGE_KEY = "ows.tracked-assets.v2";
+
+type StoredBlob = {
+ assets: Array<{
+ chainId: string;
+ address: string;
+ type: string;
+ name: string;
+ symbol: string;
+ decimals: number;
+ id: string;
+ }>;
+};
+
+export type TrackedAssetRepositoryOptions = {
+ storageKey?: string;
+ storage?: CredentialStorageBackend;
+};
+
+const EMPTY_OWNER = EVMAccountAddress("0x0");
+
+export class LocalStorageTrackedAssetRepository
+ implements ITrackedAssetRepository
+{
+ private readonly storageKey: string;
+ private readonly storage: CredentialStorageBackend;
+ private readonly balanceCache = new Map();
+
+ constructor(
+ private readonly blockchain: IBlockchainProvider,
+ private readonly eventBus: IEventBus,
+ options: TrackedAssetRepositoryOptions = {},
+ ) {
+ this.storageKey = options.storageKey ?? OWS_TRACKED_ASSETS_STORAGE_KEY;
+ this.storage =
+ options.storage ??
+ (typeof localStorage !== "undefined"
+ ? localStorage
+ : createMemoryStorageBackend());
+ }
+
+ async list(owner: EVMAccountAddressType): Promise {
+ const assets = this.mergeWithDefaults(this.readStoredAssets());
+ return this.ensureBalances(assets, owner, false);
+ }
+
+ async has(
+ chainId: EVMChainIdType,
+ address: EVMAccountAddressType,
+ ): Promise {
+ if (isDefaultTrackedUsdc(chainId, address)) {
+ return true;
+ }
+ const key = makeTrackedAssetId(chainId, address);
+ return this.readStoredAssets().some((asset) => asset.id === key);
+ }
+
+ async add(
+ asset: NewTrackedAsset,
+ owner: EVMAccountAddressType,
+ ): Promise {
+ if (isDefaultTrackedUsdc(asset.chainId, asset.address)) {
+ const existing = TrackedAsset.fromNew(asset);
+ const [withBalance] = await this.ensureBalances([existing], owner, false);
+ return withBalance!;
+ }
+
+ const assets = this.readStoredAssets();
+ const key = makeTrackedAssetId(asset.chainId, asset.address);
+ const found = assets.find((a) => a.id === key);
+ if (found) {
+ const [withBalance] = await this.ensureBalances([found], owner, false);
+ return withBalance!;
+ }
+
+ const tracked = TrackedAsset.fromNew(asset);
+ assets.push(tracked);
+ this.writeAssets(assets);
+ const [withBalance] = await this.ensureBalances([tracked], owner, false);
+ return withBalance!;
+ }
+
+ async remove(
+ chainId: EVMChainIdType,
+ address: EVMAccountAddressType,
+ ): Promise {
+ if (isDefaultTrackedUsdc(chainId, address)) {
+ return;
+ }
+ const key = makeTrackedAssetId(chainId, address);
+ const next = this.readStoredAssets().filter((asset) => asset.id !== key);
+ this.writeAssets(next);
+ this.balanceCache.delete(key);
+ }
+
+ async getBalances(
+ owner: EVMAccountAddressType,
+ id?: TrackedAssetId,
+ ): Promise {
+ if (id) {
+ this.balanceCache.delete(id);
+ } else {
+ this.balanceCache.clear();
+ }
+ const assets = this.mergeWithDefaults(this.readStoredAssets());
+ const targets = id ? assets.filter((asset) => asset.id === id) : assets;
+ return this.ensureBalances(targets, owner, true);
+ }
+
+ private mergeWithDefaults(stored: TrackedAsset[]): TrackedAsset[] {
+ const seen = new Set();
+ const merged: TrackedAsset[] = [];
+ for (const asset of DEFAULT_TRACKED_USDC) {
+ const key = makeTrackedAssetId(asset.chainId, asset.address);
+ seen.add(key);
+ merged.push(TrackedAsset.fromNew(asset));
+ }
+ for (const asset of stored) {
+ if (seen.has(asset.id)) continue;
+ seen.add(asset.id);
+ merged.push(asset);
+ }
+ return merged;
+ }
+
+ private async ensureBalances(
+ assets: TrackedAsset[],
+ owner: EVMAccountAddressType,
+ forceEmit: boolean,
+ ): Promise {
+ let anyFetched = forceEmit;
+ const result: TrackedAsset[] = [];
+
+ for (const asset of assets) {
+ if (this.balanceCache.has(asset.id)) {
+ result.push(asset.withBalance(this.balanceCache.get(asset.id)!));
+ continue;
+ }
+
+ anyFetched = true;
+ const balance = await this.fetchBalance(asset, owner);
+ if (balance !== null) {
+ this.balanceCache.set(asset.id, balance);
+ }
+ result.push(asset.withBalance(balance));
+ }
+
+ if (anyFetched && result.length > 0) {
+ this.eventBus.emit(new BalanceUpdatedEvent(result));
+ }
+ return result;
+ }
+
+ private async fetchBalance(
+ asset: TrackedAsset,
+ owner: EVMAccountAddressType,
+ ): Promise {
+ if (asset.type !== EAssetType.Erc20) {
+ return null;
+ }
+ if (owner === EMPTY_OWNER) {
+ return null;
+ }
+ try {
+ const client = this.blockchain.getPublicClient(asset.chainId);
+ return await client.readContract({
+ address: asset.address as Address,
+ abi: erc20Abi,
+ functionName: "balanceOf",
+ args: [owner as Address],
+ });
+ } catch (error: unknown) {
+ console.warn("[balances] balanceOf failed", error);
+ return null;
+ }
+ }
+
+ private readStoredAssets(): TrackedAsset[] {
+ const raw = this.storage.getItem(this.storageKey);
+ if (!raw) return [];
+ try {
+ const parsed = JSON.parse(raw) as StoredBlob;
+ if (!parsed || !Array.isArray(parsed.assets)) return [];
+ return parsed.assets
+ .filter(
+ (row) =>
+ typeof row?.chainId === "string" &&
+ typeof row?.address === "string" &&
+ typeof row?.name === "string" &&
+ typeof row?.symbol === "string" &&
+ typeof row?.decimals === "number" &&
+ /^0x[0-9a-fA-F]+$/.test(row.chainId) &&
+ /^0x[0-9a-fA-F]{40}$/.test(row.address),
+ )
+ .map((row) => {
+ const chainId = EVMChainId(row.chainId as `0x${string}`);
+ const address = EVMAccountAddress(row.address as `0x${string}`);
+ const type =
+ row.type === EAssetType.Erc721
+ ? EAssetType.Erc721
+ : row.type === EAssetType.Erc1155
+ ? EAssetType.Erc1155
+ : EAssetType.Erc20;
+ return new TrackedAsset(
+ chainId,
+ address,
+ type,
+ row.name,
+ row.symbol,
+ row.decimals,
+ makeTrackedAssetId(chainId, address),
+ null,
+ );
+ });
+ } catch {
+ return [];
+ }
+ }
+
+ private writeAssets(assets: TrackedAsset[]): void {
+ // Persist only user-added (non-default) rows as NewTrackedAsset fields + id.
+ const userAssets = assets.filter(
+ (asset) => !isDefaultTrackedUsdc(asset.chainId, asset.address),
+ );
+ const blob: StoredBlob = {
+ assets: userAssets.map((asset) => ({
+ chainId: asset.chainId,
+ address: asset.address,
+ type: asset.type,
+ name: asset.name,
+ symbol: asset.symbol,
+ decimals: asset.decimals,
+ id: asset.id,
+ })),
+ };
+ this.storage.setItem(this.storageKey, JSON.stringify(blob));
+ }
+}
diff --git a/src/lib/implementations/data/OneshotRelayerRepository.ts b/src/lib/implementations/data/OneshotRelayerRepository.ts
new file mode 100644
index 0000000..0278ed8
--- /dev/null
+++ b/src/lib/implementations/data/OneshotRelayerRepository.ts
@@ -0,0 +1,89 @@
+import {
+ prepareEvmTransaction,
+ type OWSSigner,
+ type SignHelperChainRpc,
+} from "@1shotapi/ows-signer-utils";
+import type { IBlockchainProvider } from "@1shotapi/ows-wallet-utils";
+import {
+ EVMTransactionHash,
+ HexString,
+ OwsInvalidParamsError,
+ RelayerTransactionId,
+ type EVMAccountAddress,
+ type EVMChainId,
+ type UriString,
+} from "@1shotapi/ows-types";
+import type {
+ IOneshotRelayerRepository,
+ ISendTransactionResult,
+} from "../../interfaces/data/IOneshotRelayerRepository";
+
+const ZERO_VALUE = HexString("0x0");
+const EMPTY_DATA = HexString("0x");
+
+export type OneshotRelayerRepositoryOptions = {
+ blockchain: IBlockchainProvider;
+ getSigner: () => OWSSigner;
+ getChainRpc: () => SignHelperChainRpc;
+};
+
+/**
+ * Interim submit path: prepare → passkey sign → eth_sendRawTransaction.
+ * Returns a placeholder relayer id until the public relayer is wired.
+ */
+export class OneshotRelayerRepository implements IOneshotRelayerRepository {
+ constructor(private readonly options: OneshotRelayerRepositoryOptions) {}
+
+ async sendTransaction(
+ chainId: EVMChainId,
+ contractAddress: EVMAccountAddress,
+ transactionData: HexString,
+ value?: bigint,
+ _options?: { webhookDestination?: UriString },
+ ): Promise {
+ void _options;
+ const signer = this.options.getSigner();
+ const chainRpc = this.options.getChainRpc();
+ const active = chainRpc.getChainId();
+ if (active !== chainId) {
+ throw new OwsInvalidParamsError(
+ `sendTransaction chainId ${chainId} does not match active chain ${active}`,
+ );
+ }
+
+ const from =
+ signer.getCachedAddress?.() ?? (await signer.evm.getAccountAddress());
+ const valueHex =
+ value === undefined || value === 0n
+ ? ZERO_VALUE
+ : HexString(`0x${value.toString(16)}` as `0x${string}`);
+ const data = transactionData || EMPTY_DATA;
+
+ const prepared = await prepareEvmTransaction(chainRpc, from, {
+ from,
+ to: contractAddress,
+ data,
+ value: valueHex,
+ chainId,
+ });
+ const signed = await signer.evm.signTransaction(prepared);
+
+ const client = this.options.blockchain.getPublicClient(chainId);
+ const hash = await client.request({
+ method: "eth_sendRawTransaction",
+ params: [signed],
+ });
+ if (typeof hash !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(hash)) {
+ throw new OwsInvalidParamsError(
+ "eth_sendRawTransaction returned an invalid transaction hash",
+ );
+ }
+
+ return {
+ relayerTransactionId: RelayerTransactionId(
+ `interim-${hash.slice(2, 18)}`,
+ ),
+ transactionHash: EVMTransactionHash(hash as `0x${string}`),
+ };
+ }
+}
diff --git a/src/lib/implementations/data/index.ts b/src/lib/implementations/data/index.ts
new file mode 100644
index 0000000..fd4cd11
--- /dev/null
+++ b/src/lib/implementations/data/index.ts
@@ -0,0 +1,12 @@
+export {
+ DEFAULT_TRACKED_USDC,
+ HardcodedKnownAssetRepository,
+ isDefaultTrackedUsdc,
+} from "./HardcodedKnownAssetRepository";
+export {
+ LocalStorageTrackedAssetRepository,
+ OWS_TRACKED_ASSETS_STORAGE_KEY,
+} from "./LocalStorageTrackedAssetRepository";
+export type { TrackedAssetRepositoryOptions } from "./LocalStorageTrackedAssetRepository";
+export { OneshotRelayerRepository } from "./OneshotRelayerRepository";
+export type { OneshotRelayerRepositoryOptions } from "./OneshotRelayerRepository";
diff --git a/src/lib/implementations/utils/DemoChainsBlockchainProvider.ts b/src/lib/implementations/utils/DemoChainsBlockchainProvider.ts
new file mode 100644
index 0000000..ce397f6
--- /dev/null
+++ b/src/lib/implementations/utils/DemoChainsBlockchainProvider.ts
@@ -0,0 +1,25 @@
+import { createPublicClient, http, type PublicClient } from "viem";
+import type { EVMChainId } from "@1shotapi/ows-types";
+import type { IBlockchainProvider } from "@1shotapi/ows-wallet-utils";
+import { DEMO_CHAINS } from "../../../ows/demoChains";
+
+/** Caches viem public clients keyed by demo-chain id. */
+export class DemoChainsBlockchainProvider implements IBlockchainProvider {
+ private readonly clients = new Map();
+
+ getPublicClient(chainId: EVMChainId): PublicClient {
+ const cached = this.clients.get(chainId);
+ if (cached) return cached;
+
+ const chain = DEMO_CHAINS.find((entry) => entry.chainId === chainId);
+ if (!chain) {
+ throw new Error(`Unsupported chain for RPC: ${chainId}`);
+ }
+
+ const client = createPublicClient({
+ transport: http(chain.rpcUrl),
+ });
+ this.clients.set(chainId, client);
+ return client;
+ }
+}
diff --git a/src/lib/implementations/utils/EventBus.ts b/src/lib/implementations/utils/EventBus.ts
new file mode 100644
index 0000000..8529999
--- /dev/null
+++ b/src/lib/implementations/utils/EventBus.ts
@@ -0,0 +1,63 @@
+import type { IEventBus } from "../../interfaces/utils/IEventBus";
+import { EWalletEventKind } from "../../types/enum";
+import type {
+ BalanceUpdatedEvent,
+ RefreshBalanceRequestedEvent,
+ WalletDomainEvent,
+} from "../../types/events";
+
+type Listener = (event: WalletDomainEvent) => void;
+
+/** In-process typed pub/sub for wallet domain events. */
+export class EventBus implements IEventBus {
+ private readonly listeners = new Map>();
+
+ emit(event: WalletDomainEvent): void {
+ const handlers = this.listeners.get(event.kind);
+ if (!handlers || handlers.size === 0) return;
+
+ for (const handler of [...handlers]) {
+ try {
+ handler(event);
+ } catch (error: unknown) {
+ console.error("[event-bus] listener failed", error);
+ }
+ }
+ }
+
+ onBalanceUpdated(
+ handler: (event: BalanceUpdatedEvent) => void,
+ ): () => void {
+ return this.addListener(
+ EWalletEventKind.BalanceUpdated,
+ handler as Listener,
+ );
+ }
+
+ onRefreshBalanceRequested(
+ handler: (event: RefreshBalanceRequestedEvent) => void,
+ ): () => void {
+ return this.addListener(
+ EWalletEventKind.RefreshBalanceRequested,
+ handler as Listener,
+ );
+ }
+
+ private addListener(
+ kind: EWalletEventKind,
+ handler: Listener,
+ ): () => void {
+ let handlers = this.listeners.get(kind);
+ if (!handlers) {
+ handlers = new Set();
+ this.listeners.set(kind, handlers);
+ }
+ handlers.add(handler);
+ return () => {
+ handlers!.delete(handler);
+ if (handlers!.size === 0) {
+ this.listeners.delete(kind);
+ }
+ };
+ }
+}
diff --git a/src/lib/implementations/utils/TransactionUtils.ts b/src/lib/implementations/utils/TransactionUtils.ts
new file mode 100644
index 0000000..006f734
--- /dev/null
+++ b/src/lib/implementations/utils/TransactionUtils.ts
@@ -0,0 +1,90 @@
+import {
+ decodeFunctionData,
+ erc20Abi,
+ formatUnits,
+ getAddress,
+ type Hex,
+} from "viem";
+import {
+ EVMAccountAddress,
+ type EVMChainId,
+ type HexString,
+} from "@1shotapi/ows-types";
+import type {
+ IDecodedErc20Transfer,
+ ITransactionUtils,
+} from "../../interfaces/utils/ITransactionUtils";
+
+/** EVM transfer helpers (decode, amount formatting, host/chain labels). */
+export class TransactionUtils implements ITransactionUtils {
+ tryDecodeErc20Transfer(
+ to: EVMAccountAddress | null,
+ data: HexString,
+ ): IDecodedErc20Transfer | null {
+ if (!to || !data || String(data) === "0x" || data.length < 10) {
+ return null;
+ }
+ try {
+ const decoded = decodeFunctionData({
+ abi: erc20Abi,
+ data: data as Hex,
+ });
+ if (decoded.functionName !== "transfer") {
+ return null;
+ }
+ const [recipient, amount] = decoded.args as readonly [
+ `0x${string}`,
+ bigint,
+ ];
+ return {
+ tokenAddress: to,
+ recipient: EVMAccountAddress(getAddress(recipient)),
+ amount,
+ };
+ } catch {
+ return null;
+ }
+ }
+
+ formatTokenAmount(
+ amount: bigint,
+ decimals: number | null | undefined,
+ ): string {
+ if (decimals === null || decimals === undefined) {
+ return amount.toString();
+ }
+ try {
+ return formatUnits(amount, decimals);
+ } catch {
+ return amount.toString();
+ }
+ }
+
+ resolveHostDomain(): string {
+ try {
+ const ancestorOrigins = (
+ location as Location & { ancestorOrigins?: DOMStringList }
+ ).ancestorOrigins;
+ if (ancestorOrigins && ancestorOrigins.length > 0) {
+ return new URL(ancestorOrigins[0]!).hostname;
+ }
+ } catch {
+ // fall through
+ }
+ try {
+ if (document.referrer) {
+ return new URL(document.referrer).hostname;
+ }
+ } catch {
+ // fall through
+ }
+ return "the connected app";
+ }
+
+ chainLabelFor(
+ chainId: EVMChainId,
+ chains: ReadonlyArray<{ chainId: EVMChainId; label: string }>,
+ ): string {
+ return chains.find((chain) => chain.chainId === chainId)?.label ?? chainId;
+ }
+}
diff --git a/src/lib/implementations/utils/index.ts b/src/lib/implementations/utils/index.ts
new file mode 100644
index 0000000..26f8c85
--- /dev/null
+++ b/src/lib/implementations/utils/index.ts
@@ -0,0 +1,3 @@
+export { DemoChainsBlockchainProvider } from "./DemoChainsBlockchainProvider";
+export { EventBus } from "./EventBus";
+export { TransactionUtils } from "./TransactionUtils";
diff --git a/src/lib/interfaces/data/IKnownAssetRepository.ts b/src/lib/interfaces/data/IKnownAssetRepository.ts
new file mode 100644
index 0000000..fe71dde
--- /dev/null
+++ b/src/lib/interfaces/data/IKnownAssetRepository.ts
@@ -0,0 +1,21 @@
+import type { EVMAccountAddress, EVMChainId } from "@1shotapi/ows-types";
+import type { KnownAsset, NewTrackedAsset } from "../../types/business";
+
+export interface IKnownAssetRepository {
+ getKnownAsset(
+ chainId: EVMChainId,
+ address: EVMAccountAddress,
+ ): Promise;
+
+ /**
+ * Catalog hit or on-chain ERC-20 probe → NewTrackedAsset.
+ * Throws if the address is not a contract or not ERC-20.
+ */
+ resolveForTracking(
+ chainId: EVMChainId,
+ address: EVMAccountAddress,
+ owner: EVMAccountAddress,
+ ): Promise;
+}
+
+export const IKnownAssetRepositoryType = Symbol.for("IKnownAssetRepository");
diff --git a/src/lib/interfaces/data/IOneshotRelayerRepository.ts b/src/lib/interfaces/data/IOneshotRelayerRepository.ts
new file mode 100644
index 0000000..039f757
--- /dev/null
+++ b/src/lib/interfaces/data/IOneshotRelayerRepository.ts
@@ -0,0 +1,31 @@
+import type {
+ EVMAccountAddress,
+ EVMChainId,
+ EVMTransactionHash,
+ HexString,
+ RelayerTransactionId,
+ UriString,
+} from "@1shotapi/ows-types";
+
+export interface ISendTransactionResult {
+ relayerTransactionId: RelayerTransactionId;
+ transactionHash: EVMTransactionHash;
+}
+
+/**
+ * Submits an EVM transaction (prepare + sign + broadcast).
+ * Interim: eth_sendRawTransaction via public RPC; later: 1Shot relayer.
+ */
+export interface IOneshotRelayerRepository {
+ sendTransaction(
+ chainId: EVMChainId,
+ contractAddress: EVMAccountAddress,
+ transactionData: HexString,
+ value?: bigint,
+ options?: { webhookDestination?: UriString },
+ ): Promise;
+}
+
+export const IOneshotRelayerRepositoryType = Symbol.for(
+ "IOneshotRelayerRepository",
+);
diff --git a/src/lib/interfaces/data/ITrackedAssetRepository.ts b/src/lib/interfaces/data/ITrackedAssetRepository.ts
new file mode 100644
index 0000000..534bb85
--- /dev/null
+++ b/src/lib/interfaces/data/ITrackedAssetRepository.ts
@@ -0,0 +1,22 @@
+import type { EVMAccountAddress, EVMChainId } from "@1shotapi/ows-types";
+import type { TrackedAssetId } from "../../types/primitives";
+import type { NewTrackedAsset, TrackedAsset } from "../../types/business";
+
+export interface ITrackedAssetRepository {
+ list(owner: EVMAccountAddress): Promise;
+ has(chainId: EVMChainId, address: EVMAccountAddress): Promise;
+ add(
+ asset: NewTrackedAsset,
+ owner: EVMAccountAddress,
+ ): Promise;
+ remove(chainId: EVMChainId, address: EVMAccountAddress): Promise;
+ /** Clears session balance cache (one id or all) and re-fetches. */
+ getBalances(
+ owner: EVMAccountAddress,
+ id?: TrackedAssetId,
+ ): Promise;
+}
+
+export const ITrackedAssetRepositoryType = Symbol.for(
+ "ITrackedAssetRepository",
+);
diff --git a/src/lib/interfaces/data/index.ts b/src/lib/interfaces/data/index.ts
new file mode 100644
index 0000000..1a99629
--- /dev/null
+++ b/src/lib/interfaces/data/index.ts
@@ -0,0 +1,9 @@
+export type { IKnownAssetRepository } from "./IKnownAssetRepository";
+export { IKnownAssetRepositoryType } from "./IKnownAssetRepository";
+export type { ITrackedAssetRepository } from "./ITrackedAssetRepository";
+export { ITrackedAssetRepositoryType } from "./ITrackedAssetRepository";
+export type {
+ IOneshotRelayerRepository,
+ ISendTransactionResult,
+} from "./IOneshotRelayerRepository";
+export { IOneshotRelayerRepositoryType } from "./IOneshotRelayerRepository";
diff --git a/src/lib/interfaces/utils/IEventBus.ts b/src/lib/interfaces/utils/IEventBus.ts
new file mode 100644
index 0000000..ba6573f
--- /dev/null
+++ b/src/lib/interfaces/utils/IEventBus.ts
@@ -0,0 +1,19 @@
+import type {
+ BalanceUpdatedEvent,
+ RefreshBalanceRequestedEvent,
+ WalletDomainEvent,
+} from "../../types/events";
+
+export interface IEventBus {
+ emit(event: WalletDomainEvent): void;
+
+ onBalanceUpdated(
+ handler: (event: BalanceUpdatedEvent) => void,
+ ): () => void;
+
+ onRefreshBalanceRequested(
+ handler: (event: RefreshBalanceRequestedEvent) => void,
+ ): () => void;
+}
+
+export const IEventBusType = Symbol.for("IEventBus");
diff --git a/src/lib/interfaces/utils/ITransactionUtils.ts b/src/lib/interfaces/utils/ITransactionUtils.ts
new file mode 100644
index 0000000..cbc03dd
--- /dev/null
+++ b/src/lib/interfaces/utils/ITransactionUtils.ts
@@ -0,0 +1,34 @@
+import type {
+ EVMAccountAddress,
+ EVMChainId,
+ HexString,
+} from "@1shotapi/ows-types";
+
+export interface IDecodedErc20Transfer {
+ tokenAddress: EVMAccountAddress;
+ recipient: EVMAccountAddress;
+ amount: bigint;
+}
+
+export interface ITransactionUtils {
+ /** Decode ERC-20 `transfer(address,uint256)` when calldata matches. */
+ tryDecodeErc20Transfer(
+ to: EVMAccountAddress | null,
+ data: HexString,
+ ): IDecodedErc20Transfer | null;
+
+ formatTokenAmount(
+ amount: bigint,
+ decimals: number | null | undefined,
+ ): string;
+
+ /** Best-effort host domain for consent copy (`ancestorOrigins` / referrer). */
+ resolveHostDomain(): string;
+
+ chainLabelFor(
+ chainId: EVMChainId,
+ chains: ReadonlyArray<{ chainId: EVMChainId; label: string }>,
+ ): string;
+}
+
+export const ITransactionUtilsType = Symbol.for("ITransactionUtils");
diff --git a/src/lib/interfaces/utils/index.ts b/src/lib/interfaces/utils/index.ts
new file mode 100644
index 0000000..a30c9f1
--- /dev/null
+++ b/src/lib/interfaces/utils/index.ts
@@ -0,0 +1,7 @@
+export type { IEventBus } from "./IEventBus";
+export { IEventBusType } from "./IEventBus";
+export type {
+ IDecodedErc20Transfer,
+ ITransactionUtils,
+} from "./ITransactionUtils";
+export { ITransactionUtilsType } from "./ITransactionUtils";
diff --git a/src/lib/types/business/KnownAsset.ts b/src/lib/types/business/KnownAsset.ts
new file mode 100644
index 0000000..a4778aa
--- /dev/null
+++ b/src/lib/types/business/KnownAsset.ts
@@ -0,0 +1,15 @@
+import type { EVMAccountAddress, EVMChainId } from "@1shotapi/ows-types";
+import type { EAssetType } from "../enum/EAssetType";
+
+/** Catalog metadata for a known token (hardcoded registry). */
+export class KnownAsset {
+ constructor(
+ public readonly chainId: EVMChainId,
+ public readonly address: EVMAccountAddress,
+ public readonly type: EAssetType,
+ public readonly name: string,
+ public readonly symbol: string,
+ public readonly decimals: number,
+ public readonly iconUrl?: string,
+ ) {}
+}
diff --git a/src/lib/types/business/TrackedAsset.ts b/src/lib/types/business/TrackedAsset.ts
new file mode 100644
index 0000000..c9b7936
--- /dev/null
+++ b/src/lib/types/business/TrackedAsset.ts
@@ -0,0 +1,79 @@
+import type { EVMAccountAddress, EVMChainId } from "@1shotapi/ows-types";
+import {
+ makeTrackedAssetId,
+ type TrackedAssetId,
+} from "../primitives/TrackedAssetId";
+import type { EAssetType } from "../enum/EAssetType";
+import type { KnownAsset } from "./KnownAsset";
+
+/**
+ * Persistable tracked-asset DTO (no session id/balance).
+ * Constructed from the known catalog or an on-chain ERC-20 probe.
+ */
+export class NewTrackedAsset {
+ constructor(
+ public readonly chainId: EVMChainId,
+ public readonly address: EVMAccountAddress,
+ public readonly type: EAssetType,
+ public readonly name: string,
+ public readonly symbol: string,
+ public readonly decimals: number,
+ ) {}
+
+ static fromKnown(known: KnownAsset): NewTrackedAsset {
+ return new NewTrackedAsset(
+ known.chainId,
+ known.address,
+ known.type,
+ known.name,
+ known.symbol,
+ known.decimals,
+ );
+ }
+}
+
+
+/** Session-facing tracked asset with id and optional raw balance. */
+export class TrackedAsset extends NewTrackedAsset {
+ constructor(
+ chainId: EVMChainId,
+ address: EVMAccountAddress,
+ type: EAssetType,
+ name: string,
+ symbol: string,
+ decimals: number,
+ public readonly id: TrackedAssetId,
+ public balance: bigint | null,
+ ) {
+ super(chainId, address, type, name, symbol, decimals);
+ }
+
+ static fromNew(
+ asset: NewTrackedAsset,
+ balance: bigint | null = null,
+ ): TrackedAsset {
+ return new TrackedAsset(
+ asset.chainId,
+ asset.address,
+ asset.type,
+ asset.name,
+ asset.symbol,
+ asset.decimals,
+ makeTrackedAssetId(asset.chainId, asset.address),
+ balance,
+ );
+ }
+
+ withBalance(balance: bigint | null): TrackedAsset {
+ return new TrackedAsset(
+ this.chainId,
+ this.address,
+ this.type,
+ this.name,
+ this.symbol,
+ this.decimals,
+ this.id,
+ balance,
+ );
+ }
+}
diff --git a/src/lib/types/business/index.ts b/src/lib/types/business/index.ts
new file mode 100644
index 0000000..b3dafa5
--- /dev/null
+++ b/src/lib/types/business/index.ts
@@ -0,0 +1,2 @@
+export { KnownAsset } from "./KnownAsset";
+export { NewTrackedAsset, TrackedAsset } from "./TrackedAsset";
diff --git a/src/lib/types/enum/EAssetType.ts b/src/lib/types/enum/EAssetType.ts
new file mode 100644
index 0000000..0950def
--- /dev/null
+++ b/src/lib/types/enum/EAssetType.ts
@@ -0,0 +1,6 @@
+/** On-chain asset standard. Only ERC-20 balances are fetched today. */
+export enum EAssetType {
+ Erc20 = "erc20",
+ Erc721 = "erc721",
+ Erc1155 = "erc1155",
+}
diff --git a/src/lib/types/enum/EPasskeyPromptReason.ts b/src/lib/types/enum/EPasskeyPromptReason.ts
new file mode 100644
index 0000000..4f61382
--- /dev/null
+++ b/src/lib/types/enum/EPasskeyPromptReason.ts
@@ -0,0 +1,10 @@
+/** Why the wallet is requesting a WebAuthn / passkey ceremony. */
+export enum EPasskeyPromptReason {
+ Unlock = "unlock",
+ Create = "create",
+ Sign = "sign",
+ Encrypt = "encrypt",
+ Decrypt = "decrypt",
+ RelayerAuth = "relayerAuth",
+ Backup = "backup",
+}
diff --git a/src/lib/types/enum/EWalletEventKind.ts b/src/lib/types/enum/EWalletEventKind.ts
new file mode 100644
index 0000000..169ce2f
--- /dev/null
+++ b/src/lib/types/enum/EWalletEventKind.ts
@@ -0,0 +1,5 @@
+/** Domain event kinds for the wallet event bus. */
+export enum EWalletEventKind {
+ RefreshBalanceRequested = "refreshBalanceRequested",
+ BalanceUpdated = "balanceUpdated",
+}
diff --git a/src/lib/types/enum/index.ts b/src/lib/types/enum/index.ts
new file mode 100644
index 0000000..918128e
--- /dev/null
+++ b/src/lib/types/enum/index.ts
@@ -0,0 +1,3 @@
+export { EAssetType } from "./EAssetType";
+export { EPasskeyPromptReason } from "./EPasskeyPromptReason";
+export { EWalletEventKind } from "./EWalletEventKind";
diff --git a/src/lib/types/events/BalanceUpdatedEvent.ts b/src/lib/types/events/BalanceUpdatedEvent.ts
new file mode 100644
index 0000000..99964d9
--- /dev/null
+++ b/src/lib/types/events/BalanceUpdatedEvent.ts
@@ -0,0 +1,7 @@
+import type { TrackedAsset } from "../business/TrackedAsset";
+import { EWalletEventKind } from "../enum/EWalletEventKind";
+
+export class BalanceUpdatedEvent {
+ readonly kind = EWalletEventKind.BalanceUpdated as const;
+ constructor(public readonly assets: readonly TrackedAsset[]) {}
+}
diff --git a/src/lib/types/events/RefreshBalanceRequestedEvent.ts b/src/lib/types/events/RefreshBalanceRequestedEvent.ts
new file mode 100644
index 0000000..c1bfbba
--- /dev/null
+++ b/src/lib/types/events/RefreshBalanceRequestedEvent.ts
@@ -0,0 +1,7 @@
+import type { TrackedAssetId } from "../primitives/TrackedAssetId";
+import { EWalletEventKind } from "../enum/EWalletEventKind";
+
+export class RefreshBalanceRequestedEvent {
+ readonly kind = EWalletEventKind.RefreshBalanceRequested as const;
+ constructor(public readonly trackedAssetId?: TrackedAssetId) {}
+}
diff --git a/src/lib/types/events/WalletDomainEvent.ts b/src/lib/types/events/WalletDomainEvent.ts
new file mode 100644
index 0000000..334ae27
--- /dev/null
+++ b/src/lib/types/events/WalletDomainEvent.ts
@@ -0,0 +1,6 @@
+import type { BalanceUpdatedEvent } from "./BalanceUpdatedEvent";
+import type { RefreshBalanceRequestedEvent } from "./RefreshBalanceRequestedEvent";
+
+export type WalletDomainEvent =
+ | RefreshBalanceRequestedEvent
+ | BalanceUpdatedEvent;
diff --git a/src/lib/types/events/index.ts b/src/lib/types/events/index.ts
new file mode 100644
index 0000000..6cd7054
--- /dev/null
+++ b/src/lib/types/events/index.ts
@@ -0,0 +1,3 @@
+export { BalanceUpdatedEvent } from "./BalanceUpdatedEvent";
+export { RefreshBalanceRequestedEvent } from "./RefreshBalanceRequestedEvent";
+export type { WalletDomainEvent } from "./WalletDomainEvent";
diff --git a/src/lib/types/primitives/TrackedAssetId.ts b/src/lib/types/primitives/TrackedAssetId.ts
new file mode 100644
index 0000000..5471d41
--- /dev/null
+++ b/src/lib/types/primitives/TrackedAssetId.ts
@@ -0,0 +1,17 @@
+import { type Brand, make } from "ts-brand";
+import type { EVMAccountAddress, EVMChainId } from "@1shotapi/ows-types";
+
+/**
+ * Deterministic tracked-asset id: `${chainId}:${address}` lowercased.
+ */
+export type TrackedAssetId = Brand;
+export const TrackedAssetId = make();
+
+export function makeTrackedAssetId(
+ chainId: EVMChainId | string,
+ address: EVMAccountAddress | string,
+): TrackedAssetId {
+ return TrackedAssetId(
+ `${String(chainId).toLowerCase()}:${String(address).toLowerCase()}`,
+ );
+}
diff --git a/src/lib/types/primitives/index.ts b/src/lib/types/primitives/index.ts
new file mode 100644
index 0000000..90e4170
--- /dev/null
+++ b/src/lib/types/primitives/index.ts
@@ -0,0 +1 @@
+export * from "./TrackedAssetId";
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..bd0c391
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..18c4ed4
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,20 @@
+import { createRoot } from "react-dom/client";
+import { App } from "./App";
+import { StyleProvider, styleController } from "./style";
+import { WalletProvider } from "./wallet/WalletProvider";
+import "./index.css";
+
+const root = document.getElementById("root");
+if (!root) {
+ throw new Error("#root not found");
+}
+
+styleController.init();
+
+createRoot(root).render(
+
+
+
+
+ ,
+);
diff --git a/src/ows/demoChains.ts b/src/ows/demoChains.ts
new file mode 100644
index 0000000..aadd7ea
--- /dev/null
+++ b/src/ows/demoChains.ts
@@ -0,0 +1,47 @@
+import { EVMChainId } from "@1shotapi/ows-types";
+
+/** Demo chains for the branding-layer chain dropdown (fed into RpcHelper). */
+export const DEMO_CHAINS: ReadonlyArray<{
+ chainId: EVMChainId;
+ label: string;
+ rpcUrl: string;
+ /** Base URL for tx/address explorer links (no trailing slash). */
+ blockExplorerUrl: string;
+}> = [
+ {
+ chainId: EVMChainId("0x4cef52"), // 5042002
+ label: "Arc Testnet",
+ rpcUrl: "https://arc-testnet.g.alchemy.com/v2/jqLUTbHeN_cVsIX2W7tJk",
+ blockExplorerUrl: "https://testnet.arcscan.app",
+ },
+ {
+ chainId: EVMChainId("0xaa36a7"), // 11155111
+ label: "Sepolia",
+ rpcUrl: "https://eth-sepolia.g.alchemy.com/v2/jqLUTbHeN_cVsIX2W7tJk",
+ blockExplorerUrl: "https://sepolia.etherscan.io",
+ },
+ {
+ chainId: EVMChainId("0x14a34"), // 84532
+ label: "Base Sepolia",
+ rpcUrl: "https://base-sepolia.g.alchemy.com/v2/jqLUTbHeN_cVsIX2W7tJk",
+ blockExplorerUrl: "https://sepolia.basescan.org",
+ },
+ {
+ chainId: EVMChainId("0x2105"), // 8453
+ label: "Base",
+ rpcUrl: "https://base-mainnet.g.alchemy.com/v2/jqLUTbHeN_cVsIX2W7tJk",
+ blockExplorerUrl: "https://basescan.org",
+ },
+];
+
+/** Explorer tx URL for a demo chain, or `null` when the chain is unknown. */
+export function demoTxExplorerUrl(
+ chainId: EVMChainId,
+ transactionHash: string,
+): string | null {
+ const meta = DEMO_CHAINS.find((chain) => chain.chainId === chainId);
+ if (!meta) {
+ return null;
+ }
+ return `${meta.blockExplorerUrl}/tx/${transactionHash}`;
+}
diff --git a/src/ows/registerAccountConnect.ts b/src/ows/registerAccountConnect.ts
new file mode 100644
index 0000000..7ee2359
--- /dev/null
+++ b/src/ows/registerAccountConnect.ts
@@ -0,0 +1,63 @@
+import type { OWSSigner } from "@1shotapi/ows-signer-utils";
+import type { OWSWallet } from "@1shotapi/ows-wallet-utils";
+import {
+ EVMAccountAddress,
+ OwsUserRejectedError,
+ type SolanaAccountAddress,
+} from "@1shotapi/ows-types";
+
+export type AccountConnectStorage = {
+ loadCachedEvmAddress: () => EVMAccountAddress | undefined;
+ saveCachedAddresses: (
+ evm: EVMAccountAddress,
+ solana?: SolanaAccountAddress,
+ ) => void;
+};
+
+export type RegisterAccountConnectOptions = {
+ storage: AccountConnectStorage;
+ ensureReady: () => Promise;
+ requestConnectApproval: () => Promise;
+};
+
+/**
+ * Register `eth_accounts` / `eth_requestAccounts` on the wallet (pre-`start()`).
+ */
+export function registerAccountConnect(
+ wallet: OWSWallet,
+ signer: OWSSigner,
+ options: RegisterAccountConnectOptions,
+): void {
+ wallet.registerEip1193("eth_accounts", async () => {
+ const cached = options.storage.loadCachedEvmAddress();
+ if (cached) {
+ return [cached];
+ }
+ return [];
+ });
+
+ wallet.registerEip1193("eth_requestAccounts", async () => {
+ const cached = options.storage.loadCachedEvmAddress();
+ if (cached) {
+ return [cached];
+ }
+
+ const display = await wallet.requestDisplay({ width: 420, height: 360 });
+ try {
+ const approved = await options.requestConnectApproval();
+ if (!approved) {
+ throw new OwsUserRejectedError(
+ "User rejected the account connection request",
+ );
+ }
+
+ await options.ensureReady();
+ const evm = await signer.evm.getAccountAddress();
+ const solana = await signer.solana.getAccountAddress();
+ options.storage.saveCachedAddresses(evm, solana);
+ return [evm];
+ } finally {
+ await display.hide();
+ }
+ });
+}
diff --git a/src/ows/registerApprovalSigning.ts b/src/ows/registerApprovalSigning.ts
new file mode 100644
index 0000000..2f7db89
--- /dev/null
+++ b/src/ows/registerApprovalSigning.ts
@@ -0,0 +1,58 @@
+import { SignHelper } from "@1shotapi/ows-signer-utils";
+import type {
+ OWSSigner,
+ PersonalSignApprovalRequest,
+ SendTransactionApprovalRequest,
+ SignHelperChainRpc,
+ SignTypedDataApprovalRequest,
+} from "@1shotapi/ows-signer-utils";
+import type { OWSWallet } from "@1shotapi/ows-wallet-utils";
+import type { EVMTransactionHash } from "@1shotapi/ows-types";
+
+export type RegisterApprovalSigningOptions = {
+ /**
+ * Setup-only gate for signed actions: run onboarding when no credential
+ * exists. With a known credential, skip unlock — the signing ceremony
+ * authenticates. Pair with {@link onAuthenticated}.
+ */
+ ensureReady?: () => Promise;
+ /** Mark unlocked + refresh addresses after a successful signing ceremony. */
+ onAuthenticated?: () => void | Promise;
+ chainRpc: SignHelperChainRpc;
+ requestPersonalSignApproval: (
+ request: PersonalSignApprovalRequest,
+ ) => Promise;
+ requestSignTypedDataApproval: (
+ request: SignTypedDataApprovalRequest,
+ ) => Promise;
+ /**
+ * Branding owns consent + prepare + sign + broadcast for eth_sendTransaction.
+ */
+ approveAndSignTransaction: (
+ request: SendTransactionApprovalRequest,
+ ) => Promise;
+};
+
+/**
+ * Build SignHelper handlers and register them on the wallet (pre-`start()`).
+ */
+export function registerApprovalSigning(
+ wallet: OWSWallet,
+ signer: OWSSigner,
+ options: RegisterApprovalSigningOptions,
+): SignHelper {
+ const helper = new SignHelper(signer, wallet, {
+ ensureReady: options.ensureReady,
+ onAuthenticated: options.onAuthenticated,
+ getChainId: () => options.chainRpc.getChainId(),
+ requestPersonalSignApproval: options.requestPersonalSignApproval,
+ requestSignTypedDataApproval: options.requestSignTypedDataApproval,
+ approveAndSignTransaction: options.approveAndSignTransaction,
+ });
+
+ for (const [method, handler] of Object.entries(helper.handlers)) {
+ wallet.registerEip1193(method, handler);
+ }
+
+ return helper;
+}
diff --git a/src/ows/registerCredentialsProvider.ts b/src/ows/registerCredentialsProvider.ts
new file mode 100644
index 0000000..4e3cf74
--- /dev/null
+++ b/src/ows/registerCredentialsProvider.ts
@@ -0,0 +1,95 @@
+import type { OWSSigner } from "@1shotapi/ows-signer-utils";
+import type { OWSWallet } from "@1shotapi/ows-wallet-utils";
+import {
+ CredentialsHelper,
+ type CredentialsHelperOptions,
+} from "@1shotapi/ows-oid4";
+import {
+ type CredentialOfferApprovalRequest,
+ type CredentialPresentationApprovalRequest,
+ type ICredentialRepository,
+ type ICredentialStatusValidator,
+ type IHolderSigner,
+ type IIssuerTrustRegistry,
+ type IOid4vciClient,
+ type IOid4vpClient,
+ type IWalletAttestationProvider,
+ type IssuerMetadata,
+} from "@1shotapi/ows-types";
+import { isWalletCreated } from "../storage";
+import {
+ ensureCredentialsReadable,
+ withWalletReady,
+} from "../wallet/withWalletReady";
+
+export type RegisterCredentialsProviderOptions = {
+ repository: ICredentialRepository;
+ oid4vci: IOid4vciClient;
+ oid4vp: IOid4vpClient;
+ trust: IIssuerTrustRegistry;
+ status?: ICredentialStatusValidator;
+ holderSigner?: IHolderSigner | (() => Promise);
+ getProofNonce?: (metadata: IssuerMetadata) => string | Promise;
+ attestationProvider?: IWalletAttestationProvider;
+ ensureReady?: () => Promise;
+ requestCredentialOfferApproval?: (
+ request: CredentialOfferApprovalRequest,
+ ) => Promise;
+ requestCredentialPresentationApproval?: (
+ request: CredentialPresentationApprovalRequest,
+ ) => Promise;
+};
+
+/**
+ * Register wallet.credentials handlers via {@link CredentialsHelper}
+ * (call before `wallet.start()`).
+ *
+ * Host actions are gated so a locked / first-visit wallet unlocks (or runs
+ * setup) before OID4 work. See {@link withWalletReady} /
+ * {@link ensureCredentialsReadable}.
+ */
+export function registerCredentialsProvider(
+ wallet: OWSWallet,
+ signer: OWSSigner,
+ options: RegisterCredentialsProviderOptions,
+): CredentialsHelper {
+ const ensureReady = options.ensureReady ?? (async () => {});
+
+ const helperOptions: CredentialsHelperOptions = {
+ repository: options.repository,
+ oid4vci: options.oid4vci,
+ oid4vp: options.oid4vp,
+ trust: options.trust,
+ status: options.status,
+ holderSigner: options.holderSigner,
+ getProofNonce: options.getProofNonce,
+ attestationProvider: options.attestationProvider,
+ ensureReady,
+ requestCredentialOfferApproval: options.requestCredentialOfferApproval,
+ requestCredentialPresentationApproval:
+ options.requestCredentialPresentationApproval,
+ };
+ const helper = new CredentialsHelper(wallet, signer, helperOptions);
+
+ // Gate at registration time so every credentials.* host call unlocks first
+ // when needed — including future helpers that forget an internal ensureReady.
+ wallet.credentials.register({
+ acceptOffer: withWalletReady(ensureReady, (input) =>
+ helper.handlers.acceptOffer(input),
+ ),
+ present: async (input) => {
+ await ensureCredentialsReadable({
+ ensureReady,
+ isWalletCreated,
+ listLocal: () => options.repository.list(),
+ });
+ return helper.handlers.present(input);
+ },
+ list: (filter) => helper.handlers.list(filter),
+ delete: withWalletReady(ensureReady, (input) =>
+ helper.handlers.delete(input),
+ ),
+ });
+
+ return helper;
+}
diff --git a/src/relayer/RelayerCredentialsClient.ts b/src/relayer/RelayerCredentialsClient.ts
new file mode 100644
index 0000000..0f610d9
--- /dev/null
+++ b/src/relayer/RelayerCredentialsClient.ts
@@ -0,0 +1,97 @@
+import type { COSEPublicKey } from "@1shotapi/ows-types";
+import { RELAYER_BASE_URL } from "./constants";
+import type {
+ IRecoveredCredentialBlob,
+ IRelayerCredentialsErrorBody,
+ IWalletCredentialChallengeResponse,
+ IWebAuthnAssertionRequest,
+} from "./types";
+
+export class RelayerCredentialsError extends Error {
+ readonly status: number;
+ readonly errorCode: string;
+
+ constructor(status: number, errorCode: string, message: string) {
+ super(message);
+ this.name = "RelayerCredentialsError";
+ this.status = status;
+ this.errorCode = errorCode;
+ }
+}
+
+/**
+ * Thin REST client for 1Shot Relayer wallet credential endpoints
+ * (`/wallet/credentials/*`, `/wallet/passkeys/register`).
+ */
+export class RelayerCredentialsClient {
+ private readonly baseUrl: string;
+
+ constructor(baseUrl: string = RELAYER_BASE_URL) {
+ this.baseUrl = baseUrl.replace(/\/$/, "");
+ }
+
+ async createChallenge(): Promise {
+ return this.postJson