@stellar-expert/tx-meta-effects-parser 7.0.0-rc.1 → 7.0.0-rc.10

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": "7.0.0-rc.1",
3
+ "version": "7.0.0-rc.10",
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": "^14.0.0-rc.2"
12
+ "@stellar/stellar-base": "^14.0.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@babel/core": "^7.22.9",
@@ -1,4 +1,4 @@
1
- const {StrKey} = require('@stellar/stellar-base')
1
+ const {StrKey, encodeMuxedAccount, encodeMuxedAccountToAddress} = require('@stellar/stellar-base')
2
2
  const effectTypes = require('../effect-types')
3
3
  const {xdrParseScVal, xdrParseAsset, isContractAddress} = require('../parser/tx-xdr-parser-utils')
4
4
  const {mapSacContract} = require('./sac-contract-mapper')
@@ -146,15 +146,19 @@ class EventsAnalyzer {
146
146
  if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
147
147
  return
148
148
  const from = xdrParseScVal(topics[1])
149
- const to = xdrParseScVal(topics[2])
149
+ const receiver = xdrParseScVal(topics[2])
150
+ let to = receiver
151
+ let amount = processEventBodyValue(body.data())
152
+ if (amount?.amount !== undefined) {
153
+ if (amount.to_muxed_id && !to.startsWith('M')) {
154
+ to = encodeMuxedAccountToAddress(encodeMuxedAccount(to, amount.to_muxed_id))
155
+ amount = amount.amount
156
+ }
157
+ }
158
+ if (typeof amount!=='string')
159
+ return null
150
160
  if (to === from) //self transfer - nothing happens
151
161
  return // TODO: need additional checks
152
- const amount = processEventBodyValue(body.data())
153
- if (!this.matchInvocationEffect(e =>
154
- (e.function === 'transfer' && matchArrays([from, to, amount], e.args)) ||
155
- (e.function === 'transfer_from' && matchArrays([undefined, from, to, amount], e.args))
156
- ))
157
- return
158
162
  let classicAsset
159
163
  if (topics.length > 3) {
160
164
  classicAsset = xdrParseAsset(xdrParseScVal(topics[3]))
@@ -172,7 +176,7 @@ class EventsAnalyzer {
172
176
  if (isContractAddress(to)) {
173
177
  this.effectsAnalyzer.credit(amount, classicAsset, to)
174
178
  }
175
- if (classicAsset.includes(to)) { //SAC transfer by asset issuer
179
+ if (classicAsset.includes(receiver)) { //SAC transfer by asset issuer
176
180
  this.effectsAnalyzer.burn(classicAsset, amount)
177
181
  }
178
182
  } else { //other cases
@@ -182,14 +186,21 @@ class EventsAnalyzer {
182
186
  }
183
187
  break
184
188
  case 'mint': {
185
- if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
189
+ if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']) && !matchEventTopicsShape(topics, ['address', 'str?']))
186
190
  return //throw new Error('Non-standard event')
187
- const to = xdrParseScVal(topics[2])
188
- const amount = processEventBodyValue(body.data())
189
- if (!this.matchInvocationEffect(e => e.function === 'mint' && matchArrays([to, amount], e.args)))
190
- return
191
- if (topics.length > 3) {
192
- mapSacContract(this.effectsAnalyzer, contract, xdrParseAsset(xdrParseScVal(topics[3])))
191
+ let to = xdrParseScVal(topics[topics[2]?._arm === 'address' ? 2 : 1])
192
+ let amount = processEventBodyValue(body.data())
193
+ if (amount?.amount !== undefined) {
194
+ if (amount.to_muxed_id && !to.startsWith('M')) {
195
+ to = encodeMuxedAccountToAddress(encodeMuxedAccount(to, amount.to_muxed_id))
196
+ amount = amount.amount
197
+ }
198
+ }
199
+ if (typeof amount !== 'string')
200
+ return null
201
+ const last = topics[topics.length - 1]
202
+ if (last._arm === 'str') {
203
+ mapSacContract(this.effectsAnalyzer, contract, xdrParseAsset(xdrParseScVal(last)))
193
204
  }
194
205
  const asset = this.effectsAnalyzer.resolveAsset(contract)
195
206
  this.effectsAnalyzer.mint(asset, amount)
@@ -203,13 +214,8 @@ class EventsAnalyzer {
203
214
  return //throw new Error('Non-standard event')
204
215
  const from = xdrParseScVal(topics[1])
205
216
  const amount = processEventBodyValue(body.data())
206
- if (!amount)
207
- return
208
- if (!this.matchInvocationEffect(e =>
209
- (e.function === 'burn' && matchArrays([from, amount], e.args)) ||
210
- (e.function === 'burn_from' && matchArrays([undefined, from, amount], e.args))
211
- ))
212
- return
217
+ if (typeof amount !== 'string')
218
+ return null
213
219
  if (topics.length > 2) {
214
220
  mapSacContract(this.effectsAnalyzer, contract, xdrParseAsset(xdrParseScVal(topics[2])))
215
221
  }
@@ -221,12 +227,12 @@ class EventsAnalyzer {
221
227
  }
222
228
  break
223
229
  case 'clawback': {
224
- if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
230
+ if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']) && !matchEventTopicsShape(topics, ['address', 'str?']))
225
231
  return //throw new Error('Non-standard event')
226
- const from = xdrParseScVal(topics[2])
232
+ const from = xdrParseScVal(topics[topics[2]?._arm === 'address' ? 2 : 1])
227
233
  const amount = processEventBodyValue(body.data())
228
- if (!this.matchInvocationEffect(e => e.function === 'clawback' && matchArrays([from, amount], e.args)))
229
- return
234
+ if (typeof amount !== 'string')
235
+ return null
230
236
  if (topics.length > 3) {
231
237
  mapSacContract(this.effectsAnalyzer, contract, xdrParseAsset(xdrParseScVal(topics[3])))
232
238
  }
@@ -240,15 +246,14 @@ class EventsAnalyzer {
240
246
  return //throw new Error('Non-standard event')
241
247
  const currentAdmin = xdrParseScVal(topics[1])
242
248
  const newAdmin = processEventBodyValue(body.data())
243
- if (!this.matchInvocationEffect(e => e.function === 'set_admin' && matchArrays([currentAdmin, newAdmin], [this.effectsAnalyzer.source, e.args])))
244
- return
245
249
  if (topics.length > 2) {
246
250
  mapSacContract(this.effectsAnalyzer, contract, xdrParseAsset(xdrParseScVal(topics[2])))
247
251
  }
248
252
  this.effectsAnalyzer.setAdmin(contract, newAdmin)
249
253
  }
250
254
  break
251
- /*case 'approve': { //TODO: think about processing this effect
255
+ /*case 'set_authorized':*/ //TODO: think about processing this effects
256
+ /*case 'approve': {
252
257
  if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
253
258
  throw new Error('Non-standard event')
254
259
  const from = xdrParseScVal(topics[1])
@@ -260,12 +265,14 @@ class EventsAnalyzer {
260
265
  break*/
261
266
  }
262
267
  }
263
-
264
- matchInvocationEffect(cb) {
265
- return this.effectsAnalyzer.effects.find(e => e.type === effectTypes.contractInvoked && cb(e))
266
- }
267
268
  }
268
269
 
270
+ /**
271
+ * Compare types in the topics array with expected values
272
+ * @param {ScVal[]} topics
273
+ * @param {string[]} shape
274
+ * @return {boolean}
275
+ */
269
276
  function matchEventTopicsShape(topics, shape) {
270
277
  if (topics.length > shape.length + 1)
271
278
  return false
@@ -287,24 +294,14 @@ function matchEventTopicsShape(topics, shape) {
287
294
  return true
288
295
  }
289
296
 
290
- function matchArrays(a, b) {
291
- if (!a || !b)
292
- return false
293
- if (a.length !== b.length)
294
- return false
295
- for (let i = a.length; i--;) {
296
- if (a[i] !== undefined && a[i] !== b[i]) //undefined serves as * substitution
297
- return false
298
- }
299
- return true
300
- }
301
-
297
+ /**
298
+ * Retrieve event body value
299
+ * @param value
300
+ */
302
301
  function processEventBodyValue(value) {
303
302
  const innerValue = value.value()
304
- /*if (innerValue instanceof Array) //handle simple JS arrays
305
- return innerValue.map(xdrParseScVal)*/
306
303
  if (!innerValue) //scVoid
307
- return undefined
304
+ return null
308
305
  return xdrParseScVal(value) //other scValue
309
306
  }
310
307
 
@@ -24,12 +24,16 @@ function mapSacContract(effectsAnalyzer, contractAddress, classicAsset) {
24
24
  //try to load from cache first
25
25
  const fromCache = sacCache.get(classicAsset + network)
26
26
  if (!fromCache) {
27
- const encodedContract = contractIdFromAsset(toStellarAsset(classicAsset), network)
28
- sacCache.set(classicAsset + network, contractAddress)
29
- if (contractAddress === undefined) {
30
- contractAddress = encodedContract
31
- } else if (encodedContract !== contractAddress)
27
+ try {
28
+ const encodedContract = contractIdFromAsset(toStellarAsset(classicAsset), network)
29
+ sacCache.set(classicAsset + network, contractAddress)
30
+ if (contractAddress === undefined) {
31
+ contractAddress = encodedContract
32
+ } else if (encodedContract !== contractAddress)
33
+ return false
34
+ } catch (e) {
32
35
  return false
36
+ }
33
37
  } else if (contractAddress !== fromCache)
34
38
  return false //check whether validated contract from cache matches the asset
35
39
  if (sacMap) {
@@ -82,9 +82,11 @@ const effectTypes = {
82
82
 
83
83
  contractCodeUploaded: 'contractCodeUploaded',
84
84
  contractCodeRemoved: 'contractCodeRemoved',
85
+ contractCodeRestored: 'contractCodeRestored',
85
86
 
86
87
  contractCreated: 'contractCreated',
87
88
  contractUpdated: 'contractUpdated',
89
+ contractRestored: 'contractRestored',
88
90
 
89
91
  contractInvoked: 'contractInvoked',
90
92
  contractError: 'contractError',
@@ -92,6 +94,7 @@ const effectTypes = {
92
94
  contractDataCreated: 'contractDataCreated',
93
95
  contractDataUpdated: 'contractDataUpdated',
94
96
  contractDataRemoved: 'contractDataRemoved',
97
+ contractDataRestored: 'contractDataRestored',
95
98
 
96
99
  contractEvent: 'contractEvent',
97
100
  contractMetrics: 'contractMetrics',
@@ -2,13 +2,13 @@ const {StrKey, hash, xdr, nativeToScVal} = require('@stellar/stellar-base')
2
2
  const effectTypes = require('./effect-types')
3
3
  const {parseLedgerEntryChanges} = require('./parser/ledger-entry-changes-parser')
4
4
  const {xdrParseAsset, xdrParseAccountAddress, xdrParseScVal} = require('./parser/tx-xdr-parser-utils')
5
- const {analyzeSignerChanges} = require('./aggregation/signer-changes-analyzer')
6
5
  const {contractIdFromPreimage} = require('./parser/contract-preimage-encoder')
6
+ const {generateContractCodeEntryHash} = require('./parser/ledger-key')
7
+ const {analyzeSignerChanges} = require('./aggregation/signer-changes-analyzer')
7
8
  const EventsAnalyzer = require('./aggregation/events-analyzer')
8
9
  const AssetSupplyAnalyzer = require('./aggregation/asset-supply-analyzer')
9
10
  const {mapSacContract} = require('./aggregation/sac-contract-mapper')
10
11
  const {UnexpectedTxMetaChangeError, TxMetaEffectParserError} = require('./errors')
11
- const {generateContractCodeEntryHash} = require('./parser/ledger-key')
12
12
 
13
13
  class EffectsAnalyzer {
14
14
  constructor({
@@ -491,6 +491,7 @@ class EffectsAnalyzer {
491
491
  const effect = {}
492
492
  switch (action) {
493
493
  case 'created':
494
+ case 'restored':
494
495
  if (!after.sponsor)
495
496
  continue
496
497
  effect.sponsor = after.sponsor
@@ -823,13 +824,12 @@ class EffectsAnalyzer {
823
824
  }
824
825
 
825
826
  processContractChanges({action, before, after}) {
826
- if (action !== 'created' && action !== 'updated')
827
- throw new UnexpectedTxMetaChangeError({type: 'contract', action})
828
827
  const {kind, owner: contract, keyHash} = after
829
828
  let effect = {
830
829
  type: effectTypes.contractCreated,
831
830
  contract,
832
- kind
831
+ kind,
832
+ keyHash
833
833
  }
834
834
  switch (kind) {
835
835
  case 'fromAsset':
@@ -841,22 +841,30 @@ class EffectsAnalyzer {
841
841
  default:
842
842
  throw new TxMetaEffectParserError('Unexpected contract type: ' + kind)
843
843
  }
844
- if (action === 'created') {
845
- if (this.effects.some(e => e.type === effectTypes.contractCreated && e.contract === contract)) {
846
- effect = undefined //skip contract creation effects processed by top-level createContract operation call
847
- }
848
- } else if (action === 'updated') {
849
- effect.type = effectTypes.contractUpdated
850
- effect.prevWasmHash = before.wasmHash
851
- if (before.wasmHash === after.wasmHash) {//skip if hash unchanged
852
- effect = undefined
853
- }
844
+ switch (action) {
845
+ case 'created':
846
+ if (this.effects.some(e => e.type === effectTypes.contractCreated && e.contract === contract)) {
847
+ effect = undefined //skip contract creation effects processed by top-level createContract operation call
848
+ }
849
+ break
850
+ case 'updated':
851
+ effect.type = effectTypes.contractUpdated
852
+ effect.prevWasmHash = before.wasmHash
853
+ if (before.wasmHash === after.wasmHash) {//skip if hash unchanged
854
+ effect = undefined
855
+ }
856
+ break
857
+ case 'restored':
858
+ effect.type = effectTypes.contractRestored
859
+ break
860
+ default:
861
+ throw new UnexpectedTxMetaChangeError({type: 'contract', action})
854
862
  }
855
863
  if (effect) {
856
864
  this.addEffect(effect, effect.type === effectTypes.contractCreated ? 0 : undefined)
857
865
  }
858
866
  if (before?.storage?.length || after?.storage?.length) {
859
- this.processInstanceDataChanges(before, after)
867
+ this.processInstanceDataChanges(before, after, action === 'restored')
860
868
  }
861
869
  }
862
870
 
@@ -885,6 +893,10 @@ class EffectsAnalyzer {
885
893
  effect.type = effectTypes.contractDataRemoved
886
894
  effect.prevValue = before.value
887
895
  break
896
+ case 'restored':
897
+ effect.type = effectTypes.contractDataRestored
898
+ effect.value = after.value
899
+ break
888
900
  }
889
901
  this.addEffect(effect)
890
902
  this.processContractBalance(effect)
@@ -898,17 +910,23 @@ class EffectsAnalyzer {
898
910
  case 'updated':
899
911
  break //it doesn't change the state
900
912
  case 'removed':
901
- const effect = {
913
+ this.addEffect({
902
914
  type: effectTypes.contractCodeRemoved,
903
915
  wasmHash: hash,
904
916
  keyHash
905
- }
906
- this.addEffect(effect)
917
+ })
918
+ break
919
+ case 'restored':
920
+ this.addEffect({
921
+ type: effectTypes.contractCodeRestored,
922
+ wasmHash: hash,
923
+ keyHash
924
+ })
907
925
  break
908
926
  }
909
927
  }
910
928
 
911
- processInstanceDataChanges(before, after) {
929
+ processInstanceDataChanges(before, after, restored) {
912
930
  const storageBefore = before?.storage || []
913
931
  const storageAfter = [...(after?.storage || [])]
914
932
  for (const {key, val} of storageBefore) {
@@ -948,7 +966,7 @@ class EffectsAnalyzer {
948
966
  //iterate all storage items left
949
967
  for (const {key, val} of storageAfter) {
950
968
  const effect = {
951
- type: effectTypes.contractDataCreated,
969
+ type: restored ? effectTypes.contractDataRestored : effectTypes.contractDataCreated,
952
970
  owner: after?.owner || before.owner,
953
971
  key,
954
972
  value: val,
@@ -969,12 +987,17 @@ class EffectsAnalyzer {
969
987
  ttl
970
988
  }
971
989
  if (stateEffect) {
972
- if (stateEffect.type.includes('contractCode')) {
990
+ if (stateEffect.type.startsWith('contractCode')) {
973
991
  effect.kind = 'contractCode'
974
- } else if (stateEffect.type.includes('contractData')) {
992
+ } else if (stateEffect.type.startsWith('contractData')) {
975
993
  effect.kind = 'contractData'
976
994
  effect.owner = stateEffect.owner
977
- }
995
+ } else if (stateEffect.type.startsWith('contract')) {
996
+ effect.kind = 'contractData'
997
+ effect.owner = stateEffect.contract
998
+ } else
999
+ throw new UnexpectedTxMetaChangeError({type: 'ttl', action: stateEffect.type})
1000
+ stateEffect.ttl = ttl
978
1001
  }
979
1002
  this.addEffect(effect)
980
1003
  }
@@ -1100,6 +1123,9 @@ function encodeSponsorshipEffectName(action, type) {
1100
1123
  case 'removed':
1101
1124
  actionKey = 'Removed'
1102
1125
  break
1126
+ case 'restored':
1127
+ actionKey = 'Restored'
1128
+ break
1103
1129
  default:
1104
1130
  throw new UnexpectedTxMetaChangeError({action, type})
1105
1131
  }
package/src/index.js CHANGED
@@ -139,6 +139,8 @@ function parseTxOperationsMeta({
139
139
  }
140
140
  const metaValue = meta.value()
141
141
  const opMeta = metaValue.operations()
142
+ const isV4Meta = meta.arm() === 'v4'
143
+ let txEvents = isV4Meta ? metaValue.events() : undefined
142
144
 
143
145
  //analyze operation effects for each operation
144
146
  for (let i = 0; i < parsedTx.operations.length; i++) {
@@ -155,12 +157,23 @@ function parseTxOperationsMeta({
155
157
  const isSorobanInvocation = operation.type === 'invokeHostFunction'
156
158
  //only for Soroban contract invocation
157
159
  if (isSorobanInvocation) {
158
- const sorobanMeta = metaValue._attributes.sorobanMeta
160
+ const {sorobanMeta} = metaValue._attributes
159
161
  if (sorobanMeta) {
160
- params.events = sorobanMeta.events()
161
- params.diagnosticEvents = sorobanMeta.diagnosticEvents()
162
+ if (sorobanMeta.events) {
163
+ params.events = sorobanMeta.events()
164
+ }
165
+ if (sorobanMeta.diagnosticEvents) {
166
+ params.diagnosticEvents = sorobanMeta.diagnosticEvents()
167
+ }
162
168
  params.processSystemEvents = processSystemEvents
163
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
+ }
164
177
  params.mapSac = mapSac
165
178
  }
166
179
  const analyzer = new EffectsAnalyzer(params)
@@ -6,7 +6,7 @@ const {generateContractStateEntryHash, generateContractCodeEntryHash} = require(
6
6
  /**
7
7
  * @typedef {{}} ParsedLedgerEntryMeta
8
8
  * @property {'account'|'trustline'|'offer'|'data'|'liquidityPool'|'claimableBalance'|'contractData'|'contractCode'|'ttl'} type - Ledger entry type
9
- * @property {'created'|'updated'|'removed'} action - Ledger modification action
9
+ * @property {'created'|'updated'|'removed'|'restored'} action - Ledger modification action
10
10
  * @property {{}} before - Ledger entry state before changes applied
11
11
  * @property {{}} after - Ledger entry state after changes application
12
12
  */
@@ -18,14 +18,15 @@ const {generateContractStateEntryHash, generateContractCodeEntryHash} = require(
18
18
  function parseLedgerEntryChanges(ledgerEntryChanges) {
19
19
  const changes = []
20
20
  let state
21
+ let containsTtl = false
21
22
  for (let i = 0; i < ledgerEntryChanges.length; i++) {
22
23
  const entry = ledgerEntryChanges[i]
23
24
  const action = entry._arm
24
25
  const stateData = parseEntry(entry, action)
25
26
  if (stateData === undefined)
26
27
  continue
27
- const change = {action}
28
28
  const type = entry._value._arm
29
+ const change = {action, type}
29
30
  switch (action) {
30
31
  case 'state':
31
32
  state = stateData
@@ -44,6 +45,11 @@ function parseLedgerEntryChanges(ledgerEntryChanges) {
44
45
  change.after = stateData
45
46
  change.type = stateData.entry
46
47
  break
48
+ case 'restored':
49
+ change.before = state
50
+ change.after = stateData
51
+ change.type = stateData.entry
52
+ break
47
53
  case 'removed':
48
54
  if (!state && type === 'ttl')
49
55
  continue //skip expiration processing for now
@@ -54,9 +60,17 @@ function parseLedgerEntryChanges(ledgerEntryChanges) {
54
60
  default:
55
61
  throw new TxMetaEffectParserError(`Unknown change entry type: ${action}`)
56
62
  }
63
+ if (change.type === 'ttl') {
64
+ containsTtl = true
65
+ }
57
66
  changes.push(change)
58
67
  state = null
59
68
  }
69
+ if (containsTtl) { //put ttl entries into the end of array
70
+ changes.sort((a, b) =>
71
+ a.type !== 'ttl' && b.type === 'ttl' ?
72
+ -1 : 0)
73
+ }
60
74
  return changes
61
75
  }
62
76
 
@@ -16,6 +16,7 @@ function parseTxMetaChanges(meta) {
16
16
  break
17
17
  case 'v2':
18
18
  case 'v3':
19
+ case 'v4':
19
20
  retrieveTopLevelChanges(transactionMeta.txChangesBefore(), txMetaChanges)
20
21
  retrieveTopLevelChanges(transactionMeta.txChangesAfter(), txMetaChanges)
21
22
  break
@@ -1,4 +1,4 @@
1
- const {StrKey, LiquidityPoolId, scValToBigInt, xdr, Asset} = require('@stellar/stellar-base')
1
+ const {xdr, StrKey, LiquidityPoolId, Asset, scValToBigInt, encodeMuxedAccount, encodeMuxedAccountToAddress} = require('@stellar/stellar-base')
2
2
  const {TxMetaEffectParserError} = require('../errors')
3
3
 
4
4
  /**
@@ -26,10 +26,9 @@ function toStellarAsset(assetDescriptor) {
26
26
  /**
27
27
  * Parse account address from XDR representation
28
28
  * @param accountId
29
- * @param muxedAccountsSupported
30
- * @return {String|{muxedId: String, primary: String}}
29
+ * @return {String}
31
30
  */
32
- function xdrParseAccountAddress(accountId, muxedAccountsSupported = false) {
31
+ function xdrParseAccountAddress(accountId) {
33
32
  if (!accountId)
34
33
  return undefined
35
34
  if (accountId.arm) {
@@ -37,8 +36,6 @@ function xdrParseAccountAddress(accountId, muxedAccountsSupported = false) {
37
36
  case 'ed25519':
38
37
  return StrKey.encodeEd25519PublicKey(accountId.ed25519())
39
38
  case 'med25519':
40
- if (!muxedAccountsSupported)
41
- throw new TxMetaEffectParserError(`Muxed accounts not supported here`)
42
39
  return {
43
40
  primary: StrKey.encodeEd25519PublicKey(accountId.value().ed25519()),
44
41
  muxedId: accountId.value().id().toString()
@@ -53,6 +50,17 @@ function xdrParseAccountAddress(accountId, muxedAccountsSupported = false) {
53
50
  throw new TypeError(`Failed to identify and parse account address: ${accountId}`)
54
51
  }
55
52
 
53
+ /**
54
+ * Parse muxed account address from ScAddress XDR representation
55
+ * @param {{}} value
56
+ * @return {string}
57
+ */
58
+ function xdrParseMuxedScAddress(value) {
59
+ const {ed25519, id} = value._attributes
60
+ const muxed = encodeMuxedAccount(StrKey.encodeEd25519PublicKey(ed25519), id._value.toString())
61
+ return encodeMuxedAccountToAddress(muxed)
62
+ }
63
+
56
64
  /**
57
65
  * Parse Contract ID from raw bytes
58
66
  * @param {Buffer} rawContractId
@@ -257,11 +265,15 @@ function xdrParseScVal(value, treatBytesAsContractId = false) {
257
265
  case 'duration':
258
266
  return scValToBigInt(value).toString()
259
267
  case 'address':
260
- if (value._value._arm === 'accountId')
261
- return xdrParseAccountAddress(value._value.value())
262
- if (value._value._arm === 'contractId')
263
- return xdrParseContractAddress(value._value.value())
264
- throw new TxMetaEffectParserError('Not supported XDR primitive type: ' + value.toString())
268
+ switch (value._value._arm) {
269
+ case 'accountId':
270
+ return xdrParseAccountAddress(value._value.value())
271
+ case 'contractId':
272
+ return xdrParseContractAddress(value._value.value())
273
+ case 'muxedAccount':
274
+ return xdrParseMuxedScAddress(value._value.value())
275
+ }
276
+ throw new TxMetaEffectParserError('Not supported XDR primitive type: ' + value._value._arm.toString())
265
277
  case 'bytes':
266
278
  return treatBytesAsContractId ? xdrParseContractAddress(value.value()) : value._value.toString('base64')
267
279
  case 'i32':
@@ -298,6 +310,7 @@ module.exports = {
298
310
  xdrParseAsset,
299
311
  xdrParseAccountAddress,
300
312
  xdrParseContractAddress,
313
+ xdrParseMuxedScAddress,
301
314
  xdrParseClaimant,
302
315
  xdrParseClaimedOffer,
303
316
  xdrParseTradeAtom,