@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
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import cn from 'classnames'
|
|
3
|
-
import {shortenString} from '@stellar-expert/formatter'
|
|
4
|
-
import {AccountIdenticon} from './identicon'
|
|
5
|
-
import {InfoTooltip} from '../controls/info-tooltip'
|
|
6
|
-
import {useDirectory} from '../directory/directory-hooks'
|
|
7
|
-
import {formatExplorerLink} from '../ledger/ledger-entry-href-formatter'
|
|
8
|
-
import {useStellarNetwork} from '../state/stellar-network-hooks'
|
|
9
|
-
import {decodeKeyType} from '../stellar/key-type'
|
|
10
|
-
import './account-address.scss'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
function isPublicKeyOrContract(type) {
|
|
14
|
-
return type === 'ed25519' || type === 'contract' || type === 'muxed'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getAccountPredefinedDisplayName(address) {
|
|
18
|
-
if (!window.predefinedAccountDisplayNames)
|
|
19
|
-
return undefined
|
|
20
|
-
return window.predefinedAccountDisplayNames[address]
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function AccountDisplayName({type, address, name}) {
|
|
24
|
-
const predefined = getAccountPredefinedDisplayName(address)
|
|
25
|
-
let directoryInfo = useDirectory(!predefined && !name && isPublicKeyOrContract(type) && address),
|
|
26
|
-
warning
|
|
27
|
-
if (name === false)
|
|
28
|
-
return null
|
|
29
|
-
if (predefined)
|
|
30
|
-
return `[${predefined}] `
|
|
31
|
-
if (directoryInfo) {
|
|
32
|
-
name = directoryInfo.name
|
|
33
|
-
if (directoryInfo.tags.includes('malicious')) {
|
|
34
|
-
warning = <i className="icon icon-warning color-warning"
|
|
35
|
-
title="This account was reported for illicit or fraudulent activity. Do not send funds to this address and do not trust any person affiliated with it."/>
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
if (!name && !warning) return null
|
|
39
|
-
return <>
|
|
40
|
-
{name ? `[${name}] ` : ''}
|
|
41
|
-
{warning}
|
|
42
|
-
</>
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Explorer link for Account/MuxedAccount/Sha256Hash/PreAuthTx/SignedPayload
|
|
48
|
-
* @param {
|
|
49
|
-
* @param {
|
|
50
|
-
* @param {
|
|
51
|
-
* @param {
|
|
52
|
-
* @param {
|
|
53
|
-
* @param {
|
|
54
|
-
* @param {*} [prefix] - Link prefix
|
|
55
|
-
* @param {*} [suffix] - Link suffix
|
|
56
|
-
* @param {{}} [style] - Optional CSS inline style
|
|
57
|
-
* @param {
|
|
58
|
-
* @param {...*} [otherProps] - Optional container parameters
|
|
59
|
-
* @constructor
|
|
60
|
-
*/
|
|
61
|
-
export const AccountAddress = React.memo(function AccountAddress({
|
|
62
|
-
account,
|
|
63
|
-
chars = 8,
|
|
64
|
-
name,
|
|
65
|
-
link,
|
|
66
|
-
style,
|
|
67
|
-
className,
|
|
68
|
-
icon,
|
|
69
|
-
prefix,
|
|
70
|
-
suffix,
|
|
71
|
-
network,
|
|
72
|
-
...otherProps
|
|
73
|
-
}) {
|
|
74
|
-
useStellarNetwork()
|
|
75
|
-
const keyType = decodeKeyType(account)
|
|
76
|
-
if (!keyType)
|
|
77
|
-
return null //failed to decode address type
|
|
78
|
-
let {type, address, muxedId, publicKey, payload} = keyType
|
|
79
|
-
|
|
80
|
-
let innerStyle = !style ? undefined : style
|
|
81
|
-
|
|
82
|
-
let ed25519Address = address
|
|
83
|
-
if (chars && chars !== 'all') {
|
|
84
|
-
address = shortenString(account, chars)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const children = <>
|
|
88
|
-
{prefix}
|
|
89
|
-
{icon !== false && isPublicKeyOrContract(type) && <AccountIdenticon key="identicon" address={ed25519Address}/>}
|
|
90
|
-
<AccountDisplayName type={type} address={ed25519Address} name={name}/>
|
|
91
|
-
<span className="account-key">{address}</span>
|
|
92
|
-
{muxedId !== undefined && <InfoTooltip icon="icon-plus">
|
|
93
|
-
Subaccount of a custodial account<br/>
|
|
94
|
-
<AccountAddress account={ed25519Address} name={false} chars={12}/>
|
|
95
|
-
<div className="dimmed text-tiny micro-space">Multiplexed id: {muxedId.toString()}</div>
|
|
96
|
-
</InfoTooltip>}
|
|
97
|
-
{payload !== undefined && <InfoTooltip icon="icon-plus">
|
|
98
|
-
ED25519 payload signer<br/>
|
|
99
|
-
<AccountAddress account={publicKey} name={false} chars={12}/>
|
|
100
|
-
<div className="dimmed text-tiny micro-space">Payload: {payload}</div>
|
|
101
|
-
</InfoTooltip>}
|
|
102
|
-
{suffix}
|
|
103
|
-
</>
|
|
104
|
-
|
|
105
|
-
const containerProps = {
|
|
106
|
-
title: account,
|
|
107
|
-
'aria-label': account,
|
|
108
|
-
className: cn('account-address', className),
|
|
109
|
-
style: innerStyle,
|
|
110
|
-
...otherProps
|
|
111
|
-
}
|
|
112
|
-
let renderAs = 'span'
|
|
113
|
-
|
|
114
|
-
if (link !== false && isPublicKeyOrContract(type)) {
|
|
115
|
-
renderAs = 'a'
|
|
116
|
-
if (typeof link === 'string') {
|
|
117
|
-
containerProps.href = link
|
|
118
|
-
} else {
|
|
119
|
-
containerProps.href = formatExplorerLink(type === 'contract' ? 'contract' : 'account', account, network)
|
|
120
|
-
if (window.origin !== explorerFrontendOrigin) {
|
|
121
|
-
containerProps.target = '_blank'
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return React.createElement(renderAs, containerProps, children)
|
|
127
|
-
})
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import cn from 'classnames'
|
|
3
|
+
import {shortenString} from '@stellar-expert/formatter'
|
|
4
|
+
import {AccountIdenticon} from './identicon'
|
|
5
|
+
import {InfoTooltip} from '../controls/info-tooltip'
|
|
6
|
+
import {useDirectory} from '../directory/directory-hooks'
|
|
7
|
+
import {formatExplorerLink} from '../ledger/ledger-entry-href-formatter'
|
|
8
|
+
import {useStellarNetwork} from '../state/stellar-network-hooks'
|
|
9
|
+
import {decodeKeyType} from '../stellar/key-type'
|
|
10
|
+
import './account-address.scss'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
function isPublicKeyOrContract(type) {
|
|
14
|
+
return type === 'ed25519' || type === 'contract' || type === 'muxed'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getAccountPredefinedDisplayName(address) {
|
|
18
|
+
if (!window.predefinedAccountDisplayNames)
|
|
19
|
+
return undefined
|
|
20
|
+
return window.predefinedAccountDisplayNames[address]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function AccountDisplayName({type, address, name}) {
|
|
24
|
+
const predefined = getAccountPredefinedDisplayName(address)
|
|
25
|
+
let directoryInfo = useDirectory(!predefined && !name && isPublicKeyOrContract(type) && address),
|
|
26
|
+
warning
|
|
27
|
+
if (name === false)
|
|
28
|
+
return null
|
|
29
|
+
if (predefined)
|
|
30
|
+
return `[${predefined}] `
|
|
31
|
+
if (directoryInfo) {
|
|
32
|
+
name = directoryInfo.name
|
|
33
|
+
if (directoryInfo.tags.includes('malicious')) {
|
|
34
|
+
warning = <i className="icon icon-warning color-warning"
|
|
35
|
+
title="This account was reported for illicit or fraudulent activity. Do not send funds to this address and do not trust any person affiliated with it."/>
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!name && !warning) return null
|
|
39
|
+
return <>
|
|
40
|
+
{name ? `[${name}] ` : ''}
|
|
41
|
+
{warning}
|
|
42
|
+
</>
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Explorer link for Account/MuxedAccount/Sha256Hash/PreAuthTx/SignedPayload
|
|
48
|
+
* @param {string} account - StrKey-encoded Account/MuxedAccount/Sha256Hash/PreAuthTx/SignedPayload
|
|
49
|
+
* @param {number|'all'} [chars] - Visible address characters count
|
|
50
|
+
* @param {boolean|string} [name] - Explicit account name that overrides the name from Directory; if false, friendly name is ignored
|
|
51
|
+
* @param {boolean|string} [link] - Explicit link; if false, the component is rendered without a link
|
|
52
|
+
* @param {boolean} [icon] - Whether to show/hide account identicon
|
|
53
|
+
* @param {string} [network] - Stellar network identifier
|
|
54
|
+
* @param {*} [prefix] - Link prefix
|
|
55
|
+
* @param {*} [suffix] - Link suffix
|
|
56
|
+
* @param {{}} [style] - Optional CSS inline style
|
|
57
|
+
* @param {string} [className] - Optional CSS class attribute
|
|
58
|
+
* @param {...*} [otherProps] - Optional container parameters
|
|
59
|
+
* @constructor
|
|
60
|
+
*/
|
|
61
|
+
export const AccountAddress = React.memo(function AccountAddress({
|
|
62
|
+
account,
|
|
63
|
+
chars = 8,
|
|
64
|
+
name,
|
|
65
|
+
link,
|
|
66
|
+
style,
|
|
67
|
+
className,
|
|
68
|
+
icon,
|
|
69
|
+
prefix,
|
|
70
|
+
suffix,
|
|
71
|
+
network,
|
|
72
|
+
...otherProps
|
|
73
|
+
}) {
|
|
74
|
+
useStellarNetwork()
|
|
75
|
+
const keyType = decodeKeyType(account)
|
|
76
|
+
if (!keyType)
|
|
77
|
+
return null //failed to decode address type
|
|
78
|
+
let {type, address, muxedId, publicKey, payload} = keyType
|
|
79
|
+
|
|
80
|
+
let innerStyle = !style ? undefined : style
|
|
81
|
+
|
|
82
|
+
let ed25519Address = address
|
|
83
|
+
if (chars && chars !== 'all') {
|
|
84
|
+
address = shortenString(account, chars)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const children = <>
|
|
88
|
+
{prefix}
|
|
89
|
+
{icon !== false && isPublicKeyOrContract(type) && <AccountIdenticon key="identicon" address={ed25519Address}/>}
|
|
90
|
+
<AccountDisplayName type={type} address={ed25519Address} name={name}/>
|
|
91
|
+
<span className="account-key">{address}</span>
|
|
92
|
+
{muxedId !== undefined && <InfoTooltip icon="icon-plus">
|
|
93
|
+
Subaccount of a custodial account<br/>
|
|
94
|
+
<AccountAddress account={ed25519Address} name={false} chars={12}/>
|
|
95
|
+
<div className="dimmed text-tiny micro-space">Multiplexed id: {muxedId.toString()}</div>
|
|
96
|
+
</InfoTooltip>}
|
|
97
|
+
{payload !== undefined && <InfoTooltip icon="icon-plus">
|
|
98
|
+
ED25519 payload signer<br/>
|
|
99
|
+
<AccountAddress account={publicKey} name={false} chars={12}/>
|
|
100
|
+
<div className="dimmed text-tiny micro-space">Payload: {payload}</div>
|
|
101
|
+
</InfoTooltip>}
|
|
102
|
+
{suffix}
|
|
103
|
+
</>
|
|
104
|
+
|
|
105
|
+
const containerProps = {
|
|
106
|
+
title: account,
|
|
107
|
+
'aria-label': account,
|
|
108
|
+
className: cn('account-address', className),
|
|
109
|
+
style: innerStyle,
|
|
110
|
+
...otherProps
|
|
111
|
+
}
|
|
112
|
+
let renderAs = 'span'
|
|
113
|
+
|
|
114
|
+
if (link !== false && isPublicKeyOrContract(type)) {
|
|
115
|
+
renderAs = 'a'
|
|
116
|
+
if (typeof link === 'string') {
|
|
117
|
+
containerProps.href = link
|
|
118
|
+
} else {
|
|
119
|
+
containerProps.href = formatExplorerLink(type === 'contract' ? 'contract' : 'account', account, network)
|
|
120
|
+
if (window.origin !== explorerFrontendOrigin) {
|
|
121
|
+
containerProps.target = '_blank'
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return React.createElement(renderAs, containerProps, children)
|
|
127
|
+
})
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {toStroops, fromStroops} from '@stellar-expert/formatter'
|
|
2
|
-
import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Calculate available balance for a given account balance trustline
|
|
6
|
-
* @param {AccountResponse} account
|
|
7
|
-
* @param {Horizon.BalanceLine} balance
|
|
8
|
-
* @param {
|
|
9
|
-
* @param {
|
|
10
|
-
* @return {
|
|
11
|
-
*/
|
|
12
|
-
export function calculateAvailableBalance(account, balance, additionalReserves = null, decimals = 7) {
|
|
13
|
-
let available = toStroops(balance.balance) - toStroops(balance.selling_liabilities || 0)
|
|
14
|
-
if (balance.asset_type === 'native') {
|
|
15
|
-
const reserves = 2 + account.subentry_count + account.num_sponsoring - account.num_sponsored
|
|
16
|
-
available = available - (BigInt(reserves) * 5000000n)
|
|
17
|
-
//TODO: fetch base_reserve from the Horizon
|
|
18
|
-
}
|
|
19
|
-
if (additionalReserves !== null) {
|
|
20
|
-
available = available - toStroops(additionalReserves, decimals)
|
|
21
|
-
}
|
|
22
|
-
return fromStroops(available, decimals)
|
|
1
|
+
import {toStroops, fromStroops} from '@stellar-expert/formatter'
|
|
2
|
+
import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Calculate available balance for a given account balance trustline
|
|
6
|
+
* @param {AccountResponse} account
|
|
7
|
+
* @param {Horizon.BalanceLine} balance
|
|
8
|
+
* @param {number} [additionalReserves]
|
|
9
|
+
* @param {number} [decimals=7]
|
|
10
|
+
* @return {string}
|
|
11
|
+
*/
|
|
12
|
+
export function calculateAvailableBalance(account, balance, additionalReserves = null, decimals = 7) {
|
|
13
|
+
let available = toStroops(balance.balance) - toStroops(balance.selling_liabilities || 0)
|
|
14
|
+
if (balance.asset_type === 'native') {
|
|
15
|
+
const reserves = 2 + account.subentry_count + account.num_sponsoring - account.num_sponsored
|
|
16
|
+
available = available - (BigInt(reserves) * 5000000n)
|
|
17
|
+
//TODO: fetch base_reserve from the Horizon
|
|
18
|
+
}
|
|
19
|
+
if (additionalReserves !== null) {
|
|
20
|
+
available = available - toStroops(additionalReserves, decimals)
|
|
21
|
+
}
|
|
22
|
+
return fromStroops(available, decimals)
|
|
23
23
|
}
|
package/account/identicon.js
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import './identicon.scss'
|
|
3
|
-
|
|
4
|
-
const RESOLUTION = 7
|
|
5
|
-
const SIZE = 448
|
|
6
|
-
const base32Alphabet = {}
|
|
7
|
-
|
|
8
|
-
//map base32 alphabet
|
|
9
|
-
'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.split('').map((c, i) => base32Alphabet[c] = i)
|
|
10
|
-
|
|
11
|
-
function decodeBase32(input) {
|
|
12
|
-
const buf = []
|
|
13
|
-
let shift = 8
|
|
14
|
-
let carry = 0
|
|
15
|
-
|
|
16
|
-
input.toUpperCase().split('').forEach(char => {
|
|
17
|
-
const symbol = base32Alphabet[char] & 0xff
|
|
18
|
-
shift -= 5
|
|
19
|
-
if (shift > 0) {
|
|
20
|
-
carry |= symbol << shift
|
|
21
|
-
} else if (shift < 0) {
|
|
22
|
-
buf.push(carry | (symbol >> -shift))
|
|
23
|
-
shift += 8
|
|
24
|
-
carry = (symbol << shift) & 0xff
|
|
25
|
-
} else {
|
|
26
|
-
buf.push(carry | symbol)
|
|
27
|
-
shift = 8
|
|
28
|
-
carry = 0
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
if (shift !== 8 && carry !== 0) {
|
|
33
|
-
buf.push(carry)
|
|
34
|
-
shift = 8
|
|
35
|
-
carry = 0
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return buf
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Draw Stellar address identicon on the given canvas
|
|
43
|
-
* @param {
|
|
44
|
-
* @param {
|
|
45
|
-
* @return {
|
|
46
|
-
*/
|
|
47
|
-
export function drawIdenticon(address, size = SIZE) {
|
|
48
|
-
//take 16 meaningful bytes from the raw pub key
|
|
49
|
-
const decoded = decodeBase32(address).slice(2, 16)
|
|
50
|
-
const width = RESOLUTION
|
|
51
|
-
const height = RESOLUTION
|
|
52
|
-
const columns = Math.ceil(width / 2)
|
|
53
|
-
const cellSize = size / width
|
|
54
|
-
const addressBytes = decoded.slice(1)
|
|
55
|
-
const fillStyle = `hsl(${360 * decoded[0] / 256 | 0},58%,52%)`
|
|
56
|
-
const dots = []
|
|
57
|
-
const rsize = ` width="${cellSize}" height="${cellSize}"`
|
|
58
|
-
for (let row = 0; row < height; row++) {
|
|
59
|
-
for (let column = 0; column < columns; column++) {
|
|
60
|
-
const position = column + row * columns
|
|
61
|
-
const bitSet = (addressBytes[position / 8 | 0] & (1 << (7 - position % 8))) !== 0
|
|
62
|
-
if (bitSet) {
|
|
63
|
-
dots.push(`<rect x="${cellSize * column}" y="${cellSize * row}"${rsize}/>`)
|
|
64
|
-
dots.push(`<rect x="${cellSize * (width - column - 1)}" y="${cellSize * row}"${rsize}/>`)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" fill="${fillStyle}">${dots.join('')}</svg>`
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Renders account-specific identicon
|
|
73
|
-
* @param {
|
|
74
|
-
* @param {
|
|
75
|
-
* @constructor
|
|
76
|
-
*/
|
|
77
|
-
export const AccountIdenticon = React.memo(function AccountIdenticon({address, size}) {
|
|
78
|
-
const props = {
|
|
79
|
-
className: 'identicon',
|
|
80
|
-
src: 'data:image/svg+xml;charset=utf-8,' + drawIdenticon(address),
|
|
81
|
-
width: SIZE,
|
|
82
|
-
height: SIZE
|
|
83
|
-
}
|
|
84
|
-
if (size) {
|
|
85
|
-
props.style = {
|
|
86
|
-
width: size,
|
|
87
|
-
height: size
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return <img {...props}/>
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import './identicon.scss'
|
|
3
|
+
|
|
4
|
+
const RESOLUTION = 7
|
|
5
|
+
const SIZE = 448
|
|
6
|
+
const base32Alphabet = {}
|
|
7
|
+
|
|
8
|
+
//map base32 alphabet
|
|
9
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.split('').map((c, i) => base32Alphabet[c] = i)
|
|
10
|
+
|
|
11
|
+
function decodeBase32(input) {
|
|
12
|
+
const buf = []
|
|
13
|
+
let shift = 8
|
|
14
|
+
let carry = 0
|
|
15
|
+
|
|
16
|
+
input.toUpperCase().split('').forEach(char => {
|
|
17
|
+
const symbol = base32Alphabet[char] & 0xff
|
|
18
|
+
shift -= 5
|
|
19
|
+
if (shift > 0) {
|
|
20
|
+
carry |= symbol << shift
|
|
21
|
+
} else if (shift < 0) {
|
|
22
|
+
buf.push(carry | (symbol >> -shift))
|
|
23
|
+
shift += 8
|
|
24
|
+
carry = (symbol << shift) & 0xff
|
|
25
|
+
} else {
|
|
26
|
+
buf.push(carry | symbol)
|
|
27
|
+
shift = 8
|
|
28
|
+
carry = 0
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (shift !== 8 && carry !== 0) {
|
|
33
|
+
buf.push(carry)
|
|
34
|
+
shift = 8
|
|
35
|
+
carry = 0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return buf
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Draw Stellar address identicon on the given canvas
|
|
43
|
+
* @param {string} address - StrKey-encoded account address
|
|
44
|
+
* @param {number} [size] - Identicon painting area size, in pixels (by default 448)
|
|
45
|
+
* @return {string}
|
|
46
|
+
*/
|
|
47
|
+
export function drawIdenticon(address, size = SIZE) {
|
|
48
|
+
//take 16 meaningful bytes from the raw pub key
|
|
49
|
+
const decoded = decodeBase32(address).slice(2, 16)
|
|
50
|
+
const width = RESOLUTION
|
|
51
|
+
const height = RESOLUTION
|
|
52
|
+
const columns = Math.ceil(width / 2)
|
|
53
|
+
const cellSize = size / width
|
|
54
|
+
const addressBytes = decoded.slice(1)
|
|
55
|
+
const fillStyle = `hsl(${360 * decoded[0] / 256 | 0},58%,52%)`
|
|
56
|
+
const dots = []
|
|
57
|
+
const rsize = ` width="${cellSize}" height="${cellSize}"`
|
|
58
|
+
for (let row = 0; row < height; row++) {
|
|
59
|
+
for (let column = 0; column < columns; column++) {
|
|
60
|
+
const position = column + row * columns
|
|
61
|
+
const bitSet = (addressBytes[position / 8 | 0] & (1 << (7 - position % 8))) !== 0
|
|
62
|
+
if (bitSet) {
|
|
63
|
+
dots.push(`<rect x="${cellSize * column}" y="${cellSize * row}"${rsize}/>`)
|
|
64
|
+
dots.push(`<rect x="${cellSize * (width - column - 1)}" y="${cellSize * row}"${rsize}/>`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" fill="${fillStyle}">${dots.join('')}</svg>`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Renders account-specific identicon
|
|
73
|
+
* @param {string} address - StrKey-encoded account address
|
|
74
|
+
* @param {number} size - Identicon size
|
|
75
|
+
* @constructor
|
|
76
|
+
*/
|
|
77
|
+
export const AccountIdenticon = React.memo(function AccountIdenticon({address, size}) {
|
|
78
|
+
const props = {
|
|
79
|
+
className: 'identicon',
|
|
80
|
+
src: 'data:image/svg+xml;charset=utf-8,' + drawIdenticon(address),
|
|
81
|
+
width: SIZE,
|
|
82
|
+
height: SIZE
|
|
83
|
+
}
|
|
84
|
+
if (size) {
|
|
85
|
+
props.style = {
|
|
86
|
+
width: size,
|
|
87
|
+
height: size
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return <img {...props}/>
|
|
91
91
|
})
|
package/account/signer-key.js
CHANGED
|
@@ -1,65 +1,66 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import {StrKey} from '@stellar/stellar-base'
|
|
3
|
-
import {AccountAddress} from './account-address'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
* @
|
|
8
|
-
* @
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let
|
|
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
|
-
* @param {
|
|
53
|
-
* @param {
|
|
54
|
-
* @param {
|
|
55
|
-
* @
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {StrKey} from '@stellar/stellar-base'
|
|
3
|
+
import {AccountAddress} from './account-address'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Decode a Stellar signer object (XDR or parsed) into a StrKey-encoded key and weight
|
|
7
|
+
* @param {Signer} signer
|
|
8
|
+
* @return {{key: string, weight: number}|null}
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
function decodeSigner(signer) {
|
|
12
|
+
if (!signer)
|
|
13
|
+
return null
|
|
14
|
+
let weight
|
|
15
|
+
let key
|
|
16
|
+
if (typeof signer.arm === 'function') { //raw xdr
|
|
17
|
+
const type = signer.arm()
|
|
18
|
+
weight = signer.weight()
|
|
19
|
+
switch (type) {
|
|
20
|
+
case 'ed25519':
|
|
21
|
+
key = StrKey.encodeEd25519PublicKey(signer.ed25519())
|
|
22
|
+
break
|
|
23
|
+
case 'preAuthTx':
|
|
24
|
+
key = StrKey.encodePreAuthTx(signer.preAuthTx())
|
|
25
|
+
break
|
|
26
|
+
case 'hashX':
|
|
27
|
+
key = StrKey.encodeSha256Hash(signer.hashX())
|
|
28
|
+
break
|
|
29
|
+
case 'ed25519SignedPayload':
|
|
30
|
+
key = StrKey.encodeSignedPayload(signer.ed25519SignedPayload())
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
} else { //parsed
|
|
34
|
+
if (signer.ed25519PublicKey) {
|
|
35
|
+
key = signer.ed25519PublicKey
|
|
36
|
+
} else if (signer.sha256Hash) {
|
|
37
|
+
key = StrKey.encodeSha256Hash(signer.sha256Hash)
|
|
38
|
+
} else if (signer.preAuthTx) {
|
|
39
|
+
key = StrKey.encodePreAuthTx(signer.preAuthTx)
|
|
40
|
+
} else if (signer.ed25519SignedPayload) {
|
|
41
|
+
key = StrKey.encodeSignedPayload(signer.ed25519SignedPayload)
|
|
42
|
+
}
|
|
43
|
+
weight = signer.weight
|
|
44
|
+
}
|
|
45
|
+
if (!key)
|
|
46
|
+
throw new Error('Unknown signer type: ' + JSON.stringify(signer))
|
|
47
|
+
return {key, weight}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Account signer key description
|
|
52
|
+
* @param {Signer} signer - StrKey-encoded Account/MuxedAccount/Sha256Hash/PreAuthTx/SignedPayload
|
|
53
|
+
* @param {string} [name] - Explicit account name that overrides the name from Directory; if false, friendly name is ignored
|
|
54
|
+
* @param {string} [className] - Optional CSS class attribute
|
|
55
|
+
* @param {boolean} [showWeight] - Whether to display weight attribute
|
|
56
|
+
* @constructor
|
|
57
|
+
*/
|
|
58
|
+
export const SignerKey = React.memo(function SignerKey({signer, name, className, showWeight = true}) {
|
|
59
|
+
if (!signer)
|
|
60
|
+
return null
|
|
61
|
+
const {key, weight} = decodeSigner(signer)
|
|
62
|
+
return <span className={className}>
|
|
63
|
+
<AccountAddress account={key} name={name}/>
|
|
64
|
+
{!!showWeight && <span className="dimmed text-tiny"> (weight={weight})</span>}
|
|
65
|
+
</span>
|
|
65
66
|
})
|