From b2adb85b046ee7f71993abba3e1c25d3fe7e4bfe Mon Sep 17 00:00:00 2001 From: RetricSu Date: Tue, 25 Jun 2024 14:26:13 +0800 Subject: [PATCH 1/8] deploy scripts in devnet --- app/nostr-mint/offckb.config.ts | 19 ++++++------------- rust-toolchain | 1 - 2 files changed, 6 insertions(+), 14 deletions(-) delete mode 100644 rust-toolchain diff --git a/app/nostr-mint/offckb.config.ts b/app/nostr-mint/offckb.config.ts index c0383dd..8d0f4c7 100644 --- a/app/nostr-mint/offckb.config.ts +++ b/app/nostr-mint/offckb.config.ts @@ -138,24 +138,17 @@ const lumosConfig: config.Config = { "INDEX": "0xe", "DEP_TYPE": "code" }, - "AUTH": { - "CODE_HASH": "0x7e7ad21325f83a4678034838395a495a5b174bed241730a6cf13070bc167bcf4", - "HASH_TYPE": "data2", - "TX_HASH": "0xf38b75ecb640dc035fb8a05375c0e08dfc4002aa7548ccde158246a02517be29", - "INDEX": "0x0", - "DEP_TYPE": "code" - }, "NOSTR_BINDING": { - "CODE_HASH": "0xb82641271a3954006ac7b6bb01a591029f2e87d271aac9a4adbaf1a9bef81979", - "HASH_TYPE": "data2", - "TX_HASH": "0x63327888f09006be1c6c04f6ed8300f03472bf5a07bceb6538635b6e68f6afa4", + "CODE_HASH": "0x5ea4b9d50093a19943dc788c15bbdf7cd9d89efd67989a18b162feca814d90bb", + "HASH_TYPE": "type", + "TX_HASH": "0x738f6f792cbf82c1b58fcbff518972007352d904348ebfada32fb54015735ac1", "INDEX": "0x0", "DEP_TYPE": "code" }, "NOSTR_LOCK": { - "CODE_HASH": "0xbf3767fdb36789ac91344b9523aa5d8ab86a69fa0fb65291a42c9670266d7d60", - "HASH_TYPE": "data2", - "TX_HASH": "0x7b10b148746398d0cdf60c2b402f3e0a9838deee0f499ce791a3c65db83986d7", + "CODE_HASH": "0x9157002986b3d9d4dcce16b4aa6af37b702b5366a141c15a2689ae65f83acb68", + "HASH_TYPE": "type", + "TX_HASH": "0x3027067f3be5c86be6604b195c25d00a56ad68422d1c6ffb3aa20abde4ef562f", "INDEX": "0x0", "DEP_TYPE": "code" } diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 7c7053a..0000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.75.0 From 6e6b7308f9f56bdf0fd4bb6a503d1f9e498e186d Mon Sep 17 00:00:00 2001 From: RetricSu Date: Thu, 27 Jun 2024 19:08:33 +0800 Subject: [PATCH 2/8] update demo app with nostr-lock --- .../app/conmponents/connect-nostr.tsx | 2 +- .../app/conmponents/unlock-button.tsx | 54 ++++-- .../app/protocol/ckb-helper.client.ts | 5 +- app/nostr-mint/app/protocol/content.ts | 3 + .../app/protocol/event/mint.client.ts | 2 +- .../app/protocol/event/unlock.client.ts | 175 +++++++++++++++++- .../protocol/script/nostr-binding.client.ts | 7 - .../app/protocol/script/nostr-lock.client.ts | 16 +- app/nostr-mint/app/protocol/serialize.ts | 11 +- app/nostr-mint/app/protocol/tag.ts | 4 +- app/nostr-mint/app/routes/_index.tsx | 4 +- app/nostr-mint/offckb.config.ts | 8 +- 12 files changed, 238 insertions(+), 53 deletions(-) create mode 100644 app/nostr-mint/app/protocol/content.ts diff --git a/app/nostr-mint/app/conmponents/connect-nostr.tsx b/app/nostr-mint/app/conmponents/connect-nostr.tsx index 1d9b556..f837ce9 100644 --- a/app/nostr-mint/app/conmponents/connect-nostr.tsx +++ b/app/nostr-mint/app/conmponents/connect-nostr.tsx @@ -128,7 +128,7 @@ export function ConnectNostr() { ? `${nostrPubkey.slice(0, 8)}..${nostrPubkey.slice(-4)}` : "Connect Nostr"} - {ckbAddress &&
copy(ckbAddress, {onCopy: (_) => alert("address copied: " + ckbAddress)})}>{ckbAddress.slice(0, 8)}..{ckbAddress.slice(-4)}
} + {ckbAddress &&
copy(ckbAddress, {onCopy: (_) => console.log("address copied: " + ckbAddress)})}>{ckbAddress.slice(0, 8)}..{ckbAddress.slice(-4)}
} {ckbAddress && balance != null &&
{balance} CKB
} ); diff --git a/app/nostr-mint/app/conmponents/unlock-button.tsx b/app/nostr-mint/app/conmponents/unlock-button.tsx index 84a71cc..947fc71 100644 --- a/app/nostr-mint/app/conmponents/unlock-button.tsx +++ b/app/nostr-mint/app/conmponents/unlock-button.tsx @@ -10,12 +10,12 @@ import { buildAlwaysSuccessLock, computeTransactionHash, } from "~/protocol/ckb-helper.client"; -import { Event } from "@rust-nostr/nostr-sdk"; +import { Event, EventBuilder } from "@rust-nostr/nostr-sdk"; import { NostrBinding } from "~/protocol/script/nostr-binding.client"; import offCKBConfig from "offckb.config"; export interface UnlockButtonProp { - assetEvent: Event; + assetEvent: Event | undefined; setResult: (res: string) => void; } @@ -24,6 +24,8 @@ export function UnlockButton({ setResult, assetEvent }: UnlockButtonProp) { const nostrSigner = context.nostrSigner!; const onUnlock = async () => { + if(assetEvent == null)return; + const eventId = assetEvent.id.toHex(); const typeIdTag = assetEvent.tags.find( (t) => t.asVec()[0] === "cell_type_id" @@ -56,34 +58,52 @@ export function UnlockButton({ setResult, assetEvent }: UnlockButtonProp) { }) ); - const tx = helpers.createTransactionFromSkeleton(txSkeleton); - const txHash = computeTransactionHash(tx).slice(2); + const signedTx = await Unlock.signTx(txSkeleton, [0], nostrSigner.signEventBuilder); + const txHash = await offCKB.rpc.sendTransaction( + signedTx, + "passthrough" + ); + setResult("transfer tx: " + txHash); + }; - const unlockEvent = Unlock.buildEvent(txHash).toUnsignedPowEvent( + const unlockNostrLock = async () => { + const nostrPubkey = await nostrSigner.publicKey(); + const newLock = buildAlwaysSuccessLock(); + let txSkeleton = await Unlock.buildCKBTransaction( nostrPubkey, - Unlock.unlockDifficulty + newLock, + undefined ); - const event = await nostrSigner.signEvent(unlockEvent); - const eventWitness = Serializer.packEvents([event]); - const witness = bytes.hexify( - blockchain.WitnessArgs.pack({ lock: eventWitness }) - ); - txSkeleton = txSkeleton.update( - "witnesses", - (witnesses: Immutable.List) => witnesses.set(0, witness) + const lumosConfig = offCKBConfig.lumosConfig; + txSkeleton = txSkeleton.update("cellDeps", (cellDeps) => + cellDeps.push({ + outPoint: { + txHash: lumosConfig.SCRIPTS.NOSTR_BINDING!.TX_HASH, + index: lumosConfig.SCRIPTS.NOSTR_BINDING!.INDEX, + }, + depType: lumosConfig.SCRIPTS.NOSTR_BINDING!.DEP_TYPE, + }) ); - const signedTx = helpers.createTransactionFromSkeleton(txSkeleton); - const realTxHash = await offCKB.rpc.sendTransaction( + + const signer = async (event: EventBuilder) => { + const pubkey = await nostrSigner.publicKey(); + const unsignedEvent = event.toUnsignedEvent(pubkey); + return await nostrSigner.signEvent(unsignedEvent); + } + const signedTx = await Unlock.signTx(txSkeleton, [0], signer); + const txHash = await offCKB.rpc.sendTransaction( signedTx, "passthrough" ); - setResult("transfer tx: " + realTxHash); + setResult("unlock Nostr lock tx: " + txHash); }; return (
+
+
); } diff --git a/app/nostr-mint/app/protocol/ckb-helper.client.ts b/app/nostr-mint/app/protocol/ckb-helper.client.ts index 7ea3488..8c02d3e 100644 --- a/app/nostr-mint/app/protocol/ckb-helper.client.ts +++ b/app/nostr-mint/app/protocol/ckb-helper.client.ts @@ -39,7 +39,7 @@ export async function collectCell(ckbAddress: string, neededCapacity: BI) { export async function collectTypeCell( ckbAddress: string, - type: Script, + type: Script | undefined, total: number ) { const fromScript = helpers.parseAddress(ckbAddress, { @@ -47,7 +47,7 @@ export async function collectTypeCell( }); const collected: Cell[] = []; - const collector = indexer.collector({ lock: fromScript, type }); + const collector = indexer.collector({ lock: fromScript, type, argsLen: 21 }); for await (const cell of collector.collect()) { collected.push(cell); if (collected.length >= total) break; @@ -63,6 +63,7 @@ export async function collectTypeCell( export async function capacityOf(address: string): Promise { const collector = indexer.collector({ lock: helpers.parseAddress(address), + argsLen: 21, }); let balance = BI.from(0); diff --git a/app/nostr-mint/app/protocol/content.ts b/app/nostr-mint/app/protocol/content.ts new file mode 100644 index 0000000..ae06607 --- /dev/null +++ b/app/nostr-mint/app/protocol/content.ts @@ -0,0 +1,3 @@ +export enum Content { + lock = "Signing a CKB transaction\n\nIMPORTANT: Please verify the integrity and authenticity of connected Nostr client before signing this message\n", +} diff --git a/app/nostr-mint/app/protocol/event/mint.client.ts b/app/nostr-mint/app/protocol/event/mint.client.ts index f559196..f47d40f 100644 --- a/app/nostr-mint/app/protocol/event/mint.client.ts +++ b/app/nostr-mint/app/protocol/event/mint.client.ts @@ -21,7 +21,7 @@ export class Mint { static buildEvent(assetEventId: string, cellTypeId: string, content = "") { const tags = [ Tag.event(EventId.fromHex(assetEventId)), - Tag.parse([TagName.cellTypeId, cellTypeId]), + Tag.parse([TagName.ckbGlobalUniqueId, cellTypeId]), ]; const builder = new EventBuilder(this.kind, content, tags); return builder; diff --git a/app/nostr-mint/app/protocol/event/unlock.client.ts b/app/nostr-mint/app/protocol/event/unlock.client.ts index 423ecf2..35a5b41 100644 --- a/app/nostr-mint/app/protocol/event/unlock.client.ts +++ b/app/nostr-mint/app/protocol/event/unlock.client.ts @@ -3,28 +3,193 @@ import { Cell, HexString, Script, + WitnessArgs, helpers, + utils, } from "@ckb-lumos/lumos"; -import { Tag, EventBuilder, PublicKey } from "@rust-nostr/nostr-sdk"; +import { + Tag, + EventBuilder, + PublicKey, + Keys, + Event, +} from "@rust-nostr/nostr-sdk"; import { TagName } from "../tag"; import { collectTypeCell } from "../ckb-helper.client"; import { NostrLock } from "../script/nostr-lock.client"; import { ProtocolKind } from "../kind"; +import { Content } from "../content"; +import { jsonStringToBytes } from "../serialize"; +import { blockchain } from "@ckb-lumos/base"; +import { bytes, number } from "@ckb-lumos/codec"; + +const { Uint64 } = number; export class Unlock { public static kind = ProtocolKind.unlock; public static unlockDifficulty = 10; + public static dummyCkbSigHashAll = "0x" + "00".repeat(32); + + static async signTx( + txSkeleton: helpers.TransactionSkeletonType, + lockIndexes: Array, + signer: (event: EventBuilder) => Promise + ) { + if (lockIndexes.length === 0) { + throw new Error("lockIndexes length is 0"); + } + + const keys = Keys.generate(); + const dummyEvent = this.buildDummyEvent().toEvent(keys).asJson(); + const dummyLength = jsonStringToBytes(dummyEvent).length; + console.log("dummyEvent and length: ", dummyEvent, dummyLength); + + const witnessIndex = lockIndexes[0]; + const dummyLock = "0x" + "00".repeat(dummyLength); + const newWitnessArgs: WitnessArgs = { + lock: dummyLock, + }; + + while (witnessIndex >= txSkeleton.get("witnesses").size) { + txSkeleton = txSkeleton.update("witnesses", (witnesses) => + witnesses.push("0x") + ); + } + + let witness: string = txSkeleton.get("witnesses").get(witnessIndex)!; + + if (witness !== "0x") { + const witnessArgs = blockchain.WitnessArgs.unpack(bytes.bytify(witness)); + const lock = witnessArgs.lock; + if ( + !!lock && + !!newWitnessArgs.lock && + !bytes.equal(lock, newWitnessArgs.lock) + ) { + throw new Error( + "Lock field in first witness is set aside for signature!" + ); + } + const inputType = witnessArgs.inputType; + if (!!inputType) { + newWitnessArgs.inputType = inputType; + } + const outputType = witnessArgs.outputType; + if (!!outputType) { + newWitnessArgs.outputType = outputType; + } + } + witness = bytes.hexify(blockchain.WitnessArgs.pack(newWitnessArgs)); + txSkeleton = txSkeleton.update("witnesses", (witnesses) => + witnesses.set(witnessIndex, witness) + ); + + const sigHashAll = this.buildSigHashAll(txSkeleton, lockIndexes); + console.log("sighash_all = ", sigHashAll); + + const event = this.buildEvent(sigHashAll); + + const signedEvent = await signer(event); + // const invalidSig = "a9e5f16529cbe055c1f7b6d928b980a2ee0cc0a1f07a8444b85b72b3f1d5c6baa9e5f16529cbe055c1f7b6d928b980a2ee0cc0a1f07a8444b85b72b3f1d5c6ba"; + // const e = JSON.parse(signedEvent.asJson()); + // e.sig = invalidSig; + // const eventJson = jsonStringToBytes(JSON.stringify(e)); + const eventJson = jsonStringToBytes(signedEvent.asJson()); + console.log( + "eventJson.byteLength: ", + eventJson.byteLength, + signedEvent.asJson() + ); + + // put signed event into witness + { + let witness: string = txSkeleton.get("witnesses").get(witnessIndex)!; + if (witness !== "0x") { + let witnessArgs = blockchain.WitnessArgs.unpack(bytes.bytify(witness)); + witnessArgs.lock = bytes.hexify(eventJson); + console.log(witnessArgs); + witness = bytes.hexify(blockchain.WitnessArgs.pack(witnessArgs)); + txSkeleton = txSkeleton.update("witnesses", (witnesses) => + witnesses.set(witnessIndex, witness) + ); + } + } + + return helpers.createTransactionFromSkeleton(txSkeleton); + } + + static buildSigHashAll( + txSkeleton: helpers.TransactionSkeletonType, + lockIndexes: Array + ) { + const tx = helpers.createTransactionFromSkeleton(txSkeleton); + const txHash = utils.ckbHash(blockchain.RawTransaction.pack(tx)); + const inputs = txSkeleton.get("inputs"); + const witness = txSkeleton.witnesses.get(lockIndexes[0]); + if (witness == null) throw new Error("not get lock index!"); + + let count = 0; + + const hasher = new utils.CKBHasher(); + hasher.update(txHash); + count += 32; + + const witnessLength = bytes.bytify(witness).length; + hasher.update(bytes.hexify(Uint64.pack(witnessLength))); + count += 8; + hasher.update(witness); + count += witnessLength; + + // group + if (lockIndexes.length > 1) { + for (const index of lockIndexes) { + const witness = txSkeleton.witnesses.get(lockIndexes[index]); + if (witness == null) throw new Error("not get lock index!"); + const witnessLength = bytes.bytify(witness).length; + hasher.update(bytes.hexify(Uint64.pack(witnessLength))); + count += 8; + hasher.update(witness); + count += witnessLength; + } + } + + const witnessSize = txSkeleton.witnesses.size; + + if (inputs.size < witnessSize) { + for (let j = inputs.size; j < witnessSize; j++) { + const witness = txSkeleton.witnesses.get(j); + if (witness == null) throw new Error("not get lock index!"); + const witnessLength = bytes.bytify(witness).length; + hasher.update(bytes.hexify(Uint64.pack(witnessLength))); + count += 8; + hasher.update(witness); + count += witnessLength; + } + } + + const message = hasher.digestHex(); + console.log("Hashed {} bytes in sighash_all", count, message.length); + return message; + } + + static buildDummyEvent() { + const tags = [ + Tag.parse([TagName.ckbSigHashAll, this.dummyCkbSigHashAll.slice(2)]), + ]; + const builder = new EventBuilder(this.kind, Content.lock, tags); + return builder; + } - static buildEvent(txHash: HexString) { - const tags = [Tag.parse([TagName.ckbTxHash, txHash])]; - const builder = new EventBuilder(this.kind, "", tags); + static buildEvent(ckbSigHashAll: HexString) { + const tags = [Tag.parse([TagName.ckbSigHashAll, ckbSigHashAll.slice(2)])]; + const builder = new EventBuilder(this.kind, Content.lock, tags); return builder; } static async buildCKBTransaction( nostrPublicKey: PublicKey, newLock: Script, - type: Script + type: Script | undefined ) { const ckbAddress = NostrLock.encodeToCKBAddress(nostrPublicKey); diff --git a/app/nostr-mint/app/protocol/script/nostr-binding.client.ts b/app/nostr-mint/app/protocol/script/nostr-binding.client.ts index 2ed67b2..2558aac 100644 --- a/app/nostr-mint/app/protocol/script/nostr-binding.client.ts +++ b/app/nostr-mint/app/protocol/script/nostr-binding.client.ts @@ -70,13 +70,6 @@ export class NostrBinding { index: lumosConfig.SCRIPTS.NOSTR_BINDING!.INDEX, }, depType: lumosConfig.SCRIPTS.NOSTR_BINDING!.DEP_TYPE, - }, - { - outPoint: { - txHash: lumosConfig.SCRIPTS.AUTH!.TX_HASH, - index: lumosConfig.SCRIPTS.AUTH!.INDEX, - }, - depType: lumosConfig.SCRIPTS.AUTH!.DEP_TYPE, } ); return cellDeps; diff --git a/app/nostr-mint/app/protocol/script/nostr-lock.client.ts b/app/nostr-mint/app/protocol/script/nostr-lock.client.ts index d9fcc74..6e439b3 100644 --- a/app/nostr-mint/app/protocol/script/nostr-lock.client.ts +++ b/app/nostr-mint/app/protocol/script/nostr-lock.client.ts @@ -1,6 +1,7 @@ -import { CellDep, helpers } from "@ckb-lumos/lumos"; +import { CellDep, helpers, utils } from "@ckb-lumos/lumos"; import { PublicKey } from "@rust-nostr/nostr-sdk"; import offCKBConfig from "offckb.config"; +import { bytes } from "@ckb-lumos/codec"; const lumosConfig = offCKBConfig.lumosConfig; @@ -14,7 +15,9 @@ export class NostrLock { throw new Error("nostr lock script not found. have you deploy it?"); } - const lockArgs = "0x" + ownerPubkey.toHex(); + const hasher = new utils.CKBHasher(); + hasher.update(bytes.bytify("0x" + ownerPubkey.toHex())); + const lockArgs = "0x00" + hasher.digestHex().slice(2, 42); return { codeHash: lumosConfig.SCRIPTS.NOSTR_LOCK!.CODE_HASH, hashType: lumosConfig.SCRIPTS.NOSTR_LOCK!.HASH_TYPE, @@ -56,14 +59,7 @@ export class NostrLock { index: lumosConfig.SCRIPTS.NOSTR_LOCK!.INDEX, }, depType: lumosConfig.SCRIPTS.NOSTR_LOCK!.DEP_TYPE, - }, - { - outPoint: { - txHash: lumosConfig.SCRIPTS.AUTH!.TX_HASH, - index: lumosConfig.SCRIPTS.AUTH!.INDEX, - }, - depType: lumosConfig.SCRIPTS.AUTH!.DEP_TYPE, - }, + } ]; return cellDeps; } diff --git a/app/nostr-mint/app/protocol/serialize.ts b/app/nostr-mint/app/protocol/serialize.ts index 20817a2..60d247b 100644 --- a/app/nostr-mint/app/protocol/serialize.ts +++ b/app/nostr-mint/app/protocol/serialize.ts @@ -27,7 +27,16 @@ function numberToLE8Bytes(number: number): Uint8Array { return new Uint8Array(buffer); } -function jsonStringToBytes(jsonString: string): Uint8Array { +export function jsonStringToBytes(jsonString: string): Uint8Array { + const buffer = Buffer.from(jsonString, 'utf-8'); + + const arrayBuffer = new ArrayBuffer(buffer.length); + const view = new Uint8Array(arrayBuffer); + for (let i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; + } + return view; + const encoder = new TextEncoder(); const encodedString = encoder.encode(jsonString); return encodedString; diff --git a/app/nostr-mint/app/protocol/tag.ts b/app/nostr-mint/app/protocol/tag.ts index 17c1c9d..7521ba3 100644 --- a/app/nostr-mint/app/protocol/tag.ts +++ b/app/nostr-mint/app/protocol/tag.ts @@ -1,6 +1,6 @@ export enum TagName { - cellTypeId = "cell_type_id", - ckbTxHash = "ckb_tx_hash", + ckbGlobalUniqueId = "ckb_global_unique_id", + ckbSigHashAll = "ckb_sighash_all", // asset payload name = "name", diff --git a/app/nostr-mint/app/routes/_index.tsx b/app/nostr-mint/app/routes/_index.tsx index 584dd56..96e3651 100644 --- a/app/nostr-mint/app/routes/_index.tsx +++ b/app/nostr-mint/app/routes/_index.tsx @@ -33,9 +33,7 @@ export default function Index() {
  • - {assetEvent && ( - - )} +
  • diff --git a/app/nostr-mint/offckb.config.ts b/app/nostr-mint/offckb.config.ts index 8d0f4c7..d391b04 100644 --- a/app/nostr-mint/offckb.config.ts +++ b/app/nostr-mint/offckb.config.ts @@ -139,16 +139,16 @@ const lumosConfig: config.Config = { "DEP_TYPE": "code" }, "NOSTR_BINDING": { - "CODE_HASH": "0x5ea4b9d50093a19943dc788c15bbdf7cd9d89efd67989a18b162feca814d90bb", + "CODE_HASH": "0x0bff3831ad0fa148af54acb1eb3d4b1121103b2fda548c564fe4f0554124db46", "HASH_TYPE": "type", - "TX_HASH": "0x738f6f792cbf82c1b58fcbff518972007352d904348ebfada32fb54015735ac1", + "TX_HASH": "0x57823257147f468d9e5b69d3fc6bbab157ba3421a8c8f5b436e32f1181b44c49", "INDEX": "0x0", "DEP_TYPE": "code" }, "NOSTR_LOCK": { - "CODE_HASH": "0x9157002986b3d9d4dcce16b4aa6af37b702b5366a141c15a2689ae65f83acb68", + "CODE_HASH": "0xa4dbcae2ebcebe59bd2cc0d657858084137bced6fe23437b63b512956ccc79f1", "HASH_TYPE": "type", - "TX_HASH": "0x3027067f3be5c86be6604b195c25d00a56ad68422d1c6ffb3aa20abde4ef562f", + "TX_HASH": "0x6a47de081abf43c2164a20fc5f9ffa5b4206c2d3c5aa84bdcff1d4f48ebc9523", "INDEX": "0x0", "DEP_TYPE": "code" } From 9386d7b553f63bec7e5d19bc1a120d8a1cd73eeb Mon Sep 17 00:00:00 2001 From: RetricSu Date: Fri, 28 Jun 2024 00:49:16 +0800 Subject: [PATCH 3/8] update demo app with nostr-binding script --- .../app/conmponents/mint-button.tsx | 54 ++++++++----------- .../app/protocol/ckb-helper.client.ts | 2 +- .../app/protocol/event/mint.client.ts | 28 +++++----- .../app/protocol/event/unlock.client.ts | 2 +- .../protocol/script/nostr-binding.client.ts | 10 ++-- .../app/protocol/script/nostr-lock.client.ts | 5 +- 6 files changed, 45 insertions(+), 56 deletions(-) diff --git a/app/nostr-mint/app/conmponents/mint-button.tsx b/app/nostr-mint/app/conmponents/mint-button.tsx index 6fb337d..2c56cf0 100644 --- a/app/nostr-mint/app/conmponents/mint-button.tsx +++ b/app/nostr-mint/app/conmponents/mint-button.tsx @@ -1,13 +1,12 @@ import { bytes } from "@ckb-lumos/codec"; -import { helpers } from "@ckb-lumos/lumos"; import { blockchain } from "@ckb-lumos/base"; import { ReactNode, useContext } from "react"; import { SingerContext } from "~/context/signer"; -import { Asset } from "~/protocol/event/asset"; import { Mint } from "~/protocol/event/mint.client"; -import { Serializer } from "~/protocol/serialize"; +import { jsonStringToBytes } from "~/protocol/serialize"; import offCKB from "offckb.config"; -import { Event } from "@rust-nostr/nostr-sdk"; +import { Event, EventBuilder } from "@rust-nostr/nostr-sdk"; +import { Unlock } from "~/protocol/event/unlock.client"; export interface MintButtonProp { setResult: (res: string | ReactNode) => void; @@ -20,41 +19,32 @@ export function MintButton({ setResult, setAssetEvent }: MintButtonProp) { const ckbSigner = context.ckbSigner!; const mint = async () => { - const nostrPubkey = await nostrSigner.publicKey(); - const content = "This is the definition of the Test-NFT token"; - const assetUnsignedEvent = Asset.buildEvent( - { - name: "Test-NFT token", - description: "There are only 100 NFT in total.", - }, - content - ).toUnsignedEvent(nostrPubkey); - const assetEvent = await nostrSigner.signEvent(assetUnsignedEvent); - const { txSkeleton, mintEvent } = await Mint.buildTransaction( + const signerNostrPublicKey = await nostrSigner.publicKey(); + let { txSkeleton, mintEvent } = await Mint.buildTransaction( + signerNostrPublicKey, ckbSigner.ckbAddress, - assetEvent ); - const event = await nostrSigner.signEvent(mintEvent); - setAssetEvent(event); - const eventWitness = Serializer.packEvents([event, assetEvent]); - let tx = ckbSigner.buildSigningEntries(txSkeleton, eventWitness); - const signedMessage = await ckbSigner.signMessage( - tx.signingEntries.get(0)!.message - ); - let signedLockEvent = Event.fromJson(signedMessage); - const lockEventWitness = Serializer.packEvents([signedLockEvent]); - const signedWitness = bytes.hexify( + console.log(txSkeleton.inputs) + + const signedMintEvent = await nostrSigner.signEvent(mintEvent); + const mintEventWitness = bytes.hexify(jsonStringToBytes(signedMintEvent.asJson())); + + const witness = bytes.hexify( blockchain.WitnessArgs.pack({ - lock: lockEventWitness, - outputType: eventWitness, + outputType: mintEventWitness, }) ); - tx = tx.update("witnesses", (witnesses: Immutable.List) => - witnesses.set(0, signedWitness) + txSkeleton = txSkeleton.update("witnesses", (witnesses: Immutable.List) => + witnesses.set(0, witness) ); - - const signedTx = helpers.createTransactionFromSkeleton(tx); + const signer = async (event: EventBuilder) => { + const pubkey = await nostrSigner.publicKey(); + console.log("pubkey:",pubkey); + const unsignedEvent = event.toUnsignedEvent(pubkey); + return await nostrSigner.signEvent(unsignedEvent); + } + const signedTx = await Unlock.signTx(txSkeleton, [0], signer); const txHash = await offCKB.rpc.sendTransaction(signedTx, "passthrough"); setResult( diff --git a/app/nostr-mint/app/protocol/ckb-helper.client.ts b/app/nostr-mint/app/protocol/ckb-helper.client.ts index 8c02d3e..dd0847a 100644 --- a/app/nostr-mint/app/protocol/ckb-helper.client.ts +++ b/app/nostr-mint/app/protocol/ckb-helper.client.ts @@ -23,7 +23,7 @@ export async function collectCell(ckbAddress: string, neededCapacity: BI) { let collectedSum = BI.from(0); const collected: Cell[] = []; - const collector = indexer.collector({ lock: fromScript, type: "empty" }); + const collector = indexer.collector({ lock: fromScript, type: "empty", argsLen: 21 }); for await (const cell of collector.collect()) { collectedSum = collectedSum.add(cell.cellOutput.capacity); collected.push(cell); diff --git a/app/nostr-mint/app/protocol/event/mint.client.ts b/app/nostr-mint/app/protocol/event/mint.client.ts index f47d40f..93644f7 100644 --- a/app/nostr-mint/app/protocol/event/mint.client.ts +++ b/app/nostr-mint/app/protocol/event/mint.client.ts @@ -13,38 +13,36 @@ import { NostrLock } from "../script/nostr-lock.client"; import { NostrBinding } from "../script/nostr-binding.client"; import { ProtocolKind } from "../kind"; import { mergeArraysAndRemoveDuplicates } from "../util"; +import { blockchain } from "@ckb-lumos/base"; +import { bytes, number } from "@ckb-lumos/codec"; +import { jsonStringToBytes } from "../serialize"; export class Mint { public static kind = ProtocolKind.mint; public static mintDifficulty = 10; - static buildEvent(assetEventId: string, cellTypeId: string, content = "") { + static buildEvent(globalUniqueId: string, content = "") { const tags = [ - Tag.event(EventId.fromHex(assetEventId)), - Tag.parse([TagName.ckbGlobalUniqueId, cellTypeId]), + Tag.parse([TagName.ckbGlobalUniqueId, globalUniqueId]), ]; const builder = new EventBuilder(this.kind, content, tags); return builder; } - static async buildTransaction(ckbAddress: string, assetEvent: Event) { + static async buildTransaction(nostrPublicKey: PublicKey, ckbAddress: string) { let txSkeleton = helpers.TransactionSkeleton({}); const collectedInputs = await collectCell(ckbAddress, BI.from(16000000000)); - - const typeId = NostrBinding.buildTypeId(collectedInputs[0], "0x0"); + const globalUniqueId = NostrBinding.buildGlobalUniqueId(collectedInputs[0], "0x0"); const mintEvent = this.buildEvent( - assetEvent.id.toHex(), - typeId, - "This is the content of the Test-NFT Item" - ).toUnsignedPowEvent(assetEvent.author, this.mintDifficulty); - - const ownerPubkeyStr = NostrLock.parseCBKAddressToNostrPubkey(ckbAddress); - const ownerPubkey = PublicKey.fromHex(ownerPubkeyStr.slice(2)); - const lock = NostrLock.buildScript(ownerPubkey); + globalUniqueId, + "This is the content of the Test-NFT" + ).toUnsignedPowEvent(nostrPublicKey, this.mintDifficulty); + + const lock = NostrLock.buildScript(nostrPublicKey); const bindingCell = NostrBinding.buildBindingCell( mintEvent.id.toHex(), - typeId, + globalUniqueId, lock ); // todo: add changeCell and fee rate diff --git a/app/nostr-mint/app/protocol/event/unlock.client.ts b/app/nostr-mint/app/protocol/event/unlock.client.ts index 35a5b41..9f9468c 100644 --- a/app/nostr-mint/app/protocol/event/unlock.client.ts +++ b/app/nostr-mint/app/protocol/event/unlock.client.ts @@ -39,6 +39,7 @@ export class Unlock { throw new Error("lockIndexes length is 0"); } + //todo: remove the following const keys = Keys.generate(); const dummyEvent = this.buildDummyEvent().toEvent(keys).asJson(); const dummyLength = jsonStringToBytes(dummyEvent).length; @@ -107,7 +108,6 @@ export class Unlock { if (witness !== "0x") { let witnessArgs = blockchain.WitnessArgs.unpack(bytes.bytify(witness)); witnessArgs.lock = bytes.hexify(eventJson); - console.log(witnessArgs); witness = bytes.hexify(blockchain.WitnessArgs.pack(witnessArgs)); txSkeleton = txSkeleton.update("witnesses", (witnesses) => witnesses.set(witnessIndex, witness) diff --git a/app/nostr-mint/app/protocol/script/nostr-binding.client.ts b/app/nostr-mint/app/protocol/script/nostr-binding.client.ts index 2558aac..9719631 100644 --- a/app/nostr-mint/app/protocol/script/nostr-binding.client.ts +++ b/app/nostr-mint/app/protocol/script/nostr-binding.client.ts @@ -18,12 +18,12 @@ export class NostrBinding { return lumosConfig.SCRIPTS.NOSTR_BINDING != null; } - static buildScript(eventId: HexString, typeId: HexString): Script { + static buildScript(eventId: HexString, globalUniqueId: HexString): Script { if (!this.isScriptExist()) { throw new Error("nostr binding script not found. have you deploy it?"); } - const bindingArgs = `0x${eventId}${typeId}`; + const bindingArgs = `0x${eventId}${globalUniqueId}`; return { codeHash: lumosConfig.SCRIPTS.NOSTR_BINDING!.CODE_HASH, hashType: lumosConfig.SCRIPTS.NOSTR_BINDING!.HASH_TYPE, @@ -31,8 +31,8 @@ export class NostrBinding { }; } - static buildBindingCell(eventId: HexString, typeId: HexString, lock: Script) { - const type = NostrBinding.buildScript(eventId, typeId); + static buildBindingCell(eventId: HexString, globalUniqueId: HexString, lock: Script) { + const type = NostrBinding.buildScript(eventId, globalUniqueId); const bindingOutput: Cell = { cellOutput: { capacity: BI.from(0).toHexString(), @@ -46,7 +46,7 @@ export class NostrBinding { return bindingOutput; } - static buildTypeId(inputCell: Cell, index: HexNumber) { + static buildGlobalUniqueId(inputCell: Cell, index: HexNumber) { if (!inputCell.outPoint) throw new Error("input Cell has no outpoint!"); const input: Input = { diff --git a/app/nostr-mint/app/protocol/script/nostr-lock.client.ts b/app/nostr-mint/app/protocol/script/nostr-lock.client.ts index 6e439b3..234c2b4 100644 --- a/app/nostr-mint/app/protocol/script/nostr-lock.client.ts +++ b/app/nostr-mint/app/protocol/script/nostr-lock.client.ts @@ -31,7 +31,7 @@ export class NostrLock { return address; } - public static parseCBKAddressToNostrPubkey(ckbAddress: string) { + public static parseCBKAddressToNostrPubkeyHash(ckbAddress: string) { if (!this.isScriptExist()) { throw new Error("nostr lock script not found. have you deploy it?"); } @@ -44,7 +44,8 @@ export class NostrLock { throw new Error("nostr-lock contract script info not match!"); } - return script.args; + // 20 bytes hash + return script.args.slice(4); } public static buildCellDeps() { From 20d557a5fc86642487dd9796fbb4cd957a53e918 Mon Sep 17 00:00:00 2001 From: RetricSu Date: Fri, 28 Jun 2024 09:42:45 +0800 Subject: [PATCH 4/8] clean some codes --- .../app/conmponents/connect-metamask.tsx | 149 ------------------ .../app/conmponents/connect-nostr.tsx | 27 ++-- .../app/conmponents/mint-button.tsx | 19 +-- .../app/conmponents/unlock-button.tsx | 24 +-- .../app/protocol/ckb-helper.client.ts | 5 +- app/nostr-mint/app/protocol/event/asset.ts | 32 ---- .../app/protocol/event/mint.client.ts | 15 +- .../app/protocol/event/unlock.client.ts | 2 +- app/nostr-mint/app/protocol/kind.ts | 2 - app/nostr-mint/app/protocol/serialize.ts | 54 ------- app/nostr-mint/app/protocol/tag.ts | 6 - app/nostr-mint/app/protocol/util.ts | 11 ++ app/nostr-mint/app/routes/_index.tsx | 11 +- app/nostr-mint/offckb.config.ts | 8 +- app/nostr-mint/package-lock.json | 14 -- app/nostr-mint/package.json | 1 - 16 files changed, 66 insertions(+), 314 deletions(-) delete mode 100644 app/nostr-mint/app/conmponents/connect-metamask.tsx delete mode 100644 app/nostr-mint/app/protocol/event/asset.ts delete mode 100644 app/nostr-mint/app/protocol/serialize.ts diff --git a/app/nostr-mint/app/conmponents/connect-metamask.tsx b/app/nostr-mint/app/conmponents/connect-metamask.tsx deleted file mode 100644 index 96ee08e..0000000 --- a/app/nostr-mint/app/conmponents/connect-metamask.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { Script, commons, helpers } from "@ckb-lumos/lumos"; -import { useContext, useState } from "react"; -import { CKBSigner, SingerContext } from "~/context/signer"; -import { capacityOf } from "~/protocol/ckb-helper.client"; -import { blockchain } from "@ckb-lumos/base"; -import { bytes } from "@ckb-lumos/codec"; -import offCKBConfig from "offckb.config"; - -interface EthereumRpc { - (payload: { - method: "personal_sign"; - params: [string /*from*/, string /*message*/]; - }): Promise; -} - -export interface EthereumProvider { - selectedAddress: string; - isMetaMask?: boolean; - enable: () => Promise; - addListener: ( - event: "accountsChanged", - listener: (addresses: string[]) => void - ) => void; - removeEventListener: ( - event: "accountsChanged", - listener: (addresses: string[]) => void - ) => void; - request: EthereumRpc; -} - -export function ConnectMetamask() { - const { ckbSigner: signer, setCKBSigner: setSigner } = - useContext(SingerContext)!; - - const [ethAddr, setEthAddr] = useState(""); - const [omniAddr, setOmniAddr] = useState(""); - const [omniLock, setOmniLock] = useState