Skip to content

Commit

Permalink
add lido staking
Browse files Browse the repository at this point in the history
  • Loading branch information
0xh3rman committed Jun 12, 2024
1 parent ca0e3b9 commit 01a2301
Show file tree
Hide file tree
Showing 30 changed files with 594 additions and 75 deletions.
3 changes: 2 additions & 1 deletion Gem/Assets/ViewModels/AssetDataViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ struct AssetDataViewModel {
Chain.celestia.assetId,
Chain.solana.assetId,
Chain.sui.assetId,
Chain.smartChain.assetId
Chain.smartChain.assetId,
Chain.ethereum.assetId
].contains(asset.id) {
return true
}
Expand Down
12 changes: 11 additions & 1 deletion Gem/Stake/Services/StakeService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct StakeService {
.filter { $0.isActive && !$0.name.isEmpty }
}

func getRecipientAddress(chain: StakeChain?, validatorId: String?) -> String? {
func getRecipientAddress(chain: StakeChain?, type: AmountType, validatorId: String?) -> String? {

guard let id = validatorId else {
return nil
Expand All @@ -41,6 +41,16 @@ struct StakeService {
return id
case .smartChain:
return StakeHub.address
case .ethereum:
// for Lido, it's stETH token address or withdrawal queue
switch type {
case .stake:
return LidoContract.address
case .unstake, .withdraw:
return LidoContract.withdrawal
default:
fatalError()
}
case .none:
fatalError()
}
Expand Down
4 changes: 2 additions & 2 deletions Gem/Transfer/ViewsModels/AmountViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import BigInt
import Components
import Store
import SwiftUI
import Gemstone
import GemstonePrimitives

enum AmountType: Equatable, Hashable {
Expand Down Expand Up @@ -158,7 +159,6 @@ class AmounViewModel: ObservableObject {

var minimumValue: BigInt {
let stakeChain = amountRecipientData.data.asset.chain.stakeChain

switch amountRecipientData.type {
case .stake:
return BigInt(StakeConfig.config(chain: stakeChain!).minAmount)
Expand Down Expand Up @@ -205,7 +205,7 @@ class AmounViewModel: ObservableObject {
}

func getTransferData(value: BigInt) throws -> TransferData {
let recipientAddress = stakeService.getRecipientAddress(chain: amountRecipientData.data.asset.chain.stakeChain, validatorId: currentValidator?.id)
let recipientAddress = stakeService.getRecipientAddress(chain: amountRecipientData.data.asset.chain.stakeChain, type: amountRecipientData.type, validatorId: currentValidator?.id)

// make sure validator address is correct
// FIXME. Refactor and add tests
Expand Down
3 changes: 2 additions & 1 deletion Gem/Transfer/ViewsModels/ConfirmTransferViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ class ConfirmTransferViewModel: ObservableObject {
block: input.block,
token: input.token,
utxos: input.utxos,
messageBytes: input.messageBytes
messageBytes: input.messageBytes,
extra: input.extra
)
return try signer.sign(input: input)
}
Expand Down
4 changes: 4 additions & 0 deletions Packages/Blockchain/Sources/Aptos/AptosService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ extension AptosService: ChainStakable {
public func getStakeDelegations(address: String) async throws -> [DelegationBase] {
fatalError()
}

public func getStakeBalance(address: String) async throws -> AssetBalance {
fatalError()
}
}

extension AptosService: ChainTokenable {
Expand Down
4 changes: 4 additions & 0 deletions Packages/Blockchain/Sources/Bitcoin/BitcoinService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ extension BitcoinService: ChainStakable {
public func getStakeDelegations(address: String) async throws -> [DelegationBase] {
fatalError()
}

public func getStakeBalance(address: String) async throws -> AssetBalance {
fatalError()
}
}

extension BitcoinService: ChainTokenable {
Expand Down
79 changes: 36 additions & 43 deletions Packages/Blockchain/Sources/BnbSmartChain/SmartChainService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import SwiftHTTPClient
import WalletCore

public class SmartChainService {

let provider: Provider<EthereumProvider>
let stakeHub: StakeHub

Expand All @@ -16,34 +15,30 @@ public class SmartChainService {
}
}

extension SmartChainService {
public func coinBalance(for address: String) async throws -> AssetBalance {
async let balanceCall = try provider.request(.balance(address: address))
.mapResult(BigIntable.self).value
async let delegationsCall = try getStakeDelegations(address: address)
extension SmartChainService: ChainStakable {
func getMaxElectedValidators() async throws -> UInt16 {
let params = [
"to":StakeHub.address,
"data": stakeHub.encodeMaxElectedValidators()
]

let (balance, delegations) = try await(balanceCall, delegationsCall)
let staked = delegations.filter { $0.state == .active }.map { $0.balanceValue }.reduce(0, +)
let pending = delegations.filter { $0.state == .undelegating }.map { $0.balanceValue }.reduce(0, +)
return AssetBalance(
assetId: Chain.smartChain.assetId,
balance: Balance(
available: balance,
staked: staked,
pending: pending
)
)
let result = try await provider.request(.call(params)).map(as: JSONRPCResponse<String>.self).result
guard
let data = Data(hexString: result),
let value = UInt16(EthereumAbiValue.decodeUInt256(input: data))
else {
throw AnyError("Unable to get validators")
}
return value
}
}

extension SmartChainService: ChainStakable {
public func getValidators(apr: Double) async throws -> [DelegationValidator] {
public func getValidators(apr _: Double) async throws -> [DelegationValidator] {
let limit = try await getMaxElectedValidators()
let params = [
"to":StakeHub.reader,
"data": stakeHub.encodeValidatorsCall(offset: 0, limit: limit)
"to": StakeHub.reader,
"data": stakeHub.encodeValidatorsCall(offset: 0, limit: limit),
]

let result = try await provider.request(.call(params)).map(as: JSONRPCResponse<String>.self).result
guard let data = Data(hexString: result) else {
return []
Expand All @@ -60,26 +55,24 @@ extension SmartChainService: ChainStakable {
return delegations + undelegations
}

func getMaxElectedValidators() async throws -> UInt16 {
let params = [
"to":StakeHub.address,
"data": stakeHub.encodeMaxElectedValidators()
]

let result = try await provider.request(.call(params)).map(as: JSONRPCResponse<String>.self).result
guard
let data = Data(hexString: result),
let value = UInt16(EthereumAbiValue.decodeUInt256(input: data))
else {
throw AnyError("Unable to get validators")
}
return value
public func getStakeBalance(address: String) async throws -> AssetBalance {
let delegations = try await getStakeDelegations(address: address)
let staked = delegations.filter { $0.state == .active }.map { $0.balanceValue }.reduce(0, +)
let pending = delegations.filter { $0.state == .undelegating }.map { $0.balanceValue }.reduce(0, +)
return AssetBalance(
assetId: Chain.smartChain.assetId,
balance: Balance(
available: .zero,
staked: staked,
pending: pending
)
)
}

func getDelegations(address: String, limit: UInt16) async throws -> [DelegationBase] {
let params = [
"to":StakeHub.reader,
"data": try stakeHub.encodeDelegationsCall(address: address, limit: limit)
let params = try [
"to": StakeHub.reader,
"data": stakeHub.encodeDelegationsCall(address: address, limit: limit),
]
let result = try await provider.request(.call(params)).mapResult(String.self)
guard let data = Data(hexString: result) else {
Expand All @@ -89,9 +82,9 @@ extension SmartChainService: ChainStakable {
}

func getUndelegations(address: String, limit: UInt16) async throws -> [DelegationBase] {
let params = [
"to":StakeHub.reader,
"data": try stakeHub.encodeUndelegationsCall(address: address, limit: limit)
let params = try [
"to": StakeHub.reader,
"data": stakeHub.encodeUndelegationsCall(address: address, limit: limit),
]
let result = try await provider.request(.call(params)).mapResult(String.self)
guard let data = Data(hexString: result) else {
Expand Down
8 changes: 6 additions & 2 deletions Packages/Blockchain/Sources/ChainService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,18 @@ extension ChainService: ChainTransactionStateFetchable {

extension ChainService: ChainStakable {
public func getValidators(apr: Double) async throws -> [DelegationValidator] {
return try await Self.service(chain: chain, with: url)
try await Self.service(chain: chain, with: url)
.getValidators(apr: apr)
}

public func getStakeDelegations(address: String) async throws -> [DelegationBase] {
return try await Self.service(chain: chain, with: url)
try await Self.service(chain: chain, with: url)
.getStakeDelegations(address: address)
}

public func getStakeBalance(address: String) async throws -> AssetBalance {
try await Self.service(chain: chain, with: url).getStakeBalance(address: address)
}
}

extension ChainService: ChainTokenable {
Expand Down
4 changes: 4 additions & 0 deletions Packages/Blockchain/Sources/Cosmos/CosmosService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ extension CosmosService: ChainStakable {
]
.flatMap{ $0 }
}

public func getStakeBalance(address: String) async throws -> AssetBalance {
fatalError()
}
}

extension CosmosService: ChainTokenable {
Expand Down
41 changes: 33 additions & 8 deletions Packages/Blockchain/Sources/Ethereum/EthereumFeeService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ extension EthereumService: ChainFeeCalculateable {
switch input.chain {
case .smartChain:
return try? StakeHub().encodeStake(type: stakeType, amount: input.value)
case .ethereum:
do {
let signature = Data(repeating: 0, count: 65)
return try LidoContract.encodeStake(type: stakeType, sender: input.senderAddress, amount: input.value, signature: signature)
} catch let error {
print(error)
return nil
}
default:
fatalError()
}
Expand All @@ -58,6 +66,8 @@ extension EthereumService: ChainFeeCalculateable {
switch input.chain {
case .smartChain:
return StakeHub.address
case .ethereum:
return input.destinationAddress
default:
fatalError()
}
Expand Down Expand Up @@ -89,7 +99,8 @@ extension EthereumService: ChainFeeCalculateable {
return input.value
case .stake(_, let type):
switch input.chain {
case .smartChain:
case .smartChain,
.ethereum:
switch type {
case .stake:
return input.value
Expand All @@ -115,15 +126,29 @@ extension EthereumService: ChainFeeCalculateable {
let to = getTo(input: input)
let value = getValue(input: input)

async let getGasLimit = getGasLimit(
from: input.senderAddress,
to: to,
value: value?.hexString.append0x,
data: data?.hexString.append0x
)
let gasLimit: BigInt
if case .stake(_, let stakeType) = input.type {
gasLimit = try await EthereumStakeService(service: self)
.getGasLimit(
chain: chain,
type: stakeType,
stakeValue: input.value, // original value
from: input.senderAddress,
to: to,
value: value,
data: data
)
} else {
gasLimit = try await getGasLimit(
from: input.senderAddress,
to: to,
value: value?.hexString.append0x,
data: data?.hexString.append0x
)
}

async let getGasPrice = getBasePriorityFee(rewardPercentiles: Self.rewardPercentiles)
let (basePriorityFee, gasLimit) = try await (getGasPrice, getGasLimit)
let basePriorityFee = try await getGasPrice

let gasPrice = basePriorityFee.baseFee + basePriorityFee.priorityFee
let minerFee = {
Expand Down
Loading

0 comments on commit 01a2301

Please sign in to comment.