Skip to Content
Bot Sessions (DelegationHub)

Bot Sessions (DelegationHub)

DelegationHub is the onchain session registry for opt-in bot delegation. It lets users grant time-limited, permission-scoped sessions to bot addresses, which protocol contracts verify on every delegated call. In the CLAIM stream, this is how bots automate takeovers, harvesting, Furnace entries, and lock maintenance on behalf of users.

TL;DR: Users call setSession(delegate, perms, expiry) or sign gasless via EIP-712. Bots are checked with isAuthorized(user, delegate, requiredPerms). 18 permission bits covering Crown, Harvest, Furnace, VeLock, and Config actions. Sessions should be short-lived with minimal perms.

A session is:

  • user (the identity being represented)
  • delegate (the bot / executor)
  • perms (permission bitmask)
  • expiry (unix seconds)

Sessions are verified by protocol contracts onchain via isAuthorized(...).

Contract + wiring

Source:

  • src/DelegationHub.sol
  • src/interfaces/IDelegationHub.sol
  • src/lib/DelegationPermissions.sol (canonical bit positions)

Wiring (v1.0.0+):

Every delegated path fails closed on bundle drift (see Wiring safety model). No contract trusts a raw delegationHub() read in isolation — each resolves the hub through cross-checks against the live bundle:

  • MineCore / Furnace: each stores delegationHub; authorization requires the other contract to agree on the same hub and the same CLAIM / ve roots.
  • ClaimAllHelper: resolves the hub from MineCore / ShareholderRoyalties / Furnace; rejects split-brain MineCore.furnace() vs ShareholderRoyalties.furnace() wiring.
  • VeClaimNFT: safe-maintenance wrappers resolve the hub through Furnace + MarketRouter + MineCore.
  • ShareholderRoyalties / LpStakingVault7D: resolve through Furnace + MineCore cross-checks.
  • MarketRouter: does not use DelegationHub in v1.0.0 (market actions use standard approvals, not delegation).

Wiring rule: set the hub address before going live. The setter is onlyOwner, governed through the live timelock owner path.

Read surface

MethodNotes
getSession(user, delegate) -> (perms, expiry)Raw stored session data
isAuthorized(user, delegate, requiredPerms) -> boolChecks expiry and bitmask
nonces(user) -> uint256EIP-712 nonce for gasless set-by-sig. Also increments on direct setSession(...) / revokeSession(...) to invalidate previously prepared signatures.

Write surface

MethodCallerNotes
setSession(delegate, perms, expiry)userStores session; overwrites prior
revokeSession(delegate)userClears perms + expiry
setSessionBySig(user, delegate, perms, expiry, nonce, deadline, sig)anyoneGasless; validates signature + nonce + deadline

Signature model:

  • EIP-712 domain:
    • name: ClaimRush DelegationHub
    • version: 1
  • Uses OpenZeppelin SignatureChecker (supports EOAs + EIP-1271 smart wallets).
  • Direct onchain setSession(...) and revokeSession(...) also increment nonces(user).
    • Integrators MUST re-read the current nonce after any direct session change.
    • Any previously prepared SetSession signature becomes invalid once a direct update/revoke lands onchain.

Consumers in protocol contracts

Important:

  • protocol consumers do not all trust a raw stored delegationHub pointer in isolation
  • MineCore resolves the canonical hub through the live Furnace + MineCore bundle before checking session bits
  • ClaimAllHelper, VeClaimNFT, ShareholderRoyalties, and LpStakingVault7D apply similar consumer-specific live wiring checks

MineCore: delegated takeover

MineCore.takeoverFor(newKing, maxPrice):

  • msg.sender pays ETH and becomes the executor
  • newKing becomes the King identity
  • requires DelegationHub.isAuthorized(newKing, msg.sender, P_TAKEOVER_FOR) against the canonically resolved hub

Default routing for the new reign:

  • ethRecipient = msg.sender (bot)
  • claimRecipient = newKing (user)

Optional:

  • if the session also grants P_ROUTE_REIGN_CLAIM_TO_CALLER, MineCore sets:
    • claimRecipient = msg.sender

