@shapeshiftoss/unchained-client 10.14.11 → 10.16.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.
Files changed (61) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/cosmossdk/thorchain/parser/__tests__/thorchain.test.js +1 -1
  3. package/dist/cjs/cosmossdk/thorchain/parser/rfox.js +1 -1
  4. package/dist/cjs/evm/arbitrum/parser/index.js +2 -2
  5. package/dist/cjs/evm/ethereum/parser/index.js +6 -0
  6. package/dist/cjs/generated/ethereum/apis/V1Api.d.ts +15 -0
  7. package/dist/cjs/generated/ethereum/apis/V1Api.js +24 -0
  8. package/dist/cjs/generated/mayachain/apis/V1Api.d.ts +1 -13
  9. package/dist/cjs/generated/mayachain/apis/V1Api.js +0 -27
  10. package/dist/cjs/generated/mayachain/models/index.d.ts +0 -2
  11. package/dist/cjs/generated/mayachain/models/index.js +0 -2
  12. package/dist/cjs/generated/thorchain/apis/V1Api.d.ts +1 -25
  13. package/dist/cjs/generated/thorchain/apis/V1Api.js +0 -54
  14. package/dist/cjs/generated/thorchain/models/index.d.ts +0 -3
  15. package/dist/cjs/generated/thorchain/models/index.js +0 -3
  16. package/dist/cjs/tron/__tests__/api.test.d.ts +1 -0
  17. package/dist/cjs/tron/__tests__/api.test.js +425 -0
  18. package/dist/cjs/tron/api.d.ts +42 -15
  19. package/dist/cjs/tron/api.js +222 -78
  20. package/dist/cjs/tron/types.d.ts +4 -0
  21. package/dist/esm/.tsbuildinfo +1 -1
  22. package/dist/esm/cosmossdk/thorchain/parser/__tests__/thorchain.test.js +2 -2
  23. package/dist/esm/cosmossdk/thorchain/parser/rfox.js +2 -2
  24. package/dist/esm/evm/arbitrum/parser/index.js +3 -3
  25. package/dist/esm/evm/ethereum/parser/index.js +7 -1
  26. package/dist/esm/generated/ethereum/apis/V1Api.d.ts +15 -0
  27. package/dist/esm/generated/ethereum/apis/V1Api.js +24 -0
  28. package/dist/esm/generated/mayachain/apis/V1Api.d.ts +1 -13
  29. package/dist/esm/generated/mayachain/apis/V1Api.js +1 -28
  30. package/dist/esm/generated/mayachain/models/index.d.ts +0 -2
  31. package/dist/esm/generated/mayachain/models/index.js +0 -2
  32. package/dist/esm/generated/thorchain/apis/V1Api.d.ts +1 -25
  33. package/dist/esm/generated/thorchain/apis/V1Api.js +1 -55
  34. package/dist/esm/generated/thorchain/models/index.d.ts +0 -3
  35. package/dist/esm/generated/thorchain/models/index.js +0 -3
  36. package/dist/esm/tron/__tests__/api.test.d.ts +1 -0
  37. package/dist/esm/tron/__tests__/api.test.js +423 -0
  38. package/dist/esm/tron/api.d.ts +42 -15
  39. package/dist/esm/tron/api.js +220 -77
  40. package/dist/esm/tron/types.d.ts +4 -0
  41. package/package.json +5 -5
  42. package/dist/cjs/generated/mayachain/models/AffiliateFee.d.ts +0 -67
  43. package/dist/cjs/generated/mayachain/models/AffiliateFee.js +0 -63
  44. package/dist/cjs/generated/mayachain/models/AffiliateFees.d.ts +0 -32
  45. package/dist/cjs/generated/mayachain/models/AffiliateFees.js +0 -52
  46. package/dist/cjs/generated/thorchain/models/AffiliateFee.d.ts +0 -67
  47. package/dist/cjs/generated/thorchain/models/AffiliateFee.js +0 -63
  48. package/dist/cjs/generated/thorchain/models/AffiliateFees.d.ts +0 -32
  49. package/dist/cjs/generated/thorchain/models/AffiliateFees.js +0 -52
  50. package/dist/cjs/generated/thorchain/models/AffiliateRevenue.d.ts +0 -45
  51. package/dist/cjs/generated/thorchain/models/AffiliateRevenue.js +0 -57
  52. package/dist/esm/generated/mayachain/models/AffiliateFee.d.ts +0 -67
  53. package/dist/esm/generated/mayachain/models/AffiliateFee.js +0 -56
  54. package/dist/esm/generated/mayachain/models/AffiliateFees.d.ts +0 -32
  55. package/dist/esm/generated/mayachain/models/AffiliateFees.js +0 -45
  56. package/dist/esm/generated/thorchain/models/AffiliateFee.d.ts +0 -67
  57. package/dist/esm/generated/thorchain/models/AffiliateFee.js +0 -56
  58. package/dist/esm/generated/thorchain/models/AffiliateFees.d.ts +0 -32
  59. package/dist/esm/generated/thorchain/models/AffiliateFees.js +0 -45
  60. package/dist/esm/generated/thorchain/models/AffiliateRevenue.d.ts +0 -45
  61. package/dist/esm/generated/thorchain/models/AffiliateRevenue.js +0 -50
@@ -1,11 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TronApi = void 0;
3
+ exports.TronApi = exports.getCallerEnergy = void 0;
4
+ const utils_1 = require("@shapeshiftoss/utils");
4
5
  const tronweb_1 = require("tronweb");
6
+ const PRECISION_READ_TIMEOUT_MS = 10_000;
7
+ const TRON_CALLER_PAYS_ALL = {
8
+ callerPercent: 100,
9
+ originEnergyLimit: 0,
10
+ originEnergyAvailable: 0,
11
+ };
12
+ // java-tron reads a stored zero, which TronGrid omits, as this creator default
13
+ const TRON_CREATOR_DEFAULT_ENERGY_LIMIT = 10_000_000;
14
+ // A deployer's unspent energy moves slowly next to how often the swappers re-estimate
15
+ const TRON_ORIGIN_ENERGY_TTL_MS = 15_000;
16
+ // A deployer can raise the caller's share after deployment, so the split is re-read within the minute
17
+ const TRON_CONTRACT_TTL_MS = 60_000;
18
+ // The deployer covers the rest only out of what they have staked, so a dry deployer (Tether) leaves the caller paying in full
19
+ const getCallerEnergy = (energyUsed, share) => {
20
+ const originShare = Math.floor((energyUsed * (100 - share.callerPercent)) / 100);
21
+ const originCovered = Math.max(0, Math.min(originShare, share.originEnergyLimit, share.originEnergyAvailable));
22
+ return energyUsed - originCovered;
23
+ };
24
+ exports.getCallerEnergy = getCallerEnergy;
5
25
  class TronApi {
6
26
  rpcUrl;
7
27
  apiKey;
8
28
  tronWeb = null;
29
+ contracts = new Map();
30
+ originEnergy = new Map();
9
31
  requestQueue = Promise.resolve();
10
32
  minRequestInterval = 1_500;
11
33
  constructor(config) {
@@ -98,9 +120,12 @@ class TronApi {
98
120
  }
99
121
  for (const contractAddress of Array.from(discoveredContracts).slice(0, MAX_FALLBACK_CONTRACTS)) {
100
122
  await this.throttle();
101
- const balance = await this.getTRC20Balance({ address: params.pubkey, contractAddress });
102
- if (balance !== '0')
103
- tokens.push({ contractAddress, balance });
123
+ await this.getTrc20Balance({ contractAddress, address: params.pubkey })
124
+ .then(balance => {
125
+ if (balance !== '0')
126
+ tokens.push({ contractAddress, balance });
127
+ })
128
+ .catch(() => undefined);
104
129
  }
105
130
  }
106
131
  catch (fallbackErr) {
@@ -121,17 +146,67 @@ class TronApi {
121
146
  tokens,
122
147
  };
123
148
  }
124
- async getTRC20Balance(params) {
149
+ async getTrc20Decimals(params) {
125
150
  try {
126
- const tronWeb = await this.getTronWeb();
127
- const contract = await tronWeb.contract().at(params.contractAddress);
128
- const balance = await contract.balanceOf(params.address).call();
129
- return balance.toString();
151
+ const result = await (0, utils_1.timeout)(this.getTronWeb().transactionBuilder.triggerConstantContract(params.contractAddress, 'decimals()', {}, [], params.contractAddress), PRECISION_READ_TIMEOUT_MS, undefined);
152
+ if (!result)
153
+ return;
154
+ const [decimalsHex] = result.constant_result ?? [];
155
+ if (this.isReverted(result) || !decimalsHex)
156
+ return;
157
+ // decimals() is a uint8; anything larger is a contract answering something else
158
+ const decimals = Number(BigInt(`0x${decimalsHex}`));
159
+ return decimals > 255 ? undefined : decimals;
130
160
  }
131
- catch (_err) {
132
- return '0';
161
+ catch (err) {
162
+ console.error(`[tron] failed to read decimals of ${params.contractAddress}`, err);
163
+ return;
164
+ }
165
+ }
166
+ async getTrc10Precision(params) {
167
+ try {
168
+ const data = await (0, utils_1.timeout)(fetch(`${this.rpcUrl}/v1/assets/${params.id}`, {
169
+ headers: this.tronGridHeaders,
170
+ signal: AbortSignal.timeout(PRECISION_READ_TIMEOUT_MS),
171
+ }).then(response => response.json()), PRECISION_READ_TIMEOUT_MS, undefined);
172
+ return data?.data?.[0]?.precision;
173
+ }
174
+ catch (err) {
175
+ console.error(`[tron] failed to read precision of trc10 ${params.id}`, err);
176
+ return;
133
177
  }
134
178
  }
179
+ async getTrc20Balance(params) {
180
+ const result = await this.getTronWeb().transactionBuilder.triggerConstantContract(params.contractAddress, 'balanceOf(address)', {}, [{ type: 'address', value: params.address }], params.address);
181
+ const [balance] = result.constant_result ?? [];
182
+ if (!balance)
183
+ throw new Error('[tron] balanceOf call returned no data');
184
+ return BigInt(`0x${balance}`).toString();
185
+ }
186
+ // A direct selector call - contract().at() would first fetch the ABI, doubling the requests
187
+ async getTrc20Allowance(params) {
188
+ const tronWeb = this.getTronWeb();
189
+ const result = await tronWeb.transactionBuilder.triggerConstantContract(params.contractAddress, 'allowance(address,address)', {}, [
190
+ { type: 'address', value: params.owner },
191
+ { type: 'address', value: params.spender },
192
+ ], params.owner);
193
+ const [allowance] = result.constant_result ?? [];
194
+ if (!allowance)
195
+ throw new Error('[tron] allowance call returned no data');
196
+ return BigInt(`0x${allowance}`).toString();
197
+ }
198
+ // Accounts exist on-chain only once they have received TRX; a fresh address returns {}
199
+ async isAccountActivated(address) {
200
+ const response = await fetch(`${this.rpcUrl}/wallet/getaccount`, {
201
+ method: 'POST',
202
+ headers: this.tronGridHeaders,
203
+ body: JSON.stringify({ address, visible: true }),
204
+ });
205
+ if (!response.ok)
206
+ throw new Error(`[tron] getaccount failed: ${response.status}`);
207
+ const data = await response.json();
208
+ return !!data.address;
209
+ }
135
210
  getTxHistory(_params) {
136
211
  throw new Error('Transaction history is not supported for TRON');
137
212
  }
@@ -149,13 +224,11 @@ class TronApi {
149
224
  body: JSON.stringify({ value: params.txid, visible: true }),
150
225
  }),
151
226
  ]);
