User Guide
Synapse-Chainalysis User Guide
Synapse-Chainalysis adds Storm commands to query the Chainalysis Data Solutions analytical SQL API using your existing API credentials.
Getting Started
Check with your Admin to enable permissions and find out if you need a
personal API key. Each Chainalysis product is configured independently;
configurations for the Data Solutions product live under the ds product
namespace.
Examples
Setting your personal API key
To set up a personal Data Solutions configuration:
chainalysis.config.add ds ds_local myapikey --scope self
Executing a SQL query
Pass an arbitrary SQL query as a positional argument; results stream back as rows in API order:
> chainalysis.ds.sql.execute --pprint "SELECT * FROM bitcoin.clusters WHERE address = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'"
Chainalysis DS query_id=01ef2d0a-8a49-1127-8f56-f68535c4211d
Columns:
({'name': 'chain_id', 'type': 'string'},
{'name': 'chain_name', 'type': 'string'},
{'name': 'address', 'type': 'string'},
{'name': 'cluster_id', 'type': 'string'},
{'name': 'entity_name', 'type': 'string'},
{'name': 'entity_category', 'type': 'string'},
{'name': 'entity_category_changed_at', 'type': 'timestamp'},
{'name': 'prior_entity_category', 'type': 'string'},
{'name': 'is_contract_address', 'type': 'boolean'},
{'name': '__entity_uuid', 'type': 'string'})
{'__entity_uuid': None,
'address': '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
'chain_id': 'bip122:000000000019d6689c085ae165831e93',
'chain_name': 'bitcoin',
'cluster_id': '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
'entity_category': None,
'entity_category_changed_at': None,
'entity_name': None,
'is_contract_address': False,
'prior_entity_category': None}
Ingesting query results as Synapse nodes
The chainalysis.ds Storm module exposes a sqlExecute generator
that emits a stream of (type, data, info) tuples. Each data row
arrives as a dict keyed by the column names returned by Chainalysis,
which makes it straightforward to drive Storm ingest directly from a
SQL query.
The example below issues the same Bitcoin cluster lookup as the previous
example and ingests the result into Synapse: it anchors the Bitcoin
crypto:currency:chain, links the crypto:currency:address to it,
and – when Chainalysis has attributed the row to an entity – anchors a
meta:cluster node and applies its tag to the address along with a
tag derived from the entity category.
$mod = $lib.import(chainalysis.ds)
$opts = $mod.resolveOpts()
$tag_prefix = $opts.vault.configs.tag_prefix
$source = $mod.getMetaSource()
$query = "SELECT * FROM bitcoin.clusters WHERE address = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'"
for ($mtyp, $data, $info) in $mod.sqlExecute($query, opts=$opts) {
switch $mtyp {
"init": {
$lib.print(`Chainalysis DS query_id={$data}`)
}
"data": {
$cat_tag = (null)
$cat_type = (null)
$cluster_tag = (null)
if $data.entity_category {
$cat_tag = `{$tag_prefix}.{$data.entity_category}`
$cat_type = `chainalysis.{$data.entity_category}`
}
$chain = {[
crypto:currency:chain=({
"id": $data.chain_id,
"$try": true,
"$props": {
"name": $data.chain_name,
"symbol": "btc",
}
})
<(seen)+ $source
]}
if $data."__entity_uuid" {
// Derive a stable tag from the entity uuid for
// annotating nodes associated with this cluster.
$cluster_tag = `{$tag_prefix}.cluster.{$data."__entity_uuid".replace("-", "")}`
$cluster = {[
meta:cluster=({
"id": $data."__entity_uuid",
"reporter:name": "chainalysis",
"$try": true,
"$props": {
"name": $data.entity_name,
"type": $cat_type,
"tag": $cluster_tag,
}
})
<(seen)+ $source
]}
}
[ crypto:currency:address=(btc, $data.address)
:chain=$chain
+?#$cluster_tag
+?#$cat_tag
<(seen)+ $source
]
}
"print": {
$lib.print($data)
}
"warn": {
$lib.warn($data)
}
}
}