Repository: hyperlane-xyz/hyperlane-warp-ui-template Branch: main Commit: b44b37d19b18 Files: 299 Total size: 936.6 KB Directory structure: gitextract_g1lr7_k9/ ├── .claude/ │ └── skills/ │ ├── claude-review/ │ │ └── SKILL.md │ ├── claude-security-review/ │ │ └── SKILL.md │ ├── commit/ │ │ └── SKILL.md │ ├── inline-pr-comments/ │ │ └── SKILL.md │ └── resolve-pr-reviews/ │ └── SKILL.md ├── .env.example ├── .github/ │ ├── CODEOWNERS │ ├── prompts/ │ │ └── security-scan.md │ └── workflows/ │ ├── ci.yml │ ├── claude-code-review.yml │ ├── create-merge-prs.yaml │ ├── e2e-smoke.yml │ ├── e2e-wallet-full.yml │ ├── e2e.yml │ └── update-hyperlane-deps.yml ├── .gitignore ├── .nvmrc ├── .oxfmtrc.json ├── .oxlintrc.json ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── AGENTS.md ├── CLAUDE.md ├── CUSTOMIZE.md ├── LICENSE.md ├── README.md ├── REVIEW.md ├── next-env.d.ts ├── next.config.js ├── package.json ├── patches/ │ ├── @provablehq__sdk@0.9.15.patch │ ├── @provablehq__wasm@0.9.18.patch │ └── starknetkit@2.6.1.patch ├── playwright.config.ts ├── pnpm-workspace.yaml ├── postcss.config.js ├── public/ │ ├── .well-known/ │ │ └── radix.json │ ├── browserconfig.xml │ ├── site.webmanifest │ └── theme-init.js ├── scripts/ │ ├── README.md │ ├── fetch-fonts.mjs │ ├── link-monorepo.js │ └── unlink-monorepo.js ├── sentry.client.config.js ├── sentry.default.config.js ├── src/ │ ├── components/ │ │ ├── banner/ │ │ │ ├── FormWarningBanner.tsx │ │ │ ├── RecipientWarningBanner.tsx │ │ │ └── WarningBanner.tsx │ │ ├── buttons/ │ │ │ ├── ConnectAwareSubmitButton.tsx │ │ │ └── SolidButton.tsx │ │ ├── errors/ │ │ │ └── ErrorBoundary.tsx │ │ ├── icons/ │ │ │ ├── BookIcon.tsx │ │ │ ├── ChainLogo.tsx │ │ │ ├── ChevronLargeIcon.tsx │ │ │ ├── HamburgerIcon.tsx │ │ │ ├── HyperlaneGradientLogo.tsx │ │ │ ├── HyperlaneTransparentLogo.tsx │ │ │ ├── QuestionMarkIcon.tsx │ │ │ ├── StakeIcon.tsx │ │ │ ├── SwapIcon.tsx │ │ │ ├── TokenIcon.tsx │ │ │ ├── WebSimpleIcon.tsx │ │ │ └── XIcon.tsx │ │ ├── input/ │ │ │ ├── SearchInput.tsx │ │ │ └── TextField.tsx │ │ ├── layout/ │ │ │ ├── AppLayout.tsx │ │ │ ├── Card.tsx │ │ │ └── ModalHeader.tsx │ │ ├── nav/ │ │ │ ├── Footer.tsx │ │ │ ├── Header.tsx │ │ │ └── Nav.tsx │ │ ├── tip/ │ │ │ └── TipCard.tsx │ │ └── toast/ │ │ ├── IgpDetailsToast.tsx │ │ ├── TxSuccessToast.tsx │ │ └── useToastError.tsx │ ├── consts/ │ │ ├── app.ts │ │ ├── args.ts │ │ ├── blacklist.ts │ │ ├── chainAddresses.ts │ │ ├── chainAddresses.yaml │ │ ├── chains.ts │ │ ├── chains.yaml │ │ ├── config.ts │ │ ├── defaultMultiCollateralRoutes.ts │ │ ├── links.ts │ │ ├── warpRouteWhitelist.test.ts │ │ ├── warpRouteWhitelist.ts │ │ ├── warpRoutes.ts │ │ └── warpRoutes.yaml │ ├── features/ │ │ ├── WarpContextInitGate.tsx │ │ ├── analytics/ │ │ │ ├── intercom.ts │ │ │ ├── refiner.ts │ │ │ ├── types.ts │ │ │ ├── useWalletConnectionTracking.tsx │ │ │ └── utils.ts │ │ ├── balances/ │ │ │ ├── UsdLabel.tsx │ │ │ ├── cosmos.ts │ │ │ ├── evm.ts │ │ │ ├── feeUsdDisplay.test.ts │ │ │ ├── feeUsdDisplay.ts │ │ │ ├── hooks.ts │ │ │ ├── svm.ts │ │ │ ├── tokens.ts │ │ │ ├── useFeePrices.ts │ │ │ └── utils.ts │ │ ├── chains/ │ │ │ ├── ChainConnectionWarning.test.ts │ │ │ ├── ChainConnectionWarning.tsx │ │ │ ├── ChainEditModal.tsx │ │ │ ├── ChainFilterPanel.tsx │ │ │ ├── ChainList.tsx │ │ │ ├── ChainWalletWarning.tsx │ │ │ ├── MobileChainQuickSelect.tsx │ │ │ ├── addresses.ts │ │ │ ├── chainFilterSort.test.ts │ │ │ ├── chainFilterSort.ts │ │ │ ├── hooks.ts │ │ │ ├── metadata.ts │ │ │ └── utils.ts │ │ ├── limits/ │ │ │ ├── const.ts │ │ │ ├── types.ts │ │ │ ├── utils.test.ts │ │ │ └── utils.ts │ │ ├── messages/ │ │ │ ├── graphqlClient.ts │ │ │ ├── queries/ │ │ │ │ ├── build.ts │ │ │ │ ├── encoding.ts │ │ │ │ └── fragments.ts │ │ │ ├── types.ts │ │ │ ├── useMergedTransferHistory.test.ts │ │ │ ├── useMergedTransferHistory.ts │ │ │ ├── useMessageDeliveryStatus.ts │ │ │ ├── useMessageHistory.ts │ │ │ └── useOriginFinality.ts │ │ ├── routerAddresses.test.ts │ │ ├── sanctions/ │ │ │ └── hooks/ │ │ │ ├── useIsAccountChainalysisSanctioned.ts │ │ │ ├── useIsAccountOfacSanctioned.ts │ │ │ └── useIsAccountSanctioned.ts │ │ ├── store.ts │ │ ├── theme/ │ │ │ └── ThemeContext.tsx │ │ ├── tokens/ │ │ │ ├── ImportTokenButton.tsx │ │ │ ├── SelectOrInputTokenIds.tsx │ │ │ ├── SelectTokenIdField.tsx │ │ │ ├── TokenChainIcon.tsx │ │ │ ├── TokenList.tsx │ │ │ ├── TokenListPanel.tsx │ │ │ ├── TokenSelectField.tsx │ │ │ ├── UnifiedTokenChainModal.tsx │ │ │ ├── approval.ts │ │ │ ├── hooks.ts │ │ │ ├── types.ts │ │ │ ├── useTokenPrice.tsx │ │ │ ├── utils.test.ts │ │ │ ├── utils.ts │ │ │ ├── wrappedTokenResolver.test.ts │ │ │ └── wrappedTokenResolver.ts │ │ ├── transfer/ │ │ │ ├── FeeSectionButton.tsx │ │ │ ├── RecipientConfirmationModal.tsx │ │ │ ├── TransferFeeModal.tsx │ │ │ ├── TransferSection.tsx │ │ │ ├── TransferTokenCard.tsx │ │ │ ├── TransferTokenForm.tsx │ │ │ ├── TransfersDetailsModal.tsx │ │ │ ├── fees.test.ts │ │ │ ├── fees.ts │ │ │ ├── maxAmount.ts │ │ │ ├── predicate.ts │ │ │ ├── relayApi.ts │ │ │ ├── scaleUtils.test.ts │ │ │ ├── scaleUtils.ts │ │ │ ├── types.ts │ │ │ ├── useBalanceWatcher.ts │ │ │ ├── useFeeQuotes.test.ts │ │ │ ├── useFeeQuotes.ts │ │ │ ├── useQuotedCalls.test.ts │ │ │ ├── useQuotedCalls.ts │ │ │ ├── useTokenTransfer.ts │ │ │ ├── utils.test.ts │ │ │ └── utils.ts │ │ ├── wallet/ │ │ │ ├── ConnectWalletButton.tsx │ │ │ ├── RecipientAddressModal.tsx │ │ │ ├── SideBarMenu.tsx │ │ │ ├── WalletConnectionWarning.tsx │ │ │ ├── WalletDropdown.tsx │ │ │ ├── WalletProtocolModal.test.ts │ │ │ ├── WalletProtocolModal.tsx │ │ │ ├── _e2e/ │ │ │ │ ├── E2EAutoConnectCosmos.tsx │ │ │ │ ├── E2EAutoConnectEvm.tsx │ │ │ │ ├── E2EAutoConnectRadix.tsx │ │ │ │ ├── E2EAutoConnectSolana.tsx │ │ │ │ ├── E2EAutoConnectStarknet.tsx │ │ │ │ ├── E2EAutoConnectTron.tsx │ │ │ │ ├── MockCosmosWallet.test.fixtures.ts │ │ │ │ ├── MockCosmosWallet.test.ts │ │ │ │ ├── MockCosmosWallet.ts │ │ │ │ ├── MockSolanaAdapter.ts │ │ │ │ ├── MockStarknetConnector.test.ts │ │ │ │ ├── MockStarknetConnector.ts │ │ │ │ ├── MockTronAdapter.test.ts │ │ │ │ ├── MockTronAdapter.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── isE2E.ts │ │ │ │ └── windowState.ts │ │ │ ├── context/ │ │ │ │ ├── AleoWalletContext.tsx │ │ │ │ ├── CosmosWalletContext.tsx │ │ │ │ ├── EvmWalletContext.tsx │ │ │ │ ├── RadixWalletContext.tsx │ │ │ │ ├── SolanaWalletContext.tsx │ │ │ │ ├── StarknetWalletContext.tsx │ │ │ │ └── TronWalletContext.tsx │ │ │ ├── relativeTimeTicker.test.ts │ │ │ └── relativeTimeTicker.ts │ │ └── warpCore/ │ │ ├── AddWarpConfigModal.tsx │ │ ├── warpCoreConfig.test.ts │ │ └── warpCoreConfig.ts │ ├── global.d.ts │ ├── instrumentation.ts │ ├── lib/ │ │ └── predicateClient.ts │ ├── pages/ │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── api/ │ │ │ ├── predicate/ │ │ │ │ └── attestation.ts │ │ │ ├── quote.test.ts │ │ │ └── quote.ts │ │ ├── blocked.tsx │ │ ├── embed.tsx │ │ └── index.tsx │ ├── proxy.ts │ ├── styles/ │ │ ├── Color.ts │ │ ├── embed-theme.css │ │ ├── embedTheme.test.ts │ │ ├── embedTheme.ts │ │ ├── globals.css │ │ └── mediaQueries.ts │ ├── utils/ │ │ ├── date.test.ts │ │ ├── date.ts │ │ ├── imageBrightness.test.ts │ │ ├── imageBrightness.ts │ │ ├── links.ts │ │ ├── logger.ts │ │ ├── pino-noop.js │ │ ├── promises.ts │ │ ├── queryParams.ts │ │ ├── test.ts │ │ └── theme.ts │ └── vendor/ │ ├── inpage-metamask.js │ └── polyfill.js ├── tailwind.config.js ├── tests/ │ ├── chain-selection/ │ │ ├── edit-chain.spec.ts │ │ ├── filter-by-protocol.spec.ts │ │ ├── filter-by-type.spec.ts │ │ └── sort-chains.spec.ts │ ├── e2e-wallet/ │ │ ├── approval/ │ │ │ └── evm.spec.ts │ │ ├── autoconnect/ │ │ │ ├── cosmos.spec.ts │ │ │ ├── evm.spec.ts │ │ │ ├── radix.spec.ts │ │ │ ├── solana.spec.ts │ │ │ ├── starknet.spec.ts │ │ │ └── tron.spec.ts │ │ ├── balance-display/ │ │ │ ├── evm.spec.ts │ │ │ └── solana.spec.ts │ │ ├── destination-router/ │ │ │ ├── evm.spec.ts │ │ │ └── solana.spec.ts │ │ ├── helpers/ │ │ │ ├── captured.ts │ │ │ ├── constants.ts │ │ │ ├── evmRpc.ts │ │ │ ├── formFlow.ts │ │ │ ├── page-setup.ts │ │ │ ├── solanaRpc.ts │ │ │ └── types.ts │ │ ├── invalid-route/ │ │ │ └── evm.spec.ts │ │ ├── same-symbol-dedup/ │ │ │ ├── cosmos.spec.ts │ │ │ ├── evm.spec.ts │ │ │ └── solana.spec.ts │ │ ├── smoke/ │ │ │ └── gate.spec.ts │ │ └── tx-payload/ │ │ └── evm.spec.ts │ ├── embed/ │ │ ├── basic-rendering.spec.ts │ │ ├── csp-headers.spec.ts │ │ ├── no-chrome.spec.ts │ │ ├── routes-param.spec.ts │ │ └── theme.spec.ts │ ├── helpers/ │ │ ├── constants.ts │ │ └── locators.ts │ ├── page-load/ │ │ ├── default-tokens.spec.ts │ │ ├── header-footer.spec.ts │ │ ├── query-param-override.spec.ts │ │ ├── tip-card.spec.ts │ │ └── transfer-form-visible.spec.ts │ ├── sidebar/ │ │ └── sidebar-content.spec.ts │ ├── token-selection/ │ │ ├── filter-chains.spec.ts │ │ ├── open-close-modal.spec.ts │ │ ├── search-tokens.spec.ts │ │ ├── select-destination-token.spec.ts │ │ └── select-origin-token.spec.ts │ ├── transfer-form/ │ │ ├── connect-wallet-prompt.spec.ts │ │ ├── enter-amount.spec.ts │ │ └── swap-tokens.spec.ts │ └── wallet-connect/ │ ├── evm-wallet-modal.spec.ts │ └── protocol-wallet-modals.spec.ts ├── tsconfig.json └── vitest.config.mts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .claude/skills/claude-review/SKILL.md ================================================ --- name: claude-review description: Review code changes using Hyperlane Warp UI coding standards. Use when reviewing PRs, checking your own changes, or doing self-review before committing. --- # Code Review Skill Use this skill to review code changes against Hyperlane Warp UI standards. ## When to Use - Before committing changes (self-review) - When asked to review a PR or diff - To check if changes follow project patterns ## Instructions Read and apply the guidelines from `REVIEW.md` to review the code changes. ### For PR Reviews When reviewing a PR, deliver feedback as a **single consolidated GitHub review** using `/inline-pr-comments`. Each run produces a separate review — nothing is overwritten. 1. **Fetch prior reviews first** — the `/inline-pr-comments` skill fetches existing reviews/comments so you can avoid duplicating feedback and stay aware of ongoing discussions 2. **Review body** — Overall assessment, architecture concerns, and issues found outside the diff 3. **Inline comments** — Specific issues on changed lines (attached to the same review) ### For Self-Review When reviewing your own changes before committing: 1. Run `git diff` to see changes 2. Apply the code review guidelines 3. Fix issues directly rather than commenting ================================================ FILE: .claude/skills/claude-security-review/SKILL.md ================================================ --- name: claude-security-review description: Security-focused review for frontend/Web3 code. Use for XSS, wallet security, CSP, and dependency checks. --- # Security Review Skill Use this skill for security-focused code review of frontend Web3 code. ## When to Use - Reviewing wallet integration code - Checking for XSS vulnerabilities - CSP header changes - Dependency updates ## Instructions Read and apply the security guidelines from `.github/prompts/security-scan.md` to review the code changes. Report findings with severity ratings (Critical/High/Medium/Low/Informational) and suggested fixes. ### For PR Reviews When reviewing a PR, deliver feedback using `/inline-pr-comments` to post inline comments on specific lines. ================================================ FILE: .claude/skills/commit/SKILL.md ================================================ --- name: commit description: Commit changes following project quality gates and best practices. Run before creating any git commit. --- # Commit Skill Use this skill when committing changes to ensure quality and correctness. ## Pre-Commit Checklist Run these in order. **Do not commit if any fail.** 1. **`pnpm format`** — Format all source files 2. **`pnpm lint`** — Check for ESLint errors 3. **`pnpm typecheck`** — Verify TypeScript compiles 4. **`pnpm build`** — Ensure production build succeeds (optional for small changes, required before PR) ## Staging Rules - **Only stage files related to the current task.** Review `git status` carefully. - **Never stage unrelated files** — markdown notes, scratch files, `.monorepo-tarballs/`, `agent/`, etc. should not be committed unless explicitly requested. - **Use specific file paths** with `git add`, not `git add .` or `git add -A`. - **Review `git diff --staged`** before committing to verify only intended changes are included. ## Commit Message Format - Use conventional commit prefixes: `feat:`, `fix:`, `style:`, `chore:`, `refactor:`, `docs:`, `test:` - Keep the first line under 72 characters - Add a blank line then bullet points for multi-change commits - End with `Co-Authored-By: Claude Opus 4.6 ` - Use a HEREDOC to pass the message to avoid shell escaping issues ## Things to Watch For - **Secrets**: Never commit `.env`, credentials, or API keys - **Large files**: Don't commit binaries, build artifacts, or font files (check `.gitignore`) - **Formatting drift**: If oxfmt changed files you didn't touch, stage them separately or skip them ## Example Flow ```bash pnpm format pnpm lint pnpm typecheck git status # review what changed git diff # verify changes are correct git add # only related files git diff --staged # double-check staged changes git commit -m "$(cat <<'EOF' feat: description of change - Detail 1 - Detail 2 EOF )" ``` ================================================ FILE: .claude/skills/inline-pr-comments/SKILL.md ================================================ --- name: inline-pr-comments description: Post a single consolidated PR review with summary and inline comments. Use this skill to deliver code review feedback as one unified review per run. --- # Consolidated PR Review Skill Use this skill to submit code review feedback as a **single consolidated GitHub review** containing both the summary body and all inline comments. ## When to Use - After completing a code review (use with /claude-review) - When you have specific line-by-line feedback to deliver ## Instructions Submit one consolidated review per run using the GitHub API. **Do NOT post inline comments individually.** Each run produces a new review — nothing is overwritten. ### Step 1: Fetch Prior Review Context Before reviewing, read existing reviews and comments on the PR for context: ```bash # Fetch existing reviews (summaries) gh api --paginate "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews" --jq '.[] | {user: .user.login, state: .state, body: .body}' # Fetch inline review comments gh api --paginate "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/comments" --jq '.[] | {user: .user.login, path: .path, line: .line, body: .body}' # Fetch general PR discussion comments gh api --paginate "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --jq '.[] | {user: .user.login, body: .body}' ``` Use this context to: - **Skip issues already raised** — don't re-flag something a prior review already pointed out - **Reference prior discussions** — e.g. "As noted in the previous review, ..." - **Flag unresolved issues** — if a prior review raised something that still isn't fixed, note it briefly (e.g. "Still unresolved from prior review: ...") - **Avoid contradictions** — don't suggest the opposite of what a human reviewer requested ### Step 2: Collect All Findings Complete the full review. Collect: - **Summary** — Overall assessment, architecture concerns, non-diff observations - **Inline comments** — Specific issues on changed lines (path, line, body) ### Step 3: Build Review JSON Write a JSON file to `/tmp/review.json`: ```json { "body": "## Review Summary\n\nOverall assessment here.\n\n## Observations Outside This PR\n- `file:line`: description", "event": "COMMENT", "comments": [ { "path": "src/file.ts", "line": 42, "body": "Issue description here" }, { "path": "src/other.ts", "start_line": 10, "line": 15, "body": "Multi-line comment" } ] } ``` **Fields:** - `body` — Markdown review summary (required) - `event` — Always `"COMMENT"` (never APPROVE or REQUEST_CHANGES) - `comments` — Array of inline comment objects (can be empty if no inline findings) - `path` — File path relative to repo root - `line` — Line number in the NEW version of the file - `start_line` + `line` — For multi-line comments - `body` — Markdown-formatted feedback ### Step 4: Submit the Review ```bash gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews" --input /tmp/review.json ``` The `GITHUB_REPOSITORY` and `PR_NUMBER` environment variables are set by the CI workflow. ### Limitations - Inline comments can only target lines in the diff (changed/added lines) - Comments targeting unchanged lines will cause the API call to fail - If unsure whether a line is in the diff, put the finding in the summary body instead ### Handling Non-Diff Findings Issues in code NOT changed by the PR go in the review `body` under a dedicated section: ```markdown ## Observations Outside This PR - `src/utils/foo.ts:142`: Pre-existing null check missing - `src/core/bar.ts:78-82`: Similar pattern to line 45 issue ``` ### Feedback Guidelines | Feedback Type | In Diff? | Where to Put It | | ----------------------------- | -------- | ---------------------------------------------------- | | Specific code issue | Yes | `comments` array entry for that line | | Pattern repeated across files | Yes | First occurrence in `comments` + note others in body | | Related issue found | No | `body` under "Observations Outside This PR" | | Pre-existing bug discovered | No | `body` (consider separate issue if critical) | | Overall architecture concern | N/A | `body` | Be concise. Group minor style issues together. Never use APPROVE or REQUEST_CHANGES. ================================================ FILE: .claude/skills/resolve-pr-reviews/SKILL.md ================================================ --- name: resolve-pr-reviews description: Review and resolve PR review comments interactively. Fetches unresolved comments, proposes fixes or explains why to skip, and replies on GitHub. --- # Resolve PR Reviews Use this skill to process review comments on a PR. Pass the PR number as an argument (e.g. `/resolve-pr-reviews 1041`). ## Workflow ### Step 1: Fetch unresolved review comments Use the GitHub API to get all review comments that haven't been resolved: ```bash # Get inline review comments (pull request review comments) gh api repos/{owner}/{repo}/pulls/{pr}/comments --jq '.[] | select(.position != null or .line != null)' # Get issue-level comments (general PR comments from reviewers, not bots) gh api repos/{owner}/{repo}/issues/{pr}/comments ``` Filter out: - Deployment/CI bots (Vercel deploy previews, CI status checks) - Already-resolved threads - Your own previous replies Keep: - AI review comments (Claude review bot, CodeRabbit inline suggestions) — these are actionable review feedback - Human reviewer comments ### Step 2: Analyze each comment For each unresolved comment: 1. Read the relevant code being commented on 2. Understand the reviewer's concern 3. Propose a concrete fix OR explain why it should be skipped 4. Categorize severity: **must fix**, **good idea**, or **skip** (with reasoning) ### Step 3: Present to user **IMPORTANT: You MUST ask the user this question BEFORE showing any analysis. Do NOT skip this step or present comments prematurely.** After fetching and analyzing comments internally, use the `AskUserQuestion` tool to present a selection prompt: ``` Question: "Found N review comments on PR #XXXX. How would you like to go through them?" Options: - "One by one" — Present each comment individually, user decides fix/skip before next - "All at once" — Present all comments together, user reviews full list ``` Wait for the user's selection before proceeding. Then: - **"one by one"** (default): Present each comment individually with your analysis and proposed solution. Wait for user to decide "fix" or "skip" before moving to the next one. - **"all at once"**: Present all comments together with your analysis. User reviews the full list, then says which to fix and which to skip. For each comment, show: - The reviewer's comment (abbreviated) - **Your own independent analysis** — don't just parrot the reviewer. Verify if the concern is actually valid, check the relevant code/dependencies, and explain what's really happening. If the reviewer is wrong or partially wrong, say so. - Your proposed fix (code diff) or skip reasoning - Your recommendation ### Step 4: Apply fixes Apply all approved fixes to the codebase. ### Step 5: Commit and push Run the `/commit` skill to format, lint, typecheck, stage, and commit. Then push: ```bash git push ``` ### Step 6: Reply to comments on GitHub Reply to each comment using the correct GitHub API endpoints: **For inline review comments** (pull request review comments): ```bash # Reply to an inline review comment (creates a reply in the same thread) gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \ --method POST \ -f body="" ``` **For issue-level comments** (general PR comments): ```bash gh api repos/{owner}/{repo}/issues/{pr}/comments \ --method POST \ -f body="" ``` Reply content: - If fixed: "Fixed in ." (keep it short) - If skipped: Brief explanation of why (1-2 sentences) - Tag the reviewer with `@username` when replying to top-level comments ## Important Notes - **Never guess comment IDs** — always fetch them from the API first - **Test the reply endpoint** — `pulls/{pr}/comments/{id}/replies` is for inline review comment threads. `issues/{pr}/comments` is for general PR comments. - **Don't reply to deploy/CI bots** — skip Vercel deploy previews, CI status comments. DO reply to AI review bots (Claude, CodeRabbit inline suggestions). - **Keep replies concise** — reviewers don't want essays ================================================ FILE: .env.example ================================================ NEXT_PUBLIC_WALLET_CONNECT_ID=12345678901234567890123456789012 NEXT_PUBLIC_RPC_OVERRIDES='{"chain1":{"http":"https://..."}}' # Offchain fee quoting (optional) NEXT_PUBLIC_FEE_QUOTING_URL= # Fee quoting service base URL FEE_QUOTING_API_KEY= # Server-side only API key for fee quoting service # Localhost only; production deployments must use https:// NEXT_PUBLIC_RELAY_API_URL=http://localhost:8900 # Predicate Attestation Support (server-side only, optional) # Required for routes with PredicateRouterWrapper PREDICATE_API_KEY=your_predicate_api_key_here # Optional: custom Predicate API URL (defaults to https://api.predicate.io/v2/attestation) # SECURITY: This value is server-side only and must point to a trusted endpoint. # Do not set this to an arbitrary or user-controlled host — the server-side proxy # enforces HTTPS and an allowlist (api.predicate.io / predicate.io) to prevent SSRF. PREDICATE_API_URL=https://api.predicate.io/v2/attestation # AWS S3 Font Storage (server-side only, used by prebuild script) AWS_ACCESS_KEY_ID=your_access_key_id AWS_SECRET_ACCESS_KEY=your_secret_access_key AWS_S3_BUCKET=your_bucket_name AWS_REGION=us-east-1 ================================================ FILE: .github/CODEOWNERS ================================================ * @xaroz @paulbalaji @xeno097 @troykessler ================================================ FILE: .github/prompts/security-scan.md ================================================ ## Frontend Security Focus Areas This is a Web3 frontend application. Pay special attention to: ### XSS & Content Security - Input sanitization before rendering user data - Dangerous patterns: dangerouslySetInnerHTML, eval(), innerHTML - URL validation (javascript: protocol, data: URLs) - CSP headers and inline script risks ### Web3 Wallet Security - Blind signature attacks (signing data without user understanding) - Transaction simulation before signing - Clear message display before signature requests - Proper origin/domain verification for wallet connections - **Chain-aware address validation** - EVM hex can lowercase; Solana base58/Cosmos bech32 are case-sensitive - **Don't collapse addresses** - Normalizing non-EVM addresses can create security issues ### Dependency & Supply Chain - Known vulnerabilities in dependencies - Malicious packages, typosquatting - Outdated critical security packages ### API & Token Security - CORS configuration - Token storage (avoid localStorage for sensitive tokens) - API key exposure in client-side code ### Private Key Handling - NEVER expose private keys client-side - Check for hardcoded keys or mnemonics - Wallet connection patterns should not request keys ### Content Security Policy - New external resources (scripts, styles, frames) need CSP header updates - Check `next.config.js` for script-src, style-src, connect-src, frame-src - Third-party integrations (Intercom, analytics, wallets) need explicit allowlisting - Test with CSP enabled in production mode ================================================ FILE: .github/workflows/ci.yml ================================================ name: ci on: push: branches: [main, nautilus, nexus, injective, trump, ousdt] pull_request: branches: [main, nautilus, nexus, injective, trump, ousdt] merge_group: workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: | pnpm install --frozen-lockfile CHANGES=$(git status -s) if [[ ! -z $CHANGES ]]; then echo "Changes found: $CHANGES" git diff exit 1 fi - name: Cache build output id: build-cache uses: actions/cache@v4 with: path: .next/ key: ${{ runner.os }}-nextjs-build-${{ hashFiles('src/**', 'pnpm-lock.yaml', 'next.config.js', 'tsconfig.json') }} - name: build if: steps.build-cache.outputs.cache-hit != 'true' run: pnpm run build env: NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.NEXT_PUBLIC_WALLET_CONNECT_ID }} typecheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: typecheck run: pnpm run typecheck format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: format run: | pnpm run format CHANGES=$(git status -s) if [[ ! -z $CHANGES ]]; then echo "Changes found: $CHANGES" exit 1 fi lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: lint run: pnpm run lint test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: test run: pnpm run test e2e: needs: [build] if: >- github.base_ref == 'main' || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' uses: ./.github/workflows/e2e.yml secrets: inherit # Fast wallet-connected E2E subset, gates every PR. e2e-smoke: needs: [build] uses: ./.github/workflows/e2e-smoke.yml secrets: inherit # Full wallet-connected E2E matrix (chromium/firefox/webkit). Main-only so # PRs stay fast; merge_group runs it as a final gate before landing. e2e-wallet-full: needs: [build] if: >- (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' uses: ./.github/workflows/e2e-wallet-full.yml secrets: inherit ================================================ FILE: .github/workflows/claude-code-review.yml ================================================ name: Claude Code Review on: pull_request: types: [opened, synchronize, reopened, ready_for_review] pull_request_review_comment: types: [created] issue_comment: types: [created] env: CLAUDE_OPUS_MODEL: claude-opus-4-7 CLAUDE_SONNET_MODEL: claude-sonnet-4-6 concurrency: group: claude-review-${{ github.event.pull_request.number || github.event.issue.number }} cancel-in-progress: false jobs: code-review: if: | ( github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@claude review') && ( github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR' ) ) || ( github.event_name == 'pull_request' && contains(join(github.event.pull_request.labels.*.name, ','), 'claude-review') && github.event.pull_request.head.repo.full_name == github.repository ) runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: read pull-requests: write issues: write id-token: write steps: - name: Get PR SHA id: pr-sha uses: actions/github-script@v7 with: script: | if (context.eventName === 'issue_comment') { const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); core.setOutput('head_sha', pr.head.sha); } else { core.setOutput('head_sha', context.payload.pull_request.head.sha); } - name: Checkout repository uses: actions/checkout@v6 with: ref: ${{ steps.pr-sha.outputs.head_sha }} fetch-depth: 0 - name: Run Claude Code Review uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} prompt: "Use agent teams to run /claude-review, then use /inline-pr-comments to post findings as a consolidated PR review with inline comments" track_progress: true use_sticky_comment: false claude_args: | --model ${{ env.CLAUDE_OPUS_MODEL }} --allowedTools "Bash(gh api:*)" security-review: if: | ( github.event_name == 'pull_request' && !github.event.pull_request.draft && github.event.pull_request.head.repo.full_name == github.repository ) || ( github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@claude security') && ( github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR' ) ) runs-on: ubuntu-latest timeout-minutes: 20 permissions: contents: read pull-requests: write issues: write id-token: write steps: - name: Get PR SHA id: pr-sha uses: actions/github-script@v7 with: script: | if (context.eventName === 'issue_comment') { const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); core.setOutput('head_sha', pr.head.sha); } else { core.setOutput('head_sha', context.payload.pull_request.head.sha); } - name: Checkout repository uses: actions/checkout@v6 with: ref: ${{ steps.pr-sha.outputs.head_sha }} fetch-depth: 2 - name: Run Claude Security Review uses: anthropics/claude-code-security-review@25e460eb0a12077f0c6a1934d5dbae2f50785dda with: claude-api-key: ${{ secrets.ANTHROPIC_API_KEY }} comment-pr: true upload-results: true exclude-directories: 'node_modules,dist,.next,coverage,cache' claudecode-timeout: '15' claude-model: ${{ env.CLAUDE_OPUS_MODEL }} custom-security-scan-instructions: '.github/prompts/security-scan.md' interactive: if: | ( github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@claude') && !contains(github.event.comment.body, '@claude review') && !contains(github.event.comment.body, '@claude security') && ( github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR' ) ) || ( github.event_name == 'pull_request_review_comment' && github.event.pull_request.head.repo.full_name == github.repository && contains(github.event.comment.body, '@claude') && !contains(github.event.comment.body, '@claude security') && ( github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR' ) ) runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: read pull-requests: write issues: write id-token: write steps: - name: Get PR SHA id: pr-sha uses: actions/github-script@v7 with: script: | let prNumber; if (context.eventName === 'issue_comment') { prNumber = context.issue.number; } else if (context.eventName === 'pull_request_review_comment') { prNumber = context.payload.pull_request.number; } if (prNumber) { const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber }); core.setOutput('head_sha', pr.head.sha); } - name: Checkout repository uses: actions/checkout@v6 with: ref: ${{ steps.pr-sha.outputs.head_sha || github.sha }} fetch-depth: 0 - name: Run Claude Code Assistant uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} track_progress: true use_sticky_comment: false claude_args: "--model ${{ env.CLAUDE_SONNET_MODEL }}" ================================================ FILE: .github/workflows/create-merge-prs.yaml ================================================ name: Merge Main to Other Branches on: push: branches: - main workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: create-merge-prs: runs-on: ubuntu-latest permissions: contents: write pull-requests: write strategy: fail-fast: false matrix: branch: [injective, nexus, trump, ousdt] steps: - name: Generate GitHub App Token id: generate-token uses: actions/create-github-app-token@v2 with: app-id: ${{ secrets.HYPER_GONK_APP_ID }} private-key: ${{ secrets.HYPER_GONK_PRIVATE_KEY }} - name: Checkout repository uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ steps.generate-token.outputs.token }} - name: Get GitHub App User ID id: get-user-id run: echo "user-id=$(gh api /users/${{ steps.generate-token.outputs.app-slug }}[bot] --jq .id)" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} - name: Configure Git for Hyper Gonk run: | git config user.name "${{ steps.generate-token.outputs.app-slug }}[bot]" git config user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com" - name: Check if merge is needed id: check-merge run: | git fetch origin ${{ matrix.branch }} if git merge-base --is-ancestor origin/main origin/${{ matrix.branch }}; then echo "Branch ${{ matrix.branch }} is already up-to-date with main" echo "needs_merge=false" >> $GITHUB_OUTPUT else echo "Branch ${{ matrix.branch }} needs updates from main" echo "needs_merge=true" >> $GITHUB_OUTPUT fi - name: Create and push merge branch if: steps.check-merge.outputs.needs_merge == 'true' id: merge run: | git checkout ${{ matrix.branch }} # Attempt merge, capturing if there are conflicts if git merge origin/main --no-edit; then echo "has_conflicts=false" >> $GITHUB_OUTPUT else echo "::warning::Merge conflict detected for ${{ matrix.branch }}" echo "has_conflicts=true" >> $GITHUB_OUTPUT # Stage all files including conflicts to allow branch creation git add -A git commit --no-edit || true fi git branch -M main-to-${{ matrix.branch }} git push -fu origin main-to-${{ matrix.branch }} env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - name: Create or update PR if: steps.check-merge.outputs.needs_merge == 'true' run: | PR_EXISTS=$(gh pr list --base ${{ matrix.branch }} --head main-to-${{ matrix.branch }} --json number --jq '.[0].number') if [ "${{ steps.merge.outputs.has_conflicts }}" == "true" ]; then BODY="⚠️ **This PR has merge conflicts that need to be resolved manually.** This PR was automatically created to merge changes from \`main\` into \`${{ matrix.branch }}\`." else BODY="This PR was automatically created to merge changes from \`main\` into \`${{ matrix.branch }}\`." fi if [ -z "$PR_EXISTS" ]; then gh pr create \ --base ${{ matrix.branch }} \ --head main-to-${{ matrix.branch }} \ --title "chore: merge main into ${{ matrix.branch }}" \ --body "$BODY" \ --draft echo "Created new PR for ${{ matrix.branch }}" else echo "PR #$PR_EXISTS already exists for ${{ matrix.branch }}" fi env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} ================================================ FILE: .github/workflows/e2e-smoke.yml ================================================ name: e2e-smoke # Fast subset of the wallet-connected E2E suite. Runs on every PR as a gate. # The full wallet E2E matrix runs from .github/workflows/e2e-wallet-full.yml. on: workflow_call: workflow_dispatch: jobs: e2e-smoke: timeout-minutes: 10 runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Restore build cache uses: actions/cache/restore@v4 with: path: .next/ key: ${{ runner.os }}-nextjs-build-${{ hashFiles('src/**', 'pnpm-lock.yaml', 'next.config.js', 'tsconfig.json') }} - name: Get Playwright version id: pw-version run: echo "version=$(pnpm exec playwright --version)" >> $GITHUB_OUTPUT - name: Cache Playwright browsers id: pw-cache uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ steps.pw-version.outputs.version }} - name: Install Playwright browsers if: steps.pw-cache.outputs.cache-hit != 'true' run: pnpm exec playwright install chromium - name: Install Playwright system dependencies run: pnpm exec playwright install-deps chromium - name: Run E2E wallet smoke run: pnpm run test:e2e:wallet:smoke env: NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.NEXT_PUBLIC_WALLET_CONNECT_ID }} - name: Upload report on failure if: failure() uses: actions/upload-artifact@v4 with: name: e2e-smoke-report-${{ github.run_attempt }} path: playwright-report retention-days: 7 ================================================ FILE: .github/workflows/e2e-wallet-full.yml ================================================ name: e2e-wallet-full # Full wallet-connected E2E suite across chromium/firefox/webkit. # Called from ci.yml on push-to-main/merge_group/workflow_dispatch. # On PR, only the smoke subset runs (see e2e-smoke.yml). on: workflow_call: workflow_dispatch: jobs: e2e-wallet: timeout-minutes: 25 runs-on: ubuntu-latest strategy: fail-fast: false matrix: # webkit disabled: prod build serves CSP `upgrade-insecure-requests`, # which webkit strictly honors on localhost (chromium/firefox special- # case it). All chunks get upgraded to https://localhost and fail SSL, # so the app never hydrates. Re-enable once we have a build path that # drops the upgrade directive for e2e without weakening prod CSP. browser: [chromium, firefox] steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Restore build cache uses: actions/cache/restore@v4 with: path: .next/ key: ${{ runner.os }}-nextjs-build-${{ hashFiles('src/**', 'pnpm-lock.yaml', 'next.config.js', 'tsconfig.json') }} - name: Get Playwright version id: pw-version run: echo "version=$(pnpm exec playwright --version)" >> $GITHUB_OUTPUT - name: Cache Playwright browsers id: pw-cache uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ steps.pw-version.outputs.version }}-${{ matrix.browser }} - name: Install Playwright browsers if: steps.pw-cache.outputs.cache-hit != 'true' run: pnpm exec playwright install ${{ matrix.browser }} - name: Install Playwright system dependencies run: pnpm exec playwright install-deps ${{ matrix.browser }} - name: Run E2E wallet tests run: pnpm exec playwright test tests/e2e-wallet --project=${{ matrix.browser }} env: NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.NEXT_PUBLIC_WALLET_CONNECT_ID }} - name: Upload report if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: e2e-wallet-report-${{ matrix.browser }}-${{ github.run_attempt }} path: playwright-report retention-days: 14 notify-failure: if: ${{ always() && needs.e2e-wallet.result == 'failure' }} needs: [e2e-wallet] runs-on: ubuntu-latest steps: - name: Notify Slack on e2e-wallet failure uses: slackapi/slack-github-action@v3 with: webhook: ${{ secrets.E2E_SLACK_WEBHOOK }} webhook-type: incoming-webhook payload: | text: ":alert: Warp UI full e2e-wallet matrix failed — see workflow run for details" blocks: - type: "section" text: type: "mrkdwn" text: ":alert: *Warp UI e2e-wallet full matrix failed*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow run #${{ github.run_number }}> on `${{ github.ref_name }}` @ ${{ github.sha }}" ================================================ FILE: .github/workflows/e2e.yml ================================================ name: e2e on: workflow_call: workflow_dispatch: jobs: build: # Only build when triggered standalone (workflow_dispatch). # When called from ci.yml (workflow_call), the build cache is already populated. if: ${{ github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Cache build output id: build-cache uses: actions/cache@v4 with: path: .next/ key: ${{ runner.os }}-nextjs-build-${{ hashFiles('src/**', 'pnpm-lock.yaml', 'next.config.js', 'tsconfig.json') }} - name: build if: steps.build-cache.outputs.cache-hit != 'true' run: pnpm run build env: NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.NEXT_PUBLIC_WALLET_CONNECT_ID }} e2e: timeout-minutes: 10 needs: [build] if: ${{ !cancelled() && (needs.build.result == 'success' || needs.build.result == 'skipped') }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: shardIndex: [1, 2] shardTotal: [2] steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Restore build cache uses: actions/cache/restore@v4 with: path: .next/ key: ${{ runner.os }}-nextjs-build-${{ hashFiles('src/**', 'pnpm-lock.yaml', 'next.config.js', 'tsconfig.json') }} - name: Get Playwright version id: pw-version run: echo "version=$(pnpm exec playwright --version)" >> $GITHUB_OUTPUT - name: Cache Playwright browsers id: pw-cache uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ steps.pw-version.outputs.version }} - name: Install Playwright browsers if: steps.pw-cache.outputs.cache-hit != 'true' run: pnpm exec playwright install chromium - name: Install Playwright system dependencies run: pnpm exec playwright install-deps chromium - name: Run Playwright tests run: pnpm exec playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - name: Upload blob report if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: blob-report-${{ matrix.shardIndex }} path: blob-report retention-days: 1 merge-reports: if: ${{ !cancelled() }} needs: [e2e] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - name: Install dependencies run: pnpm install --frozen-lockfile - name: Download blob reports uses: actions/download-artifact@v4 with: path: all-blob-reports pattern: blob-report-* merge-multiple: true - name: Merge into HTML report run: pnpm exec playwright merge-reports --reporter html ./all-blob-reports - name: Upload HTML report uses: actions/upload-artifact@v4 with: name: playwright-report-attempt-${{ github.run_attempt }} path: playwright-report retention-days: 14 ================================================ FILE: .github/workflows/update-hyperlane-deps.yml ================================================ name: Update Hyperlane Dependencies on: schedule: # Run weekly on Mondays at 9 AM UTC - cron: '0 9 * * 1' workflow_dispatch: # Allow manual triggering jobs: update-dependencies: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - name: Generate GitHub App Token id: generate-token uses: actions/create-github-app-token@v2 with: app-id: ${{ secrets.HYPER_GONK_APP_ID }} private-key: ${{ secrets.HYPER_GONK_PRIVATE_KEY }} - name: Checkout repository uses: actions/checkout@v6 with: ref: main token: ${{ steps.generate-token.outputs.token }} - name: Get GitHub App User ID id: get-user-id run: echo "user-id=$(gh api /users/${{ steps.generate-token.outputs.app-slug }}[bot] --jq .id)" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} - name: Configure Git run: | git config user.name "${{ steps.generate-token.outputs.app-slug }}[bot]" git config user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com" - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: '22' - name: Get latest SDK, Utils, Registry, and Widgets versions id: get-versions run: | LATEST_SDK=$(pnpm view @hyperlane-xyz/sdk version) LATEST_UTILS=$(pnpm view @hyperlane-xyz/utils version) LATEST_REGISTRY=$(pnpm view @hyperlane-xyz/registry version) LATEST_WIDGETS=$(pnpm view @hyperlane-xyz/widgets version) echo "sdk=$LATEST_SDK" >> $GITHUB_OUTPUT echo "utils=$LATEST_UTILS" >> $GITHUB_OUTPUT echo "registry=$LATEST_REGISTRY" >> $GITHUB_OUTPUT echo "widgets=$LATEST_WIDGETS" >> $GITHUB_OUTPUT echo "Latest SDK: $LATEST_SDK" echo "Latest Utils: $LATEST_UTILS" echo "Latest Registry: $LATEST_REGISTRY" echo "Latest Widgets: $LATEST_WIDGETS" - name: Update package.json with latest versions run: | npm pkg set dependencies.@hyperlane-xyz/sdk=${{ steps.get-versions.outputs.sdk }} npm pkg set dependencies.@hyperlane-xyz/utils=${{ steps.get-versions.outputs.utils }} npm pkg set dependencies.@hyperlane-xyz/registry=${{ steps.get-versions.outputs.registry }} npm pkg set dependencies.@hyperlane-xyz/widgets=${{ steps.get-versions.outputs.widgets }} - name: Install dependencies run: pnpm install --no-frozen-lockfile - name: Check for changes and create PR env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} run: | if ! git diff --quiet; then git checkout -b ci/update-hl-deps git add -A git commit -m "chore: update Hyperlane deps to SDK ${{ steps.get-versions.outputs.sdk }} and Registry ${{ steps.get-versions.outputs.registry }} - Update @hyperlane-xyz/sdk to ${{ steps.get-versions.outputs.sdk }} - Update @hyperlane-xyz/utils to ${{ steps.get-versions.outputs.utils }} - Update @hyperlane-xyz/registry to ${{ steps.get-versions.outputs.registry }} - Update @hyperlane-xyz/widgets to ${{ steps.get-versions.outputs.widgets }}" git push -fu origin ci/update-hl-deps PR_TITLE="chore: update Hyperlane deps" PR_BODY="## Automated Dependency Update This PR updates the Hyperlane dependencies to their latest versions. **Updated versions:** - \`@hyperlane-xyz/sdk\`: \`${{ steps.get-versions.outputs.sdk }}\` - \`@hyperlane-xyz/utils\`: \`${{ steps.get-versions.outputs.utils }}\` - \`@hyperlane-xyz/registry\`: \`${{ steps.get-versions.outputs.registry }}\` - \`@hyperlane-xyz/widgets\`: \`${{ steps.get-versions.outputs.widgets }}\` **Changes include:** - Updated \`package.json\` with latest Hyperlane package versions - Updated \`pnpm-lock.yaml\` via \`pnpm install\` --- 🤖 This PR was automatically generated by the [update-hyperlane-deps workflow](.github/workflows/update-hyperlane-deps.yml)" PR_EXISTS=$(gh pr list --base main --head ci/update-hl-deps --json number --jq length) if [ "$PR_EXISTS" -eq "0" ]; then gh pr create \ --base main \ --head ci/update-hl-deps \ --title "$PR_TITLE" \ --body "$PR_BODY" else echo "Pull request already exists. Updating title and description..." gh pr edit ci/update-hl-deps \ --title "$PR_TITLE" \ --body "$PR_BODY" fi else echo "No changes detected. Skipping PR creation." fi ================================================ FILE: .gitignore ================================================ # dependencies /node_modules cache/ .pnpm-store/ # testing /coverage coverage.json /test/outputs # next.js /.next/ /out/ # production /src/context/*.json /artifacts /build /dist # misc .DS_Store *.pem # debug npm-debug.log* .pnpm-debug.log* # local env files .env* !.env.example # vercel .vercel # typescript *.tsbuildinfo .idea .monorepo-tarballs .opencode .sisyphus # fonts (downloaded via scripts/fetch-fonts.mjs) public/fonts/ # Playwright node_modules/ /test-results/ /playwright-report/ /blob-report/ /playwright/.cache/ /playwright/.auth/ /.playwright-mcp/ ================================================ FILE: .nvmrc ================================================ v24 ================================================ FILE: .oxfmtrc.json ================================================ { "singleQuote": true, "sortImports": {}, "sortTailwindcss": { "functions": ["clsx"] }, "ignorePatterns": ["test/outputs", "public", "LICENSE.md"] } ================================================ FILE: .oxlintrc.json ================================================ { "plugins": ["typescript", "import", "react", "nextjs", "jsx-a11y"], "rules": { "camelcase": "error", "guard-for-in": "error", "import/no-cycle": "error", "import/no-self-import": "error", "no-console": "warn", "no-eval": "error", "no-ex-assign": "error", "no-extra-boolean-cast": "error", "no-constant-condition": "off", "typescript/ban-ts-comment": "off", "typescript/explicit-module-boundary-types": "off", "typescript/no-explicit-any": "off", "typescript/no-non-null-assertion": "off", "typescript/no-require-imports": "warn", "typescript/no-unused-vars": [ "error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "caughtErrorsIgnorePattern": "^_" } ], "no-unused-vars": "off", "nextjs/no-img-element": "off", "jsx-a11y/alt-text": "off", "jsx-a11y/label-has-associated-control": "off" }, "ignorePatterns": [ "node_modules", "dist", "build", "coverage", ".next", "postcss.config.js", "next.config.js", "tailwind.config.js", "sentry.*" ] } ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": ["oxc.oxc-vscode"], "unwantedRecommendations": [] } ================================================ FILE: .vscode/settings.json ================================================ { "search.exclude": { "**/node_modules/**": true }, "files.exclude": { "**/*.js.map": true, "**/*.js": { "when": "$(basename).ts" }, "**/*.map": { "when": "$(basename).map" } }, "files.watcherExclude": { "**/.git/objects/**": true, "**/.git/subtree-cache/**": true, "**/node_modules/*/**": true }, "editor.formatOnSave": true, "editor.tabSize": 2, "editor.detectIndentation": false, "typescript.updateImportsOnFileMove.enabled": "always", "typescript.tsdk": "node_modules/typescript/lib", "[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "[typescriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "[javascript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "[json]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "[jsonc]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "[html]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "[css]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "tailwindCSS.experimental.classRegex": [["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]] } ================================================ FILE: AGENTS.md ================================================ # AGENTS.md **Be extremely concise. Sacrifice grammar for concision. Terse responses preferred. No fluff.** This file provides guidance to AI coding assistants when working with code in this repository. ## Project Overview Hyperlane Warp UI Template is a Next.js web application for cross-chain token transfers using [Hyperlane Warp Routes](https://docs.hyperlane.xyz/docs/reference/applications/warp-routes). It enables permissionless bridging of tokens between any supported blockchain. ## Plan Mode - Make the plan extremely concise. Sacrifice grammar for the sake of concision. - At the end of each plan, give me a list of unresolved questions to answer, if any. ## Development Commands ```bash pnpm install # Install dependencies pnpm dev # Start development server pnpm build # Production build pnpm test # Run tests (vitest) pnpm lint # ESLint check pnpm typecheck # TypeScript type checking pnpm prettier # Format code with Prettier pnpm clean # Remove build artifacts (dist, cache, .next) ``` ## Architecture ### Stack - **Framework**: Next.js 15 with React 18 - **Styling**: Tailwind CSS + Chakra UI - **State**: Zustand with persist middleware (`src/features/store.ts`) - **Queries**: TanStack Query - **Wallets**: Each blockchain uses distinct, composable wallet providers (EVM/RainbowKit, Solana, Cosmos, Starknet, Radix) - **Core Libraries**: `@hyperlane-xyz/sdk`, `@hyperlane-xyz/registry`, `@hyperlane-xyz/widgets`, `@hyperlane-xyz/utils` ### Key Directories - `src/features/` - Core domain logic organized by feature: - `transfer/` - Token transfer flow (form, validation, execution via `useTokenTransfer`) - `tokens/` - Token selection, balances, approvals - `chains/` - Chain metadata, selection UI - `wallet/` - Multi-protocol wallet context providers - `warpCore/` - WarpCore configuration assembly - `store.ts` - Global Zustand store managing WarpContext, transfers, UI state - `src/consts/` - Configuration files: - `config.ts` - App configuration (feature flags, registry settings) - `warpRoutes.yaml` - Warp route token definitions - `chains.yaml` / `chains.ts` - Custom chain metadata - `app.ts` - App branding (name, colors, fonts) - `src/components/` - Reusable UI components - `src/pages/` - Next.js pages (main UI at `index.tsx`) ### Data Flow 1. **Initialization**: `WarpContextInitGate` loads registry and assembles `WarpCore` from warp route configs 2. **State Hydration**: Zustand store rehydrates persisted state (chain overrides, transfer history) 3. **Transfer Flow**: `TransferTokenForm` → `useTokenTransfer` → `WarpCore.getTransferRemoteTxs()` → wallet transaction ### Configuration Environment variables (see `.env.example`): - `NEXT_PUBLIC_WALLET_CONNECT_ID` - **Required** for wallet connections - `NEXT_PUBLIC_REGISTRY_URL` - **Optional** custom Hyperlane registry URL - `NEXT_PUBLIC_RPC_OVERRIDES` - **Optional** JSON map of chain RPC overrides ## Customization See `CUSTOMIZE.md` for detailed customization instructions: - **Warp Routes**: `src/consts/warpRoutes.yaml` + `warpRouteWhitelist.ts` - **Chains**: `src/consts/chains.yaml` or `chains.ts` - **Branding**: `src/consts/app.ts`, `tailwind.config.js`, logo files in `src/images/logos/` - **Feature Flags**: `src/consts/config.ts` (showTipBox, showAddRouteButton, etc.) ## Testing Tests use Vitest and are co-located with source files using the `*.test.ts` naming convention. Vitest automatically discovers and runs all matching test files. ```bash # Run all tests pnpm test # Run a single test file pnpm vitest src/features/transfer/fees.test.ts # Run tests in watch mode pnpm vitest --watch ``` ## Engineering Philosophy ### Keep It Simple We handle ONLY the most important cases. Don't add functionality unless it's small or absolutely necessary. ### Error Handling - **Expected issues** (external systems, user input): Use explicit error handling, try/catch at boundaries - **Unexpected issues** (invalid state, broken invariants): Fail loudly with `throw` or `console.error` - **NEVER** add silent fallbacks for unexpected issues - they mask bugs ### Backwards-Compatibility | Change Location | Backwards-Compat? | Rationale | |-----------------|-------------------|-----------| | Local/uncommitted | No | Iteration speed; no external impact | | In main unreleased | Preferred | Minimize friction for other developers | | Released | Required | Prevent breaking downstream integrations | ## Code Review For code review guidelines, see `REVIEW.md`. ### PR Review Comment Format **Use inline comments** for specific feedback on code changes. Use the GitHub API to post reviews: ```bash gh api repos/{owner}/{repo}/pulls/{pr}/reviews --input - << 'EOF' { "event": "COMMENT", "body": "Overall summary (optional)", "comments": [ {"path": "file.ts", "line": 42, "body": "Specific issue here"}, {"path": "file.ts", "start_line": 10, "line": 15, "body": "Multi-line comment"} ] } EOF ``` | Feedback Type | Where | | -------------------- | --------------------------------------- | | Specific code issue | Inline comment on that line | | Repeated pattern | Inline on first, mention others in body | | Architecture concern | Summary body | **Limitation**: Can only comment on lines in the diff (changed lines). Comments on unchanged code fail. ## Tips for AI Coding Sessions 1. **Run tests incrementally** - `pnpm vitest ` for specific test files 2. **Check existing patterns** - Search codebase for similar implementations 3. **Use SDK types** - Import from `@hyperlane-xyz/sdk`, don't redefine 4. **Zustand for state** - Global state in `src/features/store.ts` 5. **Keep changes minimal** - Only modify what's necessary; avoid scope creep 6. **Feature folders** - Domain logic in `src/features/`, not scattered 7. **Chain-aware addresses** - Only lowercase EVM addresses; Solana/Cosmos are case-sensitive 8. **Check src/utils/** - Functions like `normalizeAddress`, `isNullish` already exist 9. **CSP updates** - New external scripts need `next.config.js` CSP header updates 10. **useQuery patterns** - Use built-in `refetch`, don't create custom refresh state 11. **Flatten conditionals** - Use early returns instead of nested if/else in JSX ## Verify Before Acting **Always search the codebase before assuming.** Don't hallucinate file paths, function names, or patterns. - `grep` or search before claiming "X doesn't exist" - Read the actual file before suggesting changes to it - Check `git log` or blame before assuming why code exists - Verify imports exist in `package.json` before using them ## When the AI Gets It Wrong If output seems wrong, check: 1. **Did I read the actual file?** Or did I assume its contents? 2. **Did I search for existing patterns?** The codebase likely has examples 3. **Am I using stale context?** Re-read files that may have changed 4. **Did I verify the error message?** Run the command and read actual output ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md **Be extremely concise. Sacrifice grammar for concision. Terse responses preferred. No fluff.** This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Hyperlane Warp UI Template is a Next.js web application for cross-chain token transfers using [Hyperlane Warp Routes](https://docs.hyperlane.xyz/docs/reference/applications/warp-routes). It enables permissionless bridging of tokens between any supported blockchain. ## Plan Mode - Make the plan extremely concise. Sacrifice grammar for the sake of concision. - At the end of each plan, give me a list of unresolved questions to answer, if any. ## Development Commands ```bash pnpm install # Install dependencies pnpm dev # Start development server pnpm build # Production build pnpm test # Run tests (vitest) pnpm lint # ESLint check pnpm typecheck # TypeScript type checking pnpm prettier # Format code with Prettier pnpm clean # Remove build artifacts (dist, cache, .next) ``` ## Architecture ### Stack - **Framework**: Next.js 15 with React 18 - **Styling**: Tailwind CSS + Chakra UI - **State**: Zustand with persist middleware (`src/features/store.ts`) - **Queries**: TanStack Query - **Wallets**: Each blockchain uses distinct, composable wallet providers (EVM/RainbowKit, Solana, Cosmos, Starknet, Radix) - **Core Libraries**: `@hyperlane-xyz/sdk`, `@hyperlane-xyz/registry`, `@hyperlane-xyz/widgets`, `@hyperlane-xyz/utils` ### Key Directories - `src/features/` - Core domain logic organized by feature: - `transfer/` - Token transfer flow (form, validation, execution via `useTokenTransfer`) - `tokens/` - Token selection, balances, approvals - `chains/` - Chain metadata, selection UI - `wallet/` - Multi-protocol wallet context providers - `warpCore/` - WarpCore configuration assembly - `store.ts` - Global Zustand store managing WarpContext, transfers, UI state - `src/consts/` - Configuration files: - `config.ts` - App configuration (feature flags, registry settings) - `warpRoutes.yaml` - Warp route token definitions - `chains.yaml` / `chains.ts` - Custom chain metadata - `app.ts` - App branding (name, colors, fonts) - `src/components/` - Reusable UI components - `src/pages/` - Next.js pages (main UI at `index.tsx`) ### Data Flow 1. **Initialization**: `WarpContextInitGate` loads registry and assembles `WarpCore` from warp route configs 2. **State Hydration**: Zustand store rehydrates persisted state (chain overrides, transfer history) 3. **Transfer Flow**: `TransferTokenForm` → `useTokenTransfer` → `WarpCore.getTransferRemoteTxs()` → wallet transaction ### Configuration Environment variables (see `.env.example`): - `NEXT_PUBLIC_WALLET_CONNECT_ID` - **Required** for wallet connections - `NEXT_PUBLIC_REGISTRY_URL` - **Optional** custom Hyperlane registry URL - `NEXT_PUBLIC_RPC_OVERRIDES` - **Optional** JSON map of chain RPC overrides ## Customization See `CUSTOMIZE.md` for detailed customization instructions: - **Warp Routes**: `src/consts/warpRoutes.yaml` + `warpRouteWhitelist.ts` - **Chains**: `src/consts/chains.yaml` or `chains.ts` - **Branding**: `src/consts/app.ts`, `tailwind.config.js`, logo files in `src/images/logos/` - **Feature Flags**: `src/consts/config.ts` (showTipBox, showAddRouteButton, etc.) ## Testing Tests use Vitest and are co-located with source files using the `*.test.ts` naming convention. Vitest automatically discovers and runs all matching test files. ```bash # Run all tests pnpm test # Run a single test file pnpm vitest src/features/transfer/fees.test.ts # Run tests in watch mode pnpm vitest --watch ``` ## Engineering Philosophy ### Keep It Simple We handle ONLY the most important cases. Don't add functionality unless it's small or absolutely necessary. ### Error Handling - **Expected issues** (external systems, user input): Use explicit error handling, try/catch at boundaries - **Unexpected issues** (invalid state, broken invariants): Fail loudly with `throw` or `console.error` - **NEVER** add silent fallbacks for unexpected issues - they mask bugs ### Backwards-Compatibility | Change Location | Backwards-Compat? | Rationale | |-----------------|-------------------|-----------| | Local/uncommitted | No | Iteration speed; no external impact | | In main unreleased | Preferred | Minimize friction for other developers | | Released | Required | Prevent breaking downstream integrations | ## Tips for Claude Code Sessions 1. **Run tests incrementally** - `pnpm vitest ` for specific test files 2. **Check existing patterns** - Search codebase for similar implementations 3. **Use SDK types** - Import from `@hyperlane-xyz/sdk`, don't redefine 4. **Zustand for state** - Global state in `src/features/store.ts` 5. **Keep changes minimal** - Only modify what's necessary; avoid scope creep 6. **Feature folders** - Domain logic in `src/features/`, not scattered 7. **Chain-aware addresses** - Only lowercase EVM addresses; Solana/Cosmos are case-sensitive 8. **Check src/utils/** - Functions like `normalizeAddress`, `isNullish` already exist 9. **CSP updates** - New external scripts need `next.config.js` CSP header updates 10. **useQuery patterns** - Use built-in `refetch`, don't create custom refresh state 11. **Flatten conditionals** - Use early returns instead of nested if/else in JSX ## Verify Before Acting **Always search the codebase before assuming.** Don't hallucinate file paths, function names, or patterns. - `grep` or search before claiming "X doesn't exist" - Read the actual file before suggesting changes to it - Check `git log` or blame before assuming why code exists - Verify imports exist in `package.json` before using them ## When Claude Gets It Wrong If output seems wrong, check: 1. **Did I read the actual file?** Or did I assume its contents? 2. **Did I search for existing patterns?** The codebase likely has examples 3. **Am I using stale context?** Re-read files that may have changed 4. **Did I verify the error message?** Run the command and read actual output ================================================ FILE: CUSTOMIZE.md ================================================ # Customizing tokens and branding Find below instructions for customizing the token list and branding assets of this app. ## Registry By default, the app will use the canonical Hyperlane registry published on NPM. See `package.json` for the precise version. To use custom chains or custom warp routes, you can either configure a different registry using the `NEXT_PUBLIC_REGISTRY_URL` and `NEXT_PUBLIC_REGISTRY_BRANCH` environment variables or define them manually (see the next two sections). ## Custom Warp Route Configs This app requires a set of warp route configs to function. The configs are located in `./src/consts/warpRoutes.yaml` and `./src/consts/warpRoutes.ts`. The output artifacts of a warp route deployment using the [Hyperlane CLI](https://www.npmjs.com/package/@hyperlane-xyz/cli) can be used here. In addition to defining your warp route configs, you can control which routes display in the UI via the `warpRouteWhitelist.ts` file. ## Custom Chain Configs By default, the app will use only the chains that are included in the configured registry and included in your warp routes. To add support for additional chains, or to override a chain's properties (such as RPC URLs), add chain metadata to either `./src/consts/chains.ts` or `./src/consts/chains.yaml`. The same chain configs used in the [Hyperlane CLI](https://www.npmjs.com/package/@hyperlane-xyz/cli) will work here. You may also add an optional `logoURI` field to a chain config to show a custom logo image in the app. ## Default Multi-Collateral Warp Route By default, if there are multiples multi-collateral routes surfacing the same asset, the application will pick the token with the lowest fee and the highest collateral in the destination. You can override this behavior by updating the file `./src/consts/defaultMultiCollateralRoutes.ts` with an object that includes the `chainName`, `collateralAddressOrDenom` (or just `native` as key) and the default `addressOrDenom`. If there is a matching `origin` and `destination`, `getTransferToken` will pick this route as a priority. ## Tip Card The content of the tip card above the form can be customized in `./src/components/tip/TipCard.tsx` Or it can be hidden entirely with the `showTipBox` setting in `./src/consts/config.ts` ## Branding ## App name and description The values to describe the app itself (e.g. to WalletConnect) are in `./src/consts/app.ts` ### Color Scheme To update the color scheme, make changes in the Tailwind config file at `./tailwind.config.js` To modify just the background color, that can be changed in `./src/consts/app.ts` ### Metadata The HTML metadata tags are located in `./src/pages/_document.tsx` ### Title / Name Images The logo images you should change are: - `./src/images/logos/app-logo.svg` - `./src/images/logos/app-name.svg` - `./src/images/logos/app-title.svg` These images are primarily used in the header and footer files: - `./src/components/nav/Header.tsx` - `./src/components/nav/Footer.tsx` ### Social links The links used in the footer can be found here: `./src/consts/links.ts` ### Public assets / Favicons The images and manifest files under `./public` should also be updated. ================================================ FILE: LICENSE.md ================================================ Apache License ============== _Version 2.0, January 2004_ _<>_ ### Terms and Conditions for use, reproduction, and distribution #### 1. Definitions “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means **(i)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the outstanding shares, or **(iii)** beneficial ownership of such entity. “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. #### 2. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. #### 3. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. #### 4. Redistribution You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: * **(a)** You must give any other recipients of the Work or Derivative Works a copy of this License; and * **(b)** You must cause any modified files to carry prominent notices stating that You changed the files; and * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. #### 5. Submission of Contributions Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. #### 6. Trademarks This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. #### 7. Disclaimer of Warranty Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. #### 8. Limitation of Liability In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. #### 9. Accepting Warranty or Additional Liability While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. _END OF TERMS AND CONDITIONS_ ### APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets `[]` replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same “printed page” as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Hyperlane Warp Route UI Template This repo contains an example web interface for interchain tokens built with [Hyperlane Warp Route](https://docs.hyperlane.xyz/docs/reference/applications/warp-routes). Warp is a framework to permissionlessly bridge tokens to any chain. ## Architecture This app is built with Next & React, Wagmi, RainbowKit, and the Hyperlane SDK. - Constants that you may want to change are in `./src/consts/`, see the following Customization section for details. - The index page is located at `./src/pages/index.tsx` - The primary features are implemented in `./src/features/` ## Customization See [CUSTOMIZE.md](./CUSTOMIZE.md) for details about adjusting the tokens and branding of this app. ## Development ### Setup #### Configure You need a `projectId` from the WalletConnect Cloud to run the Hyperlane Warp Route UI. Sign up to [WalletConnect Cloud](https://cloud.walletconnect.com) to create a new project. #### Build ```sh # Install dependencies pnpm install # Build Next project pnpm run build ``` ### Run You can add `.env.local` file next to `.env.example` where you set `projectId` copied from WalletConnect Cloud. ```sh # Start the Next dev server pnpm run dev # Or with a custom projectId NEXT_PUBLIC_WALLET_CONNECT_ID= pnpm run dev ``` ### Test ```sh # Run unit tests pnpm test # Run E2E tests (reuses running dev server, or starts one via Playwright) pnpm test:e2e # Run E2E tests with headed browser and slow motion for debugging SLOW_MO=2000 pnpm test:e2e --headed --workers=1 # Run a single E2E test file with headed browser SLOW_MO=2000 pnpm test:e2e --headed --workers=1 tests/wallet-connect/protocol-wallet-modals.spec.ts # Lint check code pnpm run lint # Check code types pnpm run typecheck ``` ### Format ```sh # Format code using Prettier pnpm run prettier ``` ### Clean / Reset ```sh # Delete build artifacts to start fresh pnpm run clean ``` ### Local package linking to hyperlane-monorepo If you have to make changes to the widgets package to edit e.g. the Connect Button or other components linking the widgets package locally to test it is necessary. To do that you can run the following commands ```sh # Link monorepo packages with the warp-ui pnpm link:monorepo # Unlink packages again after testing pnpm unlink:monorepo ``` ## Embed Widget The Warp UI can be embedded as an iframe on any website, giving your users a bridge experience directly in your app. ### Setup Once deployed (e.g., to Vercel), the embed is available at `/embed`: ```html