Search

Bitcoin Transactions

Large Charts

Explore bitcoin transactions and their blockchain structure.

Bitcoin Transactions
View live example →

Blockchain transactions are often fast-paced and complex.

This demo shows bitcoin transactions and entity resolution.

We can use a graph to spot interesting patterns and activity that would normally be hidden in an unintelligible list of anonymous transactions.

For more information on blockchains and bitcoin transactions, see our Visualizing Bitcoin blocks blog post.

Blockchain Data

Each transaction is linked to nodes representing its inputs and outputs. Bitcoin transactions are public but anonymous.

Users commonly generate a new address for every transaction, but this is not always the case. As it is more convenient and some users are indifferent to anonymity, around half of the addresses in a typical block are reused.

Identical addresses are likely to be from the same wallet, and so we can group transaction inputs and outputs by address to see what activity that address has been involved with.

Demo Views

Each view guides you through the data, looking at both typical and more unusual transactions. Transaction activity is summarised in the time bar below the chart.

This data is a subset of block 611900.

Key functions used:

import KeyLines from "keylines";
import { data } from "./data";

let chart;
let comboIds;
let graph;
let timebar;

const currentViewState = {
  index: 0, // tracks currentViewState
  itemInfo: () => {}, // shows rhs info
  stateTransition: false, // flag to override timebar change event during view changes
};
// Text of differents views
const views = {
  0: document.getElementById("view0"),
  1: document.getElementById("view1"),
  2: document.getElementById("view2"),
  3: document.getElementById("view3"),
};
// Text between buttons
const viewEl = document.getElementById("view");
// State chage buttons
const button = {
  next: document.getElementById("next"),
  prev: document.getElementById("prev"),
};

// Generates and renders the rhs information text
function getSelectedItemInfo() {
  const currencySymbols = { btc: "₿", usd: "$" };
  const contentEl = document.getElementById("content");
  const headerEl = document.getElementById("header");
  function format(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
  }
  function truncate(string) {
    return string.substring(0, Math.min(25, string.length)).concat("...");
  }
  function addCommas(string) {
    return string.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  }
  function highlight(float) {
    const splitString = float.toFixed(2).split(".");
    return `<b>${addCommas(splitString[0])}</b>.${splitString[1]}`;
  }
  const handlers = {
    value: {
      label: "Value",
      content: (d) =>
        `${currencySymbols.btc + d.btc} / ${currencySymbols.usd + highlight(d.usd)}`,
    },
    hash: { label: "Hash", content: (d) => truncate(d) },
    type: { label: "Type", content: (d) => format(d) },
    level: { label: "Time", content: (d) => new Date(d).toUTCString() },
    address: { label: "Address", content: (d) => truncate(d) },
    transaction: {
      label: "Transaction",
      content: (d) => truncate(chart.getItem(d).d.hash),
    },
  };
  function getContent(props) {
    return Object.keys(props)
      .map((key) => {
        const handler = handlers[key];
        return `<tr><td><strong>${handler.label}</strong></td><td>${handler.content(
          props[key],
        )}</td></tr>`;
      })
      .join("");
  }
  return {
    show: (props) => {
      headerEl.style.display = props ? "none" : "block";
      contentEl.innerHTML = props ? getContent(props) : "";
    },
  };
}

// Converts dates to milliseconds
function toMS(dt) {
  return Date.UTC(
    dt.getFullYear(),
    dt.getMonth(),
    dt.getDate(),
    dt.getHours(),
    dt.getMinutes(),
    dt.getSeconds(),
    dt.getMilliseconds(),
  );
}

// Make the timebar range a bit broader
async function setTimebarRange() {
  const offset = 1000;
  const range = await timebar.range();
  await timebar.range(toMS(range.dt1) - offset, toMS(range.dt2) + offset, {
    animate: false,
  });
}

async function foregroundItemsInRange() {
  await chart.foreground(timebar.inRange);
}

async function closeCombos() {
  await chart.combo().close(comboIds, { animate: false });
}

