@stellar-expert/tx-meta-effects-parser 7.0.0-rc.9 → 7.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/src/index.js CHANGED
@@ -1,234 +1,227 @@
1
- const {TransactionBuilder, xdr} = require('@stellar/stellar-base')
2
- const {TxMetaEffectParserError, UnexpectedTxMetaChangeError} = require('./errors')
3
- const {processFeeChargedEffect, analyzeOperationEffects, EffectsAnalyzer} = require('./effects-analyzer')
4
- const {disposeSacCache} = require('./aggregation/sac-contract-mapper')
5
- const {parseTxResult} = require('./parser/tx-result-parser')
6
- const {parseLedgerEntryChanges} = require('./parser/ledger-entry-changes-parser')
7
- const {parseTxMetaChanges} = require('./parser/tx-meta-changes-parser')
8
- const {analyzeSignerChanges} = require('./aggregation/signer-changes-analyzer')
9
- const contractPreimageEncoder = require('./parser/contract-preimage-encoder')
10
- const xdrParserUtils = require('./parser/tx-xdr-parser-utils')
11
- const effectTypes = require('./effect-types')
12
-
13
- /**
14
- * Retrieve effects from transaction execution result metadata
15
- * @param {String} network - Network passphrase
16
- * @param {String|Buffer|xdr.TransactionEnvelope} tx - Base64-encoded tx envelope xdr
17
- * @param {String|Buffer|xdr.TransactionResult} [result] - Base64-encoded tx envelope result
18
- * @param {String|Buffer|xdr.TransactionMeta} [meta] - Base64-encoded tx envelope meta
19
- * @param {Boolean} [mapSac] - Whether to create a map SAC->Asset
20
- * @param {Boolean} [processSystemEvents] - Emit effects for contract errors and resource stats
21
- * @param {Boolean} [processFailedOpEffects] - Whether to generate operation effects for failed/rejected transactions
22
- * @param {Boolean} [processMetrics] - Process invocation metrics emitted by Soroban
23
- * @param {Number} [protocol] - Specific Stellar protocol version for the executed transaction
24
- * @return {ParsedTxOperationsMetadata}
25
- */
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
- }) {
37
- if (!network)
38
- throw new TypeError(`Network passphrase argument is required.`)
39
- if (typeof network !== 'string')
40
- throw new TypeError(`Invalid network passphrase: "${network}".`)
41
- if (!tx)
42
- throw new TypeError(`Transaction envelope argument is required.`)
43
- if (processMetrics !== false)
44
- processMetrics = true
45
- const isEphemeral = !meta
46
- //parse tx, result, and meta xdr
47
- try {
48
- tx = ensureXdrInputType(tx, xdr.TransactionEnvelope)
49
- } catch (e) {
50
- throw new TxMetaEffectParserError('Invalid transaction envelope XDR. ' + e.message)
51
- }
52
- if (!isEphemeral) {
53
- try {
54
- result = ensureXdrInputType(result, xdr.TransactionResult)
55
- } catch (e) {
56
- try {
57
- const pair = ensureXdrInputType(result, xdr.TransactionResultPair)
58
- result = pair.result()
59
- } catch {
60
- throw new TxMetaEffectParserError('Invalid transaction result XDR. ' + e.message)
61
- }
62
- }
63
- }
64
- tx = TransactionBuilder.fromXDR(tx, network)
65
-
66
- let parsedTx = tx
67
- let parsedResult = result
68
-
69
- const isFeeBump = !!parsedTx.innerTransaction
70
- let feeBumpSuccess
71
-
72
- const res = {
73
- tx,
74
- isEphemeral
75
- }
76
-
77
- //take inner transaction if parsed tx is a fee bump tx
78
- if (isFeeBump) {
79
- parsedTx = parsedTx.innerTransaction
80
- if (parsedTx.innerTransaction)
81
- throw new TxMetaEffectParserError('Failed to process FeeBumpTransaction wrapped with another FeeBumpTransaction')
82
- if (!isEphemeral) {
83
- parsedResult = result.result().innerResultPair().result()
84
- feeBumpSuccess = parsedResult.result().switch().value >= 0
85
- }
86
- }
87
-
88
- //normalize operation source and effects container
89
- if (parsedTx.operations) {
90
- res.operations = parsedTx.operations
91
-
92
- for (const op of parsedTx.operations) {
93
- if (!op.source) {
94
- op.source = parsedTx.source
95
- }
96
- op.effects = []
97
- }
98
- }
99
-
100
- res.effects = []
101
-
102
- if (isEphemeral)
103
- return res //do not parse meta for unsubmitted/rejected transactions
104
-
105
- //process fee charge
106
- const feeEffect = processFeeChargedEffect(tx, tx.feeSource || parsedTx.source, result.feeCharged().toString(), isFeeBump)
107
- res.effects.push(feeEffect)
108
-
109
- //check execution result
110
- const {success, opResults} = parseTxResult(parsedResult)
111
- if (!success || isFeeBump && !feeBumpSuccess) {
112
- res.failed = true
113
- if (!processFailedOpEffects)
114
- return res
115
- }
116
-
117
- //retrieve operations result metadata
118
- try {
119
- meta = ensureXdrInputType(meta, xdr.TransactionMeta)
120
- } catch (e) {
121
- throw new TxMetaEffectParserError('Invalid transaction metadata XDR. ' + e.message)
122
- }
123
-
124
- //add tx-level effects
125
- for (const {before, after} of parseTxMetaChanges(meta)) {
126
- if (before.entry !== 'account')
127
- throw new UnexpectedTxMetaChangeError({type: before.entry, action: 'update'})
128
- for (const effect of analyzeSignerChanges(before, after)) {
129
- effect.source = (before || after).address
130
- res.effects.push(effect)
131
- }
132
- if (isFeeBump && protocol === 20 && before.balance !== after.balance) { //bump fee calculation bug in protocol v20
133
- const currentFee = BigInt(feeEffect.charged)
134
- const diff = BigInt(after.balance) - BigInt(before.balance)
135
- if (diff < currentFee) { // do not allow negative fee
136
- feeEffect.charged = (currentFee - diff).toString()
137
- }
138
- }
139
- }
140
- const metaValue = meta.value()
141
- const opMeta = metaValue.operations()
142
- const isV4Meta = meta.arm() === 'v4'
143
- let txEvents = isV4Meta ? metaValue.events() : undefined
144
-
145
- //analyze operation effects for each operation
146
- for (let i = 0; i < parsedTx.operations.length; i++) {
147
- const operation = parsedTx.operations[i]
148
- if (success || processFailedOpEffects) {
149
- const params = {
150
- network,
151
- operation,
152
- meta: opMeta[i]?.changes() || [],
153
- result: opResults[i],
154
- processFailedOpEffects,
155
- processMetrics
156
- }
157
- const isSorobanInvocation = operation.type === 'invokeHostFunction'
158
- //only for Soroban contract invocation
159
- if (isSorobanInvocation) {
160
- const {sorobanMeta} = metaValue._attributes
161
- if (sorobanMeta) {
162
- if (sorobanMeta.events) {
163
- params.events = sorobanMeta.events()
164
- }
165
- if (sorobanMeta.diagnosticEvents) {
166
- params.diagnosticEvents = sorobanMeta.diagnosticEvents()
167
- }
168
- params.processSystemEvents = processSystemEvents
169
- }
170
- if (isV4Meta) {
171
- params.diagnosticEvents = metaValue.diagnosticEvents()
172
- const invocationOp = metaValue.operations()[0]
173
- if (invocationOp) {
174
- params.events = invocationOp.events()
175
- }
176
- }
177
- params.mapSac = mapSac
178
- }
179
- const analyzer = new EffectsAnalyzer(params)
180
- operation.effects = analyzer.analyze()
181
- if (analyzer.sacMap && !isEmptyObject(analyzer.sacMap)) {
182
- operation.sacMap = analyzer.sacMap
183
- }
184
- if (isSorobanInvocation) {
185
- analyzer.addFeeMetric(metaValue)
186
- }
187
- }
188
- }
189
- return res
190
- }
191
-
192
- /**
193
- * Convert base64/raw XDR representation to XDR type
194
- * @param {String|Buffer|Uint8Array|xdrType} value
195
- * @param xdrType
196
- * @return {xdrType|*}
197
- * @internal
198
- */
199
- function ensureXdrInputType(value, xdrType) {
200
- if (value?.toXDR) // duck-typing check XDR types
201
- return value
202
-
203
- if (!value || (typeof value !== 'string' && !(value instanceof Uint8Array)))
204
- throw new TypeError(`Invalid input format. Expected xdr.${xdrType.name} (raw, buffer, or bas64-encoded).`)
205
- return xdrType.fromXDR(value, typeof value === 'string' ? 'base64' : 'raw')
206
- }
207
-
208
- function isEmptyObject(obj) {
209
- for (const key in obj)
210
- return false
211
- return true
212
- }
213
-
214
- /**
215
- * @typedef {{}} ParsedTxOperationsMetadata
216
- * @property {Transaction|FeeBumpTransaction} tx - Parsed transaction object
217
- * @property {BaseOperation[]} operations - Transaction operations
218
- * @property {Boolean} isEphemeral - True for transactions without result metadata
219
- * @property {Boolean} [failed] - True for transactions failed during on-chain execution
220
- * @property {{}[]} [effects] - Top-level transaction effects (fee charges and )
221
- * @property {Object<String,String>} [sacMap] - Optional map of SAC->Asset
222
- */
223
-
224
- module.exports = {
225
- parseTxOperationsMeta,
226
- parseTxResult,
227
- analyzeOperationEffects,
228
- parseLedgerEntryChanges,
229
- parseTxMetaChanges,
230
- effectTypes,
231
- xdrParserUtils,
232
- contractPreimageEncoder,
233
- disposeSacCache
1
+ const {TransactionBuilder, xdr} = require('@stellar/stellar-base')
2
+ const {TxMetaEffectParserError, UnexpectedTxMetaChangeError} = require('./errors')
3
+ const {processFeeChargedEffect, analyzeOperationEffects, EffectsAnalyzer} = require('./effects-analyzer')
4
+ const {disposeSacCache} = require('./aggregation/sac-contract-mapper')
5
+ const {parseTxResult} = require('./parser/tx-result-parser')
6
+ const {parseLedgerEntryChanges} = require('./parser/ledger-entry-changes-parser')
7
+ const {parseTxMetaChanges} = require('./parser/tx-meta-changes-parser')
8
+ const {analyzeSignerChanges} = require('./aggregation/signer-changes-analyzer')
9
+ const contractPreimageEncoder = require('./parser/contract-preimage-encoder')
10
+ const xdrParserUtils = require('./parser/tx-xdr-parser-utils')
11
+ const effectTypes = require('./effect-types')
12
+
13
+ /**
14
+ * Retrieve effects from transaction execution result metadata
15
+ * @param {String} network - Network passphrase
16
+ * @param {String|Buffer|xdr.TransactionEnvelope} tx - Base64-encoded tx envelope xdr
17
+ * @param {String|Buffer|xdr.TransactionResult} [result] - Base64-encoded tx envelope result
18
+ * @param {String|Buffer|xdr.TransactionMeta} [meta] - Base64-encoded tx envelope meta
19
+ * @param {Boolean} [mapSac] - Whether to create a map SAC->Asset
20
+ * @param {Boolean} [processSystemEvents] - Emit effects for contract errors and resource stats
21
+ * @param {Boolean} [processFailedOpEffects] - Whether to generate operation effects for failed/rejected transactions
22
+ * @param {Boolean} [processMetrics] - Process invocation metrics emitted by Soroban
23
+ * @param {Number} [protocol] - Specific Stellar protocol version for the executed transaction
24
+ * @return {ParsedTxOperationsMetadata}
25
+ */
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
+ }) {
37
+ if (!network)
38
+ throw new TypeError(`Network passphrase argument is required.`)
39
+ if (typeof network !== 'string')
40
+ throw new TypeError(`Invalid network passphrase: "${network}".`)
41
+ if (!tx)
42
+ throw new TypeError(`Transaction envelope argument is required.`)
43
+ if (processMetrics !== false)
44
+ processMetrics = true
45
+ const isEphemeral = !meta
46
+ //parse tx, result, and meta xdr
47
+ try {
48
+ tx = ensureXdrInputType(tx, xdr.TransactionEnvelope)
49
+ } catch (e) {
50
+ throw new TxMetaEffectParserError('Invalid transaction envelope XDR. ' + e.message)
51
+ }
52
+ if (!isEphemeral) {
53
+ try {
54
+ result = ensureXdrInputType(result, xdr.TransactionResult)
55
+ } catch (e) {
56
+ try {
57
+ const pair = ensureXdrInputType(result, xdr.TransactionResultPair)
58
+ result = pair.result()
59
+ } catch {
60
+ throw new TxMetaEffectParserError('Invalid transaction result XDR. ' + e.message)
61
+ }
62
+ }
63
+ }
64
+ tx = TransactionBuilder.fromXDR(tx, network)
65
+
66
+ let parsedTx = tx
67
+ let parsedResult = result
68
+
69
+ const isFeeBump = !!parsedTx.innerTransaction
70
+ let feeBumpSuccess
71
+
72
+ const res = {
73
+ tx,
74
+ isEphemeral
75
+ }
76
+
77
+ //take inner transaction if parsed tx is a fee bump tx
78
+ if (isFeeBump) {
79
+ parsedTx = parsedTx.innerTransaction
80
+ if (parsedTx.innerTransaction)
81
+ throw new TxMetaEffectParserError('Failed to process FeeBumpTransaction wrapped with another FeeBumpTransaction')
82
+ if (!isEphemeral) {
83
+ parsedResult = result.result().innerResultPair().result()
84
+ feeBumpSuccess = parsedResult.result().switch().value >= 0
85
+ }
86
+ }
87
+
88
+ //normalize operation source and effects container
89
+ if (parsedTx.operations) {
90
+ res.operations = parsedTx.operations
91
+
92
+ for (const op of parsedTx.operations) {
93
+ if (!op.source) {
94
+ op.source = parsedTx.source
95
+ }
96
+ op.effects = []
97
+ }
98
+ }
99
+
100
+ res.effects = []
101
+
102
+ if (isEphemeral)
103
+ return res //do not parse meta for unsubmitted/rejected transactions
104
+
105
+ //process fee charge
106
+ const feeEffect = processFeeChargedEffect(tx, tx.feeSource || parsedTx.source, result.feeCharged().toString(), isFeeBump)
107
+ res.effects.push(feeEffect)
108
+
109
+ //check execution result
110
+ const {success, opResults} = parseTxResult(parsedResult)
111
+ if (!success || isFeeBump && !feeBumpSuccess) {
112
+ res.failed = true
113
+ if (!processFailedOpEffects)
114
+ return res
115
+ }
116
+
117
+ //retrieve operations result metadata
118
+ try {
119
+ meta = ensureXdrInputType(meta, xdr.TransactionMeta)
120
+ } catch (e) {
121
+ throw new TxMetaEffectParserError('Invalid transaction metadata XDR. ' + e.message)
122
+ }
123
+
124
+ //add tx-level effects
125
+ for (const {before, after} of parseTxMetaChanges(meta)) {
126
+ if (before.entry !== 'account')
127
+ throw new UnexpectedTxMetaChangeError({type: before.entry, action: 'update'})
128
+ for (const effect of analyzeSignerChanges(before, after)) {
129
+ effect.source = (before || after).address
130
+ res.effects.push(effect)
131
+ }
132
+ if (isFeeBump && protocol === 20 && before.balance !== after.balance) { //bump fee calculation bug in protocol v20
133
+ const currentFee = BigInt(feeEffect.charged)
134
+ const diff = BigInt(after.balance) - BigInt(before.balance)
135
+ if (diff < currentFee) { // do not allow negative fee
136
+ feeEffect.charged = (currentFee - diff).toString()
137
+ }
138
+ }
139
+ }
140
+ const metaValue = meta.value()
141
+ const opMeta = metaValue.operations()
142
+ const isV4Meta = meta.arm() === 'v4'
143
+
144
+ //analyze operation effects for each operation
145
+ for (let i = 0; i < parsedTx.operations.length; i++) {
146
+ const operation = parsedTx.operations[i]
147
+ if (success || processFailedOpEffects) {
148
+ const params = {
149
+ network,
150
+ operation,
151
+ meta: opMeta[i]?.changes() || [],
152
+ result: opResults[i],
153
+ processFailedOpEffects,
154
+ processMetrics
155
+ }
156
+ const isSorobanInvocation = operation.type === 'invokeHostFunction'
157
+ //only for Soroban contract invocation
158
+ if (isSorobanInvocation) {
159
+ const {sorobanMeta} = metaValue._attributes
160
+ if (sorobanMeta) {
161
+ if (sorobanMeta.events) {
162
+ params.events = sorobanMeta.events()
163
+ }
164
+ if (sorobanMeta.diagnosticEvents) {
165
+ params.diagnosticEvents = sorobanMeta.diagnosticEvents()
166
+ }
167
+ params.processSystemEvents = processSystemEvents
168
+ }
169
+ if (isV4Meta) {
170
+ params.diagnosticEvents = metaValue.diagnosticEvents()
171
+ const invocationOp = metaValue.operations()[0]
172
+ if (invocationOp) {
173
+ params.events = invocationOp.events()
174
+ }
175
+ }
176
+ params.mapSac = mapSac
177
+ }
178
+ const analyzer = new EffectsAnalyzer(params)
179
+ operation.effects = analyzer.analyze()
180
+ if (analyzer.sacMap && analyzer.sacMap.size > 0) {
181
+ operation.sacMap = analyzer.sacMap
182
+ }
183
+ if (isSorobanInvocation) {
184
+ analyzer.addFeeMetric(metaValue)
185
+ }
186
+ }
187
+ }
188
+ return res
189
+ }
190
+
191
+ /**
192
+ * Convert base64/raw XDR representation to XDR type
193
+ * @param {String|Buffer|Uint8Array|xdrType} value
194
+ * @param xdrType
195
+ * @return {xdrType|*}
196
+ * @internal
197
+ */
198
+ function ensureXdrInputType(value, xdrType) {
199
+ if (value?.toXDR) // duck-typing check XDR types
200
+ return value
201
+
202
+ if (!value || (typeof value !== 'string' && !(value instanceof Uint8Array)))
203
+ throw new TypeError(`Invalid input format. Expected xdr.${xdrType.name} (raw, buffer, or bas64-encoded).`)
204
+ return xdrType.fromXDR(value, typeof value === 'string' ? 'base64' : 'raw')
205
+ }
206
+
207
+ /**
208
+ * @typedef {{}} ParsedTxOperationsMetadata
209
+ * @property {Transaction|FeeBumpTransaction} tx - Parsed transaction object
210
+ * @property {BaseOperation[]} operations - Transaction operations
211
+ * @property {Boolean} isEphemeral - True for transactions without result metadata
212
+ * @property {Boolean} [failed] - True for transactions failed during on-chain execution
213
+ * @property {{}[]} [effects] - Top-level transaction effects (fee charges and )
214
+ * @property {Object<String,String>} [sacMap] - Optional map of SAC->Asset
215
+ */
216
+
217
+ module.exports = {
218
+ parseTxOperationsMeta,
219
+ parseTxResult,
220
+ analyzeOperationEffects,
221
+ parseLedgerEntryChanges,
222
+ parseTxMetaChanges,
223
+ effectTypes,
224
+ xdrParserUtils,
225
+ contractPreimageEncoder,
226
+ disposeSacCache
234
227
  }