|
|
@@ -1,12 +1,9 @@
|
|
|
import { ref, computed } from 'vue'
|
|
|
-import UniversalProvider from '@walletconnect/universal-provider'
|
|
|
-import { WalletConnectModal } from '@walletconnect/modal'
|
|
|
import { TronWeb, Types } from 'tronweb'
|
|
|
+import { tronMainnet } from '@reown/appkit/networks'
|
|
|
import {
|
|
|
TRON_MAINNET_CAIP2,
|
|
|
- TRON_MAINNET_CHAIN_ID_HEX,
|
|
|
isTronDepositNetwork,
|
|
|
- parseTronCaip10Address,
|
|
|
tronMainnetCaip10Account,
|
|
|
isTronRechargeUsdtSymbol,
|
|
|
TRC20_USDT_OFFICIAL_MAINNET,
|
|
|
@@ -14,16 +11,25 @@ import {
|
|
|
trc20AmountToMinUnits,
|
|
|
trxToSunAmount,
|
|
|
} from '@/utils/tronRecharge'
|
|
|
+import { getReownAppKit, reownAppKit, reownEnabled } from '@/config/reownAppKit'
|
|
|
+import {
|
|
|
+ abortActiveTronConnectWait,
|
|
|
+ waitForTronConnectAfterModalOpen,
|
|
|
+} from '@/utils/reownConnectWait'
|
|
|
import type { RechargeFlatNetworkOption, RechargeOrderDetail } from '@/stores/deposit'
|
|
|
|
|
|
const FEE_LIMIT_SUN = 150_000_000
|
|
|
|
|
|
-let tronProviderInit: Promise<UniversalProvider> | null = null
|
|
|
-let wcModal: WalletConnectModal | null = null
|
|
|
-let displayUriHandler: ((uri: string) => void) | null = null
|
|
|
+type TronProviderRequest = (args: { method: string; params?: unknown }) => Promise<unknown>
|
|
|
|
|
|
-function getProjectId(): string {
|
|
|
- return String(import.meta.env.VITE_WALLETCONNECT_PROJECT_ID ?? '').trim()
|
|
|
+interface TronWalletConnector {
|
|
|
+ request?: TronProviderRequest
|
|
|
+ provider?: {
|
|
|
+ session?: {
|
|
|
+ sessionProperties?: Record<string, unknown>
|
|
|
+ }
|
|
|
+ request?: (args: { method: string; params?: unknown }, chainId?: string) => Promise<unknown>
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function tronFullHost(): string {
|
|
|
@@ -39,74 +45,42 @@ function tronHeaders(): Record<string, string> | undefined {
|
|
|
return { 'TRON-PRO-API-KEY': key }
|
|
|
}
|
|
|
|
|
|
-function closeTronWcModal() {
|
|
|
- wcModal?.closeModal()
|
|
|
-}
|
|
|
-
|
|
|
-function ensureWcModal(projectId: string): WalletConnectModal {
|
|
|
- if (!wcModal && typeof window !== 'undefined') {
|
|
|
- wcModal = new WalletConnectModal({
|
|
|
- projectId,
|
|
|
- themeMode: 'dark',
|
|
|
- themeVariables: { '--wcm-z-index': '3000' },
|
|
|
- })
|
|
|
+async function ensureTronConnected(): Promise<string> {
|
|
|
+ const appKit = getReownAppKit()
|
|
|
+ try {
|
|
|
+ await appKit.switchNetwork(tronMainnet)
|
|
|
+ } catch {
|
|
|
+ // 已处于波场网络时可忽略
|
|
|
}
|
|
|
- return wcModal!
|
|
|
-}
|
|
|
|
|
|
-function attachDisplayUri(provider: UniversalProvider, projectId: string) {
|
|
|
- const modal = ensureWcModal(projectId)
|
|
|
- if (displayUriHandler) {
|
|
|
- provider.off('display_uri', displayUriHandler)
|
|
|
+ const existing = appKit.getAccount('tron')
|
|
|
+ if (existing?.isConnected && existing.address) {
|
|
|
+ return existing.address
|
|
|
}
|
|
|
- displayUriHandler = (uri: string) => {
|
|
|
- void modal.openModal({ uri, chains: [TRON_MAINNET_CAIP2] })
|
|
|
- }
|
|
|
- provider.on('display_uri', displayUriHandler)
|
|
|
-}
|
|
|
|
|
|
-async function getTronProvider(): Promise<UniversalProvider> {
|
|
|
- const projectId = getProjectId()
|
|
|
- if (!projectId) {
|
|
|
- throw new Error('未配置 VITE_WALLETCONNECT_PROJECT_ID')
|
|
|
- }
|
|
|
- if (!tronProviderInit) {
|
|
|
- tronProviderInit = UniversalProvider.init({
|
|
|
- projectId,
|
|
|
- metadata: {
|
|
|
- name: 'iBit',
|
|
|
- description: '链上充值(波场)',
|
|
|
- url: typeof window !== 'undefined' ? window.location.origin : 'https://ibit.com',
|
|
|
- icons: [],
|
|
|
- },
|
|
|
- })
|
|
|
- }
|
|
|
- const provider = await tronProviderInit
|
|
|
- attachDisplayUri(provider, projectId)
|
|
|
- return provider
|
|
|
+ await appKit.open({ namespace: 'tron', view: 'Connect' })
|
|
|
+ return waitForTronConnectAfterModalOpen()
|
|
|
}
|
|
|
|
|
|
-function firstTronAddress(provider: UniversalProvider): string {
|
|
|
- const raw = provider.session?.namespaces?.tron?.accounts ?? []
|
|
|
- for (const a of raw) {
|
|
|
- const addr = parseTronCaip10Address(String(a))
|
|
|
- if (addr) {
|
|
|
- return addr
|
|
|
- }
|
|
|
+export async function resetTronWalletSession() {
|
|
|
+ abortActiveTronConnectWait()
|
|
|
+ if (!reownEnabled) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await reownAppKit?.close()
|
|
|
+ await reownAppKit?.disconnect('tron')
|
|
|
+ } catch {
|
|
|
+ // Reset is best-effort; the next click will create a fresh session.
|
|
|
}
|
|
|
- throw new Error('未获取到波场钱包地址')
|
|
|
}
|
|
|
|
|
|
-/** 钱包校验时常要求 address 与 session.accounts 中某条完全一致(CAIP-10) */
|
|
|
-function tronSessionAccountString(provider: UniversalProvider, base58: string): string {
|
|
|
- const accounts = provider.session?.namespaces?.tron?.accounts ?? []
|
|
|
- for (const a of accounts) {
|
|
|
- const s = String(a)
|
|
|
- if (parseTronCaip10Address(s) === base58) {
|
|
|
- return s
|
|
|
- }
|
|
|
+function getTronConnector(): TronWalletConnector {
|
|
|
+ const connector = getReownAppKit().getProvider<TronWalletConnector>('tron')
|
|
|
+ if (!connector) {
|
|
|
+ throw new Error('波场钱包未连接')
|
|
|
}
|
|
|
- return tronMainnetCaip10Account(base58)
|
|
|
+ return connector
|
|
|
}
|
|
|
|
|
|
function sanitizeUnsignedTxForWc(tx: Types.Transaction): Record<string, unknown> {
|
|
|
@@ -115,9 +89,6 @@ function sanitizeUnsignedTxForWc(tx: Types.Transaction): Record<string, unknown>
|
|
|
return plain
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * 补全链上常见缺省、减少钱包端参数校验失败
|
|
|
- */
|
|
|
function prepareUnsignedForTronWalletConnect(tx: Types.Transaction): Record<string, unknown> {
|
|
|
const o = sanitizeUnsignedTxForWc(tx)
|
|
|
const rd = o.raw_data
|
|
|
@@ -210,45 +181,56 @@ function normalizeTronSignedResult(raw: unknown): Types.SignedTransaction {
|
|
|
throw new Error('钱包未返回有效签名(signature)')
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * 与 @tronweb3/walletconnect-tron 一致:v1 为扁平 transaction;非 v1 为 transaction: { transaction }。
|
|
|
- * address 须为 Base58(T…),不要用整条 CAIP-10 作为 address。
|
|
|
- */
|
|
|
-function tronSessionUsesSignV1(provider: UniversalProvider): boolean {
|
|
|
- const v = provider.session?.sessionProperties?.tron_method_version
|
|
|
+function tronSessionUsesSignV1(connector: TronWalletConnector): boolean {
|
|
|
+ const v = connector.provider?.session?.sessionProperties?.tron_method_version
|
|
|
return v === 'v1'
|
|
|
}
|
|
|
|
|
|
async function requestTronSignTransaction(
|
|
|
- provider: UniversalProvider,
|
|
|
fromBase58: string,
|
|
|
unsigned: Types.Transaction,
|
|
|
): Promise<Types.SignedTransaction> {
|
|
|
+ const connector = getTronConnector()
|
|
|
const prepared = prepareUnsignedForTronWalletConnect(unsigned)
|
|
|
const noTxId = cloneWithoutTxId(prepared)
|
|
|
- const sessionAcct = tronSessionAccountString(provider, fromBase58)
|
|
|
- const useV1 = tronSessionUsesSignV1(provider)
|
|
|
+ const sessionAcct = tronMainnetCaip10Account(fromBase58)
|
|
|
+ const useV1 = tronSessionUsesSignV1(connector)
|
|
|
|
|
|
- const paramVariants: unknown[] = []
|
|
|
+ const requestViaConnector = async (args: { method: string; params?: unknown }) => {
|
|
|
+ if (connector.request) {
|
|
|
+ return connector.request(args)
|
|
|
+ }
|
|
|
+ if (connector.provider?.request) {
|
|
|
+ return connector.provider.request(args, TRON_MAINNET_CAIP2)
|
|
|
+ }
|
|
|
+ throw new Error('波场钱包不支持签名请求')
|
|
|
+ }
|
|
|
|
|
|
+ try {
|
|
|
+ const injectedSigned = await requestViaConnector({
|
|
|
+ method: 'tron_sendTransaction',
|
|
|
+ params: { transaction: prepared },
|
|
|
+ })
|
|
|
+ return normalizeTronSignedResult(injectedSigned)
|
|
|
+ } catch (injectedErr) {
|
|
|
+ console.debug('[tron_sendTransaction] 注入钱包签名失败:', formatWalletConnectError(injectedErr))
|
|
|
+ }
|
|
|
+
|
|
|
+ const paramVariants: unknown[] = []
|
|
|
if (useV1) {
|
|
|
- // v1(session 明确声明):扁平结构,address = Base58
|
|
|
paramVariants.push({ address: fromBase58, transaction: prepared })
|
|
|
paramVariants.push({ transaction: prepared })
|
|
|
paramVariants.push({ address: fromBase58, transaction: { transaction: prepared } })
|
|
|
paramVariants.push({ address: sessionAcct, transaction: prepared })
|
|
|
- // 兜底:不带 txID
|
|
|
paramVariants.push({ address: fromBase58, transaction: noTxId })
|
|
|
paramVariants.push({ transaction: noTxId })
|
|
|
} else {
|
|
|
- // 非 v1(默认):嵌套结构优先,与 @tronweb3/walletconnect-tron adapter 一致
|
|
|
paramVariants.push({ address: fromBase58, transaction: { transaction: prepared } })
|
|
|
paramVariants.push({ address: fromBase58, transaction: prepared })
|
|
|
paramVariants.push({ transaction: { transaction: prepared } })
|
|
|
paramVariants.push({ transaction: prepared })
|
|
|
paramVariants.push({ address: sessionAcct, transaction: { transaction: prepared } })
|
|
|
paramVariants.push({ address: sessionAcct, transaction: prepared })
|
|
|
- // 兜底:不带 txID(少数钱包拒绝 txID 校验)
|
|
|
paramVariants.push({ address: fromBase58, transaction: { transaction: noTxId } })
|
|
|
paramVariants.push({ address: fromBase58, transaction: noTxId })
|
|
|
paramVariants.push({ transaction: { transaction: noTxId } })
|
|
|
@@ -259,25 +241,16 @@ async function requestTronSignTransaction(
|
|
|
for (let i = 0; i < paramVariants.length; i++) {
|
|
|
const params = paramVariants[i]
|
|
|
try {
|
|
|
- const out = await provider.request(
|
|
|
- {
|
|
|
- method: 'tron_signTransaction',
|
|
|
- params,
|
|
|
- } as never,
|
|
|
- TRON_MAINNET_CAIP2,
|
|
|
- )
|
|
|
+ const out = await requestViaConnector({
|
|
|
+ method: 'tron_signTransaction',
|
|
|
+ params,
|
|
|
+ })
|
|
|
return normalizeTronSignedResult(out)
|
|
|
} catch (e) {
|
|
|
lastErr = e
|
|
|
console.debug(
|
|
|
`[tron_signTransaction] variant ${i} 失败:`,
|
|
|
formatWalletConnectError(e),
|
|
|
- '| params keys:',
|
|
|
- params && typeof params === 'object' && !Array.isArray(params)
|
|
|
- ? Object.keys(params as object).join(',')
|
|
|
- : Array.isArray(params)
|
|
|
- ? `Array[${(params as unknown[]).length}]`
|
|
|
- : typeof params,
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
@@ -285,42 +258,13 @@ async function requestTronSignTransaction(
|
|
|
throw new Error(`tron_signTransaction 失败(已尝试多种参数格式)。最后错误:${msg}`)
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * 若当前已恢复的会话只有 eip155 等非 tron 命名空间,UniversalProvider 不会自动再连;
|
|
|
- * 若无可用 tron 账户则断开并重新用 tron 命名空间建连。
|
|
|
- */
|
|
|
-async function ensureTronWalletConnectSession(provider: UniversalProvider): Promise<void> {
|
|
|
- const raw = provider.session?.namespaces?.tron?.accounts ?? []
|
|
|
- const hasParsed = raw.some((a) => parseTronCaip10Address(String(a)) != null)
|
|
|
-
|
|
|
- if (provider.session && !hasParsed) {
|
|
|
- await provider.disconnect()
|
|
|
- }
|
|
|
-
|
|
|
- if (!provider.session) {
|
|
|
- await provider.connect({
|
|
|
- namespaces: {
|
|
|
- tron: {
|
|
|
- methods: ['tron_signTransaction', 'tron_signMessage'],
|
|
|
- chains: [TRON_MAINNET_CAIP2],
|
|
|
- events: [],
|
|
|
- rpcMap: {
|
|
|
- [TRON_MAINNET_CHAIN_ID_HEX]: tronFullHost(),
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- // 是否 v1 由钱包在回包里写入 sessionProperties;强行写 v1 可能与部分钱包参数形状不一致
|
|
|
- } as Parameters<UniversalProvider['connect']>[0])
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
export function useWalletConnectTronDeposit() {
|
|
|
const wcTronBusy = ref(false)
|
|
|
- const wcTronConfigured = computed(() => !!getProjectId())
|
|
|
+ const wcTronConfigured = computed(() => reownEnabled && reownAppKit != null)
|
|
|
|
|
|
function resetTronWalletBusy() {
|
|
|
wcTronBusy.value = false
|
|
|
- closeTronWcModal()
|
|
|
+ void resetTronWalletSession()
|
|
|
}
|
|
|
|
|
|
function canPayTronWithWallet(
|
|
|
@@ -330,14 +274,11 @@ export function useWalletConnectTronDeposit() {
|
|
|
if (!network || !order || Number(order.status) !== 0) {
|
|
|
return false
|
|
|
}
|
|
|
- if (!isTronDepositNetwork(network.protocol, network.networkName)) {
|
|
|
- return false
|
|
|
- }
|
|
|
- return true
|
|
|
+ return isTronDepositNetwork(network.protocol, network.networkName)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * WalletConnect(tron 命名空间)签名并广播:TRC20 transfer 或原生 TRX 转账。
|
|
|
+ * 通过 Reown AppKit(TronLink / MetaMask Tron / WalletConnect)签名并广播。
|
|
|
* @returns 链上 txid(与手动填写的 TxID 一致,非 0x 以太坊 hash)
|
|
|
*/
|
|
|
async function connectAndPayTron(params: {
|
|
|
@@ -346,19 +287,20 @@ export function useWalletConnectTronDeposit() {
|
|
|
}): Promise<string> {
|
|
|
const { network, order } = params
|
|
|
if (!canPayTronWithWallet(network, order)) {
|
|
|
- throw new Error('当前订单或网络不支持波场 WalletConnect')
|
|
|
+ throw new Error('当前订单或网络不支持波场钱包支付')
|
|
|
+ }
|
|
|
+ if (!reownEnabled) {
|
|
|
+ throw new Error('未配置 VITE_WALLETCONNECT_PROJECT_ID')
|
|
|
}
|
|
|
+
|
|
|
wcTronBusy.value = true
|
|
|
try {
|
|
|
- const provider = await getTronProvider()
|
|
|
+ const from = await ensureTronConnected()
|
|
|
const tronWeb = new TronWeb({
|
|
|
fullHost: tronFullHost(),
|
|
|
headers: tronHeaders(),
|
|
|
})
|
|
|
|
|
|
- await ensureTronWalletConnectSession(provider)
|
|
|
-
|
|
|
- const from = firstTronAddress(provider)
|
|
|
const to = order.rechargeAddress.trim()
|
|
|
if (!to || !to.startsWith('T')) {
|
|
|
throw new Error('收款地址不是有效的波场地址')
|
|
|
@@ -395,8 +337,8 @@ export function useWalletConnectTronDeposit() {
|
|
|
unsigned = await tronWeb.transactionBuilder.sendTrx(to, trxToSunAmount(amountStr), from)
|
|
|
}
|
|
|
|
|
|
- const signed = await requestTronSignTransaction(provider, from, unsigned)
|
|
|
- closeTronWcModal()
|
|
|
+ const signed = await requestTronSignTransaction(from, unsigned)
|
|
|
+ await reownAppKit?.close()
|
|
|
|
|
|
const broadcast = await tronWeb.trx.broadcast(signed)
|
|
|
if (!broadcast.result || !broadcast.txid) {
|
|
|
@@ -412,6 +354,7 @@ export function useWalletConnectTronDeposit() {
|
|
|
wcTronBusy,
|
|
|
wcTronConfigured,
|
|
|
resetTronWalletBusy,
|
|
|
+ clearTronWalletSession: resetTronWalletSession,
|
|
|
canPayTronWithWallet,
|
|
|
connectAndPayTron,
|
|
|
}
|