Skip to content

feat: sign and broadcast a USDT transfer in the React Tron example - #223

Open
rtomas wants to merge 1 commit into
mainfrom
rtomas/tron-sign-usdt-transfer
Open

feat: sign and broadcast a USDT transfer in the React Tron example#223
rtomas wants to merge 1 commit into
mainfrom
rtomas/tron-sign-usdt-transfer

Conversation

@rtomas

@rtomas rtomas commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Adds a Send USDT action to react/react-tron that transfers 0.01 USDT (TRC-20) to the connected account — the TriggerSmartContract transaction is built with tronweb, signed by the wallet, then broadcast — since AppKit's typed sendTransaction only moves native TRX.

Neither Tron connector accepts a plain tron_signTransaction request in appkit 1.8.21: the injected TronLink connector throws Unsupported method and only accepts tron_sendTransaction (which signs without broadcasting), while TronWalletConnectConnector.request() delegates to an internalRequest method the base WalletConnectConnector doesn't define — so the new signTransaction helper picks the right path per connector type.

Also registers the MetaMask Tron wallet adapter alongside TronLink, and fixes Send tx, which self-transferred TRX from and to the connected account (TRON rejects that outright with Cannot transfer TRX to yourself) — it now sends to a configurable RECIPIENT_ADDRESS.

Supporting changes: errors and the USDT tx hash (with an explorer link) are surfaced in InfoList, tronweb + vite-plugin-node-polyfills are added, createAppKit uses the shared networks export instead of an inline copy, and the README documents each action plus the per-network USDT contract addresses.

Verified with tsc -b, eslint, and vite build; the tronweb call was checked against both networks to confirm it produces selector a9059cbb with amount 10000 (USDT has 6 decimals). The wallet signing and broadcast steps still need a manual run with a funded account.

🤖 Generated with Claude Code

Adds a "Send USDT" action to react/react-tron that transfers 0.01 USDT
(TRC-20) to the connected account: the TriggerSmartContract transaction is
built with tronweb, signed by the wallet, then broadcast.

AppKit's typed sendTransaction only moves native TRX, and neither Tron
connector accepts a plain tron_signTransaction request in appkit 1.8.21 --
the injected TronLink connector throws "Unsupported method" and only accepts
tron_sendTransaction (which signs without broadcasting), while
TronWalletConnectConnector.request() delegates to an internalRequest method
that the base WalletConnectConnector doesn't define. The new signTransaction
helper picks the right path per connector type.

Also:
- register the MetaMask Tron wallet adapter alongside TronLink
- fix "Send tx", which self-transferred TRX from and to the connected
  account; TRON rejects that outright, so it now sends to RECIPIENT_ADDRESS
- surface errors and the USDT tx hash (with an explorer link) in InfoList
- add tronweb + vite-plugin-node-polyfills, which tronweb needs under Vite
- use the shared networks export in createAppKit instead of an inline copy
- document the actions in the README

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
appkit-web-examples-multichain-react-evm-tron Ready Ready Preview Aug 11, 2026 7:09pm
appkit-web-examples-next-multichain Error Error Aug 11, 2026 7:09pm
appkit-web-examples-react-multichain Ready Ready Preview Aug 11, 2026 7:09pm
appkit-web-examples-react-wagmi Building Building Preview Aug 11, 2026 7:09pm
appkit-web-examples-vue-solana Building Building Preview Aug 11, 2026 7:09pm
react-core-universal-connector Ready Ready Preview Aug 11, 2026 7:09pm
17 Skipped Deployments
Project Deployment Actions Updated (UTC)
appkit-web-examples-javascript-bitcoin Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-javascript-ethers Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-javascript-solana Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-javascript-wagmi Ignored Ignored Aug 11, 2026 7:09pm
appkit-web-examples-next-bitcoin Ignored Ignored Aug 11, 2026 7:09pm
appkit-web-examples-next-ethers Ignored Ignored Aug 11, 2026 7:09pm
appkit-web-examples-next-solana Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-next-wagmi Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-react-bitcoin Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-react-core-up Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-react-ethers Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-react-ethers5 Ignored Ignored Aug 11, 2026 7:09pm
appkit-web-examples-react-solana Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-vue-bitcoin Ignored Ignored Aug 11, 2026 7:09pm
appkit-web-examples-vue-ethers Ignored Ignored Preview Aug 11, 2026 7:09pm
appkit-web-examples-vue-multichain Ignored Ignored Aug 11, 2026 7:09pm
appkit-web-examples-vue-wagmi Ignored Ignored Preview Aug 11, 2026 7:09pm

