Skip to content

Commit

Permalink
dao: withdraw phase 2
Browse files Browse the repository at this point in the history
  • Loading branch information
doitian committed Dec 28, 2023
1 parent a4b9a4c commit 938d009
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 2 deletions.
19 changes: 19 additions & 0 deletions src/actions/claim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use server";

import { claimDao } from "@/lib/cobuild/publishers";
import { useConfig } from "@/lib/config";

export default async function withdraw(from, cell, config) {
config = config ?? useConfig();

try {
const buildingPacket = await claimDao(config)({ from, cell });
return {
buildingPacket,
};
} catch (err) {
return {
error: err.toString(),
};
}
}
137 changes: 137 additions & 0 deletions src/app/accounts/[address]/claim/[txHash]/[index]/form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import { Alert, Button } from "flowbite-react";

import claim from "@/actions/claim";
import useTipHeader from "@/hooks/use-tip-header";
import useHeader from "@/hooks/use-header";
import useHeaderByNumber from "@/hooks/use-header-by-number";
import useCell from "@/hooks/use-cell";

import Capacity from "@/components/capacity";
import {
DaoCycleProgress,
DaoCycleProgressHint,
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";

function CellDetailsDisplay({ cell, depositHeader, withdrawHeader }) {
return (
<dl>
<dt>Base</dt>
<dd>
<Capacity value={cell.cellOutput.capacity} />
</dd>
<dt>Reward</dt>
<dd>
+<Capacity value={dao.reward(cell, depositHeader, withdrawHeader)} />
</dd>
</dl>
);
}

function CellDetails({ cell, pending, onConfirm }) {
const tipHeader = useTipHeader();
const depositBlockNumber = dao.getDepositBlockNumberFromWithdrawCell(cell);
const depositHeader = useHeaderByNumber(depositBlockNumber);
const withdrawHeader = useHeader(cell.blockHash);

if (!tipHeader || !depositHeader || !withdrawHeader) {
return <Loading />;
}

const waitingDuration = dao.estimateWithdrawWaitingDurationUntil(
tipHeader,
depositHeader,
withdrawHeader,
);

return (
<>
<p>
{waitingDuration ? (
`Waiting for ${waitingDuration.humanize()}`
) : (
<Button
color="green"
isProcessing={pending}
disabled={pending}
onClick={() => onConfirm(cell)}
>
Claim Now
</Button>
)}
</p>
<CellDetailsDisplay
{...{ cell, tipHeader, withdrawHeader, depositHeader }}
/>
</>
);
}

function LoadCell({ outPoint, pending, onConfirm }) {
const cell = useCell(outPoint);
const childProps = { cell, pending, onConfirm };
return cell ? <CellDetails {...childProps} /> : <Loading />;
}

export default function ClaimForm({ address, outPoint, config }) {
const router = useRouter();
const [formState, setFormState] = useState({});
const [pending, setPending] = useState(false);
const [signedBuildingPacket, setSignedBuildingPacket] = useState(null);
const back = () => router.back();
const onConfirm = async (cell) => {
setPending(true);
try {
setFormState(await claim(address, cell));
} catch (err) {
setFormState({ error: err.toString() });
}
setPending(false);
};

if (
formState.buildingPacket === null ||
formState.buildingPacket === undefined
) {
const childProps = { outPoint, pending, onConfirm };
return (
<>
{formState.error ? (
<Alert className="mb-5" color="failure">
{formState.error}
</Alert>
) : null}
<LoadCell {...childProps} />
</>
);
} else if (
signedBuildingPacket === null ||
signedBuildingPacket === undefined
) {
return (
<SignForm
address={address}
buildingPacket={formState.buildingPacket}
ckbChainConfig={config.ckbChainConfig}
onSubmit={setSignedBuildingPacket}
onCancel={back}
/>
);
} else {
return (
<SubmitBuildingPacket
buildingPacket={signedBuildingPacket}
ckbChainConfig={config.ckbChainConfig}
onClose={back}
/>
);
}
}
5 changes: 5 additions & 0 deletions src/app/accounts/[address]/claim/[txHash]/[index]/loading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Spinner } from "flowbite-react";

export default function Loading() {
return <Spinner />;
}
21 changes: 21 additions & 0 deletions src/app/accounts/[address]/claim/[txHash]/[index]/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useConfig } from "@/lib/config";

import ClaimForm from "./form";

