这篇指南详细阐述了如何使用QuickNode的Blockbook RPC附加组件生成比特币交易报告。通过分步教程,开发者和财务分析师将学习如何设置环境、获取和处理交易数据,以及生成符合监管要求的报告。文章结构清晰,内容丰富,适合技术相关人员。
在今天的数字金融环境中,Bitcoin 交易报告的清晰性和合规性对在区块链技术复杂性中航行的企业至关重要。本指南演示了创建详细 Bitcoin 交易报告的过程,利用 QuickNode 的 Blockbook RPC 附加组件 的强大功能。该教程面向开发者和金融分析师,分步讲解将为你提供构建一个能够获取、分析和以全面、合规的格式呈现 Bitcoin 交易的报告工具所需的工具。
依赖项 | 版本 |
---|---|
node.js | >18.16 |
typescript | latest |
ts-node | latest |
来自 QuickNode 的 BTC Blockbook JSON-RPC 附加组件 是一个强大的工具,用于跟踪 Bitcoin 交易,通过简单的 JSON-RPC 接口轻松获取地址或 xpubs 的余额、交易和 UTXO。借助 Blockbook,用户可以获得:
利用 Blockbook 进行区块链数据查询不仅可以促进准确和合规的报告,还可以激发交易监控、资产管理等方面的创意解决方案。
截至本文撰写时,Blockbook 附加组件包括 10 个强大的 RPC 方法。我们将在本指南中利用其中两种:
bb_getAddress
:返回地址的余额和交易。bb_getTickers
:返回指定货币和日期的汇率。如果在特定时间戳下没有该货币,则返回下一个最近的汇率。在你开始之前,请注意 Blockbook RPC 是一个付费附加组件。请在 此处 查看详细信息,并根据你的需求比较计划。
使用 Blockbook RPC 设置 Bitcoin 端点非常简单。如果你尚未注册,可以在 此处 创建一个账户。
登录后,导航至 端点 页面,点击 创建端点。选择 Bitcoin 主网,然后点击下一步。接着,你将被要求配置附加组件。激活 Blockbook RPC。之后,简单点击 创建端点。
如果你已经有了没有附加组件的 Bitcoin 端点,请在 Bitcoin 端点内转到 附加组件 页面,选择 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
📘 包
在我们深入编码 Bitcoin 交易报告工具之前,先花一点时间了解它的操作流程。这种前奏将确保我们不是盲目编码,而是清晰地理解每个部分如何契合以实现我们的目标。以下是流程的概述:
导入依赖项:每个脚本文件都以导入必要的库和模块开始。例如,app.ts 可能会导入 fs-extra 以执行文件操作,以及来自 blockbookMethods.ts 和 generateReport.ts 的特定函数以进行数据检索和报告生成。
获取交易数据:在 app.ts 中,调用 blockbookMethods.ts 中的 bb_getAddress 函数,传入 Bitcoin 地址以收集其交易历史。
处理数据:获取数据后,使用 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
📘 文件
.env:存储环境变量,例如你的 QuickNode 端点 URL,确保敏感信息得以安全管理和轻松配置。
app.ts:作为你的应用程序的入口点,协调通过调用函数来获取数据、生成报告和管理输出的流程,基于用户定义的参数。
blockbookMethods.ts:包含与 Blockbook API 交互的函数,包括获取指定 Bitcoin 地址的交易数据。
calculateVariables.ts:处理由 blockbookMethods.ts 获取的区块链数据的逻辑。
generateReport.ts:将处理过的区块链数据格式化为结构化报告,通常为 CSV 格式,以便于分析和共享。
interfaces.ts:定义 TypeScript 接口,以确保在整个应用程序中的类型安全,特别是对于 Blockbook API 调用返回的数据结构以及在报告生成过程中使用的数据结构。
如果你有 QuickNode 端点和其他敏感信息,请将其存储在 .env 文件中。
打开 .env 文件并按如下方式修改它。在这里不要忘记用你的 QuickNode Bitcoin HTTP 提供程序 URL 替换 YOUR_BITCOIN_QUICKNODE_ENDPOINT 占位符。
.env
QUICKNODE_ENDPOINT = "YOUR_BITCOIN_QUICKNODE_ENDPOINT"
blockbookMethods.ts 文件包括两个主要函数:bb_getAddress 和 bb_getTickers,旨在使用 Blockbook API 与 QuickNode 端点进行交互。这些函数使得获取特定 Bitcoin 地址详细交易数据和在给定时间戳获取货币转换率变得容易。
用代码编辑器打开 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;
// 获取指定 Bitcoin 地址的详细交易数据
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("未能获取交易");
}
} 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("未能获取汇率数据");
}
} catch (error) {
console.error(error);
throw error;
}
}
calculateVariables.ts 文件通过计算交易前后的 BTC 余额、确定每笔交易的日期和方向以及计算 BTC 转账的美元等值来细化指定 Bitcoin 地址的交易数据。它聪明地过滤交易,仅保留与指定地址相关的交易,忽略与我们地址无直接关系的多地址交易。
用代码编辑器打开 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 ? "未确认" : "已确认";
// 根据地址的角色分配交易方向
direction = vinIsSender ? "外出" : "进入";
let fromAddresses = "";
let toAddresses = "";
let btcAmount = 0,
usdAmount = 0,
btcFees = 0,
usdFees = 0;
// 处理地址为发送者的逻辑
if (vinIsSender) {
// 过滤交易输入 (vin),查找包含数据地址的项
// 确定我们的地址发送 Bitcoin 的交易
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) {
// 获取当前价格数据以便准确的 USD 转换
const priceData = await bb_gettickers(transaction.blockTime);
// 计算费用和 USD 金额
btcFees = parseFloat(transaction.fees) / btcToSatoshi;
usdFees = btcFees * priceData.rates.usd;
usdAmount = btcAmount * priceData.rates.usd;
}
if (direction === "外出") {
cumulativeDiff -= btcAmount;
} else {
cumulativeDiff += btcAmount;
}
balanceBeforeTx = currentBalance - cumulativeDiff;
if (direction === "外出") {
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 文件旨在为 Bitcoin 交易生成一份全面的报告,基于处理和过滤后的数据。
用代码编辑器打开 generateReport.ts 文件,并对此文件进行如下修改。代码片段中包含注释,以便更好地理解。
generateReport.ts
import { ExtendedResult } from "./interfaces";
import { format } from "date-fns";
// 根据 Bitcoin 地址和日期范围生成报告的函数
export function generateReportForAddress(
extendedData: ExtendedResult
): [string, string] {
// 记录生成报告的过程
console.log(
`生成 Bitcoin 地址(${
extendedData.address
})从 ${format(extendedData.startDate, "yyyy-MMMM-dd")} 到 ${format(
extendedData.endDate,
"yyyy-MMMM-dd"
)} 的交易报告`
);
// 准备 CSV 表头
let reportLines: string[] = [
"日期;时间戳;交易;类型;方向;来自;至;金额 [BTC];金额 [USD];费用 [BTC];费用 [USD];前余额;后余额",
];
// 数据行
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 接口,这些接口结构化地检索 Bitcoin 区块链和相关财务信息的数据,便于类型安全的开发和与区块链数据的交互。
用代码编辑器打开 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[];
}
// 表示单笔 Bitcoin 交易的详情。
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;
}
// 表示 Bitcoin 交易中的输入。
export interface Vin {
txid: string;
vout?: number;
sequence: number;
n: number;
addresses: string[];
isAddress: boolean;
value: string;
hex: string;
isOwn?: boolean;
}
// 表示 Bitcoin 交易中的输出。
export interface Vout {
value: string;
n: number;
hex: string;
addresses: string[];
isAddress: boolean;
spent?: boolean;
isOwn?: boolean;
}
// 表示价格数据,包括时间戳和货币汇率。
export interface PriceData {
ts: number;
rates: Rates;
}
// 包含货币兑换汇率,例如从 Bitcoin 到 USD。
export interface Rates {
usd: number;
}
app.ts 文件与区块链交互以提取有意义的数据,并以用户友好的格式呈现它,使其成为任何需要详细了解与特定地址相关的 Bitcoin 交易的人士的宝贵工具。
用代码编辑器打开 app.ts 文件,并对此文件进行如下修改。
不要忘记用生成报告的 Bitcoin 地址替换 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"; // 导入生成报告的函数
// 定义要生成报告的 Bitcoin 地址
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(`报告已保存到 ${fileName}`);
})();
日期
在 JavaScript/TypeScript 中使用 Date 构造函数指定日期时,请记住月份是从零开始计数的。这意味着一月为 0,二月为 1,以此类推,直到十二月为 11。例如,new Date(2024, 2, 1) 对应于 2024 年 3 月 1 日,而不是二月。请相应调整月份数字,以准确表示意图日期。
通过运行以下命令运行脚本。
ts-node app.ts
如果一切顺利,处理过的数据将写入可读的报告格式 CSV,以便于分析和共享。
> ts-node app.ts
生成 Bitcoin 地址(3MqUP6G1daVS5YTD8fz3QgwjZortWwxXFd)的交易报告,从 2024 年 3 月 13 日 到 2024 年 3 月 13 日
报告已保存到 transaction_report_3MqUP6G1daVS5YTD8fz3QgwjZortWwxXFd_2024-March-13_2024-March-13.csv
交易报告保存在当前项目目录中。交易报告的格式如下所示。
QuickNode 的 Blockbook 附加组件使开发者和企业能够更容易地创建详细的 Bitcoin 交易报告。本指南介绍了基础知识,但你可以做的更多。无论是为审计、帮助合规工作还是市场分析,Blockbook 附加组件简化了区块链数据提取过程。
要了解有关 QuickNode 如何帮助审计公司从区块链提取此类数据的更多信息,请随时 与我们联系;我们很想和你谈谈!
订阅我们的 新闻通讯,获取关于 Web3 和区块链的更多文章和指南。如果你有任何问题或需要进一步的帮助,请随时加入我们的 Discord 服务器,或使用以下表单提供反馈。通过关注我们的 Twitter (@QuickNode)和我们的 Telegram 公告频道 及时了解最新动态。
告诉我们 如果你有任何反馈或对新主题的请求。我们很想听听你的意见。
- 原文链接: quicknode.com/guides/qui...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!