本教程展示了如何借助 Tenderly Simulation API 将”交易预览”功能集成到 Rabby Wallet 示例中。
交易预览使钱包用户可以模拟交易执行,并在不将交易发送到链上的情况下预览确切结果。这样,用户可以更好地了解交易的经济影响,并有信心地发送它们。此外,您还可以帮助保护用户的资金、消除不必要的 gas 费用,并改善产品的整体用户体验和安全性。
以下是此开源示例的 GitHub 仓库:
将 Tenderly 交易模拟集成到 Rabby Wallet 中
Tenderly Rabby 交易预览演示
本教程仅用于说明目的,展示如何将交易预览集成到
Rabby Wallet 中。它不适用也不推荐用于任何其他用途。
项目概览
在本教程中,您将学习如何将 Tenderly Simulation API 集成到 Rabby Wallet Chrome 扩展中。首先,您将学习如何与 API 交互并实现交易模拟逻辑。然后,您将对模拟数据进行分组并向用户展示。通过这种方式,您将通过添加新功能来改善此 Rabby Wallet 示例中的用户体验。
前置条件
对于本教程,您需要:
让我们开始构建!👷♂️
第 1 步:克隆 Rabby Wallet 项目
首先,fork 或克隆官方的 Rabby Wallet GitHub 仓库。为此,运行:
git clone https://github.com/RabbyHub/Rabby.git
接下来,导航到项目根目录,并使用 Yarn 包管理器安装依赖:
然后,使用以下命令在本地运行该仓库:
将生成 dist 文件夹。您可以使用它来加载 Chrome 扩展。
前往 chrome://extensions 并启用 Developer mode。然后,点击”Load unpacked”按钮,导航到您的 Rabby 目录,并选择 dist 文件夹。屏幕上将显示未打包的扩展,您可以开始使用 Rabby Wallet。
现在,您可以导入现有钱包或创建一个新钱包。
在开始之前,请务必禁用您已安装的 Rabby Wallet Chrome 扩展的生产版本。如果您遇到任何连接问题,请遵循 Rabby 团队的官方指南。
第 2 步:生成 Tenderly 访问令牌
要使用 Tenderly Simulation API,您需要生成一个访问令牌。请按以下步骤操作:
- 免费创建一个 Tenderly 账户或登录您现有的账户。
- 前往 Authorization 页面并点击 Generate Access Token 按钮。
- 令牌密钥仅显示一次。请务必将其复制并存储在安全的位置,因为您以后将无法再检索到它。
如果您在 API 身份认证流程中需要帮助,请参考以下指南:
第 3 步:构建模拟逻辑
接下来,我们将涵盖以下步骤:
- 为 Tenderly 凭据设置安全的存储系统。
- 创建必要的代码以连接到 Tenderly Simulation API。
- 使用私有和公开端点与 API 交互。
- 为这些端点构建合适的负载。
处理 Tenderly 凭据
首先,您需要获取 Tenderly 凭据。创建文件 /_raw/tenderly.json 并更新 Tenderly 凭据:
{
"tenderlyAccount": "YOUR_ACCOUNT_NAME",
"tenderlyProject": "YOUR_PROJECT_NAME",
"tenderlyAccessToken": "YOUR_ACCESS_TOKEN"
}
要在您的项目中访问 Tenderly 凭据,您需要按如下方式更新 build/webpack.common.config.js:
const { tenderlyAccount, tenderlyProject, tenderlyAccessToken } = require('../_raw/tenderly.json');
// other code...
const config = {
plugins: [
// abbreviated
new webpack.DefinePlugin({
'process.env.version': JSON.stringify(`version: ${version}`),
'process.env.release': JSON.stringify(version),
// Tenderly Credentials
'process.env.TENDERLY_ACCOUNT_SLUG': JSON.stringify(tenderlyAccount),
'process.env.TENDERLY_PROJECT_SLUG': JSON.stringify(tenderlyProject),
'process.env.TENDERLY_ACCESS_TOKEN': JSON.stringify(tenderlyAccessToken),
}),
],
resolve: {
// abbreviated
},
};
您可以在此找到该实现。
实现交易模拟逻辑
接下来,创建 src/ui/utils/tenderly.ts 文件,并使用 Tenderly Simulation API 实现逻辑。在此获取完整源代码。
export const simulateTransaction = async (tx: Tx): Promise<Record<string, any> | null> => {
const TENDERLY_ACCOUNT_SLUG = process.env.TENDERLY_ACCOUNT_SLUG;
const TENDERLY_PROJECT_SLUG = process.env.TENDERLY_PROJECT_SLUG;
const TENDERLY_ACCESS_TOKEN = process.env.TENDERLY_ACCESS_TOKEN;
if (!TENDERLY_ACCOUNT_SLUG || !TENDERLY_PROJECT_SLUG || !TENDERLY_ACCESS_TOKEN) {
throw new Error('Tenderly credentials are not set up correctly.');
}
const txPayload: any = {
...tx,
save: true,
save_if_fails: true,
simulation_type: 'full',
source: 'rabby-wallet',
network_id: tx.chainId.toString(),
gas: parseInt(tx.gas as string, 16) || null,
gas_price: parseInt(tx.gasPrice as string, 16) || null,
value: parseInt(tx.value, 16) || null,
input: tx.data,
};
try {
const simulationResponse = await axios.post(
`https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_SLUG}/project/${TENDERLY_PROJECT_SLUG}/simulate`,
txPayload,
{
headers: {
'Content-Type': 'application/json',
'X-Access-Key': TENDERLY_ACCESS_TOKEN as string,
},
},
);
// Make the simulation publicly accessible
if (simulationResponse?.data?.simulation?.id) {
await axios.post(
`https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_SLUG}/project/${TENDERLY_PROJECT_SLUG}/simulations/${simulationResponse.data.simulation.id}/share`,
{},
{
headers: {
'Content-Type': 'application/json',
'X-Access-Key': TENDERLY_ACCESS_TOKEN as string,
},
},
);
}
return simulationResponse?.data;
} catch (e) {
console.error({ e });
return null;
}
};
在这里,我们使用了两个端点。
第一个端点允许您在 Tenderly 支持的104 个网络中的任意一个上以不同参数模拟交易。前往我们的 Simulation API 参考查看该端点的所有必填和可选字段。
https://api.tenderly.co/api/v1/account/{accountId}/project/{projectId}/simulate
第二个端点使您能够将模拟后的交易公开访问。这样,您可以将其分享给团队成员或同事,以获取更多见解或快速解决问题。
https://api.tenderly.co/api/v1/account/{accountId}/project/{projectId}/simulations/{simulationID}/share
最后一步是在 SignTx.tx 组件中调用 simulateTransaction 函数,将其放入在交易签名之前被调用的 explainTx 函数中。有关代码详情,请参考此链接。
第 4 步:显示模拟的交易预览
为了帮助用户了解发送交易时会发生什么,您可以在 UI 中显示人类可读的模拟数据。例如,一旦钱包用户模拟了交易,您可以显示哪些资产已被转移。
在 Rabby Wallet 示例中,我们创建了三个易于理解的部分:
Tenderly Simulation Summary:将其视为交易报告卡。它为您提供有关交易的基本信息,并附带直接链接以在 Tenderly Dashboard 中查看详细的模拟概览。
Assets In & Assets Out:获得您的钱包即将与之交互的所有资产(ERC20 或 NFT 代币)的清晰列表。
Tokens Transferred:查看交易中发生的每一次代币转账。为了更清晰起见,按地址对所有转账进行分组,并突出显示原生代币转账。
Tenderly Simulation Summary
首先,让我们创建 Tenderly Simulation Summary 部分。在这里,我们展示模拟状态、交易执行的网络和 gas 值。
要构建此部分,您需要与 Tenderly Simulation API 响应交互。首先创建一个 TenderlySimulationSummary.tsx 文件。您可以在下方或此 GitHub 仓库中找到完整的代码实现。
const TenderlySimulationSummary = ({ simulation }: { simulation: Record<string, any> | null }) => {
const isSuccess = !!simulation?.status;
const simulationUrl = `https://dashboard.tenderly.co/${process.env.TENDERLY_ACCOUNT_SLUG}/${process.env.TENDERLY_PROJECT_SLUG}/simulator/${simulation?.id}`;
const sharedSimulationUrl = `https://tdly.co/shared/simulation/${simulation?.id}`;
return (
<div className="tenderly-simulation-summary container">
<p className="text-16 text-gray-title font-medium mb-12">Tenderly Simulation Summary</p>
{!simulation && <div>Something went wrong.</div>}
{simulation && (
<div className="flex flex-col gap-8">
<div className="flex items-center justify-between gap-2">
<div>Status</div>
<div className={isSuccess ? 'text-green' : 'text-red'}>
{isSuccess ? 'Success' : 'Failed'}
</div>
</div>
<div className="flex items-center justify-between gap-2">
<div>Network</div>
<div className="flex items-center gap-4">
<div className="flex items-center rounded-full">
<img
src={TenderlyNetwork[simulation.network_id]?.image}
className="h-16 rounded-full"
alt="Tenderly"
/>
</div>
<div>{TenderlyNetwork[simulation.network_id]?.name}</div>
</div>
</div>
<div className="flex items-center justify-between gap-2">
<div>Gas</div>
<div>{simulation.gas}</div>
</div>
<Button
className="tenderly-simulation-summary-button flex items-center justify-between gap-2 pb-0"
href={simulationUrl}
target="_blank"
rel="noreferrer"
size="large"
block
>
<div>View details</div>
<div className="flex items-center gap-2">
<img src={TenderlySvg} className="h-32" alt="Tenderly" />
<IconRightArrow />
</div>
</Button>
<Button
className="text-[#6E56CF] bg-white flex items-center justify-center gap-2 pb-0"
href={sharedSimulationUrl}
target="_blank"
rel="noreferrer"
type="link"
size="middle"
block
>
<div>Share simulation ✨</div>
</Button>
</div>
)}
</div>
);
};
export default TenderlySimulationSummary;
请注意我们如何生成引导用户前往 Tenderly Dashboard 的链接。第一个链接面向已认证用户,第二个是可分享的交易模拟链接:
const simulationUrl = `https://dashboard.tenderly.co/${process.env.TENDERLY_ACCOUNT_SLUG}/${process.env.TENDERLY_PROJECT_SLUG}/simulator/${simulation?.id}`;
const sharedSimulationUrl = `https://tdly.co/shared/simulation/${simulation?.id}`;
要从模拟交易中检索数据,请参考 Tenderly Simulation API 参考:
Assets In & Assets Out
第二部分提供了对在 Rabby Wallet 示例中管理和监控资产流动的洞察。它显示传入和传出的资产,以便您更好地查看钱包活动。
要实现这一点,首先创建一个 TenderlyWalletChanges.tsx 文件:
const TenderlyWalletChanges = ({
account,
assetChanges,
}: {
account?: Account;
assetChanges: any;
}) => {
const { assetsInList, assetsOutList } = React.useMemo(() => {
const assetsInList = assetChanges.filter(asset => asset.to === account?.address) || [];
const assetsOutList = assetChanges.filter(asset => asset.from === account?.address) || [];
return {
assetsInList,
assetsOutList,
};
}, [account, assetChanges]);
return (
<div className="TenderlyWalletChanges">
<div className="TenderlyWalletChanges__Content">
{/* Assets Out */}
{assetsOutList?.length > 0 && (
<>
<p className="text-16 text-gray-title font-medium mb-4">Assets Out</p>
<div className="flex flex-col gap-4 border border-gray-divider rounded-sm">
{assetsOutList.map((token, index) => (
<div
key={index}
className={clsx('flex gap-4 p-4', {
'border-t-[1px] border-gray-divider': index !== 0,
})}
>
<div className="flex items-center justify-center p-4">
<img src={token.token_info.logo} className="w-60 rounded" alt="Tenderly" />
</div>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<div className="font-semibold">
{token.token_info.symbol.toUpperCase()}
{token.token_id && <span className="ml-2">#{Number(token.token_id)}</span>}
</div>
<div className="text-12 text-gray-content">{token.token_info.name}</div>
</div>
<div className="flex flex-wrap items-center gap-4">
<div className="font-semibold">{token.type}:</div>
<div className="text-red-forbidden">- {formatAmount(token.amount)}</div>
<div className="text-12 text-gray-content">
(≈ {formatUsdValue(new BigNumber(token.dollar_value).toFixed())})
</div>
</div>
</div>
</div>
))}
</div>
</>
)}
{/* Assets In */}
{assetsInList?.length > 0 && (
<>
<p className="text-16 text-gray-title font-medium mt-4 mb-4">Assets In</p>
<div className="flex flex-col gap-4 border border-gray-divider rounded-sm">
{assetsInList.map((token, index) => (
<div
key={index}
className={clsx('flex gap-4 p-4', {
'border-t-[1px] border-gray-divider': index !== 0,
})}
>
<div className="flex items-center justify-center p-4">
<img src={token.token_info.logo} className="w-60 rounded" alt="Tenderly" />
</div>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<div className="font-semibold">
{token.token_info.symbol.toUpperCase()}
{token.token_id && <span className="ml-2">#{Number(token.token_id)}</span>}
</div>
<div className="text-12 text-gray-content">{token.token_info.name}</div>
</div>
<div className="flex flex-wrap items-center gap-4">
<div className="font-semibold">{token.type}:</div>
<div className="text-green">+ {formatAmount(token.amount)}</div>
<div className="text-12 text-gray-content">
(≈ {formatUsdValue(new BigNumber(token.dollar_value).toFixed())})
</div>
</div>
</div>
</div>
))}
</div>
</>
)}
</div>
<TenderlyCredits classname="mt-12" />
</div>
);
};
export default TenderlyWalletChanges;
在这里,我们过滤了资产变化数组并创建了两个列表:
- 进入钱包的资产
- 从钱包流出的资产
然后,我们使用辅助函数来预览并格式化数据。
不过,您也可以创建自己的 UI 布局来显示重要的资产信息。
在此了解更多关于模拟资产变化的信息:
Tokens Transferred
Token Transfers 部分是一个更复杂的组件。此文件主要管理布局,因此让我们首先讨论排序逻辑。您可以在此处找到该部分的实现。
首先创建一个 TenderlyAssetChanges.tsx 文件。它包含用于展示两个标签页 Tokens Transferred 和 Native Coins Transferred 的逻辑。为此,您需要添加用于排序、切换标签页以及按地址对数据进行分组的逻辑。
首先,让我们对数据进行分组并创建两个数组:已转移的代币和已转移的原生代币:
const assetChangesMappedByType = React.useMemo<
Record<AssetChangesTabs, TransactionAssetChangeData[]>
>(
() =>
transactionAssetChanges.reduce(
(
acc: {
nativeCoinsTransferred: TransactionAssetChangeData[];
tokensTransferred: TransactionAssetChangeData[];
},
assetChange: TransactionAssetChangeData,
) => {
const { type } = assetChange.tokenInfo;
if (type === TokenType.NATIVE) {
return {
...acc,
[AssetChangesTabs.nativeCoins]: [...(acc?.nativeCoinsTransferred ?? []), assetChange],
};
}
return {
...acc,
[AssetChangesTabs.tokens]: [...(acc?.tokensTransferred ?? []), assetChange],
};
},
{ [AssetChangesTabs.nativeCoins]: [], [AssetChangesTabs.tokens]: [] },
),
[assetChanges],
);
接下来,让我们编写以两种方式处理排序类型的逻辑:按时间顺序和按地址。这里,我们需要两个组件:ChronologicalContent.tsx 和 PerAddressContent.tsx。
您可以在 GitHub 仓库中找到实现:
组合和预览所有组件
最后,让我们把所有组件放在一起。为此,您需要创建一个 TenderlySimulationResult.tsx 文件。您还需要使用 Rabby 团队提供的 useWallet() hook 与已连接的钱包地址交互。
以下是代码概览:
const TenderlySimulationResult = ({ data }: { data: Record<string, any> | null }) => {
const [account, setAccount] = React.useState<Account>();
const wallet = useWallet();
const init = async () => {
const currentAccount = await wallet.syncGetCurrentAccount();
if (currentAccount) {
setAccount(currentAccount);
}
};
React.useEffect(() => {
init();
}, []);
return (
<div className="tenderly-simulation-result mt-16">
<ActionWrapper>
{!data && <div className="bg-[#f5f6fa] sticky top-[64px] z-10">Something went wrong.</div>}
{data && <TenderlySimulationSummary simulation={data.simulation} />}
</ActionWrapper>
{data?.transaction?.transaction_info.asset_changes && (
<>
<ActionWrapper>
<TenderlyWalletChanges
account={account}
assetChanges={data.transaction.transaction_info.asset_changes}
/>
</ActionWrapper>
<ActionWrapper>
<TenderlyAssetChanges
assetChanges={data.transaction.transaction_info.asset_changes}
contracts={data.contracts}
/>
</ActionWrapper>
</>
)}
</div>
);
};
export default TenderlySimulationResult;
后续内容
恭喜!您已使用 Tenderly Simulation API 将交易预览功能添加到 Rabby Wallet 示例中。🎉
要深入了解逻辑,请查看 GitHub 上的完整源代码。
此外,您可以根据项目需求使用三种不同的方法发起 Tenderly 交易模拟。要了解将交易模拟集成到您的钱包或 dapp 的其他方式,请查看以下资源: