├── x
└── btcbridge
│ ├── module_simulation.go
│ ├── types
│ ├── asset.go
│ ├── merkle_proof.go
│ ├── message_update_params.go
│ ├── message_submit_headers.go
│ ├── expected_keepers.go
│ ├── message_submit_signature.go
│ ├── message_submit_withdraw_status.go
│ ├── message_update_senders.go
│ ├── message_withdraw_bitcoin.go
│ ├── message_submit_withdraw_transaction.go
│ ├── codec.go
│ ├── message_submit_deposit_transaction.go
│ ├── runes_test.go
│ ├── varint.go
│ ├── errors.go
│ ├── keys.go
│ ├── genesis.go
│ ├── signature.go
│ ├── params.go
│ ├── deposit_policy.go
│ └── runes.go
│ ├── keeper
│ ├── event.go
│ ├── util.go
│ ├── queries.go
│ ├── keeper.go
│ ├── keeper_test.go
│ ├── msg_server.go
│ └── keeper_deposit.go
│ ├── genesis.go
│ ├── genesis_test.go
│ ├── client
│ └── cli
│ │ ├── tx.go
│ │ └── query.go
│ └── module.go
├── .gitignore
├── testutil
├── sample
│ └── sample.go
├── nullify
│ └── nullify.go
├── keeper
│ └── btc_bridge.go
└── network
│ └── network.go
├── tools
└── tools.go
├── proto
├── buf.gen.swagger.yaml
├── buf.gen.sta.yaml
├── buf.gen.ts.yaml
├── buf.gen.gogo.yaml
├── side
│ └── btcbridge
│ │ ├── genesis.proto
│ │ ├── params.proto
│ │ ├── bitcoin.proto
│ │ ├── tx.proto
│ │ └── query.proto
├── buf.gen.pulsar.yaml
├── buf.yaml
└── buf.lock
├── signet.block
├── cmd
└── sided
│ ├── main.go
│ └── cmd
│ ├── config.go
│ └── genaccounts.go
├── app
├── params
│ └── encoding.go
├── genesis.go
├── wasm_config.go
├── encoding.go
├── test_helper.go
├── export.go
└── test_setup.go
├── docs
├── docs.go
└── template
│ └── index.tpl
├── config.yml
├── create_validator.sh
├── README.md
├── .github
└── workflows
│ └── release.yml
├── second_node.sh
├── local_node_dev.sh
├── local_node.sh
└── go.mod
/x/btcbridge/module_simulation.go:
--------------------------------------------------------------------------------
1 | package btcbridge
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vue/node_modules
2 | vue/dist
3 | release/
4 | .idea/
5 | .vscode/
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/testutil/sample/sample.go:
--------------------------------------------------------------------------------
1 | package sample
2 |
3 | import (
4 | "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
5 | sdk "github.com/cosmos/cosmos-sdk/types"
6 | )
7 |
8 | // AccAddress returns a sample account address
9 | func AccAddress() string {
10 | pk := ed25519.GenPrivKey().PubKey()
11 | addr := pk.Address()
12 | return sdk.AccAddress(addr).String()
13 | }
14 |
--------------------------------------------------------------------------------
/tools/tools.go:
--------------------------------------------------------------------------------
1 | //go:build tools
2 |
3 | package tools
4 |
5 | import (
6 | _ "github.com/cosmos/gogoproto/protoc-gen-gocosmos"
7 | _ "github.com/golang/protobuf/protoc-gen-go"
8 | _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway"
9 | _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger"
10 | _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
11 | )
12 |
--------------------------------------------------------------------------------
/proto/buf.gen.swagger.yaml:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from Ignite. You can edit
2 | # the file content but do not change the file name or path.
3 | #
4 | # buf.gen.swagger.yaml
5 | #
6 | version: v1
7 | plugins:
8 | - name: openapiv2
9 | out: .
10 | opt:
11 | - logtostderr=true
12 | - openapi_naming_strategy=fqn
13 | - json_names_for_fields=false
14 | - generate_unbound_methods=true
--------------------------------------------------------------------------------
/proto/buf.gen.sta.yaml:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from Ignite. You can edit
2 | # the file content but do not change the file name or path.
3 | #
4 | # buf.gen.sta.yaml
5 | #
6 | version: v1
7 | plugins:
8 | - name: openapiv2
9 | out: .
10 | opt:
11 | - logtostderr=true
12 | - openapi_naming_strategy=simple
13 | - ignore_comments=true
14 | - simple_operation_ids=false
15 | - json_names_for_fields=false
16 |
--------------------------------------------------------------------------------
/x/btcbridge/types/asset.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // AssetTypeFromDenom returns the asset type according to the denom
9 | func AssetTypeFromDenom(denom string, p Params) AssetType {
10 | if denom == p.BtcVoucherDenom {
11 | return AssetType_ASSET_TYPE_BTC
12 | }
13 |
14 | if strings.HasPrefix(denom, fmt.Sprintf("%s/", RunesProtocolName)) {
15 | return AssetType_ASSET_TYPE_RUNE
16 | }
17 |
18 | return AssetType_ASSET_TYPE_UNSPECIFIED
19 | }
20 |
--------------------------------------------------------------------------------
/signet.block:
--------------------------------------------------------------------------------
1 | "best_block_header": {
2 | "version": "667459584",
3 | "hash": "0000000000000009fb68da72e8994f014fafb455c72978233b94580b12af778c",
4 | "height": "2815023",
5 | "previous_block_hash": "0000000000000004a29c20eb32532718de8072665620edb4c657b22b4d463967",
6 | "merkle_root": "9e219423eadce80e882cdff04b3026c9bbc994fd08a774f34a705ca3e710a332",
7 | "nonce": "3913166971",
8 | "bits": "191881b8",
9 | "time": "1715566066",
10 | "ntx": "6236"
11 | }
12 |
--------------------------------------------------------------------------------
/proto/buf.gen.ts.yaml:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from Ignite. You can edit
2 | # the file content but do not change the file name or path.
3 | #
4 | # buf.gen.ts.yaml
5 | #
6 | version: v1
7 | managed:
8 | enabled: true
9 | plugins:
10 | - plugin: buf.build/community/stephenh-ts-proto
11 | out: .
12 | opt:
13 | - logtostderr=true
14 | - allow_merge=true
15 | - json_names_for_fields=false
16 | - ts_proto_opt=snakeToCamel=true
17 | - ts_proto_opt=esModuleInterop=true
18 | - ts_proto_out=.
19 |
--------------------------------------------------------------------------------
/cmd/sided/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/cosmos/cosmos-sdk/server"
7 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
8 |
9 | "github.com/sideprotocol/side/app"
10 | "github.com/sideprotocol/side/cmd/sided/cmd"
11 | )
12 |
13 | func main() {
14 | rootCmd, _ := cmd.NewRootCmd()
15 | if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil {
16 | switch e := err.(type) {
17 | case server.ErrorCode:
18 | os.Exit(e.Code)
19 |
20 | default:
21 | os.Exit(1)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/proto/buf.gen.gogo.yaml:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from Ignite. You can edit
2 | # the file content but do not change the file name or path.
3 | #
4 | # buf.gen.gogo.yaml
5 | #
6 | version: v1
7 | plugins:
8 | - name: gocosmos
9 | out: .
10 | opt:
11 | - plugins=grpc
12 | - Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types
13 | - Mcosmos/orm/v1/orm.proto=cosmossdk.io/orm
14 | - name: grpc-gateway
15 | out: .
16 | opt:
17 | - logtostderr=true
18 | - allow_colon_final_segments=true
19 |
--------------------------------------------------------------------------------
/x/btcbridge/keeper/event.go:
--------------------------------------------------------------------------------
1 | package keeper
2 |
3 | import (
4 | sdk "github.com/cosmos/cosmos-sdk/types"
5 | "github.com/sideprotocol/side/x/btcbridge/types"
6 | )
7 |
8 | func (k Keeper) EmitEvent(ctx sdk.Context, sender string, attr ...sdk.Attribute) {
9 | headerAttr := []sdk.Attribute{
10 | {
11 | Key: "sender",
12 | Value: sender,
13 | },
14 | }
15 |
16 | headerAttr = append(headerAttr, attr...)
17 | ctx.EventManager().EmitEvent(
18 | sdk.NewEvent(
19 | types.ModuleName,
20 | // attr...,
21 | headerAttr...,
22 | ),
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/app/params/encoding.go:
--------------------------------------------------------------------------------
1 | package params
2 |
3 | import (
4 | "github.com/cosmos/cosmos-sdk/client"
5 | "github.com/cosmos/cosmos-sdk/codec"
6 | "github.com/cosmos/cosmos-sdk/codec/types"
7 | )
8 |
9 | // EncodingConfig specifies the concrete encoding types to use for a given app.
10 | // This is provided for compatibility between protobuf and amino implementations.
11 | type EncodingConfig struct {
12 | InterfaceRegistry types.InterfaceRegistry
13 | Marshaler codec.Codec
14 | TxConfig client.TxConfig
15 | Amino *codec.LegacyAmino
16 | }
17 |
--------------------------------------------------------------------------------
/proto/side/btcbridge/genesis.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package side.btcbridge;
3 |
4 | import "gogoproto/gogo.proto";
5 | import "side/btcbridge/params.proto";
6 | import "side/btcbridge/bitcoin.proto";
7 |
8 | option go_package = "github.com/sideprotocol/side/x/btcbridge/types";
9 |
10 | // GenesisState defines the btc light client module's genesis state.
11 | message GenesisState {
12 | Params params = 1 [(gogoproto.nullable) = false];
13 | // the chain tip of the bitcoin chain
14 | BlockHeader best_block_header = 2;
15 | repeated BlockHeader block_headers = 3;
16 | repeated UTXO utxos = 4;
17 | }
18 |
--------------------------------------------------------------------------------
/proto/buf.gen.pulsar.yaml:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from Ignite. You can edit
2 | # the file content but do not change the file name or path.
3 | #
4 | # buf.gen.pulsar.yaml
5 | #
6 | version: v1
7 | managed:
8 | enabled: true
9 | go_package_prefix:
10 | default: cosmossdk.io/api
11 | except:
12 | - buf.build/googleapis/googleapis
13 | - buf.build/cosmos/gogo-proto
14 | - buf.build/cosmos/cosmos-proto
15 | override:
16 | plugins:
17 | - name: go-pulsar
18 | out: ./api
19 | opt: paths=source_relative
20 | - name: go-grpc
21 | out: ./api
22 | opt: paths=source_relative
23 |
--------------------------------------------------------------------------------
/proto/buf.yaml:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from Ignite. You can edit
2 | # the file content but do not change the file name or path.
3 | #
4 | # buf.yaml
5 | #
6 | version: v1
7 | deps:
8 | - buf.build/protocolbuffers/wellknowntypes
9 | - buf.build/cosmos/cosmos-sdk
10 | - buf.build/cosmos/cosmos-proto
11 | - buf.build/cosmos/gogo-proto
12 | - buf.build/googleapis/googleapis
13 | - buf.build/cosmos/ics23
14 | breaking:
15 | use:
16 | - FILE
17 | lint:
18 | use:
19 | - DEFAULT
20 | - COMMENTS
21 | - FILE_LOWER_SNAKE_CASE
22 | except:
23 | - UNARY_RPC
24 | - COMMENT_FIELD
25 | - SERVICE_SUFFIX
26 | - PACKAGE_VERSION_SUFFIX
27 | - RPC_REQUEST_STANDARD_NAME
28 | ignore:
29 | - tendermint
30 |
--------------------------------------------------------------------------------
/app/genesis.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/cosmos/cosmos-sdk/codec"
7 | )
8 |
9 | // The genesis state of the blockchain is represented here as a map of raw json
10 | // messages key'd by a identifier string.
11 | // The identifier is used to determine which module genesis information belongs
12 | // to so it may be appropriately routed during init chain.
13 | // Within this application default genesis information is retrieved from
14 | // the ModuleBasicManager which populates json from each BasicModule
15 | // object provided to it during init.
16 | type GenesisState map[string]json.RawMessage
17 |
18 | // NewDefaultGenesisState generates the default state for the application.
19 | func NewDefaultGenesisState(cdc codec.JSONCodec) GenesisState {
20 | return ModuleBasics.DefaultGenesis(cdc)
21 | }
22 |
--------------------------------------------------------------------------------
/proto/buf.lock:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from Ignite.
2 | # DO NOT EDIT
3 | #
4 | # buf.lock
5 | #
6 | version: v1
7 | deps:
8 | - remote: buf.build
9 | owner: cosmos
10 | repository: cosmos-proto
11 | commit: 1935555c206d4afb9e94615dfd0fad31
12 | - remote: buf.build
13 | owner: cosmos
14 | repository: cosmos-sdk
15 | commit: 954f7b05f38440fc8250134b15adec47
16 | - remote: buf.build
17 | owner: cosmos
18 | repository: gogo-proto
19 | commit: 34d970b699f84aa382f3c29773a60836
20 | - remote: buf.build
21 | owner: cosmos
22 | repository: ics23
23 | commit: 3c44d8daa8b44059ac744cd17d4a49d7
24 | - remote: buf.build
25 | owner: googleapis
26 | repository: googleapis
27 | commit: 75b4300737fb4efca0831636be94e517
28 | - remote: buf.build
29 | owner: protocolbuffers
30 | repository: wellknowntypes
31 | commit: 44e83bc050a4497fa7b36b34d95ca156
32 |
--------------------------------------------------------------------------------
/docs/docs.go:
--------------------------------------------------------------------------------
1 | package docs
2 |
3 | import (
4 | "embed"
5 | httptemplate "html/template"
6 | "net/http"
7 |
8 | "github.com/gorilla/mux"
9 | )
10 |
11 | const (
12 | apiFile = "/static/openapi.yml"
13 | indexFile = "template/index.tpl"
14 | )
15 |
16 | //go:embed static
17 | var Static embed.FS
18 |
19 | //go:embed template
20 | var template embed.FS
21 |
22 | func RegisterOpenAPIService(appName string, rtr *mux.Router) {
23 | rtr.Handle(apiFile, http.FileServer(http.FS(Static)))
24 | rtr.HandleFunc("/", handler(appName))
25 | }
26 |
27 | // handler returns an http handler that servers OpenAPI console for an OpenAPI spec at specURL.
28 | func handler(title string) http.HandlerFunc {
29 | t, _ := httptemplate.ParseFS(template, indexFile)
30 |
31 | return func(w http.ResponseWriter, req *http.Request) {
32 | t.Execute(w, struct {
33 | Title string
34 | URL string
35 | }{
36 | title,
37 | apiFile,
38 | })
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/x/btcbridge/types/merkle_proof.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "encoding/base64"
5 |
6 | "github.com/btcsuite/btcd/blockchain"
7 | "github.com/btcsuite/btcd/chaincfg/chainhash"
8 | )
9 |
10 | // VerifyMerkleProof verifies a Merkle proof
11 | func VerifyMerkleProof(proofs []string, txHash, root *chainhash.Hash) bool {
12 | current := txHash
13 | for _, proof := range proofs {
14 |
15 | bytes, err := base64.StdEncoding.DecodeString(proof)
16 | if err != nil {
17 | return false
18 | }
19 | position := bytes[0]
20 | p := current
21 | if len(bytes) > 1 {
22 | p, err = chainhash.NewHash(bytes[1:])
23 | if err != nil {
24 | return false
25 | }
26 | }
27 |
28 | var temp chainhash.Hash
29 | if position == 0 {
30 | temp = blockchain.HashMerkleBranches(current, p)
31 | } else {
32 | temp = blockchain.HashMerkleBranches(p, current)
33 | }
34 | current = &temp
35 | }
36 |
37 | return current.IsEqual(root)
38 | }
39 |
--------------------------------------------------------------------------------
/cmd/sided/cmd/config.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/btcsuite/btcd/chaincfg"
5 | sdk "github.com/cosmos/cosmos-sdk/types"
6 |
7 | "github.com/sideprotocol/side/app"
8 | )
9 |
10 | func initSDKConfig() {
11 | // Set prefixes
12 | accountPubKeyPrefix := app.AccountAddressPrefix + "pub"
13 | validatorAddressPrefix := app.AccountAddressPrefix + "valoper"
14 | validatorPubKeyPrefix := app.AccountAddressPrefix + "valoperpub"
15 | consNodeAddressPrefix := app.AccountAddressPrefix + "valcons"
16 | consNodePubKeyPrefix := app.AccountAddressPrefix + "valconspub"
17 |
18 | // Set and seal config
19 | config := sdk.GetConfig()
20 | config.SetBech32PrefixForAccount(app.AccountAddressPrefix, accountPubKeyPrefix)
21 | config.SetBech32PrefixForValidator(validatorAddressPrefix, validatorPubKeyPrefix)
22 | config.SetBech32PrefixForConsensusNode(consNodeAddressPrefix, consNodePubKeyPrefix)
23 | config.SetBtcChainCfg(&chaincfg.SigNetParams)
24 | config.Seal()
25 | }
26 |
--------------------------------------------------------------------------------
/app/wasm_config.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
5 | )
6 |
7 | const (
8 | // DefaultInstanceCost is initially set the same as in wasmd
9 | DefaultInstanceCost uint64 = 60_000
10 | // DefaultCompileCost set to a large number for testing
11 | DefaultCompileCost uint64 = 100
12 | )
13 |
14 | // MunGasRegisterConfig is defaults plus a custom compile amount
15 | func GasRegisterConfig() wasmtypes.WasmGasRegisterConfig {
16 | gasConfig := wasmtypes.DefaultGasRegisterConfig()
17 | gasConfig.InstanceCost = DefaultInstanceCost
18 | gasConfig.CompileCost = DefaultCompileCost
19 |
20 | return gasConfig
21 | }
22 |
23 | func NewSideWasmGasRegister() wasmtypes.WasmGasRegister {
24 | return wasmtypes.NewWasmGasRegister(GasRegisterConfig())
25 | }
26 |
27 | func AllCapabilities() []string {
28 | return []string{
29 | "iterator",
30 | "staking",
31 | "stargate",
32 | "cosmwasm_1_1",
33 | "cosmwasm_1_2",
34 | "cosmwasm_1_3",
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/x/btcbridge/keeper/util.go:
--------------------------------------------------------------------------------
1 | package keeper
2 |
3 | import (
4 | "math/big"
5 | "time"
6 |
7 | "github.com/btcsuite/btcd/chaincfg/chainhash"
8 | "github.com/btcsuite/btcd/wire"
9 | "github.com/sideprotocol/side/x/btcbridge/types"
10 | )
11 |
12 | func HeaderConvert(header *types.BlockHeader) *wire.BlockHeader {
13 | prehash, _ := chainhash.NewHashFromStr(header.PreviousBlockHash)
14 | root, _ := chainhash.NewHashFromStr(header.MerkleRoot)
15 | n := new(big.Int)
16 | n.SetString(header.Bits, 16)
17 | return &wire.BlockHeader{
18 | Version: int32(header.Version),
19 | PrevBlock: *prehash,
20 | MerkleRoot: *root,
21 | Timestamp: time.Unix(int64(header.Time), 0),
22 | Bits: uint32(n.Uint64()),
23 | Nonce: uint32(header.Nonce),
24 | }
25 | }
26 |
27 | func BitsToTarget(bits string) *big.Int {
28 | n := new(big.Int)
29 | n.SetString(bits, 16)
30 | return n
31 | }
32 |
33 | func BitsToTargetUint32(bits string) uint32 {
34 | return uint32(BitsToTarget(bits).Uint64())
35 | }
36 |
--------------------------------------------------------------------------------
/docs/template/index.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ .Title }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
24 |
25 |
26 | Footer
27 | © 2022 GitHub, Inc.
28 | Footer navigation
29 |
--------------------------------------------------------------------------------
/app/encoding.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "github.com/cosmos/cosmos-sdk/codec"
5 | "github.com/cosmos/cosmos-sdk/codec/types"
6 | "github.com/cosmos/cosmos-sdk/std"
7 | "github.com/cosmos/cosmos-sdk/x/auth/tx"
8 |
9 | "github.com/sideprotocol/side/app/params"
10 | )
11 |
12 | // makeEncodingConfig creates an EncodingConfig for an amino based test configuration.
13 | func makeEncodingConfig() params.EncodingConfig {
14 | amino := codec.NewLegacyAmino()
15 | interfaceRegistry := types.NewInterfaceRegistry()
16 | marshaler := codec.NewProtoCodec(interfaceRegistry)
17 | txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes)
18 |
19 | return params.EncodingConfig{
20 | InterfaceRegistry: interfaceRegistry,
21 | Marshaler: marshaler,
22 | TxConfig: txCfg,
23 | Amino: amino,
24 | }
25 | }
26 |
27 | // MakeEncodingConfig creates an EncodingConfig for testing
28 | func MakeEncodingConfig() params.EncodingConfig {
29 | encodingConfig := makeEncodingConfig()
30 | std.RegisterLegacyAminoCodec(encodingConfig.Amino)
31 | std.RegisterInterfaces(encodingConfig.InterfaceRegistry)
32 | ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino)
33 | ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry)
34 | return encodingConfig
35 | }
36 |
--------------------------------------------------------------------------------
/x/btcbridge/genesis.go:
--------------------------------------------------------------------------------
1 | package btcbridge
2 |
3 | import (
4 | sdk "github.com/cosmos/cosmos-sdk/types"
5 | "github.com/sideprotocol/side/x/btcbridge/keeper"
6 | "github.com/sideprotocol/side/x/btcbridge/types"
7 | )
8 |
9 | // InitGenesis initializes the module's state from a provided genesis state.
10 | func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) {
11 | // this line is used by starport scaffolding # genesis/module/init
12 | k.SetParams(ctx, genState.Params)
13 | k.SetBestBlockHeader(ctx, genState.BestBlockHeader)
14 | if len(genState.BlockHeaders) > 0 {
15 | err := k.SetBlockHeaders(ctx, genState.BlockHeaders)
16 | if err != nil {
17 | panic(err)
18 | }
19 | }
20 | // import utxos
21 | for _, utxo := range genState.Utxos {
22 | k.SetUTXO(ctx, utxo)
23 | }
24 | }
25 |
26 | // ExportGenesis returns the module's exported genesis
27 | func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState {
28 | genesis := types.DefaultGenesis()
29 | genesis.Params = k.GetParams(ctx)
30 | genesis.BestBlockHeader = k.GetBestBlockHeader(ctx)
31 | genesis.BlockHeaders = k.GetAllBlockHeaders(ctx)
32 | genesis.Utxos = k.GetAllUTXOs(ctx)
33 |
34 | // this line is used by starport scaffolding # genesis/module/export
35 |
36 | return genesis
37 | }
38 |
--------------------------------------------------------------------------------
/x/btcbridge/types/message_update_params.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | sdk "github.com/cosmos/cosmos-sdk/types"
5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
6 | )
7 |
8 | const TypeMsgUpdateParams = "update_params"
9 |
10 | // Route returns the route of MsgUpdateParams.
11 | func (msg *MsgUpdateParamsRequest) Route() string {
12 | return RouterKey
13 | }
14 |
15 | // Type returns the type of MsgUpdateParams.
16 | func (msg *MsgUpdateParamsRequest) Type() string {
17 | return TypeMsgUpdateParams
18 | }
19 |
20 | // GetSignBytes implements the LegacyMsg interface.
21 | func (m MsgUpdateParamsRequest) GetSignBytes() []byte {
22 | return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
23 | }
24 |
25 | // GetSigners returns the expected signers for a MsgUpdateParams message.
26 | func (m *MsgUpdateParamsRequest) GetSigners() []sdk.AccAddress {
27 | addr, _ := sdk.AccAddressFromBech32(m.Authority)
28 | return []sdk.AccAddress{addr}
29 | }
30 |
31 | // ValidateBasic performs basic MsgUpdateParams message validation.
32 | func (m *MsgUpdateParamsRequest) ValidateBasic() error {
33 | if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil {
34 | return sdkerrors.Wrap(err, "invalid authority address")
35 | }
36 |
37 | if err := m.Params.Validate(); err != nil {
38 | return err
39 | }
40 |
41 | return nil
42 | }
43 |
--------------------------------------------------------------------------------
/proto/side/btcbridge/params.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package side.btcbridge;
3 |
4 | import "gogoproto/gogo.proto";
5 |
6 | option go_package = "github.com/sideprotocol/side/x/btcbridge/types";
7 |
8 | // Params defines the parameters for the module.
9 | message Params {
10 | // Only accept blocks sending from these addresses
11 | repeated string authorized_relayers = 1;
12 | // The minimum number of confirmations required for a block to be accepted
13 | int32 confirmations = 2;
14 | // Indicates the maximum depth or distance from the latest block up to which transactions are considered for acceptance.
15 | uint64 max_acceptable_block_depth = 3;
16 | // the denomanation of the voucher
17 | string btc_voucher_denom = 4;
18 | repeated Vault vaults = 5;
19 | }
20 |
21 | // AssetType defines the type of asset
22 | enum AssetType {
23 | // Unspecified asset type
24 | ASSET_TYPE_UNSPECIFIED = 0;
25 | // BTC
26 | ASSET_TYPE_BTC = 1;
27 | // BRC20: ordi, sats
28 | ASSET_TYPE_BRC20 = 2;
29 | // RUNE, dog*go*to*the*moon
30 | ASSET_TYPE_RUNE = 3;
31 | }
32 |
33 | // Vault defines the parameters for the module.
34 | message Vault {
35 | // the depositor should send their btc to this address
36 | string address = 1;
37 | // the pub key to which the voucher is sent
38 | string pub_key = 2;
39 | // the address to which the voucher is sent
40 | AssetType asset_type = 4;
41 |
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/x/btcbridge/types/message_submit_headers.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | sdkerrors "cosmossdk.io/errors"
5 | sdk "github.com/cosmos/cosmos-sdk/types"
6 | )
7 |
8 | const TypeMsgSubmitBlockHeader = "submit_block_header"
9 |
10 | func NewMsgSubmitBlockHeaderRequest(
11 | sender string,
12 | headers []*BlockHeader,
13 | ) *MsgSubmitBlockHeaderRequest {
14 | return &MsgSubmitBlockHeaderRequest{
15 | Sender: sender,
16 | BlockHeaders: headers,
17 | }
18 | }
19 |
20 | func (msg *MsgSubmitBlockHeaderRequest) Route() string {
21 | return RouterKey
22 | }
23 |
24 | func (msg *MsgSubmitBlockHeaderRequest) Type() string {
25 | return TypeMsgSubmitBlockHeader
26 | }
27 |
28 | func (msg *MsgSubmitBlockHeaderRequest) GetSigners() []sdk.AccAddress {
29 | Sender, err := sdk.AccAddressFromBech32(msg.Sender)
30 | if err != nil {
31 | panic(err)
32 | }
33 | return []sdk.AccAddress{Sender}
34 | }
35 |
36 | func (msg *MsgSubmitBlockHeaderRequest) GetSignBytes() []byte {
37 | bz := ModuleCdc.MustMarshalJSON(msg)
38 | return sdk.MustSortJSON(bz)
39 | }
40 |
41 | func (msg *MsgSubmitBlockHeaderRequest) ValidateBasic() error {
42 | _, err := sdk.AccAddressFromBech32(msg.Sender)
43 | if err != nil {
44 | return sdkerrors.Wrapf(err, "invalid Sender address (%s)", err)
45 | }
46 |
47 | if len(msg.BlockHeaders) == 0 {
48 | return sdkerrors.Wrap(ErrInvalidHeader, "block headers cannot be empty")
49 | }
50 |
51 | return nil
52 | }
53 |
--------------------------------------------------------------------------------
/x/btcbridge/types/expected_keepers.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | sdk "github.com/cosmos/cosmos-sdk/types"
5 | "github.com/cosmos/cosmos-sdk/x/auth/types"
6 | banktype "github.com/cosmos/cosmos-sdk/x/bank/types"
7 | )
8 |
9 | // AccountKeeper defines the expected account keeper used for simulations (noalias)
10 | type AccountKeeper interface {
11 | GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI
12 | // Methods imported from account should be defined here
13 | }
14 |
15 | // BankKeeper defines the expected interface needed to retrieve account balances.
16 | type BankKeeper interface {
17 | SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
18 | // Methods imported from bank should be defined here
19 |
20 | SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
21 |
22 | SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error
23 | SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
24 | SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
25 | SetDenomMetaData(ctx sdk.Context, denomMetaData banktype.Metadata)
26 |
27 | MintCoins(ctx sdk.Context, moduleName string, amounts sdk.Coins) error
28 | BurnCoins(ctx sdk.Context, moduleName string, amounts sdk.Coins) error
29 |
30 | HasSupply(ctx sdk.Context, denom string) bool
31 | GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
32 | }
33 |
--------------------------------------------------------------------------------
/config.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 | build:
3 | binary: sided
4 | proto:
5 | path: proto
6 | third_party_paths:
7 | - third_party/proto
8 | - proto_vendor
9 | accounts:
10 | - name: alice
11 | coins:
12 | - 10000000000000000000000000000000uside
13 | - 10000000000000000000000000000000uusdc
14 | - 10000000000000000000000000000000uusdt
15 |
16 | - name: bob
17 | mnemonic: "furnace wild gravity resist heavy beef bundle deliver design service cycle monkey"
18 | coins:
19 | - 10000000000000000000000000000000uside
20 | - 10000000000000000000000000000000uusdc
21 | - 10000000000000000000000000000000uusdt
22 | # faucet:
23 | # name: bob
24 | # coins:
25 | # - 10uside
26 | # host: 0.0.0.0:4500
27 | genesis:
28 | app_state:
29 | crisis:
30 | constant_fee:
31 | denom: uside
32 | gov:
33 | deposit_params:
34 | min_deposit:
35 | - amount: "10000000"
36 | denom: uside
37 | params:
38 | min_deposit:
39 | - amount: "10000000"
40 | denom: uside
41 | voting_period: "60s"
42 | mint:
43 | params:
44 | mint_denom: uside
45 | staking:
46 | params:
47 | bond_denom: uside
48 | chain_id: grimoria-testnet-1
49 | consensus_params:
50 | block:
51 | max_gas: "100000000"
52 | validators:
53 | - name: alice
54 | bonded: 10000000000000000000000uside
55 | home: $HOME/.side
56 | client:
57 | openapi:
58 | path: "docs/static/openapi.yml"
59 | prefix:
60 | address: "side"
61 |
--------------------------------------------------------------------------------
/x/btcbridge/types/message_submit_signature.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | sdkerrors "cosmossdk.io/errors"
5 | sdk "github.com/cosmos/cosmos-sdk/types"
6 | )
7 |
8 | const TypeMsgSubmitSignature = "submit_signature"
9 |
10 | func NewMsgSubmitWithdrawSignaturesRequest(
11 | sender string,
12 | txid string,
13 | pbst string,
14 | ) *MsgSubmitWithdrawSignaturesRequest {
15 | return &MsgSubmitWithdrawSignaturesRequest{
16 | Sender: sender,
17 | Txid: txid,
18 | Psbt: pbst,
19 | }
20 | }
21 |
22 | func (msg *MsgSubmitWithdrawSignaturesRequest) Route() string {
23 | return RouterKey
24 | }
25 |
26 | func (msg *MsgSubmitWithdrawSignaturesRequest) Type() string {
27 | return TypeMsgSubmitSignature
28 | }
29 |
30 | func (msg *MsgSubmitWithdrawSignaturesRequest) GetSigners() []sdk.AccAddress {
31 | Sender, err := sdk.AccAddressFromBech32(msg.Sender)
32 | if err != nil {
33 | panic(err)
34 | }
35 | return []sdk.AccAddress{Sender}
36 | }
37 |
38 | func (msg *MsgSubmitWithdrawSignaturesRequest) GetSignBytes() []byte {
39 | bz := ModuleCdc.MustMarshalJSON(msg)
40 | return sdk.MustSortJSON(bz)
41 | }
42 |
43 | func (msg *MsgSubmitWithdrawSignaturesRequest) ValidateBasic() error {
44 | _, err := sdk.AccAddressFromBech32(msg.Sender)
45 | if err != nil {
46 | return sdkerrors.Wrapf(err, "invalid Sender address (%s)", err)
47 | }
48 |
49 | if len(msg.Txid) == 0 {
50 | return sdkerrors.Wrap(ErrInvalidSignatures, "txid cannot be empty")
51 | }
52 |
53 | if len(msg.Psbt) == 0 {
54 | return sdkerrors.Wrap(ErrInvalidSignatures, "sigatures cannot be empty")
55 | }
56 |
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/x/btcbridge/types/message_submit_withdraw_status.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | sdkerrors "cosmossdk.io/errors"
5 | sdk "github.com/cosmos/cosmos-sdk/types"
6 | )
7 |
8 | const TypeMsgSubmitWithdrawStatus = "submit_withdraw_status"
9 |
10 | func NewMsgSubmitWithdrawStatusRequest(
11 | sender string,
12 | txid string,
13 | status SigningStatus,
14 | ) *MsgSubmitWithdrawStatusRequest {
15 | return &MsgSubmitWithdrawStatusRequest{
16 | Sender: sender,
17 | Txid: txid,
18 | Status: status,
19 | }
20 | }
21 |
22 | func (msg *MsgSubmitWithdrawStatusRequest) Route() string {
23 | return RouterKey
24 | }
25 |
26 | func (msg *MsgSubmitWithdrawStatusRequest) Type() string {
27 | return TypeMsgSubmitWithdrawStatus
28 | }
29 |
30 | func (msg *MsgSubmitWithdrawStatusRequest) GetSigners() []sdk.AccAddress {
31 | Sender, err := sdk.AccAddressFromBech32(msg.Sender)
32 | if err != nil {
33 | panic(err)
34 | }
35 | return []sdk.AccAddress{Sender}
36 | }
37 |
38 | func (msg *MsgSubmitWithdrawStatusRequest) GetSignBytes() []byte {
39 | bz := ModuleCdc.MustMarshalJSON(msg)
40 | return sdk.MustSortJSON(bz)
41 | }
42 |
43 | func (msg *MsgSubmitWithdrawStatusRequest) ValidateBasic() error {
44 | _, err := sdk.AccAddressFromBech32(msg.Sender)
45 | if err != nil {
46 | return sdkerrors.Wrapf(err, "invalid Sender address (%s)", err)
47 | }
48 |
49 | if len(msg.Txid) == 0 {
50 | return sdkerrors.Wrap(ErrSigningRequestNotExist, "txid cannot be empty")
51 | }
52 |
53 | if msg.Status != SigningStatus_SIGNING_STATUS_BROADCASTED {
54 | return sdkerrors.Wrap(ErrInvalidStatus, "invalid status")
55 | }
56 |
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/x/btcbridge/types/message_update_senders.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | sdkerrors "cosmossdk.io/errors"
5 | sdk "github.com/cosmos/cosmos-sdk/types"
6 | )
7 |
8 | const TypeMsgUpdateSenders = "update_qualifed_relayers"
9 |
10 | func NewMsgUpdateSendersRequest(
11 | sender string,
12 | relayers []string,
13 | ) *MsgUpdateQualifiedRelayersRequest {
14 | return &MsgUpdateQualifiedRelayersRequest{
15 | Sender: sender,
16 | Relayers: relayers,
17 | }
18 | }
19 |
20 | func (msg *MsgUpdateQualifiedRelayersRequest) Route() string {
21 | return RouterKey
22 | }
23 |
24 | func (msg *MsgUpdateQualifiedRelayersRequest) Type() string {
25 | return TypeMsgUpdateSenders
26 | }
27 |
28 | func (msg *MsgUpdateQualifiedRelayersRequest) GetSigners() []sdk.AccAddress {
29 | Sender, err := sdk.AccAddressFromBech32(msg.Sender)
30 | if err != nil {
31 | panic(err)
32 | }
33 | return []sdk.AccAddress{Sender}
34 | }
35 |
36 | func (msg *MsgUpdateQualifiedRelayersRequest) GetSignBytes() []byte {
37 | bz := ModuleCdc.MustMarshalJSON(msg)
38 | return sdk.MustSortJSON(bz)
39 | }
40 |
41 | func (msg *MsgUpdateQualifiedRelayersRequest) ValidateBasic() error {
42 | _, err := sdk.AccAddressFromBech32(msg.Sender)
43 | if err != nil {
44 | return sdkerrors.Wrapf(err, "invalid sender address (%s)", err)
45 | }
46 |
47 | if len(msg.Relayers) == 0 {
48 | return sdkerrors.Wrap(ErrInvalidSenders, "relayers cannot be empty")
49 | }
50 |
51 | for _, sender := range msg.Relayers {
52 | _, err := sdk.AccAddressFromBech32(sender)
53 | if err != nil {
54 | return sdkerrors.Wrapf(ErrInvalidSenders, "address (%s) is invalid", sender)
55 | }
56 | }
57 |
58 | return nil
59 | }
60 |
--------------------------------------------------------------------------------
/testutil/nullify/nullify.go:
--------------------------------------------------------------------------------
1 | // Package nullify provides methods to init nil values structs for test assertion.
2 | package nullify
3 |
4 | import (
5 | "reflect"
6 | "unsafe"
7 |
8 | sdk "github.com/cosmos/cosmos-sdk/types"
9 | )
10 |
11 | var (
12 | coinType = reflect.TypeOf(sdk.Coin{})
13 | coinsType = reflect.TypeOf(sdk.Coins{})
14 | )
15 |
16 | // Fill analyze all struct fields and slices with
17 | // reflection and initialize the nil and empty slices,
18 | // structs, and pointers.
19 | func Fill(x interface{}) interface{} {
20 | v := reflect.Indirect(reflect.ValueOf(x))
21 | switch v.Kind() {
22 | case reflect.Slice:
23 | for i := 0; i < v.Len(); i++ {
24 | obj := v.Index(i)
25 | objPt := reflect.NewAt(obj.Type(), unsafe.Pointer(obj.UnsafeAddr())).Interface()
26 | objPt = Fill(objPt)
27 | obj.Set(reflect.ValueOf(objPt))
28 | }
29 | case reflect.Struct:
30 | for i := 0; i < v.NumField(); i++ {
31 | f := reflect.Indirect(v.Field(i))
32 | if !f.CanSet() {
33 | continue
34 | }
35 | switch f.Kind() {
36 | case reflect.Slice:
37 | f.Set(reflect.MakeSlice(f.Type(), 0, 0))
38 | case reflect.Struct:
39 | switch f.Type() {
40 | case coinType:
41 | coin := reflect.New(coinType).Interface()
42 | s := reflect.ValueOf(coin).Elem()
43 | f.Set(s)
44 | case coinsType:
45 | coins := reflect.New(coinsType).Interface()
46 | s := reflect.ValueOf(coins).Elem()
47 | f.Set(s)
48 | default:
49 | objPt := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Interface()
50 | s := Fill(objPt)
51 | f.Set(reflect.ValueOf(s))
52 | }
53 | }
54 | }
55 | }
56 | return reflect.Indirect(v).Interface()
57 | }
58 |
--------------------------------------------------------------------------------
/x/btcbridge/types/message_withdraw_bitcoin.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "strconv"
5 |
6 | sdkerrors "cosmossdk.io/errors"
7 | sdk "github.com/cosmos/cosmos-sdk/types"
8 | )
9 |
10 | const TypeMsgWithdrawBitcoin = "withdraw_bitcoin"
11 |
12 | func NewMsgWithdrawBitcoinRequest(
13 | sender string,
14 | amount string,
15 | feeRate string,
16 | ) *MsgWithdrawBitcoinRequest {
17 | return &MsgWithdrawBitcoinRequest{
18 | Sender: sender,
19 | Amount: amount,
20 | FeeRate: feeRate,
21 | }
22 | }
23 |
24 | func (msg *MsgWithdrawBitcoinRequest) Route() string {
25 | return RouterKey
26 | }
27 |
28 | func (msg *MsgWithdrawBitcoinRequest) Type() string {
29 | return TypeMsgWithdrawBitcoin
30 | }
31 |
32 | func (msg *MsgWithdrawBitcoinRequest) GetSigners() []sdk.AccAddress {
33 | Sender, err := sdk.AccAddressFromBech32(msg.Sender)
34 | if err != nil {
35 | panic(err)
36 | }
37 | return []sdk.AccAddress{Sender}
38 | }
39 |
40 | func (msg *MsgWithdrawBitcoinRequest) GetSignBytes() []byte {
41 | bz := ModuleCdc.MustMarshalJSON(msg)
42 | return sdk.MustSortJSON(bz)
43 | }
44 |
45 | func (msg *MsgWithdrawBitcoinRequest) ValidateBasic() error {
46 | _, err := sdk.AccAddressFromBech32(msg.Sender)
47 | if err != nil {
48 | return sdkerrors.Wrapf(err, "invalid Sender address (%s)", err)
49 | }
50 |
51 | _, err = sdk.ParseCoinNormalized(msg.Amount)
52 | if err != nil {
53 | return sdkerrors.Wrapf(ErrInvalidAmount, "invalid amount %s", msg.Amount)
54 | }
55 |
56 | feeRate, err := strconv.ParseInt(msg.FeeRate, 10, 64)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | if feeRate <= 0 {
62 | return sdkerrors.Wrap(ErrInvalidFeeRate, "fee rate must be greater than zero")
63 | }
64 |
65 | return nil
66 | }
67 |
--------------------------------------------------------------------------------
/testutil/keeper/btc_bridge.go:
--------------------------------------------------------------------------------
1 | package keeper
2 |
3 | import (
4 | "testing"
5 |
6 | tmdb "github.com/cometbft/cometbft-db"
7 | "github.com/cometbft/cometbft/libs/log"
8 | tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
9 | "github.com/cosmos/cosmos-sdk/codec"
10 | codectypes "github.com/cosmos/cosmos-sdk/codec/types"
11 | "github.com/cosmos/cosmos-sdk/store"
12 | storetypes "github.com/cosmos/cosmos-sdk/store/types"
13 | sdk "github.com/cosmos/cosmos-sdk/types"
14 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
15 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
16 | "github.com/sideprotocol/side/app"
17 | "github.com/sideprotocol/side/x/btcbridge/keeper"
18 | "github.com/sideprotocol/side/x/btcbridge/types"
19 | "github.com/stretchr/testify/require"
20 | )
21 |
22 | func BtcBridgeKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) {
23 | app := app.InitSideTestApp(false)
24 |
25 | storeKey := sdk.NewKVStoreKey(types.StoreKey)
26 | memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey)
27 |
28 | db := tmdb.NewMemDB()
29 | stateStore := store.NewCommitMultiStore(db)
30 | stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db)
31 | stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil)
32 | require.NoError(t, stateStore.LoadLatestVersion())
33 |
34 | registry := codectypes.NewInterfaceRegistry()
35 | cdc := codec.NewProtoCodec(registry)
36 |
37 | authority := authtypes.NewModuleAddress(govtypes.ModuleName).String()
38 |
39 | k := keeper.NewKeeper(
40 | cdc,
41 | storeKey,
42 | memStoreKey,
43 | authority,
44 | app.BankKeeper,
45 | )
46 |
47 | ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger())
48 |
49 | // Initialize params
50 | k.SetParams(ctx, types.DefaultParams())
51 |
52 | return k, ctx
53 | }
54 |
--------------------------------------------------------------------------------
/x/btcbridge/types/message_submit_withdraw_transaction.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | sdkerrors "cosmossdk.io/errors"
5 | sdk "github.com/cosmos/cosmos-sdk/types"
6 | )
7 |
8 | const TypeMsgSubmitWithdrawTransaction = "submit_withdraw_transaction"
9 |
10 | func NewMsgSubmitWithdrawTransactionRequest(
11 | sender string,
12 | blockhash string,
13 | transaction string,
14 | proof []string,
15 | ) *MsgSubmitWithdrawTransactionRequest {
16 | return &MsgSubmitWithdrawTransactionRequest{
17 | Sender: sender,
18 | Blockhash: blockhash,
19 | TxBytes: transaction,
20 | Proof: proof,
21 | }
22 | }
23 |
24 | func (msg *MsgSubmitWithdrawTransactionRequest) Route() string {
25 | return RouterKey
26 | }
27 |
28 | func (msg *MsgSubmitWithdrawTransactionRequest) Type() string {
29 | return TypeMsgSubmitDepositTransaction
30 | }
31 |
32 | func (msg *MsgSubmitWithdrawTransactionRequest) GetSigners() []sdk.AccAddress {
33 | Sender, err := sdk.AccAddressFromBech32(msg.Sender)
34 | if err != nil {
35 | panic(err)
36 | }
37 | return []sdk.AccAddress{Sender}
38 | }
39 |
40 | func (msg *MsgSubmitWithdrawTransactionRequest) GetSignBytes() []byte {
41 | bz := ModuleCdc.MustMarshalJSON(msg)
42 | return sdk.MustSortJSON(bz)
43 | }
44 |
45 | func (msg *MsgSubmitWithdrawTransactionRequest) ValidateBasic() error {
46 | _, err := sdk.AccAddressFromBech32(msg.Sender)
47 | if err != nil {
48 | return sdkerrors.Wrapf(err, "invalid Sender address (%s)", err)
49 | }
50 |
51 | if len(msg.Blockhash) == 0 {
52 | return sdkerrors.Wrap(ErrInvalidBtcTransaction, "blockhash cannot be empty")
53 | }
54 |
55 | if len(msg.TxBytes) == 0 {
56 | return sdkerrors.Wrap(ErrInvalidBtcTransaction, "transaction cannot be empty")
57 | }
58 |
59 | if len(msg.Proof) == 0 {
60 | return sdkerrors.Wrap(ErrInvalidBtcTransaction, "proof cannot be empty")
61 | }
62 |
63 | return nil
64 | }
65 |
--------------------------------------------------------------------------------
/x/btcbridge/types/codec.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "github.com/cosmos/cosmos-sdk/codec"
5 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
6 | sdk "github.com/cosmos/cosmos-sdk/types"
7 | "github.com/cosmos/cosmos-sdk/types/msgservice"
8 | )
9 |
10 | func RegisterCodec(cdc *codec.LegacyAmino) {
11 | cdc.RegisterConcrete(&MsgSubmitBlockHeaderRequest{}, "btcbridge/MsgSubmitBlockHeaderRequest", nil)
12 | cdc.RegisterConcrete(&MsgUpdateQualifiedRelayersRequest{}, "btcbridge/MsgUpdateQualifiedRelayersRequest", nil)
13 | cdc.RegisterConcrete(&MsgSubmitDepositTransactionRequest{}, "btcbridge/MsgSubmitDepositTransactionRequest", nil)
14 | cdc.RegisterConcrete(&MsgSubmitWithdrawSignaturesRequest{}, "btcbridge/MsgSubmitWithdrawSignaturesRequest", nil)
15 | cdc.RegisterConcrete(&MsgSubmitWithdrawTransactionRequest{}, "btcbridge/MsgSubmitWithdrawTransactionRequest", nil)
16 | cdc.RegisterConcrete(&MsgUpdateParamsRequest{}, "btcbridge/MsgUpdateParamsRequest", nil)
17 | // this line is used by starport scaffolding # 2
18 | }
19 |
20 | func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
21 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitBlockHeaderRequest{})
22 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgUpdateQualifiedRelayersRequest{})
23 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitDepositTransactionRequest{})
24 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitWithdrawSignaturesRequest{})
25 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSubmitWithdrawTransactionRequest{})
26 | registry.RegisterImplementations((*sdk.Msg)(nil), &MsgUpdateParamsRequest{})
27 | // this line is used by starport scaffolding # 3
28 |
29 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
30 | }
31 |
32 | var (
33 | Amino = codec.NewLegacyAmino()
34 | ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry())
35 | )
36 |
--------------------------------------------------------------------------------
/create_validator.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | KEYS=("dev0" "dev1")
4 | CHAINID="grimoria-testnet-1"
5 | MONIKER="Side Labs"
6 | BINARY="$HOME/go/bin/sided"
7 |
8 | # Remember to change to other types of keyring like 'file' in-case exposing to outside world,
9 | # otherwise your balance will be wiped quickly
10 | # The keyring test does not require private key to steal tokens from you
11 | KEYRING="test"
12 | #KEYALGO="secp256k1"
13 | KEYALGO="segwit"
14 | LOGLEVEL="info"
15 | # Set dedicated home directory for the $BINARY instance
16 | HOMEDIR="$HOME/.side2"
17 |
18 | # Path variables
19 | CONFIG=$HOMEDIR/config/config.toml
20 | APP_TOML=$HOMEDIR/config/app.toml
21 | GENESIS=$HOMEDIR/config/genesis.json
22 | TMP_GENESIS=$HOMEDIR/config/tmp_genesis.json
23 | PEER_ID=""
24 |
25 | # validate dependencies are installed
26 | command -v jq >/dev/null 2>&1 || {
27 | echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"
28 | exit 1
29 | }
30 |
31 | # used to exit on first error (any non-zero exit code)
32 | set -e
33 |
34 | # Set client config
35 | $BINARY config keyring-backend $KEYRING --home "$HOMEDIR"
36 | $BINARY config chain-id $CHAINID --home "$HOMEDIR"
37 |
38 | # Start the node (remove the --pruning=nothing flag if historical queries are not needed)
39 | $BINARY tx staking create-validator -y --moniker segwit --pubkey=$($BINARY tendermint show-validator --home $HOMEDIR) --amount="10000000uside" --commission-rate 0.1 --commission-max-rate 0.1 --commission-max-change-rate 0.1 --min-self-delegation 1 --chain-id $CHAINID --fees 2000uside --home "$HOMEDIR" --from segwit_key
40 | sleep 6
41 | $BINARY tx staking create-validator -y --moniker secp256 --pubkey=$($BINARY tendermint show-validator --home $HOMEDIR) --amount="15000000uside" --commission-rate 0.1 --commission-max-rate 0.1 --commission-max-change-rate 0.1 --min-self-delegation 1 --chain-id $CHAINID --fees 2000uside --home "$HOMEDIR" --from secp256k1_key
42 |
--------------------------------------------------------------------------------
/x/btcbridge/types/message_submit_deposit_transaction.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | sdkerrors "cosmossdk.io/errors"
5 | sdk "github.com/cosmos/cosmos-sdk/types"
6 | )
7 |
8 | const TypeMsgSubmitDepositTransaction = "submit_deposit_transaction"
9 |
10 | func NewMsgSubmitTransactionRequest(
11 | sender string,
12 | blockhash string,
13 | transaction string,
14 | proof []string,
15 | ) *MsgSubmitDepositTransactionRequest {
16 | return &MsgSubmitDepositTransactionRequest{
17 | Sender: sender,
18 | Blockhash: blockhash,
19 | TxBytes: transaction,
20 | Proof: proof,
21 | }
22 | }
23 |
24 | func (msg *MsgSubmitDepositTransactionRequest) Route() string {
25 | return RouterKey
26 | }
27 |
28 | func (msg *MsgSubmitDepositTransactionRequest) Type() string {
29 | return TypeMsgSubmitDepositTransaction
30 | }
31 |
32 | func (msg *MsgSubmitDepositTransactionRequest) GetSigners() []sdk.AccAddress {
33 | Sender, err := sdk.AccAddressFromBech32(msg.Sender)
34 | if err != nil {
35 | panic(err)
36 | }
37 | return []sdk.AccAddress{Sender}
38 | }
39 |
40 | func (msg *MsgSubmitDepositTransactionRequest) GetSignBytes() []byte {
41 | bz := ModuleCdc.MustMarshalJSON(msg)
42 | return sdk.MustSortJSON(bz)
43 | }
44 |
45 | func (msg *MsgSubmitDepositTransactionRequest) ValidateBasic() error {
46 | _, err := sdk.AccAddressFromBech32(msg.Sender)
47 | if err != nil {
48 | return sdkerrors.Wrapf(err, "invalid Sender address (%s)", err)
49 | }
50 |
51 | if len(msg.Blockhash) == 0 {
52 | return sdkerrors.Wrap(ErrInvalidBtcTransaction, "blockhash cannot be empty")
53 | }
54 |
55 | if len(msg.PrevTxBytes) == 0 {
56 | return sdkerrors.Wrap(ErrInvalidBtcTransaction, "transaction cannot be empty")
57 | }
58 |
59 | if len(msg.TxBytes) == 0 {
60 | return sdkerrors.Wrap(ErrInvalidBtcTransaction, "transaction cannot be empty")
61 | }
62 |
63 | if len(msg.Proof) == 0 {
64 | return sdkerrors.Wrap(ErrInvalidBtcTransaction, "proof cannot be empty")
65 | }
66 |
67 | return nil
68 | }
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sidechain
2 | **sidechain** is a blockchain built using Cosmos SDK and Tendermint and created with [Ignite CLI](https://ignite.com/cli).
3 |
4 | ## Get started
5 |
6 | ```
7 | ignite chain serve
8 | ```
9 | `serve` command installs dependencies, builds, initializes, and starts your blockchain in development.
10 |
11 | ### Requirements
12 |
13 | - `ignite version: 0.27.1`
14 | - `go version >= 1.20.x`
15 |
16 | ### Configure
17 |
18 | Your blockchain in development can be configured with `config.yml`. To learn more, see the [Ignite CLI docs](https://docs.ignite.com).
19 |
20 | ### Web Frontend
21 |
22 | Ignite CLI has scaffolded a Vue.js-based web app in the `vue` directory. Run the following commands to install dependencies and start the app:
23 |
24 | ```
25 | cd vue
26 | npm install
27 | npm run serve
28 | ```
29 |
30 | The frontend app is built using the `@starport/vue` and `@starport/vuex` packages. For details, see the [monorepo for Ignite front-end development](https://github.com/ignite/web).
31 |
32 | ## Release
33 | To release a new version of your blockchain, create and push a new tag with `v` prefix. A new draft release with the configured targets will be created.
34 |
35 | ```
36 | git tag v0.1
37 | git push origin v0.1
38 | ```
39 |
40 | After a draft release is created, make your final changes from the release page and publish it.
41 |
42 | ### Install
43 | To install the latest version of your blockchain node's binary, execute the following command on your machine:
44 |
45 | ```
46 | curl https://get.ignite.com/username/sidechain@latest! | sudo bash
47 | ```
48 | `username/sidechain` should match the `username` and `repo_name` of the Github repository to which the source code was pushed. Learn more about [the install process](https://github.com/allinbits/starport-installer).
49 |
50 | ## Learn more
51 |
52 | - [Ignite CLI](https://ignite.com/cli)
53 | - [Tutorials](https://docs.ignite.com/guide)
54 | - [Ignite CLI docs](https://docs.ignite.com)
55 | - [Cosmos SDK docs](https://docs.cosmos.network)
56 | - [Developer Chat](https://discord.gg/ignite)
57 |
--------------------------------------------------------------------------------
/x/btcbridge/types/runes_test.go:
--------------------------------------------------------------------------------
1 | package types_test
2 |
3 | import (
4 | "encoding/hex"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 |
9 | "github.com/btcsuite/btcd/wire"
10 |
11 | "github.com/sideprotocol/side/x/btcbridge/types"
12 | )
13 |
14 | func TestParseRunes(t *testing.T) {
15 | testCases := []struct {
16 | name string
17 | pkScriptHex string
18 | edicts []*types.Edict
19 | expectPass bool
20 | }{
21 | {
22 | name: "valid runes edict",
23 | pkScriptHex: "6a5d0b00c0a2330380cab5ee0101",
24 | edicts: []*types.Edict{
25 | {
26 | Id: &types.RuneId{Block: 840000, Tx: 3},
27 | Amount: "500000000",
28 | Output: 1,
29 | },
30 | },
31 | expectPass: true,
32 | },
33 | {
34 | name: "output index is out of range",
35 | pkScriptHex: "6a5d0b00c0a2330380cab5ee0102",
36 | expectPass: false,
37 | },
38 | {
39 | name: "no OP_RETURN",
40 | pkScriptHex: "615d0b00c0a2330380cab5ee0102",
41 | expectPass: true,
42 | edicts: nil,
43 | },
44 | {
45 | name: "no runes magic number",
46 | pkScriptHex: "6a5c0b00c0a2330380cab5ee0102",
47 | expectPass: true,
48 | edicts: nil,
49 | },
50 | {
51 | name: "non data push op",
52 | pkScriptHex: "6a5d4f00c0a2330380cab5ee0102",
53 | expectPass: false,
54 | },
55 | {
56 | name: "no tag body for edicts",
57 | pkScriptHex: "6a5d0b01c0a2330380cab5ee0102",
58 | expectPass: false,
59 | },
60 | {
61 | name: "invalid edict",
62 | pkScriptHex: "6a5d0b00c0a2330380cab5ee01",
63 | expectPass: false,
64 | },
65 | }
66 |
67 | for _, tc := range testCases {
68 | t.Run(tc.name, func(t *testing.T) {
69 | pkScript, err := hex.DecodeString(tc.pkScriptHex)
70 | require.NoError(t, err)
71 |
72 | tx := wire.NewMsgTx(types.TxVersion)
73 | tx.AddTxOut(wire.NewTxOut(0, pkScript))
74 |
75 | edicts, err := types.ParseRunes(tx)
76 | if tc.expectPass {
77 | require.NoError(t, err)
78 | require.EqualValues(t, tc.edicts, edicts)
79 | } else {
80 | require.NotNil(t, err)
81 | }
82 | })
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/x/btcbridge/types/varint.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "errors"
5 | "math/big"
6 |
7 | "lukechampine.com/uint128"
8 | )
9 |
10 | func EncodeUint32(n uint32) []byte {
11 | var result []byte
12 |
13 | for n >= 128 {
14 | result = append(result, byte(n&0x7F|0x80))
15 | n >>= 7
16 | }
17 |
18 | result = append(result, byte(n))
19 | return result
20 | }
21 |
22 | func EncodeUint64(n uint64) []byte {
23 | var result []byte
24 |
25 | for n >= 128 {
26 | result = append(result, byte(n&0x7F|0x80))
27 | n >>= 7
28 | }
29 |
30 | result = append(result, byte(n))
31 | return result
32 | }
33 |
34 | func EncodeUint128(n *uint128.Uint128) []byte {
35 | return EncodeBigInt(n.Big())
36 | }
37 |
38 | func EncodeBigInt(n *big.Int) []byte {
39 | var result []byte
40 |
41 | for n.Cmp(big.NewInt(128)) >= 0 {
42 | temp := new(big.Int).Set(n)
43 | last := temp.And(n, new(big.Int).SetUint64(0b0111_1111))
44 | result = append(result, last.Or(last, new(big.Int).SetUint64(0b1000_0000)).Bytes()[0])
45 | n.Rsh(n, 7)
46 | }
47 |
48 | if len(n.Bytes()) == 0 {
49 | result = append(result, 0)
50 | } else {
51 | result = append(result, n.Bytes()...)
52 | }
53 |
54 | return result
55 | }
56 |
57 | func Decode(bz []byte) (uint128.Uint128, int, error) {
58 | n := big.NewInt(0)
59 |
60 | for i, b := range bz {
61 | if i > 18 {
62 | return uint128.Zero, 0, errors.New("varint overflow")
63 | }
64 |
65 | value := uint64(b) & 0b0111_1111
66 | if i == 18 && value&0b0111_1100 != 0 {
67 | return uint128.Zero, 0, errors.New("varint too large")
68 | }
69 |
70 | temp := new(big.Int).SetUint64(value)
71 | n.Or(n, temp.Lsh(temp, uint(7*i)))
72 |
73 | if b&0b1000_0000 == 0 {
74 | return uint128.FromBig(n), i + 1, nil
75 | }
76 | }
77 |
78 | return uint128.Zero, 0, errors.New("varint too short")
79 | }
80 |
81 | func DecodeVec(payload []byte) ([]uint128.Uint128, error) {
82 | vec := make([]uint128.Uint128, 0)
83 | i := 0
84 |
85 | for i < len(payload) {
86 | value, length, err := Decode(payload[i:])
87 | if err != nil {
88 | return nil, err
89 | }
90 |
91 | vec = append(vec, value)
92 | i += length
93 | }
94 |
95 | return vec, nil
96 | }
97 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This workflow is useful if you want to automate the process of:
2 | #
3 | # a) Creating a new prelease when you push a new tag with a "v" prefix (version).
4 | #
5 | # This type of prerelease is meant to be used for production: alpha, beta, rc, etc. types of releases.
6 | # After the prerelease is created, you need to make your changes on the release page at the relevant
7 | # Github page and publish your release.
8 | #
9 | # b) Creating/updating the "latest" prerelease when you push to your default branch.
10 | #
11 | # This type of prelease is useful to make your bleeding-edge binaries available to advanced users.
12 | #
13 | # The workflow will not run if there is no tag pushed with a "v" prefix and no change pushed to your
14 | # default branch.
15 | on: push
16 |
17 | jobs:
18 | might_release:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@v2
23 | with:
24 | fetch-depth: 0
25 |
26 | - name: Prepare Release Variables
27 | id: vars
28 | uses: ignite/cli/actions/release/vars@main
29 |
30 | - name: Issue Release Assets
31 | uses: ignite/cli/actions/cli@main
32 | if: ${{ steps.vars.outputs.should_release == 'true' }}
33 | with:
34 | args: chain build --release --release.prefix ${{ steps.vars.outputs.tarball_prefix }} -t linux:amd64 -t darwin:amd64 -t darwin:arm64
35 |
36 | - name: Delete the "latest" Release
37 | uses: dev-drprasad/delete-tag-and-release@v0.2.1
38 | if: ${{ steps.vars.outputs.is_release_type_latest == 'true' }}
39 | with:
40 | tag_name: ${{ steps.vars.outputs.tag_name }}
41 | delete_release: true
42 | env:
43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44 |
45 | - name: Publish the Release
46 | uses: softprops/action-gh-release@v1
47 | if: ${{ steps.vars.outputs.should_release == 'true' }}
48 | with:
49 | tag_name: ${{ steps.vars.outputs.tag_name }}
50 | files: release/*
51 | prerelease: true
52 | env:
53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54 |
--------------------------------------------------------------------------------
/proto/side/btcbridge/bitcoin.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package side.btcbridge;
3 |
4 | import "gogoproto/gogo.proto";
5 |
6 | option go_package = "github.com/sideprotocol/side/x/btcbridge/types";
7 |
8 | // Bitcoin Block Header
9 | message BlockHeader {
10 | uint64 version = 1;
11 | string hash = 2;
12 | uint64 height = 3;
13 | string previous_block_hash = 4;
14 | string merkle_root = 5;
15 | uint64 nonce = 6;
16 | string bits = 7;
17 | uint64 time = 8;
18 | uint64 ntx = 9;
19 | }
20 |
21 | // Bitcoin Signing Status
22 | enum SigningStatus {
23 | // SIGNING_STATUS_UNSPECIFIED - Default value, should not be used
24 | SIGNING_STATUS_UNSPECIFIED = 0;
25 | // SIGNING_STATUS_CREATED - The signing request is created
26 | SIGNING_STATUS_CREATED = 1;
27 | // SIGNING_STATUS_SIGNED - The signing request is signed
28 | SIGNING_STATUS_SIGNED = 2;
29 | // SIGNING_STATUS_BROADCASTED - The signing request is broadcasted
30 | SIGNING_STATUS_BROADCASTED = 3;
31 | // SIGNING_STATUS_CONFIRMED - The signing request is confirmed
32 | SIGNING_STATUS_CONFIRMED = 4;
33 | // SIGNING_STATUS_REJECTED - The signing request is rejected
34 | SIGNING_STATUS_REJECTED = 5;
35 | }
36 |
37 | // Bitcoin Signing Request
38 | message BitcoinSigningRequest {
39 | string address = 1;
40 | string txid = 2;
41 | string psbt = 3;
42 | SigningStatus status = 4;
43 | uint64 sequence = 5;
44 | // The vault address that the request is associated with
45 | string vault_address = 6;
46 | }
47 |
48 | // Bitcoin UTXO
49 | message UTXO {
50 | string txid = 1;
51 | uint64 vout = 2;
52 | string address = 3;
53 | uint64 amount = 4;
54 | // height is used for calculating confirmations
55 | uint64 height = 5;
56 | bytes pub_key_script = 6;
57 | bool is_coinbase = 7;
58 | bool is_locked = 8;
59 | // rune balances associated with the UTXO
60 | repeated RuneBalance runes = 9;
61 | }
62 |
63 | // Rune Balance
64 | message RuneBalance {
65 | // serialized rune id
66 | string id = 1;
67 | // rune amount
68 | string amount = 2;
69 | }
70 |
71 | // Rune ID
72 | message RuneId {
73 | // block height
74 | uint64 block = 1;
75 | // tx index
76 | uint32 tx = 2;
77 | }
78 |
79 | // Rune Edict
80 | message Edict {
81 | RuneId id = 1;
82 | string amount = 2;
83 | uint32 output = 3;
84 | }
85 |
--------------------------------------------------------------------------------
/x/btcbridge/types/errors.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | // DONTCOVER
4 |
5 | import (
6 | errorsmod "cosmossdk.io/errors"
7 | )
8 |
9 | // x/btcbridge module sentinel errors
10 | var (
11 | ErrSenderAddressNotAuthorized = errorsmod.Register(ModuleName, 1000, "sender address not authorized")
12 | ErrInvalidHeader = errorsmod.Register(ModuleName, 1100, "invalid block header")
13 | ErrReorgFailed = errorsmod.Register(ModuleName, 1101, "failed to reorg chain")
14 | ErrForkedBlockHeader = errorsmod.Register(ModuleName, 1102, "Invalid forked block header")
15 |
16 | ErrInvalidSenders = errorsmod.Register(ModuleName, 2100, "invalid allowed senders")
17 |
18 | ErrInvalidBtcTransaction = errorsmod.Register(ModuleName, 3100, "invalid bitcoin transaction")
19 | ErrBlockNotFound = errorsmod.Register(ModuleName, 3101, "block not found")
20 | ErrTransactionNotIncluded = errorsmod.Register(ModuleName, 3102, "transaction not included in block")
21 | ErrNotConfirmed = errorsmod.Register(ModuleName, 3200, "transaction not confirmed")
22 | ErrExceedMaxAcceptanceDepth = errorsmod.Register(ModuleName, 3201, "exceed max acceptance block depth")
23 | ErrUnsupportedScriptType = errorsmod.Register(ModuleName, 3202, "unsupported script type")
24 | ErrTransactionAlreadyMinted = errorsmod.Register(ModuleName, 3203, "transaction already minted")
25 | ErrInvalidDepositTransaction = errorsmod.Register(ModuleName, 3204, "invalid deposit transaction")
26 |
27 | ErrInvalidSignatures = errorsmod.Register(ModuleName, 4200, "invalid signatures")
28 | ErrInsufficientBalance = errorsmod.Register(ModuleName, 4201, "insufficient balance")
29 | ErrSigningRequestNotExist = errorsmod.Register(ModuleName, 4202, "signing request does not exist")
30 | ErrInvalidStatus = errorsmod.Register(ModuleName, 4203, "invalid status")
31 |
32 | ErrUTXODoesNotExist = errorsmod.Register(ModuleName, 5100, "utxo does not exist")
33 | ErrUTXOLocked = errorsmod.Register(ModuleName, 5101, "utxo locked")
34 | ErrUTXOUnlocked = errorsmod.Register(ModuleName, 5102, "utxo unlocked")
35 |
36 | ErrInvalidAmount = errorsmod.Register(ModuleName, 6100, "invalid amount")
37 | ErrInvalidFeeRate = errorsmod.Register(ModuleName, 6101, "invalid fee rate")
38 | ErrAssetNotSupported = errorsmod.Register(ModuleName, 6102, "asset not supported")
39 | ErrDustOutput = errorsmod.Register(ModuleName, 6103, "too small output amount")
40 | ErrInsufficientUTXOs = errorsmod.Register(ModuleName, 6104, "insufficient utxos")
41 | ErrFailToSerializePsbt = errorsmod.Register(ModuleName, 6105, "failed to serialize psbt")
42 |
43 | ErrInvalidRunes = errorsmod.Register(ModuleName, 7100, "invalid runes")
44 | ErrInvalidRuneId = errorsmod.Register(ModuleName, 7101, "invalid rune id")
45 |
46 | ErrInvalidParams = errorsmod.Register(ModuleName, 8100, "invalid module params")
47 | )
48 |
--------------------------------------------------------------------------------
/x/btcbridge/types/keys.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "math/big"
5 | )
6 |
7 | const (
8 | // ModuleName defines the module name
9 | ModuleName = "btcbridge"
10 |
11 | // StoreKey defines the primary module store key
12 | StoreKey = ModuleName
13 |
14 | // RouterKey defines the module's message routing key
15 | RouterKey = ModuleName
16 |
17 | // MemStoreKey defines the in-memory store key
18 | MemStoreKey = "mem_btcbridge"
19 | )
20 |
21 | func KeyPrefix(p string) []byte {
22 | return []byte(p)
23 | }
24 |
25 | var (
26 | ParamsStoreKey = []byte{0x1}
27 | SequenceKey = []byte{0x2}
28 |
29 | // Host chain keys prefix the HostChain structs
30 | BtcBlockHeaderHashPrefix = []byte{0x11} // prefix for each key to a block header, for a hash
31 | BtcBlockHeaderHeightPrefix = []byte{0x12} // prefix for each key to a block hash, for a height
32 | BtcBestBlockHeaderKey = []byte{0x13} // key for the best block height
33 | BtcSigningRequestPrefix = []byte{0x14} // prefix for each key to a signing request
34 |
35 | BtcUtxoKeyPrefix = []byte{0x15} // prefix for each key to a utxo
36 | BtcOwnerUtxoKeyPrefix = []byte{0x16} // prefix for each key to an owned utxo
37 | BtcOwnerRunesUtxoKeyPrefix = []byte{0x17} // prefix for each key to an owned runes utxo
38 |
39 | BtcMintedTxHashKeyPrefix = []byte{0x18} // prefix for each key to a minted tx hash
40 |
41 | BtcLockedAssetKeyPrefix = []byte{0x19} // prefix for each key to the locked asset
42 | )
43 |
44 | func Int64ToBytes(number uint64) []byte {
45 | big := new(big.Int)
46 | big.SetUint64(number)
47 | return big.Bytes()
48 | }
49 |
50 | func BtcUtxoKey(hash string, vout uint64) []byte {
51 | return append(append(BtcUtxoKeyPrefix, []byte(hash)...), Int64ToBytes(vout)...)
52 | }
53 |
54 | func BtcOwnerUtxoKey(owner string, hash string, vout uint64) []byte {
55 | key := append(append(BtcOwnerUtxoKeyPrefix, []byte(owner)...), []byte(hash)...)
56 | key = append(key, Int64ToBytes(vout)...)
57 |
58 | return key
59 | }
60 |
61 | func BtcOwnerRunesUtxoKey(owner string, id string, hash string, vout uint64) []byte {
62 | key := append(append(BtcOwnerRunesUtxoKeyPrefix, []byte(owner)...), []byte(id)...)
63 | key = append(key, []byte(hash)...)
64 | key = append(key, Int64ToBytes(vout)...)
65 |
66 | return key
67 | }
68 |
69 | func BtcBlockHeaderHashKey(hash string) []byte {
70 | return append(BtcBlockHeaderHashPrefix, []byte(hash)...)
71 | }
72 |
73 | func BtcBlockHeaderHeightKey(height uint64) []byte {
74 | return append(BtcBlockHeaderHeightPrefix, Int64ToBytes(height)...)
75 | }
76 |
77 | // @deprecated, use BtcSigningRequestHashKey instead
78 | func BtcSigningRequestKey(sequence uint64) []byte {
79 | return append(BtcSigningRequestPrefix, Int64ToBytes(sequence)...)
80 | }
81 |
82 | func BtcSigningRequestHashKey(txid string) []byte {
83 | return append(BtcSigningRequestPrefix, []byte(txid)...)
84 | }
85 |
86 | func BtcMintedTxHashKey(hash string) []byte {
87 | return append(BtcMintedTxHashKeyPrefix, []byte(hash)...)
88 | }
89 |
90 | func BtcLockedAssetKey(txHash string, coin []byte) []byte {
91 | return append(append(BtcLockedAssetKeyPrefix, []byte(txHash)...), coin...)
92 | }
93 |
--------------------------------------------------------------------------------
/x/btcbridge/types/genesis.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "github.com/btcsuite/btcd/chaincfg"
5 | sdk "github.com/cosmos/cosmos-sdk/types"
6 | )
7 |
8 | // this line is used by starport scaffolding # genesis/types/import
9 |
10 | func DefaultBestBlockHeader() *BlockHeader {
11 | config := sdk.GetConfig().GetBtcChainCfg()
12 | switch config.Name {
13 | case chaincfg.MainNetParams.Name:
14 | return DefaultMainNetBestBlockHeader()
15 | case chaincfg.SigNetParams.Name:
16 | return DefaultSignetBestBlockHeader()
17 | }
18 | return DefaultTestnetBestBlockHeader()
19 | }
20 |
21 | func DefaultSignetBestBlockHeader() *BlockHeader {
22 | // testnet3 block 2815023
23 | return &BlockHeader{
24 | Version: 536870912,
25 | Hash: "0000017317a7dfa637773406765d308e93cb5a8e5e266bb21687e120bf0e13d3",
26 | Height: 1,
27 | PreviousBlockHash: "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6",
28 | MerkleRoot: "f1192075c6416b02df1487f1f302d925a875bec7e37cf38079e00b1cd831898a",
29 | Time: 1718707794,
30 | Bits: "1e0377ae",
31 | Nonce: 13603325,
32 | Ntx: 1,
33 | }
34 | }
35 |
36 | func DefaultTestnetBestBlockHeader() *BlockHeader {
37 | // testnet3 block 2815023
38 | return &BlockHeader{
39 | Version: 667459584,
40 | Hash: "0000000000000009fb68da72e8994f014fafb455c72978233b94580b12af778c",
41 | Height: 2815023,
42 | PreviousBlockHash: "0000000000000004a29c20eb32532718de8072665620edb4c657b22b4d463967",
43 | MerkleRoot: "9e219423eadce80e882cdff04b3026c9bbc994fd08a774f34a705ca3e710a332",
44 | Time: 1715566066,
45 | Bits: "191881b8",
46 | Nonce: 3913166971,
47 | Ntx: 6236,
48 | }
49 | }
50 |
51 | func DefaultMainNetBestBlockHeader() *BlockHeader {
52 | // testnet3 block 2815023
53 | return &BlockHeader{
54 | Version: 667459584,
55 | Hash: "0000000000000009fb68da72e8994f014fafb455c72978233b94580b12af778c",
56 | Height: 2815023,
57 | PreviousBlockHash: "0000000000000004a29c20eb32532718de8072665620edb4c657b22b4d463967",
58 | MerkleRoot: "9e219423eadce80e882cdff04b3026c9bbc994fd08a774f34a705ca3e710a332",
59 | Time: 1715566066,
60 | Bits: "191881b8",
61 | Nonce: 3913166971,
62 | Ntx: 6236,
63 | }
64 | }
65 |
66 | // DefaultGenesis returns the default genesis state
67 | func DefaultGenesis() *GenesisState {
68 | return &GenesisState{
69 | // this line is used by starport scaffolding # genesis/types/default
70 | Params: DefaultParams(),
71 | BestBlockHeader: DefaultBestBlockHeader(),
72 | BlockHeaders: []*BlockHeader{},
73 | Utxos: []*UTXO{},
74 | }
75 | }
76 |
77 | // Validate performs basic genesis state validation returning an error upon any
78 | // failure.
79 | func (gs GenesisState) Validate() error {
80 | // this line is used by starport scaffolding # genesis/types/validate
81 | // need to be improved by checking the block headers & best block header
82 | if gs.BestBlockHeader == nil || gs.BestBlockHeader.Hash == "" || gs.BestBlockHeader.PreviousBlockHash == "" || gs.BestBlockHeader.MerkleRoot == "" {
83 | return ErrInvalidHeader
84 | }
85 | return gs.Params.Validate()
86 | }
87 |
--------------------------------------------------------------------------------
/second_node.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | KEYS=("dev0" "dev1")
4 | CHAINID="grimoria-testnet-1"
5 | MONIKER="Side Labs"
6 | BINARY="$HOME/go/bin/sided"
7 | DENOM_STR="uside,uusdt,uusdc"
8 |
9 | set -f
10 | IFS=,
11 | DENOMS=($DENOM_STR)
12 |
13 | IFS=";"
14 |
15 |
16 | INITIAL_SUPPLY="500000000000000"
17 | BLOCK_GAS=10000000
18 | MAX_GAS=10000000000
19 |
20 | # Remember to change to other types of keyring like 'file' in-case exposing to outside world,
21 | # otherwise your balance will be wiped quickly
22 | # The keyring test does not require private key to steal tokens from you
23 | KEYRING="test"
24 | #KEYALGO="secp256k1"
25 | KEYALGO="segwit"
26 | LOGLEVEL="info"
27 | # Set dedicated home directory for the $BINARY instance
28 | HOMEDIR="$HOME/.side2"
29 |
30 | # Path variables
31 | CONFIG=$HOMEDIR/config/config.toml
32 | APP_TOML=$HOMEDIR/config/app.toml
33 | GENESIS=$HOMEDIR/config/genesis.json
34 | TMP_GENESIS=$HOMEDIR/config/tmp_genesis.json
35 | PEER_ID="peer"
36 |
37 | # validate dependencies are installed
38 | command -v jq >/dev/null 2>&1 || {
39 | echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"
40 | exit 1
41 | }
42 |
43 | # used to exit on first error (any non-zero exit code)
44 | set -e
45 |
46 | # Reinstall daemon
47 | # make install
48 |
49 | # User prompt if an existing local node configuration is found.
50 | if [ -d "$HOMEDIR" ]; then
51 | printf "\nAn existing folder at '%s' was found. You can choose to delete this folder and start a new local node with new keys from genesis. When declined, the existing local node is started. \n" "$HOMEDIR"
52 | echo "Overwrite the existing configuration and start a new local node? [y/n]"
53 | read -r overwrite
54 | else
55 | overwrite="Y"
56 | fi
57 |
58 |
59 | # Setup local node if overwrite is set to Yes, otherwise skip setup
60 | if [[ $overwrite == "y" || $overwrite == "Y" ]]; then
61 | # Remove the previous folder
62 | rm -rf "$HOMEDIR"
63 |
64 | $BINARY init $MONIKER -o --chain-id $CHAINID --home "$HOMEDIR"
65 |
66 | # Set client config
67 | cp -r ~/.side/config/genesis.json $HOMEDIR/config/genesis.json
68 | $BINARY config keyring-backend $KEYRING --home "$HOMEDIR"
69 | $BINARY config chain-id $CHAINID --home "$HOMEDIR"
70 |
71 | sed -i.bak 's/127.0.0.1:26657/0.0.0.0:16657/g' "$CONFIG"
72 | sed -i.bak 's/127.0.0.1:26658/0.0.0.0:16658/g' "$CONFIG"
73 | sed -i.bak 's/0.0.0.0:26656/0.0.0.0:16656/g' "$CONFIG"
74 | sed -i.bak 's/persistent_peers = ""/persistent_peers = "dfd3e3c99414aa850f6e269cf4a674a66062cd49@127.0.0.1:26656"/g' "$CONFIG"
75 | #sed -i 's/persistent_peers = "$PEERID"/g' "$CONFIG"
76 |
77 | sed -i.bak 's/swagger = false/swagger = true/g' $APP_TOML
78 | sed -i.bak 's/localhost:9090/localhost:8090/g' $APP_TOML
79 |
80 | $BINARY keys add secp256k1_key --key-type segwit --home "$HOMEDIR"
81 | $BINARY tx bank send dev1 $($BINARY keys show secp256k1_key -a --home "$HOMEDIR") 2000000000uside --chain-id $CHAINID --fees 200uside --yes
82 | sleep 6
83 | $BINARY keys add segwit_key --key-type segwit --home "$HOMEDIR"
84 | $BINARY tx bank send dev1 $($BINARY keys show segwit_key -a --home "$HOMEDIR") 2000000000uside --chain-id $CHAINID --fees 200uside --yes
85 |
86 |
87 | fi
88 |
89 | # Start the node (remove the --pruning=nothing flag if historical queries are not needed)
90 | $BINARY start --log_level info --minimum-gas-prices=0.0001${DENOMS[0]} --home "$HOMEDIR" --pruning="everything"
91 |
--------------------------------------------------------------------------------
/testutil/network/network.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 |
8 | tmdb "github.com/cometbft/cometbft-db"
9 | tmrand "github.com/cometbft/cometbft/libs/rand"
10 | "github.com/cosmos/cosmos-sdk/baseapp"
11 | "github.com/cosmos/cosmos-sdk/crypto/hd"
12 | "github.com/cosmos/cosmos-sdk/crypto/keyring"
13 | servertypes "github.com/cosmos/cosmos-sdk/server/types"
14 | pruningtypes "github.com/cosmos/cosmos-sdk/store/pruning/types"
15 | "github.com/cosmos/cosmos-sdk/testutil/network"
16 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
17 | sdk "github.com/cosmos/cosmos-sdk/types"
18 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
19 | "github.com/stretchr/testify/require"
20 |
21 | "github.com/sideprotocol/side/app"
22 | )
23 |
24 | type (
25 | Network = network.Network
26 | Config = network.Config
27 | )
28 |
29 | // New creates instance with fully configured cosmos network.
30 | // Accepts optional config, that will be used in place of the DefaultConfig() if provided.
31 | func New(t *testing.T, configs ...Config) *Network {
32 | if len(configs) > 1 {
33 | panic("at most one config should be provided")
34 | }
35 | var cfg network.Config
36 | if len(configs) == 0 {
37 | cfg = DefaultConfig()
38 | } else {
39 | cfg = configs[0]
40 | }
41 | net, err := network.New(t, t.TempDir(), cfg)
42 | require.NoError(t, err)
43 | _, err = net.WaitForHeight(1)
44 | require.NoError(t, err)
45 | t.Cleanup(net.Cleanup)
46 | return net
47 | }
48 |
49 | // DefaultConfig will initialize config for the network with custom application,
50 | // genesis and single validator. All other parameters are inherited from cosmos-sdk/testutil/network.DefaultConfig
51 | func DefaultConfig() network.Config {
52 | var (
53 | encoding = app.MakeEncodingConfig()
54 | chainID = "chain-" + tmrand.NewRand().Str(6)
55 | )
56 | return network.Config{
57 | Codec: encoding.Marshaler,
58 | TxConfig: encoding.TxConfig,
59 | LegacyAmino: encoding.Amino,
60 | InterfaceRegistry: encoding.InterfaceRegistry,
61 | AccountRetriever: authtypes.AccountRetriever{},
62 | AppConstructor: func(val network.ValidatorI) servertypes.Application {
63 | return app.New(
64 | val.GetCtx().Logger,
65 | tmdb.NewMemDB(),
66 | nil,
67 | true,
68 | map[int64]bool{},
69 | val.GetCtx().Config.RootDir,
70 | 0,
71 | encoding,
72 | simtestutil.EmptyAppOptions{},
73 | baseapp.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)),
74 | baseapp.SetMinGasPrices(val.GetAppConfig().MinGasPrices),
75 | baseapp.SetChainID(chainID),
76 | )
77 | },
78 | GenesisState: app.ModuleBasics.DefaultGenesis(encoding.Marshaler),
79 | TimeoutCommit: 2 * time.Second,
80 | ChainID: chainID,
81 | NumValidators: 1,
82 | BondDenom: sdk.DefaultBondDenom,
83 | MinGasPrices: fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom),
84 | AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction),
85 | StakingTokens: sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction),
86 | BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction),
87 | PruningStrategy: pruningtypes.PruningOptionNothing,
88 | CleanupDir: true,
89 | SigningAlgo: string(hd.Secp256k1Type),
90 | KeyringOptions: []keyring.Option{},
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/x/btcbridge/types/signature.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | secp256k1 "github.com/btcsuite/btcd/btcec/v2"
5 | "github.com/btcsuite/btcd/btcec/v2/ecdsa"
6 | "github.com/btcsuite/btcd/btcec/v2/schnorr"
7 | "github.com/btcsuite/btcd/btcutil/psbt"
8 | "github.com/btcsuite/btcd/txscript"
9 | "github.com/btcsuite/btcd/wire"
10 | )
11 |
12 | // VerifyPsbtSignatures verifies the signatures of the given psbt
13 | // Note: assume that the psbt is finalized and all inputs are witness type
14 | func VerifyPsbtSignatures(p *psbt.Packet) bool {
15 | // extract signed tx
16 | signedTx, err := psbt.Extract(p)
17 | if err != nil {
18 | return false
19 | }
20 |
21 | // build previous output fetcher
22 | prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil)
23 |
24 | for i, txIn := range p.UnsignedTx.TxIn {
25 | prevOutput := p.Inputs[i].WitnessUtxo
26 | if prevOutput == nil {
27 | return false
28 | }
29 |
30 | prevOutputFetcher.AddPrevOut(txIn.PreviousOutPoint, prevOutput)
31 | }
32 |
33 | // verify signatures
34 | for i := range p.Inputs {
35 | prevOutput := p.Inputs[i].WitnessUtxo
36 | hashType := DefaultSigHashType
37 |
38 | switch {
39 | case txscript.IsPayToWitnessPubKeyHash(prevOutput.PkScript):
40 | if !verifyWitnessSignature(signedTx, i, prevOutput, prevOutputFetcher, hashType) {
41 | return false
42 | }
43 |
44 | case txscript.IsPayToTaproot(prevOutput.PkScript):
45 | if !verifyTaprootSignature(signedTx, i, prevOutput, prevOutputFetcher, hashType) {
46 | return false
47 | }
48 |
49 | default:
50 | return false
51 | }
52 | }
53 |
54 | return true
55 | }
56 |
57 | // verifyWitnessSignature verifies the signature of the witness v0 input
58 | func verifyWitnessSignature(tx *wire.MsgTx, idx int, prevOutput *wire.TxOut, prevOutputFetcher txscript.PrevOutputFetcher, hashType txscript.SigHashType) bool {
59 | witness := tx.TxIn[idx].Witness
60 | if len(witness) != 2 {
61 | return false
62 | }
63 |
64 | sigBytes := witness[0]
65 | pkBytes := witness[1]
66 |
67 | sig, err := ecdsa.ParseDERSignature(sigBytes)
68 | if err != nil {
69 | return false
70 | }
71 |
72 | pk, err := secp256k1.ParsePubKey(pkBytes)
73 | if err != nil {
74 | return false
75 | }
76 |
77 | if sigBytes[len(sigBytes)-1] != byte(hashType) {
78 | return false
79 | }
80 |
81 | sigHash, err := txscript.CalcWitnessSigHash(prevOutput.PkScript, txscript.NewTxSigHashes(tx, prevOutputFetcher),
82 | hashType, tx, idx, prevOutput.Value)
83 | if err != nil {
84 | return false
85 | }
86 |
87 | return sig.Verify(sigHash, pk)
88 | }
89 |
90 | // verifyTaprootSignature verifies the signature of the taproot input
91 | func verifyTaprootSignature(tx *wire.MsgTx, idx int, prevOutput *wire.TxOut, prevOutputFetcher txscript.PrevOutputFetcher, hashType txscript.SigHashType) bool {
92 | witness := tx.TxIn[idx].Witness
93 | if len(witness) != 1 || len(witness[0]) == 0 {
94 | return false
95 | }
96 |
97 | sigBytes := witness[0]
98 |
99 | if hashType != txscript.SigHashDefault {
100 | if sigBytes[len(sigBytes)-1] != byte(hashType) {
101 | return false
102 | }
103 |
104 | sigBytes = sigBytes[0 : len(sigBytes)-1]
105 | }
106 |
107 | sig, err := schnorr.ParseSignature(sigBytes)
108 | if err != nil {
109 | return false
110 | }
111 |
112 | pk, err := schnorr.ParsePubKey(prevOutput.PkScript[2:34])
113 | if err != nil {
114 | return false
115 | }
116 |
117 | sigHash, err := txscript.CalcTaprootSignatureHash(txscript.NewTxSigHashes(tx, prevOutputFetcher),
118 | hashType, tx, idx, prevOutputFetcher)
119 | if err != nil {
120 | return false
121 | }
122 |
123 | return sig.Verify(sigHash, pk)
124 | }
125 |
--------------------------------------------------------------------------------
/x/btcbridge/types/params.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "bytes"
5 | "encoding/hex"
6 |
7 | secp256k1 "github.com/btcsuite/btcd/btcec/v2"
8 | "github.com/btcsuite/btcd/btcutil"
9 | "github.com/btcsuite/btcd/txscript"
10 |
11 | sdk "github.com/cosmos/cosmos-sdk/types"
12 | )
13 |
14 | // NewParams creates a new Params instance
15 | func NewParams(relayers []string) Params {
16 | return Params{
17 | AuthorizedRelayers: relayers,
18 | Confirmations: 2,
19 | MaxAcceptableBlockDepth: 100,
20 | BtcVoucherDenom: "sat",
21 | Vaults: []*Vault{{
22 | Address: "",
23 | PubKey: "",
24 | AssetType: AssetType_ASSET_TYPE_BTC,
25 | }, {
26 | Address: "",
27 | PubKey: "",
28 | AssetType: AssetType_ASSET_TYPE_RUNE,
29 | }},
30 | }
31 | }
32 |
33 | // DefaultParams returns a default set of parameters
34 | func DefaultParams() Params {
35 | return NewParams([]string{})
36 | }
37 |
38 | // Validate validates the set of params
39 | func (p Params) Validate() error {
40 | for _, sender := range p.AuthorizedRelayers {
41 | _, err := sdk.AccAddressFromBech32(sender)
42 | if err != nil {
43 | return err
44 | }
45 | }
46 |
47 | if err := sdk.ValidateDenom(p.BtcVoucherDenom); err != nil {
48 | return err
49 | }
50 |
51 | vaults := make(map[string]bool)
52 |
53 | for _, vault := range p.Vaults {
54 | _, ok := vaults[vault.Address]
55 | if ok {
56 | return ErrInvalidParams
57 | }
58 |
59 | _, err := sdk.AccAddressFromBech32(vault.Address)
60 | if err != nil {
61 | return err
62 | }
63 |
64 | if len(vault.PubKey) != 0 {
65 | pkBytes, err := hex.DecodeString(vault.PubKey)
66 | if err != nil {
67 | return err
68 | }
69 |
70 | _, err = secp256k1.ParsePubKey(pkBytes)
71 | if err != nil {
72 | return err
73 | }
74 | }
75 |
76 | if vault.AssetType == AssetType_ASSET_TYPE_UNSPECIFIED {
77 | return ErrInvalidParams
78 | }
79 | }
80 |
81 | return nil
82 | }
83 |
84 | // checks if the given address is an authorized sender
85 | func (p Params) IsAuthorizedSender(sender string) bool {
86 | for _, s := range p.AuthorizedRelayers {
87 | if s == sender {
88 | return true
89 | }
90 | }
91 | return false
92 | }
93 |
94 | // SelectVaultByBitcoinAddress returns the vault if the address is found
95 | // returns the vault if the address is found
96 | func SelectVaultByBitcoinAddress(vaults []*Vault, address string) *Vault {
97 | for _, v := range vaults {
98 | if v.Address == address {
99 | return v
100 | }
101 | }
102 | return nil
103 | }
104 |
105 | // SelectVaultByPubKey returns the vault if the public key is found
106 | // returns the vault if the public key is found
107 | func SelectVaultByPubKey(vaults []*Vault, pubKey string) *Vault {
108 | for _, v := range vaults {
109 | if v.PubKey == pubKey {
110 | return v
111 | }
112 | }
113 |
114 | return nil
115 | }
116 |
117 | // SelectVaultByAssetType returns the vault by the asset type
118 | func SelectVaultByAssetType(vaults []*Vault, assetType AssetType) *Vault {
119 | for _, v := range vaults {
120 | if v.AssetType == assetType {
121 | return v
122 | }
123 | }
124 |
125 | return nil
126 | }
127 |
128 | // SelectVaultByPkScript returns the vault by the pk script
129 | func SelectVaultByPkScript(vaults []*Vault, pkScript []byte) *Vault {
130 | chainCfg := sdk.GetConfig().GetBtcChainCfg()
131 |
132 | for _, v := range vaults {
133 | addr, err := btcutil.DecodeAddress(v.Address, chainCfg)
134 | if err != nil {
135 | continue
136 | }
137 |
138 | addrScript, err := txscript.PayToAddrScript(addr)
139 | if err != nil {
140 | continue
141 | }
142 |
143 | if bytes.Equal(addrScript, pkScript) {
144 | return v
145 | }
146 | }
147 |
148 | return nil
149 | }
150 |
--------------------------------------------------------------------------------
/x/btcbridge/genesis_test.go:
--------------------------------------------------------------------------------
1 | package btcbridge_test
2 |
3 | // Path: x/btcbridge/genesis_test.go
4 |
5 | import (
6 | "bytes"
7 | "encoding/hex"
8 | "testing"
9 |
10 | "github.com/btcsuite/btcd/btcutil"
11 | "github.com/btcsuite/btcd/wire"
12 | keepertest "github.com/sideprotocol/side/testutil/keeper"
13 | "github.com/sideprotocol/side/testutil/nullify"
14 | "github.com/sideprotocol/side/x/btcbridge"
15 | "github.com/sideprotocol/side/x/btcbridge/types"
16 | "github.com/stretchr/testify/require"
17 | )
18 |
19 | func TestGenesis(t *testing.T) {
20 | mnemonic := "sunny bamboo garlic fold reopen exile letter addict forest vessel square lunar shell number deliver cruise calm artist fire just kangaroo suit wheel extend"
21 | println(mnemonic)
22 |
23 | genesisState := types.GenesisState{
24 | Params: types.DefaultParams(),
25 |
26 | // this line is used by starport scaffolding # genesis/test/state
27 | }
28 |
29 | k, ctx := keepertest.BtcBridgeKeeper(t)
30 | btcbridge.InitGenesis(ctx, *k, genesisState)
31 | got := btcbridge.ExportGenesis(ctx, *k)
32 | require.NotNil(t, got)
33 |
34 | nullify.Fill(&genesisState)
35 | nullify.Fill(got)
36 |
37 | // this line is used by starport scaffolding # genesis/test/assert
38 | }
39 |
40 | // TestSubmitTx tests the SubmitTx function
41 | // func TestSubmitTx(t *testing.T) {
42 |
43 | // // test tx: https://blockchain.info/tx/b657e22827039461a9493ede7bdf55b01579254c1630b0bfc9185ec564fc05ab?format=json
44 |
45 | // k, ctx := keepertest.BtcbridgeClientKeeper(t)
46 |
47 | // txHex := "02000000000101e5df234dbeff74d6da14754ebeea8ab0e2f60b1884566846cf6b36e8ceb5f5350100000000fdffffff02f0490200000000001600142ac13073e8d491428790481321a636696d00107681d7e205000000001600142bf3aa186cbdcbe88b70a67edcd5a32ce5e8e6d8024730440220081ee61d749ce8cedcf6eedde885579af2eb65ca67d29e6ae2c37109d81cbbb202203a1891ce45f91f159ccf04348ef37a3d1a12d89e5e01426e061326057e6c128d012103036bbdd77c9a932f37bd66175967c7fb7eb75ece06b87c1ad1716770cb3ca4ee79fc2a00"
48 | // prevTxHex := "0200000000010183372652f2af9ab34b3a003efada6b054c75583185ac130de72599dfdf4e462b0100000000fdffffff02f0490200000000001600142ac13073e8d491428790481321a636696d001076a64ee50500000000160014a03614eef338681373de94a2dc2574de55da1980024730440220274250f6036bea0947daf4455ab4976f81721257d163fd952fb5b0c70470edc602202fba816be260219bbc40a8983c459cf05cf2209bf1e62e7ccbf78aec54db607f0121031cee21ef69fe68b240c3032616fa310c6a60a856c0a7e0c1298815c92fb2c61788fb2a00"
49 |
50 | // msg := &types.MsgSubmitTransactionRequest{
51 | // Sender: "",
52 | // Blockhash: "000000000d73ecf25d3bf8e6ae65c35aa2a90e3271edff8bab90d87ed875f13b",
53 | // TxBytes: "0100000001b3f7",
54 | // PrevTxBytes: "0100000001b3f7",
55 | // Proof: []string{"0100000001b3f7"},
56 | // }
57 | // // this line is used by starport scaffolding # handler/test/submit
58 | // err := k.ProcessBitcoinDepositTransaction(ctx, msg)
59 | // require.NoError(t, err)
60 | // }
61 |
62 | // Decode transaction
63 | func TestDecodeTransaction(t *testing.T) {
64 | hexStr := "02000000000101e5df234dbeff74d6da14754ebeea8ab0e2f60b1884566846cf6b36e8ceb5f5350100000000fdffffff02f0490200000000001600142ac13073e8d491428790481321a636696d00107681d7e205000000001600142bf3aa186cbdcbe88b70a67edcd5a32ce5e8e6d8024730440220081ee61d749ce8cedcf6eedde885579af2eb65ca67d29e6ae2c37109d81cbbb202203a1891ce45f91f159ccf04348ef37a3d1a12d89e5e01426e061326057e6c128d012103036bbdd77c9a932f37bd66175967c7fb7eb75ece06b87c1ad1716770cb3ca4ee79fc2a00"
65 |
66 | // Decode the hex string to transaction
67 | txBytes, err := hex.DecodeString(hexStr)
68 | require.NoError(t, err)
69 |
70 | // Create a new transaction
71 | var tx wire.MsgTx
72 | err = tx.Deserialize(bytes.NewReader(txBytes))
73 | require.NoError(t, err)
74 |
75 | uTx := btcutil.NewTx(&tx)
76 |
77 | for _, input := range uTx.MsgTx().TxIn {
78 | t.Log(input.PreviousOutPoint.String())
79 | }
80 |
81 | require.GreaterOrEqual(t, len(uTx.MsgTx().TxIn), 1)
82 | }
83 |
--------------------------------------------------------------------------------
/x/btcbridge/keeper/queries.go:
--------------------------------------------------------------------------------
1 | package keeper
2 |
3 | import (
4 | "context"
5 |
6 | sdk "github.com/cosmos/cosmos-sdk/types"
7 | "github.com/sideprotocol/side/x/btcbridge/types"
8 | "google.golang.org/grpc/codes"
9 | "google.golang.org/grpc/status"
10 | )
11 |
12 | var _ types.QueryServer = Keeper{}
13 |
14 | func (k Keeper) QueryParams(goCtx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
15 | if req == nil {
16 | return nil, status.Error(codes.InvalidArgument, "invalid request")
17 | }
18 | ctx := sdk.UnwrapSDKContext(goCtx)
19 |
20 | return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil
21 | }
22 |
23 | func (k Keeper) QueryChainTip(goCtx context.Context, req *types.QueryChainTipRequest) (*types.QueryChainTipResponse, error) {
24 | if req == nil {
25 | return nil, status.Error(codes.InvalidArgument, "invalid request")
26 | }
27 | ctx := sdk.UnwrapSDKContext(goCtx)
28 |
29 | best := k.GetBestBlockHeader(ctx)
30 |
31 | return &types.QueryChainTipResponse{
32 | Hash: best.Hash,
33 | Height: best.Height,
34 | }, nil
35 | }
36 |
37 | // BlockHeaderByHash queries the block header by hash.
38 | func (k Keeper) QueryBlockHeaderByHash(goCtx context.Context, req *types.QueryBlockHeaderByHashRequest) (*types.QueryBlockHeaderByHashResponse, error) {
39 | if req == nil {
40 | return nil, status.Error(codes.InvalidArgument, "invalid request")
41 | }
42 | ctx := sdk.UnwrapSDKContext(goCtx)
43 |
44 | header := k.GetBlockHeader(ctx, req.Hash)
45 | if header == nil {
46 | return nil, status.Error(codes.NotFound, "block header not found")
47 | }
48 |
49 | return &types.QueryBlockHeaderByHashResponse{BlockHeader: header}, nil
50 | }
51 |
52 | func (k Keeper) QueryBlockHeaderByHeight(goCtx context.Context, req *types.QueryBlockHeaderByHeightRequest) (*types.QueryBlockHeaderByHeightResponse, error) {
53 | if req == nil {
54 | return nil, status.Error(codes.InvalidArgument, "invalid request")
55 | }
56 | ctx := sdk.UnwrapSDKContext(goCtx)
57 |
58 | header := k.GetBlockHeaderByHeight(ctx, req.Height)
59 | if header == nil {
60 | return nil, status.Error(codes.NotFound, "block header not found")
61 | }
62 |
63 | return &types.QueryBlockHeaderByHeightResponse{BlockHeader: header}, nil
64 | }
65 |
66 | func (k Keeper) QuerySigningRequest(goCtx context.Context, req *types.QuerySigningRequestRequest) (*types.QuerySigningRequestResponse, error) {
67 | if req == nil {
68 | return nil, status.Error(codes.InvalidArgument, "invalid request")
69 | }
70 | if req.Status == types.SigningStatus_SIGNING_STATUS_UNSPECIFIED {
71 | return nil, status.Error(codes.InvalidArgument, "invalid status")
72 | }
73 |
74 | ctx := sdk.UnwrapSDKContext(goCtx)
75 |
76 | requests := k.FilterSigningRequestsByStatus(ctx, req)
77 |
78 | return &types.QuerySigningRequestResponse{Requests: requests}, nil
79 | }
80 |
81 | func (k Keeper) QuerySigningRequestByAddress(goCtx context.Context, req *types.QuerySigningRequestByAddressRequest) (*types.QuerySigningRequestByAddressResponse, error) {
82 | if req == nil {
83 | return nil, status.Error(codes.InvalidArgument, "invalid request")
84 | }
85 |
86 | ctx := sdk.UnwrapSDKContext(goCtx)
87 |
88 | requests := k.FilterSigningRequestsByAddr(ctx, req)
89 |
90 | return &types.QuerySigningRequestByAddressResponse{Requests: requests}, nil
91 | }
92 |
93 | func (k Keeper) QuerySigningRequestByTxHash(goCtx context.Context, req *types.QuerySigningRequestByTxHashRequest) (*types.QuerySigningRequestByTxHashResponse, error) {
94 | if req == nil {
95 | return nil, status.Error(codes.InvalidArgument, "invalid request")
96 | }
97 |
98 | ctx := sdk.UnwrapSDKContext(goCtx)
99 |
100 | var request *types.BitcoinSigningRequest
101 |
102 | if k.HasSigningRequest(ctx, req.Txid) {
103 | request = k.GetSigningRequest(ctx, req.Txid)
104 | }
105 |
106 | return &types.QuerySigningRequestByTxHashResponse{Request: request}, nil
107 | }
108 |
109 | func (k Keeper) QueryUTXOs(goCtx context.Context, req *types.QueryUTXOsRequest) (*types.QueryUTXOsResponse, error) {
110 | if req == nil {
111 | return nil, status.Error(codes.InvalidArgument, "invalid request")
112 | }
113 | ctx := sdk.UnwrapSDKContext(goCtx)
114 |
115 | utxos := k.GetAllUTXOs(ctx)
116 |
117 | return &types.QueryUTXOsResponse{Utxos: utxos}, nil
118 | }
119 |
120 | func (k Keeper) QueryUTXOsByAddress(goCtx context.Context, req *types.QueryUTXOsByAddressRequest) (*types.QueryUTXOsByAddressResponse, error) {
121 | if req == nil {
122 | return nil, status.Error(codes.InvalidArgument, "invalid request")
123 | }
124 | ctx := sdk.UnwrapSDKContext(goCtx)
125 |
126 | _, err := sdk.AccAddressFromBech32(req.Address)
127 | if err != nil {
128 | return nil, err
129 | }
130 |
131 | utxos := k.GetUTXOsByAddr(ctx, req.Address)
132 |
133 | return &types.QueryUTXOsByAddressResponse{Utxos: utxos}, nil
134 | }
135 |
--------------------------------------------------------------------------------
/x/btcbridge/types/deposit_policy.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "github.com/btcsuite/btcd/btcutil"
5 | "github.com/btcsuite/btcd/chaincfg"
6 | "github.com/btcsuite/btcd/txscript"
7 | "github.com/btcsuite/btcd/wire"
8 | )
9 |
10 | const (
11 | // maximum allowed number of the non-vault outputs for the btc deposit transaction
12 | MaxNonVaultOutNum = 1
13 |
14 | // maximum allowed number of the non-vault outputs for the runes deposit transaction
15 | RunesMaxNonVaultOutNum = 3
16 |
17 | // allowed number of edicts in the runes payload for the runes deposit transaction
18 | RunesEdictNum = 1
19 | )
20 |
21 | // ExtractRecipientAddr extracts the recipient address for minting voucher token by the type of the asset to be deposited
22 | func ExtractRecipientAddr(tx *wire.MsgTx, prevTx *wire.MsgTx, vaults []*Vault, isRunes bool, chainCfg *chaincfg.Params) (btcutil.Address, error) {
23 | if isRunes {
24 | return ExtractRunesRecipientAddr(tx, prevTx, vaults, chainCfg)
25 | }
26 |
27 | return ExtractCommonRecipientAddr(tx, prevTx, vaults, chainCfg)
28 | }
29 |
30 | // ExtractCommonRecipientAddr extracts the recipient address for minting voucher token in the common case.
31 | // First, extract the recipient from the tx out which is a non-vault address;
32 | // Then fall back to the first input
33 | func ExtractCommonRecipientAddr(tx *wire.MsgTx, prevTx *wire.MsgTx, vaults []*Vault, chainCfg *chaincfg.Params) (btcutil.Address, error) {
34 | var recipient btcutil.Address
35 |
36 | nonVaultOutCount := 0
37 |
38 | // extract from the tx out which is a non-vault address
39 | for _, out := range tx.TxOut {
40 | pkScript, err := txscript.ParsePkScript(out.PkScript)
41 | if err != nil {
42 | return nil, err
43 | }
44 |
45 | addr, err := pkScript.Address(chainCfg)
46 | if err != nil {
47 | return nil, err
48 | }
49 |
50 | vault := SelectVaultByBitcoinAddress(vaults, addr.EncodeAddress())
51 | if vault == nil {
52 | recipient = addr
53 | nonVaultOutCount++
54 | }
55 | }
56 |
57 | // exceed allowed non vault out number
58 | if nonVaultOutCount > MaxNonVaultOutNum {
59 | return nil, ErrInvalidDepositTransaction
60 | }
61 |
62 | if recipient != nil {
63 | return recipient, nil
64 | }
65 |
66 | // fall back to extract from the first input
67 | pkScript, err := txscript.ParsePkScript(prevTx.TxOut[tx.TxIn[0].PreviousOutPoint.Index].PkScript)
68 | if err != nil {
69 | return nil, err
70 | }
71 |
72 | return pkScript.Address(chainCfg)
73 | }
74 |
75 | // ExtractRunesRecipientAddr extracts the recipient address for minting runes voucher token.
76 | // First, extract the recipient from the tx out which is a non-vault and non-OP_RETURN output;
77 | // Then fall back to the first input
78 | func ExtractRunesRecipientAddr(tx *wire.MsgTx, prevTx *wire.MsgTx, vaults []*Vault, chainCfg *chaincfg.Params) (btcutil.Address, error) {
79 | var recipient btcutil.Address
80 |
81 | nonVaultOutCount := 0
82 |
83 | // extract from the tx out which is a non-vault and non-OP_RETURN output
84 | for _, out := range tx.TxOut {
85 | if IsOpReturnOutput(out) {
86 | nonVaultOutCount++
87 | continue
88 | }
89 |
90 | pkScript, err := txscript.ParsePkScript(out.PkScript)
91 | if err != nil {
92 | return nil, err
93 | }
94 |
95 | addr, err := pkScript.Address(chainCfg)
96 | if err != nil {
97 | return nil, err
98 | }
99 |
100 | vault := SelectVaultByBitcoinAddress(vaults, addr.EncodeAddress())
101 | if vault == nil {
102 | recipient = addr
103 | nonVaultOutCount++
104 | }
105 | }
106 |
107 | // exceed allowed non vault out number
108 | if nonVaultOutCount > RunesMaxNonVaultOutNum {
109 | return nil, ErrInvalidDepositTransaction
110 | }
111 |
112 | if recipient != nil {
113 | return recipient, nil
114 | }
115 |
116 | // fall back to extract from the first input
117 | pkScript, err := txscript.ParsePkScript(prevTx.TxOut[tx.TxIn[0].PreviousOutPoint.Index].PkScript)
118 | if err != nil {
119 | return nil, err
120 | }
121 |
122 | return pkScript.Address(chainCfg)
123 | }
124 |
125 | // CheckRunesDepositTransaction checks if the given tx is valid runes deposit tx
126 | func CheckRunesDepositTransaction(tx *wire.MsgTx, vaults []*Vault) (*Edict, error) {
127 | edicts, err := ParseRunes(tx)
128 | if err != nil {
129 | return nil, ErrInvalidDepositTransaction
130 | }
131 |
132 | if len(edicts) == 0 {
133 | return nil, nil
134 | }
135 |
136 | if len(edicts) != RunesEdictNum {
137 | return nil, ErrInvalidDepositTransaction
138 | }
139 |
140 | // even split is not supported
141 | if edicts[0].Output == uint32(len(tx.TxOut)) {
142 | return nil, ErrInvalidDepositTransaction
143 | }
144 |
145 | vault := SelectVaultByPkScript(vaults, tx.TxOut[edicts[0].Output].PkScript)
146 | if vault == nil || vault.AssetType != AssetType_ASSET_TYPE_RUNE {
147 | return nil, ErrInvalidDepositTransaction
148 | }
149 |
150 | return edicts[0], nil
151 | }
152 |
--------------------------------------------------------------------------------
/proto/side/btcbridge/tx.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package side.btcbridge;
4 |
5 | import "gogoproto/gogo.proto";
6 | import "side/btcbridge/params.proto";
7 | import "side/btcbridge/bitcoin.proto";
8 |
9 | option go_package = "github.com/sideprotocol/side/x/btcbridge/types";
10 |
11 | // Msg defines the Msg service.
12 | service Msg {
13 | // SubmitBlockHeaders submits bitcoin block headers to the side chain.
14 | rpc SubmitBlockHeaders (MsgSubmitBlockHeaderRequest) returns (MsgSubmitBlockHeadersResponse);
15 | // SubmitDepositTransaction submits bitcoin transaction to the side chain.
16 | rpc SubmitDepositTransaction (MsgSubmitDepositTransactionRequest) returns (MsgSubmitDepositTransactionResponse);
17 | // SubmitWithdrawalTransaction submits bitcoin transaction to the side chain.
18 | rpc SubmitWithdrawTransaction (MsgSubmitWithdrawTransactionRequest) returns (MsgSubmitWithdrawTransactionResponse);
19 | // UpdateSenders updates the senders of the side chain.
20 | rpc UpdateQualifiedRelayers (MsgUpdateQualifiedRelayersRequest) returns (MsgUpdateQualifiedRelayersResponse);
21 | // WithdrawBitcoin withdraws the bitcoin from the side chain.
22 | rpc WithdrawBitcoin (MsgWithdrawBitcoinRequest) returns (MsgWithdrawBitcoinResponse);
23 | // SubmitWithdrawSignatures submits the signatures of the withdraw transaction.
24 | rpc SubmitWithdrawSignatures (MsgSubmitWithdrawSignaturesRequest) returns (MsgSubmitWithdrawSignaturesResponse);
25 | // SubmitWithdrawStatus submits the status of the withdraw transaction.
26 | rpc SubmitWithdrawStatus (MsgSubmitWithdrawStatusRequest) returns (MsgSubmitWithdrawStatusResponse);
27 | // UpdateParams defines a governance operation for updating the x/btcbridge module
28 | // parameters. The authority defaults to the x/gov module account.
29 | //
30 | // Since: cosmos-sdk 0.47
31 | rpc UpdateParams(MsgUpdateParamsRequest) returns (MsgUpdateParamsResponse);
32 | }
33 |
34 | // MsgSubmitWithdrawStatusRequest defines the Msg/SubmitWithdrawStatus request type.
35 | message MsgSubmitWithdrawStatusRequest {
36 | string sender = 1;
37 | string txid = 2;
38 | SigningStatus status = 3;
39 | }
40 |
41 | // MsgSubmitWithdrawStatusResponse defines the Msg/SubmitWithdrawStatus response type.
42 | message MsgSubmitWithdrawStatusResponse {
43 | }
44 |
45 | // MsgBlockHeaderRequest defines the Msg/SubmitBlockHeaders request type.
46 | message MsgSubmitBlockHeaderRequest {
47 | string sender = 1;
48 | repeated BlockHeader block_headers = 2;
49 | }
50 |
51 | // MsgSubmitBlockHeadersResponse defines the Msg/SubmitBlockHeaders response type.
52 | message MsgSubmitBlockHeadersResponse {
53 | }
54 |
55 | // MsgSubmitTransactionRequest defines the Msg/SubmitTransaction request type.
56 | message MsgSubmitDepositTransactionRequest {
57 | // this is relayer address who submit the bitcoin transaction to the side chain
58 | string sender = 1;
59 | string blockhash = 2;
60 | // the tx bytes in base64 format
61 | // used for parsing the sender of the transaction
62 | string prev_tx_bytes = 3;
63 | // the tx bytes in base64 format
64 | string tx_bytes = 4;
65 | repeated string proof = 5;
66 | }
67 |
68 | // MsgSubmitTransactionResponse defines the Msg/SubmitTransaction response type.
69 | message MsgSubmitDepositTransactionResponse {
70 | }
71 |
72 | // MsgSubmitTransactionRequest defines the Msg/SubmitTransaction request type.
73 | message MsgSubmitWithdrawTransactionRequest {
74 | // this is relayer address who submit the bitcoin transaction to the side chain
75 | string sender = 1;
76 | string blockhash = 2;
77 | // the tx bytes in base64 format
78 | string tx_bytes = 4;
79 | repeated string proof = 5;
80 | }
81 |
82 | // MsgSubmitTransactionResponse defines the Msg/SubmitTransaction response type.
83 | message MsgSubmitWithdrawTransactionResponse {
84 | }
85 |
86 | // Msg defines the MsgUpdateSender service.
87 | message MsgUpdateQualifiedRelayersRequest {
88 | string sender = 1;
89 | // update senders who can send block headers to the side chain
90 | repeated string relayers = 2;
91 | }
92 |
93 | // MsgUpdateSenderResponse defines the Msg/UpdateSender response type.
94 | message MsgUpdateQualifiedRelayersResponse {
95 | }
96 |
97 | // MsgWithdrawBitcoinRequest defines the Msg/WithdrawBitcoin request type.
98 | message MsgWithdrawBitcoinRequest {
99 | string sender = 1;
100 | // withdraw amount in satoshi, etc: 100000000sat = 1btc
101 | string amount = 2;
102 | // fee rate in sats/vB
103 | string fee_rate = 3;
104 | }
105 |
106 | // MsgWithdrawBitcoinResponse defines the Msg/WithdrawBitcoin response type.
107 | message MsgWithdrawBitcoinResponse {
108 | }
109 |
110 | // MsgSubmitWithdrawSignaturesRequest defines the Msg/SubmitWithdrawSignatures request type.
111 | message MsgSubmitWithdrawSignaturesRequest {
112 | string sender = 1;
113 | string txid = 2;
114 | string psbt = 3;
115 | }
116 |
117 | // MsgSubmitWithdrawSignaturesResponse defines the Msg/SubmitWithdrawSignatures response type.
118 | message MsgSubmitWithdrawSignaturesResponse {
119 | }
120 |
121 | // MsgUpdateParamsRequest is the Msg/UpdateParams request type.
122 | //
123 | // Since: cosmos-sdk 0.47
124 | message MsgUpdateParamsRequest {
125 | // authority is the address that controls the module (defaults to x/gov unless overwritten).
126 | string authority = 1;
127 |
128 | // params defines the x/btcbridge parameters to be updated.
129 | //
130 | // NOTE: All parameters must be supplied.
131 | Params params = 2 [(gogoproto.nullable) = false];
132 | }
133 |
134 | // MsgUpdateParamsResponse defines the Msg/UpdateParams response type.
135 | //
136 | // Since: cosmos-sdk 0.47
137 | message MsgUpdateParamsResponse {}
138 |
--------------------------------------------------------------------------------
/x/btcbridge/types/runes.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 |
8 | "lukechampine.com/uint128"
9 |
10 | "github.com/btcsuite/btcd/txscript"
11 | "github.com/btcsuite/btcd/wire"
12 | )
13 |
14 | const (
15 | // runes protocol name
16 | RunesProtocolName = "runes"
17 |
18 | // runes magic number
19 | MagicNumber = txscript.OP_13
20 |
21 | // tag indicating that the following are edicts
22 | TagBody = 0
23 |
24 | // the number of components of each edict
25 | EdictLen = 4
26 |
27 | // sats in the runes output by default
28 | RunesOutValue = 546
29 | )
30 |
31 | // ParseRunes parses the potential runes protocol from the given tx;
32 | // If no OP_RETURN found, no error returned
33 | // Only support edicts for now
34 | func ParseRunes(tx *wire.MsgTx) ([]*Edict, error) {
35 | for _, out := range tx.TxOut {
36 | tokenizer := txscript.MakeScriptTokenizer(0, out.PkScript)
37 | if !tokenizer.Next() || tokenizer.Err() != nil || tokenizer.Opcode() != txscript.OP_RETURN {
38 | continue
39 | }
40 |
41 | if !tokenizer.Next() || tokenizer.Err() != nil || tokenizer.Opcode() != MagicNumber {
42 | continue
43 | }
44 |
45 | var payload []byte
46 |
47 | for tokenizer.Next() {
48 | if txscript.IsSmallInt(tokenizer.Opcode()) || tokenizer.Opcode() <= txscript.OP_PUSHDATA4 {
49 | payload = append(payload, tokenizer.Data()...)
50 | } else {
51 | return nil, ErrInvalidRunes
52 | }
53 | }
54 |
55 | if tokenizer.Err() != nil {
56 | return nil, ErrInvalidRunes
57 | }
58 |
59 | return ParseEdicts(tx, payload)
60 | }
61 |
62 | return nil, nil
63 | }
64 |
65 | // ParseEdicts parses the given payload to a set of edicts
66 | func ParseEdicts(tx *wire.MsgTx, payload []byte) ([]*Edict, error) {
67 | integers, err := DecodeVec(payload)
68 | if err != nil {
69 | return nil, err
70 | }
71 |
72 | if len(integers) < EdictLen+1 || len(integers[1:])%EdictLen != 0 || !integers[0].Equals(uint128.From64(TagBody)) {
73 | return nil, ErrInvalidRunes
74 | }
75 |
76 | integers = integers[1:]
77 |
78 | edicts := make([]*Edict, 0)
79 |
80 | for i := 0; i < len(integers); i = i + 4 {
81 | output := uint32(integers[i+3].Big().Uint64())
82 | if output > uint32(len(tx.TxOut)) {
83 | return nil, ErrInvalidRunes
84 | }
85 |
86 | edict := Edict{
87 | Id: &RuneId{
88 | Block: integers[i].Big().Uint64(),
89 | Tx: uint32(integers[i+1].Big().Uint64()),
90 | },
91 | Amount: integers[i+2].String(),
92 | Output: output,
93 | }
94 |
95 | edicts = append(edicts, &edict)
96 | }
97 |
98 | return edicts, nil
99 | }
100 |
101 | // ParseEdict parses the given payload to edict
102 | func ParseEdict(payload []byte) (*Edict, error) {
103 | integers, err := DecodeVec(payload)
104 | if err != nil {
105 | return nil, err
106 | }
107 |
108 | if len(integers) != EdictLen+1 && !integers[0].Equals(uint128.From64(TagBody)) {
109 | return nil, ErrInvalidRunes
110 | }
111 |
112 | return &Edict{
113 | Id: &RuneId{
114 | Block: integers[1].Big().Uint64(),
115 | Tx: uint32(integers[2].Big().Uint64()),
116 | },
117 | Amount: integers[3].String(),
118 | Output: uint32(integers[4].Big().Uint64()),
119 | }, nil
120 | }
121 |
122 | // BuildEdictScript builds the edict script
123 | func BuildEdictScript(runeId string, amount uint128.Uint128, output uint32) ([]byte, error) {
124 | var id RuneId
125 | id.MustUnmarshal([]byte(runeId))
126 |
127 | edict := Edict{
128 | Id: &id,
129 | Amount: amount.String(),
130 | Output: output,
131 | }
132 |
133 | payload := []byte{TagBody}
134 | payload = append(payload, edict.MustMarshalLEB128()...)
135 |
136 | scriptBuilder := txscript.NewScriptBuilder()
137 | scriptBuilder.AddOp(txscript.OP_RETURN).AddOp(MagicNumber).AddData(payload)
138 |
139 | return scriptBuilder.Script()
140 | }
141 |
142 | func (id *RuneId) ToString() string {
143 | return fmt.Sprintf("%d:%d", id.Block, id.Tx)
144 | }
145 |
146 | func (id *RuneId) FromString(idStr string) error {
147 | parts := strings.Split(idStr, ":")
148 | if len(parts) != 2 {
149 | return ErrInvalidRuneId
150 | }
151 |
152 | block, err := strconv.ParseUint(parts[0], 10, 64)
153 | if err != nil {
154 | return err
155 | }
156 |
157 | tx, err := strconv.ParseUint(parts[1], 10, 32)
158 | if err != nil {
159 | return err
160 | }
161 |
162 | id.Block = block
163 | id.Tx = uint32(tx)
164 |
165 | return nil
166 | }
167 |
168 | func (id *RuneId) MustUnmarshal(bz []byte) {
169 | err := id.FromString(string(bz))
170 | if err != nil {
171 | panic(err)
172 | }
173 | }
174 |
175 | // Denom returns the corresponding denom for the runes voucher token
176 | func (id *RuneId) Denom() string {
177 | return fmt.Sprintf("%s/%s", RunesProtocolName, id.ToString())
178 | }
179 |
180 | // FromDenom converts the denom to the rune id
181 | func (id *RuneId) FromDenom(denom string) {
182 | idStr := strings.TrimPrefix(denom, fmt.Sprintf("%s/", RunesProtocolName))
183 |
184 | id.MustUnmarshal([]byte(idStr))
185 | }
186 |
187 | func (e *Edict) MustMarshalLEB128() []byte {
188 | amount := RuneAmountFromString(e.Amount)
189 |
190 | payload := make([]byte, 0)
191 |
192 | payload = append(payload, EncodeUint64(e.Id.Block)...)
193 | payload = append(payload, EncodeUint32(e.Id.Tx)...)
194 | payload = append(payload, EncodeUint128(&amount)...)
195 | payload = append(payload, EncodeUint32(e.Output)...)
196 |
197 | return payload
198 | }
199 |
200 | // RuneAmountFromString converts the given string to the rune amount
201 | // Panic if any error occurred
202 | func RuneAmountFromString(str string) uint128.Uint128 {
203 | amount, err := uint128.FromString(str)
204 | if err != nil {
205 | panic(err)
206 | }
207 |
208 | return amount
209 | }
210 |
--------------------------------------------------------------------------------
/x/btcbridge/client/cli/tx.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "strings"
8 | "time"
9 |
10 | "github.com/btcsuite/btcd/btcutil/psbt"
11 | "github.com/spf13/cobra"
12 |
13 | "github.com/cosmos/cosmos-sdk/client"
14 | "github.com/cosmos/cosmos-sdk/client/flags"
15 | "github.com/cosmos/cosmos-sdk/client/tx"
16 | sdk "github.com/cosmos/cosmos-sdk/types"
17 |
18 | // "github.com/cosmos/cosmos-sdk/client/flags"
19 | "github.com/sideprotocol/side/x/btcbridge/types"
20 | )
21 |
22 | var DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds())
23 |
24 | // const (
25 | // flagPacketTimeoutTimestamp = "packet-timeout-timestamp"
26 | // listSeparator = ","
27 | // )
28 |
29 | // GetTxCmd returns the transaction commands for this module
30 | func GetTxCmd() *cobra.Command {
31 | cmd := &cobra.Command{
32 | Use: types.ModuleName,
33 | Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName),
34 | DisableFlagParsing: true,
35 | SuggestionsMinimumDistance: 2,
36 | RunE: client.ValidateCmd,
37 | }
38 |
39 | cmd.AddCommand(CmdSubmitBlocks())
40 | cmd.AddCommand(CmdUpdateSenders())
41 | cmd.AddCommand(CmdWithdrawBitcoin())
42 | cmd.AddCommand(CmdSubmitWithdrawSignatures())
43 |
44 | return cmd
45 | }
46 |
47 | func CmdSubmitBlocks() *cobra.Command {
48 | cmd := &cobra.Command{
49 | Use: "submit-blocks [file-path-to-block-headers.json]",
50 | Short: "Submit Bitcoin block headers to the chain",
51 | Args: cobra.ExactArgs(1),
52 | RunE: func(cmd *cobra.Command, args []string) (err error) {
53 | clientCtx, err := client.GetClientTxContext(cmd)
54 | if err != nil {
55 | return err
56 | }
57 |
58 | // read the block headers from the file
59 | blockHeaders, err := readBlockHeadersFromFile(args[0])
60 | if err != nil {
61 | return err
62 | }
63 |
64 | msg := types.NewMsgSubmitBlockHeaderRequest(
65 | clientCtx.GetFromAddress().String(),
66 | blockHeaders,
67 | )
68 | if err := msg.ValidateBasic(); err != nil {
69 | return err
70 | }
71 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
72 | },
73 | }
74 |
75 | flags.AddTxFlagsToCmd(cmd)
76 |
77 | return cmd
78 | }
79 |
80 | // Update Authorized Senders
81 | func CmdUpdateSenders() *cobra.Command {
82 | cmd := &cobra.Command{
83 | Use: "update-senders [senders]",
84 | Short: "Update authorized senders",
85 | Args: cobra.ExactArgs(1),
86 | RunE: func(cmd *cobra.Command, args []string) (err error) {
87 | clientCtx, err := client.GetClientTxContext(cmd)
88 | if err != nil {
89 | return err
90 | }
91 |
92 | // split the senders from args[0]
93 | senders := strings.Split(args[0], ",")
94 | if len(senders) == 0 {
95 | return fmt.Errorf("senders can not be empty")
96 | }
97 |
98 | msg := types.NewMsgUpdateSendersRequest(
99 | clientCtx.GetFromAddress().String(),
100 | senders,
101 | )
102 | if err := msg.ValidateBasic(); err != nil {
103 | return err
104 | }
105 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
106 | },
107 | }
108 |
109 | flags.AddTxFlagsToCmd(cmd)
110 |
111 | return cmd
112 | }
113 |
114 | // Withdraw Bitcoin
115 | func CmdWithdrawBitcoin() *cobra.Command {
116 | cmd := &cobra.Command{
117 | Use: "withdraw [amount] [fee-rate]",
118 | Short: "Withdraw bitcoin to the given sender",
119 | Args: cobra.ExactArgs(2),
120 | RunE: func(cmd *cobra.Command, args []string) (err error) {
121 | clientCtx, err := client.GetClientTxContext(cmd)
122 | if err != nil {
123 | return err
124 | }
125 |
126 | _, err = sdk.ParseCoinsNormalized(args[0])
127 | if err != nil {
128 | return fmt.Errorf("invalid amount")
129 | }
130 |
131 | msg := types.NewMsgWithdrawBitcoinRequest(
132 | clientCtx.GetFromAddress().String(),
133 | args[0],
134 | args[1],
135 | )
136 |
137 | if err := msg.ValidateBasic(); err != nil {
138 | return err
139 | }
140 |
141 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
142 | },
143 | }
144 |
145 | flags.AddTxFlagsToCmd(cmd)
146 |
147 | return cmd
148 | }
149 |
150 | func CmdSubmitWithdrawSignatures() *cobra.Command {
151 | cmd := &cobra.Command{
152 | Use: "submit-signature [psbt]",
153 | Short: "Submit signed withdrawal psbt",
154 | Args: cobra.ExactArgs(1),
155 | RunE: func(cmd *cobra.Command, args []string) (err error) {
156 | clientCtx, err := client.GetClientTxContext(cmd)
157 | if err != nil {
158 | return err
159 | }
160 |
161 | p, err := psbt.NewFromRawBytes(strings.NewReader(args[0]), true)
162 | if err != nil {
163 | return fmt.Errorf("invalid psbt")
164 | }
165 |
166 | signedTx, err := psbt.Extract(p)
167 | if err != nil {
168 | return fmt.Errorf("failed to extract tx from psbt")
169 | }
170 |
171 | msg := types.NewMsgSubmitWithdrawSignaturesRequest(
172 | clientCtx.GetFromAddress().String(),
173 | signedTx.TxHash().String(),
174 | args[0],
175 | )
176 |
177 | if err := msg.ValidateBasic(); err != nil {
178 | return err
179 | }
180 |
181 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
182 | },
183 | }
184 |
185 | flags.AddTxFlagsToCmd(cmd)
186 |
187 | return cmd
188 | }
189 |
190 | // readBlockHeadersFromFile reads the block headers from the file
191 | func readBlockHeadersFromFile(filePath string) ([]*types.BlockHeader, error) {
192 | // read the file
193 | file, err := os.Open(filePath)
194 | if err != nil {
195 | return nil, err
196 | }
197 | defer file.Close()
198 |
199 | // read the block headers from the file
200 | var blockHeaders []*types.BlockHeader
201 | decoder := json.NewDecoder(file)
202 | if err := decoder.Decode(&blockHeaders); err != nil {
203 | return nil, err
204 | }
205 | return blockHeaders, nil
206 | }
207 |
--------------------------------------------------------------------------------
/x/btcbridge/keeper/keeper.go:
--------------------------------------------------------------------------------
1 | package keeper
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/btcsuite/btcd/blockchain"
7 | "github.com/cometbft/cometbft/libs/log"
8 | "github.com/cosmos/cosmos-sdk/codec"
9 | storetypes "github.com/cosmos/cosmos-sdk/store/types"
10 | sdk "github.com/cosmos/cosmos-sdk/types"
11 | "github.com/sideprotocol/side/x/btcbridge/types"
12 | )
13 |
14 | type (
15 | Keeper struct {
16 | BaseUTXOKeeper
17 |
18 | cdc codec.BinaryCodec
19 | storeKey storetypes.StoreKey
20 | memKey storetypes.StoreKey
21 |
22 | authority string
23 |
24 | bankKeeper types.BankKeeper
25 | }
26 | )
27 |
28 | func NewKeeper(
29 | cdc codec.BinaryCodec,
30 | storeKey,
31 | memKey storetypes.StoreKey,
32 | authority string,
33 | bankKeeper types.BankKeeper,
34 | ) *Keeper {
35 | return &Keeper{
36 | cdc: cdc,
37 | storeKey: storeKey,
38 | memKey: memKey,
39 | authority: authority,
40 | bankKeeper: bankKeeper,
41 | BaseUTXOKeeper: *NewBaseUTXOKeeper(cdc, storeKey),
42 | }
43 | }
44 |
45 | func (k Keeper) Logger(ctx sdk.Context) log.Logger {
46 | return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
47 | }
48 |
49 | func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
50 | store := ctx.KVStore(k.storeKey)
51 | bz := k.cdc.MustMarshal(¶ms)
52 | store.Set(types.ParamsStoreKey, bz)
53 | }
54 |
55 | func (k Keeper) GetParams(ctx sdk.Context) types.Params {
56 | store := ctx.KVStore(k.storeKey)
57 | var params types.Params
58 | bz := store.Get(types.ParamsStoreKey)
59 | k.cdc.MustUnmarshal(bz, ¶ms)
60 | return params
61 | }
62 |
63 | func (k Keeper) GetBestBlockHeader(ctx sdk.Context) *types.BlockHeader {
64 | store := ctx.KVStore(k.storeKey)
65 | var blockHeader types.BlockHeader
66 | bz := store.Get(types.BtcBestBlockHeaderKey)
67 | k.cdc.MustUnmarshal(bz, &blockHeader)
68 | return &blockHeader
69 | }
70 |
71 | func (k Keeper) SetBestBlockHeader(ctx sdk.Context, header *types.BlockHeader) {
72 | store := ctx.KVStore(k.storeKey)
73 | bz := k.cdc.MustMarshal(header)
74 | store.Set(types.BtcBestBlockHeaderKey, bz)
75 | }
76 |
77 | func (k Keeper) SetBlockHeaders(ctx sdk.Context, blockHeader []*types.BlockHeader) error {
78 | store := ctx.KVStore(k.storeKey)
79 | // check if the previous block header exists
80 | best := k.GetBestBlockHeader(ctx)
81 | for _, header := range blockHeader {
82 |
83 | // check the block header sanity
84 | err := blockchain.CheckBlockHeaderSanity(
85 | HeaderConvert(header),
86 | sdk.GetConfig().GetBtcChainCfg().PowLimit,
87 | blockchain.NewMedianTime(),
88 | blockchain.BFNone,
89 | )
90 | if err != nil {
91 | return err
92 | }
93 |
94 | // check whether it's next block header or not
95 | if best.Hash != header.PreviousBlockHash {
96 | // check if the block header already exists
97 | // if exists, then it is a forked block header
98 | if !store.Has(types.BtcBlockHeaderHeightKey(header.Height)) {
99 | return types.ErrInvalidHeader
100 | }
101 |
102 | // a forked block header is detected
103 | // check if the new block header has more work than the old one
104 | oldNode := k.GetBlockHeaderByHeight(ctx, header.Height)
105 | worksOld := blockchain.CalcWork(BitsToTargetUint32(oldNode.Bits))
106 | worksNew := blockchain.CalcWork(BitsToTargetUint32(header.Bits))
107 | if worksNew.Cmp(worksOld) <= 0 {
108 | return types.ErrForkedBlockHeader
109 | }
110 |
111 | // remove the block headers after the forked block header
112 | // and consider the forked block header as the best block header
113 | for i := header.Height; i <= best.Height; i++ {
114 | ctx.Logger().Info("Removing block header: ", i)
115 | thash := k.GetBlockHashByHeight(ctx, i)
116 | store.Delete(types.BtcBlockHeaderHashKey(thash))
117 | store.Delete(types.BtcBlockHeaderHeightKey(i))
118 | }
119 | }
120 |
121 | // store the block header
122 | bz := k.cdc.MustMarshal(header)
123 | store.Set(types.BtcBlockHeaderHashKey(header.Hash), bz)
124 | // store the height to hash mapping
125 | store.Set(types.BtcBlockHeaderHeightKey(header.Height), []byte(header.Hash))
126 | // update the best block header
127 | best = header
128 | }
129 |
130 | if len(blockHeader) > 0 {
131 | // set the best block header
132 | k.SetBestBlockHeader(ctx, best)
133 | }
134 |
135 | return nil
136 | }
137 |
138 | func (k Keeper) GetBlockHeader(ctx sdk.Context, hash string) *types.BlockHeader {
139 | store := ctx.KVStore(k.storeKey)
140 | var blockHeader types.BlockHeader
141 | bz := store.Get(types.BtcBlockHeaderHashKey(hash))
142 | k.cdc.MustUnmarshal(bz, &blockHeader)
143 | return &blockHeader
144 | }
145 |
146 | func (k Keeper) GetBlockHashByHeight(ctx sdk.Context, height uint64) string {
147 | store := ctx.KVStore(k.storeKey)
148 | hash := store.Get(types.BtcBlockHeaderHeightKey(height))
149 | return string(hash)
150 | }
151 |
152 | func (k Keeper) GetBlockHeaderByHeight(ctx sdk.Context, height uint64) *types.BlockHeader {
153 | store := ctx.KVStore(k.storeKey)
154 | hash := store.Get(types.BtcBlockHeaderHeightKey(height))
155 | return k.GetBlockHeader(ctx, string(hash))
156 | }
157 |
158 | // GetAllBlockHeaders returns all block headers
159 | func (k Keeper) GetAllBlockHeaders(ctx sdk.Context) []*types.BlockHeader {
160 | var headers []*types.BlockHeader
161 | k.IterateBlockHeaders(ctx, func(header types.BlockHeader) (stop bool) {
162 | headers = append(headers, &header)
163 | return false
164 | })
165 | return headers
166 | }
167 |
168 | // IterateBlockHeaders iterates through all block headers
169 | func (k Keeper) IterateBlockHeaders(ctx sdk.Context, process func(header types.BlockHeader) (stop bool)) {
170 | store := ctx.KVStore(k.storeKey)
171 | iterator := sdk.KVStorePrefixIterator(store, types.BtcBlockHeaderHashPrefix)
172 | defer iterator.Close()
173 | for ; iterator.Valid(); iterator.Next() {
174 | var header types.BlockHeader
175 | k.cdc.MustUnmarshal(iterator.Value(), &header)
176 | if process(header) {
177 | break
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/proto/side/btcbridge/query.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package side.btcbridge;
3 |
4 | import "gogoproto/gogo.proto";
5 | import "google/api/annotations.proto";
6 | import "cosmos/base/query/v1beta1/pagination.proto";
7 | import "side/btcbridge/params.proto";
8 | import "side/btcbridge/bitcoin.proto";
9 |
10 | option go_package = "github.com/sideprotocol/side/x/btcbridge/types";
11 |
12 | // Query defines the gRPC querier service.
13 | service Query {
14 | // Parameters queries the parameters of the module.
15 | rpc QueryParams(QueryParamsRequest) returns (QueryParamsResponse) {
16 | option (google.api.http).get = "/side/btcbridge/params";
17 | }
18 | // ChainTip queries the chain tip of the module.
19 | rpc QueryChainTip(QueryChainTipRequest) returns (QueryChainTipResponse) {
20 | option (google.api.http).get = "/side/btcbridge/tip";
21 | }
22 | // BlockHeaderByHeight queries the block header by height.
23 | rpc QueryBlockHeaderByHeight(QueryBlockHeaderByHeightRequest) returns (QueryBlockHeaderByHeightResponse) {
24 | option (google.api.http).get = "/side/btcbridge/height/{height}";
25 | }
26 | // BlockHeaderByHash queries the block header by hash.
27 | rpc QueryBlockHeaderByHash(QueryBlockHeaderByHashRequest) returns (QueryBlockHeaderByHashResponse) {
28 | option (google.api.http).get = "/side/btcbridge/hash/{hash}";
29 | }
30 | // QuerySigningRequest queries the request to sign.
31 | rpc QuerySigningRequest(QuerySigningRequestRequest) returns (QuerySigningRequestResponse) {
32 | option (google.api.http).get = "/side/btcbridge/signing/request";
33 | }
34 | // QuerySigningRequestByAddress queries the signing request by the given address.
35 | rpc QuerySigningRequestByAddress(QuerySigningRequestByAddressRequest) returns (QuerySigningRequestByAddressResponse) {
36 | option (google.api.http).get = "/side/btcbridge/signing/request/address/{address}";
37 | }
38 | // QuerySigningRequestByTxHash queries the signing request by the given tx hash.
39 | rpc QuerySigningRequestByTxHash(QuerySigningRequestByTxHashRequest) returns (QuerySigningRequestByTxHashResponse) {
40 | option (google.api.http).get = "/side/btcbridge/signing/request/tx/{txid}";
41 | }
42 | // UTXOs queries all utxos.
43 | rpc QueryUTXOs(QueryUTXOsRequest) returns (QueryUTXOsResponse) {
44 | option (google.api.http).get = "/side/btcbridge/utxos";
45 | }
46 | // UTXOsByAddress queries the utxos of the given address.
47 | rpc QueryUTXOsByAddress(QueryUTXOsByAddressRequest) returns (QueryUTXOsByAddressResponse) {
48 | option (google.api.http).get = "/side/btcbridge/utxos/{address}";
49 | }
50 | }
51 |
52 | // QuerySigningRequestRequest is request type for the Query/SigningRequest RPC method.
53 | message QuerySigningRequestRequest {
54 | SigningStatus status = 1;
55 | cosmos.base.query.v1beta1.PageResponse pagination = 2;
56 | }
57 |
58 | // QuerySigningRequestResponse is response type for the Query/SigningRequest RPC method.
59 | message QuerySigningRequestResponse {
60 | repeated BitcoinSigningRequest requests = 1;
61 | cosmos.base.query.v1beta1.PageResponse pagination = 2;
62 | }
63 |
64 | // QuerySigningRequestByAddressRequest is request type for the Query/SigningRequestByAddress RPC method.
65 | message QuerySigningRequestByAddressRequest {
66 | string address = 1;
67 | cosmos.base.query.v1beta1.PageResponse pagination = 2;
68 | }
69 |
70 | // QuerySigningRequestByAddressResponse is response type for the Query/SigningRequestByAddress RPC method.
71 | message QuerySigningRequestByAddressResponse {
72 | repeated BitcoinSigningRequest requests = 1;
73 | cosmos.base.query.v1beta1.PageResponse pagination = 2;
74 | }
75 |
76 | // QuerySigningRequestByTxHashRequest is request type for the Query/SigningRequestByTxHash RPC method.
77 | message QuerySigningRequestByTxHashRequest {
78 | string txid = 1;
79 | }
80 |
81 | // QuerySigningRequestByTxHashResponse is response type for the Query/SigningRequestByTxHash RPC method.
82 | message QuerySigningRequestByTxHashResponse {
83 | BitcoinSigningRequest request = 1;
84 | }
85 |
86 | // QueryParamsRequest is request type for the Query/Params RPC method.
87 | message QueryParamsRequest {}
88 |
89 | // QueryParamsResponse is response type for the Query/Params RPC method.
90 | message QueryParamsResponse {
91 | // params holds all the parameters of this module.
92 | Params params = 1 [(gogoproto.nullable) = false];
93 | }
94 |
95 | // QueryChainTipRequest is request type for the Query/ChainTip RPC method.
96 | message QueryChainTipRequest {}
97 |
98 | // QueryChainTipResponse is response type for the Query/ChainTip RPC method.
99 | message QueryChainTipResponse {
100 | string hash = 1;
101 | uint64 height = 2;
102 | }
103 |
104 | // QueryBlockHeaderByHeightRequest is the request type for the Query/BlockHeaderByHeight RPC method.
105 | message QueryBlockHeaderByHeightRequest {
106 | uint64 height = 1;
107 | }
108 |
109 | // QueryBlockHeaderByHeightResponse is the response type for the Query/BlockHeaderByHeight RPC method.
110 | message QueryBlockHeaderByHeightResponse {
111 | BlockHeader block_header = 1;
112 | }
113 |
114 | // QueryBlockHeaderByHashRequest is the request type for the Query/BlockHeaderByHash RPC method.
115 | message QueryBlockHeaderByHashRequest {
116 | string hash = 1;
117 | }
118 |
119 | // QueryBlockHeaderByHashResponse is the response type for the Query/BlockHeaderByHash RPC method.
120 | message QueryBlockHeaderByHashResponse {
121 | BlockHeader block_header = 1;
122 | }
123 |
124 | // QueryUTXOsRequest is the request type for the Query/UTXOs RPC method.
125 | message QueryUTXOsRequest {}
126 |
127 | // QueryUTXOsResponse is the response type for the Query/UTXOs RPC method.
128 | message QueryUTXOsResponse {
129 | repeated UTXO utxos = 1;
130 | }
131 |
132 | // QueryUTXOsByAddressRequest is the request type for the Query/UTXOsByAddress RPC method.
133 | message QueryUTXOsByAddressRequest {
134 | string address = 1;
135 | }
136 |
137 | // QueryUTXOsByAddressResponse is the response type for the Query/UTXOsByAddress RPC method.
138 | message QueryUTXOsByAddressResponse {
139 | repeated UTXO utxos = 1;
140 | }
141 |
--------------------------------------------------------------------------------
/x/btcbridge/module.go:
--------------------------------------------------------------------------------
1 | package btcbridge
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 |
8 | // this line is used by starport scaffolding # 1
9 |
10 | "github.com/grpc-ecosystem/grpc-gateway/runtime"
11 | "github.com/spf13/cobra"
12 |
13 | abci "github.com/cometbft/cometbft/abci/types"
14 |
15 | "github.com/cosmos/cosmos-sdk/client"
16 | "github.com/cosmos/cosmos-sdk/codec"
17 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
18 | sdk "github.com/cosmos/cosmos-sdk/types"
19 | "github.com/cosmos/cosmos-sdk/types/module"
20 | "github.com/sideprotocol/side/x/btcbridge/client/cli"
21 | "github.com/sideprotocol/side/x/btcbridge/keeper"
22 | "github.com/sideprotocol/side/x/btcbridge/types"
23 | )
24 |
25 | var (
26 | _ module.AppModule = AppModule{}
27 | _ module.AppModuleBasic = AppModuleBasic{}
28 | )
29 |
30 | // ----------------------------------------------------------------------------
31 | // AppModuleBasic
32 | // ----------------------------------------------------------------------------
33 |
34 | // AppModuleBasic implements the AppModuleBasic interface that defines the independent methods a Cosmos SDK module needs to implement.
35 | type AppModuleBasic struct {
36 | cdc codec.BinaryCodec
37 | }
38 |
39 | func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic {
40 | return AppModuleBasic{cdc: cdc}
41 | }
42 |
43 | // Name returns the name of the module as a string
44 | func (AppModuleBasic) Name() string {
45 | return types.ModuleName
46 | }
47 |
48 | // RegisterLegacyAminoCodec registers the amino codec for the module, which is used to marshal and unmarshal structs to/from []byte in order to persist them in the module's KVStore
49 | func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
50 | types.RegisterCodec(cdc)
51 | }
52 |
53 | // RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message
54 | func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) {
55 | types.RegisterInterfaces(reg)
56 | }
57 |
58 | // DefaultGenesis returns a default GenesisState for the module, marshaled to json.RawMessage. The default GenesisState need to be defined by the module developer and is primarily used for testing
59 | func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage {
60 | return cdc.MustMarshalJSON(types.DefaultGenesis())
61 | }
62 |
63 | // ValidateGenesis used to validate the GenesisState, given in its json.RawMessage form
64 | func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error {
65 | var genState types.GenesisState
66 | if err := cdc.UnmarshalJSON(bz, &genState); err != nil {
67 | return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)
68 | }
69 | return genState.Validate()
70 | }
71 |
72 | // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module
73 | func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
74 | if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil {
75 | panic(err)
76 | }
77 | }
78 |
79 | // GetTxCmd returns the root Tx command for the module. The subcommands of this root command are used by end-users to generate new transactions containing messages defined in the module
80 | func (a AppModuleBasic) GetTxCmd() *cobra.Command {
81 | return cli.GetTxCmd()
82 | }
83 |
84 | // GetQueryCmd returns the root query command for the module. The subcommands of this root command are used by end-users to generate new queries to the subset of the state defined by the module
85 | func (AppModuleBasic) GetQueryCmd() *cobra.Command {
86 | return cli.GetQueryCmd(types.StoreKey)
87 | }
88 |
89 | // ----------------------------------------------------------------------------
90 | // AppModule
91 | // ----------------------------------------------------------------------------
92 |
93 | // AppModule implements the AppModule interface that defines the inter-dependent methods that modules need to implement
94 | type AppModule struct {
95 | AppModuleBasic
96 |
97 | keeper keeper.Keeper
98 | }
99 |
100 | func NewAppModule(
101 | cdc codec.Codec,
102 | keeper keeper.Keeper,
103 | ) AppModule {
104 | return AppModule{
105 | AppModuleBasic: NewAppModuleBasic(cdc),
106 | keeper: keeper,
107 | }
108 | }
109 |
110 | // RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries
111 | func (am AppModule) RegisterServices(cfg module.Configurator) {
112 | types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
113 | types.RegisterQueryServer(cfg.QueryServer(), am.keeper)
114 | }
115 |
116 | // RegisterInvariants registers the invariants of the module. If an invariant deviates from its predicted value, the InvariantRegistry triggers appropriate logic (most often the chain will be halted)
117 | func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
118 |
119 | // InitGenesis performs the module's genesis initialization. It returns no validator updates.
120 | func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate {
121 | var genState types.GenesisState
122 | // Initialize global index to index in genesis state
123 | cdc.MustUnmarshalJSON(gs, &genState)
124 |
125 | InitGenesis(ctx, am.keeper, genState)
126 |
127 | return []abci.ValidatorUpdate{}
128 | }
129 |
130 | // ExportGenesis returns the module's exported genesis state as raw JSON bytes.
131 | func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage {
132 | genState := ExportGenesis(ctx, am.keeper)
133 | return cdc.MustMarshalJSON(genState)
134 | }
135 |
136 | // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1
137 | func (AppModule) ConsensusVersion() uint64 { return 1 }
138 |
139 | // BeginBlock contains the logic that is automatically triggered at the beginning of each block
140 | func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
141 |
142 | // EndBlock contains the logic that is automatically triggered at the end of each block
143 | func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
144 | return []abci.ValidatorUpdate{}
145 | }
146 |
--------------------------------------------------------------------------------
/app/test_helper.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 | "time"
7 |
8 | "github.com/cometbft/cometbft/crypto/secp256k1"
9 | tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
10 | tmtypes "github.com/cometbft/cometbft/types"
11 | "github.com/cosmos/cosmos-sdk/testutil/mock"
12 | "github.com/stretchr/testify/require"
13 |
14 | sdk "github.com/cosmos/cosmos-sdk/types"
15 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
16 | banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
17 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
18 |
19 | dbm "github.com/cometbft/cometbft-db"
20 | abci "github.com/cometbft/cometbft/abci/types"
21 | codectypes "github.com/cosmos/cosmos-sdk/codec/types"
22 | cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
23 |
24 | "github.com/cometbft/cometbft/libs/log"
25 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
26 | )
27 |
28 | const (
29 | DefaultBondDenom = "uside"
30 | USDC = "uusdc"
31 | WBTC = "wbtc"
32 | WDAI = "wdai"
33 | WUSDT = "wusdt"
34 | )
35 |
36 | // Setup initializes a new App. A Nop logger is set in App.
37 | func Setup(t *testing.T) *App {
38 | t.Helper()
39 |
40 | privVal := mock.NewPV()
41 | pubKey, err := privVal.GetPubKey()
42 | require.NoError(t, err)
43 |
44 | // create validator set with single validator
45 | validator := tmtypes.NewValidator(pubKey, 1)
46 | valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator})
47 |
48 | // generate genesis account
49 | senderPrivKey := secp256k1.GenPrivKey()
50 | acc := authtypes.NewBaseAccountWithAddress(senderPrivKey.PubKey().Address().Bytes())
51 | balance := banktypes.Balance{
52 | Address: acc.GetAddress().String(),
53 | Coins: sdk.NewCoins(
54 | sdk.NewCoin(DefaultBondDenom, sdk.NewInt(100000000000000)),
55 | sdk.NewCoin(USDC, sdk.NewInt(100000000000000)),
56 | ),
57 | }
58 |
59 | app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, balance)
60 |
61 | return app
62 | }
63 |
64 | func genesisStateWithValSet(t *testing.T,
65 | app *App, genesisState GenesisState,
66 | valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount,
67 | balances ...banktypes.Balance,
68 | ) GenesisState {
69 | // set genesis accounts
70 |
71 | authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs)
72 | genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis)
73 |
74 | validators := make([]stakingtypes.Validator, 0, len(valSet.Validators))
75 | delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators))
76 |
77 | bondAmt := sdk.DefaultPowerReduction
78 |
79 | for _, val := range valSet.Validators {
80 | pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey)
81 | require.NoError(t, err)
82 | pkAny, err := codectypes.NewAnyWithValue(pk)
83 | require.NoError(t, err)
84 | validator := stakingtypes.Validator{
85 | OperatorAddress: sdk.ValAddress(val.Address).String(),
86 | ConsensusPubkey: pkAny,
87 | Jailed: false,
88 | Status: stakingtypes.Bonded,
89 | Tokens: bondAmt,
90 | DelegatorShares: sdk.OneDec(),
91 | Description: stakingtypes.Description{},
92 | UnbondingHeight: int64(0),
93 | UnbondingTime: time.Unix(0, 0).UTC(),
94 | Commission: stakingtypes.NewCommission(sdk.OneDec(), sdk.OneDec(), sdk.OneDec()),
95 | MinSelfDelegation: sdk.ZeroInt(),
96 | }
97 | validators = append(validators, validator)
98 | delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec()))
99 |
100 | }
101 | // set validators and delegations
102 | stakingParams := stakingtypes.DefaultParams()
103 | stakingParams.BondDenom = DefaultBondDenom
104 | stakingParams.MinCommissionRate = sdk.OneDec()
105 | stakingGenesis := stakingtypes.NewGenesisState(stakingParams, validators, delegations)
106 | genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis)
107 |
108 | totalSupply := sdk.NewCoins()
109 | for _, b := range balances {
110 | // add genesis acc tokens to total supply
111 | totalSupply = totalSupply.Add(b.Coins...)
112 | }
113 |
114 | for range delegations {
115 | // add delegated tokens to total supply
116 | totalSupply = totalSupply.Add(sdk.NewCoin(DefaultBondDenom, bondAmt))
117 | }
118 |
119 | // add bonded amount to bonded pool module account
120 | balances = append(balances, banktypes.Balance{
121 | Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
122 | Coins: sdk.Coins{sdk.NewCoin(DefaultBondDenom, bondAmt)},
123 | })
124 |
125 | // update total supply
126 | bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}, []banktypes.SendEnabled{})
127 | genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis)
128 |
129 | return genesisState
130 | }
131 |
132 | // SetupWithGenesisValSet initializes a new App with a validator set and genesis accounts
133 | // that also act as delegators. For simplicity, each validator is bonded with a delegation
134 | // of one consensus engine unit (10^6) in the default token of the simapp from first genesis
135 | // account. A Nop logger is set in App.
136 | func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *App {
137 | t.Helper()
138 |
139 | app, genesisState := setup(true, 5)
140 | genesisState = genesisStateWithValSet(t, app, genesisState, valSet, genAccs, balances...)
141 |
142 | stateBytes, err := json.MarshalIndent(genesisState, "", " ")
143 | require.NoError(t, err)
144 |
145 | // init chain will set the validator set and initialize the genesis accounts
146 | app.InitChain(
147 | abci.RequestInitChain{
148 | Validators: []abci.ValidatorUpdate{},
149 | ConsensusParams: simtestutil.DefaultConsensusParams,
150 | AppStateBytes: stateBytes,
151 | },
152 | )
153 |
154 | // commit genesis changes
155 | app.Commit()
156 | app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{
157 | Height: app.LastBlockHeight() + 1,
158 | AppHash: app.LastCommitID().Hash,
159 | ValidatorsHash: valSet.Hash(),
160 | NextValidatorsHash: valSet.Hash(),
161 | }})
162 |
163 | return app
164 | }
165 |
166 | func setup(withGenesis bool, invCheckPeriod uint) (*App, GenesisState) {
167 | db := dbm.NewMemDB()
168 | encCdc := MakeEncodingConfig()
169 | appOptions := make(simtestutil.AppOptionsMap, 0)
170 |
171 | app := New(
172 | log.NewNopLogger(),
173 | db,
174 | nil, true, map[int64]bool{}, DefaultNodeHome, invCheckPeriod, encCdc, appOptions)
175 | if withGenesis {
176 | return app, NewDefaultGenesisState(encCdc.Marshaler)
177 | }
178 | return app, GenesisState{}
179 | }
180 |
--------------------------------------------------------------------------------
/x/btcbridge/client/cli/query.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strconv"
7 |
8 | // "strings"
9 |
10 | "github.com/btcsuite/btcd/chaincfg/chainhash"
11 | "github.com/sideprotocol/side/x/btcbridge/types"
12 | "github.com/spf13/cobra"
13 |
14 | "github.com/cosmos/cosmos-sdk/client"
15 | "github.com/cosmos/cosmos-sdk/client/flags"
16 | sdk "github.com/cosmos/cosmos-sdk/types"
17 | )
18 |
19 | // GetQueryCmd returns the cli query commands for this module
20 | func GetQueryCmd(_ string) *cobra.Command {
21 | // Group yield queries under a subcommand
22 | cmd := &cobra.Command{
23 | Use: types.ModuleName,
24 | Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName),
25 | DisableFlagParsing: true,
26 | SuggestionsMinimumDistance: 2,
27 | RunE: client.ValidateCmd,
28 | }
29 |
30 | cmd.AddCommand(CmdQueryParams())
31 | cmd.AddCommand(CmdBestBlock())
32 | cmd.AddCommand(CmdQueryBlock())
33 | cmd.AddCommand(CmdQueryUTXOs())
34 | cmd.AddCommand(CmdQuerySigningRequest())
35 | // this line is used by starport scaffolding # 1
36 |
37 | return cmd
38 | }
39 |
40 | func CmdQueryParams() *cobra.Command {
41 | cmd := &cobra.Command{
42 | Use: "params",
43 | Short: "shows the parameters of the module",
44 | Args: cobra.NoArgs,
45 | RunE: func(cmd *cobra.Command, args []string) error {
46 | clientCtx, err := client.GetClientQueryContext(cmd)
47 | if err != nil {
48 | return err
49 | }
50 |
51 | queryClient := types.NewQueryClient(clientCtx)
52 |
53 | res, err := queryClient.QueryParams(cmd.Context(), &types.QueryParamsRequest{})
54 | if err != nil {
55 | return err
56 | }
57 |
58 | return clientCtx.PrintProto(res)
59 | },
60 | }
61 |
62 | flags.AddQueryFlagsToCmd(cmd)
63 |
64 | return cmd
65 | }
66 |
67 | func CmdBestBlock() *cobra.Command {
68 | cmd := &cobra.Command{
69 | Use: "best-block",
70 | Short: "shows the best block header of the light client",
71 | Args: cobra.NoArgs,
72 | RunE: func(cmd *cobra.Command, args []string) error {
73 | clientCtx, err := client.GetClientQueryContext(cmd)
74 | if err != nil {
75 | return err
76 | }
77 |
78 | queryClient := types.NewQueryClient(clientCtx)
79 |
80 | res, err := queryClient.QueryChainTip(cmd.Context(), &types.QueryChainTipRequest{})
81 | if err != nil {
82 | return err
83 | }
84 |
85 | return clientCtx.PrintProto(res)
86 | },
87 | }
88 |
89 | flags.AddQueryFlagsToCmd(cmd)
90 |
91 | return cmd
92 | }
93 |
94 | // CmdQueryBlock returns the command to query the heights of the light client
95 | func CmdQueryBlock() *cobra.Command {
96 | cmd := &cobra.Command{
97 | Use: "block [hash or height]",
98 | Short: "Query block by hash or height",
99 | Args: cobra.ExactArgs(1),
100 | RunE: func(cmd *cobra.Command, args []string) error {
101 | clientCtx, err := client.GetClientQueryContext(cmd)
102 | if err != nil {
103 | return err
104 | }
105 |
106 | queryClient := types.NewQueryClient(clientCtx)
107 |
108 | height, err := strconv.ParseUint(args[0], 10, 64)
109 | if err != nil {
110 | res, err := queryClient.QueryBlockHeaderByHash(cmd.Context(), &types.QueryBlockHeaderByHashRequest{Hash: args[0]})
111 | if err != nil {
112 | return err
113 | }
114 |
115 | return clientCtx.PrintProto(res)
116 | }
117 |
118 | res, err := queryClient.QueryBlockHeaderByHeight(cmd.Context(), &types.QueryBlockHeaderByHeightRequest{Height: height})
119 | if err != nil {
120 | return err
121 | }
122 |
123 | return clientCtx.PrintProto(res)
124 | },
125 | }
126 |
127 | flags.AddQueryFlagsToCmd(cmd)
128 |
129 | return cmd
130 | }
131 |
132 | // CmdQuerySigningRequest returns the command to query signing request
133 | func CmdQuerySigningRequest() *cobra.Command {
134 | cmd := &cobra.Command{
135 | Use: "signing-request [status | address | tx hash]",
136 | Short: "Query signing requests by status, address or tx hash",
137 | Args: cobra.ExactArgs(1),
138 | RunE: func(cmd *cobra.Command, args []string) error {
139 | clientCtx, err := client.GetClientQueryContext(cmd)
140 | if err != nil {
141 | return err
142 | }
143 |
144 | queryClient := types.NewQueryClient(clientCtx)
145 |
146 | status, err := strconv.ParseInt(args[0], 10, 32)
147 | if err != nil {
148 | _, err = sdk.AccAddressFromBech32(args[0])
149 | if err != nil {
150 | _, err := chainhash.NewHashFromStr(args[0])
151 | if err != nil {
152 | return fmt.Errorf("invalid arg, neither status, address nor tx hash: %s", args[0])
153 | }
154 |
155 | res, err := queryClient.QuerySigningRequestByTxHash(cmd.Context(), &types.QuerySigningRequestByTxHashRequest{Txid: args[0]})
156 | if err != nil {
157 | return err
158 | }
159 |
160 | return clientCtx.PrintProto(res)
161 | }
162 |
163 | res, err := queryClient.QuerySigningRequestByAddress(cmd.Context(), &types.QuerySigningRequestByAddressRequest{Address: args[0]})
164 | if err != nil {
165 | return err
166 | }
167 |
168 | return clientCtx.PrintProto(res)
169 | }
170 |
171 | res, err := queryClient.QuerySigningRequest(cmd.Context(), &types.QuerySigningRequestRequest{Status: types.SigningStatus(status)})
172 | if err != nil {
173 | return err
174 | }
175 |
176 | return clientCtx.PrintProto(res)
177 | },
178 | }
179 |
180 | flags.AddQueryFlagsToCmd(cmd)
181 |
182 | return cmd
183 | }
184 |
185 | func CmdQueryUTXOs() *cobra.Command {
186 | cmd := &cobra.Command{
187 | Use: "utxos [address]",
188 | Short: "query utxos with an optional address",
189 | Args: cobra.MaximumNArgs(1),
190 | RunE: func(cmd *cobra.Command, args []string) error {
191 | clientCtx, err := client.GetClientQueryContext(cmd)
192 | if err != nil {
193 | return err
194 | }
195 |
196 | if len(args) == 0 {
197 | return queryUTXOs(cmd.Context(), &clientCtx)
198 | }
199 |
200 | return queryUTXOsByAddr(cmd.Context(), &clientCtx, args[0])
201 | },
202 | }
203 |
204 | flags.AddQueryFlagsToCmd(cmd)
205 |
206 | return cmd
207 | }
208 |
209 | func queryUTXOs(cmdCtx context.Context, clientCtx *client.Context) error {
210 | queryClient := types.NewQueryClient(clientCtx)
211 |
212 | res, err := queryClient.QueryUTXOs(cmdCtx, &types.QueryUTXOsRequest{})
213 | if err != nil {
214 | return err
215 | }
216 |
217 | return clientCtx.PrintProto(res)
218 | }
219 |
220 | func queryUTXOsByAddr(cmdCtx context.Context, clientCtx *client.Context, addr string) error {
221 | queryClient := types.NewQueryClient(clientCtx)
222 |
223 | _, err := sdk.AccAddressFromBech32(addr)
224 | if err != nil {
225 | return err
226 | }
227 |
228 | res, err := queryClient.QueryUTXOsByAddress(cmdCtx, &types.QueryUTXOsByAddressRequest{
229 | Address: addr,
230 | })
231 | if err != nil {
232 | return err
233 | }
234 |
235 | return clientCtx.PrintProto(res)
236 | }
237 |
--------------------------------------------------------------------------------
/cmd/sided/cmd/genaccounts.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "bufio"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 |
9 | "github.com/cosmos/cosmos-sdk/client"
10 | "github.com/cosmos/cosmos-sdk/client/flags"
11 | "github.com/cosmos/cosmos-sdk/crypto/keyring"
12 | "github.com/cosmos/cosmos-sdk/server"
13 | sdk "github.com/cosmos/cosmos-sdk/types"
14 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
15 | authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
16 | banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
17 | "github.com/cosmos/cosmos-sdk/x/genutil"
18 | genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
19 | "github.com/spf13/cobra"
20 | )
21 |
22 | const (
23 | flagVestingStart = "vesting-start-time"
24 | flagVestingEnd = "vesting-end-time"
25 | flagVestingAmt = "vesting-amount"
26 | )
27 |
28 | // AddGenesisAccountCmd returns add-genesis-account cobra Command.
29 | func AddGenesisAccountCmd(defaultNodeHome string) *cobra.Command {
30 | cmd := &cobra.Command{
31 | Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]",
32 | Short: "Add a genesis account to genesis.json",
33 | Long: `Add a genesis account to genesis.json. The provided account must specify
34 | the account address or key name and a list of initial coins. If a key name is given,
35 | the address will be looked up in the local Keybase. The list of initial tokens must
36 | contain valid denominations. Accounts may optionally be supplied with vesting parameters.
37 | `,
38 | Args: cobra.ExactArgs(2),
39 | RunE: func(cmd *cobra.Command, args []string) error {
40 | clientCtx := client.GetClientContextFromCmd(cmd)
41 | cdc := clientCtx.Codec
42 |
43 | serverCtx := server.GetServerContextFromCmd(cmd)
44 | config := serverCtx.Config
45 |
46 | config.SetRoot(clientCtx.HomeDir)
47 |
48 | coins, err := sdk.ParseCoinsNormalized(args[1])
49 | if err != nil {
50 | return fmt.Errorf("failed to parse coins: %w", err)
51 | }
52 |
53 | addr, err := sdk.AccAddressFromBech32(args[0])
54 | if err != nil {
55 | inBuf := bufio.NewReader(cmd.InOrStdin())
56 | keyringBackend, err := cmd.Flags().GetString(flags.FlagKeyringBackend)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | // attempt to lookup address from Keybase if no address was provided
62 | kb, err := keyring.New(sdk.KeyringServiceName(), keyringBackend, clientCtx.HomeDir, inBuf, cdc)
63 | if err != nil {
64 | return err
65 | }
66 |
67 | info, err := kb.Key(args[0])
68 | if err != nil {
69 | return fmt.Errorf("failed to get address from Keybase: %w", err)
70 | }
71 |
72 | addr, err = info.GetAddress()
73 | if err != nil {
74 | return fmt.Errorf("failed to get address from Keybase: %w", err)
75 | }
76 | }
77 |
78 | vestingStart, err := cmd.Flags().GetInt64(flagVestingStart)
79 | if err != nil {
80 | return err
81 | }
82 | vestingEnd, err := cmd.Flags().GetInt64(flagVestingEnd)
83 | if err != nil {
84 | return err
85 | }
86 | vestingAmtStr, err := cmd.Flags().GetString(flagVestingAmt)
87 | if err != nil {
88 | return err
89 | }
90 |
91 | vestingAmt, err := sdk.ParseCoinsNormalized(vestingAmtStr)
92 | if err != nil {
93 | return fmt.Errorf("failed to parse vesting amount: %w", err)
94 | }
95 |
96 | // create concrete account type based on input parameters
97 | var genAccount authtypes.GenesisAccount
98 |
99 | balances := banktypes.Balance{Address: addr.String(), Coins: coins.Sort()}
100 | baseAccount := authtypes.NewBaseAccount(addr, nil, 0, 0)
101 |
102 | if !vestingAmt.IsZero() {
103 | baseVestingAccount := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd)
104 |
105 | if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) ||
106 | baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) {
107 | return errors.New("vesting amount cannot be greater than total amount")
108 | }
109 |
110 | switch {
111 | case vestingStart != 0 && vestingEnd != 0:
112 | genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart)
113 |
114 | case vestingEnd != 0:
115 | genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount)
116 |
117 | default:
118 | return errors.New("invalid vesting parameters; must supply start and end time or end time")
119 | }
120 | } else {
121 | genAccount = baseAccount
122 | }
123 |
124 | if err := genAccount.Validate(); err != nil {
125 | return fmt.Errorf("failed to validate new genesis account: %w", err)
126 | }
127 |
128 | genFile := config.GenesisFile()
129 | appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile)
130 | if err != nil {
131 | return fmt.Errorf("failed to unmarshal genesis state: %w", err)
132 | }
133 |
134 | authGenState := authtypes.GetGenesisStateFromAppState(cdc, appState)
135 |
136 | accs, err := authtypes.UnpackAccounts(authGenState.Accounts)
137 | if err != nil {
138 | return fmt.Errorf("failed to get accounts from any: %w", err)
139 | }
140 |
141 | if accs.Contains(addr) {
142 | return fmt.Errorf("cannot add account at existing address %s", addr)
143 | }
144 |
145 | // Add the new account to the set of genesis accounts and sanitize the
146 | // accounts afterwards.
147 | accs = append(accs, genAccount)
148 | accs = authtypes.SanitizeGenesisAccounts(accs)
149 |
150 | genAccs, err := authtypes.PackAccounts(accs)
151 | if err != nil {
152 | return fmt.Errorf("failed to convert accounts into any's: %w", err)
153 | }
154 | authGenState.Accounts = genAccs
155 |
156 | authGenStateBz, err := cdc.MarshalJSON(&authGenState)
157 | if err != nil {
158 | return fmt.Errorf("failed to marshal auth genesis state: %w", err)
159 | }
160 |
161 | appState[authtypes.ModuleName] = authGenStateBz
162 |
163 | bankGenState := banktypes.GetGenesisStateFromAppState(cdc, appState)
164 | bankGenState.Balances = append(bankGenState.Balances, balances)
165 | bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances)
166 |
167 | bankGenStateBz, err := cdc.MarshalJSON(bankGenState)
168 | if err != nil {
169 | return fmt.Errorf("failed to marshal bank genesis state: %w", err)
170 | }
171 |
172 | appState[banktypes.ModuleName] = bankGenStateBz
173 |
174 | appStateJSON, err := json.Marshal(appState)
175 | if err != nil {
176 | return fmt.Errorf("failed to marshal application genesis state: %w", err)
177 | }
178 |
179 | genDoc.AppState = appStateJSON
180 | return genutil.ExportGenesisFile(genDoc, genFile)
181 | },
182 | }
183 |
184 | cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
185 | cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
186 | cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts")
187 | cmd.Flags().Int64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts")
188 | cmd.Flags().Int64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts")
189 | flags.AddQueryFlagsToCmd(cmd)
190 |
191 | return cmd
192 | }
193 |
--------------------------------------------------------------------------------
/app/export.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 |
8 | tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
9 | servertypes "github.com/cosmos/cosmos-sdk/server/types"
10 | sdk "github.com/cosmos/cosmos-sdk/types"
11 | slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
12 | "github.com/cosmos/cosmos-sdk/x/staking"
13 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
14 | )
15 |
16 | // ExportAppStateAndValidators exports the state of the application for a genesis
17 | // file.
18 | func (app *App) ExportAppStateAndValidators(
19 | forZeroHeight bool,
20 | jailAllowedAddrs []string,
21 | modulesToExport []string,
22 | ) (servertypes.ExportedApp, error) {
23 | // as if they could withdraw from the start of the next block
24 | ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()})
25 |
26 | // We export at last height + 1, because that's the height at which
27 | // Tendermint will start InitChain.
28 | height := app.LastBlockHeight() + 1
29 | if forZeroHeight {
30 | height = 0
31 | app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs)
32 | }
33 |
34 | genState := app.mm.ExportGenesisForModules(ctx, app.appCodec, modulesToExport)
35 | appState, err := json.MarshalIndent(genState, "", " ")
36 | if err != nil {
37 | return servertypes.ExportedApp{}, err
38 | }
39 |
40 | validators, err := staking.WriteValidators(ctx, app.StakingKeeper)
41 | return servertypes.ExportedApp{
42 | AppState: appState,
43 | Validators: validators,
44 | Height: height,
45 | ConsensusParams: app.BaseApp.GetConsensusParams(ctx),
46 | }, err
47 | }
48 |
49 | // prepForZeroHeightGenesis prepares for a fresh genesis
50 | //
51 | // NOTE zero height genesis is a temporary feature which will be deprecated
52 | // in favour of export at a block height
53 | func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) {
54 | applyAllowedAddrs := false
55 |
56 | // check if there is a allowed address list
57 | if len(jailAllowedAddrs) > 0 {
58 | applyAllowedAddrs = true
59 | }
60 |
61 | allowedAddrsMap := make(map[string]bool)
62 |
63 | for _, addr := range jailAllowedAddrs {
64 | _, err := sdk.ValAddressFromBech32(addr)
65 | if err != nil {
66 | log.Fatal(err)
67 | }
68 | allowedAddrsMap[addr] = true
69 | }
70 |
71 | /* Just to be safe, assert the invariants on current state. */
72 | app.CrisisKeeper.AssertInvariants(ctx)
73 |
74 | /* Handle fee distribution state. */
75 |
76 | // withdraw all validator commission
77 | app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
78 | _, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator())
79 | return false
80 | })
81 |
82 | // withdraw all delegator rewards
83 | dels := app.StakingKeeper.GetAllDelegations(ctx)
84 | for _, delegation := range dels {
85 | valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress)
86 | if err != nil {
87 | panic(err)
88 | }
89 |
90 | delAddr := sdk.MustAccAddressFromBech32(delegation.DelegatorAddress)
91 |
92 | _, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr)
93 | }
94 |
95 | // clear validator slash events
96 | app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx)
97 |
98 | // clear validator historical rewards
99 | app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx)
100 |
101 | // set context height to zero
102 | height := ctx.BlockHeight()
103 | ctx = ctx.WithBlockHeight(0)
104 |
105 | // reinitialize all validators
106 | app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
107 | // donate any unwithdrawn outstanding reward fraction tokens to the community pool
108 | scraps := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, val.GetOperator())
109 | feePool := app.DistrKeeper.GetFeePool(ctx)
110 | feePool.CommunityPool = feePool.CommunityPool.Add(scraps...)
111 | app.DistrKeeper.SetFeePool(ctx, feePool)
112 |
113 | if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()); err != nil {
114 | panic(err)
115 | }
116 | return false
117 | })
118 |
119 | // reinitialize all delegations
120 | for _, del := range dels {
121 | valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress)
122 | if err != nil {
123 | panic(err)
124 | }
125 | delAddr := sdk.MustAccAddressFromBech32(del.DelegatorAddress)
126 |
127 | if err := app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr); err != nil {
128 | // never called as BeforeDelegationCreated always returns nil
129 | panic(fmt.Errorf("error while incrementing period: %w", err))
130 | }
131 |
132 | if err := app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr); err != nil {
133 | // never called as AfterDelegationModified always returns nil
134 | panic(fmt.Errorf("error while creating a new delegation period record: %w", err))
135 | }
136 | }
137 |
138 | // reset context height
139 | ctx = ctx.WithBlockHeight(height)
140 |
141 | /* Handle staking state. */
142 |
143 | // iterate through redelegations, reset creation height
144 | app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) {
145 | for i := range red.Entries {
146 | red.Entries[i].CreationHeight = 0
147 | }
148 | app.StakingKeeper.SetRedelegation(ctx, red)
149 | return false
150 | })
151 |
152 | // iterate through unbonding delegations, reset creation height
153 | app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) {
154 | for i := range ubd.Entries {
155 | ubd.Entries[i].CreationHeight = 0
156 | }
157 | app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
158 | return false
159 | })
160 |
161 | // Iterate through validators by power descending, reset bond heights, and
162 | // update bond intra-tx counters.
163 | store := ctx.KVStore(app.GetKey(stakingtypes.StoreKey))
164 | iter := sdk.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey)
165 | counter := int16(0)
166 |
167 | for ; iter.Valid(); iter.Next() {
168 | addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key()))
169 | validator, found := app.StakingKeeper.GetValidator(ctx, addr)
170 | if !found {
171 | panic("expected validator, not found")
172 | }
173 |
174 | validator.UnbondingHeight = 0
175 | if applyAllowedAddrs && !allowedAddrsMap[addr.String()] {
176 | validator.Jailed = true
177 | }
178 |
179 | app.StakingKeeper.SetValidator(ctx, validator)
180 | counter++
181 | }
182 |
183 | if err := iter.Close(); err != nil {
184 | app.Logger().Error("error while closing the key-value store reverse prefix iterator: ", err)
185 | return
186 | }
187 |
188 | _, err := app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx)
189 | if err != nil {
190 | log.Fatal(err)
191 | }
192 |
193 | /* Handle slashing state. */
194 |
195 | // reset start height on signing infos
196 | app.SlashingKeeper.IterateValidatorSigningInfos(
197 | ctx,
198 | func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) {
199 | info.StartHeight = 0
200 | app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info)
201 | return false
202 | },
203 | )
204 | }
205 |
--------------------------------------------------------------------------------
/app/test_setup.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "encoding/json"
5 | "time"
6 |
7 | sdkmath "cosmossdk.io/math"
8 | "github.com/Stride-Labs/stride/v16/testutil"
9 | cometbftdb "github.com/cometbft/cometbft-db"
10 | abci "github.com/cometbft/cometbft/abci/types"
11 | "github.com/cometbft/cometbft/crypto/secp256k1"
12 | "github.com/cometbft/cometbft/libs/log"
13 | tmtypes "github.com/cometbft/cometbft/types"
14 | "github.com/cosmos/cosmos-sdk/codec"
15 | codectypes "github.com/cosmos/cosmos-sdk/codec/types"
16 | cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
17 | "github.com/cosmos/cosmos-sdk/testutil/mock"
18 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
19 | sdk "github.com/cosmos/cosmos-sdk/types"
20 | "github.com/cosmos/cosmos-sdk/types/address"
21 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
22 | banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
23 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
24 |
25 | // cmdcfg "github.com/Stride-Labs/stride/v16/cmd/sided/config"
26 | errorsmod "cosmossdk.io/errors"
27 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
28 | )
29 |
30 | const bech32Prefix = "cosmos"
31 |
32 | func init() {
33 | SetupConfig()
34 | }
35 |
36 | func SetupConfig() {
37 | config := sdk.GetConfig()
38 | valoper := sdk.PrefixValidator + sdk.PrefixOperator
39 | valoperpub := sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic
40 | config.SetBech32PrefixForAccount(bech32Prefix, bech32Prefix+sdk.PrefixPublic)
41 | config.SetBech32PrefixForValidator(bech32Prefix+valoper, bech32Prefix+valoperpub)
42 |
43 | config.SetCoinType(84) // use the same coin type as bitcoin
44 |
45 | // This is copied from the cosmos sdk v0.43.0-beta1
46 | // source: https://github.com/cosmos/cosmos-sdk/blob/v0.43.0-beta1/types/address.go#L141
47 | config.SetAddressVerifier(func(bytes []byte) error {
48 | if len(bytes) == 0 {
49 | return errorsmod.Wrap(sdkerrors.ErrUnknownAddress, "addresses cannot be empty")
50 | }
51 |
52 | if len(bytes) > address.MaxAddrLen {
53 | return errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", address.MaxAddrLen, len(bytes))
54 | }
55 |
56 | // TODO: Do we want to allow addresses of lengths other than 20 and 32 bytes?
57 | // if len(bytes) != 20 && len(bytes) != 32 {
58 | // return errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "address length must be 20 or 32 bytes, got %d", len(bytes))
59 | // }
60 |
61 | return nil
62 | })
63 | }
64 |
65 | // Initializes a new SideApp without IBC functionality
66 | func InitSideTestApp(initChain bool) *App {
67 | db := cometbftdb.NewMemDB()
68 | app := New(
69 | log.NewNopLogger(),
70 | db,
71 | nil,
72 | true,
73 | map[int64]bool{},
74 | DefaultNodeHome,
75 | 5,
76 | MakeEncodingConfig(),
77 | simtestutil.EmptyAppOptions{},
78 | )
79 | if initChain {
80 | genesisState := GenesisStateWithValSet(app)
81 | stateBytes, err := json.MarshalIndent(genesisState, "", " ")
82 | if err != nil {
83 | panic(err)
84 | }
85 |
86 | app.InitChain(
87 | abci.RequestInitChain{
88 | Validators: []abci.ValidatorUpdate{},
89 | ConsensusParams: simtestutil.DefaultConsensusParams,
90 | AppStateBytes: stateBytes,
91 | },
92 | )
93 | }
94 |
95 | return app
96 | }
97 |
98 | func GenesisStateWithValSet(app *App) GenesisState {
99 | privVal := mock.NewPV()
100 | pubKey, _ := privVal.GetPubKey()
101 | validator := tmtypes.NewValidator(pubKey, 1)
102 | valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator})
103 |
104 | // generate genesis account
105 | senderPrivKey := secp256k1.GenPrivKey()
106 | senderPrivKey.PubKey().Address()
107 | acc := authtypes.NewBaseAccountWithAddress(senderPrivKey.PubKey().Address().Bytes())
108 | balance := banktypes.Balance{
109 | Address: acc.GetAddress().String(),
110 | Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))),
111 | }
112 | registry := codectypes.NewInterfaceRegistry()
113 | cdc := codec.NewProtoCodec(registry)
114 |
115 | //////////////////////
116 | balances := []banktypes.Balance{balance}
117 | genesisState := NewDefaultGenesisState(cdc)
118 | genAccs := []authtypes.GenesisAccount{acc}
119 | authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs)
120 | genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis)
121 |
122 | validators := make([]stakingtypes.Validator, 0, len(valSet.Validators))
123 | delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators))
124 |
125 | bondAmt := sdk.DefaultPowerReduction
126 | initValPowers := []abci.ValidatorUpdate{}
127 |
128 | for _, val := range valSet.Validators {
129 | pk, _ := cryptocodec.FromTmPubKeyInterface(val.PubKey)
130 | pkAny, _ := codectypes.NewAnyWithValue(pk)
131 | validator := stakingtypes.Validator{
132 | OperatorAddress: sdk.ValAddress(val.Address).String(),
133 | ConsensusPubkey: pkAny,
134 | Jailed: false,
135 | Status: stakingtypes.Bonded,
136 | Tokens: bondAmt,
137 | DelegatorShares: sdk.OneDec(),
138 | Description: stakingtypes.Description{},
139 | UnbondingHeight: int64(0),
140 | UnbondingTime: time.Unix(0, 0).UTC(),
141 | Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
142 | MinSelfDelegation: sdkmath.ZeroInt(),
143 | }
144 | validators = append(validators, validator)
145 | delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec()))
146 |
147 | // add initial validator powers so consumer InitGenesis runs correctly
148 | pub, _ := val.ToProto()
149 | initValPowers = append(initValPowers, abci.ValidatorUpdate{
150 | Power: val.VotingPower,
151 | PubKey: pub.PubKey,
152 | })
153 | }
154 | // set validators and delegations
155 | stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations)
156 | genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis)
157 |
158 | totalSupply := sdk.NewCoins()
159 | for _, b := range balances {
160 | // add genesis acc tokens to total supply
161 | totalSupply = totalSupply.Add(b.Coins...)
162 | }
163 |
164 | for range delegations {
165 | // add delegated tokens to total supply
166 | totalSupply = totalSupply.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt))
167 | }
168 |
169 | // add bonded amount to bonded pool module account
170 | balances = append(balances, banktypes.Balance{
171 | Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
172 | Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)},
173 | })
174 |
175 | // update total supply
176 | bankGenesis := banktypes.NewGenesisState(
177 | banktypes.DefaultGenesisState().Params,
178 | balances,
179 | totalSupply,
180 | []banktypes.Metadata{},
181 | []banktypes.SendEnabled{},
182 | )
183 | genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis)
184 |
185 | vals, err := tmtypes.PB2TM.ValidatorUpdates(initValPowers)
186 | if err != nil {
187 | panic("failed to get vals")
188 | }
189 |
190 | consumerGenesisState := testutil.CreateMinimalConsumerTestGenesis()
191 | consumerGenesisState.InitialValSet = initValPowers
192 | consumerGenesisState.ProviderConsensusState.NextValidatorsHash = tmtypes.NewValidatorSet(vals).Hash()
193 | consumerGenesisState.Params.Enabled = true
194 |
195 | return genesisState
196 | }
197 |
198 | // Initializes a new Side App casted as a TestingApp for IBC support
199 | // func InitSideIBCTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) {
200 | // app := InitSideTestApp(false)
201 | // registry := codectypes.NewInterfaceRegistry()
202 | // cdc := codec.NewProtoCodec(registry)
203 | // return app, NewDefaultGenesisState(cdc)
204 | // }
205 |
--------------------------------------------------------------------------------
/local_node_dev.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | KEYS=("validator" "test" "relayer" "btc-vault" "runes-vault")
4 | CHAINID="devnet"
5 | MONIKER="Side Labs"
6 | BINARY="$HOME/go/bin/sided"
7 | DENOM_STR="uside,sat,uusdc,uusdt"
8 | INITIAL_ACCOUNT_STR=""
9 | set -f
10 | IFS=,
11 | DENOMS=($DENOM_STR)
12 | INITIAL_ACCOUNTS=($INITIAL_ACCOUNT_STR)
13 |
14 | IFS=";"
15 |
16 | INITIAL_SUPPLY="500000000000000"
17 | BLOCK_GAS=10000000
18 | MAX_GAS=10000000000
19 |
20 | # Remember to change to other types of keyring like 'file' in-case exposing to outside world,
21 | # otherwise your balance will be wiped quickly
22 | # The keyring test does not require private key to steal tokens from you
23 | KEYRING="test"
24 | #KEYALGO="secp256k1"
25 | KEYALGO="segwit"
26 | LOGLEVEL="info"
27 | # Set dedicated home directory for the $BINARY instance
28 | HOMEDIR="$HOME/.side"
29 |
30 | # Path variables
31 | CONFIG=$HOMEDIR/config/config.toml
32 | APP_TOML=$HOMEDIR/config/app.toml
33 | GENESIS=$HOMEDIR/config/genesis.json
34 | TMP_GENESIS=$HOMEDIR/config/tmp_genesis.json
35 |
36 | # validate dependencies are installed
37 | command -v jq >/dev/null 2>&1 || {
38 | echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"
39 | exit 1
40 | }
41 |
42 | # used to exit on first error (any non-zero exit code)
43 | set -e
44 |
45 | # Reinstall daemon
46 | make install
47 |
48 | # User prompt if an existing local node configuration is found.
49 | if [ -d "$HOMEDIR" ]; then
50 | printf "\nAn existing folder at '%s' was found. You can choose to delete this folder and start a new local node with new keys from genesis. When declined, the existing local node is started. \n" "$HOMEDIR"
51 | echo "Overwrite the existing configuration and start a new local node? [y/n]"
52 | read -r overwrite
53 | else
54 | overwrite="Y"
55 | fi
56 |
57 |
58 | # Setup local node if overwrite is set to Yes, otherwise skip setup
59 | if [[ $overwrite == "y" || $overwrite == "Y" ]]; then
60 | # Remove the previous folder
61 | rm -rf "$HOMEDIR"
62 |
63 | # Set client config
64 | $BINARY config keyring-backend $KEYRING --home "$HOMEDIR"
65 | $BINARY config chain-id $CHAINID --home "$HOMEDIR"
66 |
67 | # If keys exist they should be deleted
68 | for KEY in "${KEYS[@]}"; do
69 | $BINARY keys add "$KEY" --keyring-backend $KEYRING --algo $KEYALGO --home "$HOMEDIR"
70 | done
71 | # for KEY in "${KEYS[@]}"; do
72 | # # Add the --recover flag to initiate recovery mode
73 | # $BINARY keys add "$KEY" --keyring-backend $KEYRING --algo $KEYALGO --recover --home "$HOMEDIR"
74 | # done
75 |
76 | echo ""
77 | echo "☝️ Copy the above mnemonic phrases and import them to relayer! Press [Enter] to continue..."
78 | read -r continue
79 |
80 | # Set moniker and chain-id for Cascadia (Moniker can be anything, chain-id must be an integer)
81 | $BINARY init $MONIKER -o --chain-id $CHAINID --home "$HOMEDIR"
82 |
83 | jq --arg denom "${DENOMS[0]}" '.app_state["staking"]["params"]["bond_denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
84 | jq --arg denom "${DENOMS[0]}" '.app_state["mint"]["params"]["mint_denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
85 | jq --arg denom "${DENOMS[0]}" '.app_state["crisis"]["constant_fee"]["denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
86 | jq --arg denom "${DENOMS[0]}" '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
87 | jq --arg denom "${DENOMS[0]}" '.app_state["gov"]["params"]["min_deposit"][0]["denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
88 | jq --arg gas "$BLOCK_GAS" '.app_state["feemarket"]["block_gas"]=$gas' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
89 | # Set gas limit in genesis
90 | jq --arg max_gas "$MAX_GAS" '.consensus_params["block"]["max_gas"]=$max_gas' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
91 | # setup relayers
92 | RELAYER=$($BINARY keys show "${KEYS[2]}" -a --keyring-backend $KEYRING --home "$HOMEDIR")
93 | jq --arg relayer "$RELAYER" '.app_state["btcbridge"]["params"]["authorized_relayers"][0]=$relayer' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
94 | # setup vaults
95 | BTC_VAULT=$($BINARY keys show "${KEYS[3]}" -a --keyring-backend $KEYRING --home "$HOMEDIR")
96 | jq --arg btc_vault "$BTC_VAULT" '.app_state["btcbridge"]["params"]["vaults"][0]["address"]=$btc_vault' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
97 | PUKEY=$($BINARY keys show "${KEYS[3]}" --pubkeyhex --keyring-backend $KEYRING --home "$HOMEDIR")
98 | jq --arg pubkey "$PUKEY" '.app_state["btcbridge"]["params"]["vaults"][0]["pub_key"]=$pubkey' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
99 | RUNES_VAULT=$($BINARY keys show "${KEYS[4]}" -a --keyring-backend $KEYRING --home "$HOMEDIR")
100 | jq --arg runes_vault "$RUNES_VAULT" '.app_state["btcbridge"]["params"]["vaults"][1]["address"]=$runes_vault' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
101 | PUKEY=$($BINARY keys show "${KEYS[4]}" --pubkeyhex --keyring-backend $KEYRING --home "$HOMEDIR")
102 | jq --arg pubkey "$PUKEY" '.app_state["btcbridge"]["params"]["vaults"][1]["pub_key"]=$pubkey' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
103 |
104 | # set custom pruning settings
105 | sed -i.bak 's/pruning = "default"/pruning = "custom"/g' "$APP_TOML"
106 | sed -i.bak 's/pruning-keep-recent = "0"/pruning-keep-recent = "2"/g' "$APP_TOML"
107 | sed -i.bak 's/pruning-interval = "0"/pruning-interval = "10"/g' "$APP_TOML"
108 |
109 | sed -i.bak 's/127.0.0.1:26657/0.0.0.0:26657/g' "$CONFIG"
110 | sed -i.bak 's/cors_allowed_origins\s*=\s*\[\]/cors_allowed_origins = ["*",]/g' "$CONFIG"
111 | sed -i.bak 's/swagger = false/swagger = true/g' $APP_TOML
112 |
113 | # Allocate genesis accounts (cosmos formatted addresses)
114 | for KEY in "${KEYS[@]}"; do
115 | BALANCES=""
116 | for key in "${!DENOMS[@]}"; do
117 | BALANCES+=",${INITIAL_SUPPLY}${DENOMS[$key]}"
118 | done
119 | echo ${BALANCES:1}
120 | $BINARY add-genesis-account "$KEY" ${BALANCES:1} --keyring-backend $KEYRING --home "$HOMEDIR"
121 | done
122 |
123 | echo "Genesis accounts allocated for local accounts"
124 |
125 | # Allocate genesis accounts (cosmos formatted addresses)
126 | for ADDR in "${INITIAL_ACCOUNTS[@]}"; do
127 | BALANCES=""
128 | for key in "${!DENOMS[@]}"; do
129 | BALANCES+=",${INITIAL_SUPPLY}${DENOMS[$key]}"
130 | done
131 | echo ${BALANCES:1}
132 | $BINARY add-genesis-account "$ADDR" ${BALANCES:1} --home "$HOMEDIR"
133 | done
134 | echo "Genesis accounts allocated for initial accounts"
135 |
136 |
137 |
138 | # Sign genesis transaction
139 | # echo $INITIAL_SUPPLY${DENOMS[0]}
140 | $BINARY gentx "${KEYS[0]}" $INITIAL_SUPPLY${DENOMS[0]} --keyring-backend $KEYRING --chain-id $CHAINID --identity "666AC57CC678BEC4" --website="https://side.one" --home "$HOMEDIR"
141 | echo "Genesis transaction signed"
142 |
143 | ## In case you want to create multiple validators at genesis
144 | ## 1. Back to `$BINARY keys add` step, init more keys
145 | ## 2. Back to `$BINARY add-genesis-account` step, add balance for those
146 | ## 3. Clone this ~/.$BINARY home directory into some others, let's say `~/.clonedCascadiad`
147 | ## 4. Run `gentx` in each of those folders
148 | ## 5. Copy the `gentx-*` folders under `~/.clonedCascadiad/config/gentx/` folders into the original `~/.$BINARY/config/gentx`
149 |
150 | # Collect genesis tx
151 | $BINARY collect-gentxs --home "$HOMEDIR"
152 | echo "Genesis transactions collected"
153 |
154 | # Run this to ensure everything worked and that the genesis file is setup correctly
155 | $BINARY validate-genesis --home "$HOMEDIR"
156 | echo "Genesis file validated"
157 |
158 | if [[ $1 == "pending" ]]; then
159 | echo "pending mode is on, please wait for the first block committed."
160 | fi
161 | fi
162 |
163 |
164 | # Start the node (remove the --pruning=nothing flag if historical queries are not needed)
165 | $BINARY start --log_level info --minimum-gas-prices=0.0001${DENOMS[0]} --home "$HOMEDIR"
166 |
--------------------------------------------------------------------------------
/local_node.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | KEYS=("validator" "test" "relayer" "btc-vault" "runes-vault")
4 | CHAINID="grimoria-testnet-1"
5 | MONIKER="Side Labs"
6 | BINARY="$HOME/go/bin/sided"
7 | DENOM_STR="uside,uusdc,uusdt"
8 | INITIAL_ACCOUNT_STR="tb1qcr8te4kr609gcawutmrza0j4xv80jy8zmfp6l0"
9 | set -f
10 | IFS=,
11 | DENOMS=($DENOM_STR)
12 | INITIAL_ACCOUNTS=($INITIAL_ACCOUNT_STR)
13 |
14 | IFS=";"
15 |
16 | INITIAL_SUPPLY="500000000000000"
17 | BLOCK_GAS=10000000
18 | MAX_GAS=10000000000
19 |
20 | # Remember to change to other types of keyring like 'file' in-case exposing to outside world,
21 | # otherwise your balance will be wiped quickly
22 | # The keyring test does not require private key to steal tokens from you
23 | KEYRING="test"
24 | #KEYALGO="secp256k1"
25 | KEYALGO="segwit"
26 | LOGLEVEL="info"
27 | # Set dedicated home directory for the $BINARY instance
28 | HOMEDIR="$HOME/.side"
29 |
30 | # Path variables
31 | CONFIG=$HOMEDIR/config/config.toml
32 | APP_TOML=$HOMEDIR/config/app.toml
33 | GENESIS=$HOMEDIR/config/genesis.json
34 | TMP_GENESIS=$HOMEDIR/config/tmp_genesis.json
35 |
36 | # validate dependencies are installed
37 | command -v jq >/dev/null 2>&1 || {
38 | echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"
39 | exit 1
40 | }
41 |
42 | # used to exit on first error (any non-zero exit code)
43 | set -e
44 |
45 | # Reinstall daemon
46 | make install
47 |
48 | # User prompt if an existing local node configuration is found.
49 | if [ -d "$HOMEDIR" ]; then
50 | printf "\nAn existing folder at '%s' was found. You can choose to delete this folder and start a new local node with new keys from genesis. When declined, the existing local node is started. \n" "$HOMEDIR"
51 | echo "Overwrite the existing configuration and start a new local node? [y/n]"
52 | read -r overwrite
53 | else
54 | overwrite="Y"
55 | fi
56 |
57 |
58 | # Setup local node if overwrite is set to Yes, otherwise skip setup
59 | if [[ $overwrite == "y" || $overwrite == "Y" ]]; then
60 | # Remove the previous folder
61 | rm -rf "$HOMEDIR"
62 |
63 | # Set client config
64 | $BINARY config keyring-backend $KEYRING --home "$HOMEDIR"
65 | $BINARY config chain-id $CHAINID --home "$HOMEDIR"
66 |
67 | # If keys exist they should be deleted
68 | for KEY in "${KEYS[@]}"; do
69 | $BINARY keys add "$KEY" --keyring-backend $KEYRING --algo $KEYALGO --home "$HOMEDIR"
70 | done
71 | # for KEY in "${KEYS[@]}"; do
72 | # # Add the --recover flag to initiate recovery mode
73 | # $BINARY keys add "$KEY" --keyring-backend $KEYRING --algo $KEYALGO --recover --home "$HOMEDIR"
74 | # done
75 |
76 | echo ""
77 | echo "☝️ Copy the above mnemonic phrases and import them to relayer! Press [Enter] to continue..."
78 | read -r continue
79 |
80 | # Set moniker and chain-id for Cascadia (Moniker can be anything, chain-id must be an integer)
81 | $BINARY init $MONIKER -o --chain-id $CHAINID --home "$HOMEDIR"
82 |
83 | jq --arg denom "${DENOMS[0]}" '.app_state["staking"]["params"]["bond_denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
84 | jq --arg denom "${DENOMS[0]}" '.app_state["mint"]["params"]["mint_denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
85 | jq --arg denom "${DENOMS[0]}" '.app_state["crisis"]["constant_fee"]["denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
86 | jq --arg denom "${DENOMS[0]}" '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
87 | jq --arg denom "${DENOMS[0]}" '.app_state["gov"]["params"]["min_deposit"][0]["denom"]=$denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
88 | jq --arg gas "$BLOCK_GAS" '.app_state["feemarket"]["block_gas"]=$gas' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
89 | # Set gas limit in genesis
90 | jq --arg max_gas "$MAX_GAS" '.consensus_params["block"]["max_gas"]=$max_gas' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
91 | # setup relayers
92 | RELAYER=$($BINARY keys show "${KEYS[2]}" -a --keyring-backend $KEYRING --home "$HOMEDIR")
93 | jq --arg relayer "$RELAYER" '.app_state["btcbridge"]["params"]["authorized_relayers"][0]=$relayer' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
94 | # setup vaults
95 | BTC_VAULT=$($BINARY keys show "${KEYS[3]}" -a --keyring-backend $KEYRING --home "$HOMEDIR")
96 | jq --arg btc_vault "$BTC_VAULT" '.app_state["btcbridge"]["params"]["vaults"][0]["address"]=$btc_vault' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
97 | PUKEY=$($BINARY keys show "${KEYS[3]}" --pubkeyhex --keyring-backend $KEYRING --home "$HOMEDIR")
98 | jq --arg pubkey "$PUKEY" '.app_state["btcbridge"]["params"]["vaults"][0]["pub_key"]=$pubkey' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
99 | RUNES_VAULT=$($BINARY keys show "${KEYS[4]}" -a --keyring-backend $KEYRING --home "$HOMEDIR")
100 | jq --arg runes_vault "$RUNES_VAULT" '.app_state["btcbridge"]["params"]["vaults"][1]["address"]=$runes_vault' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
101 | PUKEY=$($BINARY keys show "${KEYS[4]}" --pubkeyhex --keyring-backend $KEYRING --home "$HOMEDIR")
102 | jq --arg pubkey "$PUKEY" '.app_state["btcbridge"]["params"]["vaults"][1]["pub_key"]=$pubkey' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
103 |
104 | # set custom pruning settings
105 | sed -i.bak 's/pruning = "default"/pruning = "custom"/g' "$APP_TOML"
106 | sed -i.bak 's/pruning-keep-recent = "0"/pruning-keep-recent = "2"/g' "$APP_TOML"
107 | sed -i.bak 's/pruning-interval = "0"/pruning-interval = "10"/g' "$APP_TOML"
108 |
109 | sed -i.bak 's/127.0.0.1:26657/0.0.0.0:26657/g' "$CONFIG"
110 | sed -i.bak 's/cors_allowed_origins\s*=\s*\[\]/cors_allowed_origins = ["*",]/g' "$CONFIG"
111 | sed -i.bak 's/swagger = false/swagger = true/g' $APP_TOML
112 |
113 | # Allocate genesis accounts (cosmos formatted addresses)
114 | for KEY in "${KEYS[@]}"; do
115 | BALANCES=""
116 | for key in "${!DENOMS[@]}"; do
117 | BALANCES+=",${INITIAL_SUPPLY}${DENOMS[$key]}"
118 | done
119 | echo ${BALANCES:1}
120 | $BINARY add-genesis-account "$KEY" ${BALANCES:1} --keyring-backend $KEYRING --home "$HOMEDIR"
121 | done
122 |
123 | echo "Genesis accounts allocated for local accounts"
124 |
125 | # Allocate genesis accounts (cosmos formatted addresses)
126 | for ADDR in "${INITIAL_ACCOUNTS[@]}"; do
127 | BALANCES=""
128 | for key in "${!DENOMS[@]}"; do
129 | BALANCES+=",${INITIAL_SUPPLY}${DENOMS[$key]}"
130 | done
131 | echo ${BALANCES:1}
132 | $BINARY add-genesis-account "$ADDR" ${BALANCES:1} --home "$HOMEDIR"
133 | done
134 | echo "Genesis accounts allocated for initial accounts"
135 |
136 |
137 |
138 | # Sign genesis transaction
139 | # echo $INITIAL_SUPPLY${DENOMS[0]}
140 | $BINARY gentx "${KEYS[0]}" $INITIAL_SUPPLY${DENOMS[0]} --keyring-backend $KEYRING --chain-id $CHAINID --identity "666AC57CC678BEC4" --website="https://side.one" --home "$HOMEDIR"
141 | echo "Genesis transaction signed"
142 |
143 | ## In case you want to create multiple validators at genesis
144 | ## 1. Back to `$BINARY keys add` step, init more keys
145 | ## 2. Back to `$BINARY add-genesis-account` step, add balance for those
146 | ## 3. Clone this ~/.$BINARY home directory into some others, let's say `~/.clonedCascadiad`
147 | ## 4. Run `gentx` in each of those folders
148 | ## 5. Copy the `gentx-*` folders under `~/.clonedCascadiad/config/gentx/` folders into the original `~/.$BINARY/config/gentx`
149 |
150 | # Collect genesis tx
151 | $BINARY collect-gentxs --home "$HOMEDIR"
152 | echo "Genesis transactions collected"
153 |
154 | # Run this to ensure everything worked and that the genesis file is setup correctly
155 | $BINARY validate-genesis --home "$HOMEDIR"
156 | echo "Genesis file validated"
157 |
158 | if [[ $1 == "pending" ]]; then
159 | echo "pending mode is on, please wait for the first block committed."
160 | fi
161 | fi
162 |
163 |
164 | # Start the node (remove the --pruning=nothing flag if historical queries are not needed)
165 | $BINARY start --log_level info --minimum-gas-prices=0.0001${DENOMS[0]} --home "$HOMEDIR"
166 |
--------------------------------------------------------------------------------
/x/btcbridge/keeper/keeper_test.go:
--------------------------------------------------------------------------------
1 | package keeper_test
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "testing"
7 | "time"
8 |
9 | "github.com/stretchr/testify/suite"
10 | "lukechampine.com/uint128"
11 |
12 | "github.com/btcsuite/btcd/btcutil"
13 | "github.com/btcsuite/btcd/btcutil/psbt"
14 | "github.com/btcsuite/btcd/chaincfg"
15 | "github.com/btcsuite/btcd/chaincfg/chainhash"
16 | "github.com/btcsuite/btcd/txscript"
17 | "github.com/btcsuite/btcd/wire"
18 |
19 | tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
20 | "github.com/cosmos/btcutil/bech32"
21 | "github.com/cosmos/cosmos-sdk/crypto/keys/segwit"
22 | sdk "github.com/cosmos/cosmos-sdk/types"
23 |
24 | simapp "github.com/sideprotocol/side/app"
25 | "github.com/sideprotocol/side/x/btcbridge/types"
26 | )
27 |
28 | type KeeperTestSuite struct {
29 | suite.Suite
30 |
31 | ctx sdk.Context
32 | app *simapp.App
33 |
34 | btcVault string
35 | runesVault string
36 | sender string
37 |
38 | btcVaultPkScript []byte
39 | runesVaultPkScript []byte
40 | senderPkScript []byte
41 | }
42 |
43 | func (suite *KeeperTestSuite) SetupTest() {
44 | app := simapp.Setup(suite.T())
45 | ctx := app.BaseApp.NewContext(false, tmproto.Header{Time: time.Now().UTC()})
46 |
47 | suite.ctx = ctx
48 | suite.app = app
49 |
50 | chainCfg := sdk.GetConfig().GetBtcChainCfg()
51 |
52 | suite.btcVault, _ = bech32.Encode(chainCfg.Bech32HRPSegwit, segwit.GenPrivKey().PubKey().Address().Bytes())
53 | suite.runesVault, _ = bech32.Encode(chainCfg.Bech32HRPSegwit, segwit.GenPrivKey().PubKey().Address())
54 | suite.sender, _ = bech32.Encode(chainCfg.Bech32HRPSegwit, segwit.GenPrivKey().PubKey().Address())
55 |
56 | suite.btcVaultPkScript = MustPkScriptFromAddress(suite.btcVault, chainCfg)
57 | suite.runesVaultPkScript = MustPkScriptFromAddress(suite.runesVault, chainCfg)
58 | suite.senderPkScript = MustPkScriptFromAddress(suite.sender, chainCfg)
59 |
60 | suite.setupParams(suite.btcVault, suite.runesVault)
61 | }
62 |
63 | func TestKeeperSuite(t *testing.T) {
64 | suite.Run(t, new(KeeperTestSuite))
65 | }
66 |
67 | func (suite *KeeperTestSuite) setupParams(btcVault string, runesVault string) {
68 | suite.app.BtcBridgeKeeper.SetParams(suite.ctx, types.Params{Vaults: []*types.Vault{
69 | {
70 | Address: btcVault,
71 | AssetType: types.AssetType_ASSET_TYPE_BTC,
72 | },
73 | {
74 | Address: runesVault,
75 | AssetType: types.AssetType_ASSET_TYPE_RUNE,
76 | },
77 | }})
78 | }
79 |
80 | func (suite *KeeperTestSuite) setupUTXOs(utxos []*types.UTXO) {
81 | for _, utxo := range utxos {
82 | suite.app.BtcBridgeKeeper.SetUTXO(suite.ctx, utxo)
83 | suite.app.BtcBridgeKeeper.SetOwnerUTXO(suite.ctx, utxo)
84 |
85 | for _, r := range utxo.Runes {
86 | suite.app.BtcBridgeKeeper.SetOwnerRunesUTXO(suite.ctx, utxo, r.Id, r.Amount)
87 | }
88 | }
89 | }
90 |
91 | func (suite *KeeperTestSuite) TestMintRunes() {
92 | runeId := "840000:3"
93 | runeAmount := 500000000
94 | runeOutputIndex := 2
95 |
96 | runesScript, err := types.BuildEdictScript(runeId, uint128.From64(uint64(runeAmount)), uint32(runeOutputIndex))
97 | suite.NoError(err)
98 |
99 | tx := wire.NewMsgTx(types.TxVersion)
100 | tx.AddTxOut(wire.NewTxOut(0, runesScript))
101 | tx.AddTxOut(wire.NewTxOut(types.RunesOutValue, suite.senderPkScript))
102 | tx.AddTxOut(wire.NewTxOut(types.RunesOutValue, suite.runesVaultPkScript))
103 |
104 | denom := fmt.Sprintf("%s/%s", types.RunesProtocolName, runeId)
105 |
106 | balanceBefore := suite.app.BankKeeper.GetBalance(suite.ctx, sdk.MustAccAddressFromBech32(suite.sender), denom)
107 | suite.True(balanceBefore.Amount.IsZero(), "%s balance before mint should be zero", denom)
108 |
109 | recipient, err := suite.app.BtcBridgeKeeper.Mint(suite.ctx, btcutil.NewTx(tx), btcutil.NewTx(tx), 0)
110 | suite.NoError(err)
111 | suite.Equal(suite.sender, recipient.EncodeAddress(), "incorrect recipient")
112 |
113 | balanceAfter := suite.app.BankKeeper.GetBalance(suite.ctx, sdk.MustAccAddressFromBech32(suite.sender), denom)
114 | suite.Equal(uint64(runeAmount), balanceAfter.Amount.Uint64(), "%s balance after mint should be %d", denom, runeAmount)
115 |
116 | utxos := suite.app.BtcBridgeKeeper.GetAllUTXOs(suite.ctx)
117 | suite.Len(utxos, 1, "there should be 1 utxo(s)")
118 |
119 | expectedUTXO := &types.UTXO{
120 | Txid: tx.TxHash().String(),
121 | Vout: uint64(runeOutputIndex),
122 | Address: suite.runesVault,
123 | Amount: types.RunesOutValue,
124 | PubKeyScript: suite.runesVaultPkScript,
125 | IsLocked: false,
126 | Runes: []*types.RuneBalance{
127 | {
128 | Id: runeId,
129 | Amount: fmt.Sprintf("%d", runeAmount),
130 | },
131 | },
132 | }
133 |
134 | suite.Equal(expectedUTXO, utxos[0], "utxos do not match")
135 | }
136 |
137 | func (suite *KeeperTestSuite) TestWithdrawRunes() {
138 | runeId := "840000:3"
139 | runeAmount := 500000000
140 |
141 | runesUTXOs := []*types.UTXO{
142 | {
143 | Txid: chainhash.HashH([]byte("runes")).String(),
144 | Vout: 1,
145 | Address: suite.runesVault,
146 | Amount: types.RunesOutValue,
147 | PubKeyScript: suite.runesVaultPkScript,
148 | IsLocked: false,
149 | Runes: []*types.RuneBalance{
150 | {
151 | Id: runeId,
152 | Amount: fmt.Sprintf("%d", runeAmount),
153 | },
154 | },
155 | },
156 | }
157 | suite.setupUTXOs(runesUTXOs)
158 |
159 | feeRate := 100
160 | amount := runeAmount + 1
161 |
162 | denom := fmt.Sprintf("%s/%s", types.RunesProtocolName, runeId)
163 | coin := sdk.NewCoin(denom, sdk.NewInt(int64(amount)))
164 |
165 | _, err := suite.app.BtcBridgeKeeper.NewSigningRequest(suite.ctx, suite.sender, coin, int64(feeRate))
166 | suite.ErrorIs(err, types.ErrInsufficientUTXOs, "should fail due to insufficient runes utxos")
167 |
168 | amount = 100000000
169 | coin = sdk.NewCoin(denom, sdk.NewInt(int64(amount)))
170 |
171 | _, err = suite.app.BtcBridgeKeeper.NewSigningRequest(suite.ctx, suite.sender, coin, int64(feeRate))
172 | suite.ErrorIs(err, types.ErrInsufficientUTXOs, "should fail due to insufficient payment utxos")
173 |
174 | paymentUTXOs := []*types.UTXO{
175 | {
176 | Txid: chainhash.HashH([]byte("payment")).String(),
177 | Vout: 1,
178 | Address: suite.btcVault,
179 | Amount: 100000,
180 | PubKeyScript: suite.btcVaultPkScript,
181 | IsLocked: false,
182 | },
183 | }
184 | suite.setupUTXOs(paymentUTXOs)
185 |
186 | req, err := suite.app.BtcBridgeKeeper.NewSigningRequest(suite.ctx, suite.sender, coin, int64(feeRate))
187 | suite.NoError(err)
188 |
189 | suite.True(suite.app.BtcBridgeKeeper.IsUTXOLocked(suite.ctx, runesUTXOs[0].Txid, runesUTXOs[0].Vout), "runes utxo should be locked")
190 | suite.True(suite.app.BtcBridgeKeeper.IsUTXOLocked(suite.ctx, paymentUTXOs[0].Txid, paymentUTXOs[0].Vout), "payment utxo should be locked")
191 |
192 | runesUTXOs = suite.app.BtcBridgeKeeper.GetUnlockedUTXOsByAddr(suite.ctx, suite.runesVault)
193 | suite.Len(runesUTXOs, 1, "there should be 1 unlocked runes utxo(s)")
194 |
195 | suite.Len(runesUTXOs[0].Runes, 1, "there should be 1 rune in the runes utxo")
196 | suite.Equal(runeId, runesUTXOs[0].Runes[0].Id, "incorrect rune id")
197 | suite.Equal(uint64(runeAmount-amount), types.RuneAmountFromString(runesUTXOs[0].Runes[0].Amount).Big().Uint64(), "incorrect rune amount")
198 |
199 | p, err := psbt.NewFromRawBytes(bytes.NewReader([]byte(req.Psbt)), true)
200 | suite.NoError(err)
201 |
202 | suite.Len(p.Inputs, 2, "there should be 2 inputs")
203 | suite.Equal(suite.runesVaultPkScript, p.Inputs[0].WitnessUtxo.PkScript, "the first input should be runes vault")
204 | suite.Equal(suite.btcVaultPkScript, p.Inputs[1].WitnessUtxo.PkScript, "the second input should be btc vault")
205 |
206 | expectedRunesScript, err := types.BuildEdictScript(runeId, uint128.From64(uint64(amount)), 2)
207 | suite.NoError(err)
208 |
209 | suite.Len(p.UnsignedTx.TxOut, 4, "there should be 4 outputs")
210 | suite.Equal(expectedRunesScript, p.UnsignedTx.TxOut[0].PkScript, "incorrect runes script")
211 | suite.Equal(suite.runesVaultPkScript, p.UnsignedTx.TxOut[1].PkScript, "the second output should be runes change output")
212 | suite.Equal(suite.senderPkScript, p.UnsignedTx.TxOut[2].PkScript, "the third output should be sender output")
213 | suite.Equal(suite.btcVaultPkScript, p.UnsignedTx.TxOut[3].PkScript, "the fouth output should be btc change output")
214 | }
215 |
216 | func MustPkScriptFromAddress(addr string, chainCfg *chaincfg.Params) []byte {
217 | address, err := btcutil.DecodeAddress(addr, chainCfg)
218 | if err != nil {
219 | panic(err)
220 | }
221 |
222 | pkScript, err := txscript.PayToAddrScript(address)
223 | if err != nil {
224 | panic(err)
225 | }
226 |
227 | return pkScript
228 | }
229 |
--------------------------------------------------------------------------------
/x/btcbridge/keeper/msg_server.go:
--------------------------------------------------------------------------------
1 | package keeper
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/base64"
7 | "strconv"
8 |
9 | "github.com/btcsuite/btcd/btcutil/psbt"
10 |
11 | "cosmossdk.io/errors"
12 | sdk "github.com/cosmos/cosmos-sdk/types"
13 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
14 |
15 | "github.com/sideprotocol/side/x/btcbridge/types"
16 | )
17 |
18 | type msgServer struct {
19 | Keeper
20 | }
21 |
22 | // SubmitBlockHeaders implements types.MsgServer.
23 | func (m msgServer) SubmitBlockHeaders(goCtx context.Context, msg *types.MsgSubmitBlockHeaderRequest) (*types.MsgSubmitBlockHeadersResponse, error) {
24 | ctx := sdk.UnwrapSDKContext(goCtx)
25 | if err := msg.ValidateBasic(); err != nil {
26 | return nil, err
27 | }
28 |
29 | // check if the sender is one of the authorized senders
30 | param := m.GetParams(ctx)
31 | if !param.IsAuthorizedSender(msg.Sender) {
32 | return nil, types.ErrSenderAddressNotAuthorized
33 | }
34 |
35 | // Set block headers
36 | err := m.SetBlockHeaders(ctx, msg.BlockHeaders)
37 | if err != nil {
38 | return nil, err
39 | }
40 |
41 | // Emit events
42 | // m.EmitEvent(
43 | // ctx,
44 | // msg.Sender,
45 | // sdk.Attribute{
46 | // Key: types.AttributeKeyPoolCreator,
47 | // Value: msg.Sender,
48 | // },
49 | // )
50 | return &types.MsgSubmitBlockHeadersResponse{}, nil
51 | }
52 |
53 | // SubmitTransaction implements types.MsgServer.
54 | // No Permission check required for this message
55 | // Since everyone can submit a transaction to mint voucher tokens
56 | // This message is usually sent by relayers
57 | func (m msgServer) SubmitDepositTransaction(goCtx context.Context, msg *types.MsgSubmitDepositTransactionRequest) (*types.MsgSubmitDepositTransactionResponse, error) {
58 | ctx := sdk.UnwrapSDKContext(goCtx)
59 |
60 | if err := msg.ValidateBasic(); err != nil {
61 | ctx.Logger().Error("Error validating basic", "error", err)
62 | return nil, err
63 | }
64 |
65 | txHash, recipient, err := m.ProcessBitcoinDepositTransaction(ctx, msg)
66 | if err != nil {
67 | ctx.Logger().Error("Error processing bitcoin deposit transaction", "error", err)
68 | return nil, err
69 | }
70 |
71 | // Emit Events
72 | m.EmitEvent(ctx, msg.Sender,
73 | sdk.NewAttribute("blockhash", msg.Blockhash),
74 | sdk.NewAttribute("txBytes", msg.TxBytes),
75 | sdk.NewAttribute("txid", txHash.String()),
76 | sdk.NewAttribute("recipient", recipient.EncodeAddress()),
77 | )
78 |
79 | return &types.MsgSubmitDepositTransactionResponse{}, nil
80 | }
81 |
82 | // SubmitTransaction implements types.MsgServer.
83 | // No Permission check required for this message
84 | // Since everyone can submit a transaction to mint voucher tokens
85 | // This message is usually sent by relayers
86 | func (m msgServer) SubmitWithdrawTransaction(goCtx context.Context, msg *types.MsgSubmitWithdrawTransactionRequest) (*types.MsgSubmitWithdrawTransactionResponse, error) {
87 | ctx := sdk.UnwrapSDKContext(goCtx)
88 |
89 | if err := msg.ValidateBasic(); err != nil {
90 | ctx.Logger().Error("Error validating basic", "error", err)
91 | return nil, err
92 | }
93 |
94 | txHash, err := m.ProcessBitcoinWithdrawTransaction(ctx, msg)
95 | if err != nil {
96 | ctx.Logger().Error("Error processing bitcoin withdraw transaction", "error", err)
97 | return nil, err
98 | }
99 |
100 | // Emit Events
101 | m.EmitEvent(ctx, msg.Sender,
102 | sdk.NewAttribute("blockhash", msg.Blockhash),
103 | sdk.NewAttribute("txBytes", msg.TxBytes),
104 | sdk.NewAttribute("txid", txHash.String()),
105 | )
106 |
107 | return &types.MsgSubmitWithdrawTransactionResponse{}, nil
108 | }
109 |
110 | // UpdateSenders implements types.MsgServer.
111 | func (m msgServer) UpdateQualifiedRelayers(goCtx context.Context, msg *types.MsgUpdateQualifiedRelayersRequest) (*types.MsgUpdateQualifiedRelayersResponse, error) {
112 | ctx := sdk.UnwrapSDKContext(goCtx)
113 | if err := msg.ValidateBasic(); err != nil {
114 | return nil, err
115 | }
116 |
117 | // check if the sender is one of the authorized senders
118 | param := m.GetParams(ctx)
119 | if !param.IsAuthorizedSender(msg.Sender) {
120 | return nil, types.ErrSenderAddressNotAuthorized
121 | }
122 |
123 | // Set block headers
124 | m.SetParams(ctx, types.NewParams(msg.Relayers))
125 |
126 | // Emit events
127 |
128 | return &types.MsgUpdateQualifiedRelayersResponse{}, nil
129 | }
130 |
131 | func (m msgServer) WithdrawBitcoin(goCtx context.Context, msg *types.MsgWithdrawBitcoinRequest) (*types.MsgWithdrawBitcoinResponse, error) {
132 | if err := msg.ValidateBasic(); err != nil {
133 | return nil, err
134 | }
135 |
136 | ctx := sdk.UnwrapSDKContext(goCtx)
137 |
138 | sender := sdk.MustAccAddressFromBech32(msg.Sender)
139 |
140 | coin, err := sdk.ParseCoinNormalized(msg.Amount)
141 | if err != nil {
142 | return nil, err
143 | }
144 |
145 | if coin.Denom == m.GetParams(ctx).BtcVoucherDenom {
146 | if err := types.CheckOutputAmount(msg.Sender, coin.Amount.Int64()); err != nil {
147 | return nil, err
148 | }
149 | }
150 |
151 | feeRate, err := strconv.ParseInt(msg.FeeRate, 10, 64)
152 | if err != nil {
153 | return nil, err
154 | }
155 |
156 | req, err := m.Keeper.NewSigningRequest(ctx, msg.Sender, coin, feeRate)
157 | if err != nil {
158 | return nil, err
159 | }
160 |
161 | if err = m.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(coin)); err != nil {
162 | return nil, err
163 | }
164 |
165 | m.lockAsset(ctx, req.Txid, coin)
166 |
167 | // Emit events
168 | m.EmitEvent(ctx, msg.Sender,
169 | sdk.NewAttribute("amount", msg.Amount),
170 | sdk.NewAttribute("txid", req.Txid),
171 | )
172 |
173 | return &types.MsgWithdrawBitcoinResponse{}, nil
174 | }
175 |
176 | // SubmitWithdrawSignatures submits the signatures of the withdraw transaction.
177 | func (m msgServer) SubmitWithdrawSignatures(goCtx context.Context, msg *types.MsgSubmitWithdrawSignaturesRequest) (*types.MsgSubmitWithdrawSignaturesResponse, error) {
178 | if err := msg.ValidateBasic(); err != nil {
179 | return nil, err
180 | }
181 | ctx := sdk.UnwrapSDKContext(goCtx)
182 | exist := m.HasSigningRequest(ctx, msg.Txid)
183 | if !exist {
184 | return nil, types.ErrSigningRequestNotExist
185 | }
186 |
187 | b, err := base64.StdEncoding.DecodeString(msg.Psbt)
188 | if err != nil {
189 | return nil, types.ErrInvalidSignatures
190 | }
191 |
192 | packet, err := psbt.NewFromRawBytes(bytes.NewReader(b), false)
193 | if err != nil {
194 | return nil, err
195 | }
196 |
197 | if packet.UnsignedTx.TxHash().String() != msg.Txid {
198 | return nil, types.ErrInvalidSignatures
199 | }
200 |
201 | if err = packet.SanityCheck(); err != nil {
202 | return nil, err
203 | }
204 | if !packet.IsComplete() {
205 | return nil, types.ErrInvalidSignatures
206 | }
207 |
208 | // verify the signatures
209 | if !types.VerifyPsbtSignatures(packet) {
210 | return nil, types.ErrInvalidSignatures
211 | }
212 |
213 | // Set the signing request status to signed
214 | request := m.GetSigningRequest(ctx, msg.Txid)
215 | request.Psbt = msg.Psbt
216 | request.Status = types.SigningStatus_SIGNING_STATUS_SIGNED
217 | m.SetSigningRequest(ctx, request)
218 |
219 | return &types.MsgSubmitWithdrawSignaturesResponse{}, nil
220 | }
221 |
222 | func (m msgServer) SubmitWithdrawStatus(goCtx context.Context, msg *types.MsgSubmitWithdrawStatusRequest) (*types.MsgSubmitWithdrawStatusResponse, error) {
223 | if err := msg.ValidateBasic(); err != nil {
224 | return nil, err
225 | }
226 | param := m.GetParams(sdk.UnwrapSDKContext(goCtx))
227 | if !param.IsAuthorizedSender(msg.Sender) {
228 | return nil, types.ErrSenderAddressNotAuthorized
229 | }
230 | ctx := sdk.UnwrapSDKContext(goCtx)
231 | exist := m.HasSigningRequest(ctx, msg.Txid)
232 | if !exist {
233 | return nil, types.ErrSigningRequestNotExist
234 | }
235 |
236 | request := m.GetSigningRequest(ctx, msg.Txid)
237 | request.Status = msg.Status
238 | m.SetSigningRequest(ctx, request)
239 |
240 | return &types.MsgSubmitWithdrawStatusResponse{}, nil
241 | }
242 |
243 | // UpdateParams updates the module params.
244 | func (m msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParamsRequest) (*types.MsgUpdateParamsResponse, error) {
245 | if m.authority != msg.Authority {
246 | return nil, errors.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", m.authority, msg.Authority)
247 | }
248 |
249 | if err := msg.ValidateBasic(); err != nil {
250 | return nil, err
251 | }
252 |
253 | ctx := sdk.UnwrapSDKContext(goCtx)
254 | m.SetParams(ctx, msg.Params)
255 |
256 | return &types.MsgUpdateParamsResponse{}, nil
257 | }
258 |
259 | // NewMsgServerImpl returns an implementation of the MsgServer interface
260 | // for the provided Keeper.
261 | func NewMsgServerImpl(keeper Keeper) types.MsgServer {
262 | return &msgServer{Keeper: keeper}
263 | }
264 |
265 | var _ types.MsgServer = msgServer{}
266 |
--------------------------------------------------------------------------------
/x/btcbridge/keeper/keeper_deposit.go:
--------------------------------------------------------------------------------
1 | package keeper
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "fmt"
7 |
8 | "github.com/btcsuite/btcd/blockchain"
9 | "github.com/btcsuite/btcd/btcutil"
10 | "github.com/btcsuite/btcd/chaincfg/chainhash"
11 | "github.com/btcsuite/btcd/txscript"
12 | "github.com/btcsuite/btcd/wire"
13 |
14 | sdk "github.com/cosmos/cosmos-sdk/types"
15 |
16 | "github.com/sideprotocol/side/x/btcbridge/types"
17 | )
18 |
19 | // Process Bitcoin Deposit Transaction
20 | func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.MsgSubmitDepositTransactionRequest) (*chainhash.Hash, btcutil.Address, error) {
21 | ctx.Logger().Info("accept bitcoin deposit tx", "blockhash", msg.Blockhash)
22 |
23 | params := k.GetParams(ctx)
24 | if !params.IsAuthorizedSender(msg.Sender) {
25 | return nil, nil, types.ErrSenderAddressNotAuthorized
26 | }
27 |
28 | tx, prevTx, err := k.ValidateDepositTransaction(ctx, msg.TxBytes, msg.PrevTxBytes, msg.Blockhash, msg.Proof)
29 | if err != nil {
30 | return nil, nil, err
31 | }
32 |
33 | recipient, err := k.Mint(ctx, tx, prevTx, k.GetBlockHeader(ctx, msg.Blockhash).Height)
34 | if err != nil {
35 | return nil, nil, err
36 | }
37 |
38 | return tx.Hash(), recipient, nil
39 | }
40 |
41 | // validateDepositTransaction validates the deposit transaction
42 | func (k Keeper) ValidateDepositTransaction(ctx sdk.Context, txBytes string, prevTxBytes string, blockHash string, proof []string) (*btcutil.Tx, *btcutil.Tx, error) {
43 | params := k.GetParams(ctx)
44 |
45 | header := k.GetBlockHeader(ctx, blockHash)
46 | // Check if block confirmed
47 | if header == nil || header.Height == 0 {
48 | return nil, nil, types.ErrBlockNotFound
49 | }
50 |
51 | best := k.GetBestBlockHeader(ctx)
52 | // Check if the block is confirmed
53 | if best.Height-header.Height < uint64(params.Confirmations) {
54 | return nil, nil, types.ErrNotConfirmed
55 | }
56 | // Check if the block is within the acceptable depth
57 | // if best.Height-header.Height > param.MaxAcceptableBlockDepth {
58 | // return types.ErrExceedMaxAcceptanceDepth
59 | // }
60 |
61 | // Decode the base64 transaction
62 | rawTx, err := base64.StdEncoding.DecodeString(txBytes)
63 | if err != nil {
64 | fmt.Println("Error decoding transaction from base64:", err)
65 | return nil, nil, err
66 | }
67 |
68 | // Create a new transaction
69 | var tx wire.MsgTx
70 | err = tx.Deserialize(bytes.NewReader(rawTx))
71 | if err != nil {
72 | fmt.Println("Error deserializing transaction:", err)
73 | return nil, nil, err
74 | }
75 |
76 | uTx := btcutil.NewTx(&tx)
77 |
78 | // Validate the transaction
79 | if err := blockchain.CheckTransactionSanity(uTx); err != nil {
80 | fmt.Println("Transaction is not valid:", err)
81 | return nil, nil, err
82 | }
83 |
84 | // Decode the previous transaction
85 | rawPrevTx, err := base64.StdEncoding.DecodeString(prevTxBytes)
86 | if err != nil {
87 | fmt.Println("Error decoding transaction from base64:", err)
88 | return nil, nil, err
89 | }
90 |
91 | // Create a new transaction
92 | var prevMsgTx wire.MsgTx
93 | err = prevMsgTx.Deserialize(bytes.NewReader(rawPrevTx))
94 | if err != nil {
95 | fmt.Println("Error deserializing transaction:", err)
96 | return nil, nil, err
97 | }
98 |
99 | prevTx := btcutil.NewTx(&prevMsgTx)
100 |
101 | // Validate the transaction
102 | if err := blockchain.CheckTransactionSanity(prevTx); err != nil {
103 | fmt.Println("Transaction is not valid:", err)
104 | return nil, nil, err
105 | }
106 |
107 | if uTx.MsgTx().TxIn[0].PreviousOutPoint.Hash.String() != prevTx.Hash().String() {
108 | return nil, nil, types.ErrInvalidBtcTransaction
109 | }
110 |
111 | // check if the proof is valid
112 | root, err := chainhash.NewHashFromStr(header.MerkleRoot)
113 | if err != nil {
114 | return nil, nil, err
115 | }
116 |
117 | if !types.VerifyMerkleProof(proof, uTx.Hash(), root) {
118 | k.Logger(ctx).Error("Invalid merkle proof", "txhash", tx, "root", root, "proof", proof)
119 | return nil, nil, types.ErrTransactionNotIncluded
120 | }
121 |
122 | return uTx, prevTx, nil
123 | }
124 |
125 | // mint performs the minting operation of the voucher token
126 | func (k Keeper) Mint(ctx sdk.Context, tx *btcutil.Tx, prevTx *btcutil.Tx, height uint64) (btcutil.Address, error) {
127 | params := k.GetParams(ctx)
128 | chainCfg := sdk.GetConfig().GetBtcChainCfg()
129 |
130 | // check if this is a valid runes deposit tx
131 | // if any error encountered, this tx is illegal runes deposit
132 | // if the edict is not nil, it indicates that this is a legal runes deposit tx
133 | edict, err := types.CheckRunesDepositTransaction(tx.MsgTx(), params.Vaults)
134 | if err != nil {
135 | return nil, err
136 | }
137 |
138 | isRunes := edict != nil
139 |
140 | // extract the recipient for minting voucher token
141 | recipient, err := types.ExtractRecipientAddr(tx.MsgTx(), prevTx.MsgTx(), params.Vaults, isRunes, chainCfg)
142 | if err != nil {
143 | return nil, err
144 | }
145 |
146 | // mint voucher token and save utxo if the receiver is a vault address
147 | for i, out := range tx.MsgTx().TxOut {
148 | if types.IsOpReturnOutput(out) {
149 | continue
150 | }
151 |
152 | // check if the output is a valid address
153 | pks, err := txscript.ParsePkScript(out.PkScript)
154 | if err != nil {
155 | return nil, err
156 | }
157 | addr, err := pks.Address(chainCfg)
158 | if err != nil {
159 | return nil, err
160 | }
161 |
162 | // check if the receiver is one of the voucher addresses
163 | vault := types.SelectVaultByBitcoinAddress(params.Vaults, addr.EncodeAddress())
164 | if vault == nil {
165 | continue
166 | }
167 |
168 | // mint the voucher token by asset type and save utxos
169 | // skip if the asset type of the sender address is unspecified
170 | switch vault.AssetType {
171 | case types.AssetType_ASSET_TYPE_BTC:
172 | err := k.mintBTC(ctx, tx, height, recipient.EncodeAddress(), vault, out, i, params.BtcVoucherDenom)
173 | if err != nil {
174 | return nil, err
175 | }
176 |
177 | case types.AssetType_ASSET_TYPE_RUNE:
178 | if isRunes && edict.Output == uint32(i) {
179 | if err := k.mintRunes(ctx, tx, height, recipient.EncodeAddress(), vault, out, i, edict.Id, edict.Amount); err != nil {
180 | return nil, err
181 | }
182 | }
183 | }
184 | }
185 |
186 | return recipient, nil
187 | }
188 |
189 | func (k Keeper) mintBTC(ctx sdk.Context, tx *btcutil.Tx, height uint64, sender string, vault *types.Vault, out *wire.TxOut, vout int, denom string) error {
190 | // save the hash of the transaction to prevent double minting
191 | hash := tx.Hash().String()
192 | if k.existsInHistory(ctx, hash) {
193 | return types.ErrTransactionAlreadyMinted
194 | }
195 | k.addToMintHistory(ctx, hash)
196 |
197 | // mint the voucher token
198 | if len(denom) == 0 {
199 | denom = "sat"
200 | }
201 | coins := sdk.NewCoins(sdk.NewCoin(denom, sdk.NewInt(out.Value)))
202 |
203 | receipient, err := sdk.AccAddressFromBech32(sender)
204 | if err != nil {
205 | return err
206 | }
207 |
208 | if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil {
209 | return err
210 | }
211 |
212 | if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, receipient, coins); err != nil {
213 | return err
214 | }
215 |
216 | utxo := types.UTXO{
217 | Txid: hash,
218 | Vout: uint64(vout),
219 | Amount: uint64(out.Value),
220 | PubKeyScript: out.PkScript,
221 | Height: height,
222 | Address: vault.Address,
223 | IsCoinbase: false,
224 | IsLocked: false,
225 | }
226 |
227 | k.saveUTXO(ctx, &utxo)
228 |
229 | return nil
230 | }
231 |
232 | func (k Keeper) mintRunes(ctx sdk.Context, tx *btcutil.Tx, height uint64, recipient string, vault *types.Vault, out *wire.TxOut, vout int, id *types.RuneId, amount string) error {
233 | // save the hash of the transaction to prevent double minting
234 | hash := tx.Hash().String()
235 | if k.existsInHistory(ctx, hash) {
236 | return types.ErrTransactionAlreadyMinted
237 | }
238 | k.addToMintHistory(ctx, hash)
239 |
240 | coins := sdk.NewCoins(sdk.NewCoin(id.Denom(), sdk.NewIntFromBigInt(types.RuneAmountFromString(amount).Big())))
241 |
242 | receipientAddr, err := sdk.AccAddressFromBech32(recipient)
243 | if err != nil {
244 | return err
245 | }
246 |
247 | if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil {
248 | return err
249 | }
250 |
251 | if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, receipientAddr, coins); err != nil {
252 | return err
253 | }
254 |
255 | utxo := types.UTXO{
256 | Txid: hash,
257 | Vout: uint64(vout),
258 | Amount: uint64(out.Value),
259 | PubKeyScript: out.PkScript,
260 | Height: height,
261 | Address: vault.Address,
262 | IsCoinbase: false,
263 | IsLocked: false,
264 | Runes: []*types.RuneBalance{{
265 | Id: id.ToString(),
266 | Amount: amount,
267 | }},
268 | }
269 |
270 | k.saveUTXO(ctx, &utxo)
271 |
272 | return nil
273 | }
274 |
275 | func (k Keeper) existsInHistory(ctx sdk.Context, txHash string) bool {
276 | store := ctx.KVStore(k.storeKey)
277 |
278 | return store.Has(types.BtcMintedTxHashKey(txHash))
279 | }
280 |
281 | func (k Keeper) addToMintHistory(ctx sdk.Context, txHash string) {
282 | store := ctx.KVStore(k.storeKey)
283 |
284 | store.Set(types.BtcMintedTxHashKey(txHash), []byte{1})
285 | }
286 |
287 | // need a query all history for exporting
288 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/sideprotocol/side
2 |
3 | go 1.19
4 |
5 | require (
6 | cosmossdk.io/api v0.3.1
7 | cosmossdk.io/errors v1.0.1
8 | cosmossdk.io/math v1.3.0
9 | github.com/CosmWasm/wasmd v0.45.0
10 | github.com/Stride-Labs/stride/v16 v16.0.0
11 | github.com/btcsuite/btcd v0.24.0
12 | github.com/btcsuite/btcd/btcec/v2 v2.3.2
13 | github.com/btcsuite/btcd/btcutil v1.1.5
14 | github.com/btcsuite/btcd/btcutil/psbt v1.1.9
15 | github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
16 | github.com/cometbft/cometbft v0.37.4
17 | github.com/cometbft/cometbft-db v0.8.0
18 | github.com/cosmos/btcutil v1.0.5
19 | github.com/cosmos/cosmos-sdk v0.47.5
20 | github.com/cosmos/gogoproto v1.4.10
21 | github.com/cosmos/ibc-go/v7 v7.3.0
22 | github.com/golang/protobuf v1.5.3
23 | github.com/gorilla/mux v1.8.0
24 | github.com/grpc-ecosystem/grpc-gateway v1.16.0
25 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
26 | github.com/prometheus/client_golang v1.16.0
27 | github.com/spf13/cast v1.5.1
28 | github.com/spf13/cobra v1.7.0
29 | github.com/spf13/pflag v1.0.5
30 | github.com/stretchr/testify v1.8.4
31 | google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0
32 | google.golang.org/grpc v1.60.1
33 | lukechampine.com/uint128 v1.3.0
34 | )
35 |
36 | require (
37 | cloud.google.com/go v0.111.0 // indirect
38 | cloud.google.com/go/compute v1.23.3 // indirect
39 | cloud.google.com/go/compute/metadata v0.2.3 // indirect
40 | cloud.google.com/go/iam v1.1.5 // indirect
41 | cloud.google.com/go/storage v1.30.1 // indirect
42 | cosmossdk.io/core v0.5.1 // indirect
43 | cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
44 | cosmossdk.io/log v1.3.1 // indirect
45 | cosmossdk.io/tools/rosetta v0.2.1 // indirect
46 | filippo.io/edwards25519 v1.0.0 // indirect
47 | github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
48 | github.com/99designs/keyring v1.2.1 // indirect
49 | github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect
50 | github.com/CosmWasm/wasmvm v1.5.2 // indirect
51 | github.com/aead/siphash v1.0.1 // indirect
52 | github.com/armon/go-metrics v0.4.1 // indirect
53 | github.com/aws/aws-sdk-go v1.44.203 // indirect
54 | github.com/beorn7/perks v1.0.1 // indirect
55 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
56 | github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
57 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
58 | github.com/cenkalti/backoff/v4 v4.1.3 // indirect
59 | github.com/cespare/xxhash v1.1.0 // indirect
60 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
61 | github.com/chzyer/readline v1.5.1 // indirect
62 | github.com/cockroachdb/apd/v2 v2.0.2 // indirect
63 | github.com/cockroachdb/errors v1.10.0 // indirect
64 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
65 | github.com/cockroachdb/redact v1.1.5 // indirect
66 | github.com/coinbase/rosetta-sdk-go/types v1.0.0 // indirect
67 | github.com/confio/ics23/go v0.9.0 // indirect
68 | github.com/cosmos/cosmos-proto v1.0.0-beta.4 // indirect
69 | github.com/cosmos/go-bip39 v1.0.0 // indirect
70 | github.com/cosmos/gogogateway v1.2.0 // indirect
71 | github.com/cosmos/iavl v0.20.1 // indirect
72 | github.com/cosmos/ics23/go v0.10.0 // indirect
73 | github.com/cosmos/interchain-security/v3 v3.1.0 // indirect
74 | github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect
75 | github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect
76 | github.com/creachadair/taskgroup v0.4.2 // indirect
77 | github.com/danieljoos/wincred v1.1.2 // indirect
78 | github.com/davecgh/go-spew v1.1.1 // indirect
79 | github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
80 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
81 | github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
82 | github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
83 | github.com/dgraph-io/ristretto v0.1.1 // indirect
84 | github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
85 | github.com/docker/distribution v2.8.2+incompatible // indirect
86 | github.com/dustin/go-humanize v1.0.1 // indirect
87 | github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
88 | github.com/felixge/httpsnoop v1.0.2 // indirect
89 | github.com/fsnotify/fsnotify v1.6.0 // indirect
90 | github.com/getsentry/sentry-go v0.23.0 // indirect
91 | github.com/ghodss/yaml v1.0.0 // indirect
92 | github.com/go-kit/kit v0.12.0 // indirect
93 | github.com/go-kit/log v0.2.1 // indirect
94 | github.com/go-logfmt/logfmt v0.6.0 // indirect
95 | github.com/go-logr/logr v1.2.4 // indirect
96 | github.com/go-logr/stdr v1.2.2 // indirect
97 | github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
98 | github.com/gogo/googleapis v1.4.1 // indirect
99 | github.com/gogo/protobuf v1.3.3 // indirect
100 | github.com/golang/glog v1.1.2 // indirect
101 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
102 | github.com/golang/mock v1.6.0 // indirect
103 | github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
104 | github.com/google/btree v1.1.2 // indirect
105 | github.com/google/go-cmp v0.6.0 // indirect
106 | github.com/google/gofuzz v1.2.0 // indirect
107 | github.com/google/orderedcode v0.0.1 // indirect
108 | github.com/google/s2a-go v0.1.7 // indirect
109 | github.com/google/uuid v1.4.0 // indirect
110 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
111 | github.com/googleapis/gax-go/v2 v2.12.0 // indirect
112 | github.com/gorilla/handlers v1.5.1 // indirect
113 | github.com/gorilla/websocket v1.5.0 // indirect
114 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
115 | github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
116 | github.com/gtank/merlin v0.1.1 // indirect
117 | github.com/gtank/ristretto255 v0.1.2 // indirect
118 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
119 | github.com/hashicorp/go-getter v1.7.1 // indirect
120 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
121 | github.com/hashicorp/go-safetemp v1.0.0 // indirect
122 | github.com/hashicorp/go-version v1.6.0 // indirect
123 | github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
124 | github.com/hashicorp/hcl v1.0.0 // indirect
125 | github.com/hdevalence/ed25519consensus v0.1.0 // indirect
126 | github.com/huandu/skiplist v1.2.0 // indirect
127 | github.com/improbable-eng/grpc-web v0.15.0 // indirect
128 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
129 | github.com/jmespath/go-jmespath v0.4.0 // indirect
130 | github.com/jmhodges/levigo v1.0.0 // indirect
131 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect
132 | github.com/klauspost/compress v1.16.7 // indirect
133 | github.com/kr/pretty v0.3.1 // indirect
134 | github.com/kr/text v0.2.0 // indirect
135 | github.com/lib/pq v1.10.7 // indirect
136 | github.com/libp2p/go-buffer-pool v0.1.0 // indirect
137 | github.com/linxGnu/grocksdb v1.7.16 // indirect
138 | github.com/magiconair/properties v1.8.7 // indirect
139 | github.com/manifoldco/promptui v0.9.0 // indirect
140 | github.com/mattn/go-colorable v0.1.13 // indirect
141 | github.com/mattn/go-isatty v0.0.20 // indirect
142 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
143 | github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
144 | github.com/minio/highwayhash v1.0.2 // indirect
145 | github.com/mitchellh/go-homedir v1.1.0 // indirect
146 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect
147 | github.com/mitchellh/mapstructure v1.5.0 // indirect
148 | github.com/mtibben/percent v0.2.1 // indirect
149 | github.com/opencontainers/go-digest v1.0.0 // indirect
150 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
151 | github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect
152 | github.com/pkg/errors v0.9.1 // indirect
153 | github.com/pmezard/go-difflib v1.0.0 // indirect
154 | github.com/prometheus/client_model v0.3.0 // indirect
155 | github.com/prometheus/common v0.42.0 // indirect
156 | github.com/prometheus/procfs v0.10.1 // indirect
157 | github.com/rakyll/statik v0.1.7 // indirect
158 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
159 | github.com/rogpeppe/go-internal v1.11.0 // indirect
160 | github.com/rs/cors v1.8.3 // indirect
161 | github.com/rs/zerolog v1.32.0 // indirect
162 | github.com/sasha-s/go-deadlock v0.3.1 // indirect
163 | github.com/spf13/afero v1.9.5 // indirect
164 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
165 | github.com/spf13/viper v1.16.0 // indirect
166 | github.com/subosito/gotenv v1.4.2 // indirect
167 | github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
168 | github.com/tendermint/go-amino v0.16.0 // indirect
169 | github.com/tidwall/btree v1.6.0 // indirect
170 | github.com/ulikunitz/xz v0.5.11 // indirect
171 | github.com/zondax/hid v0.9.2 // indirect
172 | github.com/zondax/ledger-go v0.14.3 // indirect
173 | go.etcd.io/bbolt v1.3.7 // indirect
174 | go.opencensus.io v0.24.0 // indirect
175 | go.opentelemetry.io/otel v1.19.0 // indirect
176 | go.opentelemetry.io/otel/metric v1.19.0 // indirect
177 | go.opentelemetry.io/otel/trace v1.19.0 // indirect
178 | golang.org/x/crypto v0.21.0 // indirect
179 | golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect
180 | golang.org/x/net v0.21.0 // indirect
181 | golang.org/x/oauth2 v0.13.0 // indirect
182 | golang.org/x/sync v0.4.0 // indirect
183 | golang.org/x/sys v0.18.0 // indirect
184 | golang.org/x/term v0.18.0 // indirect
185 | golang.org/x/text v0.14.0 // indirect
186 | google.golang.org/api v0.149.0 // indirect
187 | google.golang.org/appengine v1.6.8 // indirect
188 | google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect
189 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
190 | google.golang.org/protobuf v1.32.0 // indirect
191 | gopkg.in/ini.v1 v1.67.0 // indirect
192 | gopkg.in/yaml.v2 v2.4.0 // indirect
193 | gopkg.in/yaml.v3 v3.0.1 // indirect
194 | nhooyr.io/websocket v1.8.6 // indirect
195 | pgregory.net/rapid v1.1.0 // indirect
196 | sigs.k8s.io/yaml v1.4.0 // indirect
197 | )
198 |
199 | replace (
200 | github.com/cosmos/cosmos-sdk => github.com/sideprotocol/cosmos-sdk v0.47.11-btc1
201 | // github.com/cosmos/cosmos-sdk => ../cosmos-sdk
202 | github.com/cosmos/interchain-security/v3 => github.com/Stride-Labs/interchain-security/v3 v3.1.0-remove-validation-bug-7d3d9d
203 | github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
204 | github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
205 | )
206 |
--------------------------------------------------------------------------------