Showing preview only (1,018K chars total). Download the full file or copy to clipboard to get everything.
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 <noreply@anthropic.com>`
- 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 <specific-files> # 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="<reply text>"
```
**For issue-level comments** (general PR comments):
```bash
gh api repos/{owner}/{repo}/issues/{pr}/comments \
--method POST \
-f body="<reply text>"
```
Reply content:
- If fixed: "Fixed in <commit_sha>." (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 <file>` 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 <file>` 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_
_<<http://www.apache.org/licenses/>>_
### 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=<projectId> 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
<iframe
src="https://your-domain.com/embed"
width="420"
height="600"
style="border: none; border-radius: 12px;"
/>
```
You can pre-select transfer routes using query params:
```text
/embed?origin=ethereum&destination=arbitrum&originToken=USDC&destinationToken=USDC
```
### Theme Customization
Customize colors via URL params (hex values without `#`):
| Param | Description | Default (light) |
| ------------ | ---------------------------------------------- | --------------- |
| `accent` | Primary/accent color (buttons, headers, links) | `9A0DFF` |
| `bg` | Page background | transparent |
| `card` | Card/surface background | `ffffff` |
| `text` | Text color | `010101` |
| `buttonText` | Button text color | `ffffff` |
| `border` | Border color | `BFBFBF40` |
| `error` | Error state color | `dc2626` |
| `mode` | `dark` or `light` — applies preset defaults | `light` |
**Examples:**
```html
<!-- Blue accent -->
<iframe src="https://your-domain.com/embed?accent=3b82f6" ... />
<!-- Dark mode with green accent -->
<iframe src="https://your-domain.com/embed?mode=dark&accent=22c55e" ... />
<!-- Fully custom theme -->
<iframe
src="https://your-domain.com/embed?bg=0f172a&card=1e293b&text=e2e8f0&accent=8b5cf6&buttonText=ffffff&border=334155"
...
/>
```
### PostMessage Events
The embed page sends events to the parent window via `postMessage`:
```js
window.addEventListener('message', (event) => {
if (event.data?.type !== 'hyperlane-warp-widget') return;
const { type, payload } = event.data.event;
if (type === 'ready') {
console.log('Widget ready at', payload.timestamp);
}
});
```
### Solving CSP Issues
If your site has a Content Security Policy that blocks iframes, you'll need to allow the Warp UI origin in your CSP `frame-src` directive:
```text
Content-Security-Policy: frame-src https://your-warp-ui-domain.com;
```
For sites where you can't modify CSP headers (e.g., WordPress, Shopify), check if the platform has an iframe allowlist setting, or use a reverse proxy to serve the embed from your own domain.
If you self-host the Warp UI and want to restrict which sites can embed it, set the `NEXT_PUBLIC_EMBED_ALLOWED_ORIGINS` environment variable:
```text
NEXT_PUBLIC_EMBED_ALLOWED_ORIGINS=https://app-a.com https://app-b.com
```
If not set, any site can embed the widget (default: `*`).
## Deployment
The easiest hosting solution for this Next.JS app is to create a project on Vercel.
## Learn more
For more information, see the [Hyperlane documentation](https://docs.hyperlane.xyz/docs/protocol/warp-routes/warp-routes-overview).
================================================
FILE: REVIEW.md
================================================
# Code Review Guidelines
## Code Quality
- Logic errors and potential bugs
- Error handling and edge cases
- Code clarity and maintainability
- Adherence to existing patterns in the codebase
- **Use existing utilities** - Search codebase before adding new helpers
- **Prefer `??` over `||`** - Preserves zero/empty string as valid values
## Architecture
- Consistency with existing architecture patterns
- Breaking changes or backward compatibility issues
- API contract changes
- **Deduplicate** - Move repeated code/types to shared files
- **Extract utilities** - Shared functions belong in utils packages
## Testing
- Test coverage for new/modified code
- Edge cases that should be tested
- **New utility functions need unit tests**
## Performance
- Unnecessary re-renders or computations
- Bundle size impact of new dependencies
## Frontend-Specific
- **Use existing utilities** - Check `src/utils/` before adding (normalizeAddress, etc.)
- **Chain-aware addresses** - Only lowercase EVM hex; Solana/Cosmos are case-sensitive
- **CSP updates required** - New external scripts/styles need `next.config.js` CSP updates
- **Avoid floating promises** - In useEffect, use IIFE or separate async function
- **Use useQuery refetch** - Don't reinvent; use built-in refetch from TanStack Query
- **Flatten rendering logic** - Avoid nested if; use early returns instead
- **Zustand patterns** - Follow existing store patterns in `src/features/store.ts`
- **Constants outside functions** - Move config/constants outside component functions
================================================
FILE: next-env.d.ts
================================================
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
================================================
FILE: next.config.js
================================================
/** @type {import('next').NextConfig} */
const { version } = require('./package.json');
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
const isDev = process.env.NODE_ENV !== 'production';
// Sometimes useful to disable this during development
const ENABLE_CSP_HEADER = true;
const FRAME_SRC_HOSTS = [
'https://*.walletconnect.com',
'https://*.walletconnect.org',
'https://cdn.solflare.com',
'https://js.refiner.io',
'https://intercom-sheets.com',
'https://intercom-reporting.com',
];
const STYLE_SRC_HOSTS = ['https://js.refiner.io', 'https://storage.refiner.io'];
const IMG_SRC_HOSTS = [
'https://*.walletconnect.com',
'https://*.githubusercontent.com',
'https://cdn.jsdelivr.net/gh/hyperlane-xyz/hyperlane-registry@main/',
'https://js.refiner.io',
'https://storage.refiner.io',
'https://js.intercomcdn.com',
'https://static.intercomassets.com',
'https://downloads.intercomcdn.com',
'https://uploads.intercomusercontent.com',
'https://gifs.intercomcdn.com',
];
const SCRIPT_SRC_HOSTS = [
'https://snaps.consensys.io',
'https://js.refiner.io',
'https://app.intercom.io',
'https://widget.intercom.io',
'https://js.intercomcdn.com',
];
const MEDIA_SRC_HOSTS = [
'https://js.refiner.io',
'https://storage.refiner.io',
'https://js.intercomcdn.com',
'https://downloads.intercomcdn.com',
];
const cspHeader = `
default-src 'self';
script-src 'self' 'wasm-unsafe-eval'${isDev ? " 'unsafe-eval'" : ''} ${SCRIPT_SRC_HOSTS.join(' ')};
style-src 'self' 'unsafe-inline' ${STYLE_SRC_HOSTS.join(' ')};
connect-src *;
img-src 'self' blob: data: ${IMG_SRC_HOSTS.join(' ')};
font-src 'self' data: https://js.intercomcdn.com https://fonts.intercomcdn.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-src 'self' ${FRAME_SRC_HOSTS.join(' ')};
frame-ancestors 'none';
media-src 'self' ${MEDIA_SRC_HOSTS.join(' ')};
${!isDev ? 'block-all-mixed-content;' : ''}
${!isDev ? 'upgrade-insecure-requests;' : ''}
`
.replace(/\s{2,}/g, ' ')
.trim();
const securityHeaders = [
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
// Note, causes a problem for firefox: https://github.com/MetaMask/metamask-extension/issues/3133
...(ENABLE_CSP_HEADER
? [
{
key: 'Content-Security-Policy',
value: cspHeader,
},
]
: []),
];
// Embed page headers: allow framing from specified origins (default: any)
// Accepts space or comma-separated origins, e.g. "https://a.com https://b.com" or "https://a.com,https://b.com"
const rawEmbedAllowedOrigins = process.env.NEXT_PUBLIC_EMBED_ALLOWED_ORIGINS ?? '*';
const embedAllowedOrigins = rawEmbedAllowedOrigins
.split(/[,\s]+/)
.map((origin) => origin.trim())
.filter(Boolean);
if (embedAllowedOrigins.length === 0) {
throw new Error('Invalid NEXT_PUBLIC_EMBED_ALLOWED_ORIGINS: no valid origins provided');
}
if (embedAllowedOrigins.some((origin) => /[;\r\n]/.test(origin))) {
throw new Error('Invalid NEXT_PUBLIC_EMBED_ALLOWED_ORIGINS: contains forbidden characters');
}
const embedCspHeader = cspHeader.replace(
"frame-ancestors 'none'",
`frame-ancestors ${embedAllowedOrigins.join(' ')}`,
);
const embedSecurityHeaders = [
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
// No X-Frame-Options — allow embedding
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
...(ENABLE_CSP_HEADER
? [
{
key: 'Content-Security-Policy',
value: embedCspHeader,
},
]
: []),
];
const nextConfig = {
// Disable the dev-tools indicator/portal in dev when running under the
// e2e harness — its <nextjs-portal> shadow DOM intermittently intercepts
// pointer events during picker clicks (observed flake on
// `token-select-destination` in full-suite runs). Scope to an explicit
// env var so local dev UX is unchanged.
...(process.env.DISABLE_NEXT_DEV_INDICATORS === '1' ? { devIndicators: false } : {}),
turbopack: {
rules: {
'*.yaml': {
loaders: ['yaml-loader'],
as: '*.js',
},
'*.yml': {
loaders: ['yaml-loader'],
as: '*.js',
},
},
resolveAlias: {
// Only shim pino on SSR (Node) where its transport/worker resolution breaks
// under Turbopack. In the browser use the real pino browser build, which
// exports `levels` that @walletconnect/logger depends on.
pino: {
browser: 'pino/browser.js',
default: './src/utils/pino-noop.js',
},
},
},
async headers() {
return [
{
source: '/embed',
headers: embedSecurityHeaders,
},
{
source: '/((?!embed$).*)',
headers: securityHeaders,
},
];
},
env: {
NEXT_PUBLIC_VERSION: version,
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN || '',
},
reactStrictMode: true,
serverExternalPackages: ['@sentry/nextjs'],
// Exclude heavy client-only chain SDKs from serverless function file tracing.
// These packages are only used client-side and not needed in serverless functions.
// Note: @sentry and @opentelemetry are kept for server-side instrumentation (see instrumentation.ts).
outputFileTracingExcludes: {
'*': [
'./node_modules/@provablehq/**',
'./node_modules/@radixdlt/**',
'./node_modules/@solana/**',
'./node_modules/@cosmjs/**',
'./node_modules/@starknet-io/**',
'./node_modules/ethers/**',
],
},
experimental: {
turbopackFileSystemCacheForBuild: true,
parallelServerCompiles: true,
parallelServerBuildTraces: true,
optimizePackageImports: [
'@hyperlane-xyz/registry',
'@hyperlane-xyz/sdk',
'@hyperlane-xyz/utils',
'@hyperlane-xyz/widgets',
],
},
// Skip type checking during builds — CI runs these separately
typescript: { ignoreBuildErrors: true },
};
module.exports = withBundleAnalyzer(nextConfig);
================================================
FILE: package.json
================================================
{
"name": "@hyperlane-xyz/warp-ui-template",
"version": "13.0.0",
"private": true,
"description": "A web app template for building Hyperlane Warp Route UIs",
"homepage": "https://www.hyperlane.xyz",
"license": "Apache-2.0",
"author": "J M Rossy",
"repository": {
"type": "git",
"url": "https://github.com/hyperlane-xyz/hyperlane-warp-ui-template"
},
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"scripts": {
"clean": "rm -rf dist cache .next",
"predev": "node scripts/fetch-fonts.mjs",
"dev": "next dev",
"prebuild": "node scripts/fetch-fonts.mjs",
"fetch-fonts": "node --env-file-if-exists=.env.local scripts/fetch-fonts.mjs",
"build": "next build",
"typecheck": "tsc",
"lint": "oxlint ./src",
"start": "next start",
"test": "vitest --watch false",
"test:e2e": "playwright test --project=chromium",
"test:e2e:wallet": "playwright test tests/e2e-wallet --project=chromium",
"test:e2e:wallet:smoke": "playwright test tests/e2e-wallet/smoke/gate.spec.ts tests/e2e-wallet/autoconnect/evm.spec.ts tests/e2e-wallet/balance-display/evm.spec.ts tests/e2e-wallet/autoconnect/solana.spec.ts --project=chromium",
"format": "oxfmt ./src",
"link:monorepo": "node scripts/link-monorepo.js",
"unlink:monorepo": "node scripts/unlink-monorepo.js"
},
"dependencies": {
"@chakra-ui/next-js": "^2.4.2",
"@chakra-ui/provider": "^2.4.2",
"@chakra-ui/react": "^2.8.2",
"@chakra-ui/system": "^2.6.2",
"@chakra-ui/theme-utils": "^2.0.21",
"@cosmjs/cosmwasm-stargate": "^0.32.4",
"@cosmjs/proto-signing": "^0.32.4",
"@cosmjs/stargate": "^0.32.4",
"@cosmos-kit/core": "^2.13.1",
"@cosmos-kit/cosmostation": "^2.11.2",
"@cosmos-kit/keplr": "^2.12.2",
"@cosmos-kit/leap": "^2.12.2",
"@cosmos-kit/react": "2.18.0",
"@drift-labs/snap-wallet-adapter": "^0.3.0",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@headlessui/react": "^2.2.0",
"@hyperlane-xyz/registry": "24.3.0",
"@hyperlane-xyz/sdk": "33.1.0",
"@hyperlane-xyz/utils": "33.1.0",
"@hyperlane-xyz/widgets": "33.1.0",
"@interchain-ui/react": "^1.23.28",
"@intercom/messenger-js-sdk": "^0.0.18",
"@metamask/post-message-stream": "6.1.2",
"@metamask/providers": "10.2.1",
"@provablehq/aleo-wallet-adaptor-react": "0.3.0-alpha.1",
"@provablehq/aleo-wallet-adaptor-shield": "0.3.0-alpha.1",
"@radixdlt/babylon-gateway-api-sdk": "^1.10.1",
"@radixdlt/radix-dapp-toolkit": "^2.2.1",
"@rainbow-me/rainbowkit": "2.2.10",
"@sentry/browser": "8.38.0",
"@sentry/core": "8.38.0",
"@sentry/nextjs": "^8.38.0",
"@sentry/react": "8.38.0",
"@solana/spl-token": "^0.4.9",
"@solana/wallet-adapter-backpack": "^0.1.14",
"@solana/wallet-adapter-base": "^0.9.22",
"@solana/wallet-adapter-ledger": "^0.9.29",
"@solana/wallet-adapter-phantom": "^0.9.28",
"@solana/wallet-adapter-react": "^0.15.32",
"@solana/wallet-adapter-react-ui": "^0.9.31",
"@solana/wallet-adapter-salmon": "^0.1.18",
"@solana/wallet-adapter-solflare": "^0.6.32",
"@solana/wallet-adapter-trust": "^0.1.17",
"@solana/wallet-adapter-wallets": "0.19.16",
"@solana/web3.js": "^1.95.4",
"@starknet-react/chains": "^3.1.2",
"@starknet-react/core": "^3.7.2",
"@tanstack/query-core": "^5.90.12",
"@tanstack/react-query": "^5.59.20",
"@tronweb3/tronwallet-abstract-adapter": "^1.1.12",
"@tronweb3/tronwallet-adapter-react-hooks": "^1.1.11",
"@tronweb3/tronwallet-adapter-tronlink": "^1.1.15",
"@vercel/analytics": "^1.4.0",
"@vercel/functions": "^1.5.0",
"axios": "^1.7.9",
"bignumber.js": "^9.1.2",
"buffer": "^6.0.3",
"clsx": "^2.1.1",
"cosmjs-types": "^0.9.0",
"formik": "^2.4.6",
"framer-motion": "^12.38.0",
"next": "^16.2.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-toastify": "^10.0.6",
"refiner-js": "1.2.4",
"starknetkit": "2.6.1",
"viem": "^2.21.41",
"wagmi": "^2.12.26",
"zod": "3.21.4",
"zustand": "^4.4.7"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.967.0",
"@next/bundle-analyzer": "^16.2.1",
"@playwright/test": "^1.58.2",
"@types/node": "^24.10.9",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"autoprefixer": "^10.4.20",
"oxfmt": "0.42.0",
"oxlint": "1.57.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.15",
"ts-node": "^10.9.2",
"typescript": "6.0.2",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "3.0.5",
"yaml": "^2.6.0",
"yaml-loader": "^0.8.1"
},
"engines": {
"node": ">=24"
},
"packageManager": "pnpm@10.25.0",
"pnpm": {
"overrides": {
"tronweb>ethers": "^6.13.5",
"@solana/web3.js": "^1.95.4",
"axios": "^1.7.9",
"bignumber": "9.1.2",
"bn.js": "^5.2",
"cosmjs-types": "0.9",
"ethers": "^5.8.0",
"globals": "^14.0.0",
"lit-html": "2.8.0",
"react-fast-compare": "^3.2",
"viem": "^2.21.41",
"zustand": "^4.4",
"sha.js": "2.4.12",
"cipher-base": "1.0.5",
"elliptic": "6.6.1",
"pbkdf2": "3.1.3",
"form-data": "4.0.4",
"@ledgerhq/errors": "6.31.0"
},
"patchedDependencies": {
"starknetkit@2.6.1": "patches/starknetkit@2.6.1.patch",
"@provablehq/wasm@0.9.18": "patches/@provablehq__wasm@0.9.18.patch",
"@provablehq/sdk@0.9.15": "patches/@provablehq__sdk@0.9.15.patch"
},
"onlyBuiltDependencies": []
}
}
================================================
FILE: patches/@provablehq__sdk@0.9.15.patch
================================================
diff --git a/dist/mainnet/browser.js b/dist/mainnet/browser.js
index 166fc0b9e40569f8b33ecb3be7b170d3010a2d2e..8ab95f815dbee39886dd042f055adb552d0d6c26 100644
--- a/dist/mainnet/browser.js
+++ b/dist/mainnet/browser.js
@@ -466,7 +466,29 @@ async function retryWithBackoff(fn, { maxAttempts = 5, baseDelay = 100, jitter,
throw new Error("retryWithBackoff: unreachable");
}
-const KEY_STORE = Metadata.baseUrl();
+const IS_BROWSER_RUNTIME = (typeof window !== "undefined" && typeof document !== "undefined") || (typeof WorkerGlobalScope !== "undefined" && typeof self !== "undefined" && self instanceof WorkerGlobalScope);
+const SSR_CREDITS_PROGRAM_KEY = {
+ name: "",
+ locator: "",
+ prover: "",
+ verifier: "",
+ verifyingKey() {
+ throw new Error("Aleo browser runtime is unavailable during SSR");
+ },
+};
+const SSR_CREDITS_PROGRAM_KEYS = new Proxy({
+ getKey() {
+ return SSR_CREDITS_PROGRAM_KEY;
+ },
+}, {
+ get(target, prop, receiver) {
+ if (prop in target) {
+ return Reflect.get(target, prop, receiver);
+ }
+ return SSR_CREDITS_PROGRAM_KEY;
+ },
+});
+const KEY_STORE = IS_BROWSER_RUNTIME ? Metadata.baseUrl() : "";
function convert(metadata) {
// This looks up the method name in VerifyingKey
const verifyingKey = VerifyingKey[metadata.verifyingKey];
@@ -481,7 +503,7 @@ function convert(metadata) {
verifyingKey,
};
}
-const CREDITS_PROGRAM_KEYS = {
+const CREDITS_PROGRAM_KEYS = IS_BROWSER_RUNTIME ? {
bond_public: convert(Metadata.bond_public()),
bond_validator: convert(Metadata.bond_validator()),
claim_unbond_public: convert(Metadata.claim_unbond_public()),
@@ -505,7 +527,7 @@ const CREDITS_PROGRAM_KEYS = {
throw new Error(`Key "${key}" not found.`);
}
}
-};
+} : SSR_CREDITS_PROGRAM_KEYS;
const PRIVATE_TRANSFER_TYPES = new Set([
"transfer_private",
"private",
@@ -3666,7 +3688,13 @@ class RecordScanner {
* ```
*/
class SealanceMerkleTree {
- static hasher = new Poseidon4();
+ static _hasher;
+ static get hasher() {
+ if (!this._hasher) {
+ this._hasher = new Poseidon4();
+ }
+ return this._hasher;
+ }
/**
* Converts an Aleo blockchain address to a field element.
*
diff --git a/dist/testnet/browser.js b/dist/testnet/browser.js
index df2a22058c16e27b3a5878727d0e80e0f724c86c..029b7542d9a8014ff1bd04e3f2abb5b8d0801a79 100644
--- a/dist/testnet/browser.js
+++ b/dist/testnet/browser.js
@@ -466,7 +466,29 @@ async function retryWithBackoff(fn, { maxAttempts = 5, baseDelay = 100, jitter,
throw new Error("retryWithBackoff: unreachable");
}
-const KEY_STORE = Metadata.baseUrl();
+const IS_BROWSER_RUNTIME = (typeof window !== "undefined" && typeof document !== "undefined") || (typeof WorkerGlobalScope !== "undefined" && typeof self !== "undefined" && self instanceof WorkerGlobalScope);
+const SSR_CREDITS_PROGRAM_KEY = {
+ name: "",
+ locator: "",
+ prover: "",
+ verifier: "",
+ verifyingKey() {
+ throw new Error("Aleo browser runtime is unavailable during SSR");
+ },
+};
+const SSR_CREDITS_PROGRAM_KEYS = new Proxy({
+ getKey() {
+ return SSR_CREDITS_PROGRAM_KEY;
+ },
+}, {
+ get(target, prop, receiver) {
+ if (prop in target) {
+ return Reflect.get(target, prop, receiver);
+ }
+ return SSR_CREDITS_PROGRAM_KEY;
+ },
+});
+const KEY_STORE = IS_BROWSER_RUNTIME ? Metadata.baseUrl() : "";
function convert(metadata) {
// This looks up the method name in VerifyingKey
const verifyingKey = VerifyingKey[metadata.verifyingKey];
@@ -481,7 +503,7 @@ function convert(metadata) {
verifyingKey,
};
}
-const CREDITS_PROGRAM_KEYS = {
+const CREDITS_PROGRAM_KEYS = IS_BROWSER_RUNTIME ? {
bond_public: convert(Metadata.bond_public()),
bond_validator: convert(Metadata.bond_validator()),
claim_unbond_public: convert(Metadata.claim_unbond_public()),
@@ -505,7 +527,7 @@ const CREDITS_PROGRAM_KEYS = {
throw new Error(`Key "${key}" not found.`);
}
}
-};
+} : SSR_CREDITS_PROGRAM_KEYS;
const PRIVATE_TRANSFER_TYPES = new Set([
"transfer_private",
"private",
@@ -3666,7 +3688,13 @@ class RecordScanner {
* ```
*/
class SealanceMerkleTree {
- static hasher = new Poseidon4();
+ static _hasher;
+ static get hasher() {
+ if (!this._hasher) {
+ this._hasher = new Poseidon4();
+ }
+ return this._hasher;
+ }
/**
* Converts an Aleo blockchain address to a field element.
*
================================================
FILE: patches/@provablehq__wasm@0.9.18.patch
================================================
diff --git a/dist/mainnet/index.js b/dist/mainnet/index.js
index 1988ed1acaabfdd5238dd9729678033d1978aacd..3ff88a8fc24bcae8425ce764dc44814497e5734c 100644
--- a/dist/mainnet/index.js
+++ b/dist/mainnet/index.js
@@ -14222,8 +14222,11 @@ async function __wbg_init(module_or_path, memory) {
}
const module$1 = new URL("aleo_wasm.wasm", import.meta.url);
-
- await __wbg_init({ module_or_path: module$1 });
+const shouldAutoInit = (typeof window !== "undefined" && typeof document !== "undefined") || (typeof WorkerGlobalScope !== "undefined" && typeof self !== "undefined" && self instanceof WorkerGlobalScope);
+
+if (shouldAutoInit) {
+ await __wbg_init({ module_or_path: module$1 });
+}
async function initThreadPool(threads) {
if (threads == null) {
diff --git a/dist/testnet/index.js b/dist/testnet/index.js
index 180e9b86e0c8c2b579504bac26fa57e04e93275a..a3fc1b0dbb13a31872810cf2fa1d6bbfdc5881ac 100644
--- a/dist/testnet/index.js
+++ b/dist/testnet/index.js
@@ -14222,8 +14222,11 @@ async function __wbg_init(module_or_path, memory) {
}
const module$1 = new URL("aleo_wasm.wasm", import.meta.url);
-
- await __wbg_init({ module_or_path: module$1 });
+const shouldAutoInit = (typeof window !== "undefined" && typeof document !== "undefined") || (typeof WorkerGlobalScope !== "undefined" && typeof self !== "undefined" && self instanceof WorkerGlobalScope);
+
+if (shouldAutoInit) {
+ await __wbg_init({ module_or_path: module$1 });
+}
async function initThreadPool(threads) {
if (threads == null) {
================================================
FILE: patches/starknetkit@2.6.1.patch
================================================
diff --git a/dist/starknetkit.js b/dist/starknetkit.js
index 9fee9301ba99263b32be70b492787671eb9a2d26..6cfeb4756cdb7e2ddcd39f3b292d0b123fc56a2b 100644
--- a/dist/starknetkit.js
+++ b/dist/starknetkit.js
@@ -4563,7 +4563,7 @@ const mapModalWallets = ({
if (d) {
const g = d.id === "argentX" ? { light: ARGENT_X_ICON, dark: ARGENT_X_ICON } : isString(d.icon) ? { light: d.icon, dark: d.icon } : d.icon;
return {
- name: d.name,
+ name: d.id === "argentX" ? "Ready Wallet (formerly Argent)" : d.name,
id: d.id,
icon: g,
connector: c
@@ -4575,7 +4575,7 @@ const mapModalWallets = ({
if (m) {
const { downloads: g } = m, p = m.id === "argentX" ? ARGENT_X_ICON : m.icon;
return {
- name: m.name,
+ name: m.id === "argentX" ? "Ready Wallet (formerly Argent)" : m.name,
id: m.id,
icon: { light: p, dark: p },
connector: c,
================================================
FILE: playwright.config.ts
================================================
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? [['blob'], ['list']] : 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
/* Slow down actions for visual debugging. Set SLOW_MO=2000 to add 2s delay. */
launchOptions: {
slowMo: parseInt(process.env.SLOW_MO || '0'),
},
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: {
// `pnpm dev` is fast to iterate against but renders Next's <nextjs-portal>
// dev-tools web component, which intermittently intercepts picker clicks.
// CI always uses the prod build via `pnpm start`; set E2E_USE_PROD=1
// locally to match CI if you're chasing dev-only flakes.
command: process.env.CI || process.env.E2E_USE_PROD === '1' ? 'pnpm start' : 'pnpm dev',
url: 'http://localhost:3000',
timeout: 180_000,
reuseExistingServer: !process.env.CI,
// Kill the Next dev-tools <nextjs-portal> so picker clicks aren't
// intermittently intercepted in dev. CI uses the prod build where the
// portal doesn't render, so this only matters locally.
env: { DISABLE_NEXT_DEV_INDICATORS: '1' },
},
});
================================================
FILE: pnpm-workspace.yaml
================================================
# Reject packages published less than 7 days ago (supply chain protection)
minimumReleaseAge: 10080
minimumReleaseAgeExclude:
- '@hyperlane-xyz/*'
================================================
FILE: postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: public/.well-known/radix.json
================================================
{
"dApps": [
{
"dAppDefinitionAddress": "account_rdx12ycz0wsuygqa5slye9du6e7wz7fr4pzx39l5r5cznqc6yudpks20cw"
}
]
}
================================================
FILE: public/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
================================================
FILE: public/site.webmanifest
================================================
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
================================================
FILE: public/theme-init.js
================================================
(() => {
const getSystemTheme = () => {
try {
if (typeof window.matchMedia !== 'function') return 'light';
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
} catch {
return 'light';
}
};
let storedTheme = null;
try {
// Must match UI_THEME_STORAGE_KEY in src/consts/app.ts.
// This script runs before TS modules load, so keep this literal in sync manually.
storedTheme = window.localStorage.getItem('warp-ui-theme');
} catch {
// Ignore read errors (e.g. privacy mode) and fallback to system theme.
}
const themeMode = storedTheme === 'dark' || storedTheme === 'light' ? storedTheme : getSystemTheme();
document.documentElement.dataset.themeMode = themeMode;
})();
================================================
FILE: scripts/README.md
================================================
# Development Scripts
## link-monorepo.js
Links local Hyperlane monorepo packages for development using `pnpm pack` (creates tarball archives).
### Usage
```bash
# Link specific packages
pnpm link:monorepo sdk utils widgets
# Or use node directly
node scripts/link-monorepo.js sdk utils widgets tron-sdk
```
### What it does
1. **Checks monorepo setup**: Verifies the monorepo exists at `../hyperlane-monorepo`
2. **Builds entire monorepo**: Runs `pnpm build` from the monorepo root to ensure all packages and dependencies are built in the correct order
3. **Packs each package**: Runs `pnpm pack` in each specified package to create a `.tgz` tarball
4. **Updates package.json**: Changes dependency references to `file:../hyperlane-monorepo/typescript/<package>/<tarball>.tgz`
5. **Adds pnpm overrides**: Ensures all references (including transitive dependencies) use the packed versions
6. **Cleans and reinstalls**: Removes `node_modules` and `pnpm-lock.yaml`, then runs `pnpm install`
### Why pnpm pack?
**pnpm pack** creates a tarball (`.tgz` file) that:
- **Contains only published files**: Respects the `files` field in `package.json`, just like npm publish
- **Resolves workspace dependencies**: All `workspace:*` and `catalog:` references are resolved to actual versions
- **No symlinks**: Avoids issues with multiple React instances or module resolution problems
- **Works with any package manager**: The tarball can be used with pnpm, npm, or yarn
- **Fast iteration**: Just `pnpm pack` and `pnpm install` to update
This approach is simpler and more reliable than yalc for monorepos with pnpm catalogs.
### Requirements
- The Hyperlane monorepo must be located at `../hyperlane-monorepo`
- Packages must be in `../hyperlane-monorepo/typescript/<package-name>/`
- No additional global tools required (uses built-in pnpm commands)
### Common packages to link
- `sdk` - @hyperlane-xyz/sdk
- `utils` - @hyperlane-xyz/utils
- `widgets` - @hyperlane-xyz/widgets
- `registry` - @hyperlane-xyz/registry
- `deploy-sdk` - @hyperlane-xyz/deploy-sdk
- `tron-sdk` - @hyperlane-xyz/tron-sdk (unpublished)
- `provider-sdk` - @hyperlane-xyz/provider-sdk
### Development workflow
```bash
# 1. Link packages you want to develop
pnpm link:monorepo sdk utils widgets tron-sdk
# 2. Make changes in ../hyperlane-monorepo/typescript/sdk
# 3. Rebuild the monorepo (or just the package)
cd ../hyperlane-monorepo
pnpm build
# OR just rebuild the specific package:
# cd typescript/sdk && pnpm build
# 4. Re-pack the changed package
cd typescript/sdk
pnpm pack
# 5. Reinstall in your React app
cd ../../../hyperlane-warp-ui-template
pnpm install
# 6. Your React app is now using the updated package!
```
### Quick update workflow
For faster iteration after the initial link:
```bash
# In monorepo package directory
cd ../hyperlane-monorepo/typescript/sdk
pnpm build && pnpm pack && cd - && pnpm install
```
### Unlinking
To clean up packed packages and restore published versions:
```bash
pnpm unlink:monorepo
```
This will:
1. Find all dependencies pointing to packed tarballs
2. Automatically restore dependencies to their latest published versions from npm (using `npm view`)
3. Remove pnpm overrides for packed packages
4. Clean `node_modules`, lockfile, and the `.monorepo-tarballs/` directory
5. Run `pnpm install`
The script automatically fetches the latest version for each linked package from the npm registry and updates `package.json` accordingly.
## unlink-monorepo.js
Removes packed packages and cleans up overrides.
### Usage
```bash
pnpm unlink:monorepo
```
### Troubleshooting
If you encounter issues:
1. **Check monorepo location**: Ensure `../hyperlane-monorepo` exists
2. **Build errors**: Make sure the monorepo builds successfully with `cd ../hyperlane-monorepo && pnpm build`
3. **Tarball not found**: The package might not have packed correctly - check for `.tgz` files in the package directory
4. **Install fails**: Try manually deleting `node_modules` and `pnpm-lock.yaml`, then run `pnpm install`
5. **Workspace errors**: Make sure you're running `pnpm build` from the monorepo root first
### Manual commands
```bash
# Pack a single package
cd ../hyperlane-monorepo/typescript/sdk
pnpm pack
# Check what tarballs exist
ls ../hyperlane-monorepo/typescript/sdk/*.tgz
# Manually update package.json to use a packed version
# "dependencies": {
# "@hyperlane-xyz/sdk": "file:../hyperlane-monorepo/typescript/sdk/hyperlane-xyz-sdk-21.1.0.tgz"
# }
# Force reinstall
rm -rf node_modules pnpm-lock.yaml && pnpm install
```
### Notes
- Tarballs are created in the package directory (e.g., `typescript/sdk/hyperlane-xyz-sdk-21.1.0.tgz`)
- The script automatically cleans old tarballs before packing
- After linking, your `package.json` will have `file:../hyperlane-monorepo/...` references
- Tarballs contain only files listed in the package's `files` field (typically just `/dist`)
- This approach works great for development but remember to test with published versions before releasing
### Comparison: pnpm pack vs yalc
| Feature | pnpm pack | yalc |
|---------|-----------|------|
| Setup | No global tools | Requires global install |
| Catalog support | ✅ Native | ⚠️ Needs @jimsheen/yalc fork |
| Speed | Fast | Fast |
| Workflow | pack + install | publish + push |
| Cleanup | Delete tarballs | yalc remove |
| Portability | Works anywhere | Requires yalc on system |
For this monorepo with pnpm catalogs, `pnpm pack` is the recommended approach.
================================================
FILE: scripts/fetch-fonts.mjs
================================================
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { createWriteStream, existsSync, mkdirSync } from 'fs';
import { dirname, join } from 'path';
import { pipeline } from 'stream/promises';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const FONTS_DIR = join(__dirname, '..', 'public', 'fonts');
// Font files to download from S3
const FONTS = ['PPValve-PlainVariable.woff2', 'PPFraktionMono-Variable.woff2'];
async function fetchFonts() {
const { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_S3_BUCKET, AWS_REGION } = process.env;
// Gracefully skip if environment variables are not configured
if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY || !AWS_S3_BUCKET) {
console.warn('AWS environment variables not configured - skipping font download');
console.warn(
'To enable font fetching, set: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_S3_BUCKET',
);
return;
}
const s3 = new S3Client({
region: AWS_REGION || 'us-east-1',
credentials: {
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
},
});
// Ensure fonts directory exists
if (!existsSync(FONTS_DIR)) {
mkdirSync(FONTS_DIR, { recursive: true });
console.log(`Created directory: ${FONTS_DIR}`);
}
const results = { success: [], failed: [] };
// Download each font, continuing on failure
for (const fontFile of FONTS) {
const outputPath = join(FONTS_DIR, fontFile);
try {
console.log(`Downloading ${fontFile}...`);
const command = new GetObjectCommand({
Bucket: AWS_S3_BUCKET,
Key: fontFile,
});
const response = await s3.send(command);
const writeStream = createWriteStream(outputPath);
await pipeline(response.Body, writeStream);
console.log(`Downloaded ${fontFile}`);
results.success.push(fontFile);
} catch (error) {
console.warn(`Failed to download ${fontFile}: ${error.message}`);
results.failed.push(fontFile);
}
}
// Summary
console.log(
`\nFont download complete: ${results.success.length} succeeded, ${results.failed.length} failed`,
);
if (results.failed.length > 0) {
console.warn('Failed fonts:', results.failed.join(', '));
}
}
fetchFonts().catch((error) => {
console.error('Font fetch script encountered an unexpected error:', error.message);
// Don't fail the build, but log as error for visibility
});
================================================
FILE: scripts/link-monorepo.js
================================================
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
/** --- Configuration --- */
const MONOREPO_NAME = 'hyperlane-monorepo';
const REACT_APP_DIR = process.cwd();
const MONOREPO_PATH = path.resolve(REACT_APP_DIR, '..', MONOREPO_NAME);
const TYPESCRIPT_DIR = path.join(MONOREPO_PATH, 'typescript');
const SOLIDITY_DIR = path.join(MONOREPO_PATH, 'solidity');
const LOCAL_TARBALLS_DIR = path.join(REACT_APP_DIR, '.monorepo-tarballs');
// Default packages to link. Add new entries here as needed.
const DEFAULT_PACKAGES = [
'aleo-sdk',
'cosmos-sdk',
'deploy-sdk',
'provider-sdk',
'radix-sdk',
'sdk',
'starknet-sdk',
'svm-sdk',
'tron-sdk',
'utils',
'widgets',
];
// Allow overriding via CLI args, e.g.: node link-monorepo.js sdk utils
const args = process.argv.slice(2).length > 0 ? process.argv.slice(2) : DEFAULT_PACKAGES;
/**
* Helper to run commands
*/
function run(command, cwd = REACT_APP_DIR) {
try {
execSync(command, { stdio: 'inherit', cwd });
return true;
} catch (err) {
return false;
}
}
/**
* Validates that a package path stays within the typescript directory
* Prevents path traversal attacks (e.g., ../foo)
*/
function validatePackagePath(folder) {
const pkgPath = path.join(TYPESCRIPT_DIR, folder);
const resolvedPath = path.resolve(pkgPath);
const relativePath = path.relative(TYPESCRIPT_DIR, resolvedPath);
// Check if the path escapes the typescript directory
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
console.error(`❌ Invalid package path: "${folder}"`);
console.error(` Package paths must be within ${TYPESCRIPT_DIR}`);
process.exit(1);
}
return pkgPath;
}
console.log('🚀 Starting pnpm pack link workflow...\n');
/**
* 1. Check monorepo setup
*/
console.log('------------------------------------------');
console.log('📋 Checking monorepo setup...');
if (!fs.existsSync(MONOREPO_PATH)) {
console.error(`❌ Monorepo not found at: ${MONOREPO_PATH}`);
process.exit(1);
}
console.log(`✅ Found monorepo at: ${MONOREPO_PATH}\n`);
/**
* 2. Build the entire monorepo first
*/
console.log('------------------------------------------');
console.log('🏗️ Building entire monorepo...');
console.log(' This ensures all dependencies are built in the correct order\n');
if (!run('pnpm build', MONOREPO_PATH)) {
console.error('\n❌ Monorepo build failed. Please fix errors and try again.');
process.exit(1);
}
console.log('✅ Monorepo build complete\n');
/**
* 3. Prepare local tarballs directory
*/
console.log('------------------------------------------');
console.log('📁 Preparing local tarballs directory...\n');
if (!fs.existsSync(LOCAL_TARBALLS_DIR)) {
fs.mkdirSync(LOCAL_TARBALLS_DIR, { recursive: true });
console.log(` ✅ Created: ${LOCAL_TARBALLS_DIR}\n`);
} else {
console.log(` ✅ Using: ${LOCAL_TARBALLS_DIR}\n`);
}
/**
* Packs the package at pkgPath and moves the tarball to LOCAL_TARBALLS_DIR.
* Returns the packed package info, or null on failure.
*/
function packPackage(pkgPath) {
if (!fs.existsSync(pkgPath)) {
console.warn(`⚠️ Directory not found: ${pkgPath}`);
return null;
}
const pkgJsonPath = path.join(pkgPath, 'package.json');
if (!fs.existsSync(pkgJsonPath)) {
console.warn(`⚠️ package.json not found in ${pkgPath}`);
return null;
}
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
const packageName = pkgJson.name;
const packageVersion = pkgJson.version;
console.log(`📦 Packing: ${packageName}@${packageVersion}`);
// Remove old tarballs first
fs.readdirSync(pkgPath).filter(f => f.endsWith('.tgz')).forEach(tarball => {
fs.unlinkSync(path.join(pkgPath, tarball));
});
if (!run('pnpm pack', pkgPath)) {
console.error(`❌ Failed to pack ${packageName}`);
return null;
}
const tarballs = fs.readdirSync(pkgPath).filter(f => f.endsWith('.tgz'));
if (tarballs.length === 0) {
console.error(`❌ No tarball found after packing ${packageName}`);
return null;
}
const tarballName = tarballs[0];
const sourceTarballPath = path.join(pkgPath, tarballName);
const destTarballPath = path.join(LOCAL_TARBALLS_DIR, tarballName);
fs.copyFileSync(sourceTarballPath, destTarballPath);
fs.unlinkSync(sourceTarballPath);
const relativePath = path.relative(REACT_APP_DIR, destTarballPath);
console.log(` ✅ Created and moved: ${tarballName}`);
console.log(` Location: ${relativePath}\n`);
return { name: packageName, version: packageVersion, tarballPath: relativePath };
}
/**
* 4. Pack each specified package
*/
const packedPackages = [];
console.log('------------------------------------------');
console.log('📦 Packing packages...\n');
args.forEach((folder) => {
const result = packPackage(validatePackagePath(folder));
if (result) packedPackages.push(result);
});
console.log('📦 Packing hardcoded package: @hyperlane-xyz/core (from solidity/)');
const coreResult = packPackage(SOLIDITY_DIR);
if (coreResult) packedPackages.push(coreResult);
if (packedPackages.length === 0) {
console.error('❌ No packages were packed successfully.');
process.exit(1);
}
/**
* 5. Update package.json with file: references
*/
console.log('------------------------------------------');
console.log('🔧 Updating package.json with packed dependencies...\n');
const packageJsonPath = path.join(REACT_APP_DIR, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
// Ensure pnpm.overrides exists
if (!packageJson.pnpm) {
packageJson.pnpm = {};
}
if (!packageJson.pnpm.overrides) {
packageJson.pnpm.overrides = {};
}
packedPackages.forEach(({ name, tarballPath }) => {
// Update dependencies
if (packageJson.dependencies && packageJson.dependencies[name]) {
packageJson.dependencies[name] = `file:${tarballPath}`;
console.log(` ${name} -> file:${tarballPath}`);
}
// Add to overrides to ensure sub-dependencies use packed version
packageJson.pnpm.overrides[name] = `file:${tarballPath}`;
});
// Write updated package.json
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
console.log('\n✅ Updated package.json\n');
/**
* 6. Clean and reinstall
*/
console.log('------------------------------------------');
console.log('🧹 Cleaning node_modules and lockfile...\n');
const nodeModulesPath = path.join(REACT_APP_DIR, 'node_modules');
const lockfilePath = path.join(REACT_APP_DIR, 'pnpm-lock.yaml');
if (fs.existsSync(nodeModulesPath)) {
fs.rmSync(nodeModulesPath, { recursive: true, force: true });
}
if (fs.existsSync(lockfilePath)) {
fs.unlinkSync(lockfilePath);
}
console.log('✅ Cleaned\n');
console.log('------------------------------------------');
console.log('📥 Installing dependencies...\n');
if (!run('pnpm install')) {
console.error('\n❌ pnpm install failed.');
process.exit(1);
}
/**
* 7. Success!
*/
console.log('\n------------------------------------------');
console.log('✨ Done! Packages are linked.\n');
console.log('📦 Linked packages:');
packedPackages.forEach(({ name, version }) => {
console.log(` - ${name}@${version}`);
});
console.log('\n💡 To update after making changes:');
console.log(' 1. Run this script again: pnpm link:monorepo <packages>');
console.log(' OR manually:');
console.log(' 1. cd ../hyperlane-monorepo && pnpm build');
console.log(' 2. cd typescript/<package> && pnpm pack');
console.log(' 3. Move the .tgz file to .monorepo-tarballs/ here');
console.log(' 4. cd back here && pnpm install\n');
console.log(`📁 Tarballs location: ${path.relative(REACT_APP_DIR, LOCAL_TARBALLS_DIR)}\n`);
================================================
FILE: scripts/unlink-monorepo.js
================================================
const { execSync, spawnSync } = require('child_process');
const fs = require('fs');
const path = require('path');
/**
* Unlink monorepo packages and restore published versions
*/
const REACT_APP_DIR = process.cwd();
const packageJsonPath = path.join(REACT_APP_DIR, 'package.json');
const LOCAL_TARBALLS_DIR = path.join(REACT_APP_DIR, '.monorepo-tarballs');
console.log('🔗 Unlinking monorepo packages...\n');
try {
// Read package.json to find file: references to monorepo
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const packOverrides = [];
// Find dependencies pointing to packed tarballs (old or new location)
if (packageJson.dependencies) {
Object.entries(packageJson.dependencies).forEach(([name, value]) => {
if (typeof value === 'string' &&
(value.startsWith('file:../hyperlane-monorepo/') ||
value.startsWith('file:.monorepo-tarballs/'))) {
packOverrides.push(name);
}
});
}
if (packOverrides.length === 0) {
console.log('ℹ️ No packed monorepo packages found in dependencies.');
} else {
console.log('🔧 Found packed packages in dependencies:');
packOverrides.forEach((name) => {
console.log(` - ${name}`);
});
}
// Remove overrides for packed packages (old or new location)
if (packageJson.pnpm && packageJson.pnpm.overrides) {
let removedCount = 0;
Object.keys(packageJson.pnpm.overrides).forEach((name) => {
const value = packageJson.pnpm.overrides[name];
if (typeof value === 'string' &&
(value.startsWith('file:../hyperlane-monorepo/') ||
value.startsWith('file:.monorepo-tarballs/'))) {
delete packageJson.pnpm.overrides[name];
removedCount++;
}
});
if (removedCount > 0) {
console.log(`\n🔧 Removed ${removedCount} override(s) from package.json`);
}
}
// Restore dependencies to published versions
const failedToRestore = [];
if (packOverrides.length > 0) {
console.log('\n🔧 Restoring dependencies to published versions...');
packOverrides.forEach((name) => {
if (packageJson.dependencies[name]) {
const currentValue = packageJson.dependencies[name];
try {
// Fetch the latest version from npm registry
// Use spawnSync with array args to prevent command injection
const result = spawnSync('npm', ['view', name, 'version'], { encoding: 'utf8' });
if (result.status === 0 && result.stdout) {
const versionOutput = result.stdout.trim();
packageJson.dependencies[name] = versionOutput;
console.log(` ${name} -> ${versionOutput}`);
} else {
console.warn(` ⚠️ Failed to fetch version for ${name}`);
failedToRestore.push({ name, currentValue });
}
} catch (err) {
console.warn(` ⚠️ Failed to fetch version for ${name}`);
failedToRestore.push({ name, currentValue });
}
}
});
}
// Check if any dependencies couldn't be restored
if (failedToRestore.length > 0) {
console.error('\n❌ Cannot proceed: Some dependencies could not be restored to published versions.');
console.error(' This typically happens with unpublished packages (e.g., @hyperlane-xyz/tron-sdk)');
console.error(' or when you are offline.\n');
console.error(' Failed packages:');
failedToRestore.forEach(({ name, currentValue }) => {
console.error(` - ${name} (currently: ${currentValue})`);
});
console.error('\n💡 Options:');
console.error(' 1. Manually update these dependencies in package.json to published versions');
console.error(' 2. Remove these dependencies from package.json if not needed');
console.error(' 3. Keep using the linked versions (do not run this script)\n');
process.exit(1);
}
// Write updated package.json
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
console.log('\n------------------------------------------');
console.log('🧹 Cleaning node_modules, lockfile, and tarballs...\n');
const nodeModulesPath = path.join(REACT_APP_DIR, 'node_modules');
const lockfilePath = path.join(REACT_APP_DIR, 'pnpm-lock.yaml');
if (fs.existsSync(nodeModulesPath)) {
fs.rmSync(nodeModulesPath, { recursive: true, force: true });
}
if (fs.existsSync(lockfilePath)) {
fs.unlinkSync(lockfilePath);
}
// Clean up local tarballs directory
if (fs.existsSync(LOCAL_TARBALLS_DIR)) {
console.log(` Removing ${path.relative(REACT_APP_DIR, LOCAL_TARBALLS_DIR)}/`);
fs.rmSync(LOCAL_TARBALLS_DIR, { recursive: true, force: true });
}
console.log('✅ Cleaned\n');
console.log('------------------------------------------');
console.log('📥 Running pnpm install...\n');
execSync('pnpm install', {
stdio: 'inherit'
});
console.log('\n✅ Successfully unlinked packages!');
console.log(' All dependencies have been restored to their published versions from npm.\n');
} catch (err) {
console.error('\n❌ Unlink failed. See error above.\n');
process.exit(1);
}
================================================
FILE: sentry.client.config.js
================================================
import { sentryDefaultConfig } from './sentry.default.config';
import * as Sentry from '@sentry/nextjs';
if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_SENTRY_DSN) {
Sentry.init({
...sentryDefaultConfig,
integrations: [
Sentry.breadcrumbsIntegration({
console: false,
dom: false,
fetch: false,
history: false,
sentry: false,
xhr: false,
}),
Sentry.dedupeIntegration(),
Sentry.functionToStringIntegration(),
Sentry.globalHandlersIntegration(),
Sentry.httpContextIntegration(),
],
});
}
================================================
FILE: sentry.default.config.js
================================================
const filters = [
// Hyperlane custom set
"trap returned falsish for property", // Error from cosmos wallet lib
"not established yet", // Same, bug with their WC integration ^
"Refused to create a WebAssembly object", // CSP blocking wasm
"call to WebAssembly.instantiate", // Same ^
"Request rejected", // Unknown noise during Next.js init
"WebSocket connection failed for host", // WalletConnect flakiness
"Socket stalled when trying to connect", // Same ^
"Request expired. Please try again.", // Same^
"Failed to publish payload", // Same ^
// Some recommendations from https://docs.sentry.io/platforms/javascript/configuration/filtering
"top.GLOBALS",
"originalCreateNotification",
"canvas.contentDocument",
"MyApp_RemoveAllHighlights",
"atomicFindClose",
"Wallet is not initialized",
"region has been blocked from accessing this service"
]
export const sentryDefaultConfig = {
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.01,
maxBreadcrumbs: 1,
sendClientReports: false,
attachStacktrace: false,
defaultIntegrations: false,
integrations: [],
beforeSend(event, hint) {
if (event && event.message &&
filters.find((f) => event.message.match(f)))
{
return null;
}
const error = hint.originalException;
if (error && error.message &&
filters.find((f) => error.message.match(f)))
{
return null;
}
delete event.user;
return event;
},
ignoreErrors: filters,
denyUrls: [
// Chrome extensions
/extensions\//i,
/^chrome:\/\//i,
/^chrome-extension:\/\//i,
],
};
================================================
FILE: src/components/banner/FormWarningBanner.tsx
================================================
import clsx from 'clsx';
import { ComponentProps } from 'react';
import { WarningBanner } from '../../components/banner/WarningBanner';
export function FormWarningBanner({
className,
isVisible,
...props
}: ComponentProps<typeof WarningBanner>) {
return (
<div>
<WarningBanner
className={clsx('absolute -top-4 left-0 right-0 z-10', className)}
isVisible={isVisible}
{...props}
/>
<div
className={clsx('transition-all duration-500', isVisible ? 'pb-12 sm:pb-10' : 'pb-0')}
></div>
</div>
);
}
================================================
FILE: src/components/banner/RecipientWarningBanner.tsx
================================================
import { WarningIcon } from '@hyperlane-xyz/widgets';
export function RecipientWarningBanner({
destinationChain,
confirmRecipientHandler,
}: {
destinationChain: string;
confirmRecipientHandler: (checked: boolean) => void;
}) {
return (
<div className="flex items-center gap-3">
<WarningIcon width={40} height={40} />
<div>
<p className="my-2">
The recipient address is the same as the currently connected smart contract wallet,{' '}
<strong>but it does not exist as a smart contract on {destinationChain}</strong>.
</p>
<p className="my-2">This may result in losing access to your bridged tokens.</p>
<p className="my-2">
<strong>
Only proceed if you are certain you have control over this address on {destinationChain}
</strong>
</p>
<div className="justify-left flex w-max gap-2 rounded bg-white/30 px-2.5 py-1 text-center hover:bg-white/50 active:bg-white/60">
<input
onChange={({ target: { checked } }) => confirmRecipientHandler(checked)}
type="checkbox"
id="confirm-address"
name="confirm-recipient"
/>
<label htmlFor="confirm-address">I have control and want to bridge to this address</label>
</div>
</div>
</div>
);
}
================================================
FILE: src/components/banner/WarningBanner.tsx
================================================
import { WarningIcon } from '@hyperlane-xyz/widgets';
import { PropsWithChildren, ReactNode } from 'react';
export function WarningBanner({
isVisible,
cta,
onClick,
className,
children,
}: PropsWithChildren<{
isVisible: boolean;
cta?: ReactNode;
onClick?: () => void;
className?: string;
}>) {
return (
<div
className={`flex items-center justify-between gap-2 rounded bg-amber-400 px-4 text-sm ${
isVisible ? 'max-h-28 py-2' : 'max-h-0'
} overflow-hidden transition-all duration-500 ${className}`}
>
<div className="flex items-center gap-2">
<WarningIcon width={20} height={20} />
{children}
</div>
{cta && onClick && (
<button
type="button"
onClick={onClick}
className="rounded-full bg-white/30 px-2.5 py-1 text-center hover:bg-white/50 active:bg-white/60"
>
{cta}
</button>
)}
</div>
);
}
================================================
FILE: src/components/buttons/ConnectAwareSubmitButton.tsx
================================================
import { ProtocolType } from '@hyperlane-xyz/utils';
import { useTimeout } from '@hyperlane-xyz/widgets';
import {
useAccountForChain,
useConnectFns,
} from '@hyperlane-xyz/widgets/walletIntegrations/multiProtocol';
import { useFormikContext } from 'formik';
import { useCallback } from 'react';
import { EVENT_NAME } from '../../features/analytics/types';
import { trackEvent } from '../../features/analytics/utils';
import { useChainProtocol, useMultiProvider } from '../../features/chains/hooks';
import { SolidButton } from './SolidButton';
interface Props {
chainName: ChainName;
text: string;
classes?: string;
disabled?: boolean;
}
export function ConnectAwareSubmitButton<FormValues = any>({
chainName,
text,
classes,
disabled,
}: Props) {
const protocol = useChainProtocol(chainName) || ProtocolType.Ethereum;
const connectFns = useConnectFns();
const connectFn = connectFns[protocol];
const multiProvider = useMultiProvider();
const account = useAccountForChain(multiProvider, chainName);
const isAccountReady = account?.isReady;
const { errors, setErrors, touched, setTouched } = useFormikContext<FormValues>();
const hasError = Object.keys(errors).length > 0;
const firstError = `${Object.values(errors)[0]}` || 'Unknown error';
const color = hasError ? 'red' : 'accent';
const content = hasError ? firstError : isAccountReady ? text : 'Connect wallet';
const type =
disabled || !isAccountReady
? 'button' // never submits when deliberately disabled
: 'submit';
const onClick = () => {
if (isAccountReady) return undefined;
trackEvent(EVENT_NAME.WALLET_CONNECTION_INITIATED, { protocol });
connectFn();
};
// Automatically clear error state after a timeout
const clearErrors = useCallback(() => {
setErrors({});
setTouched({});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setErrors, setTouched, errors, touched]);
useTimeout(clearErrors, 3500);
return (
<SolidButton
disabled={disabled && isAccountReady}
type={type}
color={color}
onClick={onClick}
className={classes}
>
{content}
</SolidButton>
);
}
================================================
FILE: src/components/buttons/SolidButton.tsx
================================================
import { PropsWithChildren, ReactElement } from 'react';
interface ButtonProps {
type?: 'submit' | 'reset' | 'button';
color?: 'white' | 'primary' | 'accent' | 'green' | 'red' | 'gray'; // defaults to primary
bold?: boolean;
className?: string;
icon?: ReactElement;
}
export function SolidButton(
props: PropsWithChildren<ButtonProps & React.HTMLProps<HTMLButtonElement>>,
) {
const {
type,
onClick,
color: _color,
className,
bold,
icon,
disabled,
title,
...passThruProps
} = props;
const color = _color ?? 'primary';
const base =
'flex items-center justify-center rounded transition-all duration-500 active:scale-95';
let baseColors, onHover;
if (color === 'primary') {
baseColors = 'bg-primary-500 text-white';
onHover = 'hover:bg-primary-600';
} else if (color === 'accent') {
baseColors = 'bg-accent-gradient text-white shadow-accent-glow';
onHover = 'hover:opacity-90';
} else if (color === 'green') {
baseColors = 'bg-green-500 text-white';
onHover = 'hover:bg-green-600';
} else if (color === 'red') {
baseColors = 'bg-error-gradient text-white shadow-error-glow';
onHover = 'hover:opacity-90';
} else if (color === 'white') {
baseColors = 'bg-white text-black';
onHover = 'hover:bg-primary-100';
} else if (color === 'gray') {
baseColors = 'bg-gray-100 text-primary-500';
onHover = 'hover:bg-gray-200';
}
const onDisabled =
'disabled:bg-gray-300 disabled:text-gray-500 disabled:shadow-none disabled:bg-none';
const weight = bold ? 'font-semibold' : '';
const allClasses = `${base} ${baseColors} ${onHover} ${onDisabled} ${weight} ${className}`;
return (
<button
onClick={onClick}
type={type ?? 'button'}
disabled={disabled ?? false}
title={title}
className={allClasses}
{...passThruProps}
>
{icon ? (
<div className="flex items-center justify-center space-x-1">
{props.icon}
{props.children}
</div>
) : (
<>{props.children}</>
)}
</button>
);
}
================================================
FILE: src/components/errors/ErrorBoundary.tsx
================================================
import { ErrorBoundary as ErrorBoundaryInner } from '@hyperlane-xyz/widgets';
import { PropsWithChildren } from 'react';
import { links } from '../../consts/links';
export function ErrorBoundary({ children }: PropsWithChildren<unknown>) {
return <ErrorBoundaryInner supportLink={<SupportLink />}>{children}</ErrorBoundaryInner>;
}
function SupportLink() {
return (
<a href={links.discord} target="_blank" rel="noopener noreferrer" className="mt-5 text-sm">
For support, join the{' '}
<span className="underline underline-offset-2">Hyperlane Discord</span>{' '}
</a>
);
}
================================================
FILE: src/components/icons/BookIcon.tsx
================================================
import { DefaultIconProps } from '@hyperlane-xyz/widgets';
import { memo } from 'react';
import { Color } from '../../styles/Color';
function _BookIcon({ color, ...props }: DefaultIconProps) {
return (
<svg viewBox="0 0 23 16" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M21.688 6.90571C20.9608 6.90571 20.3759 7.53466 20.3759 8.3166C20.3759 8.38459 20.4076 8.45259 20.4076 8.52059L11.2708 13.5692C11.0337 13.3312 10.7175 13.1612 10.3698 13.1612C9.99038 13.1612 9.65842 13.3312 9.42131 13.6202L1.86529 9.96547L1.61237 9.84648C1.13814 9.6085 0.837801 9.13254 0.837801 8.57158C0.837801 8.07862 1.02749 7.65365 1.39107 7.38167C1.4543 7.33068 1.51753 7.29668 1.58076 7.26268L1.69141 7.21169L2.00756 7.36468L10.2117 11.3424C10.2117 11.3424 10.3223 11.3764 10.3856 11.3764C10.4488 11.3764 10.512 11.3764 10.5753 11.3254L21.8935 5.06985C22.0357 4.98486 22.1306 4.83187 22.1148 4.66189C22.1148 4.4919 22.0199 4.33891 21.8619 4.25392L13.1835 0.038247C13.0729 -0.012749 12.9306 -0.012749 12.8199 0.038247L1.50172 6.31076L1.37526 6.36175L1.20137 6.44675C0.442612 6.85471 0 7.67065 0 8.58858C0 9.48951 0.505842 10.2884 1.2646 10.6794L9.08935 14.4701C9.08935 14.4701 9.05773 14.5551 9.05773 14.5891C9.05773 15.371 9.64261 16 10.3698 16C11.0969 16 11.6818 15.371 11.6818 14.5891C11.6818 14.5211 11.6502 14.4531 11.6502 14.3851L20.7869 9.33652C21.0241 9.5745 21.3402 9.72749 21.688 9.72749C22.4151 9.72749 23 9.09854 23 8.3166C23 7.53466 22.4151 6.90571 21.688 6.90571ZM10.3698 15.0821C10.2275 15.0821 10.101 15.0141 10.022 14.8951C9.95876 14.8101 9.91134 14.6911 9.91134 14.5721C9.91134 14.3001 10.1168 14.0792 10.3698 14.0622C10.6227 14.0622 10.844 14.2831 10.844 14.5721C10.844 14.6911 10.7966 14.7931 10.7333 14.8781C10.6543 14.9971 10.5278 15.0821 10.3698 15.0821ZM21.7196 8.82656C21.7196 8.82656 21.7196 8.82656 21.7038 8.82656C21.4509 8.82656 21.2296 8.60558 21.2296 8.3166C21.2296 8.19761 21.277 8.09562 21.3402 8.01062C21.4192 7.89163 21.5615 7.80664 21.7038 7.80664C21.9567 7.80664 22.178 8.02762 22.178 8.3166C22.178 8.60558 21.9725 8.80956 21.7354 8.80956L21.7196 8.82656Z"
fill={color || Color.primary[500]}
/>
</svg>
);
}
export const BookIcon = memo(_BookIcon);
================================================
FILE: src/components/icons/ChainLogo.tsx
================================================
import { ChainLogo as ChainLogoInner } from '@hyperlane-xyz/widgets';
import { useEffect, useRef } from 'react';
import { useChainMetadata } from '../../features/chains/hooks';
import { useStore } from '../../features/store';
import { observeDarkLogosInContainer } from '../../utils/imageBrightness';
export function ChainLogo({
chainName,
background,
size,
}: {
chainName?: string;
background?: boolean;
size?: number;
}) {
const registry = useStore((s) => s.registry);
const chainMetadata = useChainMetadata(chainName);
const wrapperRef = useRef<HTMLSpanElement>(null);
const name = chainMetadata?.name || '';
const logoUri = chainMetadata?.logoURI;
// Process immediately; keep a short-lived observer until the logo image first appears.
useEffect(() => {
const el = wrapperRef.current;
if (!el) return;
const logoObserver = observeDarkLogosInContainer(el, { disconnectOnFirstImage: true });
return () => logoObserver?.disconnect();
}, [chainName, logoUri]);
return (
<span ref={wrapperRef} className="inline-flex">
<ChainLogoInner
chainName={name}
logoUri={logoUri}
registry={registry}
size={size}
background={background}
/>
</span>
);
}
================================================
FILE: src/components/icons/ChevronLargeIcon.tsx
================================================
import { DefaultIconProps } from '@hyperlane-xyz/widgets';
import { memo } from 'react';
import { Color } from '../../styles/Color';
function _ChevronLargeIcon({ color, ...props }: DefaultIconProps) {
return (
<svg viewBox="0 0 16 20" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M16 11.2601L2.31817e-07 20L1.95149e-07 16.8365L11.2573 10.992C11.8306 10.6702 12.2997 10.563 12.9251 10.563H14.5928V9.49062H12.9251C12.2997 9.49062 11.8306 9.38338 11.2573 9.06166L3.6048e-08 3.10992L0 0L16 8.68633V11.2601Z"
fill={color || Color.gray[900]}
/>
</svg>
);
}
export const ChevronLargeIcon = memo(_ChevronLargeIcon);
================================================
FILE: src/components/icons/HamburgerIcon.tsx
================================================
import { DefaultIconProps } from '@hyperlane-xyz/widgets';
import { memo } from 'react';
function _HamburgerIcon({ color, ...props }: DefaultIconProps) {
return (
<svg viewBox="0 0 20 19" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M10 18H1M19 9.5H1M19 1H1"
stroke={color || 'currentColor'}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export const HamburgerIcon = memo(_HamburgerIcon);
================================================
FILE: src/components/icons/HyperlaneGradientLogo.tsx
================================================
import { DefaultIconProps } from '@hyperlane-xyz/widgets';
import { memo } from 'react';
function _HyperlaneGradientLogo({ ...props }: DefaultIconProps) {
return (
<svg viewBox="0 0 219 18" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M18.6184 1.34497L21.4768 8.5871C21.5798 8.84574 21.5798 9.13026 21.4768 9.3889L18.6184 16.631C18.3094 17.4328 17.5111 17.976 16.6613 17.976H9.9401V17.1742H11.3049C12.5668 17.1742 13.6741 16.3983 14.1376 15.2343L16.4552 9.3889H11.5367L8.67828 16.631C8.36926 17.4328 7.57096 17.976 6.72116 17.976H0V17.1742H1.36483C2.62666 17.1742 3.73398 16.3983 4.1975 15.2343L6.51515 9.3889C6.61815 9.13026 6.61815 8.84574 6.51515 8.5871L4.1975 2.74166C3.73398 1.57775 2.60091 0.801807 1.36483 0.801807H0V0H6.72116C7.59671 0 8.36926 0.54316 8.67828 1.34497L11.5367 8.5871H16.4552L14.1376 2.74166C13.6741 1.57775 12.541 0.801807 11.3049 0.801807H9.9401V0H16.6613C17.5368 0 18.3094 0.54316 18.6184 1.34497ZM34.4298 14.1222C35.0994 14.1222 35.6659 13.9928 36.1552 13.7342C36.6445 13.4755 37.005 13.0876 37.2883 12.5703C37.5458 12.053 37.7003 11.3805 37.7003 10.5787H36.5157C36.5157 11.4839 36.3612 12.1047 36.0264 12.4668C35.6917 12.8289 35.1766 13.01 34.4556 13.01H34.4041C33.683 13.01 33.168 12.8289 32.8332 12.4668C32.4985 12.1047 32.344 11.4581 32.344 10.5787V7.52664C32.344 6.64724 32.4985 6.02649 32.8332 5.63852C33.168 5.27641 33.683 5.09536 34.4041 5.09536H34.4556C35.1766 5.09536 35.6917 5.27641 36.0264 5.63852C36.3612 6.00062 36.5157 6.62138 36.5157 7.52664H37.7003C37.7003 6.72484 37.5715 6.07822 37.2883 5.53506C37.0307 5.01776 36.6445 4.62979 36.1552 4.37114C35.6659 4.1125 35.0994 3.98317 34.4298 3.98317H34.3783C33.7088 3.98317 33.1423 4.1125 32.653 4.37114C32.1637 4.62979 31.8032 5.01776 31.5199 5.53506C31.2624 6.05235 31.1079 6.72484 31.1079 7.52664V10.5787C31.1079 11.3805 31.2366 12.053 31.5199 12.5703C31.7774 13.0876 32.1637 13.5014 32.653 13.7342C33.1423 13.9928 33.7088 14.1222 34.3783 14.1222H34.4298ZM43.0566 14.1222C43.7262 14.1222 44.2927 13.9928 44.782 13.7342C45.2712 13.4755 45.6318 13.0876 45.915 12.5703C46.1726 12.053 46.3271 11.3805 46.3271 10.5787V7.52664C46.3271 6.72484 46.1983 6.07822 45.915 5.53506C45.6575 5.01776 45.2712 4.62979 44.782 4.37114C44.2927 4.1125 43.7262 3.98317 43.0566 3.98317H43.0051C42.3356 3.98317 41.769 4.1125 41.2798 4.37114C40.7905 4.62979 40.43 5.01776 40.1467 5.53506C39.8892 6.05235 39.7347 6.72484 39.7347 7.52664V10.5787C39.7347 11.3805 39.8634 12.053 40.1467 12.5703C40.4042 13.0876 40.7905 13.5014 41.2798 13.7342C41.769 13.9928 42.3356 14.1222 43.0051 14.1222H43.0566ZM43.0051 13.0358C42.2841 13.0358 41.769 12.8548 41.4343 12.4927C41.0995 12.1306 40.945 11.4839 40.945 10.6045V7.55251C40.945 6.67311 41.0995 6.05235 41.4343 5.66438C41.769 5.30227 42.2841 5.12122 43.0051 5.12122H43.0566C43.7777 5.12122 44.2927 5.30227 44.6275 5.66438C44.9622 6.02649 45.1167 6.64724 45.1167 7.55251V10.6045C45.1167 11.5098 44.9622 12.1306 44.6275 12.4927C44.2927 12.8548 43.7777 13.0358 43.0566 13.0358H43.0051ZM49.752 13.9928V5.92303H49.8808L52.9452 13.9928H54.6963V4.13836H53.5375V12.2082H53.4087L50.3443 4.13836H48.5932V13.9928H49.752ZM58.3788 13.9928V5.92303H58.5076L61.572 13.9928H63.3231V4.13836H62.1643V12.2082H62.0355L58.9711 4.13836H57.22V13.9928H58.3788ZM72.1301 13.9928V12.8806H66.8253L67.2888 13.3203V9.23372L66.8253 9.62169H71.0228V8.5095H66.8253L67.2888 8.89747V4.83671L66.8253 5.27641H72.1301V4.16422H66.1043V14.0187H72.1301V13.9928ZM77.538 14.1222C78.2075 14.1222 78.774 13.9928 79.2633 13.7342C79.7526 13.4755 80.1131 13.0876 80.3964 12.5703C80.6539 12.053 80.8084 11.3805 80.8084 10.5787H79.6238C79.6238 11.4839 79.4693 12.1047 79.1346 12.4668C78.7998 12.8289 78.2848 13.01 77.5637 13.01H77.5122C76.7912 13.01 76.2761 12.8289 75.9414 12.4668C75.6066 12.1047 75.4521 11.4581 75.4521 10.5787V7.52664C75.4521 6.64724 75.6066 6.02649 75.9414 5.63852C76.2761 5.27641 76.7912 5.09536 77.5122 5.09536H77.5637C78.2848 5.09536 78.7998 5.27641 79.1346 5.63852C79.4693 6.00062 79.6238 6.62138 79.6238 7.52664H80.8084C80.8084 6.72484 80.6797 6.07822 80.3964 5.53506C80.1389 5.01776 79.7526 4.62979 79.2633 4.37114C78.774 4.1125 78.2075 3.98317 77.538 3.98317H77.4865C76.8169 3.98317 76.2504 4.1125 75.7611 4.37114C75.2718 4.62979 74.9113 5.01776 74.628 5.53506C74.3705 6.05235 74.216 6.72484 74.216 7.52664V10.5787C74.216 11.3805 74.3448 12.053 74.628 12.5703C74.8855 13.0876 75.2718 13.5014 75.7611 13.7342C76.2504 13.9928 76.8169 14.1222 77.4865 14.1222H77.538ZM86.7313 13.9928V4.83671L86.2677 5.27641H89.6927V4.16422H82.5853V5.27641H86.0102L85.5467 4.83671V13.9928H86.7313ZM98.0105 13.9928V12.8806H92.7056L93.1692 13.3203V9.23372L92.7056 9.62169H96.9031V8.5095H92.7056L93.1692 8.89747V4.83671L92.7056 5.27641H98.0105V4.16422H91.9846V14.0187H98.0105V13.9928ZM103.393 13.9928C104.062 13.9928 104.629 13.8635 105.118 13.6049C105.607 13.3462 105.968 12.9582 106.251 12.4409C106.508 11.9236 106.663 11.2512 106.663 10.4494V7.68183C106.663 6.88002 106.534 6.2334 106.251 5.71611C105.993 5.19881 105.607 4.78498 105.118 4.5522C104.629 4.29355 104.062 4.16422 103.393 4.16422H100.225V14.0187H103.393V13.9928ZM101.435 4.75911L100.946 5.25054H103.393C103.856 5.25054 104.242 5.32814 104.551 5.48333C104.86 5.63852 105.066 5.89716 105.221 6.25927C105.375 6.62138 105.453 7.08694 105.453 7.68183V10.4494C105.453 11.0442 105.375 11.5357 105.221 11.8978C105.066 12.2599 104.835 12.4927 104.551 12.6737C104.242 12.8289 103.856 12.9065 103.393 12.9065H100.946L101.435 13.3979V4.75911ZM121.213 13.9928C122.14 13.9928 122.835 13.76 123.324 13.2945C123.814 12.8289 124.045 12.1823 124.045 11.3288V11.2253C124.045 10.6045 123.891 10.1131 123.608 9.75101C123.324 9.3889 122.886 9.13026 122.32 8.97507V8.81988C123.221 8.5095 123.659 7.83702 123.659 6.80243V6.64724C123.659 5.84543 123.427 5.25054 122.938 4.81084C122.449 4.37114 121.753 4.13836 120.826 4.13836H117.685V13.9928H121.213ZM118.895 4.83671L118.431 5.25054H120.852C121.444 5.25054 121.882 5.37987 122.14 5.61265C122.397 5.84543 122.526 6.2334 122.526 6.7507V6.85416C122.526 7.39732 122.397 7.81115 122.14 8.04394C121.882 8.30258 121.444 8.43191 120.852 8.43191H118.431L118.895 8.84574V4.83671ZM118.895 9.13026L118.431 9.54409H121.213C121.805 9.54409 122.217 9.67342 122.474 9.93206C122.732 10.1907 122.861 10.5787 122.861 11.1477V11.2253C122.861 11.7943 122.732 12.234 122.474 12.4927C122.217 12.7513 121.779 12.8806 121.187 12.8806H118.406L118.869 13.2686V9.13026H118.895ZM129.839 13.9928V9.93206L133.007 4.16422H131.719L129.299 8.69056H129.144L126.749 4.16422H125.462L128.629 9.93206V14.0187H129.814L129.839 13.9928ZM144.544 13.9928V9.31131L144.054 9.67342H148.921L148.432 9.31131V13.9928H149.617V4.13836H148.432V8.89747L148.921 8.53537H144.054L144.544 8.89747V4.13836H143.359V13.9928H144.544ZM155.694 13.9928V9.93206L158.861 4.16422H157.574L155.153 8.69056H154.999L152.604 4.16422H151.316L154.484 9.93206V14.0187H155.668L155.694 13.9928ZM161.977 13.9928V9.72515L161.514 10.1648H164.269C165.248 10.1648 165.969 9.93206 166.484 9.44063C166.999 8.9492 167.256 8.19913 167.256 7.1904V7.13867C167.256 6.12995 166.999 5.37987 166.484 4.88844C165.969 4.39701 165.222 4.16422 164.269 4.16422H160.793V14.0187H161.977V13.9928ZM161.977 4.81084L161.514 5.25054H164.269C164.707 5.25054 165.068 5.30227 165.325 5.4316C165.583 5.53506 165.789 5.74197 165.917 6.00062C166.046 6.25927 166.098 6.64724 166.098 7.13867V7.1904C166.098 7.68183 166.046 8.04394 165.917 8.32845C165.789 8.5871 165.608 8.79402 165.351 8.89747C165.093 9.00093 164.733 9.07853 164.295 9.07853H161.54L162.003 9.51823V4.86257L161.977 4.81084ZM175.6 13.9928V12.8806H170.295L170.759 13.3203V9.23372L170.295 9.62169H174.493V8.5095H170.295L170.759 8.89747V4.83671L170.295 5.27641H175.6V4.16422H169.574V14.0187H175.6V13.9928ZM179.282 13.9928V9.72515L178.793 10.1648H181.343C182.321 10.1648 183.042 9.93206 183.557 9.44063C184.072 8.9492 184.33 8.19913 184.33 7.1904V7.13867C184.33 6.12995 184.072 5.37987 183.557 4.88844C183.042 4.39701 182.295 4.16422 181.343 4.16422H178.072V14.0187H179.257L179.282 13.9928ZM179.282 4.81084L178.793 5.25054H181.343C181.78 5.25054 182.141 5.30227 182.424 5.4316C182.682 5.53506 182.888 5.74197 182.991 6.00062C183.119 6.25927 183.171 6.64724 183.171 7.13867V7.1904C183.171 7.68183 183.119 8.04394 182.991 8.32845C182.862 8.5871 182.682 8.79402 182.424 8.89747C182.167 9.00093 181.806 9.07853 181.343 9.07853H178.793L179.282 9.51823V4.81084ZM184.252 13.9928L181.935 10.2166V9.3889L180.158 9.4665L182.913 13.967H184.252V13.9928ZM192.853 13.9928V12.8806H187.703L188.167 13.3203V4.16422H186.982V14.0187H192.828L192.853 13.9928ZM194.527 13.9928H195.815L198.184 5.27641H198.313L200.682 13.9928H201.944L199.188 4.13836H197.36L194.553 13.9928H194.527ZM200.321 10.9925L200.038 9.88033H196.407L196.124 10.9925H200.321ZM204.931 13.9928V5.92303H205.06L208.124 13.9928H209.875V4.13836H208.716V12.2082H208.588L205.523 4.13836H203.772V13.9928H204.931ZM218.708 13.9928V12.8806H213.403L213.867 13.3203V9.23372L213.403 9.62169H217.601V8.5095H213.403L213.867 8.89747V4.83671L213.403 5.27641H218.708V4.16422H212.682V14.0187H218.708V13.9928Z"
fill="url(#paint0_linear_279_1224)"
/>
<defs>
<linearGradient
id="paint0_linear_279_1224"
x1="0"
y1="8.988"
x2="218.708"
y2="8.988"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#4C52FF" />
<stop offset="0.3" stopColor="#9A0DFF" />
<stop offset="0.6" stopColor="#9A0DFF" />
<stop offset="1" stopColor="#FF4FE9" />
</linearGradient>
</defs>
</svg>
);
}
export const HyperlaneGradientLogo = memo(_HyperlaneGradientLogo);
================================================
FILE: src/components/icons/HyperlaneTransparentLogo.tsx
================================================
import { memo } from 'react';
function _HyperlaneTransparentLogo() {
return (
<svg
width="146"
height="127"
viewBox="0 0 146 127"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M111.997 0.5C118.021 0.500205 123.353 4.29477 125.572 10.0957L125.784 10.3896L125.822 10.4424L125.846 10.5029L144.975 60.709L144.978 60.7158C145.675 62.6255 145.675 64.7173 144.978 66.627L144.975 66.6338L125.846 116.837L125.847 116.838C123.643 122.677 118.291 126.5 112.243 126.5H66.999V119.931H76.6943C84.8493 119.931 92.1424 114.758 95.1924 106.801L110.287 66.9561H78.5166L59.5107 116.754L59.5098 116.753C57.3054 122.591 51.9546 126.414 45.9072 126.414H0.582031V119.846H10.1953C18.35 119.846 25.6424 114.673 28.6924 106.716L44.124 66.1133C44.7404 64.4248 44.7404 62.5752 44.124 60.8867V60.8857L28.6924 20.2832L28.6914 20.2803C25.7242 12.3299 18.3532 7.15444 10.1953 7.1543H0.5V0.585938H45.7432C51.7903 0.585938 57.1411 4.40815 59.3457 10.2461H59.3467L78.3525 60.0439H110.122L94.9453 20.1982V20.1973C91.895 12.2411 84.6027 7.06939 76.4482 7.06934H66.7529V0.5H111.997Z"
stroke="white"
/>
</svg>
);
}
export const HyperlaneTransparentLogo = memo(_HyperlaneTransparentLogo);
================================================
FILE: src/components/icons/QuestionMarkIcon.tsx
================================================
import { DefaultIconProps } from '@hyperlane-xyz/widgets';
import { memo } from 'react';
import { Color } from '../../styles/Color';
function _QuestionMarkIcon({ color, width = 20, height = 20, ...rest }: DefaultIconProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 90 90"
width={width}
height={height}
fill="none"
{...rest}
>
<path
d="M45 0C20.147 0 0 20.147 0 45c0 24.853 20.147 45 45 45s45-20.147 45-45C90 20.147 69.853 0 45 0zM49.083 68.404C47.884 69.469 46.389 70 44.597 70c-1.792 0-3.288-.531-4.486-1.596-1.199-1.063-1.798-2.424-1.798-4.082 0-1.657.599-3.018 1.798-4.082 1.198-1.064 2.693-1.596 4.486-1.596 1.792 0 3.287.532 4.486 1.596 1.199 1.064 1.799 2.425 1.799 4.082 0 1.658-.6 3.019-1.799 4.082zM59.718 38.381c-.739 1.524-1.928 3.081-3.562 4.671l-3.864 3.595c-1.099 1.053-1.861 2.133-2.285 3.242-.425 1.109-.66 2.515-.706 4.217h-9.61c0-3.271.369-5.852 1.109-7.746.739-1.893 1.937-3.533 3.595-4.923 1.658-1.388 2.918-2.66 3.781-3.813.862-1.154 1.293-2.425 1.293-3.814 0-3.382-1.456-5.074-4.368-5.074-1.344 0-2.431.493-3.259 1.479-.829.986-1.266 2.318-1.311 3.999H29.174c.044-4.48 1.456-7.969 4.234-10.467C36.185 21.249 40.083 20 45.101 20c4.996 0 8.865 1.154 11.609 3.461 2.745 2.308 4.116 5.59 4.116 9.846 0 1.859-.369 3.551-1.108 5.074z"
fill={color || Color.primary[500]}
/>
</svg>
);
}
export const QuestionMarkIcon = memo(_QuestionMarkIcon);
================================================
FILE: src/components/icons/StakeIcon.tsx
================================================
import { DefaultIconProps } from '@hyperlane-xyz/widgets';
import { memo } from 'react';
function _StakeIcon({ ...props }: DefaultIconProps) {
return (
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" {...props}>
<circle cx="10" cy="10" r="10" fill="#9A0DFF" />
<path
d="M6.91687 9.90641L4.87399 4.56765C4.80806 4.39535 4.93528 4.21045 5.11977 4.21045H7.65818C7.7658 4.21045 7.86258 4.27598 7.90253 4.37591L9.65836 8.76722H12.5701L10.8771 4.57209C10.8073 4.39913 10.9346 4.21045 11.1211 4.21045H13.7572C13.8633 4.21045 13.959 4.27415 14 4.37201L16.3163 9.90641L14.0157 15.6245C13.9756 15.7241 13.879 15.7894 13.7716 15.7894H11.2942C11.1116 15.7894 10.9845 15.6079 11.0469 15.4363L12.5701 11.2496H9.65836L7.90265 15.6243C7.86262 15.724 7.76593 15.7894 7.65843 15.7894H5.11544C4.93214 15.7894 4.80499 15.6067 4.86867 15.4348L6.91687 9.90641Z"
fill="#EBD3FF"
/>
<path d="M0 10H20" stroke="#EBD3FF" strokeWidth="1.25" />
</svg>
);
}
export const StakeIcon = memo(_StakeIcon);
================================================
FILE: src/components/icons/SwapIcon.tsx
================================================
import { DefaultIconProps } from '@hyperlane-xyz/widgets';
import { memo } from 'react';
import { Color } from '../../styles/Color';
function _SwapIcon({ color, ...props }: DefaultIconProps) {
return (
<svg viewBox="0 0 15 21" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M9.40694 4.5704L14.0221 9.09747V6.86643L7 0L0 6.86643V9.09747L4.61514 4.5704C5.32177 3.87726 5.58675 3.18412 5.58675 2.20939V1.99278H6.09464V19.0072H5.58675V18.7906C5.58675 17.7942 5.32177 17.1227 4.61514 16.4296L0 11.9025V14.1336L7.02208 21L14.0221 14.1336V11.9025L9.40694 16.4296C8.70032 17.1227 8.43533 17.8159 8.43533 18.7906V19.0072H7.92744V1.99278H8.43533V2.20939C8.43533 3.20578 8.70032 3.87726 9.40694 4.5704Z"
fill={color || Color.gray[400]}
/>
</svg>
);
}
export const SwapIcon = memo(_SwapIcon);
================================================
FILE: src/components/icons/TokenIcon.tsx
================================================
import { IToken } from '@hyperlane-xyz/sdk';
import { isHttpsUrl, isRelativeUrl } from '@hyperlane-xyz/utils';
import { Circle } from '@hyperlane-xyz/widgets';
import type { SyntheticEvent } from 'react';
import { useState } from 'react';
import { links } from '../../consts/links';
import {
markDarkLogoMissing,
processDarkLogoImage,
toOriginalVariantSrc,
} from '../../utils/imageBrightness';
interface Props {
token?: IToken | null;
size?: number;
}
export function TokenIcon({ token, size = 32 }: Props) {
const title = token?.symbol || '';
const character = title ? title.charAt(0).toUpperCase() : '';
const fontSize = Math.floor(size / 2);
const [fallbackToText, setFallbackToText] = useState(false);
const imageSrc = getImageSrc(token);
const bgColorSeed =
token && (!imageSrc || fallbackToText)
? (Buffer.from(token.addressOrDenom).at(0) || 0) % 5
: undefined;
function handleImageLoad(event: SyntheticEvent<HTMLImageElement>) {
processDarkLogoImage(event.currentTarget);
}
function handleImageError(event: SyntheticEvent<HTMLImageElement>) {
const img = event.currentTarget;
const original = img.dataset.logoOriginalSrc;
const attemptedDark = img.dataset.logoDarkSrc;
const current = img.getAttribute('src') || img.src;
const originalLoadFailed =
!!original && current === original && img.complete && img.naturalWidth === 0;
const darkFallbackAlreadyHandled =
img.dataset.logoDarkFailed === 'true' &&
!!original &&
!!attemptedDark &&
current === original;
if (darkFallbackAlreadyHandled && !originalLoadFailed) return;
const isDarkFallbackError = !!original && !!attemptedDark && current === attemptedDark;
const fallbackSrc = toOriginalVariantSrc(current);
const isDarkVariantSrc = fallbackSrc !== null;
// Dark-variant misses should fall back to original logo, not text.
if (isDarkFallbackError || isDarkVariantSrc) {
markDarkLogoMissing(current);
const nextSrc = fallbackSrc || original;
if (nextSrc) {
img.src = nextSrc;
return;
}
}
setFallbackToText(true);
}
return (
<span className="inline-flex">
<Circle size={size} bgColorSeed={bgColorSeed} title={title}>
{imageSrc && !fallbackToText ? (
<img
src={imageSrc}
className="h-full w-full p-0.5"
onLoad={handleImageLoad}
onError={handleImageError}
loading="lazy"
/>
) : (
<div className={`text-[${fontSize}px]`}>{character}</div>
)}
</Circle>
</span>
);
}
function getImageSrc(token?: IToken | null) {
if (!token?.logoURI) return null;
// If it's a valid, direct URL, return it
if (isHttpsUrl(token.logoURI)) return token.logoURI;
// Otherwise assume it's a relative URL to the registry base
if (isRelativeUrl(token.logoURI)) return `${links.imgPath}${token.logoURI}`;
return null;
}
================================================
FILE: src/components/icons/WebSimpleIcon.tsx
================================================
import { DefaultIconProps } from '@hyperlane-xyz/widgets';
import { memo } from 'react';
import { Color } from '../../styles/Color';
function _WebSimpleIcon({ color, ...props }: DefaultIconProps) {
return (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M0.75 9.75H5.75M0.75 9.75C0.75 14.7206 4.77944 18.75 9.75 18.75M0.75 9.75C0.75 4.77944 4.77944 0.75 9.75 0.75M5.75 9.75H13.75M5.75 9.75C5.75 14.7206 7.54086 18.75 9.75 18.75M5.75 9.75C5.75 4.77944 7.54086 0.75 9.75 0.75M13.75 9.75H18.75M13.75 9.75C13.75 4.77944 11.9591 0.75 9.75 0.75M13.75 9.75C13.75 14.7206 11.9591 18.75 9.75 18.75M18.75 9.75C18.75 4.77944 14.7206 0.75 9.75 0.75M18.75 9.75C18.75 14.7206 14.7206 18.75 9.75 18.75"
stroke={color || Color.primary[500]}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export const WebSimpleIcon = memo(_WebSimpleIcon);
================================================
FILE: src/components/icons/XIcon.tsx
================================================
import { DefaultIconProps } from '@hyperlane-xyz/widgets';
import { memo } from 'react';
import { Color } from '../../styles/Color';
function _XIcon({ color, ...props }: DefaultIconProps) {
return (
<svg viewBox="0 0 19 17" xmlns="http://www.w3.org/2000/svg" fill="none" {...props}>
<path
d="M14.4386 0H17.2498L11.1081 7.01958L18.3333 16.5716H12.676L8.24503 10.7784L3.17496 16.5716H0.362027L6.9312 9.06341L0 0H5.80092L9.80616 5.29528L14.4386 0ZM13.4519 14.889H15.0097L4.9545 1.59428H3.28288L13.4519 14.889Z"
fill={color || Color.primary[500]}
/>
</svg>
);
}
export const XIcon = memo(_XIcon);
================================================
FILE: src/components/input/SearchInput.tsx
================================================
import { SearchIcon, XIcon } from '@hyperlane-xyz/widgets';
import { Ref } from 'react';
import { TextInput } from './TextField';
export function SearchInput({
inputRef,
value,
onChange,
placeholder,
'aria-label': ariaLabel,
}: {
inputRef?: Ref<HTMLInputElement>;
value: string;
onChange: (s: string) => void;
placeholder: string;
'aria-label'?: string;
}) {
return (
<div className="relative w-full">
<SearchIcon
width={16}
height={16}
className="token-picker-search-icon absolute left-3 top-1/2 -translate-y-1/2 opacity-50"
/>
<TextInput
ref={inputRef}
value={value}
onChange={onChange}
placeholder={placeholder}
aria-label={ariaLabel}
name="search"
className="token-picker-search-input !mt-0 w-full pl-9 pr-8 all:border-gray-300 all:py-2 all:text-sm all:focus:border-blue-400"
autoComplete="off"
/>
{value && (
<button
type="button"
aria-label="Clear search"
onClick={() => onChange('')}
className="absolute right-2.5 top-1/2 -translate-y-1/2 rounded-full p-0.5 text-gray-400 hover:text-gray-600"
>
<XIcon width={12} height={12} />
</button>
)}
</div>
);
}
================================================
FILE: src/components/input/TextField.tsx
================================================
import clsx from 'clsx';
import { Field, FieldAttributes } from 'formik';
import { ChangeEvent, InputHTMLAttributes, Ref, forwardRef } from 'react';
export function TextField({ className, ...props }: FieldAttributes<unknown>) {
return <Field className={clsx(defaultClassName, className)} {...props} />;
}
type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
onChange: (v: string) => void;
};
export const TextInput = forwardRef(function _TextInput(
{ onChange, className, ...props }: InputProps,
ref: Ref<HTMLInputElement>,
) {
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
onChange(e?.target?.value || '');
};
return (
<input
ref={ref}
type="text"
autoComplete="off"
onChange={handleChange}
className={clsx(defaultClassName, className)}
{...props}
/>
);
});
const defaultClassName =
'rounded-lg border border-primary-300 focus:border-primary-500 disabled:bg-gray-150 outline-none transition-all duration-300';
================================================
FILE: src/components/layout/AppLayout.tsx
================================================
import Head from 'next/head';
import { PropsWithChildren, useEffect } from 'react';
import { APP_NAME } from '../../consts/app';
import { config } from '../../consts/config';
import { initIntercom } from '../../features/analytics/intercom';
import { initRefiner } from '../../features/analytics/refiner';
import { EVENT_NAME } from '../../features/analytics/types';
import { useWalletConnectionTracking } from '../../features/analytics/useWalletConnectionTracking';
import { trackEvent } from '../../features/analytics/utils';
import { useStore } from '../../features/store';
import { SideBarMenu } from '../../features/wallet/SideBarMenu';
import { WalletProtocolModal } from '../../features/wallet/WalletProtocolModal';
import { Footer } from '../nav/Footer';
import { Header } from '../nav/Header';
export function AppLayout({ children }: PropsWithChildren) {
const { showEnvSelectModal, setShowEnvSelectModal, isSideBarOpen, setIsSideBarOpen } = useStore(
(s) => ({
showEnvSelectModal: s.showEnvSelectModal,
setShowEnvSelectModal: s.setShowEnvSelectModal,
isSideBarOpen: s.isSideBarOpen,
setIsSideBarOpen: s.setIsSideBarOpen,
}),
);
useWalletConnectionTracking();
useEffect(() => {
initIntercom();
initRefiner();
trackEvent(EVENT_NAME.PAGE_VIEWED, {});
}, []);
return (
<>
<Head>
{/* https://nextjs.org/docs/messages/no-document-viewport-meta */}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{APP_NAME}</title>
</Head>
<div
id="app-content"
className="min-w-screen relative flex h-full min-h-screen w-full flex-col justify-between"
>
<Header />
<div className="mx-auto flex max-w-screen-xl grow items-center sm:px-4">
<main className="my-4 flex w-full flex-1 items-center justify-center">{children}</main>
</div>
<Footer />
</div>
<WalletProtocolModal
isOpen={showEnvSelectModal}
close={() => setShowEnvSelectModal(false)}
protocols={config.walletProtocols}
onProtocolSelected={(protocol) =>
trackEvent(EVENT_NAME.WALLET_CONNECTION_INITIATED, { protocol })
}
/>
<SideBarMenu
onClose={() => setIsSideBarOpen(false)}
isOpen={isSideBarOpen}
onClickConnectWallet={() => setShowEnvSelectModal(true)}
/>
</>
);
}
================================================
FILE: src/components/layout/Card.tsx
================================================
import { PropsWithChildren } from 'react';
interface Props {
className?: string;
}
export function Card({ className = '', children }: PropsWithChildren<Props>) {
return (
<div
className={`relative overflow-auto rounded-2xl bg-white p-1.5 xs:p-2 sm:p-3 md:p-4 ${className}`}
>
{children}
</div>
);
}
================================================
FILE: src/components/layout/ModalHeader.tsx
================================================
import clsx from 'clsx';
import { ReactNode } from 'react';
export function ModalHeader({ children, className }: { children?: ReactNode; className?: string }) {
return (
<div className={clsx('flex items-center gap-2 bg-accent-gradient px-4 py-1', className)}>
{children && (
<>
<div className="h-2 w-2 rounded-full bg-white" />
<span className="font-secondary text-xs text-white">{children}</span>
</>
)}
</div>
);
}
================================================
FILE: src/components/nav/Footer.tsx
================================================
import { HyperlaneGradientLogo } from '../icons/HyperlaneGradientLogo';
import { NavItem, navLinks } from './Nav';
export function Footer() {
return (
<footer className="footer-root relative text-white">
<div className="footer-inner relative px-8 pb-5 pt-2 sm:pt-0">
<div className="flex flex-col items-center justify-between gap-4">
<FooterLogo />
<FooterNav />
</div>
</div>
</footer>
);
}
function FooterLogo() {
return (
<div className="flex items-center justify-center rounded-full border border-transparent bg-transparent px-[0.8rem] py-[0.35rem] dark:border-primary-300/40 dark:bg-white/[0.08] dark:shadow-[0_0_22px_rgba(154,13,255,0.24)]">
<HyperlaneGradientLogo
className="dark:[filter:saturate(1.2)_brightness(1.2)_drop-shadow(0_0_10px_rgba(185,89,255,0.45))]"
width={219}
height={18}
/>
</div>
);
}
function FooterNav() {
return (
<nav className="hidden text-md font-medium lg:block">
<ul className="flex gap-9">
{navLinks.map((item) => (
<li key={item.title}>
<NavItem item={item} className="dark:text-primary-50 dark:hover:text-white" />
</li>
))}
</ul>
</nav>
);
}
================================================
FILE: src/components/nav/Header.tsx
================================================
import { DropdownMenu } from '@hyperlane-xyz/widgets';
import Image from 'next/image';
import Link from 'next/link';
import { useTheme } from '../../features/theme/ThemeContext';
import { ConnectWalletButton } from '../../features/wallet/ConnectWalletButton';
import Logo from '../../images/logos/app-logo.svg';
import Name from '../../images/logos/app-name.svg';
import Title from '../../images/logos/app-title.svg';
import { HamburgerIcon } from '../icons/HamburgerIcon';
import { NavItem, navLinks } from './Nav';
export function Header() {
const { themeMode, toggleThemeMode } = useTheme();
const nextThemeMode = themeMode === 'dark' ? 'light' : 'dark';
const nextThemeLabel = nextThemeMode === 'dark' ? 'Lights out' : 'Lights on';
return (
<header className="relative flex w-full items-center justify-between bg-primary-25 px-4 py-3 shadow-app-header lg:justify-center lg:bg-transparent lg:px-6 lg:pb-2 lg:pt-3 lg:shadow-none dark:border-b dark:border-primary-300/[0.24] dark:bg-background/[0.88] dark:shadow-app-header-dark lg:dark:border-b-0 lg:dark:bg-transparent lg:dark:shadow-none">
{/* Mobile/Tablet: Logo + Hamburger Menu */}
<div className="flex items-center gap-3 lg:hidden">
<Link href="/" aria-label="Homepage">
<Image src={Logo} width={36} alt="" className="h-auto" />
</Link>
<DropdownMenu
button={<HamburgerIcon width={20} height={19} />}
buttonClassname="rounded p-2 text-primary-500 data-[open]:bg-primary-25 data-[open]:shadow-[inset_4px_4px_4px_rgba(154,13,255,0.1)] data-[open]:text-white dark:bg-white/[0.08] dark:text-foreground-primary dark:data-[open]:bg-primary-300/25 dark:data-[open]:ring-1 dark:data-[open]:ring-primary-300/45 dark:data-[open]:text-white"
menuClassname="py-4 dark:border dark:border-primary-300/35 dark:bg-surface dark:shadow-menu-dark"
menuItems={navLinks.map((item) => (
<NavItem
key={item.title}
item={item}
className="w-full gap-3 px-6 py-2 hover:bg-primary-50/30 dark:text-foreground-primary dark:hover:bg-primary-300/[0.16] dark:[&_path]:fill-current dark:[&_path]:stroke-current"
/>
))}
/>
</div>
{/* Desktop: Centered Logo */}
<Link href="/" aria-label="Homepage" className="hidden flex-col py-2 lg:flex">
<div className="flex items-end">
<Image src={Logo} width={46} alt="" className="h-auto" />
<Image src={Name} width={150} alt="" className="ml-1.5" />
</div>
<Image src={Title} width={43} alt="" className="self-end" />
</Link>
<div className="flex items-center gap-2 lg:absolute lg:right-12">
<button
type="button"
className={`${styles.themeToggle} theme-toggle`}
onClick={toggleThemeMode}
aria-label={`Switch to ${nextThemeMode} mode`}
title={`Switch to ${nextThemeMode} mode`}
>
{nextThemeLabel}
</button>
<ConnectWalletButton />
</div>
</header>
);
}
const styles = {
themeToggle:
'rounded-md border border-primary-500/35 bg-white/85 px-2.5 py-1 text-xs font-medium capitalize text-primary-900 transition-[background-color,border-color,color,box-shadow] duration-200 hover:border-primary-300/70 hover:bg-gray-950/90 hover:text-primary-25 focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_rgba(154,13,255,0.25)] dark:border-primary-300/45 dark:bg-black/75 dark:text-primary-50 dark:hover:border-primary-500/55 dark:hover:bg-white/95 dark:hover:text-primary-900 dark:focus-visible:shadow-[0_0_0_2px_rgba(185,89,255,0.35)]',
};
================================================
FILE: src/components/nav/Nav.tsx
================================================
import { GithubIcon } from '@hyperlane-xyz/widgets';
import clsx from 'clsx';
import Link from 'next/link';
import { forwardRef, ReactNode } from 'react';
import { links } from '../../consts/links';
import { Color } from '../../styles/Color';
import { BookIcon } from '../icons/BookIcon';
import { QuestionMarkIcon } from '../icons/QuestionMarkIcon';
import { StakeIcon } from '../icons/StakeIcon';
import { WebSimpleIcon } from '../icons/WebSimpleIcon';
import { XIcon } from '../icons/XIcon';
interface NavLinkItem {
title: string;
url: string;
icon: ReactNode;
}
export const navLinks: NavLinkItem[] = [
{ title: 'Stake', url: links.stake, icon: <StakeIcon width={20} height={20} /> },
{ title: 'X.com', url: links.twitter, icon: <XIcon width={19} height={17} /> },
{ title: 'Hyperlane', url: links.home, icon: <WebSimpleIcon width={20} height={20} /> },
{
title: 'Support',
url: links.support,
icon: <QuestionMarkIcon width={20} height={20} color={Color.primary[500]} />,
},
{
title: 'Docs',
url: links.docs,
icon: <BookIcon color={Color.primary[500]} width={23} height={16} />,
},
{
title: 'Github',
url: links.github,
icon: <GithubIcon width={20} height={20} color={Color.primary[500]} />,
},
];
interface NavItemProps {
item: NavLinkItem;
className?: string;
}
export const NavItem = forwardRef<HTMLAnchorElement, NavItemProps>(function NavItem(
{ item, className },
ref,
) {
return (
<Link
ref={ref}
className={clsx(
'flex items-center gap-2 text-primary-500 decoration-primary-500 underline-offset-2 hover:underline',
className,
)}
target="_blank"
rel="noopener noreferrer"
href={item.url}
>
<div className="w-5">{item.icon}</div>
<span>{item.title}</span>
</Link>
);
});
================================================
FILE: src/components/tip/TipCard.tsx
================================================
import { IconButton, XCircleIcon } from '@hyperlane-xyz/widgets';
import Image from 'next/image';
import { useState } from 'react';
import { config } from '../../consts/config';
import { links } from '../../consts/links';
import InfoCircle from '../../images/icons/info-circle.svg';
import { HyperlaneTransparentLogo } from '../icons/HyperlaneTransparentLogo';
export function TipCard() {
const [show, setShow] = useState(config.showTipBox);
if (!show) return null;
return (
<div
data-testid="tip-card"
className="tip-card relative w-full overflow-hidden rounded bg-tip-card-gradient px-4 pb-4 pt-4 shadow-card xl:w-72 xl:pb-24 dark:bg-gradient-to-t dark:from-primary-500/30 dark:to-[#111]/95 dark:shadow-lg dark:ring-1 dark:ring-inset dark:ring-primary-500/50"
>
<div className="absolute right-2 top-2">
<IconButton
onClick={() => setShow(false)}
title="Hide tip"
className="text-gray-400 hover:text-gray-600 dark:text-foreground-secondary dark:hover:text-foreground-primary dark:[&_path]:fill-current"
>
<XCircleIcon width={14} height={14} />
</IconButton>
</div>
<h2 className="pr-6 font-secondary text-lg font-normal text-gray-900 dark:text-white">
Bridge Tokens with Hyperlane Warp Routes!
</h2>
<p className="mt-2 text-sm text-gray-600 dark:text-foreground-muted">
Warp Routes make it easy to permissionlessly take your tokens interchain. Fork this template
to get started!
</p>
<a
href={links.github}
target="_blank"
rel="noopener noreferrer"
className="mt-3 inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-1.5 font-secondary text-sm text-gray-700 transition-colors hover:bg-gray-50 dark:border-primary-500/80 dark:bg-primary-500/20 dark:text-white dark:hover:bg-primary-500/30"
>
<Image src={InfoCircle} width={12} alt="" className="dark:invert" />
<span>More</span>
</a>
<div className="tip-card-logo pointer-events-none absolute bottom-2 left-1/2 -translate-x-1/2">
<HyperlaneTransparentLogo />
</div>
</div>
);
}
================================================
FILE: src/components/toast/IgpDetailsToast.tsx
================================================
import { toast } from 'react-toastify';
import { links } from '../../consts/links';
export function toastIgpDetails(igpFee: string, tokenName = 'native token') {
toast.error(<IgpDetailsToast tokenName={tokenName} igpFee={igpFee} />, {
autoClose: 5000,
});
}
export function IgpDetailsToast({ tokenName, igpFee }: { tokenName: string; igpFee: string }) {
return (
<div>
Cross-chain transfers require a fee of {igpFee} {tokenName} to fund delivery transaction
costs. Your {tokenName} balance is insufficient.{' '}
<a className="underline" href={links.gasDocs} target="_blank" rel="noopener noreferrer">
Learn More
</a>
</div>
);
}
================================================
FILE: src/components/toast/TxSuccessToast.tsx
================================================
import { toast } from 'react-toastify';
import { useMultiProvider } from '../../features/chains/hooks';
export function toastTxSuccess(msg: string, txHash: string, chain: ChainName) {
toast.success(<TxSuccessToast msg={msg} txHash={txHash} chain={chain} />, {
autoClose: 12000,
});
}
export function TxSuccessToast({
msg,
txHash,
chain,
}: {
msg: string;
txHash: string;
chain: ChainName;
}) {
const multiProvider = useMultiProvider();
const url = multiProvider.tryGetExplorerTxUrl(chain, { hash: txHash });
return (
<div>
{msg + ' '}
{url && (
<a className="underline" href={url} target="_blank" rel="noopener noreferrer">
Open in Explorer
</a>
)}
</div>
);
}
================================================
FILE: src/components/toast/useToastError.tsx
================================================
import { errorToString } from '@hyperlane-xyz/utils';
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { logger } from '../../utils/logger';
export function useToastError(error: any, errorMsg?: string) {
useEffect(() => {
if (!error) return;
const message = errorMsg || errorToString(error, 500);
logger.error(message, error);
toast.error(errorMsg);
}, [error, errorMsg]);
}
================================================
FILE: src/consts/app.ts
================================================
import { Color } from '../styles/Color';
export type UiThemeMode = 'light' | 'dark';
export const APP_NAME = 'Hyperlane Warp UI Template';
export const APP_DESCRIPTION = 'A DApp for Hyperlane Warp Route transfers';
export const APP_URL = 'hyperlane-warp-template.vercel.app';
export const BRAND_COLOR = Color.primary['500'];
export const UI_THEME_STORAGE_KEY = 'warp-ui-theme';
export const DEFAULT_UI_THEME_MODE: UiThemeMode = 'light';
================================================
FILE: src/consts/args.ts
================================================
import { ProtocolType } from '@hyperlane-xyz/utils';
export enum WARP_QUERY_PARAMS {
ORIGIN = 'origin',
DESTINATION = 'destination',
ORIGIN_TOKEN = 'originToken',
DESTINATION_TOKEN = 'destinationToken',
}
export const ADD_ASSET_SUPPORTED_PROTOCOLS: ProtocolType[] = [ProtocolType.Ethereum];
================================================
FILE: src/consts/blacklist.ts
================================================
// A list of addresses that are cannot be used in the app
// If a wallet with this address is connected, the app will show an error
export const ADDRESS_BLACKLIST: string[] = [];
================================================
FILE: src/consts/chainAddresses.ts
================================================
import { ChainAddresses } from '@hyperlane-xyz/registry';
import { ChainMap } from '@hyperlane-xyz/sdk';
// Per-chain contract addresses to merge with the configured registry's
// addresses. Entries here override registry entries per key. Useful when
// you need TypeScript-side imports (e.g. importing pre-built `*Addresses`
// constants from `@hyperlane-xyz/registry`).
//
// For YAML-friendly definitions, use `chainAddresses.yaml` instead.
// Schema: any contract addresses you'd find in a registry chain's addresses.yaml
// (e.g. mailbox, quotedCalls, validatorAnnounce, ...)
export const addresses: ChainMap<ChainAddresses> = {
// mychain: {
// mailbox: '0x...',
// quotedCalls: '0x...',
// },
};
================================================
FILE: src/consts/chainAddresses.yaml
================================================
# A map of chain name -> contract addresses
# Merges with addresses from the configured registry. Filesystem entries
# override registry entries per key, mirroring how chains.yaml extends chains.
# Schema: any contract addresses you'd find in a registry chain's addresses.yaml
# (e.g. mailbox, quotedCalls, validatorAnnounce, ...)
# Full set of valid keys: https://github.com/hyperlane-xyz/hyperlane-registry/blob/main/chains/ethereum/addresses.yaml
# (or the ChainAddresses type from @hyperlane-xyz/registry)
#
# Note: schema validation runs at app init — a typo here will throw and
# block the whole warp context init, not just balance fetching.
---
# Example using local anvil chain:
# anvil1:
# mailbox: '0x610178dA211FEF7D417bC0e6FeD39F05609AD788'
# quotedCalls: '0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE'
# anvil2:
# mailbox: '0x610178dA211FEF7D417bC0e6FeD39F05609AD788'
# quotedCalls: '0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE'
================================================
FILE: src/consts/chains.ts
================================================
import {
eclipsemainnet,
eclipsemainnetAddresses,
solanamainnet,
solanamainnetAddresses,
solaxy,
solaxyAddresses,
sonicsvm,
sonicsvmAddresses,
soon,
soonAddresses,
} from '@hyperlane-xyz/registry';
import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk';
// A map of chain names to ChainMetadata
// Chains can be defined here, in chains.json, or in chains.yaml
// Chains already in the SDK need not be included here unless you want to override some fields
// Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts
export const chains: ChainMap<ChainMetadata & { mailbox?: Address }> = {
solanamainnet: {
...solanamainnet,
// SVM chains require mailbox addresses for the token adapters
mailbox: solanamainnetAddresses.mailbox,
},
eclipsemainnet: {
...eclipsemainnet,
mailbox: eclipsemainnetAddresses.mailbox,
},
soon: {
...soon,
mailbox: soonAddresses.mailbox,
},
sonicsvm: {
...sonicsvm,
mailbox: sonicsvmAddresses.mailbox,
},
solaxy: {
...solaxy,
mailbox: solaxyAddresses.mailbox,
},
// mycustomchain: {
// protocol: ProtocolType.Ethereum,
// chainId: 123123,
// domainId: 123123,
// name: 'mycustomchain',
// displayName: 'My Chain',
// nativeToken: { name: 'Ether', symbol: 'ETH', decimals: 18 },
// rpcUrls: [{ http: 'https://mycustomchain-rpc.com' }],
// blockExplorers: [
// {
// name: 'MyCustomScan',
// url: 'https://mycustomchain-scan.com',
// apiUrl: 'https://api.mycustomchain-scan.com/api',
// family: ExplorerFamily.Etherscan,
// },
// ],
// blocks: {
// confirmations: 1,
// reorgPeriod: 1,
// estimateBlockTime: 10,
// },
// logoURI: '/logo.svg',
// },
};
// rent account payment for (mostly for) SVM chains added on top of IGP,
// not exact but should be pretty close to actual payment
export const chainsRentEstimate: ChainMap<bigint> = {
eclipsemainnet: BigInt(Math.round(0.00004019 * 10 ** 9)),
solanamainnet: BigInt(Math.round(0.00411336 * 10 ** 9)),
sonicsvm: BigInt(Math.round(0.00411336 * 10 ** 9)),
soon: BigInt(Math.round(0.00000355 * 10 ** 9)),
};
================================================
FILE: src/consts/chains.yaml
================================================
# A map of chain names to ChainMetadata
# Chains can be defined here, in chains.json, or in chains.ts
# Chains already in the SDK need not be included here unless you want to override some fields
# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts
---
# Example using local anvil chain:
# anvil1:
# chainId: 31337
# domainId: 31337
# name: anvil1
# protocol: ethereum
# rpcUrls:
# - http: http://127.0.0.1:8545
# anvil2:
# chainId: 31338
# domainId: 31338
# name: anvil2
# protocol: ethereum
# rpcUrls:
# - http: http://127.0.0.1:8555
================================================
FILE: src/consts/config.ts
================================================
import { ChainMap } from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { ADDRESS_BLACKLIST } from './blacklist';
const isDevMode = process.env.NODE_ENV === 'development';
const version = process.env.NEXT_PUBLIC_VERSION || '2.0.0';
const registryUrl = process.env.NEXT_PUBLIC_REGISTRY_URL || undefined;
const registryBranch = process.env.NEXT_PUBLIC_REGISTRY_BRANCH || undefined;
const registryProxyUrl = process.env.NEXT_PUBLIC_GITHUB_PROXY || 'https://proxy.hyperlane.xyz';
const walletConnectProjectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_ID || '';
const transferBlacklist = process.env.NEXT_PUBLIC_TRANSFER_BLACKLIST || '';
const chainWalletWhitelists = JSON.parse(process.env.NEXT_PUBLIC_CHAIN_WALLET_WHITELISTS || '{}');
const rpcOverrides = process.env.NEXT_PUBLIC_RPC_OVERRIDES || '';
const explorerApiUrl =
process.env.NEXT_PUBLIC_EXPLORER_API_URL || 'https://explorer4.hasura.app/v1/graphql';
const feeQuotingUrl = process.env.NEXT_PUBLIC_FEE_QUOTING_URL || undefined;
const relayApiUrl = process.env.NEXT_PUBLIC_RELAY_API_URL || undefined;
interface Config {
addressBlacklist: string[]; // A list of addresses that are blacklisted and cannot be used in the app
chainWalletWhitelists: ChainMap<string[]>; // A map of chain names to a list of wallet names that work for it
defaultOriginToken: string | undefined; // The initial origin token to show when app first loads (format: chainName-symbol, e.g. "ethereum-hyper")
defaultDestinationToken: string | undefined; // The initial destination token to show when app first loads (format: chainName-symbol, e.g. "bsc-hyper")
enableExplorerLink: boolean; // Include a link to the hyperlane explorer in the transfer modal
explorerApiUrl: string; // URL for the Hyperlane Explorer GraphQL API
relayApiUrl: string | undefined; // Optional URL for the Hyperlane Relayer API
isDevMode: boolean; // Enables some debug features in the app
registryUrl: string | undefined; // Optional URL to use a custom registry instead of the published canonical version
registryBranch?: string | undefined; // Optional customization of the registry branch instead of main
registryProxyUrl?: string; // Optional URL to use a custom proxy for the GithubRegistry
showTipBox: boolean; // Show/Hide the blue tip box above the transfer form
shouldDisableChains: boolean; // Enable chain disabling for ChainSearchMenu. When true it will deactivate chains that have disabled status
transferBlacklist: string; // comma-separated list of routes between which transfers are disabled. Expects Caip2Id-Caip2Id (e.g. ethereum:1-sealevel:1399811149)
version: string; // Matches version number in package.json
walletConnectProjectId: string; // Project ID provided by walletconnect
walletProtocols: ProtocolType[] | undefined; // Wallet Protocols to show in the wallet connect modal. Leave undefined to include all of them
rpcOverrides: string; // JSON string containing a map of chain names to an object with an URL for RPC overrides (For an example check the .env.example file)
enableTrackingEvents: boolean; // Allow tracking events to happen on some actions;
featuredTokens: string[]; // List of featured tokens to prioritize in token picker (format: "chainName-symbol")
feeQuotingUrl: string | undefined; // Offchain fee quoting service base URL
}
export const config: Config = Object.freeze({
addressBlacklist: ADDRESS_BLACKLIST.map((address) => address.toLowerCase()),
chainWalletWhitelists,
enableExplorerLink: false,
explorerApiUrl,
relayApiUrl,
defaultOriginToken: 'ethereum-USDC',
defaultDestinationToken: 'base-USDC',
isDevMode,
registryUrl,
registryBranch,
registryProxyUrl,
showTipBox: true,
version,
transferBlacklist,
walletConnectProjectId,
walletProtocols: [
ProtocolType.Ethereum,
ProtocolType.Sealevel,
ProtocolType.Cosmos,
ProtocolType.Starknet,
ProtocolType.Radix,
ProtocolType.Tron,
ProtocolType.Aleo,
],
shouldDisableChains: false,
rpcOverrides,
enableTrackingEvents: false,
feeQuotingUrl,
featuredTokens: [
// USDC
'arbitrum-USDC',
'avalanche-USDC',
'base-USDC',
'eclipsemainnet-USDC',
'ethereum-USDC',
'hyperevm-USDC',
'ink-USDC',
'linea-USDC',
'monad-USDC',
'optimism-USDC',
'polygon-USDC',
'solanamainnet-USDC',
'unichain-USDC',
'worldchain-USDC',
// ETH
'arbitrum-ETH',
'base-ETH
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
SYMBOL INDEX (658 symbols across 192 files)
FILE: next.config.js
constant ENABLE_CSP_HEADER (line 10) | const ENABLE_CSP_HEADER = true;
constant FRAME_SRC_HOSTS (line 11) | const FRAME_SRC_HOSTS = [
constant STYLE_SRC_HOSTS (line 19) | const STYLE_SRC_HOSTS = ['https://js.refiner.io', 'https://storage.refin...
constant IMG_SRC_HOSTS (line 20) | const IMG_SRC_HOSTS = [
constant SCRIPT_SRC_HOSTS (line 32) | const SCRIPT_SRC_HOSTS = [
constant MEDIA_SRC_HOSTS (line 39) | const MEDIA_SRC_HOSTS = [
method headers (line 165) | async headers() {
FILE: scripts/fetch-fonts.mjs
constant FONTS_DIR (line 8) | const FONTS_DIR = join(__dirname, '..', 'public', 'fonts');
constant FONTS (line 11) | const FONTS = ['PPValve-PlainVariable.woff2', 'PPFraktionMono-Variable.w...
function fetchFonts (line 13) | async function fetchFonts() {
FILE: scripts/link-monorepo.js
constant MONOREPO_NAME (line 6) | const MONOREPO_NAME = 'hyperlane-monorepo';
constant REACT_APP_DIR (line 7) | const REACT_APP_DIR = process.cwd();
constant MONOREPO_PATH (line 8) | const MONOREPO_PATH = path.resolve(REACT_APP_DIR, '..', MONOREPO_NAME);
constant TYPESCRIPT_DIR (line 9) | const TYPESCRIPT_DIR = path.join(MONOREPO_PATH, 'typescript');
constant SOLIDITY_DIR (line 10) | const SOLIDITY_DIR = path.join(MONOREPO_PATH, 'solidity');
constant LOCAL_TARBALLS_DIR (line 11) | const LOCAL_TARBALLS_DIR = path.join(REACT_APP_DIR, '.monorepo-tarballs');
constant DEFAULT_PACKAGES (line 14) | const DEFAULT_PACKAGES = [
function run (line 34) | function run(command, cwd = REACT_APP_DIR) {
function validatePackagePath (line 47) | function validatePackagePath(folder) {
function packPackage (line 104) | function packPackage(pkgPath) {
FILE: scripts/unlink-monorepo.js
constant REACT_APP_DIR (line 9) | const REACT_APP_DIR = process.cwd();
constant LOCAL_TARBALLS_DIR (line 11) | const LOCAL_TARBALLS_DIR = path.join(REACT_APP_DIR, '.monorepo-tarballs');
FILE: sentry.default.config.js
method beforeSend (line 30) | beforeSend(event, hint) {
FILE: src/components/banner/FormWarningBanner.tsx
function FormWarningBanner (line 6) | function FormWarningBanner({
FILE: src/components/banner/RecipientWarningBanner.tsx
function RecipientWarningBanner (line 3) | function RecipientWarningBanner({
FILE: src/components/banner/WarningBanner.tsx
function WarningBanner (line 4) | function WarningBanner({
FILE: src/components/buttons/ConnectAwareSubmitButton.tsx
type Props (line 15) | interface Props {
function ConnectAwareSubmitButton (line 22) | function ConnectAwareSubmitButton<FormValues = any>({
FILE: src/components/buttons/SolidButton.tsx
type ButtonProps (line 3) | interface ButtonProps {
function SolidButton (line 11) | function SolidButton(
FILE: src/components/errors/ErrorBoundary.tsx
function ErrorBoundary (line 6) | function ErrorBoundary({ children }: PropsWithChildren<unknown>) {
function SupportLink (line 10) | function SupportLink() {
FILE: src/components/icons/BookIcon.tsx
function _BookIcon (line 6) | function _BookIcon({ color, ...props }: DefaultIconProps) {
FILE: src/components/icons/ChainLogo.tsx
function ChainLogo (line 8) | function ChainLogo({
FILE: src/components/icons/ChevronLargeIcon.tsx
function _ChevronLargeIcon (line 6) | function _ChevronLargeIcon({ color, ...props }: DefaultIconProps) {
FILE: src/components/icons/HamburgerIcon.tsx
function _HamburgerIcon (line 4) | function _HamburgerIcon({ color, ...props }: DefaultIconProps) {
FILE: src/components/icons/HyperlaneGradientLogo.tsx
function _HyperlaneGradientLogo (line 4) | function _HyperlaneGradientLogo({ ...props }: DefaultIconProps) {
FILE: src/components/icons/HyperlaneTransparentLogo.tsx
function _HyperlaneTransparentLogo (line 3) | function _HyperlaneTransparentLogo() {
FILE: src/components/icons/QuestionMarkIcon.tsx
function _QuestionMarkIcon (line 6) | function _QuestionMarkIcon({ color, width = 20, height = 20, ...rest }: ...
FILE: src/components/icons/StakeIcon.tsx
function _StakeIcon (line 4) | function _StakeIcon({ ...props }: DefaultIconProps) {
FILE: src/components/icons/SwapIcon.tsx
function _SwapIcon (line 6) | function _SwapIcon({ color, ...props }: DefaultIconProps) {
FILE: src/components/icons/TokenIcon.tsx
type Props (line 14) | interface Props {
function TokenIcon (line 19) | function TokenIcon({ token, size = 32 }: Props) {
function getImageSrc (line 84) | function getImageSrc(token?: IToken | null) {
FILE: src/components/icons/WebSimpleIcon.tsx
function _WebSimpleIcon (line 6) | function _WebSimpleIcon({ color, ...props }: DefaultIconProps) {
FILE: src/components/icons/XIcon.tsx
function _XIcon (line 6) | function _XIcon({ color, ...props }: DefaultIconProps) {
FILE: src/components/input/SearchInput.tsx
function SearchInput (line 6) | function SearchInput({
FILE: src/components/input/TextField.tsx
function TextField (line 5) | function TextField({ className, ...props }: FieldAttributes<unknown>) {
type InputProps (line 9) | type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'...
FILE: src/components/layout/AppLayout.tsx
function AppLayout (line 17) | function AppLayout({ children }: PropsWithChildren) {
FILE: src/components/layout/Card.tsx
type Props (line 3) | interface Props {
function Card (line 7) | function Card({ className = '', children }: PropsWithChildren<Props>) {
FILE: src/components/layout/ModalHeader.tsx
function ModalHeader (line 4) | function ModalHeader({ children, className }: { children?: ReactNode; cl...
FILE: src/components/nav/Footer.tsx
function Footer (line 4) | function Footer() {
function FooterLogo (line 17) | function FooterLogo() {
function FooterNav (line 29) | function FooterNav() {
FILE: src/components/nav/Header.tsx
function Header (line 13) | function Header() {
FILE: src/components/nav/Nav.tsx
type NavLinkItem (line 14) | interface NavLinkItem {
type NavItemProps (line 41) | interface NavItemProps {
FILE: src/components/tip/TipCard.tsx
function TipCard (line 10) | function TipCard() {
FILE: src/components/toast/IgpDetailsToast.tsx
function toastIgpDetails (line 5) | function toastIgpDetails(igpFee: string, tokenName = 'native token') {
function IgpDetailsToast (line 11) | function IgpDetailsToast({ tokenName, igpFee }: { tokenName: string; igp...
FILE: src/components/toast/TxSuccessToast.tsx
function toastTxSuccess (line 5) | function toastTxSuccess(msg: string, txHash: string, chain: ChainName) {
function TxSuccessToast (line 11) | function TxSuccessToast({
FILE: src/components/toast/useToastError.tsx
function useToastError (line 7) | function useToastError(error: any, errorMsg?: string) {
FILE: src/consts/app.ts
type UiThemeMode (line 3) | type UiThemeMode = 'light' | 'dark';
constant APP_NAME (line 5) | const APP_NAME = 'Hyperlane Warp UI Template';
constant APP_DESCRIPTION (line 6) | const APP_DESCRIPTION = 'A DApp for Hyperlane Warp Route transfers';
constant APP_URL (line 7) | const APP_URL = 'hyperlane-warp-template.vercel.app';
constant BRAND_COLOR (line 8) | const BRAND_COLOR = Color.primary['500'];
constant UI_THEME_STORAGE_KEY (line 10) | const UI_THEME_STORAGE_KEY = 'warp-ui-theme';
constant DEFAULT_UI_THEME_MODE (line 11) | const DEFAULT_UI_THEME_MODE: UiThemeMode = 'light';
FILE: src/consts/args.ts
type WARP_QUERY_PARAMS (line 3) | enum WARP_QUERY_PARAMS {
constant ADD_ASSET_SUPPORTED_PROTOCOLS (line 10) | const ADD_ASSET_SUPPORTED_PROTOCOLS: ProtocolType[] = [ProtocolType.Ethe...
FILE: src/consts/blacklist.ts
constant ADDRESS_BLACKLIST (line 3) | const ADDRESS_BLACKLIST: string[] = [];
FILE: src/consts/config.ts
type Config (line 20) | interface Config {
FILE: src/consts/warpRouteWhitelist.ts
function getWarpRouteWhitelist (line 18) | function getWarpRouteWhitelist(): Array<string> | null {
FILE: src/features/WarpContextInitGate.tsx
constant INIT_TIMEOUT (line 7) | const INIT_TIMEOUT = 10_000;
function WarpContextInitGate (line 10) | function WarpContextInitGate({ children }: PropsWithChildren<unknown>) {
FILE: src/features/analytics/intercom.ts
constant INTERCOM_APP_ID (line 5) | const INTERCOM_APP_ID = process.env.NEXT_PUBLIC_INTERCOM_APP_ID || '';
function initIntercom (line 13) | function initIntercom(): void {
FILE: src/features/analytics/refiner.ts
constant REFINER_PROJECT_ID (line 4) | const REFINER_PROJECT_ID = process.env.NEXT_PUBLIC_REFINER_PROJECT_ID ||...
constant REFINER_TRANSFER_FORM_ID (line 5) | const REFINER_TRANSFER_FORM_ID = process.env.NEXT_PUBLIC_REFINER_TRANSFE...
type RefinerFn (line 7) | type RefinerFn = (method: string, ...args: unknown[]) => void;
function initRefiner (line 15) | async function initRefiner(): Promise<void> {
function refinerIdentifyAndShowTransferForm (line 29) | function refinerIdentifyAndShowTransferForm(params: {
FILE: src/features/analytics/types.ts
type EVENT_NAME (line 3) | enum EVENT_NAME {
type AllowedPropertyValues (line 14) | type AllowedPropertyValues = string | number | boolean | null;
type EventProperties (line 17) | type EventProperties = {
FILE: src/features/analytics/useWalletConnectionTracking.tsx
function useWalletConnectionTracking (line 17) | function useWalletConnectionTracking() {
FILE: src/features/analytics/utils.ts
function trackEvent (line 15) | function trackEvent<T extends EVENT_NAME>(eventName: T, properties: Even...
function trackTokenSelectionEvent (line 25) | function trackTokenSelectionEvent(
function trackChainSelectionEvent (line 49) | function trackChainSelectionEvent(
constant SKIPPED_ERRORS (line 64) | const SKIPPED_ERRORS = [
function trackTransactionFailedEvent (line 71) | function trackTransactionFailedEvent(
function trackUnsupportedRouteEvent (line 117) | function trackUnsupportedRouteEvent(
FILE: src/features/balances/UsdLabel.tsx
function UsdLabel (line 6) | function UsdLabel({
FILE: src/features/balances/cosmos.ts
type CosmosChainGroup (line 9) | interface CosmosChainGroup {
constant BANK_DENOM_FROM_ADDRESS (line 16) | const BANK_DENOM_FROM_ADDRESS: TokenStandard[] = [
constant BANK_DENOM_FROM_COLLATERAL (line 25) | const BANK_DENOM_FROM_COLLATERAL: TokenStandard[] = [
function classifyCosmosToken (line 31) | function classifyCosmosToken(token: Token): { type: 'bank' | 'unknown'; ...
function groupCosmosTokensByChain (line 48) | function groupCosmosTokensByChain(tokens: Token[]): Map<string, CosmosCh...
function fetchCosmosChainBalances (line 81) | async function fetchCosmosChainBalances(
FILE: src/features/balances/evm.ts
constant MULTICALL3_ADDRESS (line 18) | const MULTICALL3_ADDRESS = '0xca11bde05977b3631167028862be2a173976ca11' ...
type TokenClassification (line 31) | type TokenClassification = 'erc20' | 'lockbox' | 'native' | 'unknown';
type ChainGroup (line 33) | interface ChainGroup {
type CallInfo (line 40) | interface CallInfo {
type Aggregate3Result (line 46) | type Aggregate3Result = Array<{ success: boolean; returnData: Hex }>;
function classifyToken (line 49) | function classifyToken(token: Token): { type: TokenClassification; erc20...
function getBatchAddress (line 65) | function getBatchAddress(chainName: string, chainAddresses: ChainMap<Cha...
function groupEvmTokensByChain (line 74) | function groupEvmTokensByChain(
function callAggregate3 (line 115) | async function callAggregate3(
type LockboxResolution (line 147) | interface LockboxResolution {
function resolveLockboxTokens (line 156) | async function resolveLockboxTokens(
function decodeBalanceResults (line 200) | function decodeBalanceResults(
function fetchChainBalances (line 222) | async function fetchChainBalances(
FILE: src/features/balances/feeUsdDisplay.test.ts
function makeAmount (line 15) | function makeAmount(token: typeof ethToken, wei: bigint) {
FILE: src/features/balances/feeUsdDisplay.ts
function getUsdDisplayForFee (line 7) | function getUsdDisplayForFee(
function getTotalFeesUsdRaw (line 19) | function getTotalFeesUsdRaw(
function getTotalFeesUsd (line 33) | function getTotalFeesUsd(
function getFeePercentage (line 42) | function getFeePercentage(totalFeesUsd: number, transferUsd: number): st...
FILE: src/features/balances/hooks.ts
function useBalance (line 24) | function useBalance(chain?: ChainName, token?: IToken, address?: Address) {
function useOriginBalance (line 53) | function useOriginBalance(originToken?: Token) {
function useDestinationBalance (line 60) | function useDestinationBalance(recipient?: string, destinationToken?: To...
function getDestinationNativeBalance (line 65) | async function getDestinationNativeBalance(
function useEvmWalletBalance (line 82) | function useEvmWalletBalance(
function useWalletAddresses (line 106) | function useWalletAddresses(multiProvider: MultiProtocolProvider): Map<P...
function useTokenBalances (line 124) | function useTokenBalances(tokens: Token[], scope: string, addressOverrid...
FILE: src/features/balances/svm.ts
type SealevelTokenClassification (line 10) | type SealevelTokenClassification = 'spl' | 'spl2022' | 'native' | 'unkno...
type SealevelTokenEntry (line 12) | interface SealevelTokenEntry extends TokenEntry {
type SealevelChainGroup (line 16) | interface SealevelChainGroup {
function classifySealevelToken (line 23) | function classifySealevelToken(token: Token): {
function groupSealevelTokensByChain (line 46) | function groupSealevelTokensByChain(tokens: Token[]): Map<string, Sealev...
function buildMintToKeysMap (line 82) | function buildMintToKeysMap(entries: SealevelTokenEntry[]): Map<string, ...
type ParsedTokenAccount (line 95) | interface ParsedTokenAccount {
function parseSplTokenAccounts (line 99) | function parseSplTokenAccounts(
function fetchSealevelChainBalances (line 117) | async function fetchSealevelChainBalances(
FILE: src/features/balances/tokens.ts
type TokenEntry (line 5) | interface TokenEntry {
function fetchSdkBalance (line 11) | async function fetchSdkBalance(
FILE: src/features/balances/useFeePrices.ts
constant FEE_PRICE_REFRESH_INTERVAL (line 8) | const FEE_PRICE_REFRESH_INTERVAL = 300_000;
type FeePrices (line 11) | type FeePrices = Record<string, number>;
function resolveCoinGeckoId (line 15) | function resolveCoinGeckoId(
function useFeePrices (line 38) | function useFeePrices(
FILE: src/features/balances/utils.ts
function formatBalance (line 6) | function formatBalance(balance: bigint, decimals: number): string {
function formatUsd (line 10) | function formatUsd(value: number, approximate = false): string {
function getUsdValue (line 16) | function getUsdValue(
FILE: src/features/chains/ChainConnectionWarning.tsx
function ChainConnectionWarning (line 12) | function ChainConnectionWarning({
function checkRpcHealth (line 67) | async function checkRpcHealth(chainMetadata: ChainMetadata) {
FILE: src/features/chains/ChainEditModal.tsx
type Props (line 9) | interface Props {
function ChainEditModal (line 17) | function ChainEditModal({ isOpen, close, chainName, onClickBack }: Props) {
FILE: src/features/chains/ChainFilterPanel.tsx
type ChainFilterPanelProps (line 28) | interface ChainFilterPanelProps {
function ChainFilterPanel (line 38) | function ChainFilterPanel({
function FilterButton (line 122) | function FilterButton({
function SortButton (line 221) | function SortButton({
function useClickOutside (line 295) | function useClickOutside(ref: React.RefObject<HTMLElement | null>, handl...
FILE: src/features/chains/ChainList.tsx
type ChainListProps (line 16) | interface ChainListProps {
function ChainList (line 25) | function ChainList({
function ChainButton (line 85) | function ChainButton({
FILE: src/features/chains/ChainWalletWarning.tsx
function ChainWalletWarning (line 15) | function ChainWalletWarning({ origin }: { origin: ChainName }) {
FILE: src/features/chains/MobileChainQuickSelect.tsx
constant DEFAULT_MAX_VISIBLE_CHAINS (line 7) | const DEFAULT_MAX_VISIBLE_CHAINS = 4;
type MobileChainQuickSelectProps (line 9) | interface MobileChainQuickSelectProps {
function MobileChainQuickSelect (line 17) | function MobileChainQuickSelect({
FILE: src/features/chains/addresses.ts
function assembleChainAddresses (line 11) | async function assembleChainAddresses(
FILE: src/features/chains/chainFilterSort.ts
type ChainSortBy (line 6) | enum ChainSortBy {
type SortOrder (line 12) | enum SortOrder {
type SortState (line 17) | interface SortState {
type FilterTestnet (line 28) | enum FilterTestnet {
type ChainFilterState (line 33) | interface ChainFilterState {
function isFilterActive (line 43) | function isFilterActive(filter: ChainFilterState): boolean {
function chainSearch (line 50) | function chainSearch({
FILE: src/features/chains/hooks.ts
function useMultiProvider (line 9) | function useMultiProvider() {
function useReadyMultiProvider (line 15) | function useReadyMultiProvider() {
function useChainMetadata (line 21) | function useChainMetadata(chainName?: ChainName) {
function useChainProtocol (line 27) | function useChainProtocol(chainName?: ChainName) {
function useChainDisplayName (line 32) | function useChainDisplayName(chainName: ChainName, shortName = false) {
type ChainInfo (line 37) | interface ChainInfo {
function useChainInfos (line 46) | function useChainInfos(): ChainInfo[] {
function useDisabledChains (line 62) | function useDisabledChains(): Set<string> {
FILE: src/features/chains/metadata.ts
function assembleChainMetadata (line 24) | async function assembleChainMetadata(
FILE: src/features/chains/utils.ts
type ChainMetadataProvider (line 8) | type ChainMetadataProvider = Pick<
function getChainDisplayName (line 13) | function getChainDisplayName(
function isPermissionlessChain (line 25) | function isPermissionlessChain(multiProvider: ChainMetadataProvider, cha...
function hasPermissionlessChain (line 31) | function hasPermissionlessChain(multiProvider: ChainMetadataProvider, id...
function getNumRoutesWithSelectedChain (line 39) | function getNumRoutesWithSelectedChain(
function isChainDisabled (line 73) | function isChainDisabled(chainMetadata: ChainMetadata | null) {
function tryGetValidChainName (line 82) | function tryGetValidChainName(
FILE: src/features/limits/types.ts
type RouteLimit (line 1) | type RouteLimit = {
FILE: src/features/limits/utils.ts
function getMultiCollateralTokenLimit (line 7) | function getMultiCollateralTokenLimit(
function isMultiCollateralLimitExceeded (line 27) | function isMultiCollateralLimitExceeded(
FILE: src/features/messages/graphqlClient.ts
type GraphQLResult (line 3) | type GraphQLResult<T> = { type: 'success'; data: T } | { type: 'error'; ...
function executeGraphQLQuery (line 5) | async function executeGraphQLQuery<T = unknown>(
FILE: src/features/messages/queries/build.ts
constant MESSAGE_HISTORY_QUERY (line 5) | const MESSAGE_HISTORY_QUERY = `
function buildMessageHistoryQuery (line 30) | function buildMessageHistoryQuery(
constant MESSAGE_BY_ID_QUERY (line 73) | const MESSAGE_BY_ID_QUERY = `
function buildMessageByIdQuery (line 87) | function buildMessageByIdQuery(msgId: string): {
FILE: src/features/messages/queries/encoding.ts
function stringToPostgresBytea (line 12) | function stringToPostgresBytea(hexString: string): string {
function postgresByteaToString (line 17) | function postgresByteaToString(byteString: string): string {
function addressToPostgresBytea (line 22) | function addressToPostgresBytea(address: string): string {
function postgresByteaToAddress (line 27) | function postgresByteaToAddress(
function postgresByteaToTxHash (line 37) | function postgresByteaToTxHash(
function parseTimestamp (line 47) | function parseTimestamp(t: string): number {
FILE: src/features/messages/queries/fragments.ts
type MessageStubEntry (line 37) | interface MessageStubEntry {
FILE: src/features/messages/types.ts
type MessageStatus (line 3) | enum MessageStatus {
type MessageTxStub (line 10) | interface MessageTxStub {
type WarpTransferInfo (line 17) | interface WarpTransferInfo {
type MessageStub (line 22) | interface MessageStub {
FILE: src/features/messages/useMergedTransferHistory.test.ts
constant TEST_CHAIN_METADATA (line 11) | const TEST_CHAIN_METADATA: ChainMap<ChainMetadata<{ mailbox?: string }>>...
FILE: src/features/messages/useMergedTransferHistory.ts
type TransferItem (line 15) | type TransferItem =
function messageToTransferContext (line 22) | function messageToTransferContext(
function useMergedTransferHistory (line 67) | function useMergedTransferHistory(
FILE: src/features/messages/useMessageDeliveryStatus.ts
constant POLL_INTERVAL_MS (line 10) | const POLL_INTERVAL_MS = 10_000;
type MessageDeliveryResult (line 12) | interface MessageDeliveryResult {
function useMessageDeliveryStatus (line 29) | function useMessageDeliveryStatus(
function parseDeliveryResult (line 71) | function parseDeliveryResult(
FILE: src/features/messages/useMessageHistory.ts
constant PAGE_LIMIT (line 23) | const PAGE_LIMIT = 15;
constant REFRESH_INTERVAL_MS (line 24) | const REFRESH_INTERVAL_MS = 60_000;
type UseMessageHistoryResult (line 26) | interface UseMessageHistoryResult {
type PageResult (line 36) | interface PageResult {
function useMessageHistory (line 41) | function useMessageHistory(
function parseMessageEntry (line 133) | function parseMessageEntry(
FILE: src/features/messages/useOriginFinality.ts
constant POLL_INTERVAL_MS (line 7) | const POLL_INTERVAL_MS = 10_000;
function useOriginFinality (line 13) | function useOriginFinality(
FILE: src/features/routerAddresses.test.ts
constant VALID_EVM_ADDRESS (line 8) | const VALID_EVM_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
FILE: src/features/sanctions/hooks/useIsAccountChainalysisSanctioned.ts
constant ORACLE_ABI (line 8) | const ORACLE_ABI = [
constant ORACLE_ADDRESS (line 29) | const ORACLE_ADDRESS = '0x40C57923924B5c5c5455c48D93317139ADDaC8fb';
function useIsAccountChainalysisSanctioned (line 31) | function useIsAccountChainalysisSanctioned() {
FILE: src/features/sanctions/hooks/useIsAccountOfacSanctioned.ts
constant OFAC_SANCTIONED_ADDRESSES_ENDPOINT (line 7) | const OFAC_SANCTIONED_ADDRESSES_ENDPOINT =
function useIsAccountOfacSanctioned (line 10) | function useIsAccountOfacSanctioned() {
FILE: src/features/sanctions/hooks/useIsAccountSanctioned.ts
function useIsAccountSanctioned (line 4) | function useIsAccountSanctioned() {
FILE: src/features/store.ts
constant PERSIST_STATE_VERSION (line 41) | const PERSIST_STATE_VERSION = 2;
type WarpContext (line 43) | interface WarpContext {
function buildE2ETokenSnapshot (line 61) | function buildE2ETokenSnapshot(tokens: Token[] | undefined): E2ETokenSna...
type AppState (line 75) | interface AppState {
function initWarpContext (line 304) | async function initWarpContext({
function getRouterAddressesByChain (line 396) | function getRouterAddressesByChain(
FILE: src/features/theme/ThemeContext.tsx
type ThemeContextValue (line 15) | interface ThemeContextValue {
function getStoredThemeMode (line 22) | function getStoredThemeMode(): UiThemeMode | null {
function persistThemeMode (line 31) | function persistThemeMode(mode: UiThemeMode) {
function ThemeProvider (line 40) | function ThemeProvider({ children }: PropsWithChildren) {
function useTheme (line 92) | function useTheme() {
FILE: src/features/tokens/ImportTokenButton.tsx
constant USER_REJECTED_ERROR (line 9) | const USER_REJECTED_ERROR = 'User rejected';
type ImportTokenButtonProps (line 11) | interface ImportTokenButtonProps {
function ImportTokenButton (line 15) | function ImportTokenButton({ token }: ImportTokenButtonProps) {
FILE: src/features/tokens/SelectOrInputTokenIds.tsx
function SelectOrInputTokenIds (line 6) | function SelectOrInputTokenIds({ disabled }: { disabled: boolean }) {
function InputTokenId (line 21) | function InputTokenId({ disabled }: { disabled: boolean }) {
FILE: src/features/tokens/SelectTokenIdField.tsx
type Props (line 5) | type Props = {
function SelectTokenIdField (line 11) | function SelectTokenIdField({ name, disabled }: Props) {
function SelectTokenIdModal (line 49) | function SelectTokenIdModal({
FILE: src/features/tokens/TokenChainIcon.tsx
type Props (line 6) | interface Props {
function TokenChainIcon (line 11) | function TokenChainIcon({ token, size = 32 }: Props) {
FILE: src/features/tokens/TokenList.tsx
function isFeaturedToken (line 18) | function isFeaturedToken(token: Token): boolean {
function matchesSearch (line 22) | function matchesSearch(
type TokenListProps (line 36) | interface TokenListProps {
function TokenList (line 46) | function TokenList({
FILE: src/features/tokens/TokenListPanel.tsx
type TokenListPanelProps (line 12) | interface TokenListPanelProps {
function TokenListPanel (line 27) | function TokenListPanel({
FILE: src/features/tokens/TokenSelectField.tsx
type Props (line 20) | type Props = {
function TokenSelectField (line 29) | function TokenSelectField({
function TokenButton (line 174) | function TokenButton({
FILE: src/features/tokens/UnifiedTokenChainModal.tsx
type Props (line 12) | interface Props {
function UnifiedTokenChainModal (line 25) | function UnifiedTokenChainModal({
FILE: src/features/tokens/approval.ts
function useIsApproveRequired (line 9) | function useIsApproveRequired(
FILE: src/features/tokens/hooks.ts
function useWarpCore (line 17) | function useWarpCore() {
function getTokenByKey (line 23) | function getTokenByKey(tokens: Token[], key: string | undefined): Token ...
function findTokenByChainSymbol (line 29) | function findTokenByChainSymbol(tokens: Token[], chainSymbol: string): T...
function getInitialTokenKeys (line 41) | function getInitialTokenKeys(
function useWarpCoreTokens (line 130) | function useWarpCoreTokens() {
function useTokens (line 135) | function useTokens() {
function useCollateralGroups (line 139) | function useCollateralGroups() {
function useTokenByKeyMap (line 144) | function useTokenByKeyMap() {
function getTokenByKeyFromMap (line 152) | function getTokenByKeyFromMap(
function tryFindToken (line 160) | function tryFindToken(
function tryFindTokenConnection (line 172) | function tryFindTokenConnection(token: Token, chainName: string) {
function useAddToken (line 180) | function useAddToken(token?: IToken) {
FILE: src/features/tokens/types.ts
type TokensWithDestinationBalance (line 3) | interface TokensWithDestinationBalance {
type TokenWithFee (line 9) | interface TokenWithFee {
type TokenSelectionMode (line 15) | type TokenSelectionMode = 'origin' | 'destination';
type DefaultMultiCollateralRoutes (line 16) | type DefaultMultiCollateralRoutes = Record<ChainName, Record<Address, Ad...
FILE: src/features/tokens/useTokenPrice.tsx
constant PRICE_STALE_TIME (line 6) | const PRICE_STALE_TIME = 300_000;
type CoinGeckoResponse (line 8) | type CoinGeckoResponse = Record<string, { usd: number }>;
function fetchPrices (line 11) | async function fetchPrices(ids: string[]): Promise<Record<string, number...
function useTokenPrices (line 38) | function useTokenPrices() {
FILE: src/features/tokens/utils.ts
constant EXTRA_COLLATERALIZED_STANDARDS (line 25) | const EXTRA_COLLATERALIZED_STANDARDS = new Set([
function setResolvedUnderlyingMap (line 36) | function setResolvedUnderlyingMap(map: Map<string, string>) {
function isCollateralizedToken (line 41) | function isCollateralizedToken(token: IToken): boolean {
function isValidMultiCollateralToken (line 49) | function isValidMultiCollateralToken(
function findConnectedDestinationToken (line 63) | function findConnectedDestinationToken(
function matchesCollateral (line 93) | function matchesCollateral(
function getTokensWithSameCollateralAddresses (line 108) | function getTokensWithSameCollateralAddresses(
function getTokenKey (line 170) | function getTokenKey(token: IToken): string {
function dedupeTokensByCollateral (line 186) | function dedupeTokensByCollateral(tokens: Token[]): Token[] {
function buildTokensArray (line 212) | function buildTokensArray(warpCoreTokens: Token[]): Token[] {
function groupTokensByCollateral (line 244) | function groupTokensByCollateral(tokens: Token[]): Map<string, Token[]> {
function getCollateralKey (line 265) | function getCollateralKey(token: IToken): string {
function checkTokenHasRoute (line 305) | function checkTokenHasRoute(
function findRouteToken (line 325) | function findRouteToken(
function tryGetDefaultOriginToken (line 365) | function tryGetDefaultOriginToken(
FILE: src/features/tokens/wrappedTokenResolver.test.ts
constant ADDR_1 (line 8) | const ADDR_1 = '0x1111111111111111111111111111111111111111';
constant ADDR_2 (line 9) | const ADDR_2 = '0x2222222222222222222222222222222222222222';
constant UNDERLYING (line 10) | const UNDERLYING = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
FILE: src/features/tokens/wrappedTokenResolver.ts
constant WRAPPED_COLLATERAL_STANDARDS (line 15) | const WRAPPED_COLLATERAL_STANDARDS: string[] = [
function resolveWrappedCollateralTokens (line 27) | async function resolveWrappedCollateralTokens(
FILE: src/features/transfer/FeeSectionButton.tsx
function useLoadingDots (line 10) | function useLoadingDots(isLoading: boolean, intervalMs = 1000) {
function FeeSectionButton (line 28) | function FeeSectionButton({
FILE: src/features/transfer/RecipientConfirmationModal.tsx
function RecipientConfirmationModal (line 13) | function RecipientConfirmationModal({
FILE: src/features/transfer/TransferFeeModal.tsx
function TransferFeeModal (line 9) | function TransferFeeModal({
FILE: src/features/transfer/TransferSection.tsx
type TransferSectionProps (line 3) | type TransferSectionProps = {
function TransferSection (line 8) | function TransferSection({ label, children }: TransferSectionProps) {
FILE: src/features/transfer/TransferTokenCard.tsx
function TransferTokenCard (line 3) | function TransferTokenCard() {
FILE: src/features/transfer/TransferTokenForm.tsx
function TransferTokenForm (line 78) | function TransferTokenForm() {
function SwapTokensButton (line 199) | function SwapTokensButton({ disabled }: { disabled?: boolean }) {
function OriginTokenCard (line 257) | function OriginTokenCard({
function DestinationTokenCard (line 342) | function DestinationTokenCard({ isReview }: { isReview: boolean }) {
function MaxButton (line 388) | function MaxButton({
function TokenBalance (line 436) | function TokenBalance({
function TransferCheckout (line 456) | function TransferCheckout({
function ButtonSection (line 513) | function ButtonSection({
function ReviewDetails (line 718) | function ReviewDetails({
function WarningBanners (line 906) | function WarningBanners() {
function useFormInitialValues (line 925) | function useFormInitialValues(): TransferFormValues {
function useIsRouteSupported (line 942) | function useIsRouteSupported(): boolean {
function validateForm (line 958) | async function validateForm(
function enrichBalanceError (line 1102) | async function enrichBalanceError(
FILE: src/features/transfer/TransfersDetailsModal.tsx
constant DEFAULT_TIMINGS (line 46) | const DEFAULT_TIMINGS: StageTimings = {
function TransfersDetailsModal (line 52) | function TransfersDetailsModal({
function TransferProperty (line 386) | function TransferProperty({ name, value, url }: { name: string; value: s...
function WideChevron (line 407) | function WideChevron() {
function useSignIssueWarning (line 420) | function useSignIssueWarning(status: TransferStatus) {
function fixDoubleSlash (line 432) | function fixDoubleSlash(url: string) {
FILE: src/features/transfer/fees.test.ts
constant MOCK_RECIPIENT (line 16) | const MOCK_RECIPIENT = '0xrecipient';
constant MOCK_SENDER (line 17) | const MOCK_SENDER = '0xsender';
constant TRANSFER_AMOUNT (line 18) | const TRANSFER_AMOUNT = '500000';
constant LARGE_TRANSFER_AMOUNT (line 19) | const LARGE_TRANSFER_AMOUNT = '1000000';
constant BALANCE_TINY (line 22) | const BALANCE_TINY = BigInt(100);
constant BALANCE_SMALL (line 23) | const BALANCE_SMALL = BigInt(500);
constant BALANCE_MEDIUM (line 24) | const BALANCE_MEDIUM = BigInt(1000);
constant BALANCE_LARGE (line 25) | const BALANCE_LARGE = BigInt(1000000);
constant BALANCE_XLARGE (line 26) | const BALANCE_XLARGE = BigInt(2000000);
constant BALANCE_XXLARGE (line 27) | const BALANCE_XXLARGE = BigInt(5000000);
constant FEE_LOW (line 30) | const FEE_LOW = BigInt(1000);
constant FEE_MEDIUM (line 31) | const FEE_MEDIUM = BigInt(3000);
constant FEE_HIGH (line 32) | const FEE_HIGH = BigInt(5000);
FILE: src/features/transfer/fees.ts
function compareByBalanceDesc (line 20) | function compareByBalanceDesc(a: { balance: bigint }, b: { balance: bigi...
function filterAndSortTokensByBalance (line 27) | function filterAndSortTokensByBalance(
function sortTokensByFee (line 35) | function sortTokensByFee(tokenFees: TokenWithFee[]): TokenWithFee[] {
function getTotalFee (line 51) | function getTotalFee({
function getInterchainQuote (line 89) | function getInterchainQuote(
function getTransferToken (line 103) | async function getTransferToken(
FILE: src/features/transfer/maxAmount.ts
type FetchMaxParams (line 16) | interface FetchMaxParams {
function useFetchMaxAmount (line 24) | function useFetchMaxAmount() {
function fetchMaxAmount (line 35) | async function fetchMaxAmount(
FILE: src/features/transfer/predicate.ts
type FetchAttestationParams (line 14) | interface FetchAttestationParams {
type PredicateAttestationResult (line 24) | interface PredicateAttestationResult {
function fetchPredicateAttestation (line 38) | async function fetchPredicateAttestation({
FILE: src/features/transfer/relayApi.ts
constant MESSAGE_SENT_TOPIC (line 9) | const MESSAGE_SENT_TOPIC = '0x8c5261668696ce22758910d05bab8f186d6eb247ce...
constant MESSAGE_TRANSMITTER_V2_ADDRESSES (line 13) | const MESSAGE_TRANSMITTER_V2_ADDRESSES = new Set([
type RelayResponse (line 19) | interface RelayResponse {
function submitToRelayApi (line 32) | async function submitToRelayApi(
FILE: src/features/transfer/scaleUtils.ts
type ScaledToken (line 7) | interface ScaledToken {
function computeDestAmount (line 18) | function computeDestAmount(
function formatMessageAmount (line 42) | function formatMessageAmount(rawAmount: string, token: ScaledToken): str...
FILE: src/features/transfer/types.ts
type TransferFormValues (line 1) | interface TransferFormValues {
type TransferStatus (line 8) | enum TransferStatus {
type TransferContext (line 28) | interface TransferContext {
FILE: src/features/transfer/useBalanceWatcher.ts
function useRecipientBalanceWatcher (line 5) | function useRecipientBalanceWatcher(recipient?: Address, balance?: Token...
FILE: src/features/transfer/useFeeQuotes.test.ts
function mockOriginToken (line 20) | function mockOriginToken(protocol: ProtocolType): Token {
function mockDestinationToken (line 28) | function mockDestinationToken(protocol: ProtocolType): IToken {
FILE: src/features/transfer/useFeeQuotes.ts
constant FEE_QUOTE_REFRESH_INTERVAL (line 26) | const FEE_QUOTE_REFRESH_INTERVAL = 30_000;
constant EVM_FEE_QUOTE_FALLBACK_ADDRESS (line 27) | const EVM_FEE_QUOTE_FALLBACK_ADDRESS = '0x000000000000000000000000000000...
function useFeeQuotes (line 29) | function useFeeQuotes(
function fetchFeeQuotes (line 104) | async function fetchFeeQuotes(
FILE: src/features/transfer/useQuotedCalls.ts
constant FEE_QUOTE_REFRESH_INTERVAL (line 25) | const FEE_QUOTE_REFRESH_INTERVAL = 30_000;
constant MAX_QUOTE_AGE_MS (line 29) | const MAX_QUOTE_AGE_MS = 4 * 60_000;
type QuotedCallsFetchResult (line 31) | interface QuotedCallsFetchResult {
type QuotedCallsFeeQuotesResult (line 39) | interface QuotedCallsFeeQuotesResult {
function useQuotedCallsFeeQuotes (line 54) | function useQuotedCallsFeeQuotes(
function generateClientSalt (line 173) | function generateClientSalt(): Hex {
function fetchQuotedCallsFees (line 179) | async function fetchQuotedCallsFees(
FILE: src/features/transfer/useTokenTransfer.ts
constant CHAIN_MISMATCH_ERROR (line 34) | const CHAIN_MISMATCH_ERROR = 'ChainMismatchError';
constant TRANSFER_TIMEOUT_ERROR1 (line 35) | const TRANSFER_TIMEOUT_ERROR1 = 'block height exceeded';
constant TRANSFER_TIMEOUT_ERROR2 (line 36) | const TRANSFER_TIMEOUT_ERROR2 = 'timeout';
function useTokenTransfer (line 38) | function useTokenTransfer(onDone?: () => void) {
function executeTransfer (line 95) | async function executeTransfer({
FILE: src/features/transfer/utils.ts
type MultiProvider (line 18) | type MultiProvider = MultiProtocolCore['multiProvider'];
function getTransferStatusLabel (line 20) | function getTransferStatusLabel(
function isTransferSent (line 60) | function isTransferSent(status: TransferStatus) {
function isTransferFailed (line 64) | function isTransferFailed(status: TransferStatus) {
constant STATUSES_WITH_ICON (line 68) | const STATUSES_WITH_ICON = [
function getIconByTransferStatus (line 74) | function getIconByTransferStatus(status: TransferStatus) {
function tryGetMsgIdFromTransferReceipt (line 86) | function tryGetMsgIdFromTransferReceipt(
function isEvmContractAddress (line 139) | async function isEvmContractAddress(
function isSmartContract (line 153) | async function isSmartContract(
constant VALIDATION_TIME_EST (line 188) | const VALIDATION_TIME_EST = 5;
constant DEFAULT_BLOCK_TIME_EST (line 189) | const DEFAULT_BLOCK_TIME_EST = 3;
constant DEFAULT_FINALITY_BLOCKS (line 190) | const DEFAULT_FINALITY_BLOCKS = 3;
function estimateDeliverySeconds (line 196) | function estimateDeliverySeconds(
function formatEta (line 228) | function formatEta(seconds: number): string {
function shouldClearAddress (line 235) | function shouldClearAddress(
FILE: src/features/wallet/ConnectWalletButton.tsx
function ConnectWalletButton (line 6) | function ConnectWalletButton() {
FILE: src/features/wallet/RecipientAddressModal.tsx
type RecipientAddressModalProps (line 7) | interface RecipientAddressModalProps {
function RecipientAddressModal (line 15) | function RecipientAddressModal({
FILE: src/features/wallet/SideBarMenu.tsx
function SideBarMenu (line 34) | function SideBarMenu({
function TransferSummary (line 269) | function TransferSummary({
FILE: src/features/wallet/WalletConnectionWarning.tsx
function WalletConnectionWarning (line 8) | function WalletConnectionWarning({ origin }: { origin: ChainName }) {
type WalletWarning (line 29) | type WalletWarning = Partial<Record<ProtocolType, Record<string, string>>>;
FILE: src/features/wallet/WalletDropdown.tsx
type WalletDropdownProps (line 16) | interface WalletDropdownProps {
function WalletDropdown (line 24) | function WalletDropdown({
function ConnectWalletButton (line 142) | function ConnectWalletButton({ chainName }: { chainName?: string }) {
function ConnectMenuItem (line 165) | function ConnectMenuItem({ protocol }: { protocol: ProtocolType }) {
function DropdownWalletButton (line 180) | function DropdownWalletButton({ address }: { address: string }) {
function MenuItemButton (line 199) | function MenuItemButton({ onClick, children }: { onClick: () => void; ch...
function MenuSeparator (line 207) | function MenuSeparator() {
FILE: src/features/wallet/WalletProtocolModal.tsx
type WalletProtocolModalProps (line 8) | interface WalletProtocolModalProps {
constant PROTOCOL_OPTIONS (line 15) | const PROTOCOL_OPTIONS = [
function WalletProtocolModal (line 30) | function WalletProtocolModal({
FILE: src/features/wallet/_e2e/E2EAutoConnectCosmos.tsx
constant MOCK_WALLET_NAME (line 4) | const MOCK_WALLET_NAME = 'warp-e2e-mock-cosmos';
function E2EAutoConnectCosmos (line 13) | function E2EAutoConnectCosmos() {
FILE: src/features/wallet/_e2e/E2EAutoConnectEvm.tsx
function E2EAutoConnectEvm (line 6) | function E2EAutoConnectEvm() {
FILE: src/features/wallet/_e2e/E2EAutoConnectRadix.tsx
constant MOCK_RADIX_ADDRESS (line 5) | const MOCK_RADIX_ADDRESS =
function E2EAutoConnectRadix (line 12) | function E2EAutoConnectRadix() {
FILE: src/features/wallet/_e2e/E2EAutoConnectSolana.tsx
constant MOCK_WALLET_NAME (line 4) | const MOCK_WALLET_NAME = 'WarpE2EMock';
function E2EAutoConnectSolana (line 6) | function E2EAutoConnectSolana() {
FILE: src/features/wallet/_e2e/E2EAutoConnectStarknet.tsx
constant MOCK_CONNECTOR_ID (line 4) | const MOCK_CONNECTOR_ID = 'warp-e2e-mock-starknet';
function E2EAutoConnectStarknet (line 6) | function E2EAutoConnectStarknet() {
FILE: src/features/wallet/_e2e/E2EAutoConnectTron.tsx
constant MOCK_ADAPTER_NAME (line 5) | const MOCK_ADAPTER_NAME = 'WarpE2EMockTron' as AdapterName;
function E2EAutoConnectTron (line 11) | function E2EAutoConnectTron() {
FILE: src/features/wallet/_e2e/MockCosmosWallet.test.fixtures.ts
constant MOCK_COSMOS_MNEMONIC_ADDRESSES (line 4) | const MOCK_COSMOS_MNEMONIC_ADDRESSES = {
FILE: src/features/wallet/_e2e/MockCosmosWallet.ts
constant FIXED_MNEMONIC (line 17) | const FIXED_MNEMONIC =
constant TRANSPARENT_ICON (line 21) | const TRANSPARENT_ICON =
class MockCosmosClient (line 35) | class MockCosmosClient implements WalletClient {
method getSignerFor (line 42) | private getSignerFor(prefix: string): Promise<DirectSecp256k1HdWallet> {
method enable (line 50) | async enable(_chainIds: string | string[]): Promise<void> {
method disconnect (line 54) | async disconnect(): Promise<void> {
method addChain (line 58) | async addChain(chainInfo: ChainRecord): Promise<void> {
method getSimpleAccount (line 65) | async getSimpleAccount(chainId: string): Promise<SimpleAccount> {
method getAccount (line 78) | async getAccount(chainId: string): Promise<WalletAccount> {
method getOfflineSigner (line 94) | getOfflineSigner(chainId: string): OfflineDirectSigner {
method getOfflineSignerDirect (line 98) | getOfflineSignerDirect(chainId: string): OfflineDirectSigner {
class MockCosmosChainWallet (line 130) | class MockCosmosChainWallet extends ChainWalletBase {
method constructor (line 131) | constructor(walletInfo: Wallet, chainInfo: ChainRecord) {
class MockCosmosWallet (line 136) | class MockCosmosWallet extends MainWalletBase {
method constructor (line 137) | constructor(walletInfo: Wallet = mockCosmosWalletInfo) {
method initClient (line 141) | async initClient(): Promise<void> {
FILE: src/features/wallet/_e2e/MockSolanaAdapter.ts
constant FIXED_SEED (line 22) | const FIXED_SEED = new Uint8Array(32).fill(0xe2);
constant MOCK_SIGNATURE (line 24) | const MOCK_SIGNATURE = 'e2e' + '1'.repeat(85);
class MockSolanaAdapter (line 26) | class MockSolanaAdapter extends BaseMessageSignerWalletAdapter {
method connect (line 37) | async connect(): Promise<void> {
method disconnect (line 54) | async disconnect(): Promise<void> {
method signTransaction (line 59) | async signTransaction<T extends Transaction | VersionedTransaction>(tr...
method signMessage (line 64) | async signMessage(message: Uint8Array): Promise<Uint8Array> {
method sendTransaction (line 71) | async sendTransaction(
function captureTx (line 81) | function captureTx(transaction: Transaction | VersionedTransaction): void {
function bufferToBase64 (line 106) | function bufferToBase64(buf: Uint8Array): string {
FILE: src/features/wallet/_e2e/MockStarknetConnector.ts
constant MOCK_STARKNET_ADDRESS (line 5) | const MOCK_STARKNET_ADDRESS =
function buildFakeAccount (line 12) | function buildFakeAccount() {
function createMockStarknetConnector (line 31) | function createMockStarknetConnector(): MockConnector {
FILE: src/features/wallet/_e2e/MockTronAdapter.ts
constant MOCK_TRON_ADDRESS (line 12) | const MOCK_TRON_ADDRESS = 'TE2EE2EE2EE2EE2EE2EE2EE2EE2EE2EE2E';
class MockTronAdapter (line 14) | class MockTronAdapter extends Adapter<'WarpE2EMockTron'> {
method connected (line 23) | get connected(): boolean {
method connect (line 27) | async connect(): Promise<void> {
method disconnect (line 43) | async disconnect(): Promise<void> {
method signMessage (line 53) | async signMessage(message: string): Promise<string> {
method signTransaction (line 59) | async signTransaction(transaction: Transaction): Promise<SignedTransac...
method switchChain (line 67) | async switchChain(_chainId: string): Promise<void> {
FILE: src/features/wallet/_e2e/constants.ts
constant MOCK_EVM_ADDRESS (line 8) | const MOCK_EVM_ADDRESS = '0xE2eE2eE2eE2eE2eE2eE2eE2eE2eE2eE2eE2eE2eE' as...
constant MOCK_SOLANA_ADDRESS (line 9) | const MOCK_SOLANA_ADDRESS = 'EY4LF4gq73QHyff6McmgPKU6UuPtErVU7vVAYcv2nwG...
constant MOCK_COSMOS_ADDRESS (line 10) | const MOCK_COSMOS_ADDRESS = 'cosmos19rl4cm2hmr8afy4kldpxz3fka4jguq0auqda...
constant MOCK_EVM_CONNECTOR_ID (line 12) | const MOCK_EVM_CONNECTOR_ID = 'mock';
FILE: src/features/wallet/_e2e/isE2E.ts
function isE2EMode (line 3) | function isE2EMode(): boolean {
FILE: src/features/wallet/_e2e/windowState.ts
type CapturedEvmTx (line 3) | interface CapturedEvmTx {
type CapturedSolanaTx (line 11) | interface CapturedSolanaTx {
type CapturedCosmosTx (line 17) | interface CapturedCosmosTx {
type E2ETokenSnapshot (line 24) | interface E2ETokenSnapshot {
type WarpE2EState (line 37) | interface WarpE2EState {
type Window (line 54) | interface Window {
function initE2EStateIfEnabled (line 59) | function initE2EStateIfEnabled(): void {
function pushSolanaTx (line 76) | function pushSolanaTx(tx: CapturedSolanaTx): void {
function pushCosmosTx (line 81) | function pushCosmosTx(tx: CapturedCosmosTx): void {
function markE2ERuntimeReady (line 86) | function markE2ERuntimeReady(buildTokens?: () => E2ETokenSnapshot[] | un...
FILE: src/features/wallet/context/AleoWalletContext.tsx
function AleoWalletContext (line 6) | function AleoWalletContext({ children }: PropsWithChildren<unknown>) {
FILE: src/features/wallet/context/CosmosWalletContext.tsx
function CosmosWalletContext (line 27) | function CosmosWalletContext({ children }: PropsWithChildren<unknown>) {
FILE: src/features/wallet/context/EvmWalletContext.tsx
function initWagmi (line 29) | function initWagmi(multiProvider: MultiProtocolProvider, e2e: boolean) {
function EvmWalletContext (line 63) | function EvmWalletContext({ children }: PropsWithChildren<unknown>) {
FILE: src/features/wallet/context/RadixWalletContext.tsx
function RadixWalletContext (line 16) | function RadixWalletContext({ children }: PropsWithChildren<unknown>) {
FILE: src/features/wallet/context/SolanaWalletContext.tsx
function SolanaWalletContext (line 24) | function SolanaWalletContext({ children }: PropsWithChildren<unknown>) {
FILE: src/features/wallet/context/StarknetWalletContext.tsx
constant READY_WALLET_ICON (line 15) | const READY_WALLET_ICON = `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzI...
function StarknetWalletContext (line 17) | function StarknetWalletContext({ children }: PropsWithChildren<unknown>) {
FILE: src/features/wallet/context/TronWalletContext.tsx
function TronWalletContext (line 9) | function TronWalletContext({ children }: PropsWithChildren<unknown>) {
FILE: src/features/wallet/relativeTimeTicker.test.ts
function createFakeDocument (line 5) | function createFakeDocument(initialVisibilityState: DocumentVisibilitySt...
FILE: src/features/wallet/relativeTimeTicker.ts
type VisibilityDocument (line 1) | interface VisibilityDocument {
type StartRelativeTimeTickerOptions (line 15) | interface StartRelativeTimeTickerOptions {
function startRelativeTimeTicker (line 22) | function startRelativeTimeTicker({
FILE: src/features/warpCore/AddWarpConfigModal.tsx
function AddWarpConfigModal (line 14) | function AddWarpConfigModal({ isOpen, close }: { isOpen: boolean; close:...
function Form (line 51) | function Form({ onAdd }: { onAdd: (warpCoreConfig: WarpCoreConfig) => vo...
function ConfigList (line 101) | function ConfigList({
function tryParseConfigInput (line 124) | function tryParseConfigInput(
FILE: src/features/warpCore/warpCoreConfig.ts
type WarpCoreToken (line 21) | type WarpCoreToken = WarpCoreConfig['tokens'][number];
type NullableAddressWarpCoreToken (line 22) | type NullableAddressWarpCoreToken = Omit<WarpCoreToken, 'addressOrDenom'...
function assembleWarpCoreConfig (line 26) | async function assembleWarpCoreConfig(
function fillMissingCoinGeckoIds (line 154) | function fillMissingCoinGeckoIds(
function filterToIds (line 179) | function filterToIds(
function dedupeTokens (line 190) | function dedupeTokens(
function reduceOptions (line 211) | function reduceOptions(optionsList: Array<WarpCoreConfig['options']>): W...
function filterUnconnectedToken (line 222) | function filterUnconnectedToken(tokens: NullableAddressWarpCoreToken[]):...
FILE: src/global.d.ts
type Address (line 1) | type Address = string;
type ChainName (line 2) | type ChainName = string;
type ChainId (line 3) | type ChainId = number | string;
type DomainId (line 4) | type DomainId = number;
FILE: src/instrumentation.ts
function register (line 3) | async function register() {
FILE: src/lib/predicateClient.ts
type AttestationRequest (line 3) | type AttestationRequest = PredicateAttestationRequest;
constant PREDICATE_PROXY_URL (line 5) | const PREDICATE_PROXY_URL = '/api/predicate/attestation';
function fetchAttestation (line 11) | async function fetchAttestation(
FILE: src/pages/_app.tsx
function useEarlyEmbedMode (line 45) | function useEarlyEmbedMode(isEmbed: boolean) {
function App (line 59) | function App({ Component, pageProps }: AppProps) {
FILE: src/pages/_document.tsx
function Document (line 5) | function Document() {
FILE: src/pages/api/predicate/attestation.ts
constant PREDICATE_API_KEY (line 6) | const PREDICATE_API_KEY = process.env.PREDICATE_API_KEY;
constant PREDICATE_API_URL (line 7) | const PREDICATE_API_URL = process.env.PREDICATE_API_URL;
constant ALLOWED_PREDICATE_DOMAINS (line 8) | const ALLOWED_PREDICATE_DOMAINS = ['api.predicate.io', 'predicate.io'];
function validateApiUrl (line 11) | function validateApiUrl(url: string): boolean {
function handler (line 29) | async function handler(req: NextApiRequest, res: NextApiResponse) {
FILE: src/pages/api/quote.test.ts
function mockReqRes (line 17) | function mockReqRes(method: string, query: Record<string, string> = {}) {
FILE: src/pages/api/quote.ts
constant ALLOWED_COMMANDS (line 10) | const ALLOWED_COMMANDS = new Set<FeeQuotingCommand>(Object.values(FeeQuo...
constant HEX_BYTES32 (line 14) | const HEX_BYTES32 = /^0x[0-9a-fA-F]{64}$/;
function handler (line 22) | async function handler(req: NextApiRequest, res: NextApiResponse) {
function firstString (line 83) | function firstString(v: string | string[] | undefined): string | undefin...
FILE: src/pages/blocked.tsx
function Page (line 3) | function Page() {
FILE: src/pages/embed.tsx
constant WIDGET_MESSAGE_TYPE (line 27) | const WIDGET_MESSAGE_TYPE = 'hyperlane-warp-widget';
function emitWidgetEvent (line 29) | function emitWidgetEvent(eventType: string, payload?: Record<string, unk...
function usePostMessageBridge (line 37) | function usePostMessageBridge() {
function useAutoTransferModal (line 49) | function useAutoTransferModal() {
FILE: src/proxy.ts
constant BLOCKED_COUNTRIES (line 9) | const BLOCKED_COUNTRIES = [
constant BLOCKED_REGIONS (line 36) | const BLOCKED_REGIONS = [
function proxy (line 47) | function proxy(req: NextRequest) {
FILE: src/styles/embedTheme.ts
type EmbedTheme (line 3) | interface EmbedTheme {
constant KNOWN_URL_PARAMS (line 16) | const KNOWN_URL_PARAMS = new Set([
constant HEX_COLOR_RE (line 29) | const HEX_COLOR_RE = /^([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-...
function parseHexParam (line 35) | function parseHexParam(params: URLSearchParams, name: string): string | ...
function normalizeHex (line 44) | function normalizeHex(hex: string): string {
function shiftColor (line 65) | function shiftColor(hex: string, amount: number): string {
constant LIGHT_DEFAULTS (line 74) | const LIGHT_DEFAULTS: EmbedTheme = {
constant DARK_DEFAULTS (line 86) | const DARK_DEFAULTS: EmbedTheme = {
constant ALLOWED_MODES (line 99) | const ALLOWED_MODES = new Set(['dark', 'light']);
function parseEmbedTheme (line 102) | function parseEmbedTheme(): EmbedTheme {
function themeToCssVars (line 125) | function themeToCssVars(theme: EmbedTheme): Record<string, string> {
FILE: src/styles/mediaQueries.ts
type WindowSize (line 3) | interface WindowSize {
function useWindowSize (line 9) | function useWindowSize() {
function isWindowSizeMobile (line 34) | function isWindowSizeMobile(windowWidth: number | undefined) {
function isWindowSizeSmallMobile (line 38) | function isWindowSizeSmallMobile(windowWidth: number | undefined) {
function useIsMobile (line 42) | function useIsMobile() {
FILE: src/utils/date.ts
function formatTimestamp (line 1) | function formatTimestamp(timestamp: number): string {
function toMs (line 6) | function toMs(timestamp: number): number {
function formatTransferHistoryTimestamp (line 11) | function formatTransferHistoryTimestamp(timestamp: number, now = Date.no...
FILE: src/utils/imageBrightness.test.ts
class FakeImage (line 11) | class FakeImage {
method constructor (line 16) | constructor(src: string) {
method src (line 20) | get src() {
method src (line 24) | set src(value: string) {
method getAttribute (line 28) | getAttribute(name: string): string | null {
method addEventListener (line 32) | addEventListener(name: string, callback: () => void) {
method dispatch (line 37) | dispatch(name: string) {
function setThemeMode (line 46) | function setThemeMode(themeMode: 'light' | 'dark') {
function restoreDomGlobals (line 63) | function restoreDomGlobals() {
FILE: src/utils/imageBrightness.ts
function getImgSrc (line 1) | function getImgSrc(img: HTMLImageElement): string {
constant DARK_LOGO_CACHE_MAX_ENTRIES (line 6) | const DARK_LOGO_CACHE_MAX_ENTRIES = 500;
function setDarkLogoAvailability (line 8) | function setDarkLogoAvailability(key: string, value: 'ok' | 'missing') {
function resetDarkLogoCache (line 20) | function resetDarkLogoCache() {
function isDarkModeEnabled (line 24) | function isDarkModeEnabled(): boolean {
function toDarkVariantSrc (line 37) | function toDarkVariantSrc(src: string): string | null {
function toOriginalVariantSrc (line 50) | function toOriginalVariantSrc(src: string): string | null {
function markDarkLogoMissing (line 64) | function markDarkLogoMissing(darkSrc: string) {
function bindFallbackHandlers (line 68) | function bindFallbackHandlers(img: HTMLImageElement) {
function syncOriginalSrc (line 97) | function syncOriginalSrc(img: HTMLImageElement): string {
function hasReadyImg (line 113) | function hasReadyImg(container: Element): boolean {
function processNodeImages (line 119) | function processNodeImages(node: Node) {
function processDarkLogoImage (line 133) | function processDarkLogoImage(img: HTMLImageElement) {
function processDarkLogosInContainer (line 165) | function processDarkLogosInContainer(container: Element) {
function observeDarkLogosInContainer (line 171) | function observeDarkLogosInContainer(
FILE: src/utils/links.ts
function getHypExplorerLink (line 8) | function getHypExplorerLink(
FILE: src/utils/logger.ts
function isSafeSentryArg (line 22) | function isSafeSentryArg(arg: any) {
FILE: src/utils/pino-noop.js
function pino (line 15) | function pino() {
FILE: src/utils/promises.ts
function getPromisesFulfilledValues (line 4) | function getPromisesFulfilledValues<T>(results: PromiseSettledResult<T |...
FILE: src/utils/queryParams.ts
function getQueryParams (line 1) | function getQueryParams() {
function updateQueryParam (line 5) | function updateQueryParam(key: string, value?: string | number) {
function updateQueryParams (line 21) | function updateQueryParams(params: Record<string, string | number>) {
FILE: src/utils/theme.ts
function parseUiThemeMode (line 3) | function parseUiThemeMode(value: string | null | undefined): UiThemeMode...
function getSystemUiThemeMode (line 8) | function getSystemUiThemeMode(): UiThemeMode {
FILE: tests/e2e-wallet/approval/evm.spec.ts
constant USDC_ETHEREUM (line 7) | const USDC_ETHEREUM = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
constant APPROVE_SELECTOR (line 8) | const APPROVE_SELECTOR = '0x095ea7b3';
constant TRANSFER_REMOTE_SELECTORS (line 12) | const TRANSFER_REMOTE_SELECTORS = [
FILE: tests/e2e-wallet/autoconnect/radix.spec.ts
constant MOCK_RADIX_ADDRESS (line 6) | const MOCK_RADIX_ADDRESS =
FILE: tests/e2e-wallet/autoconnect/solana.spec.ts
constant MOCK_SOLANA_SHORT (line 8) | const MOCK_SOLANA_SHORT = 'EY4LF...nwGi';
FILE: tests/e2e-wallet/autoconnect/tron.spec.ts
constant MOCK_TRON_ADDRESS (line 6) | const MOCK_TRON_ADDRESS = 'TE2EE2EE2EE2EE2EE2EE2EE2EE2EE2EE2E';
FILE: tests/e2e-wallet/balance-display/solana.spec.ts
constant USDC_MINT_SOLANAMAINNET (line 6) | const USDC_MINT_SOLANAMAINNET = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwy...
FILE: tests/e2e-wallet/destination-router/evm.spec.ts
constant USDC_ETHEREUM (line 7) | const USDC_ETHEREUM = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
constant REMOTE_ADDRESS_RE (line 8) | const REMOTE_ADDRESS_RE = /0x[0-9a-fA-F]{40}/;
function captureRemoteAddress (line 11) | async function captureRemoteAddress(page: Page, destPattern: RegExp) {
FILE: tests/e2e-wallet/destination-router/solana.spec.ts
constant SOLANAMAINNET_USDC_MINT (line 11) | const SOLANAMAINNET_USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwy...
constant EXPECTED_ROUTE_ENDPOINTS (line 12) | const EXPECTED_ROUTE_ENDPOINTS: Array<{ chain: string; addressOrDenom: s...
FILE: tests/e2e-wallet/helpers/captured.ts
type Window (line 5) | interface Window {
function getE2EState (line 10) | async function getE2EState(page: Page): Promise<WarpE2EState> {
function getCapturedEvmTxs (line 16) | async function getCapturedEvmTxs(page: Page): Promise<CapturedEvmTx[]> {
function getCapturedSolanaTxs (line 20) | async function getCapturedSolanaTxs(page: Page): Promise<CapturedSolanaT...
function getCapturedCosmosTxs (line 24) | async function getCapturedCosmosTxs(page: Page): Promise<CapturedCosmosT...
function waitForCapturedEvmTx (line 28) | async function waitForCapturedEvmTx(page: Page, timeoutMs = 10_000): Pro...
function waitForCapturedSolanaTx (line 36) | async function waitForCapturedSolanaTx(
function waitForCapturedCosmosTx (line 47) | async function waitForCapturedCosmosTx(
FILE: tests/e2e-wallet/helpers/evmRpc.ts
constant FAKE_TX_HASH (line 5) | const FAKE_TX_HASH = ('0x' + 'ee'.repeat(32)) as `0x${string}`;
constant ONE_ETH_HEX (line 6) | const ONE_ETH_HEX = '0xde0b6b3a7640000';
constant DEFAULT_GAS_PRICE (line 7) | const DEFAULT_GAS_PRICE = '0x3b9aca00';
constant DEFAULT_BLOCK_HEX (line 8) | const DEFAULT_BLOCK_HEX = '0x12345';
constant MAX_UINT256 (line 9) | const MAX_UINT256 = '0x' + 'f'.repeat(64);
type ChainUrlMatcher (line 11) | interface ChainUrlMatcher {
type Erc20Fixture (line 16) | interface Erc20Fixture {
type InstallEvmRpcMockOptions (line 29) | interface InstallEvmRpcMockOptions {
type EvmRpcMockHandle (line 46) | interface EvmRpcMockHandle {
function installEvmRpcMock (line 51) | async function installEvmRpcMock(
type HandleCtx (line 100) | interface HandleCtx {
function handleOne (line 108) | function handleOne(itemUnknown: unknown, ctx: HandleCtx): unknown {
function handleEthCall (line 234) | function handleEthCall(call: { to?: string; data?: string }, ctx: Handle...
function padHex (line 303) | function padHex(hex: string): string {
function encodeString (line 308) | function encodeString(s: string): string {
function readUint32Arg (line 318) | function readUint32Arg(data: string): number | undefined {
FILE: tests/e2e-wallet/helpers/formFlow.ts
constant MODAL_TIMEOUT (line 6) | const MODAL_TIMEOUT = 30_000;
function tokenPickerModal (line 8) | function tokenPickerModal(page: Page) {
function selectOriginToken (line 26) | async function selectOriginToken(page: Page, buttonName: RegExp): Promis...
function selectDestinationToken (line 37) | async function selectDestinationToken(page: Page, buttonName: RegExp): P...
function enterAmount (line 48) | async function enterAmount(page: Page, amount: string): Promise<void> {
function clickContinue (line 54) | async function clickContinue(page: Page): Promise<void> {
function clickSendInReview (line 59) | async function clickSendInReview(page: Page): Promise<void> {
FILE: tests/e2e-wallet/helpers/page-setup.ts
constant DEFAULT_BASE (line 3) | const DEFAULT_BASE = 'http://localhost:3000';
type OpenE2EAppOptions (line 5) | interface OpenE2EAppOptions {
function openE2EApp (line 10) | async function openE2EApp(page: Page, opts: OpenE2EAppOptions = {}): Pro...
function waitForWarpRuntime (line 21) | async function waitForWarpRuntime(page: Page, timeoutMs = 20_000): Promi...
FILE: tests/e2e-wallet/helpers/solanaRpc.ts
type SolanaSplFixture (line 10) | interface SolanaSplFixture {
type InstallSolanaRpcMockOptions (line 20) | interface InstallSolanaRpcMockOptions {
constant TOKEN_PROGRAM_ID (line 31) | const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
constant TOKEN_2022_PROGRAM_ID (line 32) | const TOKEN_2022_PROGRAM_ID = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxu...
constant SOL_LAMPORTS (line 33) | const SOL_LAMPORTS = 1_000_000_000;
function installSolanaRpcMock (line 35) | async function installSolanaRpcMock(
function isSolanaMethod (line 75) | function isSolanaMethod(method?: string): boolean {
type HandleCtx (line 98) | interface HandleCtx {
function handleOne (line 104) | function handleOne(itemUnknown: unknown, ctx: HandleCtx): unknown {
function buildParsedAccount (line 189) | function buildParsedAccount({
function deriveSyntheticAta (line 231) | function deriveSyntheticAta(owner: string, mint: string): string {
function formatUiAmount (line 238) | function formatUiAmount(
FILE: tests/e2e-wallet/helpers/types.ts
type CapturedEvmTx (line 4) | interface CapturedEvmTx {
type CapturedSolanaTx (line 12) | interface CapturedSolanaTx {
type CapturedCosmosTx (line 18) | interface CapturedCosmosTx {
type E2ETokenSnapshot (line 25) | interface E2ETokenSnapshot {
type WarpE2EState (line 35) | interface WarpE2EState {
FILE: tests/e2e-wallet/same-symbol-dedup/cosmos.spec.ts
function openOriginAndSearch (line 4) | async function openOriginAndSearch(page: import('@playwright/test').Page...
FILE: tests/e2e-wallet/same-symbol-dedup/evm.spec.ts
constant USDC_ARBITRUM (line 7) | const USDC_ARBITRUM = '0xaf88d065e77c8cc2239327c5edb3a432268e5831';
FILE: tests/e2e-wallet/tx-payload/evm.spec.ts
constant USDC_ETHEREUM (line 7) | const USDC_ETHEREUM = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
constant TRANSFER_REMOTE_SELECTORS (line 10) | const TRANSFER_REMOTE_SELECTORS = [
FILE: tests/helpers/constants.ts
function resolveTestRoutes (line 5) | function resolveTestRoutes(): { primary: string; secondary: string; skip...
FILE: tests/helpers/locators.ts
function getOriginTokenButton (line 3) | function getOriginTokenButton(page: Page): Locator {
function getDestinationTokenButton (line 7) | function getDestinationTokenButton(page: Page): Locator {
function getTipCard (line 11) | function getTipCard(page: Page): Locator {
FILE: tests/wallet-connect/protocol-wallet-modals.spec.ts
function selectToken (line 5) | async function selectToken(
Condensed preview — 299 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,009K chars).
[
{
"path": ".claude/skills/claude-review/SKILL.md",
"chars": 1271,
"preview": "---\nname: claude-review\ndescription: Review code changes using Hyperlane Warp UI coding standards. Use when reviewing PR"
},
{
"path": ".claude/skills/claude-security-review/SKILL.md",
"chars": 739,
"preview": "---\nname: claude-security-review\ndescription: Security-focused review for frontend/Web3 code. Use for XSS, wallet securi"
},
{
"path": ".claude/skills/commit/SKILL.md",
"chars": 2019,
"preview": "---\nname: commit\ndescription: Commit changes following project quality gates and best practices. Run before creating any"
},
{
"path": ".claude/skills/inline-pr-comments/SKILL.md",
"chars": 4437,
"preview": "---\nname: inline-pr-comments\ndescription: Post a single consolidated PR review with summary and inline comments. Use thi"
},
{
"path": ".claude/skills/resolve-pr-reviews/SKILL.md",
"chars": 4013,
"preview": "---\nname: resolve-pr-reviews\ndescription: Review and resolve PR review comments interactively. Fetches unresolved commen"
},
{
"path": ".env.example",
"chars": 1176,
"preview": "NEXT_PUBLIC_WALLET_CONNECT_ID=12345678901234567890123456789012\nNEXT_PUBLIC_RPC_OVERRIDES='{\"chain1\":{\"http\":\"https://..."
},
{
"path": ".github/CODEOWNERS",
"chars": 43,
"preview": "* @xaroz @paulbalaji @xeno097 @troykessler\n"
},
{
"path": ".github/prompts/security-scan.md",
"chars": 1525,
"preview": "## Frontend Security Focus Areas\n\nThis is a Web3 frontend application. Pay special attention to:\n\n### XSS & Content Secu"
},
{
"path": ".github/workflows/ci.yml",
"chars": 5634,
"preview": "name: ci\n\non:\n push:\n branches: [main, nautilus, nexus, injective, trump, ousdt]\n pull_request:\n branches: [main"
},
{
"path": ".github/workflows/claude-code-review.yml",
"chars": 6846,
"preview": "name: Claude Code Review\n\non:\n pull_request:\n types: [opened, synchronize, reopened, ready_for_review]\n pull_reques"
},
{
"path": ".github/workflows/create-merge-prs.yaml",
"chars": 3909,
"preview": "name: Merge Main to Other Branches\n\non:\n push:\n branches:\n - main\n workflow_dispatch:\n\nconcurrency:\n group: $"
},
{
"path": ".github/workflows/e2e-smoke.yml",
"chars": 2239,
"preview": "name: e2e-smoke\n\n# Fast subset of the wallet-connected E2E suite. Runs on every PR as a gate.\n# The full wallet E2E matr"
},
{
"path": ".github/workflows/e2e-wallet-full.yml",
"chars": 3698,
"preview": "name: e2e-wallet-full\n\n# Full wallet-connected E2E suite across chromium/firefox/webkit.\n# Called from ci.yml on push-to"
},
{
"path": ".github/workflows/e2e.yml",
"chars": 4497,
"preview": "name: e2e\n\non:\n workflow_call:\n workflow_dispatch:\n\njobs:\n build:\n # Only build when triggered standalone (workflo"
},
{
"path": ".github/workflows/update-hyperlane-deps.yml",
"chars": 5049,
"preview": "name: Update Hyperlane Dependencies\n\non:\n schedule:\n # Run weekly on Mondays at 9 AM UTC\n - cron: '0 9 * * 1'\n w"
},
{
"path": ".gitignore",
"chars": 577,
"preview": "# dependencies\n/node_modules\ncache/\n.pnpm-store/\n\n# testing\n/coverage\ncoverage.json\n/test/outputs\n\n# next.js\n/.next/\n/ou"
},
{
"path": ".nvmrc",
"chars": 4,
"preview": "v24\n"
},
{
"path": ".oxfmtrc.json",
"chars": 163,
"preview": "{\n \"singleQuote\": true,\n \"sortImports\": {},\n \"sortTailwindcss\": {\n \"functions\": [\"clsx\"]\n },\n \"ignorePatterns\": "
},
{
"path": ".oxlintrc.json",
"chars": 1120,
"preview": "{\n \"plugins\": [\"typescript\", \"import\", \"react\", \"nextjs\", \"jsx-a11y\"],\n \"rules\": {\n \"camelcase\": \"error\",\n \"guar"
},
{
"path": ".vscode/extensions.json",
"chars": 77,
"preview": "{\n \"recommendations\": [\"oxc.oxc-vscode\"],\n \"unwantedRecommendations\": []\n}\n"
},
{
"path": ".vscode/settings.json",
"chars": 1129,
"preview": "{\n \"search.exclude\": {\n \"**/node_modules/**\": true\n },\n \"files.exclude\": {\n \"**/*.js.map\": true,\n \"**/*.js\":"
},
{
"path": "AGENTS.md",
"chars": 7097,
"preview": "# AGENTS.md\n\n**Be extremely concise. Sacrifice grammar for concision. Terse responses preferred. No fluff.**\n\nThis file "
},
{
"path": "CLAUDE.md",
"chars": 6153,
"preview": "# CLAUDE.md\n\n**Be extremely concise. Sacrifice grammar for concision. Terse responses preferred. No fluff.**\n\nThis file "
},
{
"path": "CUSTOMIZE.md",
"chars": 3192,
"preview": "# Customizing tokens and branding\n\nFind below instructions for customizing the token list and branding assets of this ap"
},
{
"path": "LICENSE.md",
"chars": 10427,
"preview": "Apache License\n==============\n\n_Version 2.0, January 2004_ \n_<<http://www.apache.org/licenses/>>_\n\n### Terms and "
},
{
"path": "README.md",
"chars": 5458,
"preview": "# Hyperlane Warp Route UI Template\n\nThis repo contains an example web interface for interchain tokens built with [Hyperl"
},
{
"path": "REVIEW.md",
"chars": 1543,
"preview": "# Code Review Guidelines\n\n## Code Quality\n\n- Logic errors and potential bugs\n- Error handling and edge cases\n- Code clar"
},
{
"path": "next-env.d.ts",
"chars": 253,
"preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\nimport \"./.next/dev/types/routes.d.ts\";"
},
{
"path": "next.config.js",
"chars": 6302,
"preview": "/** @type {import('next').NextConfig} */\n\nconst { version } = require('./package.json');\nconst withBundleAnalyzer = requ"
},
{
"path": "package.json",
"chars": 5570,
"preview": "{\n \"name\": \"@hyperlane-xyz/warp-ui-template\",\n \"version\": \"13.0.0\",\n \"private\": true,\n \"description\": \"A web app tem"
},
{
"path": "patches/@provablehq__sdk@0.9.15.patch",
"chars": 4642,
"preview": "diff --git a/dist/mainnet/browser.js b/dist/mainnet/browser.js\nindex 166fc0b9e40569f8b33ecb3be7b170d3010a2d2e..8ab95f815"
},
{
"path": "patches/@provablehq__wasm@0.9.18.patch",
"chars": 1602,
"preview": "diff --git a/dist/mainnet/index.js b/dist/mainnet/index.js\nindex 1988ed1acaabfdd5238dd9729678033d1978aacd..3ff88a8fc24bc"
},
{
"path": "patches/starknetkit@2.6.1.patch",
"chars": 935,
"preview": "diff --git a/dist/starknetkit.js b/dist/starknetkit.js\nindex 9fee9301ba99263b32be70b492787671eb9a2d26..6cfeb4756cdb7e2dd"
},
{
"path": "playwright.config.ts",
"chars": 3000,
"preview": "import { defineConfig, devices } from '@playwright/test';\n\n/**\n * Read environment variables from file.\n * https://githu"
},
{
"path": "pnpm-workspace.yaml",
"chars": 149,
"preview": "# Reject packages published less than 7 days ago (supply chain protection)\nminimumReleaseAge: 10080\nminimumReleaseAgeExc"
},
{
"path": "postcss.config.js",
"chars": 82,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}\n"
},
{
"path": "public/.well-known/radix.json",
"chars": 132,
"preview": "{\n \"dApps\": [\n {\n \"dAppDefinitionAddress\": \"account_rdx12ycz0wsuygqa5slye9du6e7wz7fr4pzx39l5r5cznqc6yudpks20cw\""
},
{
"path": "public/browserconfig.xml",
"chars": 246,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n <msapplication>\n <tile>\n <square150x150logo"
},
{
"path": "public/site.webmanifest",
"chars": 426,
"preview": "{\n \"name\": \"\",\n \"short_name\": \"\",\n \"icons\": [\n {\n \"src\": \"/android-chrome-192x192.png\",\n "
},
{
"path": "public/theme-init.js",
"chars": 765,
"preview": "(() => {\n const getSystemTheme = () => {\n try {\n if (typeof window.matchMedia !== 'function') return 'light';\n "
},
{
"path": "scripts/README.md",
"chars": 5499,
"preview": "# Development Scripts\n\n## link-monorepo.js\n\nLinks local Hyperlane monorepo packages for development using `pnpm pack` (c"
},
{
"path": "scripts/fetch-fonts.mjs",
"chars": 2477,
"preview": "import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';\nimport { createWriteStream, existsSync, mkdirSync } fro"
},
{
"path": "scripts/link-monorepo.js",
"chars": 7665,
"preview": "const { execSync } = require('child_process');\nconst fs = require('fs');\nconst path = require('path');\n\n/** --- Configur"
},
{
"path": "scripts/unlink-monorepo.js",
"chars": 5131,
"preview": "const { execSync, spawnSync } = require('child_process');\nconst fs = require('fs');\nconst path = require('path');\n\n/**\n "
},
{
"path": "sentry.client.config.js",
"chars": 600,
"preview": "import { sentryDefaultConfig } from './sentry.default.config';\nimport * as Sentry from '@sentry/nextjs';\n\nif (typeof win"
},
{
"path": "sentry.default.config.js",
"chars": 1615,
"preview": "const filters = [\n // Hyperlane custom set\n \"trap returned falsish for property\", // Error from cosmos wallet lib\n \"n"
},
{
"path": "src/components/banner/FormWarningBanner.tsx",
"chars": 567,
"preview": "import clsx from 'clsx';\nimport { ComponentProps } from 'react';\n\nimport { WarningBanner } from '../../components/banner"
},
{
"path": "src/components/banner/RecipientWarningBanner.tsx",
"chars": 1351,
"preview": "import { WarningIcon } from '@hyperlane-xyz/widgets';\n\nexport function RecipientWarningBanner({\n destinationChain,\n co"
},
{
"path": "src/components/banner/WarningBanner.tsx",
"chars": 950,
"preview": "import { WarningIcon } from '@hyperlane-xyz/widgets';\nimport { PropsWithChildren, ReactNode } from 'react';\n\nexport func"
},
{
"path": "src/components/buttons/ConnectAwareSubmitButton.tsx",
"chars": 2189,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\nimport { useTimeout } from '@hyperlane-xyz/widgets';\nimport {\n use"
},
{
"path": "src/components/buttons/SolidButton.tsx",
"chars": 2105,
"preview": "import { PropsWithChildren, ReactElement } from 'react';\n\ninterface ButtonProps {\n type?: 'submit' | 'reset' | 'button'"
},
{
"path": "src/components/errors/ErrorBoundary.tsx",
"chars": 600,
"preview": "import { ErrorBoundary as ErrorBoundaryInner } from '@hyperlane-xyz/widgets';\nimport { PropsWithChildren } from 'react';"
},
{
"path": "src/components/icons/BookIcon.tsx",
"chars": 2217,
"preview": "import { DefaultIconProps } from '@hyperlane-xyz/widgets';\nimport { memo } from 'react';\n\nimport { Color } from '../../s"
},
{
"path": "src/components/icons/ChainLogo.tsx",
"chars": 1253,
"preview": "import { ChainLogo as ChainLogoInner } from '@hyperlane-xyz/widgets';\nimport { useEffect, useRef } from 'react';\n\nimport"
},
{
"path": "src/components/icons/ChevronLargeIcon.tsx",
"chars": 662,
"preview": "import { DefaultIconProps } from '@hyperlane-xyz/widgets';\nimport { memo } from 'react';\n\nimport { Color } from '../../s"
},
{
"path": "src/components/icons/HamburgerIcon.tsx",
"chars": 496,
"preview": "import { DefaultIconProps } from '@hyperlane-xyz/widgets';\nimport { memo } from 'react';\n\nfunction _HamburgerIcon({ colo"
},
{
"path": "src/components/icons/HyperlaneGradientLogo.tsx",
"chars": 9655,
"preview": "import { DefaultIconProps } from '@hyperlane-xyz/widgets';\nimport { memo } from 'react';\n\nfunction _HyperlaneGradientLog"
},
{
"path": "src/components/icons/HyperlaneTransparentLogo.tsx",
"chars": 1237,
"preview": "import { memo } from 'react';\n\nfunction _HyperlaneTransparentLogo() {\n return (\n <svg\n width=\"146\"\n height"
},
{
"path": "src/components/icons/QuestionMarkIcon.tsx",
"chars": 1463,
"preview": "import { DefaultIconProps } from '@hyperlane-xyz/widgets';\nimport { memo } from 'react';\n\nimport { Color } from '../../s"
},
{
"path": "src/components/icons/StakeIcon.tsx",
"chars": 1037,
"preview": "import { DefaultIconProps } from '@hyperlane-xyz/widgets';\nimport { memo } from 'react';\n\nfunction _StakeIcon({ ...props"
},
{
"path": "src/components/icons/SwapIcon.tsx",
"chars": 833,
"preview": "import { DefaultIconProps } from '@hyperlane-xyz/widgets';\nimport { memo } from 'react';\n\nimport { Color } from '../../s"
},
{
"path": "src/components/icons/TokenIcon.tsx",
"chars": 2983,
"preview": "import { IToken } from '@hyperlane-xyz/sdk';\nimport { isHttpsUrl, isRelativeUrl } from '@hyperlane-xyz/utils';\nimport { "
},
{
"path": "src/components/icons/WebSimpleIcon.tsx",
"chars": 967,
"preview": "import { DefaultIconProps } from '@hyperlane-xyz/widgets';\nimport { memo } from 'react';\n\nimport { Color } from '../../s"
},
{
"path": "src/components/icons/XIcon.tsx",
"chars": 636,
"preview": "import { DefaultIconProps } from '@hyperlane-xyz/widgets';\nimport { memo } from 'react';\n\nimport { Color } from '../../s"
},
{
"path": "src/components/input/SearchInput.tsx",
"chars": 1292,
"preview": "import { SearchIcon, XIcon } from '@hyperlane-xyz/widgets';\nimport { Ref } from 'react';\n\nimport { TextInput } from './T"
},
{
"path": "src/components/input/TextField.tsx",
"chars": 1021,
"preview": "import clsx from 'clsx';\nimport { Field, FieldAttributes } from 'formik';\nimport { ChangeEvent, InputHTMLAttributes, Ref"
},
{
"path": "src/components/layout/AppLayout.tsx",
"chars": 2428,
"preview": "import Head from 'next/head';\nimport { PropsWithChildren, useEffect } from 'react';\n\nimport { APP_NAME } from '../../con"
},
{
"path": "src/components/layout/Card.tsx",
"chars": 331,
"preview": "import { PropsWithChildren } from 'react';\n\ninterface Props {\n className?: string;\n}\n\nexport function Card({ className "
},
{
"path": "src/components/layout/ModalHeader.tsx",
"chars": 478,
"preview": "import clsx from 'clsx';\nimport { ReactNode } from 'react';\n\nexport function ModalHeader({ children, className }: { chil"
},
{
"path": "src/components/nav/Footer.tsx",
"chars": 1262,
"preview": "import { HyperlaneGradientLogo } from '../icons/HyperlaneGradientLogo';\nimport { NavItem, navLinks } from './Nav';\n\nexpo"
},
{
"path": "src/components/nav/Header.tsx",
"chars": 3672,
"preview": "import { DropdownMenu } from '@hyperlane-xyz/widgets';\nimport Image from 'next/image';\nimport Link from 'next/link';\n\nim"
},
{
"path": "src/components/nav/Nav.tsx",
"chars": 1834,
"preview": "import { GithubIcon } from '@hyperlane-xyz/widgets';\nimport clsx from 'clsx';\nimport Link from 'next/link';\nimport { for"
},
{
"path": "src/components/tip/TipCard.tsx",
"chars": 2207,
"preview": "import { IconButton, XCircleIcon } from '@hyperlane-xyz/widgets';\nimport Image from 'next/image';\nimport { useState } fr"
},
{
"path": "src/components/toast/IgpDetailsToast.tsx",
"chars": 684,
"preview": "import { toast } from 'react-toastify';\n\nimport { links } from '../../consts/links';\n\nexport function toastIgpDetails(ig"
},
{
"path": "src/components/toast/TxSuccessToast.tsx",
"chars": 745,
"preview": "import { toast } from 'react-toastify';\n\nimport { useMultiProvider } from '../../features/chains/hooks';\n\nexport functio"
},
{
"path": "src/components/toast/useToastError.tsx",
"chars": 430,
"preview": "import { errorToString } from '@hyperlane-xyz/utils';\nimport { useEffect } from 'react';\nimport { toast } from 'react-to"
},
{
"path": "src/consts/app.ts",
"chars": 440,
"preview": "import { Color } from '../styles/Color';\n\nexport type UiThemeMode = 'light' | 'dark';\n\nexport const APP_NAME = 'Hyperlan"
},
{
"path": "src/consts/args.ts",
"chars": 301,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\n\nexport enum WARP_QUERY_PARAMS {\n ORIGIN = 'origin',\n DESTINATION"
},
{
"path": "src/consts/blacklist.ts",
"chars": 179,
"preview": "// A list of addresses that are cannot be used in the app\n// If a wallet with this address is connected, the app will sh"
},
{
"path": "src/consts/chainAddresses.ts",
"chars": 716,
"preview": "import { ChainAddresses } from '@hyperlane-xyz/registry';\nimport { ChainMap } from '@hyperlane-xyz/sdk';\n\n// Per-chain c"
},
{
"path": "src/consts/chainAddresses.yaml",
"chars": 947,
"preview": "# A map of chain name -> contract addresses\n# Merges with addresses from the configured registry. Filesystem entries\n# o"
},
{
"path": "src/consts/chains.ts",
"chars": 2257,
"preview": "import {\n eclipsemainnet,\n eclipsemainnetAddresses,\n solanamainnet,\n solanamainnetAddresses,\n solaxy,\n solaxyAddre"
},
{
"path": "src/consts/chains.yaml",
"chars": 638,
"preview": "# A map of chain names to ChainMetadata\n# Chains can be defined here, in chains.json, or in chains.ts\n# Chains already i"
},
{
"path": "src/consts/config.ts",
"chars": 5141,
"preview": "import { ChainMap } from '@hyperlane-xyz/sdk';\nimport { ProtocolType } from '@hyperlane-xyz/utils';\n\nimport { ADDRESS_BL"
},
{
"path": "src/consts/defaultMultiCollateralRoutes.ts",
"chars": 499,
"preview": "import { DefaultMultiCollateralRoutes } from '../features/tokens/types';\n\n// Default multi-collateral warp route configu"
},
{
"path": "src/consts/links.ts",
"chars": 1078,
"preview": "export const links = {\n home: 'https://www.hyperlane.xyz',\n explorer: 'https://explorer.hyperlane.xyz',\n discord: 'ht"
},
{
"path": "src/consts/warpRouteWhitelist.test.ts",
"chars": 1006,
"preview": "import {\n GithubRegistry,\n warpRouteConfigs as publishedWarpRouteConfigs,\n} from '@hyperlane-xyz/registry';\nimport { W"
},
{
"path": "src/consts/warpRouteWhitelist.ts",
"chars": 1339,
"preview": "// A list of warp route config IDs to be included in the app\n// Warp Route IDs use format `SYMBOL/chainname1-chainname2."
},
{
"path": "src/consts/warpRoutes.ts",
"chars": 337,
"preview": "import { WarpCoreConfig } from '@hyperlane-xyz/sdk';\n\n// A list of Warp Route token configs\n// These configs will be mer"
},
{
"path": "src/consts/warpRoutes.yaml",
"chars": 225,
"preview": "# A list of Warp Route token configs\n# These configs will be merged with the warp routes in the configured registry\n# Th"
},
{
"path": "src/features/WarpContextInitGate.tsx",
"chars": 1064,
"preview": "import { SpinnerIcon, useTimeout } from '@hyperlane-xyz/widgets';\nimport { PropsWithChildren, useState } from 'react';\n\n"
},
{
"path": "src/features/analytics/intercom.ts",
"chars": 661,
"preview": "import Intercom from '@intercom/messenger-js-sdk';\n\nimport { logger } from '../../utils/logger';\n\nexport const INTERCOM_"
},
{
"path": "src/features/analytics/refiner.ts",
"chars": 1440,
"preview": "import { config } from '../../consts/config';\nimport { logger } from '../../utils/logger';\n\nconst REFINER_PROJECT_ID = p"
},
{
"path": "src/features/analytics/types.ts",
"chars": 2018,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\n\nexport enum EVENT_NAME {\n PAGE_VIEWED = 'Page Viewed',\n CHAIN_SE"
},
{
"path": "src/features/analytics/useWalletConnectionTracking.tsx",
"chars": 2278,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\nimport {\n useAccounts,\n useWalletDetails,\n} from '@hyperlane-xyz/"
},
{
"path": "src/features/analytics/utils.ts",
"chars": 4633,
"preview": "import { MultiProtocolProvider, Token, WarpCore } from '@hyperlane-xyz/sdk';\nimport { KnownProtocolType, objLength } fro"
},
{
"path": "src/features/balances/UsdLabel.tsx",
"chars": 420,
"preview": "import { TokenAmount } from '@hyperlane-xyz/sdk';\n\nimport { getUsdDisplayForFee } from './feeUsdDisplay';\nimport type { "
},
{
"path": "src/features/balances/cosmos.ts",
"chars": 3418,
"preview": "import { StargateClient } from '@cosmjs/stargate';\nimport { Token, TokenStandard } from '@hyperlane-xyz/sdk';\nimport { P"
},
{
"path": "src/features/balances/evm.ts",
"chars": 8957,
"preview": "import { ChainAddresses } from '@hyperlane-xyz/registry';\nimport { ChainMap, MultiProtocolProvider, Token } from '@hyper"
},
{
"path": "src/features/balances/feeUsdDisplay.test.ts",
"chars": 3655,
"preview": "import { TokenAmount } from '@hyperlane-xyz/sdk';\nimport { describe, expect, test } from 'vitest';\n\nimport { createMockT"
},
{
"path": "src/features/balances/feeUsdDisplay.ts",
"chars": 1729,
"preview": "import { TokenAmount } from '@hyperlane-xyz/sdk';\nimport { isNullish } from '@hyperlane-xyz/utils';\n\nimport type { FeePr"
},
{
"path": "src/features/balances/hooks.ts",
"chars": 9226,
"preview": "import { IToken, MultiProtocolProvider, Token } from '@hyperlane-xyz/sdk';\nimport { ProtocolType, getAddressProtocolType"
},
{
"path": "src/features/balances/svm.ts",
"chars": 5757,
"preview": "import { Token, TokenStandard } from '@hyperlane-xyz/sdk';\nimport { ProtocolType } from '@hyperlane-xyz/utils';\nimport {"
},
{
"path": "src/features/balances/tokens.ts",
"chars": 611,
"preview": "import { MultiProtocolProvider, Token } from '@hyperlane-xyz/sdk';\n\nimport { logger } from '../../utils/logger';\n\nexport"
},
{
"path": "src/features/balances/useFeePrices.ts",
"chars": 3070,
"preview": "import { Token, WarpCoreFeeEstimate } from '@hyperlane-xyz/sdk';\nimport { isNullish } from '@hyperlane-xyz/utils';\nimpor"
},
{
"path": "src/features/balances/utils.ts",
"chars": 934,
"preview": "import { Token } from '@hyperlane-xyz/sdk';\nimport { fromWeiRounded } from '@hyperlane-xyz/utils';\n\nimport { getTokenKey"
},
{
"path": "src/features/chains/ChainConnectionWarning.test.ts",
"chars": 2309,
"preview": "import { ChainMetadata, isRpcHealthy } from '@hyperlane-xyz/sdk';\nimport { ProtocolType } from '@hyperlane-xyz/utils';\ni"
},
{
"path": "src/features/chains/ChainConnectionWarning.tsx",
"chars": 2955,
"preview": "import { ChainMetadata, isRpcHealthy } from '@hyperlane-xyz/sdk';\nimport { ProtocolType } from '@hyperlane-xyz/utils';\ni"
},
{
"path": "src/features/chains/ChainEditModal.tsx",
"chars": 2176,
"preview": "import { ChainMetadata } from '@hyperlane-xyz/sdk';\nimport { ChainDetailsMenu, Modal } from '@hyperlane-xyz/widgets';\nim"
},
{
"path": "src/features/chains/ChainFilterPanel.tsx",
"chars": 10200,
"preview": "import { ChainName } from '@hyperlane-xyz/sdk';\nimport { ProtocolType, toTitleCase } from '@hyperlane-xyz/utils';\nimport"
},
{
"path": "src/features/chains/ChainList.tsx",
"chars": 3828,
"preview": "import { ChainName } from '@hyperlane-xyz/sdk';\nimport { PencilIcon } from '@hyperlane-xyz/widgets';\nimport { useMemo } "
},
{
"path": "src/features/chains/ChainWalletWarning.tsx",
"chars": 2074,
"preview": "import { toTitleCase } from '@hyperlane-xyz/utils';\nimport {\n useConnectFns,\n useDisconnectFns,\n useWalletDetails,\n} "
},
{
"path": "src/features/chains/MobileChainQuickSelect.tsx",
"chars": 3394,
"preview": "import { ChainName } from '@hyperlane-xyz/sdk';\nimport { useMemo } from 'react';\n\nimport { ChainLogo } from '../../compo"
},
{
"path": "src/features/chains/addresses.ts",
"chars": 1943,
"preview": "import { ChainAddresses, ChainAddressesSchema, IRegistry } from '@hyperlane-xyz/registry';\nimport { ChainMap, ChainName "
},
{
"path": "src/features/chains/chainFilterSort.test.ts",
"chars": 5434,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\nimport { describe, expect, it } from 'vitest';\n\nimport {\n ChainSor"
},
{
"path": "src/features/chains/chainFilterSort.ts",
"chars": 2715,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\n\nimport { ChainInfo } from './hooks';\n\n// ── Sort ─────────────────"
},
{
"path": "src/features/chains/hooks.ts",
"chars": 2096,
"preview": "import { ChainName, ChainStatus } from '@hyperlane-xyz/sdk';\nimport { ProtocolType } from '@hyperlane-xyz/utils';\nimport"
},
{
"path": "src/features/chains/metadata.ts",
"chars": 3451,
"preview": "import { IRegistry } from '@hyperlane-xyz/registry';\nimport {\n ChainMap,\n ChainMetadata,\n ChainMetadataSchema,\n merg"
},
{
"path": "src/features/chains/utils.ts",
"chars": 3188,
"preview": "import { isAbacusWorksChain } from '@hyperlane-xyz/registry';\nimport { ChainMap, ChainMetadata, ChainStatus, WarpCore } "
},
{
"path": "src/features/limits/const.ts",
"chars": 99,
"preview": "import { RouteLimit } from './types';\n\nexport const multiCollateralTokenLimits: RouteLimit[] = [];\n"
},
{
"path": "src/features/limits/types.ts",
"chars": 92,
"preview": "export type RouteLimit = {\n symbol: string;\n amountWei: bigint;\n chains: ChainName[];\n};\n"
},
{
"path": "src/features/limits/utils.test.ts",
"chars": 2788,
"preview": "import { TestChainName, TokenStandard } from '@hyperlane-xyz/sdk';\nimport { beforeEach, describe, expect, test, vi } fro"
},
{
"path": "src/features/limits/utils.ts",
"chars": 1226,
"preview": "import { IToken, Token } from '@hyperlane-xyz/sdk';\n\nimport { isValidMultiCollateralToken } from '../tokens/utils';\nimpo"
},
{
"path": "src/features/messages/graphqlClient.ts",
"chars": 953,
"preview": "import { config } from '../../consts/config';\n\nexport type GraphQLResult<T> = { type: 'success'; data: T } | { type: 'er"
},
{
"path": "src/features/messages/queries/build.ts",
"chars": 2398,
"preview": "import { addressToPostgresBytea, stringToPostgresBytea } from './encoding';\nimport { messageDetailFragment, messageStubF"
},
{
"path": "src/features/messages/queries/encoding.ts",
"chars": 1637,
"preview": "import type { ChainMetadata } from '@hyperlane-xyz/sdk';\nimport {\n addressToByteHexString,\n bufferToBase58,\n bytesToP"
},
{
"path": "src/features/messages/queries/fragments.ts",
"chars": 1482,
"preview": "/**\n * GraphQL fragments for message queries\n */\nexport const messageStubFragment = `\n id\n msg_id\n nonce\n sender\n r"
},
{
"path": "src/features/messages/types.ts",
"chars": 950,
"preview": "import type { Address, ChainId } from '@hyperlane-xyz/utils';\n\nexport enum MessageStatus {\n Unknown = 'unknown',\n Pend"
},
{
"path": "src/features/messages/useMergedTransferHistory.test.ts",
"chars": 2891,
"preview": "import { MultiProtocolProvider, WarpCore } from '@hyperlane-xyz/sdk';\nimport type { ChainMetadata } from '@hyperlane-xyz"
},
{
"path": "src/features/messages/useMergedTransferHistory.ts",
"chars": 3139,
"preview": "import type { MultiProtocolProvider, WarpCore } from '@hyperlane-xyz/sdk';\nimport { useMemo } from 'react';\n\nimport { lo"
},
{
"path": "src/features/messages/useMessageDeliveryStatus.ts",
"chars": 2964,
"preview": "import type { MultiProtocolProvider } from '@hyperlane-xyz/sdk';\nimport { useQuery } from '@tanstack/react-query';\n\nimpo"
},
{
"path": "src/features/messages/useMessageHistory.ts",
"chars": 7021,
"preview": "import type { MultiProtocolProvider } from '@hyperlane-xyz/sdk';\nimport {\n assert,\n bytesToProtocolAddress,\n fromHexS"
},
{
"path": "src/features/messages/useOriginFinality.ts",
"chars": 1613,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\nimport { useQuery } from '@tanstack/react-query';\n\nimport { useMult"
},
{
"path": "src/features/routerAddresses.test.ts",
"chars": 1617,
"preview": "import { TokenStandard } from '@hyperlane-xyz/sdk/token/TokenStandard';\nimport { normalizeAddress } from '@hyperlane-xyz"
},
{
"path": "src/features/sanctions/hooks/useIsAccountChainalysisSanctioned.ts",
"chars": 1142,
"preview": "import { useEthereumAccount } from '@hyperlane-xyz/widgets/walletIntegrations/ethereum';\nimport { isAddress } from 'viem"
},
{
"path": "src/features/sanctions/hooks/useIsAccountOfacSanctioned.ts",
"chars": 953,
"preview": "import { eqAddress } from '@hyperlane-xyz/utils';\nimport { useEthereumAccount } from '@hyperlane-xyz/widgets/walletInteg"
},
{
"path": "src/features/sanctions/hooks/useIsAccountSanctioned.ts",
"chars": 421,
"preview": "import { useIsAccountChainalysisSanctioned } from './useIsAccountChainalysisSanctioned';\nimport { useIsAccountOfacSancti"
},
{
"path": "src/features/store.ts",
"chars": 13674,
"preview": "import {\n ChainAddresses,\n GithubRegistry,\n IRegistry,\n PartialRegistry,\n} from '@hyperlane-xyz/registry';\nimport {\n"
},
{
"path": "src/features/theme/ThemeContext.tsx",
"chars": 3360,
"preview": "import {\n PropsWithChildren,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useState,\n"
},
{
"path": "src/features/tokens/ImportTokenButton.tsx",
"chars": 1357,
"preview": "import { Token } from '@hyperlane-xyz/sdk';\nimport { PlusIcon } from '@hyperlane-xyz/widgets';\nimport { useCallback } fr"
},
{
"path": "src/features/tokens/SelectOrInputTokenIds.tsx",
"chars": 1125,
"preview": "import { TextField } from '../../components/input/TextField';\nimport { SelectTokenIdField } from './SelectTokenIdField';"
},
{
"path": "src/features/tokens/SelectTokenIdField.tsx",
"chars": 3054,
"preview": "import { ChevronIcon, Modal, SpinnerIcon } from '@hyperlane-xyz/widgets';\nimport { useField } from 'formik';\nimport { us"
},
{
"path": "src/features/tokens/TokenChainIcon.tsx",
"chars": 1040,
"preview": "import { IToken } from '@hyperlane-xyz/sdk';\n\nimport { ChainLogo } from '../../components/icons/ChainLogo';\nimport { Tok"
},
{
"path": "src/features/tokens/TokenList.tsx",
"chars": 13552,
"preview": "import { ChainName, Token } from '@hyperlane-xyz/sdk';\nimport { Tooltip, useDebounce } from '@hyperlane-xyz/widgets';\nim"
},
{
"path": "src/features/tokens/TokenListPanel.tsx",
"chars": 2261,
"preview": "import { ChainName, Token } from '@hyperlane-xyz/sdk';\nimport { useEffect, useRef } from 'react';\n\nimport { SearchInput "
},
{
"path": "src/features/tokens/TokenSelectField.tsx",
"chars": 8366,
"preview": "import { Token } from '@hyperlane-xyz/sdk';\nimport { useField, useFormikContext } from 'formik';\nimport { useState } fro"
},
{
"path": "src/features/tokens/UnifiedTokenChainModal.tsx",
"chars": 3873,
"preview": "import { Token } from '@hyperlane-xyz/sdk';\nimport { Modal } from '@hyperlane-xyz/widgets';\nimport { useCallback, useSta"
},
{
"path": "src/features/tokens/approval.ts",
"chars": 1746,
"preview": "import { IToken, QuotedCallsParams } from '@hyperlane-xyz/sdk';\nimport { useAccountAddressForChain } from '@hyperlane-xy"
},
{
"path": "src/features/tokens/hooks.ts",
"chars": 6870,
"preview": "import { IToken, Token, WarpCore } from '@hyperlane-xyz/sdk';\nimport {\n useAccountForChain,\n useActiveChains,\n useWat"
},
{
"path": "src/features/tokens/types.ts",
"chars": 420,
"preview": "import { Token, TokenAmount } from '@hyperlane-xyz/sdk';\n\nexport interface TokensWithDestinationBalance {\n originToken:"
},
{
"path": "src/features/tokens/useTokenPrice.tsx",
"chars": 1686,
"preview": "import { useQuery } from '@tanstack/react-query';\n\nimport { logger } from '../../utils/logger';\nimport { useStore } from"
},
{
"path": "src/features/tokens/utils.test.ts",
"chars": 65732,
"preview": "import { TestChainName, TokenStandard, WarpCore } from '@hyperlane-xyz/sdk';\nimport { beforeEach, describe, expect, test"
},
{
"path": "src/features/tokens/utils.ts",
"chars": 15745,
"preview": "import {\n IToken,\n Token,\n TOKEN_COLLATERALIZED_STANDARDS,\n TokenStandard,\n WarpCore,\n} from '@hyperlane-xyz/sdk';\n"
},
{
"path": "src/features/tokens/wrappedTokenResolver.test.ts",
"chars": 4563,
"preview": "import { MultiProtocolProvider, TokenStandard } from '@hyperlane-xyz/sdk';\nimport { ProtocolType } from '@hyperlane-xyz/"
},
{
"path": "src/features/tokens/wrappedTokenResolver.ts",
"chars": 1885,
"preview": "import {\n IHypTokenAdapter,\n LOCKBOX_STANDARDS,\n MultiProtocolProvider,\n Token,\n TokenStandard,\n} from '@hyperlane-"
},
{
"path": "src/features/transfer/FeeSectionButton.tsx",
"chars": 2752,
"preview": "import { WarpCoreFeeEstimate } from '@hyperlane-xyz/sdk';\nimport { ChevronIcon, FuelPumpIcon, useModal } from '@hyperlan"
},
{
"path": "src/features/transfer/RecipientConfirmationModal.tsx",
"chars": 2031,
"preview": "import { Modal } from '@hyperlane-xyz/widgets';\nimport {\n getAccountAddressAndPubKey,\n useAccounts,\n} from '@hyperlane"
},
{
"path": "src/features/transfer/TransferFeeModal.tsx",
"chars": 3861,
"preview": "import { WarpCoreFeeEstimate } from '@hyperlane-xyz/sdk';\nimport { Modal, Skeleton, Tooltip } from '@hyperlane-xyz/widge"
},
{
"path": "src/features/transfer/TransferSection.tsx",
"chars": 806,
"preview": "import { ReactNode } from 'react';\n\ntype TransferSectionProps = {\n label: string;\n children: ReactNode;\n};\n\nexport fun"
},
{
"path": "src/features/transfer/TransferTokenCard.tsx",
"chars": 203,
"preview": "import { TransferTokenForm } from './TransferTokenForm';\n\nexport function TransferTokenCard() {\n return (\n <div clas"
},
{
"path": "src/features/transfer/TransferTokenForm.tsx",
"chars": 41742,
"preview": "import { IToken, QuotedCallsParams, Token, TokenAmount, WarpCore } from '@hyperlane-xyz/sdk';\nimport {\n KnownProtocolTy"
},
{
"path": "src/features/transfer/TransfersDetailsModal.tsx",
"chars": 15622,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\nimport {\n CopyButton,\n MessageStage,\n MessageStatus,\n MessageTi"
},
{
"path": "src/features/transfer/fees.test.ts",
"chars": 44956,
"preview": "import { Token, TokenAmount, WarpCore } from '@hyperlane-xyz/sdk';\nimport { beforeEach, describe, expect, test, vi } fro"
},
{
"path": "src/features/transfer/fees.ts",
"chars": 7091,
"preview": "import { IToken, Token, TokenAmount, WarpCore } from '@hyperlane-xyz/sdk';\nimport { objKeys } from '@hyperlane-xyz/utils"
},
{
"path": "src/features/transfer/maxAmount.ts",
"chars": 3336,
"preview": "import { MultiProtocolProvider, Token, TokenAmount, WarpCore } from '@hyperlane-xyz/sdk';\nimport { KnownProtocolType } f"
},
{
"path": "src/features/transfer/predicate.ts",
"chars": 4474,
"preview": "import type { IToken, Token, WarpCore } from '@hyperlane-xyz/sdk';\nimport {\n PredicateAttestation,\n TokenAmount,\n War"
},
{
"path": "src/features/transfer/relayApi.ts",
"chars": 2811,
"preview": "import { ProviderType, TypedTransactionReceipt } from '@hyperlane-xyz/sdk';\nimport { ProtocolType, isNullish } from '@hy"
},
{
"path": "src/features/transfer/scaleUtils.test.ts",
"chars": 3696,
"preview": "import { describe, expect, test } from 'vitest';\n\nimport { computeDestAmount, formatMessageAmount } from './scaleUtils';"
},
{
"path": "src/features/transfer/scaleUtils.ts",
"chars": 1818,
"preview": "import type { ScaleInput } from '@hyperlane-xyz/sdk';\nimport { localAmountFromMessage, messageAmountFromLocal, scalesEqu"
},
{
"path": "src/features/transfer/types.ts",
"chars": 1228,
"preview": "export interface TransferFormValues {\n originTokenKey: string | undefined;\n destinationTokenKey: string | undefined;\n "
},
{
"path": "src/features/transfer/useBalanceWatcher.ts",
"chars": 1133,
"preview": "import { TokenAmount } from '@hyperlane-xyz/sdk';\nimport { useEffect, useRef } from 'react';\nimport { toast } from 'reac"
},
{
"path": "src/features/transfer/useFeeQuotes.test.ts",
"chars": 8284,
"preview": "import type { IToken, Token, TokenAmount, WarpCore, WarpCoreFeeEstimate } from '@hyperlane-xyz/sdk';\nimport { ProtocolTy"
},
{
"path": "src/features/transfer/useFeeQuotes.ts",
"chars": 7811,
"preview": "import {\n IToken,\n PredicateAttestation,\n Token,\n TokenAmount,\n WarpCore,\n WarpCoreFeeEstimate,\n} from '@hyperlane"
},
{
"path": "src/features/transfer/useQuotedCalls.test.ts",
"chars": 1510,
"preview": "import { computeScopedSalt } from '@hyperlane-xyz/sdk';\nimport { encodePacked, keccak256 } from 'viem';\nimport { describ"
},
{
"path": "src/features/transfer/useQuotedCalls.ts",
"chars": 10017,
"preview": "import {\n IToken,\n QuotedCallsParams,\n SubmitQuoteCommand,\n Token,\n TokenAmount,\n TokenPullMode,\n WarpCore,\n com"
},
{
"path": "src/features/transfer/useTokenTransfer.ts",
"chars": 12754,
"preview": "import {\n ProviderType,\n QuotedCallsParams,\n Token,\n TypedTransactionReceipt,\n WarpCore,\n WarpTxCategory,\n} from '"
},
{
"path": "src/features/transfer/utils.test.ts",
"chars": 2504,
"preview": "import { describe, expect, test, vi } from 'vitest';\n\nimport { estimateDeliverySeconds, formatEta } from './utils';\n\ndes"
},
{
"path": "src/features/transfer/utils.ts",
"chars": 8547,
"preview": "import {\n ChainMap,\n CoreAddresses,\n MultiProtocolCore,\n ProviderType,\n TypedTransactionReceipt,\n ViemProvider,\n} "
},
{
"path": "src/features/wallet/ConnectWalletButton.tsx",
"chars": 963,
"preview": "import { ConnectWalletButton as ConnectWalletButtonInner } from '@hyperlane-xyz/widgets/walletIntegrations/ConnectWallet"
},
{
"path": "src/features/wallet/RecipientAddressModal.tsx",
"chars": 2495,
"preview": "import { isValidAddress, ProtocolType } from '@hyperlane-xyz/utils';\nimport { Modal, XIcon } from '@hyperlane-xyz/widget"
},
{
"path": "src/features/wallet/SideBarMenu.tsx",
"chars": 15245,
"preview": "import { normalizeAddress } from '@hyperlane-xyz/utils';\nimport { RefreshIcon, SpinnerIcon } from '@hyperlane-xyz/widget"
},
{
"path": "src/features/wallet/WalletConnectionWarning.tsx",
"chars": 1328,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\nimport { useWalletDetails } from '@hyperlane-xyz/widgets/walletInte"
},
{
"path": "src/features/wallet/WalletDropdown.tsx",
"chars": 7142,
"preview": "import { ProtocolType, shortenAddress } from '@hyperlane-xyz/utils';\nimport { ChevronIcon, DropdownMenu, useModal, XIcon"
},
{
"path": "src/features/wallet/WalletProtocolModal.test.ts",
"chars": 495,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\nimport { describe, expect, it } from 'vitest';\n\nimport { config } f"
},
{
"path": "src/features/wallet/WalletProtocolModal.tsx",
"chars": 3611,
"preview": "import { ProtocolType } from '@hyperlane-xyz/utils';\nimport { Modal, PROTOCOL_TO_LOGO } from '@hyperlane-xyz/widgets';\ni"
},
{
"path": "src/features/wallet/_e2e/E2EAutoConnectCosmos.tsx",
"chars": 1098,
"preview": "import { useWallet } from '@cosmos-kit/react';\nimport { useEffect } from 'react';\n\nconst MOCK_WALLET_NAME = 'warp-e2e-mo"
},
{
"path": "src/features/wallet/_e2e/E2EAutoConnectEvm.tsx",
"chars": 603,
"preview": "import { useEffect } from 'react';\nimport { useAccount, useConnect } from 'wagmi';\n\nimport { MOCK_EVM_CONNECTOR_ID } fro"
},
{
"path": "src/features/wallet/_e2e/E2EAutoConnectRadix.tsx",
"chars": 1255,
"preview": "import { useAccount } from '@hyperlane-xyz/widgets/walletIntegrations/radix/AccountContext';\nimport { useEffect } from '"
},
{
"path": "src/features/wallet/_e2e/E2EAutoConnectSolana.tsx",
"chars": 1040,
"preview": "import { useWallet } from '@solana/wallet-adapter-react';\nimport { useEffect } from 'react';\n\nconst MOCK_WALLET_NAME = '"
},
{
"path": "src/features/wallet/_e2e/E2EAutoConnectStarknet.tsx",
"chars": 559,
"preview": "import { useAccount, useConnect } from '@starknet-react/core';\nimport { useEffect } from 'react';\n\nconst MOCK_CONNECTOR_"
},
{
"path": "src/features/wallet/_e2e/E2EAutoConnectTron.tsx",
"chars": 1041,
"preview": "import type { AdapterName } from '@tronweb3/tronwallet-abstract-adapter';\nimport { useWallet } from '@tronweb3/tronwalle"
},
{
"path": "src/features/wallet/_e2e/MockCosmosWallet.test.fixtures.ts",
"chars": 410,
"preview": "// Deterministic addresses the MockCosmosWallet mnemonic resolves to under\n// various bech32 prefixes. Any change to the"
},
{
"path": "src/features/wallet/_e2e/MockCosmosWallet.test.ts",
"chars": 3332,
"preview": "import { beforeEach, describe, expect, test } from 'vitest';\n\nimport { MockCosmosWallet, mockCosmosWalletInfo } from './"
},
{
"path": "src/features/wallet/_e2e/MockCosmosWallet.ts",
"chars": 5319,
"preview": "import { DirectSecp256k1HdWallet, type OfflineDirectSigner } from '@cosmjs/proto-signing';\nimport {\n ChainWalletBase,\n "
},
{
"path": "src/features/wallet/_e2e/MockSolanaAdapter.ts",
"chars": 4087,
"preview": "import {\n BaseMessageSignerWalletAdapter,\n WalletReadyState,\n type SendTransactionOptions,\n type WalletName,\n} from "
},
{
"path": "src/features/wallet/_e2e/MockStarknetConnector.test.ts",
"chars": 756,
"preview": "import { describe, expect, test } from 'vitest';\n\nimport { MOCK_STARKNET_ADDRESS, createMockStarknetConnector } from './"
},
{
"path": "src/features/wallet/_e2e/MockStarknetConnector.ts",
"chars": 1669,
"preview": "import { MockConnector, type MockConnectorAccounts } from '@starknet-react/core';\n\n// Fixed address for deterministic E2"
},
{
"path": "src/features/wallet/_e2e/MockTronAdapter.test.ts",
"chars": 1011,
"preview": "import { AdapterState } from '@tronweb3/tronwallet-abstract-adapter';\nimport { describe, expect, test } from 'vitest';\n\n"
},
{
"path": "src/features/wallet/_e2e/MockTronAdapter.ts",
"chars": 2251,
"preview": "import {\n Adapter,\n AdapterState,\n WalletReadyState,\n type AdapterName,\n type SignedTransaction,\n type Transaction"
},
{
"path": "src/features/wallet/_e2e/constants.ts",
"chars": 701,
"preview": "// Fixed accounts the E2E mocks connect as.\n//\n// EVM: chosen so it stands out in traces (0xE2eE… pattern).\n// Solana: d"
}
]
// ... and 99 more files (download for full content)
About this extraction
This page contains the full source code of the hyperlane-xyz/hyperlane-warp-ui-template GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 299 files (936.6 KB), approximately 255.9k tokens, and a symbol index with 658 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.