@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.
Files changed (124) hide show
  1. package/.npmrc +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -0
  4. package/account/account-address.js +127 -0
  5. package/account/account-address.scss +20 -0
  6. package/account/available-balance.js +21 -0
  7. package/account/identicon.js +91 -0
  8. package/account/identicon.scss +6 -0
  9. package/account/signer-key.js +65 -0
  10. package/api/api-cache.js +5 -0
  11. package/api/explorer-api-call.js +37 -0
  12. package/api/explorer-api-hooks.js +202 -0
  13. package/api/explorer-api-paginated-list-hooks.js +439 -0
  14. package/api/explorer-batch-info-loader.js +85 -0
  15. package/api/explorer-tx-api.js +29 -0
  16. package/asset/amount.js +53 -0
  17. package/asset/asset-icon.js +34 -0
  18. package/asset/asset-issuer.js +22 -0
  19. package/asset/asset-link.js +93 -0
  20. package/asset/asset-link.scss +26 -0
  21. package/asset/asset-list-hooks.js +60 -0
  22. package/asset/asset-meta-hooks.js +78 -0
  23. package/asset/asset-selector.js +72 -0
  24. package/asset/asset-selector.scss +41 -0
  25. package/basic-styles/base.scss +53 -0
  26. package/basic-styles/form.scss +90 -0
  27. package/basic-styles/grid.scss +388 -0
  28. package/basic-styles/link.scss +17 -0
  29. package/basic-styles/lists.scss +68 -0
  30. package/basic-styles/loader.scss +22 -0
  31. package/basic-styles/responsive.scss +29 -0
  32. package/basic-styles/scrollbars.scss +19 -0
  33. package/basic-styles/spacing.scss +50 -0
  34. package/basic-styles/table.scss +104 -0
  35. package/basic-styles/themes.scss +59 -0
  36. package/basic-styles/typography.scss +191 -0
  37. package/basic-styles/variables.scss +26 -0
  38. package/claimable-balance/claimable-balance-claimants.js +12 -0
  39. package/contract/sc-val.js +70 -0
  40. package/contract/wasm-section-parser.js +159 -0
  41. package/contract/wasm-section-reader.js +79 -0
  42. package/contract/xdr-reader.js +148 -0
  43. package/controls/active-icon.scss +23 -0
  44. package/controls/button-group.js +20 -0
  45. package/controls/button-group.scss +48 -0
  46. package/controls/button.js +74 -0
  47. package/controls/button.scss +148 -0
  48. package/controls/code-block.js +35 -0
  49. package/controls/code-block.scss +74 -0
  50. package/controls/dropdown.js +281 -0
  51. package/controls/dropdown.scss +161 -0
  52. package/controls/external-link.js +5 -0
  53. package/controls/info-tooltip.js +17 -0
  54. package/controls/info-tooltip.scss +4 -0
  55. package/controls/slider.js +20 -0
  56. package/controls/slider.scss +22 -0
  57. package/controls/tabs.js +94 -0
  58. package/controls/tabs.scss +73 -0
  59. package/controls/tooltip.js +214 -0
  60. package/controls/tooltip.scss +97 -0
  61. package/controls/update-highlighter.js +28 -0
  62. package/controls/update-highlighter.scss +10 -0
  63. package/date/date-selector.js +44 -0
  64. package/date/elapsed-time.js +22 -0
  65. package/date/utc-timestamp.js +23 -0
  66. package/dex/price-dynamic.js +45 -0
  67. package/dex/price-dynamic.scss +36 -0
  68. package/directory/directory-hooks.js +89 -0
  69. package/effect/effect-description.js +289 -0
  70. package/effect/op-effects-view.js +11 -0
  71. package/effect/op-effects.scss +8 -0
  72. package/errors/error-boundary.js +98 -0
  73. package/fonts/font.scss +23 -0
  74. package/fonts/icons/config.json +1858 -0
  75. package/fonts/icons/icons-embedded.scss +644 -0
  76. package/horizon/horizon-account-helpers.js +104 -0
  77. package/horizon/horizon-client-helpers.js +52 -0
  78. package/horizon/horizon-ledger-helpers.js +36 -0
  79. package/horizon/horizon-orderbook-helpers.js +14 -0
  80. package/horizon/horizon-trades-helper.js +89 -0
  81. package/horizon/horizon-transaction-helpers.js +36 -0
  82. package/index.js +85 -0
  83. package/index.scss +16 -0
  84. package/interaction/autofocus.js +9 -0
  85. package/interaction/block-select.js +54 -0
  86. package/interaction/block-select.scss +24 -0
  87. package/interaction/copy-to-clipboard.js +19 -0
  88. package/interaction/inline-progress.js +16 -0
  89. package/interaction/qr-code.js +45 -0
  90. package/interaction/responsive.js +21 -0
  91. package/interaction/spoiler.js +40 -0
  92. package/interaction/spoiler.scss +9 -0
  93. package/interaction/theme-selector.js +11 -0
  94. package/ledger/ledger-entry-href-formatter.js +22 -0
  95. package/ledger/ledger-entry-link.js +59 -0
  96. package/meta/page-meta-tags.js +229 -0
  97. package/module/dynamic-module.js +48 -0
  98. package/package.json +41 -0
  99. package/state/on-screen-hooks.js +23 -0
  100. package/state/page-visibility-helpers.js +17 -0
  101. package/state/page-visibility-hooks.js +12 -0
  102. package/state/screen-orientation-hooks.js +16 -0
  103. package/state/state-hooks.js +77 -0
  104. package/state/stellar-network-hooks.js +45 -0
  105. package/state/theme.js +29 -0
  106. package/stellar/key-type.js +92 -0
  107. package/stellar/ledger-generic-id.js +40 -0
  108. package/stellar/signature-hint-utils.js +65 -0
  109. package/toast/toast-notification-instance.js +43 -0
  110. package/toast/toast-notification.js +46 -0
  111. package/toast/toast-notifications-block.js +55 -0
  112. package/toast/toast-notifications.scss +166 -0
  113. package/tx/op-accounting-changes.js +27 -0
  114. package/tx/op-description-view.js +925 -0
  115. package/tx/op-description.scss +98 -0
  116. package/tx/op-icon.js +93 -0
  117. package/tx/parser/op-balance-changes.js +62 -0
  118. package/tx/parser/op-descriptor.js +49 -0
  119. package/tx/parser/tx-details-parser.js +75 -0
  120. package/tx/parser/tx-matcher.js +372 -0
  121. package/tx/parser/type-filter-matcher.js +127 -0
  122. package/tx/tx-fee-effect.js +14 -0
  123. package/tx/tx-list-hooks.js +19 -0
  124. package/tx/tx-operations-list.js +105 -0
