Skip to content

feat: add multi-chain support for Base, Arbitrum, and Ethereum#111

Closed
rube-de wants to merge 50 commits intooasisprotocol:masterfrom
rube-de:feature/issue-30-multi-chain-support
Closed

feat: add multi-chain support for Base, Arbitrum, and Ethereum#111
rube-de wants to merge 50 commits intooasisprotocol:masterfrom
rube-de:feature/issue-30-multi-chain-support

Conversation

@rube-de
Copy link
Copy Markdown

@rube-de rube-de commented Jan 19, 2026

Summary

Extends bridge functionality to support USDC/USDT deposits from Base, Arbitrum, and Ethereum networks. Users can now select their preferred source chain when bridging stablecoins to receive ROSE on Sapphire.

Closes #30

Changes

New Components

  • ChainSelector - Dropdown for source chain selection with chain icons
  • Chain Icons - ArbitrumIcon, BaseIcon, EthereumIcon components

Core Updates

  • Add sourceChainId parameter throughout paymaster flow and transaction tracking
  • Update ROFL_PAYMASTER_TOKEN_CONFIG to support all three chains with shared CREATE2 vault address
  • Migrate localStorage schemas with chain validation for pending transactions and history
  • Add Arbitrum and Ethereum to wagmi chain config and block explorer utilities

Code Quality Improvements

  • Derive DEFAULT_CHAIN_OPTIONS from SUPPORTED_SOURCE_CHAINS (single source of truth)
  • Add getTokenIcon() mapping for extensibility
  • Add defensive validation for tokenConfig and sourceChainId

Test Plan

  • Select Base chain → verify USDC/USDT tokens available
  • Select Arbitrum chain → verify USDC/USDT tokens available
  • Select Ethereum chain → verify USDC/USDT tokens available
  • Complete bridge from each chain → verify success modal shows correct chain name
  • Check transaction history → verify explorer links point to correct chain
  • Test recovery flow → verify pending transaction shows correct chain
  • Verify old localStorage records migrate correctly to Base chain

🤖 Generated with Claude Code

rube-de and others added 30 commits January 14, 2026 16:42
- Remove Roffle contract, tests, deploy tasks, and frontend components
- Add beads for issue tracking
- Add serena for code intelligence
- Add CLAUDE.md and AGENTS.md project docs
- Update .gitignore for macOS artifacts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Council review identified 6 P0 and 3 P1 issues. All fixes implemented:

P0 Fixes:
- P0-1: Fix stale closure bug on currentStep using useRef
- P0-2: Validate paymentId before polling to prevent stuck funds
- P0-3: Add AbortController for cancellation support
- P0-4: Make pollPayment throw on timeout instead of silent null
- P0-5: Wrap finally block chain switch in try-catch
- P0-6: Add duplicate submission guard with isLoadingRef

P1 Fixes:
- Increase minIncrease threshold from 1 wei to 0.001 ROSE
- Remove currentStep from dependency array (use ref instead)
- Add context to additionalSteps errors

Breaking change: ProgressStepWithAction.action now accepts AbortSignal

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Codex review identified additional issues:

- Fix memory leak: Add { once: true } to abort signal event listeners
  to prevent listener accumulation in polling loops
- Add useEffect cleanup: Abort in-flight operations on component unmount
- Make catch block cancellable: Polling error retry delay now respects
  abort signal instead of forcing 4s wait

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ster

Replace hardcoded polling and timeout values with descriptive constants:
- POLL_INTERVAL_MS (4000): Interval between payment status checks
- POLL_MAX_ATTEMPTS (60): Maximum polling attempts before timeout
- BALANCE_CHECK_TIMEOUT_MS (180000): Maximum wait for balance increase
- MIN_BALANCE_INCREASE_THRESHOLD (1e15): Minimum meaningful balance change

Remove temporary code review annotations (P0/P1 FIX comments) and replace
with concise inline documentation explaining the purpose of each guard
and error handling mechanism.

Improves code maintainability by making timing configurations explicit
and self-documenting, while reducing visual noise from review artifacts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Re-throw errors in catch block instead of swallowing
- Use consistent undefined checks for feed values
- Simplify initialLoading logic for readability
- Remove redundant .catch() block that just re-throws
- Simplify step initialization (remove duplicate currentStepRef/setCurrentStep)
- Include transaction hash in error message when deposit lacks paymentId
- Make isLoadingRef check-and-set atomic to prevent theoretical race
- Abort any lingering controller before creating new one (defensive)
Components created:
- BridgeCard: main container with sections and divider
- AmountInput: token amount input with decimal validation and MAX button
- TokenSelector: dropdown for source/destination token selection
- FeeBreakdown: transaction details and fee estimates display

Code review fixes applied:
- P0: TokenSelector uses chainId:address composite key for cross-chain safety
- P0: AmountInput includes gas buffer support for native tokens
- P1: Labels linked to inputs with useId() for accessibility
- P1: FeeEstimate rate display uses relative positioning (overflow fix)
- P2: Stable keys in FeeBreakdown list rendering
- P2: Added aria-hidden to decorative SVGs