export default function Claim({ params: { address, txHash, index }, config }) {
config = config ?? useConfig();

const outPoint = {
txHash: `0x${txHash}`,
index: `0x${index.toString(16)}`,
};

const childProps = { address, outPoint, config };

return (
<main>
<h2>Claim</h2>
<ClaimForm {...childProps} />
</main>
);
}
5 changes: 4 additions & 1 deletion src/app/accounts/[address]/dao-cells.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ export function WithdrawRow({ address, cell, tipHeader }) {
<>
<td>
<>
+<Capacity value={dao.reward(cell, depositHeader, tipHeader)} />
+
<Capacity
value={dao.reward(cell, depositHeader, withdrawHeader)}
/>
</>
</td>
<td>
Expand Down
4 changes: 4 additions & 0 deletions src/lib/cobuild/publishers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ export function depositDao(config) {
export function withdrawDao(config) {
return createLumosCkbBuilder(config).withdrawDao;
}

export function claimDao(config) {
return createLumosCkbBuilder(config).claimDao;
}
12 changes: 11 additions & 1 deletion src/lib/dao.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import moment from "moment";
import { BI } from "@ckb-lumos/bi";
import { number } from "@ckb-lumos/codec";
import { number, bytes } from "@ckb-lumos/codec";
import { blockchain } from "@ckb-lumos/base";
import { dao } from "@ckb-lumos/common-scripts";

const DAO_CYCLE_EPOCHS = BI.from(180);
Expand Down Expand Up @@ -35,6 +36,15 @@ export function getDepositBlockNumberFromWithdrawCell(cell) {
return BI.from(number.Uint64.unpack(cell.data)).toHexString();
}

// Pack witness for withdraw phase 2 tx.
// depositHeaderIndex - cellDeps index of the block hash in which the deposit tx is committed.
export function packDaoWitnessArgs(depositHeaderIndex) {
const witnessArgs = {
inputType: bytes.hexify(number.Uint64LE.pack(depositHeaderIndex)),
};
return bytes.hexify(blockchain.WitnessArgs.pack(witnessArgs));
}

export function currentCycleProgress(tipHeader, depositHeader) {
const start = decomposeEpoch(depositHeader.epoch);
const end = decomposeEpoch(tipHeader.epoch);
Expand Down
77 changes: 77 additions & 0 deletions src/lib/lumos-adapter/create-lumos-ckb-builder.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { RPC } from "@ckb-lumos/rpc";
import { Indexer } from "@ckb-lumos/ckb-indexer";
import { TransactionSkeleton } from "@ckb-lumos/helpers";
import { common as commonScripts, dao } from "@ckb-lumos/common-scripts";

import initLumosCommonScripts from "./init-lumos-common-scripts";
import createBuildingPacketFromSkeleton from "./create-building-packet-from-skeleton";
import {
getDepositBlockNumberFromWithdrawCell,
packDaoWitnessArgs,
} from "../dao";

// **Attention:** There's no witnesses set yet, so I set fee rate to 3000 to hope that the final tx fee rate will be larger than 1000.
async function payFee(txSkeleton, from, ckbChainConfig) {
Expand All @@ -18,8 +23,19 @@ async function payFee(txSkeleton, from, ckbChainConfig) {
);
}

function buildCellDep(scriptInfo) {
return {
outPoint: {
txHash: scriptInfo.TX_HASH,
index: scriptInfo.INDEX,
},
depType: scriptInfo.DEP_TYPE,
};
}

export default function createLumosCkbBuilder({ ckbRpcUrl, ckbChainConfig }) {
initLumosCommonScripts(ckbChainConfig);
const rpc = new RPC(ckbRpcUrl);
const indexer = new Indexer(ckbRpcUrl);

return {
Expand Down Expand Up @@ -83,5 +99,66 @@ export default function createLumosCkbBuilder({ ckbRpcUrl, ckbChainConfig }) {
txSkeleton = await payFee(txSkeleton, from, ckbChainConfig);
return createBuildingPacketFromSkeleton(txSkeleton);
},

claimDao: async function ({ from, cell }) {
const depositBlockNumber = getDepositBlockNumberFromWithdrawCell(cell);
const depositBlockHash = await rpc.getBlockHash(depositBlockNumber);
const depositHeader = await rpc.getHeader(depositBlockHash);
const withdrawHeader = await rpc.getHeader(cell.blockHash);

const txSkeletonMutable = TransactionSkeleton({
cellProvider: indexer,
}).asMutable();

// add input
const since =
"0x" +
dao
.calculateDaoEarliestSince(depositHeader.epoch, withdrawHeader.epoch)
.toString(16);
txSkeletonMutable.update("inputs", (inputs) => inputs.push(cell));
txSkeletonMutable.update("inputSinces", (inputSinces) =>
inputSinces.set(0, since),
);

// add output
const outCapacity =
"0x" +
dao
.calculateMaximumWithdraw(cell, depositHeader.dao, withdrawHeader.dao)
.toString(16);
txSkeletonMutable.update("outputs", (outputs) =>
outputs.push({
cellOutput: {
capacity: outCapacity,
type: null,
lock: cell.cellOutput.lock,
},
data: "0x",
}),
);

// add cell deps
txSkeletonMutable.update("cellDeps", (cellDeps) =>
cellDeps.push(
buildCellDep(ckbChainConfig.SCRIPTS.DAO),
buildCellDep(ckbChainConfig.SCRIPTS.JOYID_COBUILD_POC),
),
);
// add header deps
txSkeletonMutable.update("headerDeps", (headerDeps) =>
headerDeps.push(depositBlockHash, cell.blockHash),
);

// add witness
txSkeletonMutable.update("witnesses", (witnesses) =>
witnesses.push(packDaoWitnessArgs(0)),
);

let txSkeleton = txSkeletonMutable.asImmutable();

txSkeleton = await payFee(txSkeleton, from, ckbChainConfig);
return createBuildingPacketFromSkeleton(txSkeleton);
},
};
}

0 comments on commit 938d009

Please sign in to comment.