├── .gitmodules ├── lerna.json ├── packages ├── ckb-sdk-rpc │ ├── misc │ │ └── basedirs │ │ │ ├── lib │ │ │ └── package.json │ │ │ └── lib-esm │ │ │ └── package.json │ ├── src │ │ ├── exceptions │ │ │ ├── index.ts │ │ │ ├── ErrorCode.ts │ │ │ ├── rpc.ts │ │ │ ├── batch.ts │ │ │ └── formatter.ts │ │ ├── Base │ │ │ ├── stats.ts │ │ │ ├── experimental.ts │ │ │ ├── pool.ts │ │ │ ├── net.ts │ │ │ └── chain.ts │ │ ├── method.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── README.md │ ├── tsconfig.esm.json │ ├── __tests__ │ │ ├── exceptions │ │ │ ├── index.test.js │ │ │ └── fixtures.json │ │ ├── formatters │ │ │ ├── result.test.js │ │ │ └── params.test.js │ │ ├── ckb-rpc-helpers.js │ │ └── method.test.js │ └── package.json ├── ckb-sdk-utils │ ├── src │ │ ├── reconcilers │ │ │ ├── index.ts │ │ │ └── extraInputs.ts │ │ ├── serialization │ │ │ ├── index.ts │ │ │ └── script.ts │ │ ├── exceptions │ │ │ ├── ErrorCode.ts │ │ │ ├── index.ts │ │ │ ├── privateKey.ts │ │ │ ├── transaction.ts │ │ │ ├── common.ts │ │ │ ├── string.ts │ │ │ ├── address.ts │ │ │ └── blake2b.ts │ │ ├── crypto │ │ │ ├── index.ts │ │ │ └── blake160.ts │ │ ├── const.ts │ │ ├── validators.ts │ │ ├── occupiedCapacity.ts │ │ ├── sizes.ts │ │ ├── calculateTransactionFee.ts │ │ ├── ecpair.ts │ │ ├── epochs.ts │ │ ├── convertors │ │ │ └── index.ts │ │ └── systemScripts.ts │ ├── misc │ │ └── basedirs │ │ │ ├── lib-esm │ │ │ └── package.json │ │ │ └── lib │ │ │ └── package.json │ ├── __tests__ │ │ ├── const.test.js │ │ ├── crypto │ │ │ ├── blake160.test.js │ │ │ ├── bech32.test.js │ │ │ └── blake2b.test.js │ │ ├── calculateTransactionFee │ │ │ ├── fixtures.json │ │ │ └── index.test.js │ │ ├── exceptions │ │ │ ├── index.test.js │ │ │ └── fixtures.json │ │ ├── sizes │ │ │ └── index.test.js │ │ ├── reconcilers │ │ │ └── extraInputs.test.js │ │ ├── validators │ │ │ ├── fixtures.json │ │ │ └── index.test.js │ │ ├── utils │ │ │ ├── occupiedCapacity.test.js │ │ │ ├── index.test.js │ │ │ └── rawTransactionToHash.fixtures.json │ │ ├── epochs │ │ │ ├── index.test.js │ │ │ └── fixtures.json │ │ ├── ecpair │ │ │ ├── ecpare.fixtures.json │ │ │ └── index.test.js │ │ ├── systemScripts │ │ │ ├── index.test.js │ │ │ └── fixtures.json │ │ ├── serialization │ │ │ ├── script │ │ │ │ ├── index.test.js │ │ │ │ └── fixtures.json │ │ │ └── basic │ │ │ │ ├── index.test.js │ │ │ │ └── fixtures.json │ │ ├── convertors │ │ │ ├── fixtures.json │ │ │ └── index.test.js │ │ └── address │ │ │ └── index.test.js │ ├── tsconfig.json │ ├── tsconfig.esm.json │ ├── package.json │ └── docs │ │ └── Fee_and_fee_rate.md ├── ckb-sdk-core │ ├── misc │ │ └── basedirs │ │ │ ├── lib-esm │ │ │ └── package.json │ │ │ └── lib │ │ │ └── package.json │ ├── tsconfig.json │ ├── README.md │ ├── __mocks__ │ │ ├── rpc.js │ │ ├── CellCollector.js │ │ └── data │ │ │ └── liveCell.json │ ├── tsconfig.esm.json │ ├── __tests__ │ │ ├── groupScripts │ │ │ ├── index.test.js │ │ │ └── fixtures.json │ │ ├── loadCellsFromIndexer │ │ │ ├── fixtures.json │ │ │ └── index.test.js │ │ ├── utils │ │ │ ├── index.test.js │ │ │ └── fixtures.json │ │ ├── generateRawTransaction │ │ │ ├── getLeftCells │ │ │ │ └── index.test.js │ │ │ ├── getTargetOutputs │ │ │ │ ├── index.test.js │ │ │ │ └── fixtures.json │ │ │ ├── getInputs │ │ │ │ ├── index.test.js │ │ │ │ └── fixtures.json │ │ │ ├── getBigInts │ │ │ │ ├── index.test.js │ │ │ │ └── fixtures.json │ │ │ ├── getKeyAndCellsPairs │ │ │ │ └── index.test.js │ │ │ └── index.test.js │ │ ├── signWitnesses │ │ │ └── index.test.js │ │ ├── multisig │ │ │ └── index.test.js │ │ └── signWitnessGroup │ │ │ ├── fixtures.json │ │ │ └── index.test.js │ ├── src │ │ ├── groupScripts.ts │ │ ├── utils.ts │ │ ├── loadCellsFromIndexer.ts │ │ ├── multisig.ts │ │ └── signWitnessGroup.ts │ ├── package.json │ ├── examples │ │ ├── sendTransactionWithLumosCollector.js │ │ ├── sendTransactionWithMultiplePrivateKey.js │ │ ├── sendAllBalance.js │ │ ├── signMultisig.js │ │ └── sendSimpleTransaction.js │ └── types │ │ └── global.d.ts └── ckb-types │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── makefile ├── .prettierrc ├── .gitignore ├── tsconfig.json ├── .github ├── dependabot.yml └── workflows │ ├── snyk.yml │ ├── docs.yml │ ├── merge_master_into_develop.yml │ ├── bundle.yml │ └── tests.yml ├── scripts └── bundle-umd.sh ├── codecov.yaml ├── .editorconfig ├── LICENSE ├── package.json └── .eslintrc.js /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.109.5" 3 | } 4 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/misc/basedirs/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/reconcilers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './extraInputs.js' 2 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/misc/basedirs/lib-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/misc/basedirs/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/misc/basedirs/lib-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/misc/basedirs/lib-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/misc/basedirs/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | ci-install: 2 | npm install -g codecov; 3 | npm install; 4 | lerna bootstrap --mutex file:/tmp/.yarn-mutex --concurrency=1; 5 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/serialization/index.ts: -------------------------------------------------------------------------------- 1 | export * from './basic.js' 2 | export * from './script.js' 3 | export * from './transaction.js' 4 | -------------------------------------------------------------------------------- /packages/ckb-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./lib" 5 | }, 6 | "files": ["index.d.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "arrowParens": "avoid" 9 | } 10 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/exceptions/index.ts: -------------------------------------------------------------------------------- 1 | export { HexStringWithout0xException } from '@nervosnetwork/ckb-sdk-utils' 2 | export * from './formatter.js' 3 | export * from './rpc.js' 4 | export * from './batch.js' 5 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/exceptions/ErrorCode.ts: -------------------------------------------------------------------------------- 1 | export enum ErrorCode { 2 | IdNotMatch = 201, 3 | MethodNotFound = 202, 4 | PayloadMessage = 203, 5 | ResponseMessage = 204, 6 | } 7 | 8 | export default ErrorCode 9 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/exceptions/ErrorCode.ts: -------------------------------------------------------------------------------- 1 | export enum ErrorCode { 2 | ParameterInvalid = 101, 3 | ParameterRequired, 4 | SignMessageFailed, 5 | AddressInvalid, 6 | ReconciliationFailed, 7 | } 8 | 9 | export default ErrorCode 10 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/exceptions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ErrorCode.js' 2 | export * from './common.js' 3 | export * from './string.js' 4 | export * from './address.js' 5 | export * from './blake2b.js' 6 | export * from './privateKey.js' 7 | export * from './transaction.js' 8 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "lib": ["es6", "esnext.array"], 6 | "typeRoots": ["@nervosnetwork/ckb-types", "nervosnetwork/ckb-sdk-utils", "types"] 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/crypto/index.ts: -------------------------------------------------------------------------------- 1 | import { bech32, bech32m } from 'bech32' 2 | import blake2b from './blake2b.js' 3 | import blake160 from './blake160.js' 4 | 5 | export { blake2b, blake160, bech32, bech32m } 6 | 7 | export default { 8 | blake2b, 9 | blake160, 10 | bech32, 11 | bech32m, 12 | } 13 | -------------------------------------------------------------------------------- /packages/ckb-types/README.md: -------------------------------------------------------------------------------- 1 | # `ckb-types` 2 | 3 | `@nervosnetwork/ckb-types` is the type definition module for the `@nervosnetwork/ckb-sdk-core` and its modules. 4 | 5 | [Type Doc](https://ckb-js.github.io/ckb-sdk-js/modules/ckbcomponents.html) 6 | 7 | See [Full Doc](https://github.com/ckb-js/ckb-sdk-js/blob/develop/README.md) 8 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/README.md: -------------------------------------------------------------------------------- 1 | # `ckb-sdk-core` 2 | 3 | `@nervosnetwork/ckb-sdk-core` is the JavaScript SDK for Nervos Network [CKB Project](https://github.com/nervosnetwork/ckb) 4 | 5 | [Type Doc](https://ckb-js.github.io/ckb-sdk-js/classes/ckb.html) 6 | 7 | See [Full Doc](https://github.com/ckb-js/ckb-sdk-js/blob/develop/README.md) 8 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/exceptions/privateKey.ts: -------------------------------------------------------------------------------- 1 | import ErrorCode from './ErrorCode.js' 2 | 3 | export class PrivateKeyLenException extends Error { 4 | code = ErrorCode.ParameterInvalid 5 | 6 | constructor() { 7 | super('Private key has invalid length') 8 | } 9 | } 10 | export default { 11 | PrivateKeyLenException, 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules/ 3 | *.log 4 | packages/*/lib 5 | packages/*/lib-esm 6 | .idea 7 | */**/package-lock.json 8 | */**/debug.ts 9 | */**/config.js 10 | */**/.env 11 | 12 | # playground 13 | playground.ts 14 | 15 | #docs 16 | /docs 17 | # ignore for deploy docs 18 | yarn.lock 19 | 20 | # coverage 21 | coverage 22 | 23 | */**/umd 24 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__mocks__/rpc.js: -------------------------------------------------------------------------------- 1 | const genesisBlock = require('./data/genesisBlock.json') 2 | const liveCell = require('./data/liveCell') 3 | 4 | module.exports = { 5 | getBlockByNumber: jest.fn().mockResolvedValue(genesisBlock), 6 | getTipBlockNumber: jest.fn().mockResolvedValue('0x1000'), 7 | getLiveCell: jest.fn().mockResolvedValue(liveCell), 8 | } 9 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/const.test.js: -------------------------------------------------------------------------------- 1 | const { TextDecoder } = require('util') 2 | const { PERSONAL } = require('../lib/const') 3 | 4 | describe('Test constants', () => { 5 | it('PERSONAL should be encoded ckb-default-hash', () => { 6 | const decoded = new TextDecoder().decode(PERSONAL) 7 | expect(decoded).toBe('ckb-default-hash') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es6", 5 | "lib": ["es6"], 6 | "outDir": "./lib", 7 | "typeRoots": ["types", "node_modules/@types", "node_modules"], 8 | "types": ["@nervosnetwork/ckb-types", "@nervosnetwork/ckb-sdk-utils", "rpc"] 9 | }, 10 | "include": ["./src"] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@cryptape/sdk-ts-config/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es6", 5 | "lib": ["es6"], 6 | "esModuleInterop": true, 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "importHelpers": true 10 | }, 11 | "exclude": ["node_modules", "**/*.spec.ts", "*/**/debug.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | day: wednesday 8 | timezone: Asia/Shanghai 9 | open-pull-requests-limit: 10 10 | versioning-strategy: lockfile-only 11 | ignore: 12 | - dependency-name: y18n 13 | versions: 14 | - 4.0.1 15 | rebase-strategy: disabled 16 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "node", 5 | "target": "es6", 6 | "outDir": "lib", 7 | "lib": ["DOM"], 8 | "typeRoots": ["types", "node_modules/@types", "node_modules"], 9 | "types": ["@nervosnetwork/ckb-types", "node"] 10 | }, 11 | "include": ["src"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/Base/stats.ts: -------------------------------------------------------------------------------- 1 | import resultFmts from '../resultFormatter.js' 2 | 3 | export default { 4 | getBlockchainInfo: { 5 | method: 'get_blockchain_info', 6 | paramsFormatters: [], 7 | resultFormatters: resultFmts.toBlockchainInfo, 8 | }, 9 | getFeeRateStats: { 10 | method: 'get_fee_rate_statistics', 11 | paramsFormatters: [], 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/snyk.yml: -------------------------------------------------------------------------------- 1 | name: SNYK 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | security: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Run Snyk 13 | uses: snyk/actions/node@master 14 | env: 15 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 16 | with: 17 | command: monitor 18 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/exceptions/transaction.ts: -------------------------------------------------------------------------------- 1 | import ErrorCode from './ErrorCode.js' 2 | 3 | export class ReconciliationException extends Error { 4 | code = ErrorCode.ReconciliationFailed 5 | 6 | constructor() { 7 | super(`Fail to reconcile transaction, try to increase extra count or check the transaction`) 8 | } 9 | } 10 | 11 | export default { 12 | ReconciliationException, 13 | } 14 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib-esm", 5 | "module": "ES2015", 6 | "target": "ES2015", 7 | "moduleResolution": "Node", 8 | "lib": ["es6"], 9 | "esModuleInterop": true, 10 | "typeRoots": ["@nervosnetwork/ckb-types", "nervosnetwork/ckb-sdk-utils", "types"] 11 | }, 12 | "include": ["src"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/README.md: -------------------------------------------------------------------------------- 1 | # `ckb-sdk-rpc` 2 | 3 | `@nervosnetwork/ckb-sdk-rpc` is the rpc module of `@nervosnetwork/ckb-sdk-core`. 4 | 5 | RPC list could be found [here](https://github.com/ckb-js/ckb-sdk-js/blob/develop/packages/ckb-sdk-rpc/src/Base.ts#L156) 6 | 7 | [Type Doc](https://ckb-js.github.io/ckb-sdk-js/classes/ckbrpc.html) 8 | 9 | See [Full Doc](https://github.com/ckb-js/ckb-sdk-js/blob/develop/README.md) 10 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__mocks__/CellCollector.js: -------------------------------------------------------------------------------- 1 | module.exports = class CellCollector { 2 | collect = jest.fn(() => 3 | Array.from({ length: 10 }, (_, idx) => ({ 4 | cell_output: { 5 | type: `mock_type ${idx}`, 6 | capacity: `mock_capacity ${idx}`, 7 | }, 8 | out_point: { 9 | tx_Hash: `mock_tx_hash ${idx}`, 10 | index: `mock_index ${idx}`, 11 | }, 12 | })), 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib-esm", 5 | "module": "ES2015", 6 | "target": "ES2015", 7 | "moduleResolution": "Node", 8 | "lib": ["es6"], 9 | "esModuleInterop": true, 10 | "typeRoots": ["types", "node_modules/@types", "node_modules"], 11 | "types": ["@nervosnetwork/ckb-types", "node"] 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib-esm", 5 | "module": "ES2015", 6 | "target": "ES2015", 7 | "moduleResolution": "Node", 8 | "lib": ["es6"], 9 | "esModuleInterop": true, 10 | "typeRoots": ["types", "node_modules/@types", "node_modules"], 11 | "types": ["@nervosnetwork/ckb-types", "@nervosnetwork/ckb-sdk-utils", "rpc"] 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /scripts/bundle-umd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR="packages/ckb-sdk-core/umd/" 4 | 5 | npx rimraf $BASE_DIR 6 | mkdir $BASE_DIR 7 | echo "module.exports = require('@nervosnetwork/ckb-sdk-core').default;" > "${BASE_DIR}entry.js" 8 | npx browserify "${BASE_DIR}entry.js" -o "${BASE_DIR}ckb-sdk.js" -s CKBCore 9 | npx terser --compress --mangle -o "${BASE_DIR}ckb-sdk.umd.js" -- "${BASE_DIR}ckb-sdk.js" 10 | npx rimraf "${BASE_DIR}/entry.js" 11 | npx rimraf "${BASE_DIR}/ckb-sdk.js" 12 | 13 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/crypto/blake160.test.js: -------------------------------------------------------------------------------- 1 | const { blake160 } = require('../..') 2 | 3 | describe('blake160', () => { 4 | it('blake160', () => { 5 | const fixture = { 6 | message: '024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01', 7 | digest: '36c329ed630d6ce750712a477543672adab57f4c', 8 | } 9 | const digest = blake160(new Uint8Array(Buffer.from(fixture.message, 'hex')), 'hex') 10 | expect(digest).toBe(fixture.digest) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/groupScripts/index.test.js: -------------------------------------------------------------------------------- 1 | const groupScripts = require('../../lib/groupScripts').default 2 | const fixture = require('./fixtures.json') 3 | 4 | describe(`test group inputs`, () => { 5 | const fixtureTable = Object.entries(fixture).map(([title, { cells, expected }]) => [title, cells, expected]) 6 | test.each(fixtureTable)('%s', (_title, cells, expected) => { 7 | const groups = groupScripts(cells) 8 | expect(groups).toEqual(new Map(expected)) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/exceptions/rpc.ts: -------------------------------------------------------------------------------- 1 | import ErrorCode from './ErrorCode.js' 2 | 3 | export class IdNotMatchException extends Error { 4 | code = ErrorCode.IdNotMatch 5 | 6 | constructor(requestId: number, responseId: number) { 7 | super(`Expect json rpc id to be ${requestId}, but ${responseId} received`) 8 | } 9 | } 10 | 11 | export class ResponseException extends Error { 12 | code = ErrorCode.ResponseMessage 13 | } 14 | 15 | export default { 16 | IdNotMatchException, 17 | ResponseException, 18 | } 19 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/loadCellsFromIndexer/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "loadCellsFromIndexer": { 3 | "should return 10 cells": { 4 | "params": [ 5 | { 6 | "indexer": "indexer", 7 | "lock": { 8 | "codeHash": "mock_codeHash", 9 | "hashType": "mock_hashType", 10 | "args": "mock_args" 11 | }, 12 | "start": "mock_start", 13 | "end": "mock_end" 14 | } 15 | ], 16 | "expected": 10 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/Base/experimental.ts: -------------------------------------------------------------------------------- 1 | import paramsFmts from '../paramsFormatter.js' 2 | 3 | export default { 4 | dryRunTransaction: { 5 | method: 'dry_run_transaction', 6 | paramsFormatters: [paramsFmts.toRawTransaction], 7 | }, 8 | 9 | // skip _compute_transaction_hash 10 | 11 | calculateDaoMaximumWithdraw: { 12 | method: 'calculate_dao_maximum_withdraw', 13 | paramsFormatters: [paramsFmts.toOutPoint, paramsFmts.toHash], 14 | }, 15 | 16 | // skip estimate_fee_rate 17 | 18 | // skip _compute_script_hash 19 | } 20 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/const.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encoded string 'ckb-default-hash' 3 | * 4 | * @constant 5 | * @type {bytes} 6 | */ 7 | export const PERSONAL = new Uint8Array([99, 107, 98, 45, 100, 101, 102, 97, 117, 108, 116, 45, 104, 97, 115, 104]) 8 | 9 | export const EMPTY_WITNESS_ARGS: CKBComponents.WitnessArgs = { 10 | lock: '', 11 | inputType: '', 12 | outputType: '', 13 | } 14 | 15 | export const EMPTY_SECP_SIG = `0x${'0'.repeat(130)}` 16 | 17 | export default { 18 | PERSONAL, 19 | EMPTY_WITNESS_ARGS, 20 | EMPTY_SECP_SIG, 21 | } 22 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: '90...100' 8 | 9 | status: 10 | project: 11 | default: 12 | target: auto 13 | threshold: 50 14 | base: auto 15 | patch: off 16 | changes: yes 17 | 18 | parsers: 19 | gcov: 20 | branch_detection: 21 | conditional: yes 22 | loop: yes 23 | method: no 24 | macro: no 25 | 26 | comment: 27 | layout: 'reach,diff,flags,tree' 28 | behavior: default 29 | require_changes: no 30 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/exceptions/common.ts: -------------------------------------------------------------------------------- 1 | import ErrorCode from './ErrorCode.js' 2 | 3 | export class ParameterRequiredException extends Error { 4 | code = ErrorCode.ParameterRequired 5 | 6 | constructor(name: string) { 7 | super(`${name} is required`) 8 | } 9 | } 10 | 11 | export class SignMessageException extends Error { 12 | code = ErrorCode.SignMessageFailed 13 | 14 | constructor() { 15 | super('Fail to sign the message') 16 | } 17 | } 18 | 19 | export default { 20 | ParameterRequiredException, 21 | SignMessageException, 22 | } 23 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/src/groupScripts.ts: -------------------------------------------------------------------------------- 1 | import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils' 2 | 3 | type LockHash = string 4 | type Index = number 5 | type Cell = { lock: CKBComponents.Script } 6 | 7 | export const groupScripts = (inputCells: Cell[]) => { 8 | const groups = new Map() 9 | inputCells.forEach((cell, i) => { 10 | const lockhash = scriptToHash(cell.lock) 11 | const group = groups.get(lockhash) || [] 12 | groups.set(lockhash, [...group, i]) 13 | }) 14 | return groups 15 | } 16 | 17 | export default groupScripts 18 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/calculateTransactionFee/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "cell the fee": { 3 | "transactionSize": "0x40b", 4 | "feeRate": "0x384", 5 | "expected": "0x3a4" 6 | }, 7 | "uncell the fee": { 8 | "transactionSize": "0x384", 9 | "feeRate": "0x384", 10 | "expected": "0x32a" 11 | }, 12 | "size in bigint": { 13 | "transactionSize": 900, 14 | "feeRate": "0x384", 15 | "expected": "0x32a" 16 | }, 17 | "rate in bigint": { 18 | "transactionSize": "0x384", 19 | "feeRate": 900, 20 | "expected": "0x32a" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/exceptions/string.ts: -------------------------------------------------------------------------------- 1 | import ErrorCode from './ErrorCode.js' 2 | 3 | export class HexStringException extends Error { 4 | code = ErrorCode.ParameterInvalid 5 | 6 | constructor(hex: string) { 7 | super(`${hex} is an invalid hex string`) 8 | } 9 | } 10 | 11 | export class HexStringWithout0xException extends Error { 12 | code = ErrorCode.ParameterInvalid 13 | 14 | constructor(hex: string) { 15 | super(`Hex string ${hex} should start with 0x`) 16 | } 17 | } 18 | 19 | export default { 20 | HexStringException, 21 | HexStringWithout0xException, 22 | } 23 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/src/utils.ts: -------------------------------------------------------------------------------- 1 | export const filterCellsByInputs = ( 2 | cells: Pick[], 3 | inputs: Pick[], 4 | ) => { 5 | return inputs.map(input => { 6 | const outPoint = input.previousOutput 7 | const cell = cells.find(c => c.outPoint?.txHash === outPoint?.txHash && c.outPoint?.index === outPoint?.index) 8 | if (!cell) { 9 | throw new Error(`Cell of ${JSON.stringify(outPoint)} is not found`) 10 | } 11 | return cell 12 | }) 13 | } 14 | 15 | export default { filterCellsByInputs } 16 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/exceptions/index.test.js: -------------------------------------------------------------------------------- 1 | const exceptions = require('../../lib/exceptions') 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('Test exceptions', () => { 5 | const fixtureTable = Object.entries(fixtures).map(([exceptionName, { params, expected }]) => [ 6 | exceptionName, 7 | params, 8 | expected, 9 | ]) 10 | test.each(fixtureTable)(`%s`, (exceptionName, params, expected) => { 11 | const err = new exceptions[exceptionName](...params) 12 | expect(err.code).toBe(expected.code) 13 | expect(err.message).toBe(expected.message) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/sizes/index.test.js: -------------------------------------------------------------------------------- 1 | const { getTransactionSize } = require('../../lib/sizes') 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('Test sizes', () => { 5 | describe('getTransactionSize', () => { 6 | const fixtureTable = Object.entries(fixtures.getTransactionSize).map(([title, { params, expected }]) => [ 7 | title, 8 | params, 9 | expected, 10 | ]) 11 | 12 | test.each(fixtureTable)(`%s`, (_title, params, expected) => { 13 | expect.assertions(1) 14 | expect(getTransactionSize(...params)).toBe(expected) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: macos-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | with: 13 | persist-credentials: false 14 | - name: Install and build 15 | run: | 16 | npm install 17 | yarn run docs 18 | - name: Deploy 19 | uses: JamesIves/github-pages-deploy-action@3.7.1 20 | with: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | BRANCH: gh-pages 23 | FOLDER: docs 24 | CLEAN: true 25 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/loadCellsFromIndexer/index.test.js: -------------------------------------------------------------------------------- 1 | const { loadCellsFromIndexer } = require('../../lib/loadCellsFromIndexer') 2 | const CellCollector = require('../../__mocks__/CellCollector') 3 | const fixtures = require('./fixtures.json') 4 | 5 | describe('Test load cells from indexer', () => { 6 | describe('Test loadCellsFromIndexer', () => { 7 | it('Should return cell list', async () => { 8 | const fixture = fixtures.loadCellsFromIndexer['should return 10 cells'] 9 | const actual = await loadCellsFromIndexer({ ...fixture.params[0], CellCollector }) 10 | expect(actual).toHaveLength(fixture.expected) 11 | }) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/__tests__/exceptions/index.test.js: -------------------------------------------------------------------------------- 1 | const exceptions = require('../../lib/exceptions') 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('Test exceptions', () => { 5 | const fixtureTable = Object.entries(fixtures).map(([exceptionName, { params, expected }]) => [ 6 | exceptionName, 7 | params, 8 | expected, 9 | ]) 10 | test.each(fixtureTable)(`%s`, (exceptionName, params, expected) => { 11 | const err = new exceptions[exceptionName](...params) 12 | expect(err.code).toBe(expected.code) 13 | expect(err.message).toBe(expected.message) 14 | if (err.index) { 15 | expect(err.index).toBe(expected.index) 16 | } 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__mocks__/data/liveCell.json: -------------------------------------------------------------------------------- 1 | { 2 | "cell": { 3 | "data": { 4 | "content": "0x", 5 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000000" 6 | }, 7 | "output": { 8 | "lock": [ 9 | { 10 | "lock": { 11 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 12 | "hashType": "type", 13 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6" 14 | }, 15 | "type": null, 16 | "capacity": "0x1fd9e09516" 17 | } 18 | ], 19 | "type": null, 20 | "capacity": "0x1fd9e09516" 21 | } 22 | }, 23 | "status": "live" 24 | } 25 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/reconcilers/extraInputs.test.js: -------------------------------------------------------------------------------- 1 | const fixtures = require('./extraInputs.fixtures.json') 2 | const { extraInputs } = require('../../lib/reconcilers') 3 | 4 | describe('Test reconciliation by extra inputs strategy', () => { 5 | const fixtureTable = Object.entries(fixtures).map(([title, { params, expected, exception }]) => [ 6 | title, 7 | params, 8 | expected, 9 | exception, 10 | ]) 11 | 12 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 13 | expect.assertions(1) 14 | 15 | if (exception) { 16 | expect(() => extraInputs(...params)).toThrow(exception) 17 | } else { 18 | expect(extraInputs(...params)).toEqual(expected) 19 | } 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/Base/pool.ts: -------------------------------------------------------------------------------- 1 | import paramsFmts from '../paramsFormatter.js' 2 | import resultFmts from '../resultFormatter.js' 3 | 4 | export default { 5 | sendTransaction: { 6 | method: 'send_transaction', 7 | paramsFormatters: [paramsFmts.toRawTransaction, paramsFmts.toOutputsValidator], 8 | resultFormatters: resultFmts.toHash, 9 | }, 10 | 11 | txPoolInfo: { 12 | method: 'tx_pool_info', 13 | paramsFormatters: [], 14 | resultFormatters: resultFmts.toTxPoolInfo, 15 | }, 16 | 17 | clearTxPool: { 18 | method: 'clear_tx_pool', 19 | paramsFormatters: [], 20 | }, 21 | 22 | getRawTxPool: { 23 | method: 'get_raw_tx_pool', 24 | paramsFormatters: [], 25 | resultFormatters: resultFmts.toRawTxPool, 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /packages/ckb-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nervosnetwork/ckb-types", 3 | "version": "0.109.5", 4 | "description": "Type module of @nervosnetwork/ckb-sdk-core", 5 | "author": "Nervos ", 6 | "homepage": "https://github.com/ckb-js/ckb-sdk-js#readme", 7 | "license": "MIT", 8 | "types": "index.d.ts", 9 | "directories": { 10 | "test": "__tests__" 11 | }, 12 | "publishConfig": { 13 | "registry": "https://registry.npmjs.org/", 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/ckb-js/ckb-sdk-js.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/ckb-js/ckb-sdk-js/issues" 22 | }, 23 | "gitHead": "8950ea97ca8686ec70db81f668ead0de863fae81" 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/merge_master_into_develop.yml: -------------------------------------------------------------------------------- 1 | name: Merge released into develop 2 | 3 | on: 4 | release: 5 | branches: 6 | - master 7 | types: 8 | - published 9 | - prereleased 10 | 11 | jobs: 12 | merge-to-dev: 13 | name: Merge master into develop 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@master 17 | - name: Request 18 | uses: repo-sync/pull-request@v2 19 | with: 20 | source_branch: master 21 | destination_branch: develop 22 | pr_title: 'Merge released ${{ github.ref }} into develop' 23 | pr_reviewer: keith-cy 24 | pr_label: auto-pr 25 | pr_body: ':crown: *Anautomated PR*' 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/utils/index.test.js: -------------------------------------------------------------------------------- 1 | const { filterCellsByInputs } = require('../../lib/utils') 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('Test utils', () => { 5 | describe('Test filter cells by inputs', () => { 6 | const fixtureTable = fixtures.filterCellsByInputs.map(({ params, expected, exception }) => [ 7 | params.cells, 8 | params.inputs, 9 | expected, 10 | exception, 11 | ]) 12 | 13 | test.each(fixtureTable)(`(%j, %j) => %j ? %s`, (cells, inputs, expected, exception) => { 14 | expect.assertions(1) 15 | try { 16 | const actual = filterCellsByInputs(cells, inputs) 17 | expect(actual).toEqual(expected) 18 | } catch (err) { 19 | expect(err).toEqual(new Error(exception)) 20 | } 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/generateRawTransaction/getLeftCells/index.test.js: -------------------------------------------------------------------------------- 1 | const { getLeftCells } = require('../../../lib/generateRawTransaction') 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('Test generate raw transaction', () => { 5 | describe('getLeftCells', () => { 6 | const fixtureTable = Object.entries(fixtures).map(([title, { params, expected }]) => [ 7 | title, 8 | params.map(param => ({ 9 | inputScripts: param.inputScripts, 10 | usedCells: param.usedCells, 11 | unspentCellsMap: new Map(param.unspentCellsMap), 12 | })), 13 | expected, 14 | ]) 15 | 16 | test.each(fixtureTable)(`%s`, (_title, params, expected) => { 17 | expect.assertions(1) 18 | 19 | expect(getLeftCells(...params)).toEqual(expected) 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/validators.ts: -------------------------------------------------------------------------------- 1 | import { HexStringException, HexStringWithout0xException } from './exceptions/index.js' 2 | 3 | export const assertToBeHexString = (value: string) => { 4 | if (typeof value !== 'string' || !value.startsWith('0x') || Number.isNaN(+value)) { 5 | throw new HexStringException(value) 6 | } 7 | return true 8 | } 9 | 10 | export const assertToBeHexStringOrBigint = (value: string | bigint) => { 11 | if (typeof value === 'bigint') { 12 | return true 13 | } 14 | if (typeof value === 'string') { 15 | if (!value.startsWith('0x')) { 16 | throw new HexStringWithout0xException(value) 17 | } 18 | return true 19 | } 20 | throw new TypeError(`${value} should be type of string or bigint`) 21 | } 22 | 23 | export default { 24 | assertToBeHexString, 25 | assertToBeHexStringOrBigint, 26 | } 27 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | quota_type = single 12 | spaces_around_operators = true 13 | 14 | # Use 4 spaces for the Python files 15 | [*.py] 16 | indent_size = 4 17 | max_line_length = 120 18 | 19 | # The JSON files contain newlines inconsistently 20 | [*.json] 21 | insert_final_newline = ignore 22 | 23 | # Minified JavaScript files shouldn't be changed 24 | [**.min.js] 25 | indent_style = ignore 26 | insert_final_newline = ignore 27 | 28 | # Makefiles always use tabs for indentation 29 | [Makefile] 30 | indent_style = tab 31 | 32 | # Batch files use tabs for indentation 33 | [*.bat] 34 | indent_style = tab 35 | 36 | [*.md] 37 | trim_trailing_whitespace = false 38 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/__tests__/formatters/result.test.js: -------------------------------------------------------------------------------- 1 | const { default: resultFmt } = require('../../lib/resultFormatter') 2 | const fixtures = require('./result.fixtures.json') 3 | 4 | describe('result formatter', () => { 5 | describe.each(Object.keys(fixtures))('%s', methodName => { 6 | const fixtureTable = Object.values(fixtures[methodName]).map(({ result, expected, exception }) => [ 7 | result, 8 | expected, 9 | exception, 10 | ]) 11 | test.each(fixtureTable)('%j => %j', (result, expected, exception) => { 12 | if (undefined !== expected) { 13 | const formatted = resultFmt[methodName](result) 14 | expect(formatted).toEqual(expected) 15 | } 16 | if (undefined !== exception) { 17 | expect(resultFmt[methodName](result)).toThrow(new Error(exception)) 18 | } 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/occupiedCapacity.ts: -------------------------------------------------------------------------------- 1 | const codeHashOccupied = 32 2 | const hashTypeOccupied = 1 3 | 4 | /** 5 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/helpers.html#minimalscriptcapacity minimalScriptCapacity} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#scriptoccupied example} 6 | */ 7 | export const scriptOccupied = (script: CKBComponents.Script) => { 8 | return script.args.slice(2).length / 2 + codeHashOccupied + hashTypeOccupied 9 | } 10 | 11 | /** 12 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/helpers.html#minimalcellcapacity minimalCellCapacity} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#celloccupied example} 13 | */ 14 | export const cellOccupied = (cell: CKBComponents.CellOutput) => { 15 | return 8 + scriptOccupied(cell.lock) + (cell.type ? scriptOccupied(cell.type) : 0) 16 | } 17 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/calculateTransactionFee/index.test.js: -------------------------------------------------------------------------------- 1 | const { calculateTransactionFee } = require('../../lib/calculateTransactionFee') 2 | const transactionFeeFixtures = require('./fixtures.json') 3 | 4 | describe('calculate transaction fee', () => { 5 | const fixtureTable = Object.entries( 6 | transactionFeeFixtures, 7 | ).map(([title, { transactionSize, feeRate, expected, exception }]) => [ 8 | title, 9 | typeof transactionSize === 'number' ? BigInt(transactionSize) : transactionSize, 10 | typeof feeRate === 'number' ? BigInt(feeRate) : feeRate, 11 | expected, 12 | exception, 13 | ]) 14 | test.each(fixtureTable)('%s', (_title, transactionSize, feeRate, expected, exception) => { 15 | if (undefined !== expected) { 16 | expect(calculateTransactionFee(transactionSize, feeRate)).toBe(expected) 17 | } 18 | if (undefined !== exception) { 19 | expect(() => calculateTransactionFee(transactionSize, feeRate)).toThrowError(exception) 20 | } 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/validators/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertToBeHexString": { 3 | "validHexString": "0x123", 4 | "stringWithout0x": "123", 5 | "invalidNumber": "0xg", 6 | "notString": 123 7 | }, 8 | "assertToBeHexStringOrBigint": { 9 | "hexStringStartsWith0x": "0x123", 10 | "hexStringStartsWithout0x": "123", 11 | "bigint": "123", 12 | "number": 123 13 | }, 14 | "assertToBeSingleSigAddress": { 15 | "singleSigAddress": "ckt1qyqd39fkyvnrs7sa882eu8pn6t3cyuemtpkqm6zejn", 16 | "addressWithInvalidSingleSigPayload": "ckt1qyq6jh3kt48y294mla4mxrdy380tzprcpe7sxuzm0e", 17 | "addressWithIncorrectSize": "ckt1qyqd39fkyvnrs7sa882eu8pn6t3cyuemtpkqm6zejn1" 18 | }, 19 | "assertToBeSignleSigAddressPayload": { 20 | "singleSigAddressPayload": "0x0100d895362326387a1d39d59e1c33d2e382733b586c", 21 | "payloadNotStartsWith0x0100": "0x0000d895362326387a1d39d59e1c33d2e382733b586c", 22 | "paylaodWithIncorrectSize": "0x0100d895362326387a1d39d59e1c33d2e382733b586c1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/generateRawTransaction/getTargetOutputs/index.test.js: -------------------------------------------------------------------------------- 1 | const { JSBI } = require('@nervosnetwork/ckb-sdk-utils') 2 | const { getTargetOutputs } = require('../../../lib/generateRawTransaction') 3 | const fixtures = require('./fixtures.json') 4 | 5 | describe('Test generate raw transaction', () => { 6 | describe('getTargetOutputs', () => { 7 | const fixtureTable = Object.entries(fixtures).map(([title, { params, expected, exception }]) => [ 8 | title, 9 | params, 10 | expected, 11 | exception, 12 | ]) 13 | 14 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 15 | expect.assertions(1) 16 | try { 17 | expect(getTargetOutputs({ ...params[0], minCapacity: JSBI.BigInt(params[0].minCapacity) })).toEqual( 18 | expected.map(output => ({ ...output, capacity: JSBI.BigInt(output.capacity) })), 19 | ) 20 | } catch (err) { 21 | expect(err).toEqual(new Error(exception)) 22 | } 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/generateRawTransaction/getInputs/index.test.js: -------------------------------------------------------------------------------- 1 | const { JSBI } = require('@nervosnetwork/ckb-sdk-utils') 2 | const { getInputs } = require('../../../lib/generateRawTransaction') 3 | const fixtures = require('./fixtures.json') 4 | 5 | describe('Test generate raw transaction', () => { 6 | describe('getInputs', () => { 7 | const fixtureTable = Object.entries(fixtures).map(([title, { params, expected, exception }]) => [ 8 | title, 9 | params, 10 | expected, 11 | exception, 12 | ]) 13 | 14 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 15 | expect.assertions(1) 16 | try { 17 | const [param] = params 18 | param.costCapacity = JSBI.BigInt(param.costCapacity) 19 | param.unspentCellsMap = new Map(param.unspentCellsMap) 20 | expect(getInputs(param)).toEqual({ ...expected, sum: JSBI.BigInt(expected.sum) }) 21 | } catch (err) { 22 | expect(err).toEqual(new Error(exception)) 23 | } 24 | }) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /.github/workflows/bundle.yml: -------------------------------------------------------------------------------- 1 | name: Bundle ckb.min.js 2 | 3 | on: 4 | workflow_run: 5 | workflows: ['Unit Tests'] 6 | branches: [master, develop] 7 | types: [completed] 8 | 9 | jobs: 10 | default: 11 | runs-on: macos-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Install Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: 18 20 | 21 | - name: Restore 22 | uses: actions/cache@v3 23 | with: 24 | path: | 25 | node_modules 26 | */*/node_modules 27 | key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} 28 | 29 | - name: Install Deps 30 | run: npm install 31 | 32 | - name: Compile 33 | run: | 34 | lerna run tsc 35 | npm run build:umd 36 | 37 | - name: Upload Files 38 | uses: actions/upload-artifact@v3 39 | with: 40 | name: ckb-sdk-js 41 | path: packages/ckb-sdk-core/umd/ckb-sdk.min.js 42 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/generateRawTransaction/getBigInts/index.test.js: -------------------------------------------------------------------------------- 1 | const { JSBI } = require('@nervosnetwork/ckb-sdk-utils') 2 | const { getBigInts } = require('../../../lib/generateRawTransaction') 3 | const fixtures = require('./fixtures.json') 4 | 5 | describe('Test generate raw transaction', () => { 6 | describe('getBigInts', () => { 7 | const fixtureTable = Object.entries(fixtures).map(([title, { params, expected, exception }]) => [ 8 | title, 9 | params, 10 | expected, 11 | exception, 12 | ]) 13 | 14 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 15 | expect.assertions(1) 16 | try { 17 | expect(getBigInts(...params)).toEqual({ 18 | targetFee: JSBI.BigInt(expected.targetFee), 19 | minCapacity: JSBI.BigInt(expected.minCapacity), 20 | minChange: JSBI.BigInt(expected.minChange), 21 | zeroBigInt: JSBI.BigInt(0), 22 | }) 23 | } catch (err) { 24 | expect(err).toEqual(new Error(exception)) 25 | } 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/src/loadCellsFromIndexer.ts: -------------------------------------------------------------------------------- 1 | export const loadCellsFromIndexer = async ({ 2 | CellCollector, 3 | indexer, 4 | lock, 5 | start, 6 | end, 7 | }: LoadCellsParams.FromIndexer) => { 8 | const collector = new CellCollector(indexer, { 9 | lock: { 10 | code_hash: lock.codeHash, 11 | hash_type: lock.hashType, 12 | args: lock.args, 13 | }, 14 | fromBlock: start, 15 | toBlock: end, 16 | }) 17 | 18 | const cells: RawTransactionParams.Cell[] = [] 19 | 20 | /* eslint-disable no-restricted-syntax, camelcase */ 21 | for await (const { 22 | data, 23 | cell_output: { capacity, type }, 24 | out_point, 25 | } of collector.collect()) { 26 | cells.push({ 27 | data, 28 | lock, 29 | type: type && { codeHash: type.code_hash, hashType: type.hash_type, args: type.args }, 30 | capacity, 31 | outPoint: { txHash: out_point.tx_hash, index: out_point.index }, 32 | }) 33 | } 34 | /* eslint-enable no-restricted-syntax */ 35 | 36 | return cells 37 | } 38 | 39 | export default loadCellsFromIndexer 40 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/crypto/blake160.ts: -------------------------------------------------------------------------------- 1 | import blake2b from './blake2b.js' 2 | import { hexToBytes } from '../convertors/index.js' 3 | import { PERSONAL } from '../const.js' 4 | 5 | export declare interface Blake160 { 6 | (data: Uint8Array | string): Uint8Array 7 | (data: Uint8Array | string, encode: 'binary'): Uint8Array 8 | (data: Uint8Array | string, encode: 'hex'): string 9 | (data: Uint8Array | string, encode: 'binary' | 'hex'): Uint8Array | string 10 | } 11 | /** 12 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/lumos.html#utils @ckb-lumos/lumos/utils} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#blake160 example} 13 | */ 14 | export const blake160: Blake160 = (data: Uint8Array | string, encode: 'binary' | 'hex' = 'binary'): any => { 15 | const formattedData = typeof data === 'string' ? hexToBytes(data) : data 16 | const s = blake2b(32, null, null, PERSONAL) 17 | s.update(formattedData) 18 | return s.digest(encode).slice(0, encode === 'binary' ? 20 : 40) 19 | } 20 | 21 | export default blake160 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 - 2022 Nervos Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/exceptions/batch.ts: -------------------------------------------------------------------------------- 1 | import ErrorCode from './ErrorCode.js' 2 | import { IdNotMatchException } from './rpc.js' 3 | 4 | const ERROR_LABEL = 'Batch Request' 5 | 6 | export class MethodInBatchNotFoundException extends Error { 7 | code = ErrorCode.MethodNotFound 8 | 9 | constructor(name: string) { 10 | super(`[${ERROR_LABEL}]: Method ${name} is not found`) 11 | } 12 | } 13 | 14 | export class PayloadInBatchException extends Error { 15 | code = ErrorCode.PayloadMessage 16 | 17 | index: number | undefined 18 | 19 | constructor(index: number, message: string) { 20 | super(`[${ERROR_LABEL} ${index}]: ${message}`) 21 | this.index = index 22 | } 23 | } 24 | 25 | export class IdNotMatchedInBatchException extends IdNotMatchException { 26 | index: number | undefined 27 | 28 | constructor(index: number, requestId: number, responseId: number) { 29 | super(requestId, responseId) 30 | this.message = `[${ERROR_LABEL} ${index}]: ${this.message}` 31 | this.index = index 32 | } 33 | } 34 | 35 | export default { 36 | MethodInBatchNotFoundException, 37 | PayloadInBatchException, 38 | IdNotMatchedInBatchException, 39 | } 40 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/generateRawTransaction/getKeyAndCellsPairs/index.test.js: -------------------------------------------------------------------------------- 1 | const { getKeyAndCellsPairs } = require('../../../lib/generateRawTransaction') 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('Test generate raw transaction', () => { 5 | describe('getKeyAndCellsPairs', () => { 6 | const fixtureTable = Object.entries(fixtures).map(([title, { params, expected, exception }]) => [ 7 | title, 8 | params, 9 | expected, 10 | exception, 11 | ]) 12 | 13 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 14 | expect.assertions(1) 15 | try { 16 | if (Array.isArray(expected.unspentCellsMap[0])) { 17 | // eslint-disable-next-line 18 | expected.unspentCellsMap = new Map(expected.unspentCellsMap) 19 | } 20 | if (Array.isArray(params[0].cells[0])) { 21 | // eslint-disable-next-line 22 | params[0].cells = new Map(params[0].cells) 23 | } 24 | expect(getKeyAndCellsPairs(...params)).toEqual(expected) 25 | } catch (err) { 26 | expect(err).toEqual(new Error(exception)) 27 | } 28 | }) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/sizes.ts: -------------------------------------------------------------------------------- 1 | import { serializeWitnessArgs, serializeTransaction } from './serialization/transaction.js' 2 | 3 | /** 4 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/common_scripts.html#gettransactionsize @ckb-lumos/common-script/common.getTransactionSize} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#gettransactionsize example} 5 | * @name getTransactionSize 6 | * @description return the size of a transaction cost in a block, 4 bytes more than the serialized transaction. 7 | * @param {Object} transaction - Raw transaction 8 | * @returns {String} Virtual size of a transaction in a block 9 | */ 10 | export const getTransactionSize = (transaction: CKBComponents.RawTransactionToSign) => { 11 | const tx = { 12 | ...transaction, 13 | witnesses: transaction.witnesses.map(wit => (typeof wit === 'string' ? wit : serializeWitnessArgs(wit))), 14 | } 15 | // extra 4 bytes size due to the cost of serialized tx in a block 16 | const VIRTUAL_COST = 4 17 | const serializedTransaction = serializeTransaction(tx) 18 | return serializedTransaction.slice(2).length / 2 + VIRTUAL_COST 19 | } 20 | 21 | export default { getTransactionSize } 22 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/calculateTransactionFee.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi' 2 | import { assertToBeHexStringOrBigint } from './validators.js' 3 | 4 | /** 5 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/common_scripts.html#calculatefee-2 @ckb-lumos/common-script/common.calculatefee} 6 | * @function calculateTransactionFee 7 | * @description calculate the transaction fee by transaction size and fee rate 8 | * @param {string | bigint} transactionSize, the byte size of transaction 9 | * @param {string | bigint} feeRate, the fee rate with unit of shannons/KB 10 | * @returns {string} transactionFee 11 | */ 12 | export const calculateTransactionFee = (transactionSize: string | bigint, feeRate: string | bigint): string => { 13 | assertToBeHexStringOrBigint(transactionSize) 14 | assertToBeHexStringOrBigint(feeRate) 15 | const ratio = JSBI.BigInt(1000) 16 | const base = JSBI.multiply(JSBI.BigInt(`${transactionSize}`), JSBI.BigInt(`${feeRate}`)) 17 | const fee = JSBI.divide(base, ratio) 18 | if (JSBI.lessThan(JSBI.multiply(fee, ratio), base)) { 19 | return `0x${JSBI.add(fee, JSBI.BigInt(1)).toString(16)}` 20 | } 21 | return `0x${fee.toString(16)}` 22 | } 23 | 24 | export default calculateTransactionFee 25 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/__tests__/ckb-rpc-helpers.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const https = require('https') 3 | 4 | const NODE_URL = 'http://localhost:8114' 5 | const CKBRPC = require('../lib').default 6 | 7 | const rpc = new CKBRPC(NODE_URL) 8 | describe('ckb-rpc settings and helpers', () => { 9 | it('set node url', () => { 10 | const node = { 11 | url: 'http://localhost:8114', 12 | } 13 | rpc.setNode(node) 14 | expect(rpc.node).toEqual(node) 15 | }) 16 | 17 | it('set http agent', () => { 18 | const httpAgent = new http.Agent() 19 | const node = { 20 | httpAgent, 21 | } 22 | rpc.setNode(node) 23 | expect(rpc.node.httpAgent).toBeDefined() 24 | }) 25 | 26 | it('set https agent', () => { 27 | const httpsAgent = new https.Agent() 28 | const node = { 29 | httpsAgent, 30 | } 31 | rpc.setNode(node) 32 | expect(rpc.node.httpsAgent).toBeDefined() 33 | }) 34 | 35 | it('has 34 basic rpc', () => { 36 | expect(Object.values(rpc)).toHaveLength(34) 37 | }) 38 | 39 | it('set node url to http://test.localhost:8114', () => { 40 | const url = 'http://test.localhost:8114' 41 | rpc.setNode({ 42 | url, 43 | }) 44 | expect(rpc.node.url).toBe(url) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/generateRawTransaction/getBigInts/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "should return BigInts": { 3 | "params": [ 4 | { 5 | "fee": "0x10", 6 | "capacityThreshold": "0x20", 7 | "changeThreshold": "0x30" 8 | } 9 | ], 10 | "expected": { 11 | "targetFee": "0x10", 12 | "minCapacity": "0x20", 13 | "minChange": "0x30", 14 | "zeroBigInt": "0x0" 15 | } 16 | }, 17 | "should throw an error when fee is invalid": { 18 | "params": [ 19 | { 20 | "fee": "10", 21 | "capacityThreshold": "0x20", 22 | "changeThreshold": "0x30" 23 | } 24 | ], 25 | "exception": "Hex string 10 should start with 0x" 26 | }, 27 | "should throw an error when capacityThreshold is invalid": { 28 | "params": [ 29 | { 30 | "fee": "0x10", 31 | "capacityThreshold": "20", 32 | "changeThreshold": "0x30" 33 | } 34 | ], 35 | "exception": "Hex string 20 should start with 0x" 36 | }, 37 | "should throw an error when changeThreshold is invalid": { 38 | "params": [ 39 | { 40 | "fee": "0x10", 41 | "capacityThreshold": "0x20", 42 | "changeThreshold": "30" 43 | } 44 | ], 45 | "exception": "Hex string 30 should start with 0x" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/crypto/bech32.test.js: -------------------------------------------------------------------------------- 1 | const { bech32 } = require('../..') 2 | const fixtures = require('./bech32.fixtures.json') 3 | 4 | describe('bech32', () => { 5 | fixtures.bech32.valid.forEach(f => { 6 | it(`fromWords/toWords ${f.hex}`, () => { 7 | if (f.hex) { 8 | const words = bech32.toWords(Buffer.from(f.hex, 'hex')) 9 | const bytes = Buffer.from(bech32.fromWords(f.words)) 10 | expect(words.join('')).toEqual(f.words.join('')) 11 | expect(bytes.toString('hex')).toBe(f.hex) 12 | } 13 | }) 14 | 15 | it(`encode ${f.prefix}`, () => { 16 | const encoded = bech32.encode(f.prefix, f.words, f.limit) 17 | expect(encoded).toBe(f.string.toLowerCase()) 18 | }) 19 | 20 | it(`decode ${f.string}`, () => { 21 | const decoded = bech32.decode(f.string, f.limit) 22 | expect(decoded.prefix).toBe(f.prefix.toLowerCase()) 23 | expect(decoded.words.join('')).toBe(f.words.join('')) 24 | }) 25 | 26 | it(`fails for ${f.string} with 1 bit flipped`, () => { 27 | const buf = Buffer.from(f.string, 'utf8') 28 | buf[f.string.lastIndexOf('1') + 1] ^= 0x1 29 | const str = buf.toString('utf8') 30 | expect(() => { 31 | bech32.decode(str, f.limit) 32 | }).toThrow() 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/Base/net.ts: -------------------------------------------------------------------------------- 1 | import paramsFmts from '../paramsFormatter.js' 2 | import resultFmts from '../resultFormatter.js' 3 | 4 | export default { 5 | localNodeInfo: { 6 | method: 'local_node_info', 7 | paramsFormatters: [], 8 | resultFormatters: resultFmts.toLocalNodeInfo, 9 | }, 10 | 11 | getPeers: { 12 | method: 'get_peers', 13 | paramsFormatters: [], 14 | resultFormatters: resultFmts.toPeers, 15 | }, 16 | 17 | getBannedAddresses: { 18 | method: 'get_banned_addresses', 19 | paramsFormatters: [], 20 | resultFormatters: resultFmts.toBannedAddresses, 21 | }, 22 | 23 | clearBannedAddresses: { 24 | method: 'clear_banned_addresses', 25 | paramsFormatters: [], 26 | }, 27 | 28 | setBan: { 29 | method: 'set_ban', 30 | paramsFormatters: [], 31 | }, 32 | 33 | syncState: { 34 | method: 'sync_state', 35 | paramsFormatters: [], 36 | resultFormatters: resultFmts.toSyncState, 37 | }, 38 | 39 | setNetworkActive: { 40 | method: 'set_network_active', 41 | paramsFormatters: [paramsFmts.toBoolean], 42 | }, 43 | 44 | addNode: { 45 | method: 'add_node', 46 | paramsFormatters: [], 47 | }, 48 | 49 | removeNode: { 50 | method: 'remove_node', 51 | paramsFormatters: [], 52 | }, 53 | 54 | pingPeers: { 55 | method: 'ping_peers', 56 | paramsFormatters: [], 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/exceptions/formatter.ts: -------------------------------------------------------------------------------- 1 | import { ErrorCode } from '@nervosnetwork/ckb-sdk-utils' 2 | 3 | export class PageSizeTooLargeException extends RangeError { 4 | code = ErrorCode.ParameterInvalid 5 | 6 | constructor(pageSize: bigint | string, maxSize: number) { 7 | super(`Expect page size to be at most ${maxSize}, but ${pageSize} received`) 8 | } 9 | } 10 | 11 | export class PageSizeTooSmallException extends RangeError { 12 | code = ErrorCode.ParameterInvalid 13 | 14 | constructor(pageSize: bigint | string, minSize: number) { 15 | super(`Expect page size to be at least ${minSize}, but ${pageSize} received`) 16 | } 17 | } 18 | 19 | export class OutputsValidatorTypeException extends TypeError { 20 | code = ErrorCode.ParameterInvalid 21 | 22 | constructor() { 23 | super(`Expect outputs validator to be 'default' or 'passthrough'`) 24 | } 25 | } 26 | 27 | export class BigintOrHexStringTypeException extends TypeError { 28 | code = ErrorCode.ParameterInvalid 29 | 30 | constructor(value: any) { 31 | super(`Expect number to be bigint or hex string, but ${value} received`) 32 | } 33 | } 34 | 35 | export class StringHashTypeException extends TypeError { 36 | code = ErrorCode.ParameterInvalid 37 | 38 | constructor(hash: any) { 39 | super(`Expect hash to be string, but ${hash} received`) 40 | } 41 | } 42 | 43 | export default { 44 | PageSizeTooLargeException, 45 | PageSizeTooSmallException, 46 | OutputsValidatorTypeException, 47 | BigintOrHexStringTypeException, 48 | StringHashTypeException, 49 | } 50 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/__tests__/method.test.js: -------------------------------------------------------------------------------- 1 | jest.mock('axios') 2 | const axiosMock = require('axios') 3 | const Method = require('../lib/method').default 4 | 5 | describe('Test Method', () => { 6 | const ranNum = 1 7 | const id = Math.round(ranNum * 10000) 8 | const NODE = { url: 'http://localhost:8114' } 9 | const PROPERTIES = { 10 | name: 'method name', 11 | method: 'raw_method', 12 | paramsFormatters: [], 13 | } 14 | const method = new Method(NODE, PROPERTIES) 15 | 16 | beforeAll(() => { 17 | jest.spyOn(global.Math, 'random').mockReturnValue(ranNum) 18 | }) 19 | 20 | afterAll(() => { 21 | jest.restoreAllMocks() 22 | }) 23 | afterEach(() => { 24 | axiosMock.mockClear() 25 | }) 26 | 27 | it('has properties', () => { 28 | expect(method.name).toBe(PROPERTIES.name) 29 | }) 30 | 31 | it('jsonrpc id mismatched', async () => { 32 | expect.assertions(1) 33 | axiosMock.mockResolvedValue({ 34 | data: { 35 | id: id + 1, 36 | jsonrpc: '2.0', 37 | result: null, 38 | }, 39 | }) 40 | await method 41 | .call() 42 | .catch(err => expect(err).toEqual(new Error(`Expect json rpc id to be 10000, but 10001 received`))) 43 | }) 44 | 45 | it('returns with error', async () => { 46 | expect.assertions(1) 47 | axiosMock.mockResolvedValue({ 48 | data: { 49 | id, 50 | jsonrpc: '2.0', 51 | error: 'mock error', 52 | }, 53 | }) 54 | await method.call().catch(err => expect(err).toEqual(new Error('"mock error"'))) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/utils/occupiedCapacity.test.js: -------------------------------------------------------------------------------- 1 | const { scriptOccupied, cellOccupied } = require('../..') 2 | 3 | describe('script occupied', () => { 4 | it('no args', () => { 5 | const occupied = scriptOccupied({ 6 | args: '0x', 7 | codeHash: '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e', 8 | hashType: 'type' 9 | }) 10 | expect(occupied).toBe(33) 11 | }) 12 | it('with args', () => { 13 | const occupied = scriptOccupied({ 14 | args: '0x00ffee', 15 | codeHash: '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e', 16 | hashType: 'type' 17 | }) 18 | expect(occupied).toBe(36) 19 | }) 20 | }) 21 | 22 | 23 | describe('cell occupied', () => { 24 | it('no type', () => { 25 | const occupied = cellOccupied({ 26 | capacity: '0xe8d4a51000', 27 | lock: { 28 | args: '0x', 29 | codeHash: '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e', 30 | hashType: 'type' 31 | }, 32 | }) 33 | expect(occupied).toBe(41) 34 | }) 35 | it('with type', () => { 36 | const occupied = cellOccupied({ 37 | capacity: '0xe8d4a51000', 38 | lock: { 39 | args: '0x', 40 | codeHash: '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e', 41 | hashType: 'type' 42 | }, 43 | type: { 44 | args: '0x00ff', 45 | codeHash: '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e', 46 | hashType: 'type' 47 | } 48 | }) 49 | expect(occupied).toBe(76) 50 | }) 51 | }) -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nervosnetwork/ckb-sdk-utils", 3 | "version": "0.109.5", 4 | "description": "Utils module of @nervosnetwork/ckb-sdk-core", 5 | "author": "Nervos ", 6 | "homepage": "https://github.com/ckb-js/ckb-sdk-js#readme", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "module": "lib-esm/index.js", 10 | "types": "lib/index.d.ts", 11 | "directories": { 12 | "lib": "lib", 13 | "lib-esm": "lib-esm", 14 | "test": "__tests__" 15 | }, 16 | "files": [ 17 | "lib", 18 | "lib-esm", 19 | "types" 20 | ], 21 | "exports": { 22 | ".": { 23 | "import": "./lib-esm/index.js", 24 | "require": "./lib/index.js", 25 | "types": "./lib/index.d.ts" 26 | } 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/ckb-js/ckb-sdk-js.git" 35 | }, 36 | "scripts": { 37 | "tsc": "tsc --project tsconfig.json && tsc --project tsconfig.esm.json && npx copyfiles -u 2 misc/basedirs/**/* .", 38 | "test": "../../node_modules/.bin/jest" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/ckb-js/ckb-sdk-js/issues" 42 | }, 43 | "dependencies": { 44 | "@nervosnetwork/ckb-types": "0.109.5", 45 | "bech32": "2.0.0", 46 | "elliptic": "6.6.1", 47 | "jsbi": "3.1.3", 48 | "tslib": "2.3.1" 49 | }, 50 | "devDependencies": { 51 | "@types/bitcoinjs-lib": "5.0.0", 52 | "@types/elliptic": "6.4.12" 53 | }, 54 | "gitHead": "8950ea97ca8686ec70db81f668ead0de863fae81" 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | push: 7 | pull_request: 8 | 9 | jobs: 10 | default: 11 | strategy: 12 | matrix: 13 | node: [16, 18] 14 | os: [macos-latest, ubuntu-22.04] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - name: Skip when run node.js@18 on ubuntu for compatibility reason 18 | id: is_skip 19 | if: matrix.os == 'ubuntu-22.04' && matrix.node == '18' 20 | run: echo 'IS_SKIP=true' >> $GITHUB_OUTPUT 21 | 22 | - uses: actions/checkout@v3 23 | if: ${{ steps.is_skip.outputs.IS_SKIP != 'true' }} 24 | 25 | - name: Setup Node 26 | if: ${{ steps.is_skip.outputs.IS_SKIP != 'true' }} 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node }} 30 | 31 | - name: Restore 32 | if: ${{ steps.is_skip.outputs.IS_SKIP != 'true' }} 33 | uses: actions/cache@v3 34 | with: 35 | path: | 36 | node_modules 37 | */*/node_modules 38 | key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} 39 | 40 | - name: Install Deps 41 | if: ${{ steps.is_skip.outputs.IS_SKIP != 'true' }} 42 | run: npm install 43 | 44 | - name: Compile 45 | if: ${{ steps.is_skip.outputs.IS_SKIP != 'true' }} 46 | run: npx lerna run tsc 47 | 48 | - name: Test 49 | if: ${{ steps.is_skip.outputs.IS_SKIP != 'true' }} 50 | run: yarn run test 51 | 52 | - name: Upload codecov 53 | if: matrix.os == 'macos-latest' && matrix.node == '18' 54 | uses: codecov/codecov-action@v3 55 | with: 56 | fail_ci_if_error: true 57 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/groupScripts/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "single group": { 3 | "cells": [ 4 | { 5 | "lock": { 6 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 7 | "hashType": "type", 8 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6" 9 | } 10 | }, 11 | { 12 | "lock": { 13 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 14 | "hashType": "type", 15 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6" 16 | } 17 | } 18 | ], 19 | "expected": [["0x0fec94c611533c9588c8ddfed557b9024f4431a65ace4b1e7106388ddd5dd87b", [0, 1]]] 20 | }, 21 | "multi groups": { 22 | "cells": [ 23 | { 24 | "lock": { 25 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 26 | "hashType": "type", 27 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6" 28 | } 29 | }, 30 | { 31 | "lock": { 32 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 33 | "hashType": "type", 34 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f0" 35 | } 36 | }, 37 | { 38 | "lock": { 39 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 40 | "hashType": "type", 41 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6" 42 | } 43 | } 44 | ], 45 | "expected": [ 46 | ["0x0fec94c611533c9588c8ddfed557b9024f4431a65ace4b1e7106388ddd5dd87b", [0, 2]], 47 | ["0x20994c4484aac92d9b522fd32951941f6b9e73a0829297cc6cc5d828362890f7", [1]] 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nervosnetwork/ckb-sdk-core", 3 | "version": "0.109.5", 4 | "description": "JavaScript SDK for Nervos Network CKB Project", 5 | "author": "Nervos ", 6 | "homepage": "https://github.com/ckb-js/ckb-sdk-js#readme", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "module": "lib-esm/index.js", 10 | "typings": "lib/index.d.ts", 11 | "directories": { 12 | "lib": "lib", 13 | "lib-esm": "lib-esm", 14 | "umd": "umd", 15 | "test": "__tests__" 16 | }, 17 | "files": [ 18 | "lib", 19 | "lib-esm", 20 | "umd", 21 | "types" 22 | ], 23 | "exports": { 24 | ".": { 25 | "import": "./lib-esm/index.js", 26 | "require": "./lib/index.js", 27 | "types": "./lib/index.d.ts" 28 | }, 29 | "./lib/signWitnesses": { 30 | "import": "./lib-esm/signWitnesses.js", 31 | "require": "./lib/signWitnesses.js", 32 | "types": "./lib/signWitnesses.d.ts" 33 | } 34 | }, 35 | "publishConfig": { 36 | "registry": "https://registry.npmjs.org/", 37 | "access": "public" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/ckb-js/ckb-sdk-js.git" 42 | }, 43 | "scripts": { 44 | "tsc": "tsc --project tsconfig.json && tsc --project tsconfig.esm.json && npx copyfiles -u 2 misc/basedirs/**/* .", 45 | "test": "../../node_modules/.bin/jest" 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/ckb-js/ckb-sdk-js/issues" 49 | }, 50 | "dependencies": { 51 | "@nervosnetwork/ckb-sdk-rpc": "0.109.5", 52 | "@nervosnetwork/ckb-sdk-utils": "0.109.5", 53 | "@nervosnetwork/ckb-types": "0.109.5", 54 | "tslib": "2.3.1" 55 | }, 56 | "gitHead": "8950ea97ca8686ec70db81f668ead0de863fae81" 57 | } 58 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nervosnetwork/ckb-sdk-rpc", 3 | "version": "0.109.5", 4 | "description": "RPC module of @nervosnetwork/ckb-sdk-core", 5 | "author": "Nervos ", 6 | "homepage": "https://github.com/ckb-js/ckb-sdk-js/packages/ckb-rpc#readme", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "module": "lib-esm/index.js", 10 | "typings": "lib/index.d.ts", 11 | "directories": { 12 | "lib": "lib", 13 | "lib-esm": "lib-esm", 14 | "test": "__tests__" 15 | }, 16 | "files": [ 17 | "lib", 18 | "lib-esm", 19 | "types" 20 | ], 21 | "exports": { 22 | ".": { 23 | "import": "./lib-esm/index.js", 24 | "require": "./lib/index.js", 25 | "types": "./lib/index.d.ts" 26 | } 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/ckb-js/ckb-sdk-js.git" 35 | }, 36 | "scripts": { 37 | "tsc": "tsc --project tsconfig.json && tsc --project tsconfig.esm.json && npx copyfiles -u 2 misc/basedirs/**/* .", 38 | "test": "../../node_modules/.bin/jest", 39 | "doc": "../../node_modules/.bin/typedoc --out docs --mode modules --includeDeclarations --excludeExternals --ignoreCompilerErrors --theme default", 40 | "test:watch": "../../node_modules/.bin/jest --watch" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/ckb-js/ckb-sdk-js/issues" 44 | }, 45 | "dependencies": { 46 | "@nervosnetwork/ckb-sdk-utils": "0.109.5", 47 | "axios": "1.8.2", 48 | "tslib": "2.3.1" 49 | }, 50 | "devDependencies": { 51 | "@nervosnetwork/ckb-types": "0.109.5" 52 | }, 53 | "gitHead": "8950ea97ca8686ec70db81f668ead0de863fae81" 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ckb-sdk-js", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*" 6 | ], 7 | "scripts": { 8 | "tsc": "npx lerna run tsc", 9 | "cm": "git-cz", 10 | "build:lib": "npx lerna run tsc", 11 | "build:umd": "./scripts/bundle-umd.sh", 12 | "clean:lib": "rimraf **/packages/*/lib && rimraf **/packages/*/lib-esm", 13 | "docs": "typedoc --out docs --entryPointStrategy packages 'packages/*' --name CKB-SDK.js --excludeExternals --theme default --readme README.md", 14 | "publish": "npx lerna run tsc && npx lerna publish --from-package", 15 | "test": "jest --coverage" 16 | }, 17 | "husky": { 18 | "hooks": { 19 | "pre-commit": "lint-staged" 20 | } 21 | }, 22 | "lint-staged": { 23 | "*/**/*.{ts,js}": [ 24 | "eslint --fix", 25 | "git add" 26 | ] 27 | }, 28 | "devDependencies": { 29 | "@cryptape/sdk-ts-config": "0.0.1", 30 | "@types/node": "16.10.2", 31 | "@typescript-eslint/eslint-plugin": "4.32.0", 32 | "@typescript-eslint/parser": "4.32.0", 33 | "commitizen": "4.3.0", 34 | "cz-conventional-changelog": "3.3.0", 35 | "eslint": "7.32.0", 36 | "eslint-config-airbnb-base": "14.2.1", 37 | "eslint-config-prettier": "8.3.0", 38 | "eslint-plugin-import": "2.24.2", 39 | "eslint-plugin-jest": "24.5.0", 40 | "eslint-plugin-prettier": "4.0.0", 41 | "husky": "7.0.2", 42 | "jest": "27.2.4", 43 | "lint-staged": "11.1.2", 44 | "prettier": "2.4.1", 45 | "rimraf": "3.0.2", 46 | "typedoc": "0.22.18", 47 | "typescript": "4.4.3" 48 | }, 49 | "config": { 50 | "commitizen": { 51 | "path": "./node_modules/cz-conventional-changelog" 52 | } 53 | }, 54 | "jest": { 55 | "moduleNameMapper": { 56 | "^axios$": "axios/dist/node/axios.cjs" 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": ["airbnb-base", "plugin:prettier/recommended"], 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "jest"], 5 | "rules": { 6 | "comma-dangle": [2, { 7 | "arrays": "always-multiline", 8 | "objects": "always-multiline", 9 | "imports": "always-multiline", 10 | "exports": "always-multiline", 11 | "functions": "ignore" 12 | }], 13 | "import/no-extraneous-dependencies": [2, { 14 | "devDependencies": true 15 | }], 16 | "@typescript-eslint/no-unused-vars": ["error", { 17 | "vars": "local", 18 | "args": "after-used", 19 | "ignoreRestSiblings": false 20 | }], 21 | "no-underscore-dangle": ["error", { 22 | "allowAfterThis": true 23 | }], 24 | "import/no-unresolved": "off", 25 | "semi": [2, "never"], 26 | "no-console": [0], 27 | "no-unused-vars": [0], 28 | "arrow-parens": [0], 29 | "indent": ["error", 2], 30 | "no-mixed-operators": [0], 31 | "implicit-arrow-linebreak": [0], 32 | "no-unused-expression": [0], 33 | "no-plusplus": [0], 34 | "no-bitwise": [0], 35 | "no-control-regex": [0], 36 | "operator-linebreak": [2, "after", { 37 | "overrides": { 38 | "?": "before", 39 | ":": "before" 40 | } 41 | }], 42 | "max-len": [2, { 43 | "code": 120, 44 | "ignoreUrls": true 45 | }], 46 | "object-curly-newline": ["error", { 47 | "consistent": true 48 | }], 49 | "spaced-comment": ["error", "always", { 50 | "markers": ["/"] 51 | }], 52 | "max-classes-per-file": [0], 53 | "import/extensions": [2, { 54 | "ts": "never" 55 | }] 56 | }, 57 | "globals": { 58 | "BigInt": "readonly", 59 | "globalThis": "readonly", 60 | }, 61 | "env": { 62 | "node": true, 63 | "jest": true 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/examples/sendTransactionWithLumosCollector.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const os = require('os') 3 | 4 | /* eslint-disable import/no-extraneous-dependencies */ 5 | const { Indexer, CellCollector } = require('@ckb-lumos/indexer') 6 | const CKB = require('../lib').default 7 | 8 | const LUMOS_DB = path.join(os.tmpdir(), 'lumos_db') 9 | const CKB_URL = process.env.CKB_URL || 'http://127.0.0.1:8114' 10 | 11 | const ckb = new CKB(CKB_URL) 12 | const indexer = new Indexer(CKB_URL, LUMOS_DB) 13 | 14 | // private key for demo, don't expose it in production 15 | const PRI_KEY = process.env.PRI_KEY || '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' 16 | const PUB_KEY = ckb.utils.privateKeyToPublicKey(PRI_KEY) 17 | const ARGS = `0x${ckb.utils.blake160(PUB_KEY, 'hex')}` 18 | const ADDRESS = ckb.utils.pubkeyToAddress(PUB_KEY) 19 | 20 | /** 21 | * start the lumos to sync cells 22 | */ 23 | const startSync = () => { 24 | indexer.startForever() 25 | } 26 | 27 | /** 28 | * stop the lumos sync 29 | */ 30 | const stopSync = () => { 31 | indexer.stop() 32 | } 33 | 34 | /** 35 | * generate and send a transaction 36 | */ 37 | const bootstrap = async () => { 38 | startSync() 39 | const { secp256k1Dep } = await ckb.loadDeps() 40 | const lock = { ...secp256k1Dep, args: ARGS } 41 | const cells = await ckb.loadCells({ indexer, CellCollector, lock }) 42 | stopSync() 43 | 44 | const rawTx = ckb.generateRawTransaction({ 45 | fromAddress: ADDRESS, 46 | toAddress: ADDRESS, 47 | capacity: 10000000000000n, 48 | fee: 100000n, 49 | safeMode: true, 50 | cells, 51 | deps: secp256k1Dep, 52 | }) 53 | 54 | const signedTx = ckb.signTransaction(PRI_KEY)(rawTx) 55 | const txHash = await ckb.rpc.sendTransaction(signedTx) 56 | console.info(`Transaction has been sent with tx hash ${txHash}`) 57 | return txHash 58 | } 59 | 60 | bootstrap() 61 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/generateRawTransaction/index.test.js: -------------------------------------------------------------------------------- 1 | const { reconcilers } = require('@nervosnetwork/ckb-sdk-utils') 2 | const { default: generateRawTransaction } = require('../../lib/generateRawTransaction') 3 | 4 | const fixtures = require('./fixtures.json') 5 | 6 | describe('Test generate raw transaction', () => { 7 | describe('With fee', () => { 8 | const fixtureTable = Object.entries( 9 | fixtures.generateRawTransaction, 10 | ).map(([title, { params, expected, exception }]) => [title, params, expected, exception]) 11 | 12 | test.each(fixtureTable)('%s', (_title, params, expected, exception) => { 13 | expect.assertions(1) 14 | try { 15 | let fmtParams = params 16 | if ('inputScript' in params) { 17 | fmtParams = { ...params, capacity: BigInt(params.capacity), fee: BigInt(params.fee || 0) } 18 | } else { 19 | fmtParams = { 20 | ...params, 21 | outputs: params.outputs.map(output => ({ ...output, capacity: BigInt(output.capacity) })), 22 | cells: new Map(params.cells), 23 | fee: BigInt(params.fee || 0), 24 | } 25 | } 26 | const rawTransaction = generateRawTransaction(fmtParams) 27 | expect(rawTransaction).toEqual(expected) 28 | } catch (err) { 29 | expect(err).toEqual(new Error(exception)) 30 | } 31 | }) 32 | }) 33 | 34 | describe('With fee rate', () => { 35 | const fixtureTable = Object.entries(fixtures.feeRate).map(([title, { params, expected }]) => [ 36 | title, 37 | params, 38 | expected, 39 | ]) 40 | 41 | test.each(fixtureTable.slice(0, 1))(`%s`, (_title, params, expected) => { 42 | Object.defineProperty(params[0].fee, 'reconciler', { value: reconcilers.extraInputs }) 43 | expect.assertions(1) 44 | expect(generateRawTransaction(...params)).toEqual(expected) 45 | }) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/method.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { IdNotMatchException, ResponseException } from './exceptions/index.js' 3 | 4 | class Method { 5 | #name: string 6 | 7 | get name() { 8 | return this.#name 9 | } 10 | 11 | #options: CKBComponents.Method = { 12 | name: '', 13 | method: '', 14 | paramsFormatters: [], 15 | resultFormatters: undefined, 16 | } 17 | 18 | #node: CKBComponents.Node 19 | 20 | constructor(node: CKBComponents.Node, options: CKBComponents.Method) { 21 | this.#node = node 22 | this.#options = options 23 | this.#name = options.name 24 | Object.defineProperty(this.call, 'name', { value: options.name, configurable: false, writable: false }) 25 | } 26 | 27 | public call = (...params: (string | number | boolean | object)[]) => { 28 | const payload = this.getPayload(...params) 29 | return axios({ 30 | method: 'POST', 31 | headers: { 32 | 'content-type': 'application/json', 33 | }, 34 | data: payload, 35 | url: this.#node.url, 36 | httpAgent: this.#node.httpAgent, 37 | httpsAgent: this.#node.httpsAgent, 38 | }).then(res => { 39 | if (res.data.id !== payload.id) { 40 | throw new IdNotMatchException(payload.id, res.data.id) 41 | } 42 | if (res.data.error) { 43 | throw new ResponseException(JSON.stringify(res.data.error)) 44 | } 45 | return this.#options.resultFormatters?.(res.data.result) ?? res.data.result 46 | }) 47 | } 48 | 49 | public getPayload = (...params: (string | number | boolean | object)[]) => { 50 | const data = params.map((p, i) => (this.#options.paramsFormatters[i] && this.#options.paramsFormatters[i](p)) || p) 51 | const id = Math.round(Math.random() * 10000) 52 | const payload = { 53 | id, 54 | method: this.#options.method, 55 | params: data, 56 | jsonrpc: '2.0', 57 | } 58 | return payload 59 | } 60 | } 61 | 62 | export default Method 63 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/generateRawTransaction/getTargetOutputs/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "should return outputs": { 3 | "params": [ 4 | { 5 | "outputs": [ 6 | { 7 | "lock": { 8 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 9 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 10 | "hashType": "type" 11 | }, 12 | "type": { 13 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 14 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 15 | "hashType": "type" 16 | }, 17 | "capacity": 6200000000 18 | } 19 | ], 20 | "minCapacity": 6100000000 21 | } 22 | ], 23 | "expected": [ 24 | { 25 | "capacity": 6200000000, 26 | "lock": { 27 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 28 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 29 | "hashType": "type" 30 | }, 31 | "type": { 32 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 33 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 34 | "hashType": "type" 35 | } 36 | } 37 | ] 38 | }, 39 | "should throw an error when capacity is too small": { 40 | "params": [ 41 | { 42 | "outputs": [ 43 | { 44 | "lock": { 45 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 46 | "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 47 | "hashType": "type" 48 | }, 49 | "capacity": 6000000000 50 | } 51 | ], 52 | "minCapacity": 6100000000 53 | } 54 | ], 55 | "exception": "Capacity should be at least 6100000000 shannon" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/epochs/index.test.js: -------------------------------------------------------------------------------- 1 | const { serializeEpoch, parseEpoch, getWithdrawEpoch } = require('../..') 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('Test epochs', () => { 5 | describe('serialize epoch', () => { 6 | const fixtureTable = Object.entries(fixtures.serializeEpoch).map(([title, { params, expected, exception }]) => [ 7 | title, 8 | params, 9 | expected, 10 | exception, 11 | ]) 12 | 13 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 14 | expect.assertions(1) 15 | try { 16 | const actual = serializeEpoch(...params) 17 | expect(actual).toEqual(expected) 18 | } catch (err) { 19 | expect(err).toEqual(new Error(exception)) 20 | } 21 | }) 22 | }) 23 | 24 | describe('parse epoch', () => { 25 | const fixtureTable = Object.entries(fixtures.parseEpoch).map(([title, { params, expected, exception }]) => [ 26 | title, 27 | params, 28 | expected, 29 | exception, 30 | ]) 31 | 32 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 33 | expect.assertions(1) 34 | try { 35 | const actual = parseEpoch(...params) 36 | expect(actual).toEqual(expected) 37 | } catch (err) { 38 | expect(err).toEqual(new Error(exception)) 39 | } 40 | }) 41 | }) 42 | 43 | describe('get withdraw epoch', () => { 44 | const fixtureTable = Object.entries(fixtures.getWithdrawEpoch).map(([title, { params, expected, exception }]) => [ 45 | title, 46 | params, 47 | expected, 48 | exception, 49 | ]) 50 | 51 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 52 | expect.assertions(1) 53 | try { 54 | const actual = getWithdrawEpoch(...params) 55 | expect(actual).toBe(expected) 56 | } catch (err) { 57 | expect(err).toEqual(new Error(exception)) 58 | } 59 | }) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/crypto/blake2b.test.js: -------------------------------------------------------------------------------- 1 | const { blake2b, PERSONAL } = require('../..') 2 | const fixtures = require('./blake2b.fixtures.json') 3 | 4 | describe('blake2b', () => { 5 | it('blake2b([]) with personal', () => { 6 | const fixture = { 7 | message: new Uint8Array(), 8 | digest: '44f4c69744d5f8c55d642062949dcae49bc4e7ef43d388c5a12f42b5633d163e', 9 | } 10 | const s = blake2b(32, null, null, PERSONAL) 11 | s.update(fixture.message) 12 | const digest = s.digest('hex') 13 | expect(digest).toBe(fixture.digest) 14 | }) 15 | 16 | it('blake2b(Buffer.from("The quick brown fox jumps over the lazy dog")) with personal', () => { 17 | const fixture = { 18 | message: 'The quick brown fox jumps over the lazy dog', 19 | digest: 'abfa2c08d62f6f567d088d6ba41d3bbbb9a45c241a8e3789ef39700060b5cee2', 20 | } 21 | const s = blake2b(32, null, null, PERSONAL) 22 | s.update(new Uint8Array(Buffer.from(fixture.message, 'utf8'))) 23 | const digest = s.digest('hex') 24 | expect(digest).toBe(fixture.digest) 25 | }) 26 | 27 | test.each(fixtures)('%s', ({ outlen, out, input, key, salt, personal }) => { 28 | if (+outlen < 16) { 29 | expect(() => { 30 | blake2b( 31 | outlen, 32 | key ? new Uint8Array(Buffer.from(key, 'hex')) : null, 33 | salt ? new Uint8Array(Buffer.from(salt, 'hex')) : null, 34 | personal ? new Uint8Array(Buffer.from(personal, 'hex')) : null, 35 | ) 36 | }).toThrowError(`Expect outlen to be at least 16, but ${outlen} received`) 37 | } else { 38 | const s = blake2b( 39 | outlen, 40 | key ? new Uint8Array(Buffer.from(key, 'hex')) : null, 41 | salt ? new Uint8Array(Buffer.from(salt, 'hex')) : null, 42 | personal ? new Uint8Array(Buffer.from(personal, 'hex')) : null, 43 | ) 44 | s.update(new Uint8Array(Buffer.from(input, 'hex'))) 45 | const digest = s.digest('hex') 46 | expect(digest).toBe(out) 47 | } 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/exceptions/address.ts: -------------------------------------------------------------------------------- 1 | import ErrorCode from './ErrorCode.js' 2 | 3 | export class AddressPayloadException extends Error { 4 | code = ErrorCode.AddressInvalid 5 | 6 | type: 'short' | 'full' | undefined 7 | 8 | constructor(payload: Uint8Array, type?: 'short' | 'full') { 9 | super(`'${payload}' is not a valid ${type ? `${type} version ` : ''}address payload`) 10 | this.type = type 11 | } 12 | } 13 | 14 | export class AddressException extends Error { 15 | code = ErrorCode.AddressInvalid 16 | 17 | type: 'short' | 'full' | undefined 18 | 19 | constructor(addr: string, stack: string, type?: 'short' | 'full') { 20 | super(`'${addr}' is not a valid ${type ? `${type} version ` : ''}address`) 21 | this.type = type 22 | this.stack = stack 23 | } 24 | } 25 | 26 | export class CodeHashException extends Error { 27 | code = ErrorCode.AddressInvalid 28 | 29 | constructor(codeHash: string) { 30 | super(`'${codeHash}' is not a valid code hash`) 31 | } 32 | } 33 | 34 | export class HashTypeException extends Error { 35 | code = ErrorCode.AddressInvalid 36 | 37 | constructor(hashType: string) { 38 | super(`'${hashType}' is not a valid hash type`) 39 | } 40 | } 41 | 42 | export class AddressFormatTypeException extends Error { 43 | code = ErrorCode.AddressInvalid 44 | 45 | constructor(type: number) { 46 | super(`0x${type.toString(16).padStart(2, '0')} is not a valid address format type`) 47 | } 48 | } 49 | 50 | export class AddressFormatTypeAndEncodeMethodNotMatchException extends Error { 51 | code = ErrorCode.AddressInvalid 52 | 53 | constructor(type: number, bech32Type: 'bech32' | 'bech32m' | 'unknown' = 'unknown') { 54 | super(`Address format type 0x${type.toString(16).padStart(2, '0')} doesn't match encode method ${bech32Type}`) 55 | } 56 | } 57 | 58 | export default { 59 | AddressPayloadException, 60 | AddressException, 61 | CodeHashException, 62 | HashTypeException, 63 | AddressFormatTypeException, 64 | AddressFormatTypeAndEncodeMethodNotMatchException, 65 | } 66 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/ecpair/ecpare.fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "instantiate": { 3 | "basic": { 4 | "privateKey": "0xe79f3207ea4980b7fed79956d5934249ceac4751a4fae01a0f7c4a96884bc4e3", 5 | "compressed": true, 6 | "publicKey": "0x024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01", 7 | "privateKeyWords": [ 8 | 4965603, 9 | 51553698, 10 | 33661175, 11 | 21402603, 12 | 30321735, 13 | 23384210, 14 | 24745325, 15 | 33742843, 16 | 34073161, 17 | 3794892, 18 | 0 19 | ], 20 | "publicKeyBytes": [ 21 | 2, 22 | 74, 23 | 80, 24 | 30, 25 | 253, 26 | 50, 27 | 142, 28 | 6, 29 | 44, 30 | 134, 31 | 117, 32 | 242, 33 | 54, 34 | 89, 35 | 112, 36 | 114, 37 | 140, 38 | 133, 39 | 156, 40 | 89, 41 | 43, 42 | 238, 43 | 239, 44 | 214, 45 | 190, 46 | 142, 47 | 173, 48 | 61, 49 | 144, 50 | 19, 51 | 48, 52 | 188, 53 | 1 54 | ] 55 | }, 56 | "withEmptyOption": { 57 | "privateKey": "0xe79f3207ea4980b7fed79956d5934249ceac4751a4fae01a0f7c4a96884bc4e3", 58 | "compressed": true, 59 | "publicKey": "0x024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01" 60 | }, 61 | "withDefaultOption": { 62 | "privateKey": "0xe79f3207ea4980b7fed79956d5934249ceac4751a4fae01a0f7c4a96884bc4e3", 63 | "compressed": true, 64 | "publicKey": "0x024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01" 65 | }, 66 | "privateKeyHasInvalidLength": { 67 | "privateKey": "0xf70eea3041c793746f07d47f28a62d6555041b9edc74501a75ffbf47c55fc9", 68 | "exception": "Private key has invalid length" 69 | }, 70 | "leadingZeroShouldBeRemained": { 71 | "privateKey": "0x00f70eea3041c793746f07d47f28a62d6555041b9edc74501a75ffbf47c55fc9" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/__tests__/exceptions/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "IdNotMatchException": { 3 | "params": [10000, 10001], 4 | "expected": { 5 | "code": 201, 6 | "message": "Expect json rpc id to be 10000, but 10001 received" 7 | } 8 | }, 9 | "ResponseException": { 10 | "params": ["Response_Err_Message"], 11 | "expected": { 12 | "code": 204, 13 | "message": "Response_Err_Message" 14 | } 15 | }, 16 | "PageSizeTooLargeException": { 17 | "params": [51, 50], 18 | "expected": { 19 | "code": 101, 20 | "message": "Expect page size to be at most 50, but 51 received" 21 | } 22 | }, 23 | "PageSizeTooSmallException": { 24 | "params": [-1, 0], 25 | "expected": { 26 | "code": 101, 27 | "message": "Expect page size to be at least 0, but -1 received" 28 | } 29 | }, 30 | "OutputsValidatorTypeException": { 31 | "params": [], 32 | "expected": { 33 | "code": 101, 34 | "message": "Expect outputs validator to be 'default' or 'passthrough'" 35 | } 36 | }, 37 | "BigintOrHexStringTypeException": { 38 | "params": ["ab"], 39 | "expected": { 40 | "code": 101, 41 | "message": "Expect number to be bigint or hex string, but ab received" 42 | } 43 | }, 44 | "StringHashTypeException": { 45 | "params": [1], 46 | "expected": { 47 | "code": 101, 48 | "message": "Expect hash to be string, but 1 received" 49 | } 50 | }, 51 | "MethodInBatchNotFoundException": { 52 | "params": ["Method_Name"], 53 | "expected": { 54 | "code": 202, 55 | "message": "[Batch Request]: Method Method_Name is not found" 56 | } 57 | }, 58 | "PayloadInBatchException": { 59 | "params": [1, "Payload_Err_Message"], 60 | "expected": { 61 | "code": 203, 62 | "index": 1, 63 | "message": "[Batch Request 1]: Payload_Err_Message" 64 | } 65 | }, 66 | "IdNotMatchedInBatchException": { 67 | "params": [1, 10000, 10001], 68 | "expected": { 69 | "code": 201, 70 | "index": 1, 71 | "message": "[Batch Request 1]: Expect json rpc id to be 10000, but 10001 received" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/validators/index.test.js: -------------------------------------------------------------------------------- 1 | const { assertToBeHexString, assertToBeHexStringOrBigint } = require('../../lib/validators') 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('validators', () => { 5 | describe('assert to be type of hex string', () => { 6 | const { assertToBeHexString: fixture } = fixtures 7 | 8 | it('valid hex string', () => { 9 | expect(assertToBeHexString(fixture.validHexString)).toBe(true) 10 | }) 11 | 12 | it('string without 0x should fail', () => { 13 | expect(() => assertToBeHexString(fixture.stringWithout0x)).toThrow( 14 | new Error(`${fixture.stringWithout0x} is an invalid hex string`), 15 | ) 16 | }) 17 | 18 | it('invalid number should fail', () => { 19 | expect(() => assertToBeHexString(fixture.invalidNumber)).toThrow( 20 | new Error(`${fixture.invalidNumber} is an invalid hex string`), 21 | ) 22 | }) 23 | 24 | it('value which is not a string should fail', () => { 25 | expect(() => assertToBeHexString(fixture.notString)).toThrow( 26 | new Error(`${fixture.notString} is an invalid hex string`), 27 | ) 28 | }) 29 | }) 30 | 31 | describe('assert to be type of hex string or bigint', () => { 32 | const { assertToBeHexStringOrBigint: fixture } = fixtures 33 | 34 | it('hex string starts with 0x should pass', () => { 35 | expect(assertToBeHexStringOrBigint(fixture.hexStringStartsWith0x)).toBe(true) 36 | }) 37 | 38 | it('hex string starts without 0x should throw an error', () => { 39 | expect(() => assertToBeHexStringOrBigint(fixture.hexStringStartsWithout0x)).toThrow( 40 | new TypeError(`Hex string ${fixture.hexStringStartsWithout0x} should start with 0x`), 41 | ) 42 | }) 43 | 44 | it('bigint should pass', () => { 45 | expect(assertToBeHexStringOrBigint(BigInt(fixture.bigint))).toBe(true) 46 | }) 47 | 48 | it('number should throw an error', () => { 49 | expect(() => assertToBeHexStringOrBigint(fixture.number)).toThrow( 50 | new TypeError(`${fixture.number} should be type of string or bigint`), 51 | ) 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/serialization/script.ts: -------------------------------------------------------------------------------- 1 | import { serializeArray, serializeTable, serializeFixVec } from './basic.js' 2 | import { ParameterRequiredException } from '../exceptions/index.js' 3 | 4 | /** 5 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/base.html @ckb-lumos/base/blockchain} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#serializecodehash example} 6 | */ 7 | export const serializeCodeHash = (codeHash: CKBComponents.Hash256) => serializeArray(codeHash) 8 | 9 | /** 10 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/base.html @ckb-lumos/base/blockchain} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#serializehashtype example} 11 | */ 12 | export const serializeHashType = (hashType: CKBComponents.ScriptHashType) => { 13 | if (hashType === 'data') return '0x00' 14 | if (hashType === 'type') return '0x01' 15 | if (hashType === 'data1') return '0x02' 16 | if (hashType === 'data2') return '0x04' 17 | throw new TypeError("Hash type must be either of 'data' or 'type'") 18 | } 19 | 20 | /** 21 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/base.html @ckb-lumos/base/blockchain} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#serializeargs example} 22 | */ 23 | export const serializeArgs = (args: string) => serializeFixVec(args) 24 | 25 | /** 26 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/base.html @ckb-lumos/base/blockchain} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#serializescript example} 27 | */ 28 | export const serializeScript = (script: CKBComponents.Script) => { 29 | if (!script) throw new ParameterRequiredException('Script') 30 | const { codeHash = '', hashType, args = '' } = script 31 | const serializedCodeHash = serializeCodeHash(codeHash) 32 | const serializedHashType = serializeHashType(hashType) 33 | const serializedArgs = serializeArgs(args) 34 | const table = new Map([ 35 | ['codeHash', serializedCodeHash], 36 | ['hashType', serializedHashType], 37 | ['args', serializedArgs], 38 | ]) 39 | return serializeTable(table) 40 | } 41 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/systemScripts/index.test.js: -------------------------------------------------------------------------------- 1 | const { systemScripts } = require('../..') 2 | const fixtures = require('./fixtures.json') 3 | 4 | const getInfo = ({ codeHash, hashType, depType, mainnetOutPoint, testnetOutPoint }, isMain) => { 5 | const outPoint = isMain ? mainnetOutPoint : testnetOutPoint 6 | 7 | return { codeHash, hashType, depType, ...outPoint } 8 | } 9 | 10 | describe('Test System Scripts', () => { 11 | describe('Test secp256k1/blake160', () => { 12 | it('should has mainnet script', () => { 13 | expect(getInfo(systemScripts.SECP256K1_BLAKE160, true)).toEqual(fixtures.SECP256K1_BLAKE160.mainnet) 14 | }) 15 | 16 | it('should has testnet script', () => { 17 | expect(getInfo(systemScripts.SECP256K1_BLAKE160, false)).toEqual(fixtures.SECP256K1_BLAKE160.testnet) 18 | }) 19 | }) 20 | 21 | describe('Test secp256k1/multisig', () => { 22 | it('should has mainnet script', () => { 23 | expect(getInfo(systemScripts.SECP256K1_MULTISIG, true)).toEqual(fixtures.SECP256K1_MULTISIG.mainnet) 24 | }) 25 | 26 | it('should has testnet script', () => { 27 | expect(getInfo(systemScripts.SECP256K1_MULTISIG, false)).toEqual(fixtures.SECP256K1_MULTISIG.testnet) 28 | }) 29 | }) 30 | 31 | describe('Test anyone can pay', () => { 32 | it('should has mainnet script', () => { 33 | expect(getInfo(systemScripts.ANYONE_CAN_PAY_MAINNET, true)).toEqual(fixtures.ANYONE_CAN_PAY.mainnet) 34 | }) 35 | 36 | it('should has testnet script', () => { 37 | expect(getInfo(systemScripts.ANYONE_CAN_PAY_TESTNET, false)).toEqual(fixtures.ANYONE_CAN_PAY.testnet) 38 | }) 39 | }) 40 | 41 | describe('Test nervos dao', () => { 42 | it('should has mainnet script', () => { 43 | expect(getInfo(systemScripts.NERVOS_DAO, true)).toEqual(fixtures.NERVOS_DAO.mainnet) 44 | }) 45 | 46 | it('should has testnet script', () => { 47 | expect(getInfo(systemScripts.NERVOS_DAO, false)).toEqual(fixtures.NERVOS_DAO.testnet) 48 | }) 49 | }) 50 | 51 | describe('Test simple udt', () => { 52 | it("shouldn't has mainnet script", () => { 53 | expect(systemScripts.SIMPLE_UDT.mainnet).toBeUndefined() 54 | }) 55 | 56 | it('should has testnet script', () => { 57 | expect(getInfo(systemScripts.SIMPLE_UDT, false)).toEqual(fixtures.SIMPLE_UDT.testnet) 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/types/global.d.ts: -------------------------------------------------------------------------------- 1 | interface DepCellInfo { 2 | hashType: CKBComponents.ScriptHashType 3 | codeHash: CKBComponents.Hash256 4 | typeHash?: CKBComponents.Hash256 5 | outPoint: CKBComponents.OutPoint 6 | depType: CKBComponents.DepType 7 | } 8 | 9 | type StructuredWitness = CKBComponents.WitnessArgs | CKBComponents.Witness 10 | 11 | declare namespace LoadCellsParams { 12 | interface Base { 13 | start?: string | bigint 14 | end?: string | bigint 15 | } 16 | 17 | interface Normal extends Base { 18 | lockHash: CKBComponents.Hash 19 | start?: string | bigint 20 | end?: string | bigint 21 | STEP?: string | bigint 22 | } 23 | 24 | interface FromIndexer extends Base { 25 | lock: CKBComponents.Script 26 | indexer: any 27 | CellCollector: any 28 | } 29 | } 30 | 31 | declare namespace RawTransactionParams { 32 | type LockHash = string 33 | type Capacity = string | bigint 34 | type Cell = { 35 | data: string 36 | lock: CKBComponents.Script 37 | type?: CKBComponents.Script 38 | capacity: CKBComponents.Capacity 39 | outPoint: CKBComponents.OutPoint 40 | } 41 | type Fee = 42 | | Capacity 43 | | { 44 | feeRate: Capacity 45 | reconciler: (params: { 46 | tx: CKBComponents.RawTransactionToSign 47 | feeRate: Capacity 48 | changeThreshold: Capacity 49 | cells: Array<{ capacity: string; outPoint: CKBComponents.OutPoint }> 50 | extraCount: number 51 | }) => CKBComponents.RawTransactionToSign 52 | } 53 | interface Base { 54 | fee?: Fee 55 | safeMode: boolean 56 | deps: DepCellInfo | DepCellInfo[] 57 | capacityThreshold?: Capacity 58 | changeThreshold?: Capacity 59 | changeLockScript?: CKBComponents.Script 60 | witnesses?: Array 61 | outputsData?: Array 62 | } 63 | 64 | interface Simple extends Base { 65 | inputScript: CKBComponents.Script 66 | outputScript: CKBComponents.Script 67 | capacity: Capacity 68 | cells?: Cell[] 69 | } 70 | 71 | interface Output { 72 | capacity: string | bigint 73 | lock: CKBComponents.Script 74 | type?: CKBComponents.Script | null 75 | } 76 | 77 | interface Complex extends Base { 78 | inputScripts: CKBComponents.Script[] 79 | outputs: Output[] 80 | cells?: Map 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/signWitnesses/index.test.js: -------------------------------------------------------------------------------- 1 | const signWitnesses = require('../../lib/signWitnesses').default 2 | const ECPair = require('../../../ckb-sdk-utils/lib/ecpair').default 3 | const fixtures = require('./fixtures.json') 4 | 5 | describe('test sign witnesses', () => { 6 | const fixtureTable = Object.entries(fixtures).map( 7 | ([title, { privateKey, privateKeys, signatureProviders, transactionHash, witnesses, inputCells, expected, exception, skipMissingKeys }]) => [ 8 | title, 9 | privateKey, 10 | privateKeys, 11 | signatureProviders, 12 | transactionHash, 13 | witnesses, 14 | inputCells, 15 | skipMissingKeys, 16 | exception, 17 | expected, 18 | ], 19 | ) 20 | 21 | test.each(fixtureTable)( 22 | '%s', 23 | (_title, privateKey, privateKeys, signatureProviders, transactionHash, witnesses, inputCells, skipMissingKeys, exception, expected) => { 24 | if (exception !== undefined) { 25 | const key = privateKey || (privateKeys && new Map(privateKeys)) 26 | expect( 27 | () => signWitnesses(key)({ 28 | transactionHash, 29 | witnesses, 30 | inputCells, 31 | skipMissingKeys, 32 | }) 33 | ).toThrowError(exception) 34 | } else if (privateKey !== undefined) { 35 | const signedWitnesses = signWitnesses(privateKey)({ 36 | transactionHash, 37 | witnesses, 38 | inputCells, 39 | skipMissingKeys, 40 | }) 41 | expect(signedWitnesses).toEqual(expected) 42 | } else if (privateKeys !== undefined) { 43 | const keys = new Map(privateKeys) 44 | const signedWitnesses = signWitnesses(keys)({ 45 | transactionHash, 46 | witnesses, 47 | inputCells, 48 | skipMissingKeys 49 | }) 50 | expect(signedWitnesses).toEqual(expected) 51 | } else if (signatureProviders !== undefined) { 52 | const sigProviderMaps = new Map() 53 | signatureProviders.forEach(([lockHash, key]) => { 54 | sigProviderMaps.set(lockHash, (message) => new ECPair(key).signRecoverable(message)) 55 | }) 56 | const signedWitnesses = signWitnesses(sigProviderMaps)({ 57 | transactionHash, 58 | witnesses, 59 | inputCells, 60 | skipMissingKeys 61 | }) 62 | expect(signedWitnesses).toEqual(expected) 63 | } 64 | }, 65 | ) 66 | }) 67 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/docs/Fee_and_fee_rate.md: -------------------------------------------------------------------------------- 1 | # Fee 2 | 3 | ## How to calculate transaction fee 4 | 5 | [Doc](https://docs.nervos.org/docs/essays/faq#how-do-you-calculate-transaction-fee) 6 | 7 | The size of a normal 2-in-2-out transaction is 597 bytes so that its virtual size is 601 bytes, there're 4 bytes extra cost in a block. 8 | 9 | The fee formula is 10 | 11 | ``` 12 | virtual_tx_size * fee_rate / 1000 13 | ``` 14 | 15 | where: 16 | 17 | - virtual_tx_size: size cost in a block, in `bytes`, 18 | - fee_rate: defined by user, in `shannons/kB`; 19 | 20 | Suppose the fee rate is `1000 shannons/kB`, the fee of a 2-in-2-out transaction is `601 * 1000 / 1000` shannons, namely **0.00000601 CKB** 21 | 22 | ## Fee Rate 23 | 24 | Decided by users, impacts on the sequence of committing transactions by miners. 25 | 26 | ## Generate Transaction with Fee Rate 27 | 28 | Suppose we have a transaction named `tx`, it has a change output of capacity `change` and it's size is `tx_size`. 29 | 30 | When the fee rate is set to `fee_rate`, apparently `fee = tx_size * fee_rate`. 31 | 32 | There are two cases: 33 | 34 | ### change >= minimal_change + fee 35 | 36 | The transaction can be updated to have a change output of capacity `change - fee` and that's all. 37 | 38 | ### change < minimal_change + fee 39 | 40 | It's hard to pay the fee by change, so extra inputs are required. There are also two strategies to increase inputs 41 | 42 | #### Increase inputs by replacing current inputs 43 | 44 | Say we have used an input of 1000 capacity, when we replace it with an input of 2000 capacity, obviously the fee will not change, and the capacity of change output will increase by `2000 - 1000 - fee`. 45 | 46 | #### Increase inputs by adding new input 47 | 48 | Say we add `n` new inputs, the size bumps to `tx_size + 44 * n` since each input has a size of 44 bytes. By that the fee is `fee + 44 * n * fee_rate`. 49 | 50 | In a word, `44 * n * fee_rate` extra cost is introduced by `n` inputs, so the capacity of `n` inputs should be at least `fee - (change - minimal_change) + 44 * n * fee_rate` 51 | 52 | Each input has at least 61 CKB, namely at least `61_00_000_000 * n` shannons will be introduced 53 | 54 | ``` 55 | 6100000000 * n > (fee - (change - minimal_change) + 44 * n * fee_rate) -> 56 | 6100000000 * n - 44 * fee_rate * n > fee + minimal_change - change -> 57 | n > (fee + minimal_change - change) / (6100000000 - 44 * fee_rate) 58 | ``` 59 | 60 | --- 61 | 62 | Since the dynamic picking of cells is not implemented yet, we're going to use the second strategy, increase inputs by adding new input, for now. 63 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/__tests__/formatters/params.test.js: -------------------------------------------------------------------------------- 1 | const { default: paramsFmt } = require('../../lib/paramsFormatter') 2 | const fixtures = require('./params.fixtures.json') 3 | 4 | describe('params formatter', () => { 5 | describe.each(Object.keys(fixtures))('%s', methodName => { 6 | const fixtureTable = Object.values(fixtures[methodName]).map(({ param, expected, exception }) => [ 7 | param === 'undefined' ? undefined : param, 8 | expected === 'undefined' ? undefined : expected, 9 | exception, 10 | ]) 11 | test.each(fixtureTable)('%j => %j', (param, expected, exception) => { 12 | if (undefined !== expected) { 13 | const formatted = paramsFmt[methodName](param) 14 | expect(formatted).toEqual(expected) 15 | } 16 | if (undefined !== exception) { 17 | expect(() => paramsFmt[methodName](param)).toThrow(new Error(exception)) 18 | } 19 | }) 20 | }) 21 | 22 | describe('toOptional', () => { 23 | it('toOptional with other format should return the formatted value', () => { 24 | expect(paramsFmt.toOptional(paramsFmt.toNumber)(BigInt(20))).toBe('0x14') 25 | }) 26 | 27 | it("toOptional with other format should return the raw value if it's undefined or null", () => { 28 | expect(paramsFmt.toOptional(paramsFmt.toNumber)(null)).toBe(null) 29 | expect(paramsFmt.toOptional(paramsFmt.toNumber)(undefined)).toBe(undefined) 30 | }) 31 | 32 | it('toOptional without other format should return the raw value', () => { 33 | expect(paramsFmt.toOptional()(20)).toBe(20) 34 | }) 35 | 36 | it('toOptional should throw errors which are thrown from other format', () => { 37 | expect(() => paramsFmt.toOptional(paramsFmt.toNumber)('20')).toThrow('Hex string 20 should start with 0x') 38 | }) 39 | }) 40 | 41 | describe('toArray', () => { 42 | it('toArray with other format should return the formatted value', () => { 43 | expect(paramsFmt.toArray(paramsFmt.toNumber)([BigInt(20)])).toEqual(['0x14']) 44 | }) 45 | 46 | it('toArray with invalid format should return the raw value', () => { 47 | expect(paramsFmt.toArray()(['20'])).toEqual(['20']) 48 | }) 49 | 50 | it('toArray with params not an array should return raw value', () => { 51 | expect(paramsFmt.toArray(paramsFmt.toNumber)(BigInt(20))).toBe(BigInt(20)) 52 | }) 53 | 54 | it('toArray should throw errors which are thrown from other format', () => { 55 | expect(() => paramsFmt.toArray(paramsFmt.toNumber)(['20'])).toThrow('Hex string 20 should start with 0x') 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/multisig/index.test.js: -------------------------------------------------------------------------------- 1 | const { serializeMultisigConfig, hashMultisig, getMultisigStatus, isMultisigConfig } = require('../../lib/multisig') 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('test serializeMultisigConfig', () => { 5 | const serializeMultisigConfigTable = Object.entries(fixtures.serializeMultisigConfig).map( 6 | ([title, { config, expected, exception }]) => [ 7 | title, 8 | config, 9 | exception, 10 | expected 11 | ], 12 | ) 13 | test.each(serializeMultisigConfigTable)( 14 | '%s', 15 | (_title, config, exception, expected) => { 16 | if (exception !== undefined) { 17 | expect(() =>serializeMultisigConfig(config)).toThrowError(exception) 18 | } else { 19 | const result = serializeMultisigConfig(config) 20 | expect(result).toEqual(expected) 21 | } 22 | }, 23 | ) 24 | }) 25 | describe('test hashMultisig', () => { 26 | const hashMultisigTable = Object.entries(fixtures.hashMultisig).map( 27 | ([title, { config, expected, exception }]) => [ 28 | title, 29 | config, 30 | exception, 31 | expected 32 | ], 33 | ) 34 | 35 | test.each(hashMultisigTable)( 36 | '%s', 37 | (_title, config, exception, expected) => { 38 | if (exception !== undefined) { 39 | expect(() => hashMultisig(config)).toThrowError(exception) 40 | } else { 41 | const result = hashMultisig(config) 42 | expect(result).toEqual(expected) 43 | } 44 | }, 45 | ) 46 | }) 47 | describe('test getMultisigStatus', () => { 48 | const table = Object.entries(fixtures.getMultisigStatus).map( 49 | ([title, { config, signatures, expected, exception }]) => [ 50 | title, 51 | config, 52 | signatures, 53 | exception, 54 | expected 55 | ], 56 | ) 57 | 58 | test.each(table)( 59 | '%s', 60 | (_title, config, signatures, exception, expected) => { 61 | if (exception !== undefined) { 62 | expect(() =>getMultisigStatus(config, signatures)).toThrowError(exception) 63 | } else { 64 | const result = getMultisigStatus(config, signatures) 65 | expect(result).toEqual(expected) 66 | } 67 | }, 68 | ) 69 | }) 70 | describe('test isMultisigConfig', () => { 71 | const table = Object.entries(fixtures.isMultisigConfig).map( 72 | ([title, { config, expected }]) => [ 73 | title, 74 | config, 75 | expected 76 | ], 77 | ) 78 | 79 | test.each(table)( 80 | '%s', 81 | (_title, config, expected) => { 82 | expect(isMultisigConfig(config)).toEqual(expected) 83 | }, 84 | ) 85 | }) -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/serialization/script/index.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | serializeArgs, 3 | serializeCodeHash, 4 | serializeHashType, 5 | serializeScript, 6 | } = require('../../../lib/serialization/script') 7 | const fixtures = require('./fixtures.json') 8 | 9 | describe('Test Script Serialization', () => { 10 | describe('Serialize Args', () => { 11 | const fixtureTable = Object.entries(fixtures.serializeArgs).map(([title, { args, expected, exception }]) => [ 12 | title, 13 | args, 14 | expected, 15 | exception, 16 | ]) 17 | test.each(fixtureTable)('%s', (_title, args, expected, exception) => { 18 | if (undefined !== expected) { 19 | expect(serializeArgs(args)).toBe(expected) 20 | } 21 | if (undefined !== exception) { 22 | expect(() => serializeArgs(args)).toThrow(new Error(exception)) 23 | } 24 | }) 25 | }) 26 | 27 | describe('Serialize CodeHash', () => { 28 | const fixtureTable = Object.entries(fixtures.serializeCodeHash).map( 29 | ([title, { codeHash, expected, exception }]) => [title, codeHash, expected, exception] 30 | ) 31 | test.each(fixtureTable)('%s', (_title, codeHash, expected, exception) => { 32 | if (undefined !== expected) { 33 | expect(serializeCodeHash(codeHash)).toBe(expected) 34 | } 35 | if (undefined !== exception) { 36 | expect(() => serializeCodeHash(codeHash)).toThrow(new Error(exception)) 37 | } 38 | }) 39 | }) 40 | 41 | describe('Serialize HashType', () => { 42 | const fixtureTable = Object.entries(fixtures.serializeHashType).map( 43 | ([title, { hashType, expected, exception }]) => [title, hashType, expected, exception] 44 | ) 45 | test.each(fixtureTable)('%s', (_title, hashType, expected, exception) => { 46 | if (undefined !== expected) { 47 | expect(serializeHashType(hashType)).toBe(expected) 48 | } 49 | if (undefined !== exception) { 50 | expect(() => serializeHashType(hashType)).toThrow(new Error(exception)) 51 | } 52 | }) 53 | }) 54 | 55 | describe('Serialize Script', () => { 56 | const fixtureTable = Object.entries(fixtures.serializeScript).map(([title, { script, expected, exception }]) => [ 57 | title, 58 | script, 59 | expected, 60 | exception, 61 | ]) 62 | test.each(fixtureTable)('%s', (_title, script, expected, exception) => { 63 | if (undefined !== expected) { 64 | expect(serializeScript(script)).toBe(expected) 65 | } 66 | if (undefined !== exception) { 67 | expect(() => serializeScript(script)).toThrow(new Error(exception)) 68 | } 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/serialization/script/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "serializeArgs": { 3 | "empty args": { 4 | "args": "", 5 | "expected": "0x00000000" 6 | }, 7 | "basic args": { 8 | "args": "0x8536c9d5d908bd89fc70099e4284870708b6632356aad98734fcf43f6f71c304", 9 | "expected": "0x200000008536c9d5d908bd89fc70099e4284870708b6632356aad98734fcf43f6f71c304" 10 | }, 11 | "args without 0x": { 12 | "args": "8536c9d5d908bd89fc70099e4284870708b6632356aad98734fcf43f6f71c304", 13 | "exception": "Hex string 8536c9d5d908bd89fc70099e4284870708b6632356aad98734fcf43f6f71c304 should start with 0x" 14 | } 15 | }, 16 | "serializeCodeHash": { 17 | "basic code hash": { 18 | "codeHash": "0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88", 19 | "expected": "0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88" 20 | } 21 | }, 22 | "serializeHashType": { 23 | "type of data": { 24 | "hashType": "data", 25 | "expected": "0x00" 26 | }, 27 | "type of type": { 28 | "hashType": "type", 29 | "expected": "0x01" 30 | }, 31 | "neither data nor type should throw an error": { 32 | "hashType": "unknown", 33 | "exception": "Hash type must be either of 'data' or 'type'" 34 | } 35 | }, 36 | "serializeScript": { 37 | "basic script": { 38 | "script": { 39 | "codeHash": "0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88", 40 | "args": "0x3954acece65096bfa81258983ddb83915fc56bd8", 41 | "hashType": "type" 42 | }, 43 | "expected": "0x4900000010000000300000003100000068d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e8801140000003954acece65096bfa81258983ddb83915fc56bd8" 44 | }, 45 | "serialize ckb2023 script and the data is from lumos": { 46 | "script": { 47 | "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 48 | "args": "0xaabbccdd44332211", 49 | "hashType": "data2" 50 | }, 51 | "expected": "0x3d0000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80408000000aabbccdd44332211" 52 | }, 53 | "default args should be an empty arary": { 54 | "script": { 55 | "codeHash": "0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88", 56 | "hashType": "type" 57 | }, 58 | "expected": "0x3500000010000000300000003100000068d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e880100000000" 59 | }, 60 | "undefined script should throw an error": { 61 | "script": null, 62 | "exception": "Script is required" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/signWitnessGroup/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "empty witnesses": { 3 | "privateKey": "0xdcec27d0d975b0378471183a03f7071dea8532aaf968be796719ecd20af6988f", 4 | "transactionHash": "0x4a4bcfef1b7448e27edf533df2f1de9f56be05eba645fb83f42d55816797ad2a", 5 | "witnesses": [], 6 | "exception": "WitnessGroup cannot be empty" 7 | }, 8 | "first one is witness args": { 9 | "privateKey": "0xdcec27d0d975b0378471183a03f7071dea8532aaf968be796719ecd20af6988f", 10 | "transactionHash": "0x4a4bcfef1b7448e27edf533df2f1de9f56be05eba645fb83f42d55816797ad2a", 11 | "witnesses": [ 12 | "0x", 13 | { 14 | "lock": "", 15 | "inputType": "", 16 | "outputType": "" 17 | } 18 | ], 19 | "exception": "The first witness in the group should be type of WitnessArgs" 20 | }, 21 | "Should pass": { 22 | "privateKey": "0xdcec27d0d975b0378471183a03f7071dea8532aaf968be796719ecd20af6988f", 23 | "transactionHash": "0x4a4bcfef1b7448e27edf533df2f1de9f56be05eba645fb83f42d55816797ad2a", 24 | "witnesses": [ 25 | { 26 | "lock": "", 27 | "inputType": "", 28 | "outputType": "" 29 | }, 30 | { 31 | "lock": "", 32 | "inputType": "", 33 | "outputType": "" 34 | } 35 | ], 36 | "expected": [ 37 | "0x55000000100000005500000055000000410000005bc073bf55db333d5680ddf36e4814b9ce2118cfe4504f95c7d3e9a7548e16886cf1a1481fd80ce70d5e19108a43fd17fa32aad0d46c30c3410001ed2934ad2f00", 38 | { 39 | "lock": "", 40 | "inputType": "", 41 | "outputType": "" 42 | } 43 | ] 44 | }, 45 | "sign with multisig config": { 46 | "privateKey": "0xdcec27d0d975b0378471183a03f7071dea8532aaf968be796719ecd20af6988f", 47 | "transactionHash": "0x4a4bcfef1b7448e27edf533df2f1de9f56be05eba645fb83f42d55816797ad2a", 48 | "witnesses": [ 49 | { 50 | "lock": "", 51 | "inputType": "", 52 | "outputType": "" 53 | }, 54 | { 55 | "lock": "", 56 | "inputType": "", 57 | "outputType": "" 58 | } 59 | ], 60 | "multisigConfig": { 61 | "r": 0, 62 | "m": 2, 63 | "n": 3, 64 | "blake160s": ["0x7c021957a27000e794f25828270f187c791443e3", "0xd93b3564ef1b2dcf7bca781f968b3c7d2db85fd1", "0xb7672fcde903607f6bb150a730085c2a43c422fa"] 65 | }, 66 | "expected": [ 67 | { 68 | "lock": "0x52e62abe0aa34889eef6a27f29e78a500e6534e91c3ddbeaab0ea4f5a4a53a12628773309a9b18d047f318d3647315626bfab7c64ca95a21a1e1fe32a8eec6a201", 69 | "inputType": "", 70 | "outputType": "" 71 | }, 72 | { 73 | "lock": "", 74 | "inputType": "", 75 | "outputType": "" 76 | } 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/epochs/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "serializeEpoch": { 3 | "should pass": { 4 | "params": [ 5 | { 6 | "length": "0x3e8", 7 | "index": "0x10", 8 | "number": "0x200" 9 | } 10 | ], 11 | "expected": "0x2003e80010000200" 12 | }, 13 | "should throw an error when length is invalid": { 14 | "params": [ 15 | { 16 | "length": "3e8", 17 | "index": "0x10", 18 | "number": "0x200" 19 | } 20 | ], 21 | "exception": "3e8 is an invalid hex string" 22 | }, 23 | "should throw an error when index is invalid": { 24 | "params": [ 25 | { 26 | "length": "0x3e8", 27 | "index": "10", 28 | "number": "0x200" 29 | } 30 | ], 31 | "exception": "10 is an invalid hex string" 32 | }, 33 | "should throw an error when number is invalid": { 34 | "params": [ 35 | { 36 | "length": "0x3e8", 37 | "index": "0x10", 38 | "number": "200" 39 | } 40 | ], 41 | "exception": "200 is an invalid hex string" 42 | } 43 | }, 44 | "parseEpoch": { 45 | "should pass": { 46 | "params": ["0x1e00017000090"], 47 | "expected": { 48 | "length": "0x1e0", 49 | "index": "0x17", 50 | "number": "0x90" 51 | } 52 | } 53 | }, 54 | "getWithdrawEpoch": { 55 | "should be 180 when deposit at 0 and withdrawing at 0": { 56 | "params": ["0x2000640000000000", "0x2000640000000000"], 57 | "expected": "0x20006400000000b4" 58 | }, 59 | "should be 180 when deposit at 0 and withdrawing at 179.99": { 60 | "params": ["0x2000640000000000", "0x20006400630000b3"], 61 | "expected": "0x20006400000000b4" 62 | }, 63 | "should be 180 when deposit at 0 and withdrawing at 180": { 64 | "params": ["0x2000640000000000", "0x20006400000000b4"], 65 | "expected": "0x20006400000000b4" 66 | }, 67 | "should be 360 when deposit at 0 and withdrawing at 180.01": { 68 | "params": ["0x2000640000000000", "0x20006400010000b4"], 69 | "expected": "0x2000640000000168" 70 | }, 71 | "should be 181 when deposit at 1 and withdrawing at 1": { 72 | "params": ["0x2000640000000001", "0x2000640000000001"], 73 | "expected": "0x20006400000000b5" 74 | }, 75 | "should be 180.01 when deposit at 0.01 and withdrawing at 179": { 76 | "params": ["0x2000640001000000", "0x20006400000000b3"], 77 | "expected": "0x20006400010000b4" 78 | }, 79 | "should be 181.35 when deposit at 1.35 and withdrawing at 4.45": { 80 | "params": ["0x200708027a000001", "0x2007080334000004"], 81 | "expected": "0x200708027a0000b5" 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/src/multisig.ts: -------------------------------------------------------------------------------- 1 | import { blake2b, PERSONAL, hexToBytes } from '@nervosnetwork/ckb-sdk-utils' 2 | 3 | export type MultisigConfig = { 4 | r: number 5 | m: number 6 | n: number 7 | blake160s: string[] 8 | } 9 | 10 | export function isMultisigConfig(config: any): config is MultisigConfig { 11 | return config 12 | && !Number.isNaN(+config.r) 13 | && !Number.isNaN(+config.m) 14 | && !Number.isNaN(+config.n) 15 | && Array.isArray(config.blake160s) 16 | } 17 | 18 | export type Signatures = Record 19 | 20 | export enum SignStatus { 21 | Signed = 'Signed', 22 | Unsigned = 'Unsigned', 23 | PartiallySigned = 'PartiallySigned' 24 | } 25 | 26 | const validateMultisigCount = (v: number) => { 27 | if (v < 0 || v > 255) { 28 | throw new Error('For multisig sign, signer should between 0 and 255') 29 | } 30 | } 31 | 32 | const toHex = (v: number) => { 33 | return v.toString(16).padStart(2, '0') 34 | } 35 | 36 | const validateMultisigConfig = (config: MultisigConfig) => { 37 | validateMultisigCount(config.r) 38 | validateMultisigCount(config.m) 39 | validateMultisigCount(config.n) 40 | if (config.m > config.n) throw new Error(`For m of n multisig sign, m shouldn't be greater than n`) 41 | if (config.r > config.m) throw new Error(`For m of n multisig sign, r shouldn't be greater than m`) 42 | if (config.n !== config.blake160s.length) throw new Error(`For m of n multisig sign, signer's length should equal with n`) 43 | } 44 | 45 | export const serializeMultisigConfig = (config: MultisigConfig) => { 46 | validateMultisigConfig(config) 47 | // default s is 00 48 | return `0x00${toHex(config.r)}${toHex(config.m)}${toHex(config.n)}${config.blake160s.reduce((pre, cur) => pre + cur.slice(2), '')}` 49 | } 50 | 51 | export const hashMultisig = (config: MultisigConfig) => { 52 | const blake2bHash = blake2b(32, null, null, PERSONAL) 53 | blake2bHash.update(hexToBytes(serializeMultisigConfig(config))) 54 | return `0x${blake2bHash.digest('hex')}`.slice(0, 42) 55 | } 56 | 57 | export const getMultisigStatus = (config: MultisigConfig, signatures: CKBComponents.Bytes[] = []) => { 58 | let signedForM = 0 59 | let signedForR = 0 60 | for (let i = 0; i < config.n; i++) { 61 | if (signatures.includes(config.blake160s[i])) { 62 | if (i < config.r) { 63 | signedForR += 1 64 | } else { 65 | signedForM += 1 66 | } 67 | } 68 | } 69 | if (signedForM + signedForR === 0) { 70 | return SignStatus.Unsigned 71 | } 72 | if (signedForM > config.m - config.r) { 73 | throw new Error('More signature for multisig') 74 | } 75 | if (signedForM + signedForR < config.m) { 76 | return SignStatus.PartiallySigned 77 | } 78 | return SignStatus.Signed 79 | } 80 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/Base/chain.ts: -------------------------------------------------------------------------------- 1 | import paramsFmts from '../paramsFormatter.js' 2 | import resultFmts from '../resultFormatter.js' 3 | 4 | export default { 5 | getTipBlockNumber: { 6 | method: 'get_tip_block_number', 7 | paramsFormatters: [], 8 | resultFormatters: resultFmts.toNumber, 9 | }, 10 | 11 | getTipHeader: { 12 | method: 'get_tip_header', 13 | paramsFormatters: [], 14 | resultFormatters: resultFmts.toHeader, 15 | }, 16 | 17 | getCurrentEpoch: { 18 | method: 'get_current_epoch', 19 | paramsFormatters: [], 20 | resultFormatters: resultFmts.toEpoch, 21 | }, 22 | 23 | getEpochByNumber: { 24 | method: 'get_epoch_by_number', 25 | paramsFormatters: [paramsFmts.toNumber], 26 | resultFormatters: resultFmts.toEpoch, 27 | }, 28 | 29 | getBlockHash: { 30 | method: 'get_block_hash', 31 | paramsFormatters: [paramsFmts.toNumber], 32 | }, 33 | 34 | getBlock: { 35 | method: 'get_block', 36 | paramsFormatters: [paramsFmts.toHash], 37 | resultFormatters: resultFmts.toBlock, 38 | }, 39 | 40 | getBlockByNumber: { 41 | method: 'get_block_by_number', 42 | paramsFormatters: [paramsFmts.toNumber], 43 | resultFormatters: resultFmts.toBlock, 44 | }, 45 | 46 | getHeader: { 47 | method: 'get_header', 48 | paramsFormatters: [paramsFmts.toHash], 49 | resultFormatters: resultFmts.toHeader, 50 | }, 51 | 52 | getHeaderByNumber: { 53 | method: 'get_header_by_number', 54 | paramsFormatters: [paramsFmts.toNumber], 55 | resultFormatters: resultFmts.toHeader, 56 | }, 57 | 58 | getLiveCell: { 59 | method: 'get_live_cell', 60 | paramsFormatters: [paramsFmts.toOutPoint], 61 | resultFormatters: resultFmts.toLiveCellWithStatus, 62 | }, 63 | 64 | getTransaction: { 65 | method: 'get_transaction', 66 | paramsFormatters: [paramsFmts.toHash], 67 | resultFormatters: resultFmts.toTransactionWithStatus, 68 | }, 69 | 70 | getCellbaseOutputCapacityDetails: { 71 | method: 'get_cellbase_output_capacity_details', 72 | paramsFormatters: [paramsFmts.toHash], 73 | resultFormatters: resultFmts.toCellbaseOutputCapacityDetails, 74 | }, 75 | 76 | getBlockEconomicState: { 77 | method: 'get_block_economic_state', 78 | paramsFormatters: [paramsFmts.toHash], 79 | resultFormatters: resultFmts.toBlockEconomicState, 80 | }, 81 | 82 | getTransactionProof: { 83 | method: 'get_transaction_proof', 84 | paramsFormatters: [paramsFmts.toArray(paramsFmts.toHash), paramsFmts.toOptional(paramsFmts.toHash)], 85 | resultFormatters: resultFmts.toTransactionProof, 86 | }, 87 | 88 | verifyTransactionProof: { 89 | method: 'verify_transaction_proof', 90 | paramsFormatters: [paramsFmts.toTransactionProof], 91 | }, 92 | 93 | getConsensus: { 94 | method: 'get_consensus', 95 | paramsFormatters: [], 96 | resultFormatters: resultFmts.toConsensus, 97 | }, 98 | } 99 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/systemScripts/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "SECP256K1_BLAKE160": { 3 | "mainnet": { 4 | "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 5 | "hashType": "type", 6 | "txHash": "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c", 7 | "index": "0x0", 8 | "depType": "depGroup" 9 | }, 10 | "testnet": { 11 | "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 12 | "hashType": "type", 13 | "txHash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", 14 | "index": "0x0", 15 | "depType": "depGroup" 16 | } 17 | }, 18 | "SECP256K1_MULTISIG": { 19 | "mainnet": { 20 | "codeHash": "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8", 21 | "hashType": "type", 22 | "txHash": "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c", 23 | "index": "0x1", 24 | "depType": "depGroup" 25 | }, 26 | "testnet": { 27 | "codeHash": "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8", 28 | "hashType": "type", 29 | "txHash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", 30 | "index": "0x1", 31 | "depType": "depGroup" 32 | } 33 | }, 34 | "ANYONE_CAN_PAY": { 35 | "mainnet": { 36 | "codeHash": "0xd369597ff47f29fbc0d47d2e3775370d1250b85140c670e4718af712983a2354", 37 | "hashType": "type", 38 | "txHash": "0x4153a2014952d7cac45f285ce9a7c5c0c0e1b21f2d378b82ac1433cb11c25c4d", 39 | "index": "0x0", 40 | "depType": "depGroup" 41 | }, 42 | "testnet": { 43 | "codeHash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", 44 | "hashType": "type", 45 | "txHash": "0xec26b0f85ed839ece5f11c4c4e837ec359f5adc4420410f6453b1f6b60fb96a6", 46 | "index": "0x0", 47 | "depType": "depGroup" 48 | } 49 | }, 50 | "NERVOS_DAO": { 51 | "mainnet": { 52 | "codeHash": "0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e", 53 | "hashType": "type", 54 | "txHash": "0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c", 55 | "index": "0x2", 56 | "depType": "code" 57 | }, 58 | "testnet": { 59 | "codeHash": "0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e", 60 | "hashType": "type", 61 | "txHash": "0x8f8c79eb6671709633fe6a46de93c0fedc9c1b8a6527a18d3983879542635c9f", 62 | "index": "0x2", 63 | "depType": "code" 64 | } 65 | }, 66 | "SIMPLE_UDT": { 67 | "testnet": { 68 | "codeHash": "0x48dbf59b4c7ee1547238021b4869bceedf4eea6b43772e5d66ef8865b6ae7212", 69 | "hashType": "data", 70 | "txHash": "0xc1b2ae129fad7465aaa9acc9785f842ba3e6e8b8051d899defa89f5508a77958", 71 | "index": "0x0", 72 | "depType": "code" 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/reconcilers/extraInputs.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi' 2 | import calculateTransactionFee from '../calculateTransactionFee.js' 3 | import { getTransactionSize } from '../sizes.js' 4 | import { ReconciliationException } from '../exceptions/index.js' 5 | 6 | declare namespace Reconciliation { 7 | type Cell = { capacity: string; outPoint: CKBComponents.OutPoint } 8 | interface ExtraInputsParams { 9 | tx: CKBComponents.RawTransactionToSign 10 | feeRate: string | bigint 11 | changeThreshold: string | bigint 12 | cells: Array 13 | extraCount: number 14 | } 15 | } 16 | 17 | export const extraInputs = (params: Reconciliation.ExtraInputsParams): CKBComponents.RawTransactionToSign => { 18 | const changeThreshold = JSBI.BigInt(`${params.changeThreshold}`) 19 | 20 | const feeRate = JSBI.BigInt(`${params.feeRate}`) 21 | const currentChangeOutput = params.tx.outputs[params.tx.outputs.length - 1] 22 | const currentChange = JSBI.BigInt(currentChangeOutput.capacity) 23 | 24 | const fee = JSBI.BigInt( 25 | calculateTransactionFee(`0x${getTransactionSize(params.tx).toString(16)}`, `0x${feeRate.toString(16)}`), 26 | ) 27 | const lack = JSBI.subtract(JSBI.add(fee, changeThreshold), currentChange) 28 | 29 | if (JSBI.LE(lack, JSBI.BigInt(0))) { 30 | return { 31 | ...params.tx, 32 | outputs: [ 33 | ...params.tx.outputs.slice(0, -1), 34 | { 35 | ...currentChangeOutput, 36 | capacity: `0x${JSBI.subtract(currentChange, fee).toString(16)}`, 37 | }, 38 | ], 39 | } 40 | } 41 | 42 | params.cells.sort((c1, c2) => +JSBI.subtract(JSBI.BigInt(c1.capacity), JSBI.BigInt(c2.capacity))) 43 | 44 | const SIZE_PER_INPUT = JSBI.BigInt(44) 45 | const FEE_PER_INPUT = JSBI.divide(JSBI.multiply(SIZE_PER_INPUT, feeRate), JSBI.BigInt(1000)) 46 | 47 | for (let i = 1; i <= Math.min(params.extraCount, params.cells.length); i++) { 48 | const extraCost = JSBI.multiply(JSBI.BigInt(i), FEE_PER_INPUT) 49 | const totalLack = JSBI.add(lack, extraCost) 50 | const extraCapacity = params.cells 51 | .slice(0, i) 52 | .reduce((sum, c) => JSBI.add(sum, JSBI.BigInt(c.capacity)), JSBI.BigInt(0)) 53 | if (JSBI.GE(extraCapacity, totalLack)) { 54 | const inputs = [ 55 | ...params.tx.inputs, 56 | ...params.cells.slice(0, i).map(c => ({ 57 | previousOutput: c.outPoint, 58 | since: '0x0', 59 | })), 60 | ] 61 | const change = JSBI.add(changeThreshold, JSBI.subtract(extraCapacity, totalLack)) 62 | const changeOutput = { ...currentChangeOutput, capacity: `0x${change.toString(16)}` } 63 | const outputs = [...params.tx.outputs.slice(0, -1), changeOutput] 64 | const tx: CKBComponents.RawTransactionToSign = { ...params.tx, inputs, outputs } 65 | return tx 66 | } 67 | } 68 | throw new ReconciliationException() 69 | } 70 | 71 | export default extraInputs 72 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/examples/sendTransactionWithMultiplePrivateKey.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path') 3 | const os = require('os') 4 | const { Indexer, CellCollector } = require('@ckb-lumos/indexer') 5 | const CKB = require('../lib').default 6 | 7 | const CKB_URL = process.env.CKB_URL || 'http://localhost:8114' 8 | const LUMOS_DB = path.join(os.tmpdir(), 'lumos_db') 9 | /** 10 | * lumos indexer 11 | */ 12 | const indexer = new Indexer(CKB_URL, LUMOS_DB) 13 | indexer.startForever() 14 | 15 | /** 16 | * sdk 17 | */ 18 | const ckb = new CKB(CKB_URL) 19 | 20 | const sk1 = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' // exmaple private key 21 | const sk2 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' // example private key 22 | 23 | const loadCells = async () => { 24 | await ckb.loadDeps() 25 | const lockScript1 = { 26 | codeHash: ckb.config.secp256k1Dep.codeHash, 27 | hashType: ckb.config.secp256k1Dep.hashType, 28 | args: `0x${ckb.utils.blake160(ckb.utils.privateKeyToPublicKey(sk1), 'hex')}` 29 | } 30 | 31 | const lockScript2 = { 32 | codeHash: ckb.config.secp256k1Dep.codeHash, 33 | hashType: ckb.config.secp256k1Dep.hashType, 34 | args: `0x${ckb.utils.blake160(ckb.utils.privateKeyToPublicKey(sk2), 'hex')}` 35 | } 36 | 37 | const lockHash1 = ckb.utils.scriptToHash(lockScript1) 38 | const lockHash2 = ckb.utils.scriptToHash(lockScript2) 39 | 40 | /** 41 | * load cells from lumos as `examples/sendTransactionWithLumosCollector.js` shows 42 | */ 43 | await ckb.loadCells({ indexer, CellCollector, lock: lockScript1, save: true }) 44 | await ckb.loadCells({ indexer, CellCollector, lock: lockScript2, save: true }) 45 | 46 | const cell1 = ckb.cells.get(lockHash1) 47 | const cell2 = ckb.cells.get(lockHash2) 48 | } 49 | 50 | const generateTransaction = async () => { 51 | await loadCells() 52 | 53 | const addr1 = ckb.utils.privateKeyToAddress(sk1) 54 | const addr2 = ckb.utils.privateKeyToAddress(sk2) 55 | 56 | await ckb.loadDeps() 57 | 58 | const rawTransaction = ckb.generateRawTransaction({ 59 | fromAddresses: [addr1, addr2], 60 | receivePairs: [{ 61 | address: addr2, 62 | capacity: BigInt(30621362931463) 63 | }], 64 | fee: BigInt(10000), 65 | deps: ckb.config.secp256k1Dep, 66 | }) 67 | 68 | const keys = new Map([sk1, sk2].map(sk => ([ 69 | ckb.generateLockHash(`0x${ckb.utils.blake160(ckb.utils.privateKeyToPublicKey(sk), 'hex')}`), sk 70 | ]))) 71 | const cells = [...ckb.cells.values()].flat() 72 | const signedTransaction = ckb.signTransaction(keys)(rawTransaction, cells) 73 | return signedTransaction 74 | } 75 | 76 | const sendTransaction = async () => { 77 | const signedTx = await generateTransaction() 78 | const txHash = await ckb.rpc.sendTransaction(signedTx) 79 | console.log(`tx hash: ${txHash}`) 80 | } 81 | 82 | // loadCells() 83 | // generateTransaction() 84 | sendTransaction() 85 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/examples/sendAllBalance.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const CKB = require("../lib").default 3 | const nodeUrl = process.env.NODE_URL || "http://localhost:8114" // example node url 4 | const ckb = new CKB(nodeUrl) 5 | 6 | const privateKey = 7 | process.env.PRIV_KEY || 8 | "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" // example private key 9 | 10 | const address = ckb.utils.privateKeyToAddress(privateKey) 11 | 12 | const unspentCells = [ 13 | { 14 | lock: { 15 | codeHash: 16 | "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 17 | hashType: "type", 18 | args: "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 19 | }, 20 | outPoint: { 21 | txHash: 22 | "0xdf45112919d4af12b10fed94a93798ae6ddd8c89a11d90980022dcf695babca9", 23 | index: "0x0", 24 | }, 25 | capacity: "0x2540be400", 26 | 27 | data: "0x", 28 | }, 29 | { 30 | lock: { 31 | codeHash: 32 | "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 33 | hashType: "type", 34 | args: "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 35 | }, 36 | outPoint: { 37 | txHash: 38 | "0xdf45112919d4af12b10fed94a93798ae6ddd8c89a11d90980022dcf695babca9", 39 | index: "0x1", 40 | }, 41 | capacity: "0x2540be400", 42 | data: "0x", 43 | }, 44 | ] 45 | 46 | const generateRawTransaction = async () => { 47 | await ckb.loadDeps() 48 | const rawTx = ckb.generateRawTransaction({ 49 | fromAddress: address, 50 | toAddress: address, 51 | capacity: BigInt(19999999900), 52 | fee: BigInt(100), 53 | safeMode: true, 54 | cells: unspentCells, 55 | deps: ckb.config.secp256k1Dep, 56 | changeThreshold: BigInt(0), 57 | }) 58 | console.group("generate raw tx") 59 | // console.info(`raw transaction: ${JSON.stringify(rawTx, null, 2)}`) 60 | console.info( 61 | `inputs capacity: 62 | ${rawTx.inputs 63 | .map( 64 | input => 65 | unspentCells.find( 66 | cell => 67 | cell.outPoint.txHash === input.previousOutput.txHash && 68 | cell.outPoint.index === input.previousOutput.index, 69 | ).capacity, 70 | ) 71 | .map(capacity => BigInt(capacity))} 72 | `, 73 | ) 74 | console.info( 75 | `outputs capacity: ${rawTx.outputs.map(output => BigInt(output.capacity))}`, 76 | ) 77 | console.groupEnd() 78 | return rawTx 79 | } 80 | 81 | const sendAllBalance = async () => { 82 | const rawTx = await generateRawTransaction() 83 | const signedTx = ckb.signTransaction(privateKey)(rawTx) 84 | // console.group('sign and send tx') 85 | // console.info(`signed tx: ${JSON.stringify(signedTx, null, 2)}`) 86 | // const realTxHash = await ckb.rpc.sendTransaction(signedTx) 87 | // console.info(`real tx hash: ${realTxHash}`) 88 | // return realTxHash 89 | // console.groupEnd() 90 | } 91 | 92 | sendAllBalance() 93 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/ecpair.ts: -------------------------------------------------------------------------------- 1 | import elliptic from 'elliptic' 2 | 3 | import { hexToBytes } from './convertors/index.js' 4 | import { 5 | HexStringWithout0xException, 6 | ParameterRequiredException, 7 | PrivateKeyLenException, 8 | SignMessageException, 9 | } from './exceptions/index.js' 10 | 11 | const ec = new elliptic.ec('secp256k1') 12 | 13 | export interface Options { 14 | compressed?: boolean 15 | } 16 | 17 | class ECPair { 18 | protected key: elliptic.ec.KeyPair 19 | 20 | public compressed: boolean = false 21 | 22 | constructor( 23 | sk: Uint8Array | string, 24 | { compressed = true }: Options = { 25 | compressed: true, 26 | }, 27 | ) { 28 | if (sk === undefined) throw new ParameterRequiredException('Private key') 29 | 30 | if (typeof sk === 'string' && !sk.startsWith('0x')) { 31 | throw new HexStringWithout0xException(sk) 32 | } 33 | 34 | if (typeof sk === 'string' && sk.length !== 66) { 35 | throw new PrivateKeyLenException() 36 | } 37 | 38 | if (typeof sk === 'object' && sk.byteLength !== 32) { 39 | throw new PrivateKeyLenException() 40 | } 41 | 42 | this.key = ec.keyFromPrivate(typeof sk === 'string' ? sk.replace(/^0x/, '') : sk) 43 | this.compressed = compressed 44 | } 45 | 46 | get privateKey() { 47 | return `0x${this.key.getPrivate('hex').padStart(64, '0')}` 48 | } 49 | 50 | get publicKey() { 51 | return `0x${this.key.getPublic(this.compressed, 'hex') as string}` 52 | } 53 | 54 | public getPrivateKey = (enc: 'hex' = 'hex') => { 55 | if (enc === 'hex') { 56 | return this.privateKey 57 | } 58 | return this.key.getPrivate(enc) 59 | } 60 | 61 | public getPublicKey = (enc: 'hex' | 'array') => { 62 | if (enc === 'hex') { 63 | return this.publicKey 64 | } 65 | return this.key.getPublic(this.compressed, enc) 66 | } 67 | 68 | public sign = (message: string | Uint8Array): string => { 69 | const msg = typeof message === 'string' ? hexToBytes(message) : message 70 | return `0x${this.key 71 | .sign(msg, { 72 | canonical: true, 73 | }) 74 | .toDER('hex')}` 75 | } 76 | 77 | public verify = (message: string | Buffer, sig: string | Buffer) => { 78 | const msg = typeof message === 'string' ? hexToBytes(message) : message 79 | const signature = typeof sig === 'string' ? hexToBytes(sig) : sig 80 | return this.key.verify(msg, signature as any) 81 | } 82 | 83 | public signRecoverable = (message: string | Uint8Array): string => { 84 | const msg = typeof message === 'string' ? hexToBytes(message) : message 85 | const { r, s, recoveryParam } = this.key.sign(msg, { 86 | canonical: true, 87 | }) 88 | if (recoveryParam === null) throw new SignMessageException() 89 | const fmtR = r.toString(16).padStart(64, '0') 90 | const fmtS = s.toString(16).padStart(64, '0') 91 | return `0x${fmtR}${fmtS}0${recoveryParam}` 92 | } 93 | } 94 | 95 | export default ECPair 96 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/convertors/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "uint16Le": [ 3 | { 4 | "value": "0xabcd", 5 | "expected": "0xcdab" 6 | }, 7 | { 8 | "value": "0xbcd", 9 | "expected": "0xcd0b" 10 | }, 11 | { 12 | "value": "0x00cd", 13 | "expected": "0xcd00" 14 | }, 15 | { 16 | "value": "0xab00", 17 | "expected": "0x00ab" 18 | }, 19 | { 20 | "value": "0x0bc0", 21 | "expected": "0xc00b" 22 | }, 23 | { 24 | "value": 6, 25 | "exception": "6 should be type of string or bigint" 26 | }, 27 | { 28 | "value": "ab", 29 | "exception": "Hex string ab should start with 0x" 30 | }, 31 | { 32 | "value": "hi", 33 | "exception": "Hex string hi should start with 0x" 34 | } 35 | ], 36 | "uint32Le": [ 37 | { 38 | "value": "0x12345678", 39 | "expected": "0x78563412" 40 | }, 41 | { 42 | "value": "0x12345608", 43 | "expected": "0x08563412" 44 | }, 45 | { 46 | "value": 6, 47 | "exception": "6 should be type of string or bigint" 48 | }, 49 | { 50 | "value": "ab", 51 | "exception": "Hex string ab should start with 0x" 52 | }, 53 | { 54 | "value": "hi", 55 | "exception": "Hex string hi should start with 0x" 56 | } 57 | ], 58 | "littleHexToInt": [ 59 | { 60 | "value": "0x120100", 61 | "expected": 274 62 | }, 63 | { 64 | "value": "12", 65 | "exception": "12 should be type of string or bigint" 66 | }, 67 | { 68 | "value": "ab", 69 | "exception": "Hex string ab should start with 0x" 70 | }, 71 | { 72 | "value": "hi", 73 | "exception": "Hex string hi should start with 0x" 74 | } 75 | ], 76 | "uint64Le": [ 77 | { 78 | "value": "0x1234567890abcdef", 79 | "expected": "0xefcdab9078563412" 80 | }, 81 | { 82 | "value": "0x3e8", 83 | "expected": "0xe803000000000000" 84 | }, 85 | { 86 | "value": 6, 87 | "exception": "6 should be type of string or bigint" 88 | }, 89 | { 90 | "value": "ab", 91 | "exception": "Hex string ab should start with 0x" 92 | }, 93 | { 94 | "value": "hi", 95 | "exception": "Hex string hi should start with 0x" 96 | } 97 | ], 98 | "hexToBytes": [ 99 | { 100 | "hex": "", 101 | "expected": [] 102 | }, 103 | { 104 | "hex": "0xabcd12", 105 | "expected": [ 106 | 171, 107 | 205, 108 | 18 109 | ] 110 | }, 111 | { 112 | "hex": 11259154, 113 | "expected": [ 114 | 171, 115 | 205, 116 | 18 117 | ] 118 | } 119 | ], 120 | "bytesToHex": [ 121 | { 122 | "bytes": [ 123 | 171, 124 | 205, 125 | 18 126 | ], 127 | "expected": "0xabcd12" 128 | } 129 | ] 130 | } 131 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/convertors/index.test.js: -------------------------------------------------------------------------------- 1 | const { toUint16Le, toUint32Le, toUint64Le, hexToBytes, bytesToHex, toBigEndian } = require('../../lib/convertors') 2 | const { HexStringWithout0xException } = require('../../lib/exceptions') 3 | 4 | const { 5 | uint16Le: uint16LeFixture, 6 | uint32Le: uint32LeFixture, 7 | uint64Le: uint64LeFixture, 8 | hexToBytes: hexToBytesFixture, 9 | bytesToHex: bytesToHexFixture, 10 | } = require('./fixtures.json') 11 | 12 | describe('Test toUint16Le', () => { 13 | const fixtureTable = uint16LeFixture.map(({ value, expected, exception }) => [value, expected, exception]) 14 | test.each(fixtureTable)(`%s => %s ? %s`, (value, expected, exception) => { 15 | if (exception) { 16 | expect(() => toUint16Le(value)).toThrow(exception) 17 | } else { 18 | const actualFromStr = toUint16Le(value) 19 | const actualFromBigInt = toUint16Le(BigInt(value)) 20 | expect(actualFromStr).toBe(expected) 21 | expect(actualFromBigInt).toBe(expected) 22 | } 23 | }) 24 | }) 25 | 26 | describe('Test toUint32Le', () => { 27 | const fixtureTable = uint32LeFixture.map(({ value, expected, exception }) => [value, expected, exception]) 28 | test.each(fixtureTable)(`%s => %s ? %s`, (value, expected, exception) => { 29 | if (exception) { 30 | expect(() => toUint32Le(value)).toThrow(exception) 31 | } else { 32 | const actualFromStr = toUint32Le(value) 33 | const actualFromBigInt = toUint32Le(BigInt(value)) 34 | expect(actualFromStr).toBe(expected) 35 | expect(actualFromBigInt).toBe(expected) 36 | } 37 | }) 38 | }) 39 | 40 | describe('Test toUint64Le', () => { 41 | const fixtureTable = uint64LeFixture.map(({ value, expected, exception }) => [value, expected, exception]) 42 | test.each(fixtureTable)(`%s => %s ? %s`, (value, expected, exception) => { 43 | if (exception) { 44 | expect(() => toUint64Le(value)).toThrow(exception) 45 | } else { 46 | const actualFromStr = toUint64Le(value) 47 | const actualFromBigInt = toUint64Le(BigInt(value)) 48 | expect(actualFromStr).toBe(expected) 49 | expect(actualFromBigInt).toBe(expected) 50 | } 51 | }) 52 | }) 53 | 54 | describe('hex to bytes', () => { 55 | const fixtureTable = hexToBytesFixture.map(({ hex, expected }) => [hex, expected]) 56 | test.each(fixtureTable)('%s => %j', (hex, exptected) => { 57 | expect(hexToBytes(hex).join(',')).toBe(exptected.join(',')) 58 | }) 59 | 60 | it('hex string without 0x should throw an error', () => { 61 | expect(() => hexToBytes('abcd12')).toThrow(new HexStringWithout0xException('abcd12')) 62 | }) 63 | }) 64 | 65 | describe('bytes to hex', () => { 66 | const fixtureTable = bytesToHexFixture.map(({ bytes, expected }) => [bytes, expected]) 67 | test.each(fixtureTable)('%j => %s', (bytes, expected) => { 68 | expect(bytesToHex(bytes)).toEqual(expected) 69 | }) 70 | }) 71 | 72 | describe('to big endian', () => { 73 | expect(toBigEndian('0x3ef9e8c069c92500')).toBe('0x0025c969c0e8f93e') 74 | }) -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/exceptions/blake2b.ts: -------------------------------------------------------------------------------- 1 | import ErrorCode from './ErrorCode.js' 2 | 3 | export class OutLenTooSmallException extends Error { 4 | code = ErrorCode.ParameterInvalid 5 | 6 | constructor(outlen: number, minLen: number) { 7 | super(`Expect outlen to be at least ${minLen}, but ${outlen} received`) 8 | } 9 | } 10 | 11 | export class OutLenTooLargeException extends Error { 12 | code = ErrorCode.ParameterInvalid 13 | 14 | constructor(outlen: number, maxLen: number) { 15 | super(`Expect outlen to be at most ${maxLen}, but ${outlen} received`) 16 | } 17 | } 18 | 19 | export class KeyLenTooSmallException extends Error { 20 | code = ErrorCode.ParameterInvalid 21 | 22 | constructor(keyLen: number, minLen: number) { 23 | super(`Expect key length to be at least ${minLen}, but ${keyLen} received`) 24 | } 25 | } 26 | 27 | export class KeyLenTooLargeException extends Error { 28 | code = ErrorCode.ParameterInvalid 29 | 30 | constructor(keyLen: number, maxLen: number) { 31 | super(`Expect key length to be at most ${maxLen}, but ${keyLen} received`) 32 | } 33 | } 34 | 35 | export class OutTypeException extends TypeError { 36 | code = ErrorCode.ParameterInvalid 37 | 38 | constructor() { 39 | super(`Expect out to be "binary", "hex", Uint8Array, or Buffer`) 40 | } 41 | } 42 | 43 | export class SaltTypeException extends TypeError { 44 | code = ErrorCode.ParameterInvalid 45 | 46 | constructor() { 47 | super(`Expect salt to be Uint8Array or Buffer`) 48 | } 49 | } 50 | 51 | export class SaltLenException extends Error { 52 | code = ErrorCode.ParameterInvalid 53 | 54 | constructor(saltLen: number, requiredLen: number) { 55 | super(`Expect salt length to be ${requiredLen}, but ${saltLen} received`) 56 | } 57 | } 58 | 59 | export class InputTypeException extends TypeError { 60 | code = ErrorCode.ParameterInvalid 61 | 62 | constructor() { 63 | super(`Expect input to be Uint8Array or Buffer`) 64 | } 65 | } 66 | 67 | export class KeyTypeException extends TypeError { 68 | code = ErrorCode.ParameterInvalid 69 | 70 | constructor() { 71 | super(`Expect key to be Uint8Array or Buffer`) 72 | } 73 | } 74 | 75 | export class PersonalTypeException extends TypeError { 76 | code = ErrorCode.ParameterInvalid 77 | 78 | constructor() { 79 | super(`Expect PERSONAL to be Uint8Array or Buffer`) 80 | } 81 | } 82 | 83 | export class PersonalLenException extends Error { 84 | code = ErrorCode.ParameterInvalid 85 | 86 | constructor(personalLen: number, requiredLen: number) { 87 | super(`Expect PERSONAL length to be ${requiredLen}, but ${personalLen} received`) 88 | } 89 | } 90 | 91 | export default { 92 | OutLenTooSmallException, 93 | OutLenTooLargeException, 94 | KeyLenTooSmallException, 95 | KeyLenTooLargeException, 96 | OutTypeException, 97 | SaltTypeException, 98 | SaltLenException, 99 | InputTypeException, 100 | KeyTypeException, 101 | PersonalTypeException, 102 | PersonalLenException, 103 | } 104 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/utils/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "filterCellsByInputs": [ 3 | { 4 | "params": { 5 | "cells": [], 6 | "inputs": [] 7 | }, 8 | "expected": [] 9 | }, 10 | { 11 | "params": { 12 | "cells": [ 13 | { 14 | "blockHash": "0x28011ef2c3b99a38b45809e52a3904666782bda571f2fc6b1c90eb829fc81363", 15 | "lock": { 16 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 17 | "hashType": "type", 18 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6" 19 | }, 20 | "outPoint": { 21 | "txHash": "0x8f67802f18390f0072b718cbde1805563d2d8b057383bf586b5ebf976d56ca5a", 22 | "index": "0x0" 23 | }, 24 | "capacity": "0x1fd59fe3ae" 25 | } 26 | ], 27 | "inputs": [] 28 | }, 29 | "expected": [] 30 | }, 31 | { 32 | "params": { 33 | "cells": [], 34 | "inputs": [ 35 | { 36 | "previousOutput": { 37 | "txHash": "0x8f67802f18390f0072b718cbde1805563d2d8b057383bf586b5ebf976d56ca5a", 38 | "index": "0x0" 39 | }, 40 | "since": "0x0" 41 | } 42 | ] 43 | }, 44 | "exception": "Cell of {\"txHash\":\"0x8f67802f18390f0072b718cbde1805563d2d8b057383bf586b5ebf976d56ca5a\",\"index\":\"0x0\"} is not found" 45 | }, 46 | { 47 | "params": { 48 | "cells": [ 49 | { 50 | "blockHash": "0x28011ef2c3b99a38b45809e52a3904666782bda571f2fc6b1c90eb829fc81363", 51 | "lock": { 52 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 53 | "hashType": "type", 54 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6" 55 | }, 56 | "outPoint": { 57 | "txHash": "0x8f67802f18390f0072b718cbde1805563d2d8b057383bf586b5ebf976d56ca5a", 58 | "index": "0x0" 59 | }, 60 | "capacity": "0x1fd59fe3ae" 61 | } 62 | ], 63 | "inputs": [ 64 | { 65 | "previousOutput": { 66 | "txHash": "0x8f67802f18390f0072b718cbde1805563d2d8b057383bf586b5ebf976d56ca5a", 67 | "index": "0x0" 68 | }, 69 | "since": "0x0" 70 | } 71 | ] 72 | }, 73 | "expected": [ 74 | { 75 | "blockHash": "0x28011ef2c3b99a38b45809e52a3904666782bda571f2fc6b1c90eb829fc81363", 76 | "lock": { 77 | "codeHash": "0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2", 78 | "hashType": "type", 79 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6" 80 | }, 81 | "outPoint": { 82 | "txHash": "0x8f67802f18390f0072b718cbde1805563d2d8b057383bf586b5ebf976d56ca5a", 83 | "index": "0x0" 84 | }, 85 | "capacity": "0x1fd59fe3ae" 86 | } 87 | ] 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/src/signWitnessGroup.ts: -------------------------------------------------------------------------------- 1 | import { blake2b, hexToBytes, PERSONAL, toUint64Le, serializeWitnessArgs, ECPair } from '@nervosnetwork/ckb-sdk-utils' 2 | import { serializeMultisigConfig, MultisigConfig } from './multisig.js' 3 | 4 | export type SignatureProvider = string | ((message: string | Uint8Array) => string) 5 | type TransactionHash = string 6 | 7 | function signWitnessGroup( 8 | sk: SignatureProvider, 9 | transactionHash: TransactionHash, 10 | witnessGroup: StructuredWitness[], 11 | multisigConfig?: MultisigConfig, 12 | ): StructuredWitness[] 13 | function signWitnessGroup( 14 | sk: (message: string | Uint8Array, witness: StructuredWitness[]) => Promise, 15 | transactionHash: TransactionHash, 16 | witnessGroup: StructuredWitness[], 17 | multisigConfig?: MultisigConfig, 18 | ): Promise 19 | 20 | function signWitnessGroup( 21 | sk: SignatureProvider | ((message: string | Uint8Array, witness: StructuredWitness[]) => Promise), 22 | transactionHash: TransactionHash, 23 | witnessGroup: StructuredWitness[], 24 | multisigConfig?: MultisigConfig, 25 | ) { 26 | if (!witnessGroup.length) { 27 | throw new Error('WitnessGroup cannot be empty') 28 | } 29 | if (typeof witnessGroup[0] !== 'object') { 30 | throw new Error('The first witness in the group should be type of WitnessArgs') 31 | } 32 | 33 | const emptyWitness = { 34 | ...witnessGroup[0], 35 | lock: `0x${'0'.repeat(130)}`, 36 | } 37 | if (multisigConfig) { 38 | emptyWitness.lock = `${serializeMultisigConfig(multisigConfig)}${'0'.repeat(130 * multisigConfig.m)}` 39 | } 40 | 41 | const serializedEmptyWitnessBytes = hexToBytes(serializeWitnessArgs(emptyWitness)) 42 | const serializedEmptyWitnessSize = serializedEmptyWitnessBytes.length 43 | 44 | const s = blake2b(32, null, null, PERSONAL) 45 | s.update(hexToBytes(transactionHash)) 46 | s.update(hexToBytes(toUint64Le(`0x${serializedEmptyWitnessSize.toString(16)}`))) 47 | s.update(serializedEmptyWitnessBytes) 48 | 49 | witnessGroup.slice(1).forEach(w => { 50 | const bytes = hexToBytes(typeof w === 'string' ? w : serializeWitnessArgs(w)) 51 | s.update(hexToBytes(toUint64Le(`0x${bytes.length.toString(16)}`))) 52 | s.update(bytes) 53 | }) 54 | 55 | const message = `0x${s.digest('hex')}` 56 | if (typeof sk === 'string') { 57 | const keyPair = new ECPair(sk) 58 | emptyWitness.lock = keyPair.signRecoverable(message) 59 | return [multisigConfig ? emptyWitness : serializeWitnessArgs(emptyWitness), ...witnessGroup.slice(1)] 60 | } else { 61 | const skResult = sk(message, [emptyWitness, ...witnessGroup.slice(1)]) 62 | if (typeof skResult === 'string') { 63 | emptyWitness.lock = skResult 64 | return [multisigConfig ? emptyWitness : serializeWitnessArgs(emptyWitness), ...witnessGroup.slice(1)] 65 | } 66 | return skResult.then(res => { 67 | emptyWitness.lock = res 68 | return [multisigConfig ? emptyWitness : serializeWitnessArgs(emptyWitness), ...witnessGroup.slice(1)] 69 | }) 70 | } 71 | } 72 | 73 | export default signWitnessGroup 74 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/epochs.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi' 2 | import { assertToBeHexString } from './validators.js' 3 | 4 | export interface EpochInfo { 5 | length: string 6 | index: string 7 | number: string 8 | } 9 | 10 | /** 11 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/base.html#generateabsoluteepochsince @ckb-lumos/base/since.generateAbsoluteEpochSince} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#serializeepoch example} 12 | */ 13 | export const serializeEpoch = ({ length, index, number }: EpochInfo): string => { 14 | assertToBeHexString(length) 15 | assertToBeHexString(index) 16 | assertToBeHexString(number) 17 | 18 | const epochSince = JSBI.add( 19 | JSBI.add( 20 | JSBI.add( 21 | JSBI.leftShift(JSBI.BigInt(0x20), JSBI.BigInt(56)), 22 | JSBI.leftShift(JSBI.BigInt(length), JSBI.BigInt(40)), 23 | ), 24 | JSBI.leftShift(JSBI.BigInt(index), JSBI.BigInt(24)), 25 | ), 26 | JSBI.BigInt(number), 27 | ) 28 | 29 | return `0x${epochSince.toString(16)}` 30 | } 31 | 32 | /** 33 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/base.html#parseepoch @ckb-lumos/base/since.parseEpoch} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#parseepoch example} 34 | */ 35 | export const parseEpoch = (epoch: CKBComponents.EpochInHeader) => ({ 36 | length: `0x${JSBI.bitwiseAnd( 37 | JSBI.signedRightShift(JSBI.BigInt(epoch), JSBI.BigInt(40)), 38 | JSBI.BigInt(0xffff), 39 | ).toString(16)}`, 40 | index: `0x${JSBI.bitwiseAnd(JSBI.signedRightShift(JSBI.BigInt(epoch), JSBI.BigInt(24)), JSBI.BigInt(0xffff)).toString( 41 | 16, 42 | )}`, 43 | number: `0x${JSBI.bitwiseAnd(JSBI.BigInt(epoch), JSBI.BigInt(0xffffff)).toString(16)}`, 44 | }) 45 | 46 | /** 47 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/common_scripts.html#calculatedaoearliestsince @ckb-lumos/lumos/commons.dao.calculateDaoEarliestSince} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#getwithdrawepoch example} 48 | * @memberof Utils 49 | * @function getWithdrawEpoch 50 | * @description Calculate the minimum epoch to withdraw the deposit by deposit epoch and withdrawing epoch 51 | * @param {string} depositEpoch Epoch when deposit happens 52 | * @param {string} withdrawingEpoch Epoch when withdrawing happens 53 | */ 54 | export const getWithdrawEpoch = (depositEpoch: string, withdrawingEpoch: string) => { 55 | const EPOCHS_PER_WITHDRAW_CYCLE = 180 56 | const depositEpochInfo = parseEpoch(depositEpoch) 57 | const withdrawingEpochInfo = parseEpoch(withdrawingEpoch) 58 | 59 | let depositedEpochCount = +withdrawingEpochInfo.number - +depositEpochInfo.number 60 | 61 | if (+withdrawingEpochInfo.index * +depositEpochInfo.length > +depositEpochInfo.index * +withdrawingEpochInfo.length) { 62 | depositedEpochCount += 1 63 | } 64 | 65 | const minEpockCountToLock = 66 | depositedEpochCount <= EPOCHS_PER_WITHDRAW_CYCLE 67 | ? EPOCHS_PER_WITHDRAW_CYCLE 68 | : (Math.floor((depositedEpochCount - 1) / EPOCHS_PER_WITHDRAW_CYCLE) + 1) * EPOCHS_PER_WITHDRAW_CYCLE 69 | 70 | return serializeEpoch({ 71 | index: depositEpochInfo.index, 72 | length: depositEpochInfo.length, 73 | number: `0x${(+depositEpochInfo.number + minEpockCountToLock).toString(16)}`, 74 | }) 75 | } 76 | 77 | export default { 78 | serializeEpoch, 79 | parseEpoch, 80 | getWithdrawEpoch, 81 | } 82 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/examples/signMultisig.js: -------------------------------------------------------------------------------- 1 | const CKB = require('../lib').default 2 | const { hashMultisig } = require('../lib/multisig') 3 | 4 | const alice = { 5 | blake160: '0xc9dc0591a8edf3ddbd48e3dd24c85d68706df86f', 6 | privateKey: '0x13f88a4a4f06cdbe693ef77b8fcbda1d44ea28567c47a7284e7542bc3eafe6c7' 7 | } 8 | const bob = { 9 | blake160: '0x8aa16d7f71b352fa8c3bd4ca790ca4b662343381', 10 | privateKey: '0x51594d34890e2c817ac0a58d702a4c19cc314000f308926de739faa460f149c9' 11 | } 12 | const charlie = { 13 | blake160: '0x6f8f1a16bf40f0171bbb6d5abcec5473485f916b', 14 | privateKey: '0xac5f70a5b36e645eebfaad00357e3b1d94b9d50718824a818ec08b90c68a48af' 15 | } 16 | 17 | const multisigConfig = { 18 | r: 0, 19 | m: 2, 20 | n: 3, 21 | blake160s: [alice.blake160, bob.blake160, charlie.blake160] 22 | } 23 | const ckb = new CKB('http://localhost:8114') 24 | const multisigLockScript = { 25 | hashType: ckb.utils.systemScripts.SECP256K1_MULTISIG.hashType, 26 | codeHash: ckb.utils.systemScripts.SECP256K1_MULTISIG.codeHash, 27 | args: hashMultisig(multisigConfig) 28 | } 29 | const multisigLockHash = ckb.utils.scriptToHash(multisigLockScript) 30 | 31 | const inputCells = [ 32 | { 33 | "previousOutput":{ 34 | "txHash":"0x4a978176babec5441a9a15182f3cc35799b60b4fc09e0a478f9fc640c32aa7f0", 35 | "index":"0x0" 36 | }, 37 | "since": "0x0", 38 | "lock": multisigLockScript 39 | } 40 | ] 41 | const tx = { 42 | "cellDeps":[ 43 | { 44 | "outPoint": ckb.utils.systemScripts.SECP256K1_MULTISIG.testnetOutPoint, 45 | "depType": ckb.utils.systemScripts.SECP256K1_MULTISIG.depType 46 | } 47 | ], 48 | "headerDeps":[ 49 | 50 | ], 51 | "inputs": inputCells.map(v => ({ 52 | since: v.since, 53 | previousOutput: v.previousOutput 54 | })), 55 | "outputs":[ 56 | { 57 | "capacity": `0x${BigInt('6100000000').toString(16)}`, 58 | "lock": { 59 | "args": "0x62260b4dd406bee8a021185edaa60b7a77f7e99a", 60 | "codeHash": ckb.utils.systemScripts.SECP256K1_BLAKE160.codeHash, 61 | "hashType": ckb.utils.systemScripts.SECP256K1_BLAKE160.hashType, 62 | }, 63 | }, 64 | { 65 | "capacity": `0x${(BigInt('100000000000') - BigInt('6100000000') - BigInt('593')).toString(16)}`, 66 | "lock": multisigLockScript, 67 | } 68 | ], 69 | "version":"0x0", 70 | "outputsData":[ 71 | "0x", 72 | "0x" 73 | ] 74 | } 75 | const transactionHash = ckb.utils.rawTransactionToHash(tx) 76 | 77 | // will be PartiallySigned after alice sign 78 | const aliceSign = ckb.signWitnesses(new Map([[multisigLockHash, { 79 | sk: alice.privateKey, 80 | blake160: alice.blake160, 81 | config: multisigConfig, 82 | signatures: [] 83 | }]]))({ 84 | transactionHash, 85 | witnesses: [ 86 | { 87 | lock: "", 88 | inputType: "", 89 | outputType: "" 90 | } 91 | ], 92 | inputCells 93 | }) 94 | 95 | // deliver alice's blake160 to signatures, and deliver signed witness as witness parameter will be Signed after bob sign 96 | const bobSign = ckb.signWitnesses(new Map([[multisigLockHash, { 97 | sk: bob.privateKey, 98 | blake160: bob.blake160, 99 | config: multisigConfig, 100 | signatures: [alice.blake160] 101 | }]]))({ 102 | transactionHash, 103 | witnesses: aliceSign, 104 | inputCells 105 | }); 106 | 107 | (async function(){ 108 | tx.witnesses = bobSign 109 | const txHash = await ckb.rpc.sendTransaction(tx) 110 | console.log(txHash) 111 | }()) 112 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/examples/sendSimpleTransaction.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path') 3 | const os = require('os') 4 | const { Indexer, CellCollector } = require('@ckb-lumos/indexer') 5 | const CKB = require('../lib').default 6 | 7 | const CKB_URL = process.env.CKB_URL || 'http://localhost:8114' // example node url 8 | const LUMOS_DB = path.join(os.tmpdir(), 'lumos_db') 9 | /** 10 | * lumos indexer 11 | */ 12 | const indexer = new Indexer(CKB_URL, LUMOS_DB) 13 | indexer.startForever() 14 | 15 | /** 16 | * sdk 17 | */ 18 | 19 | const bootstrap = async () => { 20 | const privateKey = process.env.PRIV_KEY || '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' // example private key 21 | 22 | const ckb = new CKB(CKB_URL) // instantiate the JS SDK with provided node url 23 | 24 | await ckb.loadDeps() // load the dependencies of secp256k1 algorithm which is used to verify the signature in transaction's witnesses. 25 | 26 | const publicKey = ckb.utils.privateKeyToPublicKey(privateKey) 27 | /** 28 | * to see the public key 29 | */ 30 | // console.log(`Public key: ${publicKey}`) 31 | 32 | const publicKeyHash = `0x${ckb.utils.blake160(publicKey, 'hex')}` 33 | /** 34 | * to see the public key hash 35 | */ 36 | // console.log(`Public key hash: ${publicKeyHash}`) 37 | 38 | const addresses = { 39 | mainnetAddress: ckb.utils.pubkeyToAddress(publicKey, { 40 | prefix: 'ckb' 41 | }), 42 | testnetAddress: ckb.utils.pubkeyToAddress(publicKey, { 43 | prefix: 'ckt' 44 | }) 45 | } 46 | 47 | /** 48 | * to see the addresses 49 | */ 50 | // console.log(JSON.stringify(addresses, null, 2)) 51 | 52 | const lock = { 53 | codeHash: ckb.config.secp256k1Dep.codeHash, 54 | hashType: ckb.config.secp256k1Dep.hashType, 55 | args: publicKeyHash 56 | } 57 | /** 58 | * load cells from lumos as `examples/sendTransactionWithLumosCollector.js` shows 59 | */ 60 | const unspentCells = await ckb.loadCells({ indexer, CellCollector, lock }) 61 | 62 | /** 63 | * to see the unspent cells 64 | */ 65 | // console.log(unspentCells) 66 | 67 | /** 68 | * send transaction 69 | */ 70 | const toAddress = ckb.utils.privateKeyToAddress("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", { 71 | prefix: 'ckt' 72 | }) 73 | 74 | /** 75 | * @param fee - transaction fee, can be set in number directly, or use an reconciler to set by SDK 76 | * say, fee: BigInt(100000) means transaction fee is 100000 shannons 77 | * or, fee: { feeRate: '0x7d0', reconciler: ckb.utils.reconcilers.extraInputs } to set transaction fee by reconcilers.extraInputs with feeRate = 2000 shannons/Byte 78 | * 79 | * @external https://docs.nervos.org/docs/essays/faq#how-do-you-calculate-transaction-fee 80 | */ 81 | const rawTransaction = ckb.generateRawTransaction({ 82 | fromAddress: addresses.testnetAddress, 83 | toAddress, 84 | capacity: BigInt(600000000000), 85 | fee: BigInt(100000), 86 | safeMode: true, 87 | cells: unspentCells, 88 | deps: ckb.config.secp256k1Dep, 89 | }) 90 | 91 | 92 | const signedTx = ckb.signTransaction(privateKey)(rawTransaction) 93 | /** 94 | * to see the signed transaction 95 | */ 96 | // console.log(JSON.stringify(signedTx, null, 2)) 97 | 98 | const realTxHash = await ckb.rpc.sendTransaction(signedTx) 99 | /** 100 | * to see the real transaction hash 101 | */ 102 | console.log(`The real transaction hash is: ${realTxHash}`) 103 | } 104 | 105 | bootstrap() 106 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/ecpair/index.test.js: -------------------------------------------------------------------------------- 1 | const { hexToBytes } = require('../..') 2 | const { default: ECPair } = require('../../lib/ecpair') 3 | const { ParameterRequiredException, HexStringWithout0xException } = require('../../lib/exceptions') 4 | const { sigFixtures, signRecoverableFixtures } = require('./signature.fixtures.json') 5 | const { instantiate: instantiateFixtures } = require('./ecpare.fixtures.json') 6 | 7 | describe('ECPair', () => { 8 | it('new ecpair', () => { 9 | const fixture = instantiateFixtures.basic 10 | 11 | const ecpair = new ECPair(hexToBytes(fixture.privateKey), { 12 | compressed: fixture.compressed, 13 | }) 14 | expect(ecpair.compressed).toBe(fixture.compressed) 15 | expect(ecpair.privateKey).toBe(fixture.privateKey) 16 | expect(ecpair.getPrivateKey('hex')).toBe(fixture.privateKey) 17 | expect(ecpair.getPrivateKey('words').words).toEqual(fixture.privateKeyWords) 18 | expect(ecpair.publicKey).toBe(fixture.publicKey) 19 | expect(ecpair.getPublicKey('hex')).toBe(fixture.publicKey) 20 | expect(ecpair.getPublicKey('bytes')).toEqual(fixture.publicKeyBytes) 21 | }) 22 | 23 | it('new ecpair with empty options, default compressed should be true', () => { 24 | const fixture = instantiateFixtures.withEmptyOption 25 | 26 | const ecpair = new ECPair(fixture.privateKey, {}) 27 | expect(ecpair.compressed).toBe(fixture.compressed) 28 | expect(ecpair.privateKey).toEqual(fixture.privateKey) 29 | expect(ecpair.publicKey).toBe(fixture.publicKey) 30 | }) 31 | 32 | it('new ecpair with default options which should be { compressed: true }', () => { 33 | const fixture = instantiateFixtures.withDefaultOption 34 | 35 | const ecpair = new ECPair(fixture.privateKey) 36 | expect(ecpair.compressed).toBe(fixture.compressed) 37 | expect(ecpair.privateKey).toEqual(fixture.privateKey) 38 | expect(ecpair.publicKey).toBe(fixture.publicKey) 39 | }) 40 | 41 | it('Instantiate with an empty private key should throw an error', () => { 42 | expect(() => new ECPair()).toThrow(new ParameterRequiredException('Private key')) 43 | }) 44 | 45 | it('Instantiate with a private key without 0x should throw an error', () => { 46 | const privateKey = 'e79f3207ea4980b7fed79956d5934249ceac4751a4fae01a0f7c4a96884bc4e3' 47 | expect(() => new ECPair(privateKey, {})).toThrow(new HexStringWithout0xException(privateKey)) 48 | }) 49 | 50 | it('shoule throw an error if private key has invalid length', () => { 51 | const fixture = instantiateFixtures.privateKeyHasInvalidLength 52 | expect(() => new ECPair(fixture.privateKey)).toThrow(new Error(fixture.exception)) 53 | expect(() => new ECPair(hexToBytes(fixture.privateKey), 'hex')).toThrow(fixture.exception) 54 | }) 55 | 56 | it('leading 0 of private key should be remained', () => { 57 | const fixture = instantiateFixtures.leadingZeroShouldBeRemained 58 | const key = new ECPair(fixture.privateKey) 59 | expect(key.privateKey).toBe(fixture.privateKey) 60 | }) 61 | 62 | it('sign and verify message', () => { 63 | sigFixtures.forEach(fixture => { 64 | const ecpair = new ECPair(`0x${fixture.privkey}`) 65 | const sig = ecpair.sign(`0x${fixture.msg}`) 66 | // slice sig from 0, -2 to ignore the recovery param 67 | expect(sig).toBe(`0x${fixture.sig.slice(0, -2)}`) 68 | expect(ecpair.verify(`0x${fixture.msg}`, `0x${fixture.sig.slice(0, -2)}`)).toBe(true) 69 | }) 70 | }) 71 | 72 | it('signRecoverable', () => { 73 | signRecoverableFixtures.forEach(fixture => { 74 | const ecpair = new ECPair(`0x${fixture.privKey}`) 75 | const sig = ecpair.signRecoverable(`0x${fixture.msg}`) 76 | expect(sig).toBe(`0x${fixture.sig}`) 77 | }) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/signWitnessGroup/index.test.js: -------------------------------------------------------------------------------- 1 | const signWitnessGroup = require('../../lib/signWitnessGroup').default 2 | const fixtures = require('./fixtures.json') 3 | 4 | describe('test sign witness group', () => { 5 | const fixtureTable = Object.entries( 6 | fixtures, 7 | ).map(([title, { privateKey, transactionHash, witnesses, multisigConfig, expected, exception }]) => [ 8 | title, 9 | privateKey, 10 | transactionHash, 11 | witnesses, 12 | multisigConfig, 13 | exception, 14 | expected, 15 | ]) 16 | 17 | test.each(fixtureTable)('%s', (_title, privateKey, transactionHash, witnesses, multisigConfig, exception, expected) => { 18 | expect.assertions(1) 19 | if (exception !== undefined) { 20 | expect(() => signWitnessGroup(privateKey, transactionHash, witnesses, multisigConfig)).toThrowError(exception) 21 | } else if (privateKey !== undefined) { 22 | const signedWitnessGroup = signWitnessGroup(privateKey, transactionHash, witnesses, multisigConfig) 23 | expect(signedWitnessGroup).toEqual(expected) 24 | } 25 | }) 26 | 27 | 28 | describe('sk is function', () => { 29 | const transactionHash = '0x4a4bcfef1b7448e27edf533df2f1de9f56be05eba645fb83f42d55816797ad2a' 30 | const witnesses = [ 31 | { 32 | "lock": "", 33 | "inputType": "", 34 | "outputType": "" 35 | }, 36 | { 37 | "lock": "", 38 | "inputType": "", 39 | "outputType": "" 40 | } 41 | ] 42 | const multisigConfig = { 43 | r: 1, 44 | m: 1, 45 | n: 2, 46 | blake160s: ["0x7c021957a27000e794f25828270f187c791443e3", "0xd93b3564ef1b2dcf7bca781f968b3c7d2db85fd1"] 47 | } 48 | it('sk is sync', () => { 49 | const privateKey = v => v 50 | const signedWitnessGroup = signWitnessGroup(privateKey, transactionHash, witnesses) 51 | expect(signedWitnessGroup).toEqual([ 52 | '0x34000000100000003400000034000000200000007739c6307c4e3698a8a8ebfdb3908a29a7cb5a382040c89806cace1ddc538b0e', 53 | { 54 | "lock": "", 55 | "inputType": "", 56 | "outputType": "" 57 | } 58 | ]) 59 | }) 60 | it('sk is sync with multisigConfig', () => { 61 | const privateKey = v => v 62 | const signedWitnessGroup = signWitnessGroup(privateKey, transactionHash, witnesses, multisigConfig) 63 | expect(signedWitnessGroup).toEqual([ 64 | { 65 | "lock": "0x040db42399af0c6e32cb68160079cc40c4e8d207052fec335c52b76e3442a8a3", 66 | "inputType": "", 67 | "outputType": "" 68 | }, 69 | { 70 | "lock": "", 71 | "inputType": "", 72 | "outputType": "" 73 | } 74 | ]) 75 | }) 76 | 77 | it('sk result is promise', async () => { 78 | const privateKey = (v) => Promise.resolve(v) 79 | const signedWitnessGroup = await signWitnessGroup(privateKey, transactionHash, witnesses) 80 | expect(signedWitnessGroup).toEqual([ 81 | '0x34000000100000003400000034000000200000007739c6307c4e3698a8a8ebfdb3908a29a7cb5a382040c89806cace1ddc538b0e', 82 | { 83 | "lock": "", 84 | "inputType": "", 85 | "outputType": "" 86 | } 87 | ]) 88 | }) 89 | 90 | it('sk result is promise with multisigConfig', async () => { 91 | const privateKey = (v) => Promise.resolve(v) 92 | const signedWitnessGroup = await signWitnessGroup(privateKey, transactionHash, witnesses, multisigConfig) 93 | expect(signedWitnessGroup).toEqual([ 94 | { 95 | "lock": "0x040db42399af0c6e32cb68160079cc40c4e8d207052fec335c52b76e3442a8a3", 96 | "inputType": "", 97 | "outputType": "" 98 | }, 99 | { 100 | "lock": "", 101 | "inputType": "", 102 | "outputType": "" 103 | } 104 | ]) 105 | }) 106 | }) 107 | }) 108 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/serialization/basic/index.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | serializeArray, 3 | serializeStruct, 4 | serializeFixVec, 5 | serializeDynVec, 6 | serializeTable, 7 | serializeOption, 8 | } = require('../../../lib') 9 | const fixtures = require('./fixtures.json') 10 | 11 | describe('Test General Serialization', () => { 12 | describe('Serialize Array', () => { 13 | const fixtureTable = Object.entries(fixtures.serializeArray).map(([title, { array, expected, exception }]) => [ 14 | title, 15 | array, 16 | expected, 17 | exception, 18 | ]) 19 | test.each(fixtureTable)('%s', (_title, array, expected, exception) => { 20 | if (undefined !== expected) { 21 | expect(serializeArray(array)).toBe(expected) 22 | } 23 | if (undefined !== exception) { 24 | expect(() => serializeArray(array)).toThrow(new Error(exception)) 25 | } 26 | }) 27 | }) 28 | 29 | describe('Serialize Struct', () => { 30 | const fixtureTable = Object.entries(fixtures.serializeStruct).map(([title, { struct, expected, exception }]) => [ 31 | title, 32 | new Map(struct), 33 | expected, 34 | exception, 35 | ]) 36 | test.each(fixtureTable)('%s', (_title, struct, expected, exception) => { 37 | if (undefined !== expected) { 38 | expect(serializeStruct(struct)).toBe(expected) 39 | } 40 | if (undefined !== exception) { 41 | expect(() => serializeStruct(struct)).toThrow(new Error(exception)) 42 | } 43 | }) 44 | }) 45 | 46 | describe('Serialize Fixed Vector', () => { 47 | const fixtureTable = Object.entries(fixtures.serializeFixVec).map(([title, { fixvec, expected, exception }]) => [ 48 | title, 49 | fixvec, 50 | expected, 51 | exception, 52 | ]) 53 | test.each(fixtureTable)('%s', (_title, fixvec, expected, exception) => { 54 | if (undefined !== expected) { 55 | expect(serializeFixVec(fixvec)).toBe(expected) 56 | } 57 | if (undefined !== exception) { 58 | expect(() => serializeFixVec(fixvec)).toThrow(new Error(exception)) 59 | } 60 | }) 61 | }) 62 | 63 | describe('Serialize Dynamic Vector', () => { 64 | const fixtureTable = Object.entries(fixtures.serializeDynVec).map(([title, { dynvec, expected, exception }]) => [ 65 | title, 66 | dynvec, 67 | expected, 68 | exception, 69 | ]) 70 | test.each(fixtureTable)('%s', (_title, dynvec, expected, exception) => { 71 | if (undefined !== expected) { 72 | expect(serializeDynVec(dynvec)).toBe(expected) 73 | } 74 | if (undefined !== exception) { 75 | expect(() => serializeDynVec(dynvec)).toThrow(new Error(exception)) 76 | } 77 | }) 78 | }) 79 | 80 | describe('Serialize Table', () => { 81 | const fixtureTable = Object.entries(fixtures.serializeTable).map(([title, { table, expected, exception }]) => [ 82 | title, 83 | new Map(table), 84 | expected, 85 | exception, 86 | ]) 87 | test.each(fixtureTable)('%s', (_title, table, expected, exception) => { 88 | if (undefined !== expected) { 89 | expect(serializeTable(table)).toBe(expected) 90 | } 91 | if (undefined !== exception) { 92 | expect(() => serializeTable(table)).toThrow(new Error(exception)) 93 | } 94 | }) 95 | }) 96 | 97 | describe('Serialize Option', () => { 98 | const fixtureTable = Object.entries(fixtures.serializeOption).map(([title, { option, expected, exception }]) => [ 99 | title, 100 | option, 101 | expected, 102 | exception, 103 | ]) 104 | test.each(fixtureTable)('%s', (_title, option, expected, exception) => { 105 | if (undefined !== expected) { 106 | expect(serializeOption(option)).toBe(expected) 107 | } 108 | if (undefined !== exception) { 109 | expect(() => serializeOption(option)).toThrow(exception) 110 | } 111 | }) 112 | }) 113 | }) 114 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/serialization/basic/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "serializeArray": { 3 | "basic array in hex string": { 4 | "array": "0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88", 5 | "expected": "0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88" 6 | }, 7 | "basic array in bytes": { 8 | "array": [ 9 | 104, 10 | 213, 11 | 67, 12 | 138, 13 | 201, 14 | 82, 15 | 210, 16 | 245, 17 | 132, 18 | 171, 19 | 248, 20 | 121, 21 | 82, 22 | 121, 23 | 70, 24 | 165, 25 | 55, 26 | 232, 27 | 44, 28 | 127, 29 | 60, 30 | 28, 31 | 191, 32 | 109, 33 | 142, 34 | 191, 35 | 151, 36 | 103, 37 | 67, 38 | 125, 39 | 142, 40 | 136 41 | ], 42 | "expected": "0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88" 43 | }, 44 | "invalid array should throw an error": { 45 | "exception": "The array to be serialized should by type of string or bytes" 46 | }, 47 | "hex string without 0x should throw an error": { 48 | "array": "ffffff", 49 | "exception": "Hex string ffffff should start with 0x" 50 | } 51 | }, 52 | "serializeStruct": { 53 | "basic struct": { 54 | "struct": [["f1", "0xab"], ["f2", "0x030201"]], 55 | "expected": "0xab030201" 56 | }, 57 | "invalid struct": { 58 | "struct": [["f1", "ab"], ["f2", "0x030201"]], 59 | "exception": "Hex string ab should start with 0x" 60 | } 61 | }, 62 | "serializeFixVec": { 63 | "empty vector": { 64 | "fixvec": [], 65 | "expected": "0x00000000" 66 | }, 67 | "hex string in vector": { 68 | "fixvec": ["0x12"], 69 | "expected": "0x0100000012" 70 | }, 71 | "bytes in vector": { 72 | "fixvec": [[18]], 73 | "expected": "0x0100000012" 74 | }, 75 | "vector in hex string": { 76 | "fixvec": "0x1234567890abcdef", 77 | "expected": "0x080000001234567890abcdef" 78 | }, 79 | "invalid vector": { 80 | "exception": "The fixed vector to be serialized should be a string or an array of bytes" 81 | } 82 | }, 83 | "serializeDynVec": { 84 | "empty vector": { 85 | "dynvec": [], 86 | "expected": "0x04000000" 87 | }, 88 | "vector has one item": { 89 | "dynvec": ["0x020000001234"], 90 | "expected": "0x0e00000008000000020000001234" 91 | }, 92 | "vector has multiple items": { 93 | "dynvec": ["0x020000001234", "0x00000000", "0x020000000567", "0x0100000089", "0x03000000abcdef"], 94 | "expected": "0x34000000180000001e00000022000000280000002d00000002000000123400000000020000000567010000008903000000abcdef" 95 | }, 96 | "vector in invalid format should throw an error": { 97 | "exception": "The dynamic vector to be serialized should be an array of bytes" 98 | }, 99 | "vector has invalid item should throw an error": { 100 | "dynvec": ["020000001234", "0x00000000", "0x020000000567", "0x0100000089", "0x03000000abcdef"], 101 | "exception": "Hex string 020000001234 should start with 0x" 102 | } 103 | }, 104 | "serializeTable": { 105 | "basic table": { 106 | "table": [["f1", "0xab"], ["f2", "0x030201"]], 107 | "expected": "0x100000000c0000000d000000ab030201" 108 | }, 109 | "table has invalid field should throw an error": { 110 | "table": [["f1", "ab"], ["f2", "0x030201"]], 111 | "exception": "Hex string ab should start with 0x" 112 | } 113 | }, 114 | "serializeOption": { 115 | "empty string":{ 116 | "option": "", 117 | "expected": "0x" 118 | }, 119 | "empty option": { 120 | "option": "0x", 121 | "expected": "0x" 122 | }, 123 | "basic option": { 124 | "option": "0x0c0000000800000000000000", 125 | "expected": "0x0c0000000800000000000000" 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/convertors/index.ts: -------------------------------------------------------------------------------- 1 | import { assertToBeHexStringOrBigint, assertToBeHexString } from '../validators.js' 2 | import { HexStringWithout0xException } from '../exceptions/index.js' 3 | 4 | /** 5 | * Converts an uint16 into a hex string in little endian 6 | * 7 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/codec.html#uint16le-2 @ckb-lumos/codec/Uint16LE} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#touint16le example} 8 | * @memberof convertors 9 | * @param {string|bigint} uint16 The uint16 to convert 10 | * @returns {string} Returns a hex string 11 | */ 12 | export const toUint16Le = (uint16: string | bigint) => { 13 | assertToBeHexStringOrBigint(uint16) 14 | const dv = new DataView(new ArrayBuffer(2)) 15 | dv.setUint16(0, Number(uint16), true) 16 | return `0x${dv.getUint16(0, false).toString(16).padStart(4, '0')}` 17 | } 18 | 19 | /** 20 | * Converts an uint32 into a hex string in little endian 21 | * 22 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/codec.html#uint32le-2 @ckb-lumos/codec/Uint32LE} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#touint32le example} 23 | * @memberof convertors 24 | * @param {string|bigint} uint32 The uint32 to convert 25 | * @returns {string} Returns a hex string 26 | */ 27 | export const toUint32Le = (uint32: string | bigint) => { 28 | assertToBeHexStringOrBigint(uint32) 29 | const dv = new DataView(new ArrayBuffer(4)) 30 | dv.setUint32(0, Number(uint32), true) 31 | return `0x${dv.getUint32(0, false).toString(16).padStart(8, '0')}` 32 | } 33 | 34 | /** 35 | * Converts an uint64 into a hex string in little endian 36 | * 37 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/codec.html#uint64le-2 @ckb-lumos/codec/Uint64LE} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#touint64le example} 38 | * @memberof convertors 39 | * @param {string|bigint} uint64 The uint64 to convert 40 | * @returns {string} Returns a hex string 41 | */ 42 | export const toUint64Le = (uint64: string | bigint) => { 43 | assertToBeHexStringOrBigint(uint64) 44 | const val = (typeof uint64 === 'bigint' ? uint64.toString(16) : uint64.slice(2)).padStart(16, '0') 45 | const viewRight = toUint32Le(`0x${val.slice(0, 8)}`).slice(2) 46 | const viewLeft = toUint32Le(`0x${val.slice(8)}`).slice(2) 47 | return `0x${viewLeft}${viewRight}` 48 | } 49 | 50 | /** 51 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/codec.html#bytify @ckb-lumos/codec/bytes} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#hextobytes example} 52 | */ 53 | export const hexToBytes = (rawhex: string | number | bigint) => { 54 | if (rawhex === '') return new Uint8Array() 55 | if (typeof rawhex === 'string' && !rawhex.startsWith('0x')) { 56 | throw new HexStringWithout0xException(rawhex) 57 | } 58 | 59 | let hex = rawhex.toString(16).replace(/^0x/i, '') 60 | hex = hex.length % 2 ? `0${hex}` : hex 61 | 62 | const bytes = [] 63 | for (let c = 0; c < hex.length; c += 2) { 64 | bytes.push(parseInt(hex.substr(c, 2), 16)) 65 | } 66 | 67 | return new Uint8Array(bytes) 68 | } 69 | 70 | /** 71 | * Converts a hex string in little endian into big endian 72 | * 73 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/codec.html#hexify @ckb-lumos/codec/bytes} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#tobigendian example} 74 | * @memberof convertors 75 | * @param {string} le16 The hex string to convert 76 | * @returns {string} Returns a big endian 77 | */ 78 | export const toBigEndian = (leHex: string) => { 79 | assertToBeHexString(leHex) 80 | const bytes = hexToBytes(leHex); 81 | return `0x${bytes.reduceRight((pre, cur) => pre + cur.toString(16).padStart(2, '0'), '')}` 82 | } 83 | 84 | /** 85 | * @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/codec.html#hexify @ckb-lumos/codec/bytes} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#hextobytes example} 86 | */ 87 | export const bytesToHex = (bytes: Uint8Array): string => 88 | `0x${[...bytes].map(b => b.toString(16).padStart(2, '0')).join('')}` 89 | 90 | export default { 91 | toUint16Le, 92 | toUint32Le, 93 | toUint64Le, 94 | hexToBytes, 95 | bytesToHex, 96 | toBigEndian 97 | } 98 | -------------------------------------------------------------------------------- /packages/ckb-sdk-rpc/src/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import axios from 'axios' 4 | import Base from './Base/index.js' 5 | import Method from './method.js' 6 | 7 | import paramsFormatter from './paramsFormatter.js' 8 | import resultFormatter from './resultFormatter.js' 9 | import { 10 | MethodInBatchNotFoundException, 11 | PayloadInBatchException, 12 | IdNotMatchedInBatchException, 13 | } from './exceptions/index.js' 14 | 15 | class CKBRPC extends Base { 16 | #node: CKBComponents.Node = { 17 | url: '', 18 | } 19 | 20 | get node() { 21 | return this.#node 22 | } 23 | 24 | #paramsFormatter = paramsFormatter 25 | 26 | get paramsFormatter() { 27 | return this.#paramsFormatter 28 | } 29 | 30 | #resultFormatter = resultFormatter 31 | 32 | get resultFormatter() { 33 | return this.#resultFormatter 34 | } 35 | 36 | constructor(url: string) { 37 | super() 38 | this.setNode({ url }) 39 | 40 | Object.defineProperties(this, { 41 | addMethod: { value: this.addMethod, enumerable: false, writable: false, configurable: false }, 42 | setNode: { value: this.setNode, enumerable: false, writable: false, configurable: false }, 43 | createBatchRequest: { value: this.createBatchRequest, enumerable: false, writable: false, configurable: false }, 44 | }) 45 | 46 | Object.keys(this.rpcProperties).forEach(name => { 47 | this.addMethod({ name, ...this.rpcProperties[name] }) 48 | }) 49 | } 50 | 51 | public setNode(node: CKBComponents.Node): CKBComponents.Node { 52 | Object.assign(this.node, node) 53 | return this.node 54 | } 55 | 56 | public addMethod = (options: CKBComponents.Method) => { 57 | const method = new Method(this.node, options) 58 | 59 | Object.defineProperty(this, options.name, { 60 | value: method.call, 61 | enumerable: true, 62 | }) 63 | } 64 | 65 | /* eslint-disable prettier/prettier */ 66 | public createBatchRequest = ( 67 | params: [method: N, ...rest: P][] = [], 68 | ) => { 69 | const ctx = this 70 | 71 | const proxied: [method: N, ...rest: P][] = new Proxy([], { 72 | set(...p) { 73 | const methods = Object.keys(ctx) 74 | if (p[1] !== 'length') { 75 | const name = p?.[2]?.[0] 76 | if (methods.indexOf(name) === -1) { 77 | throw new MethodInBatchNotFoundException(name) 78 | } 79 | } 80 | return Reflect.set(...p) 81 | }, 82 | }) 83 | 84 | Object.defineProperties(proxied, { 85 | add: { 86 | value(...args: P) { 87 | this.push(args) 88 | return this 89 | }, 90 | }, 91 | remove: { 92 | value(i: number) { 93 | this.splice(i, 1) 94 | return this 95 | }, 96 | }, 97 | exec: { 98 | async value() { 99 | const payload = proxied.map(([f, ...p], i) => { 100 | try { 101 | const method = new Method(ctx.node, { ...ctx.rpcProperties[f], name: f }) 102 | return method.getPayload(...p) 103 | } catch (err) { 104 | throw new PayloadInBatchException(i, err.message) 105 | } 106 | }) 107 | 108 | if (!payload.length) { 109 | return [] 110 | } 111 | 112 | const batchRes = await axios({ 113 | method: 'POST', 114 | headers: { 'content-type': 'application/json' }, 115 | data: payload, 116 | url: ctx.#node.url, 117 | httpAgent: ctx.#node.httpAgent, 118 | httpsAgent: ctx.#node.httpsAgent, 119 | }) 120 | 121 | return batchRes.data.map((res: any, i: number) => { 122 | if (res.id !== payload[i].id) { 123 | return new IdNotMatchedInBatchException(i, payload[i].id, res.id) 124 | } 125 | return ctx.rpcProperties[proxied[i][0]].resultFormatters?.(res.result) ?? res.result 126 | }) 127 | }, 128 | }, 129 | }) 130 | params.forEach(p => proxied.push(p)) 131 | 132 | return proxied as typeof proxied & { 133 | add: (n: N, ...p: P) => typeof proxied 134 | remove: (index: number) => typeof proxied 135 | exec: () => Promise 136 | } 137 | } 138 | } 139 | 140 | export default CKBRPC 141 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/src/systemScripts.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @summary System scripts are the smart contracts built and deployed by the CKB core team. 3 | * System scripts complement the function of CKB in a flexible way. 4 | * System scripts can provide 5 | * - core functions (e.g. secp256k1/blake160 and Nervos DAO), 6 | * - shared standard implementations (e.g. Simple UDT), 7 | * - or other auxiliary infrastructure components. 8 | * @see https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0024-ckb-system-script-list/0024-ckb-system-script-list.md 9 | */ 10 | interface SystemScript extends Omit { 11 | depType: CKBComponents.DepType 12 | } 13 | 14 | type OutPoints = Record<'mainnetOutPoint' | 'testnetOutPoint', CKBComponents.OutPoint> 15 | 16 | /** 17 | * @memberof System Scripts 18 | * @typedef {Lock Script} 19 | * @name SECP256K1_BLAKE160 20 | * @description SECP256K1_BLAKE160 is the default lock script to verify CKB transaction signature 21 | */ 22 | export const SECP256K1_BLAKE160: SystemScript & OutPoints = { 23 | codeHash: '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', 24 | hashType: 'type', 25 | depType: 'depGroup', 26 | mainnetOutPoint: { 27 | txHash: '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c', 28 | index: '0x0', 29 | }, 30 | testnetOutPoint: { 31 | txHash: '0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37', 32 | index: '0x0', 33 | }, 34 | } 35 | 36 | /** 37 | * @memberof System Scripts 38 | * @typedef {Lock Script} 39 | * @name SECP256K1_MULTISIG 40 | * @description SECP256K1_MULTISIG is a script which allows a group of users to sign a single transaction 41 | */ 42 | export const SECP256K1_MULTISIG: SystemScript & OutPoints = { 43 | codeHash: '0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8', 44 | hashType: 'type', 45 | depType: 'depGroup', 46 | mainnetOutPoint: { 47 | txHash: '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c', 48 | index: '0x1', 49 | }, 50 | testnetOutPoint: { 51 | txHash: '0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37', 52 | index: '0x1', 53 | }, 54 | } 55 | 56 | /** 57 | * @memberof System Scripts 58 | * @typedef {Lock Script} 59 | * @name ANYONE_CAN_PAY 60 | * @description ANYONE_CAN_PAY allows a recipient to provide cell capacity in asset transfer 61 | */ 62 | export const ANYONE_CAN_PAY_MAINNET: SystemScript & Pick = { 63 | codeHash: '0xd369597ff47f29fbc0d47d2e3775370d1250b85140c670e4718af712983a2354', 64 | hashType: 'type', 65 | depType: 'depGroup', 66 | mainnetOutPoint: { 67 | txHash: '0x4153a2014952d7cac45f285ce9a7c5c0c0e1b21f2d378b82ac1433cb11c25c4d', 68 | index: '0x0', 69 | }, 70 | } 71 | 72 | export const ANYONE_CAN_PAY_TESTNET: SystemScript & Pick = { 73 | codeHash: '0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356', 74 | hashType: 'type', 75 | depType: 'depGroup', 76 | testnetOutPoint: { 77 | txHash: '0xec26b0f85ed839ece5f11c4c4e837ec359f5adc4420410f6453b1f6b60fb96a6', 78 | index: '0x0', 79 | }, 80 | } 81 | 82 | /** 83 | * @memberof System Scripts 84 | * @typedef {Type Script} 85 | * @name NERVOS_DAO 86 | * @description NERVOS_DAO is the script implements Nervos DAO 87 | */ 88 | export const NERVOS_DAO: SystemScript & OutPoints = { 89 | codeHash: '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e', 90 | hashType: 'type', 91 | depType: 'code', 92 | mainnetOutPoint: { 93 | txHash: '0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c', 94 | index: '0x2', 95 | }, 96 | testnetOutPoint: { 97 | txHash: '0x8f8c79eb6671709633fe6a46de93c0fedc9c1b8a6527a18d3983879542635c9f', 98 | index: '0x2', 99 | }, 100 | } 101 | 102 | /** 103 | * @memberof System Scripts 104 | * @typedef {Type Script} 105 | * @name SIMPLE_UDT 106 | * @description SIMPLE_UDT implements the minimum standard for user defined tokens on Nervos CKB 107 | */ 108 | export const SIMPLE_UDT: SystemScript & Pick = { 109 | codeHash: '0x48dbf59b4c7ee1547238021b4869bceedf4eea6b43772e5d66ef8865b6ae7212', 110 | hashType: 'data', 111 | depType: 'code', 112 | testnetOutPoint: { 113 | txHash: '0xc1b2ae129fad7465aaa9acc9785f842ba3e6e8b8051d899defa89f5508a77958', 114 | index: '0x0', 115 | }, 116 | } 117 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/utils/index.test.js: -------------------------------------------------------------------------------- 1 | const { privateKeyToPublicKey, privateKeyToAddress, scriptToHash, rawTransactionToHash, calculateMaximumWithdraw, extractDAOData } = require('../..') 2 | const exceptions = require('../../lib/exceptions') 3 | const rawTransactionToHashFixtures = require('./rawTransactionToHash.fixtures.json') 4 | 5 | const { ParameterRequiredException } = exceptions 6 | 7 | describe('scriptToHash', () => { 8 | const fixtures = { 9 | 'Empty script': { 10 | script: { 11 | codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', 12 | args: [], 13 | hashType: 'data', 14 | }, 15 | scriptHash: '0x77c93b0632b5b6c3ef922c5b7cea208fb0a7c427a13d50e13d3fefad17e0c590', 16 | }, 17 | 'Script with hash type of data': { 18 | script: { 19 | codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', 20 | args: ['0x01'], 21 | hashType: 'data', 22 | }, 23 | scriptHash: '0x67951b34bce20cb71b7e235c1f8cda259628d99d94825bffe549c23b4dd2930f', 24 | }, 25 | 'Script with hash type of type': { 26 | script: { 27 | codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', 28 | args: ['0x01'], 29 | hashType: 'type', 30 | }, 31 | scriptHash: '0xd39f84d4702f53cf8625da4411be1640b961715cb36816501798fedb70b6e0fb', 32 | }, 33 | } 34 | test.each(Object.keys(fixtures))('%s', fixtureName => { 35 | const fixture = fixtures[fixtureName] 36 | const scriptHash = scriptToHash(fixture.script) 37 | expect(scriptHash).toBe(fixture.scriptHash) 38 | }) 39 | 40 | it('empty input should throw an error', () => { 41 | expect(() => scriptToHash()).toThrow(new ParameterRequiredException('Script')) 42 | }) 43 | }) 44 | 45 | describe('rawTransactionToHash', () => { 46 | const fixtureTable = rawTransactionToHashFixtures.rawTransactionToHash.map(({ rawTransaction, expected }) => [ 47 | rawTransaction, 48 | expected, 49 | ]) 50 | 51 | test.each(fixtureTable)('%j => %s', (rawTransaction, expected) => { 52 | expect(rawTransactionToHash(rawTransaction)).toBe(expected) 53 | }) 54 | it('throw an error if the raw transaction is not missing', () => { 55 | expect(() => rawTransactionToHash(undefined)).toThrow(new Error('Raw transaction is required')) 56 | }) 57 | }) 58 | 59 | describe('privateKeyToPublicKey', () => { 60 | const fixture = { 61 | privateKey: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', 62 | publicKey: '0x03a706ad8f73115f90500266f273f7571df9429a4cfb4bbfbcd825227202dabad1', 63 | } 64 | expect(privateKeyToPublicKey(fixture.privateKey)).toBe(fixture.publicKey) 65 | }) 66 | 67 | describe('privateKeyToAddress', () => { 68 | const fixture = { 69 | privateKey: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', 70 | mainnetAddress: 'ckb1qyqw975zuu9svtyxgjuq44lv7mspte0n2tmqqm3w53', 71 | testnetAddress: 'ckt1qyqw975zuu9svtyxgjuq44lv7mspte0n2tmqa703cd', 72 | } 73 | expect(privateKeyToAddress(fixture.privateKey)).toBe(fixture.mainnetAddress) 74 | expect( 75 | privateKeyToAddress(fixture.privateKey, { 76 | prefix: 'ckb', 77 | }), 78 | ).toBe(fixture.mainnetAddress) 79 | expect( 80 | privateKeyToAddress(fixture.privateKey, { 81 | prefix: 'ckt', 82 | }), 83 | ).toBe(fixture.testnetAddress) 84 | }) 85 | 86 | describe('calculate-maximum-withdraw', () => { 87 | const outputCell = { 88 | "capacity":"0xe8d4a51000", 89 | "lock":{ 90 | "args":"0xf601cac75568afec3b9c9af1e1ff730062007685", 91 | "codeHash":"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 92 | "hashType":"type" 93 | }, 94 | "type":{ 95 | "args":"0x", 96 | "codeHash":"0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e", 97 | "hashType":"type" 98 | } 99 | } 100 | expect( 101 | calculateMaximumWithdraw( 102 | outputCell, 103 | '0x0000000000000000', 104 | '0x1aaf2ca6847c223c3ef9e8c069c9250020212a6311e2d30200609349396eb407', 105 | '0x9bafffa73e432e3c94c6f9db34cb25009f9e4efe4b5fd60200ea63c6d4ffb407' 106 | )).toBe('0xe8df95141e') 107 | }) 108 | 109 | describe('extract header dao', () => { 110 | const DAOData = extractDAOData('0x1aaf2ca6847c223c3ef9e8c069c9250020212a6311e2d30200609349396eb407') 111 | expect(DAOData.c).toBe('0x3c227c84a62caf1a') 112 | expect(DAOData.ar).toBe('0x0025c969c0e8f93e') 113 | expect(DAOData.s).toBe('0x02d3e211632a2120') 114 | expect(DAOData.u).toBe('0x07b46e3949936000') 115 | }) -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/exceptions/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "ParameterRequiredException": { 3 | "params": ["Field Name"], 4 | "expected": { 5 | "code": 102, 6 | "message": "Field Name is required" 7 | } 8 | }, 9 | "SignMessageException": { 10 | "params": [], 11 | "expected": { 12 | "code": 103, 13 | "message": "Fail to sign the message" 14 | } 15 | }, 16 | "PrivateKeyLenException": { 17 | "params": [], 18 | "expected": { 19 | "code": 101, 20 | "message": "Private key has invalid length" 21 | } 22 | }, 23 | "HexStringException": { 24 | "params": ["Invalid hex string"], 25 | "expected": { 26 | "code": 101, 27 | "message": "Invalid hex string is an invalid hex string" 28 | } 29 | }, 30 | "HexStringWithout0xException": { 31 | "params": ["abc"], 32 | "expected": { 33 | "code": 101, 34 | "message": "Hex string abc should start with 0x" 35 | } 36 | }, 37 | "AddressPayloadException": { 38 | "params": ["Invalid Payload", "short"], 39 | "expected": { 40 | "code": 104, 41 | "message": "'Invalid Payload' is not a valid short version address payload" 42 | } 43 | }, 44 | "AddressException": { 45 | "params": ["Invalid Address", "", "full"], 46 | "expected": { 47 | "code": 104, 48 | "message": "'Invalid Address' is not a valid full version address" 49 | } 50 | }, 51 | "CodeHashException": { 52 | "params": ["0x"], 53 | "expected": { 54 | "code": 104, 55 | "message": "'0x' is not a valid code hash" 56 | } 57 | }, 58 | "HashTypeException": { 59 | "params": ["0x03"], 60 | "expected": { 61 | "code": 104, 62 | "message": "'0x03' is not a valid hash type" 63 | } 64 | }, 65 | "AddressFormatTypeException": { 66 | "params": [3], 67 | "expected": { 68 | "code": 104, 69 | "message": "0x03 is not a valid address format type" 70 | } 71 | }, 72 | "AddressFormatTypeAndEncodeMethodNotMatchException": { 73 | "params": [3, "bech32"], 74 | "expected": { 75 | "code": 104, 76 | "message": "Address format type 0x03 doesn't match encode method bech32" 77 | } 78 | }, 79 | "OutLenTooSmallException": { 80 | "params": [16, 32], 81 | "expected": { 82 | "code": 101, 83 | "message": "Expect outlen to be at least 32, but 16 received" 84 | } 85 | }, 86 | "OutLenTooLargeException": { 87 | "params": [32, 16], 88 | "expected": { 89 | "code": 101, 90 | "message": "Expect outlen to be at most 16, but 32 received" 91 | } 92 | }, 93 | "KeyLenTooSmallException": { 94 | "params": [16, 32], 95 | "expected": { 96 | "code": 101, 97 | "message": "Expect key length to be at least 32, but 16 received" 98 | } 99 | }, 100 | "KeyLenTooLargeException": { 101 | "params": [32, 16], 102 | "expected": { 103 | "code": 101, 104 | "message": "Expect key length to be at most 16, but 32 received" 105 | } 106 | }, 107 | "OutTypeException": { 108 | "params": [], 109 | "expected": { 110 | "code": 101, 111 | "message": "Expect out to be \"binary\", \"hex\", Uint8Array, or Buffer" 112 | } 113 | }, 114 | "SaltTypeException": { 115 | "params": [], 116 | "expected": { 117 | "code": 101, 118 | "message": "Expect salt to be Uint8Array or Buffer" 119 | } 120 | }, 121 | "SaltLenException": { 122 | "params": [16, 32], 123 | "expected": { 124 | "code": 101, 125 | "message": "Expect salt length to be 32, but 16 received" 126 | } 127 | }, 128 | "InputTypeException": { 129 | "params": [], 130 | "expected": { 131 | "code": 101, 132 | "message": "Expect input to be Uint8Array or Buffer" 133 | } 134 | }, 135 | "KeyTypeException": { 136 | "params": [], 137 | "expected": { 138 | "code": 101, 139 | "message": "Expect key to be Uint8Array or Buffer" 140 | } 141 | }, 142 | "PersonalTypeException": { 143 | "params": [], 144 | "expected": { 145 | "code": 101, 146 | "message": "Expect PERSONAL to be Uint8Array or Buffer" 147 | } 148 | }, 149 | "PersonalLenException": { 150 | "params": [32, 16], 151 | "expected": { 152 | "code": 101, 153 | "message": "Expect PERSONAL length to be 16, but 32 received" 154 | } 155 | }, 156 | "ReconciliationException": { 157 | "params": [], 158 | "expected": { 159 | "code": 105, 160 | "message": "Fail to reconcile transaction, try to increase extra count or check the transaction" 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/utils/rawTransactionToHash.fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "rawTransactionToHash": [ 3 | { 4 | "rawTransaction": { 5 | "version": "0x0", 6 | "cellDeps": [ 7 | { 8 | "outPoint": { 9 | "txHash": "0xc12386705b5cbb312b693874f3edf45c43a274482e27b8df0fd80c8d3f5feb8b", 10 | "index": "0x0" 11 | }, 12 | "depType": "depGroup" 13 | }, 14 | { 15 | "outPoint": { 16 | "txHash": "0x0fb4945d52baf91e0dee2a686cdd9d84cad95b566a1d7409b970ee0a0f364f60", 17 | "index": "0x2" 18 | }, 19 | "depType": "code" 20 | } 21 | ], 22 | "headerDeps": [], 23 | "inputs": [ 24 | { 25 | "previousOutput": { 26 | "txHash": "0x31f695263423a4b05045dd25ce6692bb55d7bba2965d8be16b036e138e72cc65", 27 | "index": "0x1" 28 | }, 29 | "since": "0x0" 30 | } 31 | ], 32 | "outputs": [ 33 | { 34 | "capacity": "0x174876e800", 35 | "lock": { 36 | "codeHash": "0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88", 37 | "args": "0x59a27ef3ba84f061517d13f42cf44ed020610061", 38 | "hashType": "type" 39 | }, 40 | "type": { 41 | "codeHash": "0xece45e0979030e2f8909f76258631c42333b1e906fd9701ec3600a464a90b8f6", 42 | "args": "0x", 43 | "hashType": "data" 44 | } 45 | }, 46 | { 47 | "capacity": "0x59e1416a5000", 48 | "lock": { 49 | "codeHash": "0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88", 50 | "args": "0x59a27ef3ba84f061517d13f42cf44ed020610061", 51 | "hashType": "type" 52 | }, 53 | "type": null 54 | } 55 | ], 56 | "outputsData": ["0x", "0x"], 57 | "witnesses": [ 58 | "0x82df73581bcd08cb9aa270128d15e79996229ce8ea9e4f985b49fbf36762c5c37936caf3ea3784ee326f60b8992924fcf496f9503c907982525a3436f01ab32900" 59 | ], 60 | "hash": "0x9d1bf801b235ce62812844f01381a070c0cc72876364861e00492eac1d8b54e7" 61 | }, 62 | "expected": "0xe765f9912b06c72552dae11779f6371309236e968aa045ae3b8f426d8ec8ca05" 63 | }, 64 | { 65 | "rawTransaction": { 66 | "version": "0x0", 67 | "cellDeps": [ 68 | { 69 | "outPoint": { 70 | "txHash": "0xace5ea83c478bb866edf122ff862085789158f5cbff155b7bb5f13058555b708", 71 | "index": "0x0" 72 | }, 73 | "depType": "depGroup" 74 | } 75 | ], 76 | "headerDeps": [], 77 | "inputs": [ 78 | { 79 | "since": "0x0", 80 | "previousOutput": { 81 | "txHash": "0xa563884b3686078ec7e7677a5f86449b15cf2693f3c1241766c6996f206cc541", 82 | "index": "0x7" 83 | } 84 | } 85 | ], 86 | "outputs": [ 87 | { 88 | "capacity": "0x2540be400", 89 | "lock": { 90 | "codeHash": "0x709f3fda12f561cfacf92273c57a98fede188a3f1a59b1f888d113f9cce08649", 91 | "hashType": "data", 92 | "args": "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7" 93 | }, 94 | "type": null 95 | }, 96 | { 97 | "capacity": "0x2540be400", 98 | "lock": { 99 | "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 100 | "hashType": "type", 101 | "args": "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7" 102 | }, 103 | "type": null 104 | }, 105 | { 106 | "capacity": "0x2540be400", 107 | "lock": { 108 | "codeHash": "0x709f3fda12f561cfacf92273c57a98fede188a3f1a59b1f888d113f9cce08649", 109 | "hashType": "data1", 110 | "args": "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7" 111 | }, 112 | "type": null 113 | } 114 | ], 115 | "outputsData": ["0x", "0x", "0x"], 116 | "witnesses": [ 117 | "0x550000001000000055000000550000004100000070b823564f7d1f814cc135ddd56fd8e8931b3a7040eaf1fb828adae29736a3cb0bc7f65021135b293d10a22da61fcc64f7cb660bf2c3276ad63630dad0b6099001" 118 | ] 119 | }, 120 | "expected": "0x9110ca9266f89938f09ae6f93cc914b2c856cc842440d56fda6d16ee62543f5c" 121 | } 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /packages/ckb-sdk-utils/__tests__/address/index.test.js: -------------------------------------------------------------------------------- 1 | const ckbUtils = require('../..') 2 | const fixtures = require('./fixtures.json') 3 | 4 | const { 5 | toAddressPayload, 6 | bech32Address, 7 | pubkeyToAddress, 8 | parseAddress, 9 | fullPayloadToAddress, 10 | addressToScript, 11 | scriptToAddress, 12 | } = ckbUtils 13 | 14 | describe('Test address module', () => { 15 | describe('toAddressPayload', () => { 16 | const fixtureTable = Object.entries(fixtures.toAddressPayload).map(([title, { params, expected, exception }]) => [ 17 | title, 18 | params, 19 | expected, 20 | exception, 21 | ]) 22 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 23 | expect.assertions(1) 24 | try { 25 | const actual = toAddressPayload(...params) 26 | expect(actual).toEqual(new Uint8Array(expected)) 27 | } catch (err) { 28 | expect(err).toEqual(new Error(exception)) 29 | } 30 | }) 31 | }) 32 | 33 | describe('fullPayloadToAddress', () => { 34 | const fixtureTable = Object.entries( 35 | fixtures.fullPayloadToAddress, 36 | ).map(([title, { params, expected, exception }]) => [title, params, expected, exception]) 37 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 38 | expect.assertions(1) 39 | try { 40 | const actual = fullPayloadToAddress(...params) 41 | expect(actual).toBe(expected) 42 | } catch (err) { 43 | expect(err).toEqual(new Error(exception)) 44 | } 45 | }) 46 | }) 47 | 48 | describe('bech32Address', () => { 49 | const fixtureTable = Object.entries(fixtures.bech32Address).map(([title, { params, expected, exception }]) => [ 50 | title, 51 | params, 52 | expected, 53 | exception, 54 | ]) 55 | 56 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 57 | expect.assertions(1) 58 | try { 59 | const actual = bech32Address(...params) 60 | expect(actual).toBe(expected) 61 | } catch (err) { 62 | expect(err).toEqual(new Error(exception)) 63 | } 64 | }) 65 | }) 66 | 67 | describe('pubkeyToAddress', () => { 68 | const fixtureTable = Object.entries(fixtures.pubkeyToAddress).map(([title, { params, expected, exception }]) => [ 69 | title, 70 | params, 71 | expected, 72 | exception, 73 | ]) 74 | 75 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 76 | expect.assertions(1) 77 | try { 78 | const actual = pubkeyToAddress(...params) 79 | expect(actual).toBe(expected) 80 | } catch (err) { 81 | expect(err).toEqual(new Error(exception)) 82 | } 83 | }) 84 | }) 85 | 86 | describe('parseAddress', () => { 87 | const fixtureTable = Object.entries(fixtures.parseAddress).map(([title, { params, expected, exception }]) => [ 88 | title, 89 | params, 90 | expected, 91 | exception, 92 | ]) 93 | 94 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 95 | expect.assertions(1) 96 | try { 97 | const actual = parseAddress(...params) 98 | expect(actual).toEqual(typeof expected === 'string' ? expected : new Uint8Array(expected)) 99 | } catch (err) { 100 | expect(err).toEqual(new Error(exception)) 101 | } 102 | }) 103 | }) 104 | 105 | describe('addressToScript', () => { 106 | const fixtureTable = Object.entries(fixtures.addressToScript).map(([title, { params, expected, exception }]) => [ 107 | title, 108 | params, 109 | expected, 110 | exception, 111 | ]) 112 | 113 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 114 | expect.assertions(1) 115 | 116 | try { 117 | const actual = addressToScript(...params) 118 | expect(actual).toEqual(expected) 119 | } catch (err) { 120 | expect(err).toEqual(new Error(exception)) 121 | } 122 | }) 123 | }) 124 | 125 | describe('scriptToAddress', () => { 126 | const fixtureTable = Object.entries(fixtures.scriptToAddress).map(([title, { params, expected, exception }]) => [ 127 | title, 128 | params, 129 | expected, 130 | exception, 131 | ]) 132 | test.each(fixtureTable)(`%s`, (_title, params, expected, exception) => { 133 | expect.assertions(1) 134 | try { 135 | const actual = scriptToAddress(...params) 136 | expect(actual).toEqual(expected) 137 | } catch (err) { 138 | expect(err).toEqual(new Error(exception)) 139 | } 140 | }) 141 | }) 142 | }) 143 | -------------------------------------------------------------------------------- /packages/ckb-sdk-core/__tests__/generateRawTransaction/getInputs/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "should pass when capacity is enough": { 3 | "params": [ 4 | { 5 | "inputScripts": [ 6 | { 7 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 8 | "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 9 | "hashType": "type" 10 | } 11 | ], 12 | "safeMode": false, 13 | "costCapacity": 12200000000, 14 | "unspentCellsMap": [ 15 | [ 16 | "0x641238cd95b9f5e6224da1963a0bcaa2e972dc87e56eb1ded9a35dcebbc37ff4", 17 | [ 18 | { 19 | "capacity": "0x16b969d00", 20 | "data": "0x", 21 | "outPoint": { "txHash": "0x", "index": "0x0" } 22 | }, 23 | { 24 | "capacity": "0x16b969d00", 25 | "data": "0x", 26 | "outPoint": { "txHash": "0x", "index": "0x1" } 27 | }, 28 | { 29 | "capacity": "0x16b969d00", 30 | "data": "0x", 31 | "outPoint": { "txHash": "0x", "index": "0x2" } 32 | } 33 | ] 34 | ] 35 | ] 36 | } 37 | ], 38 | "expected": { 39 | "inputs": [ 40 | { 41 | "lockhash": "0x641238cd95b9f5e6224da1963a0bcaa2e972dc87e56eb1ded9a35dcebbc37ff4", 42 | "previousOutput": { 43 | "txHash": "0x", 44 | "index": "0x0" 45 | }, 46 | "since": "0x0" 47 | }, 48 | { 49 | "lockhash": "0x641238cd95b9f5e6224da1963a0bcaa2e972dc87e56eb1ded9a35dcebbc37ff4", 50 | "previousOutput": { 51 | "txHash": "0x", 52 | "index": "0x1" 53 | }, 54 | "since": "0x0" 55 | } 56 | ], 57 | "sum": 12200000000 58 | } 59 | }, 60 | "non-plain cells should be skipped when safeMode = true": { 61 | "params": [ 62 | { 63 | "inputScripts": [ 64 | { 65 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 66 | "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 67 | "hashType": "type" 68 | } 69 | ], 70 | "safeMode": true, 71 | "costCapacity": 12200000000, 72 | "unspentCellsMap": [ 73 | [ 74 | "0x641238cd95b9f5e6224da1963a0bcaa2e972dc87e56eb1ded9a35dcebbc37ff4", 75 | [ 76 | { 77 | "capacity": "0x16b969d00", 78 | "data": "0x", 79 | "outPoint": { "txHash": "0x", "index": "0x0" } 80 | }, 81 | { 82 | "capacity": "0x16b969d00", 83 | "data": "0xff", 84 | "outPoint": { "txHash": "0x", "index": "0x1" } 85 | }, 86 | { 87 | "capacity": "0x16b969d01", 88 | "data": "0x", 89 | "outPoint": { "txHash": "0x", "index": "0x2" } 90 | } 91 | ] 92 | ] 93 | ] 94 | } 95 | ], 96 | "expected": { 97 | "inputs": [ 98 | { 99 | "lockhash": "0x641238cd95b9f5e6224da1963a0bcaa2e972dc87e56eb1ded9a35dcebbc37ff4", 100 | "previousOutput": { 101 | "txHash": "0x", 102 | "index": "0x0" 103 | }, 104 | "since": "0x0" 105 | }, 106 | { 107 | "lockhash": "0x641238cd95b9f5e6224da1963a0bcaa2e972dc87e56eb1ded9a35dcebbc37ff4", 108 | "previousOutput": { 109 | "txHash": "0x", 110 | "index": "0x2" 111 | }, 112 | "since": "0x0" 113 | } 114 | ], 115 | "sum": 12200000001 116 | } 117 | }, 118 | "should throw an error when capacity is not enough": { 119 | "params": [ 120 | { 121 | "inputScripts": [ 122 | { 123 | "args": "0xe2fa82e70b062c8644b80ad7ecf6e015e5f352f6", 124 | "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 125 | "hashType": "type" 126 | } 127 | ], 128 | "safeMode": false, 129 | "costCapacity": 12200000000, 130 | "unspentCellsMap": [ 131 | [ 132 | "0x641238cd95b9f5e6224da1963a0bcaa2e972dc87e56eb1ded9a35dcebbc37ff4", 133 | [{ "capacity": "0x16b969d00", "data": "0x", "outPoint": { "txHash": "0x", "index": "0x0" } }] 134 | ] 135 | ] 136 | } 137 | ], 138 | "exception": "Input capacity is not enough" 139 | } 140 | } 141 | --------------------------------------------------------------------------------