[Proposal] New Recovery Platform for Pre-Hack Wallets

Hello Harmony community,

Since the Horizon Bridge hack on June 23, 2022 (roughly $100M loss), the network has faced depegged bridged assets. Recovery efforts have advanced: we estimate that ~30% of all depegged assets have been burned or removed, including ~45% of depegged USDC. This shows what Harmony can achieve even with limited resources.

Yet one group has been left behind: pre-hack wallets that have waited for a 1:1 redemption that never arrived.

Purpose of this proposal

Focus on people — the forgotten wallets — who trusted Harmony before the hack and still hope for a practical path to recover value.

From public data (chefsoysauce.io), we estimate:

  • About 67,000 affected wallets still active;

  • About 93% held under $100 at the time of the hack;

  • The remaining value is concentrated in higher-balance wallets.

The Proposal: “Fixed Redemption Platform”

A simple, smart-contract-based platform to redeem supported depegged tokens at a fixed 1:1 rate into wONE (or USDC when available), with clear rules and robust governance.

Key features

  • Fixed 1:1 redemption into wONE for supported depegged tokens (e.g., 1USDC, 1USDT), or USDC when the vault has enough balance.

  • Per-wallet daily limit (set by the RMC; e.g., $100/day) with a 24-hour refresh.

  • Funds provided by the RMC (HIP-30v2) with no upfront mass distribution; usage is on-demand as redemptions occur.

  • Monthly on-chain reports showing used and unused funds.

  • Pause and emergency withdrawal: when triggered, unused funds return exclusively to the RMC multisig.

  • Automated operations: no manual processing in daily flow; governance manages parameters and security.

RMC governance defines

  • Daily per-wallet limit and adjustments;

  • Funding allocations to the vault (budget per period);

  • Parameter updates and supported token lists;

  • Use of pause/emergency actions when needed.

Note: This proposal does not replace the Recovery Program (HIP-30v2). It is complementary — a simple, predictable, ongoing path for small holders to recover value, without draining community funds upfront and without relying on an unlikely full re-peg.


Technical overview and security mechanisms

Below is how the RecoveryVault contract works and the protections built in.

How it works

  1. Funding and activation

    • The vault is funded in USDC by the RMC.

    • There is a mandatory 24-hour delay between funding and redemption start.

    • If the vault has no balance, redemptions are unavailable.

  2. Whitelist via Merkle Proof

    • Only authorized pre-hack wallets can redeem.

    • Validation uses a Merkle Proof, enabling off-chain list management and low-cost on-chain verification.

    • Invalid proofs or unauthorized wallets are rejected.

  3. Per-wallet daily limit

    • Each wallet has a daily cap (e.g., $100/day), reset every 24 hours.

    • This prevents concentration of withdrawals and protects liquidity.

  4. Dynamic fee (4 tiers)

    • Each redemption pays a fee.

    • The fee decreases by tier with a wallet’s cumulative usage (e.g., 1.00% base down to 0.25%).

    • This supports sustainability and encourages steady, responsible use.

  5. Receive in wONE or USDC

    • Prefer USDC when the vault has balance.

    • Alternatively wONE, priced via a trusted on-chain oracle, ensuring fair equivalence.

  6. Liquidity windows (rounds)

    • Operations can follow internal liquidity rounds tied to new funding.

    • When the allocated liquidity is exhausted, new redemptions wait for new funding + delay.

Security and controls

  • Governance and pause

    • A governance address can pause the contract if anomalies are detected.

    • When paused, no redemptions are allowed until unpaused.

  • Proofs and strict limits

    • Every call requires a valid Merkle Proof.

    • Daily limits are strictly enforced on-chain.

  • Price oracle

    • For wONE redemptions, amounts use a trusted on-chain oracle (e.g., Band Protocol), reducing manipulation risk.
  • On-chain transparency

    • The contract emits audit events, such as:

      • NewRoundStarted (new liquidity window),

      • RedeemProcessed (redemption completed),

      • BurnToken (when applicable to the depegged token),

      • VaultPaused / VaultUnpaused.

    • This enables public monitoring, dashboards, and accountability.

  • Reentrancy protection

    • Uses OpenZeppelin ReentrancyGuard to block reentrancy attacks.
  • Emergency and fund return

    • In extreme scenarios, emergency withdrawal returns unused funds to the RMC multisig.

Why this matters

  • Provides a continuous recovery path for thousands of small wallets, with predictability and fair limits.

  • Preserves the treasury: on-demand use, controlled budgets, and monthly on-chain reporting.

  • Operational simplicity: clear rules, on-chain automation, and governance focused on parameters and safety.


We need your voice

We invite the community to:

  • Share ideas and parameter tweaks (limits, fee tiers, supported tokens);

  • Raise risks and propose mitigations;

  • Suggest technical and process improvements.

This proposal fits into a broader, collective recovery roadmap, combining technical solutions with the community’s judgement.

Let’s recover not only tokens, but also trust.

10 Likes

Awesome work! I believe this will aggregate to what we already had! All features are already available or it’s a work in progress?

this gives hope @lij

2 Likes

Repo are private while dev. Will turn repo public asap… with all project details and docs

2 Likes

RecoveryVault DEV live preview

Access app dev

RecoveryVault — Technical Documentation

TL;DR for non-technical readers

  • You send a supported token to the Vault and choose to receive wONE or USDC.

  • A small fee is taken from your input token. The rest is burned (or sent to a burn sink), and you receive the corresponding amount of wONE/USDC based on USD prices.

  • There’s a per-wallet daily limit in USD (with 4 decimals of precision) and an optional round delay (a round is a time window configured by the owner).

  • You must be whitelisted. The app shows a non-reverting quote that explains what you’ll get and how much limit you still have.

  • Everything is protected by reentrancy guards, input validations first, transfers later, and oracle checks for price safety.


Overview

RecoveryVault lets users redeem various supported tokens into wONE or USDC at a USD-based rate:

  • Input token is charged a fee (in tokenIn), the net amount is burned (or sent to a sink address), and the user receives the output token (wONE/USDC).

  • Prices:

    • ONE is priced from an oracle (USD/ONE with oracle-provided decimals).

    • USDC is treated as $1.

    • Other supported tokens can optionally use a fixed USD price set by the owner (18-decimals scale).

  • Per-wallet daily limit enforced in USD4 (USD with 4 decimal places).

  • Rounds: the owner starts “rounds” and can enable a 24h delay before a round becomes active. A round also locks a fee tier based on the Vault’s USD balance at round start.

  • Whitelist: users must provide a valid Merkle proof.

  • User experience: quoteRedeem never reverts for normal “not allowed yet” situations; it returns flags and zero values so UIs can explain why a user cannot redeem yet.


Core Concepts

  • Supported input tokens: ERC-20 tokens approved by the owner. Native ONE is supported via wONE; when users send native ONE, the contract wraps it only after all validations pass.

  • Output tokens: wONE or USDC only.

  • Fee tiers (bps): configurable thresholds in whole USD units that select the fee rate. When a round starts, one fee tier is locked for the entire round.

  • USD precision:

    • Internal pricing uses USD18 (USD Ă— 1e18) for precise math.

    • Daily limit / usage uses USD4 (USD Ă— 1e4) for user-facing budgets with decimal tolerance.

  • Rolling 24h window:

    • Each wallet has an anchor periodStart. If 24h elapse, usage resets.

    • If the user hits the daily limit exactly, the wallet is locked until the end of the current 24h window.


Lifecycle & Flow

1) Round set-up (admin)

  • Admin deposits wONE/USDC into the Vault.

  • Admin calls startNewRound(roundId). If delay is enabled, the round starts after ROUND_DELAY (24h).

  • The contract reads the oracle, computes the vault’s USD value, picks and locks a fee tier (roundBps) for this round, and emits RoundFeeLocked and NewRoundStarted.

2) User quote (read-only)

  • Frontend calls:

    • getUserLimit(user) → remaining USD4.

    • quoteRedeem(user, tokenIn, amountIn, redeemIn, proof) → flags + precise amounts.

  • If conditions aren’t met (e.g., not whitelisted, limit exceeded, round inactive), the function returns flags and zeros instead of reverting, so the UI can show “why”.

3) Redeem (state-changing)

  • All validations first:

    • Whitelist check.

    • Round active & vault funded.

    • Token supported / output is wONE or USDC.

    • Daily limit not time-locked.

    • USD valuation passes limit check (using USD4).

    • Fee calculation (bps from locked tier).

    • Output amount computed and liquidity confirmed.

  • Only then, funds move:

    • If tokenIn == address(0): assert msg.value == amountIn, wrap to wONE.

    • Else: pull ERC-20 via safeTransferFrom.

  • Post-move:

    • Fee → devWallet.

    • Net → _burnOrSink(tokenIn, netIn) (try burn; fallback to sink).

    • Output token → user.

    • Update usage / locks; emit BurnToken and RedeemProcessed.


Math, Units & Rounding

  • Oracle: returns (price, decimals) for USD/ONE.

  • Scales:

    • USD18: 1.00 USD = 1e18. Used for calculations (usdIn18, usdNet18).

    • USD4: 1.0000 USD = 1e4. Used for daily limit configuration and accounting.

    • fixedUsdPrice[token]: USD18 per 1 token (18-decimals).

  • Rounding:

    • USD valuations for policy (limit/tiers) use floor conversion from USD18 → USD4.

    • Token output is derived from USD18 math and scaled to token decimals; result is floored by integer division.

  • Decimals caching:

    • WONE_DECIMALS and USDC_DECIMALS are immutables loaded in the constructor to save gas.

Public Interface

Events

  • BurnToken(address tokenIn, uint256 amountIn, address outputToken, uint256 amountOut) Emitted on each redeem; outputToken is the redeem token (wONE/USDC).

  • RedeemProcessed(address user, address tokenIn, uint256 amountIn, uint256 amountOut) Convenience event for indexers/analytics.

  • NewRoundStarted(uint256 roundId, uint256 woneBalance, uint256 usdcBalance, uint256 startTime)

  • RoundFeeLocked(uint256 roundId, uint16 bps, uint256 basisUsd) basisUsd is the whole USD basis used to select the tier at round start.

  • VaultPaused(bool isPaused)

  • SupportedTokenUpdated(address token, bool allowed)

  • FeeTiersUpdated(uint256[] thresholds, uint16[] bps)

  • RoundDelayToggled(bool enabled)


User Functions

redeem(address tokenIn, uint256 amountIn, address redeemIn, bytes32[] proof)

Redeems amountIn of tokenIn into redeemIn (wONE or USDC). If tokenIn == address(0), the caller must send msg.value == amountIn (native ONE), which is wrapped to wONE after validations pass.

  • Reverts on:

    • Not whitelisted / round not active / vault empty.

    • Input token not supported / invalid output token.

    • Oracle invalid.

    • Exceeds daily limit (USD4).

    • Insufficient output liquidity.


View Functions

quoteRedeem(address user, address tokenIn, uint256 amountIn, address redeemIn, bytes32[] proof) → ( … )

Returns a non-reverting quote + status flags:

  • whitelisted — user is on the Merkle allowlist.

  • roundIsActive — current round has started and vault is not locked.

  • feeAmountInTokenIn — fee in the input token units.

  • burnAmountInTokenIn — net input (what will be burned/sent to sink).

  • userLimitUsdBefore / userLimitUsdAfter — remaining USD4 before/after this request (0 if blocked).

  • usdValueIn — input USD amount used for policy (USD4).

  • tokenInDecimals / redeemInDecimals

  • oraclePrice / oracleDecimals

  • amountOutRedeemToken — output token units to receive.

If the action is blocked (e.g., over limit, time-locked), the function returns zeros for the numeric fields so UIs can display the reason/timer, not a revert.

getUserLimit(address wallet) → uint256 remainingUSD4

Remaining per-wallet daily limit in USD4.

getRoundInfo() → (roundId, startTime, isActive, paused, limitUsd4, delayEnabled, roundFeeBps, roundFeeBasisUsd)

Round and configuration snapshot. limitUsd4 is the daily limit in USD4.

Other views

  • getVaultBalances() → (woneBalance, usdcBalance)

  • getSupportedTokens() → address[]

  • getFeeTiers() → (uint256[] thresholds, uint16[] bps)

  • getLastRedeemTimestamp(address user) → uint256


Admin Functions

  • setMerkleRoot(bytes32 root)

  • setSupportedToken(address token, bool allowed)

  • setLocked(bool status) — global pause.

  • setDailyLimit(uint256 usd4) — USD4 (e.g., $100.1234 → 1_001_234).

  • setOracle(address oracle) — must expose latestPrice() → (int256 price, uint8 decimals) for USD/ONE.

  • setDevWallet(address wallet)

  • setRmcWallet(address wallet)

  • setFeeTiers(uint256[] thresholdsUSD, uint16[] bps) — bps.length = thresholds.length + 1. Thresholds are whole USD (no decimals).

  • setFixedUsdPrice(address token, uint256 usd18PerToken) — 18-dec USD per 1 token.

  • setRoundDelayEnabled(bool enabled) — toggles 24h round delay.

  • withdrawFunds(address token) — only wONE or USDC.

  • startNewRound(uint256 roundId) — roundId must strictly increase.


Pricing & Limits

  • ONE: valued via oracle (price, decimals) as USD/ONE.

  • USDC: 1 USDC = $1.

  • Other tokens: if fixedUsdPrice[token] > 0, use that USD18 price; otherwise the redemption is unsupported (reverts).

  • Fee selection:

    • If a round is active with a locked fee, use that roundBps.

    • Otherwise, the fee is selected by current USD4/whole USD thresholds.

  • Daily limit:

    • Configured and accounted in USD4.

    • Enforced on input USD value (usdIn18 → usd4).

    • Rolling 24h behavior with lock when the limit is exactly reached.


Security Considerations

  • Validation-first design: all checks (supported token, round state, oracle reading, limit window, fee, liquidity) run before any transfer or wrapping. This prevents “funds stuck in vault” on later reverts.

  • Reentrancy: nonReentrant guard on state-changing redeem.

  • Whitelist: Merkle proof validated on both quoteRedeem (for UX) and redeem.

  • Oracle: latestPrice() must be positive; otherwise the call reverts.

  • Burn or sink: _burnOrSink first attempts IERC20Burnable(token).burn(amount) in try/catch; if it fails, it safely transfers to a known burn sink.

  • Native ONE: wrapping only happens after validations; msg.value must equal amountIn.

  • Owner withdrawals: restricted to wONE/USDC only; no arbitrary tokens.

  • Cached decimals: WONE_DECIMALS / USDC_DECIMALS cached as immutable to reduce external calls.


Integration Guide (dApp / SDK)

Quoting flow (frontend):

  1. Read supported tokens, vault balances, and round info.

  2. Check user whitelist (Merkle proof).

  3. Call getUserLimit(user) (USD4) for budget display.

  4. Call quoteRedeem(user, tokenIn, amountIn, redeemIn, proof).

    • If blocked: show roundIsActive, whitelisted, and any time left until unlock.

    • If allowed: display fee / net / expected output.

Execution flow:

  1. For ERC-20 inputs: ensure allowance for the Vault.

  2. For native ONE inputs: set tokenIn = address(0), send msg.value = amountIn.

  3. Call redeem(tokenIn, amountIn, redeemIn, proof).

Ethers example (ERC-20 input):

const v = new ethers.Contract(vaultAddr, VaultABI, signer);
const proof = [...];                 // Merkle proof bytes32[]
const tokenIn = SOME_ERC20;
const amountIn = ethers.parseUnits("123.45", inDecimals);
const redeemIn = USDC;               // or wONE

// 1) Optional: non-reverting quote
const q = await v.quoteRedeem(user, tokenIn, amountIn, redeemIn, proof);

// 2) Approve if needed
await erc20.connect(signer).approve(vaultAddr, amountIn);

// 3) Redeem
const tx = await v.redeem(tokenIn, amountIn, redeemIn, proof);
await tx.wait();

Ethers example (native ONE):

const tokenIn = ethers.ZeroAddress;           // native
const amountIn = ethers.parseEther("50");
const redeemIn = wONE; // or USDC

const tx = await v.redeem(tokenIn, amountIn, redeemIn, proof, { value: amountIn });
await tx.wait();


Glossary

  • USD18: USD value scaled by 1e18.

  • USD4: USD value scaled by 1e4 (four decimals; improves UX tolerance).

  • Round: a configured period where a single fee tier is locked and (optionally) starts after a delay.

  • wONE: wrapped ONE (ERC-20).

  • Burn sink: a known address where tokens are irretrievably sent if burn() is not available.


License: MIT

Audits: Contract verified on explorer and its opensource to be audited by everyONE.

Contacts: Mauricio F. | Think in Coin channel

4 Likes

Looks good. Would love to see it in action.

1 Like

What is currently total value of Harmony One enterprise in US dollars please?

Not sure if you’re asking for, either price x circulating supply or do you think Harmony would actually respond as to what they have in the treasury?

Either way…

3 Likes