9
9
10
10
import logging
11
11
from dataclasses import dataclass
12
- from typing import Callable , Final
12
+ from typing import Callable , Final , Optional
13
13
14
14
import cachetools .func
15
15
import pycardano as pyc
@@ -73,15 +73,15 @@ def retrieve_staked_holders(self, token_policy: str) -> list:
73
73
raise NotImplementedError ()
74
74
75
75
def retrieve_nft_holders (
76
- self , policy : str , deny_list : list , seek_addr : str = None
76
+ self , policy : str , deny_list : list , addr : str = None
77
77
) -> list :
78
78
"""Retrieve a list of NFT holders, e.g. a license to operate
79
79
a decentralized node.
80
80
"""
81
81
raise NotImplementedError ()
82
82
83
83
def retrieve_metadata (
84
- self , value : int , policy : str , tag : str , callback : Callable = None
84
+ self , value : int , policy : str , tag : str , callback : Optional [ Callable | None ]
85
85
) -> list :
86
86
"""Retrieve metadata from the backend."""
87
87
raise NotImplementedError ()
@@ -100,20 +100,25 @@ def __init__(
100
100
self ._port = port
101
101
102
102
@cachetools .func .ttl_cache (ttl = 60 )
103
- def _retrieve_unspent_utxos (self , addr : str = "" ) -> dict :
103
+ def _retrieve_unspent_utxos (self , addr : str = None ) -> dict :
104
104
"""Retrieve unspent utxos from Kupo.
105
105
106
- NB. Kupo must be configured to capture sparingly.
106
+ NB. Kupo must be configured to capture sparingly, i.e. the
107
+ policies and addresses it is watching and slot from which it is
108
+ watching must be as specific as possible for this function to
109
+ perform well.
107
110
"""
108
- if not addr :
109
- resp = requests .get (
110
- f"{ self ._base_url } :{ self ._port } /matches?unspent" , timeout = 30
111
- )
112
- return resp .json ()
113
- resp = requests .get (
114
- f"{ self ._base_url } :{ self ._port } /matches/{ addr } ?unspent" , timeout = 30
115
- )
116
- return resp .json ()
111
+ kupo_err : Final [str ] = "hint"
112
+ request_string = f"{ self ._base_url } :{ self ._port } /matches?unspent"
113
+ if addr :
114
+ request_string = f"{ self ._base_url } :{ self ._port } /matches/{ addr } ?unspent"
115
+ logger .info ("requesting unspent: '%s'" , request_string )
116
+ resp = requests .get (request_string , timeout = 30 )
117
+ ret = resp .json ()
118
+ if kupo_err in ret :
119
+ logger .error ("unable to retrieve data due to Kupo request error: %s" , ret )
120
+ return []
121
+ return ret
117
122
118
123
def _retrieve_metadata (self , tag : str , tx_list : list [ValidTx ]):
119
124
"""Return metadata based on slot and transaction ID. This is
@@ -145,25 +150,26 @@ def _retrieve_metadata(self, tag: str, tx_list: list[ValidTx]):
145
150
md_list .append (md_dict [0 ])
146
151
return md_list
147
152
148
- def retrieve_staked_holders (self , token_policy : str , seek_addr : str = None ) -> list :
153
+ def retrieve_staked_holders (self , token_policy : str , addr : str = None ) -> list :
149
154
"""Retrieve a list of staked holders against a given CNT."""
150
155
unspent = self ._retrieve_unspent_utxos ()
151
156
addresses_with_fact = {}
152
157
for item in unspent :
153
- addr = item ["address" ]
154
- if seek_addr and addr != seek_addr :
158
+ unspent_addr = item ["address" ]
159
+ unspent_staking = _get_staking_from_addr (unspent_addr )
160
+ if addr and addr not in (unspent_addr , unspent_staking ):
155
161
# don't process further than we have to if we're only
156
162
# looking for a single address.
157
163
continue
158
- staking = _get_staking_from_addr ( addr )
164
+ staking = unspent_staking
159
165
assets = item ["value" ]["assets" ]
160
166
for key , value in assets .items ():
161
167
if token_policy in key :
162
168
addresses_with_fact = _sum_dict (staking , value , addresses_with_fact )
163
169
return addresses_with_fact
164
170
165
171
def retrieve_nft_holders (
166
- self , policy : str , deny_list : list = None , seek_addr : str = None
172
+ self , policy : str , deny_list : list = None , addr : str = None
167
173
) -> list :
168
174
"""Retrieve a list of NFT holders, e.g. a license to operate
169
175
a decentralized node.
@@ -172,17 +178,21 @@ def retrieve_nft_holders(
172
178
to remove some results that are unhelpful, e.g. the minting
173
179
address if desired.
174
180
"""
175
- unspent = self ._retrieve_unspent_utxos ()
181
+ if addr :
182
+ unspent = self ._retrieve_unspent_utxos (addr )
183
+ else :
184
+ unspent = self ._retrieve_unspent_utxos ()
176
185
holders = {}
177
186
for item in unspent :
178
- addr = item ["address" ]
179
- if seek_addr and addr != seek_addr :
187
+ unspent_addr = item ["address" ]
188
+ unspent_staking = _get_staking_from_addr (unspent_addr )
189
+ if addr and addr not in (unspent_addr , unspent_staking ):
180
190
# don't process further than we have to if we're only
181
191
# looking for a single address.
182
192
continue
183
- staking = _get_staking_from_addr (addr )
184
- if addr in deny_list :
193
+ if deny_list and unspent_addr in deny_list :
185
194
continue
195
+ staking = unspent_staking
186
196
assets = item ["value" ]["assets" ]
187
197
for key , _ in assets .items ():
188
198
if not key .startswith (policy ):
@@ -195,6 +205,7 @@ def _get_valid_txs(unspent: list[dict], value: int, policy: str) -> list[ValidTx
195
205
"""Retrieve a list of valid transactions according to our
196
206
policy rules.
197
207
"""
208
+ logger .info ("getting valid txs for policy: '%s'" , policy )
198
209
valid_txs = []
199
210
if not unspent :
200
211
return valid_txs
@@ -206,7 +217,6 @@ def _get_valid_txs(unspent: list[dict], value: int, policy: str) -> list[ValidTx
206
217
for asset in assets :
207
218
if policy not in asset :
208
219
continue
209
- logger .error (policy )
210
220
slot = item ["created_at" ]["slot_no" ]
211
221
tx_id = item ["transaction_id" ]
212
222
address = item ["address" ]
@@ -225,7 +235,7 @@ def retrieve_metadata(
225
235
value : int ,
226
236
policy : str ,
227
237
tag : str ,
228
- callback : Callable = None ,
238
+ callback : Optional [ Callable | None ] ,
229
239
) -> list :
230
240
"""Retrieve a list of aliased signing addresses. An aliased
231
241
signing address is an address that has been setup using a
0 commit comments