// Hides some autogenerated arrows on combolinks
function hideArrowsFromComboLinks() {
  const props = [];
  chart.each({ type: "link", items: "toplevel" }, (link) => {
    if (chart.combo().isCombo(link.id2)) {
      props.push({ id: link.id, a1: false, a2: false, c2: "#d3d3d3" });
    }
  });
  chart.setProperties(props);
}

async function revealLinks(item) {
  const itemType = item.d.type;
  if (itemType === "transaction") {
    const linksToReveal = [];
    const neighbours = await graph.neighbours(item.id);
    neighbours.links.forEach((linkId) => {
      const link = chart.getItem(linkId);
      const isChildNode = !!chart.getItem(link.id2).parentId;
      const isOpenCombo = !!chart.combo().isOpen(chart.combo().find(link.id2));
      if (isChildNode && isOpenCombo) linksToReveal.push(linkId);
    });
    chart.combo().reveal(linksToReveal);
  } else if ((itemType === "input" || itemType === "output") && item.parentId) {
    const linksToReveal = await graph.neighbours(item.id).links;
    chart.combo().reveal(linksToReveal);
  }
}

function showInfoAndRevealLinks() {
  currentViewState.itemInfo.show();
  chart.combo().reveal([]);
  const item = chart.getItem(chart.selection()[0]);
  if (item && item.type === "node") {
    currentViewState.itemInfo.show(item.d);
    revealLinks(item);
  }
}

// Zoom to fit all items
async function zoomToFit() {
  await Promise.all([
    chart.zoom("fit", { animate: true, time: 1000 }),
    timebar.zoom("fit", { animate: true, time: 1000 }),
  ]);
  await foregroundItemsInRange();
}

// Zoom to specific items
async function zoomToIds(ids) {
  await Promise.all([
    chart.zoom("fit", { animate: true, time: 1000, ids }),
    timebar.zoom("fit", { animate: true, time: 1000, id: ids }),
  ]);
  await setTimebarRange();
  await foregroundItemsInRange();
}

async function dismissChain() {
  await zoomToFit();
  await chart.filter(() => true, { type: "node" });
  await chart.hide(data.chainLinkIds);
  await chart.animateProperties(data.layout.organic);
  comboIds = await chart.combo().combine(data.comboDefs, { select: false });
  hideArrowsFromComboLinks();
  await zoomToFit();
}

async function inspectChain() {
  await zoomToIds(data.chainNodeIds);
  await chart.combo().uncombine(comboIds || data.comboIds, { select: false });
  await chart.filter((node) => data.chainNodeIds.includes(node.id), {
    type: "node",
  });
  await chart.show(data.chainLinkIds);
  await chart.animateProperties(data.layout.sequential);
  await zoomToFit();
}

async function dismissTxs() {
  await chart.filter(() => true, { type: "link", items: "toplevel" });
  await zoomToFit();
}

async function inspectTxs(ids) {
  const neighbours = await chart.graph().neighbours(ids, { hops: Infinity });
  await chart.filter((link) => neighbours.links.includes(link.id), {
    type: "link",
    items: "toplevel",
  });
  await zoomToIds([...ids, ...neighbours.nodes, ...neighbours.links]);
}

// Introduce a 500ms pause to make zooming transitions easier to follow;
async function pauseAnimation() {
  await new Promise((resolve) => setTimeout(resolve, 500));
}

// Action mapping for forward currentViewState changes
async function getPrevState() {
  await {
    0: async () => {
      await dismissTxs();
    },
    1: async () => {
      await dismissTxs();
      await pauseAnimation();
      await inspectTxs(["t1013", "t886"]);
    },
    2: async () => {
      await dismissChain();
      await pauseAnimation();
      await inspectTxs(["t282", "t283"]);
    },
    3: async () => {
      await inspectChain();
    },
  }[currentViewState.index % 4]();
}

// Action mapping for backwards currentViewState changes
async function getNextState() {
  await {
    0: async () => {
      await dismissChain();
    },
    1: async () => {
      await inspectTxs(["t1013", "t886"]);
    },
    2: async () => {
      await dismissTxs();
      await pauseAnimation();
      await inspectTxs(["t282", "t283"]);
    },
    3: async () => {
      await dismissTxs();
      await pauseAnimation();
      await inspectChain();
    },
  }[currentViewState.index % 4]();
}

