@stellar-expert/ui-framework 1.16.8 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CLAUDE.md +35 -0
  2. package/README.md +1125 -3
  3. package/account/account-address.js +127 -127
  4. package/account/available-balance.js +22 -22
  5. package/account/identicon.js +90 -90
  6. package/account/signer-key.js +65 -64
  7. package/api/explorer-api-hooks.js +209 -202
  8. package/api/explorer-api-paginated-list-hooks.js +441 -440
  9. package/api/explorer-batch-info-loader.js +111 -85
  10. package/api/explorer-tx-api.js +28 -28
  11. package/api/ledger-stream.js +15 -0
  12. package/asset/amount.js +56 -56
  13. package/asset/asset-icon.js +41 -41
  14. package/asset/asset-issuer.js +21 -21
  15. package/asset/asset-link.js +107 -107
  16. package/asset/asset-list-hooks.js +59 -59
  17. package/asset/asset-meta-hooks.js +88 -88
  18. package/asset/asset-selector.js +72 -71
  19. package/claimable-balance/claimable-balance-claimants.js +23 -18
  20. package/contract/contract-api.js +15 -0
  21. package/contract/invocation-info-view.js +10 -2
  22. package/contract/sc-val.js +131 -107
  23. package/controls/button-group.js +25 -19
  24. package/controls/button.js +93 -78
  25. package/controls/code-block.js +42 -34
  26. package/controls/dropdown.js +318 -287
  27. package/controls/external-link.js +10 -4
  28. package/controls/info-tooltip.js +23 -16
  29. package/controls/slider.js +29 -19
  30. package/controls/tabs.js +94 -94
  31. package/controls/tooltip.js +244 -240
  32. package/controls/update-highlighter.js +32 -27
  33. package/date/date-selector.js +56 -54
  34. package/date/elapsed-time.js +28 -21
  35. package/dex/price-dynamic.js +53 -44
  36. package/directory/directory-hooks.js +109 -97
  37. package/effect/effect-description.js +346 -344
  38. package/errors/error-boundary.js +110 -97
  39. package/horizon/horizon-account-helpers.js +104 -104
  40. package/horizon/horizon-ledger-helpers.js +35 -35
  41. package/horizon/horizon-trades-helper.js +88 -88
  42. package/horizon/horizon-transaction-helpers.js +36 -36
  43. package/index.d.ts +1241 -0
  44. package/interaction/accordion.js +43 -35
  45. package/interaction/autofocus.js +13 -9
  46. package/interaction/block-select.js +64 -53
  47. package/interaction/copy-to-clipboard.js +25 -18
  48. package/interaction/inline-progress.js +20 -15
  49. package/interaction/qr-code.js +34 -34
  50. package/interaction/responsive.js +20 -20
  51. package/interaction/spoiler.js +52 -39
  52. package/interaction/theme-selector.js +13 -10
  53. package/ledger/ledger-entry-href-formatter.js +27 -26
  54. package/ledger/ledger-entry-link.js +93 -58
  55. package/ledger/ledger-info-parser.js +46 -18
  56. package/meta/page-meta-tags.js +243 -238
  57. package/module/dynamic-module.js +47 -47
  58. package/package.json +41 -40
  59. package/state/on-screen-hooks.js +22 -22
  60. package/state/page-visibility-helpers.js +29 -16
  61. package/state/page-visibility-hooks.js +13 -11
  62. package/state/screen-orientation-hooks.js +19 -15
  63. package/state/state-hooks.js +76 -76
  64. package/state/stellar-network-hooks.js +56 -44
  65. package/state/theme.js +29 -28
  66. package/stellar/key-type.js +92 -91
  67. package/stellar/ledger-generic-id.js +39 -39
  68. package/stellar/signature-hint-utils.js +65 -65
  69. package/toast/toast-notifications-block.js +59 -59
  70. package/tx/op-description-view.js +945 -942
  71. package/tx/op-icon.js +98 -98
  72. package/tx/parser/op-balance-changes.js +66 -66
  73. package/tx/parser/op-descriptor.js +51 -48
  74. package/tx/parser/tx-details-parser.js +81 -81
  75. package/tx/parser/tx-matcher.js +371 -371
  76. package/tx/parser/type-filter-matcher.js +126 -126
  77. package/tx/tx-list-hooks.js +32 -18
  78. package/tx/tx-operations-list.js +117 -116
