@zebec-network/tron-transfer-sdk 1.0.0 → 2.0.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/README.md +290 -75
- package/dist/client.d.ts +6 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +25 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -46
- package/dist/config.js.map +1 -1
- package/dist/env.d.ts +8 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +50 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +25 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +33 -0
- package/dist/node.js.map +1 -0
- package/dist/services.d.ts +55 -14
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +272 -112
- package/dist/services.js.map +1 -1
- package/dist/signer.d.ts +25 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signer.js +26 -0
- package/dist/signer.js.map +1 -0
- package/dist/types.d.ts +12 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +12 -0
- package/dist/utils.js.map +1 -1
- package/package.json +12 -1
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
A small TypeScript SDK for sending **TRX** (TRON's native coin) and **TRC-20 tokens** such as **USDT** from one TRON address to another, on either **mainnet** or **testnet** (Nile or Shasta).
|
|
4
4
|
|
|
5
|
-
It wraps [TronWeb](https://tronweb.network)
|
|
5
|
+
It wraps [TronWeb](https://tronweb.network) in a `TronTransferService` class with `transferTrx` and `transferToken` methods. The service is given a **signer**: in a web app, the user's connected wallet (TronLink and others, through the TronWallet adapter); in Node, a private key. Each transfer takes care of the fiddly parts:
|
|
6
6
|
|
|
7
7
|
- **Unit conversion.** You pass amounts in whole units (`"10"` TRX, `"2.5"` USDT). The SDK converts them to on-chain units with exact integer math, so there are no floating-point rounding errors.
|
|
8
8
|
- **Pre-flight checks.** Addresses, amounts and the sender's balance are checked before anything is sent. This matters for tokens: a TRC-20 transfer that fails on-chain still burns TRX in fees.
|
|
9
|
-
- **Build, sign, broadcast.** The transaction
|
|
9
|
+
- **Build, sign, broadcast.** The SDK builds the transaction, the signer signs it (the wallet asks the user to approve), and the SDK broadcasts it. The SDK itself never holds a key.
|
|
10
10
|
- **Confirmation.** By default the SDK waits until the transaction is in a block and verifies it actually succeeded. The network accepting a transaction does not mean it executed, especially for token transfers.
|
|
11
11
|
- **Readable errors.** Node error messages are decoded, and every failure after broadcast carries the transaction ID.
|
|
12
12
|
|
|
@@ -18,6 +18,7 @@ It wraps [TronWeb](https://tronweb.network) with two functions, `transferTrx` an
|
|
|
18
18
|
- [Installation](#installation)
|
|
19
19
|
- [Configuration](#configuration)
|
|
20
20
|
- [Quick start](#quick-start)
|
|
21
|
+
- [Frontend: signing with the user's wallet](#frontend-signing-with-the-users-wallet)
|
|
21
22
|
- [API reference](#api-reference)
|
|
22
23
|
- [Types](#types)
|
|
23
24
|
- [How a transfer works](#how-a-transfer-works)
|
|
@@ -42,7 +43,7 @@ It wraps [TronWeb](https://tronweb.network) with two functions, `transferTrx` an
|
|
|
42
43
|
| Package format | **ES modules only** (`"type": "module"`). Load it with `import`, not `require()`. |
|
|
43
44
|
| TypeScript | Optional for consumers. If you use it, set `"moduleResolution"` to `"nodenext"`, `"node16"` or `"bundler"`, and turn on `"skipLibCheck": true` (see below). |
|
|
44
45
|
| A TRON account | Its hex private key, funded with TRX for fees (and holding the token, for token transfers) |
|
|
45
|
-
| TronGrid key | **Required for mainnet** (`api.trongrid.io`); optional on testnets. Get one at [trongrid.io](https://www.trongrid.io).
|
|
46
|
+
| TronGrid key | **Required for mainnet** (`api.trongrid.io`), in the browser and in Node; optional on testnets. Get one at [trongrid.io](https://www.trongrid.io). |
|
|
46
47
|
|
|
47
48
|
> **Why `skipLibCheck`?** TronWeb's bundled type definitions don't pass strict type-checking under `nodenext` resolution. Your own code is still fully checked; this only skips checking the `.d.ts` files inside `node_modules`.
|
|
48
49
|
|
|
@@ -56,10 +57,19 @@ npm install @zebec-network/tron-transfer-sdk
|
|
|
56
57
|
yarn add @zebec-network/tron-transfer-sdk
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
The package has two entry points:
|
|
61
|
+
|
|
62
|
+
| Import path | Environment | Contents |
|
|
63
|
+
| --------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `@zebec-network/tron-transfer-sdk` | Browser, Node | `TronTransferService`, signers, config helpers, types. No `dotenv`, no filesystem or `process.env` access. |
|
|
65
|
+
| `@zebec-network/tron-transfer-sdk/node` | Node only | Everything above, plus `loadConfig()` and the `.env`-driven `transferTrx()` / `transferToken()` shortcut functions. |
|
|
60
66
|
|
|
61
67
|
```ts
|
|
62
|
-
|
|
68
|
+
// Web app
|
|
69
|
+
import { TronTransferService } from '@zebec-network/tron-transfer-sdk';
|
|
70
|
+
|
|
71
|
+
// Node script or backend
|
|
72
|
+
import { transferTrx, transferToken } from '@zebec-network/tron-transfer-sdk/node';
|
|
63
73
|
```
|
|
64
74
|
|
|
65
75
|
To work on the SDK itself, see [Development](#development).
|
|
@@ -68,7 +78,9 @@ To work on the SDK itself, see [Development](#development).
|
|
|
68
78
|
|
|
69
79
|
## Configuration
|
|
70
80
|
|
|
71
|
-
|
|
81
|
+
This section is about the **Node entry** (`/node`). In a web app, pass settings straight to `TronTransferService` instead; see [`TronTransferServiceConfig`](#new-trontransferservicesigner-config).
|
|
82
|
+
|
|
83
|
+
The Node entry reads its settings from environment variables. It loads a `.env` file from the **current working directory** automatically when it's imported, via `dotenv`. Variables already set in the real environment take precedence over `.env`, so you can also configure it without a file (for example in CI or a container).
|
|
72
84
|
|
|
73
85
|
Copy the template and fill in your values:
|
|
74
86
|
|
|
@@ -87,12 +99,12 @@ cp .env.example .env
|
|
|
87
99
|
|
|
88
100
|
**Per network.** Replace `<NET>` with `TESTNET` or `MAINNET`:
|
|
89
101
|
|
|
90
|
-
| Variable | Required | Description
|
|
91
|
-
| -------------------------- | ------------------------ |
|
|
92
|
-
| `TRON_<NET>_FULL_HOST` | Yes | Full-node URL, e.g. `https://nile.trongrid.io` or `https://api.trongrid.io`.
|
|
93
|
-
| `TRON_<NET>_PRIVATE_KEY` | Yes | Sender's private key: 64 hex characters. A leading `0x` is accepted and stripped.
|
|
94
|
-
| `TRON_<NET>_API_KEY` | Mainnet on TronGrid: yes | TronGrid API key, sent as the `TRON-PRO-API-KEY` header. Keyless
|
|
95
|
-
| `TRON_<NET>_USDT_CONTRACT` | No | Default token for `transferToken` when no `tokenAddress` is passed.
|
|
102
|
+
| Variable | Required | Description |
|
|
103
|
+
| -------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `TRON_<NET>_FULL_HOST` | Yes | Full-node URL, e.g. `https://nile.trongrid.io` or `https://api.trongrid.io`. |
|
|
105
|
+
| `TRON_<NET>_PRIVATE_KEY` | Yes | Sender's private key: 64 hex characters. A leading `0x` is accepted and stripped. |
|
|
106
|
+
| `TRON_<NET>_API_KEY` | Mainnet on TronGrid: yes | TronGrid API key, sent as the `TRON-PRO-API-KEY` header. Keyless TronGrid requests are limited per IP to about 1 request/s, which a token transfer can exceed, so the SDK refuses to run on mainnet TronGrid without one. |
|
|
107
|
+
| `TRON_<NET>_USDT_CONTRACT` | No | Default token for `transferToken` when no `tokenAddress` is passed. If unset, USDT is used when the host is a TronGrid mainnet, Nile or Shasta host. |
|
|
96
108
|
|
|
97
109
|
**Tests only**
|
|
98
110
|
|
|
@@ -128,8 +140,10 @@ Configuration is read on **every call**, so each call checks only the network it
|
|
|
128
140
|
|
|
129
141
|
## Quick start
|
|
130
142
|
|
|
143
|
+
In Node, with the sender's key in `.env`:
|
|
144
|
+
|
|
131
145
|
```ts
|
|
132
|
-
import { transferTrx, transferToken, TronTransferError } from '@zebec-network/tron-transfer-sdk';
|
|
146
|
+
import { transferTrx, transferToken, TronTransferError } from '@zebec-network/tron-transfer-sdk/node';
|
|
133
147
|
|
|
134
148
|
// Send 10 TRX on the network set by TRON_NETWORK
|
|
135
149
|
const trx = await transferTrx({ to: 'TXYZ...recipient', amount: '10' });
|
|
@@ -160,13 +174,169 @@ try {
|
|
|
160
174
|
|
|
161
175
|
---
|
|
162
176
|
|
|
177
|
+
## Frontend: signing with the user's wallet
|
|
178
|
+
|
|
179
|
+
In a web app there is no `.env` private key. The user's wallet signs instead. `TronTransferService` takes any object with this shape:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
interface TronSigner {
|
|
183
|
+
readonly address: string | null; // connected account, or null when disconnected
|
|
184
|
+
signTransaction(transaction: Transaction): Promise<SignedTransaction>;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
That is exactly the interface of the [TronWallet adapters](https://github.com/web3-geek/tronwallet-adapter) (`TronLinkAdapter`, `WalletConnectAdapter`, …) and of `useWallet()` from their React hooks, so you pass them in directly. There's no glue code to write.
|
|
189
|
+
|
|
190
|
+
### React, with `@tronweb3/tronwallet-adapter-react-hooks`
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { useMemo } from 'react';
|
|
194
|
+
import { useWallet } from '@tronweb3/tronwallet-adapter-react-hooks';
|
|
195
|
+
import { TronTransferService, TronTransferError } from '@zebec-network/tron-transfer-sdk';
|
|
196
|
+
|
|
197
|
+
function useTronTransfers() {
|
|
198
|
+
const { address, signTransaction } = useWallet();
|
|
199
|
+
return useMemo(
|
|
200
|
+
() =>
|
|
201
|
+
new TronTransferService(
|
|
202
|
+
{ address, signTransaction },
|
|
203
|
+
{ network: 'mainnet', apiKey: import.meta.env.VITE_TRONGRID_KEY }, // required on mainnet TronGrid
|
|
204
|
+
),
|
|
205
|
+
[address, signTransaction],
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function SendUsdtButton({ to, amount }: { to: string; amount: string }) {
|
|
210
|
+
const service = useTronTransfers();
|
|
211
|
+
|
|
212
|
+
async function send() {
|
|
213
|
+
try {
|
|
214
|
+
// The wallet pops up to approve; the promise resolves once the transfer is confirmed in a block.
|
|
215
|
+
const result = await service.transferToken({ to, amount });
|
|
216
|
+
console.log('Sent', result.txid);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (error instanceof TronTransferError && !error.txid) {
|
|
219
|
+
// Nothing was broadcast: validation failed, balance too low, or the user rejected the request.
|
|
220
|
+
// For a rejection, `error.cause` is the wallet's original error.
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return <button onClick={send}>Send {amount} USDT</button>;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Plain adapter, no React
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { TronLinkAdapter } from '@tronweb3/tronwallet-adapters';
|
|
234
|
+
import { TronTransferService } from '@zebec-network/tron-transfer-sdk';
|
|
235
|
+
|
|
236
|
+
const adapter = new TronLinkAdapter();
|
|
237
|
+
await adapter.connect();
|
|
238
|
+
|
|
239
|
+
const service = new TronTransferService(adapter, { network: 'testnet' }); // Nile by default
|
|
240
|
+
await service.transferTrx({ to: 'TXYZ...', amount: '1' });
|
|
241
|
+
const usdt = await service.getTokenBalance(); // connected account's USDT, e.g. "12.5"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Rate limits (HTTP 429)
|
|
245
|
+
|
|
246
|
+
TronGrid can answer bursts with **HTTP 429**, even with an API key (and far more often without one, which is why mainnet requires it). The service handles this by stage:
|
|
247
|
+
|
|
248
|
+
| Stage | What the SDK does | If it still fails |
|
|
249
|
+
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
250
|
+
| Checks and build (balances, decimals, tx build) | Retries with exponential backoff and jitter (~1 s, ~2 s, ~4 s; `rateLimitRetries`, default 3). | `code: 'RATE_LIMITED'`, **no `txid`**: nothing was sent. Safe to offer a retry. |
|
|
251
|
+
| Broadcast | Resends the **same signed transaction**. It has the same txid, so it can't be sent twice, and the wallet isn't asked to sign again. A duplicate on resend counts as success. | `code: 'RATE_LIMITED'` **with `txid`**: check the txid before retrying. |
|
|
252
|
+
| Confirmation | The transfer is already sent, so it keeps polling and slows down on 429 (up to 15 s between polls). | Confirmation timeout **with `txid`**. |
|
|
253
|
+
|
|
254
|
+
HTTP **403** (IP blocked, daily quota used up, or a bad key) is not retried: `code: 'ACCESS_DENIED'`. Token decimals are cached per service, so repeat token transfers need one fewer request.
|
|
255
|
+
|
|
256
|
+
What the UI should do:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
try {
|
|
260
|
+
await service.transferToken({ to, amount });
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (!(error instanceof TronTransferError)) throw error;
|
|
263
|
+
if (error.txid) {
|
|
264
|
+
// Possibly sent. Never resend automatically; show "pending" and a link to the txid on tronscan.
|
|
265
|
+
} else if (error.code === 'RATE_LIMITED') {
|
|
266
|
+
// Nothing was sent. Show "Network busy, try again in a few seconds" with a retry button.
|
|
267
|
+
} else if (error.code === 'NOT_SIGNED') {
|
|
268
|
+
// The user cancelled in their wallet.
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Keep the service's own traffic low, too: create one service and reuse it (for example with `useMemo`), fetch balances on demand rather than on a timer, and don't call balance methods while a transfer is running. If users still hit 429 often, check the key's quota in the TronGrid dashboard, or point `fullHost` at your own proxy or node.
|
|
274
|
+
|
|
275
|
+
### How it behaves with a wallet
|
|
276
|
+
|
|
277
|
+
- **The address is read on every call.** If the user switches accounts, the next transfer comes from the new account. If they disconnect, calls throw `No wallet connected` before anything is built.
|
|
278
|
+
- **Only what was checked gets broadcast.** The SDK refuses a signed transaction whose `txID` differs from the one it built.
|
|
279
|
+
- **Rejection is a normal error.** If the user declines in the wallet, you get a `TronTransferError` with `code: 'NOT_SIGNED'` and no `txid`, and `cause` holds the wallet's error.
|
|
280
|
+
- **The wallet's selected network doesn't matter to the SDK.** Transactions are built on and broadcast to the service's `network`/`fullHost`, and the wallet just signs them. Make sure the UI's `network` matches what the user expects to spend on.
|
|
281
|
+
- **TronGrid API key on mainnet.** It's required, as in Node. A browser key is visible to every user, so restrict it to your app's origins in the TronGrid dashboard, or point `fullHost` at your own proxy or node (no key needed there).
|
|
282
|
+
- **Don't use `PrivateKeySigner` in a browser.** It's for backends, scripts and tests.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
163
286
|
## API reference
|
|
164
287
|
|
|
165
|
-
|
|
288
|
+
Unless noted, everything below is exported from both entry points.
|
|
289
|
+
|
|
290
|
+
### `new TronTransferService(signer, config)`
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
class TronTransferService {
|
|
294
|
+
constructor(signer: TronSigner, config: TronTransferServiceConfig);
|
|
295
|
+
readonly signer: TronSigner;
|
|
296
|
+
readonly config: ServiceConfig; // validated, with defaults applied
|
|
297
|
+
readonly tronWeb: TronWeb; // read-only client (no key), for anything the SDK doesn't cover
|
|
298
|
+
|
|
299
|
+
transferTrx(params: TransferTrxParams): Promise<TransferResult>;
|
|
300
|
+
transferToken(params: TransferTokenParams): Promise<TransferResult>;
|
|
301
|
+
getTrxBalance(address?: string): Promise<string>;
|
|
302
|
+
getTokenBalance(params?: TokenBalanceParams): Promise<string>;
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`config` (`TronTransferServiceConfig`). The constructor throws a plain `Error` if a value is invalid.
|
|
307
|
+
|
|
308
|
+
| Field | Type | Default | Description |
|
|
309
|
+
| ------------------ | ------------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
310
|
+
| `network` | `'testnet' \| 'mainnet'` | required | Network the service builds on, broadcasts to and reports. |
|
|
311
|
+
| `fullHost` | `string` | `https://api.trongrid.io` (mainnet), `https://nile.trongrid.io` (testnet) | Full-node URL. |
|
|
312
|
+
| `apiKey` | `string` | – | TronGrid key. **Required for mainnet on `api.trongrid.io`.** |
|
|
313
|
+
| `usdtContract` | `string` | USDT for TronGrid mainnet / Nile / Shasta hosts | Default token for `transferToken` and `getTokenBalance`. |
|
|
314
|
+
| `feeLimitSun` | `number` | `100000000` (100 TRX) | Default max TRX, in SUN, that one token transfer may burn. |
|
|
315
|
+
| `rateLimitRetries` | `number` | `3` | Retries, with backoff, for requests answered with HTTP 429. |
|
|
316
|
+
|
|
317
|
+
`transferTrx` and `transferToken` are described below; they take the same parameters as the Node shortcut functions, minus `network` (the service is bound to one).
|
|
318
|
+
|
|
319
|
+
`getTrxBalance(address?)` returns a TRX balance in whole TRX, such as `"12.5"`. `getTokenBalance({ tokenAddress?, owner? })` returns a token balance in whole tokens, using the token's `decimals()`. Both default to the signer's address and read the full node's latest state. They also work with a disconnected wallet if you pass an address.
|
|
320
|
+
|
|
321
|
+
### `TronSigner` and `PrivateKeySigner`
|
|
322
|
+
|
|
323
|
+
`TronSigner` is the interface shown in [Frontend](#frontend-signing-with-the-users-wallet). `PrivateKeySigner` implements it with a hex private key, signing locally:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import { PrivateKeySigner, TronTransferService } from '@zebec-network/tron-transfer-sdk';
|
|
327
|
+
|
|
328
|
+
const service = new TronTransferService(new PrivateKeySigner(process.env.KEY!), { network: 'testnet' });
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
It throws on a malformed key. Use it on servers and in tests only.
|
|
332
|
+
|
|
333
|
+
### `createTransferServiceFromEnv(network?)` (Node entry)
|
|
334
|
+
|
|
335
|
+
Returns a `TronTransferService` configured by `loadConfig(network)`, signing with `TRON_<NET>_PRIVATE_KEY`.
|
|
166
336
|
|
|
167
|
-
### `transferTrx(params)`
|
|
337
|
+
### `transferTrx(params)` (Node entry)
|
|
168
338
|
|
|
169
|
-
Sends native TRX from the account configured for the network
|
|
339
|
+
Sends native TRX from the account configured in `.env`. It's a shortcut for `createTransferServiceFromEnv(params.network).transferTrx(params)`; `service.transferTrx` takes the same parameters except `network`.
|
|
170
340
|
|
|
171
341
|
```ts
|
|
172
342
|
function transferTrx(params: TransferTrxParams): Promise<TransferResult>;
|
|
@@ -194,7 +364,7 @@ await transferTrx({ to: 'TXYZ...', amount: 12 }); // 12 TRX
|
|
|
194
364
|
await transferTrx({ to: 'TXYZ...', amount: '1', waitForConfirmation: false }); // return right after broadcast
|
|
195
365
|
```
|
|
196
366
|
|
|
197
|
-
### `transferToken(params)`
|
|
367
|
+
### `transferToken(params)` (Node entry)
|
|
198
368
|
|
|
199
369
|
Sends a TRC-20 token by calling its `transfer(address,uint256)` function. The default token is the USDT contract configured for the network.
|
|
200
370
|
|
|
@@ -237,7 +407,7 @@ The error class for transfer failures. It extends `Error` and adds:
|
|
|
237
407
|
|
|
238
408
|
The `message` includes the txid when there is one. See [Error handling](#error-handling) for the full list of failures.
|
|
239
409
|
|
|
240
|
-
### `loadConfig(network?)`
|
|
410
|
+
### `loadConfig(network?)` (Node entry)
|
|
241
411
|
|
|
242
412
|
Reads and validates configuration from the environment. `transferTrx` and `transferToken` call it for you; it's exported for advanced use.
|
|
243
413
|
|
|
@@ -249,19 +419,23 @@ It defaults to `TRON_NETWORK`, and throws `Error` on missing or invalid variable
|
|
|
249
419
|
|
|
250
420
|
### `createTronWeb(config)`
|
|
251
421
|
|
|
252
|
-
Creates a TronWeb instance
|
|
422
|
+
Creates a TronWeb instance with the API key header set, and the private key if one is given (omit it for a read-only client). Use it to call TronWeb directly for anything the SDK doesn't cover:
|
|
253
423
|
|
|
254
424
|
```ts
|
|
255
|
-
import { createTronWeb, loadConfig } from '@zebec-network/tron-transfer-sdk';
|
|
425
|
+
import { createTronWeb, loadConfig } from '@zebec-network/tron-transfer-sdk/node';
|
|
256
426
|
|
|
257
427
|
const tronWeb = createTronWeb(loadConfig('testnet'));
|
|
258
428
|
const account = await tronWeb.trx.getAccount('TXYZ...');
|
|
259
429
|
```
|
|
260
430
|
|
|
261
431
|
```ts
|
|
262
|
-
function createTronWeb(config:
|
|
432
|
+
function createTronWeb(config: { fullHost: string; apiKey?: string; privateKey?: string }): TronWeb;
|
|
263
433
|
```
|
|
264
434
|
|
|
435
|
+
### `resolveServiceConfig(config)`
|
|
436
|
+
|
|
437
|
+
Validates a `TronTransferServiceConfig` and applies defaults, returning a `ServiceConfig`. The service constructor calls it; it's exported so you can validate settings early, for example at app start-up.
|
|
438
|
+
|
|
265
439
|
### `toBaseUnits(amount, decimals)`
|
|
266
440
|
|
|
267
441
|
Converts a human-readable amount into the smallest unit using exact `bigint` math. This is the same conversion the transfer functions use.
|
|
@@ -277,6 +451,10 @@ toBaseUnits('1.0000001', 6); // throws: more than 6 decimal places
|
|
|
277
451
|
|
|
278
452
|
It rejects zero, negative, non-numeric and over-precise amounts. It also rejects numbers that JavaScript prints in exponent form (such as `1e21`); pass those as strings.
|
|
279
453
|
|
|
454
|
+
### `fromBaseUnits(units, decimals)`
|
|
455
|
+
|
|
456
|
+
The reverse conversion, for displaying `rawAmount` or balances: `fromBaseUnits(1_500_000n, 6) === '1.5'`.
|
|
457
|
+
|
|
280
458
|
---
|
|
281
459
|
|
|
282
460
|
## Types
|
|
@@ -290,11 +468,15 @@ type TronNetwork = 'mainnet' | 'testnet';
|
|
|
290
468
|
type Amount = string | number;
|
|
291
469
|
|
|
292
470
|
interface TransferOptions {
|
|
293
|
-
network?: TronNetwork; // default: TRON_NETWORK
|
|
294
471
|
waitForConfirmation?: boolean; // default: true
|
|
295
472
|
confirmationTimeoutMs?: number; // default: 60_000
|
|
296
473
|
}
|
|
297
474
|
|
|
475
|
+
/** Only for the Node shortcut functions; a service is bound to one network. */
|
|
476
|
+
interface EnvTransferOptions {
|
|
477
|
+
network?: TronNetwork; // default: TRON_NETWORK
|
|
478
|
+
}
|
|
479
|
+
|
|
298
480
|
interface TransferTrxParams extends TransferOptions {
|
|
299
481
|
to: string;
|
|
300
482
|
amount: Amount; // in TRX
|
|
@@ -303,14 +485,19 @@ interface TransferTrxParams extends TransferOptions {
|
|
|
303
485
|
interface TransferTokenParams extends TransferOptions {
|
|
304
486
|
to: string;
|
|
305
487
|
amount: Amount; // in whole tokens
|
|
306
|
-
tokenAddress?: string; // default:
|
|
307
|
-
feeLimitSun?: number; // default:
|
|
488
|
+
tokenAddress?: string; // default: the configured USDT contract
|
|
489
|
+
feeLimitSun?: number; // default: the service's feeLimitSun
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
interface TokenBalanceParams {
|
|
493
|
+
tokenAddress?: string; // default: the configured USDT contract
|
|
494
|
+
owner?: string; // default: the signer's address
|
|
308
495
|
}
|
|
309
496
|
|
|
310
497
|
interface TransferResult {
|
|
311
498
|
txid: string;
|
|
312
499
|
network: TronNetwork;
|
|
313
|
-
from: string; // sender
|
|
500
|
+
from: string; // sender: the signer's address
|
|
314
501
|
to: string;
|
|
315
502
|
rawAmount: string; // smallest unit: SUN for TRX, base units for tokens
|
|
316
503
|
confirmed: boolean; // false when waitForConfirmation is false
|
|
@@ -318,14 +505,25 @@ interface TransferResult {
|
|
|
318
505
|
feeSun?: number; // total fee paid, in SUN; set when confirmed
|
|
319
506
|
}
|
|
320
507
|
|
|
321
|
-
interface
|
|
508
|
+
interface TronTransferServiceConfig {
|
|
509
|
+
network: TronNetwork;
|
|
510
|
+
fullHost?: string;
|
|
511
|
+
apiKey?: string;
|
|
512
|
+
usdtContract?: string;
|
|
513
|
+
feeLimitSun?: number;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
interface ServiceConfig {
|
|
322
517
|
network: TronNetwork;
|
|
323
518
|
fullHost: string;
|
|
324
519
|
apiKey: string | undefined;
|
|
325
|
-
privateKey: string;
|
|
326
520
|
usdtContract: string | undefined;
|
|
327
521
|
feeLimitSun: number;
|
|
328
522
|
}
|
|
523
|
+
|
|
524
|
+
interface NetworkConfig extends ServiceConfig {
|
|
525
|
+
privateKey: string; // from .env
|
|
526
|
+
}
|
|
329
527
|
```
|
|
330
528
|
|
|
331
529
|
`rawAmount` is a string because token base units can exceed JavaScript's safe integer range. Use `BigInt(result.rawAmount)` to do arithmetic on it.
|
|
@@ -350,14 +548,14 @@ Example result:
|
|
|
350
548
|
## How a transfer works
|
|
351
549
|
|
|
352
550
|
```
|
|
353
|
-
|
|
551
|
+
read signer address ─► validate inputs ─► check balance ─► build tx ─► signer signs ─► broadcast ─► wait for block ─► verify result
|
|
354
552
|
```
|
|
355
553
|
|
|
356
|
-
1. **
|
|
554
|
+
1. **Read the sender address** from the signer (the connected wallet, or the key's address).
|
|
357
555
|
2. **Validate** the recipient, the amount, and (for tokens) the contract address and its `decimals()`.
|
|
358
556
|
3. **Check balance:** TRX via the full node, tokens via `balanceOf`.
|
|
359
557
|
4. **Build** the transaction: `transactionBuilder.sendTrx` for TRX, or `transactionBuilder.triggerSmartContract` calling `transfer(address,uint256)` for tokens.
|
|
360
|
-
5. **Sign** it
|
|
558
|
+
5. **Sign** it with `signer.signTransaction`. A wallet asks the user to approve; `PrivateKeySigner` signs locally. The SDK checks that the signed transaction's `txID` matches what it built.
|
|
361
559
|
6. **Broadcast** it with `trx.sendRawTransaction`. If the node rejects it, the SDK throws with the decoded reason.
|
|
362
560
|
7. **Wait for confirmation.** The SDK polls `getUnconfirmedTransactionInfo` every ~3 s (one TRON block) until the transaction appears in a block, up to `confirmationTimeoutMs`.
|
|
363
561
|
8. **Verify.** If the transaction failed on-chain (for example a token contract revert or running out of energy), the SDK throws a `TronTransferError` with the txid.
|
|
@@ -393,33 +591,42 @@ Things to keep in mind:
|
|
|
393
591
|
There are two error types:
|
|
394
592
|
|
|
395
593
|
- **`TronTransferError`**: anything about the transfer itself (addresses, balances, broadcast, on-chain result).
|
|
396
|
-
- **Plain `Error`**: configuration problems (from `loadConfig`) and invalid amounts (from `toBaseUnits`). These are thrown before anything touches the network.
|
|
397
|
-
|
|
398
|
-
| When | Message (abridged)
|
|
399
|
-
| ------------ |
|
|
400
|
-
| Config | `Missing required environment variable …`
|
|
401
|
-
| Config | `… PRIVATE_KEY must be a 64-character hex string.`
|
|
402
|
-
| Config | `TRON_NETWORK must be "mainnet" or "testnet" …`
|
|
403
|
-
| Config | `TRON_FEE_LIMIT_SUN must be a positive integer …`
|
|
404
|
-
|
|
|
405
|
-
|
|
|
406
|
-
|
|
|
407
|
-
|
|
|
408
|
-
|
|
|
409
|
-
| Validation | `
|
|
410
|
-
| Validation | `
|
|
411
|
-
|
|
|
412
|
-
|
|
|
413
|
-
|
|
|
414
|
-
|
|
|
415
|
-
|
|
|
594
|
+
- **Plain `Error`**: configuration problems (from `loadConfig`, `resolveServiceConfig` and the service constructor), malformed `PrivateKeySigner` keys, and invalid amounts (from `toBaseUnits`). These are thrown before anything touches the network.
|
|
595
|
+
|
|
596
|
+
| When | Message (abridged) | Type | `txid` set? |
|
|
597
|
+
| ------------ | ----------------------------------------------------------------------------------------- | ------------------- | ----------- |
|
|
598
|
+
| Config | `Missing required environment variable …` | `Error` | – |
|
|
599
|
+
| Config | `… PRIVATE_KEY must be a 64-character hex string.` | `Error` | – |
|
|
600
|
+
| Config | `TRON_NETWORK must be "mainnet" or "testnet" …` | `Error` | – |
|
|
601
|
+
| Config | `TRON_FEE_LIMIT_SUN must be a positive integer …` | `Error` | – |
|
|
602
|
+
| Config | `A TronGrid API key is required for … on mainnet …` | `Error` | – |
|
|
603
|
+
| Config | `network must be …` / `fullHost must be a URL …` / `feeLimitSun …` / `rateLimitRetries …` | `Error` | – |
|
|
604
|
+
| Amount | `Invalid amount …` / `Amount must be greater than zero …` | `Error` | – |
|
|
605
|
+
| Amount | `Amount … has more than N decimal places.` | `Error` | – |
|
|
606
|
+
| Wallet | `No wallet connected: the signer has no address.` | `TronTransferError` | No |
|
|
607
|
+
| Validation | `Invalid recipient address …` | `TronTransferError` | No |
|
|
608
|
+
| Validation | `Recipient address is the same as the sender address.` | `TronTransferError` | No |
|
|
609
|
+
| Validation | `Invalid token contract address …` | `TronTransferError` | No |
|
|
610
|
+
| Validation | `No token address given and no default USDT contract is known …` | `TronTransferError` | No |
|
|
611
|
+
| Validation | `Call to decimals() on … failed … Is this a TRC-20 contract …?` | `TronTransferError` | No |
|
|
612
|
+
| Balance | `Insufficient TRX balance …` / `Insufficient token balance …` | `TronTransferError` | No |
|
|
613
|
+
| Build | `Failed to build token transfer: …` | `TronTransferError` | No |
|
|
614
|
+
| Node | `Rate limited by … (HTTP 429) after N retries …` before broadcast (`RATE_LIMITED`) | `TronTransferError` | No |
|
|
615
|
+
| Node | `Request refused by … (HTTP 403) …` before broadcast (`ACCESS_DENIED`) | `TronTransferError` | No |
|
|
616
|
+
| Signing | `Transaction was not signed: <wallet error>` (`NOT_SIGNED`) | `TronTransferError` | No |
|
|
617
|
+
| Signing | `Signer returned a transaction that is unsigned or differs …` | `TronTransferError` | No |
|
|
618
|
+
| Broadcast | `Broadcast rejected: <code> <reason>` | `TronTransferError` | Yes |
|
|
619
|
+
| Broadcast | `Rate limited …` / `Request refused …` at broadcast | `TronTransferError` | Yes |
|
|
620
|
+
| Broadcast | `Broadcast request failed: … may still have reached the network` | `TronTransferError` | Yes |
|
|
621
|
+
| On-chain | `Transaction failed on-chain: <reason>` | `TronTransferError` | Yes |
|
|
622
|
+
| Confirmation | `Transaction was broadcast but not confirmed within …` | `TronTransferError` | Yes |
|
|
416
623
|
|
|
417
624
|
Guidance for retries:
|
|
418
625
|
|
|
419
626
|
- **No `txid`:** nothing was sent and no fees were charged. It's safe to fix the problem and retry.
|
|
420
|
-
- **`txid` set:** the transaction reached the network. For a confirmation timeout, it may still succeed. **Look the txid up on an explorer (or with `trx.getTransactionInfo`) before retrying**, or you may send twice.
|
|
627
|
+
- **`txid` set:** the transaction may have reached the network. For a confirmation timeout, it may still succeed. **Look the txid up on an explorer (or with `trx.getTransactionInfo`) before retrying**, or you may send twice.
|
|
421
628
|
|
|
422
|
-
|
|
629
|
+
`code` is set for `RATE_LIMITED`, `ACCESS_DENIED` and `NOT_SIGNED`, so a UI can branch without matching messages. HTTP 429 is retried first; see [Rate limits](#rate-limits-http-429). Other errors from TronWeb or the network before broadcast, such as connection failures or an unreachable node, are passed through unchanged. Errors while polling for confirmation don't end the wait.
|
|
423
630
|
|
|
424
631
|
---
|
|
425
632
|
|
|
@@ -493,15 +700,16 @@ They need these variables in `.env` (see [Configuration](#configuration)):
|
|
|
493
700
|
|
|
494
701
|
Use a recipient you control so you can reuse the funds. **On mainnet the tests spend real TRX and USDT.**
|
|
495
702
|
|
|
496
|
-
What's covered (
|
|
703
|
+
What's covered (35 tests):
|
|
497
704
|
|
|
498
|
-
| File
|
|
499
|
-
|
|
|
500
|
-
| [`test/transferTrx.test.ts`](test/transferTrx.test.ts)
|
|
501
|
-
| [`test/transferToken.test.ts`](test/transferToken.test.ts)
|
|
705
|
+
| File | Live transfers | Validation (nothing is sent) |
|
|
706
|
+
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
707
|
+
| [`test/transferTrx.test.ts`](test/transferTrx.test.ts) | 0.5 TRX with recipient balance check; 0.5 TRX as a number; `waitForConfirmation: false` | Bad address, self-send, zero / negative / non-numeric / over-precise amounts, insufficient balance |
|
|
708
|
+
| [`test/transferToken.test.ts`](test/transferToken.test.ts) | 0.5 USDT via the default contract with balance check; 0.25 USDT via explicit `tokenAddress`; `waitForConfirmation: false` | Bad recipient, self-send, bad token address, non-TRC-20 address, too many decimals, insufficient balance |
|
|
709
|
+
| [`test/TronTransferService.test.ts`](test/TronTransferService.test.ts) | 0.000001 TRX and 0.000001 USDT through a wallet-shaped signer; balance reads | Config defaults and validation, `PrivateKeySigner`, `fromBaseUnits`, disconnected wallet, user rejection, tampered signed transaction; simulated HTTP 429/403 at each stage, including a real rebroadcast answered as a duplicate |
|
|
502
710
|
|
|
503
711
|
- **Cost per run:** about 1 TRX and 0.75 USDT go to the recipient, plus network fees (up to ~0.3 TRX per TRX transfer once the sender's free bandwidth is used up; USDT transfers also burn TRX for energy unless the sender has staked energy).
|
|
504
|
-
- **Duration:** about
|
|
712
|
+
- **Duration:** about 1 minute.
|
|
505
713
|
- The test files run one after the other with a generous timeout (`-t 1000000`), since each live transfer waits for a block.
|
|
506
714
|
|
|
507
715
|
---
|
|
@@ -511,16 +719,20 @@ What's covered (19 tests):
|
|
|
511
719
|
```
|
|
512
720
|
tron-transfer-sdk/
|
|
513
721
|
├── src/
|
|
514
|
-
│ ├── index.ts #
|
|
515
|
-
│ ├──
|
|
516
|
-
│ ├──
|
|
722
|
+
│ ├── index.ts # Browser-safe entry (package root)
|
|
723
|
+
│ ├── node.ts # Node entry (/node): root + loadConfig and .env shortcut functions
|
|
724
|
+
│ ├── services.ts # TronTransferService, TronTransferError
|
|
725
|
+
│ ├── signer.ts # TronSigner interface, PrivateKeySigner
|
|
726
|
+
│ ├── config.ts # Service config, defaults and validation (resolveServiceConfig)
|
|
727
|
+
│ ├── env.ts # .env loading (loadConfig), Node only
|
|
517
728
|
│ ├── client.ts # TronWeb instance factory (createTronWeb)
|
|
518
729
|
│ ├── types.ts # Public parameter and result types
|
|
519
|
-
│ └── utils.ts # Exact amount conversion (toBaseUnits)
|
|
730
|
+
│ └── utils.ts # Exact amount conversion (toBaseUnits, fromBaseUnits)
|
|
520
731
|
├── test/
|
|
521
732
|
│ ├── helpers.ts # Shared test setup and balance readers
|
|
522
733
|
│ ├── transferTrx.test.ts
|
|
523
|
-
│
|
|
734
|
+
│ ├── transferToken.test.ts
|
|
735
|
+
│ └── TronTransferService.test.ts
|
|
524
736
|
├── examples/
|
|
525
737
|
│ └── transfer.ts # Example CLI
|
|
526
738
|
├── dist/ # Build output (the only folder that gets published)
|
|
@@ -540,7 +752,7 @@ tron-transfer-sdk/
|
|
|
540
752
|
| Package | Why |
|
|
541
753
|
| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
542
754
|
| [`tronweb`](https://www.npmjs.com/package/tronweb) `^6.5.1` | The official TRON JavaScript SDK: builds, signs and broadcasts transactions, and talks to full nodes. |
|
|
543
|
-
| [`dotenv`](https://www.npmjs.com/package/dotenv) `^18` | Loads `.env` when the
|
|
755
|
+
| [`dotenv`](https://www.npmjs.com/package/dotenv) `^18` | Loads `.env` when the Node entry (`/node`) is imported. The browser entry never loads it. |
|
|
544
756
|
|
|
545
757
|
### Development
|
|
546
758
|
|
|
@@ -561,17 +773,20 @@ The package is ESM (`"type": "module"`). ts-mocha on its own only sets up a Comm
|
|
|
561
773
|
|
|
562
774
|
## Troubleshooting
|
|
563
775
|
|
|
564
|
-
| Symptom
|
|
565
|
-
|
|
|
566
|
-
| `Missing required environment variable TRON_…`
|
|
567
|
-
| HTTP 429
|
|
568
|
-
| `Insufficient TRX balance` right after funding the account
|
|
569
|
-
| `Call to decimals() on … failed`
|
|
570
|
-
| `Transaction failed on-chain: OUT_OF_ENERGY`
|
|
571
|
-
| `Transaction was broadcast but not confirmed within …`
|
|
572
|
-
| `Broadcast rejected: … balance is not sufficient`
|
|
573
|
-
| TypeScript errors inside `node_modules/tronweb/...`
|
|
574
|
-
| `ERR_REQUIRE_ESM` or `require() of ES Module`
|
|
776
|
+
| Symptom | Likely cause and fix |
|
|
777
|
+
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
778
|
+
| `Missing required environment variable TRON_…` | `.env` isn't in the **current working directory** of the process, or the variable is empty. Run from the folder containing `.env`, or set the variable in the environment. |
|
|
779
|
+
| `RATE_LIMITED` / HTTP 429 | The SDK already retried; wait a few seconds. On testnet, set `apiKey` (`TRON_TESTNET_API_KEY` in Node). On mainnet, check the key's quota in the TronGrid dashboard or use another `fullHost`. |
|
|
780
|
+
| `Insufficient TRX balance` right after funding the account | The new funds haven't reached the node yet. Wait one or two blocks (~6 s) and retry. |
|
|
781
|
+
| `Call to decimals() on … failed` | The token address isn't a TRC-20 contract **on the selected network**. A common mistake is using the mainnet USDT address on testnet, or vice versa. |
|
|
782
|
+
| `Transaction failed on-chain: OUT_OF_ENERGY` | `feeLimitSun` was too low, or the sender ran out of TRX for energy. Raise the limit or add TRX, and check the txid. |
|
|
783
|
+
| `Transaction was broadcast but not confirmed within …` | The network is slow or congested. Look up the txid **before** retrying; it may still succeed. |
|
|
784
|
+
| `Broadcast rejected: … balance is not sufficient` | The balance covers the amount but not the fees. Leave some TRX for bandwidth and energy. |
|
|
785
|
+
| TypeScript errors inside `node_modules/tronweb/...` | Enable `"skipLibCheck": true` in your tsconfig. |
|
|
786
|
+
| `ERR_REQUIRE_ESM` or `require() of ES Module` | The package is ESM-only. Use `import`, or dynamic `import()` from CommonJS. |
|
|
787
|
+
| `loadConfig` / `transferTrx` is not exported | Since the wallet-signer release these live in the Node entry: import from `@zebec-network/tron-transfer-sdk/node`. |
|
|
788
|
+
| Bundler errors about `fs`, `path` or `process` in a web app | You imported `@zebec-network/tron-transfer-sdk/node`. Web apps must import the package root. |
|
|
789
|
+
| `No wallet connected` in the UI | The adapter has no `address` yet. Wait for `connect()` / the `connected` state before calling transfer or balance methods. |
|
|
575
790
|
|
|
576
791
|
---
|
|
577
792
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { TronWeb } from 'tronweb';
|
|
2
|
-
import type {
|
|
3
|
-
export
|
|
2
|
+
import type { ServiceConfig } from './config.js';
|
|
3
|
+
export interface TronWebOptions extends Pick<ServiceConfig, 'fullHost' | 'apiKey'> {
|
|
4
|
+
/** Omit for a read-only client, e.g. in a browser where a wallet signs. */
|
|
5
|
+
privateKey?: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare function createTronWeb(config: TronWebOptions): TronWeb;
|
|
4
8
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,wBAAgB,aAAa,CAAC,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,QAAQ,CAAC;IAChF,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAM7D"}
|
package/dist/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import { TronWeb } from 'tronweb';
|
|
|
2
2
|
export function createTronWeb(config) {
|
|
3
3
|
return new TronWeb({
|
|
4
4
|
fullHost: config.fullHost,
|
|
5
|
-
privateKey: config.privateKey,
|
|
5
|
+
...(config.privateKey ? { privateKey: config.privateKey } : {}),
|
|
6
6
|
...(config.apiKey ? { headers: { 'TRON-PRO-API-KEY': config.apiKey } } : {}),
|
|
7
7
|
});
|
|
8
8
|
}
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAQlC,MAAM,UAAU,aAAa,CAAC,MAAsB;IAClD,OAAO,IAAI,OAAO,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7E,CAAC,CAAC;AACL,CAAC"}
|