@anywalletkit/wallet-tron

中文文档
0.6.2发布于 2026/11/03Apache-2.0TypeScriptESM + CJS构建来源已验证近 30 天下载 2508

Tron wallet for Any Wallet Kit: BIP-32 m/44'/195'/0'/0/i accounts, TRX and TRC-20 transfers against a full-node HTTP API, no tronweb.

原始使用文档
文档目录6 节
  1. Install
  2. What happens before a signature
  3. Fees
  4. Memo ( raw_data.data )
  5. Signers
  6. Activity history

@anywalletkit/wallet-tron

npm

Tron accounts at m/44'/195'/0'/0/i (BIP-44 — the TronLink default), pinned by golden vectors in @anywalletkit/conformance. No tronweb: the full-node HTTP API builds raw_data, the account checks it, simulates it, signs txID with noble secp256k1 and broadcasts.

Install

npm install @anywalletkit/wallet-tron
# or: pnpm add @anywalletkit/wallet-tron  ·  yarn add @anywalletkit/wallet-tron

On npm: @anywalletkit/wallet-tron, published from the release workflow with provenance.

import WalletManagerTron from '@anywalletkit/wallet-tron'

const wallet = new WalletManagerTron(seedPhrase, { provider: ['https://api.trongrid.io', 'https://tron-rpc.example'], headers: { 'TRON-PRO-API-KEY': key } })
const account = await wallet.getAccount(0)
await account.transfer({ recipient: 'T…', amount: 1_000_000n })                    // 1 TRX (sun)
await account.transfer({ token: USDT_TRC20, recipient: 'T…', amount: 1_000_000n }) // TRC-20
await account.sendTransaction({ contractAddress, functionSelector: 'approve(address,uint256)', parameters: [...] })
const sig = await account.sign('hello')                                            // TIP-191, 0x + r||s||v (27/28)
wallet.dispose()

provider is one or more full-node urls (or custom transports); a list fails over in order, and every url request times out after timeoutMs (default 15 000). A broadcast that meets no answer is re-sent as the same signed bytes (one txID, accepted once) to the next provider, and DUP_TRANSACTION_ERROR counts as accepted; when no node answers it throws BroadcastUnknownError with the txID — watch it, do not build and sign again. Sun amounts are read from url providers without rounding (integers above 2^53 stay exact); a custom transport that hands over an unsafe number is refused, and a transfer above 2^53 − 1 sun is refused before the node builds it.

What happens before a signature

Every signTransaction / sendTransaction / transfer goes through the same gate:

  1. Hard checks (UnsafeTransactionError, with a code): exactly one contract; owner_address is this account; ref_block_bytes / ref_block_hash present and, with a provider, the reference block a block of the configured network (ref-block-mismatch; a Tron signature is bound to its network only by that block) — offline, only a mainnet account signs unless allowOfflineTestnetSigning: true (offline-network); raw_data.data at most 512 bytes (memo); expiration in the future and at most 24 h away; no TRX / TRC-20 transfer to the zero address unless allowBurn: true; fee_limit ≤ the tighter of transactionMaxFee and WriteOptions.maxFee, and fee_limit plus the bandwidth / memo / activation fees it does not cover within that cap (fee-limit); no AccountPermissionUpdateContract (it hands the account's keys over) unless allowPermissionChanges: true (permission-change); no contract type the wallet cannot decode unless allowUndecodedContracts: true (undecoded-contract; VoteWitnessContract only re-assigns votes and is signed). The same checks run in signRaw, which takes WriteOptions and keeps signatures already on the transaction (a multisig co-signer's): under a fee cap it prices the bytes through the provider and refuses a contract call without fee_limit, or any transaction when there is no provider to price it. Contract-call parameters must match the functionSelector signature and encode one ABI word each (encodeCall).
  2. Simulation (SimulationError, off with simulate: false): contract calls run through /wallet/triggerconstantcontract and the revert reason is ABI-decoded (Error(string), Panic); TRX transfers check that the balance covers amount + fees.
  3. Fee estimate and the transferMaxFee / transactionMaxFee cap.