@@ -0,0 +1,92 @@
1
+ import {StrKey} from '@stellar/stellar-base'
2
+
3
+ /**
4
+ * @param {String} key
5
+ * @return {{address: String, type: ('muxed'|'ed25519'|'hash'|'tx'|'contract'), [muxedId]: BigInt}|null}
6
+ * @internal
7
+ */
8
+ export function decodeKeyType(key) {
9
+ if (typeof key !== 'string')
10
+ return null
11
+ const prefix = key[0]
12
+ try {
13
+ switch (prefix) {
14
+ case 'G':
15
+ checkLength(key)
16
+ return {
17
+ type: 'ed25519',
18
+ address: key
19
+ }
20
+ case 'C':
21
+ checkLength(key)
22
+ return {
23
+ type: 'contract',
24
+ address: key
25
+ }
26
+ case 'M':
27
+ return {
28
+ type: 'muxed',
29
+ ...parseMuxedAccount(key)
30
+ }
31
+ case 'X':
32
+ checkLength(key)
33
+ return {
34
+ type: 'hash',
35
+ address: key
36
+ }
37
+ case 'T':
38
+ checkLength(key)
39
+ return {
40
+ type: 'tx',
41
+ address: key
42
+ }
43
+ case 'P':
44
+ const spayload = StrKey.decodeSignedPayload(key)
45
+ return {
46
+ type: 'signedPayload',
47
+ address: key,
48
+ publicKey: StrKey.encodeEd25519PublicKey(spayload.slice(0, 32)),
49
+ payload: spayload.slice(36, 36 + spayload.readUIntBE(32, 4)).toString('hex')
50
+ }
51
+ default:
52
+ console.error(new Error('Unknown key prefix: ' + typeof key))
53
+ return null
54
+ }
55
+ } catch (e) {
56
+ console.error(e)
57
+ return null
58
+ }
59
+ }
60
+
61
+ function checkLength(key, length = 56) {
62
+ if (key.length !== length)
63
+ throw new Error('Invalid key length: ' + key)
64
+ }
65
+
66
+ /**
67
+ * Parse multiplexed account address
68
+ * @param {String} muxedAddress - Multiplexed Stellar address
69
+ * @return {{address: String, muxedId: BigIint}}
70
+ */
71
+ export function parseMuxedAccount(muxedAddress) {
72
+ const muxed = StrKey.decodeMed25519PublicKey(muxedAddress)
73
+ return {
74
+ address: StrKey.encodeEd25519PublicKey(muxed.slice(0, 32)),
75
+ muxedId: muxed.readBigInt64BE(32)
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Encode address and identifier into a multiplexed address
81
+ * @param {String} address - StrKey-encode Stellar account address
82
+ * @param {BigInt} muxedId - Multiplexed int64 id
83
+ * @return {String}
84
+ */
85
+ export function encodeMuxedAccount(address, muxedId) {
86
+ const raw = Buffer.allocUnsafe(40)
87
+ //write 32 bytes of ed25519 pubkey
88
+ StrKey.decodeEd25519PublicKey(address).copy(raw)
89
+ //write 8 id bytes
90
+ raw.writeBigInt64BE(muxedId, 32)
91
+ return StrKey.encodeMed25519PublicKey(raw)
92
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @typedef {Object} ParsedStellarId
3
+ * @property {('unknown'|'ledger'|'transaction'|'operation')} type - Parsed id type
4
+ * @property {String} [id] - Parsed id
5
+ * @property {Number} [ledger] - Ledger sequence
6
+ * @property {String} [tx] - Transaction id
7
+ * @property {Number} [operationOrder] - Operation order within a transaction
8
+ */
9
+
10
+ /**
11
+ * Parse stellar generic id
12
+ * @param {String} id
13
+ * @returns {ParsedStellarId}
14
+ */
15
+ export function parseStellarGenericId(id) {
16
+ if (!/^\d{1,19}$/.test(id)) return {type: 'unknown'}
17
+ const parsed = BigInt(id)
18
+ const res = {
19
+ id,
20
+ type: 'unknown'
21
+ }
22
+ if (parsed < 4294967296n) {
23
+ res.type = 'ledger'
24
+ res.ledger = Number(parsed)
25
+ return res
26
+ }
27
+ const ledger = parsed / 4294967296n
28
+ const opOrder = parsed % 4096n
29
+ res.ledger = Number(ledger)
30
+ if (opOrder > 0n) {
31
+ res.type = 'operation'
32
+ res.tx = (parsed - opOrder).toString()
33
+ res.operationOrder = Number(opOrder)
34
+ } else {
35
+ res.type = 'transaction'
36
+ res.tx = parsed.toString()
37
+ }
38
+
39
+ return res
40
+ }
@@ -0,0 +1,65 @@
1
+ import {Keypair} from '@stellar/stellar-base'
2
+
3
+ /**
4
+ * Convert the signature hint to the StrKey mask
5
+ * @param {Buffer} hint - Hint to convert
6
+ * @return {String}
7
+ */
8
+ export function signatureHintToMask(hint) {
9
+ const partialPublicKey = Buffer.concat([new Buffer(28).fill(0), hint]),
10
+ hintKeypair = new Keypair({type: 'ed25519', publicKey: partialPublicKey}),
11
+ pk = hintKeypair.publicKey()
12
+ return pk.substring(0, 1) + '_'.repeat(46) + pk.substring(47, 52) + '_'.repeat(4)
13
+ }
14
+
15
+ /**
16
+ * Format the signature hint to the friendly form for UI
17
+ * @param {Buffer} hint - Hint to convert
18
+ * @return {string}
19
+ */
20
+ export function formatSignatureHint(hint) {
21
+ const mask = signatureHintToMask(hint)
22
+ return mask.substring(0, 2) + '…' + mask.substring(46)
23
+ }
24
+
25
+ /**
26
+ * Check if the hint matches the specific key
27
+ * @param {Buffer} hint - Hint to check
28
+ * @param {String} key - Key to compare
29
+ * @return {Boolean}
30
+ */
31
+ export function singatureHintMatchesKey(hint, key) {
32
+ return signatureHintToMask(hint).substring(47, 52) === key.substring(47, 52)
33
+ }
34
+
35
+ /**
36
+ * Find a key by the signature hint
37
+ * @param {Buffer} hint - Hint to look for
38
+ * @param {Array<String>} allKeys - Array of potentially matching keys
39
+ * @return {String|null}
40
+ */
41
+ export function findKeyBySignatureHint(hint, allKeys) {
42
+ return allKeys.find(key => singatureHintMatchesKey(hint, key))
43
+ }
44
+
45
+ /**
46
+ * Find tx signature for a given signer key
47
+ * @param {String} key
48
+ * @param {Singature} allSignatures
49
+ * @returns {Signature}
50
+ */
51
+ export function findSignatureByKey(key, allSignatures = []) {
52
+ return allSignatures.find(sig => singatureHintMatchesKey(sig.hint(), key))
53
+ }
54
+
55
+ /**
56
+ * Check if the hint matches any of the provided keys
57
+ * @param {Buffer} signature - Tx signature
58
+ * @param {Array<String>} publicKeys - Array of available public keys
59
+ * @returns {Array<String>}
60
+ */
61
+ export function findKeysBySignatureHint(signature, keys) {
62
+ const mask = signatureHintToMask(signature.hint())
63
+ return keys.filter(address => mask[0] === address[0] &&
64
+ mask.substr(47, 5) === address.substr(47, 5))
65
+ }
@@ -0,0 +1,43 @@
1
+ export default class ToastNotificationInstance {
2
+ constructor(props) {
3
+ Object.assign(this, props)
4
+ this.timer = null
5
+ this.started = new Date()
6
+ this.remaining = props.ttl || ToastNotificationInstance.ttl
7
+ }
8
+
9
+ id
10
+
11
+ type
12
+
13
+ message = ''
14
+
15
+ onDelete
16
+
17
+ deleted = false
18
+
19
+ started
20
+
21
+ remaining
22
+
23
+ delete() {
24
+ this.deleted = true
25
+ this.onDelete(this.id)
26
+ }
27
+
28
+ pauseTimer() {
29
+ clearTimeout(this.timer)
30
+ this.timer = null
31
+ this.remaining -= new Date() - this.started
32
+ }
33
+
34
+ resumeTimer(action) {
35
+ if (this.timer)
36
+ return
37
+
38
+ this.started = new Date()
39
+ this.timer = setTimeout(action, this.remaining)
40
+ }
41
+
42
+ static ttl = 10_000 // 10 seconds by default
43
+ }
@@ -0,0 +1,46 @@
1
+ import React, {useCallback, useEffect, useState} from 'react'
2
+ import cn from 'classnames'
3
+
4
+ const contextIcon = {
5
+ 'info': 'icon-info',
6
+ 'success': 'icon-ok',
7
+ 'warning': 'icon-warning',
8
+ 'error': 'icon-warning'
9
+ }
10
+
11
+ function ToastNotification({notification}) {
12
+ const {type = 'info'} = notification
13
+ const [isClosing, setIsClosing] = useState(false)
14
+ const close = useCallback(function () {
15
+ if (notification.deleted)
16
+ return
17
+ setIsClosing(closing => {
18
+ if (closing)
19
+ return closing
20
+ setTimeout(() => notification.delete(), 300)
21
+ return true
22
+ })
23
+ }, [notification])
24
+
25
+ const pauseTimer = useCallback(() => {
26
+ notification.pauseTimer()
27
+ }, [notification])
28
+
29
+ const resumeTimer = useCallback(() => {
30
+ notification.resumeTimer(close)
31
+ }, [notification, close])
32
+
33
+ useEffect(() => {
34
+ notification.timer = setTimeout(close, notification.remaining)
35
+ }, [notification, close])
36
+
37
+ const notificationClass = cn('toast-notification segment', type, {'slide-in': !isClosing, 'slide-out': isClosing})
38
+ return <div className={notificationClass} onMouseEnter={pauseTimer} onMouseLeave={resumeTimer}>
39
+ <i className={cn('toast-notification-icon', contextIcon[type])}/>
40
+ <div>{notification.message}</div>
41
+ <a href="#" className="toast-notification-icon icon-cancel" onClick={close}/>
42
+ <div className="lifetime" style={{animationDuration: notification.ttl + 'ms'}}/>
43
+ </div>
44
+ }
45
+
46
+ export default ToastNotification
@@ -0,0 +1,55 @@
1
+ import React, {useCallback, useEffect, useState} from 'react'
2
+ import {render} from 'react-dom'
3
+ import ToastNotificationInstance from './toast-notification-instance'
4
+ import Notification from './toast-notification'
5
+ import './toast-notifications.scss'
6
+
7
+ let notificationsCounter = 0
8
+
9
+ /**
10
+ * Initialize toast notifications, add toast notifications container to the DOM, and expose notify() global function
11
+ * @return {HTMLDivElement}
12
+ */
13
+ export function createToastNotificationsContainer() {
14
+ const container = document.createElement('div')
15
+ document.body.appendChild(container)
16
+ render(<ToastNotificationsBlock/>, container)
17
+ return container
18
+ }
19
+
20
+ function ToastNotificationsBlock() {
21
+ const [notifications, setNotifications] = useState([])
22
+
23
+ const deleteNotification = useCallback(function (id) {
24
+ setNotifications(prev => {
25
+ const pos = prev.findIndex(v => v.id === id)
26
+ if (pos < 0)
27
+ return prev
28
+ const res = [...prev]
29
+ res.splice(pos, 1)
30
+ return res
31
+ })
32
+ }, [])
33
+
34
+ useEffect(() => {
35
+ //declare globally available notify() function
36
+ window.notify = function ({type, message}) {
37
+ const newNotification = new ToastNotificationInstance({
38
+ type,
39
+ message,
40
+ id: ++notificationsCounter,
41
+ onDelete: deleteNotification
42
+ })
43
+ setNotifications(prevNotifications => [newNotification, ...prevNotifications])
44
+ }
45
+ //set empty callback on unload
46
+ return () => {
47
+ window.notify = function () {
48
+ }
49
+ }
50
+ }, [])
51
+
52
+ return <div className="toast-notifications-container">
53
+ {notifications.map(props => <Notification key={props.id} notification={props}/>)}
54
+ </div>
55
+ }
@@ -0,0 +1,166 @@
1
+ .toast-notifications-container {
2
+ $toast-space: 0.75*$space-standard;
3
+
4
+ position: fixed;
5
+ top: 0;
6
+ right: 0;
7
+ width: 50vw;
8
+ max-width: 30em;
9
+ padding: 4px 4px 0 0;
10
+ z-index: 999;
11
+ display: flex;
12
+ flex-direction: column-reverse;
13
+
14
+ &:not(:first-child) {
15
+ margin-bottom: 0.375em;
16
+ }
17
+
18
+ .toast-notification {
19
+ position: relative;
20
+ display: flex;
21
+ align-items: start;
22
+ width: 100%;
23
+ padding: $toast-space;
24
+ padding-right: $toast-space*2;
25
+ transition: transform 0.3s ease-out;
26
+ opacity: 0.92;
27
+ line-height: 2rem;
28
+ margin-bottom: 4px;
29
+
30
+ @media (max-width: $responsive-mobile-browser-width) {
31
+ padding: 0.5*$space-standard;
32
+ padding-right: $space-standard;
33
+ }
34
+
35
+ &:hover {
36
+ opacity: 1;
37
+
38
+ .lifetime {
39
+ animation-play-state: paused;
40
+ }
41
+ }
42
+
43
+ &:before, &:after {
44
+ clip-path: polygon(0.6rem 0, 100% 0, 100% 100%, calc(100% - 0.6rem) 100%, 0 100%, 0 1.6rem);
45
+ }
46
+
47
+ &.slide-in {
48
+ animation: .3s slideIn ease-in-out;
49
+ }
50
+
51
+ &.slide-out {
52
+ transform: translateX(150%);
53
+ flex: 0;
54
+ }
55
+
56
+ > .toast-notification-icon {
57
+ font-size: 1.2em;
58
+ padding-right: $space-micro;
59
+ @media (max-width: $responsive-mobile-browser-width) {
60
+ font-size: 1em;
61
+ padding-right: $space-micro/2;
62
+ }
63
+ }
64
+
65
+ > div {
66
+ color: var(--color-text);
67
+ font-size: 0.9em;
68
+ @media (max-width: $responsive-mobile-browser-width) {
69
+ font-size: 0.85em;
70
+ }
71
+ }
72
+
73
+ .icon-cancel {
74
+ cursor: pointer;
75
+ position: absolute;
76
+ top: $toast-space;
77
+ right: 0;
78
+
79
+ @media (max-width: $responsive-mobile-browser-width) {
80
+ top: $space-micro;
81
+ }
82
+ }
83
+
84
+ .lifetime {
85
+ position: absolute;
86
+ left: 0;
87
+ bottom: 0;
88
+ width: 100%;
89
+ max-width: 0;
90
+ height: 2px;
91
+ animation-name: timeToClose;
92
+ animation-duration: 10s;
93
+ animation-timing-function: linear;
94
+ }
95
+
96
+ &.info {
97
+ .toast-notification-icon {
98
+ color: var(--color-highlight);
99
+ }
100
+
101
+ .lifetime {
102
+ background-color: var(--color-highlight);
103
+ }
104
+ }
105
+
106
+ &.success {
107
+ &:before {
108
+ background: var(--color-success-bg);
109
+ }
110
+
111
+ .toast-notification-icon {
112
+ color: var(--color-success);
113
+ }
114
+
115
+ .lifetime {
116
+ background-color: var(--color-success);
117
+ }
118
+ }
119
+
120
+ &.warning {
121
+ &:before {
122
+ background: var(--color-warning-bg);
123
+ }
124
+
125
+ .toast-notification-icon {
126
+ color: var(--color-warning);
127
+ }
128
+
129
+ .lifetime {
130
+ background-color: var(--color-warning);
131
+ }
132
+ }
133
+
134
+ &.error {
135
+ &:before {
136
+ background: var(--color-alert-bg);
137
+ }
138
+
139
+ .toast-notification-icon {
140
+ color: var(--color-alert);
141
+ }
142
+
143
+ .lifetime {
144
+ background-color: var(--color-alert);
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ @keyframes slideIn {
151
+ from {
152
+ transform: translateX(100%);
153
+ }
154
+ to {
155
+ transform: translateX(0%);
156
+ }
157
+ }
158
+
159
+ @keyframes timeToClose {
160
+ from {
161
+ max-width: 100%;
162
+ }
163
+ to {
164
+ max-width: 0;
165
+ }
166
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react'
2
+ import {Amount} from '../asset/amount'
3
+ import {retrieveOpBalanceChanges} from './parser/op-balance-changes'
4
+
5
+ /**
6
+ * Compact accounting effects (credited/debited amounts)
7
+ * @param {OperationDescriptor} op
8
+ * @constructor
9
+ */
10
+ export function OpAccountingChanges({op}) {
11
+ const changes = retrieveOpBalanceChanges(op)
12
+ return <div className="accounting-effects condensed">
13
+ {changes.map((ch, i) => <div key={op.txHash + op.order + i}
14
+ className={ch.type === 'accountDebited' ? 'dimmed' : 'color-success'}>
15
+ {ch.type === 'accountDebited' ? '-' : '+'}
16
+ <Amount amount={ch.amount} asset={ch.asset} issuer={false} icon={false}/>
17
+ </div>)}
18
+ </div>
19
+ }
20
+
21
+ export function TxFeeAccountingChanges({amount}) {
22
+ return <div className="accounting-effects condensed">
23
+ <div className="dimmed">
24
+ -<Amount amount={amount} asset="XLM" issuer={false} icon={false}/>
25
+ </div>
26
+ </div>
27
+ }