├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .prettierignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src ├── builders │ ├── builder.ts │ ├── index.ts │ ├── simple-acp-builder.ts │ ├── simple-builder.spec.ts │ ├── simple-builder.ts │ ├── simple-sudt-acp-builder.ts │ ├── simple-sudt-builder.spec.ts │ └── simple-sudt-builder.ts ├── collectors │ ├── collector.ts │ ├── dummy-collector.ts │ ├── index.ts │ ├── pw-collector.ts │ └── sudt-collector.ts ├── constants.ts ├── core.spec.ts ├── core.ts ├── hashers │ ├── blake2b-hasher.spec.ts │ ├── blake2b-hasher.ts │ ├── hasher.ts │ ├── index.ts │ ├── keccak256-hasher.spec.ts │ └── keccak256-hasher.ts ├── index.ts ├── interfaces.ts ├── models │ ├── address.spec.ts │ ├── address.ts │ ├── amount.spec.ts │ ├── amount.spec │ │ ├── add.spec.ts │ │ ├── comp.spec.ts │ │ ├── sub.spec.ts │ │ ├── toBigInt.spec.ts │ │ ├── toHex.spec.ts │ │ └── toString.spec.ts │ ├── amount.ts │ ├── cell-dep.ts │ ├── cell-input.ts │ ├── cell.spec.ts │ ├── cell.ts │ ├── index.ts │ ├── out-point.ts │ ├── raw-transaction.spec.ts │ ├── raw-transaction.ts │ ├── script.spec.ts │ ├── script.ts │ ├── sudt.ts │ ├── transaction.spec.ts │ └── transaction.ts ├── providers │ ├── dummy-provider.ts │ ├── eth-provider.ts │ ├── index.ts │ ├── provider.ts │ └── web3modal-provider.ts ├── signers │ ├── default-signer.ts │ ├── eth-signer.ts │ ├── index.ts │ └── signer.ts ├── types │ └── example.d.ts ├── utils.spec.ts └── utils.ts ├── tea.yaml ├── tsconfig.json ├── tsconfig.module.json └── tslint.json /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Example Contributing Guidelines 2 | 3 | This is an example of GitHub's contributing guidelines file. Check out GitHub's [CONTRIBUTING.md help center article](https://help.github.com/articles/setting-guidelines-for-repository-contributors/) for more information. 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **I'm submitting a ...** 2 | [ ] bug report 3 | [ ] feature request 4 | [ ] question about the decisions made in the repository 5 | [ ] question about how to use this project 6 | 7 | * **Summary** 8 | 9 | 10 | 11 | * **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.) 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 2 | 3 | 4 | 5 | * **What is the current behavior?** (You can also link to an open issue here) 6 | 7 | 8 | 9 | * **What is the new behavior (if this is a feature change)?** 10 | 11 | 12 | 13 | * **Other information**: 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | test 4 | src/**.js 5 | .idea/* 6 | .vscode 7 | 8 | coverage 9 | .nyc_output 10 | *.log 11 | 12 | .editorconfig 13 | 14 | package-lock.json 15 | yarn.lock 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | #tsconfig.json 4 | #tsconfig.module.json 5 | tslint.json 6 | .travis.yml 7 | .github 8 | .prettierignore 9 | .vscode 10 | build/docs 11 | **/*.spec.* 12 | coverage 13 | .nyc_output 14 | *.log 15 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # package.json is formatted by package managers, so we ignore it here 2 | package.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '10' 5 | - '12' 6 | # keep the npm cache to speed up installs 7 | cache: 8 | directories: 9 | - '$HOME/.npm' 10 | after_success: 11 | - npm run cov:send 12 | - npm run cov:check 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [0.3.23](https://github.com/lay2dev/pw-core/compare/v0.3.22...v0.3.23) (2022-06-05) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * update ckb node url ([e4ecd58](https://github.com/lay2dev/pw-core/commit/e4ecd5842e206cdfa83a30c9a7945cf261a720f2)) 11 | 12 | ### [0.3.22](https://github.com/lay2dev/pw-core/compare/v0.3.22-beta.0...v0.3.22) (2020-12-29) 13 | 14 | 15 | ### Features 16 | 17 | * **merge:** merge rc branch ([bab95f3](https://github.com/lay2dev/pw-core/commit/bab95f32d45883e890166e8e9ce22c877ad2737c)) 18 | 19 | ### [0.3.21](https://github.com/lay2dev/pw-core/compare/v0.3.21-beta.0...v0.3.21) (2020-12-10) 20 | 21 | ### [0.3.21](https://github.com/lay2dev/pw-core/compare/v0.3.21-beta.0...v0.3.21) (2020-12-10) 22 | 23 | 24 | ### ⚠ BREAKING CHANGES 25 | 26 | * **transaction, signer:** required parameter 'witnessArgs' is added to Transaction's contructor. the default 27 | values can be found in static attribute WITNESS_ARGS of Builder. 28 | 29 | ### Features 30 | 31 | * **acp:** add acp builder for simple builder ([6b11497](https://github.com/lay2dev/pw-core/commit/6b1149746c9e0e6b30049da1ae76c410dc83b3ab)) 32 | * **address:** add 2 api for address. isAcp & minPaymentAmount ([f7ae547](https://github.com/lay2dev/pw-core/commit/f7ae547f92f58b797c269a77b4bf0b8f49c96807)) 33 | * **amount:** add 'fixed' to FormatOptions, which enables toFixed style format ([93439ea](https://github.com/lay2dev/pw-core/commit/93439ea31ea3b0656b0e8c93add33047fcf88b81)) 34 | * **config:** update sudt config on aggron ([0c81e17](https://github.com/lay2dev/pw-core/commit/0c81e179c39a17b8d0ef2bdcb1e6af1612063102)) 35 | * **provider:** create a new provider to support wallet connect ([07d7df8](https://github.com/lay2dev/pw-core/commit/07d7df8665f273f89709b0279ee53478c5b0ef79)) 36 | * **pw-collector:** now we can pass the api base url to PwCollector in constructor ([80cf09c](https://github.com/lay2dev/pw-core/commit/80cf09c5a5b0f1b22468b35143bcc16ce942af96)) 37 | * **sudt:** finish sudt create acp builder ([55863d9](https://github.com/lay2dev/pw-core/commit/55863d9b10bcacd039722afc9090e6668a12e136)) 38 | * **sudt-builder:** add simple sudt create acp ([31f54ec](https://github.com/lay2dev/pw-core/commit/31f54ec459052dab6765466078bc04aa259c783b)) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * **amount:** change all BigInto to JSBI.BigInt in utils.ts ([777bba9](https://github.com/lay2dev/pw-core/commit/777bba9db6daeb4ad3501e8a7beb627333f73a1e)) 44 | * **eth-signer:** remove test privkeys and ecsign, use sendAsync ([129deba](https://github.com/lay2dev/pw-core/commit/129deba2f1c2ad31df4910b24cfdb11f4e752b11)) 45 | * **package.json:** bump version to 0.2.5 ([e7ee1d8](https://github.com/lay2dev/pw-core/commit/e7ee1d86bec850ca198664395e0c3cdc28f6b6eb)) 46 | * **sudt:** fix tx build bugs: sudt amount not change ([838fbcd](https://github.com/lay2dev/pw-core/commit/838fbcd1f862b8ac5fcd1d8ab9e8d0d598fdbc37)) 47 | * **sudtcollector:** make pwcollecotr extends from sudtcollector ([065bc5d](https://github.com/lay2dev/pw-core/commit/065bc5d897052b9961325bbf9b56497b1cf6f87b)) 48 | * **utils, script:** fix generateAddress and parseAddress, more test for script.toAddress ([7509e0a](https://github.com/lay2dev/pw-core/commit/7509e0a155f59094ea6f1c63b5c0275851cee93c)) 49 | 50 | 51 | * **transaction, signer:** add WitnessArgs to transaction ([e42d02d](https://github.com/lay2dev/pw-core/commit/e42d02d25c8d605b318ce28147acbb82bb33a1d6)) 52 | 53 | ### [0.2.1](https://github.com/lay2dev/pw-core/compare/v0.2.0...v0.2.1) (2020-06-21) 54 | 55 | Update docs 56 | 57 | ## [0.2.0](https://github.com/lay2dev/pw-core/compare/v0.1.4...v0.2.0) (2020-06-17) 58 | 59 | ### ⚠ BREAKING CHANGES 60 | 61 | - **eth-signer:** Now sendAsync is on and wallets like MetaMask can be used to sign transactions. 62 | 63 | ### Bug Fixes 64 | 65 | - **eth-signer:** change from ecsign to sendAsync for prod ([3a91746](https://github.com/lay2dev/pw-core/commit/3a917469d3b8594ac64446ab912af700ea6ec960)) 66 | 67 | ### [0.1.4](https://github.com/lay2dev/pw-core/compare/v0.1.1...v0.1.4) (2020-06-16) 68 | 69 | ### Features 70 | 71 | - **release 0.1.0:** release alpha ([8814bdc](https://github.com/lay2dev/pw-core/commit/8814bdc4f33b3966c539cce632d34339ff6ddca7)) 72 | 73 | ### 0.1.1 (2020-06-15) 74 | 75 | ### Features 76 | 77 | - **models:** add more methods to CKBModel ([0c0669a](https://github.com/lay2dev/ckb-pw-core/commit/0c0669a15fd41027c943fd6caae0b7d1b89d7065)) 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 zhixian 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pw-core: A Friendly and Powerful SDK for CKB dApps 2 | 3 | > pw-core is the front-end sdk of [pw-sdk](https://talk.nervos.org/t/lay2-pw-sdk-build-dapps-on-ckb-and-run-them-everywhere/4289) 4 | 5 | ## Quick Start 6 | 7 | ### Installation 8 | 9 | You can install pw-core to your project with **npm** 10 | 11 | ```bash 12 | # in your project root 13 | $ npm install @lay2/pw-core --save 14 | ``` 15 | 16 | Or with **yarn** 17 | 18 | ```bash 19 | # in your project root 20 | $ yarn add @lay2/pw-core 21 | ``` 22 | 23 | ### Hello World 24 | 25 | Let's see how to send CKB with pw-core. 26 | 27 | ```javascript 28 | import PWCore, { 29 | EthProvider, 30 | PwCollector, 31 | ChainID, 32 | Address, 33 | Amount, 34 | AddressType, 35 | } from '@lay2/pw-core'; 36 | 37 | // insdie an async scope 38 | 39 | const pwcore = await new PWCore('https://ckb-node-url').init( 40 | new EthProvider(), // a built-in Provider for Ethereum env. 41 | new PwCollector() // a custom Collector to retrive cells from cache server. 42 | ); 43 | 44 | const txHash = await pwcore.send( 45 | new Address('0x26C5F390FF2033CbB44377361c63A3Dd2DE3121d', AddressType.eth), 46 | new Amount('100') 47 | ); 48 | ``` 49 | 50 | That's it! If CKB transaction (with Ethereum wallets, e.g. MetaMask) is the only thing you need, you can already start your integration with pw-core. 51 | 52 | ### One Step Further 53 | 54 | However, if you need more features, such as adding multiple outputs, setting data, or adding custom lock/type scripts, you can always implement you own builder extends the `Builder` class. If you have more requirements with retriving unspent cells, a custom cell collector based on `Collector` is a good choice. The same approach applies to `Signer` / `Hasher` / `Provider`. In fact, you will find that almost every aspect of buiding a transaction can be customized to meet your demands. This is because we have well encapsulated the transaction process as **build -> sign -> send**, and any kind of transaction can be created and sent given a builder and a signer. For example, the basic `send` method used in the Hello World example is implented like this: 55 | 56 | ```typescript 57 | // code from: https://github.com/lay2dev/pw-core/blob/master/src/core.ts#L80 58 | 59 | import { transformers } from 'ckb-js-toolkit' 60 | import { Address, Amount } from './models' 61 | import { SimpleBuilder } from './builders' 62 | import { EthSigner } from './signers' 63 | 64 | async send(address: Address, amount: Amount, feeRate?: number): Promise { 65 | const simpleBuilder = new SimpleBuilder(address, amount, feeRate); 66 | const = new EthSigner(address.addressString); 67 | return this.sendTransaction(simpleBuilder, ethSigner); 68 | } 69 | 70 | async sendTransaction(builder: Builder, signer: Signer): Promise { 71 | return this.rpc.send_transaction( 72 | transformers.TransformTransaction( 73 | await signer.sign((await builder.build()).validate()) 74 | ) 75 | ); 76 | } 77 | ``` 78 | 79 | Finally, here is an [example project](https://github.com/lay2dev/simplestdapp) which shows how to implement custom classes to achieve certain features. The `SDCollector` can collect unspent cells with a [ckb-indexer](https://github.com/quake/ckb-indexer), while the `SDBuilder` can build transactions for creating / updating / deleting cells. More over, the built-in `EthProvider` and `EthSigner` (along with `Keccak256Hasher`) are used to make this dApp runnable in Ethereum enviromment (such as MetaMask). 80 | 81 | ## Highlights 82 | 83 | - ### Concise API 84 | 85 | We only provide two types of interfaces, one is the most commonly used and the other is fully customizable. For example, you can use `pwcore.send` to just send some CKB, and use `pwcore.sendTransaction` to send any type of transactions. More over, you can even use `pwcore.rpc` to get direct access to ckb rpc calls, which enables abilities far beyond sending transactions. 86 | 87 | * ### Thoughtful Models 88 | 89 | Talk is cheap, let's show some code. 90 | 91 | ```typescript 92 | /* ------ Address ------ */ 93 | 94 | /* create an Address instance from a CKB address */ 95 | const ckbAddress = new Address('ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq', AddressType.ckb); 96 | 97 | /* create an Address instance from an Ethereum address */ 98 | const ethAddress = new Address('0x308f27c8595b2ee9e6a5faa875b4c1f9de6b679a', AddressType.eth); 99 | 100 | /* get the original address string */ 101 | console.log('ckb: ', ckbAddress.addressString) 102 | console.log('eth: ', ethAddress.addressString) 103 | // ckb: ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq 104 | // eth: 0x308f27c8595b2ee9e6a5faa875b4c1f9de6b679a 105 | 106 | /* get the corresponding CKB address */ 107 | console.log('ckb: ', ckbAddress.toCKBAddress()) 108 | console.log('eth: ', ethAddress.toCKBAddress()) 109 | // ckb: ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq 110 | // eth: ckt1q3vvtay34wndv9nckl8hah6fzzcltcqwcrx79apwp2a5lkd07fdxxvy0yly9jkewa8n2t74gwk6vr7w7ddne5jrkf6c 111 | 112 | /* get the corresponding lock script hash (with the toHash method of class Script) */ 113 | console.log('ckb: ', ethAddress.toLockScript().toHash()) 114 | console.log('eth: ', ethAddress.toLockScript().toHash()) 115 | // ckb: 0xe9e412caf497c69e9612d305be13f9173752b9e75bc5a9b6d1ca51eb38d07d59 116 | // eth: 0x0963476f28975bf93da673cd2442bd69c4b2d4e720af5a67ecece8a03b8926b5 117 | 118 | /* check if the address is an ACP address */ 119 | console.log('ckb: ', ckbAddress.isAcp()) 120 | console.log('eth: ', ethAddress.isAcp()) 121 | // false 122 | // true 123 | 124 | // get the minimal CKB amount (an Amount instance) you can transfer to this address 125 | console.log('ckb: ', ckbAddress.minPaymentAmount().toString() + ' CKB') 126 | console.log('eth: ', ethAddress.minPaymentAmount().toString() + ' CKB') 127 | // 61 CKB 128 | // 0.00000001 CKB 129 | 130 | /* ------ Script ------ */ 131 | 132 | const lockScript = addressCkb.toLockScript(); 133 | const lockScriptHash = lockScript.toHash(); 134 | const address1 = Address.fromLockScript(lockScript); 135 | const address2 = lockScript.toAddress(); 136 | 137 | console.log(addressEth.toLockScript().sameWith(addressCkbFull.toLockScript())); 138 | //true 139 | 140 | /* ------ Amount ------ */ 141 | 142 | const ckb100 = new Amount('100'); 143 | const shannon100 = new Amount('100', AmountUnit.shannon); 144 | const usdt = new Amount('1234.5678', 6); // Assume usdt's decimals is 6 145 | 146 | /* format */ 147 | 148 | console.log(`${ckb100.toString()} CKB is ${ckb100.toString(AmountUnit.shannon, {commify: true})} Shannon`); 149 | // 100 CKB is 1,000,000 Shannon 150 | 151 | console.log(`${shannon100.toString(AmountUnit.shannon)} Shannon is ${shannon100.toString()} CKB`) 152 | // 100 Shannon is 0.000001 CKB 153 | 154 | console.log(`${usdt.toString(6, {fixed: 2, commify: true})} USDT is rounded from ${usdt.toString(6, {pad: true})} USDT`); 155 | // 1,234.57 USDT is rounded from 1234.567800 USDT 156 | 157 | /* compare */ 158 | 159 | console.log('100 CKB is greater than 100 Shannon: ', ckb100.gt(shannon100)); 160 | console.log('100 CKB is less than 100 Shannon: ', ckb100.lt(shannon100)); 161 | // 100 CKB is greater than 100 Shannon: true 162 | // 100 CKB is less than 100 Shannon: false 163 | 164 | console.log('100 Shannon is equal to 0.000001 CKB: ', shannon100.eq(new Amount('0.000001'))); 165 | // 100 Shannon is equal to 0.000001 CKB: true 166 | 167 | /* calculate */ 168 | 169 | console.log(`100 CKB + 100 Shannon = ${ckb100.add(shannon100).toString()} CKB`); 170 | console.log(`100 CKB - 100 Shannon = ${ckb100.sub(shannon100).toString()} CKB`); 171 | // 100 CKB + 100 Shannon = 100.000001 CKB 172 | // 100 CKB - 100 Shannon = 99.999999 CKB 173 | 174 | // Amount is assumed with unit, so if we want to perform multiplication or division, the best way is to convert the Amount instance to JSBI BigInt, and convert back to Amount instance if necessary. 175 | const bn = JSBI.mul(ckb100.toBigInt(), JSBI.BigInt(10)); 176 | const amount = new Amount(bn.toString()) 177 | console.log(`100 CKB * 10 = ${amount.toString(AmountUnit.ckb, {commify: true})} CKB`); 178 | // 100 CKB * 10 = 1,000 CKB 179 | 180 | /* ------ Cell ------ */ 181 | 182 | /* load from blockchain with a rpc instance and an outpoint */ 183 | const cell1 = Cell.loadFromBlockchain(rpc, outpoint); 184 | 185 | /* convert rpc formated data to a Cell instance */ 186 | const cell2 = Cell.fromRPC(rpcData); 187 | 188 | /* check how many capacity is occupied by scripts and data */ 189 | const occupiedCapacity = cell1.occupiedCapacity(); 190 | 191 | /* check if the cell's capacity is enough for the actual size */ 192 | cell1.spaceCheck(); 193 | 194 | /* check if the cell is empty (no data is stored) */ 195 | cell2.isEmpty() 196 | 197 | /* adjust the capacity to the minimal value of this cell */ 198 | cell2.resize(); 199 | 200 | /* set / get amount of an sUDT cell */ 201 | const cell3 = cell2.clone(); 202 | cell3.setSUDTAmount(new Amount(100)); 203 | console.log('sUDT amount: ', cell3.getSUDTAmount().toString()); 204 | // sUDT amount: 100 205 | 206 | /* playing with data */ 207 | cell1.setData('data'); 208 | cell2.setHexData('0x64617461'); 209 | console.log('data of cell 1: ', cell1.getData()); 210 | console.log('data of cell 2: ', cell1.getData()); 211 | console.log('hex data of cell 1: ', cell1.getHexData()); 212 | // data of cell 1: data 213 | // data of cell 2: data 214 | // hex data of cell 1: 0x64617461 215 | ``` 216 | 217 | - ### Simple and Clear Structure 218 | 219 | CKB dApp development is mostly about manipulating cells. However, when we actually try to design a dApp, it turns out that we are always dealing with transactions. If you ever tried to build a CKB transaction, you'll definitely be impressed (or most likely, confused) by the bunch of fields. Questions may be asked like: 220 | 221 | > There are data structures like 'CellInput' and 'CellOutput', but where is 'Cell' ? 222 | > 223 | > How to calculate the transaction fee? How to adjust the change output? 224 | > 225 | > What kind of unit ( CKB or Shannon) and format (mostly hex string) to use? 226 | 227 | Things are different with pw-core. Let's see the actual constructor of `RawTransaction` 228 | 229 | ```typescript 230 | // code from: https://github.com/lay2dev/pw-core/blob/master/src/models/raw-transaction.ts#L7 231 | 232 | export class RawTransaction implements CKBModel { 233 | constructor( 234 | public inputCells: Cell[], 235 | public outputs: Cell[], 236 | public cellDeps: CellDep[] = [ 237 | PWCore.config.defaultLock.cellDep, 238 | PWCore.config.pwLock.cellDep, 239 | ], 240 | public headerDeps: string[] = [], 241 | public readonly version: string = '0x0' 242 | ) { 243 | this.inputs = inputCells.map((i) => i.toCellInput()); 244 | this.outputsData = this.outputs.map((o) => o.getHexData()); 245 | } 246 | 247 | // ... 248 | } 249 | ``` 250 | 251 | It's easy to findout that both inputs and outputs are Array of cells, and the low-level details and transformations are done silently. And yes, we have the 'Cell' structure. 252 | 253 | - ### Work With or Without pw-lock 254 | 255 | Although pw-core works closely with pw-lock (both projects are components of pw-sdk), you can still use the default blake2b lock or your own lock, as long as the corresponding builder, signer and hasher are implemented. In fact, the `Blake2bHasher` is already built-in, and a `CkbSigner` is very likely to be added in the near future. With the progress of pw-sdk, more algorithm be added to the built-in collections, such as `Sha256Hasher` and `P256Signer`. 256 | 257 | ## API Document 258 | 259 | You can find a detailed [API Document Here](https://docs.lay2.dev). 260 | 261 | ## Get Involved 262 | 263 | Currently pw-core is still at a very early stage, and all kinds of suggestions and bug reports are very much welcomed. Feel free to open an issue, or join our [Discord](https://discord.gg/ZuFQGCx) server to talk directly to us. And of couse, a **star** is very much appreciated :). 264 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lay2/pw-core", 3 | "version": "0.3.23", 4 | "description": "the javascript sdk of pw-sdk", 5 | "main": "build/main/index.js", 6 | "typings": "build/main/index.d.ts", 7 | "module": "build/module/index.js", 8 | "repository": "https://github.com/lay2dev/pw-core", 9 | "license": "MIT", 10 | "keywords": [], 11 | "scripts": { 12 | "describe": "npm-scripts-info", 13 | "build": "run-s clean && run-p build:*", 14 | "build:main": "tsc -p tsconfig.json", 15 | "build:module": "tsc -p tsconfig.module.json", 16 | "fix": "run-s fix:*", 17 | "fix:prettier": "prettier \"src/**/*.ts\" --write", 18 | "fix:tslint": "tslint --fix --project .", 19 | "test": "run-s build test:*", 20 | "test:lint": "tslint --project . && prettier \"src/**/*.ts\" --list-different", 21 | "test:unit": "nyc --silent ava", 22 | "watch": "run-s clean build:main && run-p \"build:main -- -w\" \"test:unit -- --watch\"", 23 | "cov": "run-s build test:unit cov:html && open-cli coverage/index.html", 24 | "cov:html": "nyc report --reporter=html", 25 | "cov:send": "nyc report --reporter=lcov && codecov", 26 | "cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100", 27 | "doc": "run-s doc:html && open-cli build/docs/index.html", 28 | "doc:html": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --out build/docs", 29 | "doc:json": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --json build/docs/typedoc.json", 30 | "doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs", 31 | "version": "standard-version", 32 | "reset": "git clean -dfx && git reset --hard && npm i", 33 | "clean": "trash build test", 34 | "prepare-release": "run-s reset test cov:check doc:html version doc:publish" 35 | }, 36 | "scripts-info": { 37 | "info": "Display information about the package scripts", 38 | "build": "Clean and rebuild the project", 39 | "fix": "Try to automatically fix any linting problems", 40 | "test": "Lint and unit test the project", 41 | "watch": "Watch and rebuild the project on save, then rerun relevant tests", 42 | "cov": "Rebuild, run tests, then create and open the coverage report", 43 | "doc": "Generate HTML API documentation and open it in a browser", 44 | "doc:json": "Generate API documentation in typedoc JSON format", 45 | "version": "Bump package.json version, update CHANGELOG.md, tag release", 46 | "reset": "Delete all untracked files and reset the repo to the last commit", 47 | "prepare-release": "One-step: clean, build, test, publish docs, and prep a release" 48 | }, 49 | "engines": { 50 | "node": ">=8.9" 51 | }, 52 | "dependencies": { 53 | "@ckb-lumos/types": "^0.2.4", 54 | "@nervosnetwork/ckb-sdk-utils": "^0.32.0", 55 | "axios": "^0.19.2", 56 | "bech32": "^1.1.4", 57 | "blake2b": "^2.1.3", 58 | "ckb-js-toolkit": "^0.9.1", 59 | "decimal.js": "^10.2.1", 60 | "ethereum-ens": "^0.8.0", 61 | "ethereumjs-util": "^7.0.2", 62 | "jsbi": "^3.1.2", 63 | "keccak": "^3.0.0" 64 | }, 65 | "devDependencies": { 66 | "@bitjson/npm-scripts-info": "^1.0.0", 67 | "@istanbuljs/nyc-config-typescript": "^1.0.1", 68 | "@types/keccak": "^3.0.1", 69 | "ava": "3.8.2", 70 | "codecov": "^3.6.5", 71 | "cz-conventional-changelog": "^3.1.0", 72 | "gh-pages": "^2.2.0", 73 | "npm-run-all": "^4.1.5", 74 | "nyc": "^15.0.0", 75 | "open-cli": "^6.0.1", 76 | "prettier": "^2.0.2", 77 | "standard-version": "^8.0.1", 78 | "trash-cli": "^3.0.0", 79 | "tslint": "^6.1.0", 80 | "tslint-config-prettier": "^1.18.0", 81 | "tslint-immutable": "^6.0.1", 82 | "typedoc": "^0.17.7", 83 | "typescript": "^3.8.3" 84 | }, 85 | "ava": { 86 | "failFast": true, 87 | "files": [ 88 | "build/main/**/*.spec.js" 89 | ], 90 | "ignoredByWatcher": [ 91 | "build/main/**/*.js" 92 | ], 93 | "nodeArguments": [ 94 | "--experimental-modules" 95 | ] 96 | }, 97 | "config": { 98 | "commitizen": { 99 | "path": "node_modules/cz-conventional-changelog" 100 | } 101 | }, 102 | "prettier": { 103 | "singleQuote": true, 104 | "semi": true 105 | }, 106 | "nyc": { 107 | "extends": "@istanbuljs/nyc-config-typescript", 108 | "exclude": [ 109 | "**/*.spec.js" 110 | ] 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/builders/builder.ts: -------------------------------------------------------------------------------- 1 | import { Collector } from '../collectors/collector'; 2 | import { Amount, AmountUnit, Transaction } from '../models'; 3 | import PWCore from '..'; 4 | import { SUDTCollector } from '../collectors/sudt-collector'; 5 | 6 | const FEE_BASE = 1000; 7 | 8 | export abstract class Builder { 9 | static readonly MIN_FEE_RATE = 1000; 10 | static readonly MIN_CHANGE = new Amount('61', AmountUnit.ckb); 11 | static readonly WITNESS_ARGS = { 12 | Secp256k1: { 13 | lock: '0x' + '0'.repeat(130), 14 | input_type: '', 15 | output_type: '', 16 | }, 17 | Secp256r1: { 18 | lock: '0x' + '0'.repeat(600), 19 | input_type: '', 20 | output_type: '', 21 | }, 22 | }; 23 | 24 | static calcFee( 25 | tx: Transaction, 26 | feeRate: number = Builder.MIN_FEE_RATE 27 | ): Amount { 28 | if (feeRate < Builder.MIN_FEE_RATE) { 29 | feeRate = Builder.MIN_FEE_RATE; 30 | } 31 | const txSize = tx.getSize(); 32 | const fee = (feeRate / FEE_BASE) * txSize; 33 | return new Amount(fee.toString(), AmountUnit.shannon); 34 | } 35 | 36 | protected fee: Amount; 37 | 38 | protected constructor( 39 | protected feeRate: number = Builder.MIN_FEE_RATE, 40 | protected collector: Collector | SUDTCollector = PWCore.defaultCollector 41 | ) {} 42 | 43 | getFee(): Amount { 44 | return this.fee; 45 | } 46 | 47 | abstract async build(): Promise; 48 | } 49 | -------------------------------------------------------------------------------- /src/builders/index.ts: -------------------------------------------------------------------------------- 1 | export * from './builder'; 2 | export * from './simple-builder'; 3 | export * from './simple-sudt-acp-builder'; 4 | export * from './simple-sudt-builder'; 5 | -------------------------------------------------------------------------------- /src/builders/simple-acp-builder.ts: -------------------------------------------------------------------------------- 1 | import { Builder } from '../builders/builder'; 2 | import { Collector } from '../collectors/collector'; 3 | import { 4 | Address, 5 | Amount, 6 | AmountUnit, 7 | Cell, 8 | RawTransaction, 9 | Transaction, 10 | } from '../models'; 11 | import PWCore from '..'; 12 | 13 | export class SimpleACPBuilder extends Builder { 14 | receiverInputCell: Cell; 15 | receiverOutputCell: Cell; 16 | 17 | constructor( 18 | protected address: Address, 19 | protected amount: Amount, 20 | feeRate?: number, 21 | collector?: Collector 22 | ) { 23 | super(feeRate, collector); 24 | } 25 | 26 | async build(): Promise { 27 | if (!this.address.isAcp()) { 28 | throw new Error("The Receiver's address is not anyone-can-pay cell"); 29 | } 30 | 31 | const receiverACPCells = await this.collector.collect(this.address, { 32 | neededAmount: new Amount('1', AmountUnit.shannon), 33 | }); 34 | if (!receiverACPCells || receiverACPCells.length === 0) { 35 | throw new Error('The receiver has no sudt cell'); 36 | } 37 | 38 | this.receiverInputCell = receiverACPCells[0]; 39 | this.receiverOutputCell = this.receiverInputCell.clone(); 40 | 41 | this.receiverOutputCell.capacity = this.receiverOutputCell.capacity.add( 42 | this.amount 43 | ); 44 | return this.buildSenderCells(); 45 | } 46 | 47 | async buildSenderCells(fee: Amount = Amount.ZERO): Promise { 48 | const neededAmount = this.amount.add(Builder.MIN_CHANGE).add(fee); 49 | let inputSum = new Amount('0'); 50 | const inputCells: Cell[] = []; 51 | 52 | // fill the inputs 53 | const cells = await this.collector.collect(PWCore.provider.address, { 54 | neededAmount, 55 | }); 56 | for (const cell of cells) { 57 | inputCells.push(cell); 58 | inputSum = inputSum.add(cell.capacity); 59 | if (inputSum.gt(neededAmount)) break; 60 | } 61 | 62 | if (inputSum.lt(neededAmount)) { 63 | throw new Error( 64 | `input capacity not enough, need ${neededAmount.toString( 65 | AmountUnit.ckb 66 | )}, got ${inputSum.toString(AmountUnit.ckb)}` 67 | ); 68 | } 69 | 70 | const changeCell = new Cell( 71 | inputSum.sub(this.amount), 72 | PWCore.provider.address.toLockScript() 73 | ); 74 | 75 | const tx = new Transaction( 76 | new RawTransaction( 77 | [...inputCells, this.receiverInputCell], 78 | [this.receiverOutputCell, changeCell] 79 | ), 80 | [Builder.WITNESS_ARGS.Secp256k1] 81 | ); 82 | 83 | this.fee = Builder.calcFee(tx, this.feeRate); 84 | 85 | if (changeCell.capacity.gte(Builder.MIN_CHANGE.add(this.fee))) { 86 | changeCell.capacity = changeCell.capacity.sub(this.fee); 87 | tx.raw.outputs.pop(); 88 | tx.raw.outputs.push(changeCell); 89 | return tx; 90 | } 91 | 92 | return this.buildSenderCells(this.fee); 93 | } 94 | 95 | getCollector() { 96 | return this.collector; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/builders/simple-builder.spec.ts: -------------------------------------------------------------------------------- 1 | import anyTest, { TestInterface } from 'ava'; 2 | import PWCore, { Address, AddressType, Amount, ChainID } from '..'; 3 | import { SimpleBuilder } from '.'; 4 | import { DummyCollector } from '../collectors/dummy-collector'; 5 | import { DummyProvider } from '../providers/dummy-provider'; 6 | 7 | const test = anyTest as TestInterface<{ builder: SimpleBuilder }>; 8 | 9 | test.before(async (t) => { 10 | const address = new Address( 11 | 'ckt1qyqxpayn272n8km2k08hzldynj992egs0waqnr8zjs', 12 | AddressType.ckb 13 | ); 14 | const amount = new Amount('100'); 15 | 16 | await new PWCore('https://testnet.ckb.dev').init( 17 | new DummyProvider(), 18 | new DummyCollector(), 19 | ChainID.ckb_testnet 20 | ); 21 | 22 | t.context.builder = new SimpleBuilder(address, amount); 23 | }); 24 | 25 | test('build a tx', async (t) => { 26 | const tx = await t.context.builder.build(); 27 | t.notThrows(() => tx.validate()); 28 | }); 29 | 30 | test.todo('calc fee'); 31 | -------------------------------------------------------------------------------- /src/builders/simple-builder.ts: -------------------------------------------------------------------------------- 1 | import { Builder } from '../builders/builder'; 2 | import { Collector } from '../collectors/collector'; 3 | import { 4 | Address, 5 | Amount, 6 | AmountUnit, 7 | Cell, 8 | RawTransaction, 9 | Transaction, 10 | } from '../models'; 11 | import PWCore from '..'; 12 | import { SimpleACPBuilder } from './simple-acp-builder'; 13 | 14 | export class SimpleBuilder extends Builder { 15 | simpleACPBuilder: Builder; 16 | 17 | constructor( 18 | private address: Address, 19 | private amount: Amount, 20 | feeRate?: number, 21 | collector?: Collector 22 | ) { 23 | super(feeRate, collector); 24 | this.simpleACPBuilder = new SimpleACPBuilder( 25 | this.address, 26 | this.amount, 27 | this.feeRate, 28 | this.collector 29 | ); 30 | } 31 | 32 | async build(fee: Amount = Amount.ZERO): Promise { 33 | if (this.amount.lt(Builder.MIN_CHANGE)) { 34 | return this.simpleACPBuilder.build(); 35 | } 36 | 37 | const outputCell = new Cell(this.amount, this.address.toLockScript()); 38 | const neededAmount = this.amount.add(Builder.MIN_CHANGE).add(fee); 39 | let inputSum = new Amount('0'); 40 | const inputCells: Cell[] = []; 41 | 42 | // fill the inputs 43 | const cells = await this.collector.collect(PWCore.provider.address, { 44 | neededAmount, 45 | }); 46 | for (const cell of cells) { 47 | inputCells.push(cell); 48 | inputSum = inputSum.add(cell.capacity); 49 | if (inputSum.gt(neededAmount)) break; 50 | } 51 | 52 | if (inputSum.lt(neededAmount)) { 53 | throw new Error( 54 | `input capacity not enough, need ${neededAmount.toString( 55 | AmountUnit.ckb 56 | )}, got ${inputSum.toString(AmountUnit.ckb)}` 57 | ); 58 | } 59 | 60 | const changeCell = new Cell( 61 | inputSum.sub(outputCell.capacity), 62 | PWCore.provider.address.toLockScript() 63 | ); 64 | 65 | const tx = new Transaction( 66 | new RawTransaction(inputCells, [outputCell, changeCell]), 67 | [Builder.WITNESS_ARGS.Secp256k1] 68 | ); 69 | 70 | this.fee = Builder.calcFee(tx, this.feeRate); 71 | 72 | if (changeCell.capacity.gte(Builder.MIN_CHANGE.add(this.fee))) { 73 | changeCell.capacity = changeCell.capacity.sub(this.fee); 74 | tx.raw.outputs.pop(); 75 | tx.raw.outputs.push(changeCell); 76 | return tx; 77 | } 78 | 79 | return this.build(this.fee); 80 | } 81 | 82 | getCollector() { 83 | return this.collector; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/builders/simple-sudt-acp-builder.ts: -------------------------------------------------------------------------------- 1 | import { Builder } from './builder'; 2 | import { 3 | Address, 4 | Amount, 5 | AmountUnit, 6 | Cell, 7 | RawTransaction, 8 | Transaction, 9 | SUDT, 10 | } from '../models'; 11 | import PWCore from '..'; 12 | import { SUDTCollector } from '../collectors/sudt-collector'; 13 | 14 | export class SimpleSUDTACPBuilder extends Builder { 15 | constructor( 16 | private sudt: SUDT, 17 | private address: Address, 18 | private amount: Amount, 19 | feeRate?: number, 20 | collector?: SUDTCollector 21 | ) { 22 | super(feeRate, collector); 23 | } 24 | 25 | async build(): Promise { 26 | if (!this.address.isAcp()) { 27 | throw new Error("The receiver's address is not anyone-can-pay cell"); 28 | } 29 | if (!(this.collector instanceof SUDTCollector)) { 30 | throw new Error('this.collector is not a SUDTCollector instance'); 31 | } 32 | 33 | const receiverSUDTCells = await this.collector.collectSUDT( 34 | this.sudt, 35 | this.address, 36 | { neededAmount: new Amount('1', AmountUnit.shannon) } 37 | ); 38 | if (!receiverSUDTCells || receiverSUDTCells.length === 0) { 39 | throw new Error('The receiver has no sudt cell'); 40 | } 41 | 42 | const receiverInputCell = receiverSUDTCells[0]; 43 | const receiverOuputCell = receiverInputCell.clone(); 44 | 45 | receiverOuputCell.setSUDTAmount( 46 | this.amount.add(receiverOuputCell.getSUDTAmount()) 47 | ); 48 | 49 | let senderInputSUDTSum = new Amount('0'); 50 | let senderInputCKBSum = new Amount('0'); 51 | let minSenderOccupiedCKBSum = new Amount('0'); 52 | 53 | let restNeededSUDT = new Amount( 54 | this.amount.toHexString(), 55 | AmountUnit.shannon 56 | ); 57 | 58 | const inputCells: Cell[] = []; 59 | const outputCells: Cell[] = []; 60 | 61 | // fill the inputs and the outputs 62 | const unspentSUDTCells = await this.collector.collectSUDT( 63 | this.sudt, 64 | PWCore.provider.address, 65 | { neededAmount: this.amount } 66 | ); 67 | 68 | // First step: build a tx including sender and receiver sudt cell only 69 | for (const inputCell of unspentSUDTCells) { 70 | const outputCell = inputCell.clone(); 71 | 72 | const inputSUDTAmount = inputCell.getSUDTAmount(); 73 | senderInputSUDTSum = senderInputSUDTSum.add(inputSUDTAmount); 74 | senderInputCKBSum = senderInputCKBSum.add(inputCell.capacity); 75 | minSenderOccupiedCKBSum = minSenderOccupiedCKBSum.add( 76 | outputCell.occupiedCapacity() 77 | ); 78 | 79 | if (inputSUDTAmount.lt(restNeededSUDT)) { 80 | restNeededSUDT = restNeededSUDT.sub(inputSUDTAmount); 81 | outputCell.setSUDTAmount(new Amount('0')); 82 | } else { 83 | outputCell.setSUDTAmount(inputSUDTAmount.sub(restNeededSUDT)); 84 | restNeededSUDT = new Amount('0'); 85 | } 86 | 87 | inputCells.push(inputCell); 88 | outputCells.unshift(outputCell); 89 | 90 | if (senderInputSUDTSum.gte(this.amount)) break; 91 | } 92 | 93 | if (senderInputSUDTSum.lt(this.amount)) { 94 | throw new Error( 95 | `input sudt amount not enough, need ${this.amount.toString( 96 | AmountUnit.ckb 97 | )}, got ${senderInputSUDTSum.toString(AmountUnit.ckb)}` 98 | ); 99 | } 100 | 101 | inputCells.push(receiverInputCell); 102 | outputCells.unshift(receiverOuputCell); 103 | 104 | let tx = this.rectifyTx(inputCells, outputCells); 105 | 106 | const availableCKBFee = senderInputCKBSum.sub(minSenderOccupiedCKBSum); 107 | 108 | // Second step: if sudt cell can not pay the transaction fee, fetch pure ckb cells to pay the fee. 109 | if (this.fee.gt(availableCKBFee)) { 110 | const unspentCKBCells = await this.collector.collect( 111 | PWCore.provider.address, 112 | { neededAmount: this.fee.sub(availableCKBFee).add(Builder.MIN_CHANGE) } 113 | ); 114 | 115 | if (!unspentCKBCells || unspentCKBCells.length === 0) { 116 | throw new Error('not enough CKB to pay the transaction fee'); 117 | } 118 | 119 | // append the fee cell to tx's inputs and outputs 120 | const ckbFeeInputCell = unspentCKBCells[0]; 121 | inputCells.push(ckbFeeInputCell); 122 | outputCells.push(ckbFeeInputCell.clone()); 123 | 124 | tx = this.rectifyTx(inputCells, outputCells); 125 | 126 | // if fee change cell's capacity less than occuiped capacity, merge the fee cell to sender's input sudt cell. 127 | if (this.fee.gt(availableCKBFee.add(ckbFeeInputCell.availableFee()))) { 128 | outputCells.pop(); 129 | 130 | const senderOutputCell = outputCells.pop(); 131 | senderOutputCell.capacity = senderOutputCell.capacity.add( 132 | ckbFeeInputCell.capacity 133 | ); 134 | outputCells.push(senderOutputCell); 135 | 136 | tx = this.rectifyTx(inputCells, outputCells); 137 | } 138 | } 139 | 140 | // Third step: subtract tx fee from outputs' capacity 141 | tx = this.subtractFee(inputCells, outputCells); 142 | 143 | return tx; 144 | } 145 | 146 | private subtractFee(inputCells: Cell[], outputCells: Cell[]) { 147 | let remainFee = new Amount(this.fee.toHexString(), AmountUnit.shannon); 148 | for (const cell of outputCells.slice(1)) { 149 | // throw new Error(`remainFee ${remainFee} ${cell.availableFee()}`); 150 | if (remainFee.gt(cell.availableFee())) { 151 | remainFee = remainFee.sub(cell.availableFee()); 152 | cell.capacity = cell.occupiedCapacity(); 153 | } else { 154 | cell.capacity = cell.capacity.sub(remainFee); 155 | break; 156 | } 157 | } 158 | return this.rectifyTx(inputCells, outputCells); 159 | } 160 | 161 | private rectifyTx(inputCells: Cell[], outputCells: Cell[]) { 162 | const sudtCellDeps = [ 163 | PWCore.config.defaultLock.cellDep, 164 | PWCore.config.pwLock.cellDep, 165 | PWCore.config.sudtType.cellDep, 166 | ]; 167 | const tx = new Transaction( 168 | new RawTransaction(inputCells, outputCells, sudtCellDeps), 169 | [Builder.WITNESS_ARGS.Secp256k1] 170 | ); 171 | 172 | this.fee = Builder.calcFee(tx, this.feeRate); 173 | return tx; 174 | } 175 | 176 | getCollector() { 177 | return this.collector; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/builders/simple-sudt-builder.spec.ts: -------------------------------------------------------------------------------- 1 | import anyTest, { TestInterface } from 'ava'; 2 | import PWCore, { Address, AddressType, Amount, ChainID } from '..'; 3 | import { DummyCollector } from '../collectors/dummy-collector'; 4 | import { SUDT } from '../models'; 5 | import { DummyProvider } from '../providers/dummy-provider'; 6 | import { SimpleSUDTACPBuilder } from './simple-sudt-acp-builder'; 7 | 8 | const test = anyTest as TestInterface<{ builder: SimpleSUDTACPBuilder }>; 9 | 10 | test.before(async (t) => { 11 | const address = new Address( 12 | 'ckt1qjr2r35c0f9vhcdgslx2fjwa9tylevr5qka7mfgmscd33wlhfykykn2zrv2y5rex4nnyfs2tqkde8zmayrls6d3kwa5', 13 | AddressType.ckb 14 | ); 15 | const amount = new Amount('100'); 16 | const sudt = new SUDT( 17 | '0xc369a6fc6f0f907e46de96f668d986b8e4b52ea832da213f864eda805d34c932' 18 | ); 19 | 20 | await new PWCore('https://testnet.ckb.dev').init( 21 | new DummyProvider(), 22 | new DummyCollector(), 23 | ChainID.ckb_testnet 24 | ); 25 | 26 | t.context.builder = new SimpleSUDTACPBuilder(sudt, address, amount); 27 | }); 28 | 29 | test('build a tx', async (t) => { 30 | const tx = await t.context.builder.build(); 31 | t.notThrows(() => tx.validate()); 32 | }); 33 | 34 | test.todo('calc fee'); 35 | -------------------------------------------------------------------------------- /src/builders/simple-sudt-builder.ts: -------------------------------------------------------------------------------- 1 | import { Builder } from './builder'; 2 | import { 3 | Address, 4 | Amount, 5 | AmountUnit, 6 | Cell, 7 | RawTransaction, 8 | Transaction, 9 | SUDT, 10 | } from '../models'; 11 | import PWCore from '..'; 12 | import { SUDTCollector } from '../collectors/sudt-collector'; 13 | 14 | export class SimpleSUDTBuilder extends Builder { 15 | fee: Amount; 16 | 17 | inputCells: Cell[] = []; 18 | outputCells: Cell[] = []; 19 | 20 | constructor( 21 | private sudt: SUDT, 22 | private address: Address, 23 | private amount: Amount, 24 | feeRate?: number, 25 | collector?: SUDTCollector 26 | ) { 27 | super(feeRate, collector); 28 | this.fee = new Amount('0'); 29 | } 30 | 31 | async build(): Promise { 32 | const { tx, neededCKB } = await this.buildSudtCells(); 33 | if (tx) return tx; 34 | 35 | const tx2 = await this.buildCKBCells(neededCKB); 36 | return tx2; 37 | } 38 | 39 | /** 40 | * build a transaction with only sudt cells 41 | */ 42 | async buildSudtCells(): Promise<{ tx: Transaction; neededCKB: Amount }> { 43 | let senderInputSUDTSum = new Amount('0'); 44 | let senderInputCKBSum = new Amount('0'); 45 | let minSenderOccupiedCKBSum = new Amount('0'); 46 | 47 | const receiverAmount = new Amount('142'); 48 | const receiverOutputCell = new Cell( 49 | receiverAmount, 50 | this.address.toLockScript(), 51 | this.sudt.toTypeScript(), 52 | null, 53 | this.amount.toUInt128LE() 54 | ); 55 | 56 | // acp cell with zero sudt 57 | if (this.amount.eq(new Amount('0'))) { 58 | this.outputCells.push(receiverOutputCell); 59 | return { tx: null, neededCKB: receiverAmount }; 60 | } 61 | 62 | let restNeededSUDT = new Amount( 63 | this.amount.toHexString(), 64 | AmountUnit.shannon 65 | ); 66 | 67 | if (!(this.collector instanceof SUDTCollector)) { 68 | throw new Error('this.collector is not a SUDTCollector instance'); 69 | } 70 | 71 | const unspentSUDTCells = await this.collector.collectSUDT( 72 | this.sudt, 73 | PWCore.provider.address, 74 | { neededAmount: this.amount } 75 | ); 76 | 77 | // build a tx including sender and receiver sudt cell only 78 | for (const inputCell of unspentSUDTCells) { 79 | const outputCell = inputCell.clone(); 80 | 81 | const inputSUDTAmount = inputCell.getSUDTAmount(); 82 | senderInputSUDTSum = senderInputSUDTSum.add(inputSUDTAmount); 83 | senderInputCKBSum = senderInputCKBSum.add(inputCell.capacity); 84 | 85 | minSenderOccupiedCKBSum = minSenderOccupiedCKBSum.add( 86 | outputCell.occupiedCapacity() 87 | ); 88 | 89 | if (inputSUDTAmount.lt(restNeededSUDT)) { 90 | restNeededSUDT = restNeededSUDT.sub(inputSUDTAmount); 91 | outputCell.setSUDTAmount(new Amount('0')); 92 | } else { 93 | outputCell.setSUDTAmount(inputSUDTAmount.sub(restNeededSUDT)); 94 | restNeededSUDT = new Amount('0'); 95 | } 96 | 97 | this.inputCells.push(inputCell); 98 | this.outputCells.unshift(outputCell); 99 | 100 | if (senderInputSUDTSum.gte(this.amount)) break; 101 | } 102 | 103 | if (senderInputSUDTSum.lt(this.amount)) { 104 | throw new Error( 105 | `input sudt amount not enough, need ${this.amount.toString( 106 | AmountUnit.ckb 107 | )}, got ${senderInputSUDTSum.toString(AmountUnit.ckb)}` 108 | ); 109 | } 110 | 111 | this.outputCells.unshift(receiverOutputCell); 112 | this.rectifyTx(); 113 | 114 | const availableCKB = senderInputCKBSum.sub(minSenderOccupiedCKBSum); 115 | 116 | if (receiverAmount.add(this.fee).lt(availableCKB)) { 117 | const tx = this.extractCKBFromOutputs(receiverAmount.add(this.fee)); 118 | return { tx, neededCKB: new Amount('0') }; 119 | } else { 120 | this.extractCKBFromOutputs(receiverAmount); 121 | return { tx: null, neededCKB: receiverAmount.sub(availableCKB) }; 122 | } 123 | } 124 | 125 | /** 126 | * Fetch pure CKB cells to fullfill the need CKB amount 127 | * @param ckbAmount needed CKB amount 128 | */ 129 | async buildCKBCells(ckbAmount): Promise { 130 | // fetch pure ckb cells to pay the fee. 131 | const neededAmount = ckbAmount.add(Builder.MIN_CHANGE).add(this.fee); 132 | let inputSum = new Amount('0'); 133 | 134 | const unspentCKBCells = await this.collector.collect( 135 | PWCore.provider.address, 136 | neededAmount 137 | ); 138 | 139 | if (!unspentCKBCells || unspentCKBCells.length === 0) { 140 | throw new Error('no avaiable CKB'); 141 | } 142 | 143 | for (const ckbCell of unspentCKBCells) { 144 | this.inputCells.push(ckbCell); 145 | inputSum = inputSum.add(ckbCell.capacity); 146 | 147 | if (inputSum.gt(neededAmount)) break; 148 | } 149 | 150 | if (inputSum.lt(ckbAmount.add(this.fee))) { 151 | throw new Error('no enough CKB to create acp cell 1'); 152 | } 153 | 154 | // with changeCell 155 | if (inputSum.gt(neededAmount)) { 156 | const changeCell = new Cell( 157 | inputSum.sub(ckbAmount), 158 | PWCore.provider.address.toLockScript() 159 | ); 160 | this.outputCells.push(changeCell); 161 | 162 | this.rectifyTx(); 163 | 164 | if (this.fee.add(Builder.MIN_CHANGE).lte(changeCell.capacity)) { 165 | changeCell.capacity = changeCell.capacity.sub(this.fee); 166 | return this.rectifyTx(); 167 | } else { 168 | // pop changeCell 169 | this.outputCells.pop(); 170 | } 171 | } 172 | 173 | // no change cell, merge rest CKB to last output cell 174 | const lastCell = this.outputCells.pop(); 175 | lastCell.capacity = lastCell.capacity.add(inputSum.sub(ckbAmount)); 176 | this.outputCells.push(lastCell); 177 | 178 | this.rectifyTx(); 179 | 180 | if (this.fee.add(lastCell.occupiedCapacity()).gt(lastCell.capacity)) { 181 | throw new Error('no enough CKB to create acp cell 2'); 182 | } 183 | 184 | lastCell.capacity = lastCell.capacity.sub(this.fee); 185 | return this.rectifyTx(); 186 | } 187 | 188 | /** 189 | * subtract specified ckb amount from sender's outputs 190 | * @param ckbAmount 191 | */ 192 | private extractCKBFromOutputs(ckbAmount) { 193 | for (const cell of this.outputCells.slice(1)) { 194 | if (ckbAmount.gt(cell.availableFee())) { 195 | ckbAmount = ckbAmount.sub(cell.availableFee()); 196 | cell.capacity = cell.occupiedCapacity(); 197 | } else { 198 | cell.capacity = cell.capacity.sub(ckbAmount); 199 | break; 200 | } 201 | } 202 | return this.rectifyTx(); 203 | } 204 | 205 | /** 206 | * build tx based on inputs and outputs, and calculate the tx fee 207 | */ 208 | private rectifyTx() { 209 | const sudtCellDeps = [ 210 | PWCore.config.defaultLock.cellDep, 211 | PWCore.config.pwLock.cellDep, 212 | PWCore.config.sudtType.cellDep, 213 | ]; 214 | const tx = new Transaction( 215 | new RawTransaction(this.inputCells, this.outputCells, sudtCellDeps), 216 | [Builder.WITNESS_ARGS.Secp256k1] 217 | ); 218 | 219 | this.fee = Builder.calcFee(tx, this.feeRate); 220 | return tx; 221 | } 222 | 223 | getCollector() { 224 | return this.collector; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/collectors/collector.ts: -------------------------------------------------------------------------------- 1 | import { Cell, Address, Amount } from '../models'; 2 | 3 | export interface CollectorOptions { 4 | neededAmount?: Amount; 5 | withData?: boolean; 6 | } 7 | export abstract class Collector { 8 | protected constructor() {} 9 | abstract async getBalance(address: Address): Promise; 10 | abstract async collect( 11 | address: Address, 12 | options?: CollectorOptions 13 | ): Promise; 14 | } 15 | -------------------------------------------------------------------------------- /src/collectors/dummy-collector.ts: -------------------------------------------------------------------------------- 1 | import { Cell, Address, Amount, OutPoint } from '..'; 2 | import { SUDT } from '../models'; 3 | import { SUDTCollector } from './sudt-collector'; 4 | 5 | export class DummyCollector extends SUDTCollector { 6 | getBalance(): Promise { 7 | throw new Error('Method not implemented.'); 8 | } 9 | // public collect(): CollectorResults { 10 | // return [new Cell(new Amount('1000000'), this.address.toLockScript())]; 11 | // } 12 | constructor() { 13 | super(); 14 | } 15 | 16 | public async collect(address: Address): Promise { 17 | const outPoint = new OutPoint( 18 | '0x79221866125b9aff33c4303a6c35bde25d235e7e10025a86ca2a5d6ad657f51f', 19 | '0x0' 20 | ); 21 | const cell = new Cell( 22 | new Amount('1000000'), 23 | address.toLockScript(), 24 | null, 25 | outPoint 26 | ); 27 | cell.validate(); 28 | return [cell]; 29 | } 30 | 31 | async getSUDTBalance(): Promise { 32 | throw new Error('Method not implemented.'); 33 | } 34 | 35 | async collectSUDT(sudt: SUDT, address: Address): Promise { 36 | const outPoint = new OutPoint( 37 | '0x79221866125b9aff33c4303a6c35bde25d235e7e10025a86ca2a5d6ad657f51f', 38 | '0x0' 39 | ); 40 | const cell = new Cell( 41 | new Amount('1000000'), 42 | address.toLockScript(), 43 | sudt.toTypeScript(), 44 | outPoint, 45 | new Amount('1000').toUInt128LE() 46 | ); 47 | cell.validate(); 48 | return [cell]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/collectors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './collector'; 2 | export * from './sudt-collector'; 3 | export * from './pw-collector'; 4 | -------------------------------------------------------------------------------- /src/collectors/pw-collector.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { CollectorOptions } from './collector'; 3 | import { SUDTCollector } from './sudt-collector'; 4 | import { Cell, Address, Amount, AmountUnit, OutPoint } from '..'; 5 | import { SUDT } from '../models/sudt'; 6 | 7 | export class PwCollector extends SUDTCollector { 8 | constructor(public apiBase: string) { 9 | super(); 10 | this.apiBase = apiBase; 11 | } 12 | 13 | async getBalance(address: Address): Promise { 14 | const res = await axios.get( 15 | `${this.apiBase}/cell/getCapacityByLockHash?lockHash=${address 16 | .toLockScript() 17 | .toHash()}` 18 | ); 19 | return new Amount(res.data.data, AmountUnit.shannon); 20 | } 21 | 22 | async collect(address: Address, options: CollectorOptions): Promise { 23 | if (!options || !options.neededAmount) { 24 | throw new Error("'neededAmount' in options must be provided"); 25 | } 26 | const cells: Cell[] = []; 27 | const res = await axios.get( 28 | `${this.apiBase}/cell/unSpent?lockHash=${address 29 | .toLockScript() 30 | .toHash()}&capacity=${options.neededAmount.toHexString()}` 31 | ); 32 | 33 | for (let { capacity, outPoint } of res.data.data) { 34 | capacity = new Amount(capacity, AmountUnit.shannon); 35 | outPoint = new OutPoint(outPoint.txHash, outPoint.index); 36 | cells.push(new Cell(capacity, address.toLockScript(), null, outPoint)); 37 | } 38 | 39 | return cells; 40 | } 41 | 42 | async getSUDTBalance(sudt: SUDT, address: Address): Promise { 43 | const lockHash = address.toLockScript().toHash(); 44 | const typeHash = sudt.toTypeScript().toHash(); 45 | const res = await axios.get( 46 | `${this.apiBase}/sudt/balance?lockHash=${lockHash}&typeHash=${typeHash}` 47 | ); 48 | return new Amount(res.data.data.sudtAmount, AmountUnit.shannon); 49 | } 50 | 51 | async collectSUDT( 52 | sudt: SUDT, 53 | address: Address, 54 | options: CollectorOptions 55 | ): Promise { 56 | if (!options || !options.neededAmount) { 57 | throw new Error("'neededAmount' in options must be provided"); 58 | } 59 | const cells: Cell[] = []; 60 | const lockHash = address.toLockScript().toHash(); 61 | const typeHash = sudt.toTypeScript().toHash(); 62 | 63 | const res = await axios.get( 64 | `${ 65 | this.apiBase 66 | }/cell/unSpent?lockHash=${lockHash}&capacity=0x0&typeHash=${typeHash}&sudtAmount=${options.neededAmount.toHexString()}` 67 | ); 68 | 69 | for (let { capacity, outPoint, type, data } of res.data.data) { 70 | capacity = new Amount(capacity, AmountUnit.shannon); 71 | outPoint = new OutPoint(outPoint.txHash, outPoint.index); 72 | cells.push( 73 | new Cell(capacity, address.toLockScript(), type, outPoint, data) 74 | ); 75 | } 76 | 77 | return cells; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/collectors/sudt-collector.ts: -------------------------------------------------------------------------------- 1 | import { Cell, Address, Amount } from '../models'; 2 | import { SUDT } from '../models/sudt'; 3 | import { Collector, CollectorOptions } from './collector'; 4 | 5 | export abstract class SUDTCollector extends Collector { 6 | protected constructor() { 7 | super(); 8 | } 9 | abstract async getSUDTBalance(sudt: SUDT, address: Address): Promise; 10 | abstract async collectSUDT( 11 | sudt: SUDT, 12 | address: Address, 13 | options?: CollectorOptions 14 | ): Promise; 15 | } 16 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { DepType, HashType } from './interfaces'; 2 | import { OutPoint, CellDep, Script } from './models'; 3 | 4 | export const ECDSA_WITNESS_LEN = 172; 5 | export const DAO_WITHDRAW_2_WITNESS_LEN = 196; 6 | 7 | export const DUMMY_ADDRESSES = { 8 | main: 'ckb1qyqy5vmywpty6p72wpvm0xqys8pdtxqf6cmsr8p2l0', 9 | ckb_testnet: 'ckt1qyqwknsshmvnj8tj6wnaua53adc0f8jtrrzqz4xcu2', 10 | ckb_dev: 'ckt1qyqwknsshmvnj8tj6wnaua53adc0f8jtrrzqz4xcu2', 11 | }; 12 | 13 | export const CHAIN_SPECS = { 14 | Lina: { 15 | daoType: { 16 | cellDep: new CellDep( 17 | DepType.code, 18 | new OutPoint( 19 | '0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c', 20 | '0x2' 21 | ) 22 | ), 23 | script: new Script( 24 | '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e', 25 | '0x', 26 | HashType.type 27 | ), 28 | }, 29 | sudtType: { 30 | cellDep: new CellDep( 31 | DepType.code, 32 | new OutPoint( 33 | '0xc7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5', 34 | '0x0' 35 | ) 36 | ), 37 | script: new Script( 38 | '0x5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5', 39 | '0x', 40 | HashType.type 41 | ), 42 | }, 43 | defaultLock: { 44 | cellDep: new CellDep( 45 | DepType.depGroup, 46 | new OutPoint( 47 | '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c', 48 | '0x0' 49 | ) 50 | ), 51 | script: new Script( 52 | '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', 53 | '0x', 54 | HashType.type 55 | ), 56 | }, 57 | multiSigLock: { 58 | cellDep: new CellDep( 59 | DepType.depGroup, 60 | new OutPoint( 61 | '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c', 62 | '0x1' 63 | ) 64 | ), 65 | script: new Script( 66 | '0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8', 67 | '0x', 68 | HashType.type 69 | ), 70 | }, 71 | pwLock: { 72 | cellDep: new CellDep( 73 | DepType.code, 74 | new OutPoint( 75 | '0x1d60cb8f4666e039f418ea94730b1a8c5aa0bf2f7781474406387462924d15d4', 76 | '0x0' 77 | ) 78 | ), 79 | script: new Script( 80 | '0xbf43c3602455798c1a61a596e0d95278864c552fafe231c063b3fabf97a8febc', 81 | '0x', 82 | HashType.type 83 | ), 84 | }, 85 | acpLockList: [ 86 | new Script( 87 | '0xbf43c3602455798c1a61a596e0d95278864c552fafe231c063b3fabf97a8febc', 88 | '0x', 89 | HashType.type 90 | ), 91 | new Script( 92 | '0x0fb343953ee78c9986b091defb6252154e0bb51044fd2879fde5b27314506111', 93 | '0x', 94 | HashType.data 95 | ), 96 | ], 97 | }, 98 | 99 | Aggron: { 100 | daoType: { 101 | cellDep: new CellDep( 102 | DepType.code, 103 | new OutPoint( 104 | '0x8f8c79eb6671709633fe6a46de93c0fedc9c1b8a6527a18d3983879542635c9f', 105 | '0x2' 106 | ) 107 | ), 108 | script: new Script( 109 | '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e', 110 | '0x', 111 | HashType.type 112 | ), 113 | }, 114 | defaultLock: { 115 | cellDep: new CellDep( 116 | DepType.depGroup, 117 | new OutPoint( 118 | '0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37', 119 | '0x0' 120 | ) 121 | ), 122 | script: new Script( 123 | '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', 124 | '0x', 125 | HashType.type 126 | ), 127 | }, 128 | sudtType: { 129 | cellDep: new CellDep( 130 | DepType.code, 131 | new OutPoint( 132 | '0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769', 133 | '0x0' 134 | ) 135 | ), 136 | script: new Script( 137 | '0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4', 138 | '0x', 139 | HashType.type 140 | ), 141 | }, 142 | multiSigLock: { 143 | cellDep: new CellDep( 144 | DepType.depGroup, 145 | new OutPoint( 146 | '0x6495cede8d500e4309218ae50bbcadb8f722f24cc7572dd2274f5876cb603e4e', 147 | '0x1' 148 | ) 149 | ), 150 | script: new Script( 151 | '0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8', 152 | '0x', 153 | HashType.type 154 | ), 155 | }, 156 | pwLock: { 157 | cellDep: new CellDep( 158 | DepType.code, 159 | new OutPoint( 160 | '0x57a62003daeab9d54aa29b944fc3b451213a5ebdf2e232216a3cfed0dde61b38', 161 | '0x0' 162 | ) 163 | ), 164 | script: new Script( 165 | '0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63', 166 | '0x', 167 | HashType.type 168 | ), 169 | }, 170 | acpLockList: [ 171 | new Script( 172 | '0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63', 173 | '0x', 174 | HashType.type 175 | ), 176 | new Script( 177 | '0x86a1c6987a4acbe1a887cca4c9dd2ac9fcb07405bbeda51b861b18bbf7492c4b', 178 | '0x', 179 | HashType.type 180 | ), 181 | ], 182 | }, 183 | // dev - lay2.ckb.dev 184 | Lay2: { 185 | daoType: { 186 | cellDep: new CellDep( 187 | DepType.code, 188 | new OutPoint( 189 | '0xa563884b3686078ec7e7677a5f86449b15cf2693f3c1241766c6996f206cc541', 190 | '0x2' 191 | ) 192 | ), 193 | script: new Script( 194 | '0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e', 195 | '0x', 196 | HashType.type 197 | ), 198 | }, 199 | sudtType: { 200 | cellDep: new CellDep( 201 | DepType.code, 202 | new OutPoint( 203 | '0xc1b2ae129fad7465aaa9acc9785f842ba3e6e8b8051d899defa89f5508a77958', 204 | '0x0' 205 | ) 206 | ), 207 | script: new Script( 208 | '0x48dbf59b4c7ee1547238021b4869bceedf4eea6b43772e5d66ef8865b6ae7212', 209 | '0x', 210 | HashType.data 211 | ), 212 | }, 213 | defaultLock: { 214 | cellDep: new CellDep( 215 | DepType.depGroup, 216 | new OutPoint( 217 | '0xace5ea83c478bb866edf122ff862085789158f5cbff155b7bb5f13058555b708', 218 | '0x0' 219 | ) 220 | ), 221 | script: new Script( 222 | '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', 223 | '0x', 224 | HashType.type 225 | ), 226 | }, 227 | multiSigLock: { 228 | cellDep: new CellDep( 229 | DepType.depGroup, 230 | new OutPoint( 231 | '0xace5ea83c478bb866edf122ff862085789158f5cbff155b7bb5f13058555b708', 232 | '0x1' 233 | ) 234 | ), 235 | script: new Script( 236 | '0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8', 237 | '0x', 238 | HashType.type 239 | ), 240 | }, 241 | pwLock: { 242 | cellDep: new CellDep( 243 | DepType.code, 244 | new OutPoint( 245 | '0x7822910729c566c0f8a3f4bb9aee721c5da2808f9a4688e909c0119b0ab820d7', 246 | '0x0' 247 | ) 248 | ), 249 | script: new Script( 250 | '0xc9eb3097397836e4d5b8fabed3c0cddd14fefe483caf238ca2e3095a111add0b', 251 | '0x', 252 | HashType.type 253 | ), 254 | }, 255 | acpLockList: [ 256 | new Script( 257 | '0xc9eb3097397836e4d5b8fabed3c0cddd14fefe483caf238ca2e3095a111add0b', 258 | '0x', 259 | HashType.type 260 | ), 261 | ], 262 | }, 263 | }; 264 | -------------------------------------------------------------------------------- /src/core.spec.ts: -------------------------------------------------------------------------------- 1 | import anyTest, { TestInterface } from 'ava'; 2 | import PWCore, { Address, AddressType, Amount, ChainID } from '.'; 3 | import { PwCollector } from './collectors/pw-collector'; 4 | import { CHAIN_SPECS } from './constants'; 5 | import { DummyProvider } from './providers/dummy-provider'; 6 | 7 | const test = anyTest as TestInterface<{ pw: PWCore; address: Address }>; 8 | 9 | test.before(async (t) => { 10 | const address = new Address( 11 | '0x26C5F390FF2033CbB44377361c63A3Dd2DE3121d', 12 | AddressType.eth 13 | ); 14 | const pw = await new PWCore('https://lay2.ckb.dev').init( 15 | new DummyProvider(), 16 | new PwCollector('https://cellapi.ckb.pw'), 17 | ChainID.ckb_dev, 18 | CHAIN_SPECS.Lay2 19 | ); 20 | t.context = { pw, address }; 21 | }); 22 | 23 | test.skip('send simple tx', async (t) => { 24 | // test.serial('send simple tx', async (t) => { 25 | const { pw, address } = t.context; 26 | const amount61 = new Amount('61'); 27 | const txHash = await pw.send(address, amount61); 28 | t.pass('tx sent: ' + txHash); 29 | }); 30 | 31 | test.serial('chain specs auto select', async (t) => { 32 | await new PWCore('https://mainnet.ckb.dev').init( 33 | new DummyProvider(), 34 | new PwCollector('https://cellapi.ckb.pw') 35 | ); 36 | t.is(PWCore.chainId, ChainID.ckb); 37 | 38 | await new PWCore('https://testnet.ckb.dev').init( 39 | new DummyProvider(), 40 | new PwCollector('https://cellapi.ckb.pw') 41 | ); 42 | t.is(PWCore.chainId, ChainID.ckb_testnet); 43 | }); 44 | -------------------------------------------------------------------------------- /src/core.ts: -------------------------------------------------------------------------------- 1 | import { RPC, transformers } from 'ckb-js-toolkit'; 2 | import { CHAIN_SPECS } from './constants'; 3 | import { Config } from './interfaces'; 4 | import { Address, Amount, SUDT, Transaction } from './models'; 5 | import { DefaultSigner, Signer } from './signers'; 6 | import { Collector } from './collectors'; 7 | import { 8 | SimpleBuilder, 9 | Builder, 10 | SimpleSUDTACPBuilder, 11 | SimpleSUDTBuilder, 12 | } from './builders'; 13 | import { Provider } from './providers'; 14 | import { SUDTCollector } from './collectors/sudt-collector'; 15 | 16 | export enum ChainID { 17 | ckb, 18 | ckb_testnet, 19 | ckb_dev, 20 | } 21 | 22 | /** 23 | * The default main class of pw-core 24 | */ 25 | export default class PWCore { 26 | static config: Config; 27 | static chainId: ChainID; 28 | static provider: Provider; 29 | static defaultCollector: Collector | SUDTCollector; 30 | 31 | private readonly _rpc: RPC; 32 | 33 | constructor(nodeUrl: string) { 34 | this._rpc = new RPC(nodeUrl); 35 | } 36 | 37 | /** 38 | * Initialize the environment required by pw-core 39 | */ 40 | async init( 41 | provider: Provider, 42 | defaultCollector: Collector, 43 | chainId?: ChainID, 44 | config?: Config 45 | ): Promise { 46 | if (chainId) { 47 | if (!(chainId in ChainID)) { 48 | throw new Error(`invalid chainId ${chainId}`); 49 | } 50 | PWCore.chainId = chainId; 51 | } else { 52 | const info = await this.rpc.get_blockchain_info(); 53 | PWCore.chainId = { 54 | ckb: ChainID.ckb, 55 | ckb_testnet: ChainID.ckb_testnet, 56 | ckb_dev: ChainID.ckb_dev, 57 | }[info.chain]; 58 | } 59 | 60 | if (PWCore.chainId === ChainID.ckb_dev) { 61 | if (!config) { 62 | throw new Error('config must be provided for dev chain'); 63 | } 64 | PWCore.config = config; 65 | } else { 66 | // merge customized config to default one 67 | PWCore.config = { 68 | ...[CHAIN_SPECS.Lina, CHAIN_SPECS.Aggron][PWCore.chainId], 69 | ...config, 70 | }; 71 | } 72 | 73 | if (provider instanceof Provider) { 74 | PWCore.provider = await provider.init(); 75 | } else { 76 | throw new Error('provider must be provided'); 77 | } 78 | 79 | if (defaultCollector instanceof Collector) { 80 | PWCore.defaultCollector = defaultCollector; 81 | } else { 82 | throw new Error('defaultCollector must be provided'); 83 | } 84 | 85 | return this; 86 | } 87 | 88 | /** 89 | * Return a RPC instance defined in package 'ckb-js-toolkit' 90 | */ 91 | get rpc(): RPC { 92 | return this._rpc; 93 | } 94 | 95 | /** 96 | * Transfer CKB to any address 97 | * @param address The receiver's address 98 | * @param amount The amount of CKB to send 99 | * @param feeRate The feeRate (Shannon/KB) for this transaction. 100 | */ 101 | async send( 102 | address: Address, 103 | amount: Amount, 104 | feeRate?: number 105 | ): Promise { 106 | const simpleBuilder = new SimpleBuilder(address, amount, feeRate); 107 | return this.sendTransaction(simpleBuilder); 108 | } 109 | 110 | /** 111 | * Send an built transaction or a builder 112 | * @param toSend 113 | * @param signer 114 | */ 115 | async sendTransaction( 116 | toSend: Transaction | Builder, 117 | signer?: Signer 118 | ): Promise { 119 | const tx = toSend instanceof Builder ? await toSend.build() : toSend; 120 | tx.validate(); 121 | 122 | if (!signer) { 123 | signer = new DefaultSigner(PWCore.provider); 124 | } 125 | 126 | return this.rpc.send_transaction( 127 | transformers.TransformTransaction(await signer.sign(tx)) 128 | ); 129 | } 130 | 131 | /** 132 | * Transfer sudt to any address 133 | * @param sudt The sudt definition 134 | * @param address the receiver's address 135 | * @param amount the aount of sudt to send 136 | * @param feeRate the feeRate (Shannon/CKB) for this transation 137 | * @returns the transaction hash 138 | */ 139 | async sendSUDT( 140 | sudt: SUDT, 141 | address: Address, 142 | amount: Amount, 143 | createAcp?: boolean, 144 | signer?: Signer, 145 | feeRate?: number 146 | ): Promise { 147 | const builder = createAcp 148 | ? new SimpleSUDTBuilder(sudt, address, amount, feeRate) 149 | : new SimpleSUDTACPBuilder(sudt, address, amount, feeRate); 150 | 151 | return this.sendTransaction(builder, signer); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/hashers/blake2b-hasher.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Blake2bHasher } from '.'; 3 | 4 | test('hello', (t) => { 5 | const hasher = new Blake2bHasher(); 6 | const res = hasher.hash('hello').serializeJson(); 7 | t.is( 8 | res, 9 | '0x2da1289373a9f6b7ed21db948f4dc5d942cf4023eaef1d5a2b1a45b9d12d1036' 10 | ); 11 | }); 12 | 13 | // CKB blake2b uses a personalization: 'ckb-default-hash' 14 | -------------------------------------------------------------------------------- /src/hashers/blake2b-hasher.ts: -------------------------------------------------------------------------------- 1 | import { Hasher } from '.'; 2 | import { Reader } from 'ckb-js-toolkit'; 3 | import blake2b from 'blake2b'; 4 | 5 | export class Blake2bHasher extends Hasher { 6 | constructor() { 7 | const h = blake2b( 8 | 32, 9 | null, 10 | null, 11 | new Uint8Array(Reader.fromRawString('ckb-default-hash').toArrayBuffer()) 12 | ); 13 | super(h); 14 | } 15 | 16 | update(data: string | ArrayBuffer): Hasher { 17 | let array: Buffer; 18 | if (data instanceof Reader) { 19 | /** Reader type params not enter this branch, it's weired */ 20 | array = Buffer.from(data.serializeJson().replace('0x', '')); 21 | } else if (data instanceof ArrayBuffer) { 22 | array = Buffer.from(new Uint8Array(data)); 23 | } else if (typeof data === 'string') { 24 | array = Buffer.from(data); 25 | } else { 26 | array = Buffer.from(new Uint8Array(new Reader(data).toArrayBuffer())); 27 | } 28 | this.h.update(array); 29 | return this; 30 | } 31 | 32 | digest(): Reader { 33 | const out = new Uint8Array(32); 34 | this.h.digest(out); 35 | return new Reader(out.buffer); 36 | } 37 | 38 | reset(): void { 39 | this.h = blake2b( 40 | 32, 41 | null, 42 | null, 43 | new Uint8Array(Reader.fromRawString('ckb-default-hash').toArrayBuffer()) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/hashers/hasher.ts: -------------------------------------------------------------------------------- 1 | import { Reader } from 'ckb-js-toolkit'; 2 | 3 | export interface HashMethod { 4 | update(data: string | Uint8Array): HashMethod; 5 | digest(data?: string | Uint8Array): string | Uint8Array; 6 | digest(encoding: string): string | Uint8Array; 7 | } 8 | 9 | export abstract class Hasher { 10 | constructor(protected h: HashMethod) {} 11 | abstract update(data: string | ArrayBuffer | Reader): Hasher; 12 | abstract digest(): Reader; 13 | abstract reset(): void; 14 | protected setH(h: HashMethod): void { 15 | this.h = h; 16 | } 17 | hash(data: string | Uint8Array | Reader): Reader { 18 | return this.update(data).digest(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/hashers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hasher'; 2 | export * from './blake2b-hasher'; 3 | export * from './keccak256-hasher'; 4 | -------------------------------------------------------------------------------- /src/hashers/keccak256-hasher.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Keccak256Hasher } from '.'; 3 | 4 | test('hello', (t) => { 5 | const hasher = new Keccak256Hasher(); 6 | const res = hasher.hash('hello').serializeJson(); 7 | t.is( 8 | res, 9 | '0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8' 10 | ); 11 | }); 12 | -------------------------------------------------------------------------------- /src/hashers/keccak256-hasher.ts: -------------------------------------------------------------------------------- 1 | import { Hasher } from '.'; 2 | import { Reader } from 'ckb-js-toolkit'; 3 | import keccak from 'keccak'; 4 | 5 | export class Keccak256Hasher extends Hasher { 6 | constructor() { 7 | super(keccak('keccak256')); 8 | } 9 | 10 | update(data: string | ArrayBuffer | Reader): Hasher { 11 | let array: Buffer; 12 | if (data instanceof Reader) { 13 | /** Reader type params not enter this branch, it's weired */ 14 | array = Buffer.from(data.serializeJson().replace('0x', '')); 15 | } else if (data instanceof ArrayBuffer) { 16 | array = Buffer.from(new Uint8Array(data)); 17 | } else if (typeof data === 'string') { 18 | array = Buffer.from(data); 19 | } else { 20 | array = Buffer.from(new Uint8Array(new Reader(data).toArrayBuffer())); 21 | } 22 | this.h.update(array); 23 | return this; 24 | } 25 | 26 | digest(): Reader { 27 | const hex = '0x' + this.h.digest('hex').toString(); 28 | return new Reader(hex); 29 | } 30 | 31 | reset(): void { 32 | this.h = keccak('keccak256'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import PWCore from './core'; 2 | 3 | export * from './core'; 4 | export * from './models'; 5 | export * from './interfaces'; 6 | export * from './providers'; 7 | export * from './collectors'; 8 | export * from './builders'; 9 | export * from './hashers'; 10 | export * from './signers'; 11 | export * from './utils'; 12 | export * from './constants'; 13 | 14 | export default PWCore; 15 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { Script, CellDep } from './models'; 2 | 3 | export interface CKBModel { 4 | validate(): any; 5 | sameWith(model: any): boolean; 6 | serializeJson(): object; 7 | } 8 | 9 | export enum HashType { 10 | data = 'data', 11 | type = 'type', 12 | } 13 | 14 | export enum DepType { 15 | code = 'code', 16 | depGroup = 'dep_group', 17 | } 18 | 19 | export interface WitnessArgs { 20 | lock: string; 21 | input_type: string; 22 | output_type: string; 23 | } 24 | 25 | export interface ConfigItem { 26 | cellDep: CellDep; 27 | script: Script; 28 | } 29 | 30 | export interface Config { 31 | daoType: ConfigItem; 32 | defaultLock: ConfigItem; 33 | multiSigLock: ConfigItem; 34 | pwLock: ConfigItem; 35 | sudtType: ConfigItem; 36 | acpLockList: Script[]; 37 | } 38 | -------------------------------------------------------------------------------- /src/models/address.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import PWCore, { ChainID } from '../core'; 3 | import { Address, AddressType } from './address'; 4 | import { DummyCollector } from '../collectors/dummy-collector'; 5 | import { DummyProvider } from '../providers/dummy-provider'; 6 | 7 | const eth = '0x32f4c2df50f678a94609e98f8ee7ffb14b6799bc'; 8 | const ckb = 'ckt1qyqxpayn272n8km2k08hzldynj992egs0waqnr8zjs'; 9 | const ckbFull = 10 | 'ckt1q3vvtay34wndv9nckl8hah6fzzcltcqwcrx79apwp2a5lkd07fdxxvh5ct04panc49rqn6v03mnllv2tv7vmc9kkmjq'; 11 | 12 | const ckbAddress = new Address(ckb, AddressType.ckb); 13 | const ckbFullAddress = new Address(ckbFull, AddressType.ckb); 14 | const ethAddress = new Address(eth, AddressType.eth); 15 | 16 | test.before(async () => { 17 | await new PWCore('https://testnet.ckb.dev').init( 18 | new DummyProvider(), 19 | new DummyCollector(), 20 | ChainID.ckb_testnet 21 | ); 22 | }); 23 | 24 | test('to address and type', (t) => { 25 | t.is(ethAddress.addressString, eth); 26 | t.is(ethAddress.addressType, AddressType.eth); 27 | 28 | t.is(ckbAddress.addressString, ckb); 29 | t.is(ckbAddress.addressType, AddressType.ckb); 30 | 31 | t.is(ckbFullAddress.addressString, ckbFull); 32 | t.is(ckbFullAddress.addressType, AddressType.ckb); 33 | }); 34 | 35 | test('to ckb address', (t) => { 36 | t.is(ethAddress.toCKBAddress(), ckbFull); 37 | t.is(ckbFullAddress.toCKBAddress(), ckbFull); 38 | t.is(ckbAddress.toCKBAddress(), ckb); 39 | }); 40 | 41 | test('to lock script', (t) => { 42 | t.deepEqual(ethAddress.toLockScript().serializeJson(), { 43 | args: eth, 44 | code_hash: PWCore.config.pwLock.script.codeHash, 45 | hash_type: PWCore.config.pwLock.script.hashType, 46 | }); 47 | 48 | t.deepEqual(ckbAddress.toLockScript().serializeJson(), { 49 | args: '0x60f493579533db6ab3cf717da49c8a5565107bba', 50 | code_hash: PWCore.config.defaultLock.script.codeHash, 51 | hash_type: PWCore.config.defaultLock.script.hashType, 52 | }); 53 | 54 | t.deepEqual(ckbFullAddress.toLockScript().serializeJson(), { 55 | args: '0x32f4c2df50f678a94609e98f8ee7ffb14b6799bc', 56 | code_hash: PWCore.config.pwLock.script.codeHash, 57 | hash_type: PWCore.config.pwLock.script.hashType, 58 | }); 59 | }); 60 | 61 | test('is acp address', (t) => { 62 | t.true( 63 | new Address( 64 | 'ckt1qjr2r35c0f9vhcdgslx2fjwa9tylevr5qka7mfgmscd33wlhfykykn2zrv2y5rex4nnyfs2tqkde8zmayrls6d3kwa5', 65 | AddressType.ckb 66 | ).isAcp() 67 | ); 68 | t.false( 69 | new Address( 70 | 'ckt1q35y83078t9h7nwzyvpe9qfuh8qjm08d2ktlegc22tcn4fgem6xnxwlwq4vae7nqgpkl6s59znsqmh9jkrtjhwct56efh7uep9y2xr04d4augtnmauya4s2cdvn0s6nxw9m6k7ndhf2l0un2g0tr7f88fegqns00nq', 71 | AddressType.ckb 72 | ).isAcp() 73 | ); 74 | }); 75 | 76 | test('minimal pay amount', (t) => { 77 | t.is( 78 | new Address( 79 | 'ckt1q35y83078t9h7nwzyvpe9qfuh8qjm08d2ktlegc22tcn4fgem6xnxwlwq4vae7nqgpkl6s59znsqmh9jkrtjhwct56efh7uep9y2xr04d4augtnmauya4s2cdvn0s6nxw9m6k7ndhf2l0un2g0tr7f88fegqns00nq', 80 | AddressType.ckb 81 | ) 82 | .minPaymentAmount() 83 | .toString(), 84 | '105' 85 | ); 86 | t.is( 87 | new Address( 88 | 'ckt1qyqxpayn272n8km2k08hzldynj992egs0waqnr8zjs', 89 | AddressType.ckb 90 | ) 91 | .minPaymentAmount() 92 | .toString(), 93 | '61' 94 | ); 95 | }); 96 | -------------------------------------------------------------------------------- /src/models/address.ts: -------------------------------------------------------------------------------- 1 | import { Script } from '.'; 2 | import PWCore, { ChainID } from '../core'; 3 | import { HashType } from '../interfaces'; 4 | import { 5 | parseAddress, 6 | generateAddress, 7 | LumosConfigs, 8 | verifyCkbAddress, 9 | verifyEthAddress, 10 | cellOccupiedBytes, 11 | } from '../utils'; 12 | import { 13 | fullPayloadToAddress, 14 | AddressType as AType, 15 | AddressPrefix as APrefix, 16 | } from '@nervosnetwork/ckb-sdk-utils'; 17 | import { Amount, AmountUnit } from './amount'; 18 | 19 | export enum AddressPrefix { 20 | ckb, 21 | ckt, 22 | } 23 | 24 | export enum AddressType { 25 | ckb, 26 | eth, 27 | // btc, 28 | // eos, 29 | // tron, 30 | // libra, 31 | } 32 | 33 | export enum LockType { 34 | default, 35 | multisig, 36 | pw, 37 | } 38 | 39 | export function getDefaultPrefix(): AddressPrefix { 40 | return PWCore.chainId === ChainID.ckb ? AddressPrefix.ckb : AddressPrefix.ckt; 41 | } 42 | 43 | export class Address { 44 | static fromLockScript( 45 | lockScript: Script, 46 | prefix: AddressPrefix = getDefaultPrefix() 47 | ): Address { 48 | const addressString = generateAddress(lockScript.serializeJson(), { 49 | config: LumosConfigs[prefix], 50 | }); 51 | 52 | return new Address(addressString, AddressType.ckb); 53 | } 54 | 55 | constructor( 56 | readonly addressString: string, 57 | readonly addressType: AddressType 58 | ) { 59 | this.addressString = addressString.toLowerCase(); 60 | } 61 | 62 | valid(): boolean { 63 | switch (this.addressType) { 64 | case AddressType.ckb: 65 | return verifyCkbAddress(this.addressString); 66 | case AddressType.eth: 67 | return verifyEthAddress(this.addressString); 68 | default: 69 | return true; 70 | } 71 | } 72 | 73 | minPaymentAmount(): Amount { 74 | if (this.isAcp()) { 75 | return new Amount('1', AmountUnit.shannon); 76 | } 77 | const bytes = cellOccupiedBytes({ 78 | lock: this.toLockScript(), 79 | type: null, 80 | data: '0x', 81 | }); 82 | return new Amount(bytes.toString()); 83 | } 84 | 85 | isAcp(): boolean { 86 | const script = this.toLockScript(); 87 | const { codeHash, hashType } = script; 88 | const acpLock = PWCore.config.acpLockList.filter( 89 | (x) => x.codeHash === codeHash && x.hashType === hashType 90 | ); 91 | return acpLock && acpLock.length > 0; 92 | } 93 | 94 | toCKBAddress(): string { 95 | if (this.addressType === AddressType.ckb) { 96 | return this.addressString; 97 | } 98 | 99 | const { args, codeHash, hashType } = this.toLockScript(); 100 | 101 | return fullPayloadToAddress({ 102 | arg: args, 103 | codeHash, 104 | type: 105 | hashType === HashType.data ? AType.DataCodeHash : AType.TypeCodeHash, 106 | prefix: 107 | getDefaultPrefix() === AddressPrefix.ckb 108 | ? APrefix.Mainnet 109 | : APrefix.Testnet, 110 | }); 111 | } 112 | 113 | toLockScript(): Script { 114 | if (this.addressType !== AddressType.ckb) { 115 | const { codeHash, hashType } = PWCore.config.pwLock.script; 116 | return new Script(codeHash, this.addressString, hashType); 117 | } 118 | 119 | const lock = parseAddress(this.addressString, { 120 | config: LumosConfigs[getDefaultPrefix()], 121 | }); 122 | return new Script(lock.code_hash, lock.args, HashType[lock.hash_type]); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/models/amount.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import PWCore, { ChainID } from '../core'; 3 | import { DummyCollector } from '../collectors/dummy-collector'; 4 | import { Amount, AmountUnit } from '.'; 5 | import { DummyProvider } from '../providers/dummy-provider'; 6 | import JSBI from 'jsbi'; 7 | 8 | test.before(async () => { 9 | await new PWCore('https://testnet.ckb.dev').init( 10 | new DummyProvider(), 11 | new DummyCollector(), 12 | ChainID.ckb_testnet 13 | ); 14 | }); 15 | 16 | const K = '1000'; 17 | const M = '1000000'; 18 | const B = '1000000000'; 19 | const T = '1000000000000'; 20 | const T1 = '1,000,000,000,000'; 21 | const F = '1002003400500'; 22 | 23 | const ckb1 = new Amount('1', AmountUnit.ckb); 24 | const ckb10000 = new Amount(K + '0', AmountUnit.ckb); 25 | 26 | const shannon1 = new Amount('1', AmountUnit.shannon); 27 | const shannon1M = new Amount(M, AmountUnit.shannon); 28 | const shannon1B = new Amount(B, AmountUnit.shannon); 29 | const shannon1T = new Amount(T, AmountUnit.shannon); 30 | const shannonFull = new Amount(F, AmountUnit.shannon); 31 | 32 | test('formatting test set', (t) => { 33 | t.is(ckb1.toString(AmountUnit.shannon), M + '00'); 34 | t.is(ckb1.toString(AmountUnit.ckb), '1'); 35 | 36 | t.is(ckb10000.toString(AmountUnit.shannon, { commify: true }), T1); 37 | t.is(ckb10000.toString(AmountUnit.ckb, { commify: true }), '10,000'); 38 | 39 | t.is(shannon1.toString(AmountUnit.ckb), '0.00000001'); 40 | t.is(shannon1.toString(AmountUnit.shannon), '1'); 41 | 42 | t.is(shannon1M.toString(AmountUnit.shannon), M); 43 | t.is(shannon1M.toString(AmountUnit.ckb), '0.01'); 44 | t.is(shannon1M.toString(AmountUnit.ckb, { pad: true }), '0.01000000'); 45 | 46 | t.is(shannon1B.toString(AmountUnit.shannon), B); 47 | t.is(shannon1B.toString(AmountUnit.ckb), '10'); 48 | t.is(shannon1B.toString(AmountUnit.ckb, { commify: true }), '10'); 49 | 50 | t.is(shannon1T.toString(AmountUnit.shannon), T); 51 | t.is(shannon1T.toString(AmountUnit.ckb), '10000'); 52 | t.is(shannon1T.toString(AmountUnit.ckb, { commify: true }), '10,000'); 53 | 54 | t.is(shannonFull.toString(AmountUnit.ckb), '10020.034005'); 55 | t.is( 56 | shannonFull.toString(AmountUnit.ckb, { pad: true, commify: true }), 57 | '10,020.03400500' 58 | ); 59 | t.is(shannonFull.toString(AmountUnit.ckb, { section: 'integer' }), '10020'); 60 | t.is( 61 | shannonFull.toString(AmountUnit.ckb, { section: 'integer', commify: true }), 62 | '10,020' 63 | ); 64 | t.is(shannonFull.toString(AmountUnit.ckb, { section: 'decimal' }), '034005'); 65 | t.is( 66 | shannonFull.toString(AmountUnit.ckb, { section: 'decimal', pad: true }), 67 | '03400500' 68 | ); 69 | 70 | t.is(shannonFull.toString(AmountUnit.ckb, { fixed: 1 }), '10020.0'); 71 | t.is(shannonFull.toString(AmountUnit.ckb, { fixed: 2 }), '10020.03'); 72 | t.is(shannonFull.toString(AmountUnit.ckb, { fixed: 3 }), '10020.034'); 73 | t.is(shannonFull.toString(AmountUnit.ckb, { fixed: 4 }), '10020.0340'); 74 | t.is(shannonFull.toString(AmountUnit.ckb, { fixed: 5 }), '10020.03401'); 75 | t.is(shannonFull.toString(AmountUnit.ckb, { fixed: 6 }), '10020.034005'); 76 | t.is( 77 | shannonFull.toString(AmountUnit.ckb, { section: 'decimal', fixed: 1 }), 78 | '0' 79 | ); 80 | t.is( 81 | shannonFull.toString(AmountUnit.ckb, { section: 'decimal', fixed: 2 }), 82 | '03' 83 | ); 84 | t.is( 85 | shannonFull.toString(AmountUnit.ckb, { section: 'decimal', fixed: 3 }), 86 | '034' 87 | ); 88 | t.is( 89 | shannonFull.toString(AmountUnit.ckb, { section: 'decimal', fixed: 4 }), 90 | '0340' 91 | ); 92 | t.is( 93 | shannonFull.toString(AmountUnit.ckb, { section: 'decimal', fixed: 5 }), 94 | '03401' 95 | ); 96 | t.is( 97 | shannonFull.toString(AmountUnit.ckb, { section: 'decimal', fixed: 6 }), 98 | '034005' 99 | ); 100 | 101 | t.is(new Amount('1.99').toString(AmountUnit.ckb, { fixed: 1 }), '2.0'); 102 | 103 | t.is( 104 | shannonFull.toString(AmountUnit.ckb, { 105 | section: 'decimal', 106 | fixed: 5, 107 | pad: true, 108 | }), 109 | '03401' 110 | ); 111 | }); 112 | 113 | test('calculation test set', (t) => { 114 | t.is(shannon1.add(shannon1).toString(AmountUnit.shannon), '2'); 115 | t.is( 116 | shannon1T.add(shannon1).toString(AmountUnit.shannon), 117 | T.slice(0, -1) + '1' 118 | ); 119 | t.is(ckb1.add(shannon1).toString(AmountUnit.shannon), M + '01'); 120 | t.is(ckb10000.add(ckb1).toString(AmountUnit.ckb), '10001'); 121 | 122 | // TODO more to go 123 | }); 124 | 125 | test('to hex string', (t) => { 126 | t.is(shannon1.toHexString(), '0x1'); 127 | t.is(ckb1.toHexString(), '0x5f5e100'); 128 | t.is(shannonFull.toHexString(), '0xe94c0e8734'); 129 | }); 130 | 131 | // tests for random decimals 132 | 133 | const d0 = new Amount('10', 0); 134 | const d1 = new Amount('1', 1); 135 | const d2 = new Amount('0.1', 2); 136 | const p = new Amount('0.00361', AmountUnit.ckb); 137 | const q = new Amount('213.00', AmountUnit.ckb); 138 | const r = new Amount('213.12', AmountUnit.ckb); 139 | 140 | const s = new Amount('213.1200000', 25); 141 | 142 | test.only('to BigInt', (t) => { 143 | t.is(d0.toBigInt().toString(), JSBI.BigInt(10).toString()); 144 | t.is(d1.toBigInt().toString(), JSBI.BigInt(10).toString()); 145 | t.is(d2.toBigInt().toString(), JSBI.BigInt(10).toString()); 146 | t.is(p.toString(), '0.00361'); 147 | t.is(p.toString(AmountUnit.ckb), '0.00361'); 148 | t.is(p.toString(AmountUnit.shannon), '361000'); 149 | 150 | t.is(q.toString(AmountUnit.shannon), '21300000000'); 151 | t.is(q.toString(AmountUnit.ckb), '213'); 152 | 153 | t.is(r.toString(AmountUnit.ckb), '213.12'); 154 | t.is(r.toString(AmountUnit.shannon), '21312000000'); 155 | 156 | t.is(s.toString(0), '2131200000000000000000000000'); 157 | }); 158 | -------------------------------------------------------------------------------- /src/models/amount.spec/add.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Amount } from '../..'; 3 | 4 | test('add', (c) => { 5 | const decimal = 30; 6 | let fixed = 4; 7 | const t = (appendA, appendB, expected) => { 8 | c.is( 9 | new Amount(appendA, decimal) 10 | .add(new Amount(appendB, decimal)) 11 | .toString(decimal, { fixed }), 12 | new Amount(expected, decimal).toString(decimal, { fixed }) 13 | ); 14 | }; 15 | 16 | t(1, 0, '1'); 17 | t(1, -0, '1'); 18 | t(-1, 0, '-1'); 19 | t(-1, 0, '-1'); 20 | // t(1, 'NaN', 'NaN'); 21 | // t(-1, 'NaN', 'NaN'); 22 | // t(1, 'Infinity', 'Infinity'); 23 | // t(1, '-Infinity', '-Infinity'); 24 | // t(-1, 'Infinity', 'Infinity'); 25 | // t(-1, '-Infinity', '-Infinity'); 26 | t(0, 1, '1'); 27 | t(0, -1, '-1'); 28 | t(-0, 1, '1'); 29 | t(-0, -1, '-1'); 30 | 31 | fixed = 3; 32 | t('0', '-0', '-0'); // 0 + -0 = -0 33 | t('-0', '0', '-0'); // -0 + 0 = -0 34 | t('0', '0', '0'); // 0 + 0 = 0 35 | t('-0', '-0', '-0'); // -0 + -0 = -0 36 | t('1', '-1', '-0'); // 1 + -1 = -0 37 | t('-1', '1', '-0'); // -1 + 1 = -0 38 | 39 | fixed = 4; 40 | t('0', '-0', '-0'); // 0 + -0 = -0 41 | t('-0', '0', '-0'); // -0 + 0 = -0 42 | t('0', '0', '0'); // 0 + 0 = 0 43 | t('-0', '-0', '-0'); // -0 + -0 = -0 44 | t('1', '-1', '-0'); // 1 + -1 = -0 45 | t('-1', '1', '-0'); // -1 + 1 = -0 46 | 47 | t(1, '0', '1'); 48 | t(1, '1', '2'); 49 | t(1, '-45', '-44'); 50 | t(1, '22', '23'); 51 | // t(1, 0144, '101'); 52 | t(1, '0144', '145'); 53 | t(1, '6.1915', '7.1915'); 54 | t(1, '-1.02', '-0.02'); 55 | t(1, '0.09', '1.09'); 56 | t(1, '-0.0001', '0.9999'); 57 | // t(1, '8e5', '800001'); 58 | // t(1, '9E12', '9000000000001'); 59 | // t(1, '1e-14', '1.00000000000001'); 60 | // t(1, '3.345E-9', '1.000000003345'); 61 | // t(1, '-345.43e+4', '-3454299'); 62 | // t(1, '-94.12E+0', '-93.12'); 63 | t('0', 0, '0'); 64 | t('0', '0', '0'); 65 | t(3, -0, '3'); 66 | t(9.654, 0, '9.654'); 67 | t(0, '0.001', '0.001'); 68 | t(0, '111.1111111110000', '111.111111111'); 69 | // t('NaN', '0', 'NaN'); 70 | t(-1, 1, '0'); 71 | t(-0.01, 0.01, '0'); 72 | t(54, -54, '0'); 73 | t(9.99, '-9.99', '0'); 74 | t('0.00000', '-0.000001', '-0.000001'); 75 | t('0.0000023432495704937', '-0.0000023432495704937', '0'); 76 | // t(NaN, NaN, 'NaN'); 77 | // t(NaN, 'NaN', 'NaN'); 78 | // t('NaN', NaN, 'NaN'); 79 | // t('NaN', 4, 'NaN'); 80 | // t('NaN', '4534534.45435435', 'NaN'); 81 | // t('NaN', 99999.999, 'NaN'); 82 | // t(Infinity, '354.345341', 'Infinity'); 83 | // t(3, -Infinity, '-Infinity'); 84 | // t(-Infinity, -Infinity, '-Infinity'); 85 | // t(-Infinity, -Infinity, '-Infinity'); 86 | // t(Infinity, '-999e999', 'Infinity'); 87 | // t('1.21123e43', -Infinity, '-Infinity'); 88 | // t('-999.0', Infinity, 'Infinity'); 89 | // t('657.342e-45', -Infinity, '-Infinity'); 90 | // t(Infinity, 123, 'Infinity'); 91 | t(100, 100, '200'); 92 | t(-999.99, '0.01', '-999.98'); 93 | t('10', 4, '14'); 94 | t('03.333', -4, '-0.667'); 95 | t(-1, -0.1, '-1.1'); 96 | t(43534.5435, '0.054645', '43534.598145'); 97 | t('99999', '1', '100000'); 98 | }); 99 | 100 | test('add_round', (c) => { 101 | const t = (appendA, appendB, expected, sd, rm) => { 102 | sd = 100; 103 | c.is( 104 | new Amount(appendA, sd) 105 | .add(new Amount(appendB, sd)) 106 | .toString(sd, { fixed: rm }), 107 | new Amount(expected, sd).toString(sd, { fixed: rm }) 108 | ); 109 | }; 110 | t( 111 | '593563412042.65071284077577238642546669796903116', 112 | '7399734315.5', 113 | '600963146358.15071284077577238642546669796903116', 114 | 78, 115 | 2 116 | ); 117 | t( 118 | '91834314419826105636913315.2250760351217688154427716325', 119 | '3453589484.0922087', 120 | '91834314419826109090502799.317284735121768815442772', 121 | 50, 122 | 2 123 | ); 124 | // t( 125 | // '1720025.190318830518434426408143', 126 | // '66109246773146613017341021115.139667467395061272917480324885924044', 127 | // '66109246773147000000000000000', 128 | // 14, 129 | // 5 130 | // ); 131 | t( 132 | '84956243086401045.957714583212857676046952761360420789675', 133 | '236266946455107694023566236.1067', 134 | '236266946540063937109967282.064414583212857676046952761360420789675', 135 | 76, 136 | 4 137 | ); 138 | // t( 139 | // '27679002965950558720382323007580555707.1129725764', 140 | // '3902452514422.1829293695210524511304537682971792', 141 | // '27679002965950558720382326910033070000', 142 | // 34, 143 | // 6 144 | // ); 145 | t( 146 | '9182914763445141913243843569172715469835.4885696227829197845538926', 147 | '7633444625534.6271670438', 148 | '9182914763445141913243843576806160095370.115', 149 | 43, 150 | 1 151 | ); 152 | t( 153 | '29495676.658886008145597040990327830', 154 | '6931766589765486564126938677173.788', 155 | '6931766589765486564126968172850.44688600814559704099032783', 156 | 97, 157 | 4 158 | ); 159 | t( 160 | '294375205.06', 161 | '0.4210755138711679324480015392679141', 162 | '294375205.4810755138711679324480015392679141', 163 | 80, 164 | 3 165 | ); 166 | t( 167 | '97.8245', 168 | '678086929547037858952090.4586', 169 | '678086929547037858952188.2831', 170 | 47, 171 | 2 172 | ); 173 | t( 174 | '6.6986623837182087397483631708665129676441', 175 | '4979636344219290763240.0069', 176 | '4979636344219290763246.705562383718208739748363170866513', 177 | 55, 178 | 6 179 | ); 180 | t( 181 | '70719500740711222218051791089542849911.838248343393159286401111269584528', 182 | '72611217359692246954.6315', 183 | '70719500740711222290663008449235096866.469748343393159286401111269584528', 184 | 100, 185 | 2 186 | ); 187 | t( 188 | '697637766264066731981534075222456637269.8150', 189 | '84841485703909.9296471188463060', 190 | '697637766264066731981534160063942341179.745', 191 | 42, 192 | 3 193 | ); 194 | t( 195 | '47061.38', 196 | '1757281333528971368573.2805225778643923494834101231347769030', 197 | '1757281333528971415634.660522577864392349483410123134776903', 198 | 76, 199 | 2 200 | ); 201 | t( 202 | '12589380812542.2800574047701400763640862141749065', 203 | '6621312631.8566626344982182410585379', 204 | '12596002125174.136720039268', 205 | 26, 206 | 6 207 | ); 208 | t( 209 | '7882195982742109440258991594228896.97647548424454', 210 | '549984552171.2', 211 | '7882195982742109440259541578781068.17647548424454', 212 | 62, 213 | 3 214 | ); 215 | t( 216 | '79344567.9583146261700423456539738617142116267', 217 | '76770085769491324895202178331658.82467912070', 218 | '76770085769491324895202257676226.7829937468700423456539738617142116267', 219 | 75, 220 | 1 221 | ); 222 | t( 223 | '9803952036104667191144020833.60300205', 224 | '9495298573633466486861.2007267111985', 225 | '9803961531403240824610507694.8037287611985', 226 | 91, 227 | 6 228 | ); 229 | t( 230 | '42717742030595859.2062217089376928894751002693', 231 | '4392.7633096899809016584837135701339842639427', 232 | '42717742030600251.9695313989185945479588138394339842639427', 233 | 84, 234 | 1 235 | ); 236 | t( 237 | '388528785785093377176597943386.8074346502385319459876897943393772766', 238 | '780579558740664211027356329768421.54760979278144', 239 | '780968087526449304404532927711808.355044443019971', 240 | 48, 241 | 3 242 | ); 243 | t( 244 | '8.1572164344501210656681193', 245 | '141383742681203268175705548539.5903424774279108405668653', 246 | '141383742681203268175705548547.7475589118780319062349846', 247 | 87, 248 | 1 249 | ); 250 | t( 251 | '623051342929926112359884960895415731939.5562490236742100882954960168', 252 | '503245645050146765988526273.758561914511343318476865211', 253 | '623051342930429358004935107661404258213.3148109381855534067723612278', 254 | 74, 255 | 6 256 | ); 257 | t( 258 | '6222.818987', 259 | '2745.84213504008632396977994287249342', 260 | '8968.66112204008632396977994287249342', 261 | 45, 262 | 5 263 | ); 264 | t( 265 | '58075371184786989216823527114006.8193336921861462723001823062864617438', 266 | '831577.37480626', 267 | '58075371184786989216823527945584.1941399521861463', 268 | 48, 269 | 4 270 | ); 271 | t( 272 | '171792414269.20500878', 273 | '7228500834975.6421662524366409', 274 | '7400293249244.8471750324366409', 275 | 86, 276 | 5 277 | ); 278 | t( 279 | '3686865567196977754914570753132702.86443144944286374816106555429', 280 | '252977292743732124354509767399171006.56792658736276850447471854354498', 281 | '256664158310929102109424338152303709.43235803680563225263578409783498', 282 | 76, 283 | 1 284 | ); 285 | t( 286 | '7772413527479.0292045567366574215506978466', 287 | '6951039673098790484081676762223797578.48972170861559580041987995', 288 | '6951039673098790484081684534637325057.5189262', 289 | 44, 290 | 1 291 | ); 292 | t( 293 | '897519.488463768126475213360003650464', 294 | '243345915368302813437999412228446907.03319139912318828693096', 295 | '243345915368302813437999412229344426.521655167249663500290963650464', 296 | 69, 297 | 1 298 | ); 299 | t( 300 | '9944701104687189448523634989441.1145387693210320276079647', 301 | '76461573432179845156280348475771563.06533912678377627209249', 302 | '76471518133284532345728872110761004.179877896', 303 | 44, 304 | 1 305 | ); 306 | t( 307 | '49868358282915444463601511745776323.6756', 308 | '426.127124853', 309 | '49868358282915444463601511745776749.802724853', 310 | 62, 311 | 1 312 | ); 313 | t( 314 | '462611390833337670596267664076995136589.8078310066750996553090298147792146374', 315 | '86220896429616059447274947806526227.378336884633', 316 | '462697611729767286655714939024801662817.18616789130809965530902981477921', 317 | 71, 318 | 5 319 | ); 320 | t( 321 | '3082844058.9630699111683783220405718767649310463', 322 | '9821678552282552.46', 323 | '9821681635126611.4230699111683783220405718767649310463', 324 | 85, 325 | 6 326 | ); 327 | t( 328 | '18829358460454562606796373222566693.8406444934478081754024757172', 329 | '245696827314924767344.62999383040940', 330 | '18829358460454808303623688147334038.4706383238572081754024757172', 331 | 65, 332 | 1 333 | ); 334 | t( 335 | '451443519.5', 336 | '40545287685504171383686533617478.0379433921591774655032923922180', 337 | '40545287685504171383686985060997.537943392159177465503292392218', 338 | 85, 339 | 1 340 | ); 341 | t( 342 | '9103707265251.90817165947074590284', 343 | '7132.1726911109784489', 344 | '9103707272384.08086277044919480284', 345 | 57, 346 | 1 347 | ); 348 | t( 349 | '1327536619868001635732671456652796081.82503453353162015625727828', 350 | '777720909904.3138244842545463', 351 | '1327536619868001635732672234373705986.13885901778616645625727828', 352 | 94, 353 | 6 354 | ); 355 | t( 356 | '94785498339319783.701937', 357 | '5.5284513', 358 | '94785498339319789.2303883', 359 | 45, 360 | 3 361 | ); 362 | t( 363 | '45087586598522825025991129716000.35589454049730836', 364 | '85381176658152430597133806358376750.386015723730182079339361073643', 365 | '85426264244750953422159797488092750.741910264227490439339361073643', 366 | 95, 367 | 3 368 | ); 369 | t( 370 | '1.577', 371 | '5867361133548635578508631112769.512', 372 | '5867361133548635578508631112771.089', 373 | 37, 374 | 6 375 | ); 376 | }); 377 | -------------------------------------------------------------------------------- /src/models/amount.spec/sub.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Amount } from '../..'; 3 | 4 | test('sub', (c) => { 5 | let fixed = 4; 6 | 7 | const t = (minuend, subtrahend, expected) => { 8 | c.is( 9 | new Amount(expected, 100).toString(100, { fixed }), 10 | new Amount(minuend, 100) 11 | .sub(new Amount(subtrahend, 100)) 12 | .toString(100, { fixed }) 13 | ); 14 | }; 15 | 16 | t(1, 0, '1'); 17 | t(1, -0, '1'); 18 | t(-1, 0, '-1'); 19 | t(-1, -0, '-1'); 20 | t(0, 1, '-1'); 21 | t(0, -1, '1'); 22 | t(-0, 1, '-1'); 23 | t(-0, -1, '1'); 24 | 25 | t('0', '-0', '0'); // 0 - -0 = 0 26 | t('-0', '0', '-0'); // -0 - 0 = -0 27 | t('0', '0', '-0'); // 0 - 0 = -0 28 | t('-0', '-0', '-0'); // -0 - -0 = -0 29 | t('1', '1', '-0'); // 1 - 1 = -0 30 | t('-1', '-1', '-0'); // -1 - -1 = -0 31 | 32 | fixed = 3; 33 | t('0', '-0', '0'); // 0 - -0 = 0 34 | t('-0', '0', '-0'); // -0 - 0 = -0 35 | t('0', '0', '-0'); // 0 - 0 = -0 36 | t('-0', '-0', '-0'); // -0 - -0 = -0 37 | t('1', '1', '-0'); // 1 - 1 = -0 38 | t('-1', '-1', '-0'); // -1 - -1 = -0 39 | 40 | fixed = 4; 41 | t('0', '-0', '0'); // 0 - -0 = 0 42 | t('-0', '0', '-0'); // -0 - 0 = -0 43 | t('0', '0', '0'); // 0 - 0 = 0 44 | t('-0', '-0', '0'); // -0 - -0 = 0 45 | t('1', '1', '0'); // 1 - 1 = 0 46 | t('-1', '-1', '0'); // -1 - -1 = 0 47 | 48 | t(1, '0', '1'); 49 | t(1, '1', '0'); 50 | t(1, '-45', '46'); 51 | t(1, '22', '-21'); 52 | // t(1, 0144, '-99'); 53 | t(1, '0144', '-143'); 54 | t(1, '6.1915', '-5.1915'); 55 | t(1, '-1.02', '2.02'); 56 | t(1, '0.09', '0.91'); 57 | t(1, '-0.0001', '1.0001'); 58 | t('0', 0, '0'); 59 | t('0', '0', '0'); 60 | t(3, -0, '3'); 61 | t(9.654, 0, '9.654'); 62 | t(0, '0.001', '-0.001'); 63 | t(0, '111.1111111110000', '-111.111111111'); 64 | t(-1, 1, '-2'); 65 | t(-0.01, 0.01, '-0.02'); 66 | t(54, -54, '108'); 67 | t(9.99, '-9.99', '19.98'); 68 | t('0.0000023432495704937', '-0.0000023432495704937', '0.0000046864991409874'); 69 | t(100, 100, '0'); 70 | t(-999.99, '0.01', '-1000'); 71 | t('10', 4, '6'); 72 | t('03.333', -4, '7.333'); 73 | t(-1, -0.1, '-0.9'); 74 | t(43534.5435, '0.054645', '43534.488855'); 75 | t('99999', '1', '99998'); 76 | }); 77 | 78 | test('sub_round', (c) => { 79 | const t = (minuend, subtrahend, expected, sd, rm) => { 80 | sd = 100; 81 | c.is( 82 | new Amount(expected, sd).toString(sd, { fixed: rm }), 83 | new Amount(minuend, sd) 84 | .sub(new Amount(subtrahend, sd)) 85 | .toString(sd, { fixed: rm }) 86 | ); 87 | }; 88 | 89 | t( 90 | '0.9999999999999999999876881224', 91 | '640033848998621900.19735', 92 | '-640033848998621899.1973500000000000000123118776', 93 | 49, 94 | 2 95 | ); 96 | t( 97 | '1.000000000000000000000000119823914072125503157964', 98 | '3062705906358982193618.98485068689066024569704943195219', 99 | '-3062705906358982193617.984850686890660245697049312128275927874496842036', 100 | 94, 101 | 6 102 | ); 103 | t( 104 | '0.9999999999999998265534546200191793251275036241640334423212917258444307775885354012967962729546', 105 | '258319.60193860385931337782', 106 | '-258318.60193860385931355126654537998082067487249637583596655767870827415556922241146459870320372705', 107 | 98, 108 | 4 109 | ); 110 | t( 111 | '0.99999999999999999989761950386191857639251221379227189329555371776218724908031098142396778431218707', 112 | '726355355995140387099932991211229400.158448248205932318709', 113 | '-726355355995140387099932991211229399.1584482482059323188113804961380814236074877862077281067044463', 114 | 97, 115 | 5 116 | ); 117 | t( 118 | '0.99998712469413', 119 | '794547422607164224869232243086324.8360594446035869419718131828058', 120 | '-794547422607164224869232243086323.836072319909456942', 121 | 52, 122 | 3 123 | ); 124 | t( 125 | '0.9999999999998534962024501557924401736482182589661527', 126 | '5698985430281057199751490373953345194.59621391632', 127 | '-5698985430281057199751490373953345193.596213916320146503797549844207559826351781741', 128 | 82, 129 | 2 130 | ); 131 | t( 132 | '0.281969520841', 133 | '525591380542776020976261376042231280.99050263561330377859863953', 134 | '-525591380542776020976261376042231280.70853311477230377859863953', 135 | 97, 136 | 6 137 | ); 138 | t( 139 | '0.9999999999999959369334762196188619130301649399733281630919124260101076514635673054327688686405515', 140 | '6892460359743232550483058284.326802388', 141 | '-6892460359743232550483058283.326802388000004063067', 142 | 49, 143 | 3 144 | ); 145 | t( 146 | '1.000000000000000000000000138365204598651999681848', 147 | '4.585694782001886784351261812739727', 148 | '-3.585694782001886784351261674374522401348000318152', 149 | 93, 150 | 4 151 | ); 152 | t( 153 | '0.9999', 154 | '560937701245564854434830710385849233.169675002587006894673914472266909', 155 | '-560937701245564854434830710385849232.169775002587006894673914472266909', 156 | 79, 157 | 2 158 | ); 159 | t( 160 | '0.9999998719213481023531897427716397778263888401893910733910220314504983682315253', 161 | '38792839790712300540097766016170179.72942773540650288197576877282', 162 | '-38792839790712300540097766016170178.72942786348515477', 163 | 52, 164 | 2 165 | ); 166 | t( 167 | '1.000000000000000000000000000000011337132819044149107', 168 | '97753977764753722505638575181479019.579624307127638397405681', 169 | '-97753977764753722505638575181479018.579624307127638397405681', 170 | 60, 171 | 4 172 | ); 173 | t( 174 | '1.01261395071914172725658272307075493652694434213346101402984', 175 | '1049710327243884264.3689519321', 176 | '-1049710327243884263.35633798138085827274341727692924506347305565786653898597016', 177 | 95, 178 | 6 179 | ); 180 | t( 181 | '0.99999999998635625720009473958665342273155724466867056964747162778310004832306258429717336387169', 182 | '12.1520940727652904005353452973720184', 183 | '-11.1520940727789341433352505577853649772684427553313294303525283722168999516769374157', 184 | 84, 185 | 2 186 | ); 187 | t( 188 | '0.999999999999999999999999999999999524622195377650474620654559769514364455807304047052', 189 | '7002515.6669052486740556972355374422866181829657', 190 | '-7002514.6669052486740556972355374', 191 | 32, 192 | 5 193 | ); 194 | t( 195 | '0.99999998878183120416824285037203', 196 | '38327.8407', 197 | '-38326.8407000112181687958317', 198 | 27, 199 | 1 200 | ); 201 | t( 202 | '0.999999999999999999999999999', 203 | '2.9094462300972', 204 | '-1.909446230097200000000000001', 205 | 55, 206 | 4 207 | ); 208 | t( 209 | '0.9999999999999999999999999996779494408046302584202610482', 210 | '530770914537012205776892173.865394333047000705228144653463710904', 211 | '-530770914537012205776892172.8653943330470007052281446537857614631953697415797389518', 212 | 92, 213 | 1 214 | ); 215 | t( 216 | '1.0000000000000000000000000000000000000211924266373601875662343025201383719314762448306414697', 217 | '33086682481347.6314289196308731545826716', 218 | '-33086682481346.63142891963087315458267159999999999997880757336264', 219 | 64, 220 | 6 221 | ); 222 | t( 223 | '0.9999999999999999999999999999999999999995086077216389080791500865', 224 | '43010632125906095550264.572650474808124', 225 | '-43010632125906095550263.572650474808124', 226 | 51, 227 | 4 228 | ); 229 | t( 230 | '0.999999999999999999999999999999392910865226086344321', 231 | '4584324554085375538027273692534240749302.742306700543168212764946899734664', 232 | '-4584324554085375538027273692534240749301.742306700543168212764946899735271089134773913655679', 233 | 91, 234 | 3 235 | ); 236 | t( 237 | '0.9999999999999999999999999999999999999921018515820697887426805224100947352172147724109795779', 238 | '830076114723591620059141676694501404709.4930298672', 239 | '-830076114723591620059141676694501404708.4930298672', 240 | 70, 241 | 4 242 | ); 243 | t( 244 | '1.000154688825648481267644205561160017273389804317525860318979525925354', 245 | '5590.1166644928260', 246 | '-5589.1165098040003515187323557944388', 247 | 35, 248 | 1 249 | ); 250 | t('1.00000000000000', '95.33659318661235', '-94.33659318661235', 70, 4); 251 | t( 252 | '0.99999998955431286156626', 253 | '4902.7915368894281469434', 254 | '-4901.79153689987383408183374', 255 | 80, 256 | 2 257 | ); 258 | t( 259 | '1.13135445288218095176064019413277569648722343676398316921046471781497209018036761926374980678', 260 | '3619072849985061077293.9012666679115988775', 261 | '-3619072849985061077292.76991222', 262 | 30, 263 | 4 264 | ); 265 | t( 266 | '1.0000000000', 267 | '53.0721765572571058497526522442', 268 | '-52.0721765572571058497526522442', 269 | 68, 270 | 1 271 | ); 272 | t( 273 | '1.0000000000000000010868737982065976552678875918917937663867883830744', 274 | '72274249298653961307161266.2429171261996071156986500328106121752', 275 | '-72274249298653961307161265.2429171261996071146117762346040145199321124081082062336132116', 276 | 87, 277 | 1 278 | ); 279 | t( 280 | '1.00000000000000000000000000000000001026071005118758019147403591153361193415866494922695', 281 | '145.57075488191632841598918', 282 | '-144.57075', 283 | 8, 284 | 4 285 | ); 286 | t( 287 | '1.0000000', 288 | '526650518510726171000913231536479079.037380846608', 289 | '-526650518510726171000913231536479078.037380846608', 290 | 75, 291 | 3 292 | ); 293 | t( 294 | '0.999999999999999999999999999999999999997106197957371323499604358266134753009', 295 | '3584419408840321948825.676382768', 296 | '-3584419408840321948824.6763827680000000000000000000000000000028938020426286765003956', 297 | 83, 298 | 2 299 | ); 300 | t( 301 | '0.99999999880681185203794773690375608443466417194000936368334849553283470358861975018', 302 | '5.0975685392156383162823410521', 303 | '-4.0975685404088264642443933152', 304 | 30, 305 | 3 306 | ); 307 | t( 308 | '0.999999998177929451382948935869', 309 | '10103621840183015475172718.257', 310 | '-10103621840183015475172717.257000001822070548617051064131', 311 | 70, 312 | 4 313 | ); 314 | t( 315 | '1.000000000000000000000000000000000000000144374259221118949564393862079', 316 | '99403923311484468697407136499693668.59308470138007980955', 317 | '-99403923311484468697407136499693667.5930847013800798095499999999999999999998556257407788811', 318 | 90, 319 | 5 320 | ); 321 | t( 322 | '0.999999975691341207638223751903870453583011108741353767405960466759765078360050260758989020', 323 | '4.6084648305890876715', 324 | '-3.60846485489774646386177624809612954641698889125864623259403', 325 | 60, 326 | 2 327 | ); 328 | t( 329 | '0.999999999999999999895045963233918813836521284190202155769449', 330 | '429720283413999973159.756715979', 331 | '-429720283413999973158.756715979000000000104954036766081186163478715809797', 332 | 72, 333 | 2 334 | ); 335 | t( 336 | '0.999999999999999999998773564582848665317660249642891385732648080962811751', 337 | '59990316469528.036171429611687', 338 | '-59990316469527.036171429611687000001226435417151334682339750357108614267351919037188249', 339 | 100, 340 | 2 341 | ); 342 | t( 343 | '1.000011075533608457976204143394074562732956861700803109887313366700147999338062991286462275342326012', 344 | '2792362408227880809678840930573.860305000', 345 | '-2792362408227880809678840930572.8602939244663915420237958566059254372670431382991968901126866333', 346 | 97, 347 | 6 348 | ); 349 | t( 350 | '1.000000000000000000000000000000000000015973553865744063022762207811071071600821959910304633272757', 351 | '1809301547973844603902311.5654087887338690', 352 | '-1809301547973844603902310.56540878873386899999999999999999999998402644613425593697723779218893', 353 | 93, 354 | 3 355 | ); 356 | t( 357 | '0.999999999999999999999999999999999999893329386351827298183253105991893674068332982263468835259', 358 | '2757167858879.682885', 359 | '-2757167858878.6828850000000000000000000000000000001066706136481727018167468940081063259317', 360 | 89, 361 | 4 362 | ); 363 | t( 364 | '1.0000000000000000000000000000111360158322886005852576303546314011852429419271247', 365 | '93939314.46578658799426616', 366 | '-93939313.46578658799426615999999999998886398416771139941474236964536859881475705807', 367 | 82, 368 | 2 369 | ); 370 | t( 371 | '0.98773519715148', 372 | '2549541405629529059884688169455754452231.105907313888980241014461140', 373 | '-2549541405629529059884688169455754452230.11817211673750024101446114', 374 | 74, 375 | 6 376 | ); 377 | t( 378 | '0.999999999999999999999999999995658364099102648491089', 379 | '414085174968159470504048.51', 380 | '-414085174968159470504047.510000000000000000000000001', 381 | 51, 382 | 3 383 | ); 384 | t( 385 | '0.999999999999999295252487060650057351383150516174879', 386 | '6306669915887513524394303183172309.934564178035290474234779480', 387 | '-6306669915887513524394303183172308.934564178035291178982292419349942648616849483825121', 388 | 99, 389 | 4 390 | ); 391 | t( 392 | '1.00000000000000000000081253348145920781026085647520801897170982656182514222627649830571537940570097', 393 | '34180955261225283278.9546214472426960215969277072', 394 | '-34180955261225283277.954621447242696021596115173718540792189739143524791981028290173438174857773723', 395 | 98, 396 | 1 397 | ); 398 | t( 399 | '1.0001', 400 | '1607764700164263.45772807689415459406211472014192328', 401 | '-1607764700164262.45762807689415459406211472014192328', 402 | 65, 403 | 3 404 | ); 405 | t( 406 | '0.99999999999999999999999999999999999998756496998614330743704048912', 407 | '36423078435.49585469762969072900556033', 408 | '-36423078434.49585469762969072900556033000000000001243503', 409 | 56, 410 | 1 411 | ); 412 | t( 413 | '0.99999999999999999999999999999815156221279091250236161202515555239214607133', 414 | '184535429818126810905.866146730925744980274670738002788577', 415 | '-184535429818126810904.866146730925744980274670738004637014787209087497', 416 | 69, 417 | 1 418 | ); 419 | t( 420 | '1.0000000', 421 | '6626035360301835643744155055681183579.72680430492878230246147491362', 422 | '-6626035360301835643744155055681183578.7268', 423 | 41, 424 | 4 425 | ); 426 | t( 427 | '1.0000000000000000014816657759635935574232', 428 | '32818928503629136.252295990697644196564215870800902906892', 429 | '-32818928503629135.2522959906976', 430 | 30, 431 | 5 432 | ); 433 | t( 434 | '1.000000000', 435 | '468078670327435157159036637213089.68427', 436 | '-468078670327435157159036637213088.68427', 437 | 91, 438 | 2 439 | ); 440 | t( 441 | '1.00000000000000000000000000', 442 | '248084803156.38225825687792564146883', 443 | '-248084803155.38225825687792564146883', 444 | 45, 445 | 6 446 | ); 447 | t( 448 | '0.999996430603395547879085578374943218533075889', 449 | '752798095516784566364107011187.1828743644733064738998075060481075333023', 450 | '-752798095516784566364107011186.182877933869910926020721927673164314769224111', 451 | 88, 452 | 3 453 | ); 454 | t( 455 | '0.9999999864688912316764271500401957202149887487506193902184177755581162789923303216472682', 456 | '434.57654697091569924806', 457 | '-433.576546984446808016383572849959804279785011251249380609781582224441883721007669678353', 458 | 87, 459 | 5 460 | ); 461 | t( 462 | '0.999999999999999999999999999982814515318881641819728269066787504001787393727056279440923', 463 | '83875621901513231603980702356510524090.18029474594', 464 | '-83875621901513231603980702356510524089.18029474594', 465 | 66, 466 | 2 467 | ); 468 | t( 469 | '1.10455706926852759636854514325898495936688404757420394401166868002', 470 | '35601365816445214252.5525881378161872928170', 471 | '-35601365816445214251.44803106854765969644845485674101504063311595242579605598833131998', 472 | 98, 473 | 1 474 | ); 475 | t( 476 | '1.0000000000000000000000000100777014575445669415762036017243568735466409286660372771012100670802724', 477 | '899760814583915.493920081239', 478 | '-899760814583914.49392008123899999999999999', 479 | 42, 480 | 6 481 | ); 482 | }); 483 | -------------------------------------------------------------------------------- /src/models/amount.spec/toBigInt.spec.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi'; 2 | import test from 'ava'; 3 | import { Amount } from '../..'; 4 | 5 | test('toBigInt', (c) => { 6 | const t = (expected, n) => { 7 | c.assert(JSBI.equal(new Amount(n, 0).toBigInt(), JSBI.BigInt(expected))); 8 | }; 9 | 10 | t('0x0', '0'); 11 | t('0x9', '9'); 12 | t('0x5a', '90'); 13 | t('0x0', '0'); 14 | t('0xffffffff', '4294967295'); 15 | t('0x0', '0'); 16 | t('0x60bf8d3f1cb70827109c', '456880688358819786854556'); 17 | t('0x27', '39'); 18 | t('0x14a3b3', '1352627'); 19 | t( 20 | '0xc8841d8a3e0c3184a1d13f24b7c8abcbf36', 21 | '1091713357762058306584556087055815769112374' 22 | ); 23 | t('0x2266ce3388fee3c292256', '2599310186932964178862678'); 24 | t( 25 | '0x784afb326df4e14f44919e903da2daab2c6a8c2b7', 26 | '10988016479801545098971638340838655979698598232759' 27 | ); 28 | t('0x47d', '1149'); 29 | t('0x613b8b5', '101955765'); 30 | t('0x6b63', '27491'); 31 | t( 32 | '0x2d3777760a984a21c97a0d20c628c419cab2bfc67f', 33 | '66084232243819558824679961490043082487366953191039' 34 | ); 35 | t('0x12c9942167ae93c737fc5ff39d', '1488492491002839592133151683485'); 36 | t('0xc2c44', '797764'); 37 | t( 38 | '0xfe787f84cf0ae7655d44db4e793b2a98ae8f', 39 | '22167524012235587957931446373184044722073231' 40 | ); 41 | t( 42 | '0x186b6b31486165c0bc8c778fd49af12ea08f7dad332', 43 | '571028668500605890623232664155788301681845784466226' 44 | ); 45 | 46 | t('0x2aef3ee6f864b26772c23cf6', '13287600730756091106849602806'); 47 | t( 48 | '0x193c4c1a69add29d9a5101ced0568697d923', 49 | '2198325248780085112365807036920470754023715' 50 | ); 51 | t('0x11a507516c68d662f15d9', '1333191524844399246644697'); 52 | t('0xa2669c148f4ea9e', '731388268367899294'); 53 | t('0xed2a498f218583395d57871', '4587441849070983049702111345'); 54 | t('0x1fbbac5129573', '558254565528947'); 55 | t( 56 | '0x1b91780b06a70cf46e5fa0fa8e63407e80030f027a', 57 | '40291024919626546988689644749021723762456102109818' 58 | ); 59 | t('0x25a90a2b75d596836e10d26ec831', '763841862190387816235893777156145'); 60 | t('0xfbd45cc9e2', '1081599642082'); 61 | t('0x621e835f12ad64e909cf7', '7413676543182627283180791'); 62 | t('0x17de6483675cb29063c', '7044803064789933295164'); 63 | t('0x2ecc612', '49071634'); 64 | t( 65 | '0x14bb4a5d83f3d4bf43b66adada6b0e993784', 66 | '1805977369681030296729705669300424034301828' 67 | ); 68 | t('0x8b63e2dca39c336b18', '2571294983995377543960'); 69 | t('0x3c1440de515e09b31f73cdd0da6e2', '19496787444412678571409607909811938'); 70 | t('0xd39570c80caa28598ea432085259', '4291428326751199011126676862554713'); 71 | t( 72 | '0x1d54a95161a44ae4d216da350d20954f13992a85', 73 | '167448744619550277927636746527062265701749828229' 74 | ); 75 | t( 76 | '0xe3ffcc64277a78a8256d7e1d7c05eaa9bf', 77 | '77584111689906087598180624752236124875199' 78 | ); 79 | t('0x22f4736', '36652854'); 80 | t('0x2c35e4d05c0', '3038123984320'); 81 | t('0x430e68bd9ad4952', '301994663360678226'); 82 | t('0x1d854cf1f48c4529d864b686f2d', '37421939842938093643010520149805'); 83 | t('0x5db3324efd09a682cd', '1728459668785308533453'); 84 | t('0x1b60d2a23a53bc5a', '1972808230993378394'); 85 | t( 86 | '0x1b7b2cc91769682ad42af60ef67c77c99ac81d227197', 87 | '10281919698706053870205728976619722313496732052386199' 88 | ); 89 | t( 90 | '0x78a9c9d91cb45ec86599f1b402f6635265b69a0317', 91 | '176349517282896190364726424897935661923732528169751' 92 | ); 93 | t( 94 | '0x310f7e944db1e20ca7d4db559be74aafcd', 95 | '16694431636419175813863637436681440374733' 96 | ); 97 | t( 98 | '0x11a4ae80f151dad1752e32c310a41dae552c37b7b4', 99 | '25785693883194456949802584063055012266517133244340' 100 | ); 101 | t( 102 | '0x320abd803f00618b853f0f4ad06d76e5ead8eb20d', 103 | '4571024861681675091071130733403917525672421929485' 104 | ); 105 | t('0x17f27d4daa32a', '421284028261162'); 106 | t('0x3fd14203387143ed10002039a6fed', '20709934734392064809458286974365677'); 107 | t('0xddf93c33cbe97520fb19a19', '4293593374816726677147720217'); 108 | t( 109 | '0x37bc13d810602438ce47db31c0e7a87e6b7831', 110 | '1242924848237600517379561065653635852461111345' 111 | ); 112 | t('0x15a', '346'); 113 | t('0x533921', '5454113'); 114 | t( 115 | '0x770d01b680082f7c23db6444c14f885dacb63824', 116 | '679659960629478197799609320106784570818809968676' 117 | ); 118 | t('0x1d35', '7477'); 119 | t('0xaa906c1dabfebe6da', '196647076124632540890'); 120 | t('0x655', '1621'); 121 | t('0x11af668734ef7a60033c5f518c530', '5739161962048683655238213338449200'); 122 | t( 123 | '0x9035964bc6d843d71d2ef9124632b039ca9ad9d6062cd', 124 | '863281823741412484448091339719676346358410323679011533' 125 | ); 126 | t('0xfa3d98dfd239530c9a9556', '302522339292227790310118742'); 127 | t('0x10fdb7f', '17816447'); 128 | t('0x18d260c9d8f110eea9d29', '1875481379014053338979625'); 129 | t('0x6dc6205912265fe68cb44ae5c', '543574178265693146271925448284'); 130 | t('0x19d6ad933a8e5', '454557212846309'); 131 | t('0x3e1ddad6b81e605', '279748388776437253'); 132 | t( 133 | '0xe073b0188f2d6667b75f11f65a82fda', 134 | '18646734802776877837813543784990388186' 135 | ); 136 | t( 137 | '0x81f531048349c33f91627d55e1b6bdd8a78', 138 | '707557451293532648521737780488438655322744' 139 | ); 140 | t('0x78b8be03f14', '8295928577812'); 141 | t('0x8', '8'); 142 | t( 143 | '0x659874ecb742089cd0f2fc72a2e912d48334f2d', 144 | '36250497918673919606249316600592246963107483437' 145 | ); 146 | t('0x2a03a8a82cd13dc18b5', '12400430345351117674677'); 147 | t( 148 | '0x1e634adab5f805f52f70cac738dd261d7d6', 149 | '165447251922571882378062853693168471365590' 150 | ); 151 | t( 152 | '0x2d0b0ae2b685425d29b0ecb3b5edfa717cb1417', 153 | '16071927572096392119675195392748578598193665047' 154 | ); 155 | t('0x7a87c66768a00656e6143d364', '606741009919509661621760021348'); 156 | t('0x32f102da079f77cac41', '15035262184397547023425'); 157 | t('0xa440f8912c06198b', '11835733123122207115'); 158 | t( 159 | '0xc008c78c61addc8e085ca3ff6e76fa13', 160 | '255257360887256236558111057213476239891' 161 | ); 162 | t('0x11649c762', '4668901218'); 163 | t('0x5572f75ca5f099eda', '98516089885791198938'); 164 | t('0xaf', '175'); 165 | t('0xc41a62e8bd1c7123c92220df', '60690961081480451125474894047'); 166 | t( 167 | '0x765e3e05ca0c4b853679e80667d71bb51ec118c87c1', 168 | '2767923543808337676781898227256098432891809077954497' 169 | ); 170 | t( 171 | '0x643b91b0289fe98c02b3f790ed41085ba228ba4c', 172 | '572227512274253374435718937361849836268824345164' 173 | ); 174 | t('0x12', '18'); 175 | t( 176 | '0x28ea59cbb990457243147551fc12145', 177 | '3399121160718415003848736585359106373' 178 | ); 179 | t('0x1c149ecb87796c6a9116eaa5', '8690508679745247747029527205'); 180 | t('0x3d24cfd5a3d86612', '4405874851959039506'); 181 | t('0x55', '85'); 182 | t( 183 | '0x2b1dc5d2236fc61848eb80e4032e842131c7ea1edd0', 184 | '1008232683040405259975519873135905613349630326730192' 185 | ); 186 | t('0x3591a1d0aaaa14514d8a0a8826ee', '1086505871907198290436180679337710'); 187 | t('0x1cba2a2f26dfe80b4c05d5f5496b9', '9322511400054499645799889634629305'); 188 | t('0xdd2f83', '14495619'); 189 | t('0x15c6f07', '22834951'); 190 | t('0x2f8f19d0d3ff', '52291659944959'); 191 | t('0x66877666510', '7045749564688'); 192 | t( 193 | '0x153d9792e5b2bd96679de7bbee9f34cd131', 194 | '115644794069417523805676707579411471061297' 195 | ); 196 | t( 197 | '0x1f6ce9be57023fe14fa060be4c01e7a', 198 | '2610723550735662335298250928784678522' 199 | ); 200 | t( 201 | '0x9e63632c0dca5485e48bee9ad1b5a28049aa157e307', 202 | '3703754566336033126591165191492065433661004773319431' 203 | ); 204 | t('0x8371b0d9c54b9de08c7', '38795452173112617863367'); 205 | t('0xd3b35e07218e7b0c20bfc', '15995649127931990167915516'); 206 | t('0xc6b6dfbdc40011198bd286c0', '61499113032079803212142642880'); 207 | t( 208 | '0x7cf9a45c21858e891798ad503930df741b93f61f127', 209 | '2922422513262668930583564242609727203228735133315367' 210 | ); 211 | t( 212 | '0x678049c017727c47ff4cc7ba35a849365fd111', 213 | '2308152223994224783375970725248611287908208913' 214 | ); 215 | t('0x72b397d', '120273277'); 216 | t('0xf9445c1a', '4182006810'); 217 | t('0x4aff51f382c09c710fc5302d96b22', '24338029676537462451324711222209314'); 218 | t( 219 | '0x10459598a086ea0f535900e70abab1e2ce7377', 220 | '362873575854611890806983645493404163992613751' 221 | ); 222 | t('0x13640c20a010754124', '357697310375837778212'); 223 | t('0x14147e01ca6501f252ca', '94825344299726024102602'); 224 | t('0x466', '1126'); 225 | t('0x74c698493dd81930c415', '551457940709438745789461'); 226 | t( 227 | '0x2e5f2b623ba0cfee63689d427bad6eb6be5', 228 | '252471852786048400736119714699808445524965' 229 | ); 230 | t( 231 | '0x38a52cc273671b0b8b078ec7f7f17102c9bd8c', 232 | '1263230489190036409842245570352830262209527180' 233 | ); 234 | t( 235 | '0x78655d620287e6db76141a906c5701bdc47', 236 | '655497934873142286500331809443840511433799' 237 | ); 238 | t('0x6eecb9a0d2fdd3b9a541f3', '134099742883823958727410163'); 239 | t( 240 | '0x54bd61680d7ceb5beb37894e46599ef77', 241 | '1802215542237292717156325458542562242423' 242 | ); 243 | t( 244 | '0x3a847dfcee83cd860720c8c0d8b53c7fda25d77', 245 | '20879758636120455697847204680129815020870720887' 246 | ); 247 | t( 248 | '0x76eb310380a6f577f166b90d28c3b107a', 249 | '2529121398079147937688504401561827086458' 250 | ); 251 | t('0xb73dda2c', '3074284076'); 252 | t('0x3fd278b8d795a', '1122771216988506'); 253 | t( 254 | '0x778da0c24f47774967183e20f8b1827299f', 255 | '650909720313013143267150228395755917748639' 256 | ); 257 | t('0x6500d', '413709'); 258 | t( 259 | '0x5a1bf29425c38c8f22a81fc3f105169ce4907acbb7', 260 | '131694699796400245476746024523885969905255483886519' 261 | ); 262 | t('0x1e946da5ed27825dff2', '9025563349429600509938'); 263 | t('0x23d288aea4', '153856028324'); 264 | t('0x4bdc3b2b93e8cb0967b92', '5731840537208636428417938'); 265 | t('0x1c57b1', '1857457'); 266 | t( 267 | '0xb6dab6e4caceab483d2085d9d1766a088a19', 268 | '15928860703021466826033711809396661962705433' 269 | ); 270 | t('0x3c9e2c9d', '1016999069'); 271 | t('0x2d047117ed235996e16cb8', '54422637554743220665150648'); 272 | t( 273 | '0xdda0ed04ef2e2b22a78e6455b862865ce', 274 | '4713519390205494839540473117533526320590' 275 | ); 276 | t('0xa', '10'); 277 | t('0x984e5039808eb14e793dd95', '2946024784801310608062668181'); 278 | t('0x18b', '395'); 279 | t('0x1e30c76d4c3babeded1eb9d54', '149496328017603561036003908948'); 280 | t('0x4d1fb3fde8905', '1356776945387781'); 281 | t( 282 | '0x1c6ad164b71af7cf0a92d7c2cf97ac3539cf4a190a6', 283 | '664509895940083432745213666480332815238360970924198' 284 | ); 285 | t('0x29d730cd9c087e9', '188433258449504233'); 286 | t( 287 | '0x4bbffc982422401d23e107db16617c0c0', 288 | '1611023225678920504550274579287300882624' 289 | ); 290 | t( 291 | '0x34fa796a05fee3b566107aef6fea5327c9f794ce6f2', 292 | '1238848647360391370812154035309938766206217768199922' 293 | ); 294 | t('0x2907465cdd296accc8ab4bd', '793605010131443253911401661'); 295 | t('0x8aa7eb5be50', '9528363302480'); 296 | t('0x15c14f12e032624a1206', '102735615609474072056326'); 297 | t( 298 | '0x2d2650f939302bf9306f3db1fd19a7666712ff8', 299 | '16109941674613560268665251639244419228113121272' 300 | ); 301 | t( 302 | '0xc0b42d10550ce0f4ea97567ede6bae89e93b', 303 | '16786869625005321512335207434991310677338427' 304 | ); 305 | t('0xbcb3750ecd418048f3c9a907a1', '14950433883660044294408311998369'); 306 | t('0x5dd205e2db781f491bff', '443054323304979904404479'); 307 | t('0x3', '3'); 308 | t( 309 | '0x108ba7dc0b83574862fffc703b989d5b578d862', 310 | '5903642409059499044928754369694490849414010978' 311 | ); 312 | t('0x464a1', '287905'); 313 | t('0x172f', '5935'); 314 | t('0x3da62e0cd7d5edc8b6731fc', '1192467796933113553619464700'); 315 | t('0x16077c0833e289da0b', '406367684695924791819'); 316 | t( 317 | '0x784c5768a01120f03ef7a73160baaa0c5f', 318 | '40935359210071367599328032328982052080735' 319 | ); 320 | t('0x3ba90bcf292a852be', '68783684858858197694'); 321 | t('0x481df814c99e917079aeb', '5449011495668093676919531'); 322 | t( 323 | '0xd14f6c28f6b1a66f83ec00058d939e6deae057', 324 | '4667774422027164788903448529246588503172243543' 325 | ); 326 | t('0x36818425f9ca90bbe7a5b983c074', '1105511449492687971454561936457844'); 327 | t( 328 | '0x2c977359c619b2a50e368272a2e12faa4', 329 | '948358531678320636918207632756786264740' 330 | ); 331 | t( 332 | '0x6786f6aaa24466c66772096948176f023b4ee4267e3', 333 | '2420882788095960105392950361690122210695410931099619' 334 | ); 335 | t('0x468dd654d147', '77575000215879'); 336 | t('0x466d816c1a1', '4839758545313'); 337 | t('0xd6bcf011b9965', '3777711518226789'); 338 | t('0xcbd3b0dd08123466b52d', '962545403373485503722797'); 339 | t( 340 | '0x41ea3b163a5614966ae7957bde8126e0a93d4a4', 341 | '23519245104289372491020812621093511206089774244' 342 | ); 343 | t( 344 | '0x3d42f8df046ed6958fc5b33d9fa992b492f1e', 345 | '85386222153107555893245630091838241326575390' 346 | ); 347 | t('0xfba75c3e2c8a777f', '18133563846735591295'); 348 | t('0x6a7f74151fcc6ed8ed2b3f19100', '135002082526570912993917396947200'); 349 | t( 350 | '0x5a0d410c996ff91fce14753b986e36ed02c8038a', 351 | '514104745647653703895348991238314574953430123402' 352 | ); 353 | t( 354 | '0x98ef2d49bef8f926076d15ea07db47db', 355 | '203284532859688206391148792201594226651' 356 | ); 357 | t( 358 | '0x1ed53c612a9ce8f563380bc006af575169', 359 | '10491910079311728745430929182060466098537' 360 | ); 361 | t('0x11c25b068603cce9f3bbdca', '343512914478456071211695562'); 362 | t( 363 | '0x772a8e4b457995e13897900d8000ad6c1f7c9a1', 364 | '42519933036531441133313813110786358673933846945' 365 | ); 366 | t('0x1d1cd508f519156', '131111610056020310'); 367 | t('0x5da9a9bf96be6918e50', '27644363394761964490320'); 368 | t('0x14a08119381f59538f02a2c549605', '6693834434076143282016415621879301'); 369 | t( 370 | '0x598e03f8e19f4379c5b70b9c15f4fa9156bcdf74116', 371 | '2094150575916241308211940593568291573516182910091542' 372 | ); 373 | t( 374 | '0x355b5738c3cfe719582f351c17af6b629d4', 375 | '290502049209306091381681583617785118206420' 376 | ); 377 | t('0x15a243c6d249', '23786665988681'); 378 | t( 379 | '0x3a9e2ecf094bcff872507503010fcbac11', 380 | '19946638349599244406026473172687735467025' 381 | ); 382 | t( 383 | '0x2b0142e2bc8feaa4657563be6095f30f9f787e6', 384 | '15344670653497010702533663436850880847648622566' 385 | ); 386 | t('0x249c44f17568b404f004d1fab5d14', '11880762815518784994450284839918868'); 387 | t( 388 | '0x5496b049593863de8ab664bcf192d8aa3046b16', 389 | '30182230828157060898612488525177295598317234966' 390 | ); 391 | t( 392 | '0x3308c774d7a9d93620db30a6dc7c3afb6245cda884b8', 393 | '19094196084927690615894155917265930659289199085651128' 394 | ); 395 | t( 396 | '0xe1aebc286f28a4213d5b89774ba1b2c', 397 | '18748973437595317918722188262730505004' 398 | ); 399 | t('0x16fceb35ec78f61b7', '26503318076778308023'); 400 | t('0x1e', '30'); 401 | t('0x802a2fb0ef85471', '577202901428491377'); 402 | t('0x22905c', '2265180'); 403 | t( 404 | '0x1933e587c735a0944325aec2059cbfc7447e', 405 | '2195466647218532976318479234926892387091582' 406 | ); 407 | t('0xdcb0', '56496'); 408 | t('0x86aee9b70c52fb93dea1ce85e', '666919169547294519852185479262'); 409 | t( 410 | '0x396769e718f39e316d00bcdc49721c228ea3', 411 | '5000590150769401690668401759809188422389411' 412 | ); 413 | t( 414 | '0xad7b683e1fc7cdea2dd3fda24f96f6ce5022803f0dd', 415 | '4056709020997348015695058837718936633645982841827549' 416 | ); 417 | t('0x325bd1', '3300305'); 418 | t('0x3bace918a9ac3ab0e2c9d64', '1154290724173228330560822628'); 419 | t( 420 | '0x2a549e1fc1c63f027e4af4879d42e41d2c', 421 | '14404335589336084186051447276771435879724' 422 | ); 423 | t('0x6f0cfb185fd93b13bfe1', '524422133844304697999329'); 424 | t( 425 | '0x1ed471cbb25831d740fad016d1d3b092756e7', 426 | '42970555202513419911347877108629841551775463' 427 | ); 428 | t('0xf974bd83', '4185177475'); 429 | t( 430 | '0x7f6d06d941b729ee41ec06af76ce5284c2', 431 | '43360782010770292902695704345624074945730' 432 | ); 433 | t( 434 | '0x52b6d2313c32bd8b492b829f096003bab3ae2af0afb', 435 | '1934189728430433084118852318011260708451697369746171' 436 | ); 437 | t( 438 | '0x109589b891295cc8f907838a8ff1a1a8d085d9b5e4c', 439 | '387803793636734915816188347290438110144007551802956' 440 | ); 441 | t('0xad9a3ee51ae483bc3c92ee9719ed', '3521077463500554185208755094231533'); 442 | t('0xf3f77b3b2e9b8c8ac86ff', '18433604493853855199299327'); 443 | t('0x26d3525', '40711461'); 444 | t( 445 | '0x38b954141f26f2a4afe6f7a9b5e34286428e3', 446 | '79061632155013935902994702509288944969197795' 447 | ); 448 | t('0x38240bedb839f71c66a', '16569841585813541996138'); 449 | t( 450 | '0x7518019e9f1b02abec44d0b94eb31e2641e59f76b', 451 | '10695796466552661793442196497637299434594286303083' 452 | ); 453 | t( 454 | '0x3fa622c7ed221182e769dd30636092d163', 455 | '21658621556399738720046392793916662534499' 456 | ); 457 | t('0x789b534735', '518002001717'); 458 | t( 459 | '0x3a53c2755b1260d232bb8d62729689f9aeb62e35f60e', 460 | '21822791108317911324343501911275861646156768606549518' 461 | ); 462 | t('0x5337ac5ca7b7ee1c', '5996450942817922588'); 463 | t( 464 | '0x157b2e3d04dc2d3f2c5d24471f4ea6e', 465 | '1784586140646675991730453110223858286' 466 | ); 467 | t( 468 | '0x1efcc47a12967085ad8cf3e4bd18a9d6', 469 | '41189283705768995637851264202925713878' 470 | ); 471 | t('0x1889b9fe35e07e831ee8', '115877401735749106343656'); 472 | t('0x7526131ba0fe6e6d338a003af179d', '38016936118707892590533223274977181'); 473 | t('0x1ea4cb38dc109662aff507', '37045991477844459182355719'); 474 | t('0x16166f015f2938', '6217115507829048'); 475 | t('0x5f550f99', '1599410073'); 476 | t('0x1a12b67c34d57d7b7ab7bac0', '8069232682011269556707244736'); 477 | t( 478 | '0x23310db646fa85615a6d3a159621607b', 479 | '46777680511357499186246331786523140219' 480 | ); 481 | t('0xdf', '223'); 482 | t( 483 | '0x1f000932fa1ca86508eaafa96ab453c29', 484 | '659300071197325571806191735253210840105' 485 | ); 486 | t('0x35ec5ce3922a7b1370', '994709174014332048240'); 487 | t('0xe77b7374944b', '254517404013643'); 488 | t( 489 | '0x1d0f1645dc1656b1174ce08b54fe4e40d8a45a3', 490 | '10368573985886829358840965263279576467697714595' 491 | ); 492 | t('0xef557cf221b976840f876d6fb8f40', '77668303327151794801661259781214016'); 493 | t( 494 | '0x11d071e8c8ed7f60c4fce894fa20a754', 495 | '23679184030321848843326203235370313556' 496 | ); 497 | t('0x7cee134a1dab3d5cff44b067e971', '2533881063347006744885016016710001'); 498 | t('0x2fc6363ce8caa6f2f7d7dc022c', '3785067239745545603746923020844'); 499 | t('0x3bf5488fde2e', '65924670414382'); 500 | t('0x5661a43808fca8b4914', '25495292807490092878100'); 501 | t('0x25ad8a6e4382e566', '2714978356557964646'); 502 | t( 503 | '0x43906e3351853cb2ad062c9315091712290', 504 | '367854393737791193668837913203410675507856' 505 | ); 506 | t('0xecd7559017ec', '260409597630444'); 507 | t('0x393b9b61ad34', '62928172723508'); 508 | t( 509 | '0x31b7ebb7e8b136deb2b30b4593ad1a775e7', 510 | '270692937955729861614382230692061625415143' 511 | ); 512 | t( 513 | '0x31c49f4d6a14d5a60e3be92b6c7d09a28ef733', 514 | '1109864730568327689119301182815368410691467059' 515 | ); 516 | t('0x2ce42c8496', '192806683798'); 517 | t('0x3abd', '15037'); 518 | t('0xaa954cd38c39006a0d5d65a0b2', '13514993771256587973240014872754'); 519 | t('0x6f8a84d', '116959309'); 520 | t('0x5ab9da880303', '99754281796355'); 521 | t('0x8273c422025895e75c', '2406418559385510209372'); 522 | t('0x476bd81afce52', '1256456203521618'); 523 | t( 524 | '0x84c5f2554693b9f5500f5bb85b158fde13e1f', 525 | '185058871763639409971122557584749513555394079' 526 | ); 527 | t('0x7705c09763f0c8', '33501846960337096'); 528 | t('0x514fb06b03ae4e191f9375228e62', '1649188801468585902988396540890722'); 529 | t( 530 | '0x1fc020c94aec1d384d6939fc0b55b6275a201238e6', 531 | '46403408144166737594428754848567058468276133837030' 532 | ); 533 | t('0xef3608f1c4e83', '4208245717356163'); 534 | t('0x1acb8e8c35bb51f0a97f', '126536489245502589675903'); 535 | t( 536 | '0x117eacbdcefdf0a6a17f2fff826d4a3757e9a72', 537 | '6242361556294202529040777632205572383849749106' 538 | ); 539 | t('0x1725ab', '1516971'); 540 | t('0xdee149', '14606665'); 541 | t('0x278cbbef4c3b7a5cd3ae71e1', '12240052494575754714578579937'); 542 | t('0x2570b2cd172507d801138abed2', '2966320491435734983427362569938'); 543 | t('0x7a61cd850ab00f4e75b8fdd', '2367212971376424991309139933'); 544 | t('0x1c46b1c9ef8122e5c0809', '2136485514231402911762441'); 545 | t('0x159a345bdf44d369a', '24905827046438811290'); 546 | t('0x7dd0aa8c145447eb92707ad1d07', '159489590003544512404938984070407'); 547 | t( 548 | '0x24b44822469ba933aa9cc564a0b792345', 549 | '780612549310309792407655025164475245381' 550 | ); 551 | t( 552 | '0x3a670f32d8a0d0c5fe1d860669e0fa790a28', 553 | '5087581870263472260351886848536796076771880' 554 | ); 555 | t( 556 | '0x2413dcab4dd4acf50d555c028f9dfa7', 557 | '2997208574424073828351508066743213991' 558 | ); 559 | t('0x339ef7a5f81e079987fa8c3ae71', '65437348931585993956193860824689'); 560 | t( 561 | '0x15d1fe6b5dfe7e1a87573a3946b42cb4437be39c6', 562 | '1993149199833169001344544884949567309662274861510' 563 | ); 564 | t( 565 | '0x7672ff747c781c06ec82bf84418073cc9158fdb', 566 | '42264090573810039509111928267687533762028343259' 567 | ); 568 | t('0x66d1', '26321'); 569 | t( 570 | '0x1487132dabd4006942ea4621e5753f54520400535ef3', 571 | '7680300593500415724655977000547063327920546737512179' 572 | ); 573 | t('0x8ca', '2250'); 574 | t( 575 | '0xab32097dd97de47c4f76930046f32a1', 576 | '14222362164666534040463596276719497889' 577 | ); 578 | t( 579 | '0x198c811986971ccf4c7ae1d257bd14617', 580 | '543363838528230108444050115794098538007' 581 | ); 582 | t( 583 | '0x3434042fa72b6bd086b5f583063b090b', 584 | '69389940122557978795038540059404601611' 585 | ); 586 | t( 587 | '0x5180a950fe82c6376276a586c25b5c5c5b4bb8a21e0e', 588 | '30493736786942864405814520647491453938628818133720590' 589 | ); 590 | t('0x6e1b6e52c50', '7566505880656'); 591 | t('0x977a3cf2b2f06c31b3d', '44708264591227581831997'); 592 | t('0x238d459b304a504cc07bde524ffcae', '184595724574603234807440528928406702'); 593 | t( 594 | '0x2951804f7cc040bcabed937dfc07518ebf0c31156972', 595 | '15459035341493840021436449671050677487751991559285106' 596 | ); 597 | t('0x2b0a983ec857ac84ef5f', '203257196630153042915167'); 598 | t( 599 | '0x44ebf64ca6d7048632dc366985bacf6ca72f55bb89cb3', 600 | '412586872136786946015831424411703662517991501351918771' 601 | ); 602 | }); 603 | -------------------------------------------------------------------------------- /src/models/amount.spec/toHex.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Amount, AmountUnit } from '../..'; 3 | 4 | test('toHexString', (c) => { 5 | const t = (expected, n) => { 6 | c.is(expected, new Amount(n, AmountUnit.shannon).toHexString()); 7 | }; 8 | t('0x0', '0'); 9 | t('0x9', '9'); 10 | t('0x5a', '90'); 11 | t('0x0', '0'); 12 | t('0xffffffff', '4294967295'); 13 | t('0x0', '0'); 14 | t('0x60bf8d3f1cb70827109c', '456880688358819786854556'); 15 | t('0x27', '39'); 16 | t('0x14a3b3', '1352627'); 17 | t( 18 | '0xc8841d8a3e0c3184a1d13f24b7c8abcbf36', 19 | '1091713357762058306584556087055815769112374' 20 | ); 21 | t('0x2266ce3388fee3c292256', '2599310186932964178862678'); 22 | t( 23 | '0x784afb326df4e14f44919e903da2daab2c6a8c2b7', 24 | '10988016479801545098971638340838655979698598232759' 25 | ); 26 | t('0x47d', '1149'); 27 | t('0x613b8b5', '101955765'); 28 | t('0x6b63', '27491'); 29 | t( 30 | '0x2d3777760a984a21c97a0d20c628c419cab2bfc67f', 31 | '66084232243819558824679961490043082487366953191039' 32 | ); 33 | t('0x12c9942167ae93c737fc5ff39d', '1488492491002839592133151683485'); 34 | t('0xc2c44', '797764'); 35 | t( 36 | '0xfe787f84cf0ae7655d44db4e793b2a98ae8f', 37 | '22167524012235587957931446373184044722073231' 38 | ); 39 | t( 40 | '0x186b6b31486165c0bc8c778fd49af12ea08f7dad332', 41 | '571028668500605890623232664155788301681845784466226' 42 | ); 43 | 44 | t('0x2aef3ee6f864b26772c23cf6', '13287600730756091106849602806'); 45 | t( 46 | '0x193c4c1a69add29d9a5101ced0568697d923', 47 | '2198325248780085112365807036920470754023715' 48 | ); 49 | t('0x11a507516c68d662f15d9', '1333191524844399246644697'); 50 | t('0xa2669c148f4ea9e', '731388268367899294'); 51 | t('0xed2a498f218583395d57871', '4587441849070983049702111345'); 52 | t('0x1fbbac5129573', '558254565528947'); 53 | t( 54 | '0x1b91780b06a70cf46e5fa0fa8e63407e80030f027a', 55 | '40291024919626546988689644749021723762456102109818' 56 | ); 57 | t('0x25a90a2b75d596836e10d26ec831', '763841862190387816235893777156145'); 58 | t('0xfbd45cc9e2', '1081599642082'); 59 | t('0x621e835f12ad64e909cf7', '7413676543182627283180791'); 60 | t('0x17de6483675cb29063c', '7044803064789933295164'); 61 | t('0x2ecc612', '49071634'); 62 | t( 63 | '0x14bb4a5d83f3d4bf43b66adada6b0e993784', 64 | '1805977369681030296729705669300424034301828' 65 | ); 66 | t('0x8b63e2dca39c336b18', '2571294983995377543960'); 67 | t('0x3c1440de515e09b31f73cdd0da6e2', '19496787444412678571409607909811938'); 68 | t('0xd39570c80caa28598ea432085259', '4291428326751199011126676862554713'); 69 | t( 70 | '0x1d54a95161a44ae4d216da350d20954f13992a85', 71 | '167448744619550277927636746527062265701749828229' 72 | ); 73 | t( 74 | '0xe3ffcc64277a78a8256d7e1d7c05eaa9bf', 75 | '77584111689906087598180624752236124875199' 76 | ); 77 | t('0x22f4736', '36652854'); 78 | t('0x2c35e4d05c0', '3038123984320'); 79 | t('0x430e68bd9ad4952', '301994663360678226'); 80 | t('0x1d854cf1f48c4529d864b686f2d', '37421939842938093643010520149805'); 81 | t('0x5db3324efd09a682cd', '1728459668785308533453'); 82 | t('0x1b60d2a23a53bc5a', '1972808230993378394'); 83 | t( 84 | '0x1b7b2cc91769682ad42af60ef67c77c99ac81d227197', 85 | '10281919698706053870205728976619722313496732052386199' 86 | ); 87 | t( 88 | '0x78a9c9d91cb45ec86599f1b402f6635265b69a0317', 89 | '176349517282896190364726424897935661923732528169751' 90 | ); 91 | t( 92 | '0x310f7e944db1e20ca7d4db559be74aafcd', 93 | '16694431636419175813863637436681440374733' 94 | ); 95 | t( 96 | '0x11a4ae80f151dad1752e32c310a41dae552c37b7b4', 97 | '25785693883194456949802584063055012266517133244340' 98 | ); 99 | t( 100 | '0x320abd803f00618b853f0f4ad06d76e5ead8eb20d', 101 | '4571024861681675091071130733403917525672421929485' 102 | ); 103 | t('0x17f27d4daa32a', '421284028261162'); 104 | t('0x3fd14203387143ed10002039a6fed', '20709934734392064809458286974365677'); 105 | t('0xddf93c33cbe97520fb19a19', '4293593374816726677147720217'); 106 | t( 107 | '0x37bc13d810602438ce47db31c0e7a87e6b7831', 108 | '1242924848237600517379561065653635852461111345' 109 | ); 110 | t('0x15a', '346'); 111 | t('0x533921', '5454113'); 112 | t( 113 | '0x770d01b680082f7c23db6444c14f885dacb63824', 114 | '679659960629478197799609320106784570818809968676' 115 | ); 116 | t('0x1d35', '7477'); 117 | t('0xaa906c1dabfebe6da', '196647076124632540890'); 118 | t('0x655', '1621'); 119 | t('0x11af668734ef7a60033c5f518c530', '5739161962048683655238213338449200'); 120 | t( 121 | '0x9035964bc6d843d71d2ef9124632b039ca9ad9d6062cd', 122 | '863281823741412484448091339719676346358410323679011533' 123 | ); 124 | t('0xfa3d98dfd239530c9a9556', '302522339292227790310118742'); 125 | t('0x10fdb7f', '17816447'); 126 | t('0x18d260c9d8f110eea9d29', '1875481379014053338979625'); 127 | t('0x6dc6205912265fe68cb44ae5c', '543574178265693146271925448284'); 128 | t('0x19d6ad933a8e5', '454557212846309'); 129 | t('0x3e1ddad6b81e605', '279748388776437253'); 130 | t( 131 | '0xe073b0188f2d6667b75f11f65a82fda', 132 | '18646734802776877837813543784990388186' 133 | ); 134 | t( 135 | '0x81f531048349c33f91627d55e1b6bdd8a78', 136 | '707557451293532648521737780488438655322744' 137 | ); 138 | t('0x78b8be03f14', '8295928577812'); 139 | t('0x8', '8'); 140 | t( 141 | '0x659874ecb742089cd0f2fc72a2e912d48334f2d', 142 | '36250497918673919606249316600592246963107483437' 143 | ); 144 | t('0x2a03a8a82cd13dc18b5', '12400430345351117674677'); 145 | t( 146 | '0x1e634adab5f805f52f70cac738dd261d7d6', 147 | '165447251922571882378062853693168471365590' 148 | ); 149 | t( 150 | '0x2d0b0ae2b685425d29b0ecb3b5edfa717cb1417', 151 | '16071927572096392119675195392748578598193665047' 152 | ); 153 | t('0x7a87c66768a00656e6143d364', '606741009919509661621760021348'); 154 | t('0x32f102da079f77cac41', '15035262184397547023425'); 155 | t('0xa440f8912c06198b', '11835733123122207115'); 156 | t( 157 | '0xc008c78c61addc8e085ca3ff6e76fa13', 158 | '255257360887256236558111057213476239891' 159 | ); 160 | t('0x11649c762', '4668901218'); 161 | t('0x5572f75ca5f099eda', '98516089885791198938'); 162 | t('0xaf', '175'); 163 | t('0xc41a62e8bd1c7123c92220df', '60690961081480451125474894047'); 164 | t( 165 | '0x765e3e05ca0c4b853679e80667d71bb51ec118c87c1', 166 | '2767923543808337676781898227256098432891809077954497' 167 | ); 168 | t( 169 | '0x643b91b0289fe98c02b3f790ed41085ba228ba4c', 170 | '572227512274253374435718937361849836268824345164' 171 | ); 172 | t('0x12', '18'); 173 | t( 174 | '0x28ea59cbb990457243147551fc12145', 175 | '3399121160718415003848736585359106373' 176 | ); 177 | t('0x1c149ecb87796c6a9116eaa5', '8690508679745247747029527205'); 178 | t('0x3d24cfd5a3d86612', '4405874851959039506'); 179 | t('0x55', '85'); 180 | t( 181 | '0x2b1dc5d2236fc61848eb80e4032e842131c7ea1edd0', 182 | '1008232683040405259975519873135905613349630326730192' 183 | ); 184 | t('0x3591a1d0aaaa14514d8a0a8826ee', '1086505871907198290436180679337710'); 185 | t('0x1cba2a2f26dfe80b4c05d5f5496b9', '9322511400054499645799889634629305'); 186 | t('0xdd2f83', '14495619'); 187 | t('0x15c6f07', '22834951'); 188 | t('0x2f8f19d0d3ff', '52291659944959'); 189 | t('0x66877666510', '7045749564688'); 190 | t( 191 | '0x153d9792e5b2bd96679de7bbee9f34cd131', 192 | '115644794069417523805676707579411471061297' 193 | ); 194 | t( 195 | '0x1f6ce9be57023fe14fa060be4c01e7a', 196 | '2610723550735662335298250928784678522' 197 | ); 198 | t( 199 | '0x9e63632c0dca5485e48bee9ad1b5a28049aa157e307', 200 | '3703754566336033126591165191492065433661004773319431' 201 | ); 202 | t('0x8371b0d9c54b9de08c7', '38795452173112617863367'); 203 | t('0xd3b35e07218e7b0c20bfc', '15995649127931990167915516'); 204 | t('0xc6b6dfbdc40011198bd286c0', '61499113032079803212142642880'); 205 | t( 206 | '0x7cf9a45c21858e891798ad503930df741b93f61f127', 207 | '2922422513262668930583564242609727203228735133315367' 208 | ); 209 | t( 210 | '0x678049c017727c47ff4cc7ba35a849365fd111', 211 | '2308152223994224783375970725248611287908208913' 212 | ); 213 | t('0x72b397d', '120273277'); 214 | t('0xf9445c1a', '4182006810'); 215 | t('0x4aff51f382c09c710fc5302d96b22', '24338029676537462451324711222209314'); 216 | t( 217 | '0x10459598a086ea0f535900e70abab1e2ce7377', 218 | '362873575854611890806983645493404163992613751' 219 | ); 220 | t('0x13640c20a010754124', '357697310375837778212'); 221 | t('0x14147e01ca6501f252ca', '94825344299726024102602'); 222 | t('0x466', '1126'); 223 | t('0x74c698493dd81930c415', '551457940709438745789461'); 224 | t( 225 | '0x2e5f2b623ba0cfee63689d427bad6eb6be5', 226 | '252471852786048400736119714699808445524965' 227 | ); 228 | t( 229 | '0x38a52cc273671b0b8b078ec7f7f17102c9bd8c', 230 | '1263230489190036409842245570352830262209527180' 231 | ); 232 | t( 233 | '0x78655d620287e6db76141a906c5701bdc47', 234 | '655497934873142286500331809443840511433799' 235 | ); 236 | t('0x6eecb9a0d2fdd3b9a541f3', '134099742883823958727410163'); 237 | t( 238 | '0x54bd61680d7ceb5beb37894e46599ef77', 239 | '1802215542237292717156325458542562242423' 240 | ); 241 | t( 242 | '0x3a847dfcee83cd860720c8c0d8b53c7fda25d77', 243 | '20879758636120455697847204680129815020870720887' 244 | ); 245 | t( 246 | '0x76eb310380a6f577f166b90d28c3b107a', 247 | '2529121398079147937688504401561827086458' 248 | ); 249 | t('0xb73dda2c', '3074284076'); 250 | t('0x3fd278b8d795a', '1122771216988506'); 251 | t( 252 | '0x778da0c24f47774967183e20f8b1827299f', 253 | '650909720313013143267150228395755917748639' 254 | ); 255 | t('0x6500d', '413709'); 256 | t( 257 | '0x5a1bf29425c38c8f22a81fc3f105169ce4907acbb7', 258 | '131694699796400245476746024523885969905255483886519' 259 | ); 260 | t('0x1e946da5ed27825dff2', '9025563349429600509938'); 261 | t('0x23d288aea4', '153856028324'); 262 | t('0x4bdc3b2b93e8cb0967b92', '5731840537208636428417938'); 263 | t('0x1c57b1', '1857457'); 264 | t( 265 | '0xb6dab6e4caceab483d2085d9d1766a088a19', 266 | '15928860703021466826033711809396661962705433' 267 | ); 268 | t('0x3c9e2c9d', '1016999069'); 269 | t('0x2d047117ed235996e16cb8', '54422637554743220665150648'); 270 | t( 271 | '0xdda0ed04ef2e2b22a78e6455b862865ce', 272 | '4713519390205494839540473117533526320590' 273 | ); 274 | t('0xa', '10'); 275 | t('0x984e5039808eb14e793dd95', '2946024784801310608062668181'); 276 | t('0x18b', '395'); 277 | t('0x1e30c76d4c3babeded1eb9d54', '149496328017603561036003908948'); 278 | t('0x4d1fb3fde8905', '1356776945387781'); 279 | t( 280 | '0x1c6ad164b71af7cf0a92d7c2cf97ac3539cf4a190a6', 281 | '664509895940083432745213666480332815238360970924198' 282 | ); 283 | t('0x29d730cd9c087e9', '188433258449504233'); 284 | t( 285 | '0x4bbffc982422401d23e107db16617c0c0', 286 | '1611023225678920504550274579287300882624' 287 | ); 288 | t( 289 | '0x34fa796a05fee3b566107aef6fea5327c9f794ce6f2', 290 | '1238848647360391370812154035309938766206217768199922' 291 | ); 292 | t('0x2907465cdd296accc8ab4bd', '793605010131443253911401661'); 293 | t('0x8aa7eb5be50', '9528363302480'); 294 | t('0x15c14f12e032624a1206', '102735615609474072056326'); 295 | t( 296 | '0x2d2650f939302bf9306f3db1fd19a7666712ff8', 297 | '16109941674613560268665251639244419228113121272' 298 | ); 299 | t( 300 | '0xc0b42d10550ce0f4ea97567ede6bae89e93b', 301 | '16786869625005321512335207434991310677338427' 302 | ); 303 | t('0xbcb3750ecd418048f3c9a907a1', '14950433883660044294408311998369'); 304 | t('0x5dd205e2db781f491bff', '443054323304979904404479'); 305 | t('0x3', '3'); 306 | t( 307 | '0x108ba7dc0b83574862fffc703b989d5b578d862', 308 | '5903642409059499044928754369694490849414010978' 309 | ); 310 | t('0x464a1', '287905'); 311 | t('0x172f', '5935'); 312 | t('0x3da62e0cd7d5edc8b6731fc', '1192467796933113553619464700'); 313 | t('0x16077c0833e289da0b', '406367684695924791819'); 314 | t( 315 | '0x784c5768a01120f03ef7a73160baaa0c5f', 316 | '40935359210071367599328032328982052080735' 317 | ); 318 | t('0x3ba90bcf292a852be', '68783684858858197694'); 319 | t('0x481df814c99e917079aeb', '5449011495668093676919531'); 320 | t( 321 | '0xd14f6c28f6b1a66f83ec00058d939e6deae057', 322 | '4667774422027164788903448529246588503172243543' 323 | ); 324 | t('0x36818425f9ca90bbe7a5b983c074', '1105511449492687971454561936457844'); 325 | t( 326 | '0x2c977359c619b2a50e368272a2e12faa4', 327 | '948358531678320636918207632756786264740' 328 | ); 329 | t( 330 | '0x6786f6aaa24466c66772096948176f023b4ee4267e3', 331 | '2420882788095960105392950361690122210695410931099619' 332 | ); 333 | t('0x468dd654d147', '77575000215879'); 334 | t('0x466d816c1a1', '4839758545313'); 335 | t('0xd6bcf011b9965', '3777711518226789'); 336 | t('0xcbd3b0dd08123466b52d', '962545403373485503722797'); 337 | t( 338 | '0x41ea3b163a5614966ae7957bde8126e0a93d4a4', 339 | '23519245104289372491020812621093511206089774244' 340 | ); 341 | t( 342 | '0x3d42f8df046ed6958fc5b33d9fa992b492f1e', 343 | '85386222153107555893245630091838241326575390' 344 | ); 345 | t('0xfba75c3e2c8a777f', '18133563846735591295'); 346 | t('0x6a7f74151fcc6ed8ed2b3f19100', '135002082526570912993917396947200'); 347 | t( 348 | '0x5a0d410c996ff91fce14753b986e36ed02c8038a', 349 | '514104745647653703895348991238314574953430123402' 350 | ); 351 | t( 352 | '0x98ef2d49bef8f926076d15ea07db47db', 353 | '203284532859688206391148792201594226651' 354 | ); 355 | t( 356 | '0x1ed53c612a9ce8f563380bc006af575169', 357 | '10491910079311728745430929182060466098537' 358 | ); 359 | t('0x11c25b068603cce9f3bbdca', '343512914478456071211695562'); 360 | t( 361 | '0x772a8e4b457995e13897900d8000ad6c1f7c9a1', 362 | '42519933036531441133313813110786358673933846945' 363 | ); 364 | t('0x1d1cd508f519156', '131111610056020310'); 365 | t('0x5da9a9bf96be6918e50', '27644363394761964490320'); 366 | t('0x14a08119381f59538f02a2c549605', '6693834434076143282016415621879301'); 367 | t( 368 | '0x598e03f8e19f4379c5b70b9c15f4fa9156bcdf74116', 369 | '2094150575916241308211940593568291573516182910091542' 370 | ); 371 | t( 372 | '0x355b5738c3cfe719582f351c17af6b629d4', 373 | '290502049209306091381681583617785118206420' 374 | ); 375 | t('0x15a243c6d249', '23786665988681'); 376 | t( 377 | '0x3a9e2ecf094bcff872507503010fcbac11', 378 | '19946638349599244406026473172687735467025' 379 | ); 380 | t( 381 | '0x2b0142e2bc8feaa4657563be6095f30f9f787e6', 382 | '15344670653497010702533663436850880847648622566' 383 | ); 384 | t('0x249c44f17568b404f004d1fab5d14', '11880762815518784994450284839918868'); 385 | t( 386 | '0x5496b049593863de8ab664bcf192d8aa3046b16', 387 | '30182230828157060898612488525177295598317234966' 388 | ); 389 | t( 390 | '0x3308c774d7a9d93620db30a6dc7c3afb6245cda884b8', 391 | '19094196084927690615894155917265930659289199085651128' 392 | ); 393 | t( 394 | '0xe1aebc286f28a4213d5b89774ba1b2c', 395 | '18748973437595317918722188262730505004' 396 | ); 397 | t('0x16fceb35ec78f61b7', '26503318076778308023'); 398 | t('0x1e', '30'); 399 | t('0x802a2fb0ef85471', '577202901428491377'); 400 | t('0x22905c', '2265180'); 401 | t( 402 | '0x1933e587c735a0944325aec2059cbfc7447e', 403 | '2195466647218532976318479234926892387091582' 404 | ); 405 | t('0xdcb0', '56496'); 406 | t('0x86aee9b70c52fb93dea1ce85e', '666919169547294519852185479262'); 407 | t( 408 | '0x396769e718f39e316d00bcdc49721c228ea3', 409 | '5000590150769401690668401759809188422389411' 410 | ); 411 | t( 412 | '0xad7b683e1fc7cdea2dd3fda24f96f6ce5022803f0dd', 413 | '4056709020997348015695058837718936633645982841827549' 414 | ); 415 | t('0x325bd1', '3300305'); 416 | t('0x3bace918a9ac3ab0e2c9d64', '1154290724173228330560822628'); 417 | t( 418 | '0x2a549e1fc1c63f027e4af4879d42e41d2c', 419 | '14404335589336084186051447276771435879724' 420 | ); 421 | t('0x6f0cfb185fd93b13bfe1', '524422133844304697999329'); 422 | t( 423 | '0x1ed471cbb25831d740fad016d1d3b092756e7', 424 | '42970555202513419911347877108629841551775463' 425 | ); 426 | t('0xf974bd83', '4185177475'); 427 | t( 428 | '0x7f6d06d941b729ee41ec06af76ce5284c2', 429 | '43360782010770292902695704345624074945730' 430 | ); 431 | t( 432 | '0x52b6d2313c32bd8b492b829f096003bab3ae2af0afb', 433 | '1934189728430433084118852318011260708451697369746171' 434 | ); 435 | t( 436 | '0x109589b891295cc8f907838a8ff1a1a8d085d9b5e4c', 437 | '387803793636734915816188347290438110144007551802956' 438 | ); 439 | t('0xad9a3ee51ae483bc3c92ee9719ed', '3521077463500554185208755094231533'); 440 | t('0xf3f77b3b2e9b8c8ac86ff', '18433604493853855199299327'); 441 | t('0x26d3525', '40711461'); 442 | t( 443 | '0x38b954141f26f2a4afe6f7a9b5e34286428e3', 444 | '79061632155013935902994702509288944969197795' 445 | ); 446 | t('0x38240bedb839f71c66a', '16569841585813541996138'); 447 | t( 448 | '0x7518019e9f1b02abec44d0b94eb31e2641e59f76b', 449 | '10695796466552661793442196497637299434594286303083' 450 | ); 451 | t( 452 | '0x3fa622c7ed221182e769dd30636092d163', 453 | '21658621556399738720046392793916662534499' 454 | ); 455 | t('0x789b534735', '518002001717'); 456 | t( 457 | '0x3a53c2755b1260d232bb8d62729689f9aeb62e35f60e', 458 | '21822791108317911324343501911275861646156768606549518' 459 | ); 460 | t('0x5337ac5ca7b7ee1c', '5996450942817922588'); 461 | t( 462 | '0x157b2e3d04dc2d3f2c5d24471f4ea6e', 463 | '1784586140646675991730453110223858286' 464 | ); 465 | t( 466 | '0x1efcc47a12967085ad8cf3e4bd18a9d6', 467 | '41189283705768995637851264202925713878' 468 | ); 469 | t('0x1889b9fe35e07e831ee8', '115877401735749106343656'); 470 | t('0x7526131ba0fe6e6d338a003af179d', '38016936118707892590533223274977181'); 471 | t('0x1ea4cb38dc109662aff507', '37045991477844459182355719'); 472 | t('0x16166f015f2938', '6217115507829048'); 473 | t('0x5f550f99', '1599410073'); 474 | t('0x1a12b67c34d57d7b7ab7bac0', '8069232682011269556707244736'); 475 | t( 476 | '0x23310db646fa85615a6d3a159621607b', 477 | '46777680511357499186246331786523140219' 478 | ); 479 | t('0xdf', '223'); 480 | t( 481 | '0x1f000932fa1ca86508eaafa96ab453c29', 482 | '659300071197325571806191735253210840105' 483 | ); 484 | t('0x35ec5ce3922a7b1370', '994709174014332048240'); 485 | t('0xe77b7374944b', '254517404013643'); 486 | t( 487 | '0x1d0f1645dc1656b1174ce08b54fe4e40d8a45a3', 488 | '10368573985886829358840965263279576467697714595' 489 | ); 490 | t('0xef557cf221b976840f876d6fb8f40', '77668303327151794801661259781214016'); 491 | t( 492 | '0x11d071e8c8ed7f60c4fce894fa20a754', 493 | '23679184030321848843326203235370313556' 494 | ); 495 | t('0x7cee134a1dab3d5cff44b067e971', '2533881063347006744885016016710001'); 496 | t('0x2fc6363ce8caa6f2f7d7dc022c', '3785067239745545603746923020844'); 497 | t('0x3bf5488fde2e', '65924670414382'); 498 | t('0x5661a43808fca8b4914', '25495292807490092878100'); 499 | t('0x25ad8a6e4382e566', '2714978356557964646'); 500 | t( 501 | '0x43906e3351853cb2ad062c9315091712290', 502 | '367854393737791193668837913203410675507856' 503 | ); 504 | t('0xecd7559017ec', '260409597630444'); 505 | t('0x393b9b61ad34', '62928172723508'); 506 | t( 507 | '0x31b7ebb7e8b136deb2b30b4593ad1a775e7', 508 | '270692937955729861614382230692061625415143' 509 | ); 510 | t( 511 | '0x31c49f4d6a14d5a60e3be92b6c7d09a28ef733', 512 | '1109864730568327689119301182815368410691467059' 513 | ); 514 | t('0x2ce42c8496', '192806683798'); 515 | t('0x3abd', '15037'); 516 | t('0xaa954cd38c39006a0d5d65a0b2', '13514993771256587973240014872754'); 517 | t('0x6f8a84d', '116959309'); 518 | t('0x5ab9da880303', '99754281796355'); 519 | t('0x8273c422025895e75c', '2406418559385510209372'); 520 | t('0x476bd81afce52', '1256456203521618'); 521 | t( 522 | '0x84c5f2554693b9f5500f5bb85b158fde13e1f', 523 | '185058871763639409971122557584749513555394079' 524 | ); 525 | t('0x7705c09763f0c8', '33501846960337096'); 526 | t('0x514fb06b03ae4e191f9375228e62', '1649188801468585902988396540890722'); 527 | t( 528 | '0x1fc020c94aec1d384d6939fc0b55b6275a201238e6', 529 | '46403408144166737594428754848567058468276133837030' 530 | ); 531 | t('0xef3608f1c4e83', '4208245717356163'); 532 | t('0x1acb8e8c35bb51f0a97f', '126536489245502589675903'); 533 | t( 534 | '0x117eacbdcefdf0a6a17f2fff826d4a3757e9a72', 535 | '6242361556294202529040777632205572383849749106' 536 | ); 537 | t('0x1725ab', '1516971'); 538 | t('0xdee149', '14606665'); 539 | t('0x278cbbef4c3b7a5cd3ae71e1', '12240052494575754714578579937'); 540 | t('0x2570b2cd172507d801138abed2', '2966320491435734983427362569938'); 541 | t('0x7a61cd850ab00f4e75b8fdd', '2367212971376424991309139933'); 542 | t('0x1c46b1c9ef8122e5c0809', '2136485514231402911762441'); 543 | t('0x159a345bdf44d369a', '24905827046438811290'); 544 | t('0x7dd0aa8c145447eb92707ad1d07', '159489590003544512404938984070407'); 545 | t( 546 | '0x24b44822469ba933aa9cc564a0b792345', 547 | '780612549310309792407655025164475245381' 548 | ); 549 | t( 550 | '0x3a670f32d8a0d0c5fe1d860669e0fa790a28', 551 | '5087581870263472260351886848536796076771880' 552 | ); 553 | t( 554 | '0x2413dcab4dd4acf50d555c028f9dfa7', 555 | '2997208574424073828351508066743213991' 556 | ); 557 | t('0x339ef7a5f81e079987fa8c3ae71', '65437348931585993956193860824689'); 558 | t( 559 | '0x15d1fe6b5dfe7e1a87573a3946b42cb4437be39c6', 560 | '1993149199833169001344544884949567309662274861510' 561 | ); 562 | t( 563 | '0x7672ff747c781c06ec82bf84418073cc9158fdb', 564 | '42264090573810039509111928267687533762028343259' 565 | ); 566 | t('0x66d1', '26321'); 567 | t( 568 | '0x1487132dabd4006942ea4621e5753f54520400535ef3', 569 | '7680300593500415724655977000547063327920546737512179' 570 | ); 571 | t('0x8ca', '2250'); 572 | t( 573 | '0xab32097dd97de47c4f76930046f32a1', 574 | '14222362164666534040463596276719497889' 575 | ); 576 | t( 577 | '0x198c811986971ccf4c7ae1d257bd14617', 578 | '543363838528230108444050115794098538007' 579 | ); 580 | t( 581 | '0x3434042fa72b6bd086b5f583063b090b', 582 | '69389940122557978795038540059404601611' 583 | ); 584 | t( 585 | '0x5180a950fe82c6376276a586c25b5c5c5b4bb8a21e0e', 586 | '30493736786942864405814520647491453938628818133720590' 587 | ); 588 | t('0x6e1b6e52c50', '7566505880656'); 589 | t('0x977a3cf2b2f06c31b3d', '44708264591227581831997'); 590 | t('0x238d459b304a504cc07bde524ffcae', '184595724574603234807440528928406702'); 591 | t( 592 | '0x2951804f7cc040bcabed937dfc07518ebf0c31156972', 593 | '15459035341493840021436449671050677487751991559285106' 594 | ); 595 | t('0x2b0a983ec857ac84ef5f', '203257196630153042915167'); 596 | t( 597 | '0x44ebf64ca6d7048632dc366985bacf6ca72f55bb89cb3', 598 | '412586872136786946015831424411703662517991501351918771' 599 | ); 600 | }); 601 | -------------------------------------------------------------------------------- /src/models/amount.spec/toString.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Amount } from '../..'; 3 | 4 | test('toString', (c) => { 5 | const t = (expected, n, fixed) => { 6 | c.is(new Amount(n, 100).toString(100, { fixed }), expected); 7 | }; 8 | 9 | t('100.0', 99.9512986, 1); 10 | t('10.0', 9.95036, 1); 11 | t('1.0', 0.99, 1); 12 | t('0.10', 0.09906, 2); 13 | t('0.010', 0.0098034, 3); 14 | 15 | t('0.1', 0.1, 1); 16 | t('0.10', 0.1, 2); 17 | t('0.100', 0.1, 3); 18 | t('0.01', 0.01, 2); 19 | t('0.010', 0.01, 3); 20 | t('0.0100', 0.01, 4); 21 | t('0.00', 0.001, 2); 22 | t('0.001', 0.001, 3); 23 | t('0.0010', 0.001, 4); 24 | t('1.0000', 1, 4); 25 | t('1.0', 1, 1); 26 | t('0.0000006', 0.0000006, 7); 27 | t('0.00000006', 0.00000006, 8); 28 | t('0.000000060', 0.00000006, 9); 29 | t('0.0000000600', 0.00000006, 10); 30 | t('0.0', 0, 1); 31 | t('0.00', 0, 2); 32 | 33 | t('-1111111111111111111111.00000000', '-1111111111111111111111', 8); 34 | t('-0.1', -0.1, 1); 35 | t('-0.10', -0.1, 2); 36 | t('-0.100', -0.1, 3); 37 | t('-0.01', -0.01, 2); 38 | t('-0.010', -0.01, 3); 39 | t('-0.0100', -0.01, 4); 40 | t('-0.00', -0.001, 2); 41 | t('-0.001', -0.001, 3); 42 | t('-0.0010', -0.001, 4); 43 | t('-1.0000', -1, 4); 44 | t('-1.0', -1, 1); 45 | t('-0.00000', -0.0000006, 5); 46 | t('-0.0000006', -0.0000006, 7); 47 | t('-0.00000006', -0.00000006, 8); 48 | t('-0.000000060', -0.00000006, 9); 49 | t('-0.0000000600', -0.00000006, 10); 50 | t('0.0', -0, 1); 51 | t('0.00', -0, 2); 52 | t('0.00', '-0.0', 2); 53 | t('0.00', '-0.0000', 2); 54 | t('0.0000', -0, 4); 55 | 56 | t('0.00001', 0.00001, 5); 57 | t('0.00000000000000000010', '0.0000000000000000001', 20); 58 | t('0.00001000000000000', 0.00001, 17); 59 | t('1.00000000000000000', 1, 17); 60 | t('100000000000000128.0', '100000000000000128', 1); 61 | t('10000000000000128.00', '10000000000000128', 2); 62 | t('10000000000000128.00000000000000000000', '10000000000000128', 20); 63 | t('-42.000', -42, 3); 64 | t('-0.00000000000000000010', '-0.0000000000000000001', 20); 65 | t('0.12312312312312300000', '0.123123123123123', 20); 66 | 67 | t('1.3', 1.25, 1); 68 | t('234.2041', 234.20405, 4); 69 | t('234.2041', '234.204050000000000000000000000000006', 4); 70 | 71 | t( 72 | '733744593401073823825766410831877679446.0000000000000000000', 73 | '733744593401073823825766410831877679446', 74 | 19 75 | ); 76 | t('-64.6849459', '-64.6849458687691227978', 7); 77 | t('-0.000000', '-0.00000000009', 6); 78 | t( 79 | '-62537287527837589298857228059657673223234916.95923265430000000', 80 | '-62537287527837589298857228059657673223234916.9592326543', 81 | 17 82 | ); 83 | t( 84 | '3393668096256773847245721315080265089731.000000', 85 | '3393668096256773847245721315080265089731', 86 | 6 87 | ); 88 | t('0.0', '0.0000000000000056674956638008432348702401392', 1); 89 | t('72516372734.6', '72516372734.6447', 1); 90 | t('-418.28', '-418.2800731793741351', 2); 91 | t('0.00', '0.001', 2); 92 | t('8366217346845756726.00000000', '8366217346845756726', 8); 93 | t('-0.000000', '-0.0000000000000092034548636370987112234384736726', 6); 94 | t('0.35', '0.35474830751442135112334772517193392', 2); 95 | t('64703289793894.5830', '64703289793894.58296866', 4); 96 | t( 97 | '-0.000000000000000036', 98 | '-0.000000000000000036461242408590182363418943891', 99 | 18 100 | ); 101 | t( 102 | '5494508405056449117588.631948', 103 | '5494508405056449117588.631948458033233759999', 104 | 6 105 | ); 106 | t('-0.0', '-0.00393971618499838726739122333520030506235698', 1); 107 | t('375581290738585826632.00000000', '375581290738585826632', 8); 108 | t( 109 | '254.96635275802300887', 110 | '254.96635275802300886544776010389418575738792480979736', 111 | 17 112 | ); 113 | t( 114 | '21492347.69467571391498624445', 115 | '21492347.6946757139149862444482880595559468', 116 | 20 117 | ); 118 | t('313576441324233.0000000', '313576441324233', 7); 119 | t( 120 | '-534460490015293367127173277346694900936058.0000', 121 | '-534460490015293367127173277346694900936058', 122 | 4 123 | ); 124 | t( 125 | '182707431911537249021116759327712693311898345504618668.43327000000000000000000', 126 | '182707431911537249021116759327712693311898345504618668.43327', 127 | 23 128 | ); 129 | t( 130 | '210005324615278.4586839690045963321032', 131 | '210005324615278.458683969004596332103244549279', 132 | 22 133 | ); 134 | t( 135 | '779837001772884165637922377221951347134695.644834', 136 | '779837001772884165637922377221951347134695.6448338', 137 | 6 138 | ); 139 | t( 140 | '-0.000001', 141 | '-0.00000064188301390033596845335767993853284632527964514979079', 142 | 6 143 | ); 144 | t('13.0', '13', 1); 145 | t('0.0000001269', '0.0000001269060795648365813491128357427111184222', 10); 146 | t('18446632248354.00', '18446632248354', 2); 147 | t('-1229249.79', '-1229249.7897249259', 2); 148 | t('49082.0', '49082', 1); 149 | t('-61.0', '-61', 1); 150 | t('-893.0', '-893', 1); 151 | t( 152 | '5002282278.56974877690066484', 153 | '5002282278.569748776900664839184116538222902', 154 | 17 155 | ); 156 | t('41372.00', '41372', 2); 157 | t( 158 | '-4732022445962399687294885123498809.7625585825095', 159 | '-4732022445962399687294885123498809.7625585825095', 160 | 13 161 | ); 162 | t('-55484242.036895641', '-55484242.036895641', 9); 163 | t( 164 | '-41427133134.52583323427907663268339', 165 | '-41427133134.525833234279076632683393992706825', 166 | 23 167 | ); 168 | t('0.0', '0.00004300614085218825243480119971669264977421', 1); 169 | t( 170 | '-472025754597316278339412186866.7010659789', 171 | '-472025754597316278339412186866.701065978877597089729906019843', 172 | 10 173 | ); 174 | }); 175 | -------------------------------------------------------------------------------- /src/models/amount.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi'; 2 | import { 3 | toBigUInt128LE, 4 | readBigUInt128LE, 5 | bnStringToRationalNumber, 6 | rationalNumberToBnString, 7 | } from '../utils'; 8 | 9 | export enum AmountUnit { 10 | shannon, 11 | ckb = 8, 12 | } 13 | 14 | export interface FormatOptions { 15 | section?: 'integer' | 'decimal'; 16 | pad?: boolean; 17 | commify?: boolean; 18 | fixed?: number; 19 | } 20 | 21 | export class Amount { 22 | static ZERO = new Amount('0'); 23 | 24 | add(val: Amount): Amount { 25 | return new Amount(JSBI.add(this.toBigInt(), val.toBigInt()).toString(), 0); 26 | } 27 | 28 | sub(val: Amount): Amount { 29 | return new Amount( 30 | JSBI.subtract(this.toBigInt(), val.toBigInt()).toString(), 31 | 0 32 | ); 33 | } 34 | 35 | gt(val: Amount): boolean { 36 | return JSBI.GT(this.toBigInt(), val.toBigInt()); 37 | } 38 | 39 | gte(val: Amount): boolean { 40 | return JSBI.greaterThanOrEqual(this.toBigInt(), val.toBigInt()); 41 | } 42 | 43 | lt(val: Amount): boolean { 44 | return JSBI.LT(this.toBigInt(), val.toBigInt()); 45 | } 46 | 47 | lte(val: Amount): boolean { 48 | return JSBI.lessThanOrEqual(this.toBigInt(), val.toBigInt()); 49 | } 50 | 51 | eq(val: Amount): boolean { 52 | return JSBI.EQ(this.toBigInt(), val.toBigInt()); 53 | } 54 | 55 | private amount: string; 56 | 57 | constructor(amount: string, decimals: number | AmountUnit = AmountUnit.ckb) { 58 | if (!Number.isInteger(decimals) || decimals < 0) { 59 | throw new Error(`decimals ${decimals} must be a natural number`); 60 | } 61 | 62 | if (Number.isNaN(amount)) { 63 | throw new Error(`amount ${amount} must be a valid number`); 64 | } 65 | this.amount = rationalNumberToBnString(amount, decimals); 66 | } 67 | 68 | toString( 69 | decimals: number | AmountUnit = AmountUnit.ckb, 70 | options?: FormatOptions 71 | ): string { 72 | return bnStringToRationalNumber( 73 | this.toBigInt().toString(), 74 | decimals, 75 | options 76 | ); 77 | } 78 | 79 | toBigInt() { 80 | return JSBI.BigInt(this.amount); 81 | } 82 | 83 | toHexString() { 84 | return `0x${this.toBigInt().toString(16)}`; 85 | } 86 | 87 | toUInt128LE(): string { 88 | return toBigUInt128LE(JSBI.BigInt(this.toHexString())); 89 | } 90 | 91 | static fromUInt128LE(hex) { 92 | return new Amount( 93 | `0x${readBigUInt128LE(hex).toString(16)}`, 94 | AmountUnit.shannon 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/models/cell-dep.ts: -------------------------------------------------------------------------------- 1 | import { OutPoint } from '.'; 2 | import { DepType, CKBModel } from '..'; 3 | import { validators, transformers } from 'ckb-js-toolkit'; 4 | 5 | export class CellDep implements CKBModel { 6 | static fromRPC(data: any): CellDep | undefined { 7 | if (!data) return undefined; 8 | validators.ValidateCellDep(data); 9 | return new CellDep(data.dep_type, data.out_point); 10 | } 11 | 12 | constructor(public depType: DepType, public outPoint: OutPoint) {} 13 | 14 | validate(): boolean { 15 | validators.ValidateCellDep(transformers.TransformCellDep(this)); 16 | return true; 17 | } 18 | 19 | sameWith(cellDep: CellDep): boolean { 20 | validators.ValidateCellDep(transformers.TransformCellDep(cellDep)); 21 | return ( 22 | cellDep.depType === this.depType && 23 | cellDep.outPoint.sameWith(this.outPoint) 24 | ); 25 | } 26 | 27 | serializeJson(): object { 28 | return { 29 | dep_type: this.depType, 30 | out_point: this.outPoint, 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/models/cell-input.ts: -------------------------------------------------------------------------------- 1 | import { OutPoint } from '.'; 2 | import { CKBModel } from '..'; 3 | import { validators, transformers } from 'ckb-js-toolkit'; 4 | 5 | export class CellInput implements CKBModel { 6 | static fromRPC(data: any): CellInput { 7 | if (!data) { 8 | throw new Error('Cannot create CellInput from empty data'); 9 | } 10 | validators.ValidateCellInput(data); 11 | return new CellInput(data.previous_output, data.since); 12 | } 13 | 14 | constructor(public previousOutput: OutPoint, public since: string = '0x0') {} 15 | 16 | sameWith(cellInput: CellInput): boolean { 17 | validators.ValidateCellInput(transformers.TransformCellInput(cellInput)); 18 | return ( 19 | cellInput.previousOutput.sameWith(this.previousOutput) && 20 | cellInput.since === this.since 21 | ); 22 | } 23 | 24 | validate(): boolean { 25 | validators.ValidateCellInput(transformers.TransformCellInput(this)); 26 | return true; 27 | } 28 | 29 | serializeJson(): object { 30 | return { 31 | since: this.since, 32 | previous_output: this.previousOutput, 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/models/cell.spec.ts: -------------------------------------------------------------------------------- 1 | import anyTest, { TestInterface } from 'ava'; 2 | import PWCore, { ChainID } from '../core'; 3 | import { DummyCollector } from '../collectors/dummy-collector'; 4 | import { validators, transformers } from 'ckb-js-toolkit'; 5 | import { 6 | Address, 7 | AddressType, 8 | Amount, 9 | AmountUnit, 10 | Cell, 11 | OutPoint, 12 | Script, 13 | } from '.'; 14 | import { HashType } from '../interfaces'; 15 | import { DummyProvider } from '../providers/dummy-provider'; 16 | import { 17 | cellOccupiedBytes, 18 | hexDataOccupiedBytes, 19 | scriptOccupiedBytes, 20 | } from '../utils'; 21 | 22 | const test = anyTest as TestInterface<{ pw: PWCore }>; 23 | 24 | const address = new Address( 25 | 'ckt1qyqxpayn272n8km2k08hzldynj992egs0waqnr8zjs', 26 | AddressType.ckb 27 | ); 28 | 29 | test.before(async (t) => { 30 | const pw = new PWCore('https://testnet.ckb.dev'); 31 | await pw.init(new DummyProvider(), new DummyCollector(), ChainID.ckb_testnet); 32 | 33 | t.context.pw = pw; 34 | }); 35 | 36 | // from cell at https://explorer.nervos.org/aggron/transaction/0x79221866125b9aff33c4303a6c35bde25d235e7e10025a86ca2a5d6ad657f51f 37 | const data = 'Hello from Lay2'; 38 | const hexData = '0x48656c6c6f2066726f6d204c617932'; 39 | 40 | test('data actions', (t) => { 41 | const cell = new Cell( 42 | new Amount('100', AmountUnit.ckb), 43 | address.toLockScript() 44 | ); 45 | 46 | t.true(cell.isEmpty()); 47 | 48 | // t.is(cell.resize(), 61); 49 | 50 | cell.setData(data); 51 | t.is(cell.getData(), data); 52 | t.is(cell.getHexData(), hexData); 53 | 54 | t.false(cell.isEmpty()); 55 | 56 | cell.setHexData(hexData); 57 | t.is(cell.getData(), data); 58 | t.is(cell.getHexData(), hexData); 59 | 60 | cell.setData(hexData); 61 | t.not(cell.getData(), data); 62 | t.not(cell.getHexData(), hexData); 63 | 64 | t.throws(() => cell.setHexData(data)); 65 | }); 66 | 67 | test('loadFromBlockchain and validate', async (t) => { 68 | const outPoint = new OutPoint( 69 | '0x79221866125b9aff33c4303a6c35bde25d235e7e10025a86ca2a5d6ad657f51f', 70 | '0x0' 71 | ); 72 | const loadedCell = await Cell.loadFromBlockchain(t.context.pw.rpc, outPoint); 73 | t.notThrows(() => 74 | validators.ValidateCellOutput( 75 | transformers.TransformCellOutput(loadedCell.serializeJson()) 76 | ) 77 | ); 78 | t.true(loadedCell.capacity.eq(new Amount('76', AmountUnit.ckb))); 79 | t.true( 80 | loadedCell.lock.sameWith( 81 | new Script( 82 | '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', 83 | '0x705ca2e725e9b26e6abb842ed2043ea80197dfd7', 84 | HashType.type 85 | ) 86 | ) 87 | ); 88 | t.is(loadedCell.type, null); 89 | t.true(loadedCell.outPoint.sameWith(outPoint)); 90 | t.is(loadedCell.getHexData(), hexData); 91 | t.is(loadedCell.getData(), data); 92 | }); 93 | 94 | test('space check', (t) => { 95 | const cellData = 96 | '0x00e40b5402000000000000000000000081bf212901000000000000000000000000c817a80400000001'; 97 | const cell = new Cell( 98 | new Amount('102', AmountUnit.ckb), 99 | address.toLockScript(), 100 | null, 101 | null, 102 | cellData 103 | ); 104 | 105 | // cell.setHexData(cellData);2 106 | 107 | // throw new Error(cell.getData()); 108 | 109 | t.is(cell.getHexData(), cellData); 110 | t.is(hexDataOccupiedBytes(cell.getHexData()), 41); 111 | t.is(scriptOccupiedBytes(cell.lock), 53); 112 | t.is(scriptOccupiedBytes(cell.type), 0); 113 | t.is(cellOccupiedBytes(cell), 102); 114 | }); 115 | -------------------------------------------------------------------------------- /src/models/cell.ts: -------------------------------------------------------------------------------- 1 | import { CKBModel } from '../interfaces'; 2 | import { Amount, Script, OutPoint } from '.'; 3 | import { CellInput } from './cell-input'; 4 | // import { minimalCellCapacity } from '../utils'; 5 | import { AmountUnit } from './amount'; 6 | import { RPC, validators, transformers } from 'ckb-js-toolkit'; 7 | import { HashType } from '..'; 8 | import { byteArrayToHex, cellOccupiedBytes, hexToByteArray } from '../utils'; 9 | 10 | export class Cell implements CKBModel { 11 | static fromRPC(data: any): Cell { 12 | if (!data) { 13 | throw new Error('Cannot create cell from empty data'); 14 | } 15 | validators.ValidateCellOutput(data); 16 | return new Cell( 17 | data.capacity, 18 | Script.fromRPC(data.lock), 19 | Script.fromRPC(data.type), 20 | OutPoint.fromRPC(data.out_point), 21 | data.data 22 | ); 23 | } 24 | 25 | static async loadFromBlockchain(rpc: RPC, outPoint: OutPoint): Promise { 26 | const index = Number(outPoint.index); 27 | const { 28 | transaction: { outputs, outputs_data }, 29 | } = await rpc.get_transaction(outPoint.txHash); 30 | const { capacity, lock, type } = outputs[index]; 31 | return new Cell( 32 | new Amount(capacity, AmountUnit.shannon), 33 | new Script(lock.code_hash, lock.args, HashType[lock.hash_type as string]), 34 | type 35 | ? new Script( 36 | type.code_hash, 37 | type.args, 38 | HashType[type.hash_type as string] 39 | ) 40 | : null, 41 | outPoint, 42 | outputs_data[index] 43 | ); 44 | } 45 | 46 | constructor( 47 | public capacity: Amount, 48 | public lock: Script, 49 | public type?: Script, 50 | public outPoint?: OutPoint, 51 | private data: string = '0x' 52 | ) { 53 | this.spaceCheck(); 54 | } 55 | 56 | clone() { 57 | return new Cell( 58 | this.capacity, 59 | this.lock, 60 | this.type, 61 | this.outPoint, 62 | this.data 63 | ); 64 | } 65 | 66 | sameWith(cell: Cell): boolean { 67 | if (!cell || !cell.outPoint || !this.outPoint) { 68 | throw new Error('to be compared, cells must have outPoint value'); 69 | } 70 | return cell.outPoint.sameWith(this.outPoint); 71 | } 72 | 73 | resize() { 74 | // const base = SerializeCellOutput( 75 | // normalizers.NormalizeCellOutput(transformers.TransformCellOutput(this)) 76 | // ).byteLength; 77 | const base = this.type ? 102 : 61; 78 | const extra = new Buffer(this.data.replace('0x', ''), 'hex').byteLength; 79 | const size = base + extra; 80 | this.capacity = new Amount(size.toString()); 81 | return size; 82 | } 83 | 84 | spaceCheck() { 85 | if (this.capacity.lt(this.occupiedCapacity())) { 86 | throw new Error( 87 | `cell capacity ${this.capacity.toString( 88 | AmountUnit.ckb 89 | )} less than the min capacity ${this.occupiedCapacity().toString( 90 | AmountUnit.ckb 91 | )}` 92 | ); 93 | } 94 | 95 | return true; 96 | } 97 | 98 | occupiedCapacity(): Amount { 99 | return new Amount(cellOccupiedBytes(this).toString(), AmountUnit.ckb); 100 | } 101 | 102 | availableFee(): Amount { 103 | return this.capacity.sub(this.occupiedCapacity()); 104 | } 105 | 106 | toCellInput(since: string = '0x0'): CellInput | undefined { 107 | return this.outPoint ? new CellInput(this.outPoint, since) : undefined; 108 | } 109 | 110 | validate(): Cell { 111 | validators.ValidateCellOutput(transformers.TransformCellOutput(this)); 112 | if (this.outPoint) { 113 | validators.ValidateCellInput( 114 | transformers.TransformCellInput(this.toCellInput()) 115 | ); 116 | } 117 | return this; 118 | } 119 | 120 | // CellOutput format 121 | serializeJson(): object { 122 | return { 123 | capacity: this.capacity.toHexString(), 124 | lock: this.lock, 125 | type: this.type, 126 | }; 127 | } 128 | 129 | setData(data: string) { 130 | data = data.trim(); 131 | const bytes = []; 132 | for (let i = 0; i < data.length; i++) { 133 | bytes.push(data.charCodeAt(i)); 134 | } 135 | this.data = byteArrayToHex(bytes); 136 | this.spaceCheck(); 137 | } 138 | 139 | setHexData(data: string) { 140 | data = data.trim(); 141 | if (!data.startsWith('0x')) { 142 | throw new Error('Hex data should start with 0x'); 143 | } 144 | this.data = data; 145 | this.spaceCheck(); 146 | } 147 | 148 | getData(): string { 149 | return hexToByteArray(this.data.trim()) 150 | .map((char) => String.fromCharCode(char)) 151 | .join(''); 152 | } 153 | 154 | getHexData(): string { 155 | return this.data.trim(); 156 | } 157 | 158 | setSUDTAmount(amount: Amount) { 159 | this.data = amount.toUInt128LE() + this.data.slice(34); 160 | } 161 | 162 | getSUDTAmount(): Amount { 163 | const sudtAmountData = this.data.slice(0, 34); 164 | return Amount.fromUInt128LE(sudtAmountData); 165 | } 166 | 167 | isEmpty(): boolean { 168 | return this.data.trim() === '0x'; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './address'; 2 | export * from './amount'; 3 | export * from './cell'; 4 | export * from './script'; 5 | export * from './out-point'; 6 | export * from './raw-transaction'; 7 | export * from './transaction'; 8 | export * from './cell-dep'; 9 | export * from './cell-input'; 10 | export * from './sudt'; 11 | -------------------------------------------------------------------------------- /src/models/out-point.ts: -------------------------------------------------------------------------------- 1 | import { validators, transformers } from 'ckb-js-toolkit'; 2 | import { CKBModel } from '..'; 3 | 4 | export class OutPoint implements CKBModel { 5 | static fromRPC(data: any): OutPoint | undefined { 6 | if (!data) return undefined; 7 | validators.ValidateOutPoint(data); 8 | return new OutPoint(data.tx_hash, data.index); 9 | } 10 | 11 | constructor(public txHash: string, public index: string) {} 12 | 13 | sameWith(outPoint: OutPoint): boolean { 14 | validators.ValidateOutPoint(transformers.TransformOutPoint(outPoint)); 15 | return this.txHash === outPoint.txHash && this.index === outPoint.index; 16 | } 17 | 18 | validate(): OutPoint { 19 | validators.ValidateOutPoint(transformers.TransformOutPoint(this)); 20 | return this; 21 | } 22 | 23 | serializeJson(): object { 24 | return { 25 | tx_hash: this.txHash, 26 | index: this.index, 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/models/raw-transaction.spec.ts: -------------------------------------------------------------------------------- 1 | import anyTest, { TestInterface } from 'ava'; 2 | import PWCore, { 3 | ChainID, 4 | DepType, 5 | RawTransaction, 6 | Cell, 7 | OutPoint, 8 | CellDep, 9 | } from '..'; 10 | import { DummyCollector } from '../collectors/dummy-collector'; 11 | import { DummyProvider } from '../providers/dummy-provider'; 12 | 13 | const test = anyTest as TestInterface<{ raw: RawTransaction }>; 14 | 15 | const outPoint1 = new OutPoint( 16 | '0x85f2eb3737f79af418361e6c6c03a5d9f0060b085a888c0c70d762842af1b6c1', 17 | '0x1' 18 | ); 19 | const outPoint2 = new OutPoint( 20 | '0x79221866125b9aff33c4303a6c35bde25d235e7e10025a86ca2a5d6ad657f51f', 21 | '0x0' 22 | ); 23 | const outPoint3 = new OutPoint( 24 | '0x79221866125b9aff33c4303a6c35bde25d235e7e10025a86ca2a5d6ad657f51f', 25 | '0x1' 26 | ); 27 | const outPoint4 = new OutPoint( 28 | '0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37', 29 | '0x0' 30 | ); 31 | 32 | test.before(async (t) => { 33 | const pw = new PWCore('https://testnet.ckb.dev'); 34 | await pw.init(new DummyProvider(), new DummyCollector(), ChainID.ckb_testnet); 35 | const cells = await Promise.all([ 36 | Cell.loadFromBlockchain(pw.rpc, outPoint1), 37 | Cell.loadFromBlockchain(pw.rpc, outPoint2), 38 | Cell.loadFromBlockchain(pw.rpc, outPoint3), 39 | ]); 40 | 41 | const inputs = [cells[0]]; 42 | const outputs = cells.slice(1); 43 | const cellDeps = [new CellDep(DepType.depGroup, outPoint4)]; 44 | 45 | t.context.raw = new RawTransaction(inputs, outputs, cellDeps); 46 | }); 47 | 48 | test('validate', (t) => { 49 | t.notThrows(() => t.context.raw.validate()); 50 | }); 51 | 52 | test('toHash', (t) => { 53 | t.is( 54 | t.context.raw.toHash(), 55 | '0x79221866125b9aff33c4303a6c35bde25d235e7e10025a86ca2a5d6ad657f51f' 56 | ); 57 | }); 58 | -------------------------------------------------------------------------------- /src/models/raw-transaction.ts: -------------------------------------------------------------------------------- 1 | import PWCore, { CKBModel, CellDep, Cell } from '..'; 2 | import { SerializeRawTransaction } from '@ckb-lumos/types/lib/core'; 3 | import { validators, normalizers, Reader, transformers } from 'ckb-js-toolkit'; 4 | import { CellInput } from './cell-input'; 5 | import { Blake2bHasher } from '../hashers'; 6 | 7 | export class RawTransaction implements CKBModel { 8 | public inputs: CellInput[]; 9 | public outputsData: string[]; 10 | 11 | constructor( 12 | public inputCells: Cell[], 13 | public outputs: Cell[], 14 | public cellDeps: CellDep[] = [ 15 | PWCore.config.defaultLock.cellDep, 16 | PWCore.config.pwLock.cellDep, 17 | ], 18 | public headerDeps: string[] = [], 19 | public readonly version: string = '0x0' 20 | ) { 21 | this.inputs = inputCells.map((i) => i.toCellInput()); 22 | this.outputsData = this.outputs.map((o) => o.getHexData()); 23 | } 24 | sameWith(raw: RawTransaction): boolean { 25 | validators.ValidateTransaction(transformers.TransformTransaction(raw)); 26 | return raw.toHash() === this.toHash(); 27 | } 28 | 29 | toHash() { 30 | const rawTx = transformers.TransformRawTransaction(this); 31 | const hasher = new Blake2bHasher(); 32 | return hasher 33 | .hash( 34 | new Reader( 35 | SerializeRawTransaction(normalizers.NormalizeRawTransaction(rawTx)) 36 | ) 37 | ) 38 | .serializeJson(); 39 | } 40 | 41 | validate(): boolean { 42 | validators.ValidateRawTransaction( 43 | transformers.TransformRawTransaction(this) 44 | ); 45 | return true; 46 | } 47 | 48 | serializeJson(): object { 49 | return { 50 | version: this.version, 51 | cell_deps: this.cellDeps, 52 | header_deps: this.headerDeps, 53 | inputs: this.inputs, 54 | outputs: this.outputs, 55 | outputs_data: this.outputsData, 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/models/script.spec.ts: -------------------------------------------------------------------------------- 1 | import anyTest, { TestInterface } from 'ava'; 2 | import PWCore, { ChainID } from '../core'; 3 | import { Address, AddressType, Script } from '.'; 4 | import { validators } from 'ckb-js-toolkit'; 5 | import { DummyProvider } from '../providers/dummy-provider'; 6 | import { DummyCollector } from '../collectors/dummy-collector'; 7 | 8 | const test = anyTest as TestInterface<{ 9 | lockScript: Script; 10 | ethLockScript: Script; 11 | }>; 12 | const address = new Address( 13 | 'ckt1qyqxpayn272n8km2k08hzldynj992egs0waqnr8zjs', 14 | AddressType.ckb 15 | ); 16 | 17 | const ethAddress = new Address( 18 | '0x26C5F390FF2033CbB44377361c63A3Dd2DE3121d', 19 | AddressType.eth 20 | ); 21 | 22 | test.before(async (t) => { 23 | await new PWCore('https://testnet.ckb.dev').init( 24 | new DummyProvider(), 25 | new DummyCollector(), 26 | ChainID.ckb_testnet 27 | ); 28 | 29 | t.context.lockScript = new Script( 30 | PWCore.config.defaultLock.script.codeHash, 31 | '0x60f493579533db6ab3cf717da49c8a5565107bba', 32 | PWCore.config.defaultLock.script.hashType 33 | ); 34 | 35 | t.context.ethLockScript = new Script( 36 | PWCore.config.pwLock.script.codeHash, 37 | '0x26C5F390FF2033CbB44377361c63A3Dd2DE3121d', 38 | PWCore.config.pwLock.script.hashType 39 | ); 40 | }); 41 | 42 | test('validate', (t) => { 43 | t.notThrows(() => 44 | validators.ValidateScript(t.context.lockScript.serializeJson()) 45 | ); 46 | }); 47 | 48 | test('sameWith', (t) => { 49 | t.true( 50 | t.context.lockScript.sameWith(t.context.lockScript), 51 | 'the t.context.two lock scripts are the same' 52 | ); 53 | }); 54 | 55 | test('toAddress', (t) => { 56 | t.is(t.context.lockScript.toAddress().toCKBAddress(), address.toCKBAddress()); 57 | t.is( 58 | t.context.ethLockScript.toAddress().toCKBAddress(), 59 | ethAddress.toCKBAddress() 60 | ); 61 | }); 62 | 63 | test.todo('toHash'); 64 | -------------------------------------------------------------------------------- /src/models/script.ts: -------------------------------------------------------------------------------- 1 | import { HashType, CKBModel } from '../interfaces'; 2 | import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils'; 3 | import { 4 | Address, 5 | AddressType, 6 | AddressPrefix, 7 | getDefaultPrefix, 8 | } from './address'; 9 | import { generateAddress, LumosConfigs } from '../utils'; 10 | import { validators, transformers } from 'ckb-js-toolkit'; 11 | 12 | export class Script implements CKBModel { 13 | static fromRPC(data: any): Script | undefined { 14 | if (!data) return undefined; 15 | validators.ValidateScript(data); 16 | return new Script(data.code_hash, data.args, data.hash_type); 17 | } 18 | 19 | constructor( 20 | public codeHash: string, 21 | public args: string, 22 | public hashType: HashType 23 | ) {} 24 | 25 | sameWith(script: Script) { 26 | validators.ValidateScript(transformers.TransformScript(script)); 27 | return ( 28 | this.args === script.args && 29 | this.codeHash === script.codeHash && 30 | this.hashType === script.hashType 31 | ); 32 | } 33 | 34 | validate(): boolean { 35 | validators.ValidateScript(transformers.TransformScript(this)); 36 | return true; 37 | } 38 | 39 | serializeJson(): object { 40 | return { 41 | code_hash: this.codeHash, 42 | args: this.args, 43 | hash_type: this.hashType, 44 | }; 45 | } 46 | 47 | toHash(): string { 48 | return scriptToHash(this); 49 | } 50 | 51 | toAddress(prefix: AddressPrefix = getDefaultPrefix()): Address { 52 | const address = generateAddress(this.serializeJson(), { 53 | config: LumosConfigs[prefix], 54 | }); 55 | 56 | return new Address(address, AddressType.ckb); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/models/sudt.ts: -------------------------------------------------------------------------------- 1 | import { Script } from '.'; 2 | import PWCore from '../core'; 3 | 4 | export interface SudtInfo { 5 | symbol: string; 6 | decimals: number; 7 | name: string; 8 | } 9 | export class SUDT { 10 | constructor(readonly issuerLockHash: string, readonly info?: SudtInfo) {} 11 | 12 | toTypeScript(): Script { 13 | const { codeHash, hashType } = PWCore.config.sudtType.script; 14 | return new Script(codeHash, this.issuerLockHash, hashType); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/models/transaction.spec.ts: -------------------------------------------------------------------------------- 1 | import anyTest, { TestInterface } from 'ava'; 2 | import PWCore, { 3 | ChainID, 4 | Transaction, 5 | DepType, 6 | RawTransaction, 7 | Cell, 8 | OutPoint, 9 | CellDep, 10 | } from '..'; 11 | import { DummyCollector } from '../collectors/dummy-collector'; 12 | import { DummyProvider } from '../providers/dummy-provider'; 13 | import { Builder } from '../builders'; 14 | 15 | const test = anyTest as TestInterface<{ tx: Transaction }>; 16 | 17 | const outPoint1 = new OutPoint( 18 | '0x85f2eb3737f79af418361e6c6c03a5d9f0060b085a888c0c70d762842af1b6c1', 19 | '0x1' 20 | ); 21 | const outPoint2 = new OutPoint( 22 | '0x79221866125b9aff33c4303a6c35bde25d235e7e10025a86ca2a5d6ad657f51f', 23 | '0x0' 24 | ); 25 | const outPoint3 = new OutPoint( 26 | '0x79221866125b9aff33c4303a6c35bde25d235e7e10025a86ca2a5d6ad657f51f', 27 | '0x1' 28 | ); 29 | const outPoint4 = new OutPoint( 30 | '0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37', 31 | '0x0' 32 | ); 33 | 34 | test.before(async (t) => { 35 | const pw = new PWCore('https://testnet.ckb.dev'); 36 | await pw.init(new DummyProvider(), new DummyCollector(), ChainID.ckb_testnet); 37 | 38 | const cells = await Promise.all([ 39 | Cell.loadFromBlockchain(pw.rpc, outPoint1), 40 | Cell.loadFromBlockchain(pw.rpc, outPoint2), 41 | Cell.loadFromBlockchain(pw.rpc, outPoint3), 42 | ]); 43 | 44 | const inputs = [cells[0]]; 45 | const outputs = cells.slice(1); 46 | const cellDeps = [new CellDep(DepType.depGroup, outPoint4)]; 47 | 48 | t.context.tx = new Transaction( 49 | new RawTransaction(inputs, outputs, cellDeps), 50 | [Builder.WITNESS_ARGS.Secp256k1] 51 | ); 52 | }); 53 | 54 | test('validate', (t) => { 55 | t.notThrows(() => t.context.tx.validate()); 56 | }); 57 | 58 | test.todo('getsize'); 59 | -------------------------------------------------------------------------------- /src/models/transaction.ts: -------------------------------------------------------------------------------- 1 | import { CKBModel, WitnessArgs } from '../interfaces'; 2 | import { ECDSA_WITNESS_LEN } from '../constants'; 3 | import { validators, normalizers, transformers, Reader } from 'ckb-js-toolkit'; 4 | import { 5 | SerializeTransaction, 6 | SerializeWitnessArgs, 7 | } from '@ckb-lumos/types/lib/core'; 8 | import { RawTransaction } from '.'; 9 | 10 | export class Transaction implements CKBModel { 11 | public witnesses: string[]; 12 | 13 | constructor( 14 | public raw: RawTransaction, 15 | public witnessArgs: WitnessArgs[], 16 | witnessLengths: number[] = [ECDSA_WITNESS_LEN] 17 | ) { 18 | this.witnesses = raw.inputs.map((_) => '0x'); 19 | for (let i = 0; i < witnessLengths.length; i++) { 20 | this.witnesses[i] = '0x' + '0'.repeat(witnessLengths[i] - 2); 21 | } 22 | if (!Array.isArray(witnessArgs)) 23 | throw new Error('[Transaction] - witnessArgs must be an Array!'); 24 | for (let i = 0; i < witnessArgs.length; i++) { 25 | this.witnesses[i] = new Reader( 26 | SerializeWitnessArgs( 27 | normalizers.NormalizeWitnessArgs(this.witnessArgs[i]) 28 | ) 29 | ).serializeJson(); 30 | } 31 | } 32 | 33 | sameWith(tx: Transaction): boolean { 34 | validators.ValidateTransaction(transformers.TransformTransaction(tx)); 35 | return ( 36 | tx.raw.sameWith(this.raw) && 37 | tx.witnesses.join('-') === this.witnesses.join('-') 38 | ); 39 | } 40 | 41 | getSize(): number { 42 | const tx = transformers.TransformTransaction(this); 43 | validators.ValidateTransaction(tx); 44 | 45 | // TODO: find out why the size is always smaller than the correct value by exact '4' 46 | return ( 47 | SerializeTransaction(normalizers.NormalizeTransaction(tx)).byteLength + 4 48 | ); 49 | } 50 | 51 | validate(): Transaction { 52 | validators.ValidateTransaction(transformers.TransformTransaction(this)); 53 | return this; 54 | } 55 | 56 | transform(): object { 57 | return transformers.TransformTransaction(this.serializeJson()); 58 | } 59 | 60 | serializeJson(): object { 61 | return { 62 | ...this.raw, 63 | witnesses: this.witnesses, 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/providers/dummy-provider.ts: -------------------------------------------------------------------------------- 1 | import { Provider, Platform } from './provider'; 2 | import { Address, AddressType } from '..'; 3 | 4 | export class DummyProvider extends Provider { 5 | sign(message: string): Promise { 6 | console.log('message', message); 7 | throw new Error('Method not implemented.'); 8 | } 9 | constructor(platform: Platform = Platform.eth) { 10 | super(platform); 11 | } 12 | async init(): Promise { 13 | if (this.platform === Platform.eth) { 14 | this.address = new Address( 15 | '0x26C5F390FF2033CbB44377361c63A3Dd2DE3121d', 16 | AddressType.eth 17 | ); 18 | } else { 19 | this.address = new Address( 20 | 'ckt1qyqxpayn272n8km2k08hzldynj992egs0waqnr8zjs', 21 | AddressType.ckb 22 | ); 23 | } 24 | return this; 25 | } 26 | async close() { 27 | console.log('do nothing'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/providers/eth-provider.ts: -------------------------------------------------------------------------------- 1 | import { Provider, Platform } from './provider'; 2 | import { Address, AddressType } from '..'; 3 | import ENS from 'ethereum-ens'; 4 | 5 | export class EthProvider extends Provider { 6 | onAddressChanged: (newAddress: Address) => void; 7 | constructor(onAddressChanged?: (newAddress: Address) => void) { 8 | super(Platform.eth); 9 | this.onAddressChanged = onAddressChanged; 10 | } 11 | async init(): Promise { 12 | if (typeof window.ethereum !== 'undefined') { 13 | window.ethereum.autoRefreshOnNetworkChange = false; 14 | const accounts = await window.ethereum.enable(); 15 | this.address = new Address(accounts[0], AddressType.eth); 16 | 17 | if (!!window.ethereum.on) { 18 | window.ethereum.on('accountsChanged', (newAccounts: string[]) => { 19 | this.address = new Address(newAccounts[0], AddressType.eth); 20 | if (!!this.onAddressChanged) { 21 | this.onAddressChanged(this.address); 22 | } 23 | }); 24 | } 25 | 26 | return this; 27 | } else if (!!window.web3) { 28 | console.log('[eth-provider] try window.web3'); 29 | const accounts = await new Promise((resolve, reject) => { 30 | window.web3.eth.getAccounts((err, result) => { 31 | if (!!err) { 32 | reject(err); 33 | } 34 | resolve(result); 35 | }); 36 | }); 37 | this.address = new Address(accounts[0], AddressType.eth); 38 | 39 | return this; 40 | } else { 41 | throw new Error( 42 | 'window.ethereum is undefined, Ethereum environment is required.' 43 | ); 44 | } 45 | } 46 | 47 | async ensResolver(ens: string): Promise { 48 | try { 49 | return await new ENS(window.web3.currentProvider).resolver(ens).addr(); 50 | } catch (e) { 51 | return 'Unknown ENS Name'; 52 | } 53 | } 54 | 55 | async sign(message: string): Promise { 56 | return new Promise((resolve, reject) => { 57 | const from = this.address.addressString; 58 | const params = [message, from]; 59 | const method = 'personal_sign'; 60 | 61 | window.web3.currentProvider.sendAsync( 62 | { method, params, from }, 63 | (err, result) => { 64 | if (err) { 65 | reject(err); 66 | } 67 | if (result.error) { 68 | reject(result.error); 69 | } 70 | result = result.result; 71 | let v = Number.parseInt(result.slice(-2), 16); 72 | if (v >= 27) v -= 27; 73 | result = result.slice(0, -2) + v.toString(16).padStart(2, '0'); 74 | resolve(result); 75 | } 76 | ); 77 | }); 78 | } 79 | 80 | async close() { 81 | console.log('do nothing'); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './provider'; 2 | export * from './eth-provider'; 3 | export * from './web3modal-provider'; 4 | -------------------------------------------------------------------------------- /src/providers/provider.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '..'; 2 | 3 | export enum Platform { 4 | ckb, 5 | eth, 6 | // btc, 7 | // eos, 8 | // tron, 9 | // libra 10 | } 11 | 12 | export abstract class Provider { 13 | constructor(public readonly platform: Platform) {} 14 | 15 | private _address: Address; 16 | get address(): Address { 17 | return this._address; 18 | } 19 | set address(value: Address) { 20 | this._address = value; 21 | } 22 | 23 | abstract async init(): Promise; 24 | 25 | abstract async sign(message: string): Promise; 26 | 27 | abstract async close(); 28 | } 29 | -------------------------------------------------------------------------------- /src/providers/web3modal-provider.ts: -------------------------------------------------------------------------------- 1 | import { Provider, Platform } from './provider'; 2 | import { Address, AddressType } from '../models'; 3 | import ENS from 'ethereum-ens'; 4 | import { verifyEthAddress } from '../utils'; 5 | 6 | export class Web3ModalProvider extends Provider { 7 | onAddressChanged: (newAddress: Address) => void; 8 | 9 | constructor( 10 | readonly web3: any, 11 | onAddressChanged?: (newAddress: Address) => void 12 | ) { 13 | super(Platform.eth); 14 | this.onAddressChanged = onAddressChanged; 15 | } 16 | 17 | async init(): Promise { 18 | const accounts = await this.web3.eth.getAccounts(); 19 | if (!verifyEthAddress(accounts[0])) { 20 | throw new Error('get ethereum address failed'); 21 | } 22 | 23 | this.address = new Address(accounts[0], AddressType.eth); 24 | if (this.web3.currentProvider.on) { 25 | this.web3.currentProvider.on( 26 | 'accountsChanged', 27 | async (newAccounts: string[]) => { 28 | this.address = new Address(newAccounts[0], AddressType.eth); 29 | if (this.onAddressChanged) { 30 | this.onAddressChanged(this.address); 31 | } 32 | } 33 | ); 34 | } 35 | 36 | return this; 37 | } 38 | 39 | async ensResolver(ens: string): Promise { 40 | try { 41 | return await new ENS(this.web3.currentProvider).resolver(ens).addr(); 42 | } catch (e) { 43 | return 'Unknown ENS Name'; 44 | } 45 | } 46 | 47 | async sign(message: string): Promise { 48 | let result = await this.web3.eth.personal.sign( 49 | message, 50 | this.address.addressString, 51 | null 52 | ); 53 | 54 | let v = Number.parseInt(result.slice(-2), 16); 55 | if (v >= 27) v -= 27; 56 | result = result.slice(0, -2) + v.toString(16).padStart(2, '0'); 57 | 58 | return result; 59 | } 60 | 61 | async close() { 62 | if ( 63 | this.web3 && 64 | this.web3.currentProvider && 65 | this.web3.currentProvider.close 66 | ) { 67 | await this.web3.currentProvider.close(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/signers/default-signer.ts: -------------------------------------------------------------------------------- 1 | import { Signer, Message } from '.'; 2 | import { Keccak256Hasher } from '../hashers'; 3 | import { Provider } from '../providers'; 4 | 5 | export class DefaultSigner extends Signer { 6 | constructor(public readonly provider: Provider) { 7 | super(new Keccak256Hasher()); 8 | } 9 | 10 | async signMessages(messages: Message[]): Promise { 11 | const sigs = []; 12 | for (const message of messages) { 13 | if ( 14 | this.provider.address.toLockScript().toHash() === message.lock.toHash() 15 | ) { 16 | sigs.push(await this.provider.sign(message.message)); 17 | } else { 18 | sigs.push('0x'); 19 | } 20 | } 21 | 22 | return sigs; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/signers/eth-signer.ts: -------------------------------------------------------------------------------- 1 | import { Signer, Message } from '.'; 2 | import { Keccak256Hasher } from '../hashers'; 3 | // import { 4 | // ecsign, 5 | // bufferToHex, 6 | // toBuffer, 7 | // setLengthLeft, 8 | // hashPersonalMessage, 9 | // } from 'ethereumjs-util'; 10 | 11 | // const a = '2A77A5C9DBA59D6F8B7A'; 12 | // const b = '2737A8A6D8E511CDC9439'; 13 | // const c = 'C97E919959D02502F8BCB50'; 14 | 15 | // function sendSync({ params }) { 16 | // const msg = hashPersonalMessage(toBuffer(params[0])) 17 | // .toString('hex') 18 | // .replace('0x', ''); 19 | 20 | // const { r, s, v } = ecsign( 21 | // new Buffer(msg, 'hex'), 22 | // new Buffer(`${a}${b}${c}`, 'hex') 23 | // ); 24 | 25 | // const hexsig = bufferToHex( 26 | // Buffer.concat([ 27 | // setLengthLeft(r, 32), 28 | // setLengthLeft(s, 32), 29 | // toBuffer(v - 27), 30 | // ]) 31 | // ); 32 | 33 | // return hexsig; 34 | // } 35 | 36 | export class EthSigner extends Signer { 37 | constructor(public readonly from: string) { 38 | super(new Keccak256Hasher()); 39 | } 40 | 41 | signMessages(messages: Message[]): Promise { 42 | return new Promise((resolve, reject) => { 43 | /* 44 | try { 45 | const sig = sendSync({ params: [messages[0].message] }); 46 | resolve([sig]); 47 | } catch (e) { 48 | reject(e); 49 | } 50 | */ 51 | 52 | const from = this.from; 53 | const params = [messages[0].message, from]; 54 | const method = 'personal_sign'; 55 | 56 | window.web3.currentProvider.sendAsync( 57 | { method, params, from }, 58 | (err, result) => { 59 | if (err) { 60 | reject(err); 61 | } 62 | if (result.error) { 63 | reject(result.error); 64 | } 65 | result = result.result; 66 | let v = Number.parseInt(result.slice(-2), 16); 67 | if (v >= 27) v -= 27; 68 | result = result.slice(0, -2) + v.toString(16).padStart(2, '0'); 69 | resolve([result]); 70 | } 71 | ); 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/signers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './signer'; 2 | export * from './eth-signer'; 3 | export * from './default-signer'; 4 | -------------------------------------------------------------------------------- /src/signers/signer.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '..'; 2 | import { Hasher, Blake2bHasher } from '../hashers'; 3 | import { normalizers, Reader, transformers } from 'ckb-js-toolkit'; 4 | import { 5 | SerializeWitnessArgs, 6 | SerializeRawTransaction, 7 | } from '@ckb-lumos/types/lib/core'; 8 | import { Script } from '../models'; 9 | 10 | export interface Message { 11 | index: number; 12 | message: string; 13 | lock: Script; 14 | } 15 | 16 | export abstract class Signer { 17 | protected constructor(private readonly hasher: Hasher) {} 18 | 19 | protected abstract async signMessages(messages: Message[]): Promise; 20 | 21 | async sign(tx: Transaction): Promise { 22 | const messages = this.toMessages(tx); 23 | const witnesses = await this.signMessages(messages); 24 | witnesses[0] = new Reader( 25 | SerializeWitnessArgs( 26 | normalizers.NormalizeWitnessArgs({ 27 | ...tx.witnessArgs[0], 28 | lock: witnesses[0], 29 | }) 30 | ) 31 | ).serializeJson(); 32 | tx = FillSignedWitnesses(tx, messages, witnesses); 33 | 34 | return tx; 35 | } 36 | 37 | private toMessages(tx: Transaction): Message[] { 38 | tx.validate(); 39 | 40 | if (tx.raw.inputs.length !== tx.raw.inputCells.length) { 41 | throw new Error('Input number does not match!'); 42 | } 43 | 44 | const txHash = new Blake2bHasher().hash( 45 | new Reader( 46 | SerializeRawTransaction( 47 | normalizers.NormalizeRawTransaction( 48 | transformers.TransformRawTransaction(tx.raw) 49 | ) 50 | ) 51 | ) 52 | ); 53 | 54 | const messages = []; 55 | const used = tx.raw.inputs.map((_input) => false); 56 | for (let i = 0; i < tx.raw.inputs.length; i++) { 57 | if (used[i]) { 58 | continue; 59 | } 60 | if (i >= tx.witnesses.length) { 61 | throw new Error( 62 | `Input ${i} starts a new script group, but witness is missing!` 63 | ); 64 | } 65 | used[i] = true; 66 | this.hasher.update(txHash); 67 | const firstWitness = new Reader(tx.witnesses[i]); 68 | this.hasher.update(serializeBigInt(firstWitness.length())); 69 | this.hasher.update(firstWitness); 70 | for ( 71 | let j = i + 1; 72 | j < tx.raw.inputs.length && j < tx.witnesses.length; 73 | j++ 74 | ) { 75 | if (tx.raw.inputCells[i].lock.sameWith(tx.raw.inputCells[j].lock)) { 76 | used[j] = true; 77 | const currentWitness = new Reader(tx.witnesses[j]); 78 | this.hasher.update(serializeBigInt(currentWitness.length())); 79 | this.hasher.update(currentWitness); 80 | } 81 | } 82 | messages.push({ 83 | index: i, 84 | message: this.hasher.digest().serializeJson(), // hex string 85 | lock: tx.raw.inputCells[i].lock, 86 | }); 87 | 88 | this.hasher.reset(); 89 | } 90 | return messages; 91 | } 92 | } 93 | 94 | function FillSignedWitnesses( 95 | tx: Transaction, 96 | messages: Message[], 97 | witnesses: string[] 98 | ) { 99 | if (messages.length !== witnesses.length) { 100 | throw new Error('Invalid number of witnesses!'); 101 | } 102 | for (let i = 0; i < messages.length; i++) { 103 | tx.witnesses[messages[i].index] = witnesses[i]; 104 | } 105 | return tx; 106 | } 107 | 108 | function serializeBigInt(i: number) { 109 | const view = new DataView(new ArrayBuffer(8)); 110 | view.setUint32(0, i, true); 111 | return view.buffer; 112 | } 113 | -------------------------------------------------------------------------------- /src/types/example.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * If you import a dependency which does not include its own type definitions, 3 | * TypeScript will try to find a definition for it by following the `typeRoots` 4 | * compiler option in tsconfig.json. For this project, we've configured it to 5 | * fall back to this folder if nothing is found in node_modules/@types. 6 | * 7 | * Often, you can install the DefinitelyTyped 8 | * (https://github.com/DefinitelyTyped/DefinitelyTyped) type definition for the 9 | * dependency in question. However, if no one has yet contributed definitions 10 | * for the package, you may want to declare your own. (If you're using the 11 | * `noImplicitAny` compiler options, you'll be required to declare it.) 12 | * 13 | * This is an example type definition for the `sha.js` package, used in hash.ts. 14 | * 15 | * (This definition was primarily extracted from: 16 | * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/v8/index.d.ts 17 | */ 18 | declare interface SymbolConstructor { 19 | readonly observable: symbol; 20 | } 21 | 22 | interface Window { 23 | web3: any; 24 | ethereum: any; 25 | } 26 | 27 | // declare module 'ckb-js-toolkit-contrib'; 28 | -------------------------------------------------------------------------------- /src/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi'; 2 | import anyTest, { TestInterface } from 'ava'; 3 | import { 4 | readBigUInt128LE, 5 | readBigUInt32LE, 6 | readBigUInt64LE, 7 | toBigUInt128LE, 8 | } from './utils'; 9 | 10 | // import { utils } from '@ckb-lumos/base'; 11 | 12 | const test = anyTest as TestInterface<{ utils }>; 13 | 14 | test('readUint32LE', (t) => { 15 | const hexLE = '0x78563412'; 16 | const hexBE = '0x12345678'; 17 | 18 | const num = readBigUInt32LE(hexLE); 19 | t.deepEqual(num.toString(16), hexBE.slice(2)); 20 | }); 21 | 22 | test('readUint64LE', (t) => { 23 | const hexLE = '0xefcdab9078563412'; 24 | const hexBE = '0x1234567890abcdef'; 25 | 26 | const num = readBigUInt64LE(hexLE); 27 | 28 | // t.deepEqual( 29 | // utils.readBigUInt64LE(hexLE).toString(16), 30 | // hexBE.slice(2), 31 | // 'lumos' 32 | // ); 33 | t.deepEqual(num.toString(16), hexBE.slice(2), 'self'); 34 | }); 35 | 36 | test('readUint128LE', (t) => { 37 | const hexLE = '0x8f7e6d5c4b3a2019efcdab9078563412'; 38 | const hexBE = '0x1234567890abcdef19203a4b5c6d7e8f'; 39 | 40 | const num = readBigUInt128LE(hexLE); 41 | // t.deepEqual( 42 | // utils.readBigUInt128LE(hexLE).toString(16), 43 | // hexBE.slice(2), 44 | // 'lumos' 45 | // ); 46 | 47 | t.deepEqual(num.toString(16), hexBE.slice(2), 'self'); 48 | }); 49 | 50 | test('toBigUInt128LE', (t) => { 51 | const hexLE = '0x8f7e6d5c4b3a2019efcdab9078563412'; 52 | const hexBE = '0x1234567890abcdef19203a4b5c6d7e8f'; 53 | 54 | // const numLE2 = utils.toBigUInt128LE(BigInt(hexBE)); 55 | // t.deepEqual(numLE2, hexLE, 'lumos toBigUInt128LE'); 56 | 57 | const numLE = toBigUInt128LE(JSBI.BigInt(hexBE)); 58 | t.deepEqual(numLE, hexLE, 'self toBigUInt128LE'); 59 | }); 60 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi'; 2 | import bech32 from 'bech32'; 3 | import { FormatOptions } from '.'; 4 | import { toUint64Le } from '@nervosnetwork/ckb-sdk-utils'; 5 | import Decimal from 'decimal.js'; 6 | 7 | const BECH32_LIMIT = 1023; 8 | 9 | export const shannonToCKB = ( 10 | shannonAmount: string, 11 | options: FormatOptions 12 | ): string => bnStringToRationalNumber(shannonAmount, 8, options); 13 | 14 | export const ckbToShannon = (ckbAmount: string): string => 15 | rationalNumberToBnString(ckbAmount, 8); 16 | 17 | export const bnStringToRationalNumber = ( 18 | bn: string, 19 | decimals: number, 20 | options: FormatOptions 21 | ) => { 22 | if (!Number.isInteger(decimals) || decimals < 0) { 23 | throw new Error("value of 'decimals' must be a natural integer"); 24 | } 25 | 26 | const n = new Decimal(bn); 27 | if (n.isNeg()) { 28 | bn = bn.slice(1); 29 | } 30 | 31 | let int = bn; 32 | let dec = ''; 33 | if (decimals > 0) { 34 | const intLen = bn.length - decimals; 35 | int = intLen > 0 ? bn.substr(0, intLen) : '0'; 36 | dec = intLen > 0 ? bn.slice(intLen) : `${'0'.repeat(-intLen)}${bn}`; 37 | dec = new Decimal(`0.${dec}`).toFixed().slice(2); 38 | } 39 | 40 | if (options) { 41 | if (options.fixed !== undefined) { 42 | if ( 43 | !Number.isInteger(options.fixed) || 44 | options.fixed < 1 45 | // || options.fixed > decimals 46 | ) { 47 | throw new Error( 48 | // `value of \'fixed\' must be a positive integer and not bigger than decimals value ${decimals}` 49 | `value of 'fixed' must be a positive integer` 50 | ); 51 | } 52 | const res = new Decimal(`0.${dec}`).toFixed(options.fixed).split('.'); 53 | dec = res[1]; 54 | if (res[0] === '1') { 55 | int = JSBI.add(JSBI.BigInt(int), JSBI.BigInt(1)).toString(); 56 | } 57 | } else if (options.pad && dec.length < decimals) { 58 | dec = `${dec}${'0'.repeat(decimals - dec.length)}`; 59 | } 60 | if (options.commify) { 61 | int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ','); 62 | } 63 | if (options.section === 'decimal') { 64 | return dec; 65 | } 66 | if (options.section === 'integer') { 67 | return n.isNeg() ? `-${int}` : int; 68 | } 69 | } 70 | 71 | if (n.isNeg()) { 72 | int = `-${int}`; 73 | } 74 | 75 | if (dec.length) return `${int}.${dec}`; 76 | return int; 77 | }; 78 | 79 | export const rationalNumberToBnString = ( 80 | rational: string, 81 | decimals: number 82 | ) => { 83 | if (!Number.isInteger(decimals) || decimals < 0) { 84 | throw new Error("value of 'decimals' must be a natural integer"); 85 | } 86 | if (decimals === 0) return rational; 87 | 88 | if (rational === '0x') rational = '0'; 89 | // const r = new Decimal(rational); 90 | // if (r.dp() > decimals) { 91 | // throw new Error( 92 | // `decimals ${decimals} is smaller than the digits number of ${rational}` 93 | // ); 94 | // } 95 | 96 | if (typeof rational === 'number') { 97 | const dp = new Decimal(rational).dp(); 98 | rational = Number(rational).toFixed(dp); 99 | } 100 | 101 | const parts = `${rational}`.split('.'); 102 | 103 | if (!!parts[1] && parts[1].length > decimals) { 104 | throw new Error( 105 | `decimals ${decimals} is smaller than the digits number of ${rational}` 106 | ); 107 | } 108 | 109 | return `${parts.join('')}${'0'.repeat( 110 | decimals - (!!parts[1] ? parts[1].length : 0) 111 | )}`; 112 | }; 113 | 114 | // from @lumos/helper 115 | 116 | const LINA = { 117 | PREFIX: 'ckb', 118 | SCRIPTS: { 119 | SECP256K1_BLAKE160: { 120 | SCRIPT: { 121 | code_hash: 122 | '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', 123 | hash_type: 'type', 124 | }, 125 | OUT_POINT: { 126 | tx_hash: 127 | '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c', 128 | index: '0x0', 129 | }, 130 | DEP_TYPE: 'dep_group', 131 | SHORT_ID: 0, 132 | }, 133 | SECP256K1_BLAKE160_MULTISIG: { 134 | SCRIPT: { 135 | code_hash: 136 | '0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8', 137 | hash_type: 'type', 138 | }, 139 | OUT_POINT: { 140 | tx_hash: 141 | '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c', 142 | index: '0x1', 143 | }, 144 | DEP_TYPE: 'dep_group', 145 | SHORT_ID: 1, 146 | }, 147 | }, 148 | }; 149 | 150 | const AGGRON4 = { 151 | PREFIX: 'ckt', 152 | SCRIPTS: { 153 | SECP256K1_BLAKE160: { 154 | SCRIPT: { 155 | code_hash: 156 | '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', 157 | hash_type: 'type', 158 | }, 159 | OUT_POINT: { 160 | tx_hash: 161 | '0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37', 162 | index: '0x0', 163 | }, 164 | DEP_TYPE: 'dep_group', 165 | SHORT_ID: 0, 166 | }, 167 | SECP256K1_BLAKE160_MULTISIG: { 168 | SCRIPT: { 169 | code_hash: 170 | '0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8', 171 | hash_type: 'type', 172 | }, 173 | OUT_POINT: { 174 | tx_hash: 175 | '0x6495cede8d500e4309218ae50bbcadb8f722f24cc7572dd2274f5876cb603e4e', 176 | index: '0x1', 177 | }, 178 | DEP_TYPE: 'dep_group', 179 | SHORT_ID: 1, 180 | }, 181 | }, 182 | }; 183 | 184 | export const LumosConfigs = [LINA, AGGRON4]; 185 | 186 | export function byteArrayToHex(a) { 187 | return '0x' + a.map((i) => ('00' + i.toString(16)).slice(-2)).join(''); 188 | } 189 | 190 | export function hexToByteArray(h: string) { 191 | if (!/^(0x)?([0-9a-fA-F][0-9a-fA-F])*$/.test(h)) { 192 | throw new Error('Invalid hex string!'); 193 | } 194 | if (h.startsWith('0x')) { 195 | h = h.slice(2); 196 | } 197 | const array = []; 198 | while (h.length >= 2) { 199 | array.push(parseInt(h.slice(0, 2), 16)); 200 | h = h.slice(2); 201 | } 202 | return array; 203 | } 204 | 205 | export function generateAddress(script: any, { config = LINA } = {}): string { 206 | const scriptTemplate = Object.values(config.SCRIPTS).find( 207 | (s) => 208 | s.SCRIPT.code_hash === script.code_hash && 209 | s.SCRIPT.hash_type === script.hash_type 210 | ); 211 | const data = []; 212 | if (scriptTemplate && scriptTemplate.SHORT_ID !== undefined) { 213 | data.push(1, scriptTemplate.SHORT_ID); 214 | data.push(...hexToByteArray(script.args)); 215 | } else { 216 | data.push(script.hash_type === 'type' ? 4 : 2); 217 | data.push(...hexToByteArray(script.code_hash)); 218 | data.push(...hexToByteArray(script.args)); 219 | } 220 | const words = bech32.toWords(data); 221 | return bech32.encode(config.PREFIX, words, BECH32_LIMIT); 222 | } 223 | 224 | export function parseAddress(address: string, { config = LINA } = {}) { 225 | const { prefix, words } = bech32.decode(address, BECH32_LIMIT); 226 | if (prefix !== config.PREFIX) { 227 | throw Error( 228 | `Invalid prefix! Expected: ${config.PREFIX}, actual: ${prefix}` 229 | ); 230 | } 231 | const data = bech32.fromWords(words); 232 | switch (data[0]) { 233 | case 1: 234 | if (data.length < 2) { 235 | throw Error(`Invalid payload length!`); 236 | } 237 | const scriptTemplate = Object.values(config.SCRIPTS).find( 238 | (s) => s.SHORT_ID === data[1] 239 | ); 240 | if (!scriptTemplate) { 241 | throw Error(`Invalid code hash index: ${data[1]}!`); 242 | } 243 | return { ...scriptTemplate.SCRIPT, args: byteArrayToHex(data.slice(2)) }; 244 | case 2: 245 | if (data.length < 33) { 246 | throw Error(`Invalid payload length!`); 247 | } 248 | return { 249 | code_hash: byteArrayToHex(data.slice(1, 33)), 250 | hash_type: 'data', 251 | args: byteArrayToHex(data.slice(33)), 252 | }; 253 | case 4: 254 | if (data.length < 33) { 255 | throw Error(`Invalid payload length!`); 256 | } 257 | return { 258 | code_hash: byteArrayToHex(data.slice(1, 33)), 259 | hash_type: 'type', 260 | args: byteArrayToHex(data.slice(33)), 261 | }; 262 | } 263 | throw Error(`Invalid payload format type: ${data[0]}`); 264 | } 265 | 266 | export function verifyCkbAddress(address: string): boolean { 267 | try { 268 | const config = address.startsWith('ckb') ? LINA : AGGRON4; 269 | parseAddress(address, { config }); 270 | return true; 271 | } catch (e) { 272 | return false; 273 | } 274 | } 275 | 276 | export function verifyEthAddress(address: string): boolean { 277 | return /^0x[a-fA-F0-9]{40}$/.test(address); 278 | } 279 | 280 | export const hexDataOccupiedBytes = (hexString) => { 281 | // Exclude 0x prefix, and every 2 hex digits are one byte 282 | return (hexString.length - 2) / 2; 283 | }; 284 | 285 | export const scriptOccupiedBytes = (script) => { 286 | if (script !== undefined && script !== null) { 287 | return ( 288 | 1 + 289 | hexDataOccupiedBytes(script.codeHash) + 290 | hexDataOccupiedBytes(script.args) 291 | // script.args.map(hexDataOccupiedBytes).reduce((x, y) => x + y, 0) 292 | ); 293 | } 294 | return 0; 295 | }; 296 | 297 | export const cellOccupiedBytes = (cell) => { 298 | return ( 299 | 8 + 300 | hexDataOccupiedBytes(cell.data) + 301 | scriptOccupiedBytes(cell.lock) + 302 | scriptOccupiedBytes(cell.type) 303 | ); 304 | }; 305 | export function readBigUInt32LE(hex) { 306 | if (hex.slice(0, 2) !== '0x') { 307 | throw new Error('hex must start with 0x'); 308 | } 309 | const dv = new DataView(new ArrayBuffer(4)); 310 | dv.setUint32(0, Number(hex.slice(0, 10)), true); 311 | return JSBI.BigInt(dv.getUint32(0, false)); 312 | // return BigInt(dv.getUint32(0, false)); 313 | } 314 | 315 | export function toBigUInt64LE(num) { 316 | return toUint64Le(`0x${JSBI.BigInt(num).toString(16)}`); 317 | } 318 | 319 | export function readBigUInt64LE(hex) { 320 | if (hex.slice(0, 2) !== '0x') { 321 | throw new Error('hex must start with 0x'); 322 | } 323 | const buf = hex.slice(2).padEnd(16, 0); 324 | 325 | const viewRight = `0x${buf.slice(0, 8)}`; 326 | const viewLeft = `0x${buf.slice(8, 16)}`; 327 | 328 | const numLeft = readBigUInt32LE(viewLeft).toString(16).padStart(8, '0'); 329 | const numRight = readBigUInt32LE(viewRight).toString(16).padStart(8, '0'); 330 | 331 | return JSBI.BigInt(`0x${numLeft}${numRight}`); 332 | } 333 | 334 | export function toBigUInt128LE(u128) { 335 | const viewRight = toBigUInt64LE( 336 | JSBI.signedRightShift(JSBI.BigInt(u128), JSBI.BigInt(64)) 337 | ); 338 | const viewLeft = toBigUInt64LE( 339 | JSBI.bitwiseAnd(JSBI.BigInt(u128), JSBI.BigInt('0xffffffffffffffff')) 340 | ); 341 | 342 | return `${viewLeft}${viewRight.slice(2)}`; 343 | } 344 | 345 | export function readBigUInt128LE(hex) { 346 | if (hex.slice(0, 2) !== '0x') { 347 | throw new Error('hex must start with 0x'); 348 | } 349 | const buf = hex.slice(2).padEnd(32, 0); 350 | 351 | const viewRight = `0x${buf.slice(0, 16)}`; 352 | const viewLeft = `0x${buf.slice(16, 32)}`; 353 | 354 | const numLeft = readBigUInt64LE(viewLeft).toString(16).padStart(16, '0'); 355 | const numRight = readBigUInt64LE(viewRight).toString(16).padStart(16, '0'); 356 | 357 | return JSBI.BigInt(`0x${numLeft}${numRight}`); 358 | } 359 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0x66FffEA07a8a770F32B90eAaBef23FdEeF3d070a' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "outDir": "build/main", 5 | "rootDir": "src", 6 | "moduleResolution": "node", 7 | "module": "commonjs", 8 | "declaration": true, 9 | "inlineSourceMap": true, 10 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 11 | // "strict": true /* Enable all strict type-checking options. */, 12 | /* Strict Type-Checking Options */ 13 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 14 | // "strictNullChecks": true /* Enable strict null checks. */, 15 | // "strictFunctionTypes": true /* Enable strict checking of function types. */, 16 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 17 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 18 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 19 | /* Additional Checks */ 20 | "noUnusedLocals": true /* Report errors on unused locals. */, 21 | "noUnusedParameters": true /* Report errors on unused parameters. */, 22 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 23 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 24 | /* Debugging Options */ 25 | "traceResolution": false /* Report module resolution log messages. */, 26 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 27 | "listFiles": false /* Print names of files part of the compilation. */, 28 | "pretty": true /* Stylize errors and messages using color and context. */, 29 | /* Experimental Options */ 30 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 31 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 32 | "lib": [ 33 | "es2017", 34 | "DOM" 35 | ], 36 | "types": [ 37 | "node" 38 | ], 39 | "typeRoots": [ 40 | "node_modules/@types", 41 | "src/types", 42 | ] 43 | }, 44 | "include": [ 45 | "src/**/*.ts" 46 | ], 47 | "exclude": [ 48 | "node_modules/**" 49 | ], 50 | "compileOnSave": false 51 | } -------------------------------------------------------------------------------- /tsconfig.module.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "outDir": "build/module", 6 | "module": "esnext" 7 | }, 8 | "exclude": [ 9 | "node_modules/**" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:latest", "tslint-config-prettier", "tslint-immutable"], 3 | "rules": { 4 | "interface-name": [true, "never-prefix"], 5 | // TODO: allow devDependencies only in **/*.spec.ts files: 6 | // waiting on https://github.com/palantir/tslint/pull/3708 7 | "no-implicit-dependencies": [true, "dev"], 8 | "semicolon": true, 9 | "no-console": false, 10 | "no-submodule-imports": false 11 | } 12 | } 13 | --------------------------------------------------------------------------------