Request Review

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude finished @rtomas's task in 2m 58s —— View job


Code Review

  • Gather context and read changed files
  • Analyze code for bugs, security, performance issues
  • Run license compliance check — ✅ all new deps (tronweb, @tronweb3/tronwallet-adapter-metamask-tron, vite-plugin-node-polyfills) are MIT
  • Run breaking changes check — ✅ no breaking changes
  • Run data classification check
  • Post final review

Found 4 issue(s)

Issue 1: Hardcoded fallback project ID committed to source (pre-existing, flagged by data classification scan)

ID: dcl-config-hardcoded-project-id-a3f1
File: react/react-tron/src/config/index.tsx:9
Severity: CRITICAL
Category: security / data_classification

Context:

  • Pattern: import.meta.env.VITE_PROJECT_ID || "b56e18d47c72ab683b10814fe9495694" — a real, working Reown credential committed as a source-code fallback
  • Risk: Any actor who clones the repo can call Reown platform APIs (relay, analytics, push) under this project's identity/quota. The CLAUDE.md explicitly states: "Never hardcode production projectIds — always use environment variables"
  • Impact: Quota exhaustion, analytics noise, potential abuse if the credential is ever de-restricted on the dashboard
  • Trigger: Active whenever VITE_PROJECT_ID is unset — the default out-of-the-box experience

Note: this line predates the PR; it appears in the unchanged portion of the file. Flagging for awareness.

Recommendation: Remove the fallback; fail loudly:

export const projectId = import.meta.env.VITE_PROJECT_ID
if (!projectId) {
  throw new Error('VITE_PROJECT_ID is not set — get one at https://dashboard.reown.com')
}

Issue 2: handleSendTx and handleSignMsg silently swallow errors

ID: actionbuttonlist-missing-sendError-c2f4
File: react/react-tron/src/components/ActionButtonList.tsx:65,78
Severity: MEDIUM
Category: code_quality

Context:

  • Pattern: Both handlers throw on failure but have no try/catch, so the rejected promise becomes an unhandled rejection in the browser. handleSendUsdt wraps identically shaped logic in try/catch and calls sendError()
  • Risk: Errors from "Send tx" and "Sign msg" are invisible to the user; no feedback is shown in the Error section
  • Impact: Poor DX in an example that is meant to demonstrate these flows; users see nothing when a signature is rejected

Recommendation: Wrap both in the same pattern used by handleSendUsdt: Fix this →

const handleSendTx = async () => {
  try {
    sendError('')
    if (!walletProvider || !address) throw Error('user is disconnected')
    const hash = await walletProvider.sendTransaction({ from: address, to: RECIPIENT_ADDRESS, value: '1000' })
    sendHash(hash)
  } catch (error) {
    sendError(error instanceof Error ? error.message : String(error))
  }
}

Issue 3: No null guard on WalletConnect provider before calling .request()

ID: actionbuttonlist-unsafe-wc-provider-cast-8b19
File: react/react-tron/src/components/ActionButtonList.tsx:45-53
Severity: LOW
Category: code_quality

Context:

  • Pattern: const { provider } = walletProvider as unknown as { provider: WalletConnectProvider } — no check that provider is defined before calling provider.request(...)
  • Risk: If the underlying WC connector shape changes (e.g., different AppKit version), the error is a cryptic Cannot read properties of undefined (reading 'request') rather than something actionable
  • Trigger: WalletConnect path in signTransaction() when walletProvider.provider is undefined

Recommendation: Fix this →

const { provider } = walletProvider as unknown as { provider: WalletConnectProvider }
if (!provider) throw Error('WalletConnect provider is not available')

Issue 4: Dead commented-out code leaves empty "Status:" label in UI

ID: infolist-dead-commented-status-d7a3
File: react/react-tron/src/components/InfoList.tsx:64
Severity: LOW
Category: code_quality

Context:

  • Pattern: Status: {/* receipt?.status.toString() */}<br /> renders a dangling Status: label with no value
  • Risk: Confusing UX in a demo; the label implies state that is never populated

Recommendation: Remove the line or wire it to actual status data: Fix this →


🔒 External Domain URL (Non-blocking)
URL: https://discord.gg/tron File: react/react-tron/README.md:54 — verify intentional; links to a third-party Discord server

|

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant