181 lines
4.6 KiB
Dart
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,
|
|
);
|
|
}
|