Mid-reign routing update:

  • MineCore.setCurrentReignRecipients(ethRecipient, claimRecipient)
    • MineCore again resolves the canonical hub through the live Furnace + MineCore bundle before checking any session bits
    • callable by the active King identity
    • or an authorized delegate with:
      • for ethRecipient: P_SET_REIGN_ETH_RECIPIENT or P_SET_REIGN_ETH_RECIPIENT_TO_CALLER_ONLY
      • for claimRecipient: P_SET_REIGN_CLAIM_RECIPIENT or P_SET_REIGN_CLAIM_RECIPIENT_TO_USER_ONLY

ClaimAllHelper: delegated harvest + withdraw

ClaimAllHelper provides delegation-gated wrappers:

  • claimShareholderForUser(user, ...) requires P_CLAIM_SHAREHOLDER_FOR
  • withdrawKingBalanceForUser(user) requires P_WITHDRAW_KING_BUCKET_FOR
  • claimAllFor(user, ...) requires P_CLAIM_ALL_FOR

Furnace: delegated entry (bot pays, user receives lock)

Delegated entry points:

  • enterWithEthFor(user, ...) requires P_FURNACE_ENTER_ETH_FOR
  • enterWithClaimFromCallerFor(user, ...) requires P_FURNACE_ENTER_CLAIM_FOR
  • enterWithTokenFromCallerFor(user, ...) requires P_FURNACE_ENTER_TOKEN_FOR

VeClaimNFT: delegated lock maintenance (safe, non-custodial)

Delegated lock maintenance entry points:

  • Furnace.extendWithBonusFor(user, tokenId, durationSeconds, minBonusOut) requires P_VE_EXTEND_LOCK_FOR
  • mergeLocksForUser(user, fromTokenId, intoTokenId) requires P_VE_MERGE_LOCKS_FOR
  • unlockExpiredForUser(user, tokenId) requires P_VE_UNLOCK_EXPIRED_FOR

Non-custodial rules enforced:

  • no ERC20 spend from the user
  • unlock returns CLAIM to user (never to the delegate)
  • merge/extend require the lock(s) are owned by user

Settings/config: delegated config setters (safe, non-custodial)

Delegated config entry points:

  • MineCore.setKingAutoLockConfigForUser(user, ...) requires P_SET_KING_AUTO_LOCK_CONFIG_FOR
  • ShareholderRoyalties.setAutoCompoundConfigForUser(user, ...) requires P_SET_SHAREHOLDER_AUTOCOMPOUND_CONFIG_FOR
    • The callee resolves the canonical hub from the live Furnace / MineCore pair and also rejects split-brain Baron bundles where MarketRouter / MineCore / Claim roots drift away from the active ShareholderRoyalties surface.
  • LpStakingVault7D.setAutoCompoundConfigForUser(user, ...) requires P_SET_LP_AUTOCOMPOUND_CONFIG_FOR

Non-custodial rules enforced:

  • config-only (no value transfer)
  • tokenId parameters must be veNFTs owned by user

Permissions map

Canonical bits are defined in src/lib/DelegationPermissions.sol.

