diff --git a/.gitignore b/.gitignore index 0215f1f..0b4e30f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .env .env.* -/migrations +/migrations/dev +/migrations/joyid +/migrations/ckb_auth # CKB /ckb-miner.toml diff --git a/bin/deploy-to-dev-chain.sh b/bin/deploy-to-dev-chain.sh index c2cfd02..a12419a 100755 --- a/bin/deploy-to-dev-chain.sh +++ b/bin/deploy-to-dev-chain.sh @@ -16,29 +16,42 @@ if ! [ -f specs/miner.key ]; then echo "d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc" >specs/miner.key fi -rm -rf migrations/dev && mkdir -p migrations/dev -GENESIS_TX0="$(ckb list-hashes | sed -n 's/tx_hash = "\(.*\)"/\1/p' | head -1)" -sed "s/0x8f8c79eb6671709633fe6a46de93c0fedc9c1b8a6527a18d3983879542635c9f/$GENESIS_TX0/" deployment.toml >migrations/dev/deployment.toml - -ckb-cli deploy gen-txs --from-address ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwgx292hnvmn68xf779vmzrshpmm6epn4c0cgwga \ - --fee-rate 1000 --deployment-config migrations/dev/deployment.toml --info-file migrations/dev/deployment.json --migration-dir migrations/dev - -SIGNATURES="$(ckb-cli deploy sign-txs --info-file migrations/dev/deployment.json --privkey-path specs/miner.key --output-format json | sed -n 's/: \("[^"]*"\)/: [\1]/p')" -echo "$SIGNATURES" -CELLS_SIGNATURES="$(echo "$SIGNATURES" | head -1)" -DEP_GROUPS_SIGNATURES="$(echo "$SIGNATURES" | tail -1)" - -sed -i.bak \ - -e 's/"cell_tx_signatures": {}/"cell_tx_signatures": {'"$CELLS_SIGNATURES"'}/' \ - migrations/dev/deployment.json -sed -i.bak \ - -e 's/"dep_group_tx_signatures": {}/"dep_group_tx_signatures": {'"$DEP_GROUPS_SIGNATURES"'}/' \ - migrations/dev/deployment.json -rm -f migrations/dev/deployment.json.bak - -ckb-cli deploy apply-txs --info-file migrations/dev/deployment.json --migration-dir migrations/dev - -DEPLOY_RESULT_FILE="$(ls migrations/dev/*.json | grep -v deployment | head -n 1)" -bin/use-env.sh "$DEPLOY_RESULT_FILE" >.env - -bin/generate-blocks.sh 3 +function deploy() { + local DEPLOY_NAME="$1" + local MIGRATION_DIR="migrations/$DEPLOY_NAME" + local CONFIG_FILE="$MIGRATION_DIR/deployment.toml" + local INFO_FILE="$MIGRATION_DIR/deployment.json" + local TEMPLATE_FILE="migrations/templates/$DEPLOY_NAME.toml" + + rm -rf "$MIGRATION_DIR" && mkdir -p "$MIGRATION_DIR" + GENESIS_TX0="$(ckb list-hashes | sed -n 's/tx_hash = "\(.*\)"/\1/p' | head -1)" + sed "s/0x8f8c79eb6671709633fe6a46de93c0fedc9c1b8a6527a18d3983879542635c9f/$GENESIS_TX0/" "$TEMPLATE_FILE" >"$CONFIG_FILE" + + ckb-cli deploy gen-txs --from-address ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwgx292hnvmn68xf779vmzrshpmm6epn4c0cgwga \ + --fee-rate 1000 --deployment-config "$CONFIG_FILE" --info-file "$INFO_FILE" --migration-dir "$MIGRATION_DIR" + + SIGNATURES="$(ckb-cli deploy sign-txs --info-file "$INFO_FILE" --privkey-path specs/miner.key --output-format json | sed -n 's/: \("[^"]*"\)/: [\1]/p')" + echo "$SIGNATURES" + CELLS_SIGNATURES="$(echo "$SIGNATURES" | head -1)" + DEP_GROUPS_SIGNATURES="$(echo "$SIGNATURES" | tail -1)" + + sed -i.bak \ + -e 's/"cell_tx_signatures": {}/"cell_tx_signatures": {'"$CELLS_SIGNATURES"'}/' \ + "$INFO_FILE" + sed -i.bak \ + -e 's/"dep_group_tx_signatures": {}/"dep_group_tx_signatures": {'"$DEP_GROUPS_SIGNATURES"'}/' \ + "$INFO_FILE" + rm -f "${INFO_FILE}.bak" + + ckb-cli deploy apply-txs --info-file "$INFO_FILE" --migration-dir "$MIGRATION_DIR" +} + +deploy joyid +bin/generate-blocks.sh 4 +sleep 1 + +# try twice in case the indexer has not updated yet +deploy ckb_auth || deploy ckb_auth +bin/generate-blocks.sh 4 + +bin/use-env.sh >.env diff --git a/bin/download-joyid-cells.sh b/bin/download-contracts.sh similarity index 86% rename from bin/download-joyid-cells.sh rename to bin/download-contracts.sh index 4e2a9a6..dc5ce1c 100755 --- a/bin/download-joyid-cells.sh +++ b/bin/download-contracts.sh @@ -29,3 +29,6 @@ download joyid_dep3 0x95ecf9b41701b45d431657a67bbfa3f07ef7ceb53bf87097f3674e1a4a # secp256k1 data # download joyid_dep4 0x8f8c79eb6671709633fe6a46de93c0fedc9c1b8a6527a18d3983879542635c9f 3 download joyid_dep5 0x8b3255491f3c4dcc1cfca33d5c6bcaec5409efe4bbda243900f9580c47e0242e 1 + +download ckb_auth 0xd4f72f0504373ff8effadf44f92c46a0062774fb585ebcacc24eb47b98e2d66a 0 +download unisat_lock 0xe842b43df31c92d448fa345d60a6df3e03aaab19ef88921654bf95c673a26872 0 diff --git a/bin/use-env.sh b/bin/use-env.sh index 37216b6..feb0688 100755 --- a/bin/use-env.sh +++ b/bin/use-env.sh @@ -23,12 +23,25 @@ case "${1:-}" in ;; esac +JOYID_INFO_FILE="$(ls migrations/joyid/*.json | grep -v deployment | head -n 1)" +CKB_AUTH_INFO_FILE="$(ls migrations/ckb_auth/*.json | grep -v deployment | head -n 1)" + sed -n \ -e 's/,$//' \ -e 's/^ *"type_id": /NEXT_PUBLIC_JOYID_CODE_HASH=/p' \ - "$@" | head -1 + "$JOYID_INFO_FILE" | head -1 sed -n \ -e 's/,$//' \ -e 's/^ *"tx_hash": /NEXT_PUBLIC_JOYID_TX_HASH=/p' \ - "$@" | tail -1 + "$JOYID_INFO_FILE" | tail -1 + +sed -n \ + -e 's/,$//' \ + -e 's/^ *"type_id": "/NEXT_PUBLIC_UNISAT_CODE_HASH="/p' \ + "$CKB_AUTH_INFO_FILE" | head -1 + +sed -n \ + -e 's/,$//' \ + -e 's/^ *"tx_hash": /NEXT_PUBLIC_AUTH_TX_HASH=/p' \ + "$CKB_AUTH_INFO_FILE" | tail -1 diff --git a/docs/dev.md b/docs/dev.md index ffa08de..6f37d99 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -29,10 +29,10 @@ bin/generate-blocks.sh 20 ## Deploy Contracts to the Local Dev Chain -Download JoyID cells +Download contract cells ```bash -bin/download-joyid-cells.sh +bin/download-contracts.sh ``` Deploy using ckb-cli by running the following script @@ -51,7 +51,7 @@ Start the local development server. pnpm dev ``` -Connect JoyID and copy the address displayed at the top of the page. +Connect a wallet and copy the CKB 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. diff --git a/migrations/templates/ckb_auth.toml b/migrations/templates/ckb_auth.toml new file mode 100644 index 0000000..4d01603 --- /dev/null +++ b/migrations/templates/ckb_auth.toml @@ -0,0 +1,19 @@ +[[cells]] +name = "ckb_auth" +enable_type_id = false +location = { file = "build/release/ckb_auth" } +[[cells]] +name = "unisat_lock" +enable_type_id = true +location = { file = "build/release/unisat_lock" } + +# Dep group cells +[[dep_groups]] +name = "ckb_auth_locks" +cells = ["ckb_auth", "unisat_lock"] + +# The lock script set to output cells +[lock] +code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" +args = "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7" +hash_type = "type" diff --git a/deployment.toml b/migrations/templates/joyid.toml similarity index 100% rename from deployment.toml rename to migrations/templates/joyid.toml diff --git a/package.json b/package.json index d135106..9ae7c69 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "@joyid/ckb": "^0.0.2", "@tailwindcss/typography": "^0.5.10", "base64-js": "^1.5.1", + "bech32": "^2.0.0", + "bs58": "^5.0.0", "flowbite": "^2.2.0", "flowbite-react": "^0.7.2", "moment": "^2.30.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 606b58f..df6442e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,12 @@ dependencies: base64-js: specifier: ^1.5.1 version: 1.5.1 + bech32: + specifier: ^2.0.0 + version: 2.0.0 + bs58: + specifier: ^5.0.0 + version: 5.0.0 flowbite: specifier: ^2.2.0 version: 2.2.0 @@ -2091,6 +2097,10 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + /base-x@4.0.0: + resolution: {integrity: sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==} + dev: false + /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false @@ -2147,6 +2157,12 @@ packages: node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.22.2) + /bs58@5.0.0: + resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==} + dependencies: + base-x: 4.0.0 + dev: false + /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: diff --git a/src/actions/send-tx.js b/src/actions/send-tx.js index 550221c..87a1a1b 100644 --- a/src/actions/send-tx.js +++ b/src/actions/send-tx.js @@ -43,7 +43,7 @@ export default async function sendTx(tx, config) { try { const txState = await sendTxInner(tx, txHash, config); if (txState.txStatus.status === "committed") { - revalidatePath("/accounts/[address]", "layout"); + revalidatePath("/u/[wallet]/[connection]", "layout"); } return txState; } catch (err) { diff --git a/src/app/accounts/[address]/account-header.js b/src/app/accounts/[address]/account-header.js deleted file mode 100644 index 81c1d86..0000000 --- a/src/app/accounts/[address]/account-header.js +++ /dev/null @@ -1,15 +0,0 @@ -export default function AccountHeader({ address, config: { ckbChain } }) { - return ( -
-

