Skip to content

Commit

Permalink
feat: support sending spore
Browse files Browse the repository at this point in the history
  • Loading branch information
homura committed Sep 21, 2023
1 parent 3d34fb0 commit e36dab9
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 40 deletions.
27 changes: 23 additions & 4 deletions packages/neuron-ui/src/components/NFTSend/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React, { useState, useCallback, useReducer, useMemo, useRef, useEffect } from 'react'
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useState as useGlobalState, useDispatch, AppActions } from 'states'
import { AppActions, useDispatch, useState as useGlobalState } from 'states'
import { isMainnet as isMainnetUtil, isSuccessResponse, validateAddress } from 'utils'
import useGetCountDownAndFeeRateStats from 'utils/hooks/useGetCountDownAndFeeRateStats'
import TextField from 'widgets/TextField'
import Dialog from 'widgets/Dialog'
import { generateNFTSendTransaction } from 'services/remote'
import { isErrorWithI18n } from 'exceptions'
import styles from './NFTSend.module.scss'
import { NFTType } from '../SpecialAssetList'
import { generateSporeSendTransaction } from '../../services/remote/spore'

enum Fields {
Address = 'address',
Expand Down Expand Up @@ -38,7 +40,9 @@ const NFTSend = ({
onCancel,
cell,
onSuccess,
nftType = NFTType.NFT,
}: {
nftType?: NFTType
onCancel: () => void
cell: {
nftId: string
Expand Down Expand Up @@ -138,7 +142,17 @@ const NFTSend = ({
feeRate: `${suggestFeeRate}`,
}

generateNFTSendTransaction(params)
const generate = (() => {
switch (nftType) {
case NFTType.Spore: {
return generateSporeSendTransaction
}
default:
return generateNFTSendTransaction
}
})()

generate(params)
.then(res => {
if (isSuccessResponse(res)) {
globalDispatch({
Expand All @@ -156,10 +170,15 @@ const NFTSend = ({
return clearTimer
}, [isSubmittable, globalDispatch, sendState, walletId, outPoint, suggestFeeRate])

const displayNftType = (() => {
if (nftType === NFTType.Spore) return 'Spore'
return 'mNFT'
})()

return (
<Dialog
show
title={`${t('special-assets.transfer-nft')} #${nftId} mNFT`}
title={`${t('special-assets.transfer-nft')} #${nftId} ${displayNftType}`}
disabled={!isSubmittable}
onCancel={onCancel}
onConfirm={onSubmit}
Expand Down
2 changes: 2 additions & 0 deletions packages/neuron-ui/src/components/SpecialAssetList/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ export const useSpecialAssetColumnInfo = ({
}
case NFTType.Spore: {
if (type) {
// every spore cell is transferable
status = 'transfer-nft'
sporeClusterInfo = JSON.parse(item.customizedAssetInfo.data)
amount = sporeFormatter({ args: type.args, data: item.data, clusterName: sporeClusterInfo?.name })
}
Expand Down
57 changes: 26 additions & 31 deletions packages/neuron-ui/src/components/SpecialAssetList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const SpecialAssetList = () => {
const [isExistAccountDialogOpen, setIsExistAccountDialogOpen] = useState<boolean>(false)
const [nFTSendCell, setNFTSendCell] = useState<
| {
nftType?: NFTType
nftId: string
outPoint: {
index: string
Expand Down Expand Up @@ -285,6 +286,16 @@ const SpecialAssetList = () => {
})
return
}
if (cell.customizedAssetInfo.type === 'Spore') {
setNFTSendCell({
// unnecessary id for the spore
nftId: cell.type?.args ?? '',
outPoint: cell.outPoint,
nftType: NFTType.Spore,
})
return
}

const handleRes =
(actionType: 'unlock' | 'withdraw-cheque' | 'claim-cheque') => (res: ControllerResponse<any>) => {
if (isSuccessResponse(res)) {
Expand Down Expand Up @@ -394,16 +405,14 @@ const SpecialAssetList = () => {
const { amount, isSpore, sporeClusterInfo } = handleGetSpecialAssetColumnInfo(item)

if (isSpore && item.type) {
const formattedSporeInfo = sporeFormatter({
args: item.type.args,
data: item.data,
truncate: Infinity,
clusterName: sporeClusterInfo?.name,
})
return (
<CopyZone
content={sporeFormatter({
args: item.type.args,
data: item.data,
truncate: Infinity,
clusterName: sporeClusterInfo?.name,
})}
title={sporeClusterInfo?.description}
>
<CopyZone content={formattedSporeInfo} title={sporeClusterInfo?.description}>
{amount}
</CopyZone>
)
Expand All @@ -422,15 +431,8 @@ const SpecialAssetList = () => {
customizedAssetInfo,
} = item

const {
status,
targetTime,
isLockedCheque,
isNFTTransferable,
isNFTClassOrIssuer,
epochsInfo,
isSpore,
} = handleGetSpecialAssetColumnInfo(item)
const { status, targetTime, isLockedCheque, isNFTTransferable, isNFTClassOrIssuer, epochsInfo } =
handleGetSpecialAssetColumnInfo(item)

if (isNFTClassOrIssuer || (customizedAssetInfo.type === NFTType.NFT && !isNFTTransferable)) {
return (
Expand All @@ -444,18 +446,6 @@ const SpecialAssetList = () => {
)
}

if (isSpore) {
return (
<div className={styles.actionBtnBox}>
<Button
label={t('special-assets.view-details')}
className={`${styles.actionBtn} ${styles.detailBtn}`}
onClick={() => onViewDetail(item)}
/>
</div>
)
}

let tip = ''

const showTip =
Expand Down Expand Up @@ -580,7 +570,12 @@ const SpecialAssetList = () => {
) : null}

{nFTSendCell ? (
<NFTSend cell={nFTSendCell} onCancel={() => setNFTSendCell(undefined)} onSuccess={handleActionSuccess} />
<NFTSend
nftType={nFTSendCell.nftType}
cell={nFTSendCell}
onCancel={() => setNFTSendCell(undefined)}
onSuccess={handleActionSuccess}
/>
) : null}

<Toast content={notice} onDismiss={() => setNotice('')} />
Expand Down
2 changes: 2 additions & 0 deletions packages/neuron-ui/src/services/remote/remoteApiWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ type Action =
| 'get-hold-sudt-cell-capacity'
| 'start-migrate'
| 'get-sync-progress-by-addresses'
// spore
| 'generate-transfer-spore-tx'

export const remoteApi =
<P = any, R = any>(action: Action) =>
Expand Down
7 changes: 7 additions & 0 deletions packages/neuron-ui/src/services/remote/spore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { remoteApi } from './remoteApiWrapper'

// eslint-disable-next-line import/prefer-default-export
export const generateSporeSendTransaction = remoteApi<
Controller.CreateNFTSendTransaction.Params,
Controller.CreateNFTSendTransaction.Response
>('generate-transfer-spore-tx')
4 changes: 4 additions & 0 deletions packages/neuron-wallet/src/controllers/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,10 @@ export default class ApiController {
return this.#customizedAssetsController.generateTransferNftTx(params)
})

handle('generate-transfer-spore-tx', async (_, params: Controller.Params.GenerateTransferNftTxParams) => {
return this.#customizedAssetsController.generateTransferSporeTx(params)
})

// Networks

handle('get-all-networks', async () => {
Expand Down
23 changes: 23 additions & 0 deletions packages/neuron-wallet/src/controllers/customized-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,29 @@ export default class CustomizedAssetsController {
}
}

public async generateTransferSporeTx(params: Controller.Params.GenerateTransferNftTxParams) {
const tx = await new TransactionSender().generateTransferSporeTx(
params.walletID,
params.outPoint,
params.receiveAddress,
undefined,
params.feeRate
)

if (!tx) {
throw new ServiceHasNoResponse('GenerateTransferNftTx')
}

if (params.description) {
tx.description = params.description
}

return {
status: ResponseCode.Success,
result: tx,
}
}

public async generateWithdrawCustomizedCellTx(
params: Controller.Params.GenerateWithdrawCustomizedCellTxParams
): Promise<Controller.Response<Transaction>> {
Expand Down
50 changes: 48 additions & 2 deletions packages/neuron-wallet/src/services/transaction-sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import signWitnesses from '@nervosnetwork/ckb-sdk-core/lib/signWitnesses'
import NodeService from './node'
import { serializeWitnessArgs } from '../utils/serialization'
import { scriptToAddress } from '../utils/scriptAndAddress'
import { TransactionPersistor, TransactionGenerator, TargetOutput } from './tx'
import { TargetOutput, TransactionGenerator, TransactionPersistor } from './tx'
import AddressService from './addresses'
import WalletService, { Wallet } from '../services/wallets'
import RpcService from '../services/rpc-service'
Expand All @@ -28,10 +28,10 @@ import HardwareWalletService from './hardware'
import {
CapacityNotEnoughForChange,
CapacityNotEnoughForChangeByTransfer,
CellIsNotYetLive,
MultisigConfigNeedError,
NoMatchAddressForSign,
SignTransactionFailed,
CellIsNotYetLive,
TransactionIsNotCommittedYet,
} from '../exceptions'
import AssetAccountInfo from '../models/asset-account-info'
Expand All @@ -45,6 +45,8 @@ import { generateRPC } from '../utils/ckb-rpc'
import CKB from '@nervosnetwork/ckb-sdk-core'
import CellsService from './cells'
import hd from '@ckb-lumos/hd'
import { getClusterCellByOutPoint } from '@spore-sdk/core'
import CellDep, { DepType } from '../models/chain/cell-dep'

interface SignInfo {
witnessArgs: WitnessArgs
Expand Down Expand Up @@ -557,6 +559,50 @@ export default class TransactionSender {
return tx
}

public generateTransferSporeTx = async (
walletId: string,
outPoint: OutPoint,
receiveAddress: string,
fee: string = '0',
feeRate: string = '0'
): Promise<Transaction> => {
const changeAddress: string = await this.getChangeAddress()
const nftCellOutput = await CellsService.getLiveCell(new OutPoint(outPoint.txHash, outPoint.index))
if (!nftCellOutput) {
throw new CellIsNotYetLive()
}

const assetAccountInfo = new AssetAccountInfo()
const rpcUrl: string = NodeService.getInstance().nodeUrl

// https://github.com/sporeprotocol/spore-sdk/blob/05f2cbe1c03d03e334ebd3b440b5b3b20ec67da7/packages/core/src/api/joints/spore.ts#L154-L158
const clusterDep = await (async () => {
const clusterCell = await getClusterCellByOutPoint(outPoint, assetAccountInfo.getSporeConfig(rpcUrl)).then(
_ => _,
() => undefined
)

if (!clusterCell?.outPoint) {
return undefined
}

return new CellDep(OutPoint.fromSDK(clusterCell.outPoint), DepType.Code)
})()

const tx = await TransactionGenerator.generateTransferNftTx(
walletId,
outPoint,
nftCellOutput,
receiveAddress,
changeAddress,
fee,
feeRate,
[assetAccountInfo.getSporeInfos()[0].cellDep].concat(clusterDep ?? [])
)

return tx
}

public generateDepositTx = async (
walletID: string = '',
capacity: string,
Expand Down
27 changes: 24 additions & 3 deletions packages/neuron-wallet/src/services/tx/transaction-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,30 @@ export class TransactionGenerator {
receiveAddress: string,
changeAddress: string,
fee: string = '0',
feeRate: string = '0'
feeRate: string = '0',
nftDeps?: CellDep[]
) => {
// defaults to the mNFT cell dep
const assetAccount = new AssetAccountInfo()
const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep()
const nftCellDep = assetAccount.getNftInfo().cellDep
nftDeps = nftDeps ?? [nftCellDep]

const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep()
const op = new OutPoint(outPoint.txHash, outPoint.index)
const nftCell = await CellsService.getLiveCell(op)

// the data has been sliced, so we need to get the full data from the rpc
// https://github.com/nervosnetwork/neuron/blob/dbc5a5b46dc108f660c443d43aba54ea47e233ac/packages/neuron-wallet/src/services/tx/transaction-persistor.ts#L70
await (async () => {
if (!nftCell) return

const nftTx = await this.getRpcService().getTransaction(outPoint.txHash)
const nftOriginalOutputData = nftTx?.transaction.outputsData[Number(outPoint.index)]
if (!nftOriginalOutputData) return

nftCell.data = nftOriginalOutputData
})()

const receiverLockScript = AddressParser.parse(receiveAddress)
const assetAccountInfo = new AssetAccountInfo()
const anyoneCanPayDep = assetAccountInfo.anyoneCanPayCellDep
Expand Down Expand Up @@ -92,7 +109,7 @@ export class TransactionGenerator {
const outputs: Output[] = [nftCell]
const tx = Transaction.fromObject({
version: '0',
cellDeps: [secpCellDep, nftCellDep, anyoneCanPayDep],
cellDeps: [secpCellDep, anyoneCanPayDep, ...nftDeps],
headerDeps: [],
inputs: [nftInput],
outputs,
Expand Down Expand Up @@ -346,6 +363,10 @@ export class TransactionGenerator {
return tipHeader
}

private static getRpcService(): RpcService {
return new RpcService(NodeService.getInstance().nodeUrl)
}

public static generateDepositTx = async (
walletId: string,
capacity: string,
Expand Down

0 comments on commit e36dab9

Please sign in to comment.