tronlink-signer 0.1.0 → 0.1.2

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TronLink
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # tronlink-signer
2
+
3
+ Standalone SDK for signing TRON transactions via the TronLink browser wallet. Private keys never leave TronLink — all signing happens in the browser through a local approval page.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install tronlink-signer
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { TronSigner } from "tronlink-signer";
15
+
16
+ const signer = new TronSigner();
17
+ await signer.start();
18
+
19
+ const { address, network } = await signer.connectWallet();
20
+ const { txId } = await signer.sendTrx("TXxx...", 1);
21
+ const { balance } = await signer.getBalance("TXxx...");
22
+
23
+ await signer.stop();
24
+ ```
25
+
26
+ ## API
27
+
28
+ ### `new TronSigner(config?: Partial<AppConfig>)`
29
+
30
+ Creates a new signer instance. If no config is provided, it reads from environment variables via `loadConfig()`.
31
+
32
+ ### `signer.start(): Promise<void>`
33
+
34
+ Starts the local HTTP server for browser approval.
35
+
36
+ ### `signer.stop(): Promise<void>`
37
+
38
+ Stops the server and clears all pending requests.
39
+
40
+ ### `signer.getConfig(): AppConfig`
41
+
42
+ Returns the current configuration.
43
+
44
+ ### `signer.connectWallet(network?: TronNetwork): Promise<{ address: string; network: string }>`
45
+
46
+ Opens the browser to connect TronLink and retrieve the wallet address and current network. If the wallet is already connected (same browser tab still open), auto-completes without user interaction.
47
+
48
+ ### `signer.sendTrx(to, amount, network?): Promise<{ txId: string }>`
49
+
50
+ Sends TRX to a recipient address. Opens a browser approval page for the user to confirm.
51
+
52
+ | Parameter | Type | Description |
53
+ | --------- | ---- | ----------- |
54
+ | `to` | `string` | Recipient Tron address (base58) |
55
+ | `amount` | `number` | Amount of TRX to send |
56
+ | `network` | `TronNetwork` | Optional network override |
57
+
58
+ ### `signer.sendTrc20(contractAddress, to, amount, decimals?, network?): Promise<{ txId: string }>`
59
+
60
+ Sends TRC20 tokens. Opens a browser approval page.
61
+
62
+ | Parameter | Type | Description |
63
+ | --------- | ---- | ----------- |
64
+ | `contractAddress` | `string` | TRC20 token contract address |
65
+ | `to` | `string` | Recipient Tron address (base58) |
66
+ | `amount` | `string` | Amount in human-readable units (e.g., `"1.5"` for 1.5 USDT). Decimals conversion is automatic. |
67
+ | `decimals` | `number` | Token decimals (default: 6) |
68
+ | `network` | `TronNetwork` | Optional network override |
69
+
70
+ ### `signer.signMessage(message, network?): Promise<{ signature: string }>`
71
+
72
+ Signs a plain text message.
73
+
74
+ ### `signer.signTypedData(typedData, network?): Promise<{ signature: string }>`
75
+
76
+ Signs EIP-712 typed data.
77
+
78
+ ```ts
79
+ const { signature } = await signer.signTypedData({
80
+ domain: { name: "MyDApp", version: "1", chainId: 728126428 },
81
+ types: {
82
+ Greeting: [{ name: "contents", type: "string" }],
83
+ },
84
+ primaryType: "Greeting",
85
+ message: { contents: "Hello Tron!" },
86
+ });
87
+ ```
88
+
89
+ ### `signer.signTransaction(transaction, network?, broadcast?): Promise<{ signedTransaction: Record<string, unknown>; txId?: string }>`
90
+
91
+ Signs a raw transaction. When `broadcast` is `true`, the signed transaction is also broadcast on-chain via TronLink and the `txId` is returned.
92
+
93
+ | Parameter | Type | Description |
94
+ | --------- | ---- | ----------- |
95
+ | `transaction` | `Record<string, unknown>` | Raw transaction object to sign |
96
+ | `network` | `TronNetwork` | Optional network override |
97
+ | `broadcast` | `boolean` | Whether to broadcast after signing (default: `false`) |
98
+
99
+ ```ts
100
+ // Sign only
101
+ const { signedTransaction } = await signer.signTransaction(tx);
102
+
103
+ // Sign and broadcast
104
+ const { signedTransaction, txId } = await signer.signTransaction(tx, "nile", true);
105
+ ```
106
+
107
+ ### `signer.getBalance(address, network?): Promise<{ balance: string; balanceSun: number }>`
108
+
109
+ Gets TRX balance for an address. No browser approval needed.
110
+
111
+ ## How It Works
112
+
113
+ 1. Your code calls a signing method (e.g., `signMessage`)
114
+ 2. A local HTTP server starts on port 3386 and a **single browser tab** opens the approval page
115
+ 3. The approval page discovers the wallet via **TIP-6963** protocol (fallback to `window.tron`)
116
+ 4. Auto-unlocks wallet and switches network if needed
117
+ 5. For `connectWallet`, if the wallet is already connected, it auto-completes without user interaction
118
+ 6. For signing/sending, the user reviews the transaction details and clicks Approve / Reject
119
+ 7. The approval page parses transaction types (TRX transfer, TRC20, TRC721 NFT, stake, delegate, vote, etc.) into human-readable display
120
+ 8. TronLink handles signing in the browser
121
+ 9. The result is returned to your code — the page stays open and polls for the next request
122
+
123
+ All subsequent operations reuse the same browser tab. Each server session has a unique ID — old browser tabs from previous sessions are automatically invalidated. The page detects server disconnection via heartbeat and shows a session expired message. The local server binds to `127.0.0.1` only. If port 3386 is in use, it auto-increments. Requests timeout after 5 minutes. The server gracefully shuts down on process exit (SIGINT/SIGTERM).
124
+
125
+ ## Networks
126
+
127
+ All signing methods accept an optional `network` parameter:
128
+
129
+ | Network | Full Host | Explorer |
130
+ | ------- | --------- | -------- |
131
+ | `mainnet` (default) | `https://api.trongrid.io` | `https://tronscan.org` |
132
+ | `nile` | `https://nile.trongrid.io` | `https://nile.tronscan.org` |
133
+ | `shasta` | `https://api.shasta.trongrid.io` | `https://shasta.tronscan.org` |
134
+
135
+ ## Environment Variables
136
+
137
+ | Variable | Description | Default |
138
+ | -------- | ----------- | ------- |
139
+ | `TRON_NETWORK` | Default network | `mainnet` |
140
+ | `TRON_HTTP_PORT` | Local HTTP server port | `3386` |
141
+ | `TRON_API_KEY` | TronGrid API key (optional) | - |
142
+
143
+ ## Types
144
+
145
+ ```ts
146
+ type TronNetwork = "mainnet" | "nile" | "shasta";
147
+
148
+ interface AppConfig {
149
+ network: TronNetwork;
150
+ httpPort: number;
151
+ apiKey?: string;
152
+ }
153
+
154
+ interface NetworkConfig {
155
+ name: string;
156
+ fullHost: string;
157
+ explorerUrl: string;
158
+ }
159
+ ```
160
+
161
+ ## Exports
162
+
163
+ ```ts
164
+ // Class
165
+ export { TronSigner } from "./tron-signer.js";
166
+
167
+ // Config
168
+ export { NETWORKS, DEFAULT_HTTP_PORT, REQUEST_TIMEOUT_MS, loadConfig } from "./config.js";
169
+
170
+ // Types
171
+ export type {
172
+ TronNetwork, NetworkConfig, AppConfig,
173
+ PendingRequestType, PendingRequest,
174
+ ConnectData, SendTrxData, SendTrc20Data,
175
+ SignMessageData, SignTypedDataData, SignTransactionData,
176
+ } from "./types.js";
177
+ ```
178
+
179
+ ## License
180
+
181
+ MIT License Copyright (c) 2026 TronLink
package/dist/index.d.ts CHANGED
@@ -30,6 +30,7 @@ interface SignTypedDataData {
30
30
  }
31
31
  interface SignTransactionData {
32
32
  transaction: Record<string, unknown>;
33
+ broadcast?: boolean;
33
34
  }
