Skip to content

Commit

Permalink
wip: Add address reputation
Browse files Browse the repository at this point in the history
This adds support for fetching an address' reputation lazily.

TODO:
* Add unit tests

Example:
```
risky_address = Coinbase::Address.new(:ethereum_mainnet, '0x12846c6Fd6baBFE4bC6F761eB871eFfFDEb26913')

// Returns the reputation of the address
risky_address.reputation

// Returns whether the address itself is deemed "risky"
risky_address.risky?
=> true
```
  • Loading branch information
alex-stone committed Dec 11, 2024
1 parent 1f6d6ce commit 39ca4a4
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 4 deletions.
3 changes: 2 additions & 1 deletion lib/coinbase.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# frozen_string_literal: true

require_relative 'coinbase/client'
require_relative 'coinbase/address'
require_relative 'coinbase/address/wallet_address'
require_relative 'coinbase/address/external_address'
require_relative 'coinbase/address_reputation'
require_relative 'coinbase/asset'
require_relative 'coinbase/authenticator'
require_relative 'coinbase/correlation'
require_relative 'coinbase/balance'
require_relative 'coinbase/balance_map'
require_relative 'coinbase/historical_balance'
require_relative 'coinbase/client'
require_relative 'coinbase/constants'
require_relative 'coinbase/contract_event'
require_relative 'coinbase/contract_invocation'
Expand Down
37 changes: 34 additions & 3 deletions lib/coinbase/address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ def initialize(network, id)
# Returns a String representation of the Address.
# @return [String] a String representation of the Address
def to_s
Coinbase.pretty_print_object(self.class, id: id, network_id: network.id)
Coinbase.pretty_print_object(
self.class,
**{
id: id,
network_id: network.id,
reputation_score: @reputation.nil? ? nil : reputation.score,
}.compact
)
end

# Same as to_s.
Expand All @@ -32,6 +39,18 @@ def can_sign?
false
end

# Returns the reputation of the Address.
# @return [Coinbase::AddressReputation] The reputation of the Address
def reputation
@reputation ||= Coinbase::AddressReputation.fetch(network: network, address_id: id)
end

# Returns wheth the Address's reputation is risky.
# @return [Boolean] true if the Address's reputation is risky
def risky?
reputation.risky?
end

# Returns the balances of the Address.
# @return [BalanceMap] The balances of the Address, keyed by asset ID. Ether balances are denominated
# in ETH.
Expand Down Expand Up @@ -66,7 +85,7 @@ def balance(asset_id)
# @return [Enumerable<Coinbase::HistoricalBalance>] Enumerator that returns historical_balance
def historical_balances(asset_id)
Coinbase::Pagination.enumerate(
->(page) { list_page(asset_id, page) }
->(page) { list_historical_balance_page(asset_id, page) }
) do |historical_balance|
Coinbase::HistoricalBalance.from_model(historical_balance)
end
Expand Down Expand Up @@ -265,7 +284,11 @@ def stake_api
@stake_api ||= Coinbase::Client::StakeApi.new(Coinbase.configuration.api_client)
end

def list_page(asset_id, page)
def reputation_api
@reputation_api ||= Coinbase::Client::ReputationApi.new(Coinbase.configuration.api_client)
end

def list_historical_balance_page(asset_id, page)
balance_history_api.list_address_historical_balance(
network.normalized_id,
id,
Expand All @@ -281,5 +304,13 @@ def list_transaction_page(page)
{ limit: DEFAULT_TRANSACTION_PAGE_LIMIT, page: page }
)
end

def get_reputation
response = Coinbase.call_api do
reputation_api.get_address_reputation(network.normalized_id, id)
end

response
end
end
end
70 changes: 70 additions & 0 deletions lib/coinbase/address_reputation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

module Coinbase
# A representation of the reputation of a blockchain address.
class AddressReputation
# A metadata object associated with an address reputation.
Metadata = Struct.new(
*Client::AddressReputationMetadata.attribute_map.keys.map(&:to_sym),
keyword_init: true
) do
def to_s
Coinbase.pretty_print_object(
self.class,
**to_h
)
end
end

class << self
def fetch(address_id:, network: Coinbase.default_network)
network = Coinbase::Network.from_id(network)

model = Coinbase.call_api do
reputation_api.get_address_reputation(network.normalized_id, address_id)
end

new(model)
end

private

def reputation_api
Coinbase::Client::ReputationApi.new(Coinbase.configuration.api_client)
end
end

def initialize(model)
unless model.is_a?(Coinbase::Client::AddressReputation)
raise ArgumentError,
'must be an AddressReputation client object'
end

@model = model
end

def score
@model.score
end

def metadata
@metadata ||= Metadata.new(**@model.metadata)
end

def risky?
score.negative?
end

def to_s
Coinbase.pretty_print_object(
self.class,
score: score,
**metadata.to_h
)
end

def inspect
to_s
end
end
end

0 comments on commit 39ca4a4

Please sign in to comment.