├── .github ├── release-drafter.yml └── workflows │ ├── cd.yml │ ├── on-PR-target.yml │ ├── on-PR.yml │ ├── on-release.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── address ├── address.go └── address_test.go ├── collector ├── builder │ ├── builder.go │ ├── builder_test.go │ ├── ckb.go │ ├── dao.go │ ├── sudt.go │ └── sudt_test.go ├── example │ ├── README.md │ └── example.go ├── example_test.go ├── handler │ ├── ckb.go │ ├── dao.go │ ├── omnilock.go │ └── sudt.go ├── interface.go ├── iterator.go ├── iterator_test.go ├── live_cell_collector.go ├── offchain_input_collector.go ├── offchain_input_collector_test.go └── offchain_input_iterator.go ├── crypto ├── bech32 │ ├── bech32.go │ └── bech32_test.go ├── blake2b │ └── blake2b.go ├── key.go └── secp256k1 │ ├── key.go │ └── key_test.go ├── dao ├── helper.go └── helper_test.go ├── docs └── migration-guide.md ├── go.mod ├── go.sum ├── indexer ├── indexer.go ├── indexer_test.go ├── json.go ├── json_test.go └── types.go ├── lightclient ├── client.go ├── client_test.go ├── json.go ├── mocking │ ├── fetch_header │ │ ├── request.json │ │ └── response.json │ ├── fetch_transaction │ │ ├── request.json │ │ └── response.json │ ├── get_cells │ │ ├── request.json │ │ └── response.json │ ├── get_cells_capacity │ │ ├── request.json │ │ └── response.json │ ├── get_genesis_block │ │ ├── request.json │ │ └── response.json │ ├── get_header │ │ ├── request.json │ │ └── response.json │ ├── get_peers │ │ ├── request.json │ │ └── response.json │ ├── get_scripts │ │ ├── request.json │ │ └── response.json │ ├── get_tip_header │ │ ├── request.json │ │ └── response.json │ ├── get_transaction │ │ ├── request.json │ │ └── response.json │ ├── get_transactions │ │ ├── request.json │ │ └── response.json │ ├── get_transactions_grouped │ │ ├── request.json │ │ └── response.json │ ├── local_node_info │ │ ├── request.json │ │ └── response.json │ └── set_scripts │ │ ├── request.json │ │ └── response.json └── type.go ├── mercury ├── client.go ├── client_test.go └── model │ ├── asset.go │ ├── item.go │ ├── payload.go │ ├── payload_json.go │ ├── resp.go │ ├── resp_json.go │ └── resp_json_test.go ├── mocking └── client.go ├── rpc ├── client.go └── client_test.go ├── systemscript ├── generator.go ├── generator_test.go ├── info.go ├── info_test.go ├── script.go └── script_test.go ├── transaction ├── context.go ├── script_group.go ├── signer │ ├── any_can_pay.go │ ├── example_test.go │ ├── init.go │ ├── omnilock.go │ ├── omnilock │ │ ├── molecule │ │ │ ├── molecule.go │ │ │ └── omnilock.mol │ │ ├── type.go │ │ ├── type_serialize.go │ │ ├── type_serialize_test.go │ │ ├── type_test.go │ │ └── type_witness.go │ ├── pw_lock.go │ ├── secp256k1_blake160_multisig_all.go │ ├── secp256k1_blake160_sighash_all.go │ ├── secp256k1_blake160_sighash_all_test.go │ └── signer.go ├── signer_test │ ├── fixture │ │ ├── acp_one_input.json │ │ ├── omnilock_secp256k1_blake160_multisig_all_first.json │ │ ├── omnilock_secp256k1_blake160_multisig_all_second.json │ │ ├── omnilock_secp256k1_blake160_sighash_all.json │ │ ├── pw_one_group.json │ │ ├── secp256k1_blake160_multisig_all_first.json │ │ ├── secp256k1_blake160_multisig_all_second.json │ │ ├── secp256k1_blake160_sighash_all_extra_witness.json │ │ ├── secp256k1_blake160_sighash_all_one_group.json │ │ ├── secp256k1_blake160_sighash_all_one_input.json │ │ └── secp256k1_blake160_sighash_all_two_groups.json │ └── signer_test.go └── transaction.go ├── types ├── batch.go ├── chain.go ├── chain_test.go ├── common.go ├── epoch_parser.go ├── experiment.go ├── generic.go ├── json.go ├── json_test.go ├── molecule.go ├── molecule │ ├── blockchain.mol │ └── type.go ├── molecule_test.go ├── net.go ├── numeric │ ├── capacity.go │ ├── capacity_test.go │ ├── since.go │ └── since_test.go ├── pool.go ├── serialize.go ├── serialize_test.go └── stats.go └── utils ├── util.go └── util_test.go /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | autolabeler: 2 | - label: 'chore' 3 | branch: 4 | - '/chores{0,1}\/.*/' 5 | title: 6 | - '^chore.*' 7 | files: 8 | - '.github/**' 9 | - label: 'documentation' 10 | files: 11 | - 'README.md' 12 | branch: 13 | - '/docs{0,1}\/.*/' 14 | title: 15 | - '^doc.*' 16 | - label: 'bug' 17 | branch: 18 | - '/fix\/.+/' 19 | - '/(bug|bugfix|fix|hotfix)\/.*/' 20 | title: 21 | - '^(bug|bugfix|fix|hotfix).*' 22 | - label: 'feature' 23 | branch: 24 | - '/(feat|feature)\/.*/' 25 | title: 26 | - '^(feat|feature).*' 27 | - label: 'enhancement' 28 | branch: 29 | - '/enhancement\/.*/' 30 | title: 31 | - '^enhancement.*' 32 | - label: 'refactor' 33 | branch: 34 | - '/refactor\/.*/' 35 | title: 36 | - '^refactor/*' 37 | 38 | name-template: '$RESOLVED_VERSION' 39 | 40 | template: | 41 | $CHANGES 42 | category-template: '## $TITLE' 43 | categories: 44 | - title: '🚀 Features' 45 | labels: 46 | - 'feature' 47 | - 'enhancement' 48 | - title: '🐛 Bug Fixes' 49 | labels: 50 | - 'bug' 51 | - title: '🧰 Maintenance' 52 | labels: 53 | - 'refactor' 54 | - 'chore' 55 | - title: '📝 Document' 56 | labels: 57 | - 'documentation' 58 | 59 | change-template: '- $TITLE (#$NUMBER) @$AUTHOR' 60 | 61 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 62 | 63 | sort-by: 'title' 64 | 65 | version-resolver: 66 | major: 67 | labels: 68 | - 'major' 69 | minor: 70 | labels: 71 | - 'minor' 72 | patch: 73 | labels: 74 | - 'patch' 75 | default: patch 76 | 77 | exclude-labels: 78 | - 'skip_changelog' 79 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Continuous delivery in GitHub 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'rc/v*' 7 | 8 | jobs: 9 | create-release-draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Get tag 13 | id: get-tag 14 | run: echo ::set-output name=tag::${GITHUB_REF/refs\/tags\//} 15 | - name: Run action release-drafter 16 | id: release-drafter 17 | uses: release-drafter/release-drafter@v5 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | tag: ${{ steps.get-tag.outputs.tag }} 22 | outputs: 23 | tag: ${{ steps.get-tag.outputs.tag }} 24 | id: ${{ steps.release-drafter.outputs.id }} 25 | upload-url: ${{ steps.release-drafter.outputs.upload_url }} -------------------------------------------------------------------------------- /.github/workflows/on-PR-target.yml: -------------------------------------------------------------------------------- 1 | name: On PR target 2 | 3 | on: 4 | pull_request_target: 5 | types: [ opened, reopened, synchronize, edited ] 6 | 7 | jobs: 8 | add_label_on_PR: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Add label on PR 12 | uses: release-drafter/release-drafter@master 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | with: 16 | disable-releaser: true 17 | disable-autolabeler: false -------------------------------------------------------------------------------- /.github/workflows/on-PR.yml: -------------------------------------------------------------------------------- 1 | name: On PR 2 | 3 | on: 4 | pull_request: 5 | types: [ opened, reopened, synchronize, edited ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout branch 12 | uses: actions/checkout@v1 13 | - name: Set up Go environment 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: '^1.17.4' 17 | - name: Build 18 | run: | 19 | go build ./... 20 | -------------------------------------------------------------------------------- /.github/workflows/on-release.yml: -------------------------------------------------------------------------------- 1 | name: Update CHANGELOG.md on new release 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | update-changelog: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout branch 12 | uses: actions/checkout@v2 13 | - name: Append CHANGELOG.md 14 | run: | 15 | echo -e "${{ github.event.release.body }}\n$(cat CHANGELOG.md)" > CHANGELOG.md 16 | echo -e "# ${{ github.event.release.name }}\n\n$(cat CHANGELOG.md)" > CHANGELOG.md 17 | - name: Commit and create Pull Request 18 | uses: peter-evans/create-pull-request@v3 19 | with: 20 | commit-message: update CHANGELOG.md for release ${{ github.event.release.tag_name }} 21 | branch: chore/update-CHANGELOG-for-release-${{ github.event.release.tag_name }} 22 | base: develop 23 | title: 'Update CHANGELOG for release ${{ github.event.release.tag_name }}' 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | types: [ opened, reopened, synchronize, edited ] 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Install Go 10 | uses: actions/setup-go@v2 11 | with: 12 | go-version: '1.17' 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | - name: Test 16 | run: go test ./... -short -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # IDE meta 15 | /.idea 16 | 17 | # Dependency directories 18 | /vendor 19 | 20 | /test.go 21 | 22 | **/.DS_Store 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nervos Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /collector/builder/builder_test.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | type mockIterator struct { 11 | Cells []*types.TransactionInput 12 | index int 13 | } 14 | 15 | func (m *mockIterator) HasNext() bool { 16 | return m.index < len(m.Cells) 17 | } 18 | 19 | func (m *mockIterator) Next() *types.TransactionInput { 20 | current := m.Cells[m.index] 21 | m.index += 1 22 | return current 23 | } 24 | 25 | var ( 26 | lock = &types.Script{ 27 | CodeHash: types.HexToHash("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"), 28 | HashType: types.HashTypeType, 29 | Args: common.FromHex("0xeac21ac6d373414aaa9ba34c469f805d48b62f86"), 30 | } 31 | ) 32 | 33 | func getMockIterator() *mockIterator { 34 | return &mockIterator{ 35 | Cells: []*types.TransactionInput{ 36 | { 37 | OutPoint: &types.OutPoint{ 38 | TxHash: types.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), 39 | Index: 0, 40 | }, 41 | Output: &types.CellOutput{ 42 | Capacity: 100000000000, 43 | Lock: lock, 44 | }, 45 | OutputData: []byte{}, 46 | }, 47 | { 48 | OutPoint: &types.OutPoint{ 49 | TxHash: types.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), 50 | Index: 1, 51 | }, 52 | Output: &types.CellOutput{ 53 | Capacity: 10000000000, 54 | Lock: lock, 55 | }, 56 | OutputData: []byte{}, 57 | }, 58 | }, 59 | } 60 | } 61 | 62 | func TestCkbTransactionBuilderSingleInput(t *testing.T) { 63 | iterator := getMockIterator() 64 | builder := NewCkbTransactionBuilder(types.NetworkTest, iterator) 65 | builder.FeeRate = 1000 66 | builder.AddOutputByAddress("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r", 50100000000) 67 | err := builder.AddChangeOutputByAddress("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq02cgdvd5mng9924xarf3rflqzafzmzlpsuhh83c") 68 | if err != nil { 69 | t.Error(err) 70 | } 71 | tx, err := builder.Build() 72 | if err != nil { 73 | t.Error(err) 74 | } 75 | assert.Equal(t, 1, len(tx.TxView.Inputs)) 76 | assert.Equal(t, 1, len(tx.ScriptGroups)) 77 | assert.Equal(t, 2, len(tx.TxView.Outputs)) 78 | assert.Equal(t, *lock, *tx.ScriptGroups[0].Script) 79 | fee := 100000000000 - tx.TxView.Outputs[0].Capacity - tx.TxView.Outputs[1].Capacity 80 | assert.Equal(t, uint64(464), fee) 81 | } 82 | 83 | func TestCkbTransactionBuilderMultipleInputs(t *testing.T) { 84 | iterator := getMockIterator() 85 | builder := NewCkbTransactionBuilder(types.NetworkTest, iterator) 86 | builder.FeeRate = 1000 87 | builder.AddOutputByAddress("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r", 100000000000) 88 | err := builder.AddChangeOutputByAddress("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq02cgdvd5mng9924xarf3rflqzafzmzlpsuhh83c") 89 | if err != nil { 90 | t.Error(err) 91 | } 92 | tx, err := builder.Build() 93 | if err != nil { 94 | t.Error(err) 95 | } 96 | assert.Equal(t, 2, len(tx.TxView.Inputs)) 97 | assert.Equal(t, 1, len(tx.ScriptGroups)) 98 | assert.Equal(t, 2, len(tx.TxView.Outputs)) 99 | assert.Equal(t, *lock, *tx.ScriptGroups[0].Script) 100 | fee := 110000000000 - tx.TxView.Outputs[0].Capacity - tx.TxView.Outputs[1].Capacity 101 | assert.Equal(t, uint64(516), fee) 102 | } 103 | -------------------------------------------------------------------------------- /collector/builder/sudt_test.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common/hexutil" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/address" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/systemscript" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 8 | "github.com/stretchr/testify/assert" 9 | "math/big" 10 | "testing" 11 | ) 12 | 13 | type sudtMockIterator struct { 14 | Cells []*types.TransactionInput 15 | index int 16 | } 17 | 18 | func (m *sudtMockIterator) HasNext() bool { 19 | return m.index < len(m.Cells) 20 | } 21 | 22 | func (m *sudtMockIterator) Next() *types.TransactionInput { 23 | current := m.Cells[m.index] 24 | m.index += 1 25 | return current 26 | } 27 | 28 | var ( 29 | sudtSender, _ = address.Decode("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq02cgdvd5mng9924xarf3rflqzafzmzlpsuhh83c") 30 | sudtArgs, _ = hexutil.Decode("0xae4147ba8412767b3fd9bd16d45dab2fa5df283a6fd68dae5367524daa767ca7") 31 | sudtType = &types.Script{ 32 | CodeHash: types.HexToHash("0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4"), 33 | HashType: types.HashTypeType, 34 | Args: sudtArgs, 35 | } 36 | ) 37 | 38 | func getSudtMockIterator() *sudtMockIterator { 39 | return &sudtMockIterator{ 40 | Cells: []*types.TransactionInput{ 41 | { 42 | OutPoint: &types.OutPoint{ 43 | TxHash: types.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), 44 | Index: 0, 45 | }, 46 | Output: &types.CellOutput{ 47 | Capacity: 100000000000, 48 | Lock: sudtSender.Script, 49 | Type: sudtType, 50 | }, 51 | OutputData: systemscript.EncodeSudtAmount(big.NewInt(100)), 52 | }, 53 | { 54 | OutPoint: &types.OutPoint{ 55 | TxHash: types.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), 56 | Index: 1, 57 | }, 58 | Output: &types.CellOutput{ 59 | Capacity: 10000000000, 60 | Lock: sudtSender.Script, 61 | Type: sudtType, 62 | }, 63 | OutputData: systemscript.EncodeSudtAmount(big.NewInt(10)), 64 | }, 65 | }, 66 | } 67 | } 68 | 69 | func TestSudtTransactionBuilderBalance(t *testing.T) { 70 | var err error 71 | iterator := getSudtMockIterator() 72 | builder := NewSudtTransactionBuilderFromSudtArgs(types.NetworkTest, iterator, SudtTransactionTypeTransfer, sudtArgs) 73 | 74 | if _, err = builder.AddSudtOutputByAddress("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdamwzrffgc54ef48493nfd2sd0h4cjnxg4850up", big.NewInt(1)); err != nil { 75 | t.Error(err) 76 | } 77 | builder.FeeRate = 1000 78 | if err = builder.AddChangeOutputByAddress("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdamwzrffgc54ef48493nfd2sd0h4cjnxg4850up"); err != nil { 79 | t.Error(err) 80 | } 81 | tx, err := builder.Build() 82 | if err != nil { 83 | t.Error(err) 84 | } 85 | 86 | amount1, err := systemscript.DecodeSudtAmount(tx.TxView.OutputsData[0]) 87 | if err != nil { 88 | t.Error(err) 89 | } 90 | amount2, err := systemscript.DecodeSudtAmount(tx.TxView.OutputsData[1]) 91 | if err != nil { 92 | t.Error(err) 93 | } 94 | assert.Equal(t, big.NewInt(100), amount1.Add(amount1, amount2)) 95 | } 96 | -------------------------------------------------------------------------------- /collector/example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | CKB Go SDK examples 3 | 4 | ## Introduction 5 | 6 | The [example.go](./example.go) provides some examples shows how to use this sdk to interact with CKB's RPC, transaction, and Nervos DAO(deposit and withdraw). 7 | 8 | ## Transaction Examples 9 | 10 | These examples show how to transfer CKB, in different situations. 11 | 12 | - `SendCkbExample`: sign and send CKB from single-sig address. 13 | - `SendCkbByLightClientExample`: similar to `SendCkbExample` but interact with [`ckb-light-client`](https://github.com/nervosnetwork/ckb-light-client) 14 | - `SendCkbFromMultisigAddressExample`: sign and send CKB from multi-sig address with multiple private keys. 15 | - `SendChainedTransactionExample`: sign and send offchain transaction 16 | 17 | ## SUDT Examples 18 | 19 | [SUDT](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0025-simple-udt/0025-simple-udt.md) (Simple User Defined Tokens) is a token specification on CKB. 20 | You can think SUDT is an analog of ERC20 on Ethereum. 21 | 22 | Anyone can issue his own SUDT, or transfer a specific kind of SUDT if he has enough SUDT amount. SUDT smart contract should be used in type script in these transactions. 23 | 24 | - `IssueSudtExample`: shows how to issue SUDT 25 | - `SendSudtExample`: shows how to send issued SUDT 26 | 27 | ## Nervos DAO Examples 28 | 29 | Nervos DAO is a smart contract, with which users can interact the same way as any smart contracts on CKB. Nervos DAO has deposit and withdraw (phase1 and phase2). 30 | 31 | - Deposit: Users can send a transaction to deposit CKB into Nervos DAO at any time. CKB includes a special Nervos DAO type script in the genesis block. 32 | - Withdraw: Users can send a transaction to withdraw deposited CKB from Nervos DAO at any time (but a locking period will be applied to determine when exactly the tokens can be withdrawn). 33 | 34 | Check [Nervos DAO RFC](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md) for more details 35 | 36 | - `DepositDaoExample`: shows how to deposit DAO 37 | - `WithdrawDaoExample`: shows how to withdraw deposited CKB from Nervos DAO 38 | 39 | ## OmniLock Examples 40 | 41 | Omnilock is a new lock script designed for interoperability. Check https://blog.cryptape.com/omnilock-a-universal-lock-that-powers-interoperability-1 for more details 42 | 43 | - `SendCkbOmnilockExample`: similar to `SendCkbExample`, but use Omnilock to sign. 44 | - `SendCkbMultisigOmnilockExample`: similar to `SendCkbFromMultisigAddressExample`, but use Omnilock to sign. 45 | -------------------------------------------------------------------------------- /collector/example_test.go: -------------------------------------------------------------------------------- 1 | package collector_test 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/collector" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/collector/builder" 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction" 9 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 10 | ) 11 | 12 | // SimpleLockScriptHandler is an example script handler to add specified cell dep and prefill the witness. 13 | type SimpleLockScriptHandler struct { 14 | CellDep *types.CellDep 15 | WitnessPlaceholder []byte 16 | CodeHash types.Hash 17 | } 18 | 19 | func (r *SimpleLockScriptHandler) isMatched(script *types.Script) bool { 20 | if script == nil { 21 | return false 22 | } 23 | return reflect.DeepEqual(script.CodeHash, r.CodeHash) 24 | } 25 | 26 | func (r *SimpleLockScriptHandler) BuildTransaction(builder collector.TransactionBuilder, group *transaction.ScriptGroup, context interface{}) (bool, error) { 27 | // Only run on matched groups 28 | if group == nil || !r.isMatched(group.Script) { 29 | return false, nil 30 | } 31 | index := group.InputIndices[0] 32 | // set the witness placeholder 33 | if err := builder.SetWitness(uint(index), types.WitnessTypeLock, r.WitnessPlaceholder); err != nil { 34 | return false, err 35 | } 36 | // CkbTransactionBuilder.AddCellDep will remove duplications automatically. 37 | builder.AddCellDep(r.CellDep) 38 | return true, nil 39 | } 40 | 41 | func ExampleScriptHandler() { 42 | txHash := "0x1234" 43 | typeScriptHash := "0xabcd" 44 | 45 | s := builder.SimpleTransactionBuilder{} 46 | s.Register(&SimpleLockScriptHandler{ 47 | CellDep: &types.CellDep{ 48 | OutPoint: &types.OutPoint{ 49 | TxHash: types.HexToHash(txHash), 50 | Index: 0, 51 | }, 52 | DepType: types.DepTypeCode, 53 | }, 54 | CodeHash: types.HexToHash(typeScriptHash), 55 | WitnessPlaceholder: make([]byte, 8), 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /collector/handler/sudt.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/collector" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/systemscript" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 8 | "reflect" 9 | ) 10 | 11 | type SudtScriptHandler struct { 12 | CellDep *types.CellDep 13 | CodeHash types.Hash 14 | } 15 | 16 | func NewSudtScriptHandler(network types.Network) *SudtScriptHandler { 17 | var txHash types.Hash 18 | if network == types.NetworkMain { 19 | txHash = types.HexToHash("0xc7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5") 20 | } else if network == types.NetworkTest { 21 | txHash = types.HexToHash("0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769") 22 | } else { 23 | return nil 24 | } 25 | return &SudtScriptHandler{ 26 | CellDep: &types.CellDep{ 27 | OutPoint: &types.OutPoint{ 28 | TxHash: txHash, 29 | Index: 0, 30 | }, 31 | DepType: types.DepTypeCode, 32 | }, 33 | CodeHash: systemscript.GetCodeHash(network, systemscript.Sudt), 34 | } 35 | } 36 | 37 | func (r *SudtScriptHandler) isMatched(script *types.Script) bool { 38 | if script == nil { 39 | return false 40 | } 41 | return reflect.DeepEqual(script.CodeHash, r.CodeHash) 42 | } 43 | 44 | func (r *SudtScriptHandler) BuildTransaction(builder collector.TransactionBuilder, group *transaction.ScriptGroup, context interface{}) (bool, error) { 45 | if group == nil || !r.isMatched(group.Script) { 46 | return false, nil 47 | } 48 | builder.AddCellDep(r.CellDep) 49 | return true, nil 50 | } 51 | -------------------------------------------------------------------------------- /collector/interface.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 6 | ) 7 | 8 | type TransactionBuilder interface { 9 | SetVersion(version uint32) 10 | AddHeaderDep(headerDep types.Hash) int 11 | AddCellDep(cellDep *types.CellDep) int 12 | AddInput(input *types.CellInput) int 13 | SetSince(index uint, since uint64) error 14 | AddOutput(output *types.CellOutput, data []byte) int 15 | SetOutputData(index uint, data []byte) error 16 | SetWitness(index uint, witnessType types.WitnessType, data []byte) error 17 | AddScriptGroup(group *transaction.ScriptGroup) int 18 | Build(contexts ...interface{}) (*transaction.TransactionWithScriptGroups, error) 19 | } 20 | 21 | // The interface ScriptHandler is for scripts to register their building logic. 22 | // 23 | // The function BuildTransaction is the callback called by [TransactionBuilder] 24 | // for each script group and each context passed in 25 | // TransactionBuilder.Build. The context provides extra data for the script. 26 | // 27 | // Be calfully on when to run the logic for the script. TransactionBuilder will 28 | // not check whether the script group matches the script. 29 | // 30 | // The callback often does two things: 31 | // - Fill witness placeholder to make fee calculation correct. 32 | // - Add cell deps for the script. 33 | // 34 | // Returns bool indicating whether the transaction has been modified. 35 | type ScriptHandler interface { 36 | BuildTransaction(builder TransactionBuilder, group *transaction.ScriptGroup, context interface{}) (bool, error) 37 | } 38 | -------------------------------------------------------------------------------- /collector/iterator.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "context" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/address" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/indexer" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/lightclient" 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/rpc" 9 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 10 | ) 11 | 12 | type CellIterator interface { 13 | HasNext() bool 14 | Next() *types.TransactionInput 15 | } 16 | 17 | type LiveCellsGetter interface { 18 | GetCells(searchKey *indexer.SearchKey, order indexer.SearchOrder, limit uint64, afterCursor string) (*indexer.LiveCells, error) 19 | } 20 | 21 | type CkbLiveCellGetter struct { 22 | Client rpc.Client 23 | Context context.Context 24 | } 25 | 26 | func (c *CkbLiveCellGetter) GetCells(searchKey *indexer.SearchKey, order indexer.SearchOrder, limit uint64, afterCursor string) (*indexer.LiveCells, error) { 27 | ctx := c.Context 28 | if ctx == nil { 29 | ctx = context.Background() 30 | } 31 | return c.Client.GetCells(ctx, searchKey, order, limit, afterCursor) 32 | } 33 | 34 | type LightClientLiveCellGetter struct { 35 | Client lightclient.Client 36 | Context context.Context 37 | } 38 | 39 | func (c *LightClientLiveCellGetter) GetCells(searchKey *indexer.SearchKey, order indexer.SearchOrder, limit uint64, afterCursor string) (*indexer.LiveCells, error) { 40 | ctx := c.Context 41 | if ctx == nil { 42 | ctx = context.Background() 43 | } 44 | return c.Client.GetCells(ctx, searchKey, order, limit, afterCursor) 45 | } 46 | 47 | func newLiveCellIterator(getter LiveCellsGetter, key *indexer.SearchKey) CellIterator { 48 | return &LiveCellIterator{ 49 | LiveCellGetter: getter, 50 | SearchKey: key, 51 | SearchOrder: indexer.SearchOrderAsc, 52 | Limit: 100, 53 | afterCursor: "", 54 | cells: nil, 55 | index: 0, 56 | } 57 | } 58 | 59 | func newLiveCellIteratorFromAddress(getter LiveCellsGetter, addr string) (CellIterator, error) { 60 | a, err := address.Decode(addr) 61 | if err != nil { 62 | return nil, err 63 | } 64 | searchKey := &indexer.SearchKey{ 65 | Script: a.Script, 66 | ScriptType: types.ScriptTypeLock, 67 | ScriptSearchMode: types.ScriptSearchModePrefix, 68 | Filter: nil, 69 | WithData: true, 70 | } 71 | return newLiveCellIterator(getter, searchKey), nil 72 | } 73 | 74 | func NewLiveCellIterator(client rpc.Client, key *indexer.SearchKey) CellIterator { 75 | return newLiveCellIterator(&CkbLiveCellGetter{Client: client}, key) 76 | } 77 | 78 | func NewLiveCellIteratorFromAddress(client rpc.Client, addr string) (CellIterator, error) { 79 | return newLiveCellIteratorFromAddress(&CkbLiveCellGetter{Client: client}, addr) 80 | } 81 | 82 | func NewLiveCellIteratorByLightClient(client lightclient.Client, key *indexer.SearchKey) CellIterator { 83 | return newLiveCellIterator(&LightClientLiveCellGetter{Client: client}, key) 84 | } 85 | 86 | func NewLiveCellIteratorByLightClientFromAddress(client lightclient.Client, addr string) (CellIterator, error) { 87 | return newLiveCellIteratorFromAddress(&LightClientLiveCellGetter{Client: client}, addr) 88 | } 89 | 90 | type LiveCellIterator struct { 91 | LiveCellGetter LiveCellsGetter 92 | SearchKey *indexer.SearchKey 93 | SearchOrder indexer.SearchOrder 94 | Limit uint64 95 | afterCursor string 96 | cells []*types.TransactionInput 97 | index int 98 | } 99 | 100 | func (r *LiveCellIterator) HasNext() bool { 101 | r.update() 102 | return r.index < len(r.cells) 103 | } 104 | 105 | func (r *LiveCellIterator) Next() *types.TransactionInput { 106 | current := r.cells[r.index] 107 | r.index++ 108 | return current 109 | } 110 | 111 | func (r *LiveCellIterator) update() bool { 112 | if r.index >= 0 && r.index < len(r.cells) { 113 | return false 114 | } 115 | liveCells, err := r.LiveCellGetter.GetCells(r.SearchKey, r.SearchOrder, r.Limit, r.afterCursor) 116 | if err != nil { 117 | return false 118 | } 119 | r.cells = make([]*types.TransactionInput, 0) 120 | for _, c := range liveCells.Objects { 121 | i := &types.TransactionInput{ 122 | OutPoint: c.OutPoint, 123 | Output: c.Output, 124 | OutputData: c.OutputData, 125 | } 126 | r.cells = append(r.cells, i) 127 | } 128 | r.afterCursor = liveCells.LastCursor 129 | r.index = 0 130 | return true 131 | } 132 | -------------------------------------------------------------------------------- /collector/iterator_test.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/rpc" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestLiveCellIterator(t *testing.T) { 12 | client, err := rpc.Dial("https://testnet.ckb.dev") 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | i, err := NewLiveCellIteratorFromAddress(client, "ckt1qyqgrfqrklscqeutp3tlqhlcd8xrculgufqspwdp7m") 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | count := 0 21 | for i.HasNext() { 22 | i.Next() 23 | count += 1 24 | } 25 | assert.Equal(t, 10, count) 26 | 27 | // Check outputData 28 | i, err = NewLiveCellIteratorFromAddress(client, "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqgxc8z84suk20xzx8337sckkkjfqvzk2ysq48gzc") 29 | count = 0 30 | var v *types.TransactionInput 31 | for i.HasNext() { 32 | v = i.Next() 33 | count += 1 34 | } 35 | assert.Equal(t, 1, count) 36 | assert.Equal(t, common.FromHex("0x0000000000000000"), v.OutputData) 37 | } 38 | -------------------------------------------------------------------------------- /collector/live_cell_collector.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "context" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/indexer" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/rpc" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | type ChangeOutputIndex struct { 12 | Value int 13 | } 14 | 15 | type LiveCellCollectResult struct { 16 | LiveCells []*indexer.LiveCell 17 | Capacity uint64 18 | Options map[string]interface{} 19 | } 20 | 21 | type CellCollectionIterator interface { 22 | HasNext() bool 23 | Next() error 24 | CurrentItem() (*indexer.LiveCell, error) 25 | Iterator() (CellCollectionIterator, error) 26 | } 27 | 28 | type LiveCellCollector struct { 29 | Client rpc.Client 30 | SearchKey *indexer.SearchKey 31 | SearchOrder indexer.SearchOrder 32 | Limit uint64 33 | LastCursor string 34 | EmptyData bool 35 | TypeScript *types.Script 36 | result []*indexer.LiveCell 37 | itemIndex int 38 | } 39 | 40 | func (c *LiveCellCollector) HasNext() bool { 41 | return c.itemIndex < len(c.result) 42 | } 43 | 44 | func (c *LiveCellCollector) Next() error { 45 | c.itemIndex++ 46 | if c.itemIndex >= len(c.result) && c.LastCursor != "" { 47 | c.itemIndex = 0 48 | var err error 49 | c.result, c.LastCursor, err = c.collect() 50 | if err != nil { 51 | return err 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | func (c *LiveCellCollector) CurrentItem() (*indexer.LiveCell, error) { 58 | if c.itemIndex >= len(c.result) { 59 | return nil, errors.New("no such element") 60 | } 61 | return c.result[c.itemIndex], nil 62 | } 63 | 64 | func (c *LiveCellCollector) Iterator() (CellCollectionIterator, error) { 65 | result, lastCursor, err := c.collect() 66 | if err != nil { 67 | return nil, err 68 | } 69 | c.result = result 70 | c.LastCursor = lastCursor 71 | 72 | return c, nil 73 | } 74 | 75 | func (c *LiveCellCollector) collect() ([]*indexer.LiveCell, string, error) { 76 | if c.SearchKey == nil { 77 | return nil, "", errors.New("missing SearchKey error") 78 | } 79 | if c.SearchOrder != indexer.SearchOrderAsc && c.SearchOrder != indexer.SearchOrderDesc { 80 | return nil, "", errors.New("missing SearchOrder error") 81 | } 82 | var result []*indexer.LiveCell 83 | liveCells, err := c.Client.GetCells(context.Background(), c.SearchKey, c.SearchOrder, c.Limit, c.LastCursor) 84 | if err != nil { 85 | return nil, "", err 86 | } 87 | for _, cell := range liveCells.Objects { 88 | if c.TypeScript != nil { 89 | if !c.TypeScript.Equals(cell.Output.Type) { 90 | continue 91 | } 92 | } else { 93 | if cell.Output.Type != nil { 94 | continue 95 | } 96 | } 97 | if c.EmptyData && len(cell.OutputData) > 0 { 98 | continue 99 | } 100 | result = append(result, cell) 101 | } 102 | return result, liveCells.LastCursor, nil 103 | } 104 | 105 | func NewLiveCellCollector(client rpc.Client, searchKey *indexer.SearchKey, searchOrder indexer.SearchOrder, limit uint64, afterCursor string) *LiveCellCollector { 106 | return &LiveCellCollector{ 107 | Client: client, 108 | SearchKey: searchKey, 109 | SearchOrder: searchOrder, 110 | Limit: limit, 111 | LastCursor: afterCursor, 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /collector/offchain_input_collector.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "container/list" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/rpc" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | ) 8 | 9 | type OffChainInputCollector struct { 10 | Client rpc.Client 11 | blockNumberOffset uint64 12 | usedLiveCells list.List 13 | offChainLiveCells list.List 14 | } 15 | 16 | type OutPointWithBlockNumber struct { 17 | *types.OutPoint 18 | blockNumber uint64 19 | } 20 | 21 | type TransactionInputWithBlockNumber struct { 22 | types.TransactionInput 23 | blockNumber uint64 24 | } 25 | 26 | func NewOffChainInputCollector(Client rpc.Client) *OffChainInputCollector { 27 | return &OffChainInputCollector{ 28 | Client: Client, 29 | blockNumberOffset: 13, 30 | } 31 | } 32 | 33 | func (c *OffChainInputCollector) setBlockNumberOffset(blockNumberOffset uint64) { 34 | c.blockNumberOffset = blockNumberOffset 35 | } 36 | 37 | func (c *OffChainInputCollector) ApplyOffChainTransaction(tipBlockNumber uint64, transaction types.Transaction) { 38 | transactionHash := transaction.ComputeHash() 39 | var next *list.Element 40 | for o := c.usedLiveCells.Front(); o != nil; o = next { 41 | next = o.Next() 42 | blockNumber := o.Value.(OutPointWithBlockNumber).blockNumber 43 | if tipBlockNumber >= blockNumber && tipBlockNumber-blockNumber <= c.blockNumberOffset { 44 | // keeps 45 | } else { 46 | c.usedLiveCells.Remove(o) 47 | } 48 | } 49 | next = nil 50 | for o := c.offChainLiveCells.Front(); o != nil; o = next { 51 | next = o.Next() 52 | blockNumber := o.Value.(TransactionInputWithBlockNumber).blockNumber 53 | if tipBlockNumber >= blockNumber && tipBlockNumber-blockNumber <= c.blockNumberOffset { 54 | 55 | } else { 56 | c.offChainLiveCells.Remove(o) 57 | } 58 | } 59 | 60 | for _, txInput := range transaction.Inputs { 61 | consumedOutpoint := txInput.PreviousOutput 62 | c.usedLiveCells.PushBack(OutPointWithBlockNumber{consumedOutpoint, tipBlockNumber}) 63 | next = c.offChainLiveCells.Front() 64 | for cell := next; cell != nil; cell = next { 65 | if next != nil { 66 | outpoint := next.Value.(TransactionInputWithBlockNumber).OutPoint 67 | if txInput.PreviousOutput == outpoint { 68 | c.offChainLiveCells.Remove(cell) 69 | } 70 | } 71 | next = cell.Next() 72 | } 73 | } 74 | 75 | for i, txOutput := range transaction.Outputs { 76 | c.offChainLiveCells.PushBack(TransactionInputWithBlockNumber{ 77 | TransactionInput: types.TransactionInput{ 78 | OutPoint: &types.OutPoint{ 79 | TxHash: transactionHash, Index: uint32(i), 80 | }, 81 | Output: txOutput, 82 | OutputData: transaction.OutputsData[i], 83 | }, 84 | blockNumber: tipBlockNumber, 85 | }) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /collector/offchain_input_collector_test.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/address" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/rpc" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | "github.com/stretchr/testify/assert" 8 | "math/rand" 9 | "testing" 10 | ) 11 | 12 | func TestOffChainInputCollector(t *testing.T) { 13 | input := getRandomTransactionInput(300) 14 | client, err := rpc.Dial("https://testnet.ckb.dev") 15 | if err != nil { 16 | t.Error(err) 17 | } 18 | collector := NewOffChainInputCollector(client) 19 | collector.offChainLiveCells.PushBack(input) 20 | tx := types.Transaction{} 21 | tx.Inputs = append(tx.Inputs, &types.CellInput{ 22 | PreviousOutput: input.OutPoint, 23 | }) 24 | tx.Outputs = append(tx.Outputs, getRandomOutput()) 25 | tx.OutputsData = append(tx.OutputsData, make([]byte, 0)) 26 | tx.Outputs = append(tx.Outputs, getRandomOutput()) 27 | tx.OutputsData = append(tx.OutputsData, make([]byte, 0)) 28 | 29 | assert.Equal(t, 0, collector.usedLiveCells.Len()) 30 | assert.Equal(t, 1, collector.offChainLiveCells.Len()) 31 | 32 | collector.ApplyOffChainTransaction(500, tx) 33 | 34 | assert.Equal(t, 1, collector.usedLiveCells.Len()) 35 | 36 | txInput1 := tx.Inputs[0] 37 | evaluateInput := false 38 | for cell := collector.usedLiveCells.Front(); cell != nil; cell = cell.Next() { 39 | if cell.Value.(OutPointWithBlockNumber).Index == txInput1.PreviousOutput.Index && 40 | cell.Value.(OutPointWithBlockNumber).TxHash == txInput1.PreviousOutput.TxHash { 41 | evaluateInput = true 42 | } 43 | } 44 | assert.True(t, evaluateInput) 45 | 46 | output1 := tx.Outputs[0] 47 | assert.Equal(t, 2, collector.offChainLiveCells.Len()) 48 | evaluateOutput := false 49 | for cell := collector.offChainLiveCells.Front(); cell != nil; cell = cell.Next() { 50 | if cell.Value.(TransactionInputWithBlockNumber).Output.Lock == output1.Lock && cell.Value.(TransactionInputWithBlockNumber).Output.Capacity == output1.Capacity { 51 | evaluateOutput = true 52 | } 53 | } 54 | assert.True(t, evaluateOutput) 55 | 56 | tx = types.Transaction{} 57 | tx.Inputs = append(tx.Inputs, &types.CellInput{ 58 | PreviousOutput: getRandomTransactionInput(600).OutPoint, 59 | }) 60 | tx.Outputs = append(tx.Outputs, getRandomOutput()) 61 | tx.OutputsData = append(tx.OutputsData, make([]byte, 0)) 62 | collector.ApplyOffChainTransaction(999, tx) 63 | // Because 999 > 500, so clear all usedLiveCells and offChainLiveCells at first. 64 | assert.Equal(t, 1, collector.usedLiveCells.Len()) 65 | assert.Equal(t, 1, collector.offChainLiveCells.Len()) 66 | 67 | tx = types.Transaction{} 68 | tx.Inputs = append(tx.Inputs, 69 | &types.CellInput{ 70 | PreviousOutput: collector.offChainLiveCells.Front().Value.(TransactionInputWithBlockNumber).OutPoint, 71 | }) 72 | tx.Outputs = append(tx.Outputs, getRandomOutput()) 73 | tx.OutputsData = append(tx.OutputsData, make([]byte, 0)) 74 | addr, _ := address.Decode("ckt1qrl2cyw7ulrxu48ysexpwus46r9md670h5h73cxjh3zmxsf4gt3d5qg2d5amjwfzgtqr2l72ulxw4k8c0dpga55qjzdlm749f9ffhpwl8zc422t2hvxmtlkk299l30k6xlgccjps9pe2sfhx5y3flvtlu56lu9u6pcqqqqqqqqvykxmu") 75 | tx.Outputs = append(tx.Outputs, &types.CellOutput{ 76 | Capacity: 50000000000, 77 | Lock: addr.Script, 78 | }) 79 | tx.OutputsData = append(tx.OutputsData, make([]byte, 0)) 80 | collector.ApplyOffChainTransaction(1000, tx) 81 | assert.Equal(t, 2, collector.usedLiveCells.Len()) 82 | assert.Equal(t, 2, collector.offChainLiveCells.Len()) 83 | } 84 | 85 | func getRandomTransactionInput(blockNumber uint64) TransactionInputWithBlockNumber { 86 | return TransactionInputWithBlockNumber{ 87 | TransactionInput: types.TransactionInput{ 88 | OutPoint: getRandomOutPoint(), 89 | Output: getRandomOutput(), 90 | OutputData: make([]byte, 0), 91 | }, 92 | blockNumber: blockNumber, 93 | } 94 | } 95 | 96 | func getRandomOutPoint() *types.OutPoint { 97 | hash := make([]byte, 32) 98 | rand.Read(hash) 99 | return &types.OutPoint{ 100 | TxHash: types.BytesToHash(hash), 101 | Index: uint32(rand.Intn(100)), 102 | } 103 | } 104 | 105 | func getRandomOutput() *types.CellOutput { 106 | addr, _ := address.Decode("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq03ewkvsva4cchhntydu648l7lyvn9w2cctnpask") 107 | return &types.CellOutput{ 108 | Capacity: rand.Uint64(), 109 | Lock: addr.Script, 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crypto/bech32/bech32_test.go: -------------------------------------------------------------------------------- 1 | package bech32 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEncode(t *testing.T) { 11 | bytes, err := hex.DecodeString("0004000b1e0f14121b090411031e121f0c08070716071e120f1016101b17080d1c1d0200") 12 | if err != nil { 13 | assert.Error(t, err) 14 | } 15 | address, err := Encode("ckb", bytes) 16 | assert.Equal(t, "ckb1qyqt705jmfy3r7jlvg88k87j0sksmhgduazqrr2qt2", address) 17 | } 18 | 19 | func TestDecode(t *testing.T) { 20 | encoding, hrp, decoded, err := Decode("ckb1qyqt705jmfy3r7jlvg88k87j0sksmhgduazqrr2qt2") 21 | if err != nil { 22 | assert.Error(t, err) 23 | } 24 | assert.Equal(t, BECH32, encoding) 25 | assert.Equal(t, "ckb", hrp) 26 | assert.Equal(t, "0004000b1e0f14121b090411031e121f0c08070716071e120f1016101b17080d1c1d0200", hex.EncodeToString(decoded)) 27 | } 28 | -------------------------------------------------------------------------------- /crypto/blake2b/blake2b.go: -------------------------------------------------------------------------------- 1 | package blake2b 2 | 3 | import ( 4 | "github.com/minio/blake2b-simd" 5 | ) 6 | 7 | var ckbHashPersonalization = []byte("ckb-default-hash") 8 | 9 | func Blake160(data []byte) []byte { 10 | return Blake256(data)[:20] 11 | } 12 | 13 | func Blake256(data []byte) []byte { 14 | config := &blake2b.Config{ 15 | Size: 32, 16 | Person: ckbHashPersonalization, 17 | } 18 | hash, _ := blake2b.New(config) 19 | hash.Write(data) 20 | return hash.Sum(nil) 21 | } 22 | -------------------------------------------------------------------------------- /crypto/key.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | // Key key pair 4 | type Key interface { 5 | Bytes() []byte 6 | Sign(data []byte) ([]byte, error) 7 | } 8 | -------------------------------------------------------------------------------- /crypto/secp256k1/key.go: -------------------------------------------------------------------------------- 1 | package secp256k1 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ecdsa" 6 | "crypto/elliptic" 7 | "crypto/rand" 8 | "encoding/hex" 9 | "errors" 10 | "fmt" 11 | "github.com/ethereum/go-ethereum/common/math" 12 | "github.com/ethereum/go-ethereum/crypto/secp256k1" 13 | "math/big" 14 | ) 15 | 16 | var ( 17 | secp256k1N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) 18 | secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) 19 | ) 20 | 21 | type Secp256k1Key struct { 22 | PrivateKey *ecdsa.PrivateKey 23 | } 24 | 25 | func (k *Secp256k1Key) Bytes() []byte { 26 | return math.PaddedBigBytes(k.PrivateKey.D, k.PrivateKey.Params().BitSize/8) 27 | } 28 | 29 | func (k *Secp256k1Key) Sign(data []byte) ([]byte, error) { 30 | seckey := k.Bytes() 31 | 32 | defer func(bytes []byte) { 33 | for i := range bytes { 34 | bytes[i] = 0 35 | } 36 | }(seckey) 37 | 38 | return secp256k1.Sign(data, seckey) 39 | } 40 | 41 | func (k *Secp256k1Key) PubKey() []byte { 42 | pub := &k.PrivateKey.PublicKey 43 | if pub == nil || pub.X == nil || pub.Y == nil { 44 | return nil 45 | } 46 | 47 | return secp256k1.CompressPubkey(pub.X, pub.Y) 48 | } 49 | 50 | func (k *Secp256k1Key) PubKeyUncompressed() []byte { 51 | pub := &k.PrivateKey.PublicKey 52 | if pub == nil || pub.X == nil || pub.Y == nil { 53 | return nil 54 | } 55 | return elliptic.Marshal(pub.Curve, pub.X, pub.Y) 56 | } 57 | 58 | func RandomNew() (*Secp256k1Key, error) { 59 | randBytes := make([]byte, 64) 60 | _, err := rand.Read(randBytes) 61 | if err != nil { 62 | return nil, errors.New("key generation: could not read from random source: " + err.Error()) 63 | } 64 | reader := bytes.NewReader(randBytes) 65 | priv, err := ecdsa.GenerateKey(secp256k1.S256(), reader) 66 | if err != nil { 67 | return nil, errors.New("key generation: ecdsa.GenerateKey failed: " + err.Error()) 68 | } 69 | 70 | return &Secp256k1Key{PrivateKey: priv}, nil 71 | } 72 | 73 | func HexToKey(hexKey string) (*Secp256k1Key, error) { 74 | if has0xPrefix(hexKey) { 75 | hexKey = hexKey[2:] 76 | } 77 | b, err := hex.DecodeString(hexKey) 78 | if err != nil { 79 | return nil, errors.New("invalid hex string") 80 | } 81 | return ToKey(b) 82 | } 83 | 84 | func has0xPrefix(input string) bool { 85 | return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') 86 | } 87 | 88 | func ToKey(d []byte) (*Secp256k1Key, error) { 89 | return toKey(d, true) 90 | } 91 | 92 | func toKey(d []byte, strict bool) (*Secp256k1Key, error) { 93 | priv := new(ecdsa.PrivateKey) 94 | priv.PublicKey.Curve = secp256k1.S256() 95 | if strict && 8*len(d) != priv.Params().BitSize { 96 | return nil, fmt.Errorf("invalid length, need %d bits", priv.Params().BitSize) 97 | } 98 | priv.D = new(big.Int).SetBytes(d) 99 | 100 | // The priv.D must < N 101 | if priv.D.Cmp(secp256k1N) >= 0 { 102 | return nil, errors.New("invalid private key, >=N") 103 | } 104 | // The priv.D must not be zero or negative. 105 | if priv.D.Sign() <= 0 { 106 | return nil, errors.New("invalid private key, zero or negative") 107 | } 108 | 109 | priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(d) 110 | if priv.PublicKey.X == nil { 111 | return nil, errors.New("invalid private key") 112 | } 113 | return &Secp256k1Key{PrivateKey: priv}, nil 114 | } 115 | -------------------------------------------------------------------------------- /crypto/secp256k1/key_test.go: -------------------------------------------------------------------------------- 1 | package secp256k1 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestPubKey(t *testing.T) { 10 | k, err := HexToKey("0xccb083b37aa346c5ce2e1f99a687a153baa04052f26db6ab3c26d6a4cc15c5f1") 11 | if err != nil { 12 | t.Error(err) 13 | } 14 | encoded := k.PubKeyUncompressed() 15 | assert.Equal(t, common.FromHex("0x04a0a7a7597b019828a1dda6ed52ab25181073ec3a9825d28b9abbb932fe1ec83dd117a8eef7649c25be5a591d08f80ffe7e9c14100ad1b58ac78afa606a576453"), encoded) 16 | encoded = k.PubKey() 17 | assert.Equal(t, common.FromHex("0x03a0a7a7597b019828a1dda6ed52ab25181073ec3a9825d28b9abbb932fe1ec83d"), encoded) 18 | } 19 | -------------------------------------------------------------------------------- /dao/helper.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "errors" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/rpc" 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 9 | "math/big" 10 | ) 11 | 12 | type DaoHelper struct { 13 | Client rpc.Client 14 | } 15 | 16 | type DaoDepositCellInfo struct { 17 | Outpoint types.OutPoint 18 | WithdrawBlockHash types.Hash 19 | WithdrawBlockNumber uint64 20 | DepositCapacity uint64 21 | Compensation uint64 22 | UnlockableEpoch types.EpochParams 23 | } 24 | 25 | // GetDaoDepositCellInfo Get information for DAO cell deposited as outpoint and withdrawn in block of withdrawBlockHash 26 | func (c *DaoHelper) GetDaoDepositCellInfo(outpoint *types.OutPoint, withdrawBlockHash types.Hash) (DaoDepositCellInfo, error) { 27 | blockHeader, err := c.Client.GetHeader(context.Background(), withdrawBlockHash, nil) 28 | if err != nil { 29 | return DaoDepositCellInfo{}, err 30 | } 31 | return c.getDaoDepositCellInfo(outpoint, blockHeader) 32 | } 33 | 34 | // GetDaoDepositCellInfoWithWithdrawOutpoint Get information for DAO cell deposited as outpoint and withdrawn in block where the withdrawCellOutPoint is 35 | func (c *DaoHelper) GetDaoDepositCellInfoWithWithdrawOutpoint(outpoint *types.OutPoint, withdrawCellOutPoint *types.OutPoint) (DaoDepositCellInfo, error) { 36 | withdrawTransaction, err := c.Client.GetTransaction(context.Background(), withdrawCellOutPoint.TxHash, nil, nil) 37 | if err != nil { 38 | return DaoDepositCellInfo{}, err 39 | } 40 | return c.GetDaoDepositCellInfo(outpoint, *withdrawTransaction.TxStatus.BlockHash) 41 | } 42 | 43 | // GetDaoDepositCellInfoByNow DAO information for DAO cell deposited as outpoint and withdrawn in tip block 44 | func (c *DaoHelper) GetDaoDepositCellInfoByNow(outpoint *types.OutPoint) (DaoDepositCellInfo, error) { 45 | tipBlockHeader, err := c.Client.GetTipHeader(context.Background()) 46 | if err != nil { 47 | return DaoDepositCellInfo{}, err 48 | } 49 | return c.getDaoDepositCellInfo(outpoint, tipBlockHeader) 50 | } 51 | 52 | // getDaoDepositCellInfo Get information for DAO cell deposited as outpoint and withdrawn in withdrawBlock 53 | func (c *DaoHelper) getDaoDepositCellInfo(outpoint *types.OutPoint, withdrawBlockHeader *types.Header) (DaoDepositCellInfo, error) { 54 | depositTransactionWithStatus, err := c.Client.GetTransaction(context.Background(), outpoint.TxHash, nil, nil) 55 | if err != nil { 56 | return DaoDepositCellInfo{}, err 57 | } 58 | depositBlockHeader, err := c.Client.GetHeader(context.Background(), *depositTransactionWithStatus.TxStatus.BlockHash, nil) 59 | if err != nil { 60 | return DaoDepositCellInfo{}, err 61 | } 62 | 63 | if int(outpoint.Index) >= len(depositTransactionWithStatus.Transaction.Outputs) { 64 | return DaoDepositCellInfo{}, errors.New("index out of range of outputs in deposit transaction") 65 | } 66 | outpointCell := depositTransactionWithStatus.Transaction.Outputs[outpoint.Index] 67 | outpointData := depositTransactionWithStatus.Transaction.OutputsData[outpoint.Index] 68 | occupiedCapacity := outpointCell.OccupiedCapacity(outpointData) 69 | totalCapacity := outpointCell.Capacity 70 | freeCapacity := new(big.Int).SetUint64(totalCapacity - occupiedCapacity) 71 | depositAr := new(big.Int).SetUint64(extractArFromDaoData(&depositBlockHeader.Dao)) 72 | withdrawAr := new(big.Int).SetUint64(extractArFromDaoData(&withdrawBlockHeader.Dao)) 73 | 74 | compensation := new(big.Int) 75 | compensation.Mul(freeCapacity, withdrawAr).Div(compensation, depositAr).Sub(compensation, freeCapacity) 76 | cellInfo := DaoDepositCellInfo{} 77 | cellInfo.Compensation = compensation.Uint64() 78 | cellInfo.DepositCapacity = totalCapacity 79 | 80 | withdrawEpochParams := types.ParseEpoch(withdrawBlockHeader.Epoch) 81 | depositEpochParams := types.ParseEpoch(depositBlockHeader.Epoch) 82 | // epochDistance = Ceil( (withdrawEpoch - depositEpoch ) / 180 ) * 180 83 | epochDistance := withdrawEpochParams.Number - depositEpochParams.Number 84 | if withdrawEpochParams.Index*depositEpochParams.Length > depositEpochParams.Index*withdrawEpochParams.Length { 85 | epochDistance += 1 86 | } 87 | epochDistance = (epochDistance + 179) / 180 * 180 88 | 89 | cellInfo.UnlockableEpoch = types.EpochParams{ 90 | Length: depositEpochParams.Length, 91 | Index: depositEpochParams.Index, 92 | Number: depositEpochParams.Number + epochDistance, 93 | } 94 | 95 | cellInfo.Outpoint = *outpoint 96 | cellInfo.WithdrawBlockHash = withdrawBlockHeader.Hash 97 | cellInfo.WithdrawBlockNumber = withdrawBlockHeader.Number 98 | 99 | return cellInfo, nil 100 | } 101 | 102 | func extractArFromDaoData(headerDao *types.Hash) uint64 { 103 | ar := headerDao[8:16] 104 | return binary.LittleEndian.Uint64(ar) 105 | } 106 | -------------------------------------------------------------------------------- /dao/helper_test.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/rpc" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | var client, _ = rpc.Dial("https://testnet.ckb.dev") 11 | var daoHelper = DaoHelper{Client: client} 12 | 13 | func TestGetDaoDepositCellInfo(t *testing.T) { 14 | // TODO: fix all deprecated RPC caused tests 15 | t.Skip("Skipping testing") 16 | outpoint := types.OutPoint{ 17 | TxHash: types.HexToHash("0x41bbccdf7015ea8458d7ef3499dc80cb2d3dc10cf48eb2b7f8f74468b24027fc"), 18 | Index: 0, 19 | } 20 | withdrawBlockHash := types.HexToHash("0xbaef9b22ee3d04d8fc3ad8c04f8403ad3b3b39c5ace51406c5305920976105f7") 21 | 22 | daoCellInfo, err := daoHelper.GetDaoDepositCellInfo(&outpoint, withdrawBlockHash) 23 | if err != nil { 24 | t.Error(err) 25 | } 26 | 27 | assert.Equal(t, outpoint, daoCellInfo.Outpoint) 28 | assert.Equal(t, withdrawBlockHash, daoCellInfo.WithdrawBlockHash) 29 | assert.Equal(t, uint64(2383851), daoCellInfo.Compensation) 30 | assert.Equal(t, uint64(11055500000), daoCellInfo.DepositCapacity) 31 | assert.Equal(t, uint64(648), daoCellInfo.UnlockableEpoch.Length) 32 | assert.Equal(t, uint64(609), daoCellInfo.UnlockableEpoch.Index) 33 | assert.Equal(t, uint64(271), daoCellInfo.UnlockableEpoch.Number) 34 | } 35 | 36 | func TestGetDaoDepositCellInfoWithWithdrawOutpoint(t *testing.T) { 37 | // TODO: fix all deprecated RPC caused tests 38 | t.Skip("Skipping testing") 39 | outpoint := types.OutPoint{ 40 | TxHash: types.HexToHash("0x41bbccdf7015ea8458d7ef3499dc80cb2d3dc10cf48eb2b7f8f74468b24027fc"), 41 | Index: 0, 42 | } 43 | 44 | withdrawOutpoint := types.OutPoint{ 45 | TxHash: types.HexToHash("0x88b071409d8bda119c1b4d613ccd78abbc01442566defcb7f745f6084c81adcb"), 46 | Index: 0, 47 | } 48 | 49 | daoCellInfo, err := daoHelper.GetDaoDepositCellInfoWithWithdrawOutpoint(&outpoint, &withdrawOutpoint) 50 | if err != nil { 51 | t.Error(err) 52 | } 53 | 54 | assert.Equal(t, outpoint, daoCellInfo.Outpoint) 55 | assert.Equal(t, uint64(2383851), daoCellInfo.Compensation) 56 | assert.Equal(t, uint64(11055500000), daoCellInfo.DepositCapacity) 57 | assert.Equal(t, uint64(648), daoCellInfo.UnlockableEpoch.Length) 58 | assert.Equal(t, uint64(609), daoCellInfo.UnlockableEpoch.Index) 59 | assert.Equal(t, uint64(271), daoCellInfo.UnlockableEpoch.Number) 60 | } 61 | 62 | func TestExtractArFromDaoData(t *testing.T) { 63 | daoData := types.HexToHash("8268d571c743a32ee1e547ea57872300989ceafa3e710000005d6a650b53ff06") 64 | ar := extractArFromDaoData(&daoData) 65 | assert.Equal(t, uint64(10000435847357921), ar) 66 | } 67 | -------------------------------------------------------------------------------- /docs/migration-guide.md: -------------------------------------------------------------------------------- 1 | We roll out a brand new ckb-sdk-go after refactor work. The new sdk brings plenty of BREAKING CHANGES incompatible with the deprecated sdk `v1.0.*` and earlier releases. 2 | 3 | The breaking changes include 4 | 5 | - Type or name change of quite a few fields in RPC type representation. 6 | - Unified address representation and operation. 7 | - Transaction signing mechanism by `ScriptGroup`, `ScriptSigner`, and `TransactionSigner`. 8 | - Clean some utils classes and unused classes. 9 | 10 | Other underlying breaking changes that are possibly transparent to you 11 | 12 | - More robust test. 13 | 14 | In the following part, we list the aspects you need to care about most if you are ready to migrate from deprecated sdk to the new one. 15 | 16 | ## Address 17 | 18 | The new sdk removes struct `ParsedAddress` and `AddressGenerateResult`, and uses a unified struct `Address` to represent ckb address. Below code is an example of address operations. 19 | 20 | Decode address 21 | 22 | ```go 23 | encoded := "ckt1qzda..." 24 | // Before 25 | parsedAddr, err := address.Parse(encoded) 26 | script := parsedAddr.Script 27 | network := parsedAddr.Mode 28 | 29 | // Now: 30 | addr, err := address.Decode(encoded) 31 | script := addr.Script 32 | network := addr.Network 33 | ``` 34 | 35 | Encode address from script and network 36 | 37 | ```go 38 | // Before: 39 | encoded, err := address.ConvertScriptToAddress(address.Testnet, script) 40 | 41 | // Now: 42 | addr := &address.Address{Script: script, Network: types.NetworkTest} 43 | encoded := addr.Encode() 44 | ``` 45 | 46 | ## Packages migration 47 | 48 | Some important packages moves 49 | 50 | - `github.com/nervosnetwork/ckb-sdk-go/mercury/model/*` -> `github.com/nervosnetwork/ckb-sdk-go/mercury/model` 51 | 52 | ```go 53 | // Before: 54 | import ( 55 | "github.com/nervosnetwork/ckb-sdk-go/mercury/model/common" 56 | "github.com/nervosnetwork/ckb-sdk-go/mercury/model/resp" 57 | ) 58 | 59 | // Now: 60 | import ( 61 | "github.com/nervosnetwork/ckb-sdk-go/mercury/model" 62 | ) 63 | ``` 64 | 65 | ## Remove builder 66 | 67 | For simplicity, we remove builder for Mercury JSON-RPC request struct. Here is the change to create a new Mercury JSON-RPC request. 68 | 69 | ```go 70 | // Before: 71 | payload := model.NewQueryTransactionsPayloadBuilder(). 72 | SetItem(item). 73 | AddAssetInfo(common.NewUdtAsset(constant.UDT_HASH)). 74 | SetExtra(&extra). 75 | AddBlockRange(&model.BlockRange{ 76 | From: 0, 77 | To: 4592529, 78 | }).Build() 79 | payload.StructureType = model.DoubleEntry 80 | 81 | // Now: 82 | payload := model.QueryTransactionsPayload{ 83 | Item: item, 84 | AssetInfos: []*model.AssetInfo{model.NewUdtAsset(constant.UDT_HASH)}, 85 | Extra: &extra, 86 | BlockRange: &model.BlockRange{ 87 | From: 0, 88 | To: 4592529, 89 | }, 90 | StructureType: model.StructureTypeDoubleEntry, 91 | } 92 | ``` 93 | 94 | ## Sign transaction 95 | 96 | The new go sdk introduces `ScriptGroup` for signing transactions. You can get the `ScriptGroup` with a raw transaction from mercury, or construct it by yourself. After that `TransactionSigner` can sign in the correct place as long as you provide the right secret information (e.g. private key). 97 | 98 | ```go 99 | // Before: 100 | addressWithKeys := make(map[string]string) 101 | // omit the code to put private key to `addressWithKeys` 102 | txWithGroup, err := mercuryClient.BuildSimpleTransferTransaction(req) 103 | scriptGroups := txWithGroup.GetScriptGroup() 104 | tx := txWithGroup.GetTransaction() 105 | for _, group := range scriptGroups { 106 | key, _ := secp256k1.HexToKey(addressWithKeys[group.GetAddress()]) 107 | resp.SignTransaction(tx, group, key) 108 | } 109 | txHash, err := ckbClient.SendTransaction(context.Background(), txWithScriptGroup.TxView) 110 | 111 | // Now: 112 | txWithScriptGroups, err := mercuryClient.BuildSimpleTransferTransaction(req) 113 | txSigner := signer.GetTransactionSignerInstance(types.NetworkTest) 114 | txSigner.SignTransaction(txWithScriptGroup, "0x6c9ed03816e31...") 115 | txHash, err := ckbClient.SendTransaction(context.Background(), txWithScriptGroup.TxView) 116 | ``` 117 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nervosnetwork/ckb-sdk-go/v2 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/ethereum/go-ethereum v1.9.14 7 | github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 8 | github.com/pkg/errors v0.8.1 9 | github.com/stretchr/testify v1.4.0 10 | ) 11 | 12 | require ( 13 | github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect 14 | github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 // indirect 15 | github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect 18 | github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa // indirect 19 | github.com/go-ole/go-ole v1.2.1 // indirect 20 | github.com/go-stack/stack v1.8.0 // indirect 21 | github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/stretchr/objx v0.1.0 // indirect 24 | golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect 25 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect 26 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect 27 | gopkg.in/yaml.v2 v2.2.2 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /indexer/indexer.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "context" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | "github.com/ethereum/go-ethereum/rpc" 7 | ) 8 | 9 | const SearchLimit uint64 = 1000 10 | 11 | type Client interface { 12 | // GetCells returns the live cells collection by the lock or type script. 13 | GetCells(ctx context.Context, searchKey *SearchKey, order SearchOrder, limit uint32, afterCursor string) (*LiveCells, error) 14 | 15 | // GetTransactions returns the transactions collection by the lock or type script. 16 | GetTransactions(ctx context.Context, searchKey *SearchKey, order SearchOrder, limit uint32, afterCursor string) (*TxsWithCell, error) 17 | 18 | // GetTransactionsGrouped returns the grouped transactions collection by the lock or type script. 19 | GetTransactionsGrouped(ctx context.Context, searchKey *SearchKey, order SearchOrder, limit uint32, afterCursor string) (*TxsWithCells, error) 20 | 21 | //GetTip returns the latest height processed by indexer 22 | GetTip(ctx context.Context) (*TipHeader, error) 23 | 24 | //GetCellsCapacity returns the live cells capacity by the lock or type script. 25 | GetCellsCapacity(ctx context.Context, searchKey *SearchKey) (*Capacity, error) 26 | 27 | // Close close client 28 | Close() 29 | } 30 | 31 | type client struct { 32 | c *rpc.Client 33 | } 34 | 35 | func Dial(url string) (Client, error) { 36 | return DialContext(context.Background(), url) 37 | } 38 | 39 | func DialContext(ctx context.Context, url string) (Client, error) { 40 | c, err := rpc.DialContext(ctx, url) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return NewClient(c), nil 45 | } 46 | 47 | func NewClient(c *rpc.Client) Client { 48 | return &client{c} 49 | } 50 | 51 | func (cli *client) Close() { 52 | cli.c.Close() 53 | } 54 | 55 | func (cli *client) GetCells(ctx context.Context, searchKey *SearchKey, order SearchOrder, limit uint32, afterCursor string) (*LiveCells, error) { 56 | var result LiveCells 57 | var err error 58 | if afterCursor == "" { 59 | err = cli.c.CallContext(ctx, &result, "get_cells", searchKey, order, hexutil.Uint(limit)) 60 | } else { 61 | err = cli.c.CallContext(ctx, &result, "get_cells", searchKey, order, hexutil.Uint(limit), afterCursor) 62 | } 63 | if err != nil { 64 | return nil, err 65 | } 66 | return &result, err 67 | } 68 | 69 | func (cli *client) GetTransactions(ctx context.Context, searchKey *SearchKey, order SearchOrder, limit uint32, afterCursor string) (*TxsWithCell, error) { 70 | var result TxsWithCell 71 | var err error 72 | if afterCursor == "" { 73 | err = cli.c.CallContext(ctx, &result, "get_transactions", searchKey, order, hexutil.Uint(limit)) 74 | } else { 75 | err = cli.c.CallContext(ctx, &result, "get_transactions", searchKey, order, hexutil.Uint(limit), afterCursor) 76 | } 77 | if err != nil { 78 | return nil, err 79 | } 80 | return &result, err 81 | } 82 | 83 | func (cli *client) GetTransactionsGrouped(ctx context.Context, searchKey *SearchKey, order SearchOrder, limit uint32, afterCursor string) (*TxsWithCells, error) { 84 | payload := &struct { 85 | SearchKey 86 | GroupByTransaction bool `json:"group_by_transaction"` 87 | }{ 88 | SearchKey: *searchKey, 89 | GroupByTransaction: true, 90 | } 91 | var result TxsWithCells 92 | var err error 93 | if afterCursor == "" { 94 | err = cli.c.CallContext(ctx, &result, "get_transactions", payload, order, hexutil.Uint(limit)) 95 | } else { 96 | err = cli.c.CallContext(ctx, &result, "get_transactions", payload, order, hexutil.Uint(limit), afterCursor) 97 | } 98 | if err != nil { 99 | return nil, err 100 | } 101 | return &result, err 102 | } 103 | 104 | func (cli *client) GetTip(ctx context.Context) (*TipHeader, error) { 105 | var result TipHeader 106 | err := cli.c.CallContext(ctx, &result, "get_tip") 107 | if err != nil { 108 | return nil, err 109 | } 110 | return &result, nil 111 | } 112 | 113 | func (cli *client) GetCellsCapacity(ctx context.Context, searchKey *SearchKey) (*Capacity, error) { 114 | var result Capacity 115 | err := cli.c.CallContext(ctx, &result, "get_cells_capacity", searchKey) 116 | if err != nil { 117 | return nil, err 118 | } 119 | return &result, nil 120 | } 121 | -------------------------------------------------------------------------------- /indexer/json.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | ) 8 | 9 | type jsonCellsFilter struct { 10 | Script *types.Script `json:"script"` 11 | ScriptLenRange *[2]hexutil.Uint64 `json:"script_len_range,omitempty"` 12 | OutputDataLenRange *[2]hexutil.Uint64 `json:"output_data_len_range,omitempty"` 13 | OutputCapacityRange *[2]hexutil.Uint64 `json:"output_capacity_range,omitempty"` 14 | BlockRange *[2]hexutil.Uint64 `json:"block_range,omitempty"` 15 | } 16 | 17 | func (r Filter) MarshalJSON() ([]byte, error) { 18 | toUint64Array := func(a *[2]uint64) *[2]hexutil.Uint64 { 19 | if a == nil { 20 | return nil 21 | } else { 22 | result := [2]hexutil.Uint64{} 23 | result[0] = hexutil.Uint64(a[0]) 24 | result[1] = hexutil.Uint64(a[1]) 25 | return &result 26 | } 27 | } 28 | jsonObj := &jsonCellsFilter{ 29 | Script: r.Script, 30 | ScriptLenRange: toUint64Array(r.ScriptLenRange), 31 | OutputDataLenRange: toUint64Array(r.OutputDataLenRange), 32 | OutputCapacityRange: toUint64Array(r.OutputCapacityRange), 33 | BlockRange: toUint64Array(r.BlockRange), 34 | } 35 | return json.Marshal(jsonObj) 36 | } 37 | 38 | type liveCellAlias LiveCell 39 | type jsonLiveCell struct { 40 | liveCellAlias 41 | BlockNumber hexutil.Uint64 `json:"block_number"` 42 | OutputData *hexutil.Bytes `json:"output_data"` 43 | TxIndex hexutil.Uint `json:"tx_index"` 44 | } 45 | 46 | func (r *LiveCell) UnmarshalJSON(input []byte) error { 47 | var jsonObj jsonLiveCell 48 | if err := json.Unmarshal(input, &jsonObj); err != nil { 49 | return err 50 | } 51 | *r = LiveCell{ 52 | BlockNumber: uint64(jsonObj.BlockNumber), 53 | OutPoint: jsonObj.OutPoint, 54 | Output: jsonObj.Output, 55 | TxIndex: uint(jsonObj.TxIndex), 56 | } 57 | if jsonObj.OutputData != nil { 58 | r.OutputData = *jsonObj.OutputData 59 | } 60 | return nil 61 | } 62 | 63 | type jsonTxWithCell struct { 64 | BlockNumber hexutil.Uint64 `json:"block_number"` 65 | IoIndex hexutil.Uint `json:"io_index"` 66 | IoType IoType `json:"io_type"` 67 | TxHash types.Hash `json:"tx_hash"` 68 | TxIndex hexutil.Uint `json:"tx_index"` 69 | } 70 | 71 | func (r *TxWithCell) UnmarshalJSON(input []byte) error { 72 | var jsonObj jsonTxWithCell 73 | if err := json.Unmarshal(input, &jsonObj); err != nil { 74 | return err 75 | } 76 | *r = TxWithCell{ 77 | BlockNumber: uint64(jsonObj.BlockNumber), 78 | IoIndex: uint(jsonObj.IoIndex), 79 | IoType: jsonObj.IoType, 80 | TxHash: jsonObj.TxHash, 81 | TxIndex: uint(jsonObj.TxIndex), 82 | } 83 | return nil 84 | } 85 | 86 | type jsonTxWithCells struct { 87 | TxHash types.Hash `json:"tx_hash"` 88 | BlockNumber hexutil.Uint64 `json:"block_number"` 89 | TxIndex hexutil.Uint `json:"tx_index"` 90 | Cells []*Cell `json:"Cells"` 91 | } 92 | 93 | func (r *TxWithCells) UnmarshalJSON(input []byte) error { 94 | var jsonObj jsonTxWithCells 95 | if err := json.Unmarshal(input, &jsonObj); err != nil { 96 | return err 97 | } 98 | *r = TxWithCells{ 99 | BlockNumber: uint64(jsonObj.BlockNumber), 100 | TxHash: jsonObj.TxHash, 101 | TxIndex: uint(jsonObj.TxIndex), 102 | Cells: jsonObj.Cells, 103 | } 104 | return nil 105 | } 106 | 107 | func (r *Cell) UnmarshalJSON(input []byte) error { 108 | var jsonObj struct { 109 | IoType IoType `json:"io_type"` 110 | IoIndex hexutil.Uint `json:"io_index"` 111 | } 112 | tmp := []interface{}{&jsonObj.IoType, &jsonObj.IoIndex} 113 | if err := json.Unmarshal(input, &tmp); err != nil { 114 | return err 115 | } 116 | *r = Cell{ 117 | IoType: jsonObj.IoType, 118 | IoIndex: uint(jsonObj.IoIndex), 119 | } 120 | return nil 121 | } 122 | 123 | func (r *Capacity) UnmarshalJSON(input []byte) error { 124 | var jsonObj struct { 125 | Capacity hexutil.Uint64 `json:"capacity"` 126 | BlockHash types.Hash `json:"block_hash"` 127 | BlockNumber hexutil.Uint64 `json:"block_number"` 128 | } 129 | if err := json.Unmarshal(input, &jsonObj); err != nil { 130 | return err 131 | } 132 | *r = Capacity{ 133 | Capacity: uint64(jsonObj.Capacity), 134 | BlockHash: jsonObj.BlockHash, 135 | BlockNumber: uint64(jsonObj.BlockNumber), 136 | } 137 | return nil 138 | } 139 | 140 | func (r *TipHeader) UnmarshalJSON(input []byte) error { 141 | var jsonObj struct { 142 | BlockHash types.Hash `json:"block_hash"` 143 | BlockNumber hexutil.Uint64 `json:"block_number"` 144 | } 145 | if err := json.Unmarshal(input, &jsonObj); err != nil { 146 | return err 147 | } 148 | *r = TipHeader{ 149 | BlockHash: jsonObj.BlockHash, 150 | BlockNumber: uint64(jsonObj.BlockNumber), 151 | } 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /indexer/json_test.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "encoding/json" 5 | ethcommon "github.com/ethereum/go-ethereum/common" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestJsonLiveCell(t *testing.T) { 12 | jsonText := []byte(` 13 | { 14 | "block_number": "0x55e6c8", 15 | "out_point": { 16 | "index": "0x0", 17 | "tx_hash": "0x287554d155a9b9e30a1a6fd9e5d9e41afee612b0c8996f0073afb7f2894025f9" 18 | }, 19 | "output": { 20 | "capacity": "0xba43b7400", 21 | "lock": { 22 | "args": "0x4049ed9cec8a0d39c7a1e899f0dacb8a8c28ad14", 23 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 24 | "hash_type": "type" 25 | }, 26 | "type": { 27 | "args": "0x", 28 | "code_hash": "0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e", 29 | "hash_type": "type" 30 | } 31 | }, 32 | "output_data": "0x0fe5550000000000", 33 | "tx_index": "0x1" 34 | }`) 35 | var v LiveCell 36 | json.Unmarshal(jsonText, &v) 37 | assert.Equal(t, uint64(0x55e6c8), v.BlockNumber) 38 | assert.Equal(t, ethcommon.FromHex("0x0fe5550000000000"), v.OutputData) 39 | assert.Equal(t, uint(0x1), v.TxIndex) 40 | assert.NotNil(t, v.OutPoint) 41 | assert.NotNil(t, v.Output) 42 | } 43 | 44 | func TestJsonTransaction(t *testing.T) { 45 | jsonText := []byte(` 46 | { 47 | "block_number": "0x529381", 48 | "io_index": "0x0", 49 | "io_type": "output", 50 | "tx_hash": "0xf9f01917312da067c235f790ba2d316cae884ce94f0131d7a3aee649dc1001c6", 51 | "tx_index": "0x8" 52 | }`) 53 | var v TxWithCell 54 | json.Unmarshal(jsonText, &v) 55 | assert.Equal(t, uint64(0x529381), v.BlockNumber) 56 | assert.Equal(t, uint(0x0), v.IoIndex) 57 | assert.Equal(t, IOTypeOut, v.IoType) 58 | assert.Equal(t, types.HexToHash("0xf9f01917312da067c235f790ba2d316cae884ce94f0131d7a3aee649dc1001c6"), v.TxHash) 59 | assert.Equal(t, uint(0x8), v.TxIndex) 60 | } 61 | -------------------------------------------------------------------------------- /indexer/types.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | ) 8 | 9 | type SearchOrder string 10 | type IoType string 11 | 12 | const ( 13 | SearchOrderAsc SearchOrder = "asc" 14 | SearchOrderDesc SearchOrder = "desc" 15 | 16 | IOTypeIn IoType = "input" 17 | IOTypeOut IoType = "output" 18 | ) 19 | 20 | type SearchKey struct { 21 | Script *types.Script `json:"script"` 22 | ScriptType types.ScriptType `json:"script_type"` 23 | ScriptSearchMode types.ScriptSearchMode `json:"script_search_mode,omitempty"` 24 | Filter *Filter `json:"filter,omitempty"` 25 | WithData bool `json:"with_data"` 26 | GroupByTransaction *bool `json:"group_by_transaction,omitempty"` 27 | } 28 | 29 | type Filter struct { 30 | Script *types.Script `json:"script"` 31 | ScriptLenRange *[2]uint64 `json:"script_len_range,omitempty"` 32 | OutputData *json.RawMessage `json:"output_data,omitempty"` 33 | OutputDataFilterMode *types.ScriptSearchMode `json:"output_data_filter_mode,omitempty"` 34 | OutputDataLenRange *[2]uint64 `json:"output_data_len_range,omitempty"` 35 | OutputCapacityRange *[2]uint64 `json:"output_capacity_range,omitempty"` 36 | BlockRange *[2]uint64 `json:"block_range,omitempty"` 37 | } 38 | 39 | type LiveCell struct { 40 | BlockNumber uint64 `json:"block_number"` 41 | OutPoint *types.OutPoint `json:"out_point"` 42 | Output *types.CellOutput `json:"output"` 43 | OutputData []byte `json:"output_data"` 44 | TxIndex uint `json:"tx_index"` 45 | } 46 | 47 | type LiveCells struct { 48 | LastCursor string `json:"last_cursor"` 49 | Objects []*LiveCell `json:"objects"` 50 | } 51 | 52 | type TxWithCell struct { 53 | BlockNumber uint64 `json:"block_number"` 54 | IoIndex uint `json:"io_index"` 55 | IoType IoType `json:"io_type"` 56 | TxHash types.Hash `json:"tx_hash"` 57 | TxIndex uint `json:"tx_index"` 58 | } 59 | 60 | type TxWithCells struct { 61 | TxHash types.Hash `json:"tx_hash"` 62 | BlockNumber uint64 `json:"block_number"` 63 | TxIndex uint `json:"tx_index"` 64 | Cells []*Cell `json:"Cells"` 65 | } 66 | 67 | type Cell struct { 68 | IoType IoType `json:"io_type"` 69 | IoIndex uint `json:"io_index"` 70 | } 71 | 72 | type TxsWithCell struct { 73 | LastCursor string `json:"last_cursor"` 74 | Objects []*TxWithCell `json:"objects"` 75 | } 76 | 77 | type TxsWithCells struct { 78 | LastCursor string `json:"last_cursor"` 79 | Objects []*TxWithCells `json:"objects"` 80 | } 81 | 82 | type TipHeader struct { 83 | BlockHash types.Hash `json:"block_hash"` 84 | BlockNumber uint64 `json:"block_number"` 85 | } 86 | 87 | type Capacity struct { 88 | Capacity uint64 `json:"capacity"` 89 | BlockHash types.Hash `json:"block_hash"` 90 | BlockNumber uint64 `json:"block_number"` 91 | } 92 | -------------------------------------------------------------------------------- /lightclient/json.go: -------------------------------------------------------------------------------- 1 | package lightclient 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/indexer" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 8 | ) 9 | 10 | type scriptDetailAlias ScriptDetail 11 | type jsonScriptDetail struct { 12 | scriptDetailAlias 13 | BlockNumber hexutil.Uint64 `json:"block_number"` 14 | } 15 | 16 | func (r *ScriptDetail) UnmarshalJSON(input []byte) error { 17 | var jsonObj jsonScriptDetail 18 | if err := json.Unmarshal(input, &jsonObj); err != nil { 19 | return err 20 | } 21 | *r = ScriptDetail{ 22 | Script: jsonObj.Script, 23 | ScriptType: jsonObj.ScriptType, 24 | BlockNumber: uint64(jsonObj.BlockNumber), 25 | } 26 | return nil 27 | } 28 | 29 | func (r ScriptDetail) MarshalJSON() ([]byte, error) { 30 | jsonObj := &jsonScriptDetail{ 31 | scriptDetailAlias: scriptDetailAlias(r), 32 | BlockNumber: hexutil.Uint64(r.BlockNumber), 33 | } 34 | return json.Marshal(jsonObj) 35 | } 36 | 37 | func (r *FetchedHeader) UnmarshalJSON(input []byte) error { 38 | var jsonObj struct { 39 | Status FetchStatus `json:"status"` 40 | Data *types.Header `json:"data"` 41 | FirstSent hexutil.Uint64 `json:"first_sent"` 42 | TimeStamp hexutil.Uint64 `json:"time_stamp"` 43 | } 44 | if err := json.Unmarshal(input, &jsonObj); err != nil { 45 | return err 46 | } 47 | *r = FetchedHeader{ 48 | Status: jsonObj.Status, 49 | Data: jsonObj.Data, 50 | FirstSent: uint64(jsonObj.FirstSent), 51 | TimeStamp: uint64(jsonObj.TimeStamp), 52 | } 53 | return nil 54 | } 55 | 56 | func (r *FetchedTransaction) UnmarshalJSON(input []byte) error { 57 | var jsonObj struct { 58 | Status FetchStatus `json:"status"` 59 | Data *TransactionWithHeader `json:"data"` 60 | FirstSent hexutil.Uint64 `json:"first_sent"` 61 | TimeStamp hexutil.Uint64 `json:"time_stamp"` 62 | } 63 | if err := json.Unmarshal(input, &jsonObj); err != nil { 64 | return err 65 | } 66 | *r = FetchedTransaction{ 67 | Status: jsonObj.Status, 68 | Data: jsonObj.Data, 69 | FirstSent: uint64(jsonObj.FirstSent), 70 | TimeStamp: uint64(jsonObj.TimeStamp), 71 | } 72 | return nil 73 | } 74 | 75 | type jsonTxWithCell struct { 76 | BlockNumber hexutil.Uint64 `json:"block_number"` 77 | IoIndex hexutil.Uint `json:"io_index"` 78 | IoType indexer.IoType `json:"io_type"` 79 | Transaction *types.Transaction `json:"transaction"` 80 | TxIndex hexutil.Uint `json:"tx_index"` 81 | } 82 | 83 | func (r *TxWithCell) UnmarshalJSON(input []byte) error { 84 | var jsonObj jsonTxWithCell 85 | if err := json.Unmarshal(input, &jsonObj); err != nil { 86 | return err 87 | } 88 | *r = TxWithCell{ 89 | BlockNumber: uint64(jsonObj.BlockNumber), 90 | IoIndex: uint(jsonObj.IoIndex), 91 | IoType: jsonObj.IoType, 92 | Transaction: jsonObj.Transaction, 93 | TxIndex: uint(jsonObj.TxIndex), 94 | } 95 | return nil 96 | } 97 | 98 | type jsonTxWithCells struct { 99 | Transaction *types.Transaction `json:"transaction"` 100 | BlockNumber hexutil.Uint64 `json:"block_number"` 101 | TxIndex hexutil.Uint `json:"tx_index"` 102 | Cells []*indexer.Cell `json:"Cells"` 103 | } 104 | 105 | func (r *TxWithCells) UnmarshalJSON(input []byte) error { 106 | var jsonObj jsonTxWithCells 107 | if err := json.Unmarshal(input, &jsonObj); err != nil { 108 | return err 109 | } 110 | *r = TxWithCells{ 111 | BlockNumber: uint64(jsonObj.BlockNumber), 112 | Transaction: jsonObj.Transaction, 113 | TxIndex: uint(jsonObj.TxIndex), 114 | Cells: jsonObj.Cells, 115 | } 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /lightclient/mocking/fetch_header/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "fetch_header", 5 | "params": [ 6 | "0xcb5eae958e3ea24b0486a393133aa33d51224ffaab3c4819350095b3446e4f70" 7 | ] 8 | } -------------------------------------------------------------------------------- /lightclient/mocking/fetch_header/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "result": { 4 | "data": { 5 | "compact_target": "0x1d089a37", 6 | "dao": "0x4733797533e3aa40cc8ca80d5875260064dc032439a2c80300cf8a4d48dc4908", 7 | "epoch": "0x708031400140e", 8 | "extra_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", 9 | "hash": "0xcb5eae958e3ea24b0486a393133aa33d51224ffaab3c4819350095b3446e4f70", 10 | "nonce": "0x66777ad1f3701cd9ea0da850bb8d053e", 11 | "number": "0x67fe27", 12 | "parent_hash": "0xa553fa130713daa3bd0c4f0417b3172c4a7bede472c8bae8942c4dfa81880ae9", 13 | "proposals_hash": "0x5f9691f9bb93a437be500a0996f3c7675f8f60397a5f7f140d2833d1ef72741d", 14 | "timestamp": "0x18378c166cc", 15 | "transactions_root": "0x768185c77aafa12852e82060d04a4275d3b6b9cf13b5fcf6ae95cff9eb14e46b", 16 | "version": "0x0" 17 | }, 18 | "status": "fetched" 19 | }, 20 | "id": 1 21 | } 22 | -------------------------------------------------------------------------------- /lightclient/mocking/fetch_transaction/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "fetch_transaction", 5 | "params": [ 6 | "0x716e211698d3d9499aae7903867c744b67b539beeceddad330e73d1b6b617aef" 7 | ] 8 | } -------------------------------------------------------------------------------- /lightclient/mocking/fetch_transaction/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "result": { 4 | "data": { 5 | "cycles": null, 6 | "transaction": { 7 | "cell_deps": [ 8 | { 9 | "dep_type": "dep_group", 10 | "out_point": { 11 | "index": "0x0", 12 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37" 13 | } 14 | } 15 | ], 16 | "hash": "0x716e211698d3d9499aae7903867c744b67b539beeceddad330e73d1b6b617aef", 17 | "header_deps": [], 18 | "inputs": [ 19 | { 20 | "previous_output": { 21 | "index": "0x1", 22 | "tx_hash": "0xcb95b235fb087aeace8e39eedf3314c9f40f5d915f55dddf6362025d62059f13" 23 | }, 24 | "since": "0x0" 25 | } 26 | ], 27 | "outputs": [ 28 | { 29 | "capacity": "0xe8d4a51000", 30 | "lock": { 31 | "args": "0x01698fa856ee262f4bc15e57f09c771dd2ffed546800", 32 | "code_hash": "0x79f90bb5e892d80dd213439eeab551120eb417678824f282b4ffb5f21bad2e1e", 33 | "hash_type": "type" 34 | }, 35 | "type": null 36 | }, 37 | { 38 | "capacity": "0x1197344e6ddea7f", 39 | "lock": { 40 | "args": "0x3f1573b44218d4c12a91919a58a863be415a2bc3", 41 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 42 | "hash_type": "type" 43 | }, 44 | "type": null 45 | } 46 | ], 47 | "outputs_data": [ 48 | "0x", 49 | "0x" 50 | ], 51 | "version": "0x0", 52 | "witnesses": [ 53 | "0x5500000010000000550000005500000041000000b9f7b7816f4d1c47e1712a81a27abffdea320279399ab285fffcb12d4a09386051066f12f5a0f007f42d5b2b4cae1a19443a5f70f557a4d9cd72713744509d1401" 54 | ] 55 | }, 56 | "tx_status": { 57 | "block_hash": "0x1a7e1296fed9e5c02ee42a32d55e392c89a354f4c17213b68784deeb296dabe1", 58 | "status": "committed" 59 | } 60 | }, 61 | "status": "fetched" 62 | }, 63 | "id": 1 64 | } 65 | -------------------------------------------------------------------------------- /lightclient/mocking/get_cells/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_cells", 5 | "params": [ 6 | { 7 | "script": { 8 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 9 | "hash_type": "type", 10 | "args": "0x4049ed9cec8a0d39c7a1e899f0dacb8a8c28ad14" 11 | }, 12 | "script_type": "lock", 13 | "with_data": false 14 | }, 15 | "asc", 16 | "0xa" 17 | ] 18 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_cells_capacity/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_cells_capacity", 5 | "params": [ 6 | { 7 | "script": { 8 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 9 | "hash_type": "type", 10 | "args": "0x4049ed9cec8a0d39c7a1e899f0dacb8a8c28ad14" 11 | }, 12 | "script_type": "lock", 13 | "with_data": false 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_cells_capacity/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "result": { 4 | "block_hash": "0x5ccb59e1b5779dfa7584a31aa6c751a6de66848a94348355ba29d5364e0108fe", 5 | "block_number": "0x7f2731", 6 | "capacity": "0x1628df512da" 7 | }, 8 | "id": 1 9 | } 10 | -------------------------------------------------------------------------------- /lightclient/mocking/get_genesis_block/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_genesis_block" 5 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_header/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_header", 5 | "params": [ 6 | "0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606" 7 | ] 8 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_header/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "result": { 4 | "compact_target": "0x1e015555", 5 | "dao": "0x0469b82c6c1ea12e0000c16ff286230066cbed490e00000000b2b49f02fbfe06", 6 | "epoch": "0x0", 7 | "extra_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", 8 | "hash": "0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606", 9 | "nonce": "0x0", 10 | "number": "0x0", 11 | "parent_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", 12 | "proposals_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", 13 | "timestamp": "0x172083ec170", 14 | "transactions_root": "0x00e5d0a4869bc21533d7487ee2377b514245bdfca3ac30ba0710e608011760f6", 15 | "version": "0x0" 16 | }, 17 | "id": 1 18 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_peers/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_peers" 5 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_scripts/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_scripts" 5 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_scripts/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc":"2.0", 3 | "result":[ 4 | { 5 | "block_number":"0x52d708", 6 | "script":{ 7 | "args":"0x50878ce52a68feb47237c29574d82288f58b5d21", 8 | "code_hash":"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 9 | "hash_type":"type" 10 | }, 11 | "script_type":"lock" 12 | } 13 | ], 14 | "id":1 15 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_tip_header/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_tip_header" 5 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_tip_header/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc":"2.0", 3 | "result":{ 4 | "compact_target":"0x1d07b1b0", 5 | "dao":"0xaa6430cdd1a19c4359d8fce62fe02600cee6d30d5332680400b94a81f65a8008", 6 | "epoch":"0x70803a2001754", 7 | "extra_hash":"0xa3c05306bf1bfcef5656162e0a9e092f08ecdb85822aedef80d0a1c0db876be1", 8 | "hash":"0x88228d800a344b01a61da0635ed875716e6c6dde480a17cc51dfbc050cf41d58", 9 | "nonce":"0x3a39c2dadd53c04d4a5b76bfb862463a", 10 | "number":"0x7f02e5", 11 | "parent_hash":"0x89ae187fc3ef349a3ac5bcbb92360336cd8558d361990bc9f2aefa46ce16f7a5", 12 | "proposals_hash":"0x43659ce29638aa3353dcaa68485fb11cc530d22b7711de7f3f6df48a9c19430e", 13 | "timestamp":"0x186489a0cc0", 14 | "transactions_root":"0xc0b71e58d55f3e38717991f99d1c1c14daa2b2ce64140a76a218507f8eb5c1b7", 15 | "version":"0x0" 16 | }, 17 | "id":1 18 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_transaction/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_transaction", 5 | "params": [ 6 | "0x8f8c79eb6671709633fe6a46de93c0fedc9c1b8a6527a18d3983879542635c9f" 7 | ] 8 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_transactions/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_transactions", 5 | "params": [ 6 | { 7 | "script": { 8 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 9 | "hash_type": "type", 10 | "args": "0x4049ed9cec8a0d39c7a1e899f0dacb8a8c28ad14" 11 | }, 12 | "script_type": "lock", 13 | "with_data": false 14 | }, 15 | "asc", 16 | "0xa" 17 | ] 18 | } -------------------------------------------------------------------------------- /lightclient/mocking/get_transactions_grouped/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "get_transactions", 5 | "params": [ 6 | { 7 | "script": { 8 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 9 | "hash_type": "type", 10 | "args": "0x4049ed9cec8a0d39c7a1e899f0dacb8a8c28ad14" 11 | }, 12 | "script_type": "lock", 13 | "with_data": false, 14 | "group_by_transaction": true 15 | }, 16 | "asc", 17 | "0xa" 18 | ] 19 | } -------------------------------------------------------------------------------- /lightclient/mocking/local_node_info/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "local_node_info" 5 | } -------------------------------------------------------------------------------- /lightclient/mocking/local_node_info/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "result": { 4 | "active": true, 5 | "addresses": [ 6 | { 7 | "address": "/ip4/0.0.0.0/tcp/8118", 8 | "score": "0x1" 9 | } 10 | ], 11 | "connections": "0x4", 12 | "node_id": "QmPhgeb9wnBZ9Dcc2cNsJUAB24mE9DeJZN1adhwqTr3Agp", 13 | "protocols": [ 14 | { 15 | "id": "0x64", 16 | "name": "/ckb/syn", 17 | "support_versions": [ 18 | "2" 19 | ] 20 | }, 21 | { 22 | "id": "0x65", 23 | "name": "/ckb/relay", 24 | "support_versions": [ 25 | "2" 26 | ] 27 | }, 28 | { 29 | "id": "0x78", 30 | "name": "/ckb/lightclient", 31 | "support_versions": [ 32 | "2" 33 | ] 34 | }, 35 | { 36 | "id": "0x79", 37 | "name": "/ckb/filter", 38 | "support_versions": [ 39 | "2" 40 | ] 41 | }, 42 | { 43 | "id": "0x2", 44 | "name": "/ckb/identify", 45 | "support_versions": [ 46 | "2" 47 | ] 48 | }, 49 | { 50 | "id": "0x0", 51 | "name": "/ckb/ping", 52 | "support_versions": [ 53 | "2" 54 | ] 55 | }, 56 | { 57 | "id": "0x1", 58 | "name": "/ckb/discovery", 59 | "support_versions": [ 60 | "2", 61 | "2.1" 62 | ] 63 | }, 64 | { 65 | "id": "0x3", 66 | "name": "/ckb/flr", 67 | "support_versions": [ 68 | "2" 69 | ] 70 | }, 71 | { 72 | "id": "0x4", 73 | "name": "/ckb/disconnectmsg", 74 | "support_versions": [ 75 | "2" 76 | ] 77 | } 78 | ], 79 | "version": "0.2.1" 80 | }, 81 | "id": 1 82 | } 83 | -------------------------------------------------------------------------------- /lightclient/mocking/set_scripts/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": 1, 4 | "method": "set_scripts", 5 | "params": [ 6 | [ 7 | { 8 | "script": { 9 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 10 | "hash_type": "type", 11 | "args": "0x4049ed9cec8a0d39c7a1e899f0dacb8a8c28ad14" 12 | }, 13 | "script_type": "lock", 14 | "block_number": "0x6b510c" 15 | } 16 | ] 17 | ] 18 | } -------------------------------------------------------------------------------- /lightclient/mocking/set_scripts/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "result": null, 4 | "id": 1 5 | } -------------------------------------------------------------------------------- /lightclient/type.go: -------------------------------------------------------------------------------- 1 | package lightclient 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/indexer" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 6 | ) 7 | 8 | type ScriptDetail struct { 9 | Script *types.Script `json:"script"` 10 | ScriptType types.ScriptType `json:"script_type"` 11 | BlockNumber uint64 `json:"block_number"` 12 | } 13 | 14 | type TxStatusString string 15 | 16 | const ( 17 | TxStatusPending TxStatusString = "pending" 18 | TxStatusCommitted TxStatusString = "committed" 19 | TxStatusUnknown TxStatusString = "unknown" 20 | ) 21 | 22 | type TxStatus struct { 23 | Status TxStatusString `json:"status"` 24 | BlockHash *types.Hash `json:"block_hash"` 25 | } 26 | 27 | type TransactionWithHeader struct { 28 | Transaction *types.Transaction `json:"transaction"` 29 | Header *types.Header `json:"header"` 30 | } 31 | 32 | type TransactionStatus struct { 33 | Transaction *types.Transaction `json:"transaction"` 34 | Cycles uint64 `json:"cycles"` 35 | TxStatus *TxStatus `json:"tx_status"` 36 | } 37 | 38 | type FetchStatus string 39 | 40 | const ( 41 | FetchStatusFetched FetchStatus = "fetched" 42 | FetchStatusFetching FetchStatus = "fetching" 43 | FetchStatusAdded FetchStatus = "added" 44 | FetchStatusNotFound FetchStatus = "not_found" 45 | ) 46 | 47 | type FetchedHeader struct { 48 | Status FetchStatus `json:"status"` 49 | Data *types.Header `json:"data"` 50 | FirstSent uint64 `json:"first_sent"` 51 | TimeStamp uint64 `json:"time_stamp"` 52 | } 53 | 54 | type FetchedTransaction struct { 55 | Status FetchStatus `json:"status"` 56 | Data *TransactionWithHeader `json:"data"` 57 | FirstSent uint64 `json:"first_sent"` 58 | TimeStamp uint64 `json:"time_stamp"` 59 | } 60 | 61 | type TxWithCell struct { 62 | BlockNumber uint64 `json:"block_number"` 63 | IoIndex uint `json:"io_index"` 64 | IoType indexer.IoType `json:"io_type"` 65 | Transaction *types.Transaction `json:"transaction"` 66 | TxIndex uint `json:"tx_index"` 67 | } 68 | 69 | type TxWithCells struct { 70 | Transaction *types.Transaction `json:"transaction"` 71 | BlockNumber uint64 `json:"block_number"` 72 | TxIndex uint `json:"tx_index"` 73 | Cells []*indexer.Cell `json:"Cells"` 74 | } 75 | 76 | type TxsWithCell struct { 77 | LastCursor string `json:"last_cursor"` 78 | Objects []*TxWithCell `json:"objects"` 79 | } 80 | 81 | type TxsWithCells struct { 82 | LastCursor string `json:"last_cursor"` 83 | Objects []*TxWithCells `json:"objects"` 84 | } 85 | -------------------------------------------------------------------------------- /mercury/model/asset.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/nervosnetwork/ckb-sdk-go/v2/types" 4 | 5 | type AssetType string 6 | 7 | const ( 8 | AssetTypeCKB AssetType = "CKB" 9 | AssetTypeUDT AssetType = "UDT" 10 | ) 11 | 12 | type AssetInfo struct { 13 | AssetType AssetType `json:"asset_type"` 14 | UdtHash types.Hash `json:"udt_hash"` 15 | } 16 | 17 | func NewCkbAsset() *AssetInfo { 18 | return &AssetInfo{ 19 | AssetType: AssetTypeCKB, 20 | UdtHash: types.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), 21 | } 22 | } 23 | 24 | func NewUdtAsset(udtHash types.Hash) *AssetInfo { 25 | return &AssetInfo{ 26 | AssetType: AssetTypeUDT, 27 | UdtHash: udtHash, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mercury/model/item.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/address" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/systemscript" 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 9 | "reflect" 10 | ) 11 | 12 | const ( 13 | IdentityFlagsCkb byte = 0x00 14 | ) 15 | 16 | func NewAddressItem(addr string) (*Item, error) { 17 | return &Item{ 18 | ItemTypeAddress, 19 | addr, 20 | }, nil 21 | } 22 | 23 | func NewIdentityItemByPublicKeyHash(publicKeyHash string) (*Item, error) { 24 | hash, err := hexutil.Decode(publicKeyHash) 25 | if err != nil { 26 | return nil, err 27 | } 28 | identity, err := toIdentity(IdentityFlagsCkb, hash[:20]) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return &Item{ItemTypeIdentity, identity}, nil 33 | } 34 | 35 | func NewIdentityItemByCkb(publicKeyHash string) (*Item, error) { 36 | hash, err := hexutil.Decode(publicKeyHash) 37 | if err != nil { 38 | return nil, err 39 | } 40 | identity, err := toIdentity(IdentityFlagsCkb, hash) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return &Item{ItemTypeIdentity, identity}, nil 45 | } 46 | 47 | func NewIdentityItemByAddress(address string) (*Item, error) { 48 | parse, err := decodeItemAddress(address) 49 | if err != nil { 50 | return nil, err 51 | } 52 | identity, err := toIdentity(IdentityFlagsCkb, parse.Script.Args) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return &Item{ItemTypeIdentity, identity}, nil 58 | } 59 | 60 | func decodeItemAddress(addr string) (*address.Address, error) { 61 | a, err := address.Decode(addr) 62 | if err != nil { 63 | return nil, err 64 | } 65 | systemScripts := []systemscript.SystemScript{ 66 | systemscript.Secp256k1Blake160SighashAll, 67 | systemscript.AnyoneCanPay} 68 | for _, s := range systemScripts { 69 | hash := systemscript.GetCodeHash(a.Network, s) 70 | if reflect.DeepEqual(hash, a.Script.CodeHash) { 71 | return a, nil 72 | } 73 | } 74 | return nil, errors.New("not a valid secp256k1_blake160_signhash_all or ACP addr") 75 | } 76 | 77 | func toIdentity(flag byte, content []byte) (string, error) { 78 | if len(content) != 20 { 79 | return "", errors.New("identity content should be 20 bytes length") 80 | } 81 | identity := append([]byte{flag}, content...) 82 | return hexutil.Encode(identity), nil 83 | } 84 | 85 | func NewOutPointItem(txHash types.Hash, index uint) *Item { 86 | outPoint := types.OutPoint{TxHash: txHash, Index: uint32(index)} 87 | return &Item{ 88 | ItemTypeOutPoint, 89 | outPoint, 90 | } 91 | } 92 | 93 | type Item struct { 94 | Type ItemType `json:"type"` 95 | Value interface{} `json:"value"` 96 | } 97 | 98 | type ItemType string 99 | 100 | const ( 101 | ItemTypeAddress ItemType = "Address" 102 | ItemTypeIdentity = "Identity" 103 | ItemTypeOutPoint = "OutPoint" 104 | ) 105 | -------------------------------------------------------------------------------- /mercury/model/resp_json_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestJsonDaoState(t *testing.T) { 10 | jsonText := []byte(` 11 | { 12 | "type": "Deposit", 13 | "value": "0x100" 14 | }`) 15 | var v DaoState 16 | json.Unmarshal(jsonText, &v) 17 | assert.Equal(t, DaoStateTypeDeposit, v.Type) 18 | assert.Equal(t, 1, len(v.Value)) 19 | assert.Equal(t, uint64(0x100), v.Value[0]) 20 | 21 | jsonText = []byte(` 22 | { 23 | "type": "Withdraw", 24 | "value": ["0x100", "0x400"] 25 | }`) 26 | json.Unmarshal(jsonText, &v) 27 | assert.Equal(t, DaoStateTypeWithdraw, v.Type) 28 | assert.Equal(t, 2, len(v.Value)) 29 | assert.Equal(t, uint64(0x100), v.Value[0]) 30 | assert.Equal(t, uint64(0x400), v.Value[1]) 31 | } 32 | -------------------------------------------------------------------------------- /systemscript/generator.go: -------------------------------------------------------------------------------- 1 | package systemscript 2 | 3 | import ( 4 | "fmt" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/blake2b" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/secp256k1" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 8 | ) 9 | 10 | func Secp256K1Blake160SignhashAll(key *secp256k1.Secp256k1Key) *types.Script { 11 | args := blake2b.Blake160(key.PubKey()) 12 | return &types.Script{ 13 | // The same code hash is shared by mainnet and testnet 14 | CodeHash: GetCodeHash(types.NetworkMain, 0), 15 | HashType: types.HashTypeType, 16 | Args: args, 17 | } 18 | } 19 | 20 | // Secp256K1Blake160SignhashAllByPublicKey generates scep256k1_blake160_sighash_all script with 33-byte compressed public key 21 | func Secp256K1Blake160SignhashAllByPublicKey(compressedPubKey []byte) (*types.Script, error) { 22 | if len(compressedPubKey) != 33 { 23 | return nil, fmt.Errorf("only allow 33-byte compressed public key, but accept %d bytes", len(compressedPubKey)) 24 | } 25 | args := blake2b.Blake160(compressedPubKey) 26 | return &types.Script{ 27 | // The same code hash is shared by mainnet and testnet 28 | CodeHash: GetCodeHash(types.NetworkMain, Secp256k1Blake160SighashAll), 29 | HashType: types.HashTypeType, 30 | Args: args, 31 | }, nil 32 | } 33 | 34 | // Secp256k1Blake160Multisig generates scep256k1_blake160_multisig script. 35 | func Secp256k1Blake160Multisig(config *MultisigConfig, multisigVersion MultisigVersion) (*types.Script, error) { 36 | args := config.Hash160() 37 | // secp256k1_blake160_multisig_all share the same code hash in network main and test 38 | var codeHash types.Hash 39 | var hashType types.ScriptHashType 40 | if multisigVersion == MultisigLegacy { 41 | codeHash = GetCodeHash(types.NetworkTest, Secp256k1Blake160MultisigAllLegacy) 42 | hashType = types.HashTypeType 43 | } else if multisigVersion == MultisigV2 { 44 | codeHash = GetCodeHash(types.NetworkTest, Secp256k1Blake160MultisigAllV2) 45 | hashType = types.HashTypeData1 46 | } else { 47 | return nil, nil 48 | } 49 | 50 | return &types.Script{ 51 | CodeHash: codeHash, 52 | HashType: hashType, 53 | Args: args, 54 | }, nil 55 | } 56 | -------------------------------------------------------------------------------- /systemscript/generator_test.go: -------------------------------------------------------------------------------- 1 | package systemscript 2 | 3 | import ( 4 | "encoding/hex" 5 | "github.com/ethereum/go-ethereum/common" 6 | "github.com/ethereum/go-ethereum/common/hexutil" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/blake2b" 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/secp256k1" 9 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 10 | "github.com/stretchr/testify/assert" 11 | "testing" 12 | ) 13 | 14 | func TestSecp256K1Blake160SignhashAll(t *testing.T) { 15 | key, err := secp256k1.HexToKey("e79f3207ea4980b7fed79956d5934249ceac4751a4fae01a0f7c4a96884bc4e3") 16 | if err != nil { 17 | t.Error(err) 18 | } 19 | generated := Secp256K1Blake160SignhashAll(key) 20 | expected := &types.Script{ 21 | CodeHash: types.HexToHash("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"), 22 | HashType: types.HashTypeType, 23 | Args: common.FromHex("0x36c329ed630d6ce750712a477543672adab57f4c"), 24 | } 25 | assert.Equal(t, expected, generated) 26 | 27 | generated, err = Secp256K1Blake160SignhashAllByPublicKey(common.FromHex("0x024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01")) 28 | if err != nil { 29 | t.Error(err) 30 | } 31 | assert.Equal(t, expected, generated) 32 | // test public key hex without 0x 33 | generated, err = Secp256K1Blake160SignhashAllByPublicKey(common.FromHex("024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01")) 34 | if err != nil { 35 | t.Error(err) 36 | } 37 | assert.Equal(t, expected, generated) 38 | // test invalid length 39 | _, err = Secp256K1Blake160SignhashAllByPublicKey(common.FromHex("0x024a501ef")) 40 | assert.NotNil(t, err) 41 | // test uncompressed public key 42 | _, err = Secp256K1Blake160SignhashAllByPublicKey(common.FromHex("0x044a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01d1868c7dabbf50e52ca7311e1263f917a8ced1d033e82dc2a68bed69397382f4")) 43 | assert.NotNil(t, err) 44 | } 45 | 46 | func TestSecp256k1Blake160Multisig(t *testing.T) { 47 | encodedPublicKeys := []string{ 48 | "032edb83018b57ddeb9bcc7287c5cc5da57e6e0289d31c9e98cb361e88678d6288", 49 | "033aeb3fdbfaac72e9e34c55884a401ee87115302c146dd9e314677d826375dc8f", 50 | "029a685b8206550ea1b600e347f18fd6115bffe582089d3567bec7eba57d04df01", 51 | } 52 | multisigConfig := NewMultisigConfig(0, 2) 53 | for _, publicKeyHex := range encodedPublicKeys { 54 | key, err := hex.DecodeString(publicKeyHex) 55 | if err != nil { 56 | t.Error(t, err) 57 | } 58 | multisigConfig.AddKeyHash(blake2b.Blake256(key)) 59 | } 60 | s, err := Secp256k1Blake160Multisig(multisigConfig, MultisigLegacy) 61 | if err != nil { 62 | t.Error(t, err) 63 | } 64 | 65 | // ckt1qpw9q60tppt7l3j7r09qcp7lxnp3vcanvgha8pmvsa3jplykxn32sq0sfnkgf0ph76pkzwld9ujzex4pkeuwnlsdc5tqu 66 | assert.Equal(t, hexutil.MustDecode("0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8"), s.CodeHash.Bytes()) 67 | assert.Equal(t, hexutil.MustDecode("0xf04cec84bc37f683613bed2f242c9aa1b678e9fe"), s.Args) 68 | assert.Equal(t, types.HashTypeType, s.HashType) 69 | } 70 | -------------------------------------------------------------------------------- /systemscript/info_test.go: -------------------------------------------------------------------------------- 1 | package systemscript 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestGetSystemScriptInfo(t *testing.T) { 10 | script := GetInfo(types.NetworkMain, Secp256k1Blake160SighashAll) 11 | assert.NotNil(t, script) 12 | script = GetInfo(types.NetworkMain, Secp256k1Blake160MultisigAllLegacy) 13 | assert.NotNil(t, script) 14 | script = GetInfo(types.NetworkMain, Secp256k1Blake160MultisigAllV2) 15 | assert.NotNil(t, script) 16 | script = GetInfo(types.NetworkTest, Secp256k1Blake160MultisigAllLegacy) 17 | assert.NotNil(t, script) 18 | script = GetInfo(types.NetworkTest, Secp256k1Blake160MultisigAllV2) 19 | assert.NotNil(t, script) 20 | } 21 | -------------------------------------------------------------------------------- /systemscript/script.go: -------------------------------------------------------------------------------- 1 | package systemscript 2 | 3 | import ( 4 | "fmt" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/blake2b" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | "math/big" 8 | ) 9 | 10 | type MultisigConfig struct { 11 | Version byte 12 | FirstN byte 13 | Threshold byte 14 | KeysHashes [][20]byte 15 | } 16 | 17 | func NewMultisigConfig(firstN byte, threshold byte) *MultisigConfig { 18 | return &MultisigConfig{ 19 | Version: 0, 20 | FirstN: firstN, 21 | Threshold: threshold, 22 | KeysHashes: make([][20]byte, 0), 23 | } 24 | } 25 | 26 | // AddKeyHash adds key hash, and panic if keyHash is shorter than 20 bytes. 27 | func (r *MultisigConfig) AddKeyHash(keyHash []byte) { 28 | var h [20]byte 29 | copy(h[:], keyHash[:20]) 30 | r.KeysHashes = append(r.KeysHashes, h) 31 | } 32 | 33 | func (r *MultisigConfig) Encode() []byte { 34 | out := make([]byte, 4) 35 | out[0] = r.Version 36 | out[1] = r.FirstN 37 | out[2] = r.Threshold 38 | out[3] = byte(len(r.KeysHashes)) 39 | for _, b := range r.KeysHashes { 40 | out = append(out, b[:]...) 41 | } 42 | return out 43 | } 44 | 45 | func DecodeToMultisigConfig(in []byte) (*MultisigConfig, error) { 46 | length := len(in) 47 | if length < 24 { 48 | return nil, fmt.Errorf("bytes length should be greater than 24 but receive %d bytes", length) 49 | } 50 | if (length-4)%4 != 0 { 51 | return nil, fmt.Errorf("invalid bytes length %d", length) 52 | } 53 | if length != int(in[3])*20+4 { 54 | return nil, fmt.Errorf("invalid public key list size") 55 | } 56 | m := &MultisigConfig{ 57 | Version: in[0], 58 | FirstN: in[1], 59 | Threshold: in[2], 60 | KeysHashes: make([][20]byte, 0), 61 | } 62 | for i := 0; i < int(in[3]); i++ { 63 | var b [20]byte 64 | copy(b[:], in[4+i*20:4+i*20+20]) 65 | m.KeysHashes = append(m.KeysHashes, b) 66 | } 67 | return m, nil 68 | } 69 | 70 | func (r *MultisigConfig) WitnessPlaceholder(originalWitness []byte) ([]byte, error) { 71 | var ( 72 | witnessArgs *types.WitnessArgs 73 | err error 74 | ) 75 | if len(originalWitness) == 0 { 76 | witnessArgs = &types.WitnessArgs{} 77 | } else { 78 | if witnessArgs, err = types.DeserializeWitnessArgs(originalWitness); err != nil { 79 | return nil, err 80 | } 81 | } 82 | witnessArgs.Lock = r.WitnessPlaceholderInLock() 83 | return witnessArgs.Serialize(), nil 84 | } 85 | 86 | func (r *MultisigConfig) WitnessPlaceholderInLock() []byte { 87 | header := r.Encode() 88 | out := make([]byte, len(header)+65*int(r.Threshold)) 89 | copy(out[:len(header)], header) 90 | return out 91 | } 92 | 93 | func (r *MultisigConfig) WitnessEmptyPlaceholderInLock() []byte { 94 | return make([]byte, len(r.WitnessPlaceholderInLock())) 95 | } 96 | 97 | func (r *MultisigConfig) Hash160() []byte { 98 | return blake2b.Blake160(r.Encode()[:]) 99 | } 100 | 101 | func DecodeSudtAmount(in []byte) (*big.Int, error) { 102 | if len(in) != 16 { 103 | return nil, fmt.Errorf("only accept 16 bytes but receive %d bytes", len(in)) 104 | } 105 | out := reverse(in) 106 | return big.NewInt(0).SetBytes(out), nil 107 | } 108 | 109 | func EncodeSudtAmount(amount *big.Int) []byte { 110 | out := make([]byte, 16) 111 | amount.FillBytes(out) 112 | out = reverse(out) 113 | return out 114 | } 115 | 116 | func reverse(in []byte) []byte { 117 | length := len(in) 118 | out := make([]byte, length) 119 | for i, v := range in { 120 | out[length-1-i] = v 121 | } 122 | return out 123 | } 124 | 125 | // ChequeArgs generates a args for cheque script 126 | func ChequeArgs(senderLock, receiverLock *types.Script) []byte { 127 | senderLockHash := senderLock.Hash() 128 | receiverLockHash := receiverLock.Hash() 129 | return append(receiverLockHash.Bytes()[0:20], senderLockHash.Bytes()[0:20]...) 130 | } 131 | -------------------------------------------------------------------------------- /systemscript/script_test.go: -------------------------------------------------------------------------------- 1 | package systemscript 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 6 | "github.com/stretchr/testify/assert" 7 | "math/big" 8 | "testing" 9 | ) 10 | 11 | func TestMultisigConfigEncode(t *testing.T) { 12 | config := &MultisigConfig{ 13 | Version: 0, 14 | FirstN: 0, 15 | Threshold: 2, 16 | KeysHashes: getKeysHashes(), 17 | } 18 | encoded := config.Encode() 19 | assert.Equal(t, common.FromHex("0x000002029b41c025515b00c24e2e2042df7b221af5c1891fe732dcd15b7618eb1d7a11e6a68e4579b5be0114"), encoded) 20 | hash := config.Hash160() 21 | assert.Equal(t, common.FromHex("0x35ed7b939b4ac9cb447b82340fd8f26d344f7a62"), hash) 22 | } 23 | 24 | func TestMultisigConfigDecode(t *testing.T) { 25 | bytes := common.FromHex("0x000002029b41c025515b00c24e2e2042df7b221af5c1891fe732dcd15b7618eb1d7a11e6a68e4579b5be0114") 26 | config, err := DecodeToMultisigConfig(bytes) 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | assert.Equal(t, byte(0), config.FirstN) 31 | assert.Equal(t, byte(2), config.Threshold) 32 | assert.Equal(t, getKeysHashes(), config.KeysHashes) 33 | 34 | bytes = common.FromHex("0x000002039b41c025515b00c24e2e2042df7b221af5c1891fe732dcd15b7618eb1d7a11e6a68e4579b5be0114") 35 | _, err = DecodeToMultisigConfig(bytes) 36 | assert.Error(t, err) 37 | 38 | bytes = common.FromHex("0x000002029b41c025515b00c24e2e2042df7b221af5c1891f") 39 | _, err = DecodeToMultisigConfig(bytes) 40 | assert.Error(t, err) 41 | } 42 | 43 | func getKeysHashes() [][20]byte { 44 | keysHashes := make([][20]byte, 2) 45 | copy(keysHashes[0][:], common.FromHex("0x9b41c025515b00c24e2e2042df7b221af5c1891f")) 46 | copy(keysHashes[1][:], common.FromHex("0xe732dcd15b7618eb1d7a11e6a68e4579b5be0114")) 47 | return keysHashes 48 | } 49 | 50 | func TestEncodeSudtAmount(t *testing.T) { 51 | amount := big.NewInt(10000000) 52 | data := EncodeSudtAmount(amount) 53 | expectedData := []byte{0x80, 0x96, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 54 | 55 | assert.Equal(t, expectedData, data) 56 | } 57 | 58 | func TestDecodeSudtAmount(t *testing.T) { 59 | data := []byte{0x80, 0xC3, 0xC9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 60 | amount, err := DecodeSudtAmount(data) 61 | assert.NoError(t, err) 62 | assert.Equal(t, big.NewInt(30000000), amount) 63 | 64 | data = []byte{0x80, 0x96} 65 | _, err = DecodeSudtAmount(data) 66 | assert.Error(t, err) 67 | } 68 | 69 | func TestChequeArgs(t *testing.T) { 70 | senderLock := &types.Script{ 71 | CodeHash: types.HexToHash("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"), 72 | HashType: "type", 73 | Args: common.FromHex("0xedcda9513fa030ce4308e29245a22c022d0443bb"), 74 | } 75 | receiverLock := &types.Script{ 76 | CodeHash: types.HexToHash("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"), 77 | HashType: "type", 78 | Args: common.FromHex("0xedcda9513fa030ce4308e29245a22c022d0212ab"), 79 | } 80 | 81 | senderLockHash := senderLock.Hash() 82 | receiverLockHash := receiverLock.Hash() 83 | expectedArgs := append(receiverLockHash.Bytes()[0:20], senderLockHash.Bytes()[0:20]...) 84 | actualArgs := ChequeArgs(senderLock, receiverLock) 85 | assert.Equal(t, expectedArgs, actualArgs) 86 | } 87 | -------------------------------------------------------------------------------- /transaction/context.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/secp256k1" 7 | ) 8 | 9 | // Context is user provided information for 10 | // `signer.TransactionSigner.SignTransaction`. In turn the context is passed to `signer.ScriptSigner.SignTransaction`. 11 | // 12 | // See more in github.com/nervosnetwork/ckb-sdk-go/v2/transaction/signer 13 | type Context struct { 14 | Key *secp256k1.Secp256k1Key 15 | Payload interface{} 16 | } 17 | 18 | func NewContext(ecPrivateKey string) (*Context, error) { 19 | key, err := secp256k1.HexToKey(ecPrivateKey) 20 | if err != nil { 21 | return nil, errors.WithMessage(err, ecPrivateKey) 22 | } 23 | return &Context{ 24 | Key: key, 25 | Payload: nil, 26 | }, nil 27 | } 28 | 29 | func NewContextWithPayload(ecPrivateKey string, payload interface{}) (*Context, error) { 30 | context, err := NewContext(ecPrivateKey) 31 | if err != nil { 32 | return nil, err 33 | } 34 | context.Payload = payload 35 | return context, nil 36 | } 37 | -------------------------------------------------------------------------------- /transaction/script_group.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | ) 8 | 9 | type TransactionWithScriptGroups struct { 10 | TxView *types.Transaction `json:"tx_view"` 11 | ScriptGroups []*ScriptGroup `json:"script_groups"` 12 | } 13 | 14 | type ScriptGroup struct { 15 | Script *types.Script `json:"script"` 16 | GroupType types.ScriptType `json:"group_type"` 17 | InputIndices []uint32 `json:"input_indices"` 18 | OutputIndices []uint32 `json:"output_indices"` 19 | } 20 | 21 | func (r *ScriptGroup) UnmarshalJSON(input []byte) error { 22 | var jsonObj struct { 23 | Script *types.Script `json:"script"` 24 | GroupType types.ScriptType `json:"group_type"` 25 | InputIndices []hexutil.Uint `json:"input_indices"` 26 | OutputIndices []hexutil.Uint `json:"output_indices"` 27 | } 28 | if err := json.Unmarshal(input, &jsonObj); err != nil { 29 | return err 30 | } 31 | toUint32Array := func(in []hexutil.Uint) []uint32 { 32 | out := make([]uint32, len(in)) 33 | for i, data := range in { 34 | out[i] = uint32(data) 35 | } 36 | return out 37 | } 38 | *r = ScriptGroup{ 39 | Script: jsonObj.Script, 40 | GroupType: jsonObj.GroupType, 41 | InputIndices: toUint32Array(jsonObj.InputIndices), 42 | OutputIndices: toUint32Array(jsonObj.OutputIndices), 43 | } 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /transaction/signer/any_can_pay.go: -------------------------------------------------------------------------------- 1 | package signer 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/secp256k1" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | ) 8 | 9 | type AnyCanPaySigner struct { 10 | } 11 | 12 | func (s *AnyCanPaySigner) SignTransaction(tx *types.Transaction, group *transaction.ScriptGroup, ctx *transaction.Context) (bool, error) { 13 | matched, err := IsAnyCanPayMatched(ctx.Key, group.Script.Args) 14 | if err != nil { 15 | return false, err 16 | } 17 | if matched { 18 | i0 := group.InputIndices[0] 19 | signature, err := SignTransaction(tx, uint32ArrayToIntArray(group.InputIndices), tx.Witnesses[i0], ctx.Key) 20 | if err != nil { 21 | return false, err 22 | } 23 | witnessArgs, err := types.DeserializeWitnessArgs(tx.Witnesses[i0]) 24 | if err != nil { 25 | return false, err 26 | } 27 | witnessArgs.Lock = signature 28 | tx.Witnesses[i0] = witnessArgs.Serialize() 29 | return true, nil 30 | } else { 31 | return false, nil 32 | } 33 | } 34 | 35 | func IsAnyCanPayMatched(key *secp256k1.Secp256k1Key, scriptArgs []byte) (bool, error) { 36 | return IsSingleSigMatched(key, scriptArgs[:20]) 37 | } 38 | -------------------------------------------------------------------------------- /transaction/signer/example_test.go: -------------------------------------------------------------------------------- 1 | package signer_test 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "reflect" 7 | 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/rpc" 9 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction" 10 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction/signer" 11 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 12 | ) 13 | 14 | type CapacityDiffContext struct { 15 | rpc rpc.Client 16 | ctx context.Context 17 | } 18 | 19 | func (ctx CapacityDiffContext) getInputCell(outPoint *types.OutPoint) (*types.CellOutput, error) { 20 | cellWithStatus, err := ctx.rpc.GetLiveCell(ctx.ctx, outPoint, false, nil) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | return cellWithStatus.Cell.Output, nil 26 | } 27 | 28 | type CapacityDiffScriptSigner struct{} 29 | 30 | func (s *CapacityDiffScriptSigner) SignTransaction(tx *types.Transaction, group *transaction.ScriptGroup, ctx *transaction.Context) (bool, error) { 31 | scriptContext, ok := ctx.Payload.(CapacityDiffContext) 32 | if !ok { 33 | return false, nil 34 | } 35 | 36 | total := int64(0) 37 | for _, i := range group.InputIndices { 38 | inputCell, err := scriptContext.getInputCell(tx.Inputs[i].PreviousOutput) 39 | if err != nil { 40 | return false, nil 41 | } 42 | total -= int64(inputCell.Capacity) 43 | } 44 | for _, output := range tx.Outputs { 45 | if reflect.DeepEqual(output.Lock, group.Script) { 46 | total += int64(output.Capacity) 47 | } 48 | } 49 | 50 | // The specification https://go.dev/ref/spec#Numeric_types says integres in 51 | // Go are repsented using two's complementation. So we can just cast it to 52 | // uin64 and get the little endian bytes. 53 | witness := make([]byte, 8) 54 | binary.LittleEndian.PutUint64(witness, uint64(total)) 55 | 56 | witnessIndex := group.InputIndices[0] 57 | witnessArgs, err := types.DeserializeWitnessArgs(tx.Witnesses[witnessIndex]) 58 | if err != nil { 59 | return false, err 60 | } 61 | witnessArgs.Lock = witness 62 | tx.Witnesses[witnessIndex] = witnessArgs.Serialize() 63 | 64 | return true, nil 65 | } 66 | 67 | // This example demonstrates how to use a custom script CapacityDiff 68 | // (https://github.com/doitian/ckb-sdk-examples-capacity-diff). 69 | // 70 | // CapacityDiff verifies the witness matches the capacity difference. 71 | // 72 | // - The script loads the witness for the first input in the script group using the WitnessArgs layout. 73 | // - The total input capacity is the sum of all the input cells in the script group. 74 | // - The total output capacity is the sum of all the output cells having the same lock script as the script group. 75 | // - The capacity difference is a 64-bit signed integer which equals to total output capacity minus total input capacity. 76 | // - The witness is encoded using two's complement and little endian. 77 | func ExampleScriptSigner() { 78 | signer := signer.NewTransactionSigner() 79 | signer.RegisterSigner( 80 | types.HexToHash("0x6283a479a3cf5d4276cd93594de9f1827ab9b55c7b05b3d28e4c2e0a696cfefd"), 81 | types.ScriptTypeType, 82 | &CapacityDiffScriptSigner{}, 83 | ) 84 | } 85 | -------------------------------------------------------------------------------- /transaction/signer/init.go: -------------------------------------------------------------------------------- 1 | package signer 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/systemscript" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 6 | ) 7 | 8 | func init() { 9 | networks := []types.Network{types.NetworkMain, types.NetworkTest} 10 | for _, network := range networks { 11 | instance := GetTransactionSignerInstance(network) 12 | instance.RegisterLockSigner( 13 | systemscript.GetCodeHash(network, systemscript.Secp256k1Blake160SighashAll), &Secp256k1Blake160SighashAllSigner{}) 14 | instance.RegisterLockSigner( 15 | systemscript.GetCodeHash(network, systemscript.Secp256k1Blake160MultisigAllLegacy), &Secp256k1Blake160MultisigAllSigner{}) 16 | instance.RegisterLockSigner( 17 | systemscript.GetCodeHash(network, systemscript.Secp256k1Blake160MultisigAllV2), &Secp256k1Blake160MultisigAllSigner{}) 18 | instance.RegisterLockSigner( 19 | systemscript.GetCodeHash(network, systemscript.AnyoneCanPay), &AnyCanPaySigner{}) 20 | instance.RegisterLockSigner( 21 | systemscript.GetCodeHash(network, systemscript.PwLock), &PWLockSigner{}) 22 | instance.RegisterLockSigner( 23 | systemscript.GetCodeHash(network, systemscript.Omnilock), &OmnilockSigner{}) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /transaction/signer/omnilock/molecule/omnilock.mol: -------------------------------------------------------------------------------- 1 | // https://github.com/nervosnetwork/ckb-production-scripts/blob/master/c/omni_lock.mol 2 | // https://github.com/nervosnetwork/ckb-production-scripts/blob/master/c/xudt_rce.mol 3 | 4 | vector Bytes ; 5 | option BytesOpt (Bytes); 6 | vector SmtProof ; 7 | table SmtProofEntry { 8 | mask: byte, 9 | proof: SmtProof, 10 | } 11 | array Auth[byte; 21]; 12 | vector SmtProofEntryVec ; 13 | 14 | table Identity { 15 | identity: Auth, 16 | proofs: SmtProofEntryVec, 17 | } 18 | option IdentityOpt (Identity); 19 | 20 | // the data structure used in lock field of witness 21 | table OmniLockWitnessLock { 22 | signature: BytesOpt, 23 | omni_identity: IdentityOpt, 24 | preimage: BytesOpt, 25 | } -------------------------------------------------------------------------------- /transaction/signer/omnilock/type_serialize.go: -------------------------------------------------------------------------------- 1 | package omnilock 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction/signer/omnilock/molecule" 5 | ) 6 | 7 | func DeserializeOmnilockWitnessLock(in []byte) (*OmnilockWitnessLock, error) { 8 | m, err := molecule.OmniLockWitnessLockFromSlice(in, false) 9 | if err != nil { 10 | return nil, err 11 | } 12 | return UnpackOmnilockWitnessLock(m), nil 13 | } 14 | 15 | func UnpackSmtProofEntry(v *molecule.SmtProofEntry) *SmtProofEntry { 16 | return &SmtProofEntry{ 17 | Mask: v.Mask().AsSlice()[0], 18 | SmtProof: v.Mask().AsSlice(), 19 | } 20 | } 21 | 22 | func UnpackIdentityOpt(v *molecule.IdentityOpt) *OmnilockIdentity { 23 | if v.IsNone() { 24 | return nil 25 | } 26 | var smtProofEntryVec []*SmtProofEntry 27 | mIdentity, _ := v.IntoIdentity() 28 | mSmtProofEntryVec := mIdentity.Proofs() 29 | for i := 0; i < int(mSmtProofEntryVec.Len()); i++ { 30 | smtProofEntryVec = append(smtProofEntryVec, UnpackSmtProofEntry(mSmtProofEntryVec.Get(uint(i)))) 31 | } 32 | return &OmnilockIdentity{ 33 | Identity: UnpackAuth(mIdentity.Identity()), 34 | Proofs: smtProofEntryVec, 35 | } 36 | } 37 | 38 | func UnpackAuth(v *molecule.Auth) *Auth { 39 | b := v.AsSlice() 40 | return &Auth{ 41 | Flag: OmnilockFlag(b[0]), 42 | AuthContent: b[1:], 43 | } 44 | } 45 | 46 | func UnpackOmnilockWitnessLock(v *molecule.OmniLockWitnessLock) *OmnilockWitnessLock { 47 | o := &OmnilockWitnessLock{ 48 | OmnilockIdentity: UnpackIdentityOpt(v.OmniIdentity()), 49 | } 50 | if v.Signature().IsSome() { 51 | b, _ := v.Signature().IntoBytes() 52 | o.Signature = b.RawData() 53 | } 54 | if v.Preimage().IsSome() { 55 | b, _ := v.Preimage().IntoBytes() 56 | o.Preimage = b.RawData() 57 | } 58 | return o 59 | } 60 | 61 | func (o *OmnilockWitnessLock) Serialize() []byte { 62 | return o.Pack().AsSlice() 63 | } 64 | 65 | func (o *OmnilockWitnessLock) SerializeAsPlaceholder() []byte { 66 | return make([]byte, len(o.Pack().AsSlice())) 67 | } 68 | 69 | func (o *SmtProofEntry) Pack() *molecule.SmtProofEntry { 70 | proofBuilder := molecule.NewSmtProofBuilder() 71 | for _, vv := range o.SmtProof { 72 | proofBuilder.Push(*packByte(vv)) 73 | } 74 | 75 | builder := molecule.NewSmtProofEntryBuilder() 76 | builder.Mask(*packByte(o.Mask)) 77 | builder.Proof(proofBuilder.Build()) 78 | 79 | b := builder.Build() 80 | return &b 81 | } 82 | 83 | func (o *OmnilockIdentity) PackOpt() *molecule.IdentityOpt { 84 | builder := molecule.NewIdentityOptBuilder() 85 | if o != nil { 86 | builder.Set(*o.Pack()) 87 | } 88 | b := builder.Build() 89 | return &b 90 | } 91 | 92 | func (o *OmnilockIdentity) Pack() *molecule.Identity { 93 | builder := molecule.NewIdentityBuilder() 94 | proofsBuilder := molecule.NewSmtProofEntryVecBuilder() 95 | for _, p := range o.Proofs { 96 | proofsBuilder.Push(*p.Pack()) 97 | } 98 | builder.Proofs(proofsBuilder.Build()) 99 | b := builder.Build() 100 | return &b 101 | } 102 | 103 | func (o *OmnilockWitnessLock) Pack() *molecule.OmniLockWitnessLock { 104 | builder := molecule.NewOmniLockWitnessLockBuilder() 105 | builder.Signature(*packBytesToOpt(o.Signature)) 106 | builder.OmniIdentity(*o.OmnilockIdentity.PackOpt()) 107 | builder.Preimage(*packBytesToOpt(o.Preimage)) 108 | v := builder.Build() 109 | return &v 110 | } 111 | 112 | func packBytesToOpt(v []byte) *molecule.BytesOpt { 113 | builder := molecule.NewBytesOptBuilder() 114 | if v != nil { 115 | builder.Set(*packBytes(v)) 116 | } 117 | b := builder.Build() 118 | return &b 119 | } 120 | 121 | func packByte(v byte) *molecule.Byte { 122 | b := molecule.NewByte(v) 123 | return &b 124 | } 125 | 126 | func packBytes(v []byte) *molecule.Bytes { 127 | builder := molecule.NewBytesBuilder() 128 | for _, vv := range v { 129 | builder.Push(*packByte(vv)) 130 | } 131 | b := builder.Build() 132 | return &b 133 | } 134 | -------------------------------------------------------------------------------- /transaction/signer/omnilock/type_serialize_test.go: -------------------------------------------------------------------------------- 1 | package omnilock 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/systemscript" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 8 | "github.com/stretchr/testify/assert" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | func TestOmnilockWitnessLockSerialize(t *testing.T) { 14 | witness := hexutil.MustDecode("0x690000001000000069000000690000005500000055000000100000005500000055000000410000003434ca813dc378de0146aac8e60431fb52114acb3cb639f2fb2a479e1f219223532540413a154f440e939ee888c29221c0e8d6fef39402cbeedb6155317b356200") 15 | 16 | witnessArgs, err := types.DeserializeWitnessArgs(witness) 17 | assert.NoError(t, err) 18 | omnilockWitnessLock, err := DeserializeOmnilockWitnessLock(witnessArgs.Lock) 19 | assert.NoError(t, err) 20 | 21 | expected := hexutil.MustDecode("0x55000000100000005500000055000000410000003434ca813dc378de0146aac8e60431fb52114acb3cb639f2fb2a479e1f219223532540413a154f440e939ee888c29221c0e8d6fef39402cbeedb6155317b356200") 22 | assert.Equal(t, expected, omnilockWitnessLock.Serialize()) 23 | } 24 | 25 | func TestDeserializeOmnilockWitnessLock(t *testing.T) { 26 | var codeHash = types.HexToHash("0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb") 27 | var codeHash1 = systemscript.GetCodeHash(types.NetworkTest, systemscript.Omnilock) 28 | var codeHash2 = systemscript.GetCodeHash(types.NetworkTest, systemscript.Omnilock) 29 | if codeHash == codeHash2 { 30 | fmt.Println("equal 222") 31 | } 32 | if codeHash1 == codeHash2 { 33 | fmt.Println("equal 11222") 34 | } 35 | //if bytes.Equal(codeHash.Bytes(), codeHash2.Bytes()) { 36 | // fmt.Println("equal 2222") 37 | //} 38 | fmt.Println(hexutil.Encode(systemscript.GetCodeHash(types.NetworkTest, systemscript.Omnilock).Bytes())) 39 | 40 | fmt.Println(os.Getenv("CI")) 41 | fmt.Println(os.Getenv("CI") == "") 42 | 43 | } 44 | -------------------------------------------------------------------------------- /transaction/signer/omnilock/type_test.go: -------------------------------------------------------------------------------- 1 | package omnilock 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/address" 5 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func TestNewOmnilockArgsFromAddress(t *testing.T) { 11 | _, err := NewOmnilockArgsFromAddress("ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgqgpy7m88v3gxnn3apazvlpkkt32xz3tg5qq3kzjf3") 12 | assert.NoError(t, err) 13 | 14 | // test invalid hash type 15 | a, _ := address.Decode("ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgqgpy7m88v3gxnn3apazvlpkkt32xz3tg5qq3kzjf3") 16 | a.Script.HashType = types.HashTypeData 17 | encoded, _ := a.Encode() 18 | _, err = NewOmnilockArgsFromAddress(encoded) 19 | assert.Error(t, err) 20 | 21 | // test invalid code hash 22 | _, err = NewOmnilockArgsFromAddress("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r") 23 | assert.Error(t, err) 24 | } 25 | -------------------------------------------------------------------------------- /transaction/signer/omnilock/type_witness.go: -------------------------------------------------------------------------------- 1 | package omnilock 2 | 3 | type SmtProofEntry struct { 4 | Mask byte 5 | SmtProof []byte 6 | } 7 | 8 | type OmnilockFlag byte 9 | 10 | const ( 11 | OmnilockFlagCKBSecp256k1Blake160 OmnilockFlag = 0x0 12 | OmnilockFlagLockScriptHash OmnilockFlag = 0xfc 13 | ) 14 | 15 | type Auth struct { 16 | Flag OmnilockFlag 17 | AuthContent []byte 18 | } 19 | 20 | func (a Auth) encode() []byte { 21 | var out []byte 22 | out = []byte{byte(a.Flag)} 23 | out = append(out, a.AuthContent...) 24 | return out 25 | } 26 | 27 | type OmnilockIdentity struct { 28 | Identity *Auth 29 | Proofs []*SmtProofEntry 30 | } 31 | 32 | type OmnilockWitnessLock struct { 33 | Signature []byte 34 | OmnilockIdentity *OmnilockIdentity 35 | Preimage []byte 36 | } 37 | -------------------------------------------------------------------------------- /transaction/signer/pw_lock.go: -------------------------------------------------------------------------------- 1 | package signer 2 | 3 | import ( 4 | "bytes" 5 | "github.com/ethereum/go-ethereum/crypto" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/secp256k1" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction" 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 9 | "strconv" 10 | ) 11 | 12 | type PWLockSigner struct { 13 | } 14 | 15 | func (s *PWLockSigner) SignTransaction(transaction *types.Transaction, group *transaction.ScriptGroup, ctx *transaction.Context) (bool, error) { 16 | key := ctx.Key 17 | matched := IsPWLockMatched(key, group.Script.Args) 18 | if matched { 19 | return PWLockSignTransaction(transaction, group, key) 20 | } else { 21 | return false, nil 22 | } 23 | } 24 | 25 | func PWLockSignTransaction(tx *types.Transaction, group *transaction.ScriptGroup, key *secp256k1.Secp256k1Key) (bool, error) { 26 | txHash := tx.ComputeHash() 27 | data := txHash.Bytes() 28 | for _, inputIndex := range group.InputIndices { 29 | witness := tx.Witnesses[inputIndex] 30 | data = append(data, types.SerializeUint64(uint64(len(witness)))...) 31 | data = append(data, witness...) 32 | } 33 | for i := len(tx.Inputs); i < len(tx.Witnesses); i++ { 34 | witness := tx.Witnesses[i] 35 | data = append(data, types.SerializeUint64(uint64(len(witness)))...) 36 | data = append(data, witness...) 37 | } 38 | msg := crypto.Keccak256(data) 39 | prefix := []byte("\u0019Ethereum Signed Message:\n" + strconv.Itoa(len(msg))) 40 | msg = append(prefix, msg...) 41 | msg = crypto.Keccak256(msg) 42 | signature, err := key.Sign(msg) 43 | if err != nil { 44 | return false, err 45 | } 46 | i := group.InputIndices[0] 47 | witness := tx.Witnesses[i] 48 | witnessArgs, err := types.DeserializeWitnessArgs(witness) 49 | if err != nil { 50 | return false, err 51 | } 52 | witnessArgs.Lock = signature 53 | tx.Witnesses[i] = witnessArgs.Serialize() 54 | return true, nil 55 | } 56 | 57 | func IsPWLockMatched(key *secp256k1.Secp256k1Key, scriptArgs []byte) bool { 58 | if key == nil || scriptArgs == nil { 59 | return false 60 | } 61 | encoded := key.PubKeyUncompressed() 62 | hash := crypto.Keccak256(encoded[1:]) 63 | ethAddress := hash[len(hash)-20:] 64 | return bytes.Equal(scriptArgs, ethAddress) 65 | } 66 | -------------------------------------------------------------------------------- /transaction/signer/secp256k1_blake160_multisig_all.go: -------------------------------------------------------------------------------- 1 | package signer 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/secp256k1" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/systemscript" 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction" 9 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 10 | ) 11 | 12 | type Secp256k1Blake160MultisigAllSigner struct { 13 | } 14 | 15 | func (s *Secp256k1Blake160MultisigAllSigner) SignTransaction(transaction *types.Transaction, group *transaction.ScriptGroup, ctx *transaction.Context) (bool, error) { 16 | var config *systemscript.MultisigConfig 17 | switch ctx.Payload.(type) { 18 | case systemscript.MultisigConfig: 19 | mm := ctx.Payload.(systemscript.MultisigConfig) 20 | config = &mm 21 | case *systemscript.MultisigConfig: 22 | config = ctx.Payload.(*systemscript.MultisigConfig) 23 | default: 24 | return false, nil 25 | } 26 | matched, err := IsMultiSigMatched(ctx.Key, config, group.Script.Args) 27 | if err != nil { 28 | return false, err 29 | } 30 | if matched { 31 | return MultiSignTransaction(transaction, uint32ArrayToIntArray(group.InputIndices), ctx.Key, config) 32 | } else { 33 | return false, nil 34 | } 35 | } 36 | 37 | func MultiSignTransaction(tx *types.Transaction, group []int, key *secp256k1.Secp256k1Key, config *systemscript.MultisigConfig) (bool, error) { 38 | var err error 39 | i0 := group[0] 40 | witnessPlaceholder, err := config.WitnessPlaceholder(tx.Witnesses[i0]) 41 | if err != nil { 42 | return false, nil 43 | } 44 | signature, err := SignTransaction(tx, group, witnessPlaceholder, key) 45 | if err != nil { 46 | return false, err 47 | } 48 | witnessArgs, err := types.DeserializeWitnessArgs(tx.Witnesses[i0]) 49 | if err != nil { 50 | return false, err 51 | } 52 | witnessArgs.Lock = setMultisigSignature(witnessArgs.Lock, signature, config) 53 | return true, nil 54 | } 55 | 56 | func setMultisigSignature(signatures []byte, signature []byte, multisigConfig *systemscript.MultisigConfig) []byte { 57 | offset := len(multisigConfig.Encode()) 58 | for i := 0; i < int(multisigConfig.Threshold); i++ { 59 | if isEmptyByteSlice(signatures, offset, 65) { 60 | copy(signatures[offset:offset+65], signature[:]) 61 | break 62 | } 63 | offset += 65 64 | } 65 | return signatures 66 | } 67 | 68 | func isEmptyByteSlice(lock []byte, offset int, length int) bool { 69 | for i := offset; i < offset+length; i++ { 70 | if lock[i] != 0 { 71 | return false 72 | } 73 | } 74 | return true 75 | } 76 | 77 | func IsMultiSigMatched(key *secp256k1.Secp256k1Key, config *systemscript.MultisigConfig, scriptArgs []byte) (bool, error) { 78 | if key == nil || scriptArgs == nil { 79 | return false, errors.New("key or scriptArgs is nil") 80 | } 81 | hash := config.Hash160() 82 | return bytes.Equal(scriptArgs, hash), nil 83 | } 84 | -------------------------------------------------------------------------------- /transaction/signer/secp256k1_blake160_sighash_all.go: -------------------------------------------------------------------------------- 1 | package signer 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto" 9 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/blake2b" 10 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/secp256k1" 11 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction" 12 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 13 | ) 14 | 15 | type Secp256k1Blake160SighashAllSigner struct { 16 | } 17 | 18 | func uint32ArrayToIntArray(in []uint32) []int { 19 | var out []int 20 | for _, v := range in { 21 | out = append(out, int(v)) 22 | } 23 | return out 24 | } 25 | 26 | func (s *Secp256k1Blake160SighashAllSigner) SignTransaction(tx *types.Transaction, group *transaction.ScriptGroup, ctx *transaction.Context) (bool, error) { 27 | matched, err := IsSingleSigMatched(ctx.Key, group.Script.Args) 28 | if err != nil { 29 | return false, err 30 | } 31 | if matched { 32 | i0 := group.InputIndices[0] 33 | signature, err := SignTransaction(tx, uint32ArrayToIntArray(group.InputIndices), tx.Witnesses[i0], ctx.Key) 34 | if err != nil { 35 | return false, err 36 | } 37 | witnessArgs, err := types.DeserializeWitnessArgs(tx.Witnesses[i0]) 38 | if err != nil { 39 | return false, err 40 | } 41 | witnessArgs.Lock = signature 42 | tx.Witnesses[i0] = witnessArgs.Serialize() 43 | return true, nil 44 | } else { 45 | return false, nil 46 | } 47 | } 48 | 49 | func IsSingleSigMatched(key *secp256k1.Secp256k1Key, scriptArgs []byte) (bool, error) { 50 | if key == nil || scriptArgs == nil { 51 | return false, errors.New("key or scriptArgs is nil") 52 | } 53 | hash := blake2b.Blake160(key.PubKey()) 54 | return bytes.Equal(scriptArgs, hash), nil 55 | } 56 | 57 | // SignTransaction signs transaction with index group and witness placeholder in secp256k1_blake160_sighash_all way 58 | func SignTransaction(tx *types.Transaction, group []int, witnessPlaceholder []byte, key crypto.Key) ([]byte, error) { 59 | inputsLen := len(tx.Inputs) 60 | for i := 0; i < len(group); i++ { 61 | if i > 0 && group[i] <= group[i-1] { 62 | return nil, fmt.Errorf("group index is not in ascending order") 63 | } 64 | if group[i] > inputsLen { 65 | return nil, fmt.Errorf("group index %d is greater than input bytesLen %d", group[i], inputsLen) 66 | } 67 | } 68 | txHash := tx.ComputeHash() 69 | msg := txHash.Bytes() 70 | bytesLen := make([]byte, 8) 71 | binary.LittleEndian.PutUint64(bytesLen, uint64(len(witnessPlaceholder))) 72 | msg = append(msg, bytesLen...) 73 | msg = append(msg, witnessPlaceholder...) 74 | 75 | var indexes []int 76 | for i := 1; i < len(group); i++ { 77 | indexes = append(indexes, group[i]) 78 | } 79 | for i := inputsLen; i < len(tx.Witnesses); i++ { 80 | indexes = append(indexes, i) 81 | } 82 | for _, i := range indexes { 83 | bytes := tx.Witnesses[i] 84 | bytesLen := make([]byte, 8) 85 | binary.LittleEndian.PutUint64(bytesLen, uint64(len(bytes))) 86 | msg = append(msg, bytesLen...) 87 | msg = append(msg, bytes...) 88 | } 89 | 90 | msgHash := blake2b.Blake256(msg) 91 | signature, err := key.Sign(msgHash) 92 | if err != nil { 93 | return nil, err 94 | } 95 | return signature, nil 96 | } 97 | -------------------------------------------------------------------------------- /transaction/signer/secp256k1_blake160_sighash_all_test.go: -------------------------------------------------------------------------------- 1 | package signer 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | s "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/secp256k1" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestSignTransaction(t *testing.T) { 12 | // https://pudge.explorer.nervos.org/transaction/0x150ab94cc3d35daf96d0d55a4efc420323adcc36662b2bdcab826e16ce38dd81 13 | tx := &types.Transaction{ 14 | Version: 0, 15 | CellDeps: []*types.CellDep{ 16 | { 17 | OutPoint: &types.OutPoint{ 18 | TxHash: types.HexToHash("0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37"), 19 | Index: 0, 20 | }, 21 | DepType: types.DepTypeDepGroup, 22 | }, 23 | { 24 | OutPoint: &types.OutPoint{ 25 | TxHash: types.HexToHash("0xec26b0f85ed839ece5f11c4c4e837ec359f5adc4420410f6453b1f6b60fb96a6"), 26 | Index: 0, 27 | }, 28 | DepType: types.DepTypeDepGroup, 29 | }, 30 | { 31 | OutPoint: &types.OutPoint{ 32 | TxHash: types.HexToHash("0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769"), 33 | Index: 0, 34 | }, 35 | DepType: types.DepTypeCode, 36 | }, 37 | }, 38 | Inputs: []*types.CellInput{ 39 | { 40 | Since: 0, 41 | PreviousOutput: &types.OutPoint{ 42 | TxHash: types.HexToHash("0xc43c8198c4ead3dce957cc3a3ab2ca6c8f4c23ad9d74cb083daefd5d2e4fba4e"), 43 | Index: 0, 44 | }, 45 | }, 46 | { 47 | Since: 0, 48 | PreviousOutput: &types.OutPoint{ 49 | TxHash: types.HexToHash("0x469100c2149317341756e80f369c94ed2a84b58349ff41985819d49413377ae8"), 50 | Index: 0, 51 | }, 52 | }, 53 | }, 54 | Outputs: []*types.CellOutput{ 55 | { 56 | Capacity: 0xea46318821, 57 | Lock: &types.Script{ 58 | CodeHash: types.HexToHash("0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356"), 59 | HashType: types.HashTypeType, 60 | Args: common.FromHex("0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b"), 61 | }, 62 | Type: &types.Script{ 63 | CodeHash: types.HexToHash("0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4"), 64 | HashType: types.HashTypeType, 65 | Args: common.FromHex("0xc772f4d885ca6285d87d82b8edc1643df9f3ce63c40d0f81f2a38c147328d430"), 66 | }, 67 | }, 68 | }, 69 | OutputsData: [][]byte{ 70 | common.FromHex("0x00000000000000000000000000000000"), 71 | }, 72 | Witnesses: [][]byte{ 73 | common.FromHex("0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 74 | common.FromHex("0x10000000100000001000000010000000"), 75 | }, 76 | } 77 | key, err := s.HexToKey("0x6fc935dad260867c749cf1ba6602d5f5ed7fb1131f1beb65be2d342e912eaafe") 78 | if err != nil { 79 | t.Error(err) 80 | } 81 | wa := &types.WitnessArgs{ 82 | Lock: make([]byte, 65), 83 | } 84 | signature, err := SignTransaction(tx, []int{0, 1}, wa.Serialize(), key) 85 | if err != nil { 86 | t.Error(err) 87 | } 88 | expectedSignature := common.FromHex("ed0c2ec9523029ed21be22fce92ff158d4da25da0aebd050cdd4b04a9c980ccf5f76afc8d33fa890fcb231bde3eba46b2932d4aaecd4df559ecc3d268d90ef8c01") 89 | assert.Equal(t, expectedSignature, signature) 90 | } 91 | -------------------------------------------------------------------------------- /transaction/signer/signer.go: -------------------------------------------------------------------------------- 1 | // Package signer implements a CKB transaction signing framework. 2 | // 3 | // It adopts an extension mechanism that new script can implement 4 | // [ScriptSigner] and register the signing logic via 5 | // [TransactionSigner.RegisterSigner]. 6 | package signer 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/nervosnetwork/ckb-sdk-go/v2/crypto/blake2b" 12 | "github.com/nervosnetwork/ckb-sdk-go/v2/transaction" 13 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 14 | ) 15 | 16 | // The interface ScriptSigner is for scripts to register their signing logic. 17 | // 18 | // The function SignTransaction is the callback called by [TransactionSigner] 19 | // on matched ScriptSigners, for each context passed in 20 | // [TransactionSigner.SignTransaction]. 21 | // 22 | // The [transaction.Context] provides extra data for the signer. For example, 23 | // [Secp256k1Blake160SighashAllSigner.SignTransaction] requires user to pass 24 | // the private keys as contexts. 25 | // 26 | // Returns bool indicating whether the transaction has been modified. 27 | type ScriptSigner interface { 28 | SignTransaction(transaction *types.Transaction, group *transaction.ScriptGroup, ctx *transaction.Context) (bool, error) 29 | } 30 | 31 | type TransactionSigner struct { 32 | signers map[types.Hash]ScriptSigner 33 | } 34 | 35 | func NewTransactionSigner() *TransactionSigner { 36 | return &TransactionSigner{signers: make(map[types.Hash]ScriptSigner)} 37 | } 38 | 39 | var ( 40 | testInstance = NewTransactionSigner() 41 | mainInstance = NewTransactionSigner() 42 | ) 43 | 44 | func GetTransactionSignerInstance(network types.Network) *TransactionSigner { 45 | if network == types.NetworkTest { 46 | return testInstance 47 | } else if network == types.NetworkMain { 48 | return mainInstance 49 | } else { 50 | return nil 51 | } 52 | } 53 | 54 | func (r *TransactionSigner) RegisterSigner(codeHash types.Hash, scriptType types.ScriptType, signer ScriptSigner) { 55 | hash := hash(codeHash, scriptType) 56 | r.signers[hash] = signer 57 | } 58 | 59 | func (r *TransactionSigner) RegisterTypeSigner(codeHash types.Hash, signer ScriptSigner) { 60 | r.RegisterSigner(codeHash, types.ScriptTypeType, signer) 61 | } 62 | 63 | func (r *TransactionSigner) RegisterLockSigner(codeHash types.Hash, signer ScriptSigner) { 64 | r.RegisterSigner(codeHash, types.ScriptTypeLock, signer) 65 | } 66 | 67 | func hash(codeHash types.Hash, scriptType types.ScriptType) types.Hash { 68 | data := codeHash.Bytes() 69 | data = append(data, []byte(scriptType)...) 70 | return types.BytesToHash(blake2b.Blake256(data)) 71 | } 72 | 73 | func (r *TransactionSigner) SignTransactionByPrivateKeys(tx *transaction.TransactionWithScriptGroups, privKeys ...string) ([]int, error) { 74 | var ctxs []*transaction.Context 75 | for _, key := range privKeys { 76 | ctx, err := transaction.NewContext(key) 77 | if err != nil { 78 | return nil, err 79 | } 80 | ctxs = append(ctxs, ctx) 81 | } 82 | return r.SignTransaction(tx, ctxs...) 83 | } 84 | 85 | func (r *TransactionSigner) SignTransaction(tx *transaction.TransactionWithScriptGroups, contexts ...*transaction.Context) ([]int, error) { 86 | var err error 87 | signedIndex := make([]int, 0) 88 | for i, group := range tx.ScriptGroups { 89 | if err := checkScriptGroup(group); err != nil { 90 | return signedIndex, err 91 | } 92 | key := hash(group.Script.CodeHash, group.GroupType) 93 | signer := r.signers[key] 94 | if signer != nil { 95 | for _, ctx := range contexts { 96 | var signed bool 97 | if signed, err = signer.SignTransaction(tx.TxView, group, ctx); err != nil { 98 | return signedIndex, err 99 | } 100 | if signed { 101 | signedIndex = append(signedIndex, i) 102 | break 103 | } 104 | } 105 | } 106 | } 107 | return signedIndex, nil 108 | } 109 | 110 | func checkScriptGroup(group *transaction.ScriptGroup) error { 111 | if group == nil { 112 | return fmt.Errorf("nil ScriptGroup") 113 | } 114 | switch group.GroupType { 115 | case types.ScriptTypeType: 116 | if len(group.OutputIndices)+len(group.InputIndices) < 0 { 117 | return fmt.Errorf("groupType is Type but OutputIndices and InputIndices are empty") 118 | } 119 | case types.ScriptTypeLock: 120 | if len(group.InputIndices) == 0 { 121 | return fmt.Errorf("groupType is Lock but InputIndices is empty") 122 | } 123 | default: 124 | return fmt.Errorf("unknown group type %s", group.GroupType) 125 | } 126 | return nil 127 | } 128 | -------------------------------------------------------------------------------- /transaction/signer_test/fixture/acp_one_input.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "0x6fc935dad260867c749cf1ba6602d5f5ed7fb1131f1beb65be2d342e912eaafe" 5 | } 6 | ], 7 | "raw_transaction": { 8 | "tx_view": { 9 | "cell_deps": [ 10 | { 11 | "dep_type": "dep_group", 12 | "out_point": { 13 | "index": "0x0", 14 | "tx_hash": "0xec26b0f85ed839ece5f11c4c4e837ec359f5adc4420410f6453b1f6b60fb96a6" 15 | } 16 | }, 17 | { 18 | "dep_type": "dep_group", 19 | "out_point": { 20 | "index": "0x0", 21 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37" 22 | } 23 | }, 24 | { 25 | "dep_type": "code", 26 | "out_point": { 27 | "index": "0x0", 28 | "tx_hash": "0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769" 29 | } 30 | } 31 | ], 32 | "hash": "0x5be4c85879bbe5c9eceb799c0ef09e9c839a14ec4f3ae9b107bf7b5c5c3f24ce", 33 | "header_deps": [], 34 | "inputs": [ 35 | { 36 | "previous_output": { 37 | "index": "0x1", 38 | "tx_hash": "0xd4287542c102b57476db7a63f2932ad5fabcf4c4b21de0c965f94afcc3255560" 39 | }, 40 | "since": "0x0" 41 | } 42 | ], 43 | "outputs": [ 44 | { 45 | "capacity": "0x35458af00", 46 | "lock": { 47 | "args": "0x1fc78a5fb55d20ebac89478f6181ce92bc06a741", 48 | "code_hash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", 49 | "hash_type": "type" 50 | }, 51 | "type": { 52 | "args": "0x96b4d3f7cc380ed4fe5597a2ab49a396920de4f4da153b0c6a0eae378229400c", 53 | "code_hash": "0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4", 54 | "hash_type": "type" 55 | } 56 | }, 57 | { 58 | "capacity": "0xf6eab7ac8e", 59 | "lock": { 60 | "args": "0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b", 61 | "code_hash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", 62 | "hash_type": "type" 63 | }, 64 | "type": { 65 | "args": "0xc772f4d885ca6285d87d82b8edc1643df9f3ce63c40d0f81f2a38c147328d430", 66 | "code_hash": "0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4", 67 | "hash_type": "type" 68 | } 69 | } 70 | ], 71 | "outputs_data": [ 72 | "0x00000000000000000000000000000000", 73 | "0x00000000000000000000000000000000" 74 | ], 75 | "version": "0x0", 76 | "witnesses": [ 77 | "0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 78 | ] 79 | }, 80 | "script_groups": [ 81 | { 82 | "script": { 83 | "code_hash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", 84 | "args": "0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b", 85 | "hash_type": "type" 86 | }, 87 | "group_type": "lock", 88 | "input_indices": [ 89 | "0x0" 90 | ], 91 | "output_indices": [] 92 | } 93 | ] 94 | }, 95 | "expected_witnesses": [ 96 | "0x550000001000000055000000550000004100000061fa2f71f90620340ab9c3826623cf792830d1f8cc0a809ba537663d8b7ab0bf7399aa7c21553c3ad6af8b10b5bb51960dcbaa4ed9f1b034bceec52e0d12b3fa00" 97 | ] 98 | } -------------------------------------------------------------------------------- /transaction/signer_test/fixture/omnilock_secp256k1_blake160_multisig_all_first.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "0x7438f7b35c355e3d2fb9305167a31a72d22ddeafb80a21cc99ff6329d92e8087", 5 | "omnilock_config": { 6 | "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00", 7 | "mode": "AUTH", 8 | "multisig_script": { 9 | "threshold": 2, 10 | "first_n": 0, 11 | "key_hashes": [ 12 | "0x7336b0ba900684cb3cb00f0d46d4f64c0994a562", 13 | "0x5724c1e3925a5206944d753a6f3edaedf977d77f" 14 | ] 15 | } 16 | } 17 | } 18 | ], 19 | "raw_transaction": { 20 | "tx_view": { 21 | "version": "0x0", 22 | "cell_deps": [ 23 | { 24 | "out_point": { 25 | "tx_hash": "0x27b62d8be8ed80b9f56ee0fe41355becdb6f6a40aeba82d3900434f43b1c8b60", 26 | "index": "0x0" 27 | }, 28 | "dep_type": "code" 29 | }, 30 | { 31 | "out_point": { 32 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", 33 | "index": "0x1" 34 | }, 35 | "dep_type": "dep_group" 36 | } 37 | ], 38 | "hash": "0xb2c1b732fb0b68e2da2102b3f29508ffc4da1a6499f284094c4990bfc8c4f56c", 39 | "header_deps": [], 40 | "inputs": [ 41 | { 42 | "previous_output": { 43 | "tx_hash": "0x0cbdea8a600e2405eeaa5863a9e3561b16d2b702fe85f74bfddb3bc87545b83e", 44 | "index": "0x0" 45 | }, 46 | "since": "0x0" 47 | }, 48 | { 49 | "previous_output": { 50 | "tx_hash": "0x0cbdea8a600e2405eeaa5863a9e3561b16d2b702fe85f74bfddb3bc87545b83e", 51 | "index": "0x1" 52 | }, 53 | "since": "0x0" 54 | } 55 | ], 56 | "outputs": [ 57 | { 58 | "capacity": "0xbaa315500", 59 | "lock": { 60 | "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", 61 | "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00", 62 | "hash_type": "type" 63 | } 64 | }, 65 | { 66 | "capacity": "0xdd2a73a872", 67 | "lock": { 68 | "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", 69 | "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00", 70 | "hash_type": "type" 71 | } 72 | } 73 | ], 74 | "outputs_data": [ 75 | "0x", 76 | "0x" 77 | ], 78 | "witnesses": [ 79 | "0xd600000010000000d6000000d6000000c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 80 | "0x" 81 | ] 82 | }, 83 | "script_groups": [ 84 | { 85 | "script": { 86 | "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", 87 | "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00", 88 | "hash_type": "type" 89 | }, 90 | "group_type": "lock", 91 | "input_indices": [ 92 | "0x0", 93 | "0x1" 94 | ], 95 | "output_indices": [] 96 | } 97 | ] 98 | }, 99 | "expected_witnesses": [ 100 | "0xd600000010000000d6000000d6000000c2000000c200000010000000c2000000c2000000ae000000000002027336b0ba900684cb3cb00f0d46d4f64c0994a5625724c1e3925a5206944d753a6f3edaedf977d77f1155a40d725a497c7cd9c766c9e214a059d280944783e897136a49cec3e70c7b26630928354f3321f763088eae7ebd60846d004698d0e28f762b5b7a08329de1010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 101 | "0x" 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /transaction/signer_test/fixture/omnilock_secp256k1_blake160_multisig_all_second.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "0x4fd809631a6aa6e3bb378dd65eae5d71df895a82c91a615a1e8264741515c79c", 5 | "omnilock_config": { 6 | "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00", 7 | "mode": "AUTH", 8 | "multisig_script": { 9 | "threshold": 2, 10 | "first_n": 0, 11 | "key_hashes": [ 12 | "0x7336b0ba900684cb3cb00f0d46d4f64c0994a562", 13 | "0x5724c1e3925a5206944d753a6f3edaedf977d77f" 14 | ] 15 | } 16 | } 17 | } 18 | ], 19 | "raw_transaction": { 20 | "tx_view": { 21 | "version": "0x0", 22 | "cell_deps": [ 23 | { 24 | "out_point": { 25 | "tx_hash": "0x27b62d8be8ed80b9f56ee0fe41355becdb6f6a40aeba82d3900434f43b1c8b60", 26 | "index": "0x0" 27 | }, 28 | "dep_type": "code" 29 | }, 30 | { 31 | "out_point": { 32 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", 33 | "index": "0x1" 34 | }, 35 | "dep_type": "dep_group" 36 | } 37 | ], 38 | "hash": "0xb2c1b732fb0b68e2da2102b3f29508ffc4da1a6499f284094c4990bfc8c4f56c", 39 | "header_deps": [], 40 | "inputs": [ 41 | { 42 | "previous_output": { 43 | "tx_hash": "0x0cbdea8a600e2405eeaa5863a9e3561b16d2b702fe85f74bfddb3bc87545b83e", 44 | "index": "0x0" 45 | }, 46 | "since": "0x0" 47 | }, 48 | { 49 | "previous_output": { 50 | "tx_hash": "0x0cbdea8a600e2405eeaa5863a9e3561b16d2b702fe85f74bfddb3bc87545b83e", 51 | "index": "0x1" 52 | }, 53 | "since": "0x0" 54 | } 55 | ], 56 | "outputs": [ 57 | { 58 | "capacity": "0xbaa315500", 59 | "lock": { 60 | "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", 61 | "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00", 62 | "hash_type": "type" 63 | } 64 | }, 65 | { 66 | "capacity": "0xdd2a73a872", 67 | "lock": { 68 | "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", 69 | "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00", 70 | "hash_type": "type" 71 | } 72 | } 73 | ], 74 | "outputs_data": [ 75 | "0x", 76 | "0x" 77 | ], 78 | "witnesses": [ 79 | "0xd600000010000000d6000000d6000000c2000000c200000010000000c2000000c2000000ae000000000002027336b0ba900684cb3cb00f0d46d4f64c0994a5625724c1e3925a5206944d753a6f3edaedf977d77f1155a40d725a497c7cd9c766c9e214a059d280944783e897136a49cec3e70c7b26630928354f3321f763088eae7ebd60846d004698d0e28f762b5b7a08329de1010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 80 | "0x" 81 | ] 82 | }, 83 | "script_groups": [ 84 | { 85 | "script": { 86 | "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", 87 | "args": "0x06bc9818d8a149cfc0cd0323386c46ba07920a037f00", 88 | "hash_type": "type" 89 | }, 90 | "group_type": "lock", 91 | "input_indices": [ 92 | "0x0", 93 | "0x1" 94 | ], 95 | "output_indices": [] 96 | } 97 | ] 98 | }, 99 | "expected_witnesses": [ 100 | "0xd600000010000000d6000000d6000000c2000000c200000010000000c2000000c2000000ae000000000002027336b0ba900684cb3cb00f0d46d4f64c0994a5625724c1e3925a5206944d753a6f3edaedf977d77f1155a40d725a497c7cd9c766c9e214a059d280944783e897136a49cec3e70c7b26630928354f3321f763088eae7ebd60846d004698d0e28f762b5b7a08329de1017052dd31a789b87de036de042aa620683a9ceaf450ef348cf6ef8746350df5d91c2bb4ca4f65605fae4512cedf290f9b1838d09620d5adb5c2b09201699caa5600", 101 | "0x" 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /transaction/signer_test/fixture/omnilock_secp256k1_blake160_sighash_all.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a", 5 | "omnilock_config": { 6 | "args": "0x004049ed9cec8a0d39c7a1e899f0dacb8a8c28ad1400", 7 | "mode": "AUTH" 8 | } 9 | } 10 | ], 11 | "raw_transaction": { 12 | "tx_view": { 13 | "cell_deps": [ 14 | { 15 | "dep_type": "code", 16 | "out_point": { 17 | "index": "0x0", 18 | "tx_hash": "0x27b62d8be8ed80b9f56ee0fe41355becdb6f6a40aeba82d3900434f43b1c8b60" 19 | } 20 | }, 21 | { 22 | "dep_type": "dep_group", 23 | "out_point": { 24 | "index": "0x0", 25 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37" 26 | } 27 | } 28 | ], 29 | "hash": "0xa4d4c83175de71e638727919a3339ca36462cfd522778ae69ec5278bcb7369ce", 30 | "header_deps": [], 31 | "inputs": [ 32 | { 33 | "previous_output": { 34 | "index": "0x0", 35 | "tx_hash": "0xa4894901899eff12bacb0e6d4bfe96e54c0461e8afe7388e8ab2ebb1626cf816" 36 | }, 37 | "since": "0x0" 38 | }, 39 | { 40 | "previous_output": { 41 | "index": "0x1", 42 | "tx_hash": "0xa4894901899eff12bacb0e6d4bfe96e54c0461e8afe7388e8ab2ebb1626cf816" 43 | }, 44 | "since": "0x0" 45 | } 46 | ], 47 | "outputs": [ 48 | { 49 | "capacity": "0xbaa315500", 50 | "lock": { 51 | "args": "0x004049ed9cec8a0d39c7a1e899f0dacb8a8c28ad1400", 52 | "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", 53 | "hash_type": "type" 54 | }, 55 | "type": null 56 | }, 57 | { 58 | "capacity": "0xdd2a73b230", 59 | "lock": { 60 | "args": "0x004049ed9cec8a0d39c7a1e899f0dacb8a8c28ad1400", 61 | "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", 62 | "hash_type": "type" 63 | }, 64 | "type": null 65 | } 66 | ], 67 | "outputs_data": [ 68 | "0x", 69 | "0x" 70 | ], 71 | "version": "0x0", 72 | "witnesses": [ 73 | "0x690000001000000069000000690000005500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 74 | "0x" 75 | ] 76 | }, 77 | "script_groups": [ 78 | { 79 | "script": { 80 | "code_hash": "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb", 81 | "args": "0x004049ed9cec8a0d39c7a1e899f0dacb8a8c28ad1400", 82 | "hash_type": "type" 83 | }, 84 | "group_type": "lock", 85 | "input_indices": [ 86 | "0x0", 87 | "0x1" 88 | ], 89 | "output_indices": [] 90 | } 91 | ] 92 | }, 93 | "expected_witnesses": [ 94 | "0x690000001000000069000000690000005500000055000000100000005500000055000000410000003434ca813dc378de0146aac8e60431fb52114acb3cb639f2fb2a479e1f219223532540413a154f440e939ee888c29221c0e8d6fef39402cbeedb6155317b356200", 95 | "0x" 96 | ] 97 | } 98 | -------------------------------------------------------------------------------- /transaction/signer_test/fixture/pw_one_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "0xe0ccb2548af279947b452efda4535dd4bcadf756d919701fcd4c382833277f85" 5 | } 6 | ], 7 | "raw_transaction": { 8 | "tx_view": { 9 | "cell_deps": [ 10 | { 11 | "dep_type": "dep_group", 12 | "out_point": { 13 | "index": "0x0", 14 | "tx_hash": "0xec26b0f85ed839ece5f11c4c4e837ec359f5adc4420410f6453b1f6b60fb96a6" 15 | } 16 | }, 17 | { 18 | "dep_type": "code", 19 | "out_point": { 20 | "index": "0x0", 21 | "tx_hash": "0x57a62003daeab9d54aa29b944fc3b451213a5ebdf2e232216a3cfed0dde61b38" 22 | } 23 | }, 24 | { 25 | "dep_type": "dep_group", 26 | "out_point": { 27 | "index": "0x0", 28 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37" 29 | } 30 | }, 31 | { 32 | "dep_type": "code", 33 | "out_point": { 34 | "index": "0x0", 35 | "tx_hash": "0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769" 36 | } 37 | } 38 | ], 39 | "hash": "0x8bc55b093c6dfd1b6178683e249841eafb2299cc616468f1d93e22f748aec691", 40 | "header_deps": [], 41 | "inputs": [ 42 | { 43 | "previous_output": { 44 | "index": "0x1", 45 | "tx_hash": "0x62ffb4a4dc4eae43210bfa157f283fd23a1842101050acff8784503db1100382" 46 | }, 47 | "since": "0x0" 48 | }, 49 | { 50 | "previous_output": { 51 | "index": "0x2", 52 | "tx_hash": "0x62ffb4a4dc4eae43210bfa157f283fd23a1842101050acff8784503db1100382" 53 | }, 54 | "since": "0x0" 55 | } 56 | ], 57 | "outputs": [ 58 | { 59 | "capacity": "0x8a94ae8fa", 60 | "lock": { 61 | "args": "0xadabffb9c27cb4af100ce7bca6903315220e87a2", 62 | "code_hash": "0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63", 63 | "hash_type": "type" 64 | }, 65 | "type": { 66 | "args": "0x7c7f0ee1d582c385342367792946cff3767fe02f26fd7f07dba23ae3c65b28bc", 67 | "code_hash": "0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4", 68 | "hash_type": "type" 69 | } 70 | } 71 | ], 72 | "outputs_data": [ 73 | "0x00000000000000000000000000000000" 74 | ], 75 | "version": "0x0", 76 | "witnesses": [ 77 | "0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 78 | "0x10000000100000001000000010000000" 79 | ] 80 | }, 81 | "script_groups": [ 82 | { 83 | "script": { 84 | "code_hash": "0x58c5f491aba6d61678b7cf7edf4910b1f5e00ec0cde2f42e0abb4fd9aff25a63", 85 | "args": "0xadabffb9c27cb4af100ce7bca6903315220e87a2", 86 | "hash_type": "type" 87 | }, 88 | "group_type": "lock", 89 | "input_indices": [ 90 | "0x0", 91 | "0x1" 92 | ], 93 | "output_indices": [] 94 | } 95 | ] 96 | }, 97 | "expected_witnesses": [ 98 | "0x5500000010000000550000005500000041000000fed79d78a964af9ba4151f43e8e513c003fa78a2086c89ec1157714225b85c0a360b9ba06c1acc0c1cb2fced6009b7bf3838daede1c4d6101bdc82b2080fcc0201", 99 | "0x10000000100000001000000010000000" 100 | ] 101 | } -------------------------------------------------------------------------------- /transaction/signer_test/fixture/secp256k1_blake160_multisig_all_first.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "bb3597c4daf5e2435fd47aeeb32847df32f1c710a6475f639e34b2275607eaa3", 5 | "multisig_script": { 6 | "threshold": 2, 7 | "first_n": 0, 8 | "key_hashes": [ 9 | "0x9b41c025515b00c24e2e2042df7b221af5c1891f", 10 | "0xe732dcd15b7618eb1d7a11e6a68e4579b5be0114" 11 | ] 12 | } 13 | } 14 | ], 15 | "raw_transaction": { 16 | "tx_view": { 17 | "version": "0x0", 18 | "cell_deps": [ 19 | { 20 | "dep_type": "dep_group", 21 | "out_point": { 22 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", 23 | "index": "0x1" 24 | } 25 | } 26 | ], 27 | "hash": "0x8b9027c407ee95f043b158b4bb5fe685b2e6159723b48712d91ec733b3068a5c", 28 | "header_deps": [], 29 | "inputs": [ 30 | { 31 | "previous_output": { 32 | "tx_hash": "0xb8e52009fb4dc0d63dd2a0547909bb1d66dff83e14645c70b25222c1e04ec593", 33 | "index": "0x0" 34 | }, 35 | "since": "0x0" 36 | } 37 | ], 38 | "outputs": [ 39 | { 40 | "capacity": "0x2540be400", 41 | "lock": { 42 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 43 | "args": "0x6cfd0e42b63a6fccf5eda9cef74d5fd0537fd55a", 44 | "hash_type": "type" 45 | } 46 | }, 47 | { 48 | "capacity": "0xe67aa34b00", 49 | "lock": { 50 | "code_hash": "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8", 51 | "args": "0x35ed7b939b4ac9cb447b82340fd8f26d344f7a62", 52 | "hash_type": "type" 53 | } 54 | } 55 | ], 56 | "outputs_data": [ 57 | "0x", 58 | "0x" 59 | ], 60 | "witnesses": [ 61 | "0xc200000010000000c2000000c2000000ae000000000002029b41c025515b00c24e2e2042df7b221af5c1891fe732dcd15b7618eb1d7a11e6a68e4579b5be011400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 62 | "0x1234" 63 | ] 64 | }, 65 | "script_groups": [ 66 | { 67 | "script": { 68 | "code_hash": "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8", 69 | "args": "0x35ed7b939b4ac9cb447b82340fd8f26d344f7a62", 70 | "hash_type": "type" 71 | }, 72 | "group_type": "lock", 73 | "input_indices": [ 74 | "0x0" 75 | ], 76 | "output_indices": [] 77 | } 78 | ] 79 | }, 80 | "expected_witnesses": [ 81 | "0xc200000010000000c2000000c2000000ae000000000002029b41c025515b00c24e2e2042df7b221af5c1891fe732dcd15b7618eb1d7a11e6a68e4579b5be0114309f6a35043ee852c77d525ab8181115467db6161ab2f2916ce53af28d855c5d6d3ca6efeeb640cf2d5e54f6173dfe5e8b69e163e8a34b952cb1eb233247a100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 82 | "0x1234" 83 | ] 84 | } -------------------------------------------------------------------------------- /transaction/signer_test/fixture/secp256k1_blake160_multisig_all_second.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "5271b0e474609ee280eb6ba07895718863a0eb8f114afd7217fa371fd48f6941", 5 | "multisig_script": { 6 | "threshold": 2, 7 | "first_n": 0, 8 | "key_hashes": [ 9 | "0x9b41c025515b00c24e2e2042df7b221af5c1891f", 10 | "0xe732dcd15b7618eb1d7a11e6a68e4579b5be0114" 11 | ] 12 | } 13 | } 14 | ], 15 | "raw_transaction": { 16 | "tx_view": { 17 | "version": "0x0", 18 | "cell_deps": [ 19 | { 20 | "dep_type": "dep_group", 21 | "out_point": { 22 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", 23 | "index": "0x1" 24 | } 25 | } 26 | ], 27 | "hash": "0x8b9027c407ee95f043b158b4bb5fe685b2e6159723b48712d91ec733b3068a5c", 28 | "header_deps": [], 29 | "inputs": [ 30 | { 31 | "previous_output": { 32 | "tx_hash": "0xb8e52009fb4dc0d63dd2a0547909bb1d66dff83e14645c70b25222c1e04ec593", 33 | "index": "0x0" 34 | }, 35 | "since": "0x0" 36 | } 37 | ], 38 | "outputs": [ 39 | { 40 | "capacity": "0x2540be400", 41 | "lock": { 42 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 43 | "args": "0x6cfd0e42b63a6fccf5eda9cef74d5fd0537fd55a", 44 | "hash_type": "type" 45 | } 46 | }, 47 | { 48 | "capacity": "0xe67aa34b00", 49 | "lock": { 50 | "code_hash": "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8", 51 | "args": "0x35ed7b939b4ac9cb447b82340fd8f26d344f7a62", 52 | "hash_type": "type" 53 | } 54 | } 55 | ], 56 | "outputs_data": [ 57 | "0x", 58 | "0x" 59 | ], 60 | "witnesses": [ 61 | "0xc200000010000000c2000000c2000000ae000000000002029b41c025515b00c24e2e2042df7b221af5c1891fe732dcd15b7618eb1d7a11e6a68e4579b5be0114309f6a35043ee852c77d525ab8181115467db6161ab2f2916ce53af28d855c5d6d3ca6efeeb640cf2d5e54f6173dfe5e8b69e163e8a34b952cb1eb233247a100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 62 | "0x1234" 63 | ] 64 | }, 65 | "script_groups": [ 66 | { 67 | "script": { 68 | "code_hash": "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8", 69 | "args": "0x35ed7b939b4ac9cb447b82340fd8f26d344f7a62", 70 | "hash_type": "type" 71 | }, 72 | "group_type": "lock", 73 | "input_indices": [ 74 | "0x0" 75 | ], 76 | "output_indices": [] 77 | } 78 | ] 79 | }, 80 | "expected_witnesses": [ 81 | "0xc200000010000000c2000000c2000000ae000000000002029b41c025515b00c24e2e2042df7b221af5c1891fe732dcd15b7618eb1d7a11e6a68e4579b5be0114309f6a35043ee852c77d525ab8181115467db6161ab2f2916ce53af28d855c5d6d3ca6efeeb640cf2d5e54f6173dfe5e8b69e163e8a34b952cb1eb233247a10001bf990766e3efa8253c58330f4366bef09f49cbe4efa47b2b491541ad919c90c33ca6763c5780693efd0efea89c1645e8992520e8e551c1dec50fc41fac14b3a401", 82 | "0x1234" 83 | ] 84 | } -------------------------------------------------------------------------------- /transaction/signer_test/fixture/secp256k1_blake160_sighash_all_extra_witness.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "0x6fc935dad260867c749cf1ba6602d5f5ed7fb1131f1beb65be2d342e912eaafe" 5 | } 6 | ], 7 | "raw_transaction": { 8 | "tx_view": { 9 | "cell_deps": [ 10 | { 11 | "out_point": { 12 | "tx_hash": "0xec26b0f85ed839ece5f11c4c4e837ec359f5adc4420410f6453b1f6b60fb96a6", 13 | "index": "0x0" 14 | }, 15 | "dep_type": "dep_group" 16 | }, 17 | { 18 | "out_point": { 19 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37", 20 | "index": "0x0" 21 | }, 22 | "dep_type": "dep_group" 23 | }, 24 | { 25 | "out_point": { 26 | "tx_hash": "0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769", 27 | "index": "0x0" 28 | }, 29 | "dep_type": "code" 30 | } 31 | ], 32 | "hash": "0x955c8a9c8fb0027f3ebb51f052fcdd64995189dcaa3ecb954ce8389ca74c3ce1", 33 | "header_deps": [], 34 | "inputs": [ 35 | { 36 | "previous_output": { 37 | "tx_hash": "0x0892e8f6a56439c4cf4b46571ea3cda73c9c6c52082e73dea42ead6acd1cc9fd", 38 | "index": "0x0" 39 | }, 40 | "since": "0x0" 41 | } 42 | ], 43 | "outputs": [ 44 | { 45 | "capacity": "0x2540be400", 46 | "lock": { 47 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 48 | "args": "0x839f1806e85b40c13d3c73866045476cc9a8c214", 49 | "hash_type": "type" 50 | } 51 | }, 52 | { 53 | "capacity": "0xe674ad6a00", 54 | "lock": { 55 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 56 | "args": "0x05a1fabfa84db9e538e2e7fe3ca9adf849f55ce0", 57 | "hash_type": "type" 58 | } 59 | } 60 | ], 61 | "outputs_data": [ 62 | "0x", 63 | "0x" 64 | ], 65 | "version": "0x0", 66 | "witnesses": [ 67 | "0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 68 | "0x68656c6c6f20776f726c64" 69 | ] 70 | }, 71 | "script_groups": [ 72 | { 73 | "script": { 74 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 75 | "args": "0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b", 76 | "hash_type": "type" 77 | }, 78 | "group_type": "lock", 79 | "input_indices": [ 80 | "0x0" 81 | ], 82 | "output_indices": [] 83 | } 84 | ] 85 | }, 86 | "expected_witnesses": [ 87 | "0x55000000100000005500000055000000410000005c405dd903a2ea281d879f59787f2a1aa304b48f2c2ab9c73614026b488914865704a1ed526622bbb1913d7ab1ba196f81bc7e8d4d659727881c140673ba142a01", 88 | "0x68656c6c6f20776f726c64" 89 | ] 90 | } -------------------------------------------------------------------------------- /transaction/signer_test/fixture/secp256k1_blake160_sighash_all_one_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "0x6fc935dad260867c749cf1ba6602d5f5ed7fb1131f1beb65be2d342e912eaafe" 5 | } 6 | ], 7 | "raw_transaction": { 8 | "tx_view": { 9 | "cell_deps": [ 10 | { 11 | "dep_type": "dep_group", 12 | "out_point": { 13 | "index": "0x0", 14 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37" 15 | } 16 | }, 17 | { 18 | "dep_type": "dep_group", 19 | "out_point": { 20 | "index": "0x0", 21 | "tx_hash": "0xec26b0f85ed839ece5f11c4c4e837ec359f5adc4420410f6453b1f6b60fb96a6" 22 | } 23 | }, 24 | { 25 | "dep_type": "code", 26 | "out_point": { 27 | "index": "0x0", 28 | "tx_hash": "0xe12877ebd2c3c364dc46c5c992bcfaf4fee33fa13eebdf82c591fc9825aab769" 29 | } 30 | } 31 | ], 32 | "hash": "0x150ab94cc3d35daf96d0d55a4efc420323adcc36662b2bdcab826e16ce38dd81", 33 | "header_deps": [], 34 | "inputs": [ 35 | { 36 | "previous_output": { 37 | "index": "0x0", 38 | "tx_hash": "0xc43c8198c4ead3dce957cc3a3ab2ca6c8f4c23ad9d74cb083daefd5d2e4fba4e" 39 | }, 40 | "since": "0x0" 41 | }, 42 | { 43 | "previous_output": { 44 | "index": "0x0", 45 | "tx_hash": "0x469100c2149317341756e80f369c94ed2a84b58349ff41985819d49413377ae8" 46 | }, 47 | "since": "0x0" 48 | } 49 | ], 50 | "outputs": [ 51 | { 52 | "capacity": "0xea46318821", 53 | "lock": { 54 | "args": "0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b", 55 | "code_hash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", 56 | "hash_type": "type" 57 | }, 58 | "type": { 59 | "args": "0xc772f4d885ca6285d87d82b8edc1643df9f3ce63c40d0f81f2a38c147328d430", 60 | "code_hash": "0xc5e5dcf215925f7ef4dfaf5f4b4f105bc321c02776d6e7d52a1db3fcd9d011a4", 61 | "hash_type": "type" 62 | } 63 | } 64 | ], 65 | "outputs_data": [ 66 | "0x00000000000000000000000000000000" 67 | ], 68 | "version": "0x0", 69 | "witnesses": [ 70 | "0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 71 | "0x10000000100000001000000010000000" 72 | ] 73 | }, 74 | "script_groups": [ 75 | { 76 | "script": { 77 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 78 | "args": "0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b", 79 | "hash_type": "type" 80 | }, 81 | "group_type": "lock", 82 | "input_indices": [ 83 | "0x0", 84 | "0x1" 85 | ], 86 | "output_indices": [] 87 | } 88 | ] 89 | }, 90 | "expected_witnesses": [ 91 | "0x5500000010000000550000005500000041000000ed0c2ec9523029ed21be22fce92ff158d4da25da0aebd050cdd4b04a9c980ccf5f76afc8d33fa890fcb231bde3eba46b2932d4aaecd4df559ecc3d268d90ef8c01", 92 | "0x10000000100000001000000010000000" 93 | ] 94 | } -------------------------------------------------------------------------------- /transaction/signer_test/fixture/secp256k1_blake160_sighash_all_one_input.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "0x6fc935dad260867c749cf1ba6602d5f5ed7fb1131f1beb65be2d342e912eaafe" 5 | } 6 | ], 7 | "raw_transaction": { 8 | "tx_view": { 9 | "cell_deps": [ 10 | { 11 | "dep_type": "dep_group", 12 | "out_point": { 13 | "index": "0x0", 14 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37" 15 | } 16 | }, 17 | { 18 | "dep_type": "dep_group", 19 | "out_point": { 20 | "index": "0x0", 21 | "tx_hash": "0xec26b0f85ed839ece5f11c4c4e837ec359f5adc4420410f6453b1f6b60fb96a6" 22 | } 23 | } 24 | ], 25 | "hash": "0x7be5f1df2c5eb2f33bcf20603774e485c78ab7616e059908715b4a8200e8949f", 26 | "header_deps": [], 27 | "inputs": [ 28 | { 29 | "previous_output": { 30 | "index": "0x0", 31 | "tx_hash": "0xbaf3371f487a0d40f8ebc341a34b93a2d36e1d9f77b9533fb8c579c87958b7aa" 32 | }, 33 | "since": "0x0" 34 | } 35 | ], 36 | "outputs": [ 37 | { 38 | "capacity": "0x2540be400", 39 | "lock": { 40 | "args": "0x05a1fabfa84db9e538e2e7fe3ca9adf849f55ce0", 41 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 42 | "hash_type": "type" 43 | }, 44 | "type": null 45 | }, 46 | { 47 | "capacity": "0x368f7cadfb00", 48 | "lock": { 49 | "args": "0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b", 50 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 51 | "hash_type": "type" 52 | }, 53 | "type": null 54 | } 55 | ], 56 | "outputs_data": [ 57 | "0x", 58 | "0x" 59 | ], 60 | "version": "0x0", 61 | "witnesses": [ 62 | "0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 63 | ] 64 | }, 65 | "script_groups": [ 66 | { 67 | "script": { 68 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 69 | "args": "0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b", 70 | "hash_type": "type" 71 | }, 72 | "group_type": "lock", 73 | "input_indices": [ 74 | "0x0" 75 | ], 76 | "output_indices": [] 77 | } 78 | ] 79 | }, 80 | "expected_witnesses": [ 81 | "0x550000001000000055000000550000004100000090b18cc17b8c67e20075ffcffe82d079e0b6a78cb3184157d78962bdd5a648d82c9bc8e1bbe87e7b8b0661440c1060f939be85d26742148e08dc58743a900df401" 82 | ] 83 | } -------------------------------------------------------------------------------- /transaction/signer_test/fixture/secp256k1_blake160_sighash_all_two_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | { 4 | "private_key": "0x6fc935dad260867c749cf1ba6602d5f5ed7fb1131f1beb65be2d342e912eaafe" 5 | }, 6 | { 7 | "private_key": "0x9d8ca87d75d150692211fa62b0d30de4d1ee6c530d5678b40b8cedacf0750d0f" 8 | } 9 | ], 10 | "raw_transaction": { 11 | "tx_view": { 12 | "cell_deps": [ 13 | { 14 | "dep_type": "dep_group", 15 | "out_point": { 16 | "index": "0x0", 17 | "tx_hash": "0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37" 18 | } 19 | } 20 | ], 21 | "hash": "0x7be5f1df2c5eb2f33bcf20603774e485c78ab7616e059908715b4a8200e8949f", 22 | "header_deps": [], 23 | "inputs": [ 24 | { 25 | "previous_output": { 26 | "index": "0x1", 27 | "tx_hash": "0x42dc1a2cba2a12303f4b901a78fa029b742a56f1e46b9026302d384dafdc1200" 28 | }, 29 | "since": "0x0" 30 | }, 31 | { 32 | "previous_output": { 33 | "index": "0x4", 34 | "tx_hash": "0x000b0db9fdca54cc2f9426e3868117c727d9274eb800ed54c054247f4d3ef9d2" 35 | }, 36 | "since": "0x0" 37 | }, 38 | { 39 | "previous_output": { 40 | "index": "0x1", 41 | "tx_hash": "0xb312eb6c372c6a35d18fd178bee5a4826ad08d3685b66edffd5cdd8e1542055a" 42 | }, 43 | "since": "0x0" 44 | } 45 | ], 46 | "outputs": [ 47 | { 48 | "capacity": "0x3c5985ef6", 49 | "lock": { 50 | "args": "0xaf0b41c627807fbddcee75afa174d5a7e5135ebd", 51 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 52 | "hash_type": "type" 53 | }, 54 | "type": null 55 | }, 56 | { 57 | "capacity": "0x34e62d2f0", 58 | "lock": { 59 | "args": "0xc5f94bc585ba8ddb71cfbc94c79d64d728affc0b", 60 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 61 | "hash_type": "type" 62 | }, 63 | "type": null 64 | }, 65 | { 66 | "capacity": "0x2b369ea81", 67 | "lock": { 68 | "args": "0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b", 69 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 70 | "hash_type": "type" 71 | }, 72 | "type": null 73 | } 74 | ], 75 | "outputs_data": [ 76 | "0x", 77 | "0x", 78 | "0x" 79 | ], 80 | "version": "0x0", 81 | "witnesses": [ 82 | "0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 83 | "0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 84 | "0x10000000100000001000000010000000" 85 | ] 86 | }, 87 | "script_groups": [ 88 | { 89 | "script": { 90 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 91 | "args": "0xaf0b41c627807fbddcee75afa174d5a7e5135ebd", 92 | "hash_type": "type" 93 | }, 94 | "group_type": "lock", 95 | "input_indices": [ 96 | "0x0" 97 | ], 98 | "output_indices": [] 99 | }, 100 | { 101 | "script": { 102 | "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 103 | "args": "0xa3b8598e1d53e6c5e89e8acb6b4c34d3adb13f2b", 104 | "hash_type": "type" 105 | }, 106 | "group_type": "lock", 107 | "input_indices": [ 108 | "0x1", 109 | "0x2" 110 | ], 111 | "output_indices": [] 112 | } 113 | ] 114 | }, 115 | "expected_witnesses": [ 116 | "0x5500000010000000550000005500000041000000860e2e7a830991dae28c2207263b22e6d66a41572bd315b41528bcaf6e26056a76d8c0e43157feed32741a4b038a665461ba93a91f6ce72d43034cc4fe8b9d3b00", 117 | "0x550000001000000055000000550000004100000022c333e42676e6749a806f952ca12079c2e7e634af2a5737288d2973645edae61db81fe84410c8fd20a5d0743d60429335aeb0a486c6c804fbc5424add690ec901", 118 | "0x10000000100000001000000010000000" 119 | ] 120 | } -------------------------------------------------------------------------------- /transaction/transaction.go: -------------------------------------------------------------------------------- 1 | // Package transaction implements CKB transaction signing. 2 | package transaction 3 | -------------------------------------------------------------------------------- /types/batch.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // refer BatchElem from go-ethereum 4 | type BatchTransactionItem struct { 5 | Hash Hash 6 | Result *TransactionWithStatus 7 | Error error 8 | } 9 | 10 | type BatchLiveCellItem struct { 11 | OutPoint OutPoint 12 | WithData bool 13 | Result *CellWithStatus 14 | Error error 15 | } 16 | -------------------------------------------------------------------------------- /types/chain_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/hex" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func TestScriptOccupiedCapacity(t *testing.T) { 11 | s := &Script{ 12 | CodeHash: HexToHash("0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88"), 13 | HashType: HashTypeType, 14 | Args: hexutil.MustDecode("0x36c329ed630d6ce750712a477543672adab57f4c"), 15 | } 16 | assert.Equal(t, uint64(5300000000), s.OccupiedCapacity()) 17 | } 18 | 19 | func TestCellOutputOccupiedCapacityOnlyLock(t *testing.T) { 20 | o := CellOutput{ 21 | Capacity: 100000000000, 22 | Lock: &Script{ 23 | CodeHash: HexToHash("0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88"), 24 | HashType: HashTypeType, 25 | Args: hexutil.MustDecode("0x59a27ef3ba84f061517d13f42cf44ed020610061"), 26 | }, 27 | Type: nil, 28 | } 29 | assert.Equal(t, uint64(6100000000), o.OccupiedCapacity([]byte{})) 30 | } 31 | 32 | func TestCellOutputOccupiedCapacityWithLockTypeAndData(t *testing.T) { 33 | args, _ := hex.DecodeString("3954acece65096bfa81258983ddb83915fc56bd8") 34 | s := &Script{ 35 | CodeHash: HexToHash("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"), 36 | HashType: ScriptHashType("type"), 37 | Args: args, 38 | } 39 | tArgs, _ := hex.DecodeString("32e555f3ff8e135cece1351a6a2971518392c1e30375c1e006ad0ce8eac07947") 40 | ts := &Script{ 41 | CodeHash: HexToHash("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"), 42 | HashType: ScriptHashType("type"), 43 | Args: tArgs, 44 | } 45 | o := CellOutput{ 46 | Capacity: 100000000000, 47 | Lock: s, 48 | Type: ts, 49 | } 50 | data, _ := hex.DecodeString("a0860100000000000000000000000000") 51 | assert.Equal(t, uint64(14200000000), o.OccupiedCapacity(data)) 52 | } 53 | -------------------------------------------------------------------------------- /types/common.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/common/hexutil" 8 | ) 9 | 10 | type Network uint 11 | type WitnessType uint 12 | type ScriptType string 13 | type ScriptSearchMode string 14 | 15 | const ( 16 | HashLength = 32 17 | 18 | NetworkMain Network = iota 19 | NetworkTest 20 | NetworkPreview 21 | 22 | WitnessTypeLock WitnessType = iota 23 | WitnessTypeInputType 24 | WitnessTypeOutputType 25 | 26 | ScriptTypeLock ScriptType = "lock" 27 | ScriptTypeType ScriptType = "type" 28 | 29 | ScriptSearchModePrefix ScriptSearchMode = "prefix" 30 | ScriptSearchModeExact ScriptSearchMode = "exact" 31 | ScriptSearchModePartial ScriptSearchMode = "partial" 32 | ) 33 | 34 | var ( 35 | hashT = reflect.TypeOf(Hash{}) 36 | ) 37 | 38 | type Hash [HashLength]byte 39 | 40 | func BytesToHash(b []byte) Hash { 41 | var h Hash 42 | h.SetBytes(b) 43 | return h 44 | } 45 | 46 | func HexToHash(s string) Hash { 47 | return BytesToHash(common.FromHex(s)) 48 | } 49 | 50 | func (h *Hash) SetBytes(b []byte) { 51 | if len(b) > len(h) { 52 | b = b[len(b)-HashLength:] 53 | } 54 | 55 | copy(h[HashLength-len(b):], b) 56 | } 57 | 58 | func (h Hash) Bytes() []byte { 59 | return h[:] 60 | } 61 | 62 | func (h Hash) Hex() string { 63 | return hexutil.Encode(h[:]) 64 | } 65 | 66 | func (h Hash) String() string { 67 | return h.Hex() 68 | } 69 | 70 | func (h *Hash) UnmarshalText(input []byte) error { 71 | return hexutil.UnmarshalFixedText("Hash", input, h[:]) 72 | } 73 | 74 | func (h *Hash) UnmarshalJSON(input []byte) error { 75 | return hexutil.UnmarshalFixedJSON(hashT, input, h[:]) 76 | } 77 | 78 | func (h Hash) MarshalText() ([]byte, error) { 79 | return hexutil.Bytes(h[:]).MarshalText() 80 | } 81 | -------------------------------------------------------------------------------- /types/epoch_parser.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type EpochParams struct { 4 | Length uint64 5 | Index uint64 6 | Number uint64 7 | } 8 | 9 | func ParseEpoch(epoch uint64) *EpochParams { 10 | length := (epoch >> 40) & 0xFFFF 11 | index := (epoch >> 24) & 0xFFFF 12 | number := epoch & 0xFFFFFF 13 | return &EpochParams{ 14 | Length: length, 15 | Index: index, 16 | Number: number, 17 | } 18 | } 19 | 20 | func (ep *EpochParams) Uint64() uint64 { 21 | return (32 << 56) + (ep.Length << 40) + (ep.Index << 24) + ep.Number 22 | } 23 | -------------------------------------------------------------------------------- /types/experiment.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type DryRunTransactionResult struct { 4 | Cycles uint64 `json:"cycles"` 5 | } 6 | -------------------------------------------------------------------------------- /types/generic.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "context" 4 | 5 | type GenericRPCClient interface { 6 | CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error 7 | Close() 8 | } 9 | -------------------------------------------------------------------------------- /types/molecule/blockchain.mol: -------------------------------------------------------------------------------- 1 | /* Basic Types */ 2 | 3 | // The `UintN` is used to store a `N` bits unsigned integer 4 | // as a byte array in little endian. 5 | array Uint32 [byte; 4]; 6 | array Uint64 [byte; 8]; 7 | array Uint128 [byte; 16]; 8 | array Byte32 [byte; 32]; 9 | array Uint256 [byte; 32]; 10 | 11 | vector Bytes ; 12 | option BytesOpt (Bytes); 13 | 14 | vector BytesVec ; 15 | vector Byte32Vec ; 16 | 17 | /* Types for Chain */ 18 | 19 | option ScriptOpt (Script); 20 | 21 | array ProposalShortId [byte; 10]; 22 | 23 | vector UncleBlockVec ; 24 | vector TransactionVec ; 25 | vector ProposalShortIdVec ; 26 | vector CellDepVec ; 27 | vector CellInputVec ; 28 | vector CellOutputVec ; 29 | 30 | table Script { 31 | code_hash: Byte32, 32 | hash_type: byte, 33 | args: Bytes, 34 | } 35 | 36 | struct OutPoint { 37 | tx_hash: Byte32, 38 | index: Uint32, 39 | } 40 | 41 | struct CellInput { 42 | since: Uint64, 43 | previous_output: OutPoint, 44 | } 45 | 46 | table CellOutput { 47 | capacity: Uint64, 48 | lock: Script, 49 | type_: ScriptOpt, 50 | } 51 | 52 | struct CellDep { 53 | out_point: OutPoint, 54 | dep_type: byte, 55 | } 56 | 57 | table RawTransaction { 58 | version: Uint32, 59 | cell_deps: CellDepVec, 60 | header_deps: Byte32Vec, 61 | inputs: CellInputVec, 62 | outputs: CellOutputVec, 63 | outputs_data: BytesVec, 64 | } 65 | 66 | table Transaction { 67 | raw: RawTransaction, 68 | witnesses: BytesVec, 69 | } 70 | 71 | struct RawHeader { 72 | version: Uint32, 73 | compact_target: Uint32, 74 | timestamp: Uint64, 75 | number: Uint64, 76 | epoch: Uint64, 77 | parent_hash: Byte32, 78 | transactions_root: Byte32, 79 | proposals_hash: Byte32, 80 | extra_hash: Byte32, 81 | dao: Byte32, 82 | } 83 | 84 | struct Header { 85 | raw: RawHeader, 86 | nonce: Uint128, 87 | } 88 | 89 | table UncleBlock { 90 | header: Header, 91 | proposals: ProposalShortIdVec, 92 | } 93 | 94 | table Block { 95 | header: Header, 96 | uncles: UncleBlockVec, 97 | transactions: TransactionVec, 98 | proposals: ProposalShortIdVec, 99 | } 100 | 101 | table BlockV1 { 102 | header: Header, 103 | uncles: UncleBlockVec, 104 | transactions: TransactionVec, 105 | proposals: ProposalShortIdVec, 106 | extension: Bytes, 107 | } 108 | 109 | table CellbaseWitness { 110 | lock: Script, 111 | message: Bytes, 112 | } 113 | 114 | table WitnessArgs { 115 | lock: BytesOpt, // Lock args 116 | input_type: BytesOpt, // Type args for input 117 | output_type: BytesOpt, // Type args for output 118 | } -------------------------------------------------------------------------------- /types/molecule_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestScriptPacking(t *testing.T) { 12 | rng := rand.New(rand.NewSource(1337)) 13 | for _, ty := range []types.ScriptHashType{types.HashTypeData, types.HashTypeType, types.HashTypeData1} { 14 | script := newScript(rng, ty) 15 | packed := types.UnpackScript(script.Pack()) 16 | assert.Equal(t, script, packed, "script packing/unpacking should satisfy the round-trip property") 17 | } 18 | } 19 | 20 | func newScript(rng *rand.Rand, ht types.ScriptHashType) *types.Script { 21 | hash := [32]byte{} 22 | rng.Read(hash[:]) 23 | argsLen := rng.Intn(4096) 24 | args := make([]byte, argsLen) 25 | rng.Read(args) 26 | return &types.Script{ 27 | CodeHash: hash, 28 | HashType: ht, 29 | Args: args, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /types/net.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type NodeAddress struct { 4 | Address string `json:"address"` 5 | Score uint64 `json:"score"` 6 | } 7 | 8 | type LocalNode struct { 9 | Version string `json:"version"` 10 | NodeId string `json:"node_id"` 11 | Active bool `json:"active"` 12 | Addresses []*NodeAddress `json:"addresses"` 13 | Protocols []*LocalNodeProtocol `json:"protocols"` 14 | Connections uint64 `json:"connections"` 15 | } 16 | 17 | type LocalNodeProtocol struct { 18 | Id uint64 `json:"id"` 19 | Name string `json:"name"` 20 | SupportVersions []string `json:"support_versions"` 21 | } 22 | 23 | type RemoteNode struct { 24 | Version string `json:"version"` 25 | NodeID string `json:"node_id"` 26 | Addresses []*NodeAddress `json:"addresses"` 27 | IsOutbound bool `json:"is_outbound"` 28 | ConnectedDuration uint64 `json:"connected_duration"` 29 | LastPingDuration *uint64 `json:"last_ping_duration,omitempty"` 30 | SyncState *PeerSyncState `json:"sync_state,omitempty"` 31 | Protocols []*RemoteNodeProtocol `json:"protocols"` 32 | } 33 | 34 | type RemoteNodeProtocol struct { 35 | ID uint64 `json:"id"` 36 | Version string `json:"version"` 37 | } 38 | 39 | type PeerSyncState struct { 40 | BestKnownHeaderHash *Hash `json:"best_known_header_hash,omitempty"` 41 | BestKnownHeaderNumber *uint64 `json:"best_known_header_number,omitempty"` 42 | LastCommonHeaderHash *Hash `json:"last_common_header_hash,omitempty"` 43 | LastCommonHeaderNumber *uint64 `json:"last_common_header_number,omitempty"` 44 | UnknownHeaderListSize uint64 `json:"unknown_header_list_size"` 45 | InflightCount uint64 `json:"inflight_count"` 46 | CanFetchCount uint64 `json:"can_fetch_count"` 47 | } 48 | 49 | type BannedAddress struct { 50 | Address string `json:"address"` 51 | BanReason string `json:"ban_reason"` 52 | BanUntil uint64 `json:"ban_until"` 53 | CreatedAt uint64 `json:"created_at"` 54 | } 55 | 56 | type SyncState struct { 57 | Ibd bool `json:"ibd"` 58 | BestKnownBlockNumber uint64 `json:"best_known_block_number"` 59 | BestKnownBlockTimestamp uint64 `json:"best_known_block_timestamp"` 60 | OrphanBlocksCount uint64 `json:"orphan_blocks_count"` 61 | InflightBlocksCount uint64 `json:"inflight_blocks_count"` 62 | AssumeValidTarget Hash `json:"assume_valid_target"` 63 | AssumeValidTargetReached bool `json:"assume_valid_target_reached"` 64 | MinChainWork uint64 `json:"min_chain_work"` 65 | MinChainWorkReached bool `json:"min_chain_work_reached"` 66 | FastTime uint64 `json:"fast_time"` 67 | LowTime uint64 `json:"low_time"` 68 | NormalTime uint64 `json:"normal_time"` 69 | TipHash Hash `json:"tip_hash"` 70 | TipNumber uint64 `json:"tip_number"` 71 | UnverifiedTipHash Hash `json:"unverified_tip_hash"` 72 | UnverifiedTipNumber uint64 `json:"unverified_tip_number"` 73 | } 74 | -------------------------------------------------------------------------------- /types/numeric/capacity.go: -------------------------------------------------------------------------------- 1 | package numeric 2 | 3 | type Capacity uint64 4 | 5 | const scale = 100000000 6 | 7 | func NewCapacity(shannon uint64) Capacity { 8 | return Capacity(shannon) 9 | } 10 | 11 | func NewCapacityFromCKBytes(ckBytes float64) Capacity { 12 | return Capacity(ckBytes * scale) 13 | } 14 | 15 | func (c Capacity) Shannon() uint64 { 16 | return uint64(c) 17 | } 18 | 19 | func (c Capacity) CKBytes() float64 { 20 | return float64(c) / scale 21 | } 22 | -------------------------------------------------------------------------------- /types/numeric/capacity_test.go: -------------------------------------------------------------------------------- 1 | package numeric 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestCapacity(t *testing.T) { 9 | assert.True(t, NewCapacity(1234500000000) == NewCapacityFromCKBytes(12345.0)) 10 | 11 | assert.Equal(t, uint64(1234500000000), NewCapacity(1234500000000).Shannon()) 12 | assert.Equal(t, 12345.0, NewCapacity(1234500000000).CKBytes()) 13 | assert.Equal(t, uint64(1234500000000), NewCapacityFromCKBytes(12345.0).Shannon()) 14 | assert.Equal(t, 12345.0, NewCapacityFromCKBytes(12345.0).CKBytes()) 15 | 16 | assert.Equal(t, uint64(12345000000), NewCapacityFromCKBytes(123.45).Shannon()) 17 | assert.Equal(t, 123.45, NewCapacity(12345000000).CKBytes()) 18 | 19 | assert.Equal(t, uint64(12000000), NewCapacityFromCKBytes(0.12).Shannon()) 20 | assert.Equal(t, 0.12, NewCapacity(12000000).CKBytes()) 21 | 22 | assert.Equal(t, uint64(0), NewCapacityFromCKBytes(0).Shannon()) 23 | assert.Equal(t, 0.0, NewCapacity(0).CKBytes()) 24 | } 25 | -------------------------------------------------------------------------------- /types/numeric/since.go: -------------------------------------------------------------------------------- 1 | package numeric 2 | 3 | // define some useful const 4 | // https://github.com/nervosnetwork/ckb/blob/35392279150fe4e61b7904516be91bda18c46f05/test/src/utils.rs#L24 5 | 6 | type Since uint64 7 | 8 | const ( 9 | FlagSinceRelative = 0x8000000000000000 10 | FlagSinceEpochNumber = 0x2000000000000000 11 | FlagSinceBlockNumber = 0x0 12 | FlagSinceTimestamp = 0x4000000000000000 13 | ) 14 | 15 | func NewSinceFromRelativeBlockNumber(blockNumber uint64) Since { 16 | return Since(FlagSinceRelative | FlagSinceBlockNumber | blockNumber) 17 | } 18 | 19 | func NewSinceFromAbsoluteBlockNumber(blockNumber uint64) Since { 20 | return Since(FlagSinceBlockNumber | blockNumber) 21 | } 22 | 23 | func NewSinceFromRelativeEpochNumber(epochNumber uint64) Since { 24 | return Since(FlagSinceRelative | FlagSinceEpochNumber | epochNumber) 25 | } 26 | 27 | func NewSinceFromAbsoluteEpochNumber(epochNumber uint64) Since { 28 | return Since(FlagSinceEpochNumber | epochNumber) 29 | } 30 | 31 | func NewSinceFromRelativeTimestamp(timestamp uint64) Since { 32 | return Since(FlagSinceRelative | FlagSinceTimestamp | timestamp) 33 | } 34 | 35 | func NewSinceFromAbsoluteTimestamp(timestamp uint64) Since { 36 | return Since(FlagSinceTimestamp | timestamp) 37 | } 38 | -------------------------------------------------------------------------------- /types/numeric/since_test.go: -------------------------------------------------------------------------------- 1 | package numeric 2 | 3 | import "testing" 4 | 5 | func TestSinceFromAbsoluteBlockNumber(t *testing.T) { 6 | type args struct { 7 | blockNumber uint64 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want Since 13 | }{ 14 | { 15 | "absolute block number", 16 | args{ 17 | blockNumber: 12345, 18 | }, 19 | 12345, 20 | }, 21 | } 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | if got := NewSinceFromAbsoluteBlockNumber(tt.args.blockNumber); got != tt.want { 25 | t.Errorf("NewSinceFromAbsoluteBlockNumber() = %v, want %v", got, tt.want) 26 | } 27 | }) 28 | } 29 | } 30 | 31 | func TestSinceFromAbsoluteEpochNumber(t *testing.T) { 32 | type args struct { 33 | epochNumber uint64 34 | } 35 | tests := []struct { 36 | name string 37 | args args 38 | want Since 39 | }{ 40 | { 41 | "absolute epoch number", 42 | args{ 43 | epochNumber: 1024, 44 | }, 45 | 2305843009213694976, 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | if got := NewSinceFromAbsoluteEpochNumber(tt.args.epochNumber); got != tt.want { 51 | t.Errorf("NewSinceFromAbsoluteEpochNumber() = %v, want %v", got, tt.want) 52 | } 53 | }) 54 | } 55 | } 56 | 57 | func TestSinceFromAbsoluteTimestamp(t *testing.T) { 58 | type args struct { 59 | timestamp uint64 60 | } 61 | tests := []struct { 62 | name string 63 | args args 64 | want Since 65 | }{ 66 | { 67 | "absolute timestamp", 68 | args{ 69 | timestamp: 1585699200, 70 | }, 71 | 0x400000005e83d980, 72 | }, 73 | } 74 | for _, tt := range tests { 75 | t.Run(tt.name, func(t *testing.T) { 76 | if got := NewSinceFromAbsoluteTimestamp(tt.args.timestamp); got != tt.want { 77 | t.Errorf("NewSinceFromAbsoluteTimestamp() = %v, want %v", got, tt.want) 78 | } 79 | }) 80 | } 81 | } 82 | 83 | func TestSinceFromRelativeBlockNumber(t *testing.T) { 84 | type args struct { 85 | blockNumber uint64 86 | } 87 | tests := []struct { 88 | name string 89 | args args 90 | want Since 91 | }{ 92 | { 93 | "relative block number", 94 | args{ 95 | blockNumber: 100, 96 | }, 97 | 9223372036854775908, 98 | }, 99 | } 100 | for _, tt := range tests { 101 | t.Run(tt.name, func(t *testing.T) { 102 | if got := NewSinceFromRelativeBlockNumber(tt.args.blockNumber); got != tt.want { 103 | t.Errorf("NewSinceFromRelativeBlockNumber() = %v, want %v", got, tt.want) 104 | } 105 | }) 106 | } 107 | } 108 | 109 | func TestSinceFromRelativeEpochNumber(t *testing.T) { 110 | type args struct { 111 | epochNumber uint64 112 | } 113 | tests := []struct { 114 | name string 115 | args args 116 | want Since 117 | }{ 118 | { 119 | "relative epoch number", 120 | args{ 121 | epochNumber: 6, 122 | }, 123 | 11529215046068469766, 124 | }, 125 | } 126 | for _, tt := range tests { 127 | t.Run(tt.name, func(t *testing.T) { 128 | if got := NewSinceFromRelativeEpochNumber(tt.args.epochNumber); got != tt.want { 129 | t.Errorf("NewSinceFromRelativeEpochNumber() = %v, want %v", got, tt.want) 130 | } 131 | }) 132 | } 133 | } 134 | 135 | func TestSinceFromRelativeTimestamp(t *testing.T) { 136 | type args struct { 137 | timestamp uint64 138 | } 139 | tests := []struct { 140 | name string 141 | args args 142 | want Since 143 | }{ 144 | { 145 | "relative timestamp", 146 | args{ 147 | timestamp: 14 * 24 * 60 * 60, 148 | }, 149 | 0xc000000000127500, 150 | }, 151 | } 152 | for _, tt := range tests { 153 | t.Run(tt.name, func(t *testing.T) { 154 | if got := NewSinceFromRelativeTimestamp(tt.args.timestamp); got != tt.want { 155 | t.Errorf("NewSinceFromRelativeTimestamp() = %v, want %v", got, tt.want) 156 | } 157 | }) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /types/pool.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "encoding/json" 4 | 5 | // /// The uncle block template of the new block for miners. 6 | // #[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Debug, JsonSchema)] 7 | // pub struct UncleTemplate { 8 | // /// The uncle block hash. 9 | // pub hash: H256, 10 | // /// Whether miners must include this uncle in the submit block. 11 | // pub required: bool, 12 | // /// The proposals of the uncle block. 13 | // /// 14 | // /// Miners must keep this unchanged when including this uncle in the new block. 15 | // pub proposals: Vec, 16 | // /// The header of the uncle block. 17 | // /// 18 | // /// Miners must keep this unchanged when including this uncle in the new block. 19 | // pub header: Header, 20 | // } 21 | 22 | type UncleTemplate struct { 23 | Hash Hash `json:"hash"` 24 | Required bool `json:"required"` 25 | Proposals []string `json:"proposals"` 26 | Header *Header `json:"header"` 27 | } 28 | 29 | type BlockTemplate struct { 30 | Version uint32 `json:"version"` 31 | CompactTarget uint32 `json:"compact_target"` 32 | CurrentTime uint64 `json:"current_time"` 33 | Number uint64 `json:"number"` 34 | Epoch uint64 `json:"epoch"` 35 | ParentHash Hash `json:"parent_hash"` 36 | CyclesLimit uint64 `json:"cycles_limit"` 37 | BytesLimit uint64 `json:"bytes_limit"` 38 | UnclesCountLimit uint64 `json:"uncles_count_limit"` 39 | Uncles []UncleTemplate `json:"uncles"` 40 | Transactions []TransactionTemplate `json:"transactions"` 41 | Proposals []string `json:"proposals"` 42 | Cellbase CellbaseTemplate `json:"cellbase"` 43 | WorkId uint64 `json:"work_id"` 44 | Dao Hash `json:"dao"` 45 | Extension *json.RawMessage `json:"extension"` 46 | } 47 | 48 | type CellbaseTemplate struct { 49 | Hash Hash `json:"hash"` 50 | Cycles *uint64 `json:"cycles"` 51 | Data Transaction `json:"data"` 52 | } 53 | 54 | type TransactionTemplate struct { 55 | Hash Hash `json:"hash"` 56 | Required bool `json:"required"` 57 | Cycles *uint64 `json:"cycles"` 58 | Depends *[]uint64 `json:"depends"` 59 | Data Transaction `json:"data"` 60 | } 61 | 62 | type TxPoolInfo struct { 63 | LastTxsUpdatedAt uint64 `json:"last_txs_updated_at"` 64 | MaxTxPoolSize uint64 `json:"max_tx_pool_size"` 65 | MinFeeRate uint64 `json:"min_fee_rate"` 66 | MinRbfRate uint64 `json:"min_fee_rate"` 67 | Orphan uint64 `json:"orphan"` 68 | Pending uint64 `json:"pending"` 69 | Proposed uint64 `json:"proposed"` 70 | TipHash Hash `json:"tip_hash"` 71 | TipNumber uint64 `json:"tip_number"` 72 | TotalTxCycles uint64 `json:"total_tx_cycles"` 73 | TotalTxSize uint64 `json:"total_tx_size"` 74 | TxSizeLimit uint64 `json:"tx_size_limit"` 75 | VerifyQueueSize uint64 `json:"verify_queue_size"` 76 | } 77 | 78 | type RawTxPool struct { 79 | Pending []Hash `json:"pending"` 80 | Proposed []Hash `json:"proposed"` 81 | } 82 | 83 | type RawTxPoolVerbose struct { 84 | Pending map[Hash]TxPoolEntry `json:"pending"` 85 | Proposed map[Hash]TxPoolEntry `json:"proposed"` 86 | Confilicted []Hash `json:"conflicted"` 87 | } 88 | 89 | type TxPoolEntry struct { 90 | Cycles uint64 `json:"cycles"` 91 | Size uint64 `json:"size"` 92 | Fee uint64 `json:"fee"` 93 | AncestorsSize uint64 `json:"ancestors_size"` 94 | AncestorsCycles uint64 `json:"ancestors_cycles"` 95 | AncestorsCount uint64 `json:"ancestors_count"` 96 | Timestamp uint64 `json:"timestamp"` 97 | } 98 | 99 | type AncestorsScoreSortKey struct { 100 | AncestorsFee uint64 `json:"ancestors_fee"` 101 | AncestorsWeight uint64 `json:"ancestors_weight"` 102 | Fee uint64 `json:"fee"` 103 | Weight uint64 `json:"weight"` 104 | } 105 | 106 | type PoolTxDetailInfo struct { 107 | AncestorsCount uint64 `json:"ancestors_count"` 108 | DescendantsCount uint64 `json:"descendants_count"` 109 | EntryStatus string `json:"entry_status"` 110 | PendingCount uint64 `json:"pending_count"` 111 | ProposedCount uint64 `json:"proposed_count"` 112 | RankInPending uint64 `json:"rank_in_pending"` 113 | ScoreSortKey AncestorsScoreSortKey `json:"score_sortkey"` 114 | Timestamp uint64 `json:"timestamp"` 115 | } 116 | 117 | type EntryCompleted struct { 118 | // Cached tx cycles 119 | Cycles uint64 `json:"cycles"` 120 | // Cached tx fee 121 | Fee uint64 `json:"fee"` 122 | } 123 | -------------------------------------------------------------------------------- /types/serialize.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/types/molecule" 7 | ) 8 | 9 | func SerializeUint32(n uint32) []byte { 10 | return PackUint32(n).AsSlice() 11 | } 12 | 13 | func SerializeUint64(n uint64) []byte { 14 | return PackUint64(n).AsSlice() 15 | } 16 | 17 | func (h Hash) Serialize() []byte { 18 | return h.Pack().AsSlice() 19 | } 20 | 21 | func (t ScriptHashType) Serialize() []byte { 22 | return t.Pack().AsSlice() 23 | } 24 | 25 | func (t DepType) Serialize() []byte { 26 | return t.Pack().AsSlice() 27 | } 28 | 29 | func (r *Script) Serialize() []byte { 30 | return r.Pack().AsSlice() 31 | } 32 | 33 | func (r *OutPoint) Serialize() []byte { 34 | return r.Pack().AsSlice() 35 | } 36 | 37 | func (r *CellInput) Serialize() []byte { 38 | return r.Pack().AsSlice() 39 | } 40 | 41 | func (r *CellOutput) Serialize() []byte { 42 | return r.Pack().AsSlice() 43 | } 44 | 45 | func (d *CellDep) Serialize() []byte { 46 | return d.Pack().AsSlice() 47 | } 48 | 49 | func (t *Transaction) SerializeWithoutWitnesses() []byte { 50 | return t.PackToRawTransaction().AsSlice() 51 | } 52 | 53 | func (t *Transaction) Serialize() []byte { 54 | return t.Pack().AsSlice() 55 | } 56 | 57 | func (w *WitnessArgs) Serialize() []byte { 58 | return w.Pack().AsSlice() 59 | } 60 | 61 | func SerializeHashTypeByte(hashType ScriptHashType) (byte, error) { 62 | switch hashType { 63 | case HashTypeData: 64 | return 0x00, nil 65 | case HashTypeType: 66 | return 0x01, nil 67 | case HashTypeData1: 68 | return 0x02, nil 69 | case HashTypeData2: 70 | return 0x04, nil 71 | default: 72 | return 0, errors.New(string("unknown hash type " + hashType)) 73 | } 74 | } 75 | 76 | func DeserializeHashTypeByte(hashType byte) (ScriptHashType, error) { 77 | switch hashType { 78 | case 0x00: 79 | return HashTypeData, nil 80 | case 0x01: 81 | return HashTypeType, nil 82 | case 0x02: 83 | return HashTypeData1, nil 84 | case 0x04: 85 | return HashTypeData2, nil 86 | default: 87 | return "", errors.New(fmt.Sprintf("invalid script hash_type: %x", hashType)) 88 | } 89 | } 90 | 91 | func DeserializeWitnessArgs(in []byte) (*WitnessArgs, error) { 92 | m, err := molecule.WitnessArgsFromSlice(in, false) 93 | if err != nil { 94 | return nil, err 95 | } 96 | return UnpackWitnessArgs(m), nil 97 | } 98 | -------------------------------------------------------------------------------- /types/serialize_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestDeserializeWitnessArgs(t *testing.T) { 10 | witnessArgsBytes := common.FromHex("0x2700000010000000180000002000000004000000123400000400000056780000030000009a0000") 11 | 12 | witnessArgs, err := DeserializeWitnessArgs(witnessArgsBytes) 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | 17 | assert.Equal(t, []byte{0x12, 0x34, 0x00, 0x00}, witnessArgs.Lock) 18 | assert.Equal(t, []byte{0x56, 0x78, 0x00, 0x00}, witnessArgs.InputType) 19 | assert.Equal(t, []byte{0x9a, 0x00, 0x00}, witnessArgs.OutputType) 20 | 21 | witnessArgsBytes = common.FromHex("0x10000000100000001000000010000000") 22 | witnessArgs, err = DeserializeWitnessArgs(witnessArgsBytes) 23 | if err != nil { 24 | t.Error(err) 25 | } 26 | assert.Nil(t, witnessArgs.Lock) 27 | assert.Nil(t, witnessArgs.InputType) 28 | assert.Nil(t, witnessArgs.OutputType) 29 | } 30 | -------------------------------------------------------------------------------- /types/stats.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | // pub struct Alert { 10 | // /// The identifier of the alert. Clients use id to filter duplicated alerts. 11 | // pub id: AlertId, 12 | // /// Cancel a previous sent alert. 13 | // pub cancel: AlertId, 14 | // /// Optionally set the minimal version of the target clients. 15 | // /// 16 | // /// See [Semantic Version](https://semver.org/) about how to specify a version. 17 | // pub min_version: Option, 18 | // /// Optionally set the maximal version of the target clients. 19 | // /// 20 | // /// See [Semantic Version](https://semver.org/) about how to specify a version. 21 | // pub max_version: Option, 22 | // /// Alerts are sorted by priority, highest first. 23 | // pub priority: AlertPriority, 24 | // /// The alert is expired after this timestamp. 25 | // pub notice_until: Timestamp, 26 | // /// Alert message. 27 | // pub message: String, 28 | // /// The list of required signatures. 29 | // pub signatures: Vec, 30 | // } 31 | type Alert struct { 32 | Id uint32 `json:"id"` 33 | Cancel uint32 `json:"cancel"` 34 | MinVersion *string `json:"min_version"` 35 | MaxVersion *string `json:"max_version"` 36 | Priority uint32 `json:"priority"` 37 | NoticeUntil uint64 `json:"notice_until"` 38 | Message string `json:"message"` 39 | Signatures []json.RawMessage `json:"signatures"` 40 | } 41 | 42 | type AlertMessage struct { 43 | Id uint32 `json:"id"` 44 | Message string `json:"message"` 45 | NoticeUntil uint64 `json:"notice_until"` 46 | Priority uint32 `json:"priority"` 47 | } 48 | 49 | type BlockchainInfo struct { 50 | Alerts []*AlertMessage `json:"alerts"` 51 | Chain string `json:"chain"` 52 | Difficulty *big.Int `json:"difficulty"` 53 | Epoch uint64 `json:"epoch"` 54 | IsInitialBlockDownload bool `json:"is_initial_block_download"` 55 | MedianTime uint64 `json:"median_time"` 56 | } 57 | 58 | // DeploymentState represents the possible states of a deployment. 59 | type DeploymentState int 60 | 61 | const ( 62 | // Defined is the first state that each softfork starts. 63 | Defined DeploymentState = iota 64 | // Started is the state for epochs past the `start` epoch. 65 | Started 66 | // LockedIn is the state for epochs after the first epoch period with STARTED epochs of which at least `threshold` has the associated bit set in `version`. 67 | LockedIn 68 | // Active is the state for all epochs after the LOCKED_IN epoch. 69 | Active 70 | // Failed is the state for epochs past the `timeout_epoch`, if LOCKED_IN was not reached. 71 | Failed 72 | ) 73 | 74 | // DeploymentPos represents the possible positions for deployments. 75 | type DeploymentPos int 76 | 77 | const ( 78 | // Testdummy represents a dummy deployment. 79 | Testdummy DeploymentPos = iota 80 | // LightClient represents the light client protocol deployment. 81 | LightClient 82 | ) 83 | 84 | // convert DeploymentPos to string 85 | func (d DeploymentPos) String() string { 86 | switch d { 87 | case Testdummy: 88 | return "Testdummy" 89 | case LightClient: 90 | return "LightClient" 91 | } 92 | return "Unknown" 93 | } 94 | 95 | // convert string to DeploymentPos 96 | func DeploymentPosFromString(s string) (DeploymentPos, error) { 97 | switch s { 98 | case "Testdummy": 99 | return Testdummy, nil 100 | case "LightClient": 101 | return LightClient, nil 102 | } 103 | return -1, fmt.Errorf("invalid DeploymentPos: %s", s) 104 | } 105 | 106 | // DeploymentInfo represents information about a deployment. 107 | type DeploymentInfo struct { 108 | Bit uint8 109 | Start uint64 110 | Timeout uint64 111 | MinActivationEpoch uint64 112 | Period uint64 113 | Threshold jsonRationalU256 114 | Since uint64 115 | State DeploymentState 116 | } 117 | 118 | // DeploymentsInfo represents information about multiple deployments. 119 | type DeploymentsInfo struct { 120 | Hash Hash 121 | Epoch uint64 122 | Deployments map[DeploymentPos]DeploymentInfo 123 | } 124 | -------------------------------------------------------------------------------- /utils/util.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/nervosnetwork/ckb-sdk-go/v2/indexer" 7 | "github.com/nervosnetwork/ckb-sdk-go/v2/rpc" 8 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 9 | "math/big" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | // GetMaxMatureBlockNumber return max mature block number 16 | func GetMaxMatureBlockNumber(client rpc.Client, ctx context.Context) (uint64, error) { 17 | var cellbaseMaturity *types.EpochParams 18 | cellbaseMaturity, err := getCellbaseMaturity(client, ctx, cellbaseMaturity) 19 | if err != nil { 20 | return 0, err 21 | } 22 | tipHeader, err := client.GetTipHeader(ctx) 23 | if err != nil { 24 | return 0, err 25 | } 26 | tipEpoch := types.ParseEpoch(tipHeader.Epoch) 27 | if tipEpoch.Number < cellbaseMaturity.Number { 28 | return 0, errors.New("there are no mature live cells") 29 | } else { 30 | maxMatureEpoch, err := client.GetEpochByNumber(ctx, tipEpoch.Number-cellbaseMaturity.Number) 31 | if err != nil { 32 | return 0, err 33 | } 34 | number, err := calcMaxMatureBlockNumber(tipEpoch, maxMatureEpoch.StartNumber, maxMatureEpoch.Length, cellbaseMaturity) 35 | if err != nil { 36 | return 0, err 37 | } 38 | return number, nil 39 | } 40 | } 41 | 42 | func getCellbaseMaturity(client rpc.Client, ctx context.Context, cellbaseMaturity *types.EpochParams) (*types.EpochParams, error) { 43 | nodeInfo, err := client.LocalNodeInfo(ctx) 44 | if err != nil { 45 | return nil, err 46 | } 47 | major, minor, _, err := parseNodeVersion(nodeInfo.Version) 48 | if err != nil { 49 | return nil, err 50 | } 51 | if major > 0 || minor >= 39 { 52 | consensus, err := client.GetConsensus(ctx) 53 | if err != nil { 54 | return nil, err 55 | } 56 | cellbaseMaturity = types.ParseEpoch(consensus.CellbaseMaturity) 57 | } else { 58 | cellbaseMaturity = &types.EpochParams{ 59 | Length: 1, 60 | Index: 0, 61 | Number: 4, 62 | } 63 | } 64 | return cellbaseMaturity, nil 65 | } 66 | 67 | // startNumber is maxMatureEpoch.StartNumber, length is maxMatureEpoch.Length 68 | func calcMaxMatureBlockNumber(tipEpoch *types.EpochParams, startNumber, length uint64, cellbaseMaturity *types.EpochParams) (uint64, error) { 69 | tipEpochR := big.NewRat( 70 | int64(tipEpoch.Number*tipEpoch.Length+tipEpoch.Index), 71 | int64(tipEpoch.Length), 72 | ) 73 | cellbaseMaturityR := big.NewRat( 74 | int64(cellbaseMaturity.Number*cellbaseMaturity.Length+cellbaseMaturity.Index), 75 | int64(cellbaseMaturity.Length), 76 | ) 77 | 78 | if isTipEpochLessThanCellbaseMaturity(tipEpochR, cellbaseMaturityR) { 79 | return 0, nil 80 | } else { 81 | epochDeltaR := big.NewRat(0, 1).Sub(tipEpochR, cellbaseMaturityR) 82 | num := new(big.Int).SetInt64(0).Div(epochDeltaR.Num(), epochDeltaR.Denom()).Int64() 83 | decimalR := big.NewRat(0, 1).Sub(epochDeltaR, big.NewRat(num, 1)) 84 | indexR := big.NewRat(0, 1).Mul(decimalR, big.NewRat(int64(length), 1)) 85 | iNum := new(big.Int).SetInt64(0).Div(indexR.Num(), indexR.Denom()).Uint64() 86 | blockNumber := iNum + startNumber 87 | 88 | return blockNumber, nil 89 | } 90 | } 91 | 92 | func isTipEpochLessThanCellbaseMaturity(tipEpochR, cellbaseMaturityR *big.Rat) bool { 93 | if tipEpochR.Cmp(cellbaseMaturityR) < 0 { 94 | return true 95 | } 96 | return false 97 | } 98 | 99 | func parseNodeVersion(nodeVersion string) (int, int, int, error) { 100 | reg, err := regexp.Compile("\\d+(\\.\\d+){0,2}") 101 | if err != nil { 102 | return 0, 0, 0, err 103 | } 104 | parts := reg.FindString(nodeVersion) 105 | //parts := strings.Split(nodeVersion, " (") 106 | versionArr := strings.Split(parts, ".") 107 | major, err := strconv.Atoi(versionArr[0]) 108 | if err != nil { 109 | return 0, 0, 0, err 110 | } 111 | minor, err := strconv.Atoi(versionArr[1]) 112 | if err != nil { 113 | return 0, 0, 0, err 114 | } 115 | patch, err := strconv.Atoi(versionArr[2]) 116 | if err != nil { 117 | return 0, 0, 0, err 118 | } 119 | return major, minor, patch, nil 120 | } 121 | 122 | // IsMature check if a cellbase live cell is mature 123 | func IsMature(cell *indexer.LiveCell, maxMatureBlockNumber uint64) bool { 124 | return cell.TxIndex > 0 || cell.BlockNumber == 0 || cell.BlockNumber <= maxMatureBlockNumber 125 | } 126 | -------------------------------------------------------------------------------- /utils/util_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/nervosnetwork/ckb-sdk-go/v2/types" 5 | "testing" 6 | ) 7 | 8 | func Test_calcMaxMatureBlockNumber(t *testing.T) { 9 | cellbaseMaturity := &types.EpochParams{ 10 | Length: 1, 11 | Index: 0, 12 | Number: 4, 13 | } 14 | type args struct { 15 | tipEpoch *types.EpochParams 16 | startNumber uint64 17 | length uint64 18 | cellbaseMaturity *types.EpochParams 19 | } 20 | tests := []struct { 21 | name string 22 | args args 23 | want uint64 24 | wantErr bool 25 | }{ 26 | { 27 | name: "at 3 epochs", 28 | args: args{ 29 | tipEpoch: &types.EpochParams{ 30 | Length: 1800, 31 | Index: 86, 32 | Number: 3, 33 | }, 34 | startNumber: 0, 35 | length: 3, 36 | cellbaseMaturity: cellbaseMaturity, 37 | }, 38 | want: 0, 39 | wantErr: false, 40 | }, 41 | { 42 | name: "at 4 epochs and has index", 43 | args: args{ 44 | tipEpoch: &types.EpochParams{ 45 | Length: 1800, 46 | Index: 86, 47 | Number: 4, 48 | }, 49 | startNumber: 0, 50 | length: 1000, 51 | cellbaseMaturity: cellbaseMaturity, 52 | }, 53 | want: 47, 54 | wantErr: false, 55 | }, 56 | { 57 | name: "at 4 epochs without index", 58 | args: args{ 59 | tipEpoch: &types.EpochParams{ 60 | Length: 1800, 61 | Index: 0, 62 | Number: 4, 63 | }, 64 | startNumber: 0, 65 | length: 1000, 66 | cellbaseMaturity: cellbaseMaturity, 67 | }, 68 | want: 0, 69 | wantErr: false, 70 | }, 71 | { 72 | name: "at 5 epochs", 73 | args: args{ 74 | tipEpoch: &types.EpochParams{ 75 | Length: 1800, 76 | Index: 900, 77 | Number: 5, 78 | }, 79 | startNumber: 2000, 80 | length: 1000, 81 | cellbaseMaturity: cellbaseMaturity, 82 | }, 83 | want: 2500, 84 | wantErr: false, 85 | }, 86 | } 87 | for _, tt := range tests { 88 | t.Run(tt.name, func(t *testing.T) { 89 | got, err := calcMaxMatureBlockNumber(tt.args.tipEpoch, tt.args.startNumber, tt.args.length, tt.args.cellbaseMaturity) 90 | if (err != nil) != tt.wantErr { 91 | t.Errorf("calcMaxMatureBlockNumber() error = %v, wantErr %v", err, tt.wantErr) 92 | return 93 | } 94 | if got != tt.want { 95 | t.Errorf("calcMaxMatureBlockNumber() got = %v, want %v", got, tt.want) 96 | } 97 | }) 98 | } 99 | } 100 | 101 | func Test_parseNodeVersion(t *testing.T) { 102 | type args struct { 103 | nodeVersion string 104 | } 105 | tests := []struct { 106 | name string 107 | args args 108 | want int 109 | want1 int 110 | want2 int 111 | wantErr bool 112 | }{ 113 | { 114 | name: "0.39.1", 115 | args: args{"0.39.1 (7f5d486 2021-01-06)"}, 116 | want: 0, 117 | want1: 39, 118 | want2: 1, 119 | wantErr: false, 120 | }, 121 | { 122 | name: "0.39.6", 123 | args: args{"0.39.6 (7f5d486 2021-01-06)"}, 124 | want: 0, 125 | want1: 39, 126 | want2: 6, 127 | wantErr: false, 128 | }, 129 | { 130 | name: "1.40.1", 131 | args: args{"1.40.1 (7f5d486 2021-01-06)"}, 132 | want: 1, 133 | want1: 40, 134 | want2: 1, 135 | wantErr: false, 136 | }, 137 | } 138 | for _, tt := range tests { 139 | t.Run(tt.name, func(t *testing.T) { 140 | got, got1, got2, err := parseNodeVersion(tt.args.nodeVersion) 141 | if (err != nil) != tt.wantErr { 142 | t.Errorf("parseNodeVersion() error = %v, wantErr %v", err, tt.wantErr) 143 | return 144 | } 145 | if got != tt.want { 146 | t.Errorf("parseNodeVersion() got = %v, want %v", got, tt.want) 147 | } 148 | if got1 != tt.want1 { 149 | t.Errorf("parseNodeVersion() got1 = %v, want %v", got1, tt.want1) 150 | } 151 | if got2 != tt.want2 { 152 | t.Errorf("parseNodeVersion() got2 = %v, want %v", got2, tt.want2) 153 | } 154 | }) 155 | } 156 | } 157 | --------------------------------------------------------------------------------