Skip to content

feat(staking): real on-chain stake/unstake/claim on OltinStaking V3 - #53

Merged
temrjan merged 2 commits into
mainfrom
feat/stage4-staking
Jul 29, 2026
Merged

feat(staking): real on-chain stake/unstake/claim on OltinStaking V3#53
temrjan merged 2 commits into
mainfrom
feat/stage4-staking

Conversation

@temrjan

@temrjan temrjan commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Connects the staking screen to the live OltinStaking V3 contract with non-custodial signing (the last screen still calling dead endpoints).

Changes

  • contracts: STAKING → V3 0xD3b6ffd1…
  • lib/staking.deriveStakingView: pure view model — isLocked, withdrawCap = unlocked_wei, claimable = pending >= 0.0001 OLTIN (tied to the 4-dp balance display), apy% from bps + vitest (red→green)
  • staking/page: deposit = approve+stake (2-tx, receipt-gated, wei via parseTokenAmount); withdraw = unstake unlocked only (Max = unlocked); claim (success without asserting a paid amount); pending at 8 dp; nearest-unlock from next_unlock_at*1000; 400 → empty position; invalidate staking/balances/transactions; receipt timeout → 'status unknown' (no double-stake), revert → 'reverted on-chain'
  • api/types: drop dead stakingDeposit/Withdraw/getStakingRewards + StakingInfo/StakingReward; add StakingInfoResponse
  • i18n: rewardsInfo reflects continuous on-chain accrual

Tests

tsc 0 · eslint 0 errors · vitest 36/36 · red→green on the claim threshold. Read-path + deriveStakingView verified against the live deployer position.

Gate-2: APPROVED WITH NITS (both MINORs applied).

temrjan added 2 commits July 29, 2026 13:25
The staking screen called dead endpoints (POST /staking/deposit|withdraw,
GET /staking/rewards -> 404) and read the old off-chain schema. Rewire it to
the live OltinStaking contract with non-custodial signing, matching the
Transfers/Exchange pattern.

- contracts: STAKING -> V3 0xD3b6ffd1…
- lib/staking.deriveStakingView: pure view model (isLocked, withdrawCap =
  unlocked_wei, claimable = pending >= 0.0001 OLTIN tied to the 4-dp balance
  display, apy% from bps) + vitest (red->green)
- staking/page: deposit = approve+stake (2-tx, receipt-gated, wei via
  parseTokenAmount), withdraw = unstake unlocked only (Max = unlocked_wei),
  claim (success without asserting a paid amount), pending shown at 8 dp,
  nearest-unlock date from next_unlock_at*1000, 400 -> empty position, invalidate
  staking/balances/transactions
- api: drop dead stakingDeposit/stakingWithdraw/getStakingRewards, type
  getStaking() as StakingInfoResponse
- types: drop dead StakingInfo/StakingReward, add StakingInfoResponse
- i18n: rewardsInfo now reflects continuous on-chain accrual, not 'daily 07:00'

Gates: tsc 0, eslint 0 errors, vitest 36/36. Read-path verified against the
live deployer position (getStakeInfo).
Gate-2 nits. A timed-out waitForTransactionReceipt threw viem's raw error
past explain() into the banner, and — worse — presented an unknown status as
a failure, so a blind retry would double-stake and re-lock funds.

- awaitReceipt(): catch the timeout, refresh (the tx may have landed), and
  surface 'status unknown — check history before retrying'
- unstake/claim revert now says 'reverted on-chain (gas spent)', not
  'transaction not sent' (it was sent, it reverted, gas was spent)

Gates: tsc 0, eslint 0, vitest 36/36.
@temrjan
temrjan merged commit 73ea5d8 into main Jul 29, 2026
1 check passed
@temrjan
temrjan deleted the feat/stage4-staking branch July 29, 2026 14:23
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