function disableUI() {
  button.prev.disabled = true;
  button.next.disabled = true;
}

function enableUI() {
  button.prev.disabled = currentViewState.index === 0;
  button.next.disabled = false;
}

// Update view text and text between buttons
function updateView() {
  Object.keys(views).forEach((i) => {
    views[i].style.display = "none";
  });
  views[currentViewState.index % 4].style.display = "block";
  viewEl.innerHTML = `${(currentViewState.index % 4) + 1} of 4`;
}

function enableUserInteraction() {
  enableUI();
  chart.on("selection-change", showInfoAndRevealLinks);
  // Disable node dragging
  chart.on("drag-start", ({ preventDefault, type }) => {
    if (type === "node") {
      preventDefault();
    }
  });

  timebar.on("change", foregroundItemsInRange);

  // Click handlers
  const handlers = {
    prev: async () => {
      currentViewState.index--;
      updateView();
      await getPrevState();
    },
    next: async () => {
      currentViewState.index++;
      updateView();
      await getNextState();
    },
  };

  ["prev", "next"].forEach((id) => {
    button[id].addEventListener(
      "click",
      async () => {
        // Override change event when zooming and manually foreground items
        timebar.off("change");
        closeCombos();
        disableUI();
        await handlers[id]();
        enableUI();
        timebar.on("change", foregroundItemsInRange);
      },
      false,
    );
  });
}

async function startKeyLines() {
  graph = KeyLines.getGraphEngine();
  graph.load(data);
  currentViewState.itemInfo = getSelectedItemInfo();

  const chartOpts = {
    backColour: "#282828",
    controlTheme: "dark",
    defaultStyles: { comboGlyph: null },
    drag: { links: false },
    handMode: true,
    iconFontFamily: "Font Awesome 5 Free",
    imageAlignment: { "fas fa-exchange-alt": { e: 0.7 } },
    minZoom: 0.001,
    overview: { icon: false, shown: false },
    selectionColour: "#d3d3d3",
  };

  const timebarOpts = {
    area: { colour: "#d24dff" },
    backColour: "#282828",
    controlBarTheme: "dark",
    scale: { highlightColour: "#363636" },
    sliders: "none",
    type: "area",
  };

  [chart, timebar] = await KeyLines.create([
    { container: "klchart", type: "chart", options: chartOpts },
    { container: "kltimebar", type: "timebar", options: timebarOpts },
  ]);

  await chart.load(data);
  await timebar.load(data);
  await zoomToFit();
  hideArrowsFromComboLinks();
  enableUserInteraction();
}

function loadWebFonts() {
  document.fonts.load('24px "Font Awesome 5 Free"').then(startKeyLines);
}

