Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lido staking #47

Merged
merged 12 commits into from
Jun 19, 2024
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
14 changes: 12 additions & 2 deletions 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,7 +41,17 @@ struct StakeService {
return id
case .smartChain:
return StakeHub.address
case .ethereum, .none:
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 {
0xh3rman marked this conversation as resolved.
Show resolved Hide resolved
// empty signature for gas estimation
let signature = Data(repeating: 0, count: 65)
return try LidoContract.encodeStake(type: stakeType, sender: input.senderAddress, amount: input.value, signature: signature)
} catch {
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
Loading