@tronsave/sdk
v2.0.0
Published
Fully-typed, zero-dependency TypeScript SDK for the Tronsave v2 API (TRON Energy & Bandwidth).
Maintainers
Readme
@tronsave/sdk
A fully-typed, zero-dependency TypeScript SDK for the Tronsave v2 API — buy, estimate, extend, and track TRON Energy and Bandwidth rentals from Node.js.
- ✅ Strictly typed — discriminated unions, branded
TronAddress/OrderId/Sun, noany. - ✅ Zero runtime dependencies — global
fetch+AbortController(Node.js ≥ 18). - ✅ Resilient transport — per-request timeout, exponential backoff + jitter,
Retry-After-aware 429 handling, and a built-in per-endpoint token-bucket rate limiter so you never exceed the documented limits. - ✅ Typed error tree —
catchby class or branch onerror.code. - ✅ Dual ESM + CJS build with
.d.tsand sourcemaps.
Install
npm install @tronsave/sdkRequires Node.js ≥ 18.
Quick start
import { TronsaveSDK } from '@tronsave/sdk';
const sdk = new TronsaveSDK({
network: 'mainnet', // or 'testnet' (Nile)
apiKey: process.env.TRONSAVE_API_KEY, // never hard-code secrets
});
// 1. Check your internal-account balance (SUN).
const me = await sdk.getUserInfo();
// 2. Estimate the cost.
const estimate = await sdk.estimateBuyResource({
receiver: 'TFwUFWr3QV376677Z8VWXxGUAMFSrq1MbM',
resourceType: 'ENERGY',
resourceAmount: 32_000,
durationSec: 3_600,
});
// 3. Buy.
const { orderId } = await sdk.buyResource(
{ receiver: 'TFwUFWr3QV376677Z8VWXxGUAMFSrq1MbM', resourceAmount: 32_000, durationSec: 3_600 },
{ allowPartialFill: true, preventDuplicateIncompleteOrders: true, maxPriceAccepted: 100 },
);
// 4. Poll until filled.
const order = await sdk.getOrder(orderId);
console.log(order.fulfilledPercent); // 100 = fully delegatedA complete runnable flow is in examples/buy-energy.ts (runs as a mocked
dry-run when TRONSAVE_API_KEY is unset):
TRONSAVE_API_KEY=xxx TRONSAVE_RECEIVER=Txxx npx tsx examples/buy-energy.tsConfiguration
new TronsaveSDK({
apiKey?: string; // internal-account API key (required for authenticated endpoints)
network?: 'mainnet' | 'testnet'; // default 'mainnet'
baseUrl?: string; // override the host (e.g. a proxy); takes precedence over `network`
fetch?: typeof fetch; // inject a custom fetch (for testing or non-Node runtimes)
timeoutMs?: number; // per-request timeout, default 30_000
maxRetries?: number; // transient-failure retries, default 3
rateLimit?: Record<string, { capacity: number; intervalMs: number }>; // per-path overrides
});The apiKey lives only on the instance and is never logged. Examples read it from
process.env.TRONSAVE_API_KEY.
Networks
| Network | API base URL |
| ------------------ | ----------------------------- |
| mainnet (default)| https://api.tronsave.io |
| testnet (Nile) | https://api-dev.tronsave.io |
Methods ↔ endpoints
| SDK method | HTTP | Endpoint | Auth |
| ----------------------------------- | ---- | ------------------------------------- | ---- |
| estimateBuyResource(params) | POST | /v2/estimate-buy-resource | optional |
| buyResource(params, options?) | POST | /v2/buy-resource | apikey / signedTx |
| getUserInfo() | GET | /v2/user-info | apikey |
| getOrder(id) | GET | /v2/order/:id | apikey |
| getOrders(params?) | GET | /v2/orders | apikey |
| getOrderBook(params?) | GET | /v2/order-book | apikey |
| getExtendableDelegates(params) | POST | /v2/get-extendable-delegates | apikey |
| extendRequest(params) | POST | /v2/extend-request | apikey / signedTx |
| getSignedTransaction(params) | POST | /v2/signed-tx | optional |
| listBuyerOrders(params?) ⚠️ | GET | /v2/internal/buyer/orders | whitelist |
| getMonthlyStats() ⚠️ | GET | /v2/internal/buyer/monthly-stats | whitelist |
⚠️ Whitelist-only endpoints require an API key from an address Tronsave has whitelisted.
Each method is also reachable through its resource module for tree-shaking:
sdk.orders.*, sdk.account.*, sdk.market.*, sdk.delegate.*.
Extending an order
const ext = await sdk.getExtendableDelegates({
receiver: 'TFwUFWr3QV376677Z8VWXxGUAMFSrq1MbM',
extendTo: Math.floor(Date.now() / 1000) + 86_400, // Unix seconds, +1 day
resourceType: 'ENERGY',
maxPriceAccepted: 165,
});
if (ext.isAbleToExtend) {
const { orderId } = await sdk.extendRequest({
receiver: 'TFwUFWr3QV376677Z8VWXxGUAMFSrq1MbM',
extendData: ext.extendData, // pass through unchanged
});
}Error handling
Every failure throws a subclass of TronsaveError carrying .code, .status, and .raw:
import {
TronsaveAuthError,
TronsaveValidationError,
TronsaveBusinessError,
TronsaveRateLimitError,
TronsaveNetworkError,
TronsaveErrorCode,
} from '@tronsave/sdk';
try {
await sdk.buyResource({ receiver: 'T...', resourceAmount: 32_000 });
} catch (err) {
if (err instanceof TronsaveBusinessError && err.code === TronsaveErrorCode.INTERNAL_BALANCE_ACCOUNT_TOO_LOW) {
// top up the internal account
} else if (err instanceof TronsaveRateLimitError) {
console.log('retry after', err.retryAfterMs, 'ms');
}
}| Class | Codes | HTTP |
| ------------------------- | --------------------------------------------------------------------- | ---- |
| TronsaveAuthError | API_KEY_REQUIRED, INVALID_API_KEY | 401 |
| TronsaveValidationError | MISSING_PARAMS, INVALID_PARAMS, MIN_PRICE_INVALID | 400 |
| TronsaveBusinessError | INTERNAL_ACCOUNT_NOT_FOUND, INTERNAL_BALANCE_ACCOUNT_TOO_LOW, CANNOT_FULFILLED, MUST_BE_WAIT_PREVIOUS_ORDER_FILLED, PRICE_EXCEED_MAX_PRICE_REQUIRED, SOME_DELEGATE_CANNOT_EXTEND | 400 |
| TronsaveRateLimitError | RATE_LIMIT (exposes retryAfterMs) | 429 |
| TronsaveNetworkError | NETWORK_ERROR (timeout / connection / unparseable response) | — |
Rate limits
Documented limits are 15 requests / second per endpoint, except
getExtendableDelegates at 1 / second. The SDK enforces these client-side with a token-bucket
limiter, transparently waiting when a bucket is empty so you stay within budget. Override per path
via the rateLimit config option.
Units
Amounts the API denominates in SUN are branded Sun (1 TRX = 1,000,000 SUN). Convert with the
exported helpers:
import { sunToTrx, trxToSun } from '@tronsave/sdk';
sunToTrx(6_144_000); // 6.144
trxToSun(1.5); // 1_500_000Signed-transaction (self-custody) flow
If you prefer not to hold TRX in Tronsave, sign a TRX transfer to the fund address and pass it as
signedTx to buyResource/extendRequest. The fund addresses are exported as
TRONSAVE_FUND_ADDRESS. Sign locally with TronWeb — the bundled getSignedTransaction helper
(which sends your private key to Tronsave) exists only for REST parity and is not recommended.
Scripts
| Script | Description |
| ---------------------- | -------------------------------------------- |
| npm run build | Build ESM + CJS + .d.ts (tsup) |
| npm run typecheck | tsc --noEmit (strict) |
| npm run lint | Biome lint + format check |
| npm run test | Vitest unit tests |
| npm run test:coverage| Tests with coverage (≥ 80% core/resources) |
| npm run docs | Generate TypeDoc API reference into docs/ |
License
MIT