window.addEventListener("DOMContentLoaded", loadWebFonts);
export const data = {
  type: "LinkChart",
  chainLinkIds: [
    "c172",
    "c174",
    "c171",
    "c175",
    "c157",
    "c176",
    "c156",
    "c177",
    "c155",
    "c178",
    "c124",
    "c179",
    "c123",
    "c180",
    "c122",
    "c181",
    "c182",
    "c183",
    "c184",
    "c185",
    "c186",
    "c210",
    "c211",
    "c212",
  ],
  chainNodeIds: [
    "t1162",
    "i2569",
    "o2922",
    "o2923",
    "o2918",
    "i2570",
    "t1159",
    "t1163",
    "i2566",
    "o2917",
    "o2924",
    "o2925",
    "o2916",
    "i2571",
    "t1158",
    "t1164",
    "i2565",
    "o2915",
    "o2926",
    "o2927",
    "o2743",
    "i2572",
    "t1075",
    "t1165",
    "i2409",
    "o2742",
    "o2928",
    "o2929",
    "o2741",
    "i2573",
    "t1074",
    "t1166",
    "i2408",
    "o2740",
    "o2930",
    "o2931",
    "o2739",
    "i2574",
    "t1073",
    "t1167",
    "i2407",
    "o2738",
    "o2932",
    "o2933",
    "o2466",
    "i2575",
    "t975",
    "t1168",
    "i2168",
    "o2465",
    "o2934",
    "o2935",
    "o2464",
    "i2576",
    "t974",
    "t1169",
    "i2167",
    "o2463",
    "o2936",
    "o2937",
    "o2462",
    "i2577",
    "t973",
    "t1170",
    "i2166",
    "o2461",
    "o2938",
    "o2939",
    "i2578",
    "t1171",
    "o2940",
    "o2941",
    "i2579",
    "t1172",
    "o2942",
    "o2943",
    "i2580",
    "t1173",
    "o2944",
    "o2945",
    "o2946",
    "i2581",
    "t1174",
    "o2947",
    "o2948",
    "i2763",
    "t1304",
    "o3213",
    "o3214",
    "i2764",
    "t1305",
    "o3215",
    "o3216",
    "i2765",
    "t1306",
    "o3217",
    "o3218",
    "i2766",
    "t1307",
    "o3219",
    "o3220",
  ],
  comboIds: [
    "p297",
    "p299",
    "p300",
    "p296",
    "p301",
    "p272",
    "p302",
    "p271",
    "p303",
    "p270",
    "p304",
    "p249",
    "p305",
    "p248",
    "p306",
    "p307",
    "p247",
    "p308",
    "p309",
    "p310",
    "p311",
    "p312",
    "p313",
    "p345",
    "p346",
    "p347",
  ],
  comboDefs: [
    {
      ids: ["o2462", "i2167"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3Q16JiLL6g6mMQUHGphzHs3Xs91NLf97Z8",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.11920904, 0.11920904],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2464", "i2168"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3MXJ4B9NFJacvuNy2mrzDUZWQairz6xvTn",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.11904637, 0.11904637],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2466", "i2407"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3DnCSFF56ddHp1K4nutDtqjzRqBNAg51rd",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.11807599, 0.11807599],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2739", "i2408"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3AGByVZLSns5Mg3G25nNhsXqXYSuScP2Cr",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.11736389, 0.11736389],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2741", "i2409"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3B3jbAfFccp3jqPm3kSHrPrY818bCNJMU2",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.11403361, 0.11403361],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2743", "i2565"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3Dan6U5PCSHmYk43ixwjZDxBYcTEEBYDT8",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.11248838, 0.11248838],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2916", "i2566"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "35ArfsDSVPrCjrRYmyVrYhjfaX8JuWThHC",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.11057369, 0.11057369],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2918", "i2569"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3Ed6Ss1Fb2M72APjYKdvPhkNRnDrEWhDC7",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.1093351, 0.1093351],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2922", "o3213"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3HzNzgAEDJJhAxLsABhMzuWUe2BGMKVkEM",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.0013701400000000002, 0],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 0,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 2,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2923", "i2570"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3EYJuira3E3rroyh1nsVWq7fgQaiPLtXk6",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.10871084, 0.10871084],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2925", "i2571"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3J68AUYk13BbYZq1pLtwwYRsyDwWfLmzud",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.1081993, 0.1081993],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2927", "i2572"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3Qa9VMVddmmAAajYwqLBj8LbtuvX2qtVRp",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.10784356, 0.10784356],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2929", "i2573"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "37iFM4M318yoG4jnBJJUWKbCBdTd3bPcuR",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.10710104, 0.10710104],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2931", "i2574"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3G4f7YbMYwt3QKqYqKUZHdYzZSws3G6oZw",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.10690306, 0.10690306],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2933", "i2575"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3NqAe2s6yDVSsYfugp8uTawdDcQYKEEUcP",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.10676472, 0.10676472],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2934", "o2936", "o3215", "o3217", "o3219"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "18jLvnG43HSe5kahL8veG3h4ts28LrNqg4",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.0023462400000000003, 0],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 0,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 5,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2935", "i2576"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "35b6tm2JP43pvEtpziXTdTtKyXXbW2QXUs",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.10616046, 0.10616046],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2937", "i2577"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3HfhnXTmuJ9Q6pZrx2bajepotsFS1GaXLz",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.10597464, 0.10597464],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2939", "i2578"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3P5VJvfUs4m8sveX5W5LA4F9ZEUT7U9Mp2",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.09384145, 0.09384145],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2941", "i2579"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3GFrjhxYVT3ddBhhQ1WPAohLaSFcVTBrrY",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.09196385, 0.09196385],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },
        e: 3,
        g: [
          {
            p: 270,
            t: 1,
            c: "#04b5e5",
            b: "#04b5e5",
            fc: "#282828",
            r: 12,
          },
          {
            p: 90,
            t: 1,
            c: "#f2a900",
            b: "#f2a900",
            fc: "#282828",
            r: 12,
          },
        ],
      },
    },
    {
      ids: ["o2943", "i2580"],
      open: false,
      style: {
        d: {
          type: "address",
          address: "3PVR9yiKvopd3xtxP2d7ByQpMXgYhGRhPP",
        },
        c: "#282828",
        bw: 0,
        donut: {
          v: [0.08615072, 0.08615072],
          c: ["#f2a900", "#04b5e5"],
          bw: 0,
        },
        oc: {
          c: "#282828",
          b: "#d3d3d3",
          bw: 10,
        },

// ...truncated 367605 lines
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Bitcoin Transactions</title>
    <link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/keylines.css" />
    <link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/minimalsdk.css" />
    <link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/sdk-layout.css" />
    <link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/demo.css" />
    <link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/examples-chrome.css" />
    <link
      rel="stylesheet"
      type="text/css"
      href="@fortawesome/fontawesome-free@5.15.4/css/fontawesome.css"
    />
    <link
      rel="stylesheet"
      type="text/css"
      href="@fortawesome/fontawesome-free@5.15.4/css/solid.css"
    />
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div class="chart-wrapper demo-cols">
      <div id="lhs" class="flex1">
        <div id="lhsVisual" class="toggle-content is-visible" style="width: 100%; height: 100%">
          <div id="klchart" class="klchart klchart-timebar"></div>
          <div id="kltimebar" class="kltimebar"></div>
        </div>
      </div>
      <div class="rhs citext" id="demorhscontent">
        <div class="tab-content-panel">
          <div id="controlsTab" class="toggle-content is-visible tabcontent">
            <form id="rhsForm" autocomplete="off" onsubmit="return false;">
              <div class="cipad cicontent">
                <div class="buttons">
                  <input
                    class="btn"
                    type="button"
                    value="&lt;"
                    disabled
                    style="margin: 10px"
                    id="prev"
                  />
                  <div id="view">1 of 4</div>
                  <input
                    class="btn"
                    type="button"
                    value="&gt;"
                    disabled
                    style="margin: 10px"
                    id="next"
                  />
                </div>
                <div class="view" style="display: block" id="view0">
                  <p>Blockchain transactions are often fast-paced and complex.</p>
                  <p>This view shows bitcoin transactions during a period of 6 minutes.</p>
                  <p>
                    Transactions, inputs and outputs are represented by nodes, with links
                    representing involvement in a transaction.
                  </p>
                  <p>Interact with the time bar to filter transactions outside the range.</p>
                </div>
                <div class="view" style="display: none" id="view1">
                  <p>Bitcoin transactions have an input, output, and value.</p>
                  <p>
                    The transaction on the right shows the sale by one address (blue node) to 2
                    others (orange nodes).
                  </p>
                  <p>
                    The transaction on the left shows a transaction where an input and an output
                    share the same address. This is common when change from a transaction is
                    returned to the original address.
                  </p>
                </div>
                <div class="view" style="display: none" id="view2">
                  <p>Large transactions may feature many addresses combining their resources.</p>
                  <p>Typically this indicates multiple addresses tied to the same wallet.</p>
                  <p>
                    The transactions here involve two large sums of money (totalling over US
                    $72,000) being transferred in quick succession.
                  </p>
                </div>
                <div class="view" style="display: none" id="view3">
                  <p>
                    Transactions can form long chains when the same bitcoin are quickly transferred
                    between addresses.
                  </p>
                  <p>The sequential layout can show the progress of capital over time.</p>
                  <p>
                    Addresses that are involved in multiple transactions within a single blockchain
                    form closed loops in the graph, and are highlighted here in red and green.
                  </p>
                </div>
                <div style="text-align: center"><h3>Node Legend</h3></div>
                <div class="svg-grid">
                  <svg class="c1">
                    <circle
                      cx="20"
                      cy="20"
                      r="14"
                      stroke="#04b5e5"
                      fill="#282828"
                      stroke-width="6"
                    ></circle>
                    <text x="45" y="24.5" fill="#282828">Input</text></svg
                  ><svg class="c2">
                    <circle
                      cx="20"
                      cy="20"
                      r="14"
                      stroke="#f2a900"
                      fill="#282828"
                      stroke-width="6"
                    ></circle>
                    <text x="45" y="24.5" fill="#282828">Output</text></svg
                  ><svg class="c3">
                    <circle
                      cx="20"
                      cy="20"
                      r="14"
                      stroke="#d3d3d3"
                      fill="#282828"
                      stroke-width="6"
                    ></circle>
                    <text class="fas" x="13" y="24.5" fill="#d24dff">&#xf362;</text>
                    <text x="45" y="24.5" fill="#282828">Transaction</text></svg
                  ><svg class="c4">
                    <circle
                      cx="20"
                      cy="20"
                      r="14"
                      stroke="#f2a900"
                      fill="#282828"
                      stroke-width="6"
                    ></circle>
                    <circle cx="16" cy="20" r="3" fill="#04b5e5"></circle>
                    <circle cx="24" cy="20" r="3" fill="#f2a900"></circle>
                    <text x="45" y="24.5" fill="#282828">Shared Address</text>
                  </svg>
                </div>
                <div style="text-align: center; pointer-events: none">
                  <h3>Details</h3>
                  <h5 id="header">Click on nodes for further information</h5>
                  <table>
                    <tbody id="content"></tbody>
                  </table>
                </div>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
    <script type="module" src="./code.js"></script>
  </body>
</html>
/* --- bitcoin.css --- */
.klchart {
  border: none;
  background-color: #282828;
}
.kltimebar {
  border: none;
  border-top: 1px dashed #333;
  background-color: #282828;
}
td {
  text-align: left;
  padding: 5px;
}
#view {
  width: 40px;
}
.view {
  min-height: 220px;
}
.buttons {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 60px;
}
.svg-grid > svg {
  width: 100%;
  height: 40px;
}
.svg-grid {
  margin: 20px;
  display: grid;
  grid-gap: 5px;
  grid-template: 1fr 1fr/ 1fr 1fr;
}
.c1 {
  grid-row: 1/2;
  grid-column: 1/2;
}
.c2 {
  grid-row: 1/2;
  grid-column: 2/-1;
}
.c3 {
  position: relative;
  grid-row: 2/-1;
  grid-column: 1/2;
}
.c4 {
  grid-row: 2/-1;
  grid-column: 2/-1;
}

Terms of use

These terms do not alter or supersede any existing agreements between you (or your employer) and us.

By accessing or using any Content you agree to be bound by these Terms of Use. Please review these terms carefully before using the website.

The contents of this website, including but not limited to any text, code samples, API references, schemas, interactive tools, and other materials (collectively, the 'Content'), are made available for informational and internal evaluation purposes only. All intellectual property rights in the Content are reserved. No licence is granted to use the Content for any commercial purpose, or to copy, distribute, modify, reverse-engineer, or incorporate any part of the Content into any product or service, without our prior written consent.

This Content is provided “as is” and “as available,” without any representations, warranties, or guarantees of any kind, whether express or implied, including but not limited to implied warranties of merchantability, fitness for a particular purpose, non-infringement, or accuracy. To the fullest extent permitted by applicable law, we expressly exclude and disclaim all implied warranties, conditions, and other terms that might otherwise be implied.

We disclaim all liability for any loss or damage, whether direct, indirect, incidental, consequential, or otherwise, arising from any reliance placed on the Content or from your use of it, to the fullest extent permitted by applicable law. By continuing to access or use the Content, you acknowledge and agree to these terms.