@stellar-expert/ui-framework 1.16.8 → 1.19.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/README.md +1168 -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/claimable-balance/claimable-balance-claimants.js +23 -18
- package/contract/contract-api.js +15 -0
- package/contract/invocation-info-view.js +10 -2
- package/contract/sc-val.js +131 -107
- package/controls/button-group.js +25 -19
- package/controls/button.js +93 -78
- package/controls/code-block.js +42 -34
- package/controls/dropdown.js +318 -287
- 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/tooltip.js +244 -240
- package/controls/update-highlighter.js +32 -27
- package/date/date-selector.js +56 -54
- package/date/elapsed-time.js +28 -21
- package/dex/price-dynamic.js +53 -44
- package/directory/directory-hooks.js +109 -97
- package/effect/effect-description.js +346 -344
- package/errors/error-boundary.js +110 -97
- package/filter/editors/account-filter-view.js +20 -0
- package/filter/editors/asset-filter-view.js +9 -0
- package/filter/editors/timestamp-filter-view.js +34 -0
- package/filter/editors/type-filter-view.js +148 -0
- package/filter/filter-editors.js +102 -0
- package/filter/filter-view.js +218 -0
- package/filter/filter.scss +57 -0
- 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 +1271 -0
- package/index.js +95 -93
- package/interaction/accordion.js +43 -35
- package/interaction/autofocus.js +13 -9
- package/interaction/block-select.js +64 -53
- 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/theme-selector.js +13 -10
- package/ledger/ledger-entry-href-formatter.js +27 -26
- package/ledger/ledger-entry-link.js +93 -58
- package/ledger/ledger-info-parser.js +46 -18
- package/meta/page-meta-tags.js +243 -238
- package/module/dynamic-module.js +47 -47
- package/package.json +71 -40
- 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/tx/op-description-view.js +945 -942
- 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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import React, {useCallback, useEffect, useState} from 'react'
|
|
2
|
+
import {parseQuery} from '@stellar-expert/navigation'
|
|
3
|
+
import deepmerge from 'deepmerge'
|
|
4
|
+
import {Dropdown} from '../controls/dropdown'
|
|
5
|
+
import {resolveFilterEditor} from './filter-editors'
|
|
6
|
+
import './filter.scss'
|
|
7
|
+
|
|
8
|
+
let fieldDescriptionMapping = {}
|
|
9
|
+
|
|
10
|
+
function FilterCondition({field, value, setValue, removeFilter, edit}) {
|
|
11
|
+
const updateValue = useCallback(function (value) {
|
|
12
|
+
setValue(field, value)
|
|
13
|
+
}, [field, setValue])
|
|
14
|
+
|
|
15
|
+
const removeValue = useCallback(function () {
|
|
16
|
+
removeFilter(field, value)
|
|
17
|
+
}, [field, value, removeFilter])
|
|
18
|
+
|
|
19
|
+
const childProps = {value, edit}
|
|
20
|
+
const filter = fieldDescriptionMapping[field]
|
|
21
|
+
if (edit || filter.multi === false) {
|
|
22
|
+
childProps.setValue = updateValue
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const editor = resolveFilterEditor(field)
|
|
26
|
+
const title = !edit ? '' : (field === 'type' ? '' : filter.title)
|
|
27
|
+
return <span className="filter-condition condensed" title={edit ? '' : filter.description}>
|
|
28
|
+
<span className={'icon-' + filter.icon}/>
|
|
29
|
+
{title} {React.createElement(editor, childProps)}
|
|
30
|
+
{removeFilter ? <a href="#" className="icon-delete-circle" onClick={removeValue} title="Remove filter"/> : <> </>}
|
|
31
|
+
</span>
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parses and validates filter parameters from the current URL query string
|
|
35
|
+
* @return {object}
|
|
36
|
+
*/
|
|
37
|
+
export function parseFiltersFromQuery() {
|
|
38
|
+
const params = parseQuery()
|
|
39
|
+
const filters = {}
|
|
40
|
+
let isEmpty = true
|
|
41
|
+
for (const [key, value] of Object.entries(params)) {
|
|
42
|
+
const filterDescriptor = fieldDescriptionMapping[key]
|
|
43
|
+
if (!filterDescriptor)
|
|
44
|
+
continue //skip unrelated query parameters
|
|
45
|
+
filters[key] = value
|
|
46
|
+
isEmpty = false
|
|
47
|
+
}
|
|
48
|
+
return filters
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function FiltersGroup({filters, replaceFilter, removeFilter, edit = false}) {
|
|
52
|
+
if (!filters)
|
|
53
|
+
return null
|
|
54
|
+
return <>{Object.entries(filters).map(([field, values]) => {
|
|
55
|
+
if (!(values instanceof Array)) {
|
|
56
|
+
return <FilterCondition key={field} field={field} value={values} edit={edit} setValue={replaceFilter}
|
|
57
|
+
removeFilter={removeFilter}/>
|
|
58
|
+
}
|
|
59
|
+
if (!values.length)
|
|
60
|
+
return null
|
|
61
|
+
return <React.Fragment key={field}>
|
|
62
|
+
{values.map(value => <FilterCondition key={value} field={field} value={value} edit={edit}
|
|
63
|
+
setValue={replaceFilter} removeFilter={removeFilter}/>)}
|
|
64
|
+
</React.Fragment>
|
|
65
|
+
})}</>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A component for managing search filters
|
|
70
|
+
* @param {object} [presetFilter] - Initial base filters
|
|
71
|
+
* @param {object} [fields] - Custom filter field definitions
|
|
72
|
+
* @param {func} [onChange] - Callback triggered when filters change
|
|
73
|
+
*/
|
|
74
|
+
export function FilterView({presetFilter, fields = {}, onChange}) {
|
|
75
|
+
fieldDescriptionMapping = fields
|
|
76
|
+
const [filters, setFilters] = useState(presetFilter || {})
|
|
77
|
+
const [_, setSerializedFilter] = useState(JSON.stringify(filters))
|
|
78
|
+
|
|
79
|
+
const availableFields = []
|
|
80
|
+
const readyFilters = {}
|
|
81
|
+
const editFilters = {}
|
|
82
|
+
let editorMode = false
|
|
83
|
+
for (const [field, props = {}] of Object.entries(fieldDescriptionMapping)) {
|
|
84
|
+
const values = filters[field]
|
|
85
|
+
if (values !== undefined) {
|
|
86
|
+
if (values instanceof Array) {
|
|
87
|
+
const newValues = readyFilters[field] = []
|
|
88
|
+
for (let i = 0; i < values.length; i++) {
|
|
89
|
+
const value = values[i]
|
|
90
|
+
if (!value) {
|
|
91
|
+
editFilters[field] = [value]
|
|
92
|
+
editorMode = true
|
|
93
|
+
} else if (!presetFilter || !presetFilter[field]?.includes(value)) {
|
|
94
|
+
newValues.push(value)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
if (!values) {
|
|
99
|
+
editFilters[field] = values
|
|
100
|
+
editorMode = true
|
|
101
|
+
} else {
|
|
102
|
+
readyFilters[field] = values
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (props.multi !== false || !values) {
|
|
107
|
+
availableFields.push({
|
|
108
|
+
value: field,
|
|
109
|
+
title: props.description,
|
|
110
|
+
icon: 'icon-' + props.icon
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const updateExternalFilters = useCallback(function (newFilters) {
|
|
116
|
+
setSerializedFilter(prev => {
|
|
117
|
+
const newValue = JSON.stringify(newFilters)
|
|
118
|
+
if (prev !== newValue) {
|
|
119
|
+
setTimeout(() => onChange(deepmerge(presetFilter, newFilters)), 100)
|
|
120
|
+
return newValue
|
|
121
|
+
}
|
|
122
|
+
return prev
|
|
123
|
+
})
|
|
124
|
+
}, [onChange])
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const queryParams = parseFiltersFromQuery()
|
|
128
|
+
setFilters(queryParams)
|
|
129
|
+
updateExternalFilters(queryParams)
|
|
130
|
+
}, [])
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
const replaceFilter = useCallback((field, value) => setTimeout(() => setFilters(prev => {
|
|
134
|
+
const newFilters = {...prev}
|
|
135
|
+
const filter = fieldDescriptionMapping[field] || {}
|
|
136
|
+
//atomic values
|
|
137
|
+
if (filter.multi === false) {
|
|
138
|
+
if (value === null) {
|
|
139
|
+
delete newFilters[field]
|
|
140
|
+
updateExternalFilters(newFilters)
|
|
141
|
+
} else {
|
|
142
|
+
newFilters[field] = value
|
|
143
|
+
if (value) {
|
|
144
|
+
updateExternalFilters(newFilters)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return newFilters
|
|
148
|
+
}
|
|
149
|
+
//multi-values
|
|
150
|
+
let values = newFilters[field]
|
|
151
|
+
if (!values) {
|
|
152
|
+
values = newFilters[field] = []
|
|
153
|
+
}
|
|
154
|
+
if (value !== null) {
|
|
155
|
+
if (!values.includes(value)) {
|
|
156
|
+
if (values[values.length - 1] === '') {
|
|
157
|
+
values.pop()
|
|
158
|
+
}
|
|
159
|
+
values.push(value)
|
|
160
|
+
if (value) {
|
|
161
|
+
updateExternalFilters(newFilters)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
const idx = values.indexOf('')
|
|
166
|
+
if (idx >= 0) {
|
|
167
|
+
values.splice(idx, 1)
|
|
168
|
+
updateExternalFilters(newFilters)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return newFilters
|
|
172
|
+
}), 100), [updateExternalFilters])
|
|
173
|
+
|
|
174
|
+
const removeFilter = useCallback((field, value) => setFilters(prev => {
|
|
175
|
+
const newFilters = {...prev}
|
|
176
|
+
const filter = fieldDescriptionMapping[field] || {}
|
|
177
|
+
//atomic value
|
|
178
|
+
if (filter.multi === false) {
|
|
179
|
+
if (newFilters[field] || newFilters[field] === '') {
|
|
180
|
+
newFilters[field] = undefined
|
|
181
|
+
updateExternalFilters(newFilters)
|
|
182
|
+
}
|
|
183
|
+
return newFilters
|
|
184
|
+
}
|
|
185
|
+
//multi-values
|
|
186
|
+
const values = newFilters[field]
|
|
187
|
+
const idx = values.indexOf(value)
|
|
188
|
+
if (idx >= 0) {
|
|
189
|
+
values.splice(idx, 1)
|
|
190
|
+
updateExternalFilters(newFilters)
|
|
191
|
+
}
|
|
192
|
+
return newFilters
|
|
193
|
+
}), [updateExternalFilters])
|
|
194
|
+
|
|
195
|
+
const addFilter = useCallback(field => {
|
|
196
|
+
replaceFilter(field, '')
|
|
197
|
+
}, [replaceFilter])
|
|
198
|
+
|
|
199
|
+
const title = <span className="nowrap"><span className="icon icon-add-circle"/>Add filter</span>
|
|
200
|
+
|
|
201
|
+
return <div className="op-filters">
|
|
202
|
+
<div className="mobile-only micro-space"/>
|
|
203
|
+
<span className="icon-filter"/> Filters 
|
|
204
|
+
<FiltersGroup filters={presetFilter}/>
|
|
205
|
+
<FiltersGroup filters={readyFilters} replaceFilter={replaceFilter} removeFilter={removeFilter}/>
|
|
206
|
+
{!editorMode ?
|
|
207
|
+
<Dropdown title={title} options={availableFields} onChange={addFilter}/> :
|
|
208
|
+
<div className="micro-space">
|
|
209
|
+
<FiltersGroup filters={editFilters} replaceFilter={replaceFilter} removeFilter={removeFilter} edit/>
|
|
210
|
+
</div>}
|
|
211
|
+
<div className="micro-space"/>
|
|
212
|
+
</div>
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @callback FilterView~OnChange
|
|
217
|
+
* @param {Object} mergedFilters - The combination of preset filters and newly selected values.
|
|
218
|
+
*/
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.op-filters {
|
|
2
|
+
.icon-delete-circle {
|
|
3
|
+
margin-left: 0.1em;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.op-filter-container {
|
|
8
|
+
display: flex;
|
|
9
|
+
justify-content: flex-start;
|
|
10
|
+
background-color: var(--color-bg);
|
|
11
|
+
border: 1px solid var(--color-contrast-border);
|
|
12
|
+
border-radius: $border-radius-input;
|
|
13
|
+
color: var(--color-text);
|
|
14
|
+
height: 4rem;
|
|
15
|
+
padding: 0.2rem 1rem;
|
|
16
|
+
width: 100%;
|
|
17
|
+
|
|
18
|
+
&:focus {
|
|
19
|
+
border-color: var(--color-highlight);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.filter-condition {
|
|
24
|
+
display: inline-block;
|
|
25
|
+
margin-right: 0.4em;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
|
|
28
|
+
&:before {
|
|
29
|
+
content: '/';
|
|
30
|
+
color: var(--color-highlight);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.icon-delete-circle {
|
|
34
|
+
opacity: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:hover .icon-delete-circle {
|
|
38
|
+
opacity: 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
> input {
|
|
42
|
+
margin-bottom: 0;
|
|
43
|
+
height: 3rem;
|
|
44
|
+
padding: 0 0.4rem;
|
|
45
|
+
|
|
46
|
+
+ .icon-ok {
|
|
47
|
+
display: inline-block;
|
|
48
|
+
visibility: hidden;
|
|
49
|
+
transition: visibility 0.3s;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&:focus + .icon-ok {
|
|
53
|
+
visibility: visible;
|
|
54
|
+
margin-left: 0.2em;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -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
|
+
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import {initHorizon, applyListQueryParameters} from './horizon-client-helpers'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Load ledgers from Horizon
|
|
5
|
-
* @param {ListQueryParams} [queryParams] - Query parameters (optional)
|
|
6
|
-
* @return {Promise<Array<Object>>}
|
|
7
|
-
*/
|
|
8
|
-
export function loadLedgers(queryParams = null) {
|
|
9
|
-
const query = applyListQueryParameters(initHorizon().ledgers(), queryParams)
|
|
10
|
-
return query.call()
|
|
11
|
-
.then(r => r.records)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Load ledger by its sequence
|
|
16
|
-
* @param {
|
|
17
|
-
* @return {Object}
|
|
18
|
-
*/
|
|
19
|
-
export function loadLedger(sequence) {
|
|
20
|
-
return initHorizon().ledgers()
|
|
21
|
-
.ledger(sequence)
|
|
22
|
-
.call()
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Stream ledgers from Horizon
|
|
27
|
-
* @param {
|
|
28
|
-
* @param {
|
|
29
|
-
* @return {
|
|
30
|
-
*/
|
|
31
|
-
export function streamLedgers(cursor, onNewLedger) {
|
|
32
|
-
return initHorizon().ledgers()
|
|
33
|
-
.order('asc')
|
|
34
|
-
.cursor(cursor || 'now')
|
|
35
|
-
.stream({onmessage: ledger => onNewLedger(ledger)})
|
|
1
|
+
import {initHorizon, applyListQueryParameters} from './horizon-client-helpers'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Load ledgers from Horizon
|
|
5
|
+
* @param {ListQueryParams} [queryParams] - Query parameters (optional)
|
|
6
|
+
* @return {Promise<Array<Object>>}
|
|
7
|
+
*/
|
|
8
|
+
export function loadLedgers(queryParams = null) {
|
|
9
|
+
const query = applyListQueryParameters(initHorizon().ledgers(), queryParams)
|
|
10
|
+
return query.call()
|
|
11
|
+
.then(r => r.records)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Load ledger by its sequence
|
|
16
|
+
* @param {number} sequence - Sequence of the ledger to fetch
|
|
17
|
+
* @return {Object}
|
|
18
|
+
*/
|
|
19
|
+
export function loadLedger(sequence) {
|
|
20
|
+
return initHorizon().ledgers()
|
|
21
|
+
.ledger(sequence)
|
|
22
|
+
.call()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Stream ledgers from Horizon
|
|
27
|
+
* @param {string} cursor - Cursor to start from
|
|
28
|
+
* @param {function} onNewLedger - Callback to invoke when new ledger arrives
|
|
29
|
+
* @return {function}
|
|
30
|
+
*/
|
|
31
|
+
export function streamLedgers(cursor, onNewLedger) {
|
|
32
|
+
return initHorizon().ledgers()
|
|
33
|
+
.order('asc')
|
|
34
|
+
.cursor(cursor || 'now')
|
|
35
|
+
.stream({onmessage: ledger => onNewLedger(ledger)})
|
|
36
36
|
}
|