tronlink-signer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +80 -0
- package/dist/index.js +751 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
type TronNetwork = "mainnet" | "nile" | "shasta";
|
|
2
|
+
interface NetworkConfig {
|
|
3
|
+
name: string;
|
|
4
|
+
fullHost: string;
|
|
5
|
+
explorerUrl: string;
|
|
6
|
+
}
|
|
7
|
+
type PendingRequestType = "connect" | "send_trx" | "send_trc20" | "sign_message" | "sign_typed_data" | "sign_transaction";
|
|
8
|
+
interface PendingRequest<T = unknown> {
|
|
9
|
+
id: string;
|
|
10
|
+
type: PendingRequestType;
|
|
11
|
+
data: T;
|
|
12
|
+
network: TronNetwork;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
}
|
|
15
|
+
interface SendTrxData {
|
|
16
|
+
to: string;
|
|
17
|
+
amount: number;
|
|
18
|
+
}
|
|
19
|
+
interface SendTrc20Data {
|
|
20
|
+
contractAddress: string;
|
|
21
|
+
to: string;
|
|
22
|
+
amount: string;
|
|
23
|
+
decimals: number;
|
|
24
|
+
}
|
|
25
|
+
interface SignMessageData {
|
|
26
|
+
message: string;
|
|
27
|
+
}
|
|
28
|
+
interface SignTypedDataData {
|
|
29
|
+
typedData: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
interface SignTransactionData {
|
|
32
|
+
transaction: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
interface AppConfig {
|
|
35
|
+
network: TronNetwork;
|
|
36
|
+
httpPort: number;
|
|
37
|
+
apiKey?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare class TronSigner {
|
|
41
|
+
private config;
|
|
42
|
+
private pendingStore;
|
|
43
|
+
private httpServer;
|
|
44
|
+
private tronWeb;
|
|
45
|
+
constructor();
|
|
46
|
+
start(): Promise<void>;
|
|
47
|
+
private getPort;
|
|
48
|
+
stop(): Promise<void>;
|
|
49
|
+
getConfig(): AppConfig;
|
|
50
|
+
private resolveNetwork;
|
|
51
|
+
connectWallet(network?: TronNetwork): Promise<{
|
|
52
|
+
address: string;
|
|
53
|
+
}>;
|
|
54
|
+
sendTrx(to: string, amount: number, network?: TronNetwork): Promise<{
|
|
55
|
+
txId: string;
|
|
56
|
+
}>;
|
|
57
|
+
sendTrc20(contractAddress: string, to: string, amount: string, decimals?: number, network?: TronNetwork): Promise<{
|
|
58
|
+
txId: string;
|
|
59
|
+
}>;
|
|
60
|
+
signMessage(message: string, network?: TronNetwork): Promise<{
|
|
61
|
+
signature: string;
|
|
62
|
+
}>;
|
|
63
|
+
signTypedData(typedData: Record<string, unknown>, network?: TronNetwork): Promise<{
|
|
64
|
+
signature: string;
|
|
65
|
+
}>;
|
|
66
|
+
signTransaction(transaction: Record<string, unknown>, network?: TronNetwork): Promise<{
|
|
67
|
+
signedTransaction: Record<string, unknown>;
|
|
68
|
+
}>;
|
|
69
|
+
getBalance(address: string, network?: TronNetwork): Promise<{
|
|
70
|
+
balance: string;
|
|
71
|
+
balanceSun: number;
|
|
72
|
+
}>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
declare const NETWORKS: Record<TronNetwork, NetworkConfig>;
|
|
76
|
+
declare const DEFAULT_HTTP_PORT = 3386;
|
|
77
|
+
declare const REQUEST_TIMEOUT_MS: number;
|
|
78
|
+
declare function loadConfig(): AppConfig;
|
|
79
|
+
|
|
80
|
+
export { type AppConfig, DEFAULT_HTTP_PORT, NETWORKS, type NetworkConfig, type PendingRequest, type PendingRequestType, REQUEST_TIMEOUT_MS, type SendTrc20Data, type SendTrxData, type SignMessageData, type SignTransactionData, type SignTypedDataData, type TronNetwork, TronSigner, loadConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
// src/tron-signer.ts
|
|
2
|
+
import { TronWeb } from "tronweb";
|
|
3
|
+
|
|
4
|
+
// src/pending-store.ts
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
|
|
7
|
+
// src/config.ts
|
|
8
|
+
var NETWORKS = {
|
|
9
|
+
mainnet: {
|
|
10
|
+
name: "Mainnet",
|
|
11
|
+
fullHost: "https://api.trongrid.io",
|
|
12
|
+
explorerUrl: "https://tronscan.org"
|
|
13
|
+
},
|
|
14
|
+
nile: {
|
|
15
|
+
name: "Nile Testnet",
|
|
16
|
+
fullHost: "https://nile.trongrid.io",
|
|
17
|
+
explorerUrl: "https://nile.tronscan.org"
|
|
18
|
+
},
|
|
19
|
+
shasta: {
|
|
20
|
+
name: "Shasta Testnet",
|
|
21
|
+
fullHost: "https://api.shasta.trongrid.io",
|
|
22
|
+
explorerUrl: "https://shasta.tronscan.org"
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var DEFAULT_HTTP_PORT = 3386;
|
|
26
|
+
var REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
27
|
+
function loadConfig() {
|
|
28
|
+
const network = process.env.TRON_NETWORK || "mainnet";
|
|
29
|
+
if (!NETWORKS[network]) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Invalid TRON_NETWORK: ${network}. Must be one of: ${Object.keys(NETWORKS).join(", ")}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const httpPort = process.env.TRON_HTTP_PORT ? parseInt(process.env.TRON_HTTP_PORT, 10) : DEFAULT_HTTP_PORT;
|
|
35
|
+
return {
|
|
36
|
+
network,
|
|
37
|
+
httpPort,
|
|
38
|
+
apiKey: process.env.TRON_API_KEY
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/pending-store.ts
|
|
43
|
+
var PendingStore = class {
|
|
44
|
+
pending = /* @__PURE__ */ new Map();
|
|
45
|
+
create(type, data, network) {
|
|
46
|
+
const id = randomUUID();
|
|
47
|
+
const request = {
|
|
48
|
+
id,
|
|
49
|
+
type,
|
|
50
|
+
data,
|
|
51
|
+
network,
|
|
52
|
+
createdAt: Date.now()
|
|
53
|
+
};
|
|
54
|
+
let resolve;
|
|
55
|
+
let reject;
|
|
56
|
+
const promise = new Promise((res, rej) => {
|
|
57
|
+
resolve = res;
|
|
58
|
+
reject = rej;
|
|
59
|
+
});
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
this.pending.delete(id);
|
|
62
|
+
reject(new Error("TIMEOUT: Request timed out after 5 minutes"));
|
|
63
|
+
}, REQUEST_TIMEOUT_MS);
|
|
64
|
+
this.pending.set(id, { request, resolve, reject, timer });
|
|
65
|
+
console.error(`[PendingStore] Created request: ${id}, type: ${type}, total pending: ${this.pending.size}`);
|
|
66
|
+
return { id, promise };
|
|
67
|
+
}
|
|
68
|
+
get(id) {
|
|
69
|
+
const entry = this.pending.get(id);
|
|
70
|
+
console.error(`[PendingStore] Get request: ${id}, found: ${!!entry}, total pending: ${this.pending.size}`);
|
|
71
|
+
return entry?.request;
|
|
72
|
+
}
|
|
73
|
+
resolve(id, result) {
|
|
74
|
+
const entry = this.pending.get(id);
|
|
75
|
+
if (!entry) return false;
|
|
76
|
+
clearTimeout(entry.timer);
|
|
77
|
+
this.pending.delete(id);
|
|
78
|
+
entry.resolve(result);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
reject(id, error) {
|
|
82
|
+
const entry = this.pending.get(id);
|
|
83
|
+
if (!entry) return false;
|
|
84
|
+
clearTimeout(entry.timer);
|
|
85
|
+
this.pending.delete(id);
|
|
86
|
+
entry.reject(new Error(error));
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
size() {
|
|
90
|
+
return this.pending.size;
|
|
91
|
+
}
|
|
92
|
+
clear() {
|
|
93
|
+
for (const [id, entry] of this.pending) {
|
|
94
|
+
clearTimeout(entry.timer);
|
|
95
|
+
entry.reject(new Error("Store cleared"));
|
|
96
|
+
}
|
|
97
|
+
this.pending.clear();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/http-server.ts
|
|
102
|
+
import express from "express";
|
|
103
|
+
var HttpServer = class {
|
|
104
|
+
app;
|
|
105
|
+
server = null;
|
|
106
|
+
pendingStore;
|
|
107
|
+
htmlContent;
|
|
108
|
+
port = 0;
|
|
109
|
+
constructor(pendingStore, htmlContent) {
|
|
110
|
+
this.pendingStore = pendingStore;
|
|
111
|
+
this.htmlContent = htmlContent;
|
|
112
|
+
this.app = express();
|
|
113
|
+
this.setupRoutes();
|
|
114
|
+
}
|
|
115
|
+
setupRoutes() {
|
|
116
|
+
this.app.use(express.json());
|
|
117
|
+
this.app.get("/", (_req, res) => {
|
|
118
|
+
res.setHeader("Content-Type", "text/html");
|
|
119
|
+
res.send(this.htmlContent);
|
|
120
|
+
});
|
|
121
|
+
this.app.get("/api/pending/:id", (req, res) => {
|
|
122
|
+
const request = this.pendingStore.get(req.params.id);
|
|
123
|
+
if (!request) {
|
|
124
|
+
res.status(404).json({ error: "Request not found or expired" });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
res.json({
|
|
128
|
+
...request,
|
|
129
|
+
networkConfig: NETWORKS[request.network]
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
this.app.post("/api/complete/:id", (req, res) => {
|
|
133
|
+
const id = req.params.id;
|
|
134
|
+
const { success, result, error } = req.body;
|
|
135
|
+
if (success) {
|
|
136
|
+
const resolved = this.pendingStore.resolve(id, result);
|
|
137
|
+
if (!resolved) {
|
|
138
|
+
res.status(404).json({ error: "Request not found or expired" });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
res.json({ ok: true });
|
|
142
|
+
} else {
|
|
143
|
+
const rejected = this.pendingStore.reject(id, error || "USER_REJECTED");
|
|
144
|
+
if (!rejected) {
|
|
145
|
+
res.status(404).json({ error: "Request not found or expired" });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
res.json({ ok: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
this.app.get("/api/health", (_req, res) => {
|
|
152
|
+
res.json({ status: "ok" });
|
|
153
|
+
});
|
|
154
|
+
this.app.get("/api/debug", (_req, res) => {
|
|
155
|
+
res.json({ pendingCount: this.pendingStore.size() });
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
getPort() {
|
|
159
|
+
return this.port;
|
|
160
|
+
}
|
|
161
|
+
async start(port) {
|
|
162
|
+
this.port = port;
|
|
163
|
+
return this._tryListen(port);
|
|
164
|
+
}
|
|
165
|
+
_tryListen(port) {
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
try {
|
|
168
|
+
this.server = this.app.listen(port, "127.0.0.1", () => {
|
|
169
|
+
this.port = port;
|
|
170
|
+
console.error(`[HttpServer] Listening on http://127.0.0.1:${port}`);
|
|
171
|
+
resolve();
|
|
172
|
+
});
|
|
173
|
+
this.server.on("error", (err) => {
|
|
174
|
+
if (err.code === "EADDRINUSE") {
|
|
175
|
+
const nextPort = port + 1;
|
|
176
|
+
console.error(`[HttpServer] Port ${port} is in use, trying ${nextPort}...`);
|
|
177
|
+
this._tryListen(nextPort).then(resolve, reject);
|
|
178
|
+
} else {
|
|
179
|
+
reject(err);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
} catch (err) {
|
|
183
|
+
reject(err);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async stop() {
|
|
188
|
+
return new Promise((resolve) => {
|
|
189
|
+
if (this.server) {
|
|
190
|
+
this.server.close(() => resolve());
|
|
191
|
+
} else {
|
|
192
|
+
resolve();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
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
|
+
// src/web/index.html
|
|
206
|
+
var web_default = `<!DOCTYPE html>
|
|
207
|
+
<html lang="en">
|
|
208
|
+
<head>
|
|
209
|
+
<meta charset="UTF-8">
|
|
210
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
211
|
+
<title>TronLink Signer</title>
|
|
212
|
+
<style>
|
|
213
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
214
|
+
body {
|
|
215
|
+
background: #0f0f0f;
|
|
216
|
+
color: #e0e0e0;
|
|
217
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
218
|
+
display: flex;
|
|
219
|
+
justify-content: center;
|
|
220
|
+
align-items: center;
|
|
221
|
+
min-height: 100vh;
|
|
222
|
+
padding: 20px;
|
|
223
|
+
}
|
|
224
|
+
.container {
|
|
225
|
+
background: #1a1a2e;
|
|
226
|
+
border-radius: 16px;
|
|
227
|
+
padding: 32px;
|
|
228
|
+
max-width: 480px;
|
|
229
|
+
width: 100%;
|
|
230
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
|
231
|
+
}
|
|
232
|
+
h1 {
|
|
233
|
+
font-size: 20px;
|
|
234
|
+
margin-bottom: 8px;
|
|
235
|
+
color: #fff;
|
|
236
|
+
}
|
|
237
|
+
.type-badge {
|
|
238
|
+
display: inline-block;
|
|
239
|
+
background: #2a2a4e;
|
|
240
|
+
color: #8888cc;
|
|
241
|
+
padding: 4px 12px;
|
|
242
|
+
border-radius: 6px;
|
|
243
|
+
font-size: 13px;
|
|
244
|
+
font-weight: 600;
|
|
245
|
+
margin-bottom: 20px;
|
|
246
|
+
}
|
|
247
|
+
.detail-row {
|
|
248
|
+
display: flex;
|
|
249
|
+
justify-content: space-between;
|
|
250
|
+
padding: 10px 0;
|
|
251
|
+
border-bottom: 1px solid #2a2a3e;
|
|
252
|
+
font-size: 14px;
|
|
253
|
+
}
|
|
254
|
+
.detail-row .label { color: #888; }
|
|
255
|
+
.detail-row .value {
|
|
256
|
+
color: #e0e0e0;
|
|
257
|
+
word-break: break-all;
|
|
258
|
+
text-align: right;
|
|
259
|
+
max-width: 60%;
|
|
260
|
+
}
|
|
261
|
+
.status {
|
|
262
|
+
margin-top: 20px;
|
|
263
|
+
padding: 12px 16px;
|
|
264
|
+
border-radius: 8px;
|
|
265
|
+
font-size: 13px;
|
|
266
|
+
line-height: 1.5;
|
|
267
|
+
}
|
|
268
|
+
.status.info { background: #1a2a4e; color: #6ea8fe; }
|
|
269
|
+
.status.success { background: #1a3a2a; color: #75d9a0; }
|
|
270
|
+
.status.error { background: #3a1a1a; color: #f87171; }
|
|
271
|
+
.status.waiting { background: #3a3a1a; color: #fbbf24; }
|
|
272
|
+
.buttons {
|
|
273
|
+
display: flex;
|
|
274
|
+
gap: 12px;
|
|
275
|
+
margin-top: 24px;
|
|
276
|
+
}
|
|
277
|
+
.buttons button {
|
|
278
|
+
flex: 1;
|
|
279
|
+
padding: 14px;
|
|
280
|
+
border: none;
|
|
281
|
+
border-radius: 10px;
|
|
282
|
+
font-size: 15px;
|
|
283
|
+
font-weight: 600;
|
|
284
|
+
cursor: pointer;
|
|
285
|
+
transition: opacity 0.2s;
|
|
286
|
+
}
|
|
287
|
+
.buttons button:hover { opacity: 0.85; }
|
|
288
|
+
.buttons button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
289
|
+
.btn-approve { background: #c23631; color: #fff; }
|
|
290
|
+
.btn-reject { background: #2a2a3e; color: #aaa; }
|
|
291
|
+
#details { margin-top: 16px; }
|
|
292
|
+
</style>
|
|
293
|
+
</head>
|
|
294
|
+
<body>
|
|
295
|
+
<div class="container">
|
|
296
|
+
<h1>TronLink Signer</h1>
|
|
297
|
+
<div style="display:flex;gap:8px;margin-bottom:16px;">
|
|
298
|
+
<div id="typeBadge" class="type-badge" style="display:none"></div>
|
|
299
|
+
<div id="networkBadge" class="type-badge" style="display:none"></div>
|
|
300
|
+
</div>
|
|
301
|
+
<div id="details"></div>
|
|
302
|
+
<div id="status" class="status info">Loading request...</div>
|
|
303
|
+
<div id="retryGroup" class="buttons" style="display:none">
|
|
304
|
+
<button class="btn-approve" id="retryBtn">Retry</button>
|
|
305
|
+
</div>
|
|
306
|
+
<div id="buttonGroup" class="buttons" style="display:none">
|
|
307
|
+
<button class="btn-approve" id="approveBtn">Approve</button>
|
|
308
|
+
<button class="btn-reject" id="rejectBtn">Reject</button>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
<script>
|
|
312
|
+
(function() {
|
|
313
|
+
const params = new URLSearchParams(window.location.search);
|
|
314
|
+
const requestId = params.get('requestId');
|
|
315
|
+
const statusEl = document.getElementById('status');
|
|
316
|
+
const detailsEl = document.getElementById('details');
|
|
317
|
+
const typeBadgeEl = document.getElementById('typeBadge');
|
|
318
|
+
const networkBadgeEl = document.getElementById('networkBadge');
|
|
319
|
+
const buttonGroup = document.getElementById('buttonGroup');
|
|
320
|
+
const retryGroup = document.getElementById('retryGroup');
|
|
321
|
+
const retryBtn = document.getElementById('retryBtn');
|
|
322
|
+
const approveBtn = document.getElementById('approveBtn');
|
|
323
|
+
const rejectBtn = document.getElementById('rejectBtn');
|
|
324
|
+
|
|
325
|
+
let pendingRequest = null;
|
|
326
|
+
let _providerDetail = null; // TIP-6963 discovered provider
|
|
327
|
+
|
|
328
|
+
function setStatus(msg, type) {
|
|
329
|
+
statusEl.textContent = msg;
|
|
330
|
+
statusEl.className = 'status ' + type;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function addDetail(label, value) {
|
|
334
|
+
const row = document.createElement('div');
|
|
335
|
+
row.className = 'detail-row';
|
|
336
|
+
row.innerHTML = '<span class="label">' + label + '</span><span class="value">' + escapeHtml(String(value)) + '</span>';
|
|
337
|
+
detailsEl.appendChild(row);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function escapeHtml(s) {
|
|
341
|
+
const d = document.createElement('div');
|
|
342
|
+
d.textContent = s;
|
|
343
|
+
return d.innerHTML;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
var NETWORK_NAMES = {
|
|
347
|
+
mainnet: 'Mainnet',
|
|
348
|
+
nile: 'Nile Testnet',
|
|
349
|
+
shasta: 'Shasta Testnet'
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
function renderDetails(req) {
|
|
353
|
+
typeBadgeEl.textContent = req.type;
|
|
354
|
+
typeBadgeEl.style.display = 'inline-block';
|
|
355
|
+
if (req.network) {
|
|
356
|
+
networkBadgeEl.textContent = NETWORK_NAMES[req.network] || req.network;
|
|
357
|
+
networkBadgeEl.style.display = 'inline-block';
|
|
358
|
+
}
|
|
359
|
+
detailsEl.innerHTML = '';
|
|
360
|
+
|
|
361
|
+
var data = req.data || {};
|
|
362
|
+
switch (req.type) {
|
|
363
|
+
case 'connect':
|
|
364
|
+
addDetail('Action', 'Connect Wallet');
|
|
365
|
+
break;
|
|
366
|
+
case 'send_trx':
|
|
367
|
+
addDetail('To', data.to);
|
|
368
|
+
addDetail('Amount', data.amount + ' TRX');
|
|
369
|
+
break;
|
|
370
|
+
case 'send_trc20':
|
|
371
|
+
addDetail('Contract', data.contractAddress);
|
|
372
|
+
addDetail('To', data.to);
|
|
373
|
+
addDetail('Amount', data.amount);
|
|
374
|
+
addDetail('Decimals', data.decimals);
|
|
375
|
+
break;
|
|
376
|
+
case 'sign_message':
|
|
377
|
+
addDetail('Message', data.message);
|
|
378
|
+
break;
|
|
379
|
+
case 'sign_typed_data':
|
|
380
|
+
addDetail('Typed Data', JSON.stringify(data.typedData, null, 2));
|
|
381
|
+
break;
|
|
382
|
+
case 'sign_transaction':
|
|
383
|
+
addDetail('Transaction', JSON.stringify(data.transaction, null, 2));
|
|
384
|
+
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
|
+
}
|
|
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
|
+
}
|
|
404
|
+
return window.tron || window.tronLink || null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Get tronWeb from provider or fallback
|
|
408
|
+
function getTronWeb() {
|
|
409
|
+
var provider = getProvider();
|
|
410
|
+
if (provider && provider.tronWeb) {
|
|
411
|
+
return provider.tronWeb;
|
|
412
|
+
}
|
|
413
|
+
return window.tronWeb || null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function waitForWallet(maxWait) {
|
|
417
|
+
maxWait = maxWait || 5000;
|
|
418
|
+
// Start TIP-6963 discovery
|
|
419
|
+
discoverWallets();
|
|
420
|
+
|
|
421
|
+
return new Promise(function(resolve, reject) {
|
|
422
|
+
if (getProvider()) { resolve(true); return; }
|
|
423
|
+
|
|
424
|
+
var elapsed = 0;
|
|
425
|
+
var interval = setInterval(function() {
|
|
426
|
+
elapsed += 200;
|
|
427
|
+
if (getProvider()) {
|
|
428
|
+
clearInterval(interval);
|
|
429
|
+
resolve(true);
|
|
430
|
+
} else if (elapsed >= maxWait) {
|
|
431
|
+
clearInterval(interval);
|
|
432
|
+
reject(new Error('No TRON wallet found. Please install TronLink extension.'));
|
|
433
|
+
}
|
|
434
|
+
}, 200);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// --- Wallet Connection & Network ---
|
|
439
|
+
|
|
440
|
+
async function ensureConnected() {
|
|
441
|
+
var tronWeb = getTronWeb();
|
|
442
|
+
if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {
|
|
443
|
+
await getProvider().request({ method: 'tron_requestAccounts' });
|
|
444
|
+
await new Promise(function(r) { setTimeout(r, 500); });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
var NETWORK_CHAIN_IDS = {
|
|
449
|
+
mainnet: '0x2b6653dc',
|
|
450
|
+
nile: '0xcd8690dc',
|
|
451
|
+
shasta: '0x94a9059e'
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
var NETWORK_FULL_HOSTS = {
|
|
455
|
+
mainnet: 'https://api.trongrid.io',
|
|
456
|
+
nile: 'https://nile.trongrid.io',
|
|
457
|
+
shasta: 'https://api.shasta.trongrid.io'
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
async function ensureWalletReady(expectedNetwork) {
|
|
461
|
+
var provider = getProvider();
|
|
462
|
+
|
|
463
|
+
// Step 1: Unlock wallet if locked
|
|
464
|
+
setStatus('Connecting wallet...', 'waiting');
|
|
465
|
+
var accountRes = await provider.request({ method: 'eth_requestAccounts' });
|
|
466
|
+
if (accountRes && accountRes.code === 4001) {
|
|
467
|
+
throw new Error('User rejected wallet connection.');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Step 2: Check if wallet is ready
|
|
471
|
+
var tronWeb = getTronWeb();
|
|
472
|
+
if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {
|
|
473
|
+
await new Promise(function(r) { setTimeout(r, 1000); });
|
|
474
|
+
tronWeb = getTronWeb();
|
|
475
|
+
if (!tronWeb || !tronWeb.defaultAddress || !tronWeb.defaultAddress.base58) {
|
|
476
|
+
throw new Error('Wallet not ready. Please unlock TronLink and refresh.');
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Step 3: Check network and switch if needed
|
|
481
|
+
if (expectedNetwork) {
|
|
482
|
+
var currentHost = tronWeb.fullNode.host;
|
|
483
|
+
var expectedHost = NETWORK_FULL_HOSTS[expectedNetwork];
|
|
484
|
+
if (expectedHost && currentHost !== expectedHost) {
|
|
485
|
+
setStatus('Switching to ' + expectedNetwork + ' network...', 'waiting');
|
|
486
|
+
var chainId = NETWORK_CHAIN_IDS[expectedNetwork];
|
|
487
|
+
if (chainId) {
|
|
488
|
+
try {
|
|
489
|
+
await provider.request({
|
|
490
|
+
method: 'wallet_switchEthereumChain',
|
|
491
|
+
params: [{ chainId: chainId }]
|
|
492
|
+
});
|
|
493
|
+
await new Promise(function(r) { setTimeout(r, 1500); });
|
|
494
|
+
} catch (switchErr) {
|
|
495
|
+
throw new Error('Please switch TronLink to ' + expectedNetwork + ' network manually then click Retry.');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// --- Execute Actions ---
|
|
503
|
+
|
|
504
|
+
async function executeTronAction(req) {
|
|
505
|
+
await ensureConnected();
|
|
506
|
+
var tronWeb = getTronWeb();
|
|
507
|
+
var data = req.data || {};
|
|
508
|
+
|
|
509
|
+
switch (req.type) {
|
|
510
|
+
case 'connect': {
|
|
511
|
+
return { address: tronWeb.defaultAddress.base58 };
|
|
512
|
+
}
|
|
513
|
+
case 'send_trx': {
|
|
514
|
+
var tx = await tronWeb.transactionBuilder.sendTrx(data.to, tronWeb.toSun(data.amount));
|
|
515
|
+
var signedTx = await tronWeb.trx.sign(tx);
|
|
516
|
+
var broadcast = await tronWeb.trx.sendRawTransaction(signedTx);
|
|
517
|
+
return { txId: broadcast.txid };
|
|
518
|
+
}
|
|
519
|
+
case 'send_trc20': {
|
|
520
|
+
var contract = await tronWeb.contract().at(data.contractAddress);
|
|
521
|
+
var decimals = data.decimals || 6;
|
|
522
|
+
var rawAmount = BigInt(data.amount) * BigInt(10 ** decimals);
|
|
523
|
+
var txId = await contract.methods.transfer(data.to, rawAmount.toString()).send();
|
|
524
|
+
return { txId: txId };
|
|
525
|
+
}
|
|
526
|
+
case 'sign_message': {
|
|
527
|
+
var signature = await tronWeb.trx.signMessageV2(data.message);
|
|
528
|
+
return { signature: signature };
|
|
529
|
+
}
|
|
530
|
+
case 'sign_typed_data': {
|
|
531
|
+
var typedData = data.typedData;
|
|
532
|
+
var domain = typedData.domain;
|
|
533
|
+
var types = Object.assign({}, typedData.types);
|
|
534
|
+
delete types.EIP712Domain;
|
|
535
|
+
var message = typedData.message;
|
|
536
|
+
var sig = await tronWeb.trx._signTypedData(domain, types, message);
|
|
537
|
+
return { signature: sig };
|
|
538
|
+
}
|
|
539
|
+
case 'sign_transaction': {
|
|
540
|
+
var signed = await tronWeb.trx.sign(data.transaction);
|
|
541
|
+
return { signedTransaction: signed };
|
|
542
|
+
}
|
|
543
|
+
default:
|
|
544
|
+
throw new Error('Unknown request type: ' + req.type);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// --- Request Handling ---
|
|
549
|
+
|
|
550
|
+
async function completeRequest(id, success, resultOrError) {
|
|
551
|
+
var body = success
|
|
552
|
+
? { success: true, result: resultOrError }
|
|
553
|
+
: { success: false, error: resultOrError };
|
|
554
|
+
await fetch('/api/complete/' + id, {
|
|
555
|
+
method: 'POST',
|
|
556
|
+
headers: { 'Content-Type': 'application/json' },
|
|
557
|
+
body: JSON.stringify(body),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function disableButtons() {
|
|
562
|
+
approveBtn.disabled = true;
|
|
563
|
+
rejectBtn.disabled = true;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function tryEnsureWallet() {
|
|
567
|
+
retryGroup.style.display = 'none';
|
|
568
|
+
buttonGroup.style.display = 'none';
|
|
569
|
+
try {
|
|
570
|
+
await ensureWalletReady(pendingRequest.network);
|
|
571
|
+
setStatus('Ready. Review and approve or reject.', 'info');
|
|
572
|
+
buttonGroup.style.display = 'flex';
|
|
573
|
+
} catch (e) {
|
|
574
|
+
setStatus(e.message, 'error');
|
|
575
|
+
retryGroup.style.display = 'flex';
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
retryBtn.addEventListener('click', function() {
|
|
580
|
+
tryEnsureWallet();
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
async function init() {
|
|
584
|
+
if (!requestId) {
|
|
585
|
+
setStatus('Missing request ID in URL.', 'error');
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
var res = await fetch('/api/pending/' + requestId);
|
|
591
|
+
if (!res.ok) {
|
|
592
|
+
setStatus('Request not found or expired.', 'error');
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
pendingRequest = await res.json();
|
|
596
|
+
renderDetails(pendingRequest);
|
|
597
|
+
setStatus('Discovering wallets...', 'waiting');
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
await waitForWallet(5000);
|
|
601
|
+
if (_providerDetail && _providerDetail.info) {
|
|
602
|
+
setStatus('Found wallet: ' + _providerDetail.info.name, 'info');
|
|
603
|
+
}
|
|
604
|
+
} catch (e) {
|
|
605
|
+
setStatus(e.message, 'error');
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
await tryEnsureWallet();
|
|
610
|
+
} catch (e) {
|
|
611
|
+
setStatus('Failed to load request: ' + e.message, 'error');
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
approveBtn.addEventListener('click', async function() {
|
|
616
|
+
disableButtons();
|
|
617
|
+
setStatus('Processing with wallet...', 'waiting');
|
|
618
|
+
try {
|
|
619
|
+
var result = await executeTronAction(pendingRequest);
|
|
620
|
+
await completeRequest(requestId, true, result);
|
|
621
|
+
setStatus('Approved and completed successfully.', 'success');
|
|
622
|
+
} catch (e) {
|
|
623
|
+
var msg = e.message || String(e);
|
|
624
|
+
await completeRequest(requestId, false, msg);
|
|
625
|
+
setStatus('Error: ' + msg, 'error');
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
rejectBtn.addEventListener('click', async function() {
|
|
630
|
+
disableButtons();
|
|
631
|
+
setStatus('Rejected.', 'error');
|
|
632
|
+
await completeRequest(requestId, false, 'USER_REJECTED');
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
init();
|
|
636
|
+
})();
|
|
637
|
+
</script>
|
|
638
|
+
</body>
|
|
639
|
+
</html>
|
|
640
|
+
`;
|
|
641
|
+
|
|
642
|
+
// src/tron-signer.ts
|
|
643
|
+
var TronSigner = class {
|
|
644
|
+
config;
|
|
645
|
+
pendingStore;
|
|
646
|
+
httpServer;
|
|
647
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
648
|
+
tronWeb;
|
|
649
|
+
constructor() {
|
|
650
|
+
this.config = loadConfig();
|
|
651
|
+
this.pendingStore = new PendingStore();
|
|
652
|
+
const networkConfig = NETWORKS[this.config.network];
|
|
653
|
+
const tronWebOptions = {
|
|
654
|
+
fullHost: networkConfig.fullHost
|
|
655
|
+
};
|
|
656
|
+
if (this.config.apiKey) {
|
|
657
|
+
tronWebOptions.headers = { "TRON-PRO-API-KEY": this.config.apiKey };
|
|
658
|
+
}
|
|
659
|
+
this.tronWeb = new TronWeb(tronWebOptions);
|
|
660
|
+
this.httpServer = new HttpServer(this.pendingStore, web_default);
|
|
661
|
+
}
|
|
662
|
+
async start() {
|
|
663
|
+
await this.httpServer.start(this.config.httpPort);
|
|
664
|
+
console.error(`HTTP server started on http://127.0.0.1:${this.httpServer.getPort()}`);
|
|
665
|
+
}
|
|
666
|
+
getPort() {
|
|
667
|
+
return this.httpServer.getPort();
|
|
668
|
+
}
|
|
669
|
+
async stop() {
|
|
670
|
+
this.pendingStore.clear();
|
|
671
|
+
await this.httpServer.stop();
|
|
672
|
+
}
|
|
673
|
+
getConfig() {
|
|
674
|
+
return this.config;
|
|
675
|
+
}
|
|
676
|
+
resolveNetwork(network) {
|
|
677
|
+
return network || this.config.network;
|
|
678
|
+
}
|
|
679
|
+
async connectWallet(network) {
|
|
680
|
+
const net = this.resolveNetwork(network);
|
|
681
|
+
const { id, promise } = this.pendingStore.create("connect", {}, net);
|
|
682
|
+
await openApprovalPage(this.getPort(), id);
|
|
683
|
+
const result = await promise;
|
|
684
|
+
return result;
|
|
685
|
+
}
|
|
686
|
+
async sendTrx(to, amount, network) {
|
|
687
|
+
const net = this.resolveNetwork(network);
|
|
688
|
+
const data = { to, amount };
|
|
689
|
+
const { id, promise } = this.pendingStore.create("send_trx", data, net);
|
|
690
|
+
await openApprovalPage(this.getPort(), id);
|
|
691
|
+
const result = await promise;
|
|
692
|
+
return result;
|
|
693
|
+
}
|
|
694
|
+
async sendTrc20(contractAddress, to, amount, decimals, network) {
|
|
695
|
+
const net = this.resolveNetwork(network);
|
|
696
|
+
const data = {
|
|
697
|
+
contractAddress,
|
|
698
|
+
to,
|
|
699
|
+
amount,
|
|
700
|
+
decimals: decimals ?? 6
|
|
701
|
+
};
|
|
702
|
+
const { id, promise } = this.pendingStore.create("send_trc20", data, net);
|
|
703
|
+
await openApprovalPage(this.getPort(), id);
|
|
704
|
+
const result = await promise;
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
707
|
+
async signMessage(message, network) {
|
|
708
|
+
const net = this.resolveNetwork(network);
|
|
709
|
+
const data = { message };
|
|
710
|
+
const { id, promise } = this.pendingStore.create("sign_message", data, net);
|
|
711
|
+
await openApprovalPage(this.getPort(), id);
|
|
712
|
+
const result = await promise;
|
|
713
|
+
return result;
|
|
714
|
+
}
|
|
715
|
+
async signTypedData(typedData, network) {
|
|
716
|
+
const net = this.resolveNetwork(network);
|
|
717
|
+
const data = { typedData };
|
|
718
|
+
const { id, promise } = this.pendingStore.create("sign_typed_data", data, net);
|
|
719
|
+
await openApprovalPage(this.getPort(), id);
|
|
720
|
+
const result = await promise;
|
|
721
|
+
return result;
|
|
722
|
+
}
|
|
723
|
+
async signTransaction(transaction, network) {
|
|
724
|
+
const net = this.resolveNetwork(network);
|
|
725
|
+
const data = { transaction };
|
|
726
|
+
const { id, promise } = this.pendingStore.create("sign_transaction", data, net);
|
|
727
|
+
await openApprovalPage(this.getPort(), id);
|
|
728
|
+
const result = await promise;
|
|
729
|
+
return result;
|
|
730
|
+
}
|
|
731
|
+
async getBalance(address, network) {
|
|
732
|
+
const net = this.resolveNetwork(network);
|
|
733
|
+
const networkConfig = NETWORKS[net];
|
|
734
|
+
const tronWebOptions = { fullHost: networkConfig.fullHost };
|
|
735
|
+
if (this.config.apiKey) {
|
|
736
|
+
tronWebOptions.headers = { "TRON-PRO-API-KEY": this.config.apiKey };
|
|
737
|
+
}
|
|
738
|
+
const tw = net === this.config.network ? this.tronWeb : new (await import("tronweb")).TronWeb(tronWebOptions);
|
|
739
|
+
const balanceSun = await tw.trx.getBalance(address);
|
|
740
|
+
const balance = tw.fromSun(balanceSun).toString();
|
|
741
|
+
return { balance, balanceSun };
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
export {
|
|
745
|
+
DEFAULT_HTTP_PORT,
|
|
746
|
+
NETWORKS,
|
|
747
|
+
REQUEST_TIMEOUT_MS,
|
|
748
|
+
TronSigner,
|
|
749
|
+
loadConfig
|
|
750
|
+
};
|
|
751
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tronlink-signer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TronLink browser wallet signer SDK for TRON transactions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"tron",
|
|
20
|
+
"tronlink",
|
|
21
|
+
"wallet",
|
|
22
|
+
"signer",
|
|
23
|
+
"tip-6963"
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"express": "^5.2.1",
|
|
34
|
+
"open": "^11.0.0",
|
|
35
|
+
"tronweb": "^6.2.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/express": "^5.0.6",
|
|
39
|
+
"@types/node": "^25.5.0",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"typescript": "^5.7.3"
|
|
42
|
+
}
|
|
43
|
+
}
|