Skip to content

feat: prevent sending tokens to warp route and token contract addresses#919

Open
nambrot-agent wants to merge 3 commits intomainfrom
feat/block-contract-recipient-addresses
Open

feat: prevent sending tokens to warp route and token contract addresses#919
nambrot-agent wants to merge 3 commits intomainfrom
feat/block-contract-recipient-addresses

Conversation

@nambrot-agent
Copy link

@nambrot-agent nambrot-agent commented Jan 29, 2026

Summary

Prevents users from accidentally sending tokens to the warp route contract address on the destination chain, which is a common mistake that results in lost funds. Also blocks other contract addresses and well-known invalid recipients.

Problem

When bridging tokens (e.g., from Ethereum to Base), users sometimes mistakenly enter the warp route contract address or token contract address as the recipient instead of their wallet address. This results in tokens being permanently lost.

Solution

Validates the recipient address against a blocklist that includes:

  1. Warp route contract addresses - The HypCollateral/HypSynthetic contract on the destination chain
  2. Collateral token contract addresses - The underlying token contract (e.g., USDC) on chains where it's used as collateral
  3. Well-known blocked addresses - Null address (0x0), precompile addresses (0x1-0x9), burn addresses, etc.

User Experience

When a user enters a blocked address as the recipient, they see a descriptive inline error message:

Blocked Address Type Error Message
Warp Route contract "This is a Warp Route contract address, not a wallet address"
Collateral token "This is the USDC token contract address, not a wallet address"
Null address "Null address (0x0)"
Burn address "Burn address"

Changes

  • Added BLOCKED_RECIPIENT_ADDRESSES constant for well-known invalid addresses
  • Added blockedAddressesByChainMap to store state, built from warp route config
  • Updated transfer form validation to check recipients against blocked addresses
  • Only normalizes EVM hex addresses to lowercase (preserves case for non-EVM like Solana)
  • Added comprehensive unit tests

Testing

  • 9 unit tests covering all edge cases
  • Manually tested on local dev server

Prevent users from accidentally sending tokens to contract addresses
on the destination chain by validating the recipient address against:
- Warp route contract addresses
- Collateral token contract addresses (e.g., USDC)
- Well-known blocked addresses (null, precompiles, burn addresses)

Validation shows descriptive inline error messages explaining why the
address is blocked.
@nambrot-agent nambrot-agent requested a review from Xaroz as a code owner January 29, 2026 21:07
@vercel
Copy link

vercel bot commented Jan 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperlane-warp-template Ready Ready Preview, Comment Jan 29, 2026 10:25pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics-test Ignored Ignored Jan 29, 2026 10:25pm
injective-bridge Ignored Ignored Jan 29, 2026 10:25pm
nexus-bridge Ignored Ignored Jan 29, 2026 10:25pm
ousdt-bridge Ignored Ignored Jan 29, 2026 10:25pm
trump-bridge Ignored Ignored Jan 29, 2026 10:25pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

Adds a blocked-recipient system: a blacklist constant, helper to build per-chain blocked-address maps from token configs, store plumbing to persist those maps, tests for the helper, and transfer form validation updated to consult the new maps. (50 words)

Changes

Cohort / File(s) Summary
Blocked Addresses Configuration
src/consts/blacklist.ts
Adds BLOCKED_RECIPIENT_ADDRESSES: Record<string, string> mapping well-known Ethereum addresses to human-readable labels.
Store State & Helper Logic
src/features/store.ts
Adds blockedAddressesByChainMap to WarpContext and AppState; imports BLOCKED_RECIPIENT_ADDRESSES; implements and exports getBlockedAddressesByChain(tokens) which builds per-chain Map<string, string> of blocked recipients (warp routes, collateral addresses, well-known addresses), normalizes hex keys to lowercase, and first-entry-wins. Integrates map into init/store flows.
Transfer Form Validation
src/features/transfer/TransferTokenForm.tsx
Replaces router-address Set checks with Map-based blocked-address lookup. Validation normalizes recipient (lowercase for 0x addresses) and queries blockedAddressesByChainMap[destination]?.get(recipient) to return a dynamic blocked reason. Updated function signatures and call sites to accept the map.
Test Coverage
src/features/store.test.ts
Adds tests for getBlockedAddressesByChain() covering warp route blocking, collateral blocking, cross-chain behavior, well-known address blocking, lowercase normalization, empty/missing collateral cases, and duplicate/first-entry-wins behavior.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Init
    participant Store as Store
    participant Helper as getBlockedAddressesByChain
    participant Form as TransferTokenForm
    participant Validation as Validation Logic

    App->>Store: initWarpContext(tokenConfig)
    Store->>Helper: getBlockedAddressesByChain(tokens)
    Helper->>Helper: collect warp route addresses per chain
    Helper->>Helper: collect collateral addresses per chain
    Helper->>Helper: merge BLOCKED_RECIPIENT_ADDRESSES (lowercased where applicable)
    Helper->>Store: return blockedAddressesByChainMap
    Store->>Form: provide blockedAddressesByChainMap

    Note over Form: user enters recipient and destination chain
    Form->>Validation: validate recipient
    Validation->>Validation: normalize recipient (lowercase for 0x)
    Validation->>Store: lookup blockedAddressesByChainMap[chain].get(recipient)
    alt recipient blocked
        Validation->>Form: return reason (blocked label)
        Form->>Form: show error / prevent submit
    else recipient allowed
        Validation->>Form: proceed with transfer
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • Xaroz
  • cmcewen
  • xeno097

