Skip to content

feat: refund-deposit script for manual EVM deposit forwarding#3671

Open
TaprootFreak wants to merge 2 commits intodevelopfrom
feature/refund-deposit
Open

feat: refund-deposit script for manual EVM deposit forwarding#3671
TaprootFreak wants to merge 2 commits intodevelopfrom
feature/refund-deposit

Conversation

@TaprootFreak
Copy link
Copy Markdown
Collaborator

Summary

  • New scripts/refund-deposit.js lets an operator manually forward funds from a DFX EVM deposit address to any recipient (e.g. back to the original sender of a stuck deposit)
  • Bootstraps the NestJS application context and uses the existing service layer:
    • BlockchainRegistryService.getEvmClient(blockchain) for the right client
    • EvmClient.sendNativeCoinFromAccount / sendTokenFromAccount for the actual send
    • EvmUtil.createWallet + GetConfig().blockchain.evm.walletAccount for wallet derivation
    • AssetService.getNativeAsset / getAssetByChainId for asset entities
  • Single source of truth: chain config, gas handling, RPC URLs all come from the existing codebase — no duplication

Behaviour

  • Default is dry-run; --execute is required to broadcast
  • Supports all configured EVM chains: ethereum, optimism, arbitrum, polygon, base, bsc, gnosis, citrea
  • Native or ERC20 transfers, with --amount all to sweep (native: balance − fee; token: full balance)
  • Recipient via --to <addr> or --to-sender-of <txHash> (looks up tx.from)
  • Heavy NestJS / dist requires deferred until after arg validation, so --help and input errors stay sub-100ms

Use case

Stuck deposits (unsupported asset, failed routing) sit on the deposit address with no automated path back. This gives operations a safe, auditable way to forward them without exporting the private key.

Usage

node scripts/refund-deposit.js \
  --chain citrea \
  --account-index 4486 \
  --asset native \
  --amount all \
  --to-sender-of 0x8b3bbec3... \
  --execute

Run from inside the api container on prd (Azure App Service via az webapp ssh) — the seed env var lives there.

Test plan

  • node scripts/refund-deposit.js --help prints usage instantly (no NestJS bootstrap)
  • Arg validation covers: unknown chain, missing seed, conflicting --to/--to-sender-of, invalid recipient address
  • Dry-run on a known stuck deposit reports correct deposit address (matches deposit.address in DB), correct recipient (sender of referenced tx), correct balance and gas estimate
  • After dry-run review, --execute broadcasts and the tx confirms on-chain

Bootstraps the NestJS application context and uses the existing
BlockchainRegistryService + EvmClient to derive the deposit wallet
(EVM_DEPOSIT_SEED + accountIndex) and forward a chosen asset to a chosen
recipient. Recipient can be derived from a deposit transaction (sender of
that tx).

Default behaviour is dry-run; --execute broadcasts. Supports all
configured EVM chains. Sweep via --amount all subtracts the estimated fee
for native, full balance for tokens.
@TaprootFreak TaprootFreak marked this pull request as ready for review May 3, 2026 18:14
@TaprootFreak TaprootFreak requested a review from davidleomay as a code owner May 3, 2026 18:14
Address review findings — full professional pass:

* Extract pure logic (CLI parsing, validation, sweep math, chain config)
  into src/scripts/refund-deposit.util.ts for testability and consistency
  with the rest of the TypeScript codebase.
* Add comprehensive unit tests (src/scripts/__tests__/refund-deposit.util.spec.ts,
  20 cases) covering parseArgs, validateArgs, computeNativeSweepAmount and
  the chain/explorer mappings.

Bug fixes:

* Token-lookup case-sensitivity: normalise contract addresses to lowercase
  before getAssetByChainId — asset.chainId is stored lowercase in the DB
  while operators typically paste a checksum address from a block-explorer.
* Native sweep estimateGas: probe with value=0 instead of value=balance,
  so providers that reject estimateGas with insufficient-funds-for-value
  don't fail the dry-run.
* Native sweep safety buffer: reserve fee * SWEEP_FEE_SAFETY_FACTOR (1.05)
  instead of bare fee, so small gas-price drift between dry-run and
  execute does not cause an in-flight "insufficient funds" failure.
* Token transfer gas check: estimate the actual transfer-call fee and
  reject the dry-run if it exceeds the native balance — previously a
  zero-balance native account would dry-run green and fail at execute.
* parseArgs: reject unknown flags and positional arguments fail-loud
  instead of silently ignoring them (catches typos like '--chian').
* Drop pauschales /* eslint-disable */; scripts/*.js are already ignored
  by eslint config.
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