@stellar-expert/tx-meta-effects-parser 6.0.2 → 6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stellar-expert/tx-meta-effects-parser",
3
- "version": "6.0.2",
3
+ "version": "6.1.0",
4
4
  "description": "Low-level effects parser for Stellar transaction results and meta XDR",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -9,7 +9,7 @@
9
9
  "author": "team@stellar.expert",
10
10
  "license": "MIT",
11
11
  "peerDependencies": {
12
- "@stellar/stellar-base": "^12.1.0"
12
+ "@stellar/stellar-base": "^12.1.1"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@babel/core": "^7.22.9",
@@ -61,13 +61,13 @@ class EventsAnalyzer {
61
61
  * @private
62
62
  */
63
63
  analyzeDiagnosticEvents() {
64
- const {diagnosticEvents, processSystemEvents} = this.effectsAnalyzer
64
+ const {diagnosticEvents, processSystemEvents, processMetrics, processFailedOpEffects} = this.effectsAnalyzer
65
65
  if (!diagnosticEvents)
66
66
  return
67
67
  const opContractId = this.effectsAnalyzer.retrieveOpContractId()
68
68
  //diagnostic events
69
69
  for (const evt of diagnosticEvents) {
70
- if (!processSystemEvents && !(evt.inSuccessfulContractCall() || this.effectsAnalyzer.processFailedOpEffects))
70
+ if (!processSystemEvents && !(processFailedOpEffects || evt.inSuccessfulContractCall()))
71
71
  continue //throw new UnexpectedTxMetaChangeError({type: 'diagnostic_event', action: 'failed'})
72
72
  //parse event
73
73
  const event = evt.event()
@@ -75,7 +75,7 @@ class EventsAnalyzer {
75
75
  if (contractId && typeof contractId !== 'string') {
76
76
  contractId = StrKey.encodeContract(contractId)
77
77
  }
78
- this.processDiagnosticEvent(event.body().value(), event.type().value, contractId)
78
+ this.processDiagnosticEvent(event._attributes.body._value, event._attributes.type.value, contractId, processMetrics)
79
79
  }
80
80
  }
81
81
 
@@ -83,9 +83,10 @@ class EventsAnalyzer {
83
83
  * @param {xdr.ContractEventV0} body
84
84
  * @param {Number} type
85
85
  * @param {String} contract
86
+ * @param {Boolean} processMetrics
86
87
  * @private
87
88
  */
88
- processDiagnosticEvent(body, type, contract) {
89
+ processDiagnosticEvent(body, type, contract, processMetrics) {
89
90
  const topics = body.topics()
90
91
  if (!topics?.length)
91
92
  return
@@ -121,16 +122,22 @@ class EventsAnalyzer {
121
122
  case 'error':
122
123
  if (type !== EVENT_TYPES.DIAGNOSTIC)
123
124
  return // skip non-diagnostic events
125
+ let code = topics[1].value().value()
126
+ if (code.name) {
127
+ code = code.name
128
+ }
124
129
  this.effectsAnalyzer.addEffect({
125
130
  type: effectTypes.contractError,
126
131
  contract,
127
- code: topics[1].value().value(),
132
+ code,
128
133
  details: processEventBodyValue(body.data())
129
134
  })
130
135
  break
131
136
  case 'core_metrics':
132
137
  if (type !== EVENT_TYPES.DIAGNOSTIC)
133
138
  return // skip non-diagnostic events
139
+ if (!processMetrics)
140
+ return
134
141
  this.effectsAnalyzer.addMetric(contract, xdrParseScVal(topics[1]), parseInt(processEventBodyValue(body.data())))
135
142
  break
136
143
  //handle standard token contract events
@@ -11,7 +11,18 @@ const {UnexpectedTxMetaChangeError, TxMetaEffectParserError} = require('./errors
11
11
  const {generateContractCodeEntryHash} = require('./parser/ledger-key')
12
12
 
13
13
  class EffectsAnalyzer {
14
- constructor({operation, meta, result, network, events, diagnosticEvents, mapSac, processSystemEvents, processFailedOpEffects}) {
14
+ constructor({
15
+ operation,
16
+ meta,
17
+ result,
18
+ network,
19
+ events,
20
+ diagnosticEvents,
21
+ mapSac,
22
+ processSystemEvents,
23
+ processFailedOpEffects,
24
+ processMetrics
25
+ }) {
15
26
  //set execution context
16
27
  if (!operation.source)
17
28
  throw new TxMetaEffectParserError('Operation source is not explicitly defined')
@@ -22,6 +33,7 @@ class EffectsAnalyzer {
22
33
  this.source = this.operation.source
23
34
  this.events = events
24
35
  this.processFailedOpEffects = processFailedOpEffects
36
+ this.processMetrics = processMetrics
25
37
  if (diagnosticEvents?.length) {
26
38
  this.diagnosticEvents = diagnosticEvents
27
39
  if (processSystemEvents) {
@@ -84,6 +96,11 @@ class EffectsAnalyzer {
84
96
  * @readonly
85
97
  */
86
98
  processSystemEvents = false
99
+ /**
100
+ * @type {Boolean}
101
+ * @readonly
102
+ */
103
+ processMetrics = true
87
104
  /**
88
105
  * @type {{}}
89
106
  * @private
@@ -106,6 +123,10 @@ class EffectsAnalyzer {
106
123
  this.processSponsorshipEffects()
107
124
  //calculate minted/burned assets
108
125
  new AssetSupplyAnalyzer(this).analyze()
126
+ //add Soroban op metrics if available
127
+ if (this.metrics) {
128
+ this.addEffect(this.metrics)
129
+ }
109
130
  return this.effects
110
131
  }
111
132
 
@@ -180,7 +201,6 @@ class EffectsAnalyzer {
180
201
  type: effectTypes.contractMetrics,
181
202
  contract
182
203
  }
183
- this.addEffect(metrics)
184
204
  }
185
205
  metrics[metric] = value
186
206
  }
@@ -200,7 +220,10 @@ class EffectsAnalyzer {
200
220
 
201
221
  setOptions() {
202
222
  const sourceAccount = normalizeAddress(this.source)
203
- const {before, after} = this.changes.find(ch => ch.type === 'account' && ch.before.address === sourceAccount)
223
+ const change = this.changes.find(ch => ch.type === 'account' && ch.before.address === sourceAccount)
224
+ if (!change)
225
+ return // failed tx or no changes
226
+ const {before, after} = change
204
227
  if (before.homeDomain !== after.homeDomain) {
205
228
  this.addEffect({
206
229
  type: effectTypes.accountHomeDomainUpdated,
@@ -236,34 +259,34 @@ class EffectsAnalyzer {
236
259
  if (!this.changes.length)
237
260
  return
238
261
  const trustAsset = xdrParseAsset(this.operation.asset || {code: this.operation.assetCode, issuer: normalizeAddress(this.source)})
239
- const trustlineChange = this.changes.find(ch => ch.type === 'trustline' && ch.before.asset === trustAsset)
240
- if (trustlineChange) {
241
- if (trustlineChange.action !== 'updated')
242
- throw new UnexpectedTxMetaChangeError(trustlineChange)
243
- const {before, after} = trustlineChange
244
- if (before.flags !== after.flags) {
262
+ const change = this.changes.find(ch => ch.type === 'trustline' && ch.before.asset === trustAsset)
263
+ if (!change)
264
+ return
265
+ if (change.action !== 'updated')
266
+ throw new UnexpectedTxMetaChangeError(change)
267
+ const {before, after} = change
268
+ if (before.flags !== after.flags) {
269
+ this.addEffect({
270
+ type: effectTypes.trustlineAuthorizationUpdated,
271
+ trustor: this.operation.trustor,
272
+ asset: after.asset,
273
+ flags: after.flags,
274
+ prevFlags: before.flags
275
+ })
276
+ for (const change of this.changes) {
277
+ if (change.type !== 'liquidityPool')
278
+ continue
279
+ const {before, after} = change
245
280
  this.addEffect({
246
- type: effectTypes.trustlineAuthorizationUpdated,
247
- trustor: this.operation.trustor,
248
- asset: after.asset,
249
- flags: after.flags,
250
- prevFlags: before.flags
281
+ type: effectTypes.liquidityPoolWithdrew,
282
+ source: this.operation.trustor,
283
+ pool: before.pool,
284
+ assets: before.asset.map((asset, i) => ({
285
+ asset,
286
+ amount: (BigInt(before.amount[i]) - (after ? BigInt(after.amount[i]) : 0n)).toString()
287
+ })),
288
+ shares: (BigInt(before.shares) - (after ? BigInt(after.shares) : 0n)).toString()
251
289
  })
252
- for (const change of this.changes) {
253
- if (change.type !== 'liquidityPool')
254
- continue
255
- const {before, after} = change
256
- this.addEffect({
257
- type: effectTypes.liquidityPoolWithdrew,
258
- source: this.operation.trustor,
259
- pool: before.pool,
260
- assets: before.asset.map((asset, i) => ({
261
- asset,
262
- amount: (BigInt(before.amount[i]) - (after ? BigInt(after.amount[i]) : 0n)).toString()
263
- })),
264
- shares: (BigInt(before.shares) - (after ? BigInt(after.shares) : 0n)).toString()
265
- })
266
- }
267
290
  }
268
291
  }
269
292
  }
@@ -281,7 +304,10 @@ class EffectsAnalyzer {
281
304
  bumpSequence() {
282
305
  if (!this.changes.length)
283
306
  return
284
- const {before, after} = this.changes.find(ch => ch.type === 'account')
307
+ const change = this.changes.find(ch => ch.type === 'account')
308
+ if (!change)
309
+ return //failed tx or no changes
310
+ const {before, after} = change
285
311
  if (before.sequence !== after.sequence) {
286
312
  this.addEffect({
287
313
  type: effectTypes.sequenceBumped,
@@ -312,10 +338,10 @@ class EffectsAnalyzer {
312
338
 
313
339
  liquidityPoolDeposit() {
314
340
  const {liquidityPoolId} = this.operation
315
- const poolUpdatedChanges = this.changes.find(ch => ch.type === 'liquidityPool' && ch.action === 'updated' && ch.after.pool === liquidityPoolId)
316
- if (!poolUpdatedChanges) //tx failed
341
+ const change = this.changes.find(ch => ch.type === 'liquidityPool' && ch.action === 'updated' && ch.after.pool === liquidityPoolId)
342
+ if (!change) //tx failed
317
343
  return
318
- const {before, after} = poolUpdatedChanges
344
+ const {before, after} = change
319
345
  this.addEffect({
320
346
  type: effectTypes.liquidityPoolDeposited,
321
347
  pool: this.operation.liquidityPoolId,
@@ -329,7 +355,10 @@ class EffectsAnalyzer {
329
355
 
330
356
  liquidityPoolWithdraw() {
331
357
  const pool = this.operation.liquidityPoolId
332
- const {before, after} = this.changes.find(ch => ch.type === 'liquidityPool' && ch.action === 'updated' && ch.before.pool === pool)
358
+ const change = this.changes.find(ch => ch.type === 'liquidityPool' && ch.action === 'updated' && ch.before.pool === pool)
359
+ if (!change) //tx failed
360
+ return
361
+ const {before, after} = change
333
362
  this.addEffect({
334
363
  type: effectTypes.liquidityPoolWithdrew,
335
364
  pool,
package/src/index.js CHANGED
@@ -19,16 +19,29 @@ const effectTypes = require('./effect-types')
19
19
  * @param {Boolean} [mapSac] - Whether to create a map SAC->Asset
20
20
  * @param {Boolean} [processSystemEvents] - Emit effects for contract errors and resource stats
21
21
  * @param {Boolean} [processFailedOpEffects] - Whether to generate operation effects for failed/rejected transactions
22
+ * @param {Boolean} [processMetrics] - Process invocation metrics emitted by Soroban
22
23
  * @param {Number} [protocol] - Specific Stellar protocol version for the executed transaction
23
24
  * @return {ParsedTxOperationsMetadata}
24
25
  */
25
- function parseTxOperationsMeta({network, tx, result, meta, mapSac = false, processSystemEvents = false, processFailedOpEffects = false, protocol}) {
26
+ function parseTxOperationsMeta({
27
+ network,
28
+ tx,
29
+ result,
30
+ meta,
31
+ mapSac = false,
32
+ processSystemEvents = false,
33
+ processFailedOpEffects = false,
34
+ processMetrics,
35
+ protocol
36
+ }) {
26
37
  if (!network)
27
38
  throw new TypeError(`Network passphrase argument is required.`)
28
39
  if (typeof network !== 'string')
29
40
  throw new TypeError(`Invalid network passphrase: "${network}".`)
30
41
  if (!tx)
31
42
  throw new TypeError(`Transaction envelope argument is required.`)
43
+ if (processMetrics !== false)
44
+ processMetrics = true
32
45
  const isEphemeral = !meta
33
46
  //parse tx, result, and meta xdr
34
47
  try {
@@ -132,10 +145,12 @@ function parseTxOperationsMeta({network, tx, result, meta, mapSac = false, proce
132
145
  const operation = parsedTx.operations[i]
133
146
  if (success || processFailedOpEffects) {
134
147
  const params = {
148
+ network,
135
149
  operation,
136
150
  meta: opMeta[i]?.changes() || [],
137
- result: opResults[i], network,
138
- processFailedOpEffects
151
+ result: opResults[i],
152
+ processFailedOpEffects,
153
+ processMetrics
139
154
  }
140
155
  const isSorobanInvocation = operation.type === 'invokeHostFunction'
141
156
  //only for Soroban contract invocation
@@ -280,8 +280,16 @@ function xdrParseScVal(value, treatBytesAsContractId = false) {
280
280
  case 'contractId':
281
281
  return xdrParseContractAddress(value._value)
282
282
  default:
283
- if (value.switch().name === 'scvVoid') //scVoid
284
- return undefined
283
+ switch (value._switch.name) {
284
+ case 'scvVoid':
285
+ return undefined
286
+ case 'scvContractInstance':
287
+ return '<ContractInstance>'
288
+ case 'scvLedgerKeyContractInstance':
289
+ return '<LedgerKeyContractInstance>'
290
+ case 'scvLedgerKeyNonce':
291
+ return '<LedgerKeyNonce>'
292
+ }
285
293
  throw new TxMetaEffectParserError('Not supported XDR primitive type: ' + value.toXDR ? value.toXDR() : value.toString())
286
294
  }
287
295
  }