-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Support transfer raw transaction to a format for Neuron offline…
… sign
- Loading branch information
Showing
16 changed files
with
929 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"Tools": "Tools", | ||
"Raw_Transaction_Conversion": "Raw Transaction Conversion", | ||
"Raw_Transaction_Conversion_Tip": "Since Neuron may run offline, the raw transaction requires extra information to meet Neuron signature requirements. This tool will help you process raw transactions into transactions that Neuron can sign.", | ||
"Click_Or_Drag_To_Upload": "Please click or drag in to upload the raw transaction JSON file", | ||
"Mainnet": "Mainnet", | ||
"Testnet": "Testnet", | ||
"Process": "Process", | ||
"Processing": "Processing", | ||
"Transaction_Complete": "Transaction complete", | ||
"Download": "Download", | ||
"Incorrect_JSON": "JSON format is incorrect, Please check the upload file" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"Tools": "工具", | ||
"Raw_Transaction_Conversion": "原始交易转换", | ||
"Raw_Transaction_Conversion_Tip": "由于 Neuron 可能离线运行,原始交易需要额外信息以满足 Neuron 签名要求。此工具将帮助您将原始交易处理为 Neuron 可以签名的交易。", | ||
"Click_Or_Drag_To_Upload": "请点击或拖动以上传原始交易 JSON 文件", | ||
"Mainnet": "主网", | ||
"Testnet": "测试网", | ||
"Process": "转换", | ||
"Processing": "处理中", | ||
"Transaction_Complete": "转换完成", | ||
"Download": "下载", | ||
"Incorrect_JSON": "JSON 格式不正确,请检查上传的文件" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
.page { | ||
width: 100%; | ||
|
||
.head { | ||
display: flex; | ||
justify-content: space-between; | ||
margin-top: 42px; | ||
color: #f5f5f5; | ||
font-weight: 700; | ||
font-size: 40px; | ||
|
||
.logo { | ||
display: flex; | ||
gap: 12px; | ||
align-items: flex-end; | ||
margin-top: 22px; | ||
margin-bottom: 24px; | ||
color: #f5f5f5; | ||
font-weight: 600; | ||
font-size: 26px; | ||
} | ||
} | ||
|
||
.body { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 16px; | ||
align-items: flex-start; | ||
justify-content: center; | ||
margin-bottom: 144px; | ||
padding: 24px; | ||
background: linear-gradient(180deg, rgb(54 54 54 / 40%) 0%, rgb(29 29 29 / 20%) 100%); | ||
border: 1px solid rgb(255 255 255 / 20%); | ||
border-radius: 24px; | ||
backdrop-filter: blur(40px); | ||
|
||
.upload { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
align-self: stretch; | ||
justify-content: center; | ||
height: 136px; | ||
padding: 16px; | ||
border: 1px solid rgb(255 255 255 / 20%); | ||
border-radius: 8px; | ||
|
||
input { | ||
height: 0; | ||
visibility: hidden; | ||
} | ||
|
||
.file { | ||
display: flex; | ||
gap: 8px; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
} | ||
|
||
.node { | ||
display: flex; | ||
gap: 54px; | ||
align-items: center; | ||
margin: 8px 0; | ||
|
||
& > label { | ||
display: inline-flex; | ||
align-items: center; | ||
margin-right: 54px; | ||
} | ||
|
||
input { | ||
position: relative; | ||
width: 16px; | ||
height: 16px; | ||
margin: 0; | ||
margin-right: 8px; | ||
appearance: none; | ||
|
||
&::before { | ||
display: inline-block; | ||
width: 14px; | ||
height: 14px; | ||
border: 1px solid #ccc; | ||
border-radius: 14px; | ||
content: ' '; | ||
} | ||
|
||
&:checked { | ||
&::before { | ||
border-color: var(--btnBackground); | ||
} | ||
|
||
&::after { | ||
position: absolute; | ||
top: 4px; | ||
left: 4px; | ||
width: 8px; | ||
height: 8px; | ||
background-color: var(--btnBackground); | ||
border-radius: 8px; | ||
content: ' '; | ||
} | ||
} | ||
} | ||
} | ||
|
||
.err { | ||
color: #f62a2a; | ||
font-weight: 400; | ||
} | ||
|
||
.success { | ||
color: var(--btnBackground); | ||
font-weight: 400; | ||
} | ||
|
||
.process { | ||
width: 144px; | ||
margin: 8px 0; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import { GetStaticProps, type NextPage } from 'next' | ||
import { useTranslation } from 'next-i18next' | ||
import Image from 'next/image' | ||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations' | ||
import { ChangeEventHandler, DragEventHandler, useCallback, useState } from 'react' | ||
import ImgNeuronLogo from './neuron-logo.png' | ||
import ToolsIcon from './tools.png' | ||
import UploadSvg from './upload.svg' | ||
import FileSvg from './file.svg' | ||
import RefreshSvg from './refresh.svg' | ||
import DownloadSvg from './download.svg' | ||
import { Page } from '../../components/Page' | ||
import styles from './index.module.scss' | ||
import { Button } from '../../components/Button' | ||
import exportTxToSign, { JSONFormatError } from '../../utils/export-tx-to-sign' | ||
|
||
interface PageProps {} | ||
|
||
const Download: NextPage<PageProps> = () => { | ||
const { t } = useTranslation('tools') | ||
const [selectedNodeType, setSelectedNodeType] = useState('mainnet') | ||
const [isProcess, setIsProcess] = useState(false) | ||
const [processFile, setProcessFile] = useState<File | undefined>() | ||
const [processedJSON, setProcessedJSON] = useState<object | undefined>() | ||
const [err, setErr] = useState<string | undefined>() | ||
const onDrag: DragEventHandler<HTMLLabelElement> = useCallback( | ||
e => { | ||
e.preventDefault() | ||
let file: File | null | undefined | ||
if (e.dataTransfer.items) { | ||
const firstJsonFile = [...e.dataTransfer.items].find(item => { | ||
if (item.kind === 'file') { | ||
const file = item.getAsFile() | ||
return file?.type === 'application/json' | ||
} | ||
}) | ||
file = firstJsonFile?.getAsFile() | ||
} else if (e.dataTransfer.files) { | ||
file = [...e.dataTransfer.files].find(file => { | ||
return file?.type === 'application/json' | ||
}) | ||
} | ||
if (file) { | ||
setProcessFile(file) | ||
setProcessedJSON(undefined) | ||
setErr(undefined) | ||
} | ||
}, | ||
[setProcessFile], | ||
) | ||
const onChooseFile: ChangeEventHandler<HTMLInputElement> = useCallback(e => { | ||
const chooseFile = e.target.files?.item(0) | ||
if (chooseFile) { | ||
setProcessFile(chooseFile) | ||
} | ||
}, []) | ||
const onSelectNodeType: ChangeEventHandler<HTMLInputElement> = useCallback(e => { | ||
if (e.target.checked) { | ||
setSelectedNodeType(e.target.value) | ||
setProcessedJSON(undefined) | ||
setErr(undefined) | ||
} | ||
}, []) | ||
const onProcessFile = useCallback(() => { | ||
if (processFile) { | ||
setErr(undefined) | ||
setIsProcess(true) | ||
processFile | ||
.text() | ||
.then(res => | ||
exportTxToSign({ | ||
tx: JSON.parse(res), | ||
nodeType: selectedNodeType, | ||
}), | ||
) | ||
.then(res => { | ||
setProcessedJSON(res) | ||
}) | ||
.catch((err: Error) => { | ||
if (err instanceof JSONFormatError) { | ||
setErr(t('Incorrect_JSON')!) | ||
} else { | ||
setErr(err.toString()) | ||
} | ||
}) | ||
.finally(() => { | ||
setIsProcess(false) | ||
}) | ||
} | ||
return undefined | ||
}, [processFile, selectedNodeType, t]) | ||
const onDownload = useCallback(() => { | ||
if (!processedJSON || !processFile) return | ||
const blob = new Blob([JSON.stringify(processedJSON, undefined, 2)]) | ||
const filename = `${processFile.name.split('.')[0]}_new.json` | ||
const url = window.URL.createObjectURL(blob) | ||
const link = document.createElement('a') | ||
link.style.display = 'none' | ||
link.href = url | ||
link.setAttribute('download', filename) | ||
document.body.appendChild(link) | ||
link.click() | ||
URL.revokeObjectURL(url) | ||
document.body.removeChild(link) | ||
}, [processFile, processedJSON]) | ||
return ( | ||
<Page className={styles.page}> | ||
<div className={styles.head}> | ||
<div> | ||
<div className={styles.logo}> | ||
<Image src={ImgNeuronLogo} alt="Neuron Logo" width={44} height={44} /> | ||
Neuron | ||
</div> | ||
<div>{t('Tools')}</div> | ||
</div> | ||
<Image src={ToolsIcon} alt="Tools" width={230} height={230} /> | ||
</div> | ||
<div className={styles.body}> | ||
<div>{t('Raw_Transaction_Conversion')}</div> | ||
<div>{t('Raw_Transaction_Conversion_Tip')}</div> | ||
<label className={styles.upload} onDrop={onDrag} onDragOver={onDrag}> | ||
<input type="file" accept=".json" onChange={onChooseFile} /> | ||
{processFile ? ( | ||
<div className={styles.file}> | ||
<FileSvg /> | ||
{processFile.name} | ||
</div> | ||
) : ( | ||
<> | ||
<UploadSvg /> | ||
{t('Click_Or_Drag_To_Upload')} | ||
</> | ||
)} | ||
</label> | ||
<div className={styles.node}> | ||
<label> | ||
<input type="radio" value="mainnet" checked={selectedNodeType === 'mainnet'} onChange={onSelectNodeType} /> | ||
{t('Mainnet')} | ||
</label> | ||
<label> | ||
<input type="radio" value="testnet" checked={selectedNodeType === 'testnet'} onChange={onSelectNodeType} /> | ||
{t('Testnet')} | ||
</label> | ||
</div> | ||
{err ? <div className={styles.err}>{err}</div> : undefined} | ||
{processedJSON ? <div className={styles.success}>{t('Transaction_Complete')}</div> : null} | ||
<Button | ||
className={styles.process} | ||
disabled={!processFile || isProcess} | ||
onClick={processedJSON ? onDownload : onProcessFile} | ||
> | ||
{processedJSON ? ( | ||
<> | ||
{t('Download')} | ||
<DownloadSvg /> | ||
</> | ||
) : ( | ||
t(isProcess ? 'Processing' : 'Process') | ||
)} | ||
{isProcess ? <RefreshSvg /> : null} | ||
</Button> | ||
</div> | ||
</Page> | ||
) | ||
} | ||
|
||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => { | ||
const lng = await serverSideTranslations(locale, ['common', 'tools']) | ||
|
||
const props: PageProps = { | ||
...lng, | ||
} | ||
|
||
return { props } | ||
} | ||
|
||
export default Download |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.