Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add signed XDR importer #8

Merged
merged 9 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions ui/views/component/dialog-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
orbitlens marked this conversation as resolved.
Show resolved Hide resolved

export default function DialogView({dialogOpen, children}) {
if (!dialogOpen) return null
return <div className="dialog">
<div className="dialog-backdrop"/>
<div className="dialog-content container">
<div className="row row-center v-center-block">
<div className="column column-50">
<div className="segment blank">
<div className="dialog-body">
{children}
</div>
</div>
</div>
</div>
</div>
</div>
}
42 changes: 42 additions & 0 deletions ui/views/styles/dialog-container.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.dialog {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;

> .dialog-backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--color-modal-bg);
backdrop-filter: blur(2px);
}

> .dialog-content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 100%;

> .container {
background: var(--color-bg);
border: 1px solid var(--color-contrast-border);
border-radius: $border-radius-input;
box-shadow: 0 2px 4px -1px var(--color-modal-bg);
padding: 0 $space-standard $space-standard;
}

.dialog-body {
overflow-x: hidden;
overflow-y: auto;
max-height: 90vh;
margin: 0 -1.5em;
padding: 0 1.5em;
}
}
}
1 change: 1 addition & 0 deletions ui/views/styles/styles.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import '~@stellar-expert/ui-framework/index.scss';
@import "page-layout";
@import "dialog-container";
60 changes: 60 additions & 0 deletions ui/views/tx/add-xdr-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, {useCallback, useState} from 'react'
import {Button} from '@stellar-expert/ui-framework'
import {submitTx} from '../../infrastructure/tx-dispatcher'
import DialogView from '../component/dialog-view'

export default function AddXdrView({isOpen, changeVisible, txInfo, onUpdate}) {
const [inProgress, setInProgress] = useState(false)
const [signedXdr, setSignedXdr] = useState('')

const changeSignedXdr = useCallback(e => {
const val = e.target.value.trim()
setSignedXdr(val)
}, [])

const importXdr = useCallback(() => {
setInProgress(true)
submitTx({
network: txInfo.network,
xdr: signedXdr
})
.then(txInfo => {
setSignedXdr('')
changeVisible(false)
onUpdate(txInfo)
})
.catch(e => {
setInProgress(false)
notify({type: 'error', message: e.message})
})
}, [txInfo, signedXdr])

return <DialogView dialogOpen={isOpen}>
<h3>Transaction XDR</h3>
orbitlens marked this conversation as resolved.
Show resolved Hide resolved
<textarea value={signedXdr} disabled={inProgress} onChange={changeSignedXdr}
className="text-small text-monospace condensed"
placeholder="Base64-encoded transaction envelope"
orbitlens marked this conversation as resolved.
Show resolved Hide resolved
style={{
width: '100%',
height: '100%',
minHeight: '8rem',
maxHeight: '32rem',
display: 'block',
resize: 'vertical'
}}/>
<div className="space row">
<div className="column column-50">
{!!inProgress && <>
<div className="loader inline"/>
<span className="dimmed text-small"> In progress...</span>
</>}
</div>
<div className="column column-25">
<Button block outline disabled={inProgress} onClick={changeVisible}>Cancel</Button>
</div>
<div className="column column-25">
<Button block disabled={inProgress} onClick={importXdr}>Import</Button>
</div>
</div>
</DialogView>
}
16 changes: 13 additions & 3 deletions ui/views/tx/details/tx-add-signature-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import React, {useCallback, useState} from 'react'
import {Button, ButtonGroup, withErrorBoundary} from '@stellar-expert/ui-framework'
import {submitTx} from '../../../infrastructure/tx-dispatcher'
import {delegateTxSigning, getAllProviders} from '../../../signer/tx-signer'
import AddXdrView from '../add-xdr-view'
import './add-signatures.scss'

