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 all 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
2 changes: 1 addition & 1 deletion ui/infrastructure/tx-dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export async function validateNewTx(data) {
return res
}

export async function submitTx(data) {
export async function apiSubmitTx(data) {
//validate and prepare the data
const parsedData = await validateNewTx(data)
//submit ot the server
Expand Down
4 changes: 2 additions & 2 deletions ui/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stellar-expert/refractor-ui",
"private": true,
"version": "0.5.2",
"version": "0.6.1",
"author": "[email protected]",
"description": "Refractor - pending transactions storage and multisig aggregator for Stellar Network",
"scripts": {
Expand All @@ -23,7 +23,7 @@
"@stellar-expert/formatter": "^2.3.0",
"@stellar-expert/navigation": "github:stellar-expert/navigation#v1.0.2",
"@stellar-expert/tx-signers-inspector": "^1.8.1",
"@stellar-expert/ui-framework": "1.10.5",
"@stellar-expert/ui-framework": "1.14.0",
"@stellar/freighter-api": "^2.0.0",
"@stellar/stellar-sdk": "11.3.0",
"classnames": "^2.3.2",
Expand Down
58 changes: 58 additions & 0 deletions ui/views/tx/add-xdr-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, {useCallback, useState} from 'react'
import {Button, Dialog} from '@stellar-expert/ui-framework'
import {apiSubmitTx} from '../../infrastructure/tx-dispatcher'

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)
apiSubmitTx({
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 <Dialog dialogOpen={isOpen}>
<h3>Import transaction signatures</h3>
<textarea value={signedXdr} disabled={inProgress} onChange={changeSignedXdr}
className="text-small text-monospace condensed"
placeholder="Copy-paste base64-encoded transaction XDR here to import transaction signatures (new signatures will be added to the stored transaction)"
style={{
width: '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>
</Dialog>
}
4 changes: 2 additions & 2 deletions ui/views/tx/add/add-tx-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, {useCallback, useState} from 'react'
import isEqual from 'react-fast-compare'
import {Button, Dropdown} from '@stellar-expert/ui-framework'
import {navigation} from '@stellar-expert/navigation'
import {submitTx} from '../../../infrastructure/tx-dispatcher'
import {apiSubmitTx} from '../../../infrastructure/tx-dispatcher'
import config from '../../../app.config.json'

const networkOptions = Object.keys(config.networks)
Expand Down Expand Up @@ -49,7 +49,7 @@ export default function AddTxView() {

const storeTx = useCallback(() => {
setInProgress(true)
return submitTx(data)
return apiSubmitTx(data)
.then(res => {
navigation.navigate(`/tx/${res.hash}`)
})
Expand Down
19 changes: 15 additions & 4 deletions ui/views/tx/details/tx-add-signature-view.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, {useCallback, useState} from 'react'
import {Button, ButtonGroup, withErrorBoundary} from '@stellar-expert/ui-framework'
import {submitTx} from '../../../infrastructure/tx-dispatcher'
import {apiSubmitTx} 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,6 +18,8 @@ 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

Expand All @@ -24,21 +28,28 @@ export default withErrorBoundary(function TxAddSignatureView({txInfo, onUpdate})
<div className="text-small dimmed">Sign transaction</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>)}
<Button block outline disabled={inProgress} onClick={toggleImportModal}>
<i className="icon icon-download"/> Import
</Button>
</ButtonGroup>
</div>
<div className="mobile-only">
{providers.filter(p => !!p.mobileSupported).map(provider =>
<Button key={provider.title} outline block disabled={inProgress} onClick={requestSignature} data-provider={provider.title}>
<img src={`/img/wallets/${provider.title.toLowerCase()}.svg`}/> {provider.title}
</Button>)}
<Button block outline disabled={inProgress} onClick={toggleImportModal}>
<i className="icon icon-download"/> Import
</Button>
</div>
</div>
{!!inProgress && <div className="loader"/>}
<AddXdrView isOpen={isOpen} changeVisible={toggleImportModal} txInfo={txInfo} onUpdate={onUpdate}/>
</div>
})

Expand All @@ -51,7 +62,7 @@ async function processSignature(provider, txInfo) {
throw e
}
try {
return await submitTx({...txInfo, xdr: signedTx})
return await apiSubmitTx({...txInfo, xdr: signedTx})
} catch (e) {
notify({type: 'error', message: 'Failed to store transaction signature. Please repeat the process later.'})
throw e
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
4 changes: 1 addition & 3 deletions ui/views/tx/details/tx-transaction-xdr-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ export default withErrorBoundary(function TxTransactionXDRView({xdr}) {
<span className="dimmed">Raw&nbsp;XDR:&nbsp;</span>
<BlockSelect className="condensed" style={{'overflow': 'hidden', 'whiteSpace': 'nowrap'}}>{xdr}</BlockSelect>
<CopyToClipboard text={xdr}/>
<InfoTooltip>
Base64-encoded Stellar transaction XDR with signatures
</InfoTooltip>
<span><InfoTooltip>Base64-encoded Stellar transaction XDR with signatures</InfoTooltip></span>
</div>
})
17 changes: 8 additions & 9 deletions ui/views/tx/submit/horizon-submit-tx-view.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import React, {useState, useCallback, useEffect} from 'react'
import {Horizon, TransactionBuilder} from '@stellar/stellar-sdk'
import {Button, withErrorBoundary} from '@stellar-expert/ui-framework'
import {checkTxSubmitted} from '../../../infrastructure/tx-dispatcher'
import {checkTxSubmitted, loadTx} 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])
}, [hash, status, submit, submitted])

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 {loadTx, checkTxSubmitted, apiSubmitTx} 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 apiSubmitTx({
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