11#!/usr/bin/env python3
22
3- import xml .etree .ElementTree as ET
43import argparse
5- import pathlib
6- import json
74import hashlib
5+ import json
6+ import pathlib
7+ import xml .etree .ElementTree as ET
8+
89from ofac_scraper import OfacWebsiteScraper
910
1011FEATURE_TYPE_TEXT = "Digital Currency Address - "
11- NAMESPACE = {'sdn' : 'https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/ADVANCED_XML' }
12+ NAMESPACE = {
13+ 'sdn' : 'https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/'
14+ 'ADVANCED_XML'
15+ }
1216
1317# List of implemented output formats
1418OUTPUT_FORMATS = ["TXT" , "JSON" ]
1519
1620SDN_ADVANCED_FILE_PATH = "sdn_advanced.xml"
1721
22+
1823def parse_arguments ():
1924 parser = argparse .ArgumentParser (
20- description = 'Tool to extract sanctioned digital currency addresses from the OFAC special designated nationals XML file (sdn_advanced.xml)' )
21- parser .add_argument ('assets' , nargs = '*' ,
22- default = [], help = 'the asset for which the sanctioned addresses should be extracted (default: XBT (Bitcoin))' )
23- parser .add_argument ('-sdn' , '--special-designated-nationals-list' , dest = 'sdn' , type = argparse .FileType ('rb' ),
24- help = 'the path to the sdn_advanced.xml file (can be downloaded from https://www.treasury.gov/ofac/downloads/sanctions/1.0/sdn_advanced.xml)' , default = SDN_ADVANCED_FILE_PATH )
25- parser .add_argument ('-f' , '--output-format' , dest = 'format' , nargs = '*' , choices = OUTPUT_FORMATS ,
26- default = OUTPUT_FORMATS [0 ], help = 'the output file format of the address list (default: TXT)' )
27- parser .add_argument ('-path' , '--output-path' , dest = 'outpath' , type = pathlib .Path , default = pathlib .Path (
28- "./" ), help = 'the path where the lists should be written to (default: current working directory ("./")' )
25+ description = 'Tool to extract sanctioned digital currency addresses from the '
26+ 'OFAC special designated nationals XML file (sdn_advanced.xml)' )
27+ parser .add_argument (
28+ 'assets' ,
29+ nargs = '*' ,
30+ default = [],
31+ help = 'the asset for which the sanctioned addresses should be extracted '
32+ '(default: XBT (Bitcoin))'
33+ )
34+ parser .add_argument (
35+ '-sdn' ,
36+ '--special-designated-nationals-list' ,
37+ dest = 'sdn' ,
38+ type = argparse .FileType ('rb' ),
39+ help = 'the path to the sdn_advanced.xml file (can be downloaded from '
40+ 'https://www.treasury.gov/ofac/downloads/sanctions/1.0/sdn_advanced.xml)' ,
41+ default = SDN_ADVANCED_FILE_PATH
42+ )
43+ parser .add_argument (
44+ '-f' ,
45+ '--output-format' ,
46+ dest = 'format' ,
47+ nargs = '*' ,
48+ choices = OUTPUT_FORMATS ,
49+ default = OUTPUT_FORMATS [0 ],
50+ help = 'the output file format of the address list (default: TXT)'
51+ )
52+ parser .add_argument (
53+ '-path' ,
54+ '--output-path' ,
55+ dest = 'outpath' ,
56+ type = pathlib .Path ,
57+ default = pathlib .Path ("./" ),
58+ help = 'the path where the lists should be written to (default: current working '
59+ 'directory ("./")'
60+ )
2961 return parser .parse_args ()
3062
63+
3164def feature_type_text (asset ):
3265 """returns text we expect in a <FeatureType></FeatureType> tag for a given asset"""
3366 return "Digital Currency Address - " + asset
3467
68+
3569def get_possible_assets (root ):
3670 """
3771 Returns a list of possible digital currency assets from the parsed XML.
3872 """
3973 assets = []
40- feature_types = root .findall ('sdn:ReferenceValueSets/sdn:FeatureTypeValues/sdn:FeatureType' , NAMESPACE )
74+ feature_types = root .findall (
75+ 'sdn:ReferenceValueSets/sdn:FeatureTypeValues/sdn:FeatureType' ,
76+ NAMESPACE
77+ )
4178 for feature_type in feature_types :
4279 if feature_type .text .startswith ('Digital Currency Address - ' ):
4380 asset = feature_type .text .replace ('Digital Currency Address - ' , '' )
4481 assets .append (asset )
4582 return assets
4683
84+
4785def get_address_id (root , asset ):
4886 """returns the feature id of the given asset"""
4987 feature_type = root .find (
50- "sdn:ReferenceValueSets/sdn:FeatureTypeValues/*[.='{}']" .format (feature_type_text (asset )), NAMESPACE )
51- if feature_type == None :
52- raise LookupError ("No FeatureType with the name {} found" .format (
53- feature_type_text (asset )))
88+ f"sdn:ReferenceValueSets/sdn:FeatureTypeValues"
89+ f"/*[.='{ feature_type_text (asset )} ']" ,
90+ NAMESPACE
91+ )
92+ if feature_type is None :
93+ raise LookupError (
94+ f"No FeatureType with the name { feature_type_text (asset )} found"
95+ )
5496 address_id = feature_type .attrib ["ID" ]
5597 return address_id
5698
5799
58100def get_sanctioned_addresses (root , address_id ):
59101 """returns a list of sanctioned addresses for the given address_id"""
60102 addresses = list ()
61- for feature in root .findall ("sdn:DistinctParties//*[@FeatureTypeID='{}']" .format (address_id ), NAMESPACE ):
103+ for feature in root .findall (
104+ f"sdn:DistinctParties//*[@FeatureTypeID='{ address_id } ']" ,
105+ NAMESPACE
106+ ):
62107 for version_detail in feature .findall (".//sdn:VersionDetail" , NAMESPACE ):
63108 addresses .append (version_detail .text )
64109 return addresses
@@ -72,14 +117,15 @@ def write_addresses(addresses, asset, output_formats, outpath):
72117
73118
74119def write_addresses_txt (addresses , asset , outpath ):
75- with open ("{ }/sanctioned_addresses_{}.txt". format ( outpath , asset ) , 'w' ) as out :
120+ with open (f" { outpath } /sanctioned_addresses_{ asset } .txt" , 'w' ) as out :
76121 for address in addresses :
77- out .write (address + "\n " )
122+ out .write (address + "\n " )
78123
79124
80125def write_addresses_json (addresses , asset , outpath ):
81- with open ("{}/sanctioned_addresses_{}.json" .format (outpath , asset ), 'w' ) as out :
82- out .write (json .dumps (addresses , indent = 2 )+ "\n " )
126+ with open (f"{ outpath } /sanctioned_addresses_{ asset } .json" , 'w' ) as out :
127+ out .write (json .dumps (addresses , indent = 2 ) + "\n " )
128+
83129
84130def compute_sha256 (file_path ):
85131 sha256_hash = hashlib .sha256 ()
@@ -88,10 +134,12 @@ def compute_sha256(file_path):
88134 sha256_hash .update (chunk )
89135 return sha256_hash .hexdigest ()
90136
137+
91138def write_checksum_file (sha256 , checksum_file_path ):
92139 with open (checksum_file_path , "w" ) as checksum_file :
93140 checksum_file .write (f"SHA256({ SDN_ADVANCED_FILE_PATH } ) = { sha256 } \n " )
94141
142+
95143def main ():
96144 args = parse_arguments ()
97145
@@ -110,7 +158,7 @@ def main():
110158 root = tree .getroot ()
111159
112160 assets = list ()
113- if type (args .assets ) == str :
161+ if isinstance (args .format , str ) :
114162 assets .append (args .assets )
115163 else :
116164 assets = args .assets
@@ -119,7 +167,7 @@ def main():
119167 assets = get_possible_assets (root )
120168
121169 output_formats = list ()
122- if type (args .format ) == str :
170+ if isinstance (args .format , str ) :
123171 output_formats .append (args .format )
124172 else :
125173 output_formats = args .format
@@ -150,5 +198,6 @@ def main():
150198
151199 write_checksum_file (sha256_checksum_from_site , "data/sdn_advanced_checksum.txt" )
152200
201+
153202if __name__ == "__main__" :
154203 main ()
0 commit comments