Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 45 additions & 53 deletions packages/adapters/tron/src/connectors/TronConnectConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@ import type { Adapter } from '@tronweb3/tronwallet-abstract-adapter'
import type { RequestArguments } from '@walletconnect/universal-provider'

import type { CaipNetwork } from '@reown/appkit-common'
import { ChainController, OptionsController } from '@reown/appkit-controllers'
import { CaipNetworksUtil } from '@reown/appkit-utils'
import type { TronConnector } from '@reown/appkit-utils/tron'

import { ProviderEventEmitter } from '../utils/ProviderEventEmitter.js'

/**
* Maps TRON chain IDs to fullnode API URLs for building transactions.
*/
const TRON_FULLNODE_URLS: Record<string, string> = {
'0x2b6653dc': 'https://api.trongrid.io',
'0x94a9059e': 'https://api.shasta.trongrid.io',
'0xcd8690dc': 'https://nile.trongrid.io'
}

const DEFAULT_TRON_FULLNODE_URL = 'https://api.trongrid.io'

export class TronConnectConnector implements TronConnector {
public readonly chain = 'tron'
public readonly type = 'INJECTED'
Expand Down Expand Up @@ -108,40 +99,52 @@ export class TronConnectConnector implements TronConnector {
}

async sendTransaction(params: TronConnector.SendTransactionParams): Promise<string> {
const fullNodeUrl = await this.getFullNodeUrl()
const rpcUrl = this.getRpcUrl()

// Build a proper unsigned transaction via the TRON fullnode API
const createResponse = await fetch(`${fullNodeUrl}/wallet/createtransaction`, {
// Step 1: Build unsigned transaction via Blockchain API
const createResponse = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({
owner_address: params.from,
to_address: params.to,
amount: parseInt(params.value, 10),
visible: true
jsonrpc: '2.0',
id: 1,
method: 'tron_createTransaction',
params: [params.from, params.to, parseInt(params.value, 10), true]
})
})

const unsignedTx = await createResponse.json()
const createResult = await createResponse.json()
const unsignedTx = createResult?.result

if (!unsignedTx.txID) {
throw new Error(unsignedTx.Error || 'Failed to create transaction')
if (!unsignedTx?.txID) {
throw new Error(unsignedTx?.Error || 'Failed to create transaction')
}

// Sign the transaction with the wallet adapter
// Step 2: Sign the transaction with the wallet adapter
const signedTx = await this.adapter.signTransaction(unsignedTx)

// Broadcast the signed transaction
const broadcastResponse = await fetch(`${fullNodeUrl}/wallet/broadcasttransaction`, {
// Step 3: Broadcast the signed transaction via Blockchain API
const broadcastResponse = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signedTx)
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tron_broadcastTransaction',
params: [
signedTx.txID || unsignedTx.txID,
signedTx.visible ?? unsignedTx.visible ?? true,
signedTx.raw_data || unsignedTx.raw_data,
signedTx.raw_data_hex || unsignedTx.raw_data_hex,
signedTx.signature
]
})
})

const broadcastResult = await broadcastResponse.json()