describeTransaction(tx) tells you what a transaction does, offline: trx-transfer, trc20-transfer, trc20-approve, contract-call, freeze, unfreeze — with decoded recipient / amount / spender and warnings (unlimited-approve, zero-address, memo-fee, expired). account.describeTransactionOnline(tx) adds unactivated-recipient.

Fees

account.estimateFee(tx) → { bandwidth, energy, trxBurn, breakdown }, where trxBurn is what the sender actually pays (sun) after staked / free resources:

  • bandwidth points = serialized size (raw_data + protobuf envelope + 65-byte signature)
    • 64-byte result reserve — 268 for a TRX transfer, ~345 for a TRC-20 transfer. Staked bandwidth is used first, then the free daily allowance, else getTransactionFee sun per point.
  • energy = energy_used of a constant call; what staked energy does not cover is burned at getEnergyFee. fee_limit defaults to energy × price × 1.2 (capped at transactionMaxFee and the call's maxFee).
  • account creation (TRX transfer to an unseen address): getCreateNewAccountFeeInSystemContract (1 TRX) plus getCreateAccountFee (0.1 TRX) unless staked bandwidth covers the bytes — free bandwidth is not accepted for creation.
  • memo (raw_data.data): getMemoFee (1 TRX).

quoteSendTransaction / quoteTransfer return { fee: trxBurn, activationFee, estimate }.

Memo (raw_data.data)

transfer({ …, memo }), { to, value, memo } and contract calls with memo attach the text as protobuf field 10 of the node-built raw_data. The record is spliced at its canonical position (after expiration, before contract) so the txID we sign equals the one java-tron recomputes from its own re-serialization; the JSON broadcast carries the same bytes as raw_data.data (hex). The memo fee (getMemoFee, 1 TRX) and the extra bandwidth show up in estimateFee, and describeTransaction reports the memo with a memo-fee warning. Memos are capped at 512 UTF-8 bytes; attaching one to a transaction that already carries raw_data.data is refused.

Signers

Accounts sign through a secp256k1 Signer from @anywalletkit/core: the seed path uses a LocalSigner (zeroized on dispose()); new WalletManagerTron(signer) wraps an external one (hardware, MPC, backend). Transactions ask for ecdsa-recoverable over sha256(raw_data) with context tron-tx, TIP-191 messages with tron-message. A derivable SeedSigner at m/44'/195'/0'/0 yields getAccount(i) children; any other signer is getAccount(0) only. WalletAccountTron.fromPrivateKey(…) keeps the raw-key shape.

Activity history

createTronHistory is the chain's HistorySource (@anywalletkit/core) over TronGrid.

import { createTronHistory } from '@anywalletkit/wallet-tron/history'

const history = createTronHistory({ apiKey: process.env.TRONGRID_KEY })
const page = await history.listActivity('TVSz6xJL2M53HDZyFTeDcGCmEDdwmU4zbv', { limit: 25 })
const older = await history.listActivity(address, { cursor: page.cursor })
const one = await history.getTransaction(page.items[0].hash, address)
history.explorerTxUrl(one.hash) // https://tronscan.org/#/transaction/…
optiondefaultnotes
apiUrlhttps://api.trongrid.io (shasta / nile roots by network)Any TronGrid-compatible host.
apiKeynoneTRON-PRO-API-KEY. Keyless TronGrid allows 1 request/s per endpoint (it answers request rate exceeded the allowed_rps(1)).
networkmainnettron:0x2b6653dc / shasta / nile.
rateLimit1 rps keyless, 10 with a key
fetch, timeoutMsglobal, 15 000

Lists. GET /v1/accounts/{a}/transactions and GET /v1/accounts/{a}/transactions/trc20, both with limit, order_by=block_timestamp,desc, fingerprint to continue and min_timestamp when since is given, merged by txID: a TriggerSmartContract row plus its TRC-20 rows is one ChainTx whose token legs come from the TRC-20 rows (token = contract, standard: 'trc20', symbol, decimals from token_info). A transfer known only from the TRC-20 list (someone else's call that moved tokens to the address) is a row of its own: confirmed, no fee, no call, no block. TransferContract → native transfer and nativeValue; TransferAssetContract → a transfer with the TRC-10 id as token; TriggerSmartContract → call.to = contract (base58), call.selector = first 4 bytes of data, call.data = 0x + data, call.method = 'transfer' for a9059cbb, nativeValue = call_value; other system contracts (freeze, vote, delegate…) → call.method = contract type, call.to = receiver or the owner. internal_transactions with TRX call values become native transfers. fee = ret[0].fee (falls back to net_fee + energy_fee), status from contractRet, block = blockNumber.

Cursor. Each list is read along its own meta.fingerprint. The rows of one transaction share a block timestamp, so a page takes the next timestamp's rows from both lists, merges them by txID and emits them in txID order; when they do not fit in limit, the page ends inside that timestamp. The cursor is base64 JSON { v: 2, n, s: { tx, trc20 }, t?, g? }: the rows per API request n the positions were taken with (kept for the whole listing, whatever limit later pages ask), per list the fingerprint of the page holding its unread rows and how many of them were consumed (null once read to the end), plus — for a timestamp emitted only in part — its value t and the transactions of it already emitted g. Nothing is lost or repeated however many rows share a timestamp; a fingerprint the API does not advance (a gateway dropping the parameter) ends that list instead of looping, and more than 500 pages of one list in one call fail it with HistoryUnavailableError (the cursor stays usable) rather than cut the list; a v: 1 cursor from an earlier version is refused (start the listing again).

Single transaction. POST /wallet/gettransactionbyid + POST /wallet/gettransactioninfobyid (two requests): exact fee, status from receipt.result, block / time from the info, TRC-20 legs decoded from Transfer logs (symbol / decimals unknown there). The info endpoint is not called while listing — the fee of a TRC-20-only row stays undefined (it was paid by the other party), the fee of the address's own transactions is already in the list row.

Fixtures in test/fixtures/history/ were captured on 2026-10-14 from api.trongrid.io (keyless, paced): the test wallet's /transactions page (TRX in / out, TRC-10 airdrops) and its empty /trc20 page, plus — because the test wallet has no TRC-20 or contract activity — the pages of TCanhfMgr9RsFRoxkJ8YkJD8Faha85a14p (a counterparty of the wallet: USDT transfer via TriggerSmartContract, TRC-20 rows) and the full-node gettransactionbyid / gettransactioninfobyid of that USDT transfer. No protocol module recognizes Tron transactions yet, so there is no recognizer cross-check.

版本与发布记录

@anywalletkit/wallet-tron 共发布 13 个版本,首次发布于 2026/10/25;过去 12 个月发布了 13 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.2latest2026/11/035 天前
0.6.12026/10/308 天前
0.6.02026/10/309 天前
0.5.02026/10/2810 天前
0.4.12026/10/2811 天前
0.4.02026/10/2712 天前

关于 @anywalletkit/wallet-tron 的常见问题

@anywalletkit/wallet-tron 是什么?

Tron wallet for Any Wallet Kit: BIP-32 m/44'/195'/0'/0/i accounts, TRX and TRC-20 transfers against a full-node HTTP API, no tronweb.

如何安装 @anywalletkit/wallet-tron?

在项目目录运行 npm install @anywalletkit/wallet-tron,或使用 pnpm add / yarn add / bun add。

@anywalletkit/wallet-tron 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@anywalletkit/wallet-tron 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@anywalletkit/wallet-tron 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@anywalletkit/wallet-tron 可以商用吗?

@anywalletkit/wallet-tron 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@anywalletkit/wallet-tron 的下载量有多少?

根据 npm 官方下载统计,@anywalletkit/wallet-tron 在 2026/10/06 – 2026/11/04 这 30 天内被下载 2,508 次,日均约 84 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@anywalletkit/wallet-tron 还在维护吗?

最新版本 0.6.2 发布于 2026/11/03(5 天前);至今共发布 13 个版本,其中过去 12 个月发布了 13 个;首次发布于 2026/10/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。