diff --git a/example/extension.ts b/example/extension.ts new file mode 100644 index 0000000..86c260f --- /dev/null +++ b/example/extension.ts @@ -0,0 +1,53 @@ +import { addressToScript } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { Service } from '../src' +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: '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 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}`) +} + +run() diff --git a/package.json b/package.json index b31ed1c..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 ", diff --git a/src/aggregator/index.ts b/src/aggregator/index.ts index 2f786cf..cc2fdd1 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/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/extension.ts b/src/service/cota/extension.ts new file mode 100644 index 0000000..40638fc --- /dev/null +++ b/src/service/cota/extension.ts @@ -0,0 +1,76 @@ +import { serializeScript } from '@nervosnetwork/ckb-sdk-utils' +import { Service } from '../..' +import { FEE, getCotaTypeScript, getCotaCellDep } from '../../constants' +import { ExtensionReq } from '../../types' + + +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) + 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 + + const prefix = action == Action.Add ? '0xF0' : '0xF1' + rawTx.witnesses = rawTx.inputs.map((_, i) => + 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 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, diff --git a/src/types/request.ts b/src/types/request.ts index 371d47b..c63e65d 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 +} diff --git a/src/types/response.ts b/src/types/response.ts index cd2992e..e35fa95 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 +}