if (!broadcastResult.result) {
throw new Error(broadcastResult.message || 'Failed to broadcast transaction')
if (!broadcastResult?.result?.result) {
throw new Error(broadcastResult?.result?.message || 'Failed to broadcast transaction')
}

return unsignedTx.txID
Expand All @@ -157,36 +160,25 @@ export class TronConnectConnector implements TronConnector {

// -- Private ------------------------------------------------------ //

/**
* Resolves the TRON fullnode API URL for building and broadcasting transactions.
* Uses the adapter's chainId to pick the correct network, but always maps to our
* own controlled URLs (not the adapter's fullNode) to avoid third-party CSP issues.
* TODO: Route through rpc.walletconnect.org once the RPC proxy supports TRON.
*/
private async getFullNodeUrl(): Promise<string> {
const adapterWithNetwork = this.adapter as Adapter & {
network?: () => Promise<{ chainId?: string }>
}

if (typeof adapterWithNetwork.network === 'function') {
try {
const network = await adapterWithNetwork.network()
private getRpcUrl(): string {
const chain = ChainController.getCaipNetworkByNamespace('tron')
const projectId = OptionsController.state.projectId

if (network.chainId) {
return TRON_FULLNODE_URLS[network.chainId] || DEFAULT_TRON_FULLNODE_URL
}
} catch {
// Adapter may not support network() — fall through to chain mapping
}
if (chain) {
return CaipNetworksUtil.getDefaultRpcUrl(chain, chain.caipNetworkId, projectId)
}

const chain = this.requestedChains[0]
const fallbackChain = this.requestedChains[0]

if (chain) {
return TRON_FULLNODE_URLS[chain.id] || DEFAULT_TRON_FULLNODE_URL
if (fallbackChain) {
return CaipNetworksUtil.getDefaultRpcUrl(
fallbackChain,
fallbackChain.caipNetworkId,
projectId
)
}

return DEFAULT_TRON_FULLNODE_URL
return ''
}

private setupAdapterListeners() {
Expand Down
80 changes: 68 additions & 12 deletions packages/adapters/tron/src/connectors/TronWalletConnectConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import type { CaipNetwork } from '@reown/appkit-common'
import { ConstantsUtil as CommonConstantsUtil, ConstantsUtil } from '@reown/appkit-common'
import {
ChainController,
OptionsController,
type RequestArguments,
WalletConnectConnector,
WcHelpersUtil
} from '@reown/appkit-controllers'
import { CaipNetworksUtil } from '@reown/appkit-utils'
import type { TronConnector } from '@reown/appkit-utils/tron'

import { ProviderEventEmitter } from '../utils/ProviderEventEmitter.js'
Expand Down Expand Up @@ -88,24 +90,72 @@ export class TronWalletConnectConnector
throw new Error('Chain not found')
}

const request = {
const rpcUrl = this.getRpcUrl(chain)

// Step 1: Build unsigned transaction via Blockchain API
const createTxResponse = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tron_createTransaction',
params: [params.from, params.to, parseInt(params.value, 10), true]
})
})
const createTxResult = await createTxResponse.json()
const unsignedTx = createTxResult?.result

if (!unsignedTx?.txID) {
throw new Error(unsignedTx?.Error || 'Failed to create transaction')
}

// Step 2: Send full transaction to wallet for signing via WalletConnect
const signRequest = {
method: 'tron_signTransaction',
params: {
address: params.from,
transaction: {
to: params.to,
from: params.from,
value: params.value,
data: params.data
}
transaction: unsignedTx
}
}
const result: { txID?: string; signature?: string[] } | undefined = await this.provider.request(
request,
chain.caipNetworkId
)
const signedTx:
| {
txID?: string
signature?: string[]
raw_data?: Record<string, unknown>
raw_data_hex?: string
visible?: boolean
}
| undefined = await this.provider.request(signRequest, chain.caipNetworkId)

return result?.txID || ''
if (!signedTx?.signature?.length) {
throw new Error('Transaction signing failed')
}

// Step 3: Broadcast the signed transaction via Blockchain API
const broadcastResponse = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tron_broadcastTransaction',
params: [
signedTx.txID || unsignedTx.txID,
signedTx.visible ?? unsignedTx.visible ?? true,
signedTx.raw_data || unsignedTx.raw_data,
signedTx.raw_data_hex || unsignedTx.raw_data_hex,
signedTx.signature
]
})
})
const broadcastResult = await broadcastResponse.json()

if (!broadcastResult?.result?.result) {
throw new Error(broadcastResult?.result?.message || 'Failed to broadcast transaction')
}

return signedTx.txID || unsignedTx.txID
}

async switchNetwork(): Promise<void> {
Expand All @@ -122,6 +172,12 @@ export class TronWalletConnectConnector
}

// -- Internals ----------------------------------------------------- //
private getRpcUrl(chain: CaipNetwork): string {
const projectId = OptionsController.state.projectId

return CaipNetworksUtil.getDefaultRpcUrl(chain, chain.caipNetworkId, projectId)
}

private get sessionChains() {
return WcHelpersUtil.getChainsFromNamespaces(this.provider.session?.namespaces)
}
Expand Down
Loading
Loading