Wiki Home

Wasabi CoinGraph generation

2019-07-02

Create a visual representation from a Wasabi coins dump.

How to run it

Run Wasabi, open the wallet you are interested in.

Next open a terminal and enter:

dotnet fsi coinsgraph.fsx <wallet-name> <initial-txid> | dot -Tpng | feh  -

The result

The Code

#r "nuget:FSharp.Data"

open System
open System.IO
open System.Net.Http
open FSharp.Data

type Config = JsonProvider<"""{
  "JsonRpcServerEnabled": true,
  "JsonRpcUser": "",
  "JsonRpcPassword": "",
  "JsonRpcServerPrefixes": [
    "http://127.0.0.1:37128/"
  ]
}""">

type RpcResponse = JsonProvider<"""{
  "result": [
     {"txid":"73af1dd","index":0,"amount":2390000,"anonymitySet":"a1.0","confirmed":true,"confirmations":116,"keyPath":"84/0","address":"tb1q","spentBy":"2d7c3f"}
  ]
}""">

let config = Config.Load(
  Path.Combine (
    Environment.ExpandEnvironmentVariables ("%HOME%/.walletwasabi/client/"),
    "Config.json"))

let args = Environment.GetCommandLineArgs()
let walletname = args[2]
let firstTxId = args[3] 

let http = new HttpClient()
let rpcJsonResponseAsync () = async {
  let content = new StringContent ($"{{\"jsonrpc\":\"2.0\", \"id\":\"id\", \"method\":\"selectwallet\", \"params\":[\"{walletname}\"]}}")
  let! response = http.PostAsync(config.JsonRpcServerPrefixes[0], content) |> Async.AwaitTask

  let content = new StringContent ("{\"jsonrpc\":\"2.0\", \"id\":\"id\", \"method\":\"listcoins\"}")
  let! response = http.PostAsync(config.JsonRpcServerPrefixes[0], content) |> Async.AwaitTask
  let! jsonResult = response.Content.ReadAsStringAsync() |> Async.AwaitTask
  return RpcResponse.Parse(jsonResult)
} 

let rpcJsonResult = rpcJsonResponseAsync () |> Async.RunSynchronously

let coins = 
    rpcJsonResult.Result 
    |> Array.skipWhile (fun x -> x.Txid <> firstTxId)

let coinsGroupedByTx =
    coins
    |> Array.groupBy (fun x -> x.Txid)

let graphNodes = 
    coinsGroupedByTx
    |> Seq.map ( fun (txid, outs) ->
      let indexes = outs |> Array.map (fun x -> string x.Index) |> String.concat "|"
      let paths = outs |> Array.map (fun x -> x.KeyPath) |> String.concat "|"
      let anonsets = outs |> Array.map (fun x -> x.AnonymitySet) |> String.concat "|"
      let amounts = outs |> Array.map (fun x -> $"<idx{x.Index}>{x.Amount}") |> String.concat "|"
      let addresses = outs |> Array.map (fun x -> x.Address) |> String.concat "|" 
      $"tx{txid} [label=\"Tx Id: {txid[..8]}|{{{{ {{ {{ PrvScore | {anonsets} }} | {{ Path | {paths} }} | {{ Idx | {indexes} }} | {{ Address | {addresses} }} |  {{ Amount | {amounts} }} }}}} }}}}\"]"
    )
    |> List.ofSeq
        
let graphEdges =
    coins 
    |> Array.filter (fun x -> x.SpentBy <> "")
    |> Array.map (fun x -> $"tx{x.Txid}:idx{x.Index} -> tx{x.SpentBy}")
    |> List.ofArray

let allLines =
    graphNodes @ graphEdges
    |> String.concat ";\n"

let str = $"""
digraph G {{
graph [center=1 rankdir=LR; overlap=false; splines=true;];
edge [dir=forward];
node [shape=record; ordering="in" ];
{allLines}
}}"""

Console.WriteLine(str)

Dependencies

Make sure the following packages are installed (at least on debian)