Poem

A wee blacklist tucked in the code,
Chains and addresses in tidy mode.
First one wins, lowercase for the hex,
Keeps sticky transfers off the decks. 🚪🧅

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main objective: preventing token transfers to contract addresses (warp routes and token contracts), which aligns with the comprehensive changes across store management, validation logic, and blocked address tracking.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/block-contract-recipient-addresses

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/features/store.ts`:
- Around line 317-324: The addBlockedAddress helper currently lowercases every
address which can collapse distinct case‑sensitive formats; change the
normalization to only lowercase hex EVM addresses (e.g., addresses starting with
"0x" or matching a hex pattern) and leave non‑hex addresses unchanged, and apply
the same normalization change in TransferTokenForm so both places use identical
logic; keep the existing "first reason wins" behavior using result[chain] and
result[chain].has/ set.

Comment on lines 317 to 324
// Helper to add address to the map (case-insensitive for EVM compatibility)
const addBlockedAddress = (chain: ChainName, address: string, reason: string) => {
result[chain] ||= new Map<string, string>();
const normalizedAddress = address.toLowerCase();
// Don't overwrite existing entries (first reason wins)
if (!result[chain].has(normalizedAddress)) {
result[chain].set(normalizedAddress, reason);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize only hex addresses to avoid false positives.
Line 320 lowercases every address. For case‑sensitive formats (e.g., base58), that can collapse distinct addresses and block legit recipients. I’d only case‑fold hex (0x) addys, and mirror the same logic in TransferTokenForm (Line 856).

🔧 Suggested fix
-    const normalizedAddress = address.toLowerCase();
+    const normalizedAddress = /^0x[0-9a-fA-F]{40}$/.test(address)
+      ? address.toLowerCase()
+      : address;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Helper to add address to the map (case-insensitive for EVM compatibility)
const addBlockedAddress = (chain: ChainName, address: string, reason: string) => {
result[chain] ||= new Map<string, string>();
const normalizedAddress = address.toLowerCase();
// Don't overwrite existing entries (first reason wins)
if (!result[chain].has(normalizedAddress)) {
result[chain].set(normalizedAddress, reason);
}
// Helper to add address to the map (case-insensitive for EVM compatibility)
const addBlockedAddress = (chain: ChainName, address: string, reason: string) => {
result[chain] ||= new Map<string, string>();
const normalizedAddress = /^0x[0-9a-fA-F]{40}$/.test(address)
? address.toLowerCase()
: address;
// Don't overwrite existing entries (first reason wins)
if (!result[chain].has(normalizedAddress)) {
result[chain].set(normalizedAddress, reason);
}
🤖 Prompt for AI Agents
In `@src/features/store.ts` around lines 317 - 324, The addBlockedAddress helper
currently lowercases every address which can collapse distinct case‑sensitive
formats; change the normalization to only lowercase hex EVM addresses (e.g.,
addresses starting with "0x" or matching a hex pattern) and leave non‑hex
addresses unchanged, and apply the same normalization change in
TransferTokenForm so both places use identical logic; keep the existing "first
reason wins" behavior using result[chain] and result[chain].has/ set.

Address CodeRabbit feedback: non-EVM addresses like base58 (Solana) are
case-sensitive and should not be lowercased. Now only addresses matching
the hex pattern (0x[0-9a-fA-F]+) are normalized to lowercase.
@nambrot-agent nambrot-agent changed the title feat: block contract addresses as transfer recipients feat: prevent sending tokens to warp route and token contract addresses Jan 29, 2026
Comment on lines +320 to +326
const addBlockedAddress = (chain: ChainName, address: string, reason: string) => {
result[chain] ||= new Map<string, string>();
const normalizedAddress = /^0x[0-9a-fA-F]+$/i.test(address) ? address.toLowerCase() : address;
// Don't overwrite existing entries (first reason wins)
if (!result[chain].has(normalizedAddress)) {
result[chain].set(normalizedAddress, reason);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just normalizeAddress from our utils?

Comment on lines +857 to +859
const normalizedRecipient = /^0x[0-9a-fA-F]+$/i.test(recipient)
? recipient.toLowerCase()
: recipient;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants