diff --git a/bin/use-env.sh b/bin/use-env.sh index c794482..4cb22b6 100755 --- a/bin/use-env.sh +++ b/bin/use-env.sh @@ -25,6 +25,7 @@ esac JOYID_INFO_FILE="$(ls migrations/joyid/*.json | grep -v deployment | head -n 1)" OMNILOCK_INFO_FILE="$(ls migrations/omnilock/*.json | grep -v deployment | head -n 1)" +DAO_ACTION_VERIFIER_INFO_FILE="$(ls migrations/dao-action-verifier/*.json | grep -v deployment | head -n 1)" sed -n \ -e 's/,$//' \ @@ -45,3 +46,13 @@ sed -n \ -e 's/,$//' \ -e 's/^ *"tx_hash": /NEXT_PUBLIC_OMNILOCK_TX_HASH=/p' \ "$OMNILOCK_INFO_FILE" | tail -1 + +sed -n \ + -e 's/,$//' \ + -e 's/^ *"type_id": "/NEXT_PUBLIC_DAO_ACTION_VERIFIER_CODE_HASH="/p' \ + "$DAO_ACTION_VERIFIER_INFO_FILE" | head -1 + +sed -n \ + -e 's/,$//' \ + -e 's/^ *"tx_hash": /NEXT_PUBLIC_DAO_ACTION_VERIFIER_TX_HASH=/p' \ + "$DAO_ACTION_VERIFIER_INFO_FILE" | tail -1 diff --git a/docs/packing-verifier.md b/docs/packing-verifier.md new file mode 100644 index 0000000..a8150a2 --- /dev/null +++ b/docs/packing-verifier.md @@ -0,0 +1 @@ +# Packing Verifier diff --git a/src/actions/claim.js b/src/actions/claim.js index 5e12b02..01a10f7 100644 --- a/src/actions/claim.js +++ b/src/actions/claim.js @@ -4,12 +4,16 @@ import { claimDao } from "@/lib/cobuild/publishers"; import { getConfig } from "@/lib/config"; import { prepareLockActions } from "@/lib/cobuild/lock-actions"; import { payFee } from "@/lib/cobuild/fee-manager"; +import { prepareVerifier } from "@/lib/papps/dao/verifier"; -export default async function withdraw(from, cell, config) { +export default async function withdraw(from, cell, shouldPackVerifier, config) { config = config ?? getConfig(); try { let buildingPacket = await claimDao(config)({ cell }); + if (shouldPackVerifier) { + buildingPacket = await prepareVerifier(buildingPacket, from, config); + } buildingPacket = await payFee( buildingPacket, [{ address: from, feeRate: 1200 }], diff --git a/src/actions/deposit.js b/src/actions/deposit.js index 19341bc..b7f8a47 100644 --- a/src/actions/deposit.js +++ b/src/actions/deposit.js @@ -1,18 +1,22 @@ "use server"; -import { parseUnit } from "@ckb-lumos/bi"; import { depositDao } from "@/lib/cobuild/publishers"; import { getConfig } from "@/lib/config"; import { prepareLockActions } from "@/lib/cobuild/lock-actions"; import { payFee } from "@/lib/cobuild/fee-manager"; +import { prepareVerifier } from "@/lib/papps/dao/verifier"; export default async function deposit(_prevState, formData, config) { config = config ?? getConfig(); const from = formData.get("from"); + const shouldPackVerifier = formData.get("packVerifier") !== undefined; try { let buildingPacket = await depositDao(config)(formData); + if (shouldPackVerifier) { + buildingPacket = await prepareVerifier(buildingPacket, from, config); + } buildingPacket = await payFee( buildingPacket, [{ address: from, feeRate: 1200 }], diff --git a/src/actions/get-verifier-cells.js b/src/actions/get-verifier-cells.js new file mode 100644 index 0000000..38d200c --- /dev/null +++ b/src/actions/get-verifier-cells.js @@ -0,0 +1,29 @@ +"use server"; + +import { cache } from "react"; +import { getConfig, buildScript } from "@/lib/config"; +import { addressToScript } from "@ckb-lumos/helpers"; +import { Indexer } from "@ckb-lumos/ckb-indexer"; + +function buildVerifierScript(ckbChainConfig) { + return buildScript(ckbChainConfig.SCRIPTS.DAO_ACTION_VERIFIER, "0x"); +} + +export async function getVerifierCellsWithoutCache(lockAddress, config) { + const { ckbRpcUrl, ckbChainConfig } = config ?? getConfig(); + const indexer = new Indexer(ckbRpcUrl); + + const lock = addressToScript(lockAddress, { config: ckbChainConfig }); + const collector = indexer.collector({ + lock, + argsLen: (lock.args.length - 2) / 2, + type: buildVerifierScript(ckbChainConfig), + }); + const verifierCells = []; + for await (const cell of collector.collect()) { + verifierCells.push(cell); + } + return verifierCells; +} + +export const getVerifierCellsWithCache = cache(getVerifierCellsWithoutCache); diff --git a/src/actions/reclaim.js b/src/actions/reclaim.js new file mode 100644 index 0000000..9cbd559 --- /dev/null +++ b/src/actions/reclaim.js @@ -0,0 +1,29 @@ +"use server"; + +import { reclaimDaoVerifiers } from "@/lib/cobuild/publishers"; +import { getConfig } from "@/lib/config"; +import { prepareLockActions } from "@/lib/cobuild/lock-actions"; +import { payFee } from "@/lib/cobuild/fee-manager"; + +export default async function reclaim(from, config) { + config = config ?? getConfig(); + + try { + let buildingPacket = await reclaimDaoVerifiers(config)({ from }); + buildingPacket = await payFee( + buildingPacket, + [{ address: from, feeRate: 1200 }], + config, + ); + buildingPacket = prepareLockActions(buildingPacket, config.ckbChainConfig); + + return { + buildingPacket, + }; + } catch (err) { + console.error(err.stack); + return { + error: err.toString(), + }; + } +} diff --git a/src/actions/withdraw.js b/src/actions/withdraw.js index bb2db8c..68ae07d 100644 --- a/src/actions/withdraw.js +++ b/src/actions/withdraw.js @@ -4,12 +4,16 @@ import { withdrawDao } from "@/lib/cobuild/publishers"; import { getConfig } from "@/lib/config"; import { prepareLockActions } from "@/lib/cobuild/lock-actions"; import { payFee } from "@/lib/cobuild/fee-manager"; +import { prepareVerifier } from "@/lib/papps/dao/verifier"; -export default async function withdraw(from, cell, config) { +export default async function withdraw(from, cell, shouldPackVerifier, config) { config = config ?? getConfig(); try { let buildingPacket = await withdrawDao(config)({ cell }); + if (shouldPackVerifier) { + buildingPacket = await prepareVerifier(buildingPacket, from, config); + } buildingPacket = await payFee( buildingPacket, [{ address: from, feeRate: 1200 }], diff --git a/src/app/u/[wallet]/[connection]/assets.js b/src/app/u/[wallet]/[connection]/assets.js index 9190655..f4e4868 100644 --- a/src/app/u/[wallet]/[connection]/assets.js +++ b/src/app/u/[wallet]/[connection]/assets.js @@ -1,7 +1,10 @@ -import Link from "next/link"; +import { BI, formatUnit } from "@ckb-lumos/bi"; import { Button } from "flowbite-react"; -import Capacity from "@/components/capacity"; +import Link from "next/link"; + import { fetchAssetsWithCache } from "@/actions/fetch-assets"; +import Capacity from "@/components/capacity"; +import PackingVerifierHelpText from "@/components/packing-verifier-help-text"; import DaoCells from "./dao-cells"; import Loading from "./loading"; @@ -25,7 +28,38 @@ export function CkbSection({ wallet, connection, address, ckbBalance }) { ); } -export function DaoSection({ wallet, connection, address, daoCells }) { +export function VerifierCells({ wallet, connection, verifierCells }) { + let lockedCapacity = BI.from(0); + for (const cell of verifierCells) { + lockedCapacity = lockedCapacity.add(BI.from(cell.cellOutput.capacity)); + } + return ( + <> +

Verifier Cells

+ +

+ You have {formatUnit(lockedCapacity, "ckb")} CKB locked in DAO action + verifier cells. You can reclaim them by destroy the verifier cells. +

+ + + ); +} + +export function DaoSection({ + wallet, + connection, + address, + daoCells, + verifierCells, +}) { return (

DAO

@@ -44,6 +78,13 @@ export function DaoSection({ wallet, connection, address, daoCells }) { address={address} daoCells={daoCells} /> + {verifierCells.length > 0 ? ( + + ) : null}
); } @@ -64,13 +105,18 @@ export function AssetsFallback() { } export default async function Assets({ wallet, connection, address }) { - const { ckbBalance, daoCells } = await fetchAssetsWithCache(address); + const { ckbBalance, daoCells, verifierCells } = + await fetchAssetsWithCache(address); const childProps = { wallet, connection, address }; return ( <> - + ); } diff --git a/src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/form.js b/src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/form.js index 918eeda..1aef221 100644 --- a/src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/form.js +++ b/src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/form.js @@ -1,20 +1,21 @@ "use client"; -import { useState } from "react"; +import { Alert, Button, Checkbox, Label } from "flowbite-react"; import { useRouter } from "next/navigation"; -import { Alert, Button } from "flowbite-react"; +import { useState } from "react"; import claim from "@/actions/claim"; -import useTipHeader from "@/hooks/use-tip-header"; +import PackingVerifierHelpText from "@/components/packing-verifier-help-text"; +import useCell from "@/hooks/use-cell"; import useHeader from "@/hooks/use-header"; import useHeaderByNumber from "@/hooks/use-header-by-number"; -import useCell from "@/hooks/use-cell"; +import useTipHeader from "@/hooks/use-tip-header"; import Capacity from "@/components/capacity"; import * as dao from "@/lib/dao"; -import Loading from "./loading"; import SignForm from "../../../sign-form"; import SubmitBuildingPacket from "../../../submit-building-packet"; +import Loading from "./loading"; function CellDetailsDisplay({ cell, depositHeader, withdrawHeader }) { return ( @@ -31,7 +32,13 @@ function CellDetailsDisplay({ cell, depositHeader, withdrawHeader }) { ); } -function CellDetails({ cell, pending, onConfirm }) { +function CellDetails({ + cell, + pending, + onConfirm, + shouldPackVerifier, + setShouldPackVerifier, +}) { const tipHeader = useTipHeader(); const depositBlockNumber = dao.getDepositBlockNumberFromWithdrawCell(cell); const depositHeader = useHeaderByNumber(depositBlockNumber); @@ -62,6 +69,18 @@ function CellDetails({ cell, pending, onConfirm }) { Claim Now )} + { + setShouldPackVerifier(e.target.checked); + }} + /> +

: ; } @@ -86,12 +117,13 @@ export default function ClaimForm({ const router = useRouter(); const [formState, setFormState] = useState({}); const [pending, setPending] = useState(false); + const [shouldPackVerifier, setShouldPackVerifier] = useState(false); const [signedBuildingPacket, setSignedBuildingPacket] = useState(null); const back = () => router.back(); const onConfirm = async (cell) => { setPending(true); try { - setFormState(await claim(address, cell)); + setFormState(await claim(address, cell, shouldPackVerifier)); } catch (err) { setFormState({ error: err.toString() }); } @@ -102,7 +134,13 @@ export default function ClaimForm({ formState.buildingPacket === null || formState.buildingPacket === undefined ) { - const childProps = { outPoint, pending, onConfirm }; + const childProps = { + outPoint, + pending, + onConfirm, + shouldPackVerifier, + setShouldPackVerifier, + }; return ( <> {formState.error ? ( diff --git a/src/app/u/[wallet]/[connection]/deposit/form.js b/src/app/u/[wallet]/[connection]/deposit/form.js index 1f7d603..0a9b8db 100644 --- a/src/app/u/[wallet]/[connection]/deposit/form.js +++ b/src/app/u/[wallet]/[connection]/deposit/form.js @@ -1,14 +1,15 @@ "use client"; +import { Alert, Checkbox, Label, Popover, TextInput } from "flowbite-react"; +import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { useFormState } from "react-dom"; -import { useRouter } from "next/navigation"; -import { Label, TextInput, Alert } from "flowbite-react"; +import deposit from "@/actions/deposit"; +import { fetchAssetsWithCache } from "@/actions/fetch-assets"; import Capacity from "@/components/capacity"; +import PackingVerifierHelpText from "@/components/packing-verifier-help-text"; import SubmitButton from "@/components/submit-button"; -import { fetchAssetsWithCache } from "@/actions/fetch-assets"; -import deposit from "@/actions/deposit"; import Loading from "../loading"; import SignForm from "../sign-form"; import SubmitBuildingPacket from "../submit-building-packet"; @@ -52,6 +53,12 @@ export function TransactionForm({ formAction, formState, address }) { } /> +
+ + +
Deposit ); diff --git a/src/app/u/[wallet]/[connection]/reclaim/form.js b/src/app/u/[wallet]/[connection]/reclaim/form.js new file mode 100644 index 0000000..fd3b5c6 --- /dev/null +++ b/src/app/u/[wallet]/[connection]/reclaim/form.js @@ -0,0 +1,103 @@ +"use client"; + +import { BI, formatUnit } from "@ckb-lumos/bi"; +import { Alert, Button, Checkbox, Label } from "flowbite-react"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; + +import reclaim from "@/actions/reclaim"; +import useVerifierCells from "@/hooks/use-verifier-cells"; + +import SignForm from "../sign-form"; +import SubmitBuildingPacket from "../submit-building-packet"; + +function FormDisplay({ pending, onConfirm, verifierCells }) { + let lockedCapacity = BI.from(0); + if (verifierCells !== undefined) { + for (const cell of verifierCells) { + lockedCapacity = lockedCapacity.add(BI.from(cell.cellOutput.capacity)); + } + } + + return ( + <> +

+ +

+ {lockedCapacity.gt(0) ? ( +

+ You can reclaim {formatUnit(lockedCapacity, "ckb")} CKB from{" "} + {verifierCells.length} {verifierCells.length == 1 ? "cell" : "cells"}. +

+ ) : null} + + ); +} + +export default function WithdrawForm({ wallet, connection, address, config }) { + const router = useRouter(); + const verifierCells = useVerifierCells(address); + const [formState, setFormState] = useState({}); + const [pending, setPending] = useState(false); + const [signedBuildingPacket, setSignedBuildingPacket] = useState(null); + const back = () => router.back(); + const onConfirm = async () => { + setPending(true); + try { + setFormState(await reclaim(address)); + } catch (err) { + setFormState({ error: err.toString() }); + } + setPending(false); + }; + + if ( + formState.buildingPacket === null || + formState.buildingPacket === undefined + ) { + const childProps = { + pending, + onConfirm, + verifierCells, + }; + return ( + <> + {formState.error ? ( + + {formState.error} + + ) : null} + + + ); + } else if ( + signedBuildingPacket === null || + signedBuildingPacket === undefined + ) { + return ( + + ); + } else { + return ( + + ); + } +} diff --git a/src/app/u/[wallet]/[connection]/reclaim/loading.js b/src/app/u/[wallet]/[connection]/reclaim/loading.js new file mode 100644 index 0000000..d0ed745 --- /dev/null +++ b/src/app/u/[wallet]/[connection]/reclaim/loading.js @@ -0,0 +1,5 @@ +import { Spinner } from "flowbite-react"; + +export default function Loading() { + return ; +} diff --git a/src/app/u/[wallet]/[connection]/reclaim/page.js b/src/app/u/[wallet]/[connection]/reclaim/page.js new file mode 100644 index 0000000..7082e99 --- /dev/null +++ b/src/app/u/[wallet]/[connection]/reclaim/page.js @@ -0,0 +1,22 @@ +import { getConfig } from "@/lib/config"; +import * as walletSelector from "@/lib/wallet/selector"; + +import ReclaimForm from "./form"; + +export default function Withdraw({ params: { wallet, connection }, config }) { + config = config ?? getConfig(); + const address = walletSelector.address( + wallet, + connection, + config.ckbChainConfig, + ); + + const childProps = { wallet, connection, address, config }; + + return ( +
+

Reclaim DAO Verifiers

+ +
+ ); +} diff --git a/src/app/u/[wallet]/[connection]/sign-form.js b/src/app/u/[wallet]/[connection]/sign-form.js index 3e9a4a5..1823364 100644 --- a/src/app/u/[wallet]/[connection]/sign-form.js +++ b/src/app/u/[wallet]/[connection]/sign-form.js @@ -40,7 +40,10 @@ export default function SignForm({ ckbChainConfig, ); onSubmit( - finalizeWitnesses(applyLockAction(buildingPacket, lockAction, seal)), + finalizeWitnesses( + applyLockAction(buildingPacket, lockAction, seal), + ckbChainConfig, + ), ); } catch (err) { console.error(err.stack); diff --git a/src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/form.js b/src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/form.js index 8e2face..e8e40be 100644 --- a/src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/form.js +++ b/src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/form.js @@ -1,13 +1,14 @@ "use client"; -import { useState } from "react"; +import { Alert, Button, Checkbox, Label } from "flowbite-react"; import { useRouter } from "next/navigation"; -import { Alert, Button } from "flowbite-react"; +import { useState } from "react"; import withdraw from "@/actions/withdraw"; -import useTipHeader from "@/hooks/use-tip-header"; -import useHeader from "@/hooks/use-header"; +import PackingVerifierHelpText from "@/components/packing-verifier-help-text"; import useCell from "@/hooks/use-cell"; +import useHeader from "@/hooks/use-header"; +import useTipHeader from "@/hooks/use-tip-header"; import Capacity from "@/components/capacity"; import { @@ -16,9 +17,9 @@ import { daoCycleProgressColor, } from "@/components/dao-cycle-progress"; import * as dao from "@/lib/dao"; -import Loading from "./loading"; import SignForm from "../../../sign-form"; import SubmitBuildingPacket from "../../../submit-building-packet"; +import Loading from "./loading"; function CellDetailsDisplay({ progress, cell, depositHeader, tipHeader }) { return ( @@ -44,7 +45,13 @@ function CellDetailsDisplay({ progress, cell, depositHeader, tipHeader }) { ); } -function CellDetails({ cell, pending, onConfirm }) { +function CellDetails({ + cell, + pending, + onConfirm, + shouldPackVerifier, + setShouldPackVerifier, +}) { const tipHeader = useTipHeader(); const depositHeader = useHeader(cell.blockHash); @@ -66,15 +73,39 @@ function CellDetails({ cell, pending, onConfirm }) { > Confirm Withdraw + { + setShouldPackVerifier(e.target.checked); + }} + /> +

); } -function LoadCell({ outPoint, pending, onConfirm }) { +function LoadCell({ + outPoint, + pending, + onConfirm, + shouldPackVerifier, + setShouldPackVerifier, +}) { const cell = useCell(outPoint); - const childProps = { cell, pending, onConfirm }; + const childProps = { + cell, + pending, + onConfirm, + shouldPackVerifier, + setShouldPackVerifier, + }; return cell ? : ; } @@ -88,12 +119,13 @@ export default function WithdrawForm({ const router = useRouter(); const [formState, setFormState] = useState({}); const [pending, setPending] = useState(false); + const [shouldPackVerifier, setShouldPackVerifier] = useState(false); const [signedBuildingPacket, setSignedBuildingPacket] = useState(null); const back = () => router.back(); const onConfirm = async (cell) => { setPending(true); try { - setFormState(await withdraw(address, cell)); + setFormState(await withdraw(address, cell, shouldPackVerifier)); } catch (err) { setFormState({ error: err.toString() }); } @@ -104,7 +136,13 @@ export default function WithdrawForm({ formState.buildingPacket === null || formState.buildingPacket === undefined ) { - const childProps = { outPoint, pending, onConfirm }; + const childProps = { + outPoint, + pending, + onConfirm, + shouldPackVerifier, + setShouldPackVerifier, + }; return ( <> {formState.error ? ( diff --git a/src/components/packing-verifier-help-text.js b/src/components/packing-verifier-help-text.js new file mode 100644 index 0000000..3724fe3 --- /dev/null +++ b/src/components/packing-verifier-help-text.js @@ -0,0 +1,11 @@ +export default function PackingVerifierHelpText() { + return ( + + What's This? + + ); +} diff --git a/src/hooks/use-verifier-cells.js b/src/hooks/use-verifier-cells.js new file mode 100644 index 0000000..bf8142b --- /dev/null +++ b/src/hooks/use-verifier-cells.js @@ -0,0 +1,12 @@ +import { useEffect, useState } from "react"; +import { getVerifierCellsWithCache } from "@/actions/get-verifier-cells"; + +export default function useVerifierCells(fromAddress) { + const [verifierCells, setVerifierCells] = useState(); + + useEffect(() => { + getVerifierCellsWithCache(fromAddress).then(setVerifierCells); + }, [fromAddress, setVerifierCells]); + + return verifierCells; +} diff --git a/src/lib/cobuild/assets-manager.js b/src/lib/cobuild/assets-manager.js index dcdca9e..bdc6a1e 100644 --- a/src/lib/cobuild/assets-manager.js +++ b/src/lib/cobuild/assets-manager.js @@ -1,6 +1,7 @@ import { Indexer } from "@ckb-lumos/ckb-indexer"; import { addressToScript } from "@ckb-lumos/helpers"; import { BI } from "@ckb-lumos/bi"; +import { buildScript } from "@/lib/config"; export const DEPOSIT_DAO_DATA = "0x0000000000000000"; @@ -29,14 +30,22 @@ async function reduceDao(collector) { return { deposits, withdraws }; } +async function reduceVerifierCells(collector) { + const cells = []; + + for await (const cell of collector.collect()) { + cells.push(cell); + } + + return cells; +} + function buildDaoScript(ckbChainConfig) { - const template = ckbChainConfig.SCRIPTS.DAO; + return buildScript(ckbChainConfig.SCRIPTS.DAO, "0x"); +} - return { - codeHash: template.CODE_HASH, - hashType: template.HASH_TYPE, - args: "0x", - }; +function buildVerifierScript(ckbChainConfig) { + return buildScript(ckbChainConfig.SCRIPTS.DAO_ACTION_VERIFIER, "0x"); } export async function fetchAssets(address, { ckbRpcUrl, ckbChainConfig }) { @@ -58,7 +67,14 @@ export async function fetchAssets(address, { ckbRpcUrl, ckbChainConfig }) { }); const daoCells = await reduceDao(daoCollector); - return { ckbBalance, daoCells }; + const verifierCollector = indexer.collector({ + lock, + argsLen: (lock.args.length - 2) / 2, + type: buildVerifierScript(ckbChainConfig), + }); + const verifierCells = await reduceVerifierCells(verifierCollector); + + return { ckbBalance, daoCells, verifierCells }; } export function isDaoWithdrawCell(cellOutput, cellData, ckbChainConfig) { @@ -75,7 +91,7 @@ export function isDaoDepositCell(cellOutput, cellData, ckbChainConfig) { ); } -export function isNoneDaoTypedCell(cellOutput, cellData, ckbChainConfig) { +export function isNoneDaoTypedCell(cellOutput, ckbChainConfig) { const dao = ckbChainConfig.SCRIPTS.DAO; return cellOutput.type && cellOutput.type.codeHash !== dao.CODE_HASH; } diff --git a/src/lib/cobuild/fee-manager.js b/src/lib/cobuild/fee-manager.js index 16d10da..231450b 100644 --- a/src/lib/cobuild/fee-manager.js +++ b/src/lib/cobuild/fee-manager.js @@ -23,6 +23,7 @@ export async function payFee(buildingPacket, feePayments, config) { ), buildingPacket, ), + config.ckbChainConfig, ); const buildingPacketHavePaidFee = await payFeeWithBuildingPacket( @@ -55,7 +56,12 @@ function storeWitnessForFeeEstimation( return generalLockActions.storeWitnessForFeeEstimation( buildingPacket, scriptHash, - inputIndices, + { + type: "WitnessArgsStore", + value: { + inputIndices, + }, + }, // Variable length, but 500 is usually enough. () => bytes.hexify(new Uint8Array(500)), ); @@ -65,7 +71,12 @@ function storeWitnessForFeeEstimation( return generalLockActions.storeWitnessForFeeEstimation( buildingPacket, scriptHash, - inputIndices, + { + type: "WitnessArgsStore", + value: { + inputIndices, + }, + }, // 85 = 65 signature in OmnilockWitnessLock () => `0x${"0".repeat(85 * 2)}`, ); diff --git a/src/lib/cobuild/general-lock-actions.js b/src/lib/cobuild/general-lock-actions.js index abce542..23bddff 100644 --- a/src/lib/cobuild/general-lock-actions.js +++ b/src/lib/cobuild/general-lock-actions.js @@ -66,13 +66,13 @@ export function prepareLockActionWithWitnessStore( export function storeWitnessForFeeEstimation( buildingPacket, scriptHash, - inputIndices, + witnessStore, createSealPlaceHolder, ) { - buildingPacket = prepareLockAction( + buildingPacket = prepareLockActionWithWitnessStore( buildingPacket, scriptHash, - inputIndices, + witnessStore, createSealPlaceHolder, ); const lockAction = buildingPacket.value.lockActions.find( diff --git a/src/lib/cobuild/lock-actions.js b/src/lib/cobuild/lock-actions.js index 556fae9..1e04c43 100644 --- a/src/lib/cobuild/lock-actions.js +++ b/src/lib/cobuild/lock-actions.js @@ -1,8 +1,9 @@ import { utils as lumosBaseUtils } from "@ckb-lumos/base"; +import { bytes } from "@ckb-lumos/codec"; import * as generalLockActions from "./general-lock-actions"; -import { parseWitnessType } from "./types"; import { groupByLock } from "./script-group"; +import { parseWitnessType, WitnessLayout } from "./types"; const { computeScriptHash } = lumosBaseUtils; @@ -10,6 +11,11 @@ const { computeScriptHash } = lumosBaseUtils; // // Do not set any witness in payload to use the Cobuild layout. export function prepareLockActions(buildingPacket, ckbChainConfig) { + // Workaround to pack Cobuild message as the extra witness. + // + // Before the next action, there's no actual witness set yet. So `finalizeWitnesses` will assume there's no SighashAll witness in the tx and will pack the message as the extra witness. This assumption is currect because this PoC uses WitnessArgs layout for lock actions and DAO type script. + buildingPacket = finalizeWitnesses(buildingPacket, ckbChainConfig); + const groups = groupByLock(buildingPacket.value.resolvedInputs.outputs); return Object.entries(groups).reduce( (acc, [scriptHash, inputs]) => @@ -23,11 +29,28 @@ export function prepareLockActions(buildingPacket, ckbChainConfig) { ); } -export function finalizeWitnesses(buildingPacket) { +function hasVerifierCell(buildingPacket, ckbChainConfig) { + const template = ckbChainConfig.SCRIPTS.DAO_ACTION_VERIFIER; + for (const output of buildingPacket.value.payload.outputs) { + if ( + output.type !== undefined && + output.type !== null && + output.type.codeHash === template.CODE_HASH + ) { + return true; + } + } + + return false; +} + +export function finalizeWitnesses(buildingPacket, ckbChainConfig) { // fill holes const witnesses = Array.from(buildingPacket.value.payload.witnesses).map( (w) => w ?? "0x", ); + + let hasSighashAll = false; // If there's no SighashAll before SighashAllOnly, replace the first SighashAllOnly with SighashAll for (const i of witnesses.keys()) { if (witnesses[i] === "0x") { @@ -35,8 +58,10 @@ export function finalizeWitnesses(buildingPacket) { } const witnessType = parseWitnessType(witnesses[i]); if (witnessType === "SighashAll") { + hasSighashAll = true; break; } else if (witnessType === "SighashAllOnly") { + hasSighashAll = true; const witness = WitnessLayout.unpack(witnesses[i]); witnesses[i] = bytes.hexify( WitnessLayout.pack({ @@ -50,6 +75,26 @@ export function finalizeWitnesses(buildingPacket) { } } + // if there's a verifier cell, pack the Cobuild message into witness + if (!hasSighashAll && hasVerifierCell(buildingPacket, ckbChainConfig)) { + for ( + let i = witnesses.length; + i < buildingPacket.value.payload.inputs.length; + ++i + ) { + witnesses[i] = "0x"; + } + witnesses[buildingPacket.value.payload.inputs.length] = bytes.hexify( + WitnessLayout.pack({ + type: "SighashAll", + value: { + seal: "0x", + message: buildingPacket.value.message, + }, + }), + ); + } + return { type: buildingPacket.type, value: { diff --git a/src/lib/cobuild/publishers.js b/src/lib/cobuild/publishers.js index 50dff14..6a88f78 100644 --- a/src/lib/cobuild/publishers.js +++ b/src/lib/cobuild/publishers.js @@ -8,6 +8,10 @@ export function transferCkb(config) { return createLumosCkbBuilder(config).transferCkb; } +export function reclaimDaoVerifiers(config) { + return createLumosCkbBuilder(config).reclaimDaoVerifiers; +} + export function depositDao(config) { return createLumosCkbBuilder(config).depositDao; } diff --git a/src/lib/cobuild/react/building-packet-review.js b/src/lib/cobuild/react/building-packet-review.js index ab74cfd..8b0fb15 100644 --- a/src/lib/cobuild/react/building-packet-review.js +++ b/src/lib/cobuild/react/building-packet-review.js @@ -3,21 +3,20 @@ import { Accordion, Spinner } from "flowbite-react"; import moment from "moment"; +import { DaoActionData } from "@/lib/papps/dao/schema"; +import { getDaoScriptHash } from "@/lib/papps/dao/script-info"; import { blockchain, utils as lumosBaseUtils } from "@ckb-lumos/base"; import { BI, formatUnit } from "@ckb-lumos/bi"; import * as lumosHelpers from "@ckb-lumos/helpers"; -import { DaoActionData } from "@/lib/papps/dao/schema"; -import { getDaoScriptHash } from "@/lib/papps/dao/script-info"; - -const { ckbHash } = lumosBaseUtils; - import { - isDaoWithdrawCell, isDaoDepositCell, + isDaoWithdrawCell, isNoneDaoTypedCell, } from "../assets-manager"; +const { ckbHash } = lumosBaseUtils; + function BriefAddress({ address }) { const display = `${address.slice(0, 15)}...${address.slice(-14)}`; return {display}; @@ -399,9 +398,9 @@ export function TxSection({ return (
- {process.env.DEBUG !== undefined ? ( + {process.env.NEXT_PUBLIC_DEBUG ? (
-
Hash
+
Building Packet
               {JSON.stringify(buildingPacket, null, 2)}
@@ -564,10 +563,10 @@ function collectAssets(
       }
     } else if (isDaoDepositCell(cellOutput, cellData, ckbChainConfig)) {
       assets.daoWithdrawn = assets.daoWithdrawn.add(cellCapacity);
-    } else if (isNoneDaoTypedCell(cellOutput, cellData, ckbChainConfig)) {
+    } else if (isNoneDaoTypedCell(cellOutput, ckbChainConfig)) {
       assets.destroyedTypedCells.push({
         cellOutput,
-        outPoint: buildingPacket.value.packet.inputs[i].previousOutput,
+        outPoint: buildingPacket.value.payload.inputs[i].previousOutput,
         data: cellData,
       });
     }
@@ -580,9 +579,9 @@ function collectAssets(
 
     if (isDaoDepositCell(cellOutput, cellData, ckbChainConfig)) {
       assets.daoDeposited = assets.daoDeposited.add(cellCapacity);
-    } else if (isNoneDaoTypedCell(cellOutput, cellData, ckbChainConfig)) {
+    } else if (isNoneDaoTypedCell(cellOutput, ckbChainConfig)) {
       assets.createdTypedCells.push({
-        outPoint: buildingPacket.value.packet.inputs[i].previousOutput,
+        outPoint: buildingPacket.value.payload.outputs[i].previousOutput,
         data: cellData,
       });
     }
diff --git a/src/lib/config.js b/src/lib/config.js
index fb04a8a..d0f69b0 100644
--- a/src/lib/config.js
+++ b/src/lib/config.js
@@ -27,6 +27,16 @@ const CKB_CHAINS_CONFIGS = {
         INDEX: "0x0",
         DEP_TYPE: "code",
       },
+
+      DAO_ACTION_VERIFIER: {
+        CODE_HASH:
+          "0xbdca5b74e5d0c913fed19d8482a99af1ef8a639541438b2e00189f5e18907ef9",
+        HASH_TYPE: "type",
+        TX_HASH:
+          "0x9157bcc278176ba9e823a50d72631be9e9b964e7a5ca11db2782c059c4c788ad",
+        INDEX: "0x0",
+        DEP_TYPE: "code",
+      },
     },
   },
 };
@@ -66,6 +76,15 @@ function buildCkbChainConfig(ckbChain) {
       TX_HASH: presence(process.env.NEXT_PUBLIC_OMNILOCK_TX_HASH),
     },
   );
+  const DAO_ACTION_VERIFIER = assign(
+    { ...template.SCRIPTS.DAO_ACTION_VERIFIER },
+    {
+      CODE_HASH: presence(
+        process.env.NEXT_PUBLIC_DAO_ACTION_VERIFIER_CODE_HASH,
+      ),
+      TX_HASH: presence(process.env.NEXT_PUBLIC_DAO_ACTION_VERIFIER_TX_HASH),
+    },
+  );
 
   const tx0 =
     presence(process.env.NEXT_PUBLIC_CKB_GENESIS_TX_0) ??
@@ -80,6 +99,7 @@ function buildCkbChainConfig(ckbChain) {
     SCRIPTS: {
       JOYID,
       OMNILOCK_CUSTOM,
+      DAO_ACTION_VERIFIER,
       DAO: {
         ...template.SCRIPTS.DAO,
         TX_HASH: tx0,
@@ -126,3 +146,21 @@ export function getTestnetConfig() {
     ckbChainConfig: CKB_CHAINS_CONFIGS[DEFAULT_CKB_CHAIN],
   };
 }
+
+export function buildCellDep(scriptInfo) {
+  return {
+    outPoint: {
+      txHash: scriptInfo.TX_HASH,
+      index: scriptInfo.INDEX,
+    },
+    depType: scriptInfo.DEP_TYPE,
+  };
+}
+
+export function buildScript(scriptInfo, args) {
+  return {
+    codeHash: scriptInfo.CODE_HASH,
+    hashType: scriptInfo.HASH_TYPE,
+    args,
+  };
+}
diff --git a/src/lib/lumos-adapter/create-lumos-ckb-builder.js b/src/lib/lumos-adapter/create-lumos-ckb-builder.js
index 6acea5b..bbe0fa5 100644
--- a/src/lib/lumos-adapter/create-lumos-ckb-builder.js
+++ b/src/lib/lumos-adapter/create-lumos-ckb-builder.js
@@ -1,15 +1,16 @@
-import { Indexer } from "@ckb-lumos/ckb-indexer";
-import { TransactionSkeleton } from "@ckb-lumos/helpers";
-import { common as commonScripts } from "@ckb-lumos/common-scripts";
-
+import { buildCellDep, buildScript } from "@/lib/config";
+import { getDaoPappRegistry } from "@/lib/papps/dao/registry";
 import {
-  createBuildingPacketByFormDataCreator,
   createBuildingPacketByCreator,
+  createBuildingPacketByFormDataCreator,
 } from "@/lib/papps/papp";
-import { getDaoPappRegistry } from "@/lib/papps/dao/registry";
+import { BI } from "@ckb-lumos/bi";
+import { Indexer } from "@ckb-lumos/ckb-indexer";
+import { common as commonScripts } from "@ckb-lumos/common-scripts";
+import { TransactionSkeleton, addressToScript } from "@ckb-lumos/helpers";
 
-import initLumosCommonScripts from "./init-lumos-common-scripts";
 import createBuildingPacketFromSkeleton from "./create-building-packet-from-skeleton";
+import initLumosCommonScripts from "./init-lumos-common-scripts";
 
 export default function createLumosCkbBuilder(config) {
   const { ckbRpcUrl, ckbChainConfig } = config;
@@ -58,5 +59,49 @@ export default function createLumosCkbBuilder(config) {
         cellPointer: cell.outPoint,
       });
     },
+
+    reclaimDaoVerifiers: async function ({ from }) {
+      let txSkeleton = TransactionSkeleton({
+        cellProvider: indexer,
+      });
+
+      const verifierScript = buildScript(
+        ckbChainConfig.SCRIPTS.DAO_ACTION_VERIFIER,
+        "0x",
+      );
+      const lock = addressToScript(from, { config: ckbChainConfig });
+      const collector = indexer.collector({
+        lock,
+        argsLen: (lock.args.length - 2) / 2,
+        type: verifierScript,
+      });
+      let outputCapacity = BI.from(0);
+      for await (const cell of collector.collect()) {
+        outputCapacity = outputCapacity.add(BI.from(cell.cellOutput.capacity));
+        txSkeleton = await commonScripts.setupInputCell(
+          txSkeleton,
+          cell,
+          from,
+          {
+            config: ckbChainConfig,
+          },
+        );
+      }
+      txSkeleton = txSkeleton.update("outputs", (outputs) =>
+        outputs.clear().push({
+          cellOutput: {
+            lock,
+            capacity: outputCapacity.toHexString(),
+          },
+          data: "0x",
+        }),
+      );
+
+      txSkeleton = txSkeleton.update("cellDeps", (cellDeps) =>
+        cellDeps.push(buildCellDep(ckbChainConfig.SCRIPTS.DAO_ACTION_VERIFIER)),
+      );
+
+      return createBuildingPacketFromSkeleton(txSkeleton);
+    },
   };
 }
diff --git a/src/lib/lumos-adapter/init-lumos-common-scripts.js b/src/lib/lumos-adapter/init-lumos-common-scripts.js
index 32d6010..4f1564e 100644
--- a/src/lib/lumos-adapter/init-lumos-common-scripts.js
+++ b/src/lib/lumos-adapter/init-lumos-common-scripts.js
@@ -1,6 +1,7 @@
+import { buildCellDep } from "@/lib/config";
 import {
-  parseFromInfo,
   common as commonScripts,
+  parseFromInfo,
 } from "@ckb-lumos/common-scripts";
 import { addCellDep } from "@ckb-lumos/common-scripts/lib/helper";
 
@@ -105,21 +106,9 @@ export function buildLockInfo(ckbChainConfig, scriptInfo, extraScripts) {
         // II. CellDeps
         //===========================
         // The helper method addCellDep avoids adding duplicated cell deps.
-        addCellDep(txMutable, {
-          outPoint: {
-            txHash: scriptInfo.TX_HASH,
-            index: scriptInfo.INDEX,
-          },
-          depType: scriptInfo.DEP_TYPE,
-        });
+        addCellDep(txMutable, buildCellDep(scriptInfo));
         for (const extraScriptInfo of extraScripts) {
-          addCellDep(txMutable, {
-            outPoint: {
-              txHash: extraScriptInfo.TX_HASH,
-              index: extraScriptInfo.INDEX,
-            },
-            depType: extraScriptInfo.DEP_TYPE,
-          });
+          addCellDep(txMutable, buildCellDep(extraScriptInfo));
         }
 
         return txMutable.asImmutable();
diff --git a/src/lib/papps/dao/action-creators.js b/src/lib/papps/dao/action-creators.js
index f8a8c81..e22023c 100644
--- a/src/lib/papps/dao/action-creators.js
+++ b/src/lib/papps/dao/action-creators.js
@@ -11,13 +11,6 @@ function addressToScriptOpt(address, lumosOptions) {
   return undefined;
 }
 
-function buildSingleOperation(operation) {
-  return {
-    type: "SingleOperation",
-    value: operation,
-  };
-}
-
 export function depositWithFormData(config, formData) {
   const lumosOptions = { config: config.ckbChainConfig };
 
diff --git a/src/lib/papps/dao/lumos-callbacks.js b/src/lib/papps/dao/lumos-callbacks.js
index 792440d..6c3e069 100644
--- a/src/lib/papps/dao/lumos-callbacks.js
+++ b/src/lib/papps/dao/lumos-callbacks.js
@@ -1,10 +1,3 @@
-import { RPC } from "@ckb-lumos/rpc";
-import { BI } from "@ckb-lumos/bi";
-import { common as commonScripts, dao } from "@ckb-lumos/common-scripts";
-import { blockchain } from "@ckb-lumos/base";
-import { bytes, number } from "@ckb-lumos/codec";
-import * as lumosHelpers from "@ckb-lumos/helpers";
-
 import { getCellWithoutCache } from "@/actions/get-cell";
 import {
   getDepositBlockNumberFromWithdrawCell,
@@ -12,24 +5,21 @@ import {
 } from "@/lib/dao";
 import { mergeBuildingPacketFromSkeleton } from "@/lib/lumos-adapter/create-building-packet-from-skeleton";
 import createSkeletonFromBuildingPacket from "@/lib/lumos-adapter/create-skeleton-from-building-packet";
+import { blockchain } from "@ckb-lumos/base";
+import { BI } from "@ckb-lumos/bi";
+import { bytes, number } from "@ckb-lumos/codec";
+import { common as commonScripts, dao } from "@ckb-lumos/common-scripts";
+import * as lumosHelpers from "@ckb-lumos/helpers";
+import { RPC } from "@ckb-lumos/rpc";
+import { buildCellDep } from "@/lib/config";
 
 import { DaoActionData } from "./schema";
 import {
-  getDaoScriptInfo,
   getDaoScriptHash,
+  getDaoScriptInfo,
   getDaoScriptInfoHash,
 } from "./script-info";
 
-function buildCellDep(scriptInfo) {
-  return {
-    outPoint: {
-      txHash: scriptInfo.TX_HASH,
-      index: scriptInfo.INDEX,
-    },
-    depType: scriptInfo.DEP_TYPE,
-  };
-}
-
 function addDistinctCellDep(list, ...items) {
   return pushDistinctBy(
     list,
diff --git a/src/lib/papps/dao/script-info.js b/src/lib/papps/dao/script-info.js
index 8f957c3..489a4cf 100644
--- a/src/lib/papps/dao/script-info.js
+++ b/src/lib/papps/dao/script-info.js
@@ -1,4 +1,4 @@
-import { getConfig } from "@/lib/config";
+import { getConfig, buildScript } from "@/lib/config";
 import {
   createScriptInfoFromHumanTemplate,
   computeScriptInfoHash,
@@ -21,11 +21,7 @@ export function getDaoHumanScriptInfoTemplate() {
 
 function getDaoScriptHashFromConfig(config) {
   const daoInfo = config.ckbChainConfig.SCRIPTS.DAO;
-  const daoScript = {
-    codeHash: daoInfo.CODE_HASH,
-    hashType: daoInfo.HASH_TYPE,
-    args: "0x",
-  };
+  const daoScript = buildScript(daoInfo, "0x");
   return computeScriptHash(daoScript);
 }
 
diff --git a/src/lib/papps/dao/verifier.js b/src/lib/papps/dao/verifier.js
new file mode 100644
index 0000000..bcc7e09
--- /dev/null
+++ b/src/lib/papps/dao/verifier.js
@@ -0,0 +1,98 @@
+import { buildCellDep, buildScript } from "@/lib/config";
+import { mergeBuildingPacketFromSkeleton } from "@/lib/lumos-adapter/create-building-packet-from-skeleton";
+import createSkeletonFromBuildingPacket from "@/lib/lumos-adapter/create-skeleton-from-building-packet";
+import { Indexer } from "@ckb-lumos/ckb-indexer";
+import { common as commonScripts } from "@ckb-lumos/common-scripts";
+import {
+  addressToScript,
+  minimalCellCapacityCompatible,
+} from "@ckb-lumos/helpers";
+
+export async function prepareVerifier(buildingPacket, fromAddress, config) {
+  let txSkeleton = createSkeletonFromBuildingPacket(buildingPacket, config);
+
+  const { ckbChainConfig, ckbRpcUrl } = config;
+  const indexer = new Indexer(ckbRpcUrl);
+  const verifierTypeScript = buildScript(
+    ckbChainConfig.SCRIPTS.DAO_ACTION_VERIFIER,
+    "0x",
+  );
+  const from = addressToScript(fromAddress, { config: ckbChainConfig });
+
+  const verifierCollector = indexer.collector({
+    lock: from,
+    argsLen: (from.args.length - 2) / 2,
+    type: verifierTypeScript,
+    data: "0x",
+  });
+  const verifierCell = (await verifierCollector.collect().next()).value;
+  if (verifierCell === null || verifierCell === undefined) {
+    const newVerifierCell = {
+      cellOutput: {
+        capacity: "0x0",
+        lock: from,
+        type: verifierTypeScript,
+      },
+      data: "0x",
+      outPoint: undefined,
+      blockHash: undefined,
+    };
+    const minimalCapacity = minimalCellCapacityCompatible(newVerifierCell);
+    newVerifierCell.cellOutput.capacity = minimalCapacity.toHexString();
+
+    // create a new verifier cell
+    txSkeleton = await commonScripts.injectCapacity(
+      txSkeleton,
+      [fromAddress],
+      minimalCapacity,
+      fromAddress,
+      undefined,
+      { config: ckbChainConfig },
+    );
+    txSkeleton = txSkeleton.update("outputs", (outputs) =>
+      outputs.push(newVerifierCell),
+    );
+  } else {
+    txSkeleton = await commonScripts.setupInputCell(
+      txSkeleton,
+      verifierCell,
+      fromAddress,
+      {
+        config: ckbChainConfig,
+      },
+    );
+  }
+
+  // exchange change output and verifier cell
+  const changeOutputIndex = buildingPacket.value.changeOutput;
+  const outputsSize = txSkeleton.get("outputs").size;
+
+  if (
+    changeOutputIndex !== undefined &&
+    changeOutputIndex !== null &&
+    changeOutputIndex < outputsSize - 1
+  ) {
+    // exchange change cell and verifier cell to avoid multiple change cells
+    txSkeleton = txSkeleton.update("outputs", (outputs) => {
+      const verifierCell = outputs.get(outputsSize - 1);
+      const changeCell = outputs.get(changeOutputIndex);
+      return outputs
+        .set(changeOutputIndex, verifierCell)
+        .set(outputsSize - 1, changeCell);
+    });
+    txSkeleton = txSkeleton.update("fixedEntries", (fixedEntries) =>
+      fixedEntries.push({ field: "outputs", index: outputsSize - 2 }),
+    );
+  } else {
+    // no change output, fixed all outputs
+    txSkeleton = txSkeleton.update("fixedEntries", (fixedEntries) =>
+      fixedEntries.push({ field: "outputs", index: outputsSize - 1 }),
+    );
+  }
+
+  txSkeleton = txSkeleton.update("cellDeps", (cellDeps) =>
+    cellDeps.push(buildCellDep(ckbChainConfig.SCRIPTS.DAO_ACTION_VERIFIER)),
+  );
+
+  return mergeBuildingPacketFromSkeleton(buildingPacket, txSkeleton);
+}
diff --git a/src/lib/wallet/btc-wallet.js b/src/lib/wallet/btc-wallet.js
index f17fa58..e02860d 100644
--- a/src/lib/wallet/btc-wallet.js
+++ b/src/lib/wallet/btc-wallet.js
@@ -1,5 +1,6 @@
 import { bytes } from "@ckb-lumos/codec";
 import * as lumosHelpers from "@ckb-lumos/helpers";
+import { buildScript } from "@/lib/config";
 import { bech32 } from "bech32";
 import * as bs58 from "bs58";
 import { packOmnilockWitnessLock } from "./omni-lock";
@@ -49,11 +50,7 @@ export function btcAddressToCkbAddress(btcAddress, scriptInfo, ckbChainConfig) {
   }
   args += "00";
 
-  const script = {
-    codeHash: scriptInfo.CODE_HASH,
-    hashType: scriptInfo.HASH_TYPE,
-    args: args,
-  };
+  const script = buildScript(scriptInfo, args);
   return lumosHelpers.encodeToAddress(script, {
     config: ckbChainConfig,
   });
diff --git a/src/lib/wallet/joyid.js b/src/lib/wallet/joyid.js
index 135e9a8..ce67994 100644
--- a/src/lib/wallet/joyid.js
+++ b/src/lib/wallet/joyid.js
@@ -1,8 +1,9 @@
-import * as joyid from "@joyid/ckb";
 import { bytes } from "@ckb-lumos/codec";
 import * as lumosHelpers from "@ckb-lumos/helpers";
+import * as joyid from "@joyid/ckb";
 
 import { urlSafeBase64Decode } from "../base64";
+import { buildScript } from "../config";
 
 export const title = "Joyid";
 export const lockScriptName = "Joyid Lock";
@@ -19,11 +20,7 @@ export function address(connection, ckbChainConfig) {
     config: ckbChainConfig,
   });
   const scriptInfo = ckbChainConfig.SCRIPTS.JOYID;
-  const script = {
-    codeHash: scriptInfo.CODE_HASH,
-    hashType: scriptInfo.HASH_TYPE,
-    args,
-  };
+  const script = buildScript(scriptInfo, args);
   return lumosHelpers.encodeToAddress(script, {
     config: ckbChainConfig,
   });