152
- if (!txResponse.ok) {
227
+ if (!txResponse.ok)
153
228
  return null;
154
- }
155
229
  const tx = await txResponse.json();
156
- if (!tx || !tx.txID) {
230
+ if (!tx || !tx.txID)
157
231
  return null;
158
- }
159
232
  let blockNumber = 0;
160
233
  let blockTimeStamp = 0;
161
234
  let fee = '0';
@@ -189,9 +262,8 @@ class TronApi {
189
262
  headers: this.tronGridHeaders,
190
263
  body: JSON.stringify({ num: params.height }),
191
264
  });
192
- if (!response.ok) {
265
+ if (!response.ok)
193
266
  return null;
194
- }
195
267
  return await response.json();
196
268
  }
197
269
  async sendTx(params) {
@@ -203,9 +275,8 @@ class TronApi {
203
275
  throw new Error(result.message || JSON.stringify(result) || 'Failed to broadcast transaction');
204
276
  }
205
277
  const txid = result.txid || result.transaction?.txID;
206
- if (!txid) {
278
+ if (!txid)
207
279
  throw new Error('Transaction ID not found in broadcast result');
208
- }
209
280
  return txid;
210
281
  }
211
282
  catch (error) {
@@ -213,70 +284,143 @@ class TronApi {
213
284
  }
214
285
  }
