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