export default withErrorBoundary(function TxAddSignatureView({txInfo, onUpdate}) {
const [inProgress, setInProgress] = useState(false)
const [isOpen, setIsOpen] = useState(false)

const requestSignature = useCallback(e => {
const {provider} = e.target.dataset
Expand All @@ -16,17 +18,24 @@ export default withErrorBoundary(function TxAddSignatureView({txInfo, onUpdate})
.finally(() => setInProgress(false))
}, [txInfo, onUpdate])

const toggleImportModal = useCallback(() => setIsOpen(prev => !prev), [])

if (txInfo.readyToSubmit || txInfo.submitted)
return null

const providers = getAllProviders()
return <div className="space">
<div className="text-small dimmed">Sign transaction</div>
<div className="dual-layout">
<div className="text-small dimmed">Sign transaction</div>
<a href="#" className="text-small" onClick={toggleImportModal}>
<i className="icon icon-feather"/> Import signed tx
</a>
</div>
<div className="signature-options micro-space">
<div className="desktop-only">
<ButtonGroup>
<ButtonGroup style={{display: 'flex'}}>
{providers.map(provider =>
<Button key={provider.title} outline disabled={inProgress} onClick={requestSignature} data-provider={provider.title}>
<Button key={provider.title} block outline disabled={inProgress} onClick={requestSignature} data-provider={provider.title}>
<img src={`/img/wallets/${provider.title.toLowerCase()}.svg`}/> {provider.title}
</Button>)}
</ButtonGroup>
Expand All @@ -39,6 +48,7 @@ export default withErrorBoundary(function TxAddSignatureView({txInfo, onUpdate})
</div>
</div>
{!!inProgress && <div className="loader"/>}
<AddXdrView isOpen={isOpen} changeVisible={toggleImportModal} txInfo={txInfo} onUpdate={onUpdate}/>
</div>
})

Expand Down
28 changes: 15 additions & 13 deletions ui/views/tx/details/tx-signatures-view.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useMemo} from 'react'
import React, {useEffect, useMemo} from 'react'
import {BlockSelect, UpdateHighlighter, withErrorBoundary} from '@stellar-expert/ui-framework'
import {shortenString} from '@stellar-expert/formatter'

Expand Down Expand Up @@ -26,25 +26,27 @@ function TxStoreResult({changes}) { //TODO: refactor
return null
const {accepted, rejected} = changes

accepted?.forEach(s => {
const signer = shortenString(s.key, 12)
notify({
type: 'success',
message: <span key={s.signature}>
useEffect(() => {
accepted?.forEach(s => {
const signer = shortenString(s.key, 12)
notify({
type: 'success',
message: <span key={s.signature}>
Signature from {signer} accepted
</span>
})
})
})

rejected?.forEach(s => {
const signer = s.key.replace(/_+/g, '...')
notify({
type: 'error',
message: <span key={s.signature}>
rejected?.forEach(s => {
const signer = s.key.replace(/_+/g, '...')
notify({
type: 'error',
message: <span key={s.signature}>
Signature from {signer} rejected
</span>
})
})
})
}, [accepted, rejected])

return null
}
Expand Down
15 changes: 7 additions & 8 deletions ui/views/tx/submit/horizon-submit-tx-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ import {checkTxSubmitted} from '../../../infrastructure/tx-dispatcher'
import config from '../../../app.config.json'
import {horizonErrorHandler} from './horizon-error-handler'

export default withErrorBoundary(function HorizonSubmitTxView({txInfo}) {
export default withErrorBoundary(function HorizonSubmitTxView({txInfo, onUpdate}) {
const {readyToSubmit, hash, submit, submitted, xdr, status, network, error} = txInfo
const [inProgress, setInProgress] = useState(false)
const [isExist, setIsExist] = useState(true)

useEffect(() => {
if (!txInfo.submit && !txInfo.submitted) {
if (!submit && !submitted) {
//check existence of transaction in Horizon
checkTxSubmitted(txInfo)
.then(tx => {
setIsExist(!!tx.submitted)
})
.then(tx => setIsExist(!!tx.submitted))
}
}, [txInfo])
}, [submit, submitted])
orbitlens marked this conversation as resolved.
Show resolved Hide resolved

const submitTx = useCallback(() => {
const {passphrase, horizon} = config.networks[network]
Expand All @@ -27,8 +25,9 @@ export default withErrorBoundary(function HorizonSubmitTxView({txInfo}) {

setInProgress(true)
server.submitTransaction(tx)
.then(() => {
window.location.reload()
.then(async () => {
orbitlens marked this conversation as resolved.
Show resolved Hide resolved
const submittedTx = await loadTx(hash)
onUpdate(submittedTx)
})
.catch(e => {
if (e.response.data) {
Expand Down
34 changes: 23 additions & 11 deletions ui/views/tx/tx-view.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {useCallback, useEffect, useRef, useState} from 'react'
import {useParams} from 'react-router'
import {BlockSelect, CopyToClipboard, isDocumentVisible, useDependantState, withErrorBoundary} from '@stellar-expert/ui-framework'
import {loadTx} from '../../infrastructure/tx-dispatcher'
import {checkTxSubmitted, loadTx, submitTx} from '../../infrastructure/tx-dispatcher'
import TxDetailsOperationsView from './details/tx-details-operations-view'
import TxTransactionXDRView from './details/tx-transaction-xdr-view'
import TxSignaturesView from './details/tx-signatures-view'
Expand All @@ -19,7 +19,20 @@ export default withErrorBoundary(function TxView() {
const [txInfo, setTxInfo] = useDependantState(() => {
setError('')
loadTx(txhash)
.then(txInfo => setTxInfo(txInfo))
.then(async txInfo => {
const horizonTx = await checkTxSubmitted(txInfo)
if (!horizonTx.submitted)
return setTxInfo(txInfo)
//Send transaction to the server
await submitTx({
orbitlens marked this conversation as resolved.
Show resolved Hide resolved
network: horizonTx.network,
xdr: horizonTx.xdr
})
.then(async () => {
const txInfo = await loadTx(txhash)
setTxInfo(txInfo)
})
})
.catch(e => setError(e))
return null
}, [txhash], () => clearTimeout(statusWatcher.current))
Expand All @@ -45,18 +58,17 @@ export default withErrorBoundary(function TxView() {
}, [txInfo, loadPeriodicallyTx])

useEffect(() => {
if (txInfo && !txInfo.submitted) {
statusWatcher.current = setTimeout(() => {
checkStatus()
}, statusRefreshInterval * 1000)
if (!txInfo?.submitted) {
statusWatcher.current = setTimeout(checkStatus, statusRefreshInterval * 1000)

//check active tab
document.addEventListener('visibilitychange', checkStatus)
return () => {
document.removeEventListener('visibilitychange', checkStatus)
}
}
}, [txInfo, checkStatus])
return () => {
clearTimeout(statusWatcher.current)
document.removeEventListener('visibilitychange', checkStatus)
}
}, [txInfo?.status, checkStatus])

const updateTx = useCallback(txInfo => setTxInfo(txInfo), [setTxInfo])

Expand Down Expand Up @@ -107,7 +119,7 @@ export default withErrorBoundary(function TxView() {
<h3>Status</h3>
<hr/>
<div className="space">
<HorizonSubmitTxView txInfo={txInfo}/>
<HorizonSubmitTxView txInfo={txInfo} onUpdate={updateTx}/>
</div>
<TxAddSignatureView txInfo={txInfo} onUpdate={updateTx}/>
</div>
Expand Down