34
35
  interface AppConfig {
35
36
  network: TronNetwork;
@@ -50,6 +51,7 @@ declare class TronSigner {
50
51
  private resolveNetwork;
51
52
  connectWallet(network?: TronNetwork): Promise<{
52
53
  address: string;
54
+ network: string;
53
55
  }>;
54
56
  sendTrx(to: string, amount: number, network?: TronNetwork): Promise<{
55
57
  txId: string;
@@ -63,8 +65,9 @@ declare class TronSigner {
63
65
  signTypedData(typedData: Record<string, unknown>, network?: TronNetwork): Promise<{
64
66
  signature: string;
65
67
  }>;
66
- signTransaction(transaction: Record<string, unknown>, network?: TronNetwork): Promise<{
68
+ signTransaction(transaction: Record<string, unknown>, network?: TronNetwork, broadcast?: boolean): Promise<{
67
69
  signedTransaction: Record<string, unknown>;
70
+ txId?: string;
68
71
  }>;
69
72
  getBalance(address: string, network?: TronNetwork): Promise<{
70
73
  balance: string;
package/dist/index.js CHANGED
@@ -67,7 +67,6 @@ var PendingStore = class {
67
67
  }
68
68
  get(id) {
69
69
  const entry = this.pending.get(id);
70
- console.error(`[PendingStore] Get request: ${id}, found: ${!!entry}, total pending: ${this.pending.size}`);
71
70
  return entry?.request;
72
71
  }
73
72
  resolve(id, result) {
@@ -86,6 +85,15 @@ var PendingStore = class {
86
85
  entry.reject(new Error(error));
87
86
  return true;
88
87
  }
88
+ getNext() {
89
+ let oldest;
90
+ for (const entry of this.pending.values()) {
91
+ if (!oldest || entry.request.createdAt < oldest.request.createdAt) {
92
+ oldest = entry;
93
+ }
94
+ }
95
+ return oldest?.request;
96
+ }
89
97
  size() {
90
98
  return this.pending.size;
91
99
  }
@@ -100,15 +108,45 @@ var PendingStore = class {
100
108
 
101
109
  // src/http-server.ts
102
110
  import express from "express";
111
+ import { randomUUID as randomUUID2 } from "crypto";
112
+
113
+ // src/browser.ts
114
+ import open from "open";
115
+ var pageOpened = false;
116
+ var lastHeartbeat = 0;
117
+ var HEARTBEAT_TIMEOUT = 1e4;
118
+ function recordHeartbeat() {
119
+ lastHeartbeat = Date.now();
120
+ }
121
+ function isPageAlive() {
122
+ if (!pageOpened) return false;
123
+ if (lastHeartbeat === 0) return false;
124
+ return Date.now() - lastHeartbeat < HEARTBEAT_TIMEOUT;
125
+ }
126
+ async function openApprovalPage(port, _requestId) {
127
+ const url = `http://127.0.0.1:${port}/`;
128
+ if (isPageAlive()) {
129
+ return;
130
+ }
131
+ pageOpened = true;
132
+ lastHeartbeat = Date.now();
133
+ await open(url);
134
+ }
135
+
136
+ // src/http-server.ts
103
137
  var HttpServer = class {
104
138
  app;
105
139
  server = null;
106
140
  pendingStore;
107
141
  htmlContent;
142
+ jsFiles;
108
143
  port = 0;
109
- constructor(pendingStore, htmlContent) {
144
+ sessionId;
145
+ constructor(pendingStore, htmlContent, jsFiles = {}) {
110
146
  this.pendingStore = pendingStore;
111
147
  this.htmlContent = htmlContent;
148
+ this.jsFiles = jsFiles;
149
+ this.sessionId = randomUUID2();
112
150
  this.app = express();
113
151
  this.setupRoutes();
114
152
  }
@@ -118,6 +156,26 @@ var HttpServer = class {
118
156
  res.setHeader("Content-Type", "text/html");
119
157
  res.send(this.htmlContent);
120
158
  });
159
+ this.app.get("/js/:name", (req, res) => {
160
+ const content = this.jsFiles[req.params.name];
161
+ if (!content) {
162
+ res.status(404).send("Not found");
163
+ return;
164
+ }
165
+ res.setHeader("Content-Type", "application/javascript");
166
+ res.send(content);
167
+ });
168
+ this.app.get("/api/pending/next", (_req, res) => {
169
+ const next = this.pendingStore.getNext();
170
+ if (!next) {
171
+ res.status(404).json({ error: "No pending request" });
172
+ return;
173
+ }
174
+ res.json({
175
+ ...next,
176
+ networkConfig: NETWORKS[next.network]
177
+ });
178
+ });
121
179
  this.app.get("/api/pending/:id", (req, res) => {
122
180
  const request = this.pendingStore.get(req.params.id);
123
181
  if (!request) {
@@ -148,6 +206,18 @@ var HttpServer = class {
148
206
  res.json({ ok: true });
149
207
  }
150
208
  });
209
+ this.app.get("/api/session", (_req, res) => {
210
+ res.json({ sessionId: this.sessionId });
211
+ });
212
+ this.app.post("/api/heartbeat", (req, res) => {
213
+ const clientSession = req.body && req.body.sessionId;
214
+ if (clientSession && clientSession !== this.sessionId) {
215
+ res.status(410).json({ error: "Session expired", sessionId: this.sessionId });
216
+ return;
217
+ }
218
+ recordHeartbeat();
219
+ res.json({ ok: true, sessionId: this.sessionId });
220
+ });
151
221
  this.app.get("/api/health", (_req, res) => {
152
222
  res.json({ status: "ok" });
153
223
  });
@@ -195,13 +265,6 @@ var HttpServer = class {
195
265
  }
196
266
  };
197
267
 
198
- // src/browser.ts
199
- import open from "open";
200
- async function openApprovalPage(port, requestId) {
201
- const url = `http://127.0.0.1:${port}/?requestId=${requestId}`;
202
- await open(url);
203
- }
204
-
205
268
  // src/web/index.html
206
269
  var web_default = `<!DOCTYPE html>
207
270
  <html lang="en">
@@ -308,46 +371,114 @@ var web_default = `<!DOCTYPE html>
308
371
  <button class="btn-reject" id="rejectBtn">Reject</button>
309
372
  </div>
310
373
  </div>
311
- <script>
374
+ <script src="/js/wallet.js"></script>
375
+ <script src="/js/tx-parser.js"></script>
376
+ <script src="/js/actions.js"></script>
377
+ <script src="/js/app.js"></script>
378
+ </body>
379
+ </html>
380
+ `;
381
+
382
+ // src/web/js/wallet.js
383
+ var wallet_default = "// Wallet discovery, connection, and network management\n(function() {\n var _providerDetail = null;\n\n function discoverWallets() {\n window.addEventListener('TIP6963:announceProvider', function(e) {\n if (!_providerDetail) {\n _providerDetail = e.detail;\n }\n });\n window.dispatchEvent(new Event('TIP6963:requestProvider'));\n }\n\n function getProviderDetail() {\n return _providerDetail;\n }\n\n function getProvider() {\n if (_providerDetail && _providerDetail.provider) {\n return _providerDetail.provider;\n }\n return window.tron || window.tronLink || null;\n }\n\n function getTronWeb() {\n var provider = getProvider();\n if (provider && provider.tronWeb) {\n return provider.tronWeb;\n }\n return window.tronWeb || null;\n }\n\n function waitForWallet(maxWait) {\n maxWait = maxWait || 5000;\n discoverWallets();\n\n return new Promise(function(resolve, reject) {\n if (getProvider()) { resolve(true); return; }\n\n var elapsed = 0;\n var interval = setInterval(function() {\n elapsed += 200;\n if (getProvider()) {\n clearInterval(interval);\n resolve(true);\n } else if (elapsed >= maxWait) {\n clearInterval(interval);\n reject(new Error('No TRON wallet found. Please install TronLink extension.'));\n }\n }, 200);\n });\n }\n\n var NETWORK_CHAIN_IDS = {\n mainnet: '0x2b6653dc',\n nile: '0xcd8690dc',\n shasta: '0x94a9059e'\n };\n\n var NETWORK_FULL_HOSTS = {\n mainnet: 'https://api.trongrid.io',\n nile: 'https://nile.trongrid.io',\n shasta: 'https://api.shasta.trongrid.io'\n };\n\n function isConnected() {\n var tronWeb = getTronWeb();\n return !!(tronWeb && tronWeb.defaultAddress && tronWeb.defaultAddress.base58);\n }\n\n function getAddress() {\n var tronWeb = getTronWeb();\n return (tronWeb && tronWeb.defaultAddress && tronWeb.defaultAddress.base58) || null;\n }\n\n async function ensureConnected() {\n if (isConnected()) return;\n await getProvider().request({ method: 'tron_requestAccounts' });\n await new Promise(function(r) { setTimeout(r, 500); });\n }\n\n async function ensureWalletReady(expectedNetwork, setStatus) {\n var provider = getProvider();\n if (!provider) {\n throw new Error('No TRON wallet found. Please install TronLink extension and refresh.');\n }\n\n // Skip connection prompt if already connected\n if (!isConnected()) {\n setStatus('Connecting wallet...', 'waiting');\n try {\n var accountRes = await provider.request({ method: 'eth_requestAccounts' });\n if (accountRes && accountRes.code === 4001) {\n throw new Error('User rejected wallet connection.');\n }\n } catch (e) {\n throw new Error(e.message || 'Wallet connection failed. Please click Retry.');\n }\n }\n\n var tronWeb = getTronWeb();\n if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {\n await new Promise(function(r) { setTimeout(r, 1000); });\n tronWeb = getTronWeb();\n if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {\n throw new Error('Wallet not ready. Please unlock TronLink and refresh.');\n }\n }\n\n if (expectedNetwork) {\n var currentHost = tronWeb.fullNode.host;\n var expectedHost = NETWORK_FULL_HOSTS[expectedNetwork];\n if (expectedHost && currentHost !== expectedHost) {\n setStatus('Switching to ' + expectedNetwork + ' network...', 'waiting');\n var chainId = NETWORK_CHAIN_IDS[expectedNetwork];\n if (chainId) {\n try {\n await provider.request({\n method: 'wallet_switchEthereumChain',\n params: [{ chainId: chainId }]\n });\n await new Promise(function(r) { setTimeout(r, 1500); });\n } catch (switchErr) {\n throw new Error('Please switch TronLink to ' + expectedNetwork + ' network manually then click Retry.');\n }\n }\n }\n }\n }\n\n var HOST_TO_NETWORK = {\n 'https://api.trongrid.io': 'mainnet',\n 'https://nile.trongrid.io': 'nile',\n 'https://api.shasta.trongrid.io': 'shasta'\n };\n\n function getCurrentNetwork() {\n var tw = getTronWeb();\n if (tw && tw.fullNode) {\n return HOST_TO_NETWORK[tw.fullNode.host] || 'unknown';\n }\n return 'unknown';\n }\n\n // Expose to global\n window.TronWallet = {\n discoverWallets: discoverWallets,\n getProviderDetail: getProviderDetail,\n getProvider: getProvider,\n getTronWeb: getTronWeb,\n waitForWallet: waitForWallet,\n isConnected: isConnected,\n getAddress: getAddress,\n ensureConnected: ensureConnected,\n ensureWalletReady: ensureWalletReady,\n getCurrentNetwork: getCurrentNetwork\n };\n})();\n";
384
+
385
+ // src/web/js/tx-parser.js
386
+ var tx_parser_default = "// Transaction parsing and async data fetching\n(function() {\n var RESOURCE_NAMES = { 0: 'Bandwidth', 'BANDWIDTH': 'Bandwidth', 1: 'Energy', 'ENERGY': 'Energy' };\n\n function getResourceName(r) {\n if (r === undefined || r === null) return 'Bandwidth';\n return RESOURCE_NAMES[r] || String(r);\n }\n\n function fromHexString(hex) {\n if (!hex || typeof hex !== 'string') return hex;\n try {\n var str = '';\n for (var i = 0; i < hex.length; i += 2) {\n str += String.fromCharCode(parseInt(hex.substring(i, i + 2), 16));\n }\n return str;\n } catch(e) { return hex; }\n }\n\n function fromHexAddress(hex) {\n if (!hex || typeof hex !== 'string') return hex;\n try {\n var tw = window.TronWallet.getTronWeb();\n if (tw && tw.address && tw.address.fromHex) return tw.address.fromHex(hex);\n } catch(e) {}\n return hex;\n }\n\n function fromSun(sun) {\n return (Number(sun) / 1e6) + ' TRX';\n }\n\n function parseTransaction(tx) {\n var contract = null;\n if (tx.raw_data && tx.raw_data.contract && tx.raw_data.contract[0]) {\n contract = tx.raw_data.contract[0];\n }\n if (!contract) return null;\n\n var type = contract.type;\n var v = (contract.parameter && contract.parameter.value) || {};\n var info = { type: type, label: type, details: [] };\n\n switch (type) {\n case 'TransferContract':\n info.label = 'Transfer TRX';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'To', v: fromHexAddress(v.to_address) },\n { l: 'Amount', v: fromSun(v.amount) }\n ];\n break;\n case 'TransferAssetContract':\n info.label = 'Transfer TRC10 Asset';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'To', v: fromHexAddress(v.to_address) },\n { l: 'Asset ID', v: fromHexString(v.asset_name) },\n { l: 'Amount', v: v.amount }\n ];\n break;\n case 'TriggerSmartContract': {\n var methodSig = v.data ? v.data.substring(0, 8) : '';\n if (methodSig === 'a9059cbb' && v.data && v.data.length >= 136) {\n var toHex = '41' + v.data.substring(32, 72);\n var amountHex = v.data.substring(72, 136);\n info.label = 'TRC20 Transfer';\n info._trc20 = {\n toHex: toHex,\n contractHex: v.contract_address,\n rawAmount: BigInt('0x' + amountHex).toString()\n };\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'To', v: fromHexAddress(toHex) },\n { l: 'Contract', v: fromHexAddress(v.contract_address) },\n { l: 'Amount', v: 'Loading...' }\n ];\n } else if (methodSig === '23b872dd' && v.data && v.data.length >= 200) {\n // TRC721 transferFrom(address,address,uint256)\n var nftFrom = '41' + v.data.substring(32, 72);\n var nftTo = '41' + v.data.substring(96, 136);\n var tokenIdHex = v.data.substring(136, 200);\n var tokenId = BigInt('0x' + tokenIdHex).toString();\n info.label = 'TRC721 Transfer (NFT)';\n info.details = [\n { l: 'From', v: fromHexAddress(nftFrom) },\n { l: 'To', v: fromHexAddress(nftTo) },\n { l: 'Contract', v: fromHexAddress(v.contract_address) },\n { l: 'Token ID', v: tokenId }\n ];\n } else {\n var METHOD_NAMES = {\n '095ea7b3': 'approve',\n '70a08231': 'balanceOf',\n 'dd62ed3e': 'allowance',\n '42842e0e': 'safeTransferFrom'\n };\n var methodName = METHOD_NAMES[methodSig];\n info.label = methodName ? 'Contract Call: ' + methodName : 'Contract Call';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'Contract', v: fromHexAddress(v.contract_address) },\n { l: 'Call Value', v: v.call_value ? fromSun(v.call_value) : '0 TRX' },\n { l: 'Method', v: methodName || (methodSig ? '0x' + methodSig : '-') }\n ];\n }\n break;\n }\n case 'FreezeBalanceV2Contract':\n info.label = 'Stake TRX (Freeze v2)';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Amount', v: fromSun(v.frozen_balance) },\n { l: 'Resource', v: getResourceName(v.resource) }\n ];\n break;\n case 'UnfreezeBalanceV2Contract':\n info.label = 'Unstake TRX (Unfreeze v2)';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Amount', v: fromSun(v.unfreeze_balance) },\n { l: 'Resource', v: getResourceName(v.resource) }\n ];\n break;\n case 'DelegateResourceContract': {\n info.label = 'Delegate Resource';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'To', v: fromHexAddress(v.receiver_address) },\n { l: 'Amount', v: fromSun(v.balance) },\n { l: 'Resource', v: getResourceName(v.resource) },\n { l: 'Lock', v: v.lock ? 'Yes' : 'No' }\n ];\n if (v.lock && v.lock_period) {\n var days = Math.round(v.lock_period * 3 / 86400);\n info.details.push({ l: 'Lock Period', v: days + ' days' });\n }\n break;\n }\n case 'UnDelegateResourceContract':\n info.label = 'Undelegate Resource';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'Receiver', v: fromHexAddress(v.receiver_address) },\n { l: 'Amount', v: fromSun(v.balance) },\n { l: 'Resource', v: getResourceName(v.resource) }\n ];\n break;\n case 'WithdrawExpireUnfreezeContract':\n info.label = 'Withdraw Unfrozen TRX';\n info._withdrawOwner = fromHexAddress(v.owner_address);\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Amount', v: 'Loading...' }\n ];\n break;\n case 'VoteWitnessContract':\n info.label = 'Vote for SR';\n info.details = [\n { l: 'Voter', v: fromHexAddress(v.owner_address) }\n ];\n if (v.votes && v.votes.length) {\n v.votes.forEach(function(vote, i) {\n info.details.push({ l: 'SR #' + (i+1), v: fromHexAddress(vote.vote_address) + ' (' + vote.vote_count + ')' });\n });\n }\n break;\n case 'WithdrawBalanceContract':\n info.label = 'Claim Rewards';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) }\n ];\n break;\n case 'CreateSmartContract':\n info.label = 'Deploy Contract';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Name', v: (v.new_contract && v.new_contract.name) || '-' }\n ];\n break;\n case 'AccountCreateContract':\n info.label = 'Create Account';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'New Account', v: fromHexAddress(v.account_address) }\n ];\n break;\n case 'AccountUpdateContract':\n info.label = 'Update Account Name';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Name', v: v.account_name || '-' }\n ];\n break;\n case 'AccountPermissionUpdateContract':\n info.label = 'Update Account Permission';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) }\n ];\n break;\n case 'CancelAllUnfreezeV2Contract':\n info.label = 'Cancel All Pending Unstake';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) }\n ];\n break;\n default:\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) || '-' }\n ];\n }\n return info;\n }\n\n // Update an Amount row in the details UI\n function updateAmountRow(detailsEl, text) {\n var rows = detailsEl.querySelectorAll('.detail-row');\n for (var i = 0; i < rows.length; i++) {\n var label = rows[i].querySelector('.label');\n if (label && label.textContent === 'Amount') {\n rows[i].querySelector('.value').textContent = text;\n return;\n }\n }\n }\n\n async function fetchTrc20Info(trc20, detailsEl) {\n try {\n var tronWeb = window.TronWallet.getTronWeb();\n if (!tronWeb) {\n await window.TronWallet.waitForWallet(5000);\n tronWeb = window.TronWallet.getTronWeb();\n }\n if (!tronWeb) return;\n\n var addr = fromHexAddress(trc20.contractHex);\n var contract = await tronWeb.contract().at(addr);\n var decimals = 6;\n var symbol = '';\n try { decimals = await contract.methods.decimals().call(); } catch(e) {}\n try { symbol = await contract.methods.symbol().call(); } catch(e) {}\n\n var raw = BigInt(trc20.rawAmount);\n var divisor = 10n ** BigInt(decimals);\n var whole = raw / divisor;\n var frac = raw % divisor;\n var formatted = whole.toString();\n if (frac > 0n) {\n var fracStr = frac.toString().padStart(Number(decimals), '0').replace(/0+$/, '');\n formatted += '.' + fracStr;\n }\n updateAmountRow(detailsEl, formatted + (symbol ? ' ' + symbol : ''));\n } catch(e) {\n updateAmountRow(detailsEl, trc20.rawAmount + ' (raw)');\n }\n }\n\n async function fetchWithdrawAmount(ownerAddress, detailsEl) {\n try {\n var tronWeb = window.TronWallet.getTronWeb();\n if (!tronWeb) {\n await window.TronWallet.waitForWallet(5000);\n tronWeb = window.TronWallet.getTronWeb();\n }\n if (!tronWeb) return;\n\n var account = await tronWeb.trx.getAccount(ownerAddress);\n var total = 0;\n var now = Date.now();\n if (account.unfrozenV2 && account.unfrozenV2.length) {\n account.unfrozenV2.forEach(function(item) {\n if (item.unfreeze_expire_time && item.unfreeze_expire_time <= now) {\n total += item.unfreeze_amount || 0;\n }\n });\n }\n updateAmountRow(detailsEl, fromSun(total));\n } catch(e) {\n updateAmountRow(detailsEl, 'Unable to fetch');\n }\n }\n\n window.TxParser = {\n parseTransaction: parseTransaction,\n fetchTrc20Info: fetchTrc20Info,\n fetchWithdrawAmount: fetchWithdrawAmount\n };\n})();\n";
387
+
388
+ // src/web/js/actions.js
389
+ var actions_default = "// Execute wallet actions (sign, send, connect)\n(function() {\n async function executeTronAction(req) {\n await window.TronWallet.ensureConnected();\n var tronWeb = window.TronWallet.getTronWeb();\n var data = req.data || {};\n\n switch (req.type) {\n case 'connect': {\n return { address: tronWeb.defaultAddress.base58, network: window.TronWallet.getCurrentNetwork() };\n }\n case 'send_trx': {\n var tx = await tronWeb.transactionBuilder.sendTrx(data.to, tronWeb.toSun(data.amount));\n var signedTx = await tronWeb.trx.sign(tx);\n var broadcast = await tronWeb.trx.sendRawTransaction(signedTx);\n if (broadcast.result === false) {\n throw new Error('Broadcast failed: ' + (broadcast.message || broadcast.code || 'unknown error'));\n }\n return { txId: broadcast.txid };\n }\n case 'send_trc20': {\n var contract = await tronWeb.contract().at(data.contractAddress);\n var decimals = (data.decimals !== undefined && data.decimals !== null) ? data.decimals : 6;\n var amountStr = String(data.amount).trim();\n if (!/^\\d+(\\.\\d+)?$/.test(amountStr)) {\n throw new Error('Invalid amount format: ' + data.amount);\n }\n var parts = amountStr.split('.');\n var whole = parts[0] || '0';\n var fracInput = parts[1] || '';\n if (fracInput.length > decimals) {\n throw new Error('Amount has too many decimal places (max ' + decimals + ' for this token). Got: ' + data.amount);\n }\n if (decimals > 18) {\n throw new Error('Decimals too large (max 18). Got: ' + decimals);\n }\n var frac = decimals > 0 ? fracInput.padEnd(decimals, '0') : '';\n var multiplier = 10n ** BigInt(decimals);\n var rawAmount = decimals > 0\n ? BigInt(whole) * multiplier + BigInt(frac)\n : BigInt(whole);\n if (rawAmount === 0n) {\n throw new Error('Amount is zero after conversion. Please check the amount: ' + data.amount);\n }\n var txId = await contract.methods.transfer(data.to, rawAmount.toString()).send();\n return { txId: txId };\n }\n case 'sign_message': {\n var signature = await tronWeb.trx.signMessageV2(data.message);\n return { signature: signature };\n }\n case 'sign_typed_data': {\n var typedData = data.typedData;\n var domain = typedData.domain;\n var types = Object.assign({}, typedData.types);\n delete types.EIP712Domain;\n var message = typedData.message;\n var sig = await tronWeb.trx._signTypedData(domain, types, message);\n return { signature: sig };\n }\n case 'sign_transaction': {\n var signed = await tronWeb.trx.sign(data.transaction);\n if (data.broadcast) {\n var broadcast = await tronWeb.trx.sendRawTransaction(signed);\n if (broadcast.result === false) {\n throw new Error('Broadcast failed: ' + (broadcast.message || broadcast.code || 'unknown error'));\n }\n return { signedTransaction: signed, txId: broadcast.txid };\n }\n return { signedTransaction: signed };\n }\n default:\n throw new Error('Unknown request type: ' + req.type);\n }\n }\n\n window.TronActions = {\n execute: executeTronAction\n };\n})();\n";
390
+
391
+ // src/web/js/app.js
392
+ var app_default = `// Main app: polling, request lifecycle, UI events
312
393
  (function() {
313
- const params = new URLSearchParams(window.location.search);
314
- const requestId = params.get('requestId');
315
- const statusEl = document.getElementById('status');
316
- const detailsEl = document.getElementById('details');
317
- const typeBadgeEl = document.getElementById('typeBadge');
318
- const networkBadgeEl = document.getElementById('networkBadge');
319
- const buttonGroup = document.getElementById('buttonGroup');
320
- const retryGroup = document.getElementById('retryGroup');
321
- const retryBtn = document.getElementById('retryBtn');
322
- const approveBtn = document.getElementById('approveBtn');
323
- const rejectBtn = document.getElementById('rejectBtn');
324
-
325
- let pendingRequest = null;
326
- let _providerDetail = null; // TIP-6963 discovered provider
394
+ var statusEl = document.getElementById('status');
395
+ var detailsEl = document.getElementById('details');
396
+ var typeBadgeEl = document.getElementById('typeBadge');
397
+ var networkBadgeEl = document.getElementById('networkBadge');
398
+ var buttonGroup = document.getElementById('buttonGroup');
399
+ var retryGroup = document.getElementById('retryGroup');
400
+ var retryBtn = document.getElementById('retryBtn');
401
+ var approveBtn = document.getElementById('approveBtn');
402
+ var rejectBtn = document.getElementById('rejectBtn');
403
+
404
+ var pendingRequest = null;
405
+ var currentRequestId = null;
406
+ var polling = false;
407
+ var sessionId = null;
408
+
409
+ var NETWORK_NAMES = {
410
+ mainnet: 'Mainnet',
411
+ nile: 'Nile Testnet',
412
+ shasta: 'Shasta Testnet'
413
+ };
414
+
415
+ function markSessionExpired() {
416
+ sessionExpired = true;
417
+ setStatus('Session expired. Please close this tab and try again.', 'error');
418
+ buttonGroup.style.display = 'none';
419
+ retryGroup.style.display = 'none';
420
+ approveBtn.disabled = true;
421
+ rejectBtn.disabled = true;
422
+ }
423
+
424
+ // Heartbeat \u2014 detect server disconnect or session change
425
+ var heartbeatFailCount = 0;
426
+ var sessionExpired = false;
427
+ setInterval(function() {
428
+ if (sessionExpired) return;
429
+ fetch('/api/heartbeat', {
430
+ method: 'POST',
431
+ headers: { 'Content-Type': 'application/json' },
432
+ body: JSON.stringify({ sessionId: sessionId })
433
+ })
434
+ .then(function(res) {
435
+ if (res.status === 410) {
436
+ // Server restarted with new session
437
+ markSessionExpired();
438
+ return;
439
+ }
440
+ if (res.ok) {
441
+ heartbeatFailCount = 0;
442
+ } else {
443
+ heartbeatFailCount++;
444
+ }
445
+ })
446
+ .catch(function() {
447
+ heartbeatFailCount++;
448
+ })
449
+ .finally(function() {
450
+ if (heartbeatFailCount >= 3 && !sessionExpired) {
451
+ markSessionExpired();
452
+ }
453
+ });
454
+ }, 3000);
455
+
456
+ // --- UI helpers ---
327
457
 
328
458
  function setStatus(msg, type) {
329
459
  statusEl.textContent = msg;
330
460
  statusEl.className = 'status ' + type;
331
461
  }
332
462
 
463
+ function escapeHtml(s) {
464
+ var d = document.createElement('div');
465
+ d.textContent = s;
466
+ return d.innerHTML;
467
+ }
468
+
333
469
  function addDetail(label, value) {
334
- const row = document.createElement('div');
470
+ var row = document.createElement('div');
335
471
  row.className = 'detail-row';
336
472
  row.innerHTML = '<span class="label">' + label + '</span><span class="value">' + escapeHtml(String(value)) + '</span>';
337
473
  detailsEl.appendChild(row);
338
474
  }
339
475
 
340
- function escapeHtml(s) {
341
- const d = document.createElement('div');
342
- d.textContent = s;
343
- return d.innerHTML;
476
+ function disableButtons() {
477
+ approveBtn.disabled = true;
478
+ rejectBtn.disabled = true;
344
479
  }
345
480
 
346
- var NETWORK_NAMES = {
347
- mainnet: 'Mainnet',
348
- nile: 'Nile Testnet',
349
- shasta: 'Shasta Testnet'
350
- };
481
+ // --- Render request details ---
351
482
 
352
483
  function renderDetails(req) {
353
484
  typeBadgeEl.textContent = req.type;
@@ -379,264 +510,190 @@ var web_default = `<!DOCTYPE html>
379
510
  case 'sign_typed_data':
380
511
  addDetail('Typed Data', JSON.stringify(data.typedData, null, 2));
381
512
  break;
382
- case 'sign_transaction':
383
- addDetail('Transaction', JSON.stringify(data.transaction, null, 2));
513
+ case 'sign_transaction': {
514
+ if (data.broadcast) {
515
+ addDetail('Broadcast', 'Yes (will send on-chain after signing)');
516
+ }
517
+ var parsed = window.TxParser.parseTransaction(data.transaction);
518
+ if (parsed) {
519
+ addDetail('Type', parsed.label);
520
+ parsed.details.forEach(function(d) { addDetail(d.l, d.v); });
521
+ if (parsed._trc20) {
522
+ window.TxParser.fetchTrc20Info(parsed._trc20, detailsEl);
523
+ }
524
+ if (parsed._withdrawOwner) {
525
+ window.TxParser.fetchWithdrawAmount(parsed._withdrawOwner, detailsEl);
526
+ }
527
+ } else {
528
+ addDetail('Transaction', JSON.stringify(data.transaction, null, 2));
529
+ }
384
530
  break;
385
- }
386
- }
387
-
388
- // --- TIP-6963 Wallet Discovery ---
389
-
390
- function discoverWallets() {
391
- window.addEventListener('TIP6963:announceProvider', function(e) {
392
- if (!_providerDetail) {
393
- _providerDetail = e.detail;
394
531
  }
395
- });
396
- window.dispatchEvent(new Event('TIP6963:requestProvider'));
397
- }
398
-
399
- // Get the TIP-6963 provider (or fallback to window.tron / window.tronLink)
400
- function getProvider() {
401
- if (_providerDetail && _providerDetail.provider) {
402
- return _providerDetail.provider;
403
532
  }
404
- return window.tron || window.tronLink || null;
405
533
  }
406
534
 
407
- // Get tronWeb from provider or fallback
408
- function getTronWeb() {
409
- var provider = getProvider();
410
- if (provider && provider.tronWeb) {
411
- return provider.tronWeb;
412
- }
413
- return window.tronWeb || null;
414
- }
415
-
416
- function waitForWallet(maxWait) {
417
- maxWait = maxWait || 5000;
418
- // Start TIP-6963 discovery
419
- discoverWallets();
420
-
421
- return new Promise(function(resolve, reject) {
422
- if (getProvider()) { resolve(true); return; }
423
-
424
- var elapsed = 0;
425
- var interval = setInterval(function() {
426
- elapsed += 200;
427
- if (getProvider()) {
428
- clearInterval(interval);
429
- resolve(true);
430
- } else if (elapsed >= maxWait) {
431
- clearInterval(interval);
432
- reject(new Error('No TRON wallet found. Please install TronLink extension.'));
433
- }
434
- }, 200);
435
- });
436
- }
535
+ // --- Wallet readiness ---
437
536
 
438
- // --- Wallet Connection & Network ---
439
-
440
- async function ensureConnected() {
441
- var tronWeb = getTronWeb();
442
- if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {
443
- await getProvider().request({ method: 'tron_requestAccounts' });
444
- await new Promise(function(r) { setTimeout(r, 500); });
445
- }
446
- }
447
-
448
- var NETWORK_CHAIN_IDS = {
449
- mainnet: '0x2b6653dc',
450
- nile: '0xcd8690dc',
451
- shasta: '0x94a9059e'
452
- };
453
-
454
- var NETWORK_FULL_HOSTS = {
455
- mainnet: 'https://api.trongrid.io',
456
- nile: 'https://nile.trongrid.io',
457
- shasta: 'https://api.shasta.trongrid.io'
458
- };
459
-
460
- async function ensureWalletReady(expectedNetwork) {
461
- var provider = getProvider();
462
-
463
- // Step 1: Unlock wallet if locked
464
- setStatus('Connecting wallet...', 'waiting');
465
- var accountRes = await provider.request({ method: 'eth_requestAccounts' });
466
- if (accountRes && accountRes.code === 4001) {
467
- throw new Error('User rejected wallet connection.');
468
- }
469
-
470
- // Step 2: Check if wallet is ready
471
- var tronWeb = getTronWeb();
472
- if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {
473
- await new Promise(function(r) { setTimeout(r, 1000); });
474
- tronWeb = getTronWeb();
475
- if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {
476
- throw new Error('Wallet not ready. Please unlock TronLink and refresh.');
477
- }
478
- }
479
-
480
- // Step 3: Check network and switch if needed
481
- if (expectedNetwork) {
482
- var currentHost = tronWeb.fullNode.host;
483
- var expectedHost = NETWORK_FULL_HOSTS[expectedNetwork];
484
- if (expectedHost && currentHost !== expectedHost) {
485
- setStatus('Switching to ' + expectedNetwork + ' network...', 'waiting');
486
- var chainId = NETWORK_CHAIN_IDS[expectedNetwork];
487
- if (chainId) {
488
- try {
489
- await provider.request({
490
- method: 'wallet_switchEthereumChain',
491
- params: [{ chainId: chainId }]
492
- });
493
- await new Promise(function(r) { setTimeout(r, 1500); });
494
- } catch (switchErr) {
495
- throw new Error('Please switch TronLink to ' + expectedNetwork + ' network manually then click Retry.');
537
+ async function tryEnsureWallet() {
538
+ retryGroup.style.display = 'none';
539
+ buttonGroup.style.display = 'none';
540
+ try {
541
+ await window.TronWallet.ensureWalletReady(pendingRequest.network, setStatus);
542
+
543
+ // Auto-complete connect requests once wallet is ready
544
+ if (pendingRequest.type === 'connect') {
545
+ for (var i = 0; i < 10; i++) {
546
+ var addr = window.TronWallet.getAddress();
547
+ if (addr) {
548
+ try {
549
+ await completeRequest(currentRequestId, true, { address: addr, network: window.TronWallet.getCurrentNetwork() });
550
+ setStatus('Wallet connected: ' + addr, 'success');
551
+ } catch (_) {
552
+ setStatus('Request expired or no longer available.', 'error');
553
+ }
554
+ startPollingAfterDone();
555
+ return;
496
556
  }
557
+ await new Promise(function(r) { setTimeout(r, 300); });
497
558
  }
498
559
  }
499
- }
500
- }
501
-
502
- // --- Execute Actions ---
503
560
 
504
- async function executeTronAction(req) {
505
- await ensureConnected();
506
- var tronWeb = getTronWeb();
507
- var data = req.data || {};
508
-
509
- switch (req.type) {
510
- case 'connect': {
511
- return { address: tronWeb.defaultAddress.base58 };
512
- }
513
- case 'send_trx': {
514
- var tx = await tronWeb.transactionBuilder.sendTrx(data.to, tronWeb.toSun(data.amount));
515
- var signedTx = await tronWeb.trx.sign(tx);
516
- var broadcast = await tronWeb.trx.sendRawTransaction(signedTx);
517
- return { txId: broadcast.txid };
518
- }
519
- case 'send_trc20': {
520
- var contract = await tronWeb.contract().at(data.contractAddress);
521
- var decimals = data.decimals || 6;
522
- var rawAmount = BigInt(data.amount) * BigInt(10 ** decimals);
523
- var txId = await contract.methods.transfer(data.to, rawAmount.toString()).send();
524
- return { txId: txId };
525
- }
526
- case 'sign_message': {
527
- var signature = await tronWeb.trx.signMessageV2(data.message);
528
- return { signature: signature };
529
- }
530
- case 'sign_typed_data': {
531
- var typedData = data.typedData;
532
- var domain = typedData.domain;
533
- var types = Object.assign({}, typedData.types);
534
- delete types.EIP712Domain;
535
- var message = typedData.message;
536
- var sig = await tronWeb.trx._signTypedData(domain, types, message);
537
- return { signature: sig };
538
- }
539
- case 'sign_transaction': {
540
- var signed = await tronWeb.trx.sign(data.transaction);
541
- return { signedTransaction: signed };
542
- }
543
- default:
544
- throw new Error('Unknown request type: ' + req.type);
561
+ setStatus('Ready. Review and approve or reject.', 'info');
562
+ buttonGroup.style.display = 'flex';
563
+ } catch (e) {
564
+ setStatus(e.message || 'Wallet connection failed. Please try again.', 'error');
565
+ retryGroup.style.display = 'flex';
545
566
  }
546
567
  }
547
568
 
548
- // --- Request Handling ---
569
+ // --- Request lifecycle ---
549
570
 
550
571
  async function completeRequest(id, success, resultOrError) {
551
572
  var body = success
552
573
  ? { success: true, result: resultOrError }
553
574
  : { success: false, error: resultOrError };
554
- await fetch('/api/complete/' + id, {
575
+ var res = await fetch('/api/complete/' + id, {
555
576
  method: 'POST',
556
577
  headers: { 'Content-Type': 'application/json' },
557
578
  body: JSON.stringify(body),
558
579
  });
580
+ if (!res.ok) {
581
+ throw new Error('Request expired or no longer available.');
582
+ }
559
583
  }
560
584
 
561
- function disableButtons() {
562
- approveBtn.disabled = true;
563
- rejectBtn.disabled = true;
564
- }
585
+ async function handleRequest(req) {
586
+ pendingRequest = req;
587
+ currentRequestId = req.id;
565
588
 
566
- async function tryEnsureWallet() {
567
- retryGroup.style.display = 'none';
568
- buttonGroup.style.display = 'none';
589
+ renderDetails(req);
590
+
591
+ // Wait for wallet extension to inject first
592
+ setStatus('Discovering wallets...', 'waiting');
569
593
  try {
570
- await ensureWalletReady(pendingRequest.network);
571
- setStatus('Ready. Review and approve or reject.', 'info');
572
- buttonGroup.style.display = 'flex';
594
+ await window.TronWallet.waitForWallet(5000);
595
+ var detail = window.TronWallet.getProviderDetail();
596
+ if (detail && detail.info) {
597
+ setStatus('Found wallet: ' + detail.info.name, 'info');
598
+ }
573
599
  } catch (e) {
574
600
  setStatus(e.message, 'error');
575
- retryGroup.style.display = 'flex';
601
+ return;
576
602
  }
603
+
604
+ await tryEnsureWallet();
577
605
  }
578
606
 
579
- retryBtn.addEventListener('click', function() {
580
- tryEnsureWallet();
581
- });
607
+ // --- Polling ---
582
608
 
583
- async function init() {
584
- if (!requestId) {
585
- setStatus('Missing request ID in URL.', 'error');
586
- return;
609
+ async function pollForRequests() {
610
+ if (polling) return;
611
+ polling = true;
612
+ if (!currentRequestId) {
613
+ setStatus('Waiting for request...', 'info');
587
614
  }
588
615
 
589
- try {
590
- var res = await fetch('/api/pending/' + requestId);
591
- if (!res.ok) {
592
- setStatus('Request not found or expired.', 'error');
593
- return;
594
- }
595
- pendingRequest = await res.json();
596
- renderDetails(pendingRequest);
597
- setStatus('Discovering wallets...', 'waiting');
616
+ var interval = 500;
617
+ var maxInterval = 5000;
598
618
 
619
+ while (true) {
620
+ if (sessionExpired) { polling = false; return; }
599
621
  try {
600
- await waitForWallet(5000);
601
- if (_providerDetail && _providerDetail.info) {
602
- setStatus('Found wallet: ' + _providerDetail.info.name, 'info');
622
+ var res = await fetch('/api/pending/next');
623
+ if (res.ok) {
624
+ var req = await res.json();
625
+ if (req.id !== currentRequestId) {
626
+ buttonGroup.style.display = 'none';
627
+ retryGroup.style.display = 'none';
628
+ approveBtn.disabled = false;
629
+ rejectBtn.disabled = false;
630
+ polling = false;
631
+ await handleRequest(req);
632
+ return;
633
+ }
603
634
  }
635
+ interval = Math.min(interval * 1.5, maxInterval);
604
636
  } catch (e) {
605
- setStatus(e.message, 'error');
606
- return;
637
+ interval = maxInterval;
607
638
  }
608
-
609
- await tryEnsureWallet();
610
- } catch (e) {
611
- setStatus('Failed to load request: ' + e.message, 'error');
639
+ await new Promise(function(r) { setTimeout(r, interval); });
612
640
  }
613
641
  }
614
642
 
643
+ function startPollingAfterDone() {
644
+ polling = false;
645
+ pollForRequests();
646
+ }
647
+
648
+ // --- Event listeners ---
649
+
650
+ retryBtn.addEventListener('click', function() {
651
+ tryEnsureWallet();
652
+ });
653
+
615
654
  approveBtn.addEventListener('click', async function() {
616
655
  disableButtons();
617
656
  setStatus('Processing with wallet...', 'waiting');
618
657
  try {
619
- var result = await executeTronAction(pendingRequest);
620
- await completeRequest(requestId, true, result);
658
+ var result = await window.TronActions.execute(pendingRequest);
659
+ await completeRequest(currentRequestId, true, result);
621
660
  setStatus('Approved and completed successfully.', 'success');
622
661
  } catch (e) {
623
662
  var msg = e.message || String(e);
624
- await completeRequest(requestId, false, msg);
663
+ try {
664
+ await completeRequest(currentRequestId, false, msg);
665
+ } catch (_) {
666
+ // Request already expired, ignore
667
+ }
625
668
  setStatus('Error: ' + msg, 'error');
626
669
  }
670
+ startPollingAfterDone();
627
671
  });
628
672
 
629
673
  rejectBtn.addEventListener('click', async function() {
630
674
  disableButtons();
631
- setStatus('Rejected.', 'error');
632
- await completeRequest(requestId, false, 'USER_REJECTED');
675
+ try {
676
+ await completeRequest(currentRequestId, false, 'USER_REJECTED');
677
+ setStatus('Rejected.', 'error');
678
+ } catch (_) {
679
+ setStatus('Request expired or no longer available.', 'error');
680
+ }
681
+ startPollingAfterDone();
633
682
  });
634
683
 
635
- init();
684
+ // --- Init ---
685
+ window.TronWallet.discoverWallets();
686
+ // Fetch session ID before starting, then poll
687
+ fetch('/api/session').then(function(res) {
688
+ return res.json();
689
+ }).then(function(data) {
690
+ sessionId = data.sessionId;
691
+ pollForRequests();
692
+ }).catch(function() {
693
+ // Server might not be ready yet, start polling anyway
694
+ pollForRequests();
695
+ });
636
696
  })();
637
- </script>
638
- </body>
639
- </html>
640
697
  `;
641
698
 
642
699
  // src/tron-signer.ts
@@ -657,11 +714,23 @@ var TronSigner = class {
657
714
  tronWebOptions.headers = { "TRON-PRO-API-KEY": this.config.apiKey };
658
715
  }
659
716
  this.tronWeb = new TronWeb(tronWebOptions);
660
- this.httpServer = new HttpServer(this.pendingStore, web_default);
717
+ this.httpServer = new HttpServer(this.pendingStore, web_default, {
718
+ "wallet.js": wallet_default,
719
+ "tx-parser.js": tx_parser_default,
720
+ "actions.js": actions_default,
721
+ "app.js": app_default
722
+ });
661
723
  }
662
724
  async start() {
663
725
  await this.httpServer.start(this.config.httpPort);
664
726
  console.error(`HTTP server started on http://127.0.0.1:${this.httpServer.getPort()}`);
727
+ const cleanup = () => {
728
+ this.httpServer.stop().catch(() => {
729
+ });
730
+ };
731
+ process.on("SIGINT", cleanup);
732
+ process.on("SIGTERM", cleanup);
733
+ process.on("beforeExit", cleanup);
665
734
  }
666
735
  getPort() {
667
736
  return this.httpServer.getPort();
@@ -720,9 +789,9 @@ var TronSigner = class {
720
789
  const result = await promise;
721
790
  return result;
722
791
  }
723
- async signTransaction(transaction, network) {
792
+ async signTransaction(transaction, network, broadcast) {
724
793
  const net = this.resolveNetwork(network);
725
- const data = { transaction };
794
+ const data = { transaction, broadcast: broadcast ?? false };
726
795
  const { id, promise } = this.pendingStore.create("sign_transaction", data, net);
727
796
  await openApprovalPage(this.getPort(), id);
728
797
  const result = await promise;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tron-signer.ts","../src/pending-store.ts","../src/config.ts","../src/http-server.ts","../src/browser.ts","../src/web/index.html"],"sourcesContent":["// @ts-ignore - tronweb types are complex\nimport { TronWeb } from \"tronweb\";\nimport { PendingStore } from \"./pending-store.js\";\nimport { HttpServer } from \"./http-server.js\";\nimport { openApprovalPage } from \"./browser.js\";\nimport { NETWORKS, loadConfig } from \"./config.js\";\nimport type { AppConfig, TronNetwork, SendTrxData, SendTrc20Data, SignMessageData, SignTypedDataData, SignTransactionData } from \"./types.js\";\n// @ts-ignore - HTML imported as text via tsup loader\nimport htmlContent from \"./web/index.html\";\n\nexport class TronSigner {\n private config: AppConfig;\n private pendingStore: PendingStore;\n private httpServer: HttpServer;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private tronWeb: any;\n\n constructor() {\n this.config = loadConfig();\n this.pendingStore = new PendingStore();\n\n const networkConfig = NETWORKS[this.config.network];\n const tronWebOptions: Record<string, unknown> = {\n fullHost: networkConfig.fullHost,\n };\n if (this.config.apiKey) {\n tronWebOptions.headers = { \"TRON-PRO-API-KEY\": this.config.apiKey };\n }\n // @ts-ignore - tronweb constructor typing\n this.tronWeb = new TronWeb(tronWebOptions);\n\n this.httpServer = new HttpServer(this.pendingStore, htmlContent as string);\n }\n\n async start(): Promise<void> {\n await this.httpServer.start(this.config.httpPort);\n console.error(`HTTP server started on http://127.0.0.1:${this.httpServer.getPort()}`);\n }\n\n private getPort(): number {\n return this.httpServer.getPort();\n }\n\n async stop(): Promise<void> {\n this.pendingStore.clear();\n await this.httpServer.stop();\n }\n\n getConfig(): AppConfig {\n return this.config;\n }\n\n private resolveNetwork(network?: TronNetwork): TronNetwork {\n return network || this.config.network;\n }\n\n async connectWallet(network?: TronNetwork): Promise<{ address: string }> {\n const net = this.resolveNetwork(network);\n const { id, promise } = this.pendingStore.create(\"connect\", {}, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { address: string };\n return result;\n }\n\n async sendTrx(to: string, amount: number, network?: TronNetwork): Promise<{ txId: string }> {\n const net = this.resolveNetwork(network);\n const data: SendTrxData = { to, amount };\n const { id, promise } = this.pendingStore.create(\"send_trx\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { txId: string };\n return result;\n }\n\n async sendTrc20(\n contractAddress: string,\n to: string,\n amount: string,\n decimals?: number,\n network?: TronNetwork\n ): Promise<{ txId: string }> {\n const net = this.resolveNetwork(network);\n const data: SendTrc20Data = {\n contractAddress,\n to,\n amount,\n decimals: decimals ?? 6,\n };\n const { id, promise } = this.pendingStore.create(\"send_trc20\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { txId: string };\n return result;\n }\n\n async signMessage(message: string, network?: TronNetwork): Promise<{ signature: string }> {\n const net = this.resolveNetwork(network);\n const data: SignMessageData = { message };\n const { id, promise } = this.pendingStore.create(\"sign_message\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { signature: string };\n return result;\n }\n\n async signTypedData(\n typedData: Record<string, unknown>,\n network?: TronNetwork\n ): Promise<{ signature: string }> {\n const net = this.resolveNetwork(network);\n const data: SignTypedDataData = { typedData };\n const { id, promise } = this.pendingStore.create(\"sign_typed_data\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { signature: string };\n return result;\n }\n\n async signTransaction(\n transaction: Record<string, unknown>,\n network?: TronNetwork\n ): Promise<{ signedTransaction: Record<string, unknown> }> {\n const net = this.resolveNetwork(network);\n const data: SignTransactionData = { transaction };\n const { id, promise } = this.pendingStore.create(\"sign_transaction\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { signedTransaction: Record<string, unknown> };\n return result;\n }\n\n async getBalance(address: string, network?: TronNetwork): Promise<{ balance: string; balanceSun: number }> {\n const net = this.resolveNetwork(network);\n const networkConfig = NETWORKS[net];\n const tronWebOptions: Record<string, unknown> = { fullHost: networkConfig.fullHost };\n if (this.config.apiKey) {\n tronWebOptions.headers = { \"TRON-PRO-API-KEY\": this.config.apiKey };\n }\n // @ts-ignore\n const tw = net === this.config.network ? this.tronWeb : new (await import(\"tronweb\")).TronWeb(tronWebOptions);\n const balanceSun = await tw.trx.getBalance(address);\n const balance = tw.fromSun(balanceSun).toString();\n return { balance, balanceSun };\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { REQUEST_TIMEOUT_MS } from \"./config.js\";\nimport type { PendingRequest, PendingRequestType, TronNetwork } from \"./types.js\";\n\ninterface PendingEntry<T = unknown> {\n request: PendingRequest<T>;\n resolve: (result: unknown) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport class PendingStore {\n private pending = new Map<string, PendingEntry>();\n\n create<T>(type: PendingRequestType, data: T, network: TronNetwork): { id: string; promise: Promise<unknown> } {\n const id = randomUUID();\n const request: PendingRequest<T> = {\n id,\n type,\n data,\n network,\n createdAt: Date.now(),\n };\n\n let resolve!: (result: unknown) => void;\n let reject!: (error: Error) => void;\n const promise = new Promise<unknown>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(\"TIMEOUT: Request timed out after 5 minutes\"));\n }, REQUEST_TIMEOUT_MS);\n\n this.pending.set(id, { request, resolve, reject, timer });\n console.error(`[PendingStore] Created request: ${id}, type: ${type}, total pending: ${this.pending.size}`);\n\n return { id, promise };\n }\n\n get(id: string): PendingRequest | undefined {\n const entry = this.pending.get(id);\n console.error(`[PendingStore] Get request: ${id}, found: ${!!entry}, total pending: ${this.pending.size}`);\n return entry?.request;\n }\n\n resolve(id: string, result: unknown): boolean {\n const entry = this.pending.get(id);\n if (!entry) return false;\n clearTimeout(entry.timer);\n this.pending.delete(id);\n entry.resolve(result);\n return true;\n }\n\n reject(id: string, error: string): boolean {\n const entry = this.pending.get(id);\n if (!entry) return false;\n clearTimeout(entry.timer);\n this.pending.delete(id);\n entry.reject(new Error(error));\n return true;\n }\n\n size(): number {\n return this.pending.size;\n }\n\n clear(): void {\n for (const [id, entry] of this.pending) {\n clearTimeout(entry.timer);\n entry.reject(new Error(\"Store cleared\"));\n }\n this.pending.clear();\n }\n}\n","import type { TronNetwork, NetworkConfig, AppConfig } from \"./types.js\";\n\nexport const NETWORKS: Record<TronNetwork, NetworkConfig> = {\n mainnet: {\n name: \"Mainnet\",\n fullHost: \"https://api.trongrid.io\",\n explorerUrl: \"https://tronscan.org\",\n },\n nile: {\n name: \"Nile Testnet\",\n fullHost: \"https://nile.trongrid.io\",\n explorerUrl: \"https://nile.tronscan.org\",\n },\n shasta: {\n name: \"Shasta Testnet\",\n fullHost: \"https://api.shasta.trongrid.io\",\n explorerUrl: \"https://shasta.tronscan.org\",\n },\n};\n\nexport const DEFAULT_HTTP_PORT = 3386;\nexport const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport function loadConfig(): AppConfig {\n const network = (process.env.TRON_NETWORK || \"mainnet\") as TronNetwork;\n if (!NETWORKS[network]) {\n throw new Error(\n `Invalid TRON_NETWORK: ${network}. Must be one of: ${Object.keys(NETWORKS).join(\", \")}`\n );\n }\n const httpPort = process.env.TRON_HTTP_PORT\n ? parseInt(process.env.TRON_HTTP_PORT, 10)\n : DEFAULT_HTTP_PORT;\n return {\n network,\n httpPort,\n apiKey: process.env.TRON_API_KEY,\n };\n}\n","import express, { type Express, type Request, type Response } from \"express\";\nimport type { Server } from \"node:http\";\nimport { PendingStore } from \"./pending-store.js\";\nimport { NETWORKS } from \"./config.js\";\n\nexport class HttpServer {\n private app: Express;\n private server: Server | null = null;\n private pendingStore: PendingStore;\n private htmlContent: string;\n private port: number = 0;\n\n constructor(pendingStore: PendingStore, htmlContent: string) {\n this.pendingStore = pendingStore;\n this.htmlContent = htmlContent;\n this.app = express();\n this.setupRoutes();\n }\n\n private setupRoutes(): void {\n this.app.use(express.json());\n\n this.app.get(\"/\", (_req: Request, res: Response) => {\n res.setHeader(\"Content-Type\", \"text/html\");\n res.send(this.htmlContent);\n });\n\n this.app.get(\"/api/pending/:id\", (req: Request, res: Response) => {\n const request = this.pendingStore.get(req.params.id as string);\n if (!request) {\n res.status(404).json({ error: \"Request not found or expired\" });\n return;\n }\n res.json({\n ...request,\n networkConfig: NETWORKS[request.network],\n });\n });\n\n this.app.post(\"/api/complete/:id\", (req: Request, res: Response) => {\n const id = req.params.id as string;\n const { success, result, error } = req.body;\n\n if (success) {\n const resolved = this.pendingStore.resolve(id, result);\n if (!resolved) {\n res.status(404).json({ error: \"Request not found or expired\" });\n return;\n }\n res.json({ ok: true });\n } else {\n const rejected = this.pendingStore.reject(id, error || \"USER_REJECTED\");\n if (!rejected) {\n res.status(404).json({ error: \"Request not found or expired\" });\n return;\n }\n res.json({ ok: true });\n }\n });\n\n this.app.get(\"/api/health\", (_req: Request, res: Response) => {\n res.json({ status: \"ok\" });\n });\n\n this.app.get(\"/api/debug\", (_req: Request, res: Response) => {\n res.json({ pendingCount: this.pendingStore.size() });\n });\n }\n\n getPort(): number {\n return this.port;\n }\n\n async start(port: number): Promise<void> {\n this.port = port;\n return this._tryListen(port);\n }\n\n private _tryListen(port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n this.server = this.app.listen(port, \"127.0.0.1\", () => {\n this.port = port;\n console.error(`[HttpServer] Listening on http://127.0.0.1:${port}`);\n resolve();\n });\n this.server.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n const nextPort = port + 1;\n console.error(`[HttpServer] Port ${port} is in use, trying ${nextPort}...`);\n this._tryListen(nextPort).then(resolve, reject);\n } else {\n reject(err);\n }\n });\n } catch (err) {\n reject(err);\n }\n });\n }\n\n async stop(): Promise<void> {\n return new Promise((resolve) => {\n if (this.server) {\n this.server.close(() => resolve());\n } else {\n resolve();\n }\n });\n }\n}\n","import open from \"open\";\n\nexport async function openApprovalPage(\n port: number,\n requestId: string\n): Promise<void> {\n const url = `http://127.0.0.1:${port}/?requestId=${requestId}`;\n await open(url);\n}\n","<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>TronLink Signer</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n background: #0f0f0f;\n color: #e0e0e0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n padding: 20px;\n }\n .container {\n background: #1a1a2e;\n border-radius: 16px;\n padding: 32px;\n max-width: 480px;\n width: 100%;\n box-shadow: 0 8px 32px rgba(0,0,0,0.4);\n }\n h1 {\n font-size: 20px;\n margin-bottom: 8px;\n color: #fff;\n }\n .type-badge {\n display: inline-block;\n background: #2a2a4e;\n color: #8888cc;\n padding: 4px 12px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 600;\n margin-bottom: 20px;\n }\n .detail-row {\n display: flex;\n justify-content: space-between;\n padding: 10px 0;\n border-bottom: 1px solid #2a2a3e;\n font-size: 14px;\n }\n .detail-row .label { color: #888; }\n .detail-row .value {\n color: #e0e0e0;\n word-break: break-all;\n text-align: right;\n max-width: 60%;\n }\n .status {\n margin-top: 20px;\n padding: 12px 16px;\n border-radius: 8px;\n font-size: 13px;\n line-height: 1.5;\n }\n .status.info { background: #1a2a4e; color: #6ea8fe; }\n .status.success { background: #1a3a2a; color: #75d9a0; }\n .status.error { background: #3a1a1a; color: #f87171; }\n .status.waiting { background: #3a3a1a; color: #fbbf24; }\n .buttons {\n display: flex;\n gap: 12px;\n margin-top: 24px;\n }\n .buttons button {\n flex: 1;\n padding: 14px;\n border: none;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n transition: opacity 0.2s;\n }\n .buttons button:hover { opacity: 0.85; }\n .buttons button:disabled { opacity: 0.4; cursor: not-allowed; }\n .btn-approve { background: #c23631; color: #fff; }\n .btn-reject { background: #2a2a3e; color: #aaa; }\n #details { margin-top: 16px; }\n</style>\n</head>\n<body>\n<div class=\"container\">\n <h1>TronLink Signer</h1>\n <div style=\"display:flex;gap:8px;margin-bottom:16px;\">\n <div id=\"typeBadge\" class=\"type-badge\" style=\"display:none\"></div>\n <div id=\"networkBadge\" class=\"type-badge\" style=\"display:none\"></div>\n </div>\n <div id=\"details\"></div>\n <div id=\"status\" class=\"status info\">Loading request...</div>\n <div id=\"retryGroup\" class=\"buttons\" style=\"display:none\">\n <button class=\"btn-approve\" id=\"retryBtn\">Retry</button>\n </div>\n <div id=\"buttonGroup\" class=\"buttons\" style=\"display:none\">\n <button class=\"btn-approve\" id=\"approveBtn\">Approve</button>\n <button class=\"btn-reject\" id=\"rejectBtn\">Reject</button>\n </div>\n</div>\n<script>\n(function() {\n const params = new URLSearchParams(window.location.search);\n const requestId = params.get('requestId');\n const statusEl = document.getElementById('status');\n const detailsEl = document.getElementById('details');\n const typeBadgeEl = document.getElementById('typeBadge');\n const networkBadgeEl = document.getElementById('networkBadge');\n const buttonGroup = document.getElementById('buttonGroup');\n const retryGroup = document.getElementById('retryGroup');\n const retryBtn = document.getElementById('retryBtn');\n const approveBtn = document.getElementById('approveBtn');\n const rejectBtn = document.getElementById('rejectBtn');\n\n let pendingRequest = null;\n let _providerDetail = null; // TIP-6963 discovered provider\n\n function setStatus(msg, type) {\n statusEl.textContent = msg;\n statusEl.className = 'status ' + type;\n }\n\n function addDetail(label, value) {\n const row = document.createElement('div');\n row.className = 'detail-row';\n row.innerHTML = '<span class=\"label\">' + label + '</span><span class=\"value\">' + escapeHtml(String(value)) + '</span>';\n detailsEl.appendChild(row);\n }\n\n function escapeHtml(s) {\n const d = document.createElement('div');\n d.textContent = s;\n return d.innerHTML;\n }\n\n var NETWORK_NAMES = {\n mainnet: 'Mainnet',\n nile: 'Nile Testnet',\n shasta: 'Shasta Testnet'\n };\n\n function renderDetails(req) {\n typeBadgeEl.textContent = req.type;\n typeBadgeEl.style.display = 'inline-block';\n if (req.network) {\n networkBadgeEl.textContent = NETWORK_NAMES[req.network] || req.network;\n networkBadgeEl.style.display = 'inline-block';\n }\n detailsEl.innerHTML = '';\n\n var data = req.data || {};\n switch (req.type) {\n case 'connect':\n addDetail('Action', 'Connect Wallet');\n break;\n case 'send_trx':\n addDetail('To', data.to);\n addDetail('Amount', data.amount + ' TRX');\n break;\n case 'send_trc20':\n addDetail('Contract', data.contractAddress);\n addDetail('To', data.to);\n addDetail('Amount', data.amount);\n addDetail('Decimals', data.decimals);\n break;\n case 'sign_message':\n addDetail('Message', data.message);\n break;\n case 'sign_typed_data':\n addDetail('Typed Data', JSON.stringify(data.typedData, null, 2));\n break;\n case 'sign_transaction':\n addDetail('Transaction', JSON.stringify(data.transaction, null, 2));\n break;\n }\n }\n\n // --- TIP-6963 Wallet Discovery ---\n\n function discoverWallets() {\n window.addEventListener('TIP6963:announceProvider', function(e) {\n if (!_providerDetail) {\n _providerDetail = e.detail;\n }\n });\n window.dispatchEvent(new Event('TIP6963:requestProvider'));\n }\n\n // Get the TIP-6963 provider (or fallback to window.tron / window.tronLink)\n function getProvider() {\n if (_providerDetail && _providerDetail.provider) {\n return _providerDetail.provider;\n }\n return window.tron || window.tronLink || null;\n }\n\n // Get tronWeb from provider or fallback\n function getTronWeb() {\n var provider = getProvider();\n if (provider && provider.tronWeb) {\n return provider.tronWeb;\n }\n return window.tronWeb || null;\n }\n\n function waitForWallet(maxWait) {\n maxWait = maxWait || 5000;\n // Start TIP-6963 discovery\n discoverWallets();\n\n return new Promise(function(resolve, reject) {\n if (getProvider()) { resolve(true); return; }\n\n var elapsed = 0;\n var interval = setInterval(function() {\n elapsed += 200;\n if (getProvider()) {\n clearInterval(interval);\n resolve(true);\n } else if (elapsed >= maxWait) {\n clearInterval(interval);\n reject(new Error('No TRON wallet found. Please install TronLink extension.'));\n }\n }, 200);\n });\n }\n\n // --- Wallet Connection & Network ---\n\n async function ensureConnected() {\n var tronWeb = getTronWeb();\n if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {\n await getProvider().request({ method: 'tron_requestAccounts' });\n await new Promise(function(r) { setTimeout(r, 500); });\n }\n }\n\n var NETWORK_CHAIN_IDS = {\n mainnet: '0x2b6653dc',\n nile: '0xcd8690dc',\n shasta: '0x94a9059e'\n };\n\n var NETWORK_FULL_HOSTS = {\n mainnet: 'https://api.trongrid.io',\n nile: 'https://nile.trongrid.io',\n shasta: 'https://api.shasta.trongrid.io'\n };\n\n async function ensureWalletReady(expectedNetwork) {\n var provider = getProvider();\n\n // Step 1: Unlock wallet if locked\n setStatus('Connecting wallet...', 'waiting');\n var accountRes = await provider.request({ method: 'eth_requestAccounts' });\n if (accountRes && accountRes.code === 4001) {\n throw new Error('User rejected wallet connection.');\n }\n\n // Step 2: Check if wallet is ready\n var tronWeb = getTronWeb();\n if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {\n await new Promise(function(r) { setTimeout(r, 1000); });\n tronWeb = getTronWeb();\n if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {\n throw new Error('Wallet not ready. Please unlock TronLink and refresh.');\n }\n }\n\n // Step 3: Check network and switch if needed\n if (expectedNetwork) {\n var currentHost = tronWeb.fullNode.host;\n var expectedHost = NETWORK_FULL_HOSTS[expectedNetwork];\n if (expectedHost && currentHost !== expectedHost) {\n setStatus('Switching to ' + expectedNetwork + ' network...', 'waiting');\n var chainId = NETWORK_CHAIN_IDS[expectedNetwork];\n if (chainId) {\n try {\n await provider.request({\n method: 'wallet_switchEthereumChain',\n params: [{ chainId: chainId }]\n });\n await new Promise(function(r) { setTimeout(r, 1500); });\n } catch (switchErr) {\n throw new Error('Please switch TronLink to ' + expectedNetwork + ' network manually then click Retry.');\n }\n }\n }\n }\n }\n\n // --- Execute Actions ---\n\n async function executeTronAction(req) {\n await ensureConnected();\n var tronWeb = getTronWeb();\n var data = req.data || {};\n\n switch (req.type) {\n case 'connect': {\n return { address: tronWeb.defaultAddress.base58 };\n }\n case 'send_trx': {\n var tx = await tronWeb.transactionBuilder.sendTrx(data.to, tronWeb.toSun(data.amount));\n var signedTx = await tronWeb.trx.sign(tx);\n var broadcast = await tronWeb.trx.sendRawTransaction(signedTx);\n return { txId: broadcast.txid };\n }\n case 'send_trc20': {\n var contract = await tronWeb.contract().at(data.contractAddress);\n var decimals = data.decimals || 6;\n var rawAmount = BigInt(data.amount) * BigInt(10 ** decimals);\n var txId = await contract.methods.transfer(data.to, rawAmount.toString()).send();\n return { txId: txId };\n }\n case 'sign_message': {\n var signature = await tronWeb.trx.signMessageV2(data.message);\n return { signature: signature };\n }\n case 'sign_typed_data': {\n var typedData = data.typedData;\n var domain = typedData.domain;\n var types = Object.assign({}, typedData.types);\n delete types.EIP712Domain;\n var message = typedData.message;\n var sig = await tronWeb.trx._signTypedData(domain, types, message);\n return { signature: sig };\n }\n case 'sign_transaction': {\n var signed = await tronWeb.trx.sign(data.transaction);\n return { signedTransaction: signed };\n }\n default:\n throw new Error('Unknown request type: ' + req.type);\n }\n }\n\n // --- Request Handling ---\n\n async function completeRequest(id, success, resultOrError) {\n var body = success\n ? { success: true, result: resultOrError }\n : { success: false, error: resultOrError };\n await fetch('/api/complete/' + id, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n }\n\n function disableButtons() {\n approveBtn.disabled = true;\n rejectBtn.disabled = true;\n }\n\n async function tryEnsureWallet() {\n retryGroup.style.display = 'none';\n buttonGroup.style.display = 'none';\n try {\n await ensureWalletReady(pendingRequest.network);\n setStatus('Ready. Review and approve or reject.', 'info');\n buttonGroup.style.display = 'flex';\n } catch (e) {\n setStatus(e.message, 'error');\n retryGroup.style.display = 'flex';\n }\n }\n\n retryBtn.addEventListener('click', function() {\n tryEnsureWallet();\n });\n\n async function init() {\n if (!requestId) {\n setStatus('Missing request ID in URL.', 'error');\n return;\n }\n\n try {\n var res = await fetch('/api/pending/' + requestId);\n if (!res.ok) {\n setStatus('Request not found or expired.', 'error');\n return;\n }\n pendingRequest = await res.json();\n renderDetails(pendingRequest);\n setStatus('Discovering wallets...', 'waiting');\n\n try {\n await waitForWallet(5000);\n if (_providerDetail && _providerDetail.info) {\n setStatus('Found wallet: ' + _providerDetail.info.name, 'info');\n }\n } catch (e) {\n setStatus(e.message, 'error');\n return;\n }\n\n await tryEnsureWallet();\n } catch (e) {\n setStatus('Failed to load request: ' + e.message, 'error');\n }\n }\n\n approveBtn.addEventListener('click', async function() {\n disableButtons();\n setStatus('Processing with wallet...', 'waiting');\n try {\n var result = await executeTronAction(pendingRequest);\n await completeRequest(requestId, true, result);\n setStatus('Approved and completed successfully.', 'success');\n } catch (e) {\n var msg = e.message || String(e);\n await completeRequest(requestId, false, msg);\n setStatus('Error: ' + msg, 'error');\n }\n });\n\n rejectBtn.addEventListener('click', async function() {\n disableButtons();\n setStatus('Rejected.', 'error');\n await completeRequest(requestId, false, 'USER_REJECTED');\n });\n\n init();\n})();\n</script>\n</body>\n</html>\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":[]}
1
+ {"version":3,"sources":["../src/tron-signer.ts","../src/pending-store.ts","../src/config.ts","../src/http-server.ts","../src/browser.ts","../src/web/index.html","../src/web/js/wallet.js","../src/web/js/tx-parser.js","../src/web/js/actions.js","../src/web/js/app.js"],"sourcesContent":["// @ts-ignore - tronweb types are complex\nimport { TronWeb } from \"tronweb\";\nimport { PendingStore } from \"./pending-store.js\";\nimport { HttpServer } from \"./http-server.js\";\nimport { openApprovalPage } from \"./browser.js\";\nimport { NETWORKS, loadConfig } from \"./config.js\";\nimport type { AppConfig, TronNetwork, SendTrxData, SendTrc20Data, SignMessageData, SignTypedDataData, SignTransactionData } from \"./types.js\";\n// @ts-ignore - HTML imported as text via tsup loader\nimport htmlContent from \"./web/index.html\";\n// @ts-ignore - JS imported as text via tsup loader\nimport walletJs from \"./web/js/wallet.js\";\n// @ts-ignore - JS imported as text via tsup loader\nimport txParserJs from \"./web/js/tx-parser.js\";\n// @ts-ignore - JS imported as text via tsup loader\nimport actionsJs from \"./web/js/actions.js\";\n// @ts-ignore - JS imported as text via tsup loader\nimport appJs from \"./web/js/app.js\";\n\nexport class TronSigner {\n private config: AppConfig;\n private pendingStore: PendingStore;\n private httpServer: HttpServer;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private tronWeb: any;\n\n constructor() {\n this.config = loadConfig();\n this.pendingStore = new PendingStore();\n\n const networkConfig = NETWORKS[this.config.network];\n const tronWebOptions: Record<string, unknown> = {\n fullHost: networkConfig.fullHost,\n };\n if (this.config.apiKey) {\n tronWebOptions.headers = { \"TRON-PRO-API-KEY\": this.config.apiKey };\n }\n // @ts-ignore - tronweb constructor typing\n this.tronWeb = new TronWeb(tronWebOptions);\n\n this.httpServer = new HttpServer(this.pendingStore, htmlContent as string, {\n 'wallet.js': walletJs as string,\n 'tx-parser.js': txParserJs as string,\n 'actions.js': actionsJs as string,\n 'app.js': appJs as string,\n });\n }\n\n async start(): Promise<void> {\n await this.httpServer.start(this.config.httpPort);\n console.error(`HTTP server started on http://127.0.0.1:${this.httpServer.getPort()}`);\n\n // Ensure HTTP server is closed when the process exits\n const cleanup = () => {\n this.httpServer.stop().catch(() => {});\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n process.on(\"beforeExit\", cleanup);\n }\n\n private getPort(): number {\n return this.httpServer.getPort();\n }\n\n async stop(): Promise<void> {\n this.pendingStore.clear();\n await this.httpServer.stop();\n }\n\n getConfig(): AppConfig {\n return this.config;\n }\n\n\n private resolveNetwork(network?: TronNetwork): TronNetwork {\n return network || this.config.network;\n }\n\n async connectWallet(network?: TronNetwork): Promise<{ address: string; network: string }> {\n const net = this.resolveNetwork(network);\n const { id, promise } = this.pendingStore.create(\"connect\", {}, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { address: string; network: string };\n return result;\n }\n\n async sendTrx(to: string, amount: number, network?: TronNetwork): Promise<{ txId: string }> {\n const net = this.resolveNetwork(network);\n const data: SendTrxData = { to, amount };\n const { id, promise } = this.pendingStore.create(\"send_trx\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { txId: string };\n return result;\n }\n\n async sendTrc20(\n contractAddress: string,\n to: string,\n amount: string,\n decimals?: number,\n network?: TronNetwork\n ): Promise<{ txId: string }> {\n const net = this.resolveNetwork(network);\n const data: SendTrc20Data = {\n contractAddress,\n to,\n amount,\n decimals: decimals ?? 6,\n };\n const { id, promise } = this.pendingStore.create(\"send_trc20\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { txId: string };\n return result;\n }\n\n async signMessage(message: string, network?: TronNetwork): Promise<{ signature: string }> {\n const net = this.resolveNetwork(network);\n const data: SignMessageData = { message };\n const { id, promise } = this.pendingStore.create(\"sign_message\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { signature: string };\n return result;\n }\n\n async signTypedData(\n typedData: Record<string, unknown>,\n network?: TronNetwork\n ): Promise<{ signature: string }> {\n const net = this.resolveNetwork(network);\n const data: SignTypedDataData = { typedData };\n const { id, promise } = this.pendingStore.create(\"sign_typed_data\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { signature: string };\n return result;\n }\n\n async signTransaction(\n transaction: Record<string, unknown>,\n network?: TronNetwork,\n broadcast?: boolean\n ): Promise<{ signedTransaction: Record<string, unknown>; txId?: string }> {\n const net = this.resolveNetwork(network);\n const data: SignTransactionData = { transaction, broadcast: broadcast ?? false };\n const { id, promise } = this.pendingStore.create(\"sign_transaction\", data, net);\n await openApprovalPage(this.getPort(), id);\n const result = (await promise) as { signedTransaction: Record<string, unknown>; txId?: string };\n return result;\n }\n\n async getBalance(address: string, network?: TronNetwork): Promise<{ balance: string; balanceSun: number }> {\n const net = this.resolveNetwork(network);\n const networkConfig = NETWORKS[net];\n const tronWebOptions: Record<string, unknown> = { fullHost: networkConfig.fullHost };\n if (this.config.apiKey) {\n tronWebOptions.headers = { \"TRON-PRO-API-KEY\": this.config.apiKey };\n }\n // @ts-ignore\n const tw = net === this.config.network ? this.tronWeb : new (await import(\"tronweb\")).TronWeb(tronWebOptions);\n const balanceSun = await tw.trx.getBalance(address);\n const balance = tw.fromSun(balanceSun).toString();\n return { balance, balanceSun };\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { REQUEST_TIMEOUT_MS } from \"./config.js\";\nimport type { PendingRequest, PendingRequestType, TronNetwork } from \"./types.js\";\n\ninterface PendingEntry<T = unknown> {\n request: PendingRequest<T>;\n resolve: (result: unknown) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport class PendingStore {\n private pending = new Map<string, PendingEntry>();\n\n create<T>(type: PendingRequestType, data: T, network: TronNetwork): { id: string; promise: Promise<unknown> } {\n const id = randomUUID();\n const request: PendingRequest<T> = {\n id,\n type,\n data,\n network,\n createdAt: Date.now(),\n };\n\n let resolve!: (result: unknown) => void;\n let reject!: (error: Error) => void;\n const promise = new Promise<unknown>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(\"TIMEOUT: Request timed out after 5 minutes\"));\n }, REQUEST_TIMEOUT_MS);\n\n this.pending.set(id, { request, resolve, reject, timer });\n console.error(`[PendingStore] Created request: ${id}, type: ${type}, total pending: ${this.pending.size}`);\n\n return { id, promise };\n }\n\n get(id: string): PendingRequest | undefined {\n const entry = this.pending.get(id);\n return entry?.request;\n }\n\n resolve(id: string, result: unknown): boolean {\n const entry = this.pending.get(id);\n if (!entry) return false;\n clearTimeout(entry.timer);\n this.pending.delete(id);\n entry.resolve(result);\n return true;\n }\n\n reject(id: string, error: string): boolean {\n const entry = this.pending.get(id);\n if (!entry) return false;\n clearTimeout(entry.timer);\n this.pending.delete(id);\n entry.reject(new Error(error));\n return true;\n }\n\n getNext(): PendingRequest | undefined {\n let oldest: PendingEntry | undefined;\n for (const entry of this.pending.values()) {\n if (!oldest || entry.request.createdAt < oldest.request.createdAt) {\n oldest = entry;\n }\n }\n return oldest?.request;\n }\n\n size(): number {\n return this.pending.size;\n }\n\n clear(): void {\n for (const [id, entry] of this.pending) {\n clearTimeout(entry.timer);\n entry.reject(new Error(\"Store cleared\"));\n }\n this.pending.clear();\n }\n}\n","import type { TronNetwork, NetworkConfig, AppConfig } from \"./types.js\";\n\nexport const NETWORKS: Record<TronNetwork, NetworkConfig> = {\n mainnet: {\n name: \"Mainnet\",\n fullHost: \"https://api.trongrid.io\",\n explorerUrl: \"https://tronscan.org\",\n },\n nile: {\n name: \"Nile Testnet\",\n fullHost: \"https://nile.trongrid.io\",\n explorerUrl: \"https://nile.tronscan.org\",\n },\n shasta: {\n name: \"Shasta Testnet\",\n fullHost: \"https://api.shasta.trongrid.io\",\n explorerUrl: \"https://shasta.tronscan.org\",\n },\n};\n\nexport const DEFAULT_HTTP_PORT = 3386;\nexport const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport function loadConfig(): AppConfig {\n const network = (process.env.TRON_NETWORK || \"mainnet\") as TronNetwork;\n if (!NETWORKS[network]) {\n throw new Error(\n `Invalid TRON_NETWORK: ${network}. Must be one of: ${Object.keys(NETWORKS).join(\", \")}`\n );\n }\n const httpPort = process.env.TRON_HTTP_PORT\n ? parseInt(process.env.TRON_HTTP_PORT, 10)\n : DEFAULT_HTTP_PORT;\n return {\n network,\n httpPort,\n apiKey: process.env.TRON_API_KEY,\n };\n}\n","import express, { type Express, type Request, type Response } from \"express\";\nimport type { Server } from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { PendingStore } from \"./pending-store.js\";\nimport { NETWORKS } from \"./config.js\";\nimport { recordHeartbeat } from \"./browser.js\";\n\nexport class HttpServer {\n private app: Express;\n private server: Server | null = null;\n private pendingStore: PendingStore;\n private htmlContent: string;\n private jsFiles: Record<string, string>;\n private port: number = 0;\n private sessionId: string;\n\n constructor(pendingStore: PendingStore, htmlContent: string, jsFiles: Record<string, string> = {}) {\n this.pendingStore = pendingStore;\n this.htmlContent = htmlContent;\n this.jsFiles = jsFiles;\n this.sessionId = randomUUID();\n this.app = express();\n this.setupRoutes();\n }\n\n private setupRoutes(): void {\n this.app.use(express.json());\n\n this.app.get(\"/\", (_req: Request, res: Response) => {\n res.setHeader(\"Content-Type\", \"text/html\");\n res.send(this.htmlContent);\n });\n\n this.app.get(\"/js/:name\", (req: Request, res: Response) => {\n const content = this.jsFiles[req.params.name as string];\n if (!content) {\n res.status(404).send(\"Not found\");\n return;\n }\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.send(content);\n });\n\n this.app.get(\"/api/pending/next\", (_req: Request, res: Response) => {\n const next = this.pendingStore.getNext();\n if (!next) {\n res.status(404).json({ error: \"No pending request\" });\n return;\n }\n res.json({\n ...next,\n networkConfig: NETWORKS[next.network],\n });\n });\n\n this.app.get(\"/api/pending/:id\", (req: Request, res: Response) => {\n const request = this.pendingStore.get(req.params.id as string);\n if (!request) {\n res.status(404).json({ error: \"Request not found or expired\" });\n return;\n }\n res.json({\n ...request,\n networkConfig: NETWORKS[request.network],\n });\n });\n\n this.app.post(\"/api/complete/:id\", (req: Request, res: Response) => {\n const id = req.params.id as string;\n const { success, result, error } = req.body;\n\n if (success) {\n const resolved = this.pendingStore.resolve(id, result);\n if (!resolved) {\n res.status(404).json({ error: \"Request not found or expired\" });\n return;\n }\n res.json({ ok: true });\n } else {\n const rejected = this.pendingStore.reject(id, error || \"USER_REJECTED\");\n if (!rejected) {\n res.status(404).json({ error: \"Request not found or expired\" });\n return;\n }\n res.json({ ok: true });\n }\n });\n\n this.app.get(\"/api/session\", (_req: Request, res: Response) => {\n res.json({ sessionId: this.sessionId });\n });\n\n this.app.post(\"/api/heartbeat\", (req: Request, res: Response) => {\n const clientSession = req.body && req.body.sessionId;\n if (clientSession && clientSession !== this.sessionId) {\n res.status(410).json({ error: \"Session expired\", sessionId: this.sessionId });\n return;\n }\n recordHeartbeat();\n res.json({ ok: true, sessionId: this.sessionId });\n });\n\n this.app.get(\"/api/health\", (_req: Request, res: Response) => {\n res.json({ status: \"ok\" });\n });\n\n this.app.get(\"/api/debug\", (_req: Request, res: Response) => {\n res.json({ pendingCount: this.pendingStore.size() });\n });\n }\n\n getPort(): number {\n return this.port;\n }\n\n async start(port: number): Promise<void> {\n this.port = port;\n return this._tryListen(port);\n }\n\n private _tryListen(port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n this.server = this.app.listen(port, \"127.0.0.1\", () => {\n this.port = port;\n console.error(`[HttpServer] Listening on http://127.0.0.1:${port}`);\n resolve();\n });\n this.server.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n const nextPort = port + 1;\n console.error(`[HttpServer] Port ${port} is in use, trying ${nextPort}...`);\n this._tryListen(nextPort).then(resolve, reject);\n } else {\n reject(err);\n }\n });\n } catch (err) {\n reject(err);\n }\n });\n }\n\n async stop(): Promise<void> {\n return new Promise((resolve) => {\n if (this.server) {\n this.server.close(() => resolve());\n } else {\n resolve();\n }\n });\n }\n}\n","import open from \"open\";\n\nlet pageOpened = false;\nlet lastHeartbeat = 0;\n\nconst HEARTBEAT_TIMEOUT = 10_000; // 10s no heartbeat = page closed\n\nexport function recordHeartbeat(): void {\n lastHeartbeat = Date.now();\n}\n\nfunction isPageAlive(): boolean {\n if (!pageOpened) return false;\n if (lastHeartbeat === 0) return false;\n return Date.now() - lastHeartbeat < HEARTBEAT_TIMEOUT;\n}\n\nexport async function openApprovalPage(\n port: number,\n _requestId: string\n): Promise<void> {\n const url = `http://127.0.0.1:${port}/`;\n if (isPageAlive()) {\n // Page is still open, it will pick up the new request via polling\n return;\n }\n pageOpened = true;\n lastHeartbeat = Date.now();\n await open(url);\n}\n","<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>TronLink Signer</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n background: #0f0f0f;\n color: #e0e0e0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n padding: 20px;\n }\n .container {\n background: #1a1a2e;\n border-radius: 16px;\n padding: 32px;\n max-width: 480px;\n width: 100%;\n box-shadow: 0 8px 32px rgba(0,0,0,0.4);\n }\n h1 {\n font-size: 20px;\n margin-bottom: 8px;\n color: #fff;\n }\n .type-badge {\n display: inline-block;\n background: #2a2a4e;\n color: #8888cc;\n padding: 4px 12px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 600;\n margin-bottom: 20px;\n }\n .detail-row {\n display: flex;\n justify-content: space-between;\n padding: 10px 0;\n border-bottom: 1px solid #2a2a3e;\n font-size: 14px;\n }\n .detail-row .label { color: #888; }\n .detail-row .value {\n color: #e0e0e0;\n word-break: break-all;\n text-align: right;\n max-width: 60%;\n }\n .status {\n margin-top: 20px;\n padding: 12px 16px;\n border-radius: 8px;\n font-size: 13px;\n line-height: 1.5;\n }\n .status.info { background: #1a2a4e; color: #6ea8fe; }\n .status.success { background: #1a3a2a; color: #75d9a0; }\n .status.error { background: #3a1a1a; color: #f87171; }\n .status.waiting { background: #3a3a1a; color: #fbbf24; }\n .buttons {\n display: flex;\n gap: 12px;\n margin-top: 24px;\n }\n .buttons button {\n flex: 1;\n padding: 14px;\n border: none;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n transition: opacity 0.2s;\n }\n .buttons button:hover { opacity: 0.85; }\n .buttons button:disabled { opacity: 0.4; cursor: not-allowed; }\n .btn-approve { background: #c23631; color: #fff; }\n .btn-reject { background: #2a2a3e; color: #aaa; }\n #details { margin-top: 16px; }\n</style>\n</head>\n<body>\n<div class=\"container\">\n <h1>TronLink Signer</h1>\n <div style=\"display:flex;gap:8px;margin-bottom:16px;\">\n <div id=\"typeBadge\" class=\"type-badge\" style=\"display:none\"></div>\n <div id=\"networkBadge\" class=\"type-badge\" style=\"display:none\"></div>\n </div>\n <div id=\"details\"></div>\n <div id=\"status\" class=\"status info\">Loading request...</div>\n <div id=\"retryGroup\" class=\"buttons\" style=\"display:none\">\n <button class=\"btn-approve\" id=\"retryBtn\">Retry</button>\n </div>\n <div id=\"buttonGroup\" class=\"buttons\" style=\"display:none\">\n <button class=\"btn-approve\" id=\"approveBtn\">Approve</button>\n <button class=\"btn-reject\" id=\"rejectBtn\">Reject</button>\n </div>\n</div>\n<script src=\"/js/wallet.js\"></script>\n<script src=\"/js/tx-parser.js\"></script>\n<script src=\"/js/actions.js\"></script>\n<script src=\"/js/app.js\"></script>\n</body>\n</html>\n","// Wallet discovery, connection, and network management\n(function() {\n var _providerDetail = null;\n\n function discoverWallets() {\n window.addEventListener('TIP6963:announceProvider', function(e) {\n if (!_providerDetail) {\n _providerDetail = e.detail;\n }\n });\n window.dispatchEvent(new Event('TIP6963:requestProvider'));\n }\n\n function getProviderDetail() {\n return _providerDetail;\n }\n\n function getProvider() {\n if (_providerDetail && _providerDetail.provider) {\n return _providerDetail.provider;\n }\n return window.tron || window.tronLink || null;\n }\n\n function getTronWeb() {\n var provider = getProvider();\n if (provider && provider.tronWeb) {\n return provider.tronWeb;\n }\n return window.tronWeb || null;\n }\n\n function waitForWallet(maxWait) {\n maxWait = maxWait || 5000;\n discoverWallets();\n\n return new Promise(function(resolve, reject) {\n if (getProvider()) { resolve(true); return; }\n\n var elapsed = 0;\n var interval = setInterval(function() {\n elapsed += 200;\n if (getProvider()) {\n clearInterval(interval);\n resolve(true);\n } else if (elapsed >= maxWait) {\n clearInterval(interval);\n reject(new Error('No TRON wallet found. Please install TronLink extension.'));\n }\n }, 200);\n });\n }\n\n var NETWORK_CHAIN_IDS = {\n mainnet: '0x2b6653dc',\n nile: '0xcd8690dc',\n shasta: '0x94a9059e'\n };\n\n var NETWORK_FULL_HOSTS = {\n mainnet: 'https://api.trongrid.io',\n nile: 'https://nile.trongrid.io',\n shasta: 'https://api.shasta.trongrid.io'\n };\n\n function isConnected() {\n var tronWeb = getTronWeb();\n return !!(tronWeb && tronWeb.defaultAddress && tronWeb.defaultAddress.base58);\n }\n\n function getAddress() {\n var tronWeb = getTronWeb();\n return (tronWeb && tronWeb.defaultAddress && tronWeb.defaultAddress.base58) || null;\n }\n\n async function ensureConnected() {\n if (isConnected()) return;\n await getProvider().request({ method: 'tron_requestAccounts' });\n await new Promise(function(r) { setTimeout(r, 500); });\n }\n\n async function ensureWalletReady(expectedNetwork, setStatus) {\n var provider = getProvider();\n if (!provider) {\n throw new Error('No TRON wallet found. Please install TronLink extension and refresh.');\n }\n\n // Skip connection prompt if already connected\n if (!isConnected()) {\n setStatus('Connecting wallet...', 'waiting');\n try {\n var accountRes = await provider.request({ method: 'eth_requestAccounts' });\n if (accountRes && accountRes.code === 4001) {\n throw new Error('User rejected wallet connection.');\n }\n } catch (e) {\n throw new Error(e.message || 'Wallet connection failed. Please click Retry.');\n }\n }\n\n var tronWeb = getTronWeb();\n if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {\n await new Promise(function(r) { setTimeout(r, 1000); });\n tronWeb = getTronWeb();\n if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {\n throw new Error('Wallet not ready. Please unlock TronLink and refresh.');\n }\n }\n\n if (expectedNetwork) {\n var currentHost = tronWeb.fullNode.host;\n var expectedHost = NETWORK_FULL_HOSTS[expectedNetwork];\n if (expectedHost && currentHost !== expectedHost) {\n setStatus('Switching to ' + expectedNetwork + ' network...', 'waiting');\n var chainId = NETWORK_CHAIN_IDS[expectedNetwork];\n if (chainId) {\n try {\n await provider.request({\n method: 'wallet_switchEthereumChain',\n params: [{ chainId: chainId }]\n });\n await new Promise(function(r) { setTimeout(r, 1500); });\n } catch (switchErr) {\n throw new Error('Please switch TronLink to ' + expectedNetwork + ' network manually then click Retry.');\n }\n }\n }\n }\n }\n\n var HOST_TO_NETWORK = {\n 'https://api.trongrid.io': 'mainnet',\n 'https://nile.trongrid.io': 'nile',\n 'https://api.shasta.trongrid.io': 'shasta'\n };\n\n function getCurrentNetwork() {\n var tw = getTronWeb();\n if (tw && tw.fullNode) {\n return HOST_TO_NETWORK[tw.fullNode.host] || 'unknown';\n }\n return 'unknown';\n }\n\n // Expose to global\n window.TronWallet = {\n discoverWallets: discoverWallets,\n getProviderDetail: getProviderDetail,\n getProvider: getProvider,\n getTronWeb: getTronWeb,\n waitForWallet: waitForWallet,\n isConnected: isConnected,\n getAddress: getAddress,\n ensureConnected: ensureConnected,\n ensureWalletReady: ensureWalletReady,\n getCurrentNetwork: getCurrentNetwork\n };\n})();\n","// Transaction parsing and async data fetching\n(function() {\n var RESOURCE_NAMES = { 0: 'Bandwidth', 'BANDWIDTH': 'Bandwidth', 1: 'Energy', 'ENERGY': 'Energy' };\n\n function getResourceName(r) {\n if (r === undefined || r === null) return 'Bandwidth';\n return RESOURCE_NAMES[r] || String(r);\n }\n\n function fromHexString(hex) {\n if (!hex || typeof hex !== 'string') return hex;\n try {\n var str = '';\n for (var i = 0; i < hex.length; i += 2) {\n str += String.fromCharCode(parseInt(hex.substring(i, i + 2), 16));\n }\n return str;\n } catch(e) { return hex; }\n }\n\n function fromHexAddress(hex) {\n if (!hex || typeof hex !== 'string') return hex;\n try {\n var tw = window.TronWallet.getTronWeb();\n if (tw && tw.address && tw.address.fromHex) return tw.address.fromHex(hex);\n } catch(e) {}\n return hex;\n }\n\n function fromSun(sun) {\n return (Number(sun) / 1e6) + ' TRX';\n }\n\n function parseTransaction(tx) {\n var contract = null;\n if (tx.raw_data && tx.raw_data.contract && tx.raw_data.contract[0]) {\n contract = tx.raw_data.contract[0];\n }\n if (!contract) return null;\n\n var type = contract.type;\n var v = (contract.parameter && contract.parameter.value) || {};\n var info = { type: type, label: type, details: [] };\n\n switch (type) {\n case 'TransferContract':\n info.label = 'Transfer TRX';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'To', v: fromHexAddress(v.to_address) },\n { l: 'Amount', v: fromSun(v.amount) }\n ];\n break;\n case 'TransferAssetContract':\n info.label = 'Transfer TRC10 Asset';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'To', v: fromHexAddress(v.to_address) },\n { l: 'Asset ID', v: fromHexString(v.asset_name) },\n { l: 'Amount', v: v.amount }\n ];\n break;\n case 'TriggerSmartContract': {\n var methodSig = v.data ? v.data.substring(0, 8) : '';\n if (methodSig === 'a9059cbb' && v.data && v.data.length >= 136) {\n var toHex = '41' + v.data.substring(32, 72);\n var amountHex = v.data.substring(72, 136);\n info.label = 'TRC20 Transfer';\n info._trc20 = {\n toHex: toHex,\n contractHex: v.contract_address,\n rawAmount: BigInt('0x' + amountHex).toString()\n };\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'To', v: fromHexAddress(toHex) },\n { l: 'Contract', v: fromHexAddress(v.contract_address) },\n { l: 'Amount', v: 'Loading...' }\n ];\n } else if (methodSig === '23b872dd' && v.data && v.data.length >= 200) {\n // TRC721 transferFrom(address,address,uint256)\n var nftFrom = '41' + v.data.substring(32, 72);\n var nftTo = '41' + v.data.substring(96, 136);\n var tokenIdHex = v.data.substring(136, 200);\n var tokenId = BigInt('0x' + tokenIdHex).toString();\n info.label = 'TRC721 Transfer (NFT)';\n info.details = [\n { l: 'From', v: fromHexAddress(nftFrom) },\n { l: 'To', v: fromHexAddress(nftTo) },\n { l: 'Contract', v: fromHexAddress(v.contract_address) },\n { l: 'Token ID', v: tokenId }\n ];\n } else {\n var METHOD_NAMES = {\n '095ea7b3': 'approve',\n '70a08231': 'balanceOf',\n 'dd62ed3e': 'allowance',\n '42842e0e': 'safeTransferFrom'\n };\n var methodName = METHOD_NAMES[methodSig];\n info.label = methodName ? 'Contract Call: ' + methodName : 'Contract Call';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'Contract', v: fromHexAddress(v.contract_address) },\n { l: 'Call Value', v: v.call_value ? fromSun(v.call_value) : '0 TRX' },\n { l: 'Method', v: methodName || (methodSig ? '0x' + methodSig : '-') }\n ];\n }\n break;\n }\n case 'FreezeBalanceV2Contract':\n info.label = 'Stake TRX (Freeze v2)';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Amount', v: fromSun(v.frozen_balance) },\n { l: 'Resource', v: getResourceName(v.resource) }\n ];\n break;\n case 'UnfreezeBalanceV2Contract':\n info.label = 'Unstake TRX (Unfreeze v2)';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Amount', v: fromSun(v.unfreeze_balance) },\n { l: 'Resource', v: getResourceName(v.resource) }\n ];\n break;\n case 'DelegateResourceContract': {\n info.label = 'Delegate Resource';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'To', v: fromHexAddress(v.receiver_address) },\n { l: 'Amount', v: fromSun(v.balance) },\n { l: 'Resource', v: getResourceName(v.resource) },\n { l: 'Lock', v: v.lock ? 'Yes' : 'No' }\n ];\n if (v.lock && v.lock_period) {\n var days = Math.round(v.lock_period * 3 / 86400);\n info.details.push({ l: 'Lock Period', v: days + ' days' });\n }\n break;\n }\n case 'UnDelegateResourceContract':\n info.label = 'Undelegate Resource';\n info.details = [\n { l: 'From', v: fromHexAddress(v.owner_address) },\n { l: 'Receiver', v: fromHexAddress(v.receiver_address) },\n { l: 'Amount', v: fromSun(v.balance) },\n { l: 'Resource', v: getResourceName(v.resource) }\n ];\n break;\n case 'WithdrawExpireUnfreezeContract':\n info.label = 'Withdraw Unfrozen TRX';\n info._withdrawOwner = fromHexAddress(v.owner_address);\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Amount', v: 'Loading...' }\n ];\n break;\n case 'VoteWitnessContract':\n info.label = 'Vote for SR';\n info.details = [\n { l: 'Voter', v: fromHexAddress(v.owner_address) }\n ];\n if (v.votes && v.votes.length) {\n v.votes.forEach(function(vote, i) {\n info.details.push({ l: 'SR #' + (i+1), v: fromHexAddress(vote.vote_address) + ' (' + vote.vote_count + ')' });\n });\n }\n break;\n case 'WithdrawBalanceContract':\n info.label = 'Claim Rewards';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) }\n ];\n break;\n case 'CreateSmartContract':\n info.label = 'Deploy Contract';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Name', v: (v.new_contract && v.new_contract.name) || '-' }\n ];\n break;\n case 'AccountCreateContract':\n info.label = 'Create Account';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'New Account', v: fromHexAddress(v.account_address) }\n ];\n break;\n case 'AccountUpdateContract':\n info.label = 'Update Account Name';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) },\n { l: 'Name', v: v.account_name || '-' }\n ];\n break;\n case 'AccountPermissionUpdateContract':\n info.label = 'Update Account Permission';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) }\n ];\n break;\n case 'CancelAllUnfreezeV2Contract':\n info.label = 'Cancel All Pending Unstake';\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) }\n ];\n break;\n default:\n info.details = [\n { l: 'Owner', v: fromHexAddress(v.owner_address) || '-' }\n ];\n }\n return info;\n }\n\n // Update an Amount row in the details UI\n function updateAmountRow(detailsEl, text) {\n var rows = detailsEl.querySelectorAll('.detail-row');\n for (var i = 0; i < rows.length; i++) {\n var label = rows[i].querySelector('.label');\n if (label && label.textContent === 'Amount') {\n rows[i].querySelector('.value').textContent = text;\n return;\n }\n }\n }\n\n async function fetchTrc20Info(trc20, detailsEl) {\n try {\n var tronWeb = window.TronWallet.getTronWeb();\n if (!tronWeb) {\n await window.TronWallet.waitForWallet(5000);\n tronWeb = window.TronWallet.getTronWeb();\n }\n if (!tronWeb) return;\n\n var addr = fromHexAddress(trc20.contractHex);\n var contract = await tronWeb.contract().at(addr);\n var decimals = 6;\n var symbol = '';\n try { decimals = await contract.methods.decimals().call(); } catch(e) {}\n try { symbol = await contract.methods.symbol().call(); } catch(e) {}\n\n var raw = BigInt(trc20.rawAmount);\n var divisor = 10n ** BigInt(decimals);\n var whole = raw / divisor;\n var frac = raw % divisor;\n var formatted = whole.toString();\n if (frac > 0n) {\n var fracStr = frac.toString().padStart(Number(decimals), '0').replace(/0+$/, '');\n formatted += '.' + fracStr;\n }\n updateAmountRow(detailsEl, formatted + (symbol ? ' ' + symbol : ''));\n } catch(e) {\n updateAmountRow(detailsEl, trc20.rawAmount + ' (raw)');\n }\n }\n\n async function fetchWithdrawAmount(ownerAddress, detailsEl) {\n try {\n var tronWeb = window.TronWallet.getTronWeb();\n if (!tronWeb) {\n await window.TronWallet.waitForWallet(5000);\n tronWeb = window.TronWallet.getTronWeb();\n }\n if (!tronWeb) return;\n\n var account = await tronWeb.trx.getAccount(ownerAddress);\n var total = 0;\n var now = Date.now();\n if (account.unfrozenV2 && account.unfrozenV2.length) {\n account.unfrozenV2.forEach(function(item) {\n if (item.unfreeze_expire_time && item.unfreeze_expire_time <= now) {\n total += item.unfreeze_amount || 0;\n }\n });\n }\n updateAmountRow(detailsEl, fromSun(total));\n } catch(e) {\n updateAmountRow(detailsEl, 'Unable to fetch');\n }\n }\n\n window.TxParser = {\n parseTransaction: parseTransaction,\n fetchTrc20Info: fetchTrc20Info,\n fetchWithdrawAmount: fetchWithdrawAmount\n };\n})();\n","// Execute wallet actions (sign, send, connect)\n(function() {\n async function executeTronAction(req) {\n await window.TronWallet.ensureConnected();\n var tronWeb = window.TronWallet.getTronWeb();\n var data = req.data || {};\n\n switch (req.type) {\n case 'connect': {\n return { address: tronWeb.defaultAddress.base58, network: window.TronWallet.getCurrentNetwork() };\n }\n case 'send_trx': {\n var tx = await tronWeb.transactionBuilder.sendTrx(data.to, tronWeb.toSun(data.amount));\n var signedTx = await tronWeb.trx.sign(tx);\n var broadcast = await tronWeb.trx.sendRawTransaction(signedTx);\n if (broadcast.result === false) {\n throw new Error('Broadcast failed: ' + (broadcast.message || broadcast.code || 'unknown error'));\n }\n return { txId: broadcast.txid };\n }\n case 'send_trc20': {\n var contract = await tronWeb.contract().at(data.contractAddress);\n var decimals = (data.decimals !== undefined && data.decimals !== null) ? data.decimals : 6;\n var amountStr = String(data.amount).trim();\n if (!/^\\d+(\\.\\d+)?$/.test(amountStr)) {\n throw new Error('Invalid amount format: ' + data.amount);\n }\n var parts = amountStr.split('.');\n var whole = parts[0] || '0';\n var fracInput = parts[1] || '';\n if (fracInput.length > decimals) {\n throw new Error('Amount has too many decimal places (max ' + decimals + ' for this token). Got: ' + data.amount);\n }\n if (decimals > 18) {\n throw new Error('Decimals too large (max 18). Got: ' + decimals);\n }\n var frac = decimals > 0 ? fracInput.padEnd(decimals, '0') : '';\n var multiplier = 10n ** BigInt(decimals);\n var rawAmount = decimals > 0\n ? BigInt(whole) * multiplier + BigInt(frac)\n : BigInt(whole);\n if (rawAmount === 0n) {\n throw new Error('Amount is zero after conversion. Please check the amount: ' + data.amount);\n }\n var txId = await contract.methods.transfer(data.to, rawAmount.toString()).send();\n return { txId: txId };\n }\n case 'sign_message': {\n var signature = await tronWeb.trx.signMessageV2(data.message);\n return { signature: signature };\n }\n case 'sign_typed_data': {\n var typedData = data.typedData;\n var domain = typedData.domain;\n var types = Object.assign({}, typedData.types);\n delete types.EIP712Domain;\n var message = typedData.message;\n var sig = await tronWeb.trx._signTypedData(domain, types, message);\n return { signature: sig };\n }\n case 'sign_transaction': {\n var signed = await tronWeb.trx.sign(data.transaction);\n if (data.broadcast) {\n var broadcast = await tronWeb.trx.sendRawTransaction(signed);\n if (broadcast.result === false) {\n throw new Error('Broadcast failed: ' + (broadcast.message || broadcast.code || 'unknown error'));\n }\n return { signedTransaction: signed, txId: broadcast.txid };\n }\n return { signedTransaction: signed };\n }\n default:\n throw new Error('Unknown request type: ' + req.type);\n }\n }\n\n window.TronActions = {\n execute: executeTronAction\n };\n})();\n","// Main app: polling, request lifecycle, UI events\n(function() {\n var statusEl = document.getElementById('status');\n var detailsEl = document.getElementById('details');\n var typeBadgeEl = document.getElementById('typeBadge');\n var networkBadgeEl = document.getElementById('networkBadge');\n var buttonGroup = document.getElementById('buttonGroup');\n var retryGroup = document.getElementById('retryGroup');\n var retryBtn = document.getElementById('retryBtn');\n var approveBtn = document.getElementById('approveBtn');\n var rejectBtn = document.getElementById('rejectBtn');\n\n var pendingRequest = null;\n var currentRequestId = null;\n var polling = false;\n var sessionId = null;\n\n var NETWORK_NAMES = {\n mainnet: 'Mainnet',\n nile: 'Nile Testnet',\n shasta: 'Shasta Testnet'\n };\n\n function markSessionExpired() {\n sessionExpired = true;\n setStatus('Session expired. Please close this tab and try again.', 'error');\n buttonGroup.style.display = 'none';\n retryGroup.style.display = 'none';\n approveBtn.disabled = true;\n rejectBtn.disabled = true;\n }\n\n // Heartbeat — detect server disconnect or session change\n var heartbeatFailCount = 0;\n var sessionExpired = false;\n setInterval(function() {\n if (sessionExpired) return;\n fetch('/api/heartbeat', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId: sessionId })\n })\n .then(function(res) {\n if (res.status === 410) {\n // Server restarted with new session\n markSessionExpired();\n return;\n }\n if (res.ok) {\n heartbeatFailCount = 0;\n } else {\n heartbeatFailCount++;\n }\n })\n .catch(function() {\n heartbeatFailCount++;\n })\n .finally(function() {\n if (heartbeatFailCount >= 3 && !sessionExpired) {\n markSessionExpired();\n }\n });\n }, 3000);\n\n // --- UI helpers ---\n\n function setStatus(msg, type) {\n statusEl.textContent = msg;\n statusEl.className = 'status ' + type;\n }\n\n function escapeHtml(s) {\n var d = document.createElement('div');\n d.textContent = s;\n return d.innerHTML;\n }\n\n function addDetail(label, value) {\n var row = document.createElement('div');\n row.className = 'detail-row';\n row.innerHTML = '<span class=\"label\">' + label + '</span><span class=\"value\">' + escapeHtml(String(value)) + '</span>';\n detailsEl.appendChild(row);\n }\n\n function disableButtons() {\n approveBtn.disabled = true;\n rejectBtn.disabled = true;\n }\n\n // --- Render request details ---\n\n function renderDetails(req) {\n typeBadgeEl.textContent = req.type;\n typeBadgeEl.style.display = 'inline-block';\n if (req.network) {\n networkBadgeEl.textContent = NETWORK_NAMES[req.network] || req.network;\n networkBadgeEl.style.display = 'inline-block';\n }\n detailsEl.innerHTML = '';\n\n var data = req.data || {};\n switch (req.type) {\n case 'connect':\n addDetail('Action', 'Connect Wallet');\n break;\n case 'send_trx':\n addDetail('To', data.to);\n addDetail('Amount', data.amount + ' TRX');\n break;\n case 'send_trc20':\n addDetail('Contract', data.contractAddress);\n addDetail('To', data.to);\n addDetail('Amount', data.amount);\n addDetail('Decimals', data.decimals);\n break;\n case 'sign_message':\n addDetail('Message', data.message);\n break;\n case 'sign_typed_data':\n addDetail('Typed Data', JSON.stringify(data.typedData, null, 2));\n break;\n case 'sign_transaction': {\n if (data.broadcast) {\n addDetail('Broadcast', 'Yes (will send on-chain after signing)');\n }\n var parsed = window.TxParser.parseTransaction(data.transaction);\n if (parsed) {\n addDetail('Type', parsed.label);\n parsed.details.forEach(function(d) { addDetail(d.l, d.v); });\n if (parsed._trc20) {\n window.TxParser.fetchTrc20Info(parsed._trc20, detailsEl);\n }\n if (parsed._withdrawOwner) {\n window.TxParser.fetchWithdrawAmount(parsed._withdrawOwner, detailsEl);\n }\n } else {\n addDetail('Transaction', JSON.stringify(data.transaction, null, 2));\n }\n break;\n }\n }\n }\n\n // --- Wallet readiness ---\n\n async function tryEnsureWallet() {\n retryGroup.style.display = 'none';\n buttonGroup.style.display = 'none';\n try {\n await window.TronWallet.ensureWalletReady(pendingRequest.network, setStatus);\n\n // Auto-complete connect requests once wallet is ready\n if (pendingRequest.type === 'connect') {\n for (var i = 0; i < 10; i++) {\n var addr = window.TronWallet.getAddress();\n if (addr) {\n try {\n await completeRequest(currentRequestId, true, { address: addr, network: window.TronWallet.getCurrentNetwork() });\n setStatus('Wallet connected: ' + addr, 'success');\n } catch (_) {\n setStatus('Request expired or no longer available.', 'error');\n }\n startPollingAfterDone();\n return;\n }\n await new Promise(function(r) { setTimeout(r, 300); });\n }\n }\n\n setStatus('Ready. Review and approve or reject.', 'info');\n buttonGroup.style.display = 'flex';\n } catch (e) {\n setStatus(e.message || 'Wallet connection failed. Please try again.', 'error');\n retryGroup.style.display = 'flex';\n }\n }\n\n // --- Request lifecycle ---\n\n async function completeRequest(id, success, resultOrError) {\n var body = success\n ? { success: true, result: resultOrError }\n : { success: false, error: resultOrError };\n var res = await fetch('/api/complete/' + id, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n throw new Error('Request expired or no longer available.');\n }\n }\n\n async function handleRequest(req) {\n pendingRequest = req;\n currentRequestId = req.id;\n\n renderDetails(req);\n\n // Wait for wallet extension to inject first\n setStatus('Discovering wallets...', 'waiting');\n try {\n await window.TronWallet.waitForWallet(5000);\n var detail = window.TronWallet.getProviderDetail();\n if (detail && detail.info) {\n setStatus('Found wallet: ' + detail.info.name, 'info');\n }\n } catch (e) {\n setStatus(e.message, 'error');\n return;\n }\n\n await tryEnsureWallet();\n }\n\n // --- Polling ---\n\n async function pollForRequests() {\n if (polling) return;\n polling = true;\n if (!currentRequestId) {\n setStatus('Waiting for request...', 'info');\n }\n\n var interval = 500;\n var maxInterval = 5000;\n\n while (true) {\n if (sessionExpired) { polling = false; return; }\n try {\n var res = await fetch('/api/pending/next');\n if (res.ok) {\n var req = await res.json();\n if (req.id !== currentRequestId) {\n buttonGroup.style.display = 'none';\n retryGroup.style.display = 'none';\n approveBtn.disabled = false;\n rejectBtn.disabled = false;\n polling = false;\n await handleRequest(req);\n return;\n }\n }\n interval = Math.min(interval * 1.5, maxInterval);\n } catch (e) {\n interval = maxInterval;\n }\n await new Promise(function(r) { setTimeout(r, interval); });\n }\n }\n\n function startPollingAfterDone() {\n polling = false;\n pollForRequests();\n }\n\n // --- Event listeners ---\n\n retryBtn.addEventListener('click', function() {\n tryEnsureWallet();\n });\n\n approveBtn.addEventListener('click', async function() {\n disableButtons();\n setStatus('Processing with wallet...', 'waiting');\n try {\n var result = await window.TronActions.execute(pendingRequest);\n await completeRequest(currentRequestId, true, result);\n setStatus('Approved and completed successfully.', 'success');\n } catch (e) {\n var msg = e.message || String(e);\n try {\n await completeRequest(currentRequestId, false, msg);\n } catch (_) {\n // Request already expired, ignore\n }\n setStatus('Error: ' + msg, 'error');\n }\n startPollingAfterDone();\n });\n\n rejectBtn.addEventListener('click', async function() {\n disableButtons();\n try {\n await completeRequest(currentRequestId, false, 'USER_REJECTED');\n setStatus('Rejected.', 'error');\n } catch (_) {\n setStatus('Request expired or no longer available.', 'error');\n }\n startPollingAfterDone();\n });\n\n // --- Init ---\n window.TronWallet.discoverWallets();\n // Fetch session ID before starting, then poll\n fetch('/api/session').then(function(res) {\n return res.json();\n }).then(function(data) {\n sessionId = data.sessionId;\n pollForRequests();\n }).catch(function() {\n // Server might not be ready yet, start polling anyway\n pollForRequests();\n });\n})();\n"],"mappings":";AACA,SAAS,eAAe;;;ACDxB,SAAS,kBAAkB;;;ACEpB,IAAM,WAA+C;AAAA,EAC1D,SAAS;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AACF;AAEO,IAAM,oBAAoB;AAC1B,IAAM,qBAAqB,IAAI,KAAK;AAEpC,SAAS,aAAwB;AACtC,QAAM,UAAW,QAAQ,IAAI,gBAAgB;AAC7C,MAAI,CAAC,SAAS,OAAO,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,yBAAyB,OAAO,qBAAqB,OAAO,KAAK,QAAQ,EAAE,KAAK,IAAI,CAAC;AAAA,IACvF;AAAA,EACF;AACA,QAAM,WAAW,QAAQ,IAAI,iBACzB,SAAS,QAAQ,IAAI,gBAAgB,EAAE,IACvC;AACJ,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ,QAAQ,IAAI;AAAA,EACtB;AACF;;;AD3BO,IAAM,eAAN,MAAmB;AAAA,EAChB,UAAU,oBAAI,IAA0B;AAAA,EAEhD,OAAU,MAA0B,MAAS,SAAiE;AAC5G,UAAM,KAAK,WAAW;AACtB,UAAM,UAA6B;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,IACtB;AAEA,QAAI;AACJ,QAAI;AACJ,UAAM,UAAU,IAAI,QAAiB,CAAC,KAAK,QAAQ;AACjD,gBAAU;AACV,eAAS;AAAA,IACX,CAAC;AAED,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK,QAAQ,OAAO,EAAE;AACtB,aAAO,IAAI,MAAM,4CAA4C,CAAC;AAAA,IAChE,GAAG,kBAAkB;AAErB,SAAK,QAAQ,IAAI,IAAI,EAAE,SAAS,SAAS,QAAQ,MAAM,CAAC;AACxD,YAAQ,MAAM,mCAAmC,EAAE,WAAW,IAAI,oBAAoB,KAAK,QAAQ,IAAI,EAAE;AAEzG,WAAO,EAAE,IAAI,QAAQ;AAAA,EACvB;AAAA,EAEA,IAAI,IAAwC;AAC1C,UAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE;AACjC,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,QAAQ,IAAY,QAA0B;AAC5C,UAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE;AACjC,QAAI,CAAC,MAAO,QAAO;AACnB,iBAAa,MAAM,KAAK;AACxB,SAAK,QAAQ,OAAO,EAAE;AACtB,UAAM,QAAQ,MAAM;AACpB,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,IAAY,OAAwB;AACzC,UAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE;AACjC,QAAI,CAAC,MAAO,QAAO;AACnB,iBAAa,MAAM,KAAK;AACxB,SAAK,QAAQ,OAAO,EAAE;AACtB,UAAM,OAAO,IAAI,MAAM,KAAK,CAAC;AAC7B,WAAO;AAAA,EACT;AAAA,EAEA,UAAsC;AACpC,QAAI;AACJ,eAAW,SAAS,KAAK,QAAQ,OAAO,GAAG;AACzC,UAAI,CAAC,UAAU,MAAM,QAAQ,YAAY,OAAO,QAAQ,WAAW;AACjE,iBAAS;AAAA,MACX;AAAA,IACF;AACA,WAAO,QAAQ;AAAA,EACjB;AAAA,EAEA,OAAe;AACb,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EAEA,QAAc;AACZ,eAAW,CAAC,IAAI,KAAK,KAAK,KAAK,SAAS;AACtC,mBAAa,MAAM,KAAK;AACxB,YAAM,OAAO,IAAI,MAAM,eAAe,CAAC;AAAA,IACzC;AACA,SAAK,QAAQ,MAAM;AAAA,EACrB;AACF;;;AEtFA,OAAO,aAA4D;AAEnE,SAAS,cAAAA,mBAAkB;;;ACF3B,OAAO,UAAU;AAEjB,IAAI,aAAa;AACjB,IAAI,gBAAgB;AAEpB,IAAM,oBAAoB;AAEnB,SAAS,kBAAwB;AACtC,kBAAgB,KAAK,IAAI;AAC3B;AAEA,SAAS,cAAuB;AAC9B,MAAI,CAAC,WAAY,QAAO;AACxB,MAAI,kBAAkB,EAAG,QAAO;AAChC,SAAO,KAAK,IAAI,IAAI,gBAAgB;AACtC;AAEA,eAAsB,iBACpB,MACA,YACe;AACf,QAAM,MAAM,oBAAoB,IAAI;AACpC,MAAI,YAAY,GAAG;AAEjB;AAAA,EACF;AACA,eAAa;AACb,kBAAgB,KAAK,IAAI;AACzB,QAAM,KAAK,GAAG;AAChB;;;ADtBO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA,SAAwB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAe;AAAA,EACf;AAAA,EAER,YAAY,cAA4B,aAAqB,UAAkC,CAAC,GAAG;AACjG,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,UAAU;AACf,SAAK,YAAYC,YAAW;AAC5B,SAAK,MAAM,QAAQ;AACnB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,cAAoB;AAC1B,SAAK,IAAI,IAAI,QAAQ,KAAK,CAAC;AAE3B,SAAK,IAAI,IAAI,KAAK,CAAC,MAAe,QAAkB;AAClD,UAAI,UAAU,gBAAgB,WAAW;AACzC,UAAI,KAAK,KAAK,WAAW;AAAA,IAC3B,CAAC;AAED,SAAK,IAAI,IAAI,aAAa,CAAC,KAAc,QAAkB;AACzD,YAAM,UAAU,KAAK,QAAQ,IAAI,OAAO,IAAc;AACtD,UAAI,CAAC,SAAS;AACZ,YAAI,OAAO,GAAG,EAAE,KAAK,WAAW;AAChC;AAAA,MACF;AACA,UAAI,UAAU,gBAAgB,wBAAwB;AACtD,UAAI,KAAK,OAAO;AAAA,IAClB,CAAC;AAED,SAAK,IAAI,IAAI,qBAAqB,CAAC,MAAe,QAAkB;AAClE,YAAM,OAAO,KAAK,aAAa,QAAQ;AACvC,UAAI,CAAC,MAAM;AACT,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,qBAAqB,CAAC;AACpD;AAAA,MACF;AACA,UAAI,KAAK;AAAA,QACP,GAAG;AAAA,QACH,eAAe,SAAS,KAAK,OAAO;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAED,SAAK,IAAI,IAAI,oBAAoB,CAAC,KAAc,QAAkB;AAChE,YAAM,UAAU,KAAK,aAAa,IAAI,IAAI,OAAO,EAAY;AAC7D,UAAI,CAAC,SAAS;AACZ,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,+BAA+B,CAAC;AAC9D;AAAA,MACF;AACA,UAAI,KAAK;AAAA,QACP,GAAG;AAAA,QACH,eAAe,SAAS,QAAQ,OAAO;AAAA,MACzC,CAAC;AAAA,IACH,CAAC;AAED,SAAK,IAAI,KAAK,qBAAqB,CAAC,KAAc,QAAkB;AAClE,YAAM,KAAK,IAAI,OAAO;AACtB,YAAM,EAAE,SAAS,QAAQ,MAAM,IAAI,IAAI;AAEvC,UAAI,SAAS;AACX,cAAM,WAAW,KAAK,aAAa,QAAQ,IAAI,MAAM;AACrD,YAAI,CAAC,UAAU;AACb,cAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,+BAA+B,CAAC;AAC9D;AAAA,QACF;AACA,YAAI,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,MACvB,OAAO;AACL,cAAM,WAAW,KAAK,aAAa,OAAO,IAAI,SAAS,eAAe;AACtE,YAAI,CAAC,UAAU;AACb,cAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,+BAA+B,CAAC;AAC9D;AAAA,QACF;AACA,YAAI,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,MACvB;AAAA,IACF,CAAC;AAED,SAAK,IAAI,IAAI,gBAAgB,CAAC,MAAe,QAAkB;AAC7D,UAAI,KAAK,EAAE,WAAW,KAAK,UAAU,CAAC;AAAA,IACxC,CAAC;AAED,SAAK,IAAI,KAAK,kBAAkB,CAAC,KAAc,QAAkB;AAC/D,YAAM,gBAAgB,IAAI,QAAQ,IAAI,KAAK;AAC3C,UAAI,iBAAiB,kBAAkB,KAAK,WAAW;AACrD,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,WAAW,KAAK,UAAU,CAAC;AAC5E;AAAA,MACF;AACA,sBAAgB;AAChB,UAAI,KAAK,EAAE,IAAI,MAAM,WAAW,KAAK,UAAU,CAAC;AAAA,IAClD,CAAC;AAED,SAAK,IAAI,IAAI,eAAe,CAAC,MAAe,QAAkB;AAC5D,UAAI,KAAK,EAAE,QAAQ,KAAK,CAAC;AAAA,IAC3B,CAAC;AAED,SAAK,IAAI,IAAI,cAAc,CAAC,MAAe,QAAkB;AAC3D,UAAI,KAAK,EAAE,cAAc,KAAK,aAAa,KAAK,EAAE,CAAC;AAAA,IACrD,CAAC;AAAA,EACH;AAAA,EAEA,UAAkB;AAChB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,MAAM,MAA6B;AACvC,SAAK,OAAO;AACZ,WAAO,KAAK,WAAW,IAAI;AAAA,EAC7B;AAAA,EAEQ,WAAW,MAA6B;AAC9C,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI;AACF,aAAK,SAAS,KAAK,IAAI,OAAO,MAAM,aAAa,MAAM;AACrD,eAAK,OAAO;AACZ,kBAAQ,MAAM,8CAA8C,IAAI,EAAE;AAClE,kBAAQ;AAAA,QACV,CAAC;AACD,aAAK,OAAO,GAAG,SAAS,CAAC,QAA+B;AACtD,cAAI,IAAI,SAAS,cAAc;AAC7B,kBAAM,WAAW,OAAO;AACxB,oBAAQ,MAAM,qBAAqB,IAAI,sBAAsB,QAAQ,KAAK;AAC1E,iBAAK,WAAW,QAAQ,EAAE,KAAK,SAAS,MAAM;AAAA,UAChD,OAAO;AACL,mBAAO,GAAG;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAsB;AAC1B,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,MAAM,MAAM,QAAQ,CAAC;AAAA,MACnC,OAAO;AACL,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AExJA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;;;ACAA;;;ACAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ATkBO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EAER,cAAc;AACZ,SAAK,SAAS,WAAW;AACzB,SAAK,eAAe,IAAI,aAAa;AAErC,UAAM,gBAAgB,SAAS,KAAK,OAAO,OAAO;AAClD,UAAM,iBAA0C;AAAA,MAC9C,UAAU,cAAc;AAAA,IAC1B;AACA,QAAI,KAAK,OAAO,QAAQ;AACtB,qBAAe,UAAU,EAAE,oBAAoB,KAAK,OAAO,OAAO;AAAA,IACpE;AAEA,SAAK,UAAU,IAAI,QAAQ,cAAc;AAEzC,SAAK,aAAa,IAAI,WAAW,KAAK,cAAc,aAAuB;AAAA,MACzE,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,cAAc;AAAA,MACd,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK,WAAW,MAAM,KAAK,OAAO,QAAQ;AAChD,YAAQ,MAAM,2CAA2C,KAAK,WAAW,QAAQ,CAAC,EAAE;AAGpF,UAAM,UAAU,MAAM;AACpB,WAAK,WAAW,KAAK,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACvC;AACA,YAAQ,GAAG,UAAU,OAAO;AAC5B,YAAQ,GAAG,WAAW,OAAO;AAC7B,YAAQ,GAAG,cAAc,OAAO;AAAA,EAClC;AAAA,EAEQ,UAAkB;AACxB,WAAO,KAAK,WAAW,QAAQ;AAAA,EACjC;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,aAAa,MAAM;AACxB,UAAM,KAAK,WAAW,KAAK;AAAA,EAC7B;AAAA,EAEA,YAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAGQ,eAAe,SAAoC;AACzD,WAAO,WAAW,KAAK,OAAO;AAAA,EAChC;AAAA,EAEA,MAAM,cAAc,SAAsE;AACxF,UAAM,MAAM,KAAK,eAAe,OAAO;AACvC,UAAM,EAAE,IAAI,QAAQ,IAAI,KAAK,aAAa,OAAO,WAAW,CAAC,GAAG,GAAG;AACnE,UAAM,iBAAiB,KAAK,QAAQ,GAAG,EAAE;AACzC,UAAM,SAAU,MAAM;AACtB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ,IAAY,QAAgB,SAAkD;AAC1F,UAAM,MAAM,KAAK,eAAe,OAAO;AACvC,UAAM,OAAoB,EAAE,IAAI,OAAO;AACvC,UAAM,EAAE,IAAI,QAAQ,IAAI,KAAK,aAAa,OAAO,YAAY,MAAM,GAAG;AACtE,UAAM,iBAAiB,KAAK,QAAQ,GAAG,EAAE;AACzC,UAAM,SAAU,MAAM;AACtB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UACJ,iBACA,IACA,QACA,UACA,SAC2B;AAC3B,UAAM,MAAM,KAAK,eAAe,OAAO;AACvC,UAAM,OAAsB;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,YAAY;AAAA,IACxB;AACA,UAAM,EAAE,IAAI,QAAQ,IAAI,KAAK,aAAa,OAAO,cAAc,MAAM,GAAG;AACxE,UAAM,iBAAiB,KAAK,QAAQ,GAAG,EAAE;AACzC,UAAM,SAAU,MAAM;AACtB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,SAAiB,SAAuD;AACxF,UAAM,MAAM,KAAK,eAAe,OAAO;AACvC,UAAM,OAAwB,EAAE,QAAQ;AACxC,UAAM,EAAE,IAAI,QAAQ,IAAI,KAAK,aAAa,OAAO,gBAAgB,MAAM,GAAG;AAC1E,UAAM,iBAAiB,KAAK,QAAQ,GAAG,EAAE;AACzC,UAAM,SAAU,MAAM;AACtB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,cACJ,WACA,SACgC;AAChC,UAAM,MAAM,KAAK,eAAe,OAAO;AACvC,UAAM,OAA0B,EAAE,UAAU;AAC5C,UAAM,EAAE,IAAI,QAAQ,IAAI,KAAK,aAAa,OAAO,mBAAmB,MAAM,GAAG;AAC7E,UAAM,iBAAiB,KAAK,QAAQ,GAAG,EAAE;AACzC,UAAM,SAAU,MAAM;AACtB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,gBACJ,aACA,SACA,WACwE;AACxE,UAAM,MAAM,KAAK,eAAe,OAAO;AACvC,UAAM,OAA4B,EAAE,aAAa,WAAW,aAAa,MAAM;AAC/E,UAAM,EAAE,IAAI,QAAQ,IAAI,KAAK,aAAa,OAAO,oBAAoB,MAAM,GAAG;AAC9E,UAAM,iBAAiB,KAAK,QAAQ,GAAG,EAAE;AACzC,UAAM,SAAU,MAAM;AACtB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,SAAiB,SAAyE;AACzG,UAAM,MAAM,KAAK,eAAe,OAAO;AACvC,UAAM,gBAAgB,SAAS,GAAG;AAClC,UAAM,iBAA0C,EAAE,UAAU,cAAc,SAAS;AACnF,QAAI,KAAK,OAAO,QAAQ;AACtB,qBAAe,UAAU,EAAE,oBAAoB,KAAK,OAAO,OAAO;AAAA,IACpE;AAEA,UAAM,KAAK,QAAQ,KAAK,OAAO,UAAU,KAAK,UAAU,KAAK,MAAM,OAAO,SAAS,GAAG,QAAQ,cAAc;AAC5G,UAAM,aAAa,MAAM,GAAG,IAAI,WAAW,OAAO;AAClD,UAAM,UAAU,GAAG,QAAQ,UAAU,EAAE,SAAS;AAChD,WAAO,EAAE,SAAS,WAAW;AAAA,EAC/B;AACF;","names":["randomUUID","randomUUID"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tronlink-signer",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "TronLink browser wallet signer SDK for TRON transactions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,21 +11,29 @@
11
11
  "types": "./dist/index.d.ts"
12
12
  }
13
13
  },
14
- "scripts": {
15
- "build": "tsup",
16
- "typecheck": "tsc --noEmit"
17
- },
18
14
  "keywords": [
15
+ "tronlink-signer",
19
16
  "tron",
20
17
  "tronlink",
18
+ "tronweb",
21
19
  "wallet",
22
20
  "signer",
21
+ "blockchain",
23
22
  "tip-6963"
24
23
  ],
25
24
  "files": [
26
25
  "dist"
27
26
  ],
28
27
  "license": "MIT",
28
+ "homepage": "https://github.com/TronLink/mcp-tronlink-signer/tree/main/packages/tronlink-signer#readme",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/TronLink/mcp-tronlink-signer.git",
32
+ "directory": "packages/tronlink-signer"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/TronLink/mcp-tronlink-signer/issues"
36
+ },
29
37
  "engines": {
30
38
  "node": ">=18"
31
39
  },
@@ -39,5 +47,9 @@
39
47
  "@types/node": "^25.5.0",
40
48
  "tsup": "^8.5.1",
41
49
  "typescript": "^5.7.3"
50
+ },
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "typecheck": "tsc --noEmit"
42
54
  }
43
- }
55
+ }