Web3 DEX Starter
受众:开发者 摘要:PancakeSwap V2 Router 报价、池子储备只读查询、用户侧加减池报价 / calldata 构建和流动性订单追踪。Token 元数据缓存和地址本见 web3-token.md。
何时使用
| 场景 | 建议 |
|---|---|
Token 链上报价(默认 pancake-v2,降级 pancake-v2-pool) | Web3.price(...) |
| DEX 池子只读(储备 / 流动性 / 手续费后输出 / 价格影响) | Web3.dexPool(...) |
| 用户侧加池子 / 减池子报价与 calldata 构建 | Web3DexClient#quoteLiquidity(...) |
| 用户侧加池子 / 减池子订单追踪 | Core Web3 /api/v1/money/web3/liquidity/orders/** |
| 低频常用链上地址收口 | 见 web3-token.md 的「地址本与元数据缓存」 |
Token 静态元数据缓存(name / symbol / decimals) | 见 web3-token.md 的「地址本与元数据缓存」 |
代币价格查询
Web3PriceClient 是 Web3 价格查询入口。默认 Provider 为 pancake-v2,底层通过 PancakeSwap V2 Router 的 getAmountsOut(uint256,address[]) 做只读报价,不发起 swap、授权、签名或广播。
内置 Provider:
| Provider | 实现 | 说明 |
|---|---|---|
pancake-v2 | pancake-v2 | 通过 Router getAmountsOut 查询指定数量可兑换多少报价 Token,适合常规报价 |
pancake-v2-pool | pancake-v2-pool | 通过 Factory getPair 找池子,再读取 Pair token0/token1/getReserves 计算池子中间价,适合 Router 或报价服务不可用时兜底 |
当请求没有显式指定 providerName 时,客户端会先调用 default-provider,失败后按 fallback-providers 顺序降级。显式指定 providerName 表示调用者要精确使用某个 Provider,不会自动降级。
@RequiredArgsConstructor
public class TokenPriceService {
private final Web3PriceClient priceClient;
public Web3TokenPrice price(String tokenAddress) {
return priceClient.price(Web3TokenPriceRequest.builder()
.chainName("bsc")
.tokenAddress(tokenAddress)
.tokenSymbol("TOKEN")
.amountRaw(new BigInteger("1000000000000000000"))
.build());
}
}返回值中的 amountInRaw / amountOutRaw 是链上最小单位,amountIn / amountOut 会按精度换算成人类可读数量,price 表示 1 个基础 Token 可兑换多少报价 Token。价格缓存、业务展示和后台管理都应该以 chainName + tokenAddress 作为唯一键;仅使用合约地址不够,因为不同链上可能存在相同地址。
Money Web3 模块提供只读后台接口,方便运维页或调试页按链查询报价:
POST /api/v1/money/web3/chains/{chainName}/token-prices请求示例:
{
"providerName": "pancake-v2-pool",
"tokenAddress": "0x0000000000000000000000000000000000000001",
"quoteTokenAddress": "0x55d398326f99059fF775485246999027B3197955",
"pairAddress": null,
"tokenSymbol": "TOKEN",
"quoteTokenSymbol": "USDT",
"tokenDecimals": 18,
"quoteTokenDecimals": 18,
"amountRaw": 1000000000000000000
}providerName 为空时使用默认 Provider;显式传入 pancake-v2-pool 时会走池子储备量报价,并在返回值中包含 pairAddress / pairAddressUrl。该接口只读 RPC,不签名、不授权、不广播,也不写入业务订单或流水。
配置示例:
spring:
open:
web3:
price:
enabled: true
default-provider: pancake-v2
fallback-providers:
- pancake-v2-pool
providers:
pancake-v2:
enabled: true
provider: pancake-v2
chain-name: bsc
router-address: 0x10ED43C718714eb63d5aA57B78B54704E256024E
quote-token-address: 0x55d398326f99059fF775485246999027B3197955
quote-token-symbol: USDT
quote-token-decimals: 18
base-token-decimals: 18
default-amount-raw: 1000000000000000000
fee-bps: 25
default-slippage-bps: 50
pancake-v2-pool:
enabled: true
provider: pancake-v2-pool
chain-name: bsc
factory-address: 0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73
# 为空时通过 factory-address 自动查找 token/quote 对应 Pair。
pair-address:
quote-token-address: 0x55d398326f99059fF775485246999027B3197955
quote-token-symbol: USDT
quote-token-decimals: 18
base-token-decimals: 18
default-amount-raw: 1000000000000000000
fee-bps: 25
default-slippage-bps: 50
dex:
enabled: true
default-provider: pancake-v2-pool配置优先级同样遵循 Config starter,可在数据库配置中维护 spring.open.web3.price.* 和 spring.open.web3.dex.*。生产环境要确保 chain-name 对应链已启用,且 RPC 节点可访问 PancakeSwap V2 Router、Factory、Pair 和目标 Token 合约。
DEX 池子只读查询
Web3DexClient 是 DEX 只读入口。第一阶段内置 pancake-v2-pool Provider,复用 spring.open.web3.price.providers.pancake-v2-pool 的链、Factory、Pair、报价 Token、手续费和默认滑点配置。
它只做只读 RPC:
- 通过 Factory
getPair(token, quoteToken)自动查找 Pair,或使用请求中的pairAddress。 - 读取 Pair
token0、token1、getReserves。 - 读取 Pair 作为 LP Token 的
totalSupply;传入walletAddress后额外读取balanceOf(walletAddress),传入或默认 Router 作为spenderAddress后读取allowance(walletAddress, spenderAddress)。 - 计算基础 Token 储备、报价 Token 储备、按报价 Token 估算的池子总流动性。
- 基于 V2 恒定乘积公式、
fee-bps和default-slippage-bps给出amountOut、minimumAmountOut和priceImpactBps提示。
它不发起 swap、不授权、不签名、不广播。priceImpactBps 是给运维和风控参考的只读提示,不等于最终成交保障;真正下单仍应由业务侧结合 DEX Router、交易截止时间、滑点和风控规则处理。
Money Web3 模块提供只读后台接口:
POST /api/v1/money/web3/chains/{chainName}/dex/pools/info请求示例:
{
"providerName": "pancake-v2-pool",
"tokenAddress": "0x0000000000000000000000000000000000000001",
"quoteTokenAddress": "0x55d398326f99059fF775485246999027B3197955",
"pairAddress": null,
"walletAddress": "0x0000000000000000000000000000000000000004",
"spenderAddress": null,
"tokenSymbol": "TOKEN",
"quoteTokenSymbol": "USDT",
"tokenDecimals": 18,
"quoteTokenDecimals": 18,
"amountRaw": 1000000000000000000,
"slippageBps": 50
}常用返回字段:
| 字段 | 说明 |
|---|---|
tokenReserve / quoteReserve | 池子中基础 Token 和报价 Token 的人类可读储备量 |
lpTokenAddress | LP Token 地址;Pancake V2 Pair 本身就是 LP Token |
lpTotalSupply | LP Token 总发行量 |
lpBalance | 请求钱包的 LP Token 持仓;未传 walletAddress 时为空 |
lpAllowance | 请求钱包对授权目标的 LP Token 授权额度;未传钱包或授权目标时为空 |
lpApproved | 钱包是否已对授权目标授权 LP Token;未传钱包或授权目标时为空 |
estimatedLiquidityInQuoteToken | 以报价 Token 估算的池子总流动性,约等于 quoteReserve * 2 |
price | 由储备量计算的中间价 |
amountOut | 按 V2 公式和 fee-bps 估算的输出数量 |
midAmountOut | 不考虑手续费和池子深度影响的中间价输出数量 |
minimumAmountOut | 按 slippageBps 计算的最小输出提示 |
priceImpactBps | 手续费和池子深度共同造成的输出差异,单位 bps |
流动性加减池报价与订单 contract
Core Web3 已提供用户侧 Pancake V2 创建池子、加池子 / 减池子报价和订单追踪接口。该 contract 面向自托管钱包:平台只计算比例、滑点、授权和交易参数,不签名、不广播、不托管普通用户私钥;用户完成钱包广播后再把 txHash 绑定到订单,由服务端同步 receipt 并解析 Mint / Burn 结果。
App 钱包中心和 PC 用户中心已接入该 contract。App 可从钱包首页进入兑换页,PC 在 /account/balance 的链上资产区域展示流动性表单;两端都支持创建池子、加池子 / 减池子报价、创建订单、填写钱包广播后的交易哈希和手动同步链上结果。前端只消费后端返回的订单与报价,不伪造本地流动性订单。
报价支持 ERC-20 / ERC-20、native coin / ERC-20 和 ERC-20 / native coin 三种组合。native coin 场景必须提供 Wrapped Native Token 地址,Pair 查询和 reserves 计算走 wrapped native,交易 calldata 走 Pancake V2 Router 的 addLiquidityETH / removeLiquidityETH;native 侧不生成 ERC-20 授权,add-quote 会返回 transactionValueRaw 作为钱包交易的 value。
用户侧入口:
POST /api/v1/money/web3/liquidity/add-quote
POST /api/v1/money/web3/liquidity/create-quote
POST /api/v1/money/web3/liquidity/remove-quote
POST /api/v1/money/web3/liquidity/orders
GET /api/v1/money/web3/liquidity/orders
GET /api/v1/money/web3/liquidity/orders/{orderNo}
POST /api/v1/money/web3/liquidity/orders/{orderNo}/tx
POST /api/v1/money/web3/liquidity/orders/{orderNo}/syncadd-quote 请求示例:
{
"chainName": "bsc",
"providerName": "pancake-v2-pool",
"tokenAAddress": "0x0000000000000000000000000000000000000001",
"tokenBAddress": "0x55d398326f99059fF775485246999027B3197955",
"tokenANative": false,
"tokenBNative": false,
"wrappedNativeAddress": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
"pairAddress": null,
"walletAddress": "0x0000000000000000000000000000000000000004",
"recipientAddress": "0x0000000000000000000000000000000000000004",
"routerAddress": null,
"amountADesiredRaw": 1000000000000000000,
"amountBDesiredRaw": 30000000000000000000,
"slippageBps": 100,
"deadlineSeconds": 900
}create-quote 使用同一请求结构,但 pairAddress 通常为空;Provider 会通过 Factory 查询 Pair 是否存在。Pair 不存在时返回首次流动性 quote,交易 calldata 仍走 Router addLiquidity,由兼容 Router 隐式创建 Pair 并注入首笔流动性;首次流动性由用户两侧数量决定初始价格,因此返回显著风险提示,并标记为非官方池。
remove-quote 使用同一请求结构,但传入 liquidityRaw 表示要移除的 LP Token 最小单位数量,不传 amountADesiredRaw / amountBDesiredRaw。
native coin 请求可把对应 token 地址填为 native、0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee 或 wrapped native 地址,同时把对应 tokenANative / tokenBNative 置为 true。只允许一侧为 native;两侧 native 会被拒绝。
Admin 官方池入口:
POST /api/v1/admin/web3/liquidity/create-quote
POST /api/v1/admin/web3/liquidity/add-quote
POST /api/v1/admin/web3/liquidity/remove-quote
GET /api/v1/admin/web3/liquidity/orders
GET /api/v1/admin/web3/liquidity/orders/{orderNo}
POST /api/v1/admin/web3/liquidity/orders/{orderNo}/syncAdmin 官方池报价会在调用 Pancake V2 Provider 前读取当前启用的 DEX 配置。后台可以通过 money_web3_swap_dex_config.metadata_json 配置官方池 token / pair 白名单和单笔限额;未配置白名单时不额外限制 token 组合,配置后只允许命中的 pair 或无序 token 对。native coin 报价会先映射到 DEX 配置或请求中的 wrapped native 地址再匹配白名单。
{
"officialPool": {
"allowedPairs": [
{
"tokenA": "0x0000000000000000000000000000000000000001",
"tokenB": "0x55d398326f99059fF775485246999027B3197955"
},
"0x0000000000000000000000000000000000000005"
],
"singleAmountLimitRaw": "1000000000000000000000",
"maxAmountADesiredRaw": "500000000000000000000",
"maxAmountBDesiredRaw": "1000000000000000000000",
"maxLiquidityRaw": "200000000000000000000"
}
}singleAmountLimitRaw 会作为 tokenA、tokenB 和 LP 的默认单笔上限;单侧配置 maxAmountADesiredRaw、maxAmountBDesiredRaw 或 maxLiquidityRaw 时优先生效。metadata_json 不是合法 JSON 或限额不是整数时,官方池报价会失败;用户侧普通流动性报价不读取该官方池规则。
常用返回字段:
| 字段 | 说明 |
|---|---|
tokenAAmountRaw / tokenBAmountRaw | 按 Pair reserves 修正后的实际加池投入,或减池预计取回数量 |
tokenAMinRaw / tokenBMinRaw | 按 slippageBps 计算的最小投入 / 到账数量 |
liquidityRaw | 加池预计获得或减池消耗的 LP Token 数量 |
tokenANative / tokenBNative | 当前报价是否把该侧视为 native coin |
wrappedNativeAddress | native coin 对应的 Wrapped Native Token 地址,用于 Pair 查询和 reserves 计算 |
transactionValueRaw | native coin 加池时钱包交易需要携带的原生币 value;ERC-20 / ERC-20 或减池时为 0 |
pairExists | Factory 或显式 Pair 是否已存在 |
createPairRequired | 创建池子报价是否需要新建 Pair |
firstLiquidity | 是否属于首次流动性注入,首次注入会决定初始价格 |
nonOfficialPool | 是否为用户侧非官方池操作,前端必须展示风险提示 |
warnings | 风险提示 key,例如 liquidity-create-pair-required、liquidity-first-price-set-by-user、liquidity-non-official-pool、liquidity-native-coin-uses-wrapped-native-pair |
tokenAAllowanceRaw / tokenBAllowanceRaw / lpAllowanceRaw | 钱包当前授权额度 |
needsTokenAApproval / needsTokenBApproval / needsLpApproval | 是否需要先执行授权交易 |
approveTokenACalldata / approveTokenBCalldata / approveLpCalldata | ERC-20 approve(spender, amount) calldata |
transactionToAddress / transactionCalldata | addLiquidity / removeLiquidity / addLiquidityETH / removeLiquidityETH 的目标地址与 calldata |
routerAddressUrl / pairAddressUrl / token*AddressUrl | 按链浏览器配置生成的地址链接 |
当前实现复用 pancake-v2-pool Provider 的 Factory / Router / Pair 配置。Provider 会通过 Factory 自动查找 Pair,读取 token0、token1、getReserves 和 LP totalSupply,再按 tokenA/tokenB 请求顺序映射 reserves。传入 walletAddress 时会读取 Token 或 LP allowance;传入 recipientAddress 或可由 walletAddress 推导时才返回交易 calldata。
/orders 会基于同一报价创建订单快照,保存 action、Pair / Router、Token、报价数量、授权与交易 calldata、风险评估、当前登录用户和 traceId。action 支持 add、remove 和 create;create 成功回执按 Mint 事件解析。订单初始状态按报价结果进入 pending-approval 或 pending-signature;人工复核阻断时进入 manual-review。
用户完成钱包签名并广播后,前端把交易哈希提交到 /orders/{orderNo}/tx。系统会校验订单归属、终态、人工复核状态和 txHash 唯一性;同一订单已绑定 txHash 时只允许同哈希幂等同步,不允许换绑。提交后系统立即尝试读取交易回执:未出块时订单进入 confirming,回执失败或 revert 时进入 failed 并记录失败原因;成功回执必须解析到 Pancake V2 Mint 或 Burn 事件,系统会保存 receiptStatus、blockNumber、gasUsed、实际 tokenA / tokenB 数量、LP Token 数量和事件 JSON,订单进入 succeeded。弱网或 RPC 延迟时可调用 /orders/{orderNo}/sync 重新同步,GET /orders/{orderNo} 用于订单历史和详情展示。
Swap 报价与订单 contract
Core Web3 已提供 Swap 领域 contract,用于把后台 DEX 配置、路由策略、报价结果和用户订单快照串起来。该 contract 面向自托管钱包:平台生成报价、交易参数和订单状态,真实授权、签名和广播仍由用户钱包完成。
核心表:
| 表 | 说明 |
|---|---|
money_web3_swap_dex_config | 按 chainName + dexCode 保存 Pancake V2 兼容 DEX Router / Factory / WNATIVE / 默认报价 Token / 滑点 / deadline / 手续费配置 |
money_web3_swap_route_strategy | 按 chainName + strategyCode 保存默认 DEX、基础路由 Token、exact-in / exact-out、最大跳数、最大滑点和路径策略 |
money_web3_swap_order | 保存用户 Swap 订单快照、报价快照、交易请求、风险评估、订单状态、txHash、回执状态、区块高度、gasUsed、真实成交数量、Swap 事件和 traceId |
用户侧报价入口:
POST /api/v1/money/web3/swap/quote用户侧订单入口:
POST /api/v1/money/web3/swap/orders用户侧链上追踪入口:
POST /api/v1/money/web3/swap/orders/{orderNo}/tx
POST /api/v1/money/web3/swap/orders/{orderNo}/sync
GET /api/v1/money/web3/swap/orders/{orderNo}/quote 会结合启用的 DEX 配置和默认路由策略补齐 providerName、slippageBps、deadlineSeconds、maxHops 和基础路由 Token,并在调用底层 Pancake V2 兼容报价前读取 Token 列表安全快照。已入目录 Token 会继承禁用、黑名单、风险标签和元数据完整性判断;未入目录 ERC-20 会先通过现有 metadata 预览读取 name / symbol / decimals,解析失败时以 token-unresolved 阻断报价,解析成功时以 token-custom 进入 watch 风险。DEX 报价返回后,系统会合并 DEX 价格影响等风险与 Token 风险,统一输出 securityAssessment、minimumAmountOut、priceImpactBps 等字段。/orders 需要登录,会基于同一报价创建 pending-approval、pending-signature 或 manual-review 状态订单,并保存输入 / 输出 Token、amount、路径、报价和风险快照。
用户完成钱包签名并广播后,前端把链上交易哈希提交到 /orders/{orderNo}/tx。系统会校验订单归属、终态、人工复核状态和 txHash 唯一性;同一订单已绑定 txHash 时只允许同哈希幂等同步,不允许换绑。提交后系统立即尝试读取交易回执:未出块时订单进入 confirming,回执失败或 revert 时进入 failed 并记录失败原因;成功回执必须解析到 Pancake V2 Swap 事件,系统会保存 receiptStatus、blockNumber、gasUsed、actualAmountInRaw、actualAmountOutRaw 和事件 JSON,订单进入 succeeded。弱网或 RPC 延迟时可调用 /orders/{orderNo}/sync 重新同步,GET /orders/{orderNo} 用于订单历史和详情展示。
Admin 运维入口已提供 DEX 配置、路由策略、Swap 订单查询 / 同步和 DEX 暂停 / 恢复能力:
GET /api/v1/admin/web3/swap/dex-configs
POST /api/v1/admin/web3/swap/dex-configs
PUT /api/v1/admin/web3/swap/dex-configs/{id}
POST /api/v1/admin/web3/swap/dex-configs/{id}/pause
POST /api/v1/admin/web3/swap/dex-configs/{id}/resume
GET /api/v1/admin/web3/swap/route-strategies
POST /api/v1/admin/web3/swap/route-strategies
PUT /api/v1/admin/web3/swap/route-strategies/{id}
GET /api/v1/admin/web3/swap/orders
GET /api/v1/admin/web3/swap/orders/{orderNo}
POST /api/v1/admin/web3/swap/orders/{orderNo}/syncAdmin 前端入口位于“Web3 运维 / Token 行情”页面中的 DEX 配置、路由策略和 Swap 订单页签。当前 contract 仍不替代 App / PC 真实用户入口;上线时用户侧入口也必须闭合后再开放生产 Swap。
用户侧安全体验入口
App / PC 钱包兑换页会先读取 Web3 用户体验元数据,再展示 Swap、后续 Bridge 和组合路由共用的安全基线。
GET /api/v1/money/web3/security/user-experience该接口需要登录,返回内容包括:
| 字段 | 说明 |
|---|---|
defaultMode | 当前固定为 preset-first,表示默认展示后台预设链和 Token |
walletExecutionMode | 当前固定为 self-custody,表示平台只生成报价和交易参数,真实交易由用户钱包签名 |
advancedTokenInputEnabled | 是否允许用户进入高级自定义 Token 合约地址输入 |
customTokenImportRequiresConfirmation | 自定义 Token 是否必须经过解析和风险确认 |
quoteRequiredBeforeTransaction | 是否必须先报价再生成交易 |
chains | 已启用链列表的用户侧选项 |
presetTokens | 已启用 Token 列表的用户侧默认选项 |
riskWarnings | 预设资产、自定义 Token、自托管签名和跨链托管资金池等统一风险提示 |
前端必须优先展示 chains / presetTokens,自定义 ERC-20 合约地址只作为高级入口;后续 Swap / Bridge / Swap + Bridge + Swap 组合路由不得绕过 Token 解析、黑名单、风险标签、流动性、价格影响、滑点、deadline、钱包签名和人工复核等校验。
相关
- Token 只读 + 写交易:web3-token.md
- Web3 顶层概览:web3.md