@stellar-expert/ui-framework 1.16.7 → 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.
- package/CLAUDE.md +35 -0
- package/README.md +1125 -3
- package/account/account-address.js +127 -127
- package/account/available-balance.js +22 -22
- package/account/identicon.js +90 -90
- package/account/signer-key.js +65 -64
- package/api/explorer-api-hooks.js +209 -202
- package/api/explorer-api-paginated-list-hooks.js +441 -440
- package/api/explorer-batch-info-loader.js +111 -85
- package/api/explorer-tx-api.js +28 -28
- package/api/ledger-stream.js +15 -0
- package/asset/amount.js +56 -56
- package/asset/asset-icon.js +41 -41
- package/asset/asset-issuer.js +21 -21
- package/asset/asset-link.js +107 -107
- package/asset/asset-list-hooks.js +59 -59
- package/asset/asset-meta-hooks.js +88 -88
- package/asset/asset-selector.js +72 -71
- package/basic-styles/base.scss +50 -50
- package/basic-styles/grid.scss +1 -1
- package/basic-styles/table.scss +1 -1
- package/claimable-balance/claimable-balance-claimants.js +5 -0
- package/contract/contract-api.js +15 -0
- package/contract/invocation-info-view.js +10 -2
- package/contract/sc-val.js +24 -0
- package/controls/button-group.js +25 -19
- package/controls/button-group.scss +46 -46
- package/controls/button.js +93 -78
- package/controls/button.scss +173 -173
- package/controls/code-block.js +42 -34
- package/controls/code-block.scss +71 -71
- package/controls/dropdown.js +318 -287
- package/controls/dropdown.scss +159 -159
- package/controls/external-link.js +10 -4
- package/controls/info-tooltip.js +23 -16
- package/controls/slider.js +29 -19
- package/controls/tabs.js +94 -94
- package/controls/tabs.scss +70 -70
- package/controls/tooltip.js +244 -240
- package/controls/tooltip.scss +116 -116
- package/controls/update-highlighter.js +32 -27
- package/controls/update-highlighter.scss +7 -7
- package/date/date-selector.js +56 -54
- package/date/elapsed-time.js +28 -21
- package/dex/price-dynamic.js +53 -44
- package/dex/price-dynamic.scss +33 -33
- package/directory/directory-hooks.js +109 -97
- package/effect/effect-description.js +5 -3
- package/errors/error-boundary.js +110 -97
- package/horizon/horizon-account-helpers.js +104 -104
- package/horizon/horizon-ledger-helpers.js +35 -35
- package/horizon/horizon-trades-helper.js +88 -88
- package/horizon/horizon-transaction-helpers.js +36 -36
- package/index.d.ts +1241 -0
- package/interaction/accordion.js +43 -35
- package/interaction/autofocus.js +13 -9
- package/interaction/block-select.js +64 -53
- package/interaction/block-select.scss +21 -21
- package/interaction/copy-to-clipboard.js +25 -18
- package/interaction/inline-progress.js +20 -15
- package/interaction/qr-code.js +34 -34
- package/interaction/responsive.js +20 -20
- package/interaction/spoiler.js +52 -39
- package/interaction/spoiler.scss +6 -6
- package/interaction/theme-selector.js +13 -10
- package/ledger/ledger-entry-href-formatter.js +4 -3
- package/ledger/ledger-entry-link.js +93 -58
- package/ledger/ledger-info-parser.js +28 -0
- package/meta/page-meta-tags.js +243 -238
- package/module/dynamic-module.js +47 -47
- package/package.json +3 -2
- package/state/on-screen-hooks.js +22 -22
- package/state/page-visibility-helpers.js +29 -16
- package/state/page-visibility-hooks.js +13 -11
- package/state/screen-orientation-hooks.js +19 -15
- package/state/state-hooks.js +76 -76
- package/state/stellar-network-hooks.js +56 -44
- package/state/theme.js +29 -28
- package/stellar/key-type.js +92 -91
- package/stellar/ledger-generic-id.js +39 -39
- package/stellar/signature-hint-utils.js +65 -65
- package/toast/toast-notifications-block.js +59 -59
- package/toast/toast-notifications.scss +1 -1
- package/tx/op-description-view.js +84 -81
- package/tx/op-icon.js +98 -98
- package/tx/parser/op-balance-changes.js +66 -66
- package/tx/parser/op-descriptor.js +51 -48
- package/tx/parser/tx-details-parser.js +81 -81
- package/tx/parser/tx-matcher.js +371 -371
- package/tx/parser/type-filter-matcher.js +126 -126
- package/tx/tx-list-hooks.js +32 -18
- package/tx/tx-operations-list.js +117 -116
|
@@ -1,98 +1,110 @@
|
|
|
1
|
-
import {useEffect, useState} from 'react'
|
|
2
|
-
import {StrKey} from '@stellar/stellar-base'
|
|
3
|
-
import {stringifyQuery} from '@stellar-expert/navigation'
|
|
4
|
-
import {InMemoryClientCache} from '@stellar-expert/client-cache'
|
|
5
|
-
import {fetchExplorerApi} from '../api/explorer-api-call'
|
|
6
|
-
import {ExplorerBatchInfoLoader} from '../api/explorer-batch-info-loader'
|
|
7
|
-
|
|
8
|
-
const cache = new InMemoryClientCache({})
|
|
9
|
-
|
|
10
|
-
function isValidAddress(address) {
|
|
11
|
-
if (!address)
|
|
12
|
-
return false
|
|
13
|
-
if (!StrKey.isValidEd25519PublicKey(address) && !StrKey.isValidContract(address))
|
|
14
|
-
return false
|
|
15
|
-
return true
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const loader = new ExplorerBatchInfoLoader(batch => {
|
|
19
|
-
return fetchExplorerApi('directory' + stringifyQuery({address: batch}))
|
|
20
|
-
}, entry => {
|
|
21
|
-
cache.set(entry.address, entry)
|
|
22
|
-
return {key: entry.address, info: entry}
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
import {useEffect, useState} from 'react'
|
|
2
|
+
import {StrKey} from '@stellar/stellar-base'
|
|
3
|
+
import {stringifyQuery} from '@stellar-expert/navigation'
|
|
4
|
+
import {InMemoryClientCache} from '@stellar-expert/client-cache'
|
|
5
|
+
import {fetchExplorerApi} from '../api/explorer-api-call'
|
|
6
|
+
import {ExplorerBatchInfoLoader} from '../api/explorer-batch-info-loader'
|
|
7
|
+
|
|
8
|
+
const cache = new InMemoryClientCache({})
|
|
9
|
+
|
|
10
|
+
function isValidAddress(address) {
|
|
11
|
+
if (!address)
|
|
12
|
+
return false
|
|
13
|
+
if (!StrKey.isValidEd25519PublicKey(address) && !StrKey.isValidContract(address))
|
|
14
|
+
return false
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const loader = new ExplorerBatchInfoLoader(batch => {
|
|
19
|
+
return fetchExplorerApi('directory' + stringifyQuery({address: batch}))
|
|
20
|
+
}, entry => {
|
|
21
|
+
cache.set(entry.address, entry)
|
|
22
|
+
return {key: entry.address, info: entry}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fetch a directory entry for a Stellar address
|
|
27
|
+
* @param {string} address - Stellar account or contract address
|
|
28
|
+
* @param {Object} [options]
|
|
29
|
+
* @param {boolean} [options.forceRefresh=false] - Bypass cache and fetch fresh data
|
|
30
|
+
* @param {boolean} [options.extended=false] - Fetch extended directory information
|
|
31
|
+
* @return {Promise<Object|null>} Directory entry or null if not found
|
|
32
|
+
*/
|
|
33
|
+
export async function getDirectoryEntry(address, options) {
|
|
34
|
+
const {forceRefresh = false, extended = false} = options || {}
|
|
35
|
+
//ignore invalid addresses
|
|
36
|
+
if (!isValidAddress(address))
|
|
37
|
+
return null
|
|
38
|
+
if (extended) {
|
|
39
|
+
return fetchExplorerApi(`directory/${address}` + stringifyQuery({
|
|
40
|
+
extended: true,
|
|
41
|
+
s: Math.random().toString(36).substr(2)
|
|
42
|
+
}))
|
|
43
|
+
.catch(err => {
|
|
44
|
+
console.error(err)
|
|
45
|
+
return null
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
//try to load from the shared cache
|
|
49
|
+
if (forceRefresh) {
|
|
50
|
+
const cachedEntry = cache.get(address)
|
|
51
|
+
if (cachedEntry && !cachedEntry.isExpired) return cachedEntry.data
|
|
52
|
+
}
|
|
53
|
+
//load from the server
|
|
54
|
+
return loader.loadEntry(address)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
* @param {string} address
|
|
60
|
+
* @param {{[forceRefresh]: boolean}} [options]
|
|
61
|
+
* @return {string|null}
|
|
62
|
+
*/
|
|
63
|
+
export function useDirectory(address, options) {
|
|
64
|
+
const {forceRefresh = false} = options || {}
|
|
65
|
+
const [directoryInfo, setDirectoryInfo] = useState(null)
|
|
66
|
+
let unloaded = false
|
|
67
|
+
useEffect(function () {
|
|
68
|
+
if (!isValidAddress(address))
|
|
69
|
+
return
|
|
70
|
+
if (!forceRefresh) {
|
|
71
|
+
const cachedEntry = cache.get(address)
|
|
72
|
+
if (cachedEntry && !cachedEntry.isStale) {
|
|
73
|
+
if (!cachedEntry.isExpired)
|
|
74
|
+
return setDirectoryInfo(cachedEntry.data) //everything is up to date - no need to re-fetch
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//load from the server
|
|
78
|
+
loader.loadEntry(address)
|
|
79
|
+
.then(di => {
|
|
80
|
+
if (!unloaded) {
|
|
81
|
+
setDirectoryInfo(di)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
return () => {
|
|
85
|
+
unloaded = true
|
|
86
|
+
}
|
|
87
|
+
}, [address, forceRefresh])
|
|
88
|
+
return directoryInfo
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* React hook that fetches and returns all available directory tags
|
|
93
|
+
* @return {Array<Object>} Array of directory tag objects
|
|
94
|
+
*/
|
|
95
|
+
export function useDirectoryTags() {
|
|
96
|
+
const [tags, setTags] = useState(() => {
|
|
97
|
+
const cachedEntry = cache.get('tags')
|
|
98
|
+
if (cachedEntry && !cachedEntry.isExpired) {
|
|
99
|
+
return cachedEntry.data
|
|
100
|
+
}
|
|
101
|
+
fetchExplorerApi('directory/tags')
|
|
102
|
+
.then(data => {
|
|
103
|
+
cache.set('tags', data, 10 * 60) //10 minutes
|
|
104
|
+
setTags(data)
|
|
105
|
+
})
|
|
106
|
+
.catch(err => console.error(err))
|
|
107
|
+
return []
|
|
108
|
+
})
|
|
109
|
+
return tags
|
|
98
110
|
}
|
|
@@ -14,10 +14,12 @@ import {AssetLink} from '../asset/asset-link'
|
|
|
14
14
|
import {Amount} from '../asset/amount'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* @param {
|
|
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
|
|
19
22
|
* @return {JSX.Element}
|
|
20
|
-
* @constructor
|
|
21
23
|
*/
|
|
22
24
|
export function EffectDescription({effect, operation}) {
|
|
23
25
|
switch (effect.type) {
|
package/errors/error-boundary.js
CHANGED
|
@@ -1,98 +1,111 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import {navigation as nav} from '@stellar-expert/navigation'
|
|
4
|
-
import {BlockSelect} from '../interaction/block-select'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{this.renderError()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import {navigation as nav} from '@stellar-expert/navigation'
|
|
4
|
+
import {BlockSelect} from '../interaction/block-select'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* React error boundary that catches rendering errors and displays a fallback UI
|
|
8
|
+
* @extends React.Component
|
|
9
|
+
*/
|
|
10
|
+
export class ErrorBoundary extends React.Component {
|
|
11
|
+
constructor(props) {
|
|
12
|
+
super(props)
|
|
13
|
+
this.state = {lastError: null, url: null}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
componentDidCatch(e, errorInfo) {
|
|
17
|
+
e.componentStack = errorInfo?.componentStack
|
|
18
|
+
console.error(e)
|
|
19
|
+
this.setState({lastError: e, url: window.location.href}, () => {
|
|
20
|
+
const stopListening = nav.history.listen((location) => {
|
|
21
|
+
if (this.state.url !== window.location.href) {
|
|
22
|
+
stopListening()
|
|
23
|
+
this.setState({lastError: null, url: null})
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
renderError() {
|
|
30
|
+
const {errorBoundarySendErrors = true} = this.props
|
|
31
|
+
return <>
|
|
32
|
+
{this.renderErrorDetails()}
|
|
33
|
+
{errorBoundarySendErrors && this.renderSendErrorBlock()}
|
|
34
|
+
</>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
renderSendErrorBlock() {
|
|
38
|
+
const {lastError, url} = this.state
|
|
39
|
+
const {message, stack, componentStack} = lastError
|
|
40
|
+
const compiledText = `Error details:
|
|
41
|
+
"${message}" at ${url}
|
|
42
|
+
${stack}
|
|
43
|
+
${componentStack ? 'Components stack: ' + componentStack : ''}
|
|
44
|
+
${navigator.userAgent}`
|
|
45
|
+
return <div className="space dimmed text-small text-right">
|
|
46
|
+
If this error persists please{' '}
|
|
47
|
+
<a href={'mailto:support@stellar.expert?subject=Exception&body=' + encodeURIComponent(compiledText)}
|
|
48
|
+
target="_blank">contact our support</a>.
|
|
49
|
+
</div>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
renderErrorDetails() {
|
|
53
|
+
const {lastError, url} = this.state
|
|
54
|
+
const {message, stack, componentStack} = lastError
|
|
55
|
+
const text = `"${message}" at ${url}`
|
|
56
|
+
const {errorBoundaryErrorDetails = true} = this.props
|
|
57
|
+
if (errorBoundaryErrorDetails === false)
|
|
58
|
+
return null
|
|
59
|
+
if (errorBoundaryErrorDetails === true) {
|
|
60
|
+
return <div className="error space text-small" style={{overflow: 'auto', maxWidth: '100%', padding: '1rem 2rem'}}>
|
|
61
|
+
<BlockSelect as="div">
|
|
62
|
+
<div className="micro-space">{text}</div>
|
|
63
|
+
<div className="text-tiny">
|
|
64
|
+
<pre style={{whiteSpace: 'pre-wrap'}}>{stack}</pre>
|
|
65
|
+
<div>{navigator.userAgent}</div>
|
|
66
|
+
</div>
|
|
67
|
+
</BlockSelect>
|
|
68
|
+
</div>
|
|
69
|
+
}
|
|
70
|
+
return <>{errorBoundaryErrorDetails}</>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
render() {
|
|
74
|
+
if (!this.state.lastError)
|
|
75
|
+
return this.props.children
|
|
76
|
+
const {title = 'Unhandled error occurred', wrapper, ...otherProps} = this.props
|
|
77
|
+
if (wrapper)
|
|
78
|
+
return React.createElement(wrapper, {...otherProps}, this.renderError())
|
|
79
|
+
return <div className="segment blank">
|
|
80
|
+
<h3 className="color-danger">{title}</h3>
|
|
81
|
+
<hr className="flare"/>
|
|
82
|
+
{this.renderError()}
|
|
83
|
+
</div>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static propTypes = {
|
|
87
|
+
errorBoundaryTitle: PropTypes.string,
|
|
88
|
+
errorBoundarySendErrors: PropTypes.bool,
|
|
89
|
+
errorBoundaryErrorDetails: PropTypes.oneOfType([PropTypes.bool, PropTypes.any])
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Higher-order component that wraps a component with an ErrorBoundary
|
|
95
|
+
* @param {function} wrapped - React component to wrap
|
|
96
|
+
* @param {Object} [options]
|
|
97
|
+
* @param {string} [options.errorBoundaryTitle] - Title displayed in the error UI
|
|
98
|
+
* @param {boolean} [options.errorBoundarySendErrors] - Show "contact support" link
|
|
99
|
+
* @param {boolean|*} [options.errorBoundaryErrorDetails] - Show error details (true), hide (false), or provide custom content
|
|
100
|
+
* @return {function} Wrapped component with error boundary
|
|
101
|
+
*/
|
|
102
|
+
export function withErrorBoundary(wrapped, {errorBoundaryTitle, errorBoundarySendErrors, errorBoundaryErrorDetails} = {}) {
|
|
103
|
+
function ErrorBoundaryWrapper(props) {
|
|
104
|
+
const nested = /*#__PURE__*/React.createElement(wrapped, props)
|
|
105
|
+
return <ErrorBoundary {...{errorBoundaryTitle, errorBoundarySendErrors, errorBoundaryErrorDetails}}>{nested}</ErrorBoundary>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ErrorBoundaryWrapper.displayName = `withErrorBoundary(${wrapped.displayName || wrapped.name})`
|
|
109
|
+
ErrorBoundaryWrapper.WrappedComponent = wrapped
|
|
110
|
+
return ErrorBoundaryWrapper
|
|
98
111
|
}
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
import {applyListQueryParameters, loadAllHorizonRecords, initHorizon} from './horizon-client-helpers'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Retrieve account status from Horizon
|
|
5
|
-
* @param {
|
|
6
|
-
* @returns {Promise<AccountResponse>}
|
|
7
|
-
*/
|
|
8
|
-
export function loadAccount(accountAddress) {
|
|
9
|
-
return initHorizon()
|
|
10
|
-
.loadAccount(accountAddress)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Load assets issued by the account from Horizon
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {ListQueryParams} [queryParams] - Query parameters (optional)
|
|
17
|
-
* @return {Promise<Array<Object>>}
|
|
18
|
-
*/
|
|
19
|
-
export function loadIssuedAssets(account, queryParams = null) {
|
|
20
|
-
const query = applyListQueryParameters(initHorizon().assets().forIssuer(account).limit(200), queryParams)
|
|
21
|
-
return query.call()
|
|
22
|
-
.then(({records}) => {
|
|
23
|
-
if (records && records.length) {
|
|
24
|
-
records.sort((a, b) => a.asset_code - b.asset_code)
|
|
25
|
-
}
|
|
26
|
-
return records || []
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Load offer from Horizon for the particular account
|
|
32
|
-
* @param {
|
|
33
|
-
* @param {ListQueryParams} [queryParams] - Query parameters (optional)
|
|
34
|
-
* @return {Promise<Array<Object>>}
|
|
35
|
-
*/
|
|
36
|
-
export function loadAccountOffers(account, queryParams = null) {
|
|
37
|
-
const query = applyListQueryParameters(initHorizon().offers().forAccount(account), queryParams)
|
|
38
|
-
return loadAllHorizonRecords(query)
|
|
39
|
-
.then(r => r.records)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Load pending claimable balances for a given account pubkey
|
|
45
|
-
* @param {
|
|
46
|
-
* @return {Promise<Array<Object>>}
|
|
47
|
-
*/
|
|
48
|
-
export function loadAccountClaimableBalances(account) {
|
|
49
|
-
return initHorizon().claimableBalances()
|
|
50
|
-
.claimant(account)
|
|
51
|
-
.limit(100)
|
|
52
|
-
.call()
|
|
53
|
-
.then(r => r.records)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Detect account lock status based on the signers configuration
|
|
58
|
-
* @param {AccountResponse} account - Account info from Horizon
|
|
59
|
-
* @returns {'unlocked'|'locked'|'partially locked'}
|
|
60
|
-
*/
|
|
61
|
-
export function getAccountLockStatus(account) {
|
|
62
|
-
const {high_threshold, med_threshold, low_threshold} = account.thresholds
|
|
63
|
-
|
|
64
|
-
//calculate the sum of all signers weights
|
|
65
|
-
const totalSignersWeight = account.signers.reduce((res, signer) => res + signer.weight, 0)
|
|
66
|
-
|
|
67
|
-
if (totalSignersWeight > 0) {
|
|
68
|
-
//compare it with med_threshold and high_threshold
|
|
69
|
-
if (totalSignersWeight >= med_threshold || totalSignersWeight >= high_threshold) {
|
|
70
|
-
return 'unlocked'
|
|
71
|
-
} else if (totalSignersWeight >= low_threshold) {
|
|
72
|
-
return 'partially locked'
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return 'locked'
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Retrieve balance for a given Horizon account info
|
|
80
|
-
* @param {AccountResponse} account - Account Horizon info
|
|
81
|
-
* @param {{code:
|
|
82
|
-
* @returns {{total:
|
|
83
|
-
*/
|
|
84
|
-
export function getAccountBalance(account, asset = null) {
|
|
85
|
-
if (!account.balances) return {asset, total: 0, available: 0}
|
|
86
|
-
const assetBalance = account.balances.find(b => (!asset && b.asset_type === 'native') ||
|
|
87
|
-
(b.asset_issuer === asset.issuer && b.asset_code === asset.code)),
|
|
88
|
-
res = {asset, total: 0, available: 0}
|
|
89
|
-
|
|
90
|
-
if (assetBalance) {
|
|
91
|
-
res.total = parseFloat(assetBalance.balance)
|
|
92
|
-
res.available = res.total - parseFloat(assetBalance.selling_liabilities)
|
|
93
|
-
if (!asset) { //for XLM we also need to check reserved amount
|
|
94
|
-
res.available -= (parseInt(account.subentry_count) + 2) * baseReserve
|
|
95
|
-
}
|
|
96
|
-
} else if (asset && asset.issuer === account.address) {
|
|
97
|
-
res.total = res.available = 922337203685
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return res
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
import {applyListQueryParameters, loadAllHorizonRecords, initHorizon} from './horizon-client-helpers'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Retrieve account status from Horizon
|
|
5
|
+
* @param {string} accountAddress
|
|
6
|
+
* @returns {Promise<AccountResponse>}
|
|
7
|
+
*/
|
|
8
|
+
export function loadAccount(accountAddress) {
|
|
9
|
+
return initHorizon()
|
|
10
|
+
.loadAccount(accountAddress)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Load assets issued by the account from Horizon
|
|
15
|
+
* @param {string} account - Account address
|
|
16
|
+
* @param {ListQueryParams} [queryParams] - Query parameters (optional)
|
|
17
|
+
* @return {Promise<Array<Object>>}
|
|
18
|
+
*/
|
|
19
|
+
export function loadIssuedAssets(account, queryParams = null) {
|
|
20
|
+
const query = applyListQueryParameters(initHorizon().assets().forIssuer(account).limit(200), queryParams)
|
|
21
|
+
return query.call()
|
|
22
|
+
.then(({records}) => {
|
|
23
|
+
if (records && records.length) {
|
|
24
|
+
records.sort((a, b) => a.asset_code - b.asset_code)
|
|
25
|
+
}
|
|
26
|
+
return records || []
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load offer from Horizon for the particular account
|
|
32
|
+
* @param {string} account - Account address
|
|
33
|
+
* @param {ListQueryParams} [queryParams] - Query parameters (optional)
|
|
34
|
+
* @return {Promise<Array<Object>>}
|
|
35
|
+
*/
|
|
36
|
+
export function loadAccountOffers(account, queryParams = null) {
|
|
37
|
+
const query = applyListQueryParameters(initHorizon().offers().forAccount(account), queryParams)
|
|
38
|
+
return loadAllHorizonRecords(query)
|
|
39
|
+
.then(r => r.records)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load pending claimable balances for a given account pubkey
|
|
45
|
+
* @param {string} account - Account address
|
|
46
|
+
* @return {Promise<Array<Object>>}
|
|
47
|
+
*/
|
|
48
|
+
export function loadAccountClaimableBalances(account) {
|
|
49
|
+
return initHorizon().claimableBalances()
|
|
50
|
+
.claimant(account)
|
|
51
|
+
.limit(100)
|
|
52
|
+
.call()
|
|
53
|
+
.then(r => r.records)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Detect account lock status based on the signers configuration
|
|
58
|
+
* @param {AccountResponse} account - Account info from Horizon
|
|
59
|
+
* @returns {'unlocked'|'locked'|'partially locked'}
|
|
60
|
+
*/
|
|
61
|
+
export function getAccountLockStatus(account) {
|
|
62
|
+
const {high_threshold, med_threshold, low_threshold} = account.thresholds
|
|
63
|
+
|
|
64
|
+
//calculate the sum of all signers weights
|
|
65
|
+
const totalSignersWeight = account.signers.reduce((res, signer) => res + signer.weight, 0)
|
|
66
|
+
|
|
67
|
+
if (totalSignersWeight > 0) {
|
|
68
|
+
//compare it with med_threshold and high_threshold
|
|
69
|
+
if (totalSignersWeight >= med_threshold || totalSignersWeight >= high_threshold) {
|
|
70
|
+
return 'unlocked'
|
|
71
|
+
} else if (totalSignersWeight >= low_threshold) {
|
|
72
|
+
return 'partially locked'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return 'locked'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Retrieve balance for a given Horizon account info
|
|
80
|
+
* @param {AccountResponse} account - Account Horizon info
|
|
81
|
+
* @param {{code: string, issuer: string}} asset - Asset or null (for XLM)
|
|
82
|
+
* @returns {{total: number, available: number, [asset]: {code: string, issuer: string}}}
|
|
83
|
+
*/
|
|
84
|
+
export function getAccountBalance(account, asset = null) {
|
|
85
|
+
if (!account.balances) return {asset, total: 0, available: 0}
|
|
86
|
+
const assetBalance = account.balances.find(b => (!asset && b.asset_type === 'native') ||
|
|
87
|
+
(b.asset_issuer === asset.issuer && b.asset_code === asset.code)),
|
|
88
|
+
res = {asset, total: 0, available: 0}
|
|
89
|
+
|
|
90
|
+
if (assetBalance) {
|
|
91
|
+
res.total = parseFloat(assetBalance.balance)
|
|
92
|
+
res.available = res.total - parseFloat(assetBalance.selling_liabilities)
|
|
93
|
+
if (!asset) { //for XLM we also need to check reserved amount
|
|
94
|
+
res.available -= (parseInt(account.subentry_count) + 2) * baseReserve
|
|
95
|
+
}
|
|
96
|
+
} else if (asset && asset.issuer === account.address) {
|
|
97
|
+
res.total = res.available = 922337203685
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return res
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|