@stellar-expert/ui-framework 1.16.8 → 1.17.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 (78) hide show
  1. package/CLAUDE.md +35 -0
  2. package/README.md +1125 -3
  3. package/account/account-address.js +127 -127
  4. package/account/available-balance.js +22 -22
  5. package/account/identicon.js +90 -90
  6. package/account/signer-key.js +65 -64
  7. package/api/explorer-api-hooks.js +209 -202
  8. package/api/explorer-api-paginated-list-hooks.js +441 -440
  9. package/api/explorer-batch-info-loader.js +111 -85
  10. package/api/explorer-tx-api.js +28 -28
  11. package/api/ledger-stream.js +15 -0
  12. package/asset/amount.js +56 -56
  13. package/asset/asset-icon.js +41 -41
  14. package/asset/asset-issuer.js +21 -21
  15. package/asset/asset-link.js +107 -107
  16. package/asset/asset-list-hooks.js +59 -59
  17. package/asset/asset-meta-hooks.js +88 -88
  18. package/asset/asset-selector.js +72 -71
  19. package/claimable-balance/claimable-balance-claimants.js +23 -18
  20. package/contract/contract-api.js +15 -0
  21. package/contract/invocation-info-view.js +10 -2
  22. package/contract/sc-val.js +131 -107
  23. package/controls/button-group.js +25 -19
  24. package/controls/button.js +93 -78
  25. package/controls/code-block.js +42 -34
  26. package/controls/dropdown.js +318 -287
  27. package/controls/external-link.js +10 -4
  28. package/controls/info-tooltip.js +23 -16
  29. package/controls/slider.js +29 -19
  30. package/controls/tabs.js +94 -94
  31. package/controls/tooltip.js +244 -240
  32. package/controls/update-highlighter.js +32 -27
  33. package/date/date-selector.js +56 -54
  34. package/date/elapsed-time.js +28 -21
  35. package/dex/price-dynamic.js +53 -44
  36. package/directory/directory-hooks.js +109 -97
  37. package/effect/effect-description.js +346 -344
  38. package/errors/error-boundary.js +110 -97
  39. package/horizon/horizon-account-helpers.js +104 -104
  40. package/horizon/horizon-ledger-helpers.js +35 -35
  41. package/horizon/horizon-trades-helper.js +88 -88
  42. package/horizon/horizon-transaction-helpers.js +36 -36
  43. package/index.d.ts +1241 -0
  44. package/interaction/accordion.js +43 -35
  45. package/interaction/autofocus.js +13 -9
  46. package/interaction/block-select.js +64 -53
  47. package/interaction/copy-to-clipboard.js +25 -18
  48. package/interaction/inline-progress.js +20 -15
  49. package/interaction/qr-code.js +34 -34
  50. package/interaction/responsive.js +20 -20
  51. package/interaction/spoiler.js +52 -39
  52. package/interaction/theme-selector.js +13 -10
  53. package/ledger/ledger-entry-href-formatter.js +27 -26
  54. package/ledger/ledger-entry-link.js +93 -58
  55. package/ledger/ledger-info-parser.js +46 -18
  56. package/meta/page-meta-tags.js +243 -238
  57. package/module/dynamic-module.js +47 -47
  58. package/package.json +41 -40
  59. package/state/on-screen-hooks.js +22 -22
  60. package/state/page-visibility-helpers.js +29 -16
  61. package/state/page-visibility-hooks.js +13 -11
  62. package/state/screen-orientation-hooks.js +19 -15
  63. package/state/state-hooks.js +76 -76
  64. package/state/stellar-network-hooks.js +56 -44
  65. package/state/theme.js +29 -28
  66. package/stellar/key-type.js +92 -91
  67. package/stellar/ledger-generic-id.js +39 -39
  68. package/stellar/signature-hint-utils.js +65 -65
  69. package/toast/toast-notifications-block.js +59 -59
  70. package/tx/op-description-view.js +945 -942
  71. package/tx/op-icon.js +98 -98
  72. package/tx/parser/op-balance-changes.js +66 -66
  73. package/tx/parser/op-descriptor.js +51 -48
  74. package/tx/parser/tx-details-parser.js +81 -81
  75. package/tx/parser/tx-matcher.js +371 -371
  76. package/tx/parser/type-filter-matcher.js +126 -126
  77. package/tx/tx-list-hooks.js +32 -18
  78. package/tx/tx-operations-list.js +117 -116
