From 24a7cd34fa2cfceb3cacbf7678760cab8466746c Mon Sep 17 00:00:00 2001 From: duanyytop Date: Thu, 29 Sep 2022 13:44:41 +0800 Subject: [PATCH 1/7] feat: Add extension smt service --- src/aggregator/index.ts | 6 ++++ src/service/cota/extension.ts | 52 +++++++++++++++++++++++++++++++++++ src/types/request.ts | 4 +++ src/types/response.ts | 6 ++++ 4 files changed, 68 insertions(+) create mode 100644 src/service/cota/extension.ts diff --git a/src/aggregator/index.ts b/src/aggregator/index.ts index 2f786cf..7ddc601 100644 --- a/src/aggregator/index.ts +++ b/src/aggregator/index.ts @@ -16,6 +16,7 @@ import { GetDefineInfoReq, GetIssuerInfoReq, GetCotaCountReq, + ExtensionReq, } from '../types/request' import { ClaimResp, @@ -37,6 +38,7 @@ import { GetDefineInfoResp, GetIssuerInfoResp, GetCotaCountResp, + ExtensionResp, } from '../types/response' import { Byte32 } from '../types/common' @@ -159,6 +161,10 @@ export class Aggregator { async getCotaCount(req: GetCotaCountReq): Promise { return (await this.baseRPC('get_cota_count', req)) as Promise } + + async generateExtensionSmt(extension: ExtensionReq): Promise { + return (await this.baseRPC('generate_extension_smt', extension)) as Promise + } } const convert = (req: GetCotaReq) => ({ diff --git a/src/service/cota/extension.ts b/src/service/cota/extension.ts new file mode 100644 index 0000000..d0779f1 --- /dev/null +++ b/src/service/cota/extension.ts @@ -0,0 +1,52 @@ +import { serializeScript } from '@nervosnetwork/ckb-sdk-utils' +import { Service } from '../..' +import { FEE, getCotaTypeScript, getCotaCellDep } from '../../constants' +import { ExtensionReq } from '../../types' + + +export const generateExtensionTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + fee = FEE, + isMainnet = false, +) => { + const cotaType = getCotaTypeScript(isMainnet) + const cotaCells = await service.collector.getCells(cotaLock, cotaType) + if (!cotaCells || cotaCells.length === 0) { + throw new Error("Cota cell doesn't exist") + } + const cotaCell = cotaCells[0] + const inputs = [ + { + previousOutput: cotaCell.outPoint, + since: '0x0', + }, + ] + + const outputs = [cotaCell.output] + outputs[0].capacity = `0x${(BigInt(outputs[0].capacity) - fee).toString(16)}` + + const extensionReq: ExtensionReq = { + lockScript: serializeScript(cotaLock), + } + + const { smtRootHash, extensionSmtEntry } = await service.aggregator.generateExtensionSmt(extensionReq) + const cotaCellData = `0x02${smtRootHash}` + + const outputsData = [cotaCellData] + const cellDeps = [getCotaCellDep(isMainnet)] + + const rawTx = { + version: '0x0', + cellDeps, + headerDeps: [], + inputs, + outputs, + outputsData, + witnesses: [], + } as any + rawTx.witnesses = rawTx.inputs.map((_, i) => + i > 0 ? '0x' : { lock: '', inputType: `0xF0${extensionSmtEntry}`, outputType: '' }, + ) + return rawTx +} diff --git a/src/types/request.ts b/src/types/request.ts index 371d47b..89a88cb 100644 --- a/src/types/request.ts +++ b/src/types/request.ts @@ -118,3 +118,7 @@ export interface GetCotaCountReq extends SmtReq { lockScript: Bytes cotaId: Byte20 } + +export interface ExtensionReq extends SmtReq { + lockScript: Bytes +} \ No newline at end of file diff --git a/src/types/response.ts b/src/types/response.ts index cd2992e..c52897c 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -146,3 +146,9 @@ export interface GetCotaCountResp { count: number blockNumber: bigint } + +export interface ExtensionResp extends SmtResp { + smtRootHash: Byte32 + extensionSmtEntry: Bytes + blockNumber: bigint +} \ No newline at end of file From eeccb41633a83af343a00c3cd5b92264d5c5200a Mon Sep 17 00:00:00 2001 From: duanyytop Date: Thu, 29 Sep 2022 13:45:07 +0800 Subject: [PATCH 2/7] feat: Add extension example --- example/extension.ts | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 example/extension.ts diff --git a/example/extension.ts b/example/extension.ts new file mode 100644 index 0000000..c7c894e --- /dev/null +++ b/example/extension.ts @@ -0,0 +1,44 @@ +import { addressToScript } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { Service } from '../src' +import { generateExtensionTx } from '../src/service/cota/extension' + +const TEST_PRIVATE_KEY = '0xee56672e70cec79941adc0637c1edb1546a0c39c72eff8c41bc4f1e03bf663b3' +const TEST_ADDRESS = 'ckt1qyq897k5m53wxzup078jwkucvvsu8kzv55rqqm6glm' + +const secp256k1CellDep = (isMainnet: boolean): CKBComponents.CellDep => { + if (isMainnet) { + return { outPoint: { + txHash: "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c", + index: "0x0", + }, depType: 'depGroup' } + } + return { outPoint: { + txHash: "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", + index: "0x0", + }, depType: 'depGroup' } +} + +const run = async () => { + // True for mainnet and false for testnet + const isMainnet = false + + const service: Service = { + collector: new Collector({ ckbNodeUrl: 'https://testnet.ckb.dev/rpc', ckbIndexerUrl: 'https://testnet.ckb.dev/indexer' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const extensionLock = addressToScript(TEST_ADDRESS) + + let rawTx = await generateExtensionTx(service, extensionLock, BigInt(6000), isMainnet) + rawTx.cellDeps.push(secp256k1CellDep(isMainnet)) + + const signedTx = ckb.signTransaction(TEST_PRIVATE_KEY)(rawTx) + console.log(JSON.stringify(signedTx)) + + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.info(`Add cota extension tx has been sent with tx hash ${txHash}`) +} + +run() From 516e62711d8825377bad0b61cca6d578416e14c1 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Thu, 29 Sep 2022 17:31:33 +0800 Subject: [PATCH 3/7] feat: Update extension service --- src/aggregator/index.ts | 2 +- src/service/cota/extension.ts | 2 +- src/types/request.ts | 2 +- src/types/response.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aggregator/index.ts b/src/aggregator/index.ts index 7ddc601..cc2fdd1 100644 --- a/src/aggregator/index.ts +++ b/src/aggregator/index.ts @@ -162,7 +162,7 @@ export class Aggregator { return (await this.baseRPC('get_cota_count', req)) as Promise } - async generateExtensionSmt(extension: ExtensionReq): Promise { + async generateExtensionSmt(extension: ExtensionReq): Promise { return (await this.baseRPC('generate_extension_smt', extension)) as Promise } } diff --git a/src/service/cota/extension.ts b/src/service/cota/extension.ts index d0779f1..45aff81 100644 --- a/src/service/cota/extension.ts +++ b/src/service/cota/extension.ts @@ -46,7 +46,7 @@ export const generateExtensionTx = async ( witnesses: [], } as any rawTx.witnesses = rawTx.inputs.map((_, i) => - i > 0 ? '0x' : { lock: '', inputType: `0xF0${extensionSmtEntry}`, outputType: '' }, + i > 0 ? '0x' : { lock: '', inputType: `0xF1${extensionSmtEntry}`, outputType: '' }, ) return rawTx } diff --git a/src/types/request.ts b/src/types/request.ts index 89a88cb..c63e65d 100644 --- a/src/types/request.ts +++ b/src/types/request.ts @@ -121,4 +121,4 @@ export interface GetCotaCountReq extends SmtReq { export interface ExtensionReq extends SmtReq { lockScript: Bytes -} \ No newline at end of file +} diff --git a/src/types/response.ts b/src/types/response.ts index c52897c..e35fa95 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -151,4 +151,4 @@ export interface ExtensionResp extends SmtResp { smtRootHash: Byte32 extensionSmtEntry: Bytes blockNumber: bigint -} \ No newline at end of file +} From e32696a7d492613c4717b2fa306e5365da911958 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Fri, 30 Sep 2022 15:40:15 +0800 Subject: [PATCH 4/7] feat: Add and update extension --- src/service/cota/extension.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/service/cota/extension.ts b/src/service/cota/extension.ts index 45aff81..40638fc 100644 --- a/src/service/cota/extension.ts +++ b/src/service/cota/extension.ts @@ -4,11 +4,17 @@ import { FEE, getCotaTypeScript, getCotaCellDep } from '../../constants' import { ExtensionReq } from '../../types' -export const generateExtensionTx = async ( +enum Action { + Add, + Update +} + +const generateExtensionTx = async ( service: Service, cotaLock: CKBComponents.Script, fee = FEE, isMainnet = false, + action: Action, ) => { const cotaType = getCotaTypeScript(isMainnet) const cotaCells = await service.collector.getCells(cotaLock, cotaType) @@ -45,8 +51,26 @@ export const generateExtensionTx = async ( outputsData, witnesses: [], } as any + + const prefix = action == Action.Add ? '0xF0' : '0xF1' rawTx.witnesses = rawTx.inputs.map((_, i) => - i > 0 ? '0x' : { lock: '', inputType: `0xF1${extensionSmtEntry}`, outputType: '' }, + i > 0 ? '0x' : { lock: '', inputType: `${prefix}${extensionSmtEntry}`, outputType: '' }, ) return rawTx } + + +export const generateAddExtensionTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + fee = FEE, + isMainnet = false +) => await generateExtensionTx(service, cotaLock, fee, isMainnet, Action.Add) + + + export const generateUpdateExtensionTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + fee = FEE, + isMainnet = false +) => await generateExtensionTx(service, cotaLock, fee, isMainnet, Action.Update) \ No newline at end of file From 20c6449465a8c78b343c3413cac5314afa8dfff0 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Fri, 30 Sep 2022 15:41:29 +0800 Subject: [PATCH 5/7] refactor: Format code --- package.json | 2 +- src/service/cota/claim-update.ts | 4 +- src/service/cota/cota-meta.ts | 138 ++++++++++++++-------------- src/service/cota/transfer-update.ts | 5 +- src/service/cota/transfer.ts | 6 +- src/service/registry/index.ts | 5 +- 6 files changed, 82 insertions(+), 78 deletions(-) diff --git a/package.json b/package.json index b31ed1c..aaa1e7c 100644 --- a/package.json +++ b/package.json @@ -47,4 +47,4 @@ "typescript": "4.8.3" }, "homepage": "https://github.com/nervina-labs/cota-sdk-js#readme" -} \ No newline at end of file +} diff --git a/src/service/cota/claim-update.ts b/src/service/cota/claim-update.ts index 78993f7..7d6976b 100644 --- a/src/service/cota/claim-update.ts +++ b/src/service/cota/claim-update.ts @@ -35,7 +35,9 @@ export const generateClaimUpdateCotaTx = async ( nfts, } - const { smtRootHash, claimUpdateSmtEntry, withdrawBlockHash } = await service.aggregator.generateClaimUpdateCotaSmt(claimUpdateReq) + const { smtRootHash, claimUpdateSmtEntry, withdrawBlockHash } = await service.aggregator.generateClaimUpdateCotaSmt( + claimUpdateReq, + ) const outputsData = [`0x02${smtRootHash}`] const cellDeps = [getCotaCellDep(isMainnet)] const headerDeps = [`0x${withdrawBlockHash}`] diff --git a/src/service/cota/cota-meta.ts b/src/service/cota/cota-meta.ts index 437bd3c..882dc35 100644 --- a/src/service/cota/cota-meta.ts +++ b/src/service/cota/cota-meta.ts @@ -1,69 +1,69 @@ -import { Service } from '../..' -import { FEE, getCotaTypeScript, getCotaCellDep } from '../../constants' -import { CotaInfo, Hex } from '../../types' -import { append0x, utf8ToHex, toSnakeCase } from '../../utils' - -const generateCotaMetadata = (cotaInfo: CotaInfo, cotaId: Hex): Hex => { - const cotaInfoTemp = { - cotaId, - ...cotaInfo, - } - const cotaMeta = { - id: 'CTMeta', - ver: '1.0', - metadata: { - target: 'output#0', - type: 'cota', - data: { - version: '0', - ...cotaInfoTemp, - }, - }, - } - return append0x(utf8ToHex(JSON.stringify(toSnakeCase(cotaMeta)))) -} - -export const generateCotaMetadataTx = async ( - service: Service, - cotaLock: CKBComponents.Script, - cotaId: Hex, - cotaInfo: CotaInfo, - fee = FEE, - isMainnet = false, -) => { - const cotaType = getCotaTypeScript(isMainnet) - const cotaCells = await service.collector.getCells(cotaLock, cotaType) - if (!cotaCells || cotaCells.length === 0) { - throw new Error("Cota cell doesn't exist") - } - - const cotaCell = cotaCells[0] - const inputs = [ - { - previousOutput: cotaCell.outPoint, - since: '0x0', - }, - ] - - const outputs = [cotaCell.output] - outputs[0].capacity = `0x${(BigInt(outputs[0].capacity) - fee).toString(16)}` - - const cotaOutput = await service.collector.getLiveCell(cotaCell.outPoint) - const outputsData = [cotaOutput.data?.content] - - const cellDeps = [getCotaCellDep(isMainnet)] - - const rawTx: any = { - version: '0x0', - cellDeps, - headerDeps: [], - inputs, - outputs, - outputsData, - witnesses: [], - } - rawTx.witnesses = rawTx.inputs.map((_, i) => - i > 0 ? '0x' : { lock: '', inputType: '', outputType: generateCotaMetadata(cotaInfo, cotaId) }, - ) - return rawTx -} +import { Service } from '../..' +import { FEE, getCotaTypeScript, getCotaCellDep } from '../../constants' +import { CotaInfo, Hex } from '../../types' +import { append0x, utf8ToHex, toSnakeCase } from '../../utils' + +const generateCotaMetadata = (cotaInfo: CotaInfo, cotaId: Hex): Hex => { + const cotaInfoTemp = { + cotaId, + ...cotaInfo, + } + const cotaMeta = { + id: 'CTMeta', + ver: '1.0', + metadata: { + target: 'output#0', + type: 'cota', + data: { + version: '0', + ...cotaInfoTemp, + }, + }, + } + return append0x(utf8ToHex(JSON.stringify(toSnakeCase(cotaMeta)))) +} + +export const generateCotaMetadataTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + cotaId: Hex, + cotaInfo: CotaInfo, + fee = FEE, + isMainnet = false, +) => { + const cotaType = getCotaTypeScript(isMainnet) + const cotaCells = await service.collector.getCells(cotaLock, cotaType) + if (!cotaCells || cotaCells.length === 0) { + throw new Error("Cota cell doesn't exist") + } + + const cotaCell = cotaCells[0] + const inputs = [ + { + previousOutput: cotaCell.outPoint, + since: '0x0', + }, + ] + + const outputs = [cotaCell.output] + outputs[0].capacity = `0x${(BigInt(outputs[0].capacity) - fee).toString(16)}` + + const cotaOutput = await service.collector.getLiveCell(cotaCell.outPoint) + const outputsData = [cotaOutput.data?.content] + + const cellDeps = [getCotaCellDep(isMainnet)] + + const rawTx: any = { + version: '0x0', + cellDeps, + headerDeps: [], + inputs, + outputs, + outputsData, + witnesses: [], + } + rawTx.witnesses = rawTx.inputs.map((_, i) => + i > 0 ? '0x' : { lock: '', inputType: '', outputType: generateCotaMetadata(cotaInfo, cotaId) }, + ) + return rawTx +} diff --git a/src/service/cota/transfer-update.ts b/src/service/cota/transfer-update.ts index 355037d..e759bb5 100644 --- a/src/service/cota/transfer-update.ts +++ b/src/service/cota/transfer-update.ts @@ -35,9 +35,8 @@ export const generateTransferUpdateCotaTx = async ( transferOutPoint: append0x(serializeOutPoint(cotaCell.outPoint).slice(26)), transfers, } - const { smtRootHash, transferUpdateSmtEntry, withdrawBlockHash } = await service.aggregator.generateTransferUpdateCotaSmt( - transferUpdateReq, - ) + const { smtRootHash, transferUpdateSmtEntry, withdrawBlockHash } = + await service.aggregator.generateTransferUpdateCotaSmt(transferUpdateReq) const outputsData = [`0x02${smtRootHash}`] const cellDeps = [getCotaCellDep(isMainnet)] diff --git a/src/service/cota/transfer.ts b/src/service/cota/transfer.ts index b923d77..670eb08 100644 --- a/src/service/cota/transfer.ts +++ b/src/service/cota/transfer.ts @@ -35,12 +35,14 @@ export const generateTransferCotaTx = async ( transferOutPoint: append0x(serializeOutPoint(cotaCell.outPoint).slice(26)), transfers, } - const { smtRootHash, transferSmtEntry, withdrawBlockHash } = await service.aggregator.generateTransferCotaSmt(transferReq) + const { smtRootHash, transferSmtEntry, withdrawBlockHash } = await service.aggregator.generateTransferCotaSmt( + transferReq, + ) const outputsData = [`0x02${smtRootHash}`] const cellDeps = [getCotaCellDep(isMainnet)] const headerDeps = [`0x${withdrawBlockHash}`] - + const rawTx = { version: '0x0', cellDeps, diff --git a/src/service/registry/index.ts b/src/service/registry/index.ts index 1cf45cc..a2110f9 100644 --- a/src/service/registry/index.ts +++ b/src/service/registry/index.ts @@ -75,7 +75,9 @@ export const generateRegisterCotaTx = async ( outputs[length - 1].capacity = `0x${(BigInt(outputs[length - 1].capacity) - fee).toString(16)}` const lockHashes = cotaLocks.map(lock => scriptToHash(lock)) - const { smtRootHash, registrySmtEntry, outputAccountNum } = await service.aggregator.generateRegisterCotaSmt(lockHashes) + const { smtRootHash, registrySmtEntry, outputAccountNum } = await service.aggregator.generateRegisterCotaSmt( + lockHashes, + ) const registryCellData = `0x01${smtRootHash}${u64ToBe(BigInt(outputAccountNum))}` const outputsData = outputs.map((_, i) => (i === 0 ? registryCellData : i !== outputs.length - 1 ? '0x02' : '0x')) @@ -97,7 +99,6 @@ export const generateRegisterCotaTx = async ( return rawTx } - export const generateUpdateCcidsTx = async ( service: Service, fee = FEE, From 90955e5a75e31ddedda802915ce38f9a601b45e7 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Fri, 30 Sep 2022 15:42:17 +0800 Subject: [PATCH 6/7] refactor: Update extension example --- example/extension.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/example/extension.ts b/example/extension.ts index c7c894e..86c260f 100644 --- a/example/extension.ts +++ b/example/extension.ts @@ -2,22 +2,28 @@ import { addressToScript } from '@nervosnetwork/ckb-sdk-utils' import { Collector } from '../src/collector' import { Aggregator } from '../src/aggregator' import { Service } from '../src' -import { generateExtensionTx } from '../src/service/cota/extension' +import { generateAddExtensionTx } from '../src/service/cota/extension' const TEST_PRIVATE_KEY = '0xee56672e70cec79941adc0637c1edb1546a0c39c72eff8c41bc4f1e03bf663b3' const TEST_ADDRESS = 'ckt1qyq897k5m53wxzup078jwkucvvsu8kzv55rqqm6glm' const secp256k1CellDep = (isMainnet: boolean): CKBComponents.CellDep => { if (isMainnet) { - return { outPoint: { - txHash: "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c", - index: "0x0", - }, depType: 'depGroup' } + return { + outPoint: { + txHash: '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c', + index: '0x0', + }, + depType: 'depGroup', + } + } + return { + outPoint: { + txHash: '0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37', + index: '0x0', + }, + depType: 'depGroup', } - return { outPoint: { - txHash: "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", - index: "0x0", - }, depType: 'depGroup' } } const run = async () => { @@ -25,18 +31,21 @@ const run = async () => { const isMainnet = false const service: Service = { - collector: new Collector({ ckbNodeUrl: 'https://testnet.ckb.dev/rpc', ckbIndexerUrl: 'https://testnet.ckb.dev/indexer' }), + collector: new Collector({ + ckbNodeUrl: 'https://testnet.ckb.dev/rpc', + ckbIndexerUrl: 'https://testnet.ckb.dev/indexer', + }), aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), } const ckb = service.collector.getCkb() const extensionLock = addressToScript(TEST_ADDRESS) - let rawTx = await generateExtensionTx(service, extensionLock, BigInt(6000), isMainnet) + let rawTx = await generateAddExtensionTx(service, extensionLock, BigInt(6000), isMainnet) rawTx.cellDeps.push(secp256k1CellDep(isMainnet)) const signedTx = ckb.signTransaction(TEST_PRIVATE_KEY)(rawTx) console.log(JSON.stringify(signedTx)) - + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') console.info(`Add cota extension tx has been sent with tx hash ${txHash}`) } From 071c7295e92e3a7e66e49d86df7bb2d1880f5dfa Mon Sep 17 00:00:00 2001 From: duanyytop Date: Fri, 30 Sep 2022 15:46:46 +0800 Subject: [PATCH 7/7] chore: Bump to v0.7.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index aaa1e7c..5fe9f6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nervina-labs/cota-sdk", - "version": "0.6.6", + "version": "0.7.0", "description": "The SDK of CoTA", "repository": "git@github.com:nervina-labs/cota-sdk-js.git", "author": "duanyytop ", @@ -47,4 +47,4 @@ "typescript": "4.8.3" }, "homepage": "https://github.com/nervina-labs/cota-sdk-js#readme" -} +} \ No newline at end of file