GroupBitPermissionEnables
Crown0P_TAKEOVER_FORMineCore.takeoverFor(newKing, maxPrice)
Crown1P_ROUTE_REIGN_CLAIM_TO_CALLERRoute King-stream mined CLAIM to bot during delegated takeover
Crown2P_SET_REIGN_ETH_RECIPIENTAllow delegate to set ethRecipient mid-reign to any address
Crown3P_SET_REIGN_ETH_RECIPIENT_TO_CALLER_ONLYAllow delegate to set ethRecipient = msg.sender mid-reign
Crown4P_SET_REIGN_CLAIM_RECIPIENTAllow delegate to set claimRecipient mid-reign to any address
Crown5P_SET_REIGN_CLAIM_RECIPIENT_TO_USER_ONLYAllow delegate to set claimRecipient = king mid-reign
Harvest6P_WITHDRAW_KING_BUCKET_FORClaimAllHelper.withdrawKingBalanceForUser(user)
Harvest7P_CLAIM_SHAREHOLDER_FORClaimAllHelper.claimShareholderForUser(user, ...)
Harvest8P_CLAIM_ALL_FORClaimAllHelper.claimAllFor(user, ...)
Furnace9P_FURNACE_ENTER_ETH_FORFurnace.enterWithEthFor(user, ...)
Furnace10P_FURNACE_ENTER_CLAIM_FORFurnace.enterWithClaimFromCallerFor(user, ...)
Furnace11P_FURNACE_ENTER_TOKEN_FORFurnace.enterWithTokenFromCallerFor(user, ...)
VeLock12P_VE_EXTEND_LOCK_FORFurnace.extendWithBonusFor(user, tokenId, durationSeconds, minBonusOut)
VeLock13P_VE_MERGE_LOCKS_FORVeClaimNFT.mergeLocksForUser(user, fromTokenId, intoTokenId)
VeLock14P_VE_UNLOCK_EXPIRED_FORVeClaimNFT.unlockExpiredForUser(user, tokenId)
Config15P_SET_KING_AUTO_LOCK_CONFIG_FORMineCore.setKingAutoLockConfigForUser(user, ...)
Config16P_SET_SHAREHOLDER_AUTOCOMPOUND_CONFIG_FORShareholderRoyalties.setAutoCompoundConfigForUser(user, ...)
Config17P_SET_LP_AUTOCOMPOUND_CONFIG_FORLpStakingVault7D.setAutoCompoundConfigForUser(user, ...)

Integration guidance

  • Keep sessions short-lived (hours/days), refresh as needed.
  • Offchain tooling should not treat a raw stored delegationHub pointer as sufficient; protocol consumers may reject a session if live wiring drift breaks their canonical hub resolution.
  • Use minimal perms for the bot’s job.
  • Treat recipient routing perms as high risk:
    • P_SET_REIGN_ETH_RECIPIENT can redirect the dethroned-King ETH payout for the active reign.
    • P_SET_REIGN_CLAIM_RECIPIENT can redirect mined CLAIM mid-reign.
    • Prefer constrained variants (...TO_CALLER_ONLY, ...TO_USER_ONLY) where possible.
  • Treat delegation as security-sensitive UX (like approvals):
    • show the delegate address
    • show expiry
    • show selected permissions
    • provide a one-click revoke

Agent SDK

This repo’s TypeScript SDK (agents/sdk/) includes helpers for DelegationHub:

  • canonical permission bits (agents/sdk/src/delegation/permissions.ts)
  • EIP-712 typed data builder (buildSetSessionTypedData)
  • sign + submit setSessionBySig (signSetSession, submitSetSessionBySig)

Examples:

# Local demo (user signs with derived actor0; delegate submits with derived actor1) RPC_URL=http://127.0.0.1:8545 npm -C agents/sdk run example:delegation # Production-style: user wallet signs typed data; delegate submits signature RPC_URL=http://127.0.0.1:8545 \ npm -C agents/sdk run example:session -- --cmd build \ --user 0xUserAddress \ --delegate 0xDelegateAddress \ --perms TAKEOVER_FOR,CLAIM_ALL_FOR \ --out /tmp/session.json \ --pretty # user signs /tmp/session.json via eth_signTypedData_v4 RPC_URL=http://127.0.0.1:8545 PRIVATE_KEYS=0x<delegatePrivateKey> \ npm -C agents/sdk run example:session -- --cmd submit --typed-data /tmp/session.json --sig 0x... # Revoke (gasless) RPC_URL=http://127.0.0.1:8545 \ npm -C agents/sdk run example:session -- --cmd build --revoke --user 0xUserAddress --delegate 0xDelegateAddress --out /tmp/revoke.json --pretty RPC_URL=http://127.0.0.1:8545 PRIVATE_KEYS=0x<delegatePrivateKey> \ npm -C agents/sdk run example:session -- --cmd submit --typed-data /tmp/revoke.json --sig 0x... # Run a delegated agent loop RPC_URL=http://127.0.0.1:8545 npm -C agents/sdk run example:agent -- --actor-index 1 --acting-for 0xUserAddress --once # Delegated safe maintenance (ve upkeep + optional config sync) RPC_URL=http://127.0.0.1:8545 \ ENABLE_SAFE_MAINTENANCE=1 \ VE_EXTEND_IF_REMAINING_DAYS=7 \ VE_EXTEND_BY_DAYS=30 \ npm -C agents/sdk run example:agent -- --actor-index 1 --acting-for 0xUserAddress --once Optional config sync env vars: - `KING_AUTO_LOCK_*` (MineCore King auto-lock config) - `ROYALTIES_AUTOCOMPOUND_*` (ShareholderRoyalties auto-compound config) - `LP_AUTOCOMPOUND_*` (LP vault auto-compound config)

