Skip to content

Recycle fees via chain ext with immutable custodial fallback#48

Merged
entrius merged 2 commits into
testfrom
feat/recycle-fallback
May 1, 2026
Merged

Recycle fees via chain ext with immutable custodial fallback#48
entrius merged 2 commits into
testfrom
feat/recycle-fallback

Conversation

@LandynDev

@LandynDev LandynDev commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Wires the AddStakeRecycleV1 chain extension (subtensor PR #2560, function_id=18) into recycle_fees behind an owner-flipped one-way latch.

  • Pre-latch: fees go to an immutable custodial fallback (recycle_address, set once in the constructor — no setter). The off-chain fee-recycler service in alw-utils picks them up and stakes them back to the subnet.
  • Post-latch: fees dispatch through the chain extension to (staking_hotkey, netuid). Subtensor stakes-and-burns directly from the contract via add_stake_recycle.
  • Latch flip: owner-only enable_chain_ext(), exposed as alw admin enable-chain-ext. Reverts if already set; emits ChainExtensionLatched { at_block } once.

Why owner-flip and not auto-probe-on-first-success

pallet-contracts traps the entire contract call when a chain extension returns Err(DispatchError) — which is exactly what subtensor returns for unknown function_ids on networks where PR #2560 hasn't landed yet (mainnet today). An auto-probing recycle_fees would revert before reaching its fallback branch, so fees would pile up in accumulated_fees until #2560 lands. Owner-flip sidesteps that entirely.

Verified mappings against subtensor PR #2560

  • extension id 0x1000 matches subtensor-proxy registration
  • function_id 18 matches FunctionId::AddStakeRecycleV1
  • param order (AccountId, u16, u64) matches (AccountId, NetUid, TaoBalance) — both balance/netuid types are repr(transparent) newtypes with SCALE-equivalent encoding
  • return u64 matches encoded alpha amount
  • status code 0 = Success matches Output::Success

Contract

  • New storage: staking_hotkey, netuid, chain_ext_enabled (constructor-set; only chain_ext_enabled mutates, via enable_chain_ext).
  • recycle_fees stays permissionless; branches on chain_ext_enabled — chain ext only or env.transfer to recycle_address.
  • New enable_chain_ext (owner-only, one-way).
  • set_recycle_address removed.
  • New getters: get_staking_hotkey, get_netuid, get_chain_ext_enabled.
  • FeesRecycled gains via_chain_ext: bool; new ChainExtensionLatched { at_block } event.

Python / CLI

  • contract_client.py: drop set_recycle_address; add the new getters, enable_chain_ext writer, u16 read primitive.
  • admin.py: drop set-recycle-address; add enable-chain-ext (one-way confirm); recycle-fees UX shows pre/post-latch destination.
  • view.py: alw view contract shows the recycle path (custodial vs chain ext) and the latch target.

Companion PR

alw-utils#45 wires staking_hotkey + netuid into the contract deployer, restores the off-chain fee-recycler for the pre-latch path, and renames --wallet-name--owner-hotkey. Hard-blocked on this one.

Test plan

  • cargo contract build --release clean
  • pytest tests/ passes (387 cases)
  • cd alw-utils/dev-environment && ./down.sh --force && ./up.sh --chains btc brings up the stack against the rebased deployer
  • ./tests/run.sh --chains btc --suite 02 passes
  • alw view contract shows pre-latch path with custodial address + latch target row
  • alw admin recycle-fees (pre-latch) transfers to recycler wallet; recycler service stakes back
  • alw admin enable-chain-ext flips latch; second call errors out
  • alw admin recycle-fees (post-latch) routes through chain extension; via_chain_ext = true in event

@anderdc anderdc added the feature Net-new functionality label Apr 21, 2026
@LandynDev LandynDev force-pushed the feat/recycle-fallback branch from 6bb73d5 to e05a67c Compare May 1, 2026 19:24
@LandynDev LandynDev changed the base branch from main to test May 1, 2026 19:24
Wires the AddStakeRecycleV1 chain extension (subtensor PR #2560,
function_id=18) into recycle_fees behind an owner-flipped one-way
latch. Pre-latch: fees go to an immutable custodial fallback
(constructor-only, no setter). Post-latch: fees stake-and-recycle
through the chain extension at (staking_hotkey, netuid). Owner flips
the latch once subtensor PR #2560 is confirmed live on the deploy
network.

Auto-probe-on-first-success was rejected: pallet-contracts traps the
entire call when a chain extension returns Err(DispatchError) (e.g.,
unknown function_id pre-#2560 mainnet), so the fallback branch would
be unreachable and fees would pile up until the chain extension
landed. Owner-flip sidesteps that.

Contract:
- New storage: staking_hotkey, netuid, chain_ext_enabled (set in
  constructor; only chain_ext_enabled mutates, via enable_chain_ext).
- recycle_fees stays permissionless; branch on chain_ext_enabled —
  chain ext only or env.transfer to recycle_address.
- New enable_chain_ext (owner-only, one-way; reverts if already set).
- set_recycle_address removed.
- New getters: get_staking_hotkey, get_netuid, get_chain_ext_enabled.
- FeesRecycled gains via_chain_ext: bool.
- New ChainExtensionLatched event with at_block.

Python:
- contract_client.py: drop set_recycle_address; add the three getters,
  enable_chain_ext writer, and a u16 read primitive.
- admin.py: drop set-recycle-address; add enable-chain-ext (owner-only,
  one-way confirm); recycle-fees UX shows pre/post-latch destination.
- Metadata regenerated; SubtensorExtension (id=0x1000) wired in env.
@LandynDev LandynDev force-pushed the feat/recycle-fallback branch from d346d26 to 513b26c Compare May 1, 2026 19:56
@LandynDev LandynDev marked this pull request as ready for review May 1, 2026 20:01
@entrius entrius merged commit 0ae2c7d into test May 1, 2026
@anderdc anderdc deleted the feat/recycle-fallback branch June 8, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants