Skip to content

Add PEN → Base migration page - #655

Open
ebma wants to merge 3 commits into
mainfrom
feat/pen-base-migration
Open

Add PEN → Base migration page#655
ebma wants to merge 3 commits into
mainfrom
feat/pen-base-migration

Conversation

@ebma

@ebma ebma commented Jul 8, 2026

Copy link
Copy Markdown
Member

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_getCode smart-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) and useBaseReleaseStatus (polls the vault over plain JSON-RPC).
  • src/helpers/ethereum.ts — EIP-55 checksum, payload-hash mirroring the vault's abi.encode, minimal eth_call/eth_getCode client. No EVM library added — keccak via @polkadot/util-crypto.
  • src/constants/migration.ts — vault address via VITE_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 the Amount component's current control/name (react-hook-form Controller) API. Verified with a full yarn build (tsc && vite build) against main.

Verification

yarn build clean (tsc + vite); committed through the repo's lint-staged hook.

Before deploy

Set VITE_MIGRATION_VAULT_ADDRESS (and optionally VITE_BASE_RPC_URL) once the vault is deployed on Base.

🤖 Generated with Claude Code

@ebma
ebma changed the base branch from fix-issues-with-new-ss58format to main July 8, 2026 07:49
@ebma
ebma marked this pull request as ready for review July 8, 2026 07:49
@netlify

netlify Bot commented Jul 8, 2026

Copy link
Copy Markdown

Deploy Preview for rococo-souffle-a625f5 ready!

Name Link
🔨 Latest commit 796142b
🔍 Latest deploy log https://app.netlify.com/projects/rococo-souffle-a625f5/deploys/6a4f68aeb9b82300085ace7b
😎 Deploy Preview https://deploy-preview-655--rococo-souffle-a625f5.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 thread src/helpers/ethereum.ts
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;
}
ebma added 2 commits July 8, 2026 09:59
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
ebma force-pushed the feat/pen-base-migration branch from be6923f to 488ebc2 Compare July 8, 2026 08:00
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.
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.

2 participants