Skip to content

Commit

Permalink
feat: Support transfer raw transaction to a format for Neuron offline…
Browse files Browse the repository at this point in the history
… sign
  • Loading branch information
yanguoyu committed Jul 25, 2024
1 parent 7d4d2ce commit fe3f421
Show file tree
Hide file tree
Showing 16 changed files with 929 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/neuron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"update:locales": "i18next"
},
"dependencies": {
"@ckb-lumos/lumos": "0.23.0",
"@docsearch/css": "3",
"@docsearch/react": "3",
"@magickbase-website/shared": "workspace:^",
Expand Down
3 changes: 2 additions & 1 deletion packages/neuron/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@
"Status": "Status",
"Sync": "Sync",
"Transaction": "Transaction",
"Usage Tutorial": "Usage Tutorial"
"Usage Tutorial": "Usage Tutorial",
"Tools": "Tools"
}
13 changes: 13 additions & 0 deletions packages/neuron/public/locales/en/tools.json
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"
}
3 changes: 2 additions & 1 deletion packages/neuron/public/locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@
"Status": "状态",
"Sync": "同步",
"Transaction": "交易",
"Usage Tutorial": "使用教程"
"Usage Tutorial": "使用教程",
"Tools": "工具"
}
13 changes: 13 additions & 0 deletions packages/neuron/public/locales/zh/tools.json
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 格式不正确,请检查上传的文件"
}
1 change: 1 addition & 0 deletions packages/neuron/src/components/Page/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const Page = forwardRef<HTMLDivElement, PageProps>(function Page(props, r
{ name: t('Changelog'), link: '/changelog' },
{ name: t('Help Center'), link: '/help-center' },
{ name: t('Download Neuron'), link: '/download' },
{ name: t('Tools'), link: '/tools' },
],
[t],
)
Expand Down
4 changes: 4 additions & 0 deletions packages/neuron/src/pages/tools/download.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions packages/neuron/src/pages/tools/file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
124 changes: 124 additions & 0 deletions packages/neuron/src/pages/tools/index.module.scss
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;
}
}
}
177 changes: 177 additions & 0 deletions packages/neuron/src/pages/tools/index.page.tsx
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
Binary file added packages/neuron/src/pages/tools/neuron-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/neuron/src/pages/tools/refresh.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/neuron/src/pages/tools/tools.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/neuron/src/pages/tools/upload.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit fe3f421

Please sign in to comment.