@@ -1,345 +1,347 @@
1
- import React from 'react'
2
- import PropTypes from 'prop-types'
3
- import {AuthRequiredFlag, AuthRevocableFlag, AuthImmutableFlag, AuthClawbackEnabledFlag} from '@stellar/stellar-base'
4
- import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
5
- import {shortenString, formatWithAutoPrecision} from '@stellar-expert/formatter'
6
- import {CopyToClipboard} from '../interaction/copy-to-clipboard'
7
- import {AccountAddress} from '../account/account-address'
8
- import {ClaimableBalanceId} from '../claimable-balance/claimable-balance-id'
9
- import InvocationInfoView from '../contract/invocation-info-view'
10
- import {ScVal} from '../contract/sc-val'
11
- import {SignerKey} from '../account/signer-key'
12
- import {OfferLink} from '../ledger/ledger-entry-link'
13
- import {AssetLink} from '../asset/asset-link'
14
- import {Amount} from '../asset/amount'
15
-
16
- /**
17
- * @param {{}} effect
18
- * @param {{}} operation
19
- * @return {JSX.Element}
20
- * @constructor
21
- */
22
- export function EffectDescription({effect, operation}) {
23
- switch (effect.type) {
24
- case 'accountCreated':
25
- return <>Account <AccountAddress account={effect.account}/> created</>
26
- case 'accountRemoved':
27
- return <>Account <AccountAddress account={effect.source}/> removed</>
28
- case 'accountCredited':
29
- return <><Amount asset={effect.asset} amount={effect.amount} adjust/> credited to
30
- {effect.source.startsWith('C') ? ' contract' : ' account'} <AccountAddress account={effect.source}/></>
31
- case 'accountDebited':
32
- return <><Amount asset={effect.asset} amount={effect.amount} adjust/> debited from
33
- {effect.source.startsWith('C') ? ' contract' : ' account'} <AccountAddress account={effect.source}/></>
34
- case 'accountThresholdsUpdated':
35
- return <>Account <AccountAddress account={effect.source}/> set thresholds to {effect.thresholds.join('/')}</>
36
- case 'accountHomeDomainUpdated':
37
- return <>
38
- Account <AccountAddress account={effect.source}/> set home domain to{' '}
39
- <a href={'https://' + effect.home_domain} target="_blank" rel="noreferrer noopener">{effect.home_domain}</a>
40
- </>
41
- case 'accountFlagsUpdated':
42
- return <>Account <AccountAddress account={effect.source}/> updated authorization
43
- flags: {decodeAccountAuthFlags(effect.flags).join(', ')}</>
44
- case 'accountInflationDestinationUpdated':
45
- return <>Account <AccountAddress account={effect.source}/> set inflation destination
46
- to <AccountAddress account={effect.inflationDestination}/></>
47
- case 'accountSignerCreated':
48
- return <>Account <AccountAddress account={effect.source}/> added
49
- signer <AccountAddress account={effect.signer}/>(w:{effect.weight})</>
50
- case 'accountSignerUpdated':
51
- return <>Account <AccountAddress account={effect.source}/> updated
52
- signer <AccountAddress account={effect.signer}/>(w:{effect.weight})</>
53
- case 'accountSignerRemoved':
54
- return <>Account <AccountAddress account={effect.source}/> removed
55
- signer <AccountAddress account={effect.signer}/></>
56
- case 'trustlineCreated':
57
- return <>
58
- Account <AccountAddress account={effect.source}/> established trustline to <AssetLink asset={effect.asset}/> with
59
- limit <Amount asset={effect.asset} amount={effect.limit} issuer={false} icon={false} adjust/>
60
- </>
61
- case 'trustlineUpdated':
62
- return <>
63
- Account <AccountAddress account={effect.source}/> updated trustline to <AssetLink asset={effect.asset}/> with
64
- limit <Amount asset={effect.asset} amount={effect.limit} issuer={false} icon={false} adjust/>
65
- </>
66
- case 'trustlineRemoved':
67
- return <>Account <AccountAddress account={effect.source}/> removed trustline to <AssetLink asset={effect.asset}/></>
68
- case 'trustlineAuthorizationUpdated':
69
- return <>
70
- Account <AccountAddress account={effect.source}/> updated <AssetLink asset={effect.asset}/> trustline authorization
71
- flags ({decodeTrustlineFlags(effect.flags)}) for <AccountAddress account={effect.trustor}/>
72
- </>
73
- case 'assetMinted':
74
- return <><Amount asset={effect.asset} amount={effect.amount} adjust/> minted</>
75
- case 'assetBurned':
76
- return <>Account <AccountAddress account={effect.source}/> burned <Amount asset={effect.asset} amount={effect.amount} adjust/></>
77
- case 'offerCreated':
78
- return <OfferChange effect={effect} action="created"/>
79
- case 'offerUpdated':
80
- return <OfferChange effect={effect} action="updated"/>
81
- case 'offerRemoved':
82
- return <>DEX offer <OfferLink offer={effect.offer}/> removed</>
83
- case 'trade':
84
- return <span>
85
- Account <AccountAddress account={effect.source}/> exchanged{' '}
86
- <span className="nowrap">
87
- <Amount asset={effect.asset[0]} amount={effect.amount[0]} adjust/>{' '}
88
- <span className="icon icon-shuffle color-primary"/>{' '}
89
- <Amount asset={effect.asset[1]} amount={effect.amount[1]} adjust/>
90
- </span>{' '}
91
- {effect.pool ?
92
- <>(pool <AssetLink asset={effect.pool}/>)</> :
93
- <>(DEX offer <OfferLink offer={effect.offer}/> by <AccountAddress account={effect.seller}/>)</>}
94
- </span>
95
- case 'sequenceBumped':
96
- return <>Account <AccountAddress account={effect.source}/> bumped sequence to {effect.sequence}</>
97
- case 'dataEntryCreated':
98
- case 'dataEntryUpdated':
99
- return <>
100
- Account <AccountAddress account={effect.source}/> {effect.type === 'dataEntryCreated' ? 'created' : 'updated'} data entry
101
- <span className="word-break condensed text-small"><code>"{effect.name}"</code> = <code>"{effect.value}"</code></span>
102
- </>
103
- case 'dataEntryRemoved':
104
- return <>
105
- Account <AccountAddress account={effect.source}/> removed data entry
106
- <span className="word-break condensed text-small"><code>"{effect.name}"</code></span>
107
- </>
108
- case 'claimableBalanceCreated':
109
- return <>
110
- Account <AccountAddress account={effect.source}/> created claimable
111
- balance <ClaimableBalanceId balance={effect.balance}/> <Amount asset={effect.asset} amount={effect.amount} adjust/>
112
- </>
113
- case 'claimableBalanceRemoved':
114
- return <>
115
- Account <AccountAddress account={effect.source}/> claimed claimable
116
- balance <ClaimableBalanceId balance={effect.balance}/> <Amount asset={effect.asset} amount={effect.amount} adjust/>
117
- </>
118
- case 'accountSponsorshipCreated':
119
- case 'accountSponsorshipUpdated':
120
- return <>
121
- Account <AccountAddress account={effect.sponsor}/> sponsored account base reserve
122
- for <AccountAddress account={effect.account}/>
123
- </>
124
- case 'accountSponsorshipRemoved':
125
- return <>
126
- Account <AccountAddress account={effect.prevSponsor}/> revoked account sponsorship
127
- for <AccountAddress account={effect.account}/>
128
- </>
129
- case 'trustlineSponsorshipCreated':
130
- case 'trustlineSponsorshipUpdated':
131
- return <>
132
- Account <AccountAddress account={effect.sponsor}/> sponsored <AssetLink asset={effect.asset}/> trustline reserve
133
- for <AccountAddress account={effect.account}/>
134
- </>
135
- case 'trustlineSponsorshipRemoved':
136
- return <>
137
- Account <AccountAddress account={effect.prevSponsor}/> revoked <AssetLink asset={effect.asset}/> trustline sponsorship
138
- for <AccountAddress account={effect.account}/>
139
- </>
140
- case 'offerSponsorshipCreated':
141
- case 'offerSponsorshipUpdated':
142
- return <>
143
- Account <AccountAddress account={effect.sponsor}/> sponsored offer <OfferLink offer={effect.offer}/> reserve
144
- for <AccountAddress account={effect.account}/>
145
- </>
146
- case 'offerSponsorshipRemoved':
147
- return <>
148
- Account <AccountAddress account={effect.prevSponsor}/> revoked offer <OfferLink offer={effect.offer}/> sponsorship
149
- for <AccountAddress account={effect.account}/>
150
- </>
151
- case 'dataSponsorshipCreated':
152
- case 'dataSponsorshipUpdated':
153
- return <>
154
- Account <AccountAddress account={effect.sponsor}/> sponsored "{shortenString(effect.name)}" data reserve
155
- for <AccountAddress account={effect.account}/>
156
- </>
157
- case 'dataSponsorshipRemoved':
158
- return <>
159
- Account <AccountAddress account={effect.prevSponsor}/> revoked "{shortenString(effect.name)}" data sponsorship
160
- for <AccountAddress account={effect.account}/>
161
- </>
162
- case 'claimableBalanceSponsorshipCreated':
163
- case 'claimableBalanceSponsorshipUpdated':
164
- return <>
165
- Account <AccountAddress account={effect.sponsor}/> sponsored
166
- claimable balance <ClaimableBalanceId balance={effect.balance}/>
167
- {effect.source !== effect.sponsor && <> for account <AccountAddress account={effect.source}/></>}
168
- </>
169
- case 'claimableBalanceSponsorshipRemoved':
170
- return <>
171
- Account <AccountAddress account={effect.prevSponsor}/> revoked claimable
172
- balance <ClaimableBalanceId balance={effect.balance}/> sponsorship
173
- {effect.source !== effect.prevSponsor && <> for account <AccountAddress account={effect.source}/></>}
174
- </>
175
- case 'signerSponsorshipCreated':
176
- case 'signerSponsorshipUpdated':
177
- return <>
178
- Account <AccountAddress account={effect.sponsor}/> sponsored signer <SignerKey account={effect.signer}/>{' '}
179
- for account <AccountAddress account={effect.account}/>
180
- </>
181
- case 'signerSponsorshipRemoved':
182
- return <>
183
- Account <AccountAddress account={effect.prevSponsor}/> revoked signer <SignerKey account={effect.signer}/> sponsorship{' '}
184
- for account <AccountAddress account={effect.account}/>
185
- </>
186
- case 'liquidityPoolDeposited':
187
- return <>
188
- <span className="nowrap">
189
- <Amount asset={effect.assets[0].asset} amount={effect.assets[0].amount} adjust/>{' '}
190
- and <Amount asset={effect.assets[1].asset} amount={effect.assets[1].amount} adjust/>
191
- </span>{' '}
192
- deposited to liquidity <span className="nowrap">pool <AssetLink asset={effect.pool}/></span>{' '}
193
- <i className="icon icon-shuffle"/> <span className="nowrap">{formatWithAutoPrecision(effect.shares)} pool shares</span>
194
- </>
195
- case 'liquidityPoolWithdrew':
196
- return <>
197
- {formatWithAutoPrecision(effect.shares)} pool shares <i className="icon icon-shuffle"/>{' '}
198
- <span className="nowrap">
199
- <Amount asset={effect.assets[0].asset} amount={effect.assets[0].amount} adjust/>{' '}
200
- and <Amount asset={effect.assets[1].asset} amount={effect.assets[1].amount} adjust/>
201
- </span>{' '}
202
- withdrawn from <span className="nowrap"><AssetLink asset={effect.pool}/> pool</span>
203
- </>
204
- case 'liquidityPoolCreated':
205
- return <>Liquidity pool <AssetLink asset={effect.pool}/> created</>
206
- case 'liquidityPoolUpdated':
207
- return <>Liquidity pool <AssetLink asset={effect.pool}/> updated / {' '}
208
- <span className="nowrap">
209
- <Amount asset={effect.reserves[0].asset} amount={effect.reserves[0].amount} adjust/> <i className="icon-plus"/>{' '}
210
- <Amount asset={effect.reserves[1].asset} amount={effect.reserves[1].amount} adjust/></span> /{' '}
211
- <span className="nowrap">{formatWithAutoPrecision(effect.shares)} pool shares</span>,{' '}
212
- <span className="nowrap">{effect.accounts} accounts</span></>
213
- case 'liquidityPoolRemoved':
214
- return <>Liquidity pool <AssetLink asset={effect.pool}/> removed</>
215
- case 'inflation':
216
- return <>Inflation distribution initialized</>
217
- case 'contractCodeUploaded':
218
- case 'contractCodeRestored':
219
- return <>Contract code {getEffectAction(effect, 'contractCode')}
220
- <LedgerKeyHint effect={effect}><ContractCodeWasm wasm={effect.wasm || effect.wasmHash}/></LedgerKeyHint></>
221
- case 'contractCreated':
222
- case 'contractUpdated':
223
- case 'contractRestored':
224
- return <>Contract <AccountAddress account={effect.contract}/> {getEffectAction(effect, 'contract')}
225
- <ContractDetails effect={effect}/><ConstructorDetails effect={effect}/></>
226
- case 'contractInvoked':
227
- return <>{effect.depth > 0 &&
228
- <i className="icon-level-down text-tiny color-primary" style={{paddingLeft: (effect.depth - 1) + 'em'}}/>}
229
- Invoked contract <AccountAddress account={effect.contract}/>{' '}
230
- <InvocationInfoView func={effect.function} args={effect.rawArgs} contract={effect.contract} result={effect.result} sac={operation.operation.sacMap?.[effect.contract]}/>
231
- </>
232
- case 'contractEvent':
233
- return <>Contract <AccountAddress account={effect.contract}/> raised event <ScVal value={effect.rawTopics}/>{' '}
234
- with data <ScVal value={effect.rawData}/></>
235
- case 'contractDataCreated':
236
- case 'contractDataUpdated':
237
- case 'contractDataRestored':
238
- return <>Contract <AccountAddress account={effect.owner}/> {getEffectAction(effect, 'contractData')}
239
- {effect.durability} data{' '}
240
- <LedgerKeyHint effect={effect}><ScVal value={effect.key}/></LedgerKeyHint> = <ScVal value={effect.value}/>
241
- </>
242
- case 'contractDataRemoved':
243
- return <>Contract <AccountAddress account={effect.owner}/> removed {effect.durability}{' '}
244
- data <LedgerKeyHint effect={effect}><ScVal value={effect.key}/></LedgerKeyHint></>
245
- case 'contractError':
246
- let errCode = effect.code
247
- if (errCode?.name) {
248
- errCode = errCode.name
249
- }
250
- return <>Execution error {errCode ? <><code>{errCode}</code> </> : null}in <AccountAddress account={effect.contract}/>{': '}
251
- <code>{JSON.stringify(effect.details)}</code> </>
252
- case 'setTtl':
253
- return <>Time-to-live extended to ledger {effect.ttl} for {!!effect.owner && <AccountAddress account={effect.owner}/>}{' '}
254
- {ledgerEntryKind[effect.kind]}{' '}
255
- <CopyToClipboard text={effect.keyHash}>
256
- <code title={effect.keyHash + ' - click to copy'}>{shortenString(effect.keyHash, 12)}</code>
257
- </CopyToClipboard></>
258
- case 'feeCharged':
259
- return <><Amount asset="XLM" amount={effect.charged} adjust/> fee charged from <AccountAddress account={effect.source}/> (
260
- bid <Amount asset="XLM" amount={effect.bid} adjust/>)</>
261
- }
262
- return <><i className="icon-warning color-warning"/> Effect {effect.type} not supported</>
263
- }
264
-
265
- EffectDescription.propTypes = {
266
- effect: PropTypes.object.isRequired
267
- }
268
-
269
- function decodeAccountAuthFlags(flags) {
270
- const res = []
271
- if ((flags & AuthRequiredFlag) === AuthRequiredFlag) {
272
- res.push('REQUIRED')
273
- }
274
- if ((flags & AuthRevocableFlag) === AuthRevocableFlag) {
275
- res.push('REVOCABLE')
276
- }
277
- if ((flags & AuthImmutableFlag) === AuthImmutableFlag) {
278
- res.push('IMMUTABLE')
279
- }
280
- if ((flags & AuthClawbackEnabledFlag) === AuthClawbackEnabledFlag) {
281
- res.push('CLAWBACK_ENABLED')
282
- }
283
- return res
284
- }
285
-
286
- function decodeTrustlineFlags(flags) {
287
- if ((flags & 2) === 2)
288
- return 'FROZEN'
289
- if ((flags & 1) === 1)
290
- return 'AUTHORIZED'
291
- return 'UNAUTHORIZED'
292
- }
293
-
294
- function OfferChange({effect, action}) {
295
- return <>DEX offer <OfferLink offer={effect.offer}/> {action} /{' '}
296
- <span className="nowrap">
297
- <Amount asset={effect.asset[0]} amount={effect.amount} adjust/> <i className="icon icon-shuffle color-primary"/>{' '}
298
- <AssetLink asset={effect.asset[1]}/>
299
- </span> at <span className="nowrap">
300
- {formatWithAutoPrecision(parseFloat(effect.price))}{' '}
301
- {AssetDescriptor.parse(effect.asset[1]).toCurrency()}/{AssetDescriptor.parse(effect.asset[0]).toCurrency()}
302
- </span>
303
- </>
304
- }
305
-
306
- function ContractDetails({effect}) {
307
- switch (effect.kind) {
308
- case 'wasm':
309
- return <>from WASM <ContractCodeWasm wasm={effect.wasmHash}/></>
310
- case 'fromAddress':
311
- return <>from issuer <AccountAddress account={effect.issuer}/> with salt <ContractCodeWasm wasm={effect.salt}/></>
312
- case 'fromAsset':
313
- return <>from asset <AssetLink asset={effect.asset}/></>
314
- default:
315
- return <span className="dimme">(contract type not supported)</span>
316
- }
317
- }
318
-
319
- function ConstructorDetails({effect}) {
320
- if (!effect.constructorArgs?.length)
321
- return null
322
- return <span>{' '}<InvocationInfoView func="__constructor" args={effect.constructorArgs}/></span>
323
- }
324
-
325
- function ContractCodeWasm({wasm}) {
326
- return <>
327
- <code title={wasm}>{shortenString(wasm, 16)}</code>
328
- <CopyToClipboard text={wasm}/>
329
- </>
330
- }
331
-
332
- function LedgerKeyHint({effect, children}) {
333
- if (!effect.keyHash)
334
- return children
335
- return <span title={'Ledger key ' + effect.keyHash}>{children}</span>
336
- }
337
-
338
- function getEffectAction(effect, prefix) {
339
- return effect.type.replace(prefix, '').toLowerCase() + ' '
340
- }
341
-
342
- const ledgerEntryKind = {
343
- contractData: 'contract state entry',
344
- contractCode: 'contract code WASM'
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import {AuthRequiredFlag, AuthRevocableFlag, AuthImmutableFlag, AuthClawbackEnabledFlag} from '@stellar/stellar-base'
4
+ import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
5
+ import {shortenString, formatWithAutoPrecision} from '@stellar-expert/formatter'
6
+ import {CopyToClipboard} from '../interaction/copy-to-clipboard'
7
+ import {AccountAddress} from '../account/account-address'
8
+ import {ClaimableBalanceId} from '../claimable-balance/claimable-balance-id'
9
+ import InvocationInfoView from '../contract/invocation-info-view'
10
+ import {ScVal} from '../contract/sc-val'
11
+ import {SignerKey} from '../account/signer-key'
12
+ import {OfferLink} from '../ledger/ledger-entry-link'
13
+ import {AssetLink} from '../asset/asset-link'
14
+ import {Amount} from '../asset/amount'
15
+
16
+ /**
17
+ * Renders a human-readable description of a Stellar transaction effect
18
+ * @param {Object} props
19
+ * @param {Object} props.effect - Parsed transaction effect object
20
+ * @param {string} props.effect.type - Effect type identifier
21
+ * @param {Object} [props.operation] - Parent operation context
22
+ * @return {JSX.Element}
23
+ */
24
+ export function EffectDescription({effect, operation}) {
25
+ switch (effect.type) {
26
+ case 'accountCreated':
27
+ return <>Account <AccountAddress account={effect.account}/> created</>
28
+ case 'accountRemoved':
29
+ return <>Account <AccountAddress account={effect.source}/> removed</>
30
+ case 'accountCredited':
31
+ return <><Amount asset={effect.asset} amount={effect.amount} adjust/> credited to
32
+ {effect.source.startsWith('C') ? ' contract' : ' account'} <AccountAddress account={effect.source}/></>
33
+ case 'accountDebited':
34
+ return <><Amount asset={effect.asset} amount={effect.amount} adjust/> debited from
35
+ {effect.source.startsWith('C') ? ' contract' : ' account'} <AccountAddress account={effect.source}/></>
36
+ case 'accountThresholdsUpdated':
37
+ return <>Account <AccountAddress account={effect.source}/> set thresholds to {effect.thresholds.join('/')}</>
38
+ case 'accountHomeDomainUpdated':
39
+ return <>
40
+ Account <AccountAddress account={effect.source}/> set home domain to{' '}
41
+ <a href={'https://' + effect.home_domain} target="_blank" rel="noreferrer noopener">{effect.home_domain}</a>
42
+ </>
43
+ case 'accountFlagsUpdated':
44
+ return <>Account <AccountAddress account={effect.source}/> updated authorization
45
+ flags: {decodeAccountAuthFlags(effect.flags).join(', ')}</>
46
+ case 'accountInflationDestinationUpdated':
47
+ return <>Account <AccountAddress account={effect.source}/> set inflation destination
48
+ to <AccountAddress account={effect.inflationDestination}/></>
49
+ case 'accountSignerCreated':
50
+ return <>Account <AccountAddress account={effect.source}/> added
51
+ signer <AccountAddress account={effect.signer}/>(w:{effect.weight})</>
52
+ case 'accountSignerUpdated':
53
+ return <>Account <AccountAddress account={effect.source}/> updated
54
+ signer <AccountAddress account={effect.signer}/>(w:{effect.weight})</>
55
+ case 'accountSignerRemoved':
56
+ return <>Account <AccountAddress account={effect.source}/> removed
57
+ signer <AccountAddress account={effect.signer}/></>
58
+ case 'trustlineCreated':
59
+ return <>
60
+ Account <AccountAddress account={effect.source}/> established trustline to <AssetLink asset={effect.asset}/> with
61
+ limit <Amount asset={effect.asset} amount={effect.limit} issuer={false} icon={false} adjust/>
62
+ </>
63
+ case 'trustlineUpdated':
64
+ return <>
65
+ Account <AccountAddress account={effect.source}/> updated trustline to <AssetLink asset={effect.asset}/> with
66
+ limit <Amount asset={effect.asset} amount={effect.limit} issuer={false} icon={false} adjust/>
67
+ </>
68
+ case 'trustlineRemoved':
69
+ return <>Account <AccountAddress account={effect.source}/> removed trustline to <AssetLink asset={effect.asset}/></>
70
+ case 'trustlineAuthorizationUpdated':
71
+ return <>
72
+ Account <AccountAddress account={effect.source}/> updated <AssetLink asset={effect.asset}/> trustline authorization
73
+ flags ({decodeTrustlineFlags(effect.flags)}) for <AccountAddress account={effect.trustor}/>
74
+ </>
75
+ case 'assetMinted':
76
+ return <><Amount asset={effect.asset} amount={effect.amount} adjust/> minted</>
77
+ case 'assetBurned':
78
+ return <>Account <AccountAddress account={effect.source}/> burned <Amount asset={effect.asset} amount={effect.amount} adjust/></>
79
+ case 'offerCreated':
80
+ return <OfferChange effect={effect} action="created"/>
81
+ case 'offerUpdated':
82
+ return <OfferChange effect={effect} action="updated"/>
83
+ case 'offerRemoved':
84
+ return <>DEX offer <OfferLink offer={effect.offer}/> removed</>
85
+ case 'trade':
86
+ return <span>
87
+ Account <AccountAddress account={effect.source}/> exchanged{' '}
88
+ <span className="nowrap">
89
+ <Amount asset={effect.asset[0]} amount={effect.amount[0]} adjust/>{' '}
90
+ <span className="icon icon-shuffle color-primary"/>{' '}
91
+ <Amount asset={effect.asset[1]} amount={effect.amount[1]} adjust/>
92
+ </span>{' '}
93
+ {effect.pool ?
94
+ <>(pool <AssetLink asset={effect.pool}/>)</> :
95
+ <>(DEX offer <OfferLink offer={effect.offer}/> by <AccountAddress account={effect.seller}/>)</>}
96
+ </span>
97
+ case 'sequenceBumped':
98
+ return <>Account <AccountAddress account={effect.source}/> bumped sequence to {effect.sequence}</>
99
+ case 'dataEntryCreated':
100
+ case 'dataEntryUpdated':
101
+ return <>
102
+ Account <AccountAddress account={effect.source}/> {effect.type === 'dataEntryCreated' ? 'created' : 'updated'} data entry
103
+ <span className="word-break condensed text-small"><code>"{effect.name}"</code> = <code>"{effect.value}"</code></span>
104
+ </>
105
+ case 'dataEntryRemoved':
106
+ return <>
107
+ Account <AccountAddress account={effect.source}/> removed data entry
108
+ <span className="word-break condensed text-small"><code>"{effect.name}"</code></span>
109
+ </>
110
+ case 'claimableBalanceCreated':
111
+ return <>
112
+ Account <AccountAddress account={effect.source}/> created claimable
113
+ balance <ClaimableBalanceId balance={effect.balance}/> <Amount asset={effect.asset} amount={effect.amount} adjust/>
114
+ </>
115
+ case 'claimableBalanceRemoved':
116
+ return <>
117
+ Account <AccountAddress account={effect.source}/> claimed claimable
118
+ balance <ClaimableBalanceId balance={effect.balance}/> <Amount asset={effect.asset} amount={effect.amount} adjust/>
119
+ </>
120
+ case 'accountSponsorshipCreated':
121
+ case 'accountSponsorshipUpdated':
122
+ return <>
123
+ Account <AccountAddress account={effect.sponsor}/> sponsored account base reserve
124
+ for <AccountAddress account={effect.account}/>
125
+ </>
126
+ case 'accountSponsorshipRemoved':
127
+ return <>
128
+ Account <AccountAddress account={effect.prevSponsor}/> revoked account sponsorship
129
+ for <AccountAddress account={effect.account}/>
130
+ </>
131
+ case 'trustlineSponsorshipCreated':
132
+ case 'trustlineSponsorshipUpdated':
133
+ return <>
134
+ Account <AccountAddress account={effect.sponsor}/> sponsored <AssetLink asset={effect.asset}/> trustline reserve
135
+ for <AccountAddress account={effect.account}/>
136
+ </>
137
+ case 'trustlineSponsorshipRemoved':
138
+ return <>
139
+ Account <AccountAddress account={effect.prevSponsor}/> revoked <AssetLink asset={effect.asset}/> trustline sponsorship
140
+ for <AccountAddress account={effect.account}/>
141
+ </>
142
+ case 'offerSponsorshipCreated':
143
+ case 'offerSponsorshipUpdated':
144
+ return <>
145
+ Account <AccountAddress account={effect.sponsor}/> sponsored offer <OfferLink offer={effect.offer}/> reserve
146
+ for <AccountAddress account={effect.account}/>
147
+ </>
148
+ case 'offerSponsorshipRemoved':
149
+ return <>
150
+ Account <AccountAddress account={effect.prevSponsor}/> revoked offer <OfferLink offer={effect.offer}/> sponsorship
151
+ for <AccountAddress account={effect.account}/>
152
+ </>
153
+ case 'dataSponsorshipCreated':
154
+ case 'dataSponsorshipUpdated':
155
+ return <>
156
+ Account <AccountAddress account={effect.sponsor}/> sponsored "{shortenString(effect.name)}" data reserve
157
+ for <AccountAddress account={effect.account}/>
158
+ </>
159
+ case 'dataSponsorshipRemoved':
160
+ return <>
161
+ Account <AccountAddress account={effect.prevSponsor}/> revoked "{shortenString(effect.name)}" data sponsorship
162
+ for <AccountAddress account={effect.account}/>
163
+ </>
164
+ case 'claimableBalanceSponsorshipCreated':
165
+ case 'claimableBalanceSponsorshipUpdated':
166
+ return <>
167
+ Account <AccountAddress account={effect.sponsor}/> sponsored
168
+ claimable balance <ClaimableBalanceId balance={effect.balance}/>
169
+ {effect.source !== effect.sponsor && <> for account <AccountAddress account={effect.source}/></>}
170
+ </>
171
+ case 'claimableBalanceSponsorshipRemoved':
172
+ return <>
173
+ Account <AccountAddress account={effect.prevSponsor}/> revoked claimable
174
+ balance <ClaimableBalanceId balance={effect.balance}/> sponsorship
175
+ {effect.source !== effect.prevSponsor && <> for account <AccountAddress account={effect.source}/></>}
176
+ </>
177
+ case 'signerSponsorshipCreated':
178
+ case 'signerSponsorshipUpdated':
179
+ return <>
180
+ Account <AccountAddress account={effect.sponsor}/> sponsored signer <SignerKey account={effect.signer}/>{' '}
181
+ for account <AccountAddress account={effect.account}/>
182
+ </>
183
+ case 'signerSponsorshipRemoved':
184
+ return <>
185
+ Account <AccountAddress account={effect.prevSponsor}/> revoked signer <SignerKey account={effect.signer}/> sponsorship{' '}
186
+ for account <AccountAddress account={effect.account}/>
187
+ </>
188
+ case 'liquidityPoolDeposited':
189
+ return <>
190
+ <span className="nowrap">
191
+ <Amount asset={effect.assets[0].asset} amount={effect.assets[0].amount} adjust/>{' '}
192
+ and <Amount asset={effect.assets[1].asset} amount={effect.assets[1].amount} adjust/>
193
+ </span>{' '}
194
+ deposited to liquidity <span className="nowrap">pool <AssetLink asset={effect.pool}/></span>{' '}
195
+ <i className="icon icon-shuffle"/> <span className="nowrap">{formatWithAutoPrecision(effect.shares)} pool shares</span>
196
+ </>
197
+ case 'liquidityPoolWithdrew':
198
+ return <>
199
+ {formatWithAutoPrecision(effect.shares)} pool shares <i className="icon icon-shuffle"/>{' '}
200
+ <span className="nowrap">
201
+ <Amount asset={effect.assets[0].asset} amount={effect.assets[0].amount} adjust/>{' '}
202
+ and <Amount asset={effect.assets[1].asset} amount={effect.assets[1].amount} adjust/>
203
+ </span>{' '}
204
+ withdrawn from <span className="nowrap"><AssetLink asset={effect.pool}/> pool</span>
205
+ </>
206
+ case 'liquidityPoolCreated':
207
+ return <>Liquidity pool <AssetLink asset={effect.pool}/> created</>
208
+ case 'liquidityPoolUpdated':
209
+ return <>Liquidity pool <AssetLink asset={effect.pool}/> updated / {' '}
210
+ <span className="nowrap">
211
+ <Amount asset={effect.reserves[0].asset} amount={effect.reserves[0].amount} adjust/> <i className="icon-plus"/>{' '}
212
+ <Amount asset={effect.reserves[1].asset} amount={effect.reserves[1].amount} adjust/></span> /{' '}
213
+ <span className="nowrap">{formatWithAutoPrecision(effect.shares)} pool shares</span>,{' '}
214
+ <span className="nowrap">{effect.accounts} accounts</span></>
215
+ case 'liquidityPoolRemoved':
216
+ return <>Liquidity pool <AssetLink asset={effect.pool}/> removed</>
217
+ case 'inflation':
218
+ return <>Inflation distribution initialized</>
219
+ case 'contractCodeUploaded':
220
+ case 'contractCodeRestored':
221
+ return <>Contract code {getEffectAction(effect, 'contractCode')}
222
+ <LedgerKeyHint effect={effect}><ContractCodeWasm wasm={effect.wasm || effect.wasmHash}/></LedgerKeyHint></>
223
+ case 'contractCreated':
224
+ case 'contractUpdated':
225
+ case 'contractRestored':
226
+ return <>Contract <AccountAddress account={effect.contract}/> {getEffectAction(effect, 'contract')}
227
+ <ContractDetails effect={effect}/><ConstructorDetails effect={effect}/></>
228
+ case 'contractInvoked':
229
+ return <>{effect.depth > 0 &&
230
+ <i className="icon-level-down text-tiny color-primary" style={{paddingLeft: (effect.depth - 1) + 'em'}}/>}
231
+ Invoked contract <AccountAddress account={effect.contract}/>{' '}
232
+ <InvocationInfoView func={effect.function} args={effect.rawArgs} contract={effect.contract} result={effect.result} sac={operation.operation.sacMap?.[effect.contract]}/>
233
+ </>
234
+ case 'contractEvent':
235
+ return <>Contract <AccountAddress account={effect.contract}/> raised event <ScVal value={effect.rawTopics}/>{' '}
236
+ with data <ScVal value={effect.rawData}/></>
237
+ case 'contractDataCreated':
238
+ case 'contractDataUpdated':
239
+ case 'contractDataRestored':
240
+ return <>Contract <AccountAddress account={effect.owner}/> {getEffectAction(effect, 'contractData')}
241
+ {effect.durability} data{' '}
242
+ <LedgerKeyHint effect={effect}><ScVal value={effect.key}/></LedgerKeyHint> = <ScVal value={effect.value}/>
243
+ </>
244
+ case 'contractDataRemoved':
245
+ return <>Contract <AccountAddress account={effect.owner}/> removed {effect.durability}{' '}
246
+ data <LedgerKeyHint effect={effect}><ScVal value={effect.key}/></LedgerKeyHint></>
247
+ case 'contractError':
248
+ let errCode = effect.code
249
+ if (errCode?.name) {
250
+ errCode = errCode.name
251
+ }
252
+ return <>Execution error {errCode ? <><code>{errCode}</code> </> : null}in <AccountAddress account={effect.contract}/>{': '}
253
+ <code>{JSON.stringify(effect.details)}</code> </>
254
+ case 'setTtl':
255
+ return <>Time-to-live extended to ledger {effect.ttl} for {!!effect.owner && <AccountAddress account={effect.owner}/>}{' '}
256
+ {ledgerEntryKind[effect.kind]}{' '}
257
+ <CopyToClipboard text={effect.keyHash}>
258
+ <code title={effect.keyHash + ' - click to copy'}>{shortenString(effect.keyHash, 12)}</code>
259
+ </CopyToClipboard></>
260
+ case 'feeCharged':
261
+ return <><Amount asset="XLM" amount={effect.charged} adjust/> fee charged from <AccountAddress account={effect.source}/> (
262
+ bid <Amount asset="XLM" amount={effect.bid} adjust/>)</>
263
+ }
264
+ return <><i className="icon-warning color-warning"/> Effect {effect.type} not supported</>
265
+ }
266
+
267
+ EffectDescription.propTypes = {
268
+ effect: PropTypes.object.isRequired
269
+ }
270
+
271
+ function decodeAccountAuthFlags(flags) {
272
+ const res = []
273
+ if ((flags & AuthRequiredFlag) === AuthRequiredFlag) {
274
+ res.push('REQUIRED')
275
+ }
276
+ if ((flags & AuthRevocableFlag) === AuthRevocableFlag) {
277
+ res.push('REVOCABLE')
278
+ }
279
+ if ((flags & AuthImmutableFlag) === AuthImmutableFlag) {
280
+ res.push('IMMUTABLE')
281
+ }
282
+ if ((flags & AuthClawbackEnabledFlag) === AuthClawbackEnabledFlag) {
283
+ res.push('CLAWBACK_ENABLED')
284
+ }
285
+ return res
286
+ }
287
+
288
+ function decodeTrustlineFlags(flags) {
289
+ if ((flags & 2) === 2)
290
+ return 'FROZEN'
291
+ if ((flags & 1) === 1)
292
+ return 'AUTHORIZED'
293
+ return 'UNAUTHORIZED'
294
+ }
295
+
296
+ function OfferChange({effect, action}) {
297
+ return <>DEX offer <OfferLink offer={effect.offer}/> {action} /{' '}
298
+ <span className="nowrap">
299
+ <Amount asset={effect.asset[0]} amount={effect.amount} adjust/> <i className="icon icon-shuffle color-primary"/>{' '}
300
+ <AssetLink asset={effect.asset[1]}/>
301
+ </span> at <span className="nowrap">
302
+ {formatWithAutoPrecision(parseFloat(effect.price))}{' '}
303
+ {AssetDescriptor.parse(effect.asset[1]).toCurrency()}/{AssetDescriptor.parse(effect.asset[0]).toCurrency()}
304
+ </span>
305
+ </>
306
+ }
307
+
308
+ function ContractDetails({effect}) {
309
+ switch (effect.kind) {
310
+ case 'wasm':
311
+ return <>from WASM <ContractCodeWasm wasm={effect.wasmHash}/></>
312
+ case 'fromAddress':
313
+ return <>from issuer <AccountAddress account={effect.issuer}/> with salt <ContractCodeWasm wasm={effect.salt}/></>
314
+ case 'fromAsset':
315
+ return <>from asset <AssetLink asset={effect.asset}/></>
316
+ default:
317
+ return <span className="dimme">(contract type not supported)</span>
318
+ }
319
+ }
320
+
321
+ function ConstructorDetails({effect}) {
322
+ if (!effect.constructorArgs?.length)
323
+ return null
324
+ return <span>{' '}<InvocationInfoView func="__constructor" args={effect.constructorArgs}/></span>
325
+ }
326
+
327
+ function ContractCodeWasm({wasm}) {
328
+ return <>
329
+ <code title={wasm}>{shortenString(wasm, 16)}</code>
330
+ <CopyToClipboard text={wasm}/>
331
+ </>
332
+ }
333
+
334
+ function LedgerKeyHint({effect, children}) {
335
+ if (!effect.keyHash)
336
+ return children
337
+ return <span title={'Ledger key ' + effect.keyHash}>{children}</span>
338
+ }
339
+
340
+ function getEffectAction(effect, prefix) {
341
+ return effect.type.replace(prefix, '').toLowerCase() + ' '
342
+ }
343
+
344
+ const ledgerEntryKind = {
345
+ contractData: 'contract state entry',
346
+ contractCode: 'contract code WASM'
345
347
  }