From a63cb278894c84dc59ff0d6beac7036b3573f545 Mon Sep 17 00:00:00 2001 From: ian Date: Fri, 5 Jan 2024 16:28:51 +0800 Subject: [PATCH 1/6] deploy unisat lock to dev chain --- .gitignore | 4 +- bin/deploy-to-dev-chain.sh | 65 +++++++++++-------- ...d-joyid-cells.sh => download-contracts.sh} | 3 + bin/use-env.sh | 17 ++++- docs/dev.md | 6 +- migrations/templates/ckb_auth.toml | 19 ++++++ .../templates/joyid.toml | 0 7 files changed, 82 insertions(+), 32 deletions(-) rename bin/{download-joyid-cells.sh => download-contracts.sh} (86%) create mode 100644 migrations/templates/ckb_auth.toml rename deployment.toml => migrations/templates/joyid.toml (100%) 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 From 09a06a612819da495215e950dd7ddfe174b8cc39 Mon Sep 17 00:00:00 2001 From: ian Date: Fri, 5 Jan 2024 19:05:10 +0800 Subject: [PATCH 2/6] setup wallet dispatcher and handle unisat address --- package.json | 2 + pnpm-lock.yaml | 16 +++ src/app/accounts/[address]/account-header.js | 15 --- src/app/accounts/[address]/deposit/page.js | 14 --- src/app/accounts/[address]/transfer/page.js | 14 --- src/app/connector.js | 104 ++++++++++++++++++ src/app/header.js | 34 ------ src/app/page.js | 4 +- .../u/[wallet]/[connection]/account-header.js | 31 ++++++ .../[wallet]/[connection]}/assets.js | 0 .../claim/[txHash]/[index]/form.js | 0 .../claim/[txHash]/[index]/loading.js | 0 .../claim/[txHash]/[index]/page.js | 11 +- .../[wallet]/[connection]}/dao-cells.js | 0 .../[wallet]/[connection]}/deposit/form.js | 0 .../u/[wallet]/[connection]/deposit/page.js | 20 ++++ .../[wallet]/[connection]}/loading.js | 0 .../[wallet]/[connection]}/page.js | 15 ++- .../[wallet]/[connection]}/sign-form.js | 0 .../[connection]}/submit-building-packet.js | 0 .../[wallet]/[connection]}/transfer/form.js | 0 .../u/[wallet]/[connection]/transfer/page.js | 20 ++++ .../withdraw/[txHash]/[index]/form.js | 0 .../withdraw/[txHash]/[index]/loading.js | 0 .../withdraw/[txHash]/[index]/page.js | 8 +- src/lib/config.js | 28 ++++- src/lib/wallet/joyid.js | 69 +++--------- src/lib/wallet/selector.js | 21 ++++ src/lib/wallet/unisat.js | 66 +++++++++++ 29 files changed, 355 insertions(+), 137 deletions(-) delete mode 100644 src/app/accounts/[address]/account-header.js delete mode 100644 src/app/accounts/[address]/deposit/page.js delete mode 100644 src/app/accounts/[address]/transfer/page.js create mode 100644 src/app/connector.js delete mode 100644 src/app/header.js create mode 100644 src/app/u/[wallet]/[connection]/account-header.js rename src/app/{accounts/[address] => u/[wallet]/[connection]}/assets.js (100%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/claim/[txHash]/[index]/form.js (100%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/claim/[txHash]/[index]/loading.js (100%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/claim/[txHash]/[index]/page.js (58%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/dao-cells.js (100%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/deposit/form.js (100%) create mode 100644 src/app/u/[wallet]/[connection]/deposit/page.js rename src/app/{accounts/[address] => u/[wallet]/[connection]}/loading.js (100%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/page.js (51%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/sign-form.js (100%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/submit-building-packet.js (100%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/transfer/form.js (100%) create mode 100644 src/app/u/[wallet]/[connection]/transfer/page.js rename src/app/{accounts/[address] => u/[wallet]/[connection]}/withdraw/[txHash]/[index]/form.js (100%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/withdraw/[txHash]/[index]/loading.js (100%) rename src/app/{accounts/[address] => u/[wallet]/[connection]}/withdraw/[txHash]/[index]/page.js (66%) create mode 100644 src/lib/wallet/selector.js create mode 100644 src/lib/wallet/unisat.js 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/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]/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]/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..6d780ec --- /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, + processingWalletSlug, + connect, + select, + reset, +}) { + return ( +
+ {state.error ? ( + + {state.error} + + ) : null} + {state.connections.length > 0 && processingWalletSlug !== null ? ( + + + Choose {walletSelector.walletName(processingWalletSlug)} Account + + + + {state.connections.map((c) => ( + select(processingWalletSlug, c)} + key={getAccountName(c)} + className="break-all" + > + {getAccountName(c)} + + ))} + + + + ) : null} +
+ {Object.keys(walletSelector.providers).map((walletSlug) => ( + + ))} +
+
+ ); +} + +export default function Connector() { + const router = useRouter(); + const [processingWalletSlug, setProcessingWalletSlug] = useState(null); + const [state, setState] = useState({ connections: [], error: null }); + + const reset = (error = null) => { + setState({ connections: [], error }); + setProcessingWalletSlug(null); + }; + + const select = (walletSlug, connection) => { + router.push(`/u/${walletSlug}/${connection}`); + }; + + const connect = async (walletSlug) => { + setProcessingWalletSlug(walletSlug); + let connections = []; + try { + connections = await walletSelector.connect(walletSlug); + } catch (err) { + console.error(err.stack); + reset(err.toString()); + return; + } + + if (connections.length === 1) { + return select(walletSlug, connections[0]); + } + + setState({ + connections, + error: connections.length > 0 ? undefined : "No accounts selected", + }); + }; + + const childProps = { state, processingWalletSlug, 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..9c145c6 --- /dev/null +++ b/src/app/u/[wallet]/[connection]/account-header.js @@ -0,0 +1,31 @@ +import * as walletSelector from "@/lib/wallet/selector"; + +export default function AccountHeader({ + address, + walletSlug, + connection, + config: { ckbChain }, +}) { + const walletName = walletSelector.walletName(walletSlug); + + return ( +
+

+ CKB Address: {address} +

+ {address !== connection ? ( +

+ {walletName} Address: {connection} +

+ ) : null} + {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 100% rename from src/app/accounts/[address]/assets.js rename to src/app/u/[wallet]/[connection]/assets.js 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 100% rename from src/app/accounts/[address]/claim/[txHash]/[index]/form.js rename to src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/form.js diff --git a/src/app/accounts/[address]/claim/[txHash]/[index]/loading.js b/src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/loading.js similarity index 100% rename from src/app/accounts/[address]/claim/[txHash]/[index]/loading.js rename to src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/loading.js diff --git a/src/app/accounts/[address]/claim/[txHash]/[index]/page.js b/src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/page.js similarity index 58% rename from src/app/accounts/[address]/claim/[txHash]/[index]/page.js rename to src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/page.js index 605b485..d640bbe 100644 --- a/src/app/accounts/[address]/claim/[txHash]/[index]/page.js +++ b/src/app/u/[wallet]/[connection]/claim/[txHash]/[index]/page.js @@ -1,9 +1,18 @@ import { useConfig } from "@/lib/config"; +import * as walletSelector from "@/lib/wallet/selector"; import ClaimForm from "./form"; -export default function Claim({ params: { address, txHash, index }, config }) { +export default function Claim({ + params: { wallet, connection, txHash, index }, + config, +}) { config = config ?? useConfig(); + const address = walletSelector.address( + wallet, + connection, + config.ckbChainConfig, + ); const outPoint = { txHash: `0x${txHash}`, diff --git a/src/app/accounts/[address]/dao-cells.js b/src/app/u/[wallet]/[connection]/dao-cells.js similarity index 100% rename from src/app/accounts/[address]/dao-cells.js rename to src/app/u/[wallet]/[connection]/dao-cells.js diff --git a/src/app/accounts/[address]/deposit/form.js b/src/app/u/[wallet]/[connection]/deposit/form.js similarity index 100% rename from src/app/accounts/[address]/deposit/form.js rename to src/app/u/[wallet]/[connection]/deposit/form.js diff --git a/src/app/u/[wallet]/[connection]/deposit/page.js b/src/app/u/[wallet]/[connection]/deposit/page.js new file mode 100644 index 0000000..60b48de --- /dev/null +++ b/src/app/u/[wallet]/[connection]/deposit/page.js @@ -0,0 +1,20 @@ +import { useConfig } from "@/lib/config"; +import * as walletSelector from "@/lib/wallet/selector"; + +import DepositForm from "./form"; + +export default function Deposit({ params: { wallet, connection }, config }) { + config = config ?? useConfig(); + const address = walletSelector.address( + wallet, + connection, + config.ckbChainConfig, + ); + + return ( +
+

Deposit

+ +
+ ); +} diff --git a/src/app/accounts/[address]/loading.js b/src/app/u/[wallet]/[connection]/loading.js similarity index 100% rename from src/app/accounts/[address]/loading.js rename to src/app/u/[wallet]/[connection]/loading.js diff --git a/src/app/accounts/[address]/page.js b/src/app/u/[wallet]/[connection]/page.js similarity index 51% rename from src/app/accounts/[address]/page.js rename to src/app/u/[wallet]/[connection]/page.js index 59ae182..7e003e3 100644 --- a/src/app/accounts/[address]/page.js +++ b/src/app/u/[wallet]/[connection]/page.js @@ -1,15 +1,26 @@ import { Suspense } from "react"; import { useConfig } from "@/lib/config"; +import * as walletSelector from "@/lib/wallet/selector"; import Assets, { AssetsFallback } from "./assets"; import AccountHeader from "./account-header"; -export default function Account({ params: { address }, config }) { +export default function Account({ params: { wallet, connection }, config }) { config = config ?? useConfig(); + const address = walletSelector.address( + wallet, + connection, + config.ckbChainConfig, + ); return ( <>
- +
}> diff --git a/src/app/accounts/[address]/sign-form.js b/src/app/u/[wallet]/[connection]/sign-form.js similarity index 100% rename from src/app/accounts/[address]/sign-form.js rename to src/app/u/[wallet]/[connection]/sign-form.js diff --git a/src/app/accounts/[address]/submit-building-packet.js b/src/app/u/[wallet]/[connection]/submit-building-packet.js similarity index 100% rename from src/app/accounts/[address]/submit-building-packet.js rename to src/app/u/[wallet]/[connection]/submit-building-packet.js diff --git a/src/app/accounts/[address]/transfer/form.js b/src/app/u/[wallet]/[connection]/transfer/form.js similarity index 100% rename from src/app/accounts/[address]/transfer/form.js rename to src/app/u/[wallet]/[connection]/transfer/form.js diff --git a/src/app/u/[wallet]/[connection]/transfer/page.js b/src/app/u/[wallet]/[connection]/transfer/page.js new file mode 100644 index 0000000..d71c7da --- /dev/null +++ b/src/app/u/[wallet]/[connection]/transfer/page.js @@ -0,0 +1,20 @@ +import { useConfig } from "@/lib/config"; +import * as walletSelector from "@/lib/wallet/selector"; + +import TransferForm from "./form"; + +export default function Transfer({ params: { wallet, connection }, config }) { + config = config ?? useConfig(); + const address = walletSelector.address( + wallet, + connection, + config.ckbChainConfig, + ); + + return ( +
+

Transfer

+ +
+ ); +} diff --git a/src/app/accounts/[address]/withdraw/[txHash]/[index]/form.js b/src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/form.js similarity index 100% rename from src/app/accounts/[address]/withdraw/[txHash]/[index]/form.js rename to src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/form.js diff --git a/src/app/accounts/[address]/withdraw/[txHash]/[index]/loading.js b/src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/loading.js similarity index 100% rename from src/app/accounts/[address]/withdraw/[txHash]/[index]/loading.js rename to src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/loading.js diff --git a/src/app/accounts/[address]/withdraw/[txHash]/[index]/page.js b/src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/page.js similarity index 66% rename from src/app/accounts/[address]/withdraw/[txHash]/[index]/page.js rename to src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/page.js index 8e8650b..aa7940b 100644 --- a/src/app/accounts/[address]/withdraw/[txHash]/[index]/page.js +++ b/src/app/u/[wallet]/[connection]/withdraw/[txHash]/[index]/page.js @@ -1,12 +1,18 @@ import { useConfig } from "@/lib/config"; +import * as walletSelector from "@/lib/wallet/selector"; import WithdrawForm from "./form"; export default function Withdraw({ - params: { address, txHash, index }, + params: { wallet, connection, txHash, index }, config, }) { config = config ?? useConfig(); + const address = walletSelector.address( + wallet, + connection, + config.ckbChainConfig, + ); const outPoint = { txHash: `0x${txHash}`, diff --git a/src/lib/config.js b/src/lib/config.js index 8976f59..ed66309 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -15,6 +15,21 @@ const CKB_CHAINS_CONFIGS = { INDEX: "0x0", DEP_TYPE: "depGroup", }, + AUTH: { + TX_HASH: + "0xd4f72f0504373ff8effadf44f92c46a0062774fb585ebcacc24eb47b98e2d66a", + INDEX: "0x0", + DEP_TYPE: "code", + }, + UNISAT: { + CODE_HASH: + "0xd7aac16927b2d572b3803c1f68e49d082d3acc2af2614c9be752ff9cec5dc3ea", + HASH_TYPE: "data1", + TX_HASH: + "0xe842b43df31c92d448fa345d60a6df3e03aaab19ef88921654bf95c673a26872", + INDEX: "0x0", + DEP_TYPE: "code", + }, }, }, }; @@ -45,7 +60,15 @@ function buildCkbChainConfig(ckbChain) { { CODE_HASH: presence(process.env.NEXT_PUBLIC_JOYID_CODE_HASH), TX_HASH: presence(process.env.NEXT_PUBLIC_JOYID_TX_HASH), - INDEX: presence(process.env.NEXT_PUBLIC_JOYID_INDEX) ?? "0x0", + }, + ); + const UNISAT = assign( + { ...template.SCRIPTS.JOYID }, + { + CODE_HASH: presence(process.env.NEXT_PUBLIC_UNISAT_CODE_HASH), + HASH_TYPE: "type", + TX_HASH: presence(process.env.NEXT_PUBLIC_AUTH_TX_HASH), + DEP_TYPE: "depGroup", }, ); @@ -61,7 +84,8 @@ function buildCkbChainConfig(ckbChain) { EXPLORER_URL: null, SCRIPTS: { JOYID, - JOYID_APP: template.SCRIPTS.JOYID, + UNISAT, + AUTH: template.SCRIPTS.AUTH, DAO: { ...template.SCRIPTS.DAO, TX_HASH: tx0, diff --git a/src/lib/wallet/joyid.js b/src/lib/wallet/joyid.js index b0e2491..0515b59 100644 --- a/src/lib/wallet/joyid.js +++ b/src/lib/wallet/joyid.js @@ -1,53 +1,35 @@ import * as joyid from "@joyid/ckb"; import { bytes } from "@ckb-lumos/codec"; -import { utils as lumosBaseUtils } from "@ckb-lumos/base"; import * as lumosHelpers from "@ckb-lumos/helpers"; import base64 from "base64-js"; -const ckbHash = lumosBaseUtils.ckbHash; - -function buildScript(pubkey, scriptInfo) { - // 0001: webauthn - // + first 20 bytes of pubkey hash - const args = `0x0001${ckbHash(pubkey).substring(2, 42)}`; - return { - codeHash: scriptInfo.CODE_HASH, - hashType: scriptInfo.HASH_TYPE, - args, - }; -} +export const title = "Joyid"; // Connects to the wallet. export async function connect() { - return await joyid.connect(); + const connection = await joyid.connect(); + return [connection.address]; } // Gets the CKB address. -// -// Calls this function only when wallet is connected. export function address(connection, ckbChainConfig) { - if (connection !== null && connection !== undefined) { - // for non-dev, just return the offical address - if (ckbChainConfig.SCRIPTS.JOYID_APP === undefined) { - return connection.address; - } - - const scriptInfo = ckbChainConfig.SCRIPTS.JOYID; - const script = buildScript(`0x${connection.pubkey}`, scriptInfo); - return lumosHelpers.encodeToAddress(script, { - config: ckbChainConfig, - }); - } - - return null; + const { args } = lumosHelpers.addressToScript(connection, { + config: ckbChainConfig, + }); + const scriptInfo = ckbChainConfig.SCRIPTS.JOYID; + const script = { + codeHash: scriptInfo.CODE_HASH, + hashType: scriptInfo.HASH_TYPE, + args, + }; + return lumosHelpers.encodeToAddress(script, { + config: ckbChainConfig, + }); } // Calls this function only when wallet is connected. -export async function sign(address, message, ckbChainConfig) { - const resp = await joyid.signChallenge( - message, - toJoyidAddress(address, ckbChainConfig), - ); +export async function sign(connection, message) { + const resp = await joyid.signChallenge(message, connection); const seal = ["0x01", resp.pubkey]; seal.push(bytes.hexify(signatureFromDer(resp.signature)).substring(2)); @@ -55,23 +37,6 @@ export async function sign(address, message, ckbChainConfig) { return seal.join(""); } -function toJoyidAddress(address, ckbChainConfig) { - if (ckbChainConfig.SCRIPTS.JOYID_APP === undefined) { - return address; - } - - const scriptInfo = ckbChainConfig.SCRIPTS.JOYID_APP; - const { args } = lumosHelpers.addressToScript(address, { - config: ckbChainConfig, - }); - const joyidScript = { - codeHash: scriptInfo.CODE_HASH, - hashType: scriptInfo.HASH_TYPE, - args, - }; - return lumosHelpers.encodeToAddress(joyidScript, { config: ckbChainConfig }); -} - function urlSafeBase64Decode(str) { const remainder = str.length % 4; if (remainder !== 0) { diff --git a/src/lib/wallet/selector.js b/src/lib/wallet/selector.js new file mode 100644 index 0000000..f3619fa --- /dev/null +++ b/src/lib/wallet/selector.js @@ -0,0 +1,21 @@ +import * as joyid from "./joyid"; +import * as unisat from "./unisat"; + +export const providers = { joyid, unisat }; + +export function walletName(slug) { + return providers[slug].title; +} + +export async function connect(slug) { + return await providers[slug].connect(); +} + +// Wallet connection to address +export function address(slug, connection, ckbChainConfig) { + return providers[slug].address(connection, ckbChainConfig); +} + +export async function sign(slug, connection, message, ckbChainConfig) { + return await providers[slug].connect(connection, message, ckbChainConfig); +} diff --git a/src/lib/wallet/unisat.js b/src/lib/wallet/unisat.js new file mode 100644 index 0000000..1945dff --- /dev/null +++ b/src/lib/wallet/unisat.js @@ -0,0 +1,66 @@ +import { bech32 } from "bech32"; +import * as bs58 from "bs58"; +import * as lumosHelpers from "@ckb-lumos/helpers"; +import { bytes } from "@ckb-lumos/codec"; + +export const title = "UniSat"; + +function isSupported(btcAddress) { + return ( + btcAddress.startsWith("bc1q") || + btcAddress.startsWith("3") || + btcAddress.startsWith("1") || + btcAddress.startsWith("tb1q") || + btcAddress.startsWith("2") || + btcAddress.startsWith("n") + ); +} + +// Connects to the wallet. +export async function connect() { + if (window.unisat !== null && window.unisat !== undefined) { + const accounts = await unisat.requestAccounts(); + const availableAccounts = accounts.filter(isSupported); + if (accounts.length > 0 && availableAccounts.length === 0) { + throw new Error( + "Please connect LIVENET and choose an address type that is NOT Taproot (P2TR).", + ); + } + return availableAccounts; + } else { + throw new Error("Please install UniSat first!"); + } +} + +// Gets the CKB address. +// +// Calls this function only when wallet is connected. +export function address(btcAddress, ckbChainConfig) { + let args = "0x04"; + if (btcAddress.startsWith("bc1q") || btcAddress.startsWith("tb1q")) { + // NativeSegwit + args += bytes + .hexify(bech32.fromWords(bech32.decode(btcAddress).words.slice(1))) + .slice(2); + } else if (btcAddress.startsWith("3") || btcAddress.startsWith("2")) { + // NestedSegwit + args += bytes.hexify(bs58.decode(btcAddress).slice(1, 21)).slice(2); + } else if (btcAddress.startsWith("1") || btcAddress.startsWith("n")) { + // Legacy + args += bytes.hexify(bs58.decode(btcAddress).slice(1, 21)).slice(2); + } else { + throw new Error( + "Please connect LIVENET and choose an address type that is NOT Taproot (P2TR).", + ); + } + + const scriptInfo = ckbChainConfig.SCRIPTS.UNISAT; + const script = { + codeHash: scriptInfo.CODE_HASH, + hashType: scriptInfo.HASH_TYPE, + args: args, + }; + return lumosHelpers.encodeToAddress(script, { + config: ckbChainConfig, + }); +} From 6a1e0e907fa2a094a12112c45590d738d5291923 Mon Sep 17 00:00:00 2001 From: ian Date: Fri, 5 Jan 2024 19:28:41 +0800 Subject: [PATCH 3/6] add sign out button --- .../u/[wallet]/[connection]/account-header.js | 11 ++++++----- src/app/u/[wallet]/[connection]/sign-out.js | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 src/app/u/[wallet]/[connection]/sign-out.js diff --git a/src/app/u/[wallet]/[connection]/account-header.js b/src/app/u/[wallet]/[connection]/account-header.js index 9c145c6..e73163f 100644 --- a/src/app/u/[wallet]/[connection]/account-header.js +++ b/src/app/u/[wallet]/[connection]/account-header.js @@ -1,4 +1,5 @@ import * as walletSelector from "@/lib/wallet/selector"; +import SignOut from "./sign-out"; export default function AccountHeader({ address, @@ -10,14 +11,14 @@ export default function AccountHeader({ return (
+ +

CKB Address: {address}

- {address !== connection ? ( -

- {walletName} Address: {connection} -

- ) : null} {ckbChain === "AGGRON4" ? (

Claim CKB From{" "} diff --git a/src/app/u/[wallet]/[connection]/sign-out.js b/src/app/u/[wallet]/[connection]/sign-out.js new file mode 100644 index 0000000..fbec64c --- /dev/null +++ b/src/app/u/[wallet]/[connection]/sign-out.js @@ -0,0 +1,18 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { Dropdown } from "flowbite-react"; + +export default function SignOut({ walletName, connection }) { + const router = useRouter(); + return ( +

+ ); +} From f5436afba270bccf7d20920e94891e060b16de4a Mon Sep 17 00:00:00 2001 From: ian Date: Fri, 5 Jan 2024 21:44:39 +0800 Subject: [PATCH 4/6] support unisat --- src/app/connector.js | 38 +++++----- .../u/[wallet]/[connection]/account-header.js | 4 +- src/app/u/[wallet]/[connection]/assets.js | 11 +-- src/app/u/[wallet]/[connection]/page.js | 4 +- src/app/u/[wallet]/[connection]/sign-form.js | 11 ++- .../u/[wallet]/[connection]/transfer/form.js | 4 +- .../u/[wallet]/[connection]/transfer/page.js | 7 +- src/lib/base64.js | 9 +++ src/lib/cobuild/fee-manager.js | 8 ++ src/lib/cobuild/lock-actions.js | 13 +++- .../cobuild/react/building-packet-review.js | 8 ++ .../init-lumos-common-scripts.js | 35 +++++++-- src/lib/wallet/btc-wallet.js | 76 +++++++++++++++++++ src/lib/wallet/joyid.js | 13 +--- src/lib/wallet/selector.js | 2 +- src/lib/wallet/unisat.js | 62 ++++----------- 16 files changed, 203 insertions(+), 102 deletions(-) create mode 100644 src/lib/base64.js create mode 100644 src/lib/wallet/btc-wallet.js diff --git a/src/app/connector.js b/src/app/connector.js index 6d780ec..d1fcc17 100644 --- a/src/app/connector.js +++ b/src/app/connector.js @@ -16,7 +16,7 @@ function getAccountName(connection) { export function ConnectorView({ state, - processingWalletSlug, + processingWallet, connect, select, reset, @@ -28,16 +28,16 @@ export function ConnectorView({ {state.error} ) : null} - {state.connections.length > 0 && processingWalletSlug !== null ? ( + {state.connections.length > 0 && processingWallet !== null ? ( - Choose {walletSelector.walletName(processingWalletSlug)} Account + Choose {walletSelector.walletName(processingWallet)} Account {state.connections.map((c) => ( select(processingWalletSlug, c)} + onClick={() => select(processingWallet, c)} key={getAccountName(c)} className="break-all" > @@ -49,14 +49,14 @@ export function ConnectorView({ ) : null}
- {Object.keys(walletSelector.providers).map((walletSlug) => ( + {Object.keys(walletSelector.providers).map((wallet) => ( ))}
@@ -66,23 +66,23 @@ export function ConnectorView({ export default function Connector() { const router = useRouter(); - const [processingWalletSlug, setProcessingWalletSlug] = useState(null); + const [processingWallet, setProcessingWallet] = useState(null); const [state, setState] = useState({ connections: [], error: null }); const reset = (error = null) => { setState({ connections: [], error }); - setProcessingWalletSlug(null); + setProcessingWallet(null); }; - const select = (walletSlug, connection) => { - router.push(`/u/${walletSlug}/${connection}`); + const select = (wallet, connection) => { + router.push(`/u/${wallet}/${connection}`); }; - const connect = async (walletSlug) => { - setProcessingWalletSlug(walletSlug); + const connect = async (wallet) => { + setProcessingWallet(wallet); let connections = []; try { - connections = await walletSelector.connect(walletSlug); + connections = await walletSelector.connect(wallet); } catch (err) { console.error(err.stack); reset(err.toString()); @@ -90,7 +90,7 @@ export default function Connector() { } if (connections.length === 1) { - return select(walletSlug, connections[0]); + return select(wallet, connections[0]); } setState({ @@ -99,6 +99,6 @@ export default function Connector() { }); }; - const childProps = { state, processingWalletSlug, connect, select, reset }; + const childProps = { state, processingWallet, connect, select, reset }; return ; } diff --git a/src/app/u/[wallet]/[connection]/account-header.js b/src/app/u/[wallet]/[connection]/account-header.js index e73163f..a2e08b1 100644 --- a/src/app/u/[wallet]/[connection]/account-header.js +++ b/src/app/u/[wallet]/[connection]/account-header.js @@ -3,11 +3,11 @@ import SignOut from "./sign-out"; export default function AccountHeader({ address, - walletSlug, + wallet, connection, config: { ckbChain }, }) { - const walletName = walletSelector.walletName(walletSlug); + const walletName = walletSelector.walletName(wallet); return (
diff --git a/src/app/u/[wallet]/[connection]/assets.js b/src/app/u/[wallet]/[connection]/assets.js index 74c686f..acd1978 100644 --- a/src/app/u/[wallet]/[connection]/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 }) {

); } diff --git a/src/lib/base64.js b/src/lib/base64.js new file mode 100644 index 0000000..55565db --- /dev/null +++ b/src/lib/base64.js @@ -0,0 +1,9 @@ +import base64 from "base64-js"; + +export function urlSafeBase64Decode(str) { + const remainder = str.length % 4; + if (remainder !== 0) { + str = str + "=".repeat(4 - remainder); + } + return base64.toByteArray(str); +} diff --git a/src/lib/cobuild/fee-manager.js b/src/lib/cobuild/fee-manager.js index d1e6d62..23686a6 100644 --- a/src/lib/cobuild/fee-manager.js +++ b/src/lib/cobuild/fee-manager.js @@ -59,6 +59,14 @@ function storeWitnessForFeeEstimation( // Variable length, but 500 is usually enough. () => bytes.hexify(new Uint8Array(500)), ); + } else if (script.codeHash === ckbChainConfig.SCRIPTS.UNISAT.CODE_HASH) { + return generalLockActions.storeWitnessForFeeEstimation( + buildingPacket, + scriptHash, + inputIndices, + // Variable length, but 500 is usually enough. + () => `0x${"0".repeat(65 * 2)}`, + ); } throw new Error( diff --git a/src/lib/cobuild/lock-actions.js b/src/lib/cobuild/lock-actions.js index 6ba0d4e..ca37421 100644 --- a/src/lib/cobuild/lock-actions.js +++ b/src/lib/cobuild/lock-actions.js @@ -87,9 +87,20 @@ function dispatchLockActions( inputIndices, }, }, - // reset WitnessArgs.lock to null () => `0x${"0".repeat(129 * 2)}`, ); + } else if (script.codeHash === ckbChainConfig.SCRIPTS.UNISAT.CODE_HASH) { + return generalLockActions.prepareLockActionWithWitnessStore( + buildingPacket, + scriptHash, + { + type: "WitnessArgsStore", + value: { + inputIndices, + }, + }, + () => `0x${"0".repeat(65 * 2)}`, + ); } throw new Error( diff --git a/src/lib/cobuild/react/building-packet-review.js b/src/lib/cobuild/react/building-packet-review.js index bafc583..c88f61e 100644 --- a/src/lib/cobuild/react/building-packet-review.js +++ b/src/lib/cobuild/react/building-packet-review.js @@ -199,6 +199,14 @@ export function TxSection({ return (
+
+
Dump
+
+
+            {JSON.stringify(buildingPacket.value.payload, null, 2)}
+          
+
+
Hash
diff --git a/src/lib/lumos-adapter/init-lumos-common-scripts.js b/src/lib/lumos-adapter/init-lumos-common-scripts.js index 8a71e6e..ed83dc1 100644 --- a/src/lib/lumos-adapter/init-lumos-common-scripts.js +++ b/src/lib/lumos-adapter/init-lumos-common-scripts.js @@ -5,10 +5,22 @@ import { import { addCellDep } from "@ckb-lumos/common-scripts/lib/helper"; export function buildJoyidLockInfo(ckbChainConfig) { - return buildLockInfo(ckbChainConfig, ckbChainConfig.SCRIPTS.JOYID); + return buildLockInfo(ckbChainConfig, ckbChainConfig.SCRIPTS.JOYID, []); } -export function buildLockInfo(ckbChainConfig, scriptInfo) { +export function buildUniSatLockInfo(ckbChainConfig) { + const UNISAT = ckbChainConfig.SCRIPTS.UNISAT; + // Require the secp256k1 data cell + const extraScripts = [ckbChainConfig.SCRIPTS.SECP256K1_BLAKE160]; + // if not depGroup, add AUTH as well + if (UNISAT.DEP_TYPE === "code") { + extraScripts.push(ckbChainConfig.SCRIPTS.AUTH); + } + + return buildLockInfo(ckbChainConfig, UNISAT, extraScripts); +} + +export function buildLockInfo(ckbChainConfig, scriptInfo, extraScripts) { return { codeHash: scriptInfo.CODE_HASH, hashType: scriptInfo.HASH_TYPE, @@ -95,15 +107,23 @@ export function buildLockInfo(ckbChainConfig, scriptInfo) { //=========================== // II. CellDeps //=========================== - const scriptOutPoint = { - txHash: scriptInfo.TX_HASH, - index: scriptInfo.INDEX, - }; // The helper method addCellDep avoids adding duplicated cell deps. addCellDep(txMutable, { - outPoint: scriptOutPoint, + outPoint: { + txHash: scriptInfo.TX_HASH, + index: scriptInfo.INDEX, + }, depType: scriptInfo.DEP_TYPE, }); + for (const extraScriptInfo of extraScripts) { + addCellDep(txMutable, { + outPoint: { + txHash: extraScriptInfo.TX_HASH, + index: extraScriptInfo.INDEX, + }, + depType: extraScriptInfo.DEP_TYPE, + }); + } return txMutable.asImmutable(); }, @@ -117,6 +137,7 @@ export default function initLumosCommonScripts(ckbChainConfig) { if (!inited) { commonScripts.registerCustomLockScriptInfos([ buildJoyidLockInfo(ckbChainConfig), + buildUniSatLockInfo(ckbChainConfig), ]); inited = true; } diff --git a/src/lib/wallet/btc-wallet.js b/src/lib/wallet/btc-wallet.js new file mode 100644 index 0000000..f250492 --- /dev/null +++ b/src/lib/wallet/btc-wallet.js @@ -0,0 +1,76 @@ +import { bech32 } from "bech32"; +import * as bs58 from "bs58"; +import * as lumosHelpers from "@ckb-lumos/helpers"; +import { bytes } from "@ckb-lumos/codec"; + +import { urlSafeBase64Decode } from "@/lib/base64"; + +export function isNativeSegwit(btcAddress) { + return btcAddress.startsWith("bc1q") || btcAddress.startsWith("tb1q"); +} +export function isNestedSegwit(btcAddress) { + return btcAddress.startsWith("3") || btcAddress.startsWith("2"); +} +export function isLegacy(btcAddress) { + return btcAddress.startsWith("1") || btcAddress.startsWith("n"); +} + +export function isSupported(btcAddress) { + return ( + isNativeSegwit(btcAddress) || + isNestedSegwit(btcAddress) || + isLegacy(btcAddress) + ); +} + +export function didConnected(btcAddresses) { + const availableAccounts = btcAddresses.filter(isSupported); + if (btcAddresses.length > 0 && availableAccounts.length === 0) { + throw new Error( + "Please choose an address type that is NOT Taproot (P2TR).", + ); + } + return availableAccounts; +} + +export function btcAddressToCkbAddress(btcAddress, scriptInfo, ckbChainConfig) { + let args = "0x04"; + if (isNativeSegwit(btcAddress)) { + args += bytes + .hexify(bech32.fromWords(bech32.decode(btcAddress).words.slice(1))) + .slice(2); + } else if (isNestedSegwit(btcAddress)) { + args += bytes.hexify(bs58.decode(btcAddress).slice(1, 21)).slice(2); + } else if (isLegacy(btcAddress)) { + args += bytes.hexify(bs58.decode(btcAddress).slice(1, 21)).slice(2); + } else { + throw new Error( + "Please choose an address type that is NOT Taproot (P2TR).", + ); + } + + const script = { + codeHash: scriptInfo.CODE_HASH, + hashType: scriptInfo.HASH_TYPE, + args: args, + }; + return lumosHelpers.encodeToAddress(script, { + config: ckbChainConfig, + }); +} + +export function didSign(btcAddress, signature) { + if (isNativeSegwit(btcAddress)) { + signature[0] = 39 + ((signature[0] - 27) % 4); + } else if (isNestedSegwit(btcAddress)) { + signature[0] = 35 + ((signature[0] - 27) % 4); + } else if (isLegacy(btcAddress)) { + signature[0] = 31 + ((signature[0] - 27) % 4); + } else { + throw new Error( + "Please choose an address type that is NOT Taproot (P2TR).", + ); + } + + return bytes.hexify(signature); +} diff --git a/src/lib/wallet/joyid.js b/src/lib/wallet/joyid.js index 0515b59..101857d 100644 --- a/src/lib/wallet/joyid.js +++ b/src/lib/wallet/joyid.js @@ -1,7 +1,8 @@ import * as joyid from "@joyid/ckb"; import { bytes } from "@ckb-lumos/codec"; import * as lumosHelpers from "@ckb-lumos/helpers"; -import base64 from "base64-js"; + +import { urlSafeBase64Decode } from "@/lib/base64"; export const title = "Joyid"; @@ -29,7 +30,7 @@ export function address(connection, ckbChainConfig) { // Calls this function only when wallet is connected. export async function sign(connection, message) { - const resp = await joyid.signChallenge(message, connection); + const resp = await joyid.signChallenge(message.slice(2), connection); const seal = ["0x01", resp.pubkey]; seal.push(bytes.hexify(signatureFromDer(resp.signature)).substring(2)); @@ -37,14 +38,6 @@ export async function sign(connection, message) { return seal.join(""); } -function urlSafeBase64Decode(str) { - const remainder = str.length % 4; - if (remainder !== 0) { - str = str + "=".repeat(4 - remainder); - } - return base64.toByteArray(str); -} - export function signatureFromDer(hexString) { const bytes = urlSafeBase64Decode(hexString); const xSize = Math.min(bytes[3], 32); diff --git a/src/lib/wallet/selector.js b/src/lib/wallet/selector.js index f3619fa..b983b1b 100644 --- a/src/lib/wallet/selector.js +++ b/src/lib/wallet/selector.js @@ -17,5 +17,5 @@ export function address(slug, connection, ckbChainConfig) { } export async function sign(slug, connection, message, ckbChainConfig) { - return await providers[slug].connect(connection, message, ckbChainConfig); + return await providers[slug].sign(connection, message, ckbChainConfig); } diff --git a/src/lib/wallet/unisat.js b/src/lib/wallet/unisat.js index 1945dff..d1e0167 100644 --- a/src/lib/wallet/unisat.js +++ b/src/lib/wallet/unisat.js @@ -1,32 +1,14 @@ -import { bech32 } from "bech32"; -import * as bs58 from "bs58"; -import * as lumosHelpers from "@ckb-lumos/helpers"; -import { bytes } from "@ckb-lumos/codec"; +import { urlSafeBase64Decode } from "@/lib/base64"; -export const title = "UniSat"; +import { didConnected, didSign, btcAddressToCkbAddress } from "./btc-wallet"; -function isSupported(btcAddress) { - return ( - btcAddress.startsWith("bc1q") || - btcAddress.startsWith("3") || - btcAddress.startsWith("1") || - btcAddress.startsWith("tb1q") || - btcAddress.startsWith("2") || - btcAddress.startsWith("n") - ); -} +export const title = "UniSat"; // Connects to the wallet. export async function connect() { if (window.unisat !== null && window.unisat !== undefined) { - const accounts = await unisat.requestAccounts(); - const availableAccounts = accounts.filter(isSupported); - if (accounts.length > 0 && availableAccounts.length === 0) { - throw new Error( - "Please connect LIVENET and choose an address type that is NOT Taproot (P2TR).", - ); - } - return availableAccounts; + const btcAddresses = await unisat.requestAccounts(); + return didConnected(btcAddresses); } else { throw new Error("Please install UniSat first!"); } @@ -36,31 +18,13 @@ export async function connect() { // // Calls this function only when wallet is connected. export function address(btcAddress, ckbChainConfig) { - let args = "0x04"; - if (btcAddress.startsWith("bc1q") || btcAddress.startsWith("tb1q")) { - // NativeSegwit - args += bytes - .hexify(bech32.fromWords(bech32.decode(btcAddress).words.slice(1))) - .slice(2); - } else if (btcAddress.startsWith("3") || btcAddress.startsWith("2")) { - // NestedSegwit - args += bytes.hexify(bs58.decode(btcAddress).slice(1, 21)).slice(2); - } else if (btcAddress.startsWith("1") || btcAddress.startsWith("n")) { - // Legacy - args += bytes.hexify(bs58.decode(btcAddress).slice(1, 21)).slice(2); - } else { - throw new Error( - "Please connect LIVENET and choose an address type that is NOT Taproot (P2TR).", - ); - } - const scriptInfo = ckbChainConfig.SCRIPTS.UNISAT; - const script = { - codeHash: scriptInfo.CODE_HASH, - hashType: scriptInfo.HASH_TYPE, - args: args, - }; - return lumosHelpers.encodeToAddress(script, { - config: ckbChainConfig, - }); + return btcAddressToCkbAddress(btcAddress, scriptInfo, ckbChainConfig); +} + +export async function sign(btcAddress, message) { + const signature = urlSafeBase64Decode( + await unisat.signMessage(message.slice(2)), + ); + return didSign(btcAddress, signature); } From eef9f4d702e165b47468b57ec9decd261ae95c9d Mon Sep 17 00:00:00 2001 From: ian Date: Fri, 5 Jan 2024 21:54:50 +0800 Subject: [PATCH 5/6] fix ci errors --- src/lib/cobuild/__tests__/lock-actions.test.js | 14 ++++++-------- src/lib/config.js | 8 ++++++++ src/lib/wallet/joyid.js | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/lib/cobuild/__tests__/lock-actions.test.js b/src/lib/cobuild/__tests__/lock-actions.test.js index d9db733..d812523 100644 --- a/src/lib/cobuild/__tests__/lock-actions.test.js +++ b/src/lib/cobuild/__tests__/lock-actions.test.js @@ -1,10 +1,8 @@ -import { number } from "@ckb-lumos/codec"; - -import { useConfig } from "../../config"; +import { useTestnetConfig } from "../../config"; import { prepareLockActions } from "../lock-actions"; import { GeneralLockAction, chooseWitnessStore } from "../general-lock-actions"; -const { ckbChainConfig } = useConfig(); +const { ckbChainConfig } = useTestnetConfig(); test("cobuild layout", () => { const tx = { @@ -58,7 +56,7 @@ test("cobuild layout", () => { capacity: "0x174876e800", lock: { codeHash: - "0x04dd652246af5f32ae10c04821ae32bff3dce37da52b6c60354c8ba867959e1e", + "0xd23761b364210735c19c60561d213fb3beae2fd6172743719eff6920e020baac", hashType: "type", args: "0xac4fb598d2e089e62406707d1aee4a27219515cc", }, @@ -82,7 +80,7 @@ test("cobuild layout", () => { outputBuildingPacket.value.lockActions[0].data, ); expect(lockAction.digest).toBe( - "0xaed2bd2b5fb3d5967fc0eb792a14e4c980e96d6d516903a488113c6d21a3e04a", + "0xb878fcba4398b284a80c0d752154e7920b0aefc6296b31af23dff069d18f3760", ); }); @@ -140,7 +138,7 @@ test("witness args layout", () => { capacity: "0x174876e800", lock: { codeHash: - "0x04dd652246af5f32ae10c04821ae32bff3dce37da52b6c60354c8ba867959e1e", + "0xd23761b364210735c19c60561d213fb3beae2fd6172743719eff6920e020baac", hashType: "type", args: "0xac4fb598d2e089e62406707d1aee4a27219515cc", }, @@ -164,6 +162,6 @@ test("witness args layout", () => { outputBuildingPacket.value.lockActions[0].data, ); expect(lockAction.digest).toBe( - "0xeb97071b64e5ce0ebd3f46e63764920ae5f7b2093c90301d6bddfaf6ef50e91e", + "0xb878fcba4398b284a80c0d752154e7920b0aefc6296b31af23dff069d18f3760", ); }); diff --git a/src/lib/config.js b/src/lib/config.js index ed66309..08158a2 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -124,3 +124,11 @@ export const useConfig = (() => { return config; }; })(); + +export function useTestnetConfig() { + return { + ckbChain: DEFAULT_CKB_RPC_URL, + ckbRpcUrl: DEFAULT_CKB_CHAIN, + ckbChainConfig: CKB_CHAINS_CONFIGS[DEFAULT_CKB_CHAIN], + }; +} diff --git a/src/lib/wallet/joyid.js b/src/lib/wallet/joyid.js index 101857d..95ad6cf 100644 --- a/src/lib/wallet/joyid.js +++ b/src/lib/wallet/joyid.js @@ -2,7 +2,7 @@ import * as joyid from "@joyid/ckb"; import { bytes } from "@ckb-lumos/codec"; import * as lumosHelpers from "@ckb-lumos/helpers"; -import { urlSafeBase64Decode } from "@/lib/base64"; +import { urlSafeBase64Decode } from "../base64"; export const title = "Joyid"; From f6ee9592b25e51465d66714f2f6e4470fbfac646 Mon Sep 17 00:00:00 2001 From: ian Date: Fri, 5 Jan 2024 22:57:00 +0800 Subject: [PATCH 6/6] update dao pages for multiple wallets support --- src/actions/send-tx.js | 2 +- src/app/u/[wallet]/[connection]/assets.js | 11 +++-- .../claim/[txHash]/[index]/form.js | 10 ++++- .../claim/[txHash]/[index]/page.js | 2 +- src/app/u/[wallet]/[connection]/dao-cells.js | 43 ++++++++++++------- .../u/[wallet]/[connection]/deposit/form.js | 4 +- .../u/[wallet]/[connection]/deposit/page.js | 7 ++- .../withdraw/[txHash]/[index]/form.js | 10 ++++- .../withdraw/[txHash]/[index]/page.js | 2 +- .../cobuild/react/building-packet-review.js | 8 ---- src/lib/papps/dao/lumos-callbacks.js | 37 +++++++++------- 11 files changed, 88 insertions(+), 48 deletions(-) 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/u/[wallet]/[connection]/assets.js b/src/app/u/[wallet]/[connection]/assets.js index acd1978..9190655 100644 --- a/src/app/u/[wallet]/[connection]/assets.js +++ b/src/app/u/[wallet]/[connection]/assets.js @@ -25,20 +25,25 @@ export function CkbSection({ wallet, connection, address, ckbBalance }) { ); } -export function DaoSection({ address, daoCells }) { +export function DaoSection({ wallet, connection, address, daoCells }) { return (

DAO

- +
); } 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 8edde11..9d802b9 100644 --- a/src/app/u/[wallet]/[connection]/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 ( diff --git a/src/app/u/[wallet]/[connection]/dao-cells.js b/src/app/u/[wallet]/[connection]/dao-cells.js index b68ed1a..3fd408e 100644 --- a/src/app/u/[wallet]/[connection]/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 }) {