@@ -5,6 +5,7 @@ public enum KeetaApiError: Error {
55 case invalidBalanceValue( _ value: String )
66 case invalidSupplyValue( _ value: String )
77 case clientRepresentativeNotFound( _ address: String )
8+ case noVotes( errors: [ Error ] )
89 case missingAtLeastOneRep
910 case blockMismatch
1011 case notPublished
@@ -13,9 +14,9 @@ public enum KeetaApiError: Error {
1314public final class KeetaApi : HTTPClient {
1415
1516 public var preferredRep : ClientRepresentative
17+ public var reps : [ ClientRepresentative ]
1618
1719 private let publishAidUrl : String
18- private let reps : [ ClientRepresentative ]
1920 private let decoder : Decoder = JSONDecoder ( )
2021
2122 public convenience init ( config: NetworkConfig ) throws {
@@ -27,7 +28,7 @@ public final class KeetaApi: HTTPClient {
2728
2829 self . publishAidUrl = publishAidUrl
2930 self . reps = reps
30- self . preferredRep = preferredRep ?? reps [ 0 ]
31+ self . preferredRep = reps . preferred ?? reps [ 0 ]
3132 }
3233
3334 public func votes( for blocks: [ Block ] , temporaryVotes: [ Vote ] ? = nil ) async throws -> [ Vote ] {
@@ -47,17 +48,38 @@ public final class KeetaApi: HTTPClient {
4748
4849 // Request votes
4950 let requests = try KeetaEndpoint . votes ( for: blocks, temporaryVotes: temporaryVotes, from: repUrls)
51+ var errors = [ Error] ( )
5052
51- return try await withThrowingTaskGroup ( of: VoteResponse . self) { group in
53+ return try await withThrowingTaskGroup ( of: VoteResponse ? . self) { group in
5254 for request in requests {
53- group. addTask { try await self . sendRequest ( to: request) }
55+ group. addTask {
56+ do {
57+ return try await self . sendRequest ( to: request)
58+ } catch {
59+ if temporaryVotes? . isEmpty == false {
60+ // a permanent vote is required for each temporary vote
61+ throw error
62+ } else {
63+ // silently skip reps that can't provide a temporary vote
64+ errors. append ( error)
65+ return nil
66+ }
67+ }
68+ }
5469 }
5570
5671 var results : [ Vote ] = [ ]
5772 for try await result in group {
58- let vote = try Vote . create ( from: result. vote. binary)
59- results. append ( vote)
73+ if let result {
74+ let vote = try Vote . create ( from: result. vote. binary)
75+ results. append ( vote)
76+ }
77+ }
78+
79+ guard !results. isEmpty else {
80+ throw KeetaApiError . noVotes ( errors: errors)
6081 }
82+
6183 return results
6284 }
6385 }
@@ -131,7 +153,9 @@ public final class KeetaApi: HTTPClient {
131153 }
132154 }
133155
134- public func balance( for account: Account ) async throws -> AccountBalance {
156+ public func balance( for account: Account , replaceReps: Bool = false ) async throws -> AccountBalance {
157+ try await updateRepresentatives ( replace: replaceReps)
158+
135159 let repUrl = preferredRep. apiUrl
136160 let result : AccountStateResponse = try await sendRequest ( to: KeetaEndpoint . accountInfo ( of: account, baseUrl: repUrl) )
137161
@@ -146,6 +170,39 @@ public final class KeetaApi: HTTPClient {
146170 return . init( account: result. account, balances: balances, currentHeadBlock: result. currentHeadBlock)
147171 }
148172
173+ @discardableResult
174+ public func updateRepresentatives( replace: Bool = true ) async throws -> [ ClientRepresentative ] {
175+ let endpoint = KeetaEndpoint . representatives ( baseUrl: preferredRep. apiUrl)
176+ let response : RepresentativesResponse = try await sendRequest ( to: endpoint)
177+
178+ func rep( from rep: RepresentativeResponse ) -> ClientRepresentative {
179+ . init(
180+ address: rep. representative,
181+ apiUrl: rep. endpoints. api,
182+ socketUrl: rep. endpoints. p2p,
183+ weight: BigInt ( hex: rep. weight)
184+ )
185+ }
186+
187+ if reps. isEmpty || replace {
188+ reps = response. representatives. map { rep ( from: $0) }
189+ } else {
190+ // only update known reps
191+ for (index, knownRep) in reps. enumerated ( ) {
192+ if let update = response. representatives
193+ . first ( where: { $0. representative. lowercased ( ) == knownRep. address. lowercased ( ) } ) {
194+ reps [ index] = rep ( from: update)
195+ }
196+ }
197+ }
198+
199+ if let preferredRep = reps. preferred {
200+ self . preferredRep = preferredRep
201+ }
202+
203+ return reps
204+ }
205+
149206 public func accountInfo( for account: Account ) async throws -> AccountInfo {
150207 let repUrl = preferredRep. apiUrl
151208 let result : AccountStateResponse = try await sendRequest ( to: KeetaEndpoint . accountInfo ( of: account, baseUrl: repUrl) )
0 commit comments