所有的公链本身就是一个大的网络,分析链上数据大概率是逃不掉关于网络的分析。常用的数据平台比如Dune现有的可视化功能其实目前很难比较好地刻画公链上各个节点之间的关系。这里我们以之前传的沸沸扬扬的FTX"黑客"地址(0x59ABf3837Fa962d6853b4Cc0a19513AA031fd32b)为例做一些网络分析(具体是黑客还是巴拿马政府这里就不细究了),去看下这个地址下的ETH都去了哪里(这里我们看从这个地址往外的2层关系)
二、用pandas读取本地文件到Dataframe并通过Etherscan API补充Balnace列
## 路径改成自己本地的文件路径
df_target_label = pd.read_csv(u'YOUE FILE PATH/graph_raw_label.csv')
df_target_relation = pd.read_csv(u'YOUE FILE PATH/graph_relation.csv')
##取所有addresss list用于请求API
address_list=list(df_target_label.address.values)
balance_list=[]
print(address_list)
while len(address_list)>0:
for address in address_list:
api_key = "api_key"
try:
response = requests.get(
"https://api.etherscan.io/api?module=account&action=balance&address=" + address + "&tag=latest&apikey=" + api_key
)
# Parse the JSON-formatted response
response_json = json.loads(response.text)
# Get the balance information from the response
eth_balance = response_json["result"]
eth_balance= int(eth_balance)/(1E18)
balance_list.append((address,eth_balance))
address_list.remove(address)
time.sleep(1)
print(eth_balance)
except:
print('Error')
print('List Length:'+str(len(address_list)))
df_balance = pd.DataFrame(balance_list, columns=['address', 'Balance'])
df_target_label=df_target_label.merge(df_balance,left_on=['address'],right_on=['address'],how='left')
print('end')
##定一个一个函数根据值的大小去返回不同的标签,类似于SQL里的case when
def get_balance_level(x):
if x ==0 :
output = 'Small'
elif x > 0 and x<1000:
output = 'Medium'
elif x > 1000 and x<10000:
output = 'Large'
else:
output = 'Huge'
return output
df_target_label['Balance_level'] = df_target_label['Balance'].round(2).apply(lambda x: get_balance_level(x))
df_target_label['Balance'] = df_target_label['Balance'].round(2).astype('string')
df_target_label['label'] =df_target_label['label']+' | '+ df_target_label['Balance'] +' ETH'
三、定义一个函数通过Network X处理节点关系并使用Plotly画图
def drew_graph(df_target_relation,df_target_label):
def add_node_base_data(df_target_relation):
df_target_relation = df_target_relation
node_list = list(set(df_target_relation['from_address'].to_list()+df_target_relation['to_address'].to_list()))
edges = list(set(df_target_relation.apply(lambda x: (x.from_address, x.to_address), axis=1).to_list()))
G.add_nodes_from(node_list)
G.add_edges_from(edges)
return node_list,edges
def add_node_attributes(df_target_label,df_key_list,df_vlaue_list,color_list):
for node, (n,p) in zip(G.nodes(), pos.items()):
G.nodes[node]['pos'] = p
G.nodes[node]['color'] = '#614433'
for id,label,layer_type,Balance_level in list(set(df_target_label.apply(lambda x: (x.address, x.label, x.level_type,x.Balance_level), axis=1).to_list())):
if node==id:
G.nodes[node]['label']=label
if Balance_level=='Large':
G.nodes[node]['size']=40
elif Balance_level=='Medium':
G.nodes[node]['size']=20
elif Balance_level=='Small':
G.nodes[node]['size']=10
elif Balance_level=='Huge':
G.nodes[node]['size']=80
for x,y,z in zip(df_key_list,df_vlaue_list,color_list):
target_list = df_target_label[df_target_label[x]==y]['address'].values.tolist()
if len(target_list)>0:
for id in target_list:
if id==node and G.nodes[node]['color']=='#614433':
G.nodes[node]['color'] = z
###############画出所有的边
def get_edge_trace(G):
xtext=[]
ytext=[]
edge_x = []
edge_y = []
for edge in G.edges():
x0, y0 = G.nodes[edge[0]]['pos']
x1, y1 = G.nodes[edge[1]]['pos']
xtext.append((x0+x1)/2)
ytext.append((y0+y1)/2)
edge_x.append(x0)
edge_x.append(x1)
edge_x.append(None)
edge_y.append(y0)
edge_y.append(y1)
edge_y.append(None)
xtext.append((x0+x1)/2)
ytext.append((y0+y1)/2)
edge_trace = go.Scatter(
x=edge_x, y=edge_y,
line=dict(width=0.5, color='#333'),
hoverinfo='none',
mode='lines')
eweights_trace = go.Scatter(x=xtext,y= ytext, mode='text',
marker_size=0.5,
text=[0.45, 0.7, 0.34],
textposition='top center',
hovertemplate='weight: %{text}<extra></extra>')
return edge_trace, eweights_trace
def get_node_trace(G):
node_x = []
node_y = []
for node in G.nodes():
x, y = G.nodes[node]['pos']
node_x.append(x)
node_y.append(y)
node_trace = go.Scatter(
x=node_x, y=node_y,
mode='markers',
hoverinfo='text',
marker=dict(
color=[],
colorscale = px.colors.qualitative.Plotly,
size=10,
line_width=0))
return node_trace
###############定义Graph
G = nx.Graph()
###############给Graph添加Node以及Edge
node_list = add_node_base_data(df_target_relation)[0]
edges = add_node_base_data(df_target_relation)[1]
# eweights_trace = add_node_base_data(df_target_relation)[1]
###############选择layout并得到相关node的pos
pos = nx.fruchterman_reingold_layout(G)
df_key_list = [ 'level_type' ,'account_type' , 'account_type' , 'account_type' ]
df_vlaue_list = [ 'Core' , 'EOA' , 'Cex Address' , 'Contract Address']
color_list = [ '#109947' ,'#0031DE' , '#F7F022' , '#E831D6' ]
###############给node添加label,Size,color属性
add_node_attributes(df_target_label,df_key_list,df_vlaue_list,color_list)
edge_trace, eweights_trace = get_edge_trace(G)
node_trace = get_node_trace(G)
###############定义color的规则
###############将node_text,node_size,node_color写入list
node_text = []
node_size = []
node_color = []
for node in G.nodes():
x = G.nodes[node]['label']
y = G.nodes[node]['size']
z = G.nodes[node]['color']
node_text.append(x)
node_size.append(y)
node_color.append(z)
# 依据设置label,size,color
node_trace.marker.color = node_color
node_trace.marker.size =node_size
node_trace.text = node_text
fig_target_id=go.Figure()
fig_target_id.add_trace(edge_trace)
fig_target_id.add_trace(node_trace)
fig_target_id.update_layout(
height=1000,
width=1000,
xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
showlegend=False,
hovermode='closest',
)
return fig_target_id
四、调用函数drew_graph,传入2个Dataframe画图。并导出HTML文件
fig =drew_graph(df_target_relation,df_target_label)
fig.show()
fig.write_html(u'YOUR FILE PATH/FTX_Accounts_Drainer.html')
print('end')