@stellar-expert/ui-framework 1.9.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/.npmrc +1 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/account/account-address.js +127 -0
- package/account/account-address.scss +20 -0
- package/account/available-balance.js +21 -0
- package/account/identicon.js +91 -0
- package/account/identicon.scss +6 -0
- package/account/signer-key.js +65 -0
- package/api/api-cache.js +5 -0
- package/api/explorer-api-call.js +37 -0
- package/api/explorer-api-hooks.js +202 -0
- package/api/explorer-api-paginated-list-hooks.js +439 -0
- package/api/explorer-batch-info-loader.js +85 -0
- package/api/explorer-tx-api.js +29 -0
- package/asset/amount.js +53 -0
- package/asset/asset-icon.js +34 -0
- package/asset/asset-issuer.js +22 -0
- package/asset/asset-link.js +93 -0
- package/asset/asset-link.scss +26 -0
- package/asset/asset-list-hooks.js +60 -0
- package/asset/asset-meta-hooks.js +78 -0
- package/asset/asset-selector.js +72 -0
- package/asset/asset-selector.scss +41 -0
- package/basic-styles/base.scss +53 -0
- package/basic-styles/form.scss +90 -0
- package/basic-styles/grid.scss +388 -0
- package/basic-styles/link.scss +17 -0
- package/basic-styles/lists.scss +68 -0
- package/basic-styles/loader.scss +22 -0
- package/basic-styles/responsive.scss +29 -0
- package/basic-styles/scrollbars.scss +19 -0
- package/basic-styles/spacing.scss +50 -0
- package/basic-styles/table.scss +104 -0
- package/basic-styles/themes.scss +59 -0
- package/basic-styles/typography.scss +191 -0
- package/basic-styles/variables.scss +26 -0
- package/claimable-balance/claimable-balance-claimants.js +12 -0
- package/contract/sc-val.js +70 -0
- package/contract/wasm-section-parser.js +159 -0
- package/contract/wasm-section-reader.js +79 -0
- package/contract/xdr-reader.js +148 -0
- package/controls/active-icon.scss +23 -0
- package/controls/button-group.js +20 -0
- package/controls/button-group.scss +48 -0
- package/controls/button.js +74 -0
- package/controls/button.scss +148 -0
- package/controls/code-block.js +35 -0
- package/controls/code-block.scss +74 -0
- package/controls/dropdown.js +281 -0
- package/controls/dropdown.scss +161 -0
- package/controls/external-link.js +5 -0
- package/controls/info-tooltip.js +17 -0
- package/controls/info-tooltip.scss +4 -0
- package/controls/slider.js +20 -0
- package/controls/slider.scss +22 -0
- package/controls/tabs.js +94 -0
- package/controls/tabs.scss +73 -0
- package/controls/tooltip.js +214 -0
- package/controls/tooltip.scss +97 -0
- package/controls/update-highlighter.js +28 -0
- package/controls/update-highlighter.scss +10 -0
- package/date/date-selector.js +44 -0
- package/date/elapsed-time.js +22 -0
- package/date/utc-timestamp.js +23 -0
- package/dex/price-dynamic.js +45 -0
- package/dex/price-dynamic.scss +36 -0
- package/directory/directory-hooks.js +89 -0
- package/effect/effect-description.js +289 -0
- package/effect/op-effects-view.js +11 -0
- package/effect/op-effects.scss +8 -0
- package/errors/error-boundary.js +98 -0
- package/fonts/font.scss +23 -0
- package/fonts/icons/config.json +1858 -0
- package/fonts/icons/icons-embedded.scss +644 -0
- package/horizon/horizon-account-helpers.js +104 -0
- package/horizon/horizon-client-helpers.js +52 -0
- package/horizon/horizon-ledger-helpers.js +36 -0
- package/horizon/horizon-orderbook-helpers.js +14 -0
- package/horizon/horizon-trades-helper.js +89 -0
- package/horizon/horizon-transaction-helpers.js +36 -0
- package/index.js +85 -0
- package/index.scss +16 -0
- package/interaction/autofocus.js +9 -0
- package/interaction/block-select.js +54 -0
- package/interaction/block-select.scss +24 -0
- package/interaction/copy-to-clipboard.js +19 -0
- package/interaction/inline-progress.js +16 -0
- package/interaction/qr-code.js +45 -0
- package/interaction/responsive.js +21 -0
- package/interaction/spoiler.js +40 -0
- package/interaction/spoiler.scss +9 -0
- package/interaction/theme-selector.js +11 -0
- package/ledger/ledger-entry-href-formatter.js +22 -0
- package/ledger/ledger-entry-link.js +59 -0
- package/meta/page-meta-tags.js +229 -0
- package/module/dynamic-module.js +48 -0
- package/package.json +41 -0
- package/state/on-screen-hooks.js +23 -0
- package/state/page-visibility-helpers.js +17 -0
- package/state/page-visibility-hooks.js +12 -0
- package/state/screen-orientation-hooks.js +16 -0
- package/state/state-hooks.js +77 -0
- package/state/stellar-network-hooks.js +45 -0
- package/state/theme.js +29 -0
- package/stellar/key-type.js +92 -0
- package/stellar/ledger-generic-id.js +40 -0
- package/stellar/signature-hint-utils.js +65 -0
- package/toast/toast-notification-instance.js +43 -0
- package/toast/toast-notification.js +46 -0
- package/toast/toast-notifications-block.js +55 -0
- package/toast/toast-notifications.scss +166 -0
- package/tx/op-accounting-changes.js +27 -0
- package/tx/op-description-view.js +925 -0
- package/tx/op-description.scss +98 -0
- package/tx/op-icon.js +93 -0
- package/tx/parser/op-balance-changes.js +62 -0
- package/tx/parser/op-descriptor.js +49 -0
- package/tx/parser/tx-details-parser.js +75 -0
- package/tx/parser/tx-matcher.js +372 -0
- package/tx/parser/type-filter-matcher.js +127 -0
- package/tx/tx-fee-effect.js +14 -0
- package/tx/tx-list-hooks.js +19 -0
- package/tx/tx-operations-list.js +105 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import {LiquidityPoolAsset} from '@stellar/stellar-base'
|
|
2
|
+
import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
|
|
3
|
+
import {TypeFilterMatcher} from './type-filter-matcher'
|
|
4
|
+
|
|
5
|
+
export default class TxMatcher {
|
|
6
|
+
constructor(filters, skipUnrelated = false) {
|
|
7
|
+
const matcherFilters = {}
|
|
8
|
+
if (filters) {
|
|
9
|
+
for (const key of ['account', 'source', 'destination', 'asset', 'src_asset', 'dest_asset', 'offer', 'pool']) {
|
|
10
|
+
const values = filters[key]
|
|
11
|
+
if (!values?.length)
|
|
12
|
+
continue
|
|
13
|
+
matcherFilters[key] = values
|
|
14
|
+
this.noFilters = false
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
this.filters = matcherFilters
|
|
18
|
+
this.typeMatcher = new TypeFilterMatcher(filters.type)
|
|
19
|
+
this.skipUnrelated = skipUnrelated
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @type {{[type], [account], [source], [destination], [asset], [src_asset], [dest_asset], [offer], [pool]}}
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
filters
|
|
27
|
+
/**
|
|
28
|
+
* @type {Boolean}
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
noFilters = true
|
|
32
|
+
/**
|
|
33
|
+
* @type {Boolean}
|
|
34
|
+
* @private
|
|
35
|
+
*/
|
|
36
|
+
skipUnrelated
|
|
37
|
+
/**
|
|
38
|
+
* @type {TypeFilterMatcher}
|
|
39
|
+
* @private
|
|
40
|
+
*/
|
|
41
|
+
typeMatcher
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {OperationDescriptor} od
|
|
45
|
+
* @return {Boolean}
|
|
46
|
+
*/
|
|
47
|
+
matchOperation(od) {
|
|
48
|
+
if (this.noFilters)
|
|
49
|
+
return true
|
|
50
|
+
if (!this.typeMatcher.match(od.operation.type))
|
|
51
|
+
return false
|
|
52
|
+
if (!this.matchOperationProps(od))
|
|
53
|
+
return false
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {TransactionBase} tx
|
|
59
|
+
* @return {Boolean}
|
|
60
|
+
*/
|
|
61
|
+
matchTxFee(tx) {
|
|
62
|
+
if (!this.filter.asset?.includes('XLM'))
|
|
63
|
+
return false
|
|
64
|
+
const feeSource = tx.feeSource ? tx.feeSource : tx.source
|
|
65
|
+
if (this.filter.account?.includes(feeSource))
|
|
66
|
+
return true
|
|
67
|
+
return !this.skipUnrelated
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {OperationDescriptor} od
|
|
72
|
+
* @return {Boolean}
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
matchOperationProps(od) {
|
|
76
|
+
const matchingProps = this.prepareOperationMatchingProps(od)
|
|
77
|
+
for (const [key, values] of Object.entries(this.filters)) {
|
|
78
|
+
const options = matchingProps[key]
|
|
79
|
+
if (!options)
|
|
80
|
+
continue
|
|
81
|
+
for (const value of values)
|
|
82
|
+
if (options.has(value))
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {OperationDescriptor} od
|
|
90
|
+
* @returns {{[account], [source], [destination], [asset], [src_asset], [dest_asset], [offer], [pool]}}
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
prepareOperationMatchingProps(od) {
|
|
94
|
+
const {filters, skipUnrelated} = this
|
|
95
|
+
const {operation: op} = od
|
|
96
|
+
const matchingProps = {}
|
|
97
|
+
//process accounts
|
|
98
|
+
if (filters.account || filters.destination) {
|
|
99
|
+
switch (op.type) {
|
|
100
|
+
case 'createAccount':
|
|
101
|
+
case 'accountMerge':
|
|
102
|
+
case 'payment':
|
|
103
|
+
case 'pathPaymentStrictSend':
|
|
104
|
+
case 'pathPaymentStrictReceive':
|
|
105
|
+
matchingProps.account = new Set([op.destination])
|
|
106
|
+
matchingProps.destination = new Set([op.destination])
|
|
107
|
+
break
|
|
108
|
+
case 'inflation':
|
|
109
|
+
if (od.successful) {
|
|
110
|
+
const payouts = op.effects.filter(e => e.type === 'accountCredited').map(e => e.source)
|
|
111
|
+
if (payouts.length) {
|
|
112
|
+
matchingProps.account = new Set(payouts)
|
|
113
|
+
matchingProps.destination = new Set(payouts)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
break
|
|
117
|
+
case 'setOptions':
|
|
118
|
+
matchingProps.account = new Set()
|
|
119
|
+
if (!skipUnrelated) {
|
|
120
|
+
const optionsRelated = [op.inflationDest, op.signer?.key].filter(acc => !!acc)
|
|
121
|
+
if (optionsRelated.length) {
|
|
122
|
+
matchingProps.account.add(optionsRelated)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const e of (op.effects || []))
|
|
126
|
+
if (e.prevSponsor) {
|
|
127
|
+
matchingProps.account.add(e.prevSponsor)
|
|
128
|
+
}
|
|
129
|
+
break
|
|
130
|
+
case 'allowTrust':
|
|
131
|
+
case 'setTrustLineFlags':
|
|
132
|
+
matchingProps.account = new Set([op.trustor])
|
|
133
|
+
matchingProps.destination = new Set([op.trustor])
|
|
134
|
+
break
|
|
135
|
+
case 'createClaimableBalance':
|
|
136
|
+
if (!skipUnrelated) {
|
|
137
|
+
const indirect = op.claimants.map(c => c.destination)
|
|
138
|
+
if (indirect.length) {
|
|
139
|
+
matchingProps.account = new Set(...indirect)
|
|
140
|
+
matchingProps.destination = new Set(...indirect)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
break
|
|
144
|
+
case 'beginSponsoringFutureReserves':
|
|
145
|
+
matchingProps.account = new Set([op.sponsoredId])
|
|
146
|
+
matchingProps.destination = new Set([op.sponsoredId])
|
|
147
|
+
break
|
|
148
|
+
case 'revokeAccountSponsorship':
|
|
149
|
+
case 'revokeTrustlineSponsorship':
|
|
150
|
+
case 'revokeOfferSponsorship':
|
|
151
|
+
case 'revokeDataSponsorship':
|
|
152
|
+
case 'revokeClaimableBalanceSponsorship':
|
|
153
|
+
case 'revokeLiquidityPoolSponsorship':
|
|
154
|
+
case 'revokeSignerSponsorship':
|
|
155
|
+
case 'revokeSponsorship':
|
|
156
|
+
const revokedAccounts = [op.account, op.signer?.key, op.seller].filter(a => !!a)
|
|
157
|
+
if (revokedAccounts.length) {
|
|
158
|
+
matchingProps.account = new Set(revokedAccounts)
|
|
159
|
+
matchingProps.destination = new Set(revokedAccounts)
|
|
160
|
+
}
|
|
161
|
+
break
|
|
162
|
+
case 'clawback':
|
|
163
|
+
matchingProps.account = new Set([op.from])
|
|
164
|
+
matchingProps.destination = new Set([op.from])
|
|
165
|
+
break
|
|
166
|
+
case 'invokeHostFunction':
|
|
167
|
+
matchingProps.account = new Set()
|
|
168
|
+
for (const effect of op.effects) {
|
|
169
|
+
matchingProps.account.add(effect.source)
|
|
170
|
+
if (effect.issuer !== undefined) {
|
|
171
|
+
matchingProps.account.add(effect.issuer)
|
|
172
|
+
}
|
|
173
|
+
if (effect.contract !== undefined) {
|
|
174
|
+
matchingProps.account.add(effect.contract)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
break
|
|
178
|
+
case 'liquidityPoolDeposit':
|
|
179
|
+
case 'liquidityPoolWithdraw':
|
|
180
|
+
case 'claimClaimableBalance':
|
|
181
|
+
case 'changeTrust':
|
|
182
|
+
case 'manageBuyOffer':
|
|
183
|
+
case 'manageSellOffer':
|
|
184
|
+
case 'createPassiveSellOffer':
|
|
185
|
+
case 'manageData':
|
|
186
|
+
case 'bumpSequence':
|
|
187
|
+
case 'endSponsoringFutureReserves':
|
|
188
|
+
case 'clawbackClaimableBalance':
|
|
189
|
+
case 'bumpFootprintExpiration':
|
|
190
|
+
case 'restoreFootprint':
|
|
191
|
+
matchingProps.account = new Set()
|
|
192
|
+
break
|
|
193
|
+
default:
|
|
194
|
+
throw new Error('Unsupported op type: ' + op.type)
|
|
195
|
+
}
|
|
196
|
+
if (!matchingProps.account) {
|
|
197
|
+
matchingProps.account = new Set()
|
|
198
|
+
}
|
|
199
|
+
if (!matchingProps.account.has(op.source)) {
|
|
200
|
+
matchingProps.account.add(op.source)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//add op source account
|
|
204
|
+
matchingProps.source = new Set([op.source])
|
|
205
|
+
//porcess assets
|
|
206
|
+
if (filters.asset || filters.src_asset || filters.dest_asset)
|
|
207
|
+
switch (op.type) {
|
|
208
|
+
case 'createAccount':
|
|
209
|
+
case 'accountMerge':
|
|
210
|
+
case 'inflation':
|
|
211
|
+
matchingProps.asset = new Set(['XLM'])
|
|
212
|
+
matchingProps.src_asset = new Set(['XLM'])
|
|
213
|
+
matchingProps.dest_asset = new Set(['XLM'])
|
|
214
|
+
break
|
|
215
|
+
case 'payment':
|
|
216
|
+
case 'clawback':
|
|
217
|
+
case 'setTrustLineFlags':
|
|
218
|
+
case 'createClaimableBalance':
|
|
219
|
+
case 'revokeAccountSponsorship':
|
|
220
|
+
case 'revokeTrustlineSponsorship':
|
|
221
|
+
case 'revokeOfferSponsorship':
|
|
222
|
+
case 'revokeDataSponsorship':
|
|
223
|
+
case 'revokeClaimableBalanceSponsorship':
|
|
224
|
+
case 'revokeLiquidityPoolSponsorship':
|
|
225
|
+
case 'revokeSignerSponsorship':
|
|
226
|
+
case 'revokeSponsorship':
|
|
227
|
+
if (op.asset) {
|
|
228
|
+
const a = [AssetDescriptor.parse(op.asset).toFQAN()]
|
|
229
|
+
matchingProps.asset = new Set(a)
|
|
230
|
+
matchingProps.src_asset = new Set(a)
|
|
231
|
+
matchingProps.dest_asset = new Set(a)
|
|
232
|
+
}
|
|
233
|
+
break
|
|
234
|
+
case 'changeTrust': {
|
|
235
|
+
const a = [AssetDescriptor.parse(op.line).toFQAN()]
|
|
236
|
+
matchingProps.dest_asset = new Set(a)
|
|
237
|
+
if (op.line instanceof LiquidityPoolAsset) {
|
|
238
|
+
a.push(AssetDescriptor.parse(op.line.assetA).toFQAN())
|
|
239
|
+
a.push(AssetDescriptor.parse(op.line.assetB).toFQAN())
|
|
240
|
+
}
|
|
241
|
+
matchingProps.asset = new Set(a)
|
|
242
|
+
matchingProps.src_asset = new Set(a)
|
|
243
|
+
}
|
|
244
|
+
break
|
|
245
|
+
case 'pathPaymentStrictSend':
|
|
246
|
+
case 'pathPaymentStrictReceive': {
|
|
247
|
+
const src = AssetDescriptor.parse(op.sendAsset).toFQAN()
|
|
248
|
+
const dest = AssetDescriptor.parse(op.destAsset).toFQAN()
|
|
249
|
+
matchingProps.asset = new Set([src, dest, ...op.path.map(a => AssetDescriptor.parse(a).toFQAN())])
|
|
250
|
+
matchingProps.src_asset = new Set([src])
|
|
251
|
+
matchingProps.dest_asset = new Set([dest])
|
|
252
|
+
}
|
|
253
|
+
break
|
|
254
|
+
case 'manageBuyOffer':
|
|
255
|
+
case 'manageSellOffer':
|
|
256
|
+
case 'createPassiveSellOffer': {
|
|
257
|
+
const selling = AssetDescriptor.parse(op.selling).toFQAN()
|
|
258
|
+
const buying = AssetDescriptor.parse(op.buying).toFQAN()
|
|
259
|
+
matchingProps.asset = new Set([selling, buying])
|
|
260
|
+
matchingProps.src_asset = new Set([selling])
|
|
261
|
+
matchingProps.dest_asset = new Set([buying])
|
|
262
|
+
}
|
|
263
|
+
break
|
|
264
|
+
case 'allowTrust': {
|
|
265
|
+
const asset = AssetDescriptor.parse({code: op.assetCode, issuer: op.source}).toFQAN()
|
|
266
|
+
matchingProps.asset = new Set([asset])
|
|
267
|
+
matchingProps.src_asset = new Set([asset])
|
|
268
|
+
matchingProps.dest_asset = new Set([asset])
|
|
269
|
+
}
|
|
270
|
+
break
|
|
271
|
+
case 'liquidityPoolDeposit':
|
|
272
|
+
case 'liquidityPoolWithdraw':
|
|
273
|
+
if (od.successful) { // TODO: find a workaround for this (operations won't match for failed transactions)
|
|
274
|
+
const poolUpdatedEffect = op.effects.find(e => e.type === 'liquidityPoolUpdated')
|
|
275
|
+
const assets = poolUpdatedEffect.reserves.map(r => AssetDescriptor.parse(r.asset).toFQAN())
|
|
276
|
+
matchingProps.asset = new Set(assets)
|
|
277
|
+
matchingProps.src_asset = new Set(assets)
|
|
278
|
+
matchingProps.dest_asset = new Set(assets)
|
|
279
|
+
}
|
|
280
|
+
break
|
|
281
|
+
case 'invokeHostFunction':
|
|
282
|
+
matchingProps.asset = new Set()
|
|
283
|
+
matchingProps.src_asset = new Set()
|
|
284
|
+
matchingProps.dest_asset = new Set()
|
|
285
|
+
const assets = new Set()
|
|
286
|
+
for (const effect of op.effects) {
|
|
287
|
+
if (effect.asset !== undefined) {
|
|
288
|
+
matchingProps.asset.add(effect.asset)
|
|
289
|
+
matchingProps.src_asset.add(effect.asset)
|
|
290
|
+
matchingProps.dest_asset.add(effect.asset)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
break
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
//process liquidity pools and offers
|
|
297
|
+
if (filters.pool || filters.offer) {
|
|
298
|
+
switch (op.type) {
|
|
299
|
+
case 'liquidityPoolDeposit':
|
|
300
|
+
case 'liquidityPoolWithdraw':
|
|
301
|
+
case 'revokeLiquidityPoolSponsorship':
|
|
302
|
+
matchingProps.pool = new Set([op.liquidityPoolId])
|
|
303
|
+
break
|
|
304
|
+
case 'changeTrust':
|
|
305
|
+
case 'revokeTrustlineSponsorship':
|
|
306
|
+
const asset = AssetDescriptor.parse(op.line || op.asset).toFQAN()
|
|
307
|
+
if (asset.length === 64 && !asset.includes('-')) {
|
|
308
|
+
matchingProps.pool = new Set([asset])
|
|
309
|
+
}
|
|
310
|
+
break
|
|
311
|
+
case 'manageBuyOffer':
|
|
312
|
+
case 'manageSellOffer':
|
|
313
|
+
case 'createPassiveSellOffer':
|
|
314
|
+
case 'pathPaymentStrictSend':
|
|
315
|
+
case 'pathPaymentStrictReceive':
|
|
316
|
+
if (od.successful) {
|
|
317
|
+
const offers = []
|
|
318
|
+
const pools = []
|
|
319
|
+
for (const e of op.effects)
|
|
320
|
+
if (e.type === 'trade') {
|
|
321
|
+
if (e.pool) {
|
|
322
|
+
pools.push(e.pool)
|
|
323
|
+
}
|
|
324
|
+
if (e.offer) {
|
|
325
|
+
offers.push(e.offer)
|
|
326
|
+
if (!skipUnrelated) {
|
|
327
|
+
if (!matchingProps.account) {
|
|
328
|
+
matchingProps.account = new Set()
|
|
329
|
+
}
|
|
330
|
+
if (!matchingProps.account.has(e.seller)) {
|
|
331
|
+
matchingProps.account.add(e.seller)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (offers.length) {
|
|
337
|
+
matchingProps.offer = new Set(offers)
|
|
338
|
+
}
|
|
339
|
+
if (pools.length) {
|
|
340
|
+
matchingProps.pool = new Set(pools)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (op.offerId) {
|
|
344
|
+
if (!matchingProps.offer) {
|
|
345
|
+
matchingProps.offer = new Set()
|
|
346
|
+
}
|
|
347
|
+
if (!matchingProps.offer.has(op.offerId)) {
|
|
348
|
+
matchingProps.offer.add(op.offerId)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
break
|
|
352
|
+
case 'revokeOfferSponsorship':
|
|
353
|
+
matchingProps.offer = new Set([op.offerId])
|
|
354
|
+
break
|
|
355
|
+
case 'clawback':
|
|
356
|
+
matchingProps.account = new Set([op.from])
|
|
357
|
+
matchingProps.destination = new Set([op.from])
|
|
358
|
+
break
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
//TODO: handle trustline authorization effects (deauthorization may cancel offers and withdraw liquidity pool stakes)
|
|
362
|
+
return matchingProps
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Callback for matching operation by type
|
|
368
|
+
* @callback TypeMatcherCallback
|
|
369
|
+
* @param {OperationDescriptor} op - Operation descriptor to match
|
|
370
|
+
* @return {Boolean}
|
|
371
|
+
* @internal
|
|
372
|
+
*/
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const operatinTypeMap = {
|
|
2
|
+
createAccount: 0,
|
|
3
|
+
payment: 1,
|
|
4
|
+
pathPaymentStrictReceive: 2,
|
|
5
|
+
pathPaymentStrictSend: 13,
|
|
6
|
+
createPassiveSellOffer: 4,
|
|
7
|
+
manageSellOffer: 3,
|
|
8
|
+
manageBuyOffer: 12,
|
|
9
|
+
setOptions: 5,
|
|
10
|
+
changeTrust: 6,
|
|
11
|
+
allowTrust: 7,
|
|
12
|
+
accountMerge: 8,
|
|
13
|
+
inflation: 9,
|
|
14
|
+
manageData: 10,
|
|
15
|
+
bumpSequence: 11,
|
|
16
|
+
createClaimableBalance: 14,
|
|
17
|
+
claimClaimableBalance: 15,
|
|
18
|
+
beginSponsoringFutureReserves: 16,
|
|
19
|
+
endSponsoringFutureReserves: 17,
|
|
20
|
+
revokeSponsorship: 18,
|
|
21
|
+
clawback: 19,
|
|
22
|
+
clawbackClaimableBalance: 20,
|
|
23
|
+
setTrustLineFlags: 21,
|
|
24
|
+
liquidityPoolDeposit: 22,
|
|
25
|
+
liquidityPoolWithdraw: 23,
|
|
26
|
+
invokeHostFunction: 24,
|
|
27
|
+
bumpFootprintExpiration: 25,
|
|
28
|
+
restoreFootprint: 26
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class TypeFilterMatcher {
|
|
32
|
+
/**
|
|
33
|
+
* @param {String[]} filters
|
|
34
|
+
*/
|
|
35
|
+
constructor(filters) {
|
|
36
|
+
if (!filters?.length)
|
|
37
|
+
return
|
|
38
|
+
this.types = new Set()
|
|
39
|
+
for (const filter of filters) {
|
|
40
|
+
this.processFilter(filter)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @type {Set}
|
|
46
|
+
* @readonly
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
types
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get unique set of type filters
|
|
53
|
+
* @return {Boolean}
|
|
54
|
+
*/
|
|
55
|
+
match(operationType) {
|
|
56
|
+
return !this.types || this.types.has(operatinTypeMap[operationType])
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {String} filter
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
processFilter(filter) {
|
|
64
|
+
for (const type of this.decodeFilter(filter)) {
|
|
65
|
+
this.types.add(type)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {String} filter
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
decodeFilter(filter) {
|
|
74
|
+
switch (filter) {
|
|
75
|
+
case 'payments':
|
|
76
|
+
return [
|
|
77
|
+
operatinTypeMap.createAccount,
|
|
78
|
+
operatinTypeMap.accountMerge,
|
|
79
|
+
operatinTypeMap.payment,
|
|
80
|
+
operatinTypeMap.pathPaymentStrictReceive,
|
|
81
|
+
operatinTypeMap.pathPaymentStrictSend,
|
|
82
|
+
operatinTypeMap.createClaimableBalance,
|
|
83
|
+
operatinTypeMap.claimClaimableBalance,
|
|
84
|
+
operatinTypeMap.clawback,
|
|
85
|
+
operatinTypeMap.clawbackClaimableBalance,
|
|
86
|
+
operatinTypeMap.inflation
|
|
87
|
+
]
|
|
88
|
+
case 'trustlines':
|
|
89
|
+
return [
|
|
90
|
+
operatinTypeMap.changeTrust,
|
|
91
|
+
operatinTypeMap.allowTrust,
|
|
92
|
+
operatinTypeMap.setTrustLineFlags
|
|
93
|
+
]
|
|
94
|
+
case 'dex':
|
|
95
|
+
return [
|
|
96
|
+
operatinTypeMap.manageSellOffer,
|
|
97
|
+
operatinTypeMap.manageBuyOffer,
|
|
98
|
+
operatinTypeMap.createPassiveSellOffer,
|
|
99
|
+
operatinTypeMap.liquidityPoolDeposit,
|
|
100
|
+
operatinTypeMap.liquidityPoolWithdraw
|
|
101
|
+
]
|
|
102
|
+
case 'settings':
|
|
103
|
+
return [
|
|
104
|
+
operatinTypeMap.createAccount,
|
|
105
|
+
operatinTypeMap.setOptions,
|
|
106
|
+
operatinTypeMap.changeTrust,
|
|
107
|
+
operatinTypeMap.allowTrust,
|
|
108
|
+
operatinTypeMap.accountMerge,
|
|
109
|
+
operatinTypeMap.inflation,
|
|
110
|
+
operatinTypeMap.manageData,
|
|
111
|
+
operatinTypeMap.bumpSequence,
|
|
112
|
+
operatinTypeMap.beginSponsoringFutureReserves,
|
|
113
|
+
operatinTypeMap.endSponsoringFutureReserves,
|
|
114
|
+
operatinTypeMap.revokeSponsorship,
|
|
115
|
+
operatinTypeMap.setTrustLineFlags
|
|
116
|
+
]
|
|
117
|
+
case 'contracts':
|
|
118
|
+
return [
|
|
119
|
+
operatinTypeMap.invokeHostFunction,
|
|
120
|
+
operatinTypeMap.bumpFootprintExpiration,
|
|
121
|
+
operatinTypeMap.restoreFootprint
|
|
122
|
+
]
|
|
123
|
+
default:
|
|
124
|
+
return [parseInt(filter, 10)]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {Amount} from '../asset/amount'
|
|
3
|
+
import {fromStroops} from '@stellar-expert/formatter'
|
|
4
|
+
|
|
5
|
+
export default function TxFeeEffect({feeEffect, compact}) {
|
|
6
|
+
const {charged, bid, bump} = feeEffect
|
|
7
|
+
if (charged)
|
|
8
|
+
return <div>
|
|
9
|
+
<Amount amount={fromStroops(charged)} asset="XLM" issuer={!compact}/> transaction {!!bump && 'bump'} fee charged
|
|
10
|
+
</div>
|
|
11
|
+
return <div>
|
|
12
|
+
Charge transaction {!!bump && 'bump'} fee <Amount amount={fromStroops(bid)} asset="XLM" issuer={!compact}/>
|
|
13
|
+
</div>
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {useExplorerPaginatedApi} from '../api/explorer-api-paginated-list-hooks'
|
|
2
|
+
import {useExplorerApi} from '../api/explorer-api-hooks'
|
|
3
|
+
|
|
4
|
+
export function useTxHistory({filters, order = 'desc', rows = 40, updateLocation = true}) {
|
|
5
|
+
return useExplorerPaginatedApi(
|
|
6
|
+
{
|
|
7
|
+
path: 'tx',
|
|
8
|
+
query: filters
|
|
9
|
+
}, {
|
|
10
|
+
defaultSortOrder: order,
|
|
11
|
+
limit: rows,
|
|
12
|
+
updateLocation,
|
|
13
|
+
defaultQueryParams: {order}
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useTxInfo(idOrHash) {
|
|
18
|
+
return useExplorerApi(`tx/${idOrHash}`)
|
|
19
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, {useCallback, useState} from 'react'
|
|
2
|
+
import {Spoiler} from '../interaction/spoiler'
|
|
3
|
+
import {OpEffectsView} from '../effect/op-effects-view'
|
|
4
|
+
import {OpAccountingChanges, TxFeeAccountingChanges} from './op-accounting-changes'
|
|
5
|
+
import TxFeeEffect from './tx-fee-effect'
|
|
6
|
+
import {OpDescriptionView} from './op-description-view'
|
|
7
|
+
import {OpIcon} from './op-icon'
|
|
8
|
+
import './op-description.scss'
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
Every operation can be in one of the following states (detected automatically):
|
|
12
|
+
- Ephemeral
|
|
13
|
+
- Pending (not processed by StellarCore yet)
|
|
14
|
+
- Rejected (rejected by Horizon or StellarCore during execution)
|
|
15
|
+
- Applied
|
|
16
|
+
- Processed (applied to the ledger and resulted in some on-chain state changes)
|
|
17
|
+
- Processed without effects (applied to the ledger but yielded no changes in the ledger state)
|
|
18
|
+
- Failed during the execution
|
|
19
|
+
|
|
20
|
+
Display context
|
|
21
|
+
- Transaction context
|
|
22
|
+
- All operations displayed
|
|
23
|
+
- Source account always visible
|
|
24
|
+
- No fee charges
|
|
25
|
+
- Effects displayed
|
|
26
|
+
- Unfiltered view context
|
|
27
|
+
- All operations displayed
|
|
28
|
+
- Source account always visible
|
|
29
|
+
- No fee charges
|
|
30
|
+
- All effects displayed
|
|
31
|
+
- Filtered view context
|
|
32
|
+
- Only operations related to the filter context (account/asset/order)
|
|
33
|
+
- Source account always visible
|
|
34
|
+
- Fee charges ony for account context
|
|
35
|
+
- Effects related to the filter context
|
|
36
|
+
- Account history view context
|
|
37
|
+
- Only operations DIRECTLY related to the current account (skip third-party operations for claimable balances, sponsorship, etc)
|
|
38
|
+
- Source account displayed only when operation (or transaction) source account not matches current account
|
|
39
|
+
- Fee charge effects displayed only if charged from the current account
|
|
40
|
+
- Effects related to the current account
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
const TxChargedFee = React.memo(function TxChargedFee({parsedTx, compact}) {
|
|
44
|
+
if (parsedTx.isEphemeral || !parsedTx.context?.account)
|
|
45
|
+
return null
|
|
46
|
+
const fee = parsedTx.effects.find(e => e.type === 'feeCharged')
|
|
47
|
+
if (!fee || !parsedTx.context.account.includes(fee.source))
|
|
48
|
+
return null
|
|
49
|
+
return <div className="op-container">
|
|
50
|
+
<div className="op-layout">
|
|
51
|
+
<OpIcon op="feeCharge"/>
|
|
52
|
+
<TxFeeEffect feeEffect={fee} compact={compact}/>
|
|
53
|
+
{!!compact && !parsedTx.isEphemeral && <TxFeeAccountingChanges amount={fee.charged}/>}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {ParsedTxDetails} parsedTx - Transaction descriptor
|
|
60
|
+
* @param {Function} [filter] - Filter matcher function
|
|
61
|
+
* @param {Boolean} [showFees] - Whether to display transaction fees
|
|
62
|
+
* @param {Boolean} [showEffects] - Whether to show operation effects
|
|
63
|
+
* @param {Boolean} [compact] - Compact view (without fee charges and accounting changes effects)
|
|
64
|
+
*/
|
|
65
|
+
export const TxOperationsList = React.memo(function TxOperationsList({
|
|
66
|
+
parsedTx,
|
|
67
|
+
filter = null,
|
|
68
|
+
showFees = true,
|
|
69
|
+
compact = false,
|
|
70
|
+
showEffects
|
|
71
|
+
}) {
|
|
72
|
+
const [effectsExpanded, setEffectsExpanded] = useState(false)
|
|
73
|
+
const toggleEffects = useCallback(e => setEffectsExpanded(e.expanded), [])
|
|
74
|
+
let {operations} = parsedTx
|
|
75
|
+
let opdiff = 0
|
|
76
|
+
//filter operations if filtered output is requested
|
|
77
|
+
if (filter) {
|
|
78
|
+
operations = operations.filter(filter)
|
|
79
|
+
opdiff = parsedTx.operations.length - operations.length
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return <div>
|
|
83
|
+
{!compact && showEffects !== false &&
|
|
84
|
+
<div className="tx-effects-toggle">
|
|
85
|
+
<Spoiler micro active expanded={effectsExpanded} showLess="Hide operation effects" showMore="Show operation effects"
|
|
86
|
+
onChange={toggleEffects} style={{margin: '0.2em'}}/>
|
|
87
|
+
</div>}
|
|
88
|
+
<div className="condensed">
|
|
89
|
+
{operations.map((op, i) => <div className="op-container" key={op.txHash + op.order + op.isEphemeral}>
|
|
90
|
+
<div className="op-layout">
|
|
91
|
+
<OpIcon op={op}/>
|
|
92
|
+
<div>
|
|
93
|
+
<OpDescriptionView key={parsedTx.txHash + op.order} op={op} compact={compact}/>
|
|
94
|
+
</div>
|
|
95
|
+
{!!compact && !op.isEphemeral && <OpAccountingChanges op={op}/>}
|
|
96
|
+
</div>
|
|
97
|
+
{effectsExpanded && <OpEffectsView effects={op.operation.effects}/>}
|
|
98
|
+
</div>)}
|
|
99
|
+
{showFees && <TxChargedFee {...{parsedTx, compact}}/>}
|
|
100
|
+
{opdiff > 0 && <div className="dimmed text-small block-indent">
|
|
101
|
+
{opdiff} more operation{opdiff > 1 && 's'} hidden
|
|
102
|
+
</div>}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
})
|