Closes #2

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Address Copilot review comment: BridgeCardSection is a container that
wraps children with their own labeled inputs. Using <label> without
htmlFor creates dangling/competing label semantics. Changed to <span>
for semantic correctness.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add network-specific documentation for gasBuffer prop
- Improve getTokenKey fallback to use symbol:name for collision safety

Storybook stories tracked in paymaster-ui-xyl for follow-up.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Per Copilot review suggestion - the variable is only used for isLast
calculation, not as a React key.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete transformation from raffle dApp to cross-chain bridge interface:

- Replace raffle components (tickets, winners, timers, FAQ) with bridge UI
- Integrate BridgeCard, AmountInput, TokenSelector, and FeeBreakdown components
- Implement Base (USDC/USDT) to Sapphire (ROSE) bridging flow via usePaymaster
- Reduce component complexity from ~850 to ~345 lines
- Fix footer link to use correct oasis.net domain
- Return transaction hash from paymasterVault deposit for improved error handling
- Update usePaymaster hook to utilize returned hash property

Closes #3

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add getRoseEstimate function (inverse of getQuote) to calculate
  ROSE output for token input amount
- Fix slippage direction: subtract from ROSE output (user receives less)
- Add null guard for tokenConfig with fallback to first token
- Fix useEffect dependency to use stable getRoseEstimate reference
- Calculate and display actual exchange rate instead of hardcoded string

Fixes: paymaster-ui-7h5, paymaster-ui-l6v, paymaster-ui-tjf, paymaster-ui-dug
- Fix handleTokenChange signature to match TokenSelector API
- Make TokenSelector.onChange optional when singleToken=true
- Add console.warn for estimate fetch errors (aids debugging)
- Remove empty onChange prop from destination selector

Note: depositResult.hash fallback comment rejected - paymasterVault.ts
returns { hash } directly, no transactionHash property exists.
- Fix FeeEstimate to use roseEstimate instead of quote
- Fix handleBridge deps: use paymaster.startTopUp not paymaster
- Fix handleTokenChange deps: use paymaster.reset not paymaster
Prevents stale exchange rate data from persisting when user
changes tokens or resets the bridge flow.
Slippage was being passed both in feeItems array and as a separate
slippage prop, causing it to render twice. Keep only the dedicated
prop which provides clearer 'Max slippage' labeling.
- Fix duplicate step labels (Connecting to Base/Sapphire)
- Export PROGRESS_STEP_COUNT constant for external use
- Fix magic number in TopUpButton progress display
- Fix progress reset in finally block (preserve failed step visibility)
- Add expandable error messages with Show more/less toggle
- Add transaction recovery support via localStorage persistence
  - Save pending transaction after deposit commits
  - Resume from step 4 if user returns with pending tx
  - Clear on success or manual dismiss

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The new bridge architecture in master handles everything in App.tsx,
not TopUpButton. This adds:

- Recovery banner for interrupted bridge transactions
- Resume and Dismiss buttons for pending transactions
- Disable bridge button when recovery pending

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update project_overview.md: Roffle → Bridge
- Update suggested_commands.md: deploy:roffle → deploy

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add complete field validation in getPendingTransaction
- Use correct token decimals from pendingTransaction in recovery UI
- Preserve pending tx in localStorage when user views different token
  (allows recovery when they switch back to correct token)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add eslint-disable comments for react-hooks/exhaustive-deps where
  we intentionally depend on specific methods instead of whole objects
  to avoid unnecessary re-renders
- Add eslint-disable for unused _token parameter required by
  TokenSelector onChange signature
- Apply Prettier formatting to all modified files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove "Build contracts" step from ci-lint workflow (Hardhat no longer needed)
- Disable cloudflare-pages automatic triggers until project is configured
- Remove dead import for deleted tasks/deploy module in hardhat.config.ts
- Track Cloudflare Pages setup in #13

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The existence of pendingTransaction is already guaranteed by the
conditional rendering check on line 312, making the optional chaining
unnecessary.

Addresses Gemini code review comment.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract pendingTransaction to local const in IIFE for TypeScript
  type narrowing (fixes TS18047 null-safety error)
- Clarify comment about ref vs state reset behavior in finally block

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace IIFE pattern with dedicated component for pending transaction
recovery UI. This eliminates the need for TypeScript type narrowing
workarounds inside closures and improves code clarity.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Configure Claude Code project settings:
- Enable sandbox mode for secure command execution
- Add allowed commands for git, package managers, dev tools
- Restrict destructive operations (branch deletion, PR merge)
- Enable official plugins for features, design, context, security
- Address security review: remove dangerous wildcards

Security fixes per PR review:
- Remove `echo *` (allows arbitrary file writes)
- Remove `docker *` (allows sandbox escape)
- Remove `gh api *` (allows destructive API calls)
- Restrict package managers to specific commands
- Restrict gh commands to read-only operations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Restructure sandbox config with autoAllowBashIfSandboxed: false

