@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,85 +1,111 @@
1
- import {throttle} from 'throttle-debounce'
2
-
3
- export class ExplorerBatchInfoLoader {
4
- constructor(fetchCallback, processResponseCallback) {
5
- this.pendingRequests = {}
6
- this.requestsQueue = []
7
- this.fetchDataFromServer = throttle(500, this.fetchDataFromServer, {noLeading: true})
8
- this.fetchCallback = fetchCallback
9
- this.processResponseCallback = processResponseCallback
10
- }
11
-
12
- pendingRequests
13
-
14
- requestsQueue = []
15
-
16
- fetchCallback
17
-
18
- processResponseCallback
19
-
20
- loadEntry(key) {
21
- //check whether the same address is being fetched right now
22
- const pending = this.pendingRequests[key]
23
- if (pending) return pending
24
- const promise = new Promise(resolve => {
25
- this.requestsQueue.push({
26
- key,
27
- finished: data => {
28
- delete this.pendingRequests[key]
29
- resolve(data)
30
- }
31
- })
32
- this.fetchDataFromServer()
33
- })
34
- this.pendingRequests[key] = promise
35
- return promise
36
- }
37
-
38
- fetchDataFromServer() {
39
- if (!this.requestsQueue.length) return
40
- const batch = this.requestsQueue.splice(0, 50)
41
- if (!batch.length) return
42
- return this.fetchCallback(batch.map(e => e.key))
43
- .then(data => {
44
- if (data.error)
45
- return //TODO: handle errors in a more elegant way, with retries
46
- //reschedule another batch fetch if the queue is not empty
47
- if (this.requestsQueue.length) {
48
- setTimeout(() => this.fetchDataFromServer(), 400)
49
- }
50
- //remap batch array to an object for the faster elements access
51
- const batchMap = {}
52
- for (const {key, finished} of batch) {
53
- batchMap[key] = finished
54
- }
55
- for (const entry of data._embedded.records) {
56
- const {key} = this.processResponseCallback(entry)
57
- const finalizeRequest = batchMap[key]
58
- if (finalizeRequest) {
59
- finalizeRequest(entry)
60
- }
61
- //cleanup promise reference
62
- delete batchMap[key]
63
- delete this.pendingRequests[key]
64
- }
65
- //return null for all keys that have not been found
66
- for (const key of Object.keys(batchMap)) {
67
- const finalizeRequest = batchMap[key]
68
- if (finalizeRequest) {
69
- finalizeRequest(null)
70
- }
71
- //cleanup promise reference
72
- delete this.pendingRequests[key]
73
- }
74
- })
75
- .catch(err => {
76
- console.error(err)
77
- //finalize all pending promises with nulls
78
- for (const {key, finished} of batch) {
79
- finished(null)
80
- //cleanup promise reference
81
- delete this.pendingRequests[key]
82
- }
83
- })
84
- }
85
- }
1
+ import {throttle} from 'throttle-debounce'
2
+
3
+ /**
4
+ * Batches multiple individual load requests into single API calls for efficiency.
5
+ * Accumulates requests over a short window and sends them together.
6
+ */
7
+ export class ExplorerBatchInfoLoader {
8
+ /**
9
+ * @param {function(string[]): Promise<{_embedded: {records: Object[]}}>} fetchCallback - Fetches a batch of entries by their keys
10
+ * @param {function(Object): {key: string, info: *}} processResponseCallback - Extracts key and info from each response record
11
+ */
12
+ constructor(fetchCallback, processResponseCallback) {
13
+ this.pendingRequests = {}
14
+ this.requestsQueue = []
15
+ this.fetchDataFromServer = throttle(500, this.fetchDataFromServer, {noLeading: true})
16
+ this.fetchCallback = fetchCallback
17
+ this.processResponseCallback = processResponseCallback
18
+ }
19
+
20
+ /**
21
+ * @type {{}}
22
+ * @private
23
+ */
24
+ pendingRequests
25
+ /**
26
+ * @type {[]}
27
+ * @private
28
+ */
29
+ requestsQueue = []
30
+ /**
31
+ * @type {function}
32
+ * @private
33
+ */
34
+ fetchCallback
35
+ /**
36
+ * @type {function}
37
+ * @private
38
+ */
39
+ processResponseCallback
40
+
41
+ /**
42
+ * Load a single entry by key, batching the request with other pending loads
43
+ * @param {string} key - Entry identifier
44
+ * @return {Promise<*>} Resolves with the entry data or null if not found
45
+ */
46
+ loadEntry(key) {
47
+ //check whether the same address is being fetched right now
48
+ const pending = this.pendingRequests[key]
49
+ if (pending) return pending
50
+ const promise = new Promise(resolve => {
51
+ this.requestsQueue.push({
52
+ key,
53
+ finished: data => {
54
+ delete this.pendingRequests[key]
55
+ resolve(data)
56
+ }
57
+ })
58
+ this.fetchDataFromServer()
59
+ })
60
+ this.pendingRequests[key] = promise
61
+ return promise
62
+ }
63
+
64
+ fetchDataFromServer() {
65
+ if (!this.requestsQueue.length) return
66
+ const batch = this.requestsQueue.splice(0, 50)
67
+ if (!batch.length) return
68
+ return this.fetchCallback(batch.map(e => e.key))
69
+ .then(data => {
70
+ if (data.error)
71
+ return //TODO: handle errors in a more elegant way, with retries
72
+ //reschedule another batch fetch if the queue is not empty
73
+ if (this.requestsQueue.length) {
74
+ setTimeout(() => this.fetchDataFromServer(), 400)
75
+ }
76
+ //remap batch array to an object for the faster elements access
77
+ const batchMap = {}
78
+ for (const {key, finished} of batch) {
79
+ batchMap[key] = finished
80
+ }
81
+ for (const entry of data._embedded.records) {
82
+ const {key} = this.processResponseCallback(entry)
83
+ const finalizeRequest = batchMap[key]
84
+ if (finalizeRequest) {
85
+ finalizeRequest(entry)
86
+ }
87
+ //cleanup promise reference
88
+ delete batchMap[key]
89
+ delete this.pendingRequests[key]
90
+ }
91
+ //return null for all keys that have not been found
92
+ for (const key of Object.keys(batchMap)) {
93
+ const finalizeRequest = batchMap[key]
94
+ if (finalizeRequest) {
95
+ finalizeRequest(null)
96
+ }
97
+ //cleanup promise reference
98
+ delete this.pendingRequests[key]
99
+ }
100
+ })
101
+ .catch(err => {
102
+ console.error(err)
103
+ //finalize all pending promises with nulls
104
+ for (const {key, finished} of batch) {
105
+ finished(null)
106
+ //cleanup promise reference
107
+ delete this.pendingRequests[key]
108
+ }
109
+ })
110
+ }
111
+ }
@@ -1,29 +1,29 @@
1
- import {parseStellarGenericId} from '../stellar/ledger-generic-id'
2
- import {getCurrentStellarNetwork} from '../state/stellar-network-hooks'
3
- import {fetchExplorerApi} from './explorer-api-call'
4
-
5
- /**
6
- * Load transaction from API
7
- * @param {String} txHashOrId - Transaction hash or generic id
8
- * @return {Promise<Object>}
9
- */
10
- export function loadTransaction(txHashOrId) {
11
- if (/^\d+$/.test(txHashOrId)) { //treat as generic tx id
12
- const {type} = parseStellarGenericId(txHashOrId)
13
- if (type !== 'transaction')
14
- return Promise.reject(new Error('Invalid transaction id: ' + txHashOrId))
15
- } else if (!/^[a-fA-F0-9]{64}$/.test(txHashOrId))
16
- return Promise.reject(new Error('Invalid transaction hash or id: ' + txHashOrId))
17
-
18
- return fetchExplorerApi(getCurrentStellarNetwork() + '/tx/' + txHashOrId)
19
- }
20
-
21
-
22
- /**
23
- * Load transactions included into the ledger
24
- * @param {Number} sequence - Ledger sequence
25
- * @return {Promise<Array<Object>>}
26
- */
27
- export function loadLedgerTransactions(sequence) {
28
- return fetchExplorerApi(getCurrentStellarNetwork() + '/ledger/' + sequence + '/tx')
1
+ import {parseStellarGenericId} from '../stellar/ledger-generic-id'
2
+ import {getCurrentStellarNetwork} from '../state/stellar-network-hooks'
3
+ import {fetchExplorerApi} from './explorer-api-call'
4
+
5
+ /**
6
+ * Load transaction from API
7
+ * @param {string} txHashOrId - Transaction hash or generic id
8
+ * @return {Promise<Object>}
9
+ */
10
+ export function loadTransaction(txHashOrId) {
11
+ if (/^\d+$/.test(txHashOrId)) { //treat as generic tx id
12
+ const {type} = parseStellarGenericId(txHashOrId)
13
+ if (type !== 'transaction')
14
+ return Promise.reject(new Error('Invalid transaction id: ' + txHashOrId))
15
+ } else if (!/^[a-fA-F0-9]{64}$/.test(txHashOrId))
16
+ return Promise.reject(new Error('Invalid transaction hash or id: ' + txHashOrId))
17
+
18
+ return fetchExplorerApi(getCurrentStellarNetwork() + '/tx/' + txHashOrId)
19
+ }
20
+
21
+
22
+ /**
23
+ * Load transactions included into the ledger
24
+ * @param {number} sequence - Ledger sequence
25
+ * @return {Promise<Array<Object>>}
26
+ */
27
+ export function loadLedgerTransactions(sequence) {
28
+ return fetchExplorerApi(getCurrentStellarNetwork() + '/ledger/' + sequence + '/tx')
29
29
  }
@@ -1,6 +1,9 @@
1
1
  import {getCurrentStellarNetwork} from '../state/stellar-network-hooks'
2
2
  import {fetchExplorerApi} from './explorer-api-call'
3
3
 
4
+ /**
5
+ * Long-polling stream that notifies listeners about new ledgers
6
+ */
4
7
  class LedgerStream {
5
8
  constructor() {
6
9
  this.listeners = new Set()
@@ -89,13 +92,25 @@ class LedgerStream {
89
92
  }
90
93
  }
91
94
 
95
+ /**
96
+ * Fetch the most recent ledger info from the API
97
+ * @return {Promise<Object>}
98
+ */
92
99
  async getLast() {
93
100
  return fetchExplorerApi(getCurrentStellarNetwork() + '/ledger/last')
94
101
  }
95
102
 
103
+ /**
104
+ * Fetch the sequence number of the most recent ledger
105
+ * @return {Promise<number>}
106
+ */
96
107
  async getLastSequence() {
97
108
  return (await this.getLast()).sequence
98
109
  }
99
110
  }
100
111
 
112
+ /**
113
+ * Singleton ledger stream instance for subscribing to new ledger notifications
114
+ * @type {LedgerStream}
115
+ */
101
116
  export const ledgerStream = new LedgerStream()
package/asset/amount.js CHANGED
@@ -1,57 +1,57 @@
1
- import React from 'react'
2
- import {formatWithPrecision, formatWithAutoPrecision, fromStroops} from '@stellar-expert/formatter'
3
- import {AssetDescriptor, isAssetValid, isValidPoolId} from '@stellar-expert/asset-descriptor'
4
- import {useAssetMeta} from './asset-meta-hooks'
5
- import {AssetLink} from './asset-link'
6
-
7
- /**
8
- * Formatted tokens amount
9
- * @param {String|Number} amount - Amount of tokens
10
- * @param {String|AssetDescriptor|Asset} asset - Asset name/descriptor
11
- * @param {String|Number|'auto'} decimals - Number of decimals to show for a formatted numeric value
12
- * @param {Boolean} [adjust] - Treat amount value as raw Int64 stroops amount
13
- * @param {Boolean|'floor'} [round] - Round the amount
14
- * @param {Boolean} [issuer] - Whether to show asset issuer
15
- * @param {Boolean} [icon] - Whether to show asset icon
16
- * @constructor
17
- */
18
- export const Amount = React.memo(function Amount({amount, asset, decimals, adjust, round, issuer, icon}) {
19
- const meta = useAssetMeta(asset)
20
- if (amount === undefined || amount === null)
21
- return null
22
- if (adjust === true) {
23
- try {
24
- amount = fromStroops(amount, meta?.decimals ?? 7)
25
- } catch (e) {
26
- console.error(e)
27
- return null
28
- }
29
- }
30
- if (round) {
31
- let [int, fract] = (typeof amount === 'number' ? amount.toFixed(7) : amount).split('.')
32
- if (fract > 0) {
33
- int = parseFloat(int)
34
- fract = parseFloat('0.' + fract)
35
- if (amount < 0) {
36
- fract *= -1
37
- }
38
-
39
- const rounded = round === 'floor' ? Math.floor(fract) : Math.round(fract)
40
- amount = int + rounded
41
- }
42
- }
43
- try {
44
- amount = decimals === 'auto' ? formatWithAutoPrecision(amount) : formatWithPrecision(amount, decimals)
45
- } catch (e) {
46
- console.error(e)
47
- return null
48
- }
49
- return <span className="amount nowrap condensed">
50
- {amount}
51
- {!!asset && <>
52
- {' '}{isAssetValid(asset) || isValidPoolId(asset) ?
53
- <AssetLink asset={asset} icon={icon} issuer={issuer}/> :
54
- asset.toString()}
55
- </>}
56
- </span>
1
+ import React from 'react'
2
+ import {formatWithPrecision, formatWithAutoPrecision, fromStroops} from '@stellar-expert/formatter'
3
+ import {AssetDescriptor, isAssetValid, isValidPoolId} from '@stellar-expert/asset-descriptor'
4
+ import {useAssetMeta} from './asset-meta-hooks'
5
+ import {AssetLink} from './asset-link'
6
+
7
+ /**
8
+ * Formatted tokens amount
9
+ * @param {string|number} amount - Amount of tokens
10
+ * @param {string|AssetDescriptor|Asset} asset - Asset name/descriptor
11
+ * @param {string|number|'auto'} decimals - Number of decimals to show for a formatted numeric value
12
+ * @param {boolean} [adjust] - Treat amount value as raw Int64 stroops amount
13
+ * @param {boolean|'floor'} [round] - Round the amount
14
+ * @param {boolean} [issuer] - Whether to show asset issuer
15
+ * @param {boolean} [icon] - Whether to show asset icon
16
+ * @constructor
17
+ */
18
+ export const Amount = React.memo(function Amount({amount, asset, decimals, adjust, round, issuer, icon}) {
19
+ const meta = useAssetMeta(asset)
20
+ if (amount === undefined || amount === null)
21
+ return null
22
+ if (adjust === true) {
23
+ try {
24
+ amount = fromStroops(amount, meta?.decimals ?? 7)
25
+ } catch (e) {
26
+ console.error(e)
27
+ return null
28
+ }
29
+ }
30
+ if (round) {
31
+ let [int, fract] = (typeof amount === 'number' ? amount.toFixed(7) : amount).split('.')
32
+ if (fract > 0) {
33
+ int = parseFloat(int)
34
+ fract = parseFloat('0.' + fract)
35
+ if (amount < 0) {
36
+ fract *= -1
37
+ }
38
+
39
+ const rounded = round === 'floor' ? Math.floor(fract) : Math.round(fract)
40
+ amount = int + rounded
41
+ }
42
+ }
43
+ try {
44
+ amount = decimals === 'auto' ? formatWithAutoPrecision(amount) : formatWithPrecision(amount, decimals)
45
+ } catch (e) {
46
+ console.error(e)
47
+ return null
48
+ }
49
+ return <span className="amount nowrap condensed">
50
+ {amount}
51
+ {!!asset && <>
52
+ {' '}{isAssetValid(asset) || isValidPoolId(asset) ?
53
+ <AssetLink asset={asset} icon={icon} issuer={issuer}/> :
54
+ asset.toString()}
55
+ </>}
56
+ </span>
57
57
  })
@@ -1,41 +1,41 @@
1
- import React from 'react'
2
- import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
3
- import {drawIdenticon} from '../account/identicon'
4
- import {useAssetMeta} from './asset-meta-hooks'
5
-
6
- /**
7
- * Inline asset icon
8
- * @param {String|AssetDescriptor|Asset} asset - Asset name/descriptor
9
- * @param {String} [className] - Optional CSS class name
10
- * @param {{}} [style] - Optional CSS inline style
11
- * @param {*} [children] - Optional inner text
12
- * @constructor
13
- */
14
- export const AssetIcon = React.memo(function AssetIcon({asset, className, style, children}) {
15
- const meta = useAssetMeta(asset)
16
- if (typeof asset === 'string') {
17
- asset = AssetDescriptor.parse(asset)
18
- }
19
- const icon = meta?.toml_info?.image || meta?.toml_info?.orgLogo
20
- const classes = ['asset-icon']
21
- if (!style) {
22
- style = {}
23
- }
24
-
25
- if (className) {
26
- classes.push(className)
27
- }
28
-
29
- if (asset.isNative) { // native asset
30
- classes.push('icon icon-stellar')
31
- } else if (icon) { // asset with an icon
32
- style.backgroundImage = `url('${icon}')`
33
- } else if (asset.isContract) {
34
- style.backgroundImage = `url('${'data:image/svg+xml;charset=utf-8,' + drawIdenticon(asset.toString())}')`
35
- style.borderRadius = '50%'
36
- } else { // asset without metadata
37
- classes.push('icon icon-empty-circle')
38
- }
39
-
40
- return <span className={classes.join(' ')} style={style}>{children}</span>
41
- })
1
+ import React from 'react'
2
+ import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
3
+ import {drawIdenticon} from '../account/identicon'
4
+ import {useAssetMeta} from './asset-meta-hooks'
5
+
6
+ /**
7
+ * Inline asset icon
8
+ * @param {string|AssetDescriptor|Asset} asset - Asset name/descriptor
9
+ * @param {string} [className] - Optional CSS class name
10
+ * @param {{}} [style] - Optional CSS inline style
11
+ * @param {*} [children] - Optional inner text
12
+ * @constructor
13
+ */
14
+ export const AssetIcon = React.memo(function AssetIcon({asset, className, style, children}) {
15
+ const meta = useAssetMeta(asset)
16
+ if (typeof asset === 'string') {
17
+ asset = AssetDescriptor.parse(asset)
18
+ }
19
+ const icon = meta?.toml_info?.image || meta?.toml_info?.orgLogo
20
+ const classes = ['asset-icon']
21
+ if (!style) {
22
+ style = {}
23
+ }
24
+
25
+ if (className) {
26
+ classes.push(className)
27
+ }
28
+
29
+ if (asset.isNative) { // native asset
30
+ classes.push('icon icon-stellar')
31
+ } else if (icon) { // asset with an icon
32
+ style.backgroundImage = `url('${icon}')`
33
+ } else if (asset.isContract) {
34
+ style.backgroundImage = `url('${'data:image/svg+xml;charset=utf-8,' + drawIdenticon(asset.toString())}')`
35
+ style.borderRadius = '50%'
36
+ } else { // asset without metadata
37
+ classes.push('icon icon-empty-circle')
38
+ }
39
+
40
+ return <span className={classes.join(' ')} style={style}>{children}</span>
41
+ })
@@ -1,22 +1,22 @@
1
- import React from 'react'
2
- import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
3
- import {AccountAddress} from '../account/account-address'
4
- import {useAssetMeta} from './asset-meta-hooks'
5
-
6
- /**
7
- * Inline asset issuer
8
- * @param {String|AssetDescriptor|Asset} asset - Asset name/descriptor
9
- * @constructor
10
- */
11
- export const AssetIssuer = React.memo(function AssetIssuer({asset}) {
12
- let meta = useAssetMeta(asset)
13
- if (asset === 'XLM' || asset.isNative) {
14
- meta = {domain: 'stellar.org'}
15
- }
16
- asset = AssetDescriptor.parse(asset)
17
- return <span className="asset-issuer condensed">
18
- {meta?.domain ?
19
- <>{meta.domain}</> :
20
- <><AccountAddress account={asset.issuer} link={false} chars={8} icon={false}/></>}
21
- </span>
1
+ import React from 'react'
2
+ import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
3
+ import {AccountAddress} from '../account/account-address'
4
+ import {useAssetMeta} from './asset-meta-hooks'
5
+
6
+ /**
7
+ * Inline asset issuer
8
+ * @param {string|AssetDescriptor|Asset} asset - Asset name/descriptor
9
+ * @constructor
10
+ */
11
+ export const AssetIssuer = React.memo(function AssetIssuer({asset}) {
12
+ let meta = useAssetMeta(asset)
13
+ if (asset === 'XLM' || asset.isNative) {
14
+ meta = {domain: 'stellar.org'}
15
+ }
16
+ asset = AssetDescriptor.parse(asset)
17
+ return <span className="asset-issuer condensed">
18
+ {meta?.domain ?
19
+ <>{meta.domain}</> :
20
+ <><AccountAddress account={asset.issuer} link={false} chars={8} icon={false}/></>}
21
+ </span>
22
22
  })