{address}

- {ckbChain === "AGGRON4" ? ( -

- Claim CKB From{" "} - - Faucet - -

- ) : null} -
- ); -} diff --git a/src/app/accounts/[address]/claim/[txHash]/[index]/page.js b/src/app/accounts/[address]/claim/[txHash]/[index]/page.js deleted file mode 100644 index 605b485..0000000 --- a/src/app/accounts/[address]/claim/[txHash]/[index]/page.js +++ /dev/null @@ -1,21 +0,0 @@ -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 ( -
-

Claim

- -
- ); -} diff --git a/src/app/accounts/[address]/deposit/page.js b/src/app/accounts/[address]/deposit/page.js deleted file mode 100644 index d869c9a..0000000 --- a/src/app/accounts/[address]/deposit/page.js +++ /dev/null @@ -1,14 +0,0 @@ -import { useConfig } from "@/lib/config"; - -import DepositForm from "./form"; - -export default function Deposit({ params: { address }, config }) { - config = config ?? useConfig(); - - return ( -
-

Deposit

- -
- ); -} diff --git a/src/app/accounts/[address]/page.js b/src/app/accounts/[address]/page.js deleted file mode 100644 index 59ae182..0000000 --- a/src/app/accounts/[address]/page.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Suspense } from "react"; -import { useConfig } from "@/lib/config"; -import Assets, { AssetsFallback } from "./assets"; -import AccountHeader from "./account-header"; - -export default function Account({ params: { address }, config }) { - config = config ?? useConfig(); - - return ( - <> -
- -
-
- }> - - -
- - ); -} diff --git a/src/app/accounts/[address]/transfer/page.js b/src/app/accounts/[address]/transfer/page.js deleted file mode 100644 index 076cc6c..0000000 --- a/src/app/accounts/[address]/transfer/page.js +++ /dev/null @@ -1,14 +0,0 @@ -import { useConfig } from "@/lib/config"; - -import TransferForm from "./form"; - -export default function Transfer({ params: { address }, config }) { - config = config ?? useConfig(); - - return ( -
-

Transfer

- -
- ); -} diff --git a/src/app/connector.js b/src/app/connector.js new file mode 100644 index 0000000..d1fcc17 --- /dev/null +++ b/src/app/connector.js @@ -0,0 +1,104 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Alert, Button, Modal, ListGroup } from "flowbite-react"; + +import * as walletSelector from "@/lib/wallet/selector"; + +function getAccountName(connection) { + if (typeof connection === "string" || connection instanceof String) { + return connection; + } else { + return connection.title; + } +} + +export function ConnectorView({ + state, + processingWallet, + connect, + select, + reset, +}) { + return ( +
+ {state.error ? ( + + {state.error} + + ) : null} + {state.connections.length > 0 && processingWallet !== null ? ( + + + Choose {walletSelector.walletName(processingWallet)} Account + + + + {state.connections.map((c) => ( + select(processingWallet, c)} + key={getAccountName(c)} + className="break-all" + > + {getAccountName(c)} + + ))} + + + + ) : null} +
+ {Object.keys(walletSelector.providers).map((wallet) => ( + + ))} +
+
+ ); +} + +export default function Connector() { + const router = useRouter(); + const [processingWallet, setProcessingWallet] = useState(null); + const [state, setState] = useState({ connections: [], error: null }); + + const reset = (error = null) => { + setState({ connections: [], error }); + setProcessingWallet(null); + }; + + const select = (wallet, connection) => { + router.push(`/u/${wallet}/${connection}`); + }; + + const connect = async (wallet) => { + setProcessingWallet(wallet); + let connections = []; + try { + connections = await walletSelector.connect(wallet); + } catch (err) { + console.error(err.stack); + reset(err.toString()); + return; + } + + if (connections.length === 1) { + return select(wallet, connections[0]); + } + + setState({ + connections, + error: connections.length > 0 ? undefined : "No accounts selected", + }); + }; + + const childProps = { state, processingWallet, connect, select, reset }; + return ; +} diff --git a/src/app/header.js b/src/app/header.js deleted file mode 100644 index a944903..0000000 --- a/src/app/header.js +++ /dev/null @@ -1,34 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useRouter } from "next/navigation"; -import { Alert } from "flowbite-react"; - -import * as joyid from "@/lib/wallet/joyid"; -import SubmitButton from "@/components/submit-button"; - -export default function RootClientPage({ config }) { - const router = useRouter(); - const [error, setError] = useState(); - - const connect = async () => { - try { - const connection = await joyid.connect(); - const address = joyid.address(connection, config.ckbChainConfig); - router.push(`/accounts/${address}`); - } catch (err) { - setError(err.toString()); - } - }; - - return ( -
- {error ? ( - - {error} - - ) : null} - Connect -
- ); -} diff --git a/src/app/page.js b/src/app/page.js index 47261bc..89810ad 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -1,5 +1,5 @@ import { useConfig } from "@/lib/config"; -import RootHeader from "./header"; +import Connector from "./connector"; import Disclaimer from "./disclaimer.js"; export default function RootPage({ config }) { @@ -7,7 +7,7 @@ export default function RootPage({ config }) { return (
- +
); } diff --git a/src/app/u/[wallet]/[connection]/account-header.js b/src/app/u/[wallet]/[connection]/account-header.js new file mode 100644 index 0000000..a2e08b1 --- /dev/null +++ b/src/app/u/[wallet]/[connection]/account-header.js @@ -0,0 +1,32 @@ +import * as walletSelector from "@/lib/wallet/selector"; +import SignOut from "./sign-out"; + +export default function AccountHeader({ + address, + wallet, + connection, + config: { ckbChain }, +}) { + const walletName = walletSelector.walletName(wallet); + + return ( +
+ + +

+ CKB Address: {address} +

+ {ckbChain === "AGGRON4" ? ( +

+ Claim CKB From{" "} + + Faucet + +

+ ) : null} +
+ ); +} diff --git a/src/app/accounts/[address]/assets.js b/src/app/u/[wallet]/[connection]/assets.js similarity index 62% rename from src/app/accounts/[address]/assets.js rename to src/app/u/[wallet]/[connection]/assets.js index 74c686f..9190655 100644 --- a/src/app/accounts/[address]/assets.js +++ b/src/app/u/[wallet]/[connection]/assets.js @@ -7,7 +7,7 @@ import Loading from "./loading"; export const revalidate = 12; -export function CkbSection({ address, ckbBalance }) { +export function CkbSection({ wallet, connection, address, ckbBalance }) { return (

CKB

@@ -16,7 +16,7 @@ export function CkbSection({ address, ckbBalance }) {

- +
); } @@ -58,13 +63,14 @@ export function AssetsFallback() { ); } -export default async function Assets({ address }) { +export default async function Assets({ wallet, connection, address }) { const { ckbBalance, daoCells } = await fetchAssetsWithCache(address); + const childProps = { wallet, connection, address }; return ( <> - - + + ); } diff --git a/src/app/accounts/[address]/claim/[txHash]/[index]/form.js b/src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/form.js similarity index 95% rename from src/app/accounts/[address]/claim/[txHash]/[index]/form.js rename to src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/form.js index 8edde11..9d802b9 100644 --- a/src/app/accounts/[address]/claim/[txHash]/[index]/form.js +++ b/src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/form.js @@ -81,7 +81,13 @@ function LoadCell({ outPoint, pending, onConfirm }) { return cell ? : ; } -export default function ClaimForm({ address, outPoint, config }) { +export default function ClaimForm({ + wallet, + connection, + address, + outPoint, + config, +}) { const router = useRouter(); const [formState, setFormState] = useState({}); const [pending, setPending] = useState(false); @@ -118,6 +124,8 @@ export default function ClaimForm({ address, outPoint, config }) { ) { return ( +

Claim

+ + + ); +} diff --git a/src/app/accounts/[address]/dao-cells.js b/src/app/u/[wallet]/[connection]/dao-cells.js similarity index 78% rename from src/app/accounts/[address]/dao-cells.js rename to src/app/u/[wallet]/[connection]/dao-cells.js index b68ed1a..3fd408e 100644 --- a/src/app/accounts/[address]/dao-cells.js +++ b/src/app/u/[wallet]/[connection]/dao-cells.js @@ -17,7 +17,7 @@ function cellKey(cell) { )}`; } -export function DepositRow({ address, cell, tipHeader }) { +export function DepositRow({ wallet, connection, cell, tipHeader }) { const depositHeader = useHeaderByNumber(cell.blockNumber); const key = cellKey(cell); @@ -46,7 +46,7 @@ export function DepositRow({ address, cell, tipHeader }) {