- Add git worktree, yarn tsc/eslint, codex, gemini commands

- Add WebSearch to permissions.allow

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
rube-de and others added 20 commits January 15, 2026 16:54
Update meta tags, package info, and wagmi config for Oasis Bridge.
Remove outdated Roffle social images and non-existent domain URLs.

Changes:
- Update index.html title, description, and keywords
- Update package.json name to @oasisprotocol/oasis-bridge
- Update wagmi.ts appName to 'Oasis Bridge'
- Remove og:url, twitter:url, canonical (bridge.oasis.io doesn't exist)
- Delete og-image.png and twitter.png (old Roffle branding)

Closes #4

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace inline error display with toast notifications featuring retry button
- Stack FeeEstimate component vertically on mobile (<640px)
- Trigger wallet connect modal on button click when disconnected
- Add vertical arrow icon for mobile fee estimate display
- Configure dark theme toaster with custom styling
- Fix stale closure in retry handler using ref pattern

Closes #6

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Address review feedback from Gemini and Copilot:
- Updated comment to accurately explain why deps are limited
- paymaster object is not memoized (new ref each render)
- paymaster.reset is stable but accessed via unstable parent
- Only error changes should trigger the effect

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add BridgeSuccessModal component with explorer links and copy-to-clipboard
- Add block explorer URL utilities using wagmi chain definitions
- Add successData state to usePaymaster hook for modal trigger
- Support both startTopUp and resumeFromPending success flows
- Add controlled mode to TransactionHistory for external open control
- Replace hardcoded basescan URL with dynamic explorer utility

Fixes #19

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Cache getExplorerTxUrl result in TransactionHistory to avoid duplicate calls
- Add warning log when transaction record is missing txHash in resumeFromPending
- Apply Prettier formatting to affected files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds husky + lint-staged to run CI lint checks locally before commits:
- ESLint with --max-warnings 0 on staged .ts/.tsx/.js files
- Prettier check on staged .ts/.tsx/.js/.json/.md/.css files
- TypeScript type check (full project)

Implements #24
The prepare script runs 'husky' but husky was only installed in
frontend/. CI failed because root yarn install couldn't find husky.
Address review feedback from Gemini and Copilot:
- Use --cwd flag for lint-staged and yarn instead of cd
- Cleaner approach that doesn't change shell execution context
Add comprehensive Storybook stories for 5 bridge UI components:
- BridgeCard: Container with sections and divider variants
- AmountInput: Decimal validation, MAX button, gas buffer tests
- TokenSelector: Dropdown interaction, keyboard navigation a11y
- FeeBreakdown: Loading states, partial loading, collapsed view
- FeeEstimate: Responsive layout, loading states

Includes interaction tests for:
- Decimal input validation (reject excess precision)
- MAX button behavior with gas buffer subtraction
- Native token vs ERC20 gas buffer handling
- Keyboard navigation accessibility (Enter, Arrow, Escape)

Fixes #20
- Add maxValue to Disabled story to render MAX button for test
- Use args spread in InContext story for Storybook controls
Document that `npx husky install` must be run in each new git worktree
to enable pre-commit hooks.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update to new deployment address:
- Old: 0x7D3B4dd07bd523E519e0A91afD8e3B325586fb5b
- New: 0x4A390256055264787F1d7d9b75bDBe78F6b7f49C

Fixes #29
Change default className from `size-full` to `size-6` to prevent
the icon from expanding to fill its container. This matches the
visual scale of other token icons (USDCIcon, USDTIcon).

Fixes #28
Update all branding references across the project:
- index.html: title, meta tags, Open Graph, Twitter cards
- wagmi.ts: wallet connect app name
- README.md: project description and features
- CLAUDE.md: project overview

Fixes #31
Enable users to bridge stablecoins from Base, Arbitrum, or Ethereum to Oasis Sapphire.

Key changes:
- Add ChainSelector component with dropdown UI for source chain selection
- Create chain icons (ArbitrumIcon, BaseIcon, EthereumIcon) matching token icon patterns
- Extend PaymasterVault config to support all three chains with shared CREATE2 address
- Thread sourceChainId through entire paymaster flow, transaction tracking, and UI
- Implement localStorage migrations with backward compatibility for Base-only records
- Add chain validation to filter unsupported chains from pending transactions and history
- Update block explorers to support Arbitrum and Ethereum transaction links
- Build token options dynamically from chain config with extensible icon mapping
- Reset token selection and clear amount when user switches chains
- Update UI labels to reflect multi-chain support in descriptions and success modals

Resolves #30

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@rube-de rube-de closed this Jan 19, 2026
@rube-de rube-de deleted the feature/issue-30-multi-chain-support branch January 19, 2026 17:41
@rube-de rube-de restored the feature/issue-30-multi-chain-support branch January 19, 2026 17:44
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.

1 participant