Observability and indexing

DelegationHub emits SessionSet(user, delegate, perms, expiry) on:

  • setSession(...)
  • revokeSession(delegate) (stored as perms=0, expiry=0)
  • setSessionBySig(...)

Expiry notes (important for integrators):

  • isAuthorized(...) returns false if expiry == 0 or expiry <= block.timestamp (expired sessions are invalid at the exact second of expiry, not one second later).
  • No “never-expires” session value exists in v1.0.0.
  • perms != 0 with expiry == 0 or expiry <= block.timestamp reverts (Errors.DeadlineExpired()). Enforced in _setSession(...) and setSessionBySig(...) to prevent unreachable sessions from polluting storage.

In addition, protocol contracts emit Events.DelegationSessionUsed(user, delegate, actionTypeId, permsUsed, refId, timestamp) when a delegated entrypoint succeeds.

actionTypeId is a numeric uint8 defined in src/lib/DelegationActionTypes.sol:

actionTypeIdConstantSubgraph categoryrefId meaning
1TAKEOVER_FORTAKEOVERnewReignId
2MINECORE_SET_REIGN_RECIPIENTSREIGN_RECIPIENTSreignId
10CLAIM_SHAREHOLDER_FORCLAIM0
11WITHDRAW_KING_BUCKET_FORCLAIM0
12CLAIM_ALL_FORCLAIM0
20FURNACE_ENTER_WITH_ETH_FORFURNACE_ENTERtokenIdUsed
21FURNACE_ENTER_WITH_CLAIM_FORFURNACE_ENTERtokenIdUsed
22FURNACE_ENTER_WITH_TOKEN_FORFURNACE_ENTERtokenIdUsed
30VE_EXTEND_LOCK_FORVE_LOCKtokenId
31VE_MERGE_LOCKS_FORVE_LOCKintoTokenId (destination)
32VE_UNLOCK_EXPIRED_FORVE_LOCKtokenId
40MINECORE_SET_KING_AUTO_LOCK_CONFIG_FORCONFIGdestination tokenId (0 when disabling / not yet pinned)
41SHAREHOLDER_SET_AUTOCOMPOUND_CONFIG_FORCONFIGtokenId (0 when disabling)
42LP_STAKING_SET_AUTOCOMPOUND_CONFIG_FORCONFIGtokenId (0 when disabling)

Subgraph behavior (this repo):

  • The subgraph stores both:
    • actionTypeId (exact numeric id; canonical for audits/analytics)
    • actionType (coarse enum for UI), derived by id ranges (see subgraph/src/utils/delegation.ts)
  • Raw actionTypeId is canonical. The shipped coarse mapper in subgraph/src/utils/delegation.ts maps actionTypeId = 2 (MINECORE_SET_REIGN_RECIPIENTS) to actionType = REIGN_RECIPIENTS.
  • Rolling session state is kept in DelegationSession (including lastUsedAt, lastActionType, lastTxHash).
  • Full history is recorded in DelegationSessionUse (immutable, tx hashes included).
  • Session change events are recorded in DelegationSessionSetEvent (immutable, tx hashes included).

These entities power:

  • Security → Bot access (sessions list + activity feed + one-click revoke)
  • Radar Inbox alerts (bot session used, session granted/updated/revoked, recipients changed mid-reign)

See also