tronlink-signer 0.1.1 → 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/README.md +27 -11
- package/dist/index.d.ts +4 -1
- package/dist/index.js +306 -237
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ import { TronSigner } from "tronlink-signer";
|
|
|
16
16
|
const signer = new TronSigner();
|
|
17
17
|
await signer.start();
|
|
18
18
|
|
|
19
|
-
const { address } = await signer.connectWallet();
|
|
19
|
+
const { address, network } = await signer.connectWallet();
|
|
20
20
|
const { txId } = await signer.sendTrx("TXxx...", 1);
|
|
21
21
|
const { balance } = await signer.getBalance("TXxx...");
|
|
22
22
|
|
|
@@ -41,9 +41,9 @@ Stops the server and clears all pending requests.
|
|
|
41
41
|
|
|
42
42
|
Returns the current configuration.
|
|
43
43
|
|
|
44
|
-
### `signer.connectWallet(network?: TronNetwork): Promise<{ address: string }>`
|
|
44
|
+
### `signer.connectWallet(network?: TronNetwork): Promise<{ address: string; network: string }>`
|
|
45
45
|
|
|
46
|
-
Opens the browser to connect TronLink and retrieve the wallet address.
|
|
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
47
|
|
|
48
48
|
### `signer.sendTrx(to, amount, network?): Promise<{ txId: string }>`
|
|
49
49
|
|
|
@@ -63,7 +63,7 @@ Sends TRC20 tokens. Opens a browser approval page.
|
|
|
63
63
|
| --------- | ---- | ----------- |
|
|
64
64
|
| `contractAddress` | `string` | TRC20 token contract address |
|
|
65
65
|
| `to` | `string` | Recipient Tron address (base58) |
|
|
66
|
-
| `amount` | `string` | Amount in
|
|
66
|
+
| `amount` | `string` | Amount in human-readable units (e.g., `"1.5"` for 1.5 USDT). Decimals conversion is automatic. |
|
|
67
67
|
| `decimals` | `number` | Token decimals (default: 6) |
|
|
68
68
|
| `network` | `TronNetwork` | Optional network override |
|
|
69
69
|
|
|
@@ -86,9 +86,23 @@ const { signature } = await signer.signTypedData({
|
|
|
86
86
|
});
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
### `signer.signTransaction(transaction, network?): Promise<{ signedTransaction: Record<string, unknown
|
|
89
|
+
### `signer.signTransaction(transaction, network?, broadcast?): Promise<{ signedTransaction: Record<string, unknown>; txId?: string }>`
|
|
90
90
|
|
|
91
|
-
Signs a raw transaction
|
|
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
|
+
```
|
|
92
106
|
|
|
93
107
|
### `signer.getBalance(address, network?): Promise<{ balance: string; balanceSun: number }>`
|
|
94
108
|
|
|
@@ -97,14 +111,16 @@ Gets TRX balance for an address. No browser approval needed.
|
|
|
97
111
|
## How It Works
|
|
98
112
|
|
|
99
113
|
1. Your code calls a signing method (e.g., `signMessage`)
|
|
100
|
-
2. A local HTTP server starts on port 3386 and
|
|
114
|
+
2. A local HTTP server starts on port 3386 and a **single browser tab** opens the approval page
|
|
101
115
|
3. The approval page discovers the wallet via **TIP-6963** protocol (fallback to `window.tron`)
|
|
102
116
|
4. Auto-unlocks wallet and switches network if needed
|
|
103
|
-
5.
|
|
104
|
-
6.
|
|
105
|
-
7. The
|
|
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
|
|
106
122
|
|
|
107
|
-
The local server binds to `127.0.0.1` only. If port 3386 is in use, it auto-increments. Requests timeout after 5 minutes.
|
|
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).
|
|
108
124
|
|
|
109
125
|
## Networks
|
|
110
126
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
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
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return d.innerHTML;
|
|
476
|
+
function disableButtons() {
|
|
477
|
+
approveBtn.disabled = true;
|
|
478
|
+
rejectBtn.disabled = true;
|
|
344
479
|
}
|
|
345
480
|
|
|
346
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
|
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
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
585
|
+
async function handleRequest(req) {
|
|
586
|
+
pendingRequest = req;
|
|
587
|
+
currentRequestId = req.id;
|
|
565
588
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
589
|
+
renderDetails(req);
|
|
590
|
+
|
|
591
|
+
// Wait for wallet extension to inject first
|
|
592
|
+
setStatus('Discovering wallets...', 'waiting');
|
|
569
593
|
try {
|
|
570
|
-
await
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
601
|
+
return;
|
|
576
602
|
}
|
|
603
|
+
|
|
604
|
+
await tryEnsureWallet();
|
|
577
605
|
}
|
|
578
606
|
|
|
579
|
-
|
|
580
|
-
tryEnsureWallet();
|
|
581
|
-
});
|
|
607
|
+
// --- Polling ---
|
|
582
608
|
|
|
583
|
-
async function
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
|
|
590
|
-
|
|
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
|
|
601
|
-
if (
|
|
602
|
-
|
|
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
|
-
|
|
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
|
|
620
|
-
await completeRequest(
|
|
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
|
-
|
|
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
|
-
|
|
632
|
-
|
|
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
|
-
|
|
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":"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AAA;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;AAA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","names":["randomUUID","randomUUID"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tronlink-signer",
|
|
3
|
-
"version": "0.1.
|
|
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",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
|
+
"tronlink-signer",
|
|
15
16
|
"tron",
|
|
16
17
|
"tronlink",
|
|
17
18
|
"tronweb",
|