diff --git a/.gitignore b/.gitignore index 0f661fa..844801c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,13 @@ .env +# CKB +/ckb-miner.toml +/ckb.toml +/data +/default.db-options +/specs +/migrations/dev + # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies diff --git a/bin/generate-blocks.sh b/bin/generate-blocks.sh new file mode 100755 index 0000000..51674f8 --- /dev/null +++ b/bin/generate-blocks.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +CKB_RPC_URL="${CKB_RPC_URL:-http://127.0.0.1:8114}" + +function generate_one() { + curl --request POST \ + --url "$CKB_RPC_URL" \ + --data '{ + "id": 42, + "jsonrpc": "2.0", + "method": "generate_block", + "params": [] + }' + echo +} + +function generate_n() { + if [ $# -eq 0 ]; then + generate_one + else + for i in $(seq "$@"); do + generate_one + done + fi +} + +case "${1:-}" in +--url) + CKB_RPC_URL="$2" + shift + shift + generate_n "$@" + ;; +--url=*) + CKB_RPC_URL="${1#*=}" + shift + generate_n "$@" + ;; +--help) + echo 'usage: generate-blocks.sh [--help|--url CKB_RPC_URL] [count]' + ;; +*) + generate_n "$@" + ;; +esac diff --git a/bin/generate-epochs.sh b/bin/generate-epochs.sh new file mode 100755 index 0000000..6bdc243 --- /dev/null +++ b/bin/generate-epochs.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +CKB_RPC_URL="${CKB_RPC_URL:-http://127.0.0.1:8114}" + +function generate() { + local n="$(printf '%x' "${1:-1}")" + curl --request POST \ + --url http://127.0.0.1:8114/ \ + --data '{ + "id": 42, + "jsonrpc": "2.0", + "method": "generate_epochs", + "params": ["0x'"$n"'"] +}' +} + +case "${1:-}" in +--url) + CKB_RPC_URL="$2" + shift + shift + generate "$@" + ;; +--url=*) + CKB_RPC_URL="${1#*=}" + shift + generate "$@" + ;; +--help) + echo 'usage: generate-epochs.sh [--help|--url CKB_RPC_URL] [count]' + ;; +*) + generate "$@" + ;; +esac diff --git a/bin/use-env.sh b/bin/use-env.sh new file mode 100755 index 0000000..824c387 --- /dev/null +++ b/bin/use-env.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +case "${1:-}" in +--testnet) + shift + cat env.example + ;; +--mainnet) + shift + echo 'NEXT_PUBLIC_CKB_CHAIN="LINA"' + echo 'NEXT_PUBLIC_CKB_RPC_URL=" https://mainnet.ckb.dev/"' + ;; +*) + echo 'NEXT_PUBLIC_CKB_CHAIN="DEV"' + echo 'NEXT_PUBLIC_CKB_RPC_URL="http://127.0.0.1:8114/"' + ;; +esac + +sed -n \ + -e 's/,$//' \ + -e 's/^ *"type_id": /NEXT_PUBLIC_JOYID_COBUILD_POC_CODE_HASH=/p' \ + -e 's/^ *"tx_hash": /NEXT_PUBLIC_JOYID_COBUILD_POC_TX_HASH=/p' \ + "$@" diff --git a/deployment.toml b/deployment.toml index 8996ec2..a088549 100644 --- a/deployment.toml +++ b/deployment.toml @@ -7,5 +7,5 @@ location = { file = "build/release/joyid-cobuild-poc" } # For example the secp256k1 lock [lock] code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" -args = "0xe463d7c4cb28457b3a2f735d1d92a971b0f5a751" +args = "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7" hash_type = "type" diff --git a/docs/dev.md b/docs/dev.md new file mode 100644 index 0000000..eff7762 --- /dev/null +++ b/docs/dev.md @@ -0,0 +1,119 @@ +# Development Docs + +## Start Local Dev Chain + +Init a dev chain using the test account that has 20 billions of CKB tokens in the genesis block. + +```bash +ckb init -c dev --ba-arg 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 +``` + +Edit `specs/dev.toml` to append `Indexer` and `IntegrationTest` to the `modules` option under the `[rpc]` section. + +```toml +modules = ["Net", "Pool", "Miner", "Chain", "Stats", "Subscription", "Experiment", "Debug", "Indexer", "IntegrationTest"] +``` + +Import the test account into `ckb-cli` with empty password + +```bash +echo 'd00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc' > specs/miner.key +ckb-cli account import --privkey-path specs/miner.key { +# => "cell_tx_signatures": { +# => "0xc8328...": "0x92d7..." +# => } +# => } +``` + +Add the signatures to the info file `migrations/dev/deployment.json` manually. Attention that wrap the value in an array. + +```json + "cell_tx_signatures": { + "0xc8328...": ["0x92d7..."] + }, +``` + +Step 3: Send txs + +```bash +ckb-cli deploy apply-txs --info-file migrations/dev/deployment.json --migration-dir migrations/dev +``` + +Step 4: Mine 3 blocks to commit txs + +```bash +bin/generate-blocks.sh 3 +``` + +## Configure The Web App + +Generate .env file form the deployment. + +```bash +bin/use-env.sh migrations/dev/20*.json > .env +``` + +Start the local development server. + +```bash +pnpm dev +``` + +Connect JoyID and copy the address displayed at the top of the page. + +Transfer some CKB tokens from the miner account to the copied address. Replace `ckbt1qz...` with the real address in the following command. + +```bash +ckb-cli wallet transfer --skip-check-to-address --to-address ckt1qz... --capacity 300000 --privkey-path specs/miner.key +``` + +Mine some blocks to commit the transfer transaction. + +```bash +bin/generate-blocks.sh 3 +``` + +Now it's OK to run the web app with the local dev chain. You can run a miner process to generate blocks automatically in another terminal, or just run `bin/generate-blocks.sh 3` to manually commit transactions in the memory pool. + +To test the phase 2 withdraw, use the command `bin/generate-epochs.sh 180` to generate 180 epochs, which is a full DAO cycle. + +```bash +bin/generate_epochs.sh 180 +``` diff --git a/src/actions/deposit.js b/src/actions/deposit.js index 3a92fbc..46904ef 100644 --- a/src/actions/deposit.js +++ b/src/actions/deposit.js @@ -2,10 +2,10 @@ import { parseUnit } from "@ckb-lumos/bi"; import { depositDao } from "@/lib/cobuild/publishers"; -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; export default async function deposit(_prevState, formData, config) { - config = config ?? configFromEnv(process.env); + config = config ?? useConfig(); const from = formData.get("from"); const amount = parseUnit(formData.get("amount"), "ckb"); diff --git a/src/actions/fetch-assets.js b/src/actions/fetch-assets.js index a1d1dc5..dc2284e 100644 --- a/src/actions/fetch-assets.js +++ b/src/actions/fetch-assets.js @@ -1,11 +1,11 @@ "use server"; import { cache } from "react"; -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; import { fetchAssets as fetchAssetsWithConfig } from "@/lib/cobuild/assets-manager"; export async function fetchAssetsWithoutCache(address, config) { - config = config ?? configFromEnv(process.env); + config = config ?? useConfig(); return await fetchAssetsWithConfig(address, config); } diff --git a/src/actions/get-cell.js b/src/actions/get-cell.js index 11b98ba..e49688d 100644 --- a/src/actions/get-cell.js +++ b/src/actions/get-cell.js @@ -1,11 +1,11 @@ "use server"; import { cache } from "react"; -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; import { RPC } from "@ckb-lumos/rpc"; export async function getCellWithoutCache(outPoint, config) { - const { ckbRpcUrl } = config ?? configFromEnv(process.env); + const { ckbRpcUrl } = config ?? useConfig(); const rpc = new RPC(ckbRpcUrl); diff --git a/src/actions/get-header-by-number.js b/src/actions/get-header-by-number.js index c247e0f..611dedd 100644 --- a/src/actions/get-header-by-number.js +++ b/src/actions/get-header-by-number.js @@ -1,11 +1,11 @@ "use server"; import { cache } from "react"; -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; import { RPC } from "@ckb-lumos/rpc"; export async function getHeaderByNumberWithoutCache(blockNumber, config) { - const { ckbRpcUrl } = config ?? configFromEnv(process.env); + const { ckbRpcUrl } = config ?? useConfig(); const rpc = new RPC(ckbRpcUrl); return await rpc.getHeaderByNumber(blockNumber); diff --git a/src/actions/get-header.js b/src/actions/get-header.js index 0819398..3ac29a2 100644 --- a/src/actions/get-header.js +++ b/src/actions/get-header.js @@ -1,11 +1,11 @@ "use server"; import { cache } from "react"; -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; import { RPC } from "@ckb-lumos/rpc"; export async function getHeaderWithoutCache(blockHash, config) { - const { ckbRpcUrl } = config ?? configFromEnv(process.env); + const { ckbRpcUrl } = config ?? useConfig(); const rpc = new RPC(ckbRpcUrl); return await rpc.getHeader(blockHash); diff --git a/src/actions/get-tip-header.js b/src/actions/get-tip-header.js index ee18df2..94bdc40 100644 --- a/src/actions/get-tip-header.js +++ b/src/actions/get-tip-header.js @@ -1,11 +1,11 @@ "use server"; import { cache } from "react"; -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; import { RPC } from "@ckb-lumos/rpc"; export async function getTipHeaderWithoutCache(config) { - const { ckbRpcUrl } = config ?? configFromEnv(process.env); + const { ckbRpcUrl } = config ?? useConfig(); const rpc = new RPC(ckbRpcUrl); return await rpc.getTipHeader(); diff --git a/src/actions/send-tx.js b/src/actions/send-tx.js index c633e75..550221c 100644 --- a/src/actions/send-tx.js +++ b/src/actions/send-tx.js @@ -4,7 +4,7 @@ import { revalidatePath } from "next/cache"; import { RPC } from "@ckb-lumos/rpc"; import { blockchain, utils as lumosBaseUtils } from "@ckb-lumos/base"; -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; const { ckbHash } = lumosBaseUtils; @@ -37,7 +37,7 @@ async function sendTxInner(tx, txHash, { ckbRpcUrl }) { } export default async function sendTx(tx, config) { - config = config ?? configFromEnv(process.env); + config = config ?? useConfig(); const txHash = ckbHash(blockchain.RawTransaction.pack(tx)); try { diff --git a/src/actions/transfer.js b/src/actions/transfer.js index 1afc9ff..8b5f087 100644 --- a/src/actions/transfer.js +++ b/src/actions/transfer.js @@ -2,10 +2,10 @@ import { parseUnit } from "@ckb-lumos/bi"; import { transferCkb } from "@/lib/cobuild/publishers"; -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; export default async function transfer(_prevState, formData, config) { - config = config ?? configFromEnv(process.env); + config = config ?? useConfig(); const from = formData.get("from"); const to = formData.get("to"); diff --git a/src/actions/withdraw.js b/src/actions/withdraw.js index d93651b..8a26735 100644 --- a/src/actions/withdraw.js +++ b/src/actions/withdraw.js @@ -1,10 +1,10 @@ "use server"; import { withdrawDao } from "@/lib/cobuild/publishers"; -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; export default async function withdraw(from, cell, config) { - config = config ?? configFromEnv(process.env); + config = config ?? useConfig(); try { const buildingPacket = await withdrawDao(config)({ from, cell }); diff --git a/src/app/accounts/[address]/deposit/page.js b/src/app/accounts/[address]/deposit/page.js index ceae957..d869c9a 100644 --- a/src/app/accounts/[address]/deposit/page.js +++ b/src/app/accounts/[address]/deposit/page.js @@ -1,9 +1,9 @@ -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; import DepositForm from "./form"; export default function Deposit({ params: { address }, config }) { - config = config ?? configFromEnv(process.env); + config = config ?? useConfig(); return (
diff --git a/src/app/accounts/[address]/transfer/page.js b/src/app/accounts/[address]/transfer/page.js index dd520b5..076cc6c 100644 --- a/src/app/accounts/[address]/transfer/page.js +++ b/src/app/accounts/[address]/transfer/page.js @@ -1,9 +1,9 @@ -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; import TransferForm from "./form"; export default function Transfer({ params: { address }, config }) { - config = config ?? configFromEnv(process.env); + config = config ?? useConfig(); return (
diff --git a/src/app/accounts/[address]/withdraw/[txHash]/[index]/page.js b/src/app/accounts/[address]/withdraw/[txHash]/[index]/page.js index 3418f60..8e8650b 100644 --- a/src/app/accounts/[address]/withdraw/[txHash]/[index]/page.js +++ b/src/app/accounts/[address]/withdraw/[txHash]/[index]/page.js @@ -1,4 +1,4 @@ -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; import WithdrawForm from "./form"; @@ -6,7 +6,7 @@ export default function Withdraw({ params: { address, txHash, index }, config, }) { - config = config ?? configFromEnv(process.env); + config = config ?? useConfig(); const outPoint = { txHash: `0x${txHash}`, diff --git a/src/app/page.js b/src/app/page.js index 6f18b15..37aba49 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -1,8 +1,8 @@ -import { configFromEnv } from "@/lib/config"; +import { useConfig } from "@/lib/config"; import RootHeader from "./header"; export default function RootPage({ config }) { - config = config ?? configFromEnv(process.env); + config = config ?? useConfig(); return (
diff --git a/src/lib/cobuild/__tests__/lock-actions.test.js b/src/lib/cobuild/__tests__/lock-actions.test.js index e2afdc7..d9db733 100644 --- a/src/lib/cobuild/__tests__/lock-actions.test.js +++ b/src/lib/cobuild/__tests__/lock-actions.test.js @@ -1,10 +1,10 @@ import { number } from "@ckb-lumos/codec"; -import { defaultConfig } from "../../config"; +import { useConfig } from "../../config"; import { prepareLockActions } from "../lock-actions"; import { GeneralLockAction, chooseWitnessStore } from "../general-lock-actions"; -const { ckbChainConfig } = defaultConfig(); +const { ckbChainConfig } = useConfig(); test("cobuild layout", () => { const tx = { diff --git a/src/lib/config.js b/src/lib/config.js index 411baec..1fc9123 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -24,43 +24,64 @@ const CKB_CHAINS_CONFIGS = { }, }; -const NEXT_PUBLIC_PREFIX = "NEXT_PUBLIC_"; -const ENV_VARIABLE_NAMES = { - ckbChain: "CKB_CHAIN", - ckbRpcUrl: "CKB_RPC_URL", - ckbChainConfig: "CKB_CHAIN_CONFIG", -}; - -export function configFromEnv(env) { - const config = defaultConfig(); +// consider empty string as null +function presence(val) { + return val !== "" ? val : null; +} - for (const key in config) { - const envName = ENV_VARIABLE_NAMES[key]; - if (envName !== undefined) { - config[key] = - env[envName] ?? env[NEXT_PUBLIC_PREFIX + envName] ?? config[key]; - } +// assign source to dest, if source[key] is null or undefined, use dest[key] +function assign(dest, source) { + for (const key in source) { + dest[key] = source[key] ?? dest[key]; } + return dest; +} - config.ckbChainConfig ??= CKB_CHAINS_CONFIGS[config.ckbChain]; - if (typeof config.ckbChainConfig === "string") { - config.ckbChainConfig = JSON.load(config.ckbChainConfig); +function buildCkbChainConfig(ckbChain) { + if (ckbChain in CKB_CHAINS_CONFIGS) { + return CKB_CHAINS_CONFIGS[ckbChain]; } - return config; + // for custom env, duplicate from AGGRON4 + const template = CKB_CHAINS_CONFIGS.AGGRON4; + const JOYID_COBUILD_POC = assign( + { ...template.SCRIPTS.JOYID_COBUILD_POC }, + { + CODE_HASH: presence(process.env.NEXT_PUBLIC_JOYID_COBUILD_POC_CODE_HASH), + TX_HASH: presence(process.env.NEXT_PUBLIC_JOYID_COBUILD_POC_TX_HASH), + INDEX: presence(process.env.NEXT_PUBLIC_JOYID_COBUILD_POC_INDEX) ?? "0x0", + }, + ); + + return { + ...template, + EXPLORER_URL: null, + SCRIPTS: { + ...template.SCRIPTS, + JOYID_COBUILD_POC, + }, + }; } export const DEFAULT_CKB_CHAIN = "AGGRON4"; +export const DEFAULT_CKB_RPC_URL = "https://testnet.ckbapp.dev/"; // () => { // ckbChain, // ckbRpcUrl, // ckbChainConfig // } -export function defaultConfig() { - return { - ckbChain: DEFAULT_CKB_CHAIN, - ckbRpcUrl: "https://testnet.ckbapp.dev/", - ckbChainConfig: CKB_CHAINS_CONFIGS[DEFAULT_CKB_CHAIN], +export const useConfig = (() => { + const ckbChain = + presence(process.env.NEXT_PUBLIC_CKB_CHAIN) ?? DEFAULT_CKB_CHAIN; + const config = { + ckbChain, + ckbRpcUrl: + presence(process.env.NEXT_PUBLIC_CKB_RPC_URL) ?? DEFAULT_CKB_RPC_URL, + ckbChainConfig: buildCkbChainConfig(ckbChain), }; -} + console.log(JSON.stringify(config, null, 2)); + return () => { + return config; + }; +})();