-
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.
- Loading branch information
Showing
11 changed files
with
263 additions
and
6 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
81 changes: 81 additions & 0 deletions
81
packages/neuron/src/pages/tools/RecycleCells/generate_tx.ts
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,81 @@ | ||
import { Indexer } from '@ckb-lumos/ckb-indexer' | ||
import { BI, helpers, Script, config, Cell, BIish } from '@ckb-lumos/lumos' | ||
import { sealTransaction } from '@ckb-lumos/lumos/helpers' | ||
import { CKB_MAINNET_NODE_URL, CKB_TESTNET_NODE_URL } from '../../../utils/constants' | ||
|
||
export async function generateTxToRecycleSUDTCells(holder: string, sudtArgs: string, receiver: string) { | ||
const IS_MAINNET = holder.startsWith('ckb') | ||
const ENDPOINT = IS_MAINNET ? CKB_MAINNET_NODE_URL : CKB_TESTNET_NODE_URL | ||
const indexer = new Indexer(ENDPOINT) | ||
|
||
const CONFIG = IS_MAINNET ? config.predefined.LINA : config.predefined.AGGRON4 | ||
|
||
const SUDT_CONFIG = CONFIG.SCRIPTS.SUDT | ||
const SUDT_CELL_DEP = { | ||
outPoint: { txHash: SUDT_CONFIG.TX_HASH, index: SUDT_CONFIG.INDEX }, | ||
depType: SUDT_CONFIG.DEP_TYPE, | ||
} | ||
|
||
const ANYONE_CAN_PAY_CONFIG = CONFIG.SCRIPTS.ANYONE_CAN_PAY | ||
const ANYONE_CAN_PAY_DEP = { | ||
outPoint: { txHash: ANYONE_CAN_PAY_CONFIG.TX_HASH, index: ANYONE_CAN_PAY_CONFIG.INDEX }, | ||
depType: ANYONE_CAN_PAY_CONFIG.DEP_TYPE, | ||
} | ||
|
||
const lock: Script = helpers.parseAddress(holder, { config: CONFIG }) | ||
const type: Script = { | ||
codeHash: SUDT_CONFIG.CODE_HASH, | ||
hashType: SUDT_CONFIG.HASH_TYPE, | ||
args: sudtArgs, | ||
} | ||
|
||
const receiverLock = helpers.parseAddress(receiver, { config: CONFIG }) | ||
|
||
const cells: Array<Cell> = [] | ||
|
||
const collector = indexer.collector({ type, lock }) | ||
|
||
for await (const cell of collector.collect()) { | ||
cells.push(cell) | ||
} | ||
if (!cells.length) { | ||
throw new Error('No cells found') | ||
} | ||
|
||
const totalCKB = cells.reduce((acc, cur) => BI.from(cur.cellOutput.capacity).add(acc), BI.from(0)) | ||
|
||
let txSkeleton = helpers.TransactionSkeleton() | ||
txSkeleton = txSkeleton.update('cellDeps', deps => deps.push(SUDT_CELL_DEP, ANYONE_CAN_PAY_DEP)) | ||
|
||
txSkeleton = txSkeleton = txSkeleton = txSkeleton.update('inputs', inputs => inputs.push(...cells)) | ||
|
||
const SIZE = 340 + 52 * cells.length // precomputed | ||
const fee = calculateFee(SIZE) | ||
|
||
const total = totalCKB.sub(fee).toHexString() | ||
|
||
txSkeleton = txSkeleton = txSkeleton.update('outputs', outputs => { | ||
const receiverCell: Cell = { | ||
cellOutput: { | ||
capacity: total, | ||
lock: receiverLock, | ||
}, | ||
data: '0x', | ||
} | ||
return outputs.push(receiverCell) | ||
}) | ||
|
||
const tx = sealTransaction(txSkeleton, []) | ||
return { tx, total } | ||
} | ||
|
||
function calculateFee(size: number, feeRate: BIish = 1000): BI { | ||
const ratio = BI.from(1000) | ||
const base = BI.from(size).mul(feeRate) | ||
const fee = base.div(ratio) | ||
|
||
if (fee.mul(ratio).lt(base)) { | ||
return fee.add(1) | ||
} | ||
return BI.from(fee) | ||
} |
55 changes: 55 additions & 0 deletions
55
packages/neuron/src/pages/tools/RecycleCells/index.module.scss
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,55 @@ | ||
.container { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 16px; | ||
align-items: start; | ||
justify-content: center; | ||
width: 100%; | ||
|
||
fieldset { | ||
display: flex; | ||
align-items: center; | ||
width: 100%; | ||
padding: 0; | ||
border: none; | ||
appearance: none; | ||
|
||
label { | ||
flex-basis: 240px; | ||
} | ||
} | ||
|
||
input { | ||
width: 100%; | ||
height: 2em; | ||
padding: 16px 8px; | ||
color: #fff; | ||
background: transparent; | ||
border: 1px solid rgb(255 255 255 / 20%); | ||
border-radius: 8px; | ||
} | ||
|
||
.overview { | ||
height: 1rem; | ||
} | ||
|
||
.err { | ||
color: #f62a2a; | ||
font-weight: 400; | ||
} | ||
|
||
.loading { | ||
width: 1rem; | ||
height: 1rem; | ||
border: #000 2px solid; | ||
border-top-color: transparent; | ||
border-radius: 50%; | ||
animation: spin 1s infinite linear; | ||
} | ||
|
||
@keyframes spin { | ||
100% { | ||
transform: rotate(1turn); | ||
} | ||
} | ||
} |
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,90 @@ | ||
import { useTranslation } from 'next-i18next' | ||
import { useState } from 'react' | ||
import { BI } from '@ckb-lumos/lumos' | ||
import { Button } from '../../../components/Button' | ||
import exportTxToSign from '../../../utils/export-tx-to-sign' | ||
import { generateTxToRecycleSUDTCells } from './generate_tx' | ||
import { downloadFile } from '../../../utils' | ||
import styles from './index.module.scss' | ||
|
||
export const RecycleCells = () => { | ||
const { t } = useTranslation('tools') | ||
const [isLoading, setIsLoading] = useState(false) | ||
const [overview, setOverview] = useState('') | ||
const [err, setErr] = useState('') | ||
|
||
const handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => { | ||
e.stopPropagation() | ||
e.preventDefault() | ||
setIsLoading(true) | ||
setOverview('') | ||
setErr('') | ||
|
||
const { holder: holderElm, sudt: sudtElm, receiver: receiverElm } = e.currentTarget | ||
|
||
if ( | ||
!(holderElm instanceof HTMLInputElement) || | ||
!(sudtElm instanceof HTMLInputElement) || | ||
!(receiverElm instanceof HTMLInputElement) | ||
) { | ||
return | ||
} | ||
|
||
const handle = async () => { | ||
try { | ||
const holder = holderElm.value | ||
const sudtArgs = sudtElm.value | ||
const receiver = receiverElm.value | ||
const { tx, total } = await generateTxToRecycleSUDTCells(holder, sudtArgs, receiver) | ||
setOverview( | ||
t('Generate_TX_to_recycle_sudt_cells', { | ||
total: BI.from(total) | ||
.div(10 ** 8) | ||
.toString(), | ||
cellCount: tx.inputs.length, | ||
}).toString(), | ||
) | ||
|
||
const nodeType = holder.startsWith('ckb') ? 'mainnet' : 'testnet' | ||
|
||
const formatedTx = await exportTxToSign({ json: tx, nodeType }) | ||
|
||
const blob = new Blob([JSON.stringify(formatedTx, undefined, 2)]) | ||
const filename = `tx_to_recycle_cells.json` | ||
downloadFile(blob, filename) | ||
} catch (e) { | ||
if (e instanceof Error) { | ||
setErr(e.message) | ||
} | ||
} finally { | ||
setIsLoading(false) | ||
} | ||
} | ||
void handle() | ||
} | ||
|
||
return ( | ||
<form className={styles.container} onSubmit={handleSubmit}> | ||
<div id="recycle_cells">{t('Recycle_SUDT_Cells')}</div> | ||
<div>{t('Recycle_SUDT_Cells_Tip')}</div> | ||
<fieldset> | ||
<label htmlFor="holder">{t('Holder_Address')}</label> | ||
<input id="holder" placeholder={t('Holder_Address')!} /> | ||
</fieldset> | ||
<fieldset> | ||
<label htmlFor="sudt">{t('Args_of_SUDT')}</label> | ||
<input id="sudt" placeholder={t('Args_of_SUDT')!} /> | ||
</fieldset> | ||
<fieldset> | ||
<label htmlFor="receiver">{t('Receiver_Address')}</label> | ||
<input id="receiver" placeholder={t('Receiver_Address')!} /> | ||
</fieldset> | ||
{err ? <div className={styles.err}>{err}</div> : <div className={styles.overview}>{overview}</div>} | ||
<Button style={{ width: 144 }} disabled={isLoading} type="submit"> | ||
{isLoading ? <div className={styles.loading} /> : t('Generate')} | ||
</Button> | ||
</form> | ||
) | ||
} | ||
|
||
export default RecycleCells |
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,11 @@ | ||
export const downloadFile = (blob: Blob | MediaSource, filename: string) => { | ||
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) | ||
} |
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,2 @@ | ||
export const CKB_MAINNET_NODE_URL = 'https://mainnet.ckb.dev/rpc' | ||
export const CKB_TESTNET_NODE_URL = 'https://testnet.ckb.dev/rpc' |
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