diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6748471..e22ead6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,6 @@ name: Build on: [pull_request, push] - jobs: build: runs-on: ubuntu-latest @@ -16,5 +15,7 @@ jobs: run: yarn - name: Lint run: yarn lint + - name: Prettier + run: yarn check - name: Build - run: yarn build \ No newline at end of file + run: yarn build diff --git a/.husky/pre-commit b/.husky/pre-commit index 101f7b9..2bccb3c 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,5 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -yarn lint \ No newline at end of file +yarn lint +yarn check \ No newline at end of file diff --git a/README.md b/README.md index cf8fa1c..b19b51a 100644 --- a/README.md +++ b/README.md @@ -1 +1,22 @@ -# cota-sdk-js \ No newline at end of file +# cota-sdk-js + +[![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/nervina-labs/cota-sdk-js/blob/develop/LICENSE) +[![CI](https://github.com/nervina-labs/cota-sdk-js/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/nervina-labs/cota-sdk-js/actions) +[![NPM](https://img.shields.io/npm/v/@nervina-labs/cota-sdk/latest.svg)](https://www.npmjs.com/package/@nervina-labs/cota-sdk) + +JavaScript SDK for [CoTA](https://talk.nervos.org/t/rfc-cota-a-compact-token-aggregator-standard-for-extremely-low-cost-nfts-and-fts/6338). + +## Feature + +- Provide methods for [cota-aggregator](https://github.com/nervina-labs/cota-aggregator) and [cota-registry-aggregator](https://github.com/nervina-labs/cota-registry-aggregator) RPC APIs +- Provide methods to generate CoTA operating transactions + +## Examples + +- [aggregator example](https://github.com/nervina-labs/cota-sdk-js/blob/develop/example/aggregator.ts): Fetch CoTA NFT data and [SMT](https://github.com/nervosnetwork/sparse-merkle-tree) data from Aggregator server +- [registry example](https://github.com/nervina-labs/cota-sdk-js/blob/develop/example/define.ts): Generate defining CoTA cells transaction +- [mint example](https://github.com/nervina-labs/cota-sdk-js/blob/develop/example/mint.ts): Generate minting CoTA NFT transaction +- [claim example](https://github.com/nervina-labs/cota-sdk-js/blob/develop/example/claim.ts): Generate claiming CoTA NFT transaction +- [withdraw example](https://github.com/nervina-labs/cota-sdk-js/blob/develop/example/withdraw.ts): Generate withdrawing CoTA NFT transaction +- [transfer example](https://github.com/nervina-labs/cota-sdk-js/blob/develop/example/transfer.ts): Generate transferring CoTA NFT transaction +- [update example](https://github.com/nervina-labs/cota-sdk-js/blob/develop/example/update.ts): Generate updating CoTA NFT information transaction \ No newline at end of file diff --git a/example/aggregator.js b/example/aggregator.js index 12712a3..ceac26a 100644 --- a/example/aggregator.js +++ b/example/aggregator.js @@ -1,20 +1,22 @@ -const { Aggregator } = require("../lib/aggregator") +const { Aggregator } = require('../lib/aggregator') const run = async () => { - const aggregator = new Aggregator({registryUrl: "http://localhost:3050", cotaUrl: "http://localhost:3030"}) + const aggregator = new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }) const holds = await aggregator.getHoldCotaNft({ - lockScript: "0x490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000dc70f33de86fdf381b4fc5bf092bb23d02774801", + lockScript: + '0x490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000dc70f33de86fdf381b4fc5bf092bb23d02774801', page: 0, - pageSize: 10 + pageSize: 10, }) console.log(JSON.stringify(holds)) - const lock_hash = await aggregator.getCotaNftSender({ - lockScript: "0x490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000dc70f33de86fdf381b4fc5bf092bb23d02774801", - cotaId: "0xb22585a8053af3fed0fd39127f5b1487ce08b756", - tokenIndex: "0x00000000" + const senderLockHash = await aggregator.getCotaNftSender({ + lockScript: + '0x490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000dc70f33de86fdf381b4fc5bf092bb23d02774801', + cotaId: '0xb22585a8053af3fed0fd39127f5b1487ce08b756', + tokenIndex: '0x00000000', }) - console.log(JSON.stringify(lock_hash)) + console.log(JSON.stringify(senderLockHash)) } -run() \ No newline at end of file +run() diff --git a/example/aggregator.ts b/example/aggregator.ts index f28b983..fe12551 100644 --- a/example/aggregator.ts +++ b/example/aggregator.ts @@ -1,13 +1,22 @@ -import { Aggregator } from "../src/aggregator" +import { Aggregator } from '../src/aggregator' const run = async () => { - const aggregator = new Aggregator({registryUrl: "http://localhost:3050", cotaUrl: "http://localhost:3030"}) + const aggregator = new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }) const holds = await aggregator.getHoldCotaNft({ - lockScript: "0x490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000dc70f33de86fdf381b4fc5bf092bb23d02774801", + lockScript: + '0x490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000dc70f33de86fdf381b4fc5bf092bb23d02774801', page: 0, - pageSize: 10 + pageSize: 10, }) console.log(JSON.stringify(holds)) + + const senderLockHash = await aggregator.getCotaNftSender({ + lockScript: + '0x490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000dc70f33de86fdf381b4fc5bf092bb23d02774801', + cotaId: '0xb22585a8053af3fed0fd39127f5b1487ce08b756', + tokenIndex: '0x00000000', + }) + console.log(JSON.stringify(senderLockHash)) } -run() \ No newline at end of file +run() diff --git a/example/claim.ts b/example/claim.ts new file mode 100644 index 0000000..d3cbc76 --- /dev/null +++ b/example/claim.ts @@ -0,0 +1,43 @@ +import { addressToScript } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { generateClaimCotaTx } from '../src/service/cota' +import { Claim, Service } from '../src' +import CKB from '@nervosnetwork/ckb-sdk-core' + +const TEST_ADDRESS = 'ckt1qyq0scej4vn0uka238m63azcel7cmcme7f2sxj5ska' +const RECEIVER_PRIVATE_KEY = '0xcf56c11ce3fbec627e5118acd215838d1f9c5048039792d42143f933cde76311' +const RECEIVER_ADDRESS = 'ckt1qyqdcu8n8h5xlhecrd8ut0cf9wer6qnhfqqsnz3lw9' + +const secp256k1CellDep = async (ckb: CKB): Promise => { + const secp256k1Dep = (await ckb.loadDeps()).secp256k1Dep + return { outPoint: secp256k1Dep.outPoint, depType: 'depGroup' } +} + +const run = async () => { + const service: Service = { + collector: new Collector({ ckbNodeUrl: 'http://localhost:8114', ckbIndexerUrl: 'http://localhost:8116' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const claimLock = addressToScript(RECEIVER_ADDRESS) + const withdrawLock = addressToScript(TEST_ADDRESS) + + const claims: Claim[] = [ + { + cotaId: '0x1deb31f603652bf59ff5027b522e1d81c288b72f', + tokenIndex: '0x00000000', + }, + ] + let rawTx = await generateClaimCotaTx(service, claimLock, withdrawLock, claims) + + const secp256k1Dep = await secp256k1CellDep(ckb) + rawTx.cellDeps.push(secp256k1Dep) + + const signedTx = ckb.signTransaction(RECEIVER_PRIVATE_KEY)(rawTx) + console.log(JSON.stringify(signedTx)) + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.info(`Claim cota nft tx has been sent with tx hash ${txHash}`) +} + +run() diff --git a/example/define-flashsigner.ts b/example/define-flashsigner.ts new file mode 100644 index 0000000..97a5d57 --- /dev/null +++ b/example/define-flashsigner.ts @@ -0,0 +1,41 @@ +import { addressToScript, serializeWitnessArgs } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { generateDefineCotaTx } from '../src/service/cota' +import { Service } from '../src' +import { toSnakeCase } from '../src/utils' + +const TEST_ADDRESS = 'ckt1qpth5hjexr3wehtzqpm97dzzucgemjv7sl05wnez7y72hqvuszeyyqvz2vhrf3xz0jr8dcmxlv059kmpx4tt5vcluapd4' + +const run = async () => { + const service: Service = { + collector: new Collector({ ckbNodeUrl: 'http://localhost:8114', ckbIndexerUrl: 'http://localhost:8116' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const defineLock = addressToScript(TEST_ADDRESS) + let {rawTx, cotaId} = await generateDefineCotaTx(service, defineLock, 100, "0x00") + console.log(`cotaId: ${cotaId}`) + const flashsingerDep: CKBComponents.CellDep = { + outPoint: { + txHash: "0xb66776ff3244033fcd15312ae8b17d384c11bebbb923fce3bd896d89f4744d48", + index: "0x0", + }, + depType: "depGroup" + } + rawTx.cellDeps.push(flashsingerDep) + rawTx.witnesses = rawTx.witnesses.map(witness => witness !== '0x' ? serializeWitnessArgs(witness) : '0x') + + let signedTx = rawTx + rawTx = toSnakeCase(rawTx) + console.log(JSON.stringify(rawTx)) + + // TODO: Add witnesses signed by flashsigner + signedTx.witnesses = ["flashsigner-signed-witness"] + + console.log(JSON.stringify(signedTx)) + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.info(`Define cota nft tx has been sent with tx hash ${txHash}`) +} + +run() diff --git a/example/define.ts b/example/define.ts new file mode 100644 index 0000000..7c4e884 --- /dev/null +++ b/example/define.ts @@ -0,0 +1,34 @@ +import { addressToScript } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { generateDefineCotaTx } from '../src/service/cota' +import { Service } from '../src' +import CKB from '@nervosnetwork/ckb-sdk-core' + +const TEST_PRIVATE_KEY = '0xc5bd09c9b954559c70a77d68bde95369e2ce910556ddc20f739080cde3b62ef2' +const TEST_ADDRESS = 'ckt1qyq0scej4vn0uka238m63azcel7cmcme7f2sxj5ska' + +const secp256k1CellDep = async (ckb: CKB): Promise => { + const secp256k1Dep = (await ckb.loadDeps()).secp256k1Dep + return { outPoint: secp256k1Dep.outPoint, depType: 'depGroup' } +} + +const run = async () => { + const service: Service = { + collector: new Collector({ ckbNodeUrl: 'http://localhost:8114', ckbIndexerUrl: 'http://localhost:8116' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const defineLock = addressToScript(TEST_ADDRESS) + let {rawTx, cotaId} = await generateDefineCotaTx(service, defineLock, 100, "0x00") + console.log(`cotaId: ${cotaId}`) + const secp256k1Dep = await secp256k1CellDep(ckb) + rawTx.cellDeps.push(secp256k1Dep) + + const signedTx = ckb.signTransaction(TEST_PRIVATE_KEY)(rawTx) + console.log(JSON.stringify(signedTx)) + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.info(`Define cota nft tx has been sent with tx hash ${txHash}`) +} + +run() diff --git a/example/mint.ts b/example/mint.ts new file mode 100644 index 0000000..763d8b0 --- /dev/null +++ b/example/mint.ts @@ -0,0 +1,53 @@ +import { addressToScript, serializeScript } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { generateMintCotaTx } from '../src/service/cota' +import { MintCotaInfo, Service } from '../src' +import CKB from '@nervosnetwork/ckb-sdk-core' + +const TEST_PRIVATE_KEY = '0xc5bd09c9b954559c70a77d68bde95369e2ce910556ddc20f739080cde3b62ef2' +const TEST_ADDRESS = 'ckt1qyq0scej4vn0uka238m63azcel7cmcme7f2sxj5ska' +const RECEIVER_ADDRESS = 'ckt1qyqdcu8n8h5xlhecrd8ut0cf9wer6qnhfqqsnz3lw9' + +const secp256k1CellDep = async (ckb: CKB): Promise => { + const secp256k1Dep = (await ckb.loadDeps()).secp256k1Dep + return { outPoint: secp256k1Dep.outPoint, depType: 'depGroup' } +} + +const run = async () => { + const service: Service = { + collector: new Collector({ ckbNodeUrl: 'http://localhost:8114', ckbIndexerUrl: 'http://localhost:8116' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const mintLock = addressToScript(TEST_ADDRESS) + + const mintCotaInfo: MintCotaInfo = { + cotaId: "0x1deb31f603652bf59ff5027b522e1d81c288b72f", + withdrawals: [ + { + tokenIndex: '0x00000000', + state: '0x00', + characteristic: '0xa505050505050505050505050505050505050505', + toLockScript: serializeScript(addressToScript(RECEIVER_ADDRESS)), + }, + { + tokenIndex: '0x00000001', + state: '0x00', + characteristic: '0xa505050505050505050505050505050505050505', + toLockScript: serializeScript(addressToScript(RECEIVER_ADDRESS)), + }, + ] + } + let rawTx = await generateMintCotaTx(service, mintLock, mintCotaInfo) + + const secp256k1Dep = await secp256k1CellDep(ckb) + rawTx.cellDeps.push(secp256k1Dep) + + const signedTx = ckb.signTransaction(TEST_PRIVATE_KEY)(rawTx) + console.log(JSON.stringify(signedTx)) + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.info(`Mint cota nft tx has been sent with tx hash ${txHash}`) +} + +run() diff --git a/example/registry.js b/example/registry.js new file mode 100644 index 0000000..62ca2c7 --- /dev/null +++ b/example/registry.js @@ -0,0 +1,61 @@ +const { + addressToScript, + rawTransactionToHash, + scriptToHash, + serializeWitnessArgs, +} = require('@nervosnetwork/ckb-sdk-utils') +const { Collector } = require('../lib/collector') +const { Aggregator } = require('../lib/aggregator') +const { TestnetDeployment } = require('../lib/constants') +const { generateRegisterCotaTx } = require('../lib/service/registry') +const signWitnesses = require('@nervosnetwork/ckb-sdk-core/lib/signWitnesses') + +const TEST_PRIVATE_KEY = '0xc5bd09c9b954559c70a77d68bde95369e2ce910556ddc20f739080cde3b62ef2' +const TEST_ADDRESS = 'ckt1qyq0scej4vn0uka238m63azcel7cmcme7f2sxj5ska' + +const secp256k1CellDep = async ckb => { + const secp256k1Dep = (await ckb.loadDeps()).secp256k1Dep + return { outPoint: secp256k1Dep.outPoint, depType: 'depGroup' } +} + +const run = async () => { + const service = { + collector: new Collector({ ckbNodeUrl: 'http://localhost:8114', ckbIndexerUrl: 'http://localhost:8116' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const provideCKBLock = addressToScript(TEST_ADDRESS) + const unregisteredCotaLock = addressToScript(TEST_ADDRESS) + let rawTx = await generateRegisterCotaTx(service, [unregisteredCotaLock], provideCKBLock) + const secp256k1Dep = await secp256k1CellDep(ckb) + rawTx.cellDeps.push(secp256k1Dep) + + const registryLock = TestnetDeployment.AlwaysSuccessLockScript + + let keyMap = new Map() + keyMap.set(scriptToHash(registryLock), '') + keyMap.set(scriptToHash(provideCKBLock), TEST_PRIVATE_KEY) + + const cells = rawTx.inputs.map((input, index) => ({ + outPoint: input.previousOutput, + lock: index === 0 ? registryLock : provideCKBLock, + })) + + const transactionHash = rawTransactionToHash(rawTx) + + const signedWitnesses = signWitnesses(keyMap)({ + transactionHash, + witnesses: rawTx.witnesses, + inputCells: cells, + skipMissingKeys: true, + }) + const signedTx = { + ...rawTx, + witnesses: signedWitnesses.map(witness => (typeof witness === 'string' ? witness : serializeWitnessArgs(witness))), + } + console.log(JSON.stringify(signedTx)) + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.log(`Register cota cell tx has been sent with tx hash ${txHash}`) +} + +run() diff --git a/example/registry.ts b/example/registry.ts new file mode 100644 index 0000000..5d80c1b --- /dev/null +++ b/example/registry.ts @@ -0,0 +1,58 @@ +import { addressToScript, rawTransactionToHash, scriptToHash, serializeWitnessArgs } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { TestnetDeployment } from '../src/constants' +import { generateRegisterCotaTx } from '../src/service/registry' +import { Service } from '../src' +import CKB from '@nervosnetwork/ckb-sdk-core' +import signWitnesses from '@nervosnetwork/ckb-sdk-core/lib/signWitnesses' + +const TEST_PRIVATE_KEY = '0xc5bd09c9b954559c70a77d68bde95369e2ce910556ddc20f739080cde3b62ef2' +const TEST_ADDRESS = 'ckt1qyq0scej4vn0uka238m63azcel7cmcme7f2sxj5ska' + +const secp256k1CellDep = async (ckb: CKB): Promise => { + const secp256k1Dep = (await ckb.loadDeps()).secp256k1Dep + return { outPoint: secp256k1Dep.outPoint, depType: 'depGroup' } +} + +const run = async () => { + const service: Service = { + collector: new Collector({ ckbNodeUrl: 'http://localhost:8114', ckbIndexerUrl: 'http://localhost:8116' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const provideCKBLock = addressToScript(TEST_ADDRESS) + const unregisteredCotaLock = addressToScript(TEST_ADDRESS) + let rawTx = await generateRegisterCotaTx(service, [unregisteredCotaLock], provideCKBLock) + const secp256k1Dep = await secp256k1CellDep(ckb) + rawTx.cellDeps.push(secp256k1Dep) + + const registryLock = TestnetDeployment.AlwaysSuccessLockScript + + let keyMap = new Map() + keyMap.set(scriptToHash(registryLock), '') + keyMap.set(scriptToHash(provideCKBLock), TEST_PRIVATE_KEY) + + const cells = rawTx.inputs.map((input, index) => ({ + outPoint: input.previousOutput, + lock: index === 0 ? registryLock : provideCKBLock, + })) + + const transactionHash = rawTransactionToHash(rawTx) + + const signedWitnesses = signWitnesses(keyMap)({ + transactionHash, + witnesses: rawTx.witnesses, + inputCells: cells, + skipMissingKeys: true, + }) + const signedTx = { + ...rawTx, + witnesses: signedWitnesses.map(witness => (typeof witness === 'string' ? witness : serializeWitnessArgs(witness))), + } + console.log(JSON.stringify(signedTx)) + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.log(`Register cota cell tx has been sent with tx hash ${txHash}`) +} + +run() diff --git a/example/transfer.ts b/example/transfer.ts new file mode 100644 index 0000000..de9dc72 --- /dev/null +++ b/example/transfer.ts @@ -0,0 +1,45 @@ +import { addressToScript, serializeScript } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { generateTransferCotaTx } from '../src/service/cota' +import { TransferWithdrawal, Service } from '../src' +import CKB from '@nervosnetwork/ckb-sdk-core' + +const TEST_ADDRESS = 'ckt1qyq0scej4vn0uka238m63azcel7cmcme7f2sxj5ska' +const RECEIVER_PRIVATE_KEY = '0xcf56c11ce3fbec627e5118acd215838d1f9c5048039792d42143f933cde76311' +const RECEIVER_ADDRESS = 'ckt1qyqdcu8n8h5xlhecrd8ut0cf9wer6qnhfqqsnz3lw9' +const OTHER_ADDRESS = 'ckt1qyqz8vxeyrv4nur4j27ktp34fmwnua9wuyqqggd748' + +const secp256k1CellDep = async (ckb: CKB): Promise => { + const secp256k1Dep = (await ckb.loadDeps()).secp256k1Dep + return { outPoint: secp256k1Dep.outPoint, depType: 'depGroup' } +} + +const run = async () => { + const service: Service = { + collector: new Collector({ ckbNodeUrl: 'http://localhost:8114', ckbIndexerUrl: 'http://localhost:8116' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const cotaLock = addressToScript(RECEIVER_ADDRESS) + const withdrawLock = addressToScript(TEST_ADDRESS) + + const transfers: TransferWithdrawal[] = [ + { + cotaId: '0x1deb31f603652bf59ff5027b522e1d81c288b72f', + tokenIndex: '0x00000001', + toLockScript: serializeScript(addressToScript(OTHER_ADDRESS)) + }, + ] + let rawTx = await generateTransferCotaTx(service, cotaLock, withdrawLock, transfers) + + const secp256k1Dep = await secp256k1CellDep(ckb) + rawTx.cellDeps.push(secp256k1Dep) + + const signedTx = ckb.signTransaction(RECEIVER_PRIVATE_KEY)(rawTx) + console.log(JSON.stringify(signedTx)) + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.info(`Transfer cota nft tx has been sent with tx hash ${txHash}`) +} + +run() diff --git a/example/update.ts b/example/update.ts new file mode 100644 index 0000000..ff1d7c2 --- /dev/null +++ b/example/update.ts @@ -0,0 +1,44 @@ +import { addressToScript } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { generateUpdateCotaTx } from '../src/service/cota' +import { CotaNft, Service } from '../src' +import CKB from '@nervosnetwork/ckb-sdk-core' + +const TEST_PRIVATE_KEY = '0xc5bd09c9b954559c70a77d68bde95369e2ce910556ddc20f739080cde3b62ef2' +const TEST_ADDRESS = 'ckt1qyq0scej4vn0uka238m63azcel7cmcme7f2sxj5ska' + +const secp256k1CellDep = async (ckb: CKB): Promise => { + const secp256k1Dep = (await ckb.loadDeps()).secp256k1Dep + return { outPoint: secp256k1Dep.outPoint, depType: 'depGroup' } +} + +const run = async () => { + const service: Service = { + collector: new Collector({ ckbNodeUrl: 'http://localhost:8114', ckbIndexerUrl: 'http://localhost:8116' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const cotaLock = addressToScript(TEST_ADDRESS) + + const cotaNfts: CotaNft[] = [ + { + cotaId: '0x1deb31f603652bf59ff5027b522e1d81c288b72f', + tokenIndex: '0x00000000', + state: '0x00', + characteristic: '0x0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a' + } + ] + + let rawTx = await generateUpdateCotaTx(service, cotaLock, cotaNfts) + + const secp256k1Dep = await secp256k1CellDep(ckb) + rawTx.cellDeps.push(secp256k1Dep) + + const signedTx = ckb.signTransaction(TEST_PRIVATE_KEY)(rawTx) + console.log(JSON.stringify(signedTx)) + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.info(`Update cota nft tx has been sent with tx hash ${txHash}`) +} + +run() diff --git a/example/withdraw.ts b/example/withdraw.ts new file mode 100644 index 0000000..9ca3e3b --- /dev/null +++ b/example/withdraw.ts @@ -0,0 +1,44 @@ +import { addressToScript, serializeScript } from '@nervosnetwork/ckb-sdk-utils' +import { Collector } from '../src/collector' +import { Aggregator } from '../src/aggregator' +import { generateWithdrawCotaTx } from '../src/service/cota' +import { Service, TransferWithdrawal } from '../src' +import CKB from '@nervosnetwork/ckb-sdk-core' + +const TEST_ADDRESS = 'ckt1qyq0scej4vn0uka238m63azcel7cmcme7f2sxj5ska' +const RECEIVER_PRIVATE_KEY = '0xcf56c11ce3fbec627e5118acd215838d1f9c5048039792d42143f933cde76311' +const RECEIVER_ADDRESS = 'ckt1qyqdcu8n8h5xlhecrd8ut0cf9wer6qnhfqqsnz3lw9' + +const secp256k1CellDep = async (ckb: CKB): Promise => { + const secp256k1Dep = (await ckb.loadDeps()).secp256k1Dep + return { outPoint: secp256k1Dep.outPoint, depType: 'depGroup' } +} + +const run = async () => { + const service: Service = { + collector: new Collector({ ckbNodeUrl: 'http://localhost:8114', ckbIndexerUrl: 'http://localhost:8116' }), + aggregator: new Aggregator({ registryUrl: 'http://localhost:3050', cotaUrl: 'http://localhost:3030' }), + } + const ckb = service.collector.getCkb() + const withdrawLock = addressToScript(RECEIVER_ADDRESS) + const toLock = addressToScript(TEST_ADDRESS) + + const withdrawals: TransferWithdrawal[] = [ + { + cotaId: '0x1deb31f603652bf59ff5027b522e1d81c288b72f', + tokenIndex: '0x00000000', + toLockScript: serializeScript(toLock) + }, + ] + let rawTx = await generateWithdrawCotaTx(service, withdrawLock, withdrawals) + + const secp256k1Dep = await secp256k1CellDep(ckb) + rawTx.cellDeps.push(secp256k1Dep) + + const signedTx = ckb.signTransaction(RECEIVER_PRIVATE_KEY)(rawTx) + console.log(JSON.stringify(signedTx)) + let txHash = await ckb.rpc.sendTransaction(signedTx, 'passthrough') + console.info(`Withdraw cota nft tx has been sent with tx hash ${txHash}`) +} + +run() diff --git a/package.json b/package.json index 8df841e..576460d 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,16 @@ { - "name": "cota-sdk-js", - "version": "1.0.0", + "name": "@nervina-labs/cota-sdk", + "version": "0.1.0", "description": "The SDK of CoTA", "repository": "git@github.com:nervina-labs/cota-sdk-js.git", "author": "duanyytop ", "license": "MIT", + "bugs": { + "url": "https://github.com/nervina-labs/cota-sdk-js/issues" + }, + "publishConfig": { + "access": "public" + }, "main": "lib/index.js", "types": "lib/index.d.ts", "files": [ @@ -13,22 +19,24 @@ "scripts": { "build": "rm -rf lib && tsc", "lint": "eslint src/**/*.ts --fix", + "format": "prettier --write .", + "check": "prettier --check .", "prepare": "husky install" }, "dependencies": { - "@nervosnetwork/ckb-sdk-core": "^0.101.0", - "@nervosnetwork/ckb-sdk-utils": "^0.101.0", - "@nervosnetwork/ckb-types": "^0.101.0", - "axios": "^0.25.0", - "camelcase-keys": "^7.0.1" + "@nervosnetwork/ckb-sdk-core": "^0.102.0", + "@nervosnetwork/ckb-sdk-utils": "^0.102.0", + "@nervosnetwork/ckb-types": "^0.102.0", + "axios": "^0.26.0", + "camelcase-keys": "^7.0.2" }, "devDependencies": { - "@types/crypto-js": "4.1.0", - "@types/node": "17.0.13", - "@typescript-eslint/parser": "5.11.0", + "@types/crypto-js": "4.1.1", + "@types/node": "17.0.18", + "@typescript-eslint/parser": "5.12.0", "babel-eslint": "10.1.0", "convert-keys": "1.3.4", - "eslint": "8.8.0", + "eslint": "8.9.0", "eslint-config-prettier": "8.3.0", "eslint-plugin-import": "2.25.4", "eslint-plugin-prettier": "4.0.0", @@ -37,5 +45,6 @@ "ts-node": "10.5.0", "tslib": "2.3.1", "typescript": "4.5.5" - } + }, + "homepage": "https://github.com/nervina-labs/cota-sdk-js#readme" } \ No newline at end of file diff --git a/renovate.json b/renovate.json index f45d8f1..4f39080 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,3 @@ { - "extends": [ - "config:base" - ] + "extends": ["config:base"] } diff --git a/src/collector/index.ts b/src/collector/index.ts index b9d64c7..e7e0c51 100644 --- a/src/collector/index.ts +++ b/src/collector/index.ts @@ -7,11 +7,15 @@ export class Collector { private ckbNodeUrl: string private ckbIndexerUrl: string - constructor(ckbNodeUrl: string, ckbIndexerUrl: string) { + constructor({ ckbNodeUrl, ckbIndexerUrl }: { ckbNodeUrl: string; ckbIndexerUrl: string }) { this.ckbNodeUrl = ckbNodeUrl this.ckbIndexerUrl = ckbIndexerUrl } + getCkb() { + return new CKB(this.ckbNodeUrl) + } + async getCells(lock: CKBComponents.Script, type?: CKBComponents.Script): Promise { const filter = type ? { @@ -60,7 +64,7 @@ export class Collector { throw Error('Get cells error') } else { console.log(JSON.stringify(response)) - return toCamelcase(response.result) + return toCamelcase(response.result.objects) } } diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..ea68597 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,32 @@ +export const FEE = BigInt(1600000) +export const MIN_CAPACITY = BigInt(61) * BigInt(100000000) + +export const TestnetDeployment = { + RegistryTypeScript: { + codeHash: '0x9302db6cc1344b81a5efee06962abcb40427ecfcbe69d471b01b2658ed948075', + hashType: 'type', + args: '0xf9910364e0ca81a0e074f3aa42fe78cfcc880da6', + } as CKBComponents.Script, + + CotaTypeScript: { + codeHash: '0x89cd8003a0eaf8e65e0c31525b7d1d5c1becefd2ea75bb4cff87810ae37764d8', + hashType: 'type', + args: '0x', + } as CKBComponents.Script, + + CotaTypeDep: { + outPoint: { txHash: '0x678fb90d09e44e1389d71cf1734b3e98c71718b0c2582a1088c27e2aa384895f', index: '0x0' }, + depType: 'depGroup', + } as CKBComponents.CellDep, + + AlwaysSuccessLockScript: { + codeHash: '0x1157470ca9de091c21c262bf0754b777f3529e10d2728db8f6b4e04cfc2fbb5f', + hashType: 'data', + args: '0x', + } as CKBComponents.Script, + + AlwaysSuccessLockDep: { + outPoint: { txHash: '0x46a7625a76cf7401eff1dfe4f46138be69316518c9771c9f780a428843c6b5b1', index: '0x0' }, + depType: 'code', + } as CKBComponents.CellDep, +} diff --git a/src/index.ts b/src/index.ts index 19e5ab3..298131c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,8 @@ -export { Aggregator } from "./aggregator" -export * from "./types/index" +export { Aggregator } from './aggregator' +export { Collector } from './collector' -export { Collector } from "./collector" -export { IndexerCell, CollectResult } from "./types/collector"; \ No newline at end of file +export * from './types/index' +export * from './constants' +export * from './service/registry' +export * from './service/cota' +export * from './utils' diff --git a/src/service/cota/claim.ts b/src/service/cota/claim.ts new file mode 100644 index 0000000..cdce281 --- /dev/null +++ b/src/service/cota/claim.ts @@ -0,0 +1,61 @@ +import { scriptToHash, serializeScript } from '@nervosnetwork/ckb-sdk-utils' +import { ClaimReq, Service, Claim } from '../..' +import { FEE, TestnetDeployment } from '../../constants' + +export const generateClaimCotaTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + withdrawalLock: CKBComponents.Script, + claims: Claim[], + fee = FEE, +) => { + const cotaType = TestnetDeployment.CotaTypeScript + 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 withdrawalLockHash = scriptToHash(withdrawalLock) + const withdrawalCotaCells = await service.collector.getCells(withdrawalLock, cotaType) + if (!withdrawalCotaCells || withdrawalCotaCells.length === 0) { + throw new Error("Withdrawal cota cell doesn't exist") + } + const withdrawalCotaCell = withdrawalCotaCells[0] + + const claimReq: ClaimReq = { + lockScript: serializeScript(cotaLock), + withdrawalLockHash, + claims: claims, + } + + const { smtRootHash, claimSmtEntry } = await service.aggregator.generateClaimCotaSmt(claimReq) + const outputsData = [`0x00${smtRootHash}`] + + const withdrawalCellDep: CKBComponents.CellDep = { outPoint: withdrawalCotaCell.outPoint, depType: 'code' } + const cellDeps = [withdrawalCellDep, TestnetDeployment.CotaTypeDep] + + const rawTx = { + version: '0x0', + cellDeps, + headerDeps: [], + inputs, + outputs, + outputsData, + witnesses: [], + } + rawTx.witnesses = rawTx.inputs.map((_, i) => + i > 0 ? '0x' : { lock: '', inputType: `0x04${claimSmtEntry}`, outputType: '' }, + ) + return rawTx +} diff --git a/src/service/cota/define.ts b/src/service/cota/define.ts new file mode 100644 index 0000000..5314014 --- /dev/null +++ b/src/service/cota/define.ts @@ -0,0 +1,68 @@ +import { hexToBytes, PERSONAL, scriptToHash, serializeInput } from '@nervosnetwork/ckb-sdk-utils' +import blake2b from '@nervosnetwork/ckb-sdk-utils/lib/crypto/blake2b' +import { Byte, Service, DefineReq } from '../..' +import { FEE, TestnetDeployment } from '../../constants' +import { u8ToHex, u32ToBe, append0x } from '../../utils' + +const generateCotaId = (firstInput: CKBComponents.CellInput, definesIndex: number) => { + const input = hexToBytes(serializeInput(firstInput)) + const s = blake2b(32, null, null, PERSONAL) + s.update(input) + s.update(hexToBytes(`0x${u8ToHex(definesIndex)}`)) + return `0x${s.digest('hex').slice(0, 40)}` +} + +export const generateDefineCotaTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + total: number, + confiure: Byte, + fee = FEE, +) => { + const cotaType = TestnetDeployment.CotaTypeScript + 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 cotaId = generateCotaId(inputs[0], 0) + console.info(`cotaId: ${cotaId}`) + + const defineReq: DefineReq = { + lockHash: scriptToHash(cotaLock), + cotaId, + total: append0x(u32ToBe(total)), + issued: '0x00000000', + configure: append0x(confiure), + } + + const { smtRootHash, defineSmtEntry } = await service.aggregator.generateDefineCotaSmt(defineReq) + const cotaCellData = `0x00${smtRootHash}` + + const outputsData = [cotaCellData] + const cellDeps = [TestnetDeployment.CotaTypeDep] + + const rawTx = { + version: '0x0', + cellDeps, + headerDeps: [], + inputs, + outputs, + outputsData, + witnesses: [], + } + rawTx.witnesses = rawTx.inputs.map((_, i) => + i > 0 ? '0x' : { lock: '', inputType: `0x01${defineSmtEntry}`, outputType: '' }, + ) + return { rawTx, cotaId } +} diff --git a/src/service/cota/index.ts b/src/service/cota/index.ts new file mode 100644 index 0000000..c28881b --- /dev/null +++ b/src/service/cota/index.ts @@ -0,0 +1,6 @@ +export * from './define' +export * from './mint' +export * from './withdraw' +export * from './claim' +export * from './update' +export * from './transfer' diff --git a/src/service/cota/mint.ts b/src/service/cota/mint.ts new file mode 100644 index 0000000..4ca2a71 --- /dev/null +++ b/src/service/cota/mint.ts @@ -0,0 +1,55 @@ +import { scriptToHash, serializeOutPoint } from '@nervosnetwork/ckb-sdk-utils' +import { Byte, Byte20, Byte4, Bytes, MintReq, Service } from '../..' +import { FEE, TestnetDeployment } from '../../constants' +import { MintCotaInfo } from '../../types/service' +import { append0x } from '../../utils' + +export const generateMintCotaTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + mintCotaInfo: MintCotaInfo, + fee = FEE, +) => { + const cotaType = TestnetDeployment.CotaTypeScript + 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 mintReq: MintReq = { + lockHash: scriptToHash(cotaLock), + cotaId: append0x(mintCotaInfo.cotaId), + outPoint: append0x(serializeOutPoint(cotaCell.outPoint).slice(26)), + withdrawals: mintCotaInfo.withdrawals, + } + + const { smtRootHash, mintSmtEntry } = await service.aggregator.generateMintCotaSmt(mintReq) + const cotaCellData = `0x00${smtRootHash}` + + const outputsData = [cotaCellData] + const cellDeps = [TestnetDeployment.CotaTypeDep] + + const rawTx = { + version: '0x0', + cellDeps, + headerDeps: [], + inputs, + outputs, + outputsData, + witnesses: [], + } + rawTx.witnesses = rawTx.inputs.map((_, i) => + i > 0 ? '0x' : { lock: '', inputType: `0x02${mintSmtEntry}`, outputType: '' }, + ) + return rawTx +} diff --git a/src/service/cota/transfer.ts b/src/service/cota/transfer.ts new file mode 100644 index 0000000..54b8cf1 --- /dev/null +++ b/src/service/cota/transfer.ts @@ -0,0 +1,60 @@ +import { scriptToHash, serializeOutPoint, serializeScript } from '@nervosnetwork/ckb-sdk-utils' +import { Service, TransferReq, TransferWithdrawal } from '../..' +import { FEE, TestnetDeployment } from '../../constants' +import { append0x } from '../../utils/hex' + +export const generateTransferCotaTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + withdrawalLock: CKBComponents.Script, + transfers: TransferWithdrawal[], + fee = FEE, +) => { + const cotaType = TestnetDeployment.CotaTypeScript + 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 cotaLockScript = serializeScript(cotaLock) + const withdrawalLockHash = scriptToHash(withdrawalLock) + const withdrawalCotaCells = await service.collector.getCells(withdrawalLock, cotaType) + if (!withdrawalCotaCells || withdrawalCotaCells.length === 0) { + throw new Error("Withdrawal cota cell doesn't exist") + } + const withdrawalCotaCell = withdrawalCotaCells[0] + + const transferReq: TransferReq = { + lockScript: cotaLockScript, + withdrawalLockHash, + transferOutPoint: append0x(serializeOutPoint(cotaCell.outPoint).slice(26)), + transfers, + } + const { smtRootHash, transferSmtEntry } = await service.aggregator.generateTransferCotaSmt(transferReq) + const outputsData = [`0x00${smtRootHash}`] + + const withdrawalCellDep: CKBComponents.CellDep = { outPoint: withdrawalCotaCell.outPoint, depType: 'code' } + const cellDeps = [withdrawalCellDep, TestnetDeployment.CotaTypeDep] + const rawTx = { + version: '0x0', + cellDeps, + headerDeps: [], + inputs, + outputs, + outputsData, + witnesses: [], + } + rawTx.witnesses = rawTx.inputs.map((_, i) => + i > 0 ? '0x' : { lock: '', inputType: `0x06${transferSmtEntry}`, outputType: '' }, + ) + return rawTx +} diff --git a/src/service/cota/update.ts b/src/service/cota/update.ts new file mode 100644 index 0000000..a08c00a --- /dev/null +++ b/src/service/cota/update.ts @@ -0,0 +1,45 @@ +import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils' +import { CotaNft, Service, UpdateReq } from '../..' +import { FEE, TestnetDeployment } from '../../constants' + +export const generateUpdateCotaTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + cotaNfts: CotaNft[], + fee = FEE, +) => { + const cotaType = TestnetDeployment.CotaTypeScript + 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 updateReq: UpdateReq = { + lockHash: scriptToHash(cotaLock), + nfts: cotaNfts, + } + const { smtRootHash, updateSmtEntry } = await service.aggregator.generateUpdateCotaSmt(updateReq) + const outputsData = [`0x00${smtRootHash}`] + const cellDeps = [TestnetDeployment.CotaTypeDep] + const rawTx = { + version: '0x0', + cellDeps, + headerDeps: [], + inputs, + outputs, + outputsData, + witnesses: [], + } + rawTx.witnesses = rawTx.inputs.map((_, i) => + i > 0 ? '0x' : { lock: '', inputType: `0x05${updateSmtEntry}`, outputType: '' }, + ) + return rawTx +} diff --git a/src/service/cota/withdraw.ts b/src/service/cota/withdraw.ts new file mode 100644 index 0000000..e34a529 --- /dev/null +++ b/src/service/cota/withdraw.ts @@ -0,0 +1,48 @@ +import { scriptToHash, serializeOutPoint } from '@nervosnetwork/ckb-sdk-utils' +import { Service, TransferWithdrawal, WithdrawalReq } from '../../types' +import { FEE, TestnetDeployment } from '../../constants' +import { append0x } from '../../utils' + +export const generateWithdrawCotaTx = async ( + service: Service, + cotaLock: CKBComponents.Script, + withdrawals: TransferWithdrawal[], + fee = FEE, +) => { + const cotaType = TestnetDeployment.CotaTypeScript + 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 withdrawalReq: WithdrawalReq = { + lockHash: scriptToHash(cotaLock), + outPoint: append0x(serializeOutPoint(cotaCell.outPoint).slice(26)), + withdrawals: withdrawals, + } + const { smtRootHash, withdrawalSmtEntry } = await service.aggregator.generateWithdrawalCotaSmt(withdrawalReq) + const outputsData = [`0x00${smtRootHash}`] + const cellDeps = [TestnetDeployment.CotaTypeDep] + const rawTx = { + version: '0x0', + cellDeps, + headerDeps: [], + inputs, + outputs, + outputsData, + witnesses: [], + } + rawTx.witnesses = rawTx.inputs.map((_, i) => + i > 0 ? '0x' : { lock: '', inputType: `0x03${withdrawalSmtEntry}`, outputType: '' }, + ) + return rawTx +} diff --git a/src/service/registry/index.ts b/src/service/registry/index.ts new file mode 100644 index 0000000..4ab5f93 --- /dev/null +++ b/src/service/registry/index.ts @@ -0,0 +1,89 @@ +import { scriptToHash, serializeWitnessArgs } from '@nervosnetwork/ckb-sdk-utils' +import { Service } from '../..' +import { FEE, TestnetDeployment } from '../../constants' +import { append0x, remove0x } from '../../utils/hex' + +const COTA_CELL_CAPACITY = BigInt(150) * BigInt(100000000) + +const generateCotaOutputs = async ( + inputCapacity: bigint, + cotaLocks: CKBComponents.Script[], +): Promise => { + const registryLock = TestnetDeployment.AlwaysSuccessLockScript + let outputs: CKBComponents.CellOutput[] = cotaLocks.map(lock => { + const args = append0x(remove0x(scriptToHash(lock)).slice(0, 40)) + const cotaType = { ...TestnetDeployment.CotaTypeScript, args } + return { + capacity: `0x${COTA_CELL_CAPACITY.toString(16)}`, + lock, + type: cotaType, + } + }) + + const cotaCellsLength = BigInt(cotaLocks.length) + const changeCapacity = inputCapacity - FEE - COTA_CELL_CAPACITY * cotaCellsLength + outputs.push({ + capacity: `0x${changeCapacity.toString(16)}`, + lock: registryLock, + type: null, + }) + return outputs +} + +export const generateRegisterCotaTx = async ( + service: Service, + cotaLocks: CKBComponents.Script[], + lock: CKBComponents.Script, + fee = FEE, +): Promise => { + const cotaCount = BigInt(cotaLocks.length) + const registryLock = TestnetDeployment.AlwaysSuccessLockScript + const registryType = TestnetDeployment.RegistryTypeScript + const registryCells = await service.collector.getCells(registryLock, registryType) + if (!registryCells || registryCells.length === 0) { + throw new Error("Registry cell doesn't exist") + } + let registryCell = registryCells[0] + const liveCells = await service.collector.getCells(lock) + console.log(JSON.stringify(liveCells)) + const { inputs: normalInputs, capacity } = await service.collector.collectInputs( + liveCells, + COTA_CELL_CAPACITY * cotaCount, + fee, + ) + + let inputs = [ + { + previousOutput: registryCell.outPoint, + since: '0x0', + }, + ] + inputs = inputs.concat(normalInputs) + + let outputs = await generateCotaOutputs(capacity, cotaLocks) + outputs = [registryCell.output].concat(outputs) + outputs.at(-1).capacity = `0x${(BigInt(outputs.at(-1).capacity) - FEE).toString(16)}` + + const lockHashes = cotaLocks.map(lock => scriptToHash(lock)) + const { smtRootHash, registrySmtEntry } = await service.aggregator.generateRegisterCotaSmt(lockHashes) + const registryCellData = `0x00${smtRootHash}` + + const outputsData = outputs.map((_, i) => (i === 0 ? registryCellData : i !== outputs.length - 1 ? '0x00' : '0x')) + + const cellDeps = [TestnetDeployment.AlwaysSuccessLockDep, TestnetDeployment.CotaTypeDep] + + let rawTx = { + version: '0x0', + cellDeps, + headerDeps: [], + inputs, + outputs, + outputsData, + witnesses: [], + } + const registryWitness = serializeWitnessArgs({ lock: '', inputType: append0x(registrySmtEntry), outputType: '' }) + const emptyWitness = { lock: '', inputType: '', outputType: '' } + rawTx.witnesses = rawTx.inputs.map((_, i) => (i === 0 ? registryWitness : i === 1 ? emptyWitness : '0x')) + console.log(JSON.stringify(rawTx)) + return rawTx +} diff --git a/src/types/index.ts b/src/types/index.ts index 73be32e..f214efb 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,3 +2,4 @@ export * from './common' export * from './request' export * from './response' export * from './collector' +export * from './service' diff --git a/src/types/request.ts b/src/types/request.ts index 43ad046..41e7f5e 100644 --- a/src/types/request.ts +++ b/src/types/request.ts @@ -54,14 +54,16 @@ export interface ClaimReq extends SmtReq { claims: Claim[] } +export interface CotaNft { + cotaId: Byte20 + tokenIndex: Byte4 + state: Byte + characteristic: Byte20 +} + export interface UpdateReq extends SmtReq { lockHash: Byte32 - nfts: { - cotaId: Byte20 - tokenIndex: Byte4 - state: Byte - characteristic: Byte20 - }[] + nfts: CotaNft[] } export interface GetCotaReq extends SmtReq { @@ -80,4 +82,4 @@ export interface GetCotaSenderReq extends SmtReq { lockScript: Bytes cotaId: Byte20 tokenIndex: Byte4 -} \ No newline at end of file +} diff --git a/src/types/response.ts b/src/types/response.ts index c7fd582..bbd40d2 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -84,8 +84,7 @@ export interface IsClaimedResp { blockNumber: bigint } - export interface GetCotaSenderResp { senderLockHash: Byte32 blockNumber: bigint -} \ No newline at end of file +} diff --git a/src/types/service.ts b/src/types/service.ts new file mode 100644 index 0000000..a0eb023 --- /dev/null +++ b/src/types/service.ts @@ -0,0 +1,12 @@ +import { Byte20, MintWithdrawal } from '.' +import { Aggregator, Collector } from '..' + +export interface MintCotaInfo { + cotaId: Byte20 + withdrawals: MintWithdrawal[] +} + +export interface Service { + collector: Collector + aggregator: Aggregator +} diff --git a/tsconfig.json b/tsconfig.json index fb2aef5..c873a1c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,11 +6,6 @@ "declaration": true, "moduleResolution": "node" }, - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules", - "example" - ] -} \ No newline at end of file + "include": ["src/**/*"], + "exclude": ["node_modules", "example"] +} diff --git a/yarn.lock b/yarn.lock index ad70506..94e3807 100644 --- a/yarn.lock +++ b/yarn.lock @@ -119,14 +119,14 @@ dependencies: "@cspotcode/source-map-consumer" "0.8.0" -"@eslint/eslintrc@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" - integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== +"@eslint/eslintrc@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.1.0.tgz#583d12dbec5d4f22f333f9669f7d0b7c7815b4d3" + integrity sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.2.0" + espree "^9.3.1" globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" @@ -148,40 +148,40 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== -"@nervosnetwork/ckb-sdk-core@^0.101.0": - version "0.101.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-core/-/ckb-sdk-core-0.101.0.tgz#2168869938e46f19a7f5b82ecdf0cb1c8ddc66a6" - integrity sha512-6vaYReSljgWiWhkzEMxKyV+utJnECzrWZW2817N1rQzWLYGeF/1yBKPNM4s34rglo14/Ktz8JcTAiebQCwM42g== +"@nervosnetwork/ckb-sdk-core@^0.102.0": + version "0.102.1" + resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-core/-/ckb-sdk-core-0.102.1.tgz#680c1ebe6425549132d96da288d7600b24dcd98a" + integrity sha512-q6T5ooNYzA9ZhlV4ad39Zn6ubVOa7gCFZ31O+LJAhSxRZDpKctTiwVbgvNFoa2vDvTZej6hT98fb2pACW4rYnQ== dependencies: - "@nervosnetwork/ckb-sdk-rpc" "0.101.0" - "@nervosnetwork/ckb-sdk-utils" "0.101.0" - "@nervosnetwork/ckb-types" "0.101.0" + "@nervosnetwork/ckb-sdk-rpc" "0.102.1" + "@nervosnetwork/ckb-sdk-utils" "0.102.1" + "@nervosnetwork/ckb-types" "0.102.1" tslib "2.3.1" -"@nervosnetwork/ckb-sdk-rpc@0.101.0": - version "0.101.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-rpc/-/ckb-sdk-rpc-0.101.0.tgz#aa8d6b25b66306e6d1dc21cf7a9c01ddc1f8aa2a" - integrity sha512-7cO0BpKGL1ujO5yj6GZ3hDXpnDpOlgIDdTUc5Cs776+6nH+Amb3xfRvk6Jn3PlCzpHBQ4KD80BezCdaxeBLJdg== +"@nervosnetwork/ckb-sdk-rpc@0.102.1": + version "0.102.1" + resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-rpc/-/ckb-sdk-rpc-0.102.1.tgz#6b9d98071428e81d1c28880991a4455458850478" + integrity sha512-xCIhiAunHt9peEY+doxNk28Nc74I8RAN0JHyyhxOYWTZqWg7EwvPeI9CNSi0Do4M/zgJ3Cln6yWjJQrSChMQuA== dependencies: - "@nervosnetwork/ckb-sdk-utils" "0.101.0" + "@nervosnetwork/ckb-sdk-utils" "0.102.1" axios "0.21.4" tslib "2.3.1" -"@nervosnetwork/ckb-sdk-utils@0.101.0", "@nervosnetwork/ckb-sdk-utils@^0.101.0": - version "0.101.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-utils/-/ckb-sdk-utils-0.101.0.tgz#f51b6dc6df9299544217984c2c3e29e09d050c60" - integrity sha512-1Cm66rQ8XawavZ69TjMiPRECzX77ndRf8DPUskn0QXyy9YU9bDGNSWVgIBnt0T5Nxl4Y+itAWFukltoEWpzU3A== +"@nervosnetwork/ckb-sdk-utils@0.102.1", "@nervosnetwork/ckb-sdk-utils@^0.102.0": + version "0.102.1" + resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-sdk-utils/-/ckb-sdk-utils-0.102.1.tgz#5e9bd9e2ebfbbd7b55f915e087e5cbd557bab34f" + integrity sha512-M+6P+8qhXiJoV5e77KtCLQQoKvuOAYdaCd6FEF5V6OhURbkRdhy6gxulY2YB7chlaw7QbvPhlVHlWB1DFOjkHw== dependencies: - "@nervosnetwork/ckb-types" "0.101.0" + "@nervosnetwork/ckb-types" "0.102.1" bech32 "2.0.0" elliptic "6.5.4" jsbi "3.1.3" tslib "2.3.1" -"@nervosnetwork/ckb-types@0.101.0", "@nervosnetwork/ckb-types@^0.101.0": - version "0.101.0" - resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-types/-/ckb-types-0.101.0.tgz#84c5f590bd8e2e06fdf56bab0c09995da0ada4ca" - integrity sha512-J82ANP44TvdYY1fCEom9RodMIFJYCb2oteSCU1TD9bY3tZZFrhRi+Muz0qh6f9wrR5F3NzdXQD8wJoPZ3Exr9Q== +"@nervosnetwork/ckb-types@0.102.1", "@nervosnetwork/ckb-types@^0.102.0": + version "0.102.1" + resolved "https://registry.yarnpkg.com/@nervosnetwork/ckb-types/-/ckb-types-0.102.1.tgz#95278bfdbc489ad6225ed8125a235ee7ba378882" + integrity sha512-WFE/wPwZm+UYpgKmP2QX2zQyzCkAocfjQyP8DWJds3KhsI+7Z9EjFZ3lzDEWclU/KokC1fOd9+Bu5DMrfkcZng== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -224,63 +224,63 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== -"@types/crypto-js@4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.0.tgz#09ba1b49bcce62c9a8e6d5e50a3364aa98975578" - integrity sha512-DCFfy/vh2lG6qHSGezQ+Sn2Ulf/1Mx51dqOdmOKyW5nMK3maLlxeS3onC7r212OnBM2pBR95HkAmAjjF08YkxQ== +"@types/crypto-js@4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" + integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/node@17.0.13": - version "17.0.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.13.tgz#5ed7ed7c662948335fcad6c412bb42d99ea754e3" - integrity sha512-Y86MAxASe25hNzlDbsviXl8jQHb0RDvKt4c40ZJQ1Don0AAL0STLZSs4N+6gLEO55pedy7r2cLwS+ZDxPm/2Bw== +"@types/node@17.0.18": + version "17.0.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074" + integrity sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA== -"@typescript-eslint/parser@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.11.0.tgz#b4fcaf65513f9b34bdcbffdda055724a5efb7e04" - integrity sha512-x0DCjetHZYBRovJdr3U0zG9OOdNXUaFLJ82ehr1AlkArljJuwEsgnud+Q7umlGDFLFrs8tU8ybQDFocp/eX8mQ== +"@typescript-eslint/parser@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.12.0.tgz#0ca669861813df99ce54916f66f524c625ed2434" + integrity sha512-MfSwg9JMBojMUoGjUmX+D2stoQj1CBYTCP0qnnVtu9A+YQXVKNtLjasYh+jozOcrb/wau8TCfWOkQTiOAruBog== dependencies: - "@typescript-eslint/scope-manager" "5.11.0" - "@typescript-eslint/types" "5.11.0" - "@typescript-eslint/typescript-estree" "5.11.0" + "@typescript-eslint/scope-manager" "5.12.0" + "@typescript-eslint/types" "5.12.0" + "@typescript-eslint/typescript-estree" "5.12.0" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.11.0.tgz#f5aef83ff253f457ecbee5f46f762298f0101e4b" - integrity sha512-z+K4LlahDFVMww20t/0zcA7gq/NgOawaLuxgqGRVKS0PiZlCTIUtX0EJbC0BK1JtR4CelmkPK67zuCgpdlF4EA== +"@typescript-eslint/scope-manager@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.12.0.tgz#59619e6e5e2b1ce6cb3948b56014d3a24da83f5e" + integrity sha512-GAMobtIJI8FGf1sLlUWNUm2IOkIjvn7laFWyRx7CLrv6nLBI7su+B7lbStqVlK5NdLvHRFiJo2HhiDF7Ki01WQ== dependencies: - "@typescript-eslint/types" "5.11.0" - "@typescript-eslint/visitor-keys" "5.11.0" + "@typescript-eslint/types" "5.12.0" + "@typescript-eslint/visitor-keys" "5.12.0" -"@typescript-eslint/types@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.11.0.tgz#ba345818a2540fdf2755c804dc2158517ab61188" - integrity sha512-cxgBFGSRCoBEhvSVLkKw39+kMzUKHlJGVwwMbPcTZX3qEhuXhrjwaZXWMxVfxDgyMm+b5Q5b29Llo2yow8Y7xQ== +"@typescript-eslint/types@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.12.0.tgz#5b4030a28222ee01e851836562c07769eecda0b8" + integrity sha512-JowqbwPf93nvf8fZn5XrPGFBdIK8+yx5UEGs2QFAYFI8IWYfrzz+6zqlurGr2ctShMaJxqwsqmra3WXWjH1nRQ== -"@typescript-eslint/typescript-estree@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.11.0.tgz#53f9e09b88368191e52020af77c312a4777ffa43" - integrity sha512-yVH9hKIv3ZN3lw8m/Jy5I4oXO4ZBMqijcXCdA4mY8ull6TPTAoQnKKrcZ0HDXg7Bsl0Unwwx7jcXMuNZc0m4lg== +"@typescript-eslint/typescript-estree@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.12.0.tgz#cabf545fd592722f0e2b4104711e63bf89525cd2" + integrity sha512-Dd9gVeOqt38QHR0BEA8oRaT65WYqPYbIc5tRFQPkfLquVEFPD1HAtbZT98TLBkEcCkvwDYOAvuSvAD9DnQhMfQ== dependencies: - "@typescript-eslint/types" "5.11.0" - "@typescript-eslint/visitor-keys" "5.11.0" + "@typescript-eslint/types" "5.12.0" + "@typescript-eslint/visitor-keys" "5.12.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.11.0.tgz#888542381f1a2ac745b06d110c83c0b261487ebb" - integrity sha512-E8w/vJReMGuloGxJDkpPlGwhxocxOpSVgSvjiLO5IxZPmxZF30weOeJYyPSEACwM+X4NziYS9q+WkN/2DHYQwA== +"@typescript-eslint/visitor-keys@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.12.0.tgz#1ac9352ed140b07ba144ebf371b743fdf537ec16" + integrity sha512-cFwTlgnMV6TgezQynx2c/4/tx9Tufbuo9LPzmWqyRC3QC4qTGkAG1C6pBr0/4I10PAI/FlYunI3vJjIcu+ZHMg== dependencies: - "@typescript-eslint/types" "5.11.0" + "@typescript-eslint/types" "5.12.0" eslint-visitor-keys "^3.0.0" acorn-jsx@^5.3.1: @@ -369,12 +369,12 @@ axios@0.21.4: dependencies: follow-redirects "^1.14.0" -axios@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" - integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== +axios@^0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.0.tgz#9a318f1c69ec108f8cd5f3c3d390366635e13928" + integrity sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og== dependencies: - follow-redirects "^1.14.7" + follow-redirects "^1.14.8" babel-eslint@10.1.0: version "10.1.0" @@ -436,17 +436,17 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase-keys@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.1.tgz#5a57e6dfb3f6c7929dad15599ee4476a7e9a3b2d" - integrity sha512-P331lEls98pW8JLyodNWfzuz91BEDVA4VpW2/SwXnyv2K495tq1N777xzDbFgnEigfA7UIY0xa6PwR/H9jijjA== +camelcase-keys@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252" + integrity sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg== dependencies: - camelcase "^6.2.0" + camelcase "^6.3.0" map-obj "^4.1.0" quick-lru "^5.1.1" type-fest "^1.2.1" -camelcase@^6.2.0: +camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -685,10 +685,10 @@ eslint-plugin-prettier@4.0.0: dependencies: prettier-linter-helpers "^1.0.0" -eslint-scope@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" - integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -710,17 +710,22 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: +eslint-visitor-keys@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== -eslint@8.8.0: - version "8.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d" - integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@8.9.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.9.0.tgz#a2a8227a99599adc4342fd9b854cb8d8d6412fdb" + integrity sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q== dependencies: - "@eslint/eslintrc" "^1.0.5" + "@eslint/eslintrc" "^1.1.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -728,10 +733,10 @@ eslint@8.8.0: debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.0" + eslint-scope "^7.1.1" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.2.0" - espree "^9.3.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -756,14 +761,14 @@ eslint@8.8.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.2.0, espree@^9.3.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" - integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== +espree@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" + integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== dependencies: acorn "^8.7.0" acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.1.0" + eslint-visitor-keys "^3.3.0" esquery@^1.4.0: version "1.4.0" @@ -861,11 +866,16 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== -follow-redirects@^1.14.0, follow-redirects@^1.14.7: +follow-redirects@^1.14.0: version "1.14.7" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== +follow-redirects@^1.14.8: + version "1.14.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" + integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"