Add PEN → Base migration page - #655
Open
ebma wants to merge 3 commits into
Open
Conversation
ebma
marked this pull request as ready for review
July 8, 2026 07:49
✅ Deploy Preview for rococo-souffle-a625f5 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Adds a new Pendulum-only “PEN → Base” migration flow to the portal, including form validation, Substrate extrinsic submission at finality, and Base-side release-status tracking via JSON-RPC calls to a MigrationVault.
Changes:
- Introduces the migration page UI + Yup validation schema (amount rules + EIP-55 address validation + user confirmations).
- Adds hooks for submitting
tokenMigration.migrate(...)and polling Base vault release status/approvals. - Adds minimal Ethereum helpers (EIP-55 checksum + JSON-RPC
eth_call/eth_getCode) and wires the page into routing + navigation.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/pages/migration/ValidationSchema.ts | Adds Yup schema for migration form validation (amount + Base address + irreversible confirmation). |
| src/pages/migration/index.tsx | Implements the migration page UI, submission flow, contract-destination warning, and release tracking display. |
| src/hooks/migration/useMigrationPallet.tsx | Adds hook to read pallet constants/paused state and submit the migration extrinsic to finality. |
| src/hooks/migration/useBaseReleaseStatus.ts | Adds hook to poll MigrationVault for approvals/released status of a finalized migration. |
| src/helpers/ethereum.ts | Adds EIP-55 checksum helpers, payload hash encoding, and minimal Base JSON-RPC calls. |
| src/constants/migration.ts | Adds Base target configuration (RPC URL + vault address + explorer URL) and lookup helper. |
| src/components/Layout/links.tsx | Adds a “Migrate to Base” nav item (hidden outside Pendulum). |
| src/app.tsx | Registers the /migration route and page loader. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+79
to
+102
| return new Promise<PendingMigration>((resolve, reject) => | ||
| extrinsic | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| .signAndSend(walletAccount.address, { signer: walletAccount.signer as any }, (result) => { | ||
| const { status, events } = result; | ||
| const errors = getErrors(events, api); | ||
|
|
||
| if (status.isInBlock && errors.length > 0) { | ||
| reject(new Error(`Transaction failed: ${errors.join('\n')}`)); | ||
| } else if (status.isFinalized) { | ||
| if (errors.length > 0) { | ||
| reject(new Error(`Transaction failed: ${errors.join('\n')}`)); | ||
| return; | ||
| } | ||
| const migration = extractMigrationInitiated(api, events); | ||
| if (migration) { | ||
| resolve(migration); | ||
| } else { | ||
| reject(new Error('MigrationInitiated event not found in finalized transaction')); | ||
| } | ||
| } | ||
| }) | ||
| .catch((error: Error) => reject(error)), | ||
| ); |
| placeholder="0x…" | ||
| autoComplete="off" | ||
| spellCheck={false} | ||
| {...register('baseAddress')} |
Comment on lines
+36
to
+46
| const response = await fetch(baseRpcUrl, { | ||
| method: 'POST', | ||
| headers: { 'content-type': 'application/json' }, | ||
| body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }), | ||
| }); | ||
| const json = (await response.json()) as { result?: string; error?: { message: string } }; | ||
| if (json.error || json.result === undefined) { | ||
| throw new Error(`Base RPC ${method} failed: ${json.error?.message ?? 'no result'}`); | ||
| } | ||
| return json.result; | ||
| } |
New /pendulum/migration page implementing the migration UX (PRD U1-U4): amount input validated against transferable balance, the minimum migration amount and the leave-ED-or-migrate-all rule; EIP-55-validated Base address input with checksummed preview; smart-contract destination detection via eth_getCode with an extra confirmation; irreversibility confirmation; pallet pause banner; locked-balance hint pointing at unstaking; and post-finalization release tracking that polls the MigrationVault on Base (attestor approvals x/3, released) over plain JSON-RPC without adding an EVM dependency. The nav item is Pendulum-only and the Base vault address is configured via VITE_MIGRATION_VAULT_ADDRESS at deploy time.
The vault refuses a zero recipient, so a burn towards it could never be released (round-2 audit finding C1); the validator now rejects it before submission.
ebma
force-pushed
the
feat/pen-base-migration
branch
from
July 8, 2026 08:00
be6923f to
488ebc2
Compare
Defence-in-depth companion to the pendulum-repo round-6 fix: burning towards the MigrationVault can never be released (the vault refuses a self-transfer), and it is the one destination the pallet cannot reject on-chain since it has no knowledge of Base state. The form now rejects it (case-insensitively) alongside the zero address, with a unit test covering both.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Adds the user-facing PEN → Base migration page to the portal — the frontend companion to the migration stack in
pendulum-chain/pendulum#558.Users connect their Substrate wallet, enter an amount and a Base (EVM) destination, and submit
tokenMigration.migrate(...); after finalization the page tracks the release on the Base MigrationVault (attestor approvals x/3 → released).What's in this PR (2 commits)
src/pages/migration/— the migration page: amount validation (transferable balance, minimum, migrate-all-or-leave-ED), EIP-55 address validation with checksummed preview, zero-address rejection,eth_getCodesmart-contract-destination warning + extra confirmation, irreversibility confirmation, pallet-pause banner, locked-balance hint, and post-finalization release tracking.src/hooks/migration/—useMigrationPallet(extrinsic submission resolving at finality with the emitted nonce) anduseBaseReleaseStatus(polls the vault over plain JSON-RPC).src/helpers/ethereum.ts— EIP-55 checksum, payload-hash mirroring the vault'sabi.encode, minimaleth_call/eth_getCodeclient. No EVM library added — keccak via@polkadot/util-crypto.src/constants/migration.ts— vault address viaVITE_MIGRATION_VAULT_ADDRESS; the page degrades gracefully when unset. Nav item is Pendulum-only.Base branch
Rebased onto
main(the current React 19 mainline). The amount input uses theAmountcomponent's currentcontrol/name(react-hook-formController) API. Verified with a fullyarn build(tsc && vite build) againstmain.Verification
yarn buildclean (tsc + vite); committed through the repo's lint-staged hook.Before deploy
Set
VITE_MIGRATION_VAULT_ADDRESS(and optionallyVITE_BASE_RPC_URL) once the vault is deployed on Base.🤖 Generated with Claude Code