@@ -1,72 +1,73 @@
1
- import React, {useCallback, useRef, useState} from 'react'
2
- import {Dropdown} from '../controls/dropdown'
3
- import {AssetLink} from './asset-link'
4
- import {useAssetList} from './asset-list-hooks'
5
- import './asset-selector.scss'
6
-
7
- /**
8
- * @param {AssetSelectorOnAssetChanged} onChange - On asset selected callback
9
- * @param {String} [value] - Selected asset
10
- * @param {String[]} [predefinedAssets] - Optional lists of predefined assets that should be shown at the top of the dropdown list
11
- * @param {Boolean} [restricted] - If set, the selector is limited to the predefined assets list only
12
- * @param {String} [title] - Dropdown selector title
13
- * @param {String} [expanded] - Expanded by default
14
- * @return {JSX.Element}
15
- * @constructor
16
- */
17
- export function AssetSelector({value, predefinedAssets, onChange, restricted, title, expanded}) {
18
- const [search, setSearch] = useState('')
19
- const searchRef = useRef()
20
- const options = []
21
-
22
- const focusSearch = useCallback(() => {
23
- setTimeout(() => searchRef.current?.focus(), 200)
24
- }, [])
25
-
26
- if (predefinedAssets) {
27
- for (const asset of predefinedAssets) {
28
- options.push({
29
- value: asset,
30
- title: <AssetLink link={false} asset={asset}/>,
31
- hidden: search && !asset.split('-')[0].toLowerCase().includes(search.toLowerCase())
32
- })
33
- }
34
- }
35
-
36
- let loadNextPage
37
- if (!restricted) {
38
- const {assets, loadPage, loading} = useAssetList({search: search?.trim() || undefined})
39
- for (let {asset} of assets) {
40
- if (!predefinedAssets || !predefinedAssets.includes(asset)) {
41
- options.push({value: asset, title: <AssetLink link={false} asset={asset}/>})
42
- }
43
- }
44
- if (!options.filter(opt => !opt.hidden).length) {
45
- if (loading) {
46
- options.push({value: '...', disabled: true, title: <div className="loader"/>})
47
- } else {
48
- options.push({
49
- value: 'no',
50
- disabled: true,
51
- title: <div className="dimmed text-center text-small">(not found)</div>
52
- })
53
- }
54
- }
55
- loadNextPage = loadPage
56
- }
57
-
58
- return <Dropdown solo className="asset-selector" options={options} value={value} onOpen={focusSearch} title={title} expanded={expanded}
59
- showToggle={!title} onChange={onChange} onScroll={e => e.rel === 'bottom' && loadNextPage?.call(this)} header={<>
60
- <h3>Select an asset</h3>
61
- <div className="relative">
62
- <input type="text" value={search} ref={searchRef} onChange={e => setSearch(e.target.value)}
63
- placeholder="Search by asset code or website"/>
64
- <i className="icon-search dimmed"/>
65
- </div>
66
- </>}/>
67
- }
68
-
69
- /**
70
- * @callback AssetSelectorOnAssetChanged
71
- * @param {String} value
1
+ import React, {useCallback, useRef, useState} from 'react'
2
+ import {Dropdown} from '../controls/dropdown'
3
+ import {AssetLink} from './asset-link'
4
+ import {useAssetList} from './asset-list-hooks'
5
+ import './asset-selector.scss'
6
+
7
+ /**
8
+ * Asset picker dropdown with search
9
+ * @param {AssetSelectorOnAssetChanged} onChange - On asset selected callback
10
+ * @param {string} [value] - Selected asset
11
+ * @param {string[]} [predefinedAssets] - Optional lists of predefined assets that should be shown at the top of the dropdown list
12
+ * @param {boolean} [restricted] - If set, the selector is limited to the predefined assets list only
13
+ * @param {string} [title] - Dropdown selector title
14
+ * @param {string} [expanded] - Expanded by default
15
+ * @return {JSX.Element}
16
+ * @constructor
17
+ */
18
+ export function AssetSelector({value, predefinedAssets, onChange, restricted, title, expanded}) {
19
+ const [search, setSearch] = useState('')
20
+ const searchRef = useRef()
21
+ const options = []
22
+
23
+ const focusSearch = useCallback(() => {
24
+ setTimeout(() => searchRef.current?.focus(), 200)
25
+ }, [])
26
+
27
+ if (predefinedAssets) {
28
+ for (const asset of predefinedAssets) {
29
+ options.push({
30
+ value: asset,
31
+ title: <AssetLink link={false} asset={asset}/>,
32
+ hidden: search && !asset.split('-')[0].toLowerCase().includes(search.toLowerCase())
33
+ })
34
+ }
35
+ }
36
+
37
+ let loadNextPage
38
+ if (!restricted) {
39
+ const {assets, loadPage, loading} = useAssetList({search: search?.trim() || undefined})
40
+ for (let {asset} of assets) {
41
+ if (!predefinedAssets || !predefinedAssets.includes(asset)) {
42
+ options.push({value: asset, title: <AssetLink link={false} asset={asset}/>})
43
+ }
44
+ }
45
+ if (!options.filter(opt => !opt.hidden).length) {
46
+ if (loading) {
47
+ options.push({value: '...', disabled: true, title: <div className="loader"/>})
48
+ } else {
49
+ options.push({
50
+ value: 'no',
51
+ disabled: true,
52
+ title: <div className="dimmed text-center text-small">(not found)</div>
53
+ })
54
+ }
55
+ }
56
+ loadNextPage = loadPage
57
+ }
58
+
59
+ return <Dropdown solo className="asset-selector" options={options} value={value} onOpen={focusSearch} title={title} expanded={expanded}
60
+ showToggle={!title} onChange={onChange} onScroll={e => e.rel === 'bottom' && loadNextPage?.call(this)} header={<>
61
+ <h3>Select an asset</h3>
62
+ <div className="relative">
63
+ <input type="text" value={search} ref={searchRef} onChange={e => setSearch(e.target.value)}
64
+ placeholder="Search by asset code or website"/>
65
+ <i className="icon-search dimmed"/>
66
+ </div>
67
+ </>}/>
68
+ }
69
+
70
+ /**
71
+ * @callback AssetSelectorOnAssetChanged
72
+ * @param {string} value
72
73
  */
@@ -1,19 +1,24 @@
1
- import React from 'react'
2
- import {xdrParseClaimant} from '@stellar-expert/claimable-balance-utils'
3
- import {AccountAddress} from '../account/account-address'
4
- import {CodeBlock} from '../controls/code-block'
5
-
6
- export const ClaimableBalanceClaimants = React.memo(function ClaimableBalanceClaimants({claimants}) {
7
- const parsed = claimants.map(c => {
8
- if (c.destination && c.predicate && !c._predicate)
9
- return c
10
- return xdrParseClaimant(c)
11
- })
12
- return <>
13
- {parsed.map((c, i) => <span key={i + c.destination}>{i > 0 && ', '}
14
- <AccountAddress account={c.destination}/> {typeof c.predicate === 'string' ?
15
- c.predicate :
16
- <div className="block-indent"><CodeBlock>{JSON.stringify(c.predicate, null, 2)}</CodeBlock></div>}
17
- </span>)}
18
- </>
1
+ import React from 'react'
2
+ import {xdrParseClaimant} from '@stellar-expert/claimable-balance-utils'
3
+ import {AccountAddress} from '../account/account-address'
4
+ import {CodeBlock} from '../controls/code-block'
5
+
6
+ /**
7
+ * Displays a list of claimable balance claimants with their addresses and predicates
8
+ * @param {Object} props
9
+ * @param {Array<Object>} props.claimants - Array of claimant objects (raw XDR or parsed)
10
+ */
11
+ export const ClaimableBalanceClaimants = React.memo(function ClaimableBalanceClaimants({claimants}) {
12
+ const parsed = claimants.map(c => {
13
+ if (c.destination && c.predicate && !c._predicate)
14
+ return c
15
+ return xdrParseClaimant(c)
16
+ })
17
+ return <>
18
+ {parsed.map((c, i) => <span key={i + c.destination}>{i > 0 && ', '}
19
+ <AccountAddress account={c.destination}/> {typeof c.predicate === 'string' ?
20
+ c.predicate :
21
+ <div className="block-indent"><CodeBlock>{JSON.stringify(c.predicate, null, 2)}</CodeBlock></div>}
22
+ </span>)}
23
+ </>
19
24
  })
@@ -2,6 +2,11 @@ import {useEffect, useState} from 'react'
2
2
  import {useExplorerApi} from '../api/explorer-api-hooks'
3
3
  import {getCurrentStellarNetwork} from '../state/stellar-network-hooks'
4
4
 
5
+ /**
6
+ * React hook that fetches contract information from the Explorer API
7
+ * @param {string} address - Contract address
8
+ * @return {ExplorerApiResult}
9
+ */
5
10
  export function useContractInfo(address) {
6
11
  return useExplorerApi('contract/' + address, {
7
12
  processResult(data) {
@@ -14,10 +19,20 @@ export function useContractInfo(address) {
14
19
  })
15
20
  }
16
21
 
22
+ /**
23
+ * Generate a URL to download the WASM source for a contract
24
+ * @param {string} hash - Contract WASM hash
25
+ * @return {string} Full URL to the contract WASM source
26
+ */
17
27
  export function generateContractSourceLink(hash) {
18
28
  return `${explorerApiOrigin}/explorer/${getCurrentStellarNetwork()}/contract/wasm/${hash}`
19
29
  }
20
30
 
31
+ /**
32
+ * React hook that fetches contract WASM binary source by hash
33
+ * @param {string} hash - Contract WASM hash
34
+ * @return {ArrayBuffer|null|undefined} WASM binary (undefined while loading, null if not found)
35
+ */
21
36
  export function useContractSource(hash) {
22
37
  const [source, setSource] = useState()
23
38
  useEffect(() => {
@@ -8,6 +8,14 @@ import {parseScValValue, primitiveTypes, ScVal, ScValStruct} from './sc-val'
8
8
  import {useContractSource} from './contract-api'
9
9
  import {sacInterface} from './sac-interface'
10
10
 
11
+ /**
12
+ * Basic Soroban contract invocation info
13
+ * @param {string} [contract] - Contract address
14
+ * @param {string} func - Invoked function name
15
+ * @param {Array|string} args - Function arguments (ScVal array or XDR string)
16
+ * @param {string|xdr.ScVal} [result] - Invocation result
17
+ * @param {string} [sac] - Stellar Asset Contract identifier
18
+ */
11
19
  export default function InvocationInfoView({contract, func, args, result, sac}) {
12
20
  if (typeof args === 'string') {
13
21
  args = parseScValValue(args)
@@ -45,13 +53,13 @@ const ExtendedInvocationInfoView = React.memo(function ({contract, func, args, r
45
53
  const fd = meta.functions[func]
46
54
  if (!fd)
47
55
  return <div className="error"><i className="icon-warning-circle"/> Failed to parse contract code</div>
48
- const shouldIndent = args.length > 2 || Object.values(fd.inputs).some(i => !primitiveTypes.has(i.type)) // multi-line display for large argument number or complex types
56
+ const shouldIndent = args.length > 2 || fd.inputs.some(i => !primitiveTypes.has(i.type)) // multi-line display for large argument number or complex types
49
57
  const contractInfo = sac ?
50
58
  <> from asset <AssetLink asset={sac}/></> :
51
59
  <span className="text-tiny dimmed">
52
60
  &emsp;SDK v{meta.sdkVersion.split('#')[0]}&emsp;RUST v{shortenString(meta.rustVersion, 12)}
53
61
  </span>
54
- const contractArgNames = Object.keys(fd.inputs)
62
+ const contractArgNames = fd.inputs.map(i => i.name)
55
63
  //TODO: show contract validation info
56
64
  return <div>
57
65
  <div>
@@ -1,108 +1,132 @@
1
- import React from 'react'
2
- import cn from 'classnames'
3
- import {xdr, scValToBigInt} from '@stellar/stellar-base'
4
- import {xdrParserUtils} from '@stellar-expert/tx-meta-effects-parser'
5
- import {shortenString} from '@stellar-expert/formatter'
6
- import {AccountAddress} from '../account/account-address'
7
- import './sc-val.scss'
8
-
9
- export const ScVal = React.memo(function ScVal({value, nested = false, indent = false, wrapObjects = true}) {
10
- if (!nested)
11
- return <code className={cn('sc-val', {block: indent})}><ScVal value={value} indent={indent} nested/></code>
12
- if (!value)
13
- return 'void'
14
- if (typeof value === 'string') {
15
- value = xdr.ScVal.fromXDR(value, 'base64')
16
- }
17
- if (value instanceof Array) {
18
- const values = value.map((v, i) => <ScValStruct key={i} indent={indent} separate={value.length - i}>
19
- <ScVal value={v} indent={indent} nested/>
20
- </ScValStruct>)
21
- return wrapObjects ? <>[{values}]</> : <>{values}</>
22
- }
23
- const val = value._value
24
- switch (value._arm) {
25
- case 'vec':
26
- return <>[{val.map((v, i) => <ScValStruct key={i} indent={indent} separate={val.length - i}>
27
- <ScVal value={v} indent={indent} nested/>
28
- </ScValStruct>)}]</>
29
- case 'map':
30
- const values = val.map((kv, i) =>
31
- <ScValStruct key={i} indent={indent} separate={val.length - i}>
32
- <ScVal value={kv.key()} indent={indent} nested/>: <ScVal value={kv.val()} indent={indent} nested/>
33
- </ScValStruct>)
34
- return wrapObjects ? <>&#123;{values}&#125;</> : <>{values}</>
35
- case 'b':
36
- return <>{val.toString()}<ScValType type="bool"/></>
37
- case 'i32':
38
- case 'u32':
39
- return <>{val}<ScValType type={value._arm}/></>
40
- case 'i256':
41
- case 'u256':
42
- case 'i128':
43
- case 'u128':
44
- case 'i64':
45
- case 'u64':
46
- case 'timepoint':
47
- case 'duration':
48
- return <>{scValToBigInt(value).toString()}<ScValType type={value._arm}/></>
49
- case 'address':
50
- switch (val._arm) {
51
- case 'accountId':
52
- return <AccountAddress account={xdrParserUtils.xdrParseAccountAddress(val.value())}/>
53
- case 'contractId':
54
- return <AccountAddress account={xdrParserUtils.xdrParseContractAddress(val.value())}/>
55
- case 'muxedAccount':
56
- return <AccountAddress account={xdrParserUtils.xdrParseMuxedScAddress(val.value())}/>
57
- }
58
- return <span className="dimmed">(unsupported address)</span>
59
- case 'bytes':
60
- const asBytes = val.toString('base64')
61
- return <><span className="condensed">{shortenString(asBytes, 86)}</span><ScValType type="bytes"/></>
62
- case 'str':
63
- case 'sym':
64
- return <span className="word-break">"{val.toString()}"<ScValType type={value._arm}/></span>
65
- case 'nonceKey':
66
- return <>{val.nonce()._value.toString()}<ScValType type="nonce"/></>
67
- case 'instance':
68
- if (val._attributes.executable._switch.name==="contractExecutableStellarAsset")
69
- return <span>StellarAsset<ScValType type="instance"/></span>
70
- return <span className="word-break">{val._attributes.executable.wasmHash().toString('hex')}<ScValType type="wasm"/></span>
71
- case 'error':
72
- const errMessage = value.toXDR('base64')
73
- return <><span className="condensed" title={errMessage}>{shortenString(errMessage, 50)}</span><ScValType type="error"/></>
74
- case 'contractId':
75
- return <AccountAddress account={xdrParserUtils.xdrParseContractAddress(value._value)}/>
76
- default:
77
- switch (value._switch.name) {
78
- case 'scvVoid':
79
- return '()'
80
- case 'scvContractInstance':
81
- return '<ContractInstance>'
82
- case 'scvLedgerKeyContractInstance':
83
- return '<LedgerKeyContractInstance>'
84
- case 'scvLedgerKeyNonce':
85
- return '<LedgerKeyNonce>'
86
- }
87
- return <span className="dimmed">(unknown)</span>
88
- }
89
- })
90
-
91
- export function parseScValValue(value) {
92
- return xdr.ScVal.fromXDR(value, 'base64')
93
- }
94
-
95
- const ScValType = React.memo(function ScValType({type}) {
96
- return <sub className="dimmed text-tiny" style={{padding: '0 0.2em'}}>{type}</sub>
97
- })
98
-
99
- export const ScValStruct = React.memo(function ScValStruct({indent, children, separate}) {
100
- const separator = separate > 1 ? <>, </> : null
101
- if (!indent)
102
- return <>{children}{separator}</>
103
- return <div className="block-indent">
104
- {children}{separator}
105
- </div>
106
- })
107
-
1
+ import React from 'react'
2
+ import cn from 'classnames'
3
+ import {xdr, scValToBigInt} from '@stellar/stellar-base'
4
+ import {xdrParserUtils} from '@stellar-expert/tx-meta-effects-parser'
5
+ import {shortenString} from '@stellar-expert/formatter'
6
+ import {AccountAddress} from '../account/account-address'
7
+ import './sc-val.scss'
8
+
9
+ /**
10
+ * Renders a Stellar smart contract value (ScVal) in a human-readable format
11
+ * @param {Object} props
12
+ * @param {string|xdr.ScVal|Array} props.value - ScVal as base64 XDR string, parsed XDR object, or array
13
+ * @param {boolean} [props.nested=false] - Whether this is a nested rendering (internal use)
14
+ * @param {boolean} [props.indent=false] - Enable block-level indentation for nested structures
15
+ * @param {boolean} [props.wrapObjects=true] - Wrap maps and arrays with brackets
16
+ */
17
+ export const ScVal = React.memo(function ScVal({value, nested = false, indent = false, wrapObjects = true}) {
18
+ if (!nested)
19
+ return <code className={cn('sc-val', {block: indent})}><ScVal value={value} indent={indent} nested/></code>
20
+ if (!value)
21
+ return 'void'
22
+ if (typeof value === 'string') {
23
+ value = xdr.ScVal.fromXDR(value, 'base64')
24
+ }
25
+ if (value instanceof Array) {
26
+ const values = value.map((v, i) => <ScValStruct key={i} indent={indent} separate={value.length - i}>
27
+ <ScVal value={v} indent={indent} nested/>
28
+ </ScValStruct>)
29
+ return wrapObjects ? <>[{values}]</> : <>{values}</>
30
+ }
31
+ const val = value._value
32
+ switch (value._arm) {
33
+ case 'vec':
34
+ return <>[{val.map((v, i) => <ScValStruct key={i} indent={indent} separate={val.length - i}>
35
+ <ScVal value={v} indent={indent} nested/>
36
+ </ScValStruct>)}]</>
37
+ case 'map':
38
+ const values = val.map((kv, i) =>
39
+ <ScValStruct key={i} indent={indent} separate={val.length - i}>
40
+ <ScVal value={kv.key()} indent={indent} nested/>: <ScVal value={kv.val()} indent={indent} nested/>
41
+ </ScValStruct>)
42
+ return wrapObjects ? <>&#123;{values}&#125;</> : <>{values}</>
43
+ case 'b':
44
+ return <>{val.toString()}<ScValType type="bool"/></>
45
+ case 'i32':
46
+ case 'u32':
47
+ return <>{val}<ScValType type={value._arm}/></>
48
+ case 'i256':
49
+ case 'u256':
50
+ case 'i128':
51
+ case 'u128':
52
+ case 'i64':
53
+ case 'u64':
54
+ case 'timepoint':
55
+ case 'duration':
56
+ return <>{scValToBigInt(value).toString()}<ScValType type={value._arm}/></>
57
+ case 'address':
58
+ switch (val._arm) {
59
+ case 'accountId':
60
+ return <AccountAddress account={xdrParserUtils.xdrParseAccountAddress(val.value())}/>
61
+ case 'contractId':
62
+ return <AccountAddress account={xdrParserUtils.xdrParseContractAddress(val.value())}/>
63
+ case 'muxedAccount':
64
+ return <AccountAddress account={xdrParserUtils.xdrParseMuxedScAddress(val.value())}/>
65
+ }
66
+ return <span className="dimmed">(unsupported address)</span>
67
+ case 'bytes':
68
+ const asBytes = val.toString('base64')
69
+ return <><span className="condensed">{shortenString(asBytes, 86)}</span><ScValType type="bytes"/></>
70
+ case 'str':
71
+ case 'sym':
72
+ return <span className="word-break">"{val.toString()}"<ScValType type={value._arm}/></span>
73
+ case 'nonceKey':
74
+ return <>{val.nonce()._value.toString()}<ScValType type="nonce"/></>
75
+ case 'instance':
76
+ if (val._attributes.executable._switch.name==="contractExecutableStellarAsset")
77
+ return <span>StellarAsset<ScValType type="instance"/></span>
78
+ return <span className="word-break">{val._attributes.executable.wasmHash().toString('hex')}<ScValType type="wasm"/></span>
79
+ case 'error':
80
+ const errMessage = value.toXDR('base64')
81
+ return <><span className="condensed" title={errMessage}>{shortenString(errMessage, 50)}</span><ScValType type="error"/></>
82
+ case 'contractId':
83
+ return <AccountAddress account={xdrParserUtils.xdrParseContractAddress(value._value)}/>
84
+ default:
85
+ switch (value._switch.name) {
86
+ case 'scvVoid':
87
+ return '()'
88
+ case 'scvContractInstance':
89
+ return '<ContractInstance>'
90
+ case 'scvLedgerKeyContractInstance':
91
+ return '<LedgerKeyContractInstance>'
92
+ case 'scvLedgerKeyNonce':
93
+ return '<LedgerKeyNonce>'
94
+ }
95
+ return <span className="dimmed">(unknown)</span>
96
+ }
97
+ })
98
+
99
+ /**
100
+ * Parse a base64-encoded XDR ScVal string into an xdr.ScVal object
101
+ * @param {string} value - Base64-encoded XDR string
102
+ * @return {xdr.ScVal}
103
+ */
104
+ export function parseScValValue(value) {
105
+ return xdr.ScVal.fromXDR(value, 'base64')
106
+ }
107
+
108
+ const ScValType = React.memo(function ScValType({type}) {
109
+ return <sub className="dimmed text-tiny" style={{padding: '0 0.2em'}}>{type}</sub>
110
+ })
111
+
112
+ /**
113
+ * Structural wrapper for ScVal elements, handling indentation and separators
114
+ * @param {Object} props
115
+ * @param {boolean} [props.indent] - Enable block-level indentation
116
+ * @param {*} props.children - Nested content
117
+ * @param {number} [props.separate] - Show comma separator when > 1
118
+ */
119
+ export const ScValStruct = React.memo(function ScValStruct({indent, children, separate}) {
120
+ const separator = separate > 1 ? <>, </> : null
121
+ if (!indent)
122
+ return <>{children}{separator}</>
123
+ return <div className="block-indent">
124
+ {children}{separator}
125
+ </div>
126
+ })
127
+
128
+ /**
129
+ * Set of primitive ScVal type identifiers
130
+ * @type {Set<string>}
131
+ */
108
132
  export const primitiveTypes = new Set(['b', 'i32', 'u32', 'i256', 'u256', 'i128', 'u128', 'i64', 'u64', 'timepoint', 'duration', 'address', 'bytes', 'str', 'sym', 'nonceKey', 'contractId', 'instance'])
@@ -1,20 +1,26 @@
1
- import React from 'react'
2
- import PropTypes from 'prop-types'
3
- import './button-group.scss'
4
-
5
- export const ButtonGroup = React.memo(function ButtonGroup({inline, children, ...otherProps}) {
6
- if (inline)
7
- return <span className="button-group" {...otherProps}>{children}</span>
8
- return <div className="button-group" {...otherProps}>{children}</div>
9
- })
10
-
11
- ButtonGroup.propTypes = {
12
- /**
13
- * Nested buttons
14
- */
15
- children: PropTypes.any.isRequired,
16
- /**
17
- * Whether to render the group as inline element (span)
18
- */
19
- inline: PropTypes.bool
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import './button-group.scss'
4
+
5
+ /**
6
+ * Groups multiple buttons together with consistent spacing
7
+ * @param {Object} props
8
+ * @param {boolean} [props.inline] - Render as inline element (`<span>`) instead of block (`<div>`)
9
+ * @param {*} props.children - Nested buttons
10
+ */
11
+ export const ButtonGroup = React.memo(function ButtonGroup({inline, children, ...otherProps}) {
12
+ if (inline)
13
+ return <span className="button-group" {...otherProps}>{children}</span>
14
+ return <div className="button-group" {...otherProps}>{children}</div>
15
+ })
16
+
17
+ ButtonGroup.propTypes = {
18
+ /**
19
+ * Nested buttons
20
+ */
21
+ children: PropTypes.any.isRequired,
22
+ /**
23
+ * Whether to render the group as inline element (span)
24
+ */
25
+ inline: PropTypes.bool
20
26
  }