本文介绍了如何利用QuickNode的Blockbook RPC add-on生成详细的比特币交易报告。通过分步教程,开发者和金融分析师可以使用该工具获取、分析和展示比特币交易,并以符合监管要求的格式呈现。文章详细说明了使用Blockbook API查询比特币交易、处理交易数据、生成报告以及将报告保存为CSV文件的完整流程。
在当今的数字金融领域,比特币交易报告的清晰度和合规性对于在区块链技术复杂性中游刃有余的企业至关重要。本指南演示了创建详细比特币交易报告的过程,利用了 QuickNode 的 Blockbook RPC add-on 的强大功能。本分步教程面向开发人员和金融分析师,将为你配备构建报告工具的工具,该工具能够以全面、符合法规的格式获取、分析和呈现比特币交易。
依赖 | 版本 |
---|---|
node.js | >18.16 |
typescript | 最新 |
ts-node | 最新 |
QuickNode 的 BTC Blockbook JSON-RPC Add-On 是一款强大的比特币交易跟踪工具,它可以通过简单的 JSON-RPC 接口轻松访问地址或 xpub 的余额、交易和 UTXO。借助 Blockbook,用户可以获得:
利用 Blockbook 进行区块链数据处理不仅有助于实现精确和合规的报告,还可以激发交易监控、资产管理等方面的创造性解决方案。
在撰写本文时,Blockbook add-on 包括 10 种强大的 RPC 方法。在本指南中,我们将利用其中的两种:
bb_getAddress
: 返回地址的余额和交易。bb_getTickers
: 返回指定货币和日期的货币汇率。如果该货币在该特定时间戳不可用,则将返回下一个最接近的汇率。在开始之前,请注意 Blockbook RPC 是一项付费 add-on。请 在此处 查看详细信息,并根据你的需求比较方案。
使用 Blockbook RPC 设置你的比特币节点非常简单。如果你尚未注册,可以在 此处 创建一个帐户。
登录后,导航到 Endpoints 页面,然后单击 Create an endpoint。选择 Bitcoin mainnet,然后单击 Next。然后,系统会提示你配置 add-on。激活 Blockbook RPC。之后,只需单击 Create Endpoint。
如果你已经有一个没有 add-on 的比特币节点,请转到比特币节点中的 Add-ons 页面,选择 Blockbook RPC,然后激活它。
准备好节点后,复制 HTTP Provider 链接并放在手边,因为你将在下一节中需要它。
首先,确保你的计算机上安装了 Node.js。Node.js 将作为运行应用程序的基础,而 npm(默认包管理器,随 Node.js 安装一起提供)将有效地管理所有依赖项。有关安装说明,请参阅 他们的官方页面。
此外,如果你之前没有安装 TypeScript,请全局安装 TypeScript 和 ts-node,以便通过运行以下命令在所有项目中使用 TypeScript。
npm install -g typescript
npm install -g ts-node
创建一个项目文件夹并在该文件夹中初始化一个新的 Node.js 项目:
mkdir bitcoin-transaction-reports
cd bitcoin-transaction-reports
npm init -y
tsc -init --resolveJsonModule true
安装必要的包:
npm install axios date-fns dotenv fs-extra
npm i --save-dev @types/fs-extra
📘 Packages
在我们深入编写比特币交易报告工具的代码之前,让我们花一点时间来了解其操作流程。这个前奏将确保我们不会盲目地进行编码,而是清楚地了解每个部分如何组合在一起以实现我们的目标。以下是流程的摘要:
导入依赖项:每个脚本文件都以导入必要的库和模块开始。例如,app.ts 可能会导入 fs-extra 用于文件操作,以及来自 blockbookMethods.ts 和 generateReport.ts 的特定函数用于数据检索和报告生成。
获取交易数据:在 app.ts 中,调用来自 blockbookMethods.ts 的 bb_getaddress 函数,传入一个比特币地址以从 Blockbook 收集其交易历史。
处理数据:获取数据后,来自 calculateVariables.ts 的 calculateVariables 函数会处理该数据。
生成报告:处理数据后,app.ts 调用来自 generateReport.ts 的 generateReportForAddress,然后将该数据格式化为一份全面的报告。
将报告保存为 CSV:最后,app.ts 使用来自 fs-extra 模块的 fs.writeFileSync 将格式化的报告保存到 CSV 文件中。
现在,让我们开始编写代码。
在你的项目目录(即 bitcoin-transaction-reports)中创建必要的文件:
echo > .env
echo > app.ts
echo > blockbookMethods.ts
echo > calculateVariables.ts
echo > generateReport.ts
echo > interfaces.ts
📘 Files
.env:存储环境变量,例如你的 QuickNode 节点 URL,确保安全地管理敏感信息并轻松配置。
app.ts:充当应用程序的入口点,通过调用函数来获取数据、生成报告和根据用户定义的参数管理输出来协调流程。
blockbookMethods.ts:包含与 Blockbook API 交互的函数,包括获取给定比特币地址的交易数据。
calculateVariables.ts:处理由 blockbookMethods.ts 获取的区块链数据的逻辑。
generateReport.ts:将处理后的区块链数据格式化为结构化报告(通常为 CSV 格式),以便于分析和共享。
interfaces.ts:定义 TypeScript 接口以确保整个应用程序的类型安全和清晰度,特别是对于 Blockbook API 调用返回并在整个报告生成过程中使用的数据结构。
将 QuickNode 节点和其他敏感信息(如果有)存储在 .env 文件中。
打开 .env 文件并按如下所示修改它。不要忘记将 YOUR_BITCOIN_QUICKNODE_ENDPOINT 占位符替换为你的 QuickNode 比特币 HTTP 提供商 URL。
.env
QUICKNODE_ENDPOINT = "YOUR_BITCOIN_QUICKNODE_ENDPOINT"
blockbookMethods.ts 文件包含两个基本函数 bb_getaddress 和 bb_gettickers,旨在通过 Blockbook API 与 QuickNode 节点进行交互。这些函数分别有助于获取特定比特币地址的详细交易数据,并在给定的时间戳获取货币兑换率。
使用你的代码编辑器打开 blockbookMethods.ts 文件并按如下所示修改该文件。
blockbookMethods.ts
// 导入必要的类型和库
import { Result, PriceData } from "./interfaces";
import axios from "axios";
import dotenv from "dotenv";
dotenv.config(); // 初始化 dotenv 以使用环境变量
// 从环境变量中检索 QuickNode 节点 URL
const QUICKNODE_ENDPOINT = process.env.QUICKNODE_ENDPOINT as string;
// 获取指定比特币地址的详细交易数据
export async function bb_getaddress(address: string): Promise<Result> {
try {
// 准备 bb_getaddress 方法的请求有效负载
const postData = {
method: "bb_getaddress",
params: [
address,
{ page: "1", size: "1000", fromHeight: "0", details: "txs" }, // 查询参数
],
id: 1,
jsonrpc: "2.0",
};
// 向 QuickNode 节点发出 POST 请求
const response = await axios.post(QUICKNODE_ENDPOINT, postData, {
headers: { "Content-Type": "application/json" },
maxBodyLength: Infinity,
});
// 检查响应是否成功并返回数据
if (response.status === 200 && response.data) {
return response.data.result;
} else {
throw new Error("Failed to fetch transactions");
}
} catch (error) {
console.error(error);
throw error;
}
}
// 获取给定时间戳的货币兑换率
export async function bb_gettickers(timestamp: number): Promise<PriceData> {
try {
// 准备 bb_gettickers 方法的请求有效负载
const postData = {
method: "bb_gettickers",
params: [{ timestamp: timestamp }], // 查询参数
id: 1,
jsonrpc: "2.0",
};
// 向 QuickNode 节点发出 POST 请求
const response = await axios.post(QUICKNODE_ENDPOINT, postData, {
headers: { "Content-Type": "application/json" },
maxBodyLength: Infinity,
});
// 检查响应是否成功并提取所需数据
if (response.status === 200 && response.data) {
return {
ts: response.data.result.ts, // 时间戳
rates: { usd: response.data.result.rates.usd }, // 转换率
};
} else {
throw new Error("Failed to fetch tickers");
}
} catch (error) {
console.error(error);
throw error;
}
}
calculateVariables.ts 文件通过计算交易前和交易后的 BTC 余额、识别每笔交易的日期和方向以及计算 BTC 转移的美元等值,从而微调特定比特币地址的交易数据。它会智能地过滤交易,仅包括与指定地址相关的交易,而忽略不直接涉及我们地址的多地址交易。
使用你的代码编辑器打开 calculateVariables.ts 文件并按如下所示修改该文件。该代码段包含注释,以便更好地理解。
calculateVariables.ts
// 导入必要的类型和库
import { format, startOfDay, endOfDay, isWithinInterval } from "date-fns";
import { Result, ExtendedTransaction, ExtendedResult } from "./interfaces";
import { bb_gettickers } from "./blockbookMethods";
export async function calculateVariables(
result: Result,
startDate: Date = new Date(), // 如果未提供,则默认为当前日期
endDate: Date = new Date() // 如果未提供,则默认为当前日期
): Promise<ExtendedResult> {
let extendedTransactions = [];
// 将开始日期和结束日期转换为各自日期的开始和结束
const startOfPeriod = startOfDay(startDate);
const endOfPeriod = endOfDay(endDate);
// 从 Satoshi 到 BTC 的转换率
const btcToSatoshi = 100000000;
// 将字符串 currentBalance 转换为数字以进行计算,然后转换回字符串以进行存储。
let currentBalance = parseFloat(result.balance) / btcToSatoshi;
let direction;
let balanceBeforeTx = 0,
balanceAfterTx = 0,
cumulativeDiff = 0;
// 迭代与地址关联的每笔交易
for (const transaction of result.transactions) {
const blockTime = new Date(transaction.blockTime * 1000); // 将时间戳转换为 Date 对象
// 检查交易日期是否在指定日期范围内
const withinInterval = isWithinInterval(blockTime, {
start: startOfPeriod,
end: endOfPeriod,
})
? true
: false;
// 格式化报告的日期和时间戳
const day = format(blockTime, "yyyy-MM-dd");
const timestamp = blockTime.toISOString();
// 确定该地址是否是任何交易输入中的发送者
const vinIsSender = transaction.vin.some((vin) =>
vin.addresses.includes(result.address)
);
// 确定交易是否已确认
const type = transaction.confirmations === 0 ? "Unconfirmed" : "Confirmed";
// 根据地址的角色分配交易方向
direction = vinIsSender ? "Outgoing" : "Incoming";
let fromAddresses = "";
let toAddresses = "";
let btcAmount = 0,
usdAmount = 0,
btcFees = 0,
usdFees = 0;
// 地址是发送者时的逻辑
if (vinIsSender) {
// 筛选交易输入 (vin) 以查找那些包含数据地址的输入
// 表示我们的地址发送比特币的交易
const vinSelfRecipient = transaction.vin.filter((vin) =>
vin.addresses.includes(result.address)
);
// 通过查找包含数据地址的输出 (vout) 来检查是否存在“发回”交易
const isSentBack = transaction.vout.some(
(vout) => vout.addresses && vout.addresses.includes(result.address)
);
// 筛选交易输出 (vout) 以排除那些发送回发送者地址的输出,
// 重点关注发送到其他地址的实际交易输出
const voutRecipient = transaction.vout.filter(
(vout) => !vout.addresses.includes(result.address)
);
// 筛选交易输出 (vout) 以查找那些包含数据地址的输出
// 表示与我们的地址进行的交易
const voutSelfRecipient = transaction.vout.filter((vout) =>
vout.addresses.includes(result.address)
);
// 此计算确定与数据地址关联的实际金额
if (isSentBack) {
const btcAmountIn = vinSelfRecipient.reduce(
(acc, vin) => acc + parseFloat(vin.value),
0
);
const btcAmountOut = voutSelfRecipient.reduce(
(acc, vout) => acc + parseFloat(vout.value),
0
);
btcAmount = (btcAmountIn - btcAmountOut) / btcToSatoshi;
} else {
btcAmount =
vinSelfRecipient.reduce(
(acc, vin) => acc + parseFloat(vin.value),
0
) / btcToSatoshi;
}
fromAddresses = result.address; // 发送者地址
toAddresses = voutRecipient
.map((vout) => vout.addresses.join(", "))
.join(", "); // 连接收件人地址
} else {
// 地址是收件人时的逻辑
btcAmount =
transaction.vout
.filter((vout) => vout.addresses.includes(result.address))
.reduce((acc, vout) => acc + parseFloat(vout.value), 0) /
btcToSatoshi;
fromAddresses = transaction.vin
.map((vin) => vin.addresses.join(", "))
.join(", "); // 连接发送者地址
toAddresses = result.address; // 收件人地址
}
if (withinInterval) {
// 获取当前价格数据以进行准确的美元转换
const priceData = await bb_gettickers(transaction.blockTime);
// 以美元计算费用和金额
btcFees = parseFloat(transaction.fees) / btcToSatoshi;
usdFees = btcFees * priceData.rates.usd;
usdAmount = btcAmount * priceData.rates.usd;
}
if (direction === "Outgoing") {
cumulativeDiff -= btcAmount;
} else {
cumulativeDiff += btcAmount;
}
balanceBeforeTx = currentBalance - cumulativeDiff;
if (direction === "Outgoing") {
balanceAfterTx = balanceBeforeTx - btcAmount;
} else {
balanceAfterTx = balanceBeforeTx + btcAmount;
}
let extendedTransaction: ExtendedTransaction = {
...transaction,
day,
timestamp,
direction,
fromAddresses,
toAddresses,
btcAmount,
usdAmount,
btcFees,
usdFees,
type,
balanceBeforeTx,
balanceAfterTx,
withinInterval,
};
extendedTransactions.push(extendedTransaction);
}
// 筛选时间间隔内的交易
const filteredTransactions = extendedTransactions.filter(
(transaction) => transaction.withinInterval
);
// 解构原始结果以排除 transactions 属性
const { transactions, ...rest } = result;
// 使用剩余的属性并添加 extendedTransactions
const extendedResult: ExtendedResult = {
...rest,
extendedTransactions: filteredTransactions,
startDate: startOfPeriod,
endDate: endOfPeriod,
};
return extendedResult;
}
generateReport.ts 文件旨在生成比特币交易的综合报告,其中包含已处理和筛选的数据。
使用你的代码编辑器打开 generateReport.ts 文件并按如下所示修改该文件。该代码段包含注释,以便更好地理解。
generateReport.ts
import { ExtendedResult } from "./interfaces";
import { format } from "date-fns";
// 根据比特币地址和日期范围生成报告的函数
export function generateReportForAddress(
extendedData: ExtendedResult
): [string, string] {
// 记录报告生成过程
console.log(
**Generating transaction report for Bitcoin address (${
extendedData.address
}) from ${format(extendedData.startDate, "yyyy-MMMM-dd")} to ${format(
extendedData.endDate,
"yyyy-MMMM-dd"
)}**
);
// 准备 CSV 标头
let reportLines: string[] = [
"Day;Timestamp;Tx;Type;Direction;From;To;Amount [BTC];Amount [USD];Fees [BTC];Fees [USD];Pre Balance;Post Balance",
];
// 数据行
for (const item of extendedData.extendedTransactions) {
// 将交易详细信息添加到报告中
reportLines.push(
`${item.day};${item.timestamp};${item.txid};${item.type};${
item.direction
};${item.fromAddresses};${item.toAddresses};${item.btcAmount.toFixed(
8
)};${item.usdAmount.toFixed(2)};${item.btcFees.toFixed(
8
)};${item.usdFees.toFixed(2)};${item.balanceBeforeTx.toFixed(8)};${
item.balanceAfterTx.toFixed(
8
)
}`
);
}
const fileName = `transaction_report_${extendedData.address}_${format(
extendedData.startDate,
"yyyy-MMMM-dd"
)}_${format(extendedData.endDate, "yyyy-MMMM-dd")}.csv`;
// 连接所有行以形成 CSV 内容
return [reportLines.join("\n"), fileName];
}
interfaces.ts 文件定义了一组 TypeScript 接口,用于构造从比特币区块链检索的数据和相关的财务信息,从而促进类型安全开发和与区块链数据的交互。
使用你的代码编辑器打开 interfaces.ts 文件并按如下所示修改该文件。
interfaces.ts
// 定义区块链查询的整体结果的结构,
export interface Result {
page: number;
totalPages: number;
itemsOnPage: number;
address: string;
balance: string;
totalReceived: string;
totalSent: string;
unconfirmedBalance: string;
unconfirmedTxs: number;
txs: number;
transactions: Transaction[];
}
// 表示单个比特币交易的详细信息。
export interface Transaction {
txid: string;
version: number;
vin: Vin[];
vout: Vout[];
blockHash: string;
blockHeight: number;
confirmations: number;
blockTime: number;
size: number;
vsize: number;
value: string;
valueIn: string;
fees: string;
hex?: string;
}
export interface ExtendedTransaction extends Transaction {
day: string;
timestamp: string;
direction: string;
fromAddresses: string;
toAddresses: string;
btcAmount: number;
usdAmount: number;
btcFees: number;
usdFees: number;
type: string;
balanceBeforeTx: number;
balanceAfterTx: number;
withinInterval: boolean;
}
export interface ExtendedResult {
page: number;
totalPages: number;
itemsOnPage: number;
address: string;
balance: string;
totalReceived: string;
totalSent: string;
unconfirmedBalance: string;
unconfirmedTxs: number;
txs: number;
extendedTransactions: ExtendedTransaction[];
startDate: Date;
endDate: Date;
}
// 表示比特币交易中的输入。
export interface Vin {
txid: string;
vout?: number;
sequence: number;
n: number;
addresses: string[];
isAddress: boolean;
value: string;
hex: string;
isOwn?: boolean;
}
// 表示比特币交易中的输出。
export interface Vout {
value: string;
n: number;
hex: string;
addresses: string[];
isAddress: boolean;
spent?: boolean;
isOwn?: boolean;
}
// 表示价格数据,包括时间戳和货币汇率。
export interface PriceData {
ts: number;
rates: Rates;
}
// 包含货币兑换率,例如从比特币到美元。
export interface Rates {
usd: number;
}
app.ts 文件与区块链交互以提取有意义的数据并以用户友好的格式呈现它,使其成为需要深入了解与特定地址关联的比特币交易的任何人的宝贵工具。
使用你的代码编辑器打开 app.ts 文件并按如下所示修改该文件。
不要忘记将 THE_BITCOIN_ADDRESS 占位符替换为将为其生成报告的比特币地址。在本指南中,我们将使用 Coinbase 的一个热钱包 ( 3MqUP6G1daVS5YTD8fz3QgwjZortWwxXFd )。
如果你未指定任何时间段,则默认情况下,只会为当前日期创建报告。
app.ts
// 导入必要的模块和函数
import * as fs from "fs-extra"; // 导入 fs-extra 模块以进行文件系统操作
import { bb_getaddress } from "./blockbookMethods"; // 导入用于获取地址数据的函数
import { calculateVariables } from "./calculateVariables";
import { generateReportForAddress } from "./generateReport"; // 导入用于生成报告的函数
// 定义将为其生成报告的比特币地址
const address = "3MqUP6G1daVS5YTD8fz3QgwjZortWwxXFd";
// 报告的可选日期范围(默认情况下已注释掉)
// const startDate = new Date(2024, 2, 1); // 2024 年 3 月 1 日
// const endDate = new Date(2024, 2, 12); // 2024 年 3 月 12 日
(async () => {
// 获取指定地址的交易数据
const data = await bb_getaddress(address);
const extendedData = await calculateVariables(data);
// 可选日期范围(默认情况下已注释掉)
// const extendedData = await calculateVariables(data, startDate, endDate);
// 根据获取的数据生成报告
const [report, fileName] = generateReportForAddress(extendedData);
// 将生成的报告写入 CSV 文件
fs.writeFileSync(fileName, report);
// 记录指示报告保存位置的确认消息
console.log(**Report saved to ${fileName}**);
})();
Date
使用 JavaScript/TypeScript 中的 Date 构造函数指定日期时,请记住月份是从零开始索引的。这意味着 1 月是 0,2 月是 1,依此类推,直到 12 月是 11。例如,new Date(2024, 2, 1) 对应于 2024 年 3 月 1 日,而不是 2 月。请相应地调整月份编号以准确表示预期日期。
通过运行以下命令来运行脚本。
ts-node app.ts
如果一切顺利,处理后的数据将以可读的报告格式(CSV)写入,以便于分析和共享。
> ts-node app.ts
Generating transaction report for Bitcoin address (3MqUP6G1daVS5YTD8fz3QgwjZortWwxXFd) from 2024-March-13 to 2024-March-13
Report saved to transaction_report_3MqUP6G1daVS5YTD8fz3QgwjZortWwxXFd_2024-March-13_2024-March-13.csv
交易报告保存在当前项目目录中。交易报告的格式如下所示。
QuickNode 的 Blockbook add-on 使开发人员和企业可以更轻松地创建详细的比特币交易报告。本指南介绍了基础知识,但你可以做更多的事情。无论是用于审计、帮助完成监管任务还是市场分析,Blockbook add-on 都可以简化区块链数据提取过程。
要了解有关 QuickNode 如何帮助审计公司从区块链中提取此类数据的更多信息,请随时 与我们联系;我们很乐意与你交谈!
订阅我们的 新闻通讯 以获取有关 Web3 和区块链的更多文章和指南。如果你有任何疑问或需要进一步的帮助,请随时加入我们的 Discord 服务器或使用下面的表单提供反馈。通过在 Twitter (@QuickNode) 上和我们的 Telegram 公告频道 上关注我们,随时了解最新信息。
如果你对新主题有任何反馈或要求,请 告诉我们。我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/mar...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!