@meshconnect/uwc-tron-connector 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/events/state-machine.d.ts +26 -0
  2. package/dist/events/state-machine.d.ts.map +1 -0
  3. package/dist/events/state-machine.js +21 -0
  4. package/dist/events/state-machine.js.map +1 -0
  5. package/dist/index.d.ts +7 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +3 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/rest/abi.d.ts +23 -0
  10. package/dist/rest/abi.d.ts.map +1 -0
  11. package/dist/rest/abi.js +28 -0
  12. package/dist/rest/abi.js.map +1 -0
  13. package/dist/rest/address.d.ts +45 -0
  14. package/dist/rest/address.d.ts.map +1 -0
  15. package/dist/rest/address.js +124 -0
  16. package/dist/rest/address.js.map +1 -0
  17. package/dist/rest/trongrid-client.d.ts +57 -0
  18. package/dist/rest/trongrid-client.d.ts.map +1 -0
  19. package/dist/rest/trongrid-client.js +133 -0
  20. package/dist/rest/trongrid-client.js.map +1 -0
  21. package/dist/shared/error-utils.d.ts +9 -0
  22. package/dist/shared/error-utils.d.ts.map +1 -0
  23. package/dist/shared/error-utils.js +52 -0
  24. package/dist/shared/error-utils.js.map +1 -0
  25. package/dist/tron-connector.d.ts +85 -0
  26. package/dist/tron-connector.d.ts.map +1 -0
  27. package/dist/tron-connector.js +456 -0
  28. package/dist/tron-connector.js.map +1 -0
  29. package/dist/types.d.ts +8 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +2 -0
  32. package/dist/types.js.map +1 -0
  33. package/dist/wallets/base.d.ts +87 -0
  34. package/dist/wallets/base.d.ts.map +1 -0
  35. package/dist/wallets/base.js +118 -0
  36. package/dist/wallets/base.js.map +1 -0
  37. package/dist/wallets/bitget.d.ts +8 -0
  38. package/dist/wallets/bitget.d.ts.map +1 -0
  39. package/dist/wallets/bitget.js +14 -0
  40. package/dist/wallets/bitget.js.map +1 -0
  41. package/dist/wallets/okx.d.ts +4 -0
  42. package/dist/wallets/okx.d.ts.map +1 -0
  43. package/dist/wallets/okx.js +10 -0
  44. package/dist/wallets/okx.js.map +1 -0
  45. package/dist/wallets/registry.d.ts +8 -0
  46. package/dist/wallets/registry.d.ts.map +1 -0
  47. package/dist/wallets/registry.js +18 -0
  48. package/dist/wallets/registry.js.map +1 -0
  49. package/dist/wallets/tokenpocket.d.ts +9 -0
  50. package/dist/wallets/tokenpocket.d.ts.map +1 -0
  51. package/dist/wallets/tokenpocket.js +15 -0
  52. package/dist/wallets/tokenpocket.js.map +1 -0
  53. package/dist/wallets/tronlink.d.ts +8 -0
  54. package/dist/wallets/tronlink.d.ts.map +1 -0
  55. package/dist/wallets/tronlink.js +15 -0
  56. package/dist/wallets/tronlink.js.map +1 -0
  57. package/dist/wallets/trust.d.ts +9 -0
  58. package/dist/wallets/trust.d.ts.map +1 -0
  59. package/dist/wallets/trust.js +15 -0
  60. package/dist/wallets/trust.js.map +1 -0
  61. package/package.json +34 -0
  62. package/src/events/state-machine.ts +44 -0
  63. package/src/index.ts +17 -0
  64. package/src/rest/abi.test.ts +25 -0
  65. package/src/rest/abi.ts +33 -0
  66. package/src/rest/address.test.ts +55 -0
  67. package/src/rest/address.ts +140 -0
  68. package/src/rest/trongrid-client.test.ts +169 -0
  69. package/src/rest/trongrid-client.ts +205 -0
  70. package/src/shared/error-utils.ts +60 -0
  71. package/src/tron-connector.test.ts +612 -0
  72. package/src/tron-connector.ts +568 -0
  73. package/src/types.ts +11 -0
  74. package/src/wallets/base.ts +184 -0
  75. package/src/wallets/bitget.ts +17 -0
  76. package/src/wallets/okx.ts +10 -0
  77. package/src/wallets/registry.ts +26 -0
  78. package/src/wallets/tokenpocket.ts +15 -0
  79. package/src/wallets/tronlink.ts +15 -0
  80. package/src/wallets/trust.ts +18 -0
@@ -0,0 +1,568 @@
1
+ import type {
2
+ AvailableAddress,
3
+ ConnectorResult,
4
+ DetectedTronWalletInfo,
5
+ Namespace,
6
+ Network,
7
+ NetworkId,
8
+ SignatureType,
9
+ SwitchNetworkResult,
10
+ TransactionRequest,
11
+ TronNativeTransferRequest,
12
+ TronTRC20TransferRequest,
13
+ WalletMetadata
14
+ } from '@meshconnect/uwc-types'
15
+ import { WalletConnectorError } from '@meshconnect/uwc-types'
16
+ import { parseError } from './shared/error-utils'
17
+ import {
18
+ TronStateMachine,
19
+ type TronStateListener
20
+ } from './events/state-machine'
21
+ import {
22
+ TronGridClient,
23
+ type UnsignedTronTransaction
24
+ } from './rest/trongrid-client'
25
+ import { assertValidTronChecksum, tronAddressToHex } from './rest/address'
26
+ import {
27
+ encodeTrc20TransferParams,
28
+ TRC20_TRANSFER_SELECTOR_HASH
29
+ } from './rest/abi'
30
+ import {
31
+ detectProvider,
32
+ waitForWalletAddress,
33
+ type InjectedTronProvider
34
+ } from './wallets/base'
35
+ import {
36
+ ALL_TRON_WALLET_IDS,
37
+ TRON_WALLET_REGISTRY,
38
+ isTronWalletId,
39
+ type TronWalletId
40
+ } from './wallets/registry'
41
+ import type { TronConnectorConfig } from './types'
42
+
43
+ /** Wallet events we mirror into the state machine while connected. */
44
+ const WALLET_EVENTS = ['accountsChanged', 'disconnect'] as const
45
+
46
+ /**
47
+ * Default probe window for `getAvailableWallets`. Short on purpose — see the
48
+ * rationale in that method. Override via `TronConnectorConfig.discoveryTimeoutMs`.
49
+ */
50
+ const DEFAULT_DISCOVERY_TIMEOUT_MS = 300
51
+
52
+ interface ActiveTronConnection {
53
+ walletId: TronWalletId
54
+ provider: InjectedTronProvider
55
+ address: string
56
+ }
57
+
58
+ /**
59
+ * Mesh-owned Tron wallet connector. Zero third-party Tron runtime deps.
60
+ *
61
+ * One instance fronts all 5 supported wallets — TronLink, Bitget, OKX,
62
+ * TokenPocket, Trust — selected per-connect by the wallet id. Per-wallet
63
+ * enablement is controlled by `config.enabledWallets`.
64
+ *
65
+ * Transactions are built and broadcast against the Tron full-node HTTP API
66
+ * (`/wallet/*`); signing goes through the wallet's own injected
67
+ * `provider.tronWeb.trx.sign(unsigned)`, so the crypto stays in the wallet and
68
+ * costs us no bundle.
69
+ */
70
+ export class TronConnector {
71
+ private readonly config: TronConnectorConfig
72
+ private readonly enabled: ReadonlySet<TronWalletId>
73
+ private readonly clientByNetwork = new Map<NetworkId, TronGridClient>()
74
+ private readonly state = new TronStateMachine()
75
+
76
+ private active: ActiveTronConnection | null = null
77
+ private activeNetworkId: NetworkId | null = null
78
+ /** Listeners attached to the active provider, kept so we can detach on cleanup. */
79
+ private boundListeners: Array<{
80
+ event: string
81
+ handler: (...args: unknown[]) => void
82
+ }> = []
83
+
84
+ constructor(config: TronConnectorConfig) {
85
+ this.config = config
86
+ this.enabled = new Set(Array.from(config.enabledWallets))
87
+ }
88
+
89
+ /** Subscribe to connection-state transitions (decision D3). */
90
+ subscribe(listener: TronStateListener): () => void {
91
+ return this.state.subscribe(listener)
92
+ }
93
+
94
+ /**
95
+ * Returns the wallets that are (a) enabled in config and (b) currently
96
+ * injected into the page. Conforms to `Connector.getAvailableWallets` for
97
+ * namespace `'tron'`.
98
+ */
99
+ async getAvailableWallets(
100
+ namespace: Namespace,
101
+ expectedWallets?: WalletMetadata[]
102
+ ): Promise<DetectedTronWalletInfo[]> {
103
+ if (namespace !== 'tron') return []
104
+
105
+ // Discovery uses a deliberately SHORT timeout, distinct from connect()'s
106
+ // longer `readyStateTimeoutMs`. `detectProvider` only resolves early when a
107
+ // wallet *appears* — an absent wallet always waits out the full timeout. In
108
+ // discovery every enabled-but-not-installed wallet is probed in parallel, so
109
+ // a 3s timeout would make a user who has only TronLink installed wait ~3s
110
+ // for the wallet list on every call. A short window still catches wallets
111
+ // that inject slightly after page load (injection is async) without
112
+ // stalling the UI; connect(), which targets one already-chosen wallet and
113
+ // can afford to wait, keeps the longer timeout.
114
+ const timeout =
115
+ this.config.discoveryTimeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS
116
+ const interval = this.config.readyStatePollIntervalMs ?? 50
117
+ const allowedIds = this.resolveAllowedIds(expectedWallets)
118
+
119
+ // Probe in parallel but place each result by its index, so the returned
120
+ // order is stable (matches resolveAllowedIds) regardless of which wallet's
121
+ // probe resolves first.
122
+ const detected = await Promise.all(
123
+ allowedIds.map(async (id): Promise<DetectedTronWalletInfo | null> => {
124
+ const wrapper = TRON_WALLET_REGISTRY[id]
125
+ const provider = await detectProvider(wrapper, timeout, interval)
126
+ return provider ? { uuid: id, name: wrapper.displayName } : null
127
+ })
128
+ )
129
+ return detected.filter((w): w is DetectedTronWalletInfo => w !== null)
130
+ }
131
+
132
+ /**
133
+ * Connect to a Tron wallet on the given network. The wallet is identified by
134
+ * `walletProvider.uuid` (or `.walletId`), which must be an enabled
135
+ * `TronWalletId`. On success this connector holds the active provider until
136
+ * `disconnect()` or the next `connect()`.
137
+ */
138
+ async connect(
139
+ network: Network,
140
+ walletProvider?: { uuid?: string } | { walletId?: string }
141
+ ): Promise<ConnectorResult> {
142
+ this.assertTronNetwork(network, 'connect')
143
+ const walletId = this.requireWalletId(walletProvider)
144
+ const wrapper = TRON_WALLET_REGISTRY[walletId]
145
+
146
+ this.state.set({ status: 'connecting', walletId })
147
+
148
+ const provider = await detectProvider(
149
+ wrapper,
150
+ this.config.readyStateTimeoutMs ?? 3000,
151
+ this.config.readyStatePollIntervalMs ?? 50
152
+ )
153
+ if (!provider) {
154
+ // Same consistency guard as the catch below: a failed reconnect (target
155
+ // not installed) must not leave `active` pointing at the prior wallet.
156
+ this.cleanupActive()
157
+ this.state.set({ status: 'disconnected' })
158
+ throw new WalletConnectorError({
159
+ type: 'unknown',
160
+ message: `Tron wallet "${wrapper.displayName}" is not installed`
161
+ })
162
+ }
163
+
164
+ try {
165
+ // Prompts the wallet to expose the account. The wallet populates its
166
+ // `tronWeb` + account a tick after this resolves, so poll briefly.
167
+ await provider.request?.({ method: 'tron_requestAccounts' })
168
+ const address = await waitForWalletAddress(
169
+ provider,
170
+ this.config.readyStateTimeoutMs ?? 3000,
171
+ this.config.readyStatePollIntervalMs ?? 50
172
+ )
173
+ if (!address) {
174
+ throw new WalletConnectorError({
175
+ type: 'unknown',
176
+ message: `Tron wallet "${wrapper.displayName}" connected but exposed no address`
177
+ })
178
+ }
179
+
180
+ // Pre-warm the per-network client so the first send doesn't pay the cost.
181
+ this.getClient(network.id)
182
+ this.cleanupActive()
183
+ this.active = { walletId, provider, address }
184
+ this.activeNetworkId = network.id
185
+ this.bindWalletEvents(provider)
186
+ this.state.set({ status: 'connected', walletId, address })
187
+
188
+ const availableAddresses: AvailableAddress[] = [
189
+ { networkId: network.id, address }
190
+ ]
191
+ return { networkId: network.id, address, availableAddresses }
192
+ } catch (error) {
193
+ // Clear any prior/partial connection so `active` can't be left pointing
194
+ // at a stale wallet while we report 'disconnected' (e.g. a failed
195
+ // reconnect after a previous connect succeeded).
196
+ this.cleanupActive()
197
+ this.state.set({ status: 'disconnected' })
198
+ parseError(error)
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Tron network selection is wallet-driven (the user picks the chain in the
204
+ * wallet UI); there is no standardised programmatic switch across these
205
+ * injected providers. We repoint the build/broadcast endpoint to the target
206
+ * network and report the current address.
207
+ */
208
+ async switchNetwork(network: Network): Promise<SwitchNetworkResult> {
209
+ this.assertTronNetwork(network, 'switchNetwork')
210
+ if (!this.active) {
211
+ throw new WalletConnectorError({
212
+ type: 'unknown',
213
+ message: 'TronConnector.switchNetwork called before connect()'
214
+ })
215
+ }
216
+ try {
217
+ this.getClient(network.id)
218
+ } catch (error) {
219
+ // Surface a missing-endpoint (or similar) failure as a WalletConnectorError,
220
+ // consistent with connect/sendTransaction/signMessage.
221
+ parseError(error)
222
+ }
223
+ this.activeNetworkId = network.id
224
+ return { networkId: network.id, address: this.active.address }
225
+ }
226
+
227
+ async disconnect(): Promise<void> {
228
+ this.cleanupActive()
229
+ this.state.set({ status: 'disconnected' })
230
+ }
231
+
232
+ async signMessage(message: string): Promise<SignatureType> {
233
+ const active = this.requireActive('signMessage')
234
+ const tronWeb = active.provider.tronWeb
235
+ const signMessageV2 = tronWeb?.trx.signMessageV2
236
+ if (!tronWeb || !signMessageV2) {
237
+ throw new WalletConnectorError({
238
+ type: 'unknown',
239
+ message: 'Active Tron wallet does not support message signing'
240
+ })
241
+ }
242
+ try {
243
+ const signature = await signMessageV2.call(tronWeb.trx, message)
244
+ // Match the existing injected-connector tron path, which wraps the
245
+ // signature as a StandardSignature rather than the TronSignature variant.
246
+ return { type: 'standard', signature }
247
+ } catch (error) {
248
+ parseError(error)
249
+ }
250
+ }
251
+
252
+ async sendTransaction(request: TransactionRequest): Promise<string> {
253
+ const active = this.requireActive('sendTransaction')
254
+ if (!this.activeNetworkId) {
255
+ throw new WalletConnectorError({
256
+ type: 'unknown',
257
+ message: 'TronConnector.sendTransaction: no active network'
258
+ })
259
+ }
260
+ const client = this.getClient(this.activeNetworkId)
261
+
262
+ // Build, sign, and broadcast all run inside the try so node/network errors
263
+ // from the build step surface as WalletConnectorError (via parseError),
264
+ // consistent with the sign/broadcast path; the validation/guard throws below
265
+ // are already WalletConnectorError and pass through parseError unchanged.
266
+ try {
267
+ let unsigned: UnsignedTronTransaction
268
+ if (isTronTRC20(request)) {
269
+ this.assertValidAmount(request.amount)
270
+ this.assertFromMatchesActive(request.from, active.address)
271
+ // TRC20 encodes `to`/`contract` as raw hex, so the node never sees (or
272
+ // checksum-validates) the base58 forms — verify both here so a typo'd
273
+ // recipient or contract can't silently misdirect funds.
274
+ await this.assertValidRecipient(request.to)
275
+ await this.assertValidRecipient(request.contractAddress)
276
+ unsigned = await client.createTrc20Transfer({
277
+ from: request.from,
278
+ to: request.to,
279
+ amount: request.amount,
280
+ contractAddress: request.contractAddress
281
+ })
282
+ } else if (isTronNative(request)) {
283
+ // The node validates `to` (sent as base58 with visible:true), so no
284
+ // checksum check is needed on this path.
285
+ this.assertValidAmount(request.amount)
286
+ this.assertFromMatchesActive(request.from, active.address)
287
+ unsigned = await client.createTransaction({
288
+ from: request.from,
289
+ to: request.to,
290
+ amount: request.amount
291
+ })
292
+ } else {
293
+ throw new WalletConnectorError({
294
+ type: 'unknown',
295
+ message: 'TronConnector.sendTransaction: unsupported request shape'
296
+ })
297
+ }
298
+
299
+ // Don't blindly sign what the node returned: confirm the built tx encodes
300
+ // the requested recipient/amount/contract. A compromised or MITM'd
301
+ // endpoint could otherwise swap the destination, with the wallet's confirm
302
+ // UI as the only backstop.
303
+ this.assertBuiltTxMatchesRequest(unsigned, request)
304
+
305
+ // A wallet can lock/disconnect without firing an event, dropping
306
+ // `tronWeb`. Guard so we surface a clear error, not a raw TypeError.
307
+ const tronWeb = active.provider.tronWeb
308
+ if (!tronWeb) {
309
+ throw new WalletConnectorError({
310
+ type: 'unknown',
311
+ message: 'Active Tron wallet is unavailable; reconnect and try again'
312
+ })
313
+ }
314
+
315
+ const signed = await tronWeb.trx.sign(unsigned)
316
+ return await client.broadcastTransaction(signed)
317
+ } catch (error) {
318
+ parseError(error)
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Verify the node-built transaction encodes exactly what we asked for, before
324
+ * handing it to the wallet to sign. Reads `raw_data.contract[0].parameter.value`:
325
+ * native → owner/to/amount; TRC20 → owner/contract + the full `transfer` call
326
+ * data. Addresses are compared hex-normalized (base58/hex agnostic), falling
327
+ * back to exact string match for non-decodable placeholders.
328
+ */
329
+ private assertBuiltTxMatchesRequest(
330
+ unsigned: UnsignedTronTransaction,
331
+ request: TronNativeTransferRequest | TronTRC20TransferRequest
332
+ ): void {
333
+ const mismatch = (): never => {
334
+ throw new WalletConnectorError({
335
+ type: 'unknown',
336
+ message:
337
+ 'TronConnector.sendTransaction: the node-built transaction does not match the request (possible tampered/MITM endpoint)'
338
+ })
339
+ }
340
+ const value = (
341
+ unsigned.raw_data as
342
+ | {
343
+ contract?: Array<{
344
+ parameter?: { value?: Record<string, unknown> }
345
+ }>
346
+ }
347
+ | undefined
348
+ )?.contract?.[0]?.parameter?.value
349
+ if (!value) {
350
+ throw new WalletConnectorError({
351
+ type: 'unknown',
352
+ message:
353
+ 'TronConnector.sendTransaction: the node-built transaction does not match the request (possible tampered/MITM endpoint)'
354
+ })
355
+ }
356
+
357
+ const sameAddress = (built: unknown, requested: string): boolean => {
358
+ const a = String(built ?? '')
359
+ if (a === requested) return true
360
+ try {
361
+ return tronAddressToHex(a) === tronAddressToHex(requested)
362
+ } catch {
363
+ return false
364
+ }
365
+ }
366
+
367
+ if (!sameAddress(value['owner_address'], request.from)) mismatch()
368
+ if (isTronTRC20(request)) {
369
+ if (!sameAddress(value['contract_address'], request.contractAddress)) {
370
+ mismatch()
371
+ }
372
+ const expectedData = (
373
+ TRC20_TRANSFER_SELECTOR_HASH +
374
+ encodeTrc20TransferParams(request.to, request.amount)
375
+ ).toLowerCase()
376
+ if (String(value['data'] ?? '').toLowerCase() !== expectedData) mismatch()
377
+ } else {
378
+ if (!sameAddress(value['to_address'], request.to)) mismatch()
379
+ if (Number(value['amount']) !== request.amount) mismatch()
380
+ }
381
+ }
382
+
383
+ /** The wallet only signs for its own key — reject a mismatched `from` early. */
384
+ private assertFromMatchesActive(from: string, activeAddress: string): void {
385
+ if (from !== activeAddress) {
386
+ throw new WalletConnectorError({
387
+ type: 'unknown',
388
+ message: `TronConnector.sendTransaction: "from" (${from}) does not match the connected address (${activeAddress})`
389
+ })
390
+ }
391
+ }
392
+
393
+ // ---- internals -----------------------------------------------------------
394
+
395
+ private assertTronNetwork(network: Network, method: string): void {
396
+ if (network.namespace !== 'tron') {
397
+ throw new Error(
398
+ `TronConnector.${method}: expected a 'tron' network, got '${network.namespace}'`
399
+ )
400
+ }
401
+ }
402
+
403
+ private requireActive(method: string): ActiveTronConnection {
404
+ if (!this.active) {
405
+ throw new WalletConnectorError({
406
+ type: 'unknown',
407
+ message: `TronConnector.${method} called before connect()`
408
+ })
409
+ }
410
+ return this.active
411
+ }
412
+
413
+ /**
414
+ * Reject amounts that aren't safe non-negative integers. Two reasons, both on
415
+ * the money path: a fractional value makes `BigInt(amount)` throw a raw
416
+ * `RangeError` (and the build runs outside the sign/broadcast try/catch, so it
417
+ * would escape unwrapped); and a value beyond `Number.MAX_SAFE_INTEGER` can't
418
+ * be represented exactly, so it would silently transfer the wrong amount.
419
+ * `Number.isSafeInteger` covers both.
420
+ */
421
+ private assertValidAmount(amount: number): void {
422
+ if (!Number.isSafeInteger(amount) || amount < 0) {
423
+ throw new WalletConnectorError({
424
+ type: 'unknown',
425
+ message: `TronConnector.sendTransaction: amount must be a non-negative integer within the safe range, got ${amount}`
426
+ })
427
+ }
428
+ }
429
+
430
+ /** Verify a TRC20 recipient's base58 checksum, surfaced as a typed error. */
431
+ private async assertValidRecipient(to: string): Promise<void> {
432
+ try {
433
+ await assertValidTronChecksum(to)
434
+ } catch (error) {
435
+ throw new WalletConnectorError({
436
+ type: 'unknown',
437
+ message:
438
+ error instanceof Error
439
+ ? error.message
440
+ : 'TronConnector.sendTransaction: invalid recipient address'
441
+ })
442
+ }
443
+ }
444
+
445
+ private resolveAllowedIds(
446
+ expectedWallets?: WalletMetadata[]
447
+ ): TronWalletId[] {
448
+ if (!expectedWallets || expectedWallets.length === 0) {
449
+ return ALL_TRON_WALLET_IDS.filter(id => this.enabled.has(id))
450
+ }
451
+ const expectedIds = new Set(expectedWallets.map(wallet => wallet.id))
452
+ return ALL_TRON_WALLET_IDS.filter(
453
+ id => this.enabled.has(id) && expectedIds.has(id)
454
+ )
455
+ }
456
+
457
+ private requireWalletId(
458
+ provider: { uuid?: string } | { walletId?: string } | undefined
459
+ ): TronWalletId {
460
+ const raw =
461
+ (provider as { uuid?: string } | undefined)?.uuid ??
462
+ (provider as { walletId?: string } | undefined)?.walletId
463
+ if (!raw) {
464
+ throw new WalletConnectorError({
465
+ type: 'unknown',
466
+ message: 'TronConnector.connect: missing wallet identifier'
467
+ })
468
+ }
469
+ if (!isTronWalletId(raw)) {
470
+ throw new WalletConnectorError({
471
+ type: 'unknown',
472
+ message: `TronConnector.connect: unknown wallet "${raw}"`
473
+ })
474
+ }
475
+ if (!this.enabled.has(raw)) {
476
+ throw new WalletConnectorError({
477
+ type: 'unknown',
478
+ message: `TronConnector.connect: wallet "${raw}" is not enabled in config`
479
+ })
480
+ }
481
+ return raw
482
+ }
483
+
484
+ private getClient(networkId: NetworkId): TronGridClient {
485
+ let client = this.clientByNetwork.get(networkId)
486
+ if (!client) {
487
+ client = new TronGridClient(networkId, this.config.tronGrid)
488
+ this.clientByNetwork.set(networkId, client)
489
+ }
490
+ return client
491
+ }
492
+
493
+ private bindWalletEvents(provider: InjectedTronProvider): void {
494
+ if (!provider.on) return
495
+ for (const event of WALLET_EVENTS) {
496
+ const handler = (...args: unknown[]) => this.onWalletEvent(event, args[0])
497
+ provider.on(event, handler)
498
+ this.boundListeners.push({ event, handler })
499
+ }
500
+ }
501
+
502
+ private onWalletEvent(event: string, payload: unknown): void {
503
+ if (!this.active) return
504
+ if (event === 'disconnect') {
505
+ this.cleanupActive()
506
+ this.state.set({ status: 'disconnected' })
507
+ return
508
+ }
509
+ // accountsChanged payload shape varies by wallet: TronLink/TIP-1193 emit a
510
+ // bare address string, while the EIP-1193-style providers (OKX, Trust,
511
+ // Bitget) emit an array. An empty array is the logout signal. We normalise
512
+ // both, falling back to the wallet's current default address only when the
513
+ // payload carries nothing usable.
514
+ let nextAddress: string | undefined
515
+ if (typeof payload === 'string') {
516
+ nextAddress = payload
517
+ } else if (Array.isArray(payload)) {
518
+ nextAddress = payload[0] as string | undefined
519
+ } else {
520
+ nextAddress =
521
+ this.active.provider.tronWeb?.defaultAddress?.base58 || undefined
522
+ }
523
+ if (!nextAddress) {
524
+ this.cleanupActive()
525
+ this.state.set({ status: 'disconnected' })
526
+ return
527
+ }
528
+ this.active = { ...this.active, address: nextAddress }
529
+ this.state.set({
530
+ status: 'connected',
531
+ walletId: this.active.walletId,
532
+ address: nextAddress
533
+ })
534
+ }
535
+
536
+ private cleanupActive(): void {
537
+ if (this.active?.provider.removeListener) {
538
+ for (const { event, handler } of this.boundListeners) {
539
+ this.active.provider.removeListener(event, handler)
540
+ }
541
+ }
542
+ this.boundListeners = []
543
+ this.active = null
544
+ this.activeNetworkId = null
545
+ }
546
+ }
547
+
548
+ function isTronNative(
549
+ request: TransactionRequest
550
+ ): request is TronNativeTransferRequest {
551
+ return (
552
+ 'from' in request &&
553
+ 'to' in request &&
554
+ typeof (request as TronNativeTransferRequest).amount === 'number' &&
555
+ !('contractAddress' in request)
556
+ )
557
+ }
558
+
559
+ function isTronTRC20(
560
+ request: TransactionRequest
561
+ ): request is TronTRC20TransferRequest {
562
+ return (
563
+ 'from' in request &&
564
+ 'to' in request &&
565
+ 'contractAddress' in request &&
566
+ typeof (request as TronTRC20TransferRequest).amount === 'number'
567
+ )
568
+ }
package/src/types.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Public configuration types for the Tron connector now live in
3
+ * `@meshconnect/uwc-types` (alongside `TonConnectConfig` / `WalletConnectConfig`)
4
+ * so they can thread through `UniversalWalletConnector`. Re-exported here for
5
+ * convenience and backward compatibility.
6
+ */
7
+ export type {
8
+ TronConnectorConfig,
9
+ TronGridConfig,
10
+ TronWalletId
11
+ } from '@meshconnect/uwc-types'