Generate or update a controlled Flap custom Vault UI inside flap-vault-ui-template. Use when creating src/vaults/{folder-name} components, manifests, ABI fragments, i18n, or when running vault:check/package for a Vault UI zip submission.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Generate or update a controlled Flap custom Vault UI inside flap-vault-ui-template. Use when creating src/vaults/{folder-name} components, manifests, ABI fragments, i18n, or when running vault:check/package for a Vault UI zip submission.
Flap Vault UI Generator
Use this skill inside flap-vault-ui-template to create or update a private custom Vault UI package.
Core Rule
Custom Vault UI is a controlled Flap business component, not a free-form website container.
Do not bind custom UI by type fields. Binding must come from manifest/registry-controlled:
Read agent-contract.json, the active tool-specific entry point, docs/ai-agent.md, docs/agent-entrypoints.md, docs/ui-pattern-snippets.md, and this skill.
Read references/reference-workflow.md.
Read references/patterns.md.
If the user mentions an existing Flap Vault, a screenshot, a URL, or "make it like the current/online page", inspect that reference before coding.
Read references/existing-vaults.md as the public-safe pattern map for choosing the closest template fixture. If an internal Flap frontend checkout is separately available, treat it as read-only reference material and extract behavior only.
Consult README.md, docs/prd.md, docs/safety-boundaries.md, and docs/manifest.md only when the startup sequence and direct references do not answer a specific detail.
Gather inputs:
Vault folder name
artifactId, generated by scaffold unless restoring an existing artifact identity
binding targets for the core path: one {chainId, factoryAddress} per factory-scoped target, or {chainId, vaultAddresses: [vaultAddress]} for no-factory targets
caRestrictionMode: none, reserved, or verified; this is Workbench/registry production policy, not a public manifest field
real deployed 7777/8888-suffix token values for any match.bindings[].tokenAddresses; for Robinhood use either token scope on chain 4663 or a real test token on chain 46630, using the standard proof tokens in docs/robinhood-testnet.md when no developer-submitted token is provided; do not treat factory-mode tokenAddresses as production CA restrictions
final real mainnet factory address when mainnet production launch is planned
required Vault address for the core no-factory path; optional Vault references in factory mode
optional fixed extra contract targets only when needed, stored per binding as match.bindings[].externalContracts[] with address and label
minimal Vault ABI fragments
custom token ABI fragments only when the token has non-standard methods; standard ERC20 uses erc20Abi from @/src/sdk
read methods
write methods
approve spender
oracle usage, only if unavoidable; do not declare oracle config in manifest
non-oracle endpoints, only if unavoidable and explicitly declared for review
one display-only external frame, only if unavoidable and explicitly declared in manifest.externalFrames for TradingView, DexScreener, or CoinGecko Terminal/GeckoTerminal with a full static query URL
UI workflow
action availability stage: internal-market, dex-listed, both, or read-only
market phase handling through context.host?.marketPhase
token media handling through context.tokenImageUrl, not private token metadata API calls
visual assets through CSS/HTML shapes or approved icon packages first; inline SVG JSX only for static pure graphic nodes
i18n requirements
Pick the closest pattern.
Select the closest public-safe snippet from docs/ui-pattern-snippets.md for section order, transaction state, and empty/error states.
For a new package, prefer yarn vault:scaffold {folder-name} --name "..." --chain 97 --factory 0xTestnetFactory --token 0xReal7777TestToken --chain 56 --factory 0xMainnetFactory --locales ... for factory mode with mainnet launch intent, or yarn vault:scaffold {folder-name} --name "..." --chain 56 --vault 0x... --token 0x... --locales ... for no-factory mode before implementation.
Implement package files only under src/vaults/{folder-name}. Default Vault UI must not create auxiliary files, helpers, local modules, assets, or nested folders. The sole 3D exception is capabilities: ["three-r3f-v1"] on either a mode-less 7777 Vault UI or token-scoped 8888 Mini App; it may use only recursively statically reachable profile-approved files and the four pinned 3D packages. The 7777 surface keeps host risk status before the 3D visual and receives no Mini App audio/full-height/displayTitle exemptions. Read docs/mini-app-3d.md, follow flap-skies-showcase for experience quality, flap-gamefi-arena for keyboard/touch GameFi interaction, and three-r3f-example for the compact 7777 technical fixture.
Generate:
src/vaults/{folder-name}/Component.tsx
src/vaults/{folder-name}/manifest.json
src/vaults/{folder-name}/VaultABI.ts
src/vaults/{folder-name}/i18n.json
If scaffold did not create the package, run yarn vault:register {folder-name} after the four files exist. Do not hand-edit src/vaults/index.ts unless the script reports an unexpected index shape.
Fix blocking issues before finishing. vault:e2e is deterministic Playwright DOM/layout/state checking, not AI image judgment. On first local runs, especially Windows, install Chromium with yarn playwright install chromium if the command returns vault-e2e/playwright-browser-missing. As its first step, vault:package fetches the official template and fast-forwards a checkout that is only behind origin/main, preserving non-conflicting local Vault work; conflicts and ahead/diverged branches stop without discarding changes.
Implementation Rules
Use @/src/sdk for chain, wallet, contract, oracle, notify, i18n, formatting, tx errors.
Use @/src/ui primitives before custom UI.
Use the scaffold default surface / NiePan-style compact template as the visual default. Do not ship row-heavy dashboard stacks; vault:check blocks stacked sample cards or large metric grids before the primary action.
Prefer CSS/HTML card shapes and lucide-react icons before ad hoc SVG. Search the official Lucide icon library first: https://lucide.dev/icons/ (main site: https://lucide.dev/). Use FontAwesome only in host repositories or runtimes that explicitly include and allow it; this template's Vault package allowlist does not include FontAwesome by default.
Handwritten inline SVG JSX is allowed only for static pure graphic nodes such as svg, g, defs, path, circle, rect, line, polyline, polygon, ellipse, linearGradient, radialGradient, stop, clipPath, mask, title, and desc. Keep references local, for example fill="url(#gradient)".
canvas only for component-scoped business visualization inside the Vault body. Use a React ref plus local state or SDK/host-derived data, keep required host risk status ahead of any large canvas block, and do not treat canvas as a whiteboard/editor shell or rely on browser-global DOM queries, workers, direct browser network/media APIs, or external assets.
Use docs/ui-pattern-snippets.md for public-safe Flap style and workflow organization.
Use agent-contract.json as the machine-readable source of allowed files, imports, manifest fields, commands, preview route, and done criteria.
Keep default Vault UI folders strict: only Component.tsx, manifest.json, VaultABI.ts, and i18n.json. Mini Apps may add reviewed top-level audio files.
A mode-less 7777 Vault UI or token-scoped 8888 Mini App declaring three-r3f-v1 may add recursively statically reachable profile-approved source, shaders, models, textures, environments, fonts, and controlled decoder assets inside the same folder. Every local file must be reached through a static import from ; import model/texture URLs before passing them to Drei/Three loaders instead of using relative string paths. Do not add unreferenced files, path escapes, arbitrary npm packages, auxiliary documents, or sample data.
Hard Stops
window.ethereum.request
eval / the Function constructor
raw iframe, iframe srcDoc, or script injection, including document.write / document.writeln; ReviewedFrame is the only reviewed frame path
unsafe inline SVG JSX, including scripts, event attributes, foreignObject, image, use, external URLs, non-local url(...), styleurl(...) / @import, href / src except static local fragments, spread attributes, or unsupported nodes
canvas flows that depend on browser-global DOM queries, workers, direct browser network/media APIs, new Image(), external assets, or full-screen/infinite-canvas editor behavior
runtime remote import
undeclared URL, endpoint, or external resource
undeclared fixed extra contract target
hidden transaction target
missing current contract risk-status integration
row-heavy sample-dashboard layout instead of the scaffold default surface
current contract risk status placed after the first three business UI rows or after preview/hero/media/chart visuals
manual Low risk / 低风险 labels not derived from host riskLevel === 1
unapproved dependency
custom third-party image or external resource not controlled by Flap runtime/Artifact Workbench
missing or invalid locale declarations in manifest.i18n; locale strings must be at least two characters
binding by type fields
References
references/reference-workflow.md: how to inspect provided URLs, screenshots, and existing code before generation.
references/existing-vaults.md: current Flap Vault UI reference matrix and what to reuse from each pattern.
references/patterns.md: choose an implementation pattern.
docs/ui-pattern-snippets.md: public-safe UI organization and state-flow snippets.
Component.tsx
Vault folder name controls src/vaults/{folder-name} and preview route /{folder-name}. It must be 3-64 characters of lowercase kebab-case.
artifactId controls unique artifact identity and must match vaultui_<folder-name>_<ULID>. It is not the route and not the registry match.
Manifest match requires explicit runtime targets in match.bindings. Use {chainId, factoryAddress} for factory mode or {chainId, vaultAddresses: [vaultAddress]} for the core no-factory path. If any binding includes tokenAddresses, including a factory binding, every entry must be a real deployed ERC20 address ending in 7777 or 8888. The checker also accepts no-factory tokenAddresses and vaultAddresses + tokenAddresses mappings when Flap review/runtime supplies them. Do not use zero factory addresses; omit factoryAddress for no-factory mode. Optional externalContracts belongs inside the specific binding only.
Manifest is intentionally minimal: artifactId, name, match, i18n, optional mode: "mini-app" only for token-scoped 8888-token Mini App artifacts, optional non-oracle endpoints, and optional reviewed externalFrames only. Omit mode for the default Vault UI. Mini App mode is strongly bound to the token address: it must use match.bindings[].tokenAddresses ending in 8888 and must not use factory or Vault bindings. Do not add chainIds, id, owner, version, sdkVersion, actions, oracles, media, fallback, contracts, restrictTokenAddresses, global tokenAddresses, or caPolicy. Chain IDs, factory or Vault targets, required manifest test tokenAddresses, and optional externalContracts live inside match.bindings entries. Production CA restriction is decided by Workbench/registry caRestrictionMode, not public manifest fields.
The only allowed local relative import for default Vault UI is ./VaultABI; Mini App may also statically import reviewed top-level audio. three-r3f-v1 is the sole exception for recursively reachable profile-approved local source and 3D assets. Do not use path escapes, dynamic imports, or local files outside the declared profile. Use public aliases such as @/src/sdk and @/src/ui for shared runtime surfaces.
Treat yarn vault:package {folder-name} as the only local packaging path. It runs vault:check, requires a current passing dist/e2e/{folder-name}/qa-report.json, and produces the source zip for the Flap Artifact Workbench.
Submit only the zip produced by yarn vault:package {folder-name}. The script writes format-version 6flap-vault-package.json, E2E report v2, recursive source/asset hashes, and matching E2E summary fields. Workbench may read legacy format 5 only without capabilities; hand-made zips without the marker, proof, profile contract, or matching hashes are rejected.
Run yarn vault:verify-package dist/{folder-name}.zip after packaging to validate the marker, exact file list, metadata, E2E proof, and hashes from the Workbench side.
Do not treat a developer-local tx hash or wallet trace as strong proof that a future write transaction originated from the local UI. Strong write-UI origin assurance requires a platform-controlled Playwright + wallet runner.
Runtime artifacts are built by the Flap Artifact Workbench into readable ESM JS (component.mjs). Do not hand-write, minify, or directly upload runtime JS from the template.
Use minimal ABI fragments only.
Use erc20Abi or standardErc20Abi from @/src/sdk for standard ERC20 balanceOf, allowance, approve, decimals, symbol, transfer, and transferFrom. Do not copy standard ERC20 ABI into VaultABI.ts.
Add token ABI fragments to VaultABI.ts only for custom non-standard token methods or special token mechanics.
If VaultABI.ts uses human-readable ABI signature strings, import parseAbi from viem and export parseAbi([...]); do not export raw string arrays as ABI.
If a Vault ABI method returns multiple values, type sdk.readContract as a tuple such as readonly [currentPool: bigint, totalReceived: bigint], then map indexes into object-shaped UI state. Do not type multi-output reads as object interfaces. A single returned Solidity tuple / struct output declared as one ABI output with components may still use an object result type.
Put user-facing copy in i18n.json for every locale declared by manifest.i18n.
Test EN/ZH in the local example page; the preview shell uses Flap's flap:language localStorage key and flap_language cookie, and passes the active locale into sdk.i18n.
Do not import res/content.json or res/content_zh.json from a Vault component. Those files are only for the Flap preview shell/header.
Avoid external endpoints and external resources by default. If a special non-oracle endpoint is unavoidable, declare it in manifest.json for Flap review as a single absolute HTTPS URL string without username/password credentials or an array of those strings. Direct fetch(...) targets must be static absolute HTTPS URLs covered by that declaration. Oracle usage is detected by vault:check and provisioned by the Flap Artifact Workbench/runtime, not declared in manifest. Declaration does not guarantee approval, and undeclared endpoints/resources are rejected. If a Vault-specific immutable image is unavoidable, import IpfsImage from @/src/ui; for a decorative full-area background, import IpfsBackground from @/src/ui. Pass only a static image/directory CID via cid. IpfsImage may append a safe dynamic NFT path only with a static validationPath sample; full gateway URLs, metadata CIDs, ipfs:// values, dynamic CIDs, and unsafe paths are rejected.
Avoid external frames by default. If a display-only chart embed is unavoidable, declare at most one entry in manifest.externalFrames[] with id, provider, src, and title; provider must be tradingview, dexscreener, or coingecko-terminal, and src must be one complete static HTTPS provider URL with a fixed non-empty query string. Render it only through at most one ReviewedFrame from @/src/ui with static string literal props that exactly match the manifest declaration. Do not use raw iframe, multiple ReviewedFrame instances, srcDoc, dynamic URL construction, postMessage, wallet connection, transaction flows, or frame-driven quote/risk/settlement logic. Declaration does not guarantee approval.
Avoid fixed extra contract targets by default. If a non-token/non-Vault/non-factory contract target is unavoidable, declare it under match.bindings[].externalContracts with address and label; declaration does not guarantee approval, and undeclared fixed targets are rejected.
For Vaults with internal module addresses such as wrap factories, routers, dividend distributors, staking wrappers, or trigger helpers, ask the Vault contract to expose UI-facing view methods or public proxy actions and call those through context.vaultAddress. Do not read dynamic module addresses directly, do not declare dynamic module targets as externalContracts, and do not expose operator/admin config methods such as setConfig, setSwapPath, or setSplit from Component.tsx.
Do not declare write/approve actions in manifest.json; transaction behavior belongs in Component.tsx through SDK methods and runtime context addresses.
Do not hardcode undeclared transaction target addresses in Vault source. Use context.vaultAddress, context.tokenAddress, context.factoryAddress, and other runtime context values. If a fixed extra contract target is unavoidable, declare it under match.bindings[].externalContracts.
Use approve -> wait -> refetch -> simulate -> write -> wait -> refetch for ERC20 spend flows.
Decide and state whether actions are available in internal-market, DEX-listed, both, or read-only stage. Do not silently hide supported actions; show disabled/unavailable states with clear copy.
Use context.host?.marketPhase as the runtime source for internal-market vs DEX-listed checks. The current template preview panel provides this API for local self-test; production Flap host injects equivalent context. Existing tokens with tokenInfo.status < 2 are internal-market; existing tokens with tokenInfo.status >= 2 are dex-listed; missing token info is unknown.
Use isActionAvailableForPhase(stage, context.host?.marketPhase ?? "unknown") for stage-gated buttons.
Every onboarded Vault UI must read and visibly render the current contract risk status from host context. Use readTaxVaultHostContext(context.host) and derive riskLevel from host.vaultInfo?.riskLevel ?? host.taxInfo?.vaultInfo?.riskLevel; place the risk status within the first three visible Vault-specific business rows/blocks and before any preview, hero, banner, showcase, media, chart, or large visual block. If it is unavailable, show a prominent warning/danger message that risk-status integration is required. Do not hardcode or unconditionally render Low risk / 低风险 labels, badges, summaries, or reassuring copy; those labels may appear only when selected from the host-derived riskLevel === 1 branch. vault:check blocks packages that omit this, place it too low, place it after large visuals, or add manual low-risk labels.
Use context.tokenImageUrl, context.tokenName, and context.tokenSymbol for token media/header data. The template preview shell first asks the same-origin runtime proxy for host-owned token presentation data, then falls back to on-chain ERC20 symbol() / name(); /logo.png is reserved for the neutral preview fixture only. Do not call private token metadata APIs from Component.tsx.
For Vault V2 NFT art, use NftMetadataImage from @/src/ui with only tokenId and localized alt. It consumes shared SDK context internally, so no caller SDK prop, project image ABI, or NFT address is needed. The runtime owns the minimal Vault.nft() and NFT.tokenURI(tokenId) ABIs and resolves mode 0/1 data JSON plus mode 2 IPFS/HTTPS metadata. Do not read tokenURIBase, append .json, fetch metadata, or pass sdk/ABI/nftAddress/tokenURI/endpoint/src/imageUrl/CID/path props. Paginate or render only visible token ids; do not mount an unbounded collection.
Local preview uses real wallet/runtime data. If data is needed, use real addresses supplied by the user or preview URL params.
Preview both phase states with marketPhase=internal-market and marketPhase=dex-listed before packaging an action-heavy Vault UI.
Let the preview shell own the token header, breadcrumb, close control, Vault Information frame, wallet header, language selector, invalid token fallback, page width, and any host-provided shared summary/header block. Vault components should render only the Vault-specific business UI below that frame.
Do not default to a component-owned VaultBanner or top hero card. Use one only when the target host surface truly lacks a shared summary/header block.
If referencing private Flap code, extract patterns and behavior only. Do not copy private business constants, private configuration, secrets, or project-specific code into public artifacts.
Do not copy private component names, project names, factory addresses, token addresses, endpoints, or exact private code into snippets, docs, or generated Vault source.