Files
peyawallet/lib/domain/staking.dart
T
Codex Bot 01d9627a6b
build / Build Linux wallet (push) Successful in 19m0s
build / Build Windows wallet (push) Successful in 20m14s
Add active stake yield via monero_c
2026-04-12 19:11:41 +02:00

181 lines
4.6 KiB
Dart

import 'dart:math' as math;
import 'transactions.dart';
const int kPeyaBlockTimeSeconds = 120;
const int kPeyaMainnetStakeLockPeriod = 30 * 24 * 30;
class StakePreview {
const StakePreview({
required this.amountAtomic,
required this.feeAtomic,
});
final int amountAtomic;
final int feeAtomic;
}
class StakeResult {
const StakeResult({
required this.txIds,
required this.amountAtomic,
required this.feeAtomic,
});
final List<String> txIds;
final int amountAtomic;
final int feeAtomic;
}
class StakeYieldInfo {
const StakeYieldInfo({
required this.yieldAtomic,
required this.yieldPercent,
});
final int yieldAtomic;
final double yieldPercent;
}
class YieldBlockSample {
const YieldBlockSample({
required this.blockHeight,
required this.slippageTotalThisBlock,
required this.lockedCoinsTally,
});
final int blockHeight;
final int slippageTotalThisBlock;
final int lockedCoinsTally;
}
WalletTransaction? findLinkedStakeTransaction(
WalletTransaction tx,
List<WalletTransaction> allTransactions,
) {
if (tx.type != TransactionType.protocol || tx.returnAddresses.isEmpty) {
return null;
}
final lookup = tx.returnAddresses.toSet();
for (final candidate in allTransactions) {
if (candidate.type != TransactionType.stake) {
continue;
}
if (candidate.returnAddresses.any(lookup.contains)) {
return candidate;
}
}
return null;
}
WalletTransaction? findLinkedUnstakeTransaction(
WalletTransaction tx,
List<WalletTransaction> allTransactions,
) {
if (tx.type != TransactionType.stake || tx.returnAddresses.isEmpty) {
return null;
}
final lookup = tx.returnAddresses.toSet();
for (final candidate in allTransactions) {
if (candidate.type != TransactionType.protocol) {
continue;
}
if (candidate.returnAddresses.any(lookup.contains)) {
return candidate;
}
}
return null;
}
StakeYieldInfo? computeStakeYield(
WalletTransaction stake,
WalletTransaction protocol,
) {
final stakeAmount = stake.amountAtomic.abs();
final protocolAmount = protocol.amountAtomic.abs();
if (stakeAmount <= 0 || protocolAmount <= 0) {
return null;
}
var yieldAtomic = protocolAmount - stakeAmount;
if (yieldAtomic < 0) {
yieldAtomic = 0;
}
final percent = stakeAmount == 0 ? 0.0 : (yieldAtomic / stakeAmount) * 100.0;
return StakeYieldInfo(
yieldAtomic: yieldAtomic,
yieldPercent: percent,
);
}
int effectiveStakeUnlockHeight(WalletTransaction stakeTx) {
final relativeUnlock =
stakeTx.unlockTime > 0 ? stakeTx.unlockTime : kPeyaMainnetStakeLockPeriod;
return stakeTx.blockHeight + relativeUnlock;
}
int remainingStakeBlocks(WalletTransaction stakeTx, int currentHeight) {
return math.max(0, effectiveStakeUnlockHeight(stakeTx) - currentHeight);
}
Duration estimatedStakeRemaining(WalletTransaction stakeTx, int currentHeight) {
return Duration(
seconds:
remainingStakeBlocks(stakeTx, currentHeight) * kPeyaBlockTimeSeconds,
);
}
StakeYieldInfo computeAccruedStakeYield(
WalletTransaction stakeTx,
int currentHeight,
Iterable<YieldBlockSample> samples,
) {
return computeAccruedStakeYieldForRange(
stakeAmountAtomic: stakeTx.amountAtomic,
stakeStartHeight: stakeTx.blockHeight,
stakeUnlockHeight: effectiveStakeUnlockHeight(stakeTx),
currentHeight: currentHeight,
samples: samples,
);
}
StakeYieldInfo computeAccruedStakeYieldForRange({
required int stakeAmountAtomic,
required int stakeStartHeight,
required int stakeUnlockHeight,
required int currentHeight,
required Iterable<YieldBlockSample> samples,
}) {
final stakeAmount = stakeAmountAtomic.abs();
if (stakeAmount <= 0) {
return const StakeYieldInfo(yieldAtomic: 0, yieldPercent: 0);
}
final endExclusive = math.min(currentHeight, stakeUnlockHeight);
if (endExclusive <= stakeStartHeight) {
return const StakeYieldInfo(yieldAtomic: 0, yieldPercent: 0);
}
var yieldAtomic = BigInt.zero;
final stakeAmountBig = BigInt.from(stakeAmount);
for (final sample in samples) {
if (sample.blockHeight < stakeStartHeight ||
sample.blockHeight >= endExclusive) {
continue;
}
if (sample.slippageTotalThisBlock <= 0 || sample.lockedCoinsTally <= 0) {
continue;
}
yieldAtomic +=
(stakeAmountBig * BigInt.from(sample.slippageTotalThisBlock)) ~/
BigInt.from(sample.lockedCoinsTally);
}
final yieldValue = yieldAtomic.toInt();
final yieldPercent =
stakeAmount == 0 ? 0.0 : (yieldValue / stakeAmount) * 100.0;
return StakeYieldInfo(
yieldAtomic: yieldValue,
yieldPercent: yieldPercent,
);
}