The reclaim funding page is the rescuer side of the recovery flow. It lets
someone who has already swept funds away from a compromised Cardano credential
lock those funds at the project's ReclaimBase script address with an inline
datum identifying the compromised payment key credential.
The intended route is:
/reclaim
The page is deliberately separate from the ownership proof page. A rescuer does not need the original owner's recovery phrase. The original owner later proves, with the proof tool, that their master private key derives to the compromised payment key credential recorded in the datum.
The rescuer controls the wallet currently holding the swept funds. The rescuer
uses /reclaim to deposit those funds into the reclaim script. The rescuer must
know the compromised payment key credential that should be allowed to reclaim
the funds later.
The original owner controls the master private key or recovery phrase that derives to the compromised payment key credential. The owner does not use this funding page to prove ownership. They later use the ownership proof flow and the reclaim transaction flow to spend the script output.
The operator publishes the reclaim deployment manifest. The manifest identifies the network, reclaim base address, reclaim base script hash, global reclaim credential, parameter NFT, contract version, and verifier key hash.
The page helps the rescuer build and submit a Cardano transaction with a script output like:
address = deployed ReclaimBase address
value = selected rescued ADA and native tokens
datum = ReclaimBaseDatum(compromised payment key hash)
The inline datum is the critical part. It tells the reclaim contracts which payment key credential the future proof must match.
Transaction construction is backend-owned and uses Lucid Evolution. The browser connects to the rescuer's wallet, reads the CIP-30 change address and used payment addresses, sends those public addresses plus the selected token bundle to the backend, asks the backend to build completed unsigned transaction CBOR, then asks the wallet to sign it. Private keys and seed phrases never go to the backend.
The current implementation lives at apps/ownership-proof-web/app/reclaim and
uses these web-owned API routes:
GET /reclaim-api/deploymentPOST /reclaim-api/wallet-assetsPOST /reclaim-api/buildPOST /reclaim-api/submit
The React flow is components/ReclaimFundingFlow.tsx; shared /reclaim and
/claim shell primitives are in components/ReclaimShell.tsx. Request types
and browser validation live in lib/reclaim, while provider queries,
manifest/config loading, review binding, transaction construction, inspection,
and submission live in lib/reclaim-server.
The page does not:
- ask for a recovery phrase;
- ask for a master private key;
- generate a zero-knowledge proof;
- verify the original owner's proof;
- reclaim funds back to the owner;
- prove that the rescuer was the original owner;
- let users choose an arbitrary script address.
- The page loads the reclaim deployment manifest.
- The rescuer connects a CIP-30 Cardano wallet.
- The page checks that the wallet network matches the deployment network.
- The rescuer enters the compromised payment key credential.
- The page validates that the credential is exactly 28 bytes, displayed as 56 hex characters.
- The rescuer chooses ADA and native tokens, or UTxOs containing those assets, to lock.
- The page sends the CIP-30 change address, CIP-30 used payment addresses, selected token bundle, network id, and compromised credential to the backend builder.
- The backend validates those public addresses, queries UTxOs across the
supplied address set, deduplicates the resulting inputs, initializes Lucid
Evolution, selects the address-only wallet with
lucid.selectWallet.fromAddress(changeAddress, utxos), and completes an unsigned transaction that sends the selected value to the deployedReclaimBaseaddress with an inlineReclaimBaseDatum. - The page shows the transaction review and asks the wallet to sign.
- The page sends the unsigned transaction CBOR and wallet witness set to
/reclaim-api/submit, where the backend assembles and submits the signed transaction through the configured provider. The submit route can also accept fully signed transaction CBOR. - The page shows the transaction hash, protected value, and datum credential used.
The reclaim datum is:
data ReclaimBaseDatum = ReclaimBaseDatum
{ reclaimPaymentKeyHash :: BuiltinByteString
}reclaimPaymentKeyHash must be the raw 28-byte Cardano payment key hash for
the compromised credential.
It is not:
- a full Cardano address;
- a bech32 string;
- a stake key hash;
- a script hash;
- a wallet id;
- a recovery phrase hash.
If the datum is wrong, the funds may be locked for the wrong claimant. The page must show the normalized credential before the rescuer signs.
The ReclaimBase script only allows its UTxOs to be spent in a transaction that
also invokes the configured ReclaimGlobalV2 rewarding script. ReclaimGlobalV2
then checks proofs for the matching ReclaimBase inputs.
For each matching input, the global reclaim script:
- reads the
ReclaimBaseDatum; - checks that the datum payment key hash is 28 bytes;
- verifies a destination-bound ownership proof for that payment key hash;
- checks that the protected input value is paid to the proof-bound destination output.
This means the rescuer can lock funds for a compromised credential without knowing the owner's recovery phrase, and the owner can later reclaim only by proving derivation to that credential and binding the reclaim to the destination address used in the spend.
The page needs a browser wallet that supports CIP-30. The wallet must provide:
- connection approval;
- network id;
getChangeAddress()for Lucid change;getUsedAddresses()for candidate funded payment addresses;- transaction signing;
- transaction submission, or signed transaction handoff to the backend submit provider.
The backend builder receives only public transaction intent such as wallet change address, wallet payment addresses, selected ADA/native-token quantities, network id, and the compromised credential. It queries UTxOs for those addresses through its configured provider and uses Lucid Evolution's address-only wallet selection to balance and complete the unsigned transaction. It must not receive seed phrases, private keys, wallet passwords, or signed witnesses before the user approves signing in their wallet.
When no wallet is injected, the page should keep the connect action available
and explain which window.cardano providers were detected.
The current browser-wallet address handling is CIP-30-only. The page reads both
getChangeAddress() and getUsedAddresses(), accepts the standard CIP-30 hex
address payload or a bech32 address, converts each locally to addr/addr_test,
and sends changeAddress plus walletAddresses[] to the backend. The page does
not call getUnusedAddresses(), because unused addresses are not evidence of
spendable funds. The UI does not expose a wallet-address input field; the
rescuer does not manually type the funding or change address.
The page is disabled unless the backend has a pinned reclaim deployment. Configure these environment variables:
RECLAIM_NETWORK(Mainnet,Preprod, orPreview)RECLAIM_BASE_ADDRESSRECLAIM_BASE_SCRIPT_HASHRECLAIM_GLOBAL_CREDENTIALRECLAIM_GLOBAL_SCRIPT_HASHRECLAIM_PARAMS_CURRENCY_SYMBOLRECLAIM_PARAMS_TOKEN_NAMERECLAIM_VERIFIER_VK_HASH(Cardano wire-format VK hash embedded on-chain)RECLAIM_PROOF_VK_HASH(native gnark VK hash used by provers)RECLAIM_PROOF_CARDANO_VK_BLAKE2B256(must equalRECLAIM_VERIFIER_VK_HASH)RECLAIM_GLOBAL_BATCH_TRANSCRIPT_VK_HASH(the same Cardano hash used by the V2 transcript preflight)RECLAIM_CONTRACT_VERSIONRECLAIM_SOURCE_COMMIT
Provider configuration:
RECLAIM_PROVIDER=koiosuses public Koios URLs by network unlessRECLAIM_KOIOS_URLis set.RECLAIM_KOIOS_TOKENis optional.RECLAIM_PROVIDER=blockfrostuses network-specific Blockfrost URLs unlessRECLAIM_BLOCKFROST_URLis set.RECLAIM_BLOCKFROST_PROJECT_IDis required.
The wallet network must match the reclaim deployment manifest. A mainnet wallet must not submit to a preprod reclaim script, and a preprod wallet must not submit to a mainnet reclaim script.
The page should show:
- deployment network;
- wallet network;
- reclaim base address;
- reclaim base script hash;
- verifier key hash;
- contract version.
Before signing, the rescuer should see:
- the destination script address;
- the compromised credential that will be written into the datum;
- the ADA and native tokens being locked;
- the estimated fee;
- the network;
- any native assets included;
- the wallet change address and queried funding-address count;
- the deployment id or contract version.
The transaction should be rebuilt whenever the credential, selected value, wallet state, or deployment manifest changes.
The current review shows destination, credential datum, datum CBOR, unsigned transaction hash, and the selected multi-asset bundle. A production hardening pass should add explicit estimated-fee display and a signed-transaction inspect gate before enabling mainnet deposits.
The page protects against accidental deposits to the wrong script or wrong datum by pinning deployment data, validating credential shape, checking network ids, and showing a final review before signing.
The page does not protect against:
- a rescuer entering the wrong compromised credential;
- a malicious or compromised browser wallet;
- a malicious hosted frontend deployment;
- provider outages or stale chain data;
- future owner inability to prove the recorded credential.
The page must not claim that the deposit is recoverable unless the datum credential is correct and the reclaim contracts are deployed as shown.
Before enabling deposits:
- Publish a valid reclaim deployment manifest.
- Verify the
ReclaimBaseaddress from the manifest matches the deployed script parameters. - Verify
ReclaimGlobalV2embeds the published Cardano wire-format verifier key hash, whileproof.vk_hashindependently matches the native proof-helper key bundle. - Run a preprod deposit from the page.
- Confirm the output has inline
ReclaimBaseDatumwith the expected payment key hash. - Run a preprod reclaim spend through
ReclaimGlobalV2. - Save tx hashes and manifest version in release notes.
Install or enable a CIP-30 Cardano wallet in the browser. The page should list which wallet providers it can see.
Switch the wallet to the network shown in the deployment manifest. Do not bypass this check.
Use the 56-hex payment key credential. Do not paste a full address unless the page explicitly supports local address-to-credential extraction and shows the extracted credential.
Check that the wallet has enough ADA for the script output min-ADA requirement and the transaction fee. If native assets are selected, the required min-ADA may be higher. Also check that the selected token quantities still exist in the address UTxOs queried by the backend builder.
Check the on-chain datum first. If the datum credential does not match a payment key credential derivable from the owner's master private key, the proof will not authorize reclaim for that owner.
Reconnect the wallet and confirm that the wallet exposes a CIP-30 change or used payment address. The page refuses to build a transaction without a usable CIP-30-provided payment address.
The six product steps are Deployment, Funding wallet, Compromised credential,
Assets, Review transaction, and Submit. The component keeps reviewed
transactions invalidated when wallet, credential, or selected asset state
changes; tests assert unchanged build/submit payloads and partial signing via
signTx(txCbor, true).
pnpm --dir apps/ownership-proof-web test components/ReclaimFundingFlow.test.tsx
pnpm --dir apps/ownership-proof-web typecheckDeterministic rendering states are available outside production, or explicitly
with NEXT_PUBLIC_LOCK_FUNDS_UI_FIXTURE=1, at
/reclaim?fixtureState=<state>. Capture desktop and mobile review artifacts
with:
pnpm --dir apps/ownership-proof-web visual:lock-fundsOutput is under output/playwright/lock-funds/. Review mode checks that every
state renders and records visual differences; visual:lock-funds:strict adds
the generated-reference pixel threshold.