215
286
  async getChainPrices() {
216
- try {
217
- const tronWeb = await this.getTronWeb();
218
- const params = await tronWeb.trx.getChainParameters();
219
- const bandwidthPrice = params.find(p => p.key === 'getTransactionFee')?.value ?? 1000;
220
- const energyPrice = params.find(p => p.key === 'getEnergyFee')?.value ?? 420;
221
- return { bandwidthPrice, energyPrice };
222
- }
223
- catch (_err) {
224
- return { bandwidthPrice: 1000, energyPrice: 420 };
225
- }
287
+ const params = await this.getTronWeb().trx.getChainParameters();
288
+ const param = (key) => {
289
+ const value = params.find(p => p.key === key)?.value;
290
+ if (value === undefined)
291
+ throw new Error(`[tron] chain parameter ${key} missing`);
292
+ return value;
293
+ };
294
+ return {
295
+ bandwidthPrice: param('getTransactionFee'),
296
+ energyPrice: param('getEnergyFee'),
297
+ memoFee: param('getMemoFee'),
298
+ };
226
299
  }
227
- async estimateFees(params) {
228
- try {
229
- const { bandwidthPrice } = await this.getChainPrices();
230
- const rawDataBytes = Buffer.from(params.estimateFeesBody.serializedTx, 'hex').length;
231
- const signatureBytes = 65;
232
- const totalBytes = rawDataBytes + signatureBytes;
233
- const feeInSun = totalBytes * bandwidthPrice;
234
- return String(feeInSun);
235
- }
236
- catch (err) {
237
- throw new Error(`Failed to estimate fees: ${err}`);
238
- }
300
+ // A plain address answers getcontract with {} and pays in full
301
+ async getContractEnergyShare(contractAddress) {
302
+ const contract = await this.getContract(contractAddress);
303
+ if (!contract.origin_address)
304
+ return TRON_CALLER_PAYS_ALL;
305
+ // TronGrid omits zero-valued fields, so a contract whose deployer pays everything carries no percent at all
306
+ const callerPercent = contract.consume_user_resource_percent ?? 0;
307
+ if (callerPercent >= 100)
308
+ return TRON_CALLER_PAYS_ALL;
309
+ return {
310
+ callerPercent,
311
+ originEnergyLimit: contract.origin_energy_limit || TRON_CREATOR_DEFAULT_ENERGY_LIMIT,
312
+ originEnergyAvailable: await this.getOriginEnergyAvailable(contract.origin_address),
313
+ };
239
314
  }
240
- async estimateTRC20TransferFee(params) {
241
- try {
242
- const tronWeb = await this.getTronWeb();
243
- const { energyPrice } = await this.getChainPrices();
244
- const result = await tronWeb.transactionBuilder.triggerConstantContract(params.contractAddress, 'transfer(address,uint256)', {}, [
245
- { type: 'address', value: params.to },
246
- { type: 'uint256', value: params.amount },
247
- ], params.from);
248
- const energyUsed = result.energy_used ?? 0;
249
- const feeInSun = energyUsed * energyPrice;
250
- return String(feeInSun);
251
- }
252
- catch (_err) {
253
- // Fallback: Worst case 130k energy at current 100 sun/energy
254
- return '13000000'; // 13 TRX (more realistic than 31 TRX)
255
- }
315
+ getContract(contractAddress) {
316
+ const cached = this.contracts.get(contractAddress);
317
+ if (cached && Date.now() - cached.readAt < TRON_CONTRACT_TTL_MS)
318
+ return cached.value;
319
+ const value = this.post('/wallet/getcontract', {
320
+ value: contractAddress,
321
+ visible: true,
322
+ })
323
+ .then(body => {
324
+ if (body.Error)
325
+ throw new Error(`[tron] getcontract failed: ${body.Error}`);
326
+ // only a contract record is worth keeping; an empty body is re-read next time
327
+ if (!body.origin_address && this.contracts.get(contractAddress)?.value === value) {
328
+ this.contracts.delete(contractAddress);
329
+ }
330
+ return body;
331
+ })
332
+ .catch(err => {
333
+ if (this.contracts.get(contractAddress)?.value === value) {
334
+ this.contracts.delete(contractAddress);
335
+ }
336
+ throw err;
337
+ });
338
+ this.contracts.set(contractAddress, { readAt: Date.now(), value });
339
+ return value;
256
340
  }
257
- async getPriorityFees() {
258
- try {
259
- const { bandwidthPrice } = await this.getChainPrices();
260
- const estimatedBytes = 268;
261
- const baseFee = String(estimatedBytes * bandwidthPrice);
262
- return {
263
- baseFee,
264
- fast: baseFee,
265
- average: baseFee,
266
- slow: baseFee,
267
- estimatedBandwidth: String(estimatedBytes),
268
- };
341
+ // A failed share lookup prices the simulation as if the caller paid in full, unless the caller must not guess
342
+ async getPricingContext(contractAddress, requireEnergyShare = false) {
343
+ const [{ energyPrice }, share] = await Promise.all([
344
+ this.getChainPrices(),
345
+ this.getContractEnergyShare(contractAddress).catch((error) => {
346
+ if (requireEnergyShare)
347
+ throw error;
348
+ console.warn(`[tron] energy share lookup failed for ${contractAddress}, pricing in full`, error);
349
+ return TRON_CALLER_PAYS_ALL;
350
+ }),
351
+ ]);
352
+ return { energyPrice, share };
353
+ }
354
+ getOriginEnergyAvailable(originAddress) {
355
+ const cached = this.originEnergy.get(originAddress);
356
+ if (cached && Date.now() - cached.readAt < TRON_ORIGIN_ENERGY_TTL_MS)
357
+ return cached.value;
358
+ const value = this.post('/wallet/getaccountresource', { address: originAddress, visible: true })
359
+ .then(body => {
360
+ if (body.Error)
361
+ throw new Error(`[tron] getaccountresource failed: ${body.Error}`);
362
+ return Math.max(0, (body.EnergyLimit ?? 0) - (body.EnergyUsed ?? 0));
363
+ })
364
+ .catch(err => {
365
+ if (this.originEnergy.get(originAddress)?.value === value) {
366
+ this.originEnergy.delete(originAddress);
367
+ }
368
+ throw err;
369
+ });
370
+ this.originEnergy.set(originAddress, { readAt: Date.now(), value });
371
+ return value;
372
+ }
373
+ async post(path, body) {
374
+ const response = await fetch(`${this.rpcUrl}${path}`, {
375
+ method: 'POST',
376
+ headers: this.tronGridHeaders,
377
+ body: JSON.stringify(body),
378
+ });
379
+ if (!response.ok)
380
+ throw new Error(`[tron] ${path} failed: ${response.status}`);
381
+ return response.json();
382
+ }
383
+ async estimateTrc20TransferFee(params) {
384
+ const tronWeb = this.getTronWeb();
385
+ const { energyPrice, share } = await this.getPricingContext(params.contractAddress, params.requireEnergyShare);
386
+ const result = await tronWeb.transactionBuilder.triggerConstantContract(params.contractAddress, 'transfer(address,uint256)', {}, [
387
+ { type: 'address', value: params.to },
388
+ { type: 'uint256', value: params.amount },
389
+ ], params.from);
390
+ const energy = this.getSimulatedEnergy(result, 'trc20 transfer');
391
+ return String((0, exports.getCallerEnergy)(energy, share) * energyPrice);
392
+ }
393
+ async estimateContractCallFee(params) {
394
+ const { energyPrice, share } = await this.getPricingContext(params.contractAddress, params.requireEnergyShare);
395
+ const response = await fetch(`${this.rpcUrl}/wallet/triggerconstantcontract`, {
396
+ method: 'POST',
397
+ headers: { 'Content-Type': 'application/json', ...this.tronGridHeaders },
398
+ body: JSON.stringify({
399
+ owner_address: params.from,
400
+ contract_address: params.contractAddress,
401
+ data: params.data.startsWith('0x') ? params.data.slice(2) : params.data,
402
+ call_value: Number(params.callValue) || 0,
403
+ visible: true,
404
+ }),
405
+ });
406
+ if (!response.ok) {
407
+ throw new Error(`[tron] contract call simulation request failed: ${response.status}`);
269
408
  }
270
- catch (_err) {
271
- const defaultFee = '268000';
272
- return {
273
- baseFee: defaultFee,
274
- fast: defaultFee,
275
- average: defaultFee,
276
- slow: defaultFee,
277
- estimatedBandwidth: '268',
278
- };
409
+ const result = await response.json();
410
+ const energy = this.getSimulatedEnergy(result, 'contract call');
411
+ return String((0, exports.getCallerEnergy)(energy, share) * energyPrice);
412
+ }
413
+ // A revert still reports result.result: true - the failure is only visible on the transaction's ret
414
+ isReverted(result) {
415
+ const failed = result.transaction?.ret?.some(ret => ret.ret === 'FAILED');
416
+ return result.result?.result !== true || Boolean(failed);
417
+ }
418
+ // Trusting a revert's partial energy would underestimate
419
+ getSimulatedEnergy(result, label) {
420
+ if (this.isReverted(result) || !result.energy_used) {
421
+ throw new Error(`[tron] ${label} simulation failed: ${result.result?.message ?? 'unknown error'}`);
279
422
  }
423
+ return result.energy_used;
280
424
  }
281
425
  }
282
426
  exports.TronApi = TronApi;
@@ -29,6 +29,9 @@ export interface TronTx {
29
29
  amount?: number;
30
30
  owner_address?: string;
31
31
  to_address?: string;
32
+ contract_address?: string;
33
+ call_value?: number;
34
+ data?: string;
32
35
  };
33
36
  type_url: string;
34
37
  };
@@ -38,6 +41,7 @@ export interface TronTx {
38
41
  ref_block_hash: string;
39
42
  expiration: number;
40
43
  timestamp: number;
44
+ fee_limit?: number;
41
45
  };
42
46
  raw_data_hex: string;
43
47
  txID: string;