我正在尝试构建一个 Telegram 机器人,用户可以在其中跟踪多个链上特定钱包上发生的 DEX 交换。
我注意到不同的 DEX 处理掉期的方式不同,并且根据我收集的信息,您必须解码每个收据的日志(以获取所有转账、存款、掉期等),并且根据 DEX,使用特定方法获取准确的进出代币和金额。
Swap() 似乎显示 amount1 和 amount2,但这些值(特别是最后进入钱包的金额)有时不正确(我认为是由于费用)。进入钱包的值似乎是错误的,所以这就是为什么必须使用转账(查看转账顺序、钱包地址以查看它是否在起始地址或终止地址等)。
我觉得必须有一种更简化的方法来处理这个问题(简单地获取准确的输入和输出以及从收据中获取令牌)。
据我所知,没有任何 API 可以简单地聚合和解码所有这些。 Moralis 提供了一个钱包历史记录端点,但它获取钱包的所有交易,而不是只有 1 个交易,而且价格昂贵。
如果可能的话,我希望通过 RPC 完成所有工作,除非 API 使用起来非常便宜。
我现在使用 Base 和 NodeJS。
收据(TXID为:0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb)看起来像这样(在我的receipts.js文件中):
export const receiptThree = {
"receipt":{
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"contractAddress":null,
"cumulativeGasUsed":"0x762315",
"effectiveGasPrice":"0x2198a60",
"from":"0xf4123c2a38d9aa57801a6469b415f8116208feab",
"gasUsed":"0x3fd0b",
"l1BaseFeeScalar":"0x8dd",
"l1BlobBaseFee":"0x1f3e2b33d",
"l1BlobBaseFeeScalar":"0x101c12",
"l1Fee":"0x31abeaa5c88",
"l1GasPrice":"0x1bf934a78",
"l1GasUsed":"0x175f",
"logs":[
{
"address":"0x000000000022d473030f116ddee9f6b43ac78ba3",
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"data":"0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000006773824b0000000000000000000000000000000000000000000000000000000000000000",
"logIndex":"0xc7",
"removed":false,
"topics":[
"0xc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec",
"0x000000000000000000000000f4123c2a38d9aa57801a6469b415f8116208feab",
"0x000000000000000000000000fde4c96c8593536e31f229ea8f37b2ada2699bb2",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad"
],
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24"
},
{
"address":"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"data":"0x000000000000000000000000000000000000000000000000000000000003d340",
"logIndex":"0xc8",
"removed":false,
"topics":[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000d56da2b74ba826f19015e6b7dd9dae1903e85da1",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad"
],
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24"
},
{
"address":"0xfde4c96c8593536e31f229ea8f37b2ada2699bb2",
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"data":"0x000000000000000000000000000000000000000000000000000000000003d4b0",
"logIndex":"0xc9",
"removed":false,
"topics":[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000f4123c2a38d9aa57801a6469b415f8116208feab",
"0x000000000000000000000000d56da2b74ba826f19015e6b7dd9dae1903e85da1"
],
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24"
},
{
"address":"0xd56da2b74ba826f19015e6b7dd9dae1903e85da1",
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"data":"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc2cc0000000000000000000000000000000000000000000000000000000000003d4b00000000000000000000000000000000000000001002c925e1b662b91a39fa1b3000000000000000000000000000000000000000000000000000022b0e3a0afc4000000000000000000000000000000000000000000000000000000000000000d",
"logIndex":"0xca",
"removed":false,
"topics":[
"0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad"
],
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24"
},
{
"address":"0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"data":"0x000000000000000000000000000000000000000000000000be44b2865cd2dc95",
"logIndex":"0xcb",
"removed":false,
"topics":[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x00000000000000000000000029715d8d279cab143a12ff515b40a2b35d7bad37",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad"
],
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24"
},
{
"address":"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"data":"0x000000000000000000000000000000000000000000000000000000000003d340",
"logIndex":"0xcc",
"removed":false,
"topics":[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"0x00000000000000000000000029715d8d279cab143a12ff515b40a2b35d7bad37"
],
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24"
},
{
"address":"0x29715d8d279cab143a12ff515b40a2b35d7bad37",
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"data":"0xffffffffffffffffffffffffffffffffffffffffffffffff41bb4d79a32d236b000000000000000000000000000000000000000000000000000000000003d340000000000000000000000000000000000000000000000241dc032d37480a0d2600000000000000000000000000000000000000000000000004879f8717efe6dcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2be4",
"logIndex":"0xcd",
"removed":false,
"topics":[
"0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad"
],
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24"
},
{
"address":"0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"data":"0x0000000000000000000000000000000000000000000000000079c590f9d501d4",
"logIndex":"0xce",
"removed":false,
"topics":[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"0x0000000000000000000000005d64d14d2cf4fe5fe4e65b1c7e3d11e18d493091"
],
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24"
},
{
"address":"0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"blockHash":"0xbe2cadb0b88ae723bb2d87293f41c64f3242c12fe7e841f3ecc0698d6e3e9701",
"blockNumber":"0x160cc3e",
"data":"0x000000000000000000000000000000000000000000000000bdcaecf562fddac1",
"logIndex":"0xcf",
"removed":false,
"topics":[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"0x000000000000000000000000f4123c2a38d9aa57801a6469b415f8116208feab"
],
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24"
}
],
"logsBloom":"0x00010000000000800000000000010000040000040000000000000000000000000000000000000000000000000000100000010000100020000080400014080000000000080000000800000008000000000000000000080000004000000020000000800000000000001000000000400000000844040000000000000010000800001040004000000000000000000000000002000000000000000000000000000000000008000000000100000000000080000401000020000000000000000000000000000002008000000000000000000000000000000000000000000000000000000000000000000000020000000000000000001000000000000000000002000400",
"status":"0x1",
"to":"0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"transactionHash":"0x72823453899fe671cdf0742f5dd3725d3f52e6f65660d31318e327969a850bcb",
"transactionIndex":"0x24",
"type":"0x2"
}
}
我的解码和聚合解决方案如下所示:
import { createPublicClient, http, parseAbiItem, formatUnits, getEventSelector, erc20Abi } from 'viem'
import { base, mainnet } from 'viem/chains'
import { receipt, receiptEight, receiptFive, receiptFour, receiptNine, receiptSeven, receiptSix, receiptTen, receiptThree, receiptTwo } from './receipts.js'
const baseClient = createPublicClient({
chain: base,
transport: http()
})
const ethereumClient = createPublicClient({
chain: mainnet,
transport: http()
})
// Define transfer event for filtering
const transferEvent = parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)')
const transferEventSelector = getEventSelector(transferEvent)
// Add deposit event definition
const depositEvent = parseAbiItem('event Deposit(address indexed dst, uint256 wad)')
const depositEventSelector = getEventSelector(depositEvent)
async function decodeSwap(receipt, network = 'base') {
const client = network === 'base' ? baseClient : ethereumClient
const userAddress = receipt.from.toLowerCase()
let logicPath = ''
// Get all transfer and deposit logs from the receipt
const transferLogs = receipt.logs.filter(log =>
log.topics[0] === transferEventSelector
)
const depositLogs = receipt.logs.filter(log =>
log.topics[0] === depositEventSelector
)
// Sort transfers by logIndex
const sortedTransfers = transferLogs.sort((a, b) =>
Number(a.logIndex) - Number(b.logIndex)
)
let sentTransfer, receivedTransfer
// First try to find direct transfers involving the user
for (const transfer of sortedTransfers) {
if (transfer.topics[1].toLowerCase().includes(userAddress.slice(2))) {
sentTransfer = transfer
logicPath = 'Direct user transfer found'
break
}
}
// If no direct user transfer found, check for WETH deposit
if (!sentTransfer) {
const wethDeposit = depositLogs[0]
if (wethDeposit) {
sentTransfer = wethDeposit
logicPath = 'WETH deposit found (fallback)'
}
}
// Look for the received transfer after the sent transfer
if (sentTransfer) {
const laterTransfers = sortedTransfers.filter(t =>
Number(t.logIndex) > Number(sentTransfer.logIndex)
).reverse()
receivedTransfer = laterTransfers.find(t =>
t.address.toLowerCase() !== sentTransfer.address.toLowerCase() &&
t.topics[1].toLowerCase() !== t.topics[2].toLowerCase()
)
if (receivedTransfer) {
logicPath += ' → Different token after sent transfer'
} else {
receivedTransfer = laterTransfers.find(t =>
t.topics[1].toLowerCase() !== t.topics[2].toLowerCase()
)
if (receivedTransfer) {
logicPath += ' → Any valid transfer after sent transfer'
}
}
}
// If we still haven't found the transfers, look for the first out and last in of different tokens
if (!sentTransfer || !receivedTransfer) {
sentTransfer = sortedTransfers.find(t =>
t.topics[1].toLowerCase() !== t.topics[2].toLowerCase()
)
if (sentTransfer) {
logicPath = 'First valid transfer'
receivedTransfer = [...sortedTransfers].reverse().find(t =>
t.address.toLowerCase() !== sentTransfer.address.toLowerCase() &&
t.topics[1].toLowerCase() !== t.topics[2].toLowerCase() &&
t !== sentTransfer
)
if (receivedTransfer) {
logicPath += ' → Last different token transfer'
} else {
receivedTransfer = [...sortedTransfers].reverse().find(t =>
t.topics[1].toLowerCase() !== t.topics[2].toLowerCase() &&
t !== sentTransfer
)
if (receivedTransfer) {
logicPath += ' → Last valid transfer'
}
}
}
}
if (!receivedTransfer || !sentTransfer) {
throw new Error('Could not find swap transfers')
}
// Get token details
const [sentToken, receivedToken] = await Promise.all([
{
address: sentTransfer.address,
symbol: await client.readContract({
address: sentTransfer.address,
abi: erc20Abi,
functionName: 'symbol'
}),
decimals: await client.readContract({
address: sentTransfer.address,
abi: erc20Abi,
functionName: 'decimals'
})
},
{
address: receivedTransfer.address,
symbol: await client.readContract({
address: receivedTransfer.address,
abi: erc20Abi,
functionName: 'symbol'
}),
decimals: await client.readContract({
address: receivedTransfer.address,
abi: erc20Abi,
functionName: 'decimals'
})
}
])
// Parse the transfer amounts
const sentAmount = formatUnits(
BigInt(sentTransfer.data),
sentToken.decimals
)
const receivedAmount = formatUnits(
BigInt(receivedTransfer.data),
receivedToken.decimals
)
console.log('Decoded Swap Summary:')
console.log('--------------------')
console.log(`Logic Path: ${logicPath}`)
console.log('\nSwap Details:')
console.log(`Sent: ${sentAmount} ${sentToken.symbol} (${sentTransfer.address})`)
console.log(`Received: ${receivedAmount} ${receivedToken.symbol} (${receivedTransfer.address})`)
console.log('\n')
}
// const receiptOneMain = receipt.receipt
// const receiptTwoMain = receiptTwo.receipt
const receiptThreeMain = receiptThree.receipt
// const receiptFourMain = receiptFour.receipt
// const receiptFiveMain = receiptFive.receipt
// const receiptSixMain = receiptSix.receipt
// const receiptSevenMain = receiptSeven.receipt
// const receiptEightMain = receiptEight.receipt
// const receiptNineMain = receiptNine.receipt
// const receiptTenMain = receiptTen.receipt
// console.log("Receipt One")
// await decodeSwap(receiptOneMain, "base")
// console.log("Receipt Two")
// await decodeSwap(receiptTwoMain, "base")
console.log("Receipt Three")
await decodeSwap(receiptThreeMain, "base")
// console.log("Receipt Four")
// await decodeSwap(receiptFourMain, "base")
// console.log("Receipt Five")
// await decodeSwap(receiptFiveMain, "base")
// console.log("Receipt Six")
// await decodeSwap(receiptSixMain, "base")
// console.log("Receipt Seven")
// await decodeSwap(receiptSevenMain, "base")
// console.log("Receipt Eight")
// await decodeSwap(receiptEightMain, "base")
// console.log("Receipt Nine")
// await decodeSwap(receiptNineMain, "base")
// console.log("Receipt Ten")
// await decodeSwap(receiptTenMain, "ethereum")
运行时,会提供正确的输出,如下所示:
Receipt Three
Decoded Swap Summary:
--------------------
Logic Path: Direct user transfer found → Different token after sent transfer
Swap Details:
Sent: 0.251056 USDT (0xfde4c96c8593536e31f229ea8f37b2ada2699bb2)
Received: 13.676003757135878849 DEGEN (0x4ed4e862860bed51a9570b96d89af5e1b0efefed)
我已经测试了其他 9 个收据,它们来自 UniSwap、1Inch 和其他 3 个 DEX,我的逻辑运行良好并且似乎是正确的,使用不同的后备和策略来获得准确的进出。
我想知道是否有更好的解决方案来获取准确的交换信息(即人类可读的最终结果)?
我的方法有什么缺陷吗?
我应该使用基于 DEX 和版本的 switch-case 方法吗?
如果有人能给我一些指点,那就太好了。
(仅供参考,我已经有一种使用 QuickNode Streams 获取收据的方法,所以不用担心)。
不幸的是,DEX 或其事件没有标准。