├── .github
└── workflows
│ └── go.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── SuiStake.md
├── core
├── README.md
├── aptos
│ ├── account.go
│ ├── account_test.go
│ ├── address_util.go
│ ├── chain.go
│ ├── chain_test.go
│ ├── dapp.go
│ ├── dapp_test.go
│ ├── interface_test.go
│ ├── nft.go
│ ├── nft_payload.go
│ ├── nft_payload_test.go
│ ├── nft_test.go
│ ├── rest_reachability.go
│ ├── rest_reachability_test.go
│ ├── token.go
│ ├── token_test.go
│ └── transaction.go
├── base
│ ├── account.go
│ ├── any.go
│ ├── any_test.go
│ ├── balance.go
│ ├── big.go
│ ├── chain.go
│ ├── error.go
│ ├── inter
│ │ ├── README.md
│ │ ├── any_array.go
│ │ ├── any_array_test.go
│ │ ├── any_map.go
│ │ ├── any_map_test.go
│ │ ├── pageable.go
│ │ ├── pageable_test.go
│ │ └── util.go
│ ├── jsonable.go
│ ├── jsonable_test.go
│ ├── nft.go
│ ├── reach_monitor.go
│ ├── token.go
│ ├── transaction.go
│ ├── types.go
│ ├── types_test.go
│ ├── util.go
│ └── util_test.go
├── btc
│ ├── account.go
│ ├── account_derivation.go
│ ├── account_derivation_test.go
│ ├── account_test.go
│ ├── address_type.go
│ ├── address_util.go
│ ├── balance.go
│ ├── balance_test.go
│ ├── brc20_inscription.go
│ ├── brc20_inscription_test.go
│ ├── brc20_mint.go
│ ├── brc20_mint_test.go
│ ├── brc20_token.go
│ ├── brc20_token_test.go
│ ├── brc20_transfer.go
│ ├── brc20_transfer_test.go
│ ├── brc20_types.go
│ ├── brc20_util.go
│ ├── chain.go
│ ├── chain_test.go
│ ├── chainnet.go
│ ├── errs.go
│ ├── interface_test.go
│ ├── ordinal
│ │ ├── brc20.go
│ │ ├── decimal.go
│ │ ├── ordinals.go
│ │ └── ordinals_test.go
│ ├── runes
│ │ ├── cenotaph.go
│ │ ├── edict.go
│ │ ├── etching.go
│ │ ├── flaw.go
│ │ ├── message.go
│ │ ├── rune.go
│ │ ├── rune_id.go
│ │ ├── runestone.go
│ │ ├── runestone
│ │ │ ├── flag.go
│ │ │ └── tag.go
│ │ ├── runestone_test.go
│ │ ├── terms.go
│ │ └── varint.go
│ ├── token.go
│ ├── transaction.go
│ ├── transaction_build.go
│ ├── transaction_build_test.go
│ ├── transaction_decode.go
│ ├── transaction_detail.go
│ ├── transaction_psbt.go
│ ├── transaction_sign.go
│ └── transaction_test.go
├── cosmos
│ ├── account.go
│ ├── account_test.go
│ ├── address_util.go
│ ├── address_util_test.go
│ ├── chain.go
│ ├── chain_rest.go
│ ├── chain_test.go
│ ├── constant.go
│ ├── interface_test.go
│ ├── rpc_reachability.go
│ ├── rpc_reachability_test.go
│ └── token.go
├── doge
│ ├── account.go
│ ├── account_test.go
│ ├── account_util.go
│ ├── chain.go
│ ├── chain_rest.go
│ ├── chain_test.go
│ ├── chainnet.go
│ ├── interface_test.go
│ ├── token.go
│ ├── transaction.go
│ └── utxo.go
├── eth
│ ├── abi_coder.go
│ ├── abi_coder_test.go
│ ├── account.go
│ ├── account_test.go
│ ├── address_util.go
│ ├── blockscout.go
│ ├── blockscout_test.go
│ ├── chain.go
│ ├── chainManager.go
│ ├── chain_gas.go
│ ├── chain_gas_test.go
│ ├── chain_nft.go
│ ├── chain_nft_test.go
│ ├── chain_other.go
│ ├── chain_test.go
│ ├── chain_test_base_test.go
│ ├── chain_test_linea_test.go
│ ├── constants.go
│ ├── donut.go
│ ├── donut_test.go
│ ├── erc20token.go
│ ├── erc20token_test.go
│ ├── eth_common.go
│ ├── ethchain.go
│ ├── ethchain_basic.go
│ ├── ethchain_erc20.go
│ ├── ethchain_gas.go
│ ├── ethchain_sign.go
│ ├── ethchain_test.go
│ ├── ethchain_transactionDetail.go
│ ├── ethchain_tx.go
│ ├── ethchain_zksync.go
│ ├── interface_test.go
│ ├── nft.go
│ ├── nft_test.go
│ ├── red_packet.go
│ ├── red_packet_test.go
│ ├── rpc_config_test.go
│ ├── rpc_reachability.go
│ ├── rpc_reachability_test.go
│ ├── token.go
│ ├── token_test.go
│ ├── types.go
│ ├── types_test.go
│ └── utils.go
├── multi-signature-check
│ ├── chainRpc.go
│ └── chainRpc_test.go
├── polka
│ ├── account.go
│ ├── account_test.go
│ ├── address_util.go
│ ├── chain.go
│ ├── chain_test.go
│ ├── client.go
│ ├── error.go
│ ├── estimate_fee.go
│ ├── estimate_fee_test.go
│ ├── interface_test.go
│ ├── keystore.go
│ ├── keystore_test.go
│ ├── metadata.go
│ ├── password.go
│ ├── rpcCall.go
│ ├── rpc_reachability.go
│ ├── rpc_reachability_test.go
│ ├── sign.go
│ ├── token.go
│ ├── transaction.go
│ ├── transaction_test.go
│ ├── utils.go
│ ├── xbtc.go
│ └── xbtc_test.go
├── solana
│ ├── account.go
│ ├── account_test.go
│ ├── address_util.go
│ ├── chain.go
│ ├── chain_test.go
│ ├── error.go
│ ├── interface_test.go
│ ├── rpc_reachability.go
│ ├── rpc_reachability_test.go
│ ├── spltoken.go
│ ├── spltoken_test.go
│ ├── token.go
│ ├── token_test.go
│ └── transaction.go
├── starcoin
│ ├── account.go
│ ├── account_test.go
│ ├── address_util.go
│ ├── chain.go
│ ├── chain_test.go
│ ├── interface_test.go
│ ├── token.go
│ ├── token_test.go
│ ├── transaction.go
│ ├── util.go
│ └── util_test.go
├── starknet
│ ├── account.go
│ ├── account_test.go
│ ├── address_util.go
│ ├── chain.go
│ ├── chain_test.go
│ ├── error.go
│ ├── interface_test.go
│ ├── token.go
│ ├── token_test.go
│ ├── transaction.go
│ ├── transaction_deploy.go
│ └── utils.go
├── substrate
│ ├── err.go
│ └── types
│ │ ├── customscale
│ │ ├── argType.go
│ │ ├── call.go
│ │ ├── event.go
│ │ ├── metadata.go
│ │ └── scale_test.go
│ │ ├── map.go
│ │ └── map_test.go
├── sui
│ ├── account.go
│ ├── account_test.go
│ ├── address_util.go
│ ├── chain.go
│ ├── chain_nft.go
│ ├── chain_nft_test.go
│ ├── chain_stake.go
│ ├── chain_stake_test.go
│ ├── chain_test.go
│ ├── error.go
│ ├── interface_test.go
│ ├── merge_split.go
│ ├── merge_split_test.go
│ ├── move_call.go
│ ├── rest_reachability.go
│ ├── rest_reachability_test.go
│ ├── sui_cat.go
│ ├── sui_cat_test.go
│ ├── sui_cat_types.go
│ ├── token.go
│ ├── token_test.go
│ ├── transaction.go
│ └── transaction_test.go
├── testcase
│ ├── account.go
│ └── amount.go
└── wallet
│ ├── cache.go
│ ├── cache_test.go
│ ├── cache_wallet.go
│ ├── err.go
│ ├── mnemonic.go
│ ├── mnemonic_test.go
│ ├── utils.go
│ ├── wallet.go
│ ├── wallet_test.go
│ ├── watch.go
│ ├── watch_account.go
│ └── watch_test.go
├── crypto
├── blake2b256Hash.go
├── crypto.go
├── ed25519
│ ├── utils.go
│ └── utils_test.go
├── hash.go
├── secp256k1
│ ├── utils.go
│ └── utils_test.go
└── sr25519
│ ├── utils.go
│ └── utils_test.go
├── examples
└── eth_example.go
├── go.mod
├── go.sum
├── graphql
├── graphql.go
└── graphql_test.go
├── pkg
└── httpUtil
│ └── httpReq.go
└── util
├── hexutil
├── hexutil.go
└── hexutil_test.go
├── mathutil
├── mathutil.go
└── mathutil_test.go
└── u8util
├── u8util.go
└── u8util_test.go
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 |
11 | TestAndBuild:
12 | runs-on: ubuntu-latest
13 | env:
14 | WalletSdkTestM1: ${{ secrets.WALLETSDKTESTM1 }}
15 | WalletSdkTestM2: ${{ secrets.WALLETSDKTESTM2 }}
16 | WalletSdkTestMterra: ${{ secrets.WALLETSDKTESTMTERRA }}
17 | InfuraKey: ${{ secrets.INFURAKEY }}
18 | steps:
19 | - uses: actions/checkout@v3
20 |
21 | - name: Set up Go
22 | uses: actions/setup-go@v3
23 | with:
24 | go-version: 1.19
25 |
26 | - name: Test
27 | run: go test -v ./...
28 |
29 | - name: Build
30 | run: go build -v ./...
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 coming-chat
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SuiStake.md:
--------------------------------------------------------------------------------
1 | ## Sui Stake Usage
2 |
3 | ### fetch validator list
4 |
5 | ```golang
6 | var chain = sui.NewChainWithRpcUrl(rpcUrl)
7 | var state = chain.GetValidatorState()
8 | print(state.Epoch/TotalStaked/TotalRewards)
9 | print("validator list = ", state.Validators)
10 |
11 | // show validator information.
12 | var validator = state.Validators[idx]
13 | print(validator.Name/Address/ImageUrl/APY ....)
14 | ```
15 |
16 |
17 |
18 | ### fetch user staked delagation
19 |
20 | ```golang
21 | var ownerAddress = "0x123456...."
22 | var stakes = chain.GetDelegatedStakes(ownerAddress)
23 |
24 | var stake = stakes[idx]
25 | print(stake.Principal/StakeId/ValidatorAddress/Status ...)
26 | if stake.Status == DelegationStatusPending {
27 | // pending
28 | } else if stake.Status == DelegationStatusActived {
29 | print(stake.DelegationId/EarnedAmount ...)
30 | }
31 | ```
32 |
33 |
34 |
35 | ### add stake delegation
36 |
37 | ```go
38 | var validator = state.Validators[idx]
39 |
40 | var amount = "1000000000" // 1 SUI
41 | var txn = chain.AddDelegation(ownerAddress, amount, validator.Address)
42 |
43 | var hash = // sign & send `txn`
44 | ```
45 |
46 |
47 |
48 | ### withdraw stake delegation
49 |
50 | ```golang
51 | var stake = stakes[idx]
52 |
53 | if stake.Status == DelegationStatusPending {
54 | return error("The pending stake delegation cannot be withdraw")
55 | } else if stake.Status == DelegationStatusActived {
56 | var txn = chain.WithdrawDelegation(ownerAddress, stake.delegationId, stake.stakeId)
57 |
58 | var hash = // sign & send `txn`
59 | }
60 | ```
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/core/README.md:
--------------------------------------------------------------------------------
1 |
2 | ### Internal Package dependencies
3 |
4 | ```
5 | base
6 | ↓
7 | btc, eth, polka
8 | ↓
9 | wallet
10 | ```
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/core/aptos/account_test.go:
--------------------------------------------------------------------------------
1 | package aptos
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/coming-chat/wallet-SDK/core/testcase"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func M1Account(t *testing.T) *Account {
12 | acc, err := NewAccountWithMnemonic(testcase.M1)
13 | require.Nil(t, err)
14 | return acc
15 | }
16 |
17 | var (
18 | PriMartian1 = os.Getenv("PriMartian1")
19 | PriMartian2 = os.Getenv("PriMartian2")
20 | PriPetra1 = os.Getenv("PriPetra1")
21 | )
22 |
23 | func TestAccount(t *testing.T) {
24 | mnemonic := testcase.M1
25 | account, err := NewAccountWithMnemonic(mnemonic)
26 | require.Nil(t, err)
27 |
28 | prihex, _ := account.PrivateKeyHex()
29 | acc2, err := AccountWithPrivateKey(prihex)
30 | require.Nil(t, err)
31 |
32 | require.Equal(t, account.PublicKey(), acc2.PublicKey())
33 | require.Equal(t, account.Address(), acc2.Address())
34 | require.Equal(t, account.Address(), "0x11dd2037a613716fdc7cdbd96390b6450bce6754e46b9251cd3c8cd7733683bd")
35 |
36 | t.Log(acc2.PrivateKeyHex())
37 | t.Log(acc2.PublicKeyHex())
38 | t.Log(acc2.Address())
39 | }
40 |
41 | func TestIsValidAddress(t *testing.T) {
42 | tests := []struct {
43 | name string
44 | address string
45 | want bool
46 | }{
47 | {
48 | address: "0x1",
49 | want: true,
50 | },
51 | {
52 | address: "0x1234567890abcdefABCDEF",
53 | want: true,
54 | },
55 | {
56 | address: "0X1234567890123456789012345678901234567890123456789012345678901234",
57 | want: true,
58 | },
59 | {
60 | address: "012345aabcdF",
61 | want: true,
62 | },
63 | {address: "1x23239444"},
64 | {address: "0x1fg"},
65 | {address: "0X12345678901234567890123456789012345678901234567890123456789012345"},
66 | }
67 | for _, tt := range tests {
68 | t.Run(tt.name, func(t *testing.T) {
69 | if got := IsValidAddress(tt.address); got != tt.want {
70 | t.Errorf("IsValidAddress() = %v, want %v", got, tt.want)
71 | }
72 | })
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/core/aptos/address_util.go:
--------------------------------------------------------------------------------
1 | package aptos
2 |
3 | import (
4 | "errors"
5 | "regexp"
6 |
7 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
8 | "golang.org/x/crypto/sha3"
9 | )
10 |
11 | type Util struct {
12 | }
13 |
14 | func NewUtil() (*Util, error) {
15 | return &Util{}, nil
16 | }
17 |
18 | // MARK - Implement the protocol Util
19 |
20 | // @param publicKey can start with 0x or not.
21 | func (u *Util) EncodePublicKeyToAddress(publicKey string) (string, error) {
22 | return EncodePublicKeyToAddress(publicKey)
23 | }
24 |
25 | // Warning: Aptos cannot support decode address to public key
26 | func (u *Util) DecodeAddressToPublicKey(address string) (string, error) {
27 | return DecodeAddressToPublicKey(address)
28 | }
29 |
30 | func (u *Util) IsValidAddress(address string) bool {
31 | return IsValidAddress(address)
32 | }
33 |
34 | // MARK - like Util
35 |
36 | // @param publicKey can start with 0x or not.
37 | func EncodePublicKeyToAddress(publicKey string) (string, error) {
38 | publicBytes, err := types.HexDecodeString(publicKey)
39 | if err != nil {
40 | return "", err
41 | }
42 | publicBytes = append(publicBytes, 0x00)
43 | addressBytes := sha3.Sum256(publicBytes)
44 | return types.HexEncodeToString(addressBytes[:]), nil
45 | }
46 |
47 | func DecodeAddressToPublicKey(address string) (string, error) {
48 | return "", errors.New("Aptos cannot support decode address to public key")
49 | }
50 |
51 | // @param chainnet chain name
52 | func IsValidAddress(address string) bool {
53 | reg := regexp.MustCompile(`^(0x|0X)?[0-9a-fA-F]{1,64}$`)
54 | return reg.MatchString(address)
55 | }
56 |
--------------------------------------------------------------------------------
/core/aptos/interface_test.go:
--------------------------------------------------------------------------------
1 | package aptos
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Token)(nil)
11 | _ base.Transaction = (*Transaction)(nil)
12 |
13 | _ base.SignedTransaction = (*SignedTransaction)(nil)
14 | )
15 |
--------------------------------------------------------------------------------
/core/aptos/nft_test.go:
--------------------------------------------------------------------------------
1 | package aptos
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestFetchNFTs(t *testing.T) {
10 | nftFetcher := NewNFTFetcher("")
11 | // owner := "0x6ed6f83f1891e02c00c58bf8172e3311c982b1c4fbb1be2d85a55562d4085fb1"
12 | owner := "0xf5bb1482c28e3c600edf4cac9a10511b3d9a8e162d5b64d9741e2a8cb086bb50"
13 | creatorAddress := "0x93341710806dbac4fed2fd8251f6c2a49566c75aec00150fadc5db14c07f3d4c"
14 | if true { /*
15 | if false { /**/
16 | nfts, err := nftFetcher.FetchNFTs(owner)
17 | require.Nil(t, err)
18 | for name, group := range nfts {
19 | t.Log("=======================================")
20 | t.Logf("group: %s, count: %d", name, len(group))
21 | for idx, nft := range group {
22 | t.Logf("%4d: %+v", idx, nft)
23 | }
24 | }
25 |
26 | t.Log("\n<====================test filter============>\n")
27 | nftList, err := nftFetcher.FetchNFTsFilterByCreatorAddr(owner, creatorAddress)
28 | require.NoError(t, err)
29 | require.NotEmptyf(t, nftList, "fetch nft empty")
30 | collectName := nftList[0].Collection
31 | for _, v := range nftList {
32 | if v.Collection != collectName {
33 | t.Errorf("filter failed")
34 | }
35 | t.Logf("%+v", v)
36 | }
37 | }
38 |
39 | // if true { /*
40 | if false { /**/
41 | jsonString, err := nftFetcher.FetchNFTsJsonString(owner)
42 | require.Nil(t, err)
43 | t.Log(jsonString)
44 | }
45 | }
46 |
47 | func TestFetchNFTsTestnet(t *testing.T) {
48 | owner := "0xd77e3ea8aa559bc7d5a238314201f0dfd2643a0fdd7ee32d7139d8b2310b4001"
49 |
50 | nftFetcher := NewNFTFetcher(GraphUrlTestnet)
51 | nfts, err := nftFetcher.FetchNFTs(owner)
52 | require.Nil(t, err)
53 | t.Log(nfts)
54 | }
55 |
--------------------------------------------------------------------------------
/core/aptos/rest_reachability.go:
--------------------------------------------------------------------------------
1 | package aptos
2 |
3 | import (
4 | "encoding/json"
5 | "strconv"
6 | "time"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | "github.com/coming-chat/wallet-SDK/pkg/httpUtil"
10 | )
11 |
12 | type RestReachability struct {
13 | }
14 |
15 | func NewRestReachability() *RestReachability {
16 | return &RestReachability{}
17 | }
18 |
19 | // @return latency (ms) of rpc query blockNumber. -1 means the connection failed.
20 | func (r *RestReachability) LatencyOf(rpc string, timeout int64) (l *base.RpcLatency, err error) {
21 | l = &base.RpcLatency{
22 | RpcUrl: rpc,
23 | Latency: -1,
24 | Height: -1,
25 | }
26 |
27 | timeStart := time.Now() // Time Start
28 | body, err := httpUtil.Get(rpc, nil)
29 | if err != nil {
30 | return l, err
31 | }
32 |
33 | model := struct {
34 | BlockHeight string `json:"block_height"`
35 | }{}
36 | err = json.Unmarshal(body, &model)
37 | if err != nil {
38 | return l, err
39 | }
40 | heightInt, err := strconv.ParseInt(model.BlockHeight, 10, 64)
41 | if err != nil {
42 | heightInt = 0
43 | err = nil
44 | }
45 | timeCost := time.Since(timeStart) // Time End
46 |
47 | l.Height = heightInt
48 | l.Latency = timeCost.Milliseconds()
49 | return l, nil
50 | }
51 |
--------------------------------------------------------------------------------
/core/aptos/rest_reachability_test.go:
--------------------------------------------------------------------------------
1 | package aptos
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | )
10 |
11 | type dddd struct {
12 | }
13 |
14 | func (d *dddd) ReachabilityDidReceiveNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
15 | fmt.Printf(".... delegate did receive height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
16 | }
17 |
18 | func (d *dddd) ReachabilityDidFailNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
19 | fmt.Printf(".... delegate did fail height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
20 | // tester.StopConnectivity()
21 | }
22 |
23 | func (d *dddd) ReachabilityDidFinish(tester *base.ReachMonitor, overview string) {
24 | fmt.Printf(".... delegate did finish %v\n", overview)
25 | }
26 |
27 | func TestRpcReachability_Test(t *testing.T) {
28 | reach := NewRestReachability()
29 | monitor := base.NewReachMonitorWithReachability(reach)
30 | monitor.ReachCount = 3
31 | monitor.Delay = 3000
32 | monitor.Timeout = 1500
33 | t.Log(reach)
34 |
35 | rpcUrls := []string{devnetRestUrl}
36 | rpcListString := strings.Join(rpcUrls, ",")
37 | // res := reach.StartConnectivitySync(rpcListString)
38 | // t.Log(res)
39 |
40 | delegate := &dddd{}
41 | monitor.StartConnectivityDelegate(rpcListString, delegate)
42 | }
43 |
--------------------------------------------------------------------------------
/core/aptos/transaction.go:
--------------------------------------------------------------------------------
1 | package aptos
2 |
3 | import (
4 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
5 | txbuilder "github.com/coming-chat/go-aptos/transaction_builder"
6 | "github.com/coming-chat/wallet-SDK/core/base"
7 | )
8 |
9 | type Transaction struct {
10 | RawTxn txbuilder.RawTransaction
11 | }
12 |
13 | func (t *Transaction) SignWithAccount(account base.Account) (signedTx *base.OptionalString, err error) {
14 | txn, err := t.SignedTransactionWithAccount(account)
15 | if err != nil {
16 | return nil, err
17 | }
18 | return txn.HexString()
19 | }
20 |
21 | func (t *Transaction) SignedTransactionWithAccount(account base.Account) (signedTx base.SignedTransaction, err error) {
22 | acc, ok := account.(*Account)
23 | if !ok {
24 | return nil, base.ErrInvalidAccountType
25 | }
26 | signedBytes, err := txbuilder.GenerateBCSTransaction(acc.account, &t.RawTxn)
27 | if err != nil {
28 | return nil, err
29 | }
30 | return &SignedTransaction{
31 | RawTxn: &t.RawTxn,
32 | SignedBytes: signedBytes,
33 | }, nil
34 | }
35 |
36 | type SignedTransaction struct {
37 | RawTxn *txbuilder.RawTransaction
38 |
39 | SignedBytes []byte
40 | }
41 |
42 | func (txn *SignedTransaction) HexString() (res *base.OptionalString, err error) {
43 | return &base.OptionalString{Value: types.HexEncodeToString(txn.SignedBytes)}, nil
44 | }
45 |
46 | func AsSignedTransaction(txn base.SignedTransaction) *SignedTransaction {
47 | if res, ok := txn.(*SignedTransaction); ok {
48 | return res
49 | }
50 | return nil
51 | }
52 |
--------------------------------------------------------------------------------
/core/base/account.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | type AddressUtil interface {
4 | // @param publicKey can start with 0x or not.
5 | EncodePublicKeyToAddress(publicKey string) (string, error)
6 | // @return publicKey that will start with 0x.
7 | DecodeAddressToPublicKey(address string) (string, error)
8 |
9 | IsValidAddress(address string) bool
10 | }
11 |
12 | type Account interface {
13 | // @return privateKey data
14 | PrivateKey() ([]byte, error)
15 | // @return privateKey string that will start with 0x.
16 | PrivateKeyHex() (string, error)
17 |
18 | // @return publicKey data
19 | PublicKey() []byte
20 | // @return publicKey string that will start with 0x.
21 | PublicKeyHex() string
22 |
23 | // @return address string
24 | Address() string
25 |
26 | Sign(message []byte, password string) ([]byte, error)
27 | SignHex(messageHex string, password string) (*OptionalString, error)
28 | }
29 |
--------------------------------------------------------------------------------
/core/base/any_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | type AnyPerson struct {
10 | Name string `json:"name"`
11 | Age int `json:"age"`
12 | }
13 |
14 | func (o *AnyPerson) AsAny() *Any {
15 | return &Any{o}
16 | }
17 |
18 | func AsAnyPerson(a *Any) *AnyPerson {
19 | if res, ok := a.Value.(*AnyPerson); ok {
20 | return res
21 | }
22 | return nil
23 | }
24 |
25 | func TestAny(t *testing.T) {
26 | a := Any{}
27 |
28 | a.SetBool(true)
29 | require.Equal(t, a.GetBool(), true)
30 |
31 | a.SetInt(1234)
32 | require.Equal(t, a.GetInt(), 1234)
33 |
34 | a.SetString("qwer")
35 | require.Equal(t, a.GetString(), "qwer")
36 | }
37 |
38 | func TestAnyArray(t *testing.T) {
39 | arr := AnyArray{}
40 |
41 | a1 := &Any{true}
42 | arr.Append(a1)
43 | require.Equal(t, arr.Count(), 1)
44 |
45 | a2 := &Any{int(123)}
46 | arr.Append(a2)
47 | require.Equal(t, arr.ValueAt(0), a1)
48 | require.Equal(t, arr.ValueAt(1), a2)
49 |
50 | a3 := &Any{"abc"}
51 | arr.SetValue(a3, 1)
52 | require.Equal(t, arr.ValueAt(1), a3)
53 | require.Equal(t, arr.JsonString(), `[true,"abc"]`)
54 | require.Equal(t, arr.Count(), 2)
55 |
56 | a4 := &Any{uint16(456)}
57 | arr.Append(a4)
58 | require.Equal(t, arr.Count(), 3)
59 | arr.Remove(0)
60 | require.Equal(t, arr.Count(), 2)
61 | require.Equal(t, arr.JsonString(), `["abc",456]`)
62 |
63 | ap := &AnyPerson{Name: "GGG", Age: 22}
64 | arr.Append(ap.AsAny())
65 | t.Log(arr.JsonString())
66 | require.NotNil(t, AsAnyPerson(arr.ValueAt(2)))
67 | require.Nil(t, AsAnyPerson(arr.ValueAt(0)))
68 | }
69 |
70 | func TestAnyMap(t *testing.T) {
71 | mp := NewAnyMap()
72 |
73 | require.Equal(t, mp.JsonString(), "{}")
74 |
75 | a1 := &Any{true}
76 | mp.SetValue(a1, "bbb")
77 | require.Equal(t, mp.Keys().Count(), 1)
78 | require.Equal(t, mp.Keys().JsonString(), `["bbb"]`)
79 | require.Equal(t, mp.ValueOf("bbb"), a1)
80 |
81 | a2 := &Any{"abcd"}
82 | mp.SetValue(a2, "alpha")
83 | t.Log(mp.JsonString())
84 | require.Equal(t, mp.Contains("alpha"), true)
85 | require.Equal(t, mp.Contains("beta"), false)
86 |
87 | require.Nil(t, mp.Remove("notkey"))
88 | require.Equal(t, mp.Remove("bbb"), a1)
89 |
90 | ap := &AnyPerson{Name: "GGG"}
91 | mp.SetValue(ap.AsAny(), "person")
92 | t.Log(mp.JsonString())
93 | p := AsAnyPerson(mp.ValueOf("person"))
94 | require.Equal(t, p.Name, "GGG")
95 | }
96 |
97 | func TestBigInt(t *testing.T) {
98 | number := "99999999999999999999999999999999999999999999999999999999999999999999999999999"
99 | a := NewAny()
100 | a.SetBigInt(NewBigIntFromString(number, 10))
101 | require.Equal(t, number, a.GetBigInt().String())
102 | }
103 |
--------------------------------------------------------------------------------
/core/base/balance.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import "strconv"
4 |
5 | type Balance struct {
6 | Total string
7 | Usable string
8 | }
9 |
10 | // Deprecated: use `NewBalance("0")`
11 | func EmptyBalance() *Balance {
12 | return &Balance{
13 | Total: "0",
14 | Usable: "0",
15 | }
16 | }
17 |
18 | func NewBalance(amount string) *Balance {
19 | return &Balance{Total: amount, Usable: amount}
20 | }
21 |
22 | func NewBalanceWithInt(amount int64) *Balance {
23 | a := strconv.FormatInt(amount, 10)
24 | return &Balance{Total: a, Usable: a}
25 | }
26 |
--------------------------------------------------------------------------------
/core/base/chain.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | type Chain interface {
4 | MainToken() Token
5 |
6 | BalanceOfAddress(address string) (*Balance, error)
7 | BalanceOfPublicKey(publicKey string) (*Balance, error)
8 | BalanceOfAccount(account Account) (*Balance, error)
9 |
10 | // Send the raw transaction on-chain
11 | // @return the hex hash string
12 | SendRawTransaction(signedTx string) (string, error)
13 |
14 | // Send the signed transaction on-chain
15 | // @return the hex hash string
16 | SendSignedTransaction(signedTxn SignedTransaction) (*OptionalString, error)
17 |
18 | // Fetch transaction details through transaction hash
19 | FetchTransactionDetail(hash string) (*TransactionDetail, error)
20 |
21 | // Fetch transaction status through transaction hash
22 | FetchTransactionStatus(hash string) TransactionStatus
23 |
24 | // Batch fetch the transaction status, the hash list and the return value,
25 | // which can only be passed as strings separated by ","
26 | // @param hashListString The hash of the transactions to be queried in batches, a string concatenated with ",": "hash1,hash2,hash3"
27 | // @return Batch transaction status, its order is consistent with hashListString: "status1,status2,status3"
28 | BatchFetchTransactionStatus(hashListString string) string
29 |
30 | // Most chains can estimate the fee directly to the transaction object
31 | // **But two chains don't work: `aptos`, `starcoin`**
32 | EstimateTransactionFee(transaction Transaction) (fee *OptionalString, err error)
33 |
34 | // All chains can call this method to estimate the gas fee.
35 | // **Chain `aptos`, `starcoin` must pass in publickey**
36 | EstimateTransactionFeeUsePublicKey(transaction Transaction, pubkey string) (fee *OptionalString, err error)
37 |
38 | // -----------------------------
39 | // polka
40 | // GetSignDataFromChain(t *Transaction, walletAddress string) ([]byte, error)
41 |
42 | // EstimateFeeForTransaction(transaction *Transaction) (s string, err error)
43 |
44 | // FetchScriptHashForMiniX(transferTo, amount string) (*MiniXScriptHash, error)
45 | }
46 |
--------------------------------------------------------------------------------
/core/base/error.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import "errors"
4 |
5 | var (
6 | ErrUnsupportedFunction = errors.New("this method is not supported")
7 |
8 | ErrInvalidPrivateKey = errors.New("invalid private key")
9 | ErrInvalidPublicKey = errors.New("invalid public key")
10 | ErrInvalidAddress = errors.New("invalid address")
11 |
12 | ErrInvalidChainType = errors.New("invalid chain type")
13 | ErrInvalidAccountType = errors.New("invalid account type")
14 | ErrInvalidTransactionType = errors.New("invalid transaction type")
15 | ErrInvalidTransactionData = errors.New("invalid transaction data")
16 | ErrInvalidTransactionHash = errors.New("invalid transaction hash")
17 |
18 | ErrInvalidAccountAddress = errors.New("invalid account address")
19 | ErrInvalidAmount = errors.New("invalid amount")
20 |
21 | ErrMissingTransaction = errors.New("missing transaction information")
22 |
23 | ErrNotCoinTransferTxn = errors.New("not a coin transfer transaction")
24 |
25 | ErrEstimateGasNeedPublicKey = errors.New("the estimated fee should invoking `EstimateTransactionFeeUsePublicKey`")
26 |
27 | ErrInsufficientBalance = errors.New("insufficient account balance")
28 | )
29 |
--------------------------------------------------------------------------------
/core/base/inter/README.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | This module is used to place code that needs to be exported to go code access, but cannot be exported to go mobile sdk access.
4 |
--------------------------------------------------------------------------------
/core/base/inter/any_array.go:
--------------------------------------------------------------------------------
1 | package inter
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | // AnyArray
8 | // ### Usage example for SDK
9 | //
10 | // type StringArray struct { AnyArray[string] }
11 | // func NewStringArray() *StringArray { return &StringArray{[]string{}} }
12 | //
13 | // ### Usage Done
14 | type AnyArray[T any] []T
15 |
16 | func (a AnyArray[T]) MarshalJSON() ([]byte, error) {
17 | var temp []T = a
18 | return json.Marshal(temp)
19 | }
20 |
21 | func (a *AnyArray[T]) UnmarshalJSON(data []byte) error {
22 | var out []T
23 | err := json.Unmarshal(data, &out)
24 | *a = out
25 | return err
26 | }
27 |
28 | func (a AnyArray[T]) JsonString() string {
29 | data, err := json.Marshal(a)
30 | if err != nil {
31 | return "null"
32 | }
33 | return string(data)
34 | }
35 |
36 | func (a AnyArray[T]) Count() int {
37 | return len(a)
38 | }
39 |
40 | func (a AnyArray[T]) ValueAt(index int) T {
41 | return a[index]
42 | }
43 |
44 | func (a *AnyArray[T]) Append(value T) {
45 | *a = append(*a, value)
46 | }
47 |
48 | func (a *AnyArray[T]) Remove(index int) T {
49 | r := (*a)[index]
50 | *a = append((*a)[:index], (*a)[index+1:]...)
51 | return r
52 | }
53 |
54 | func (a *AnyArray[T]) SetValue(value T, index int) {
55 | (*a)[index] = value
56 | }
57 |
58 | // FirstIndexOf
59 | // 该方法的参数无法打包到 sdk, 因此从对象方法中移出为公共方法
60 | // return -1 if not found
61 | func FirstIndexOf[T any](arr []T, matcher func(elem T) bool) int {
62 | for idx, elem := range arr {
63 | if matcher(elem) {
64 | return idx
65 | }
66 | }
67 | return -1
68 | }
69 |
70 | // LastIndexOf
71 | // 该方法的参数无法打包到 sdk, 因此从对象方法中移出为公共方法
72 | // return -1 if not found
73 | func LastIndexOf[T any](arr []T, matcher func(elem T) bool) int {
74 | for i := len(arr) - 1; i >= 0; i-- {
75 | if matcher(arr[i]) {
76 | return i
77 | }
78 | }
79 | return -1
80 | }
81 |
--------------------------------------------------------------------------------
/core/base/inter/any_array_test.go:
--------------------------------------------------------------------------------
1 | package inter
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | // MARK - StringArray
11 |
12 | type stringArray struct {
13 | AnyArray[string]
14 | }
15 |
16 | func NewStringArray() *stringArray {
17 | return &stringArray{[]string{}}
18 | }
19 |
20 | func NewStringArrayWithItem(elem string) *stringArray {
21 | return &stringArray{[]string{elem}}
22 | }
23 |
24 | func (a stringArray) Contains(value string) bool {
25 | idx := FirstIndexOf(a.AnyArray, func(elem string) bool { return elem == value })
26 | return idx != -1
27 | }
28 |
29 | func TestStringArray(t *testing.T) {
30 | arr2 := NewStringArray()
31 | require.Equal(t, arr2.JsonString(), "[]")
32 |
33 | arr := stringArray{}
34 | require.Equal(t, arr.JsonString(), "null")
35 |
36 | arr.Append("AA")
37 | arr.Append("BB")
38 | require.Equal(t, arr.Count(), 2)
39 | require.Equal(t, arr.ValueAt(1), "BB")
40 |
41 | arr.Append("CC")
42 | require.Equal(t, arr.Count(), 3)
43 |
44 | require.Equal(t, arr.Remove(1), "BB")
45 | require.Equal(t, arr.Count(), 2)
46 |
47 | arr.Append("DD")
48 | require.Equal(t, arr.JsonString(), `["AA","CC","DD"]`)
49 |
50 | arr.Append("CC")
51 | idx1 := FirstIndexOf(arr.AnyArray, func(elem string) bool { return elem == "CC" })
52 | require.Equal(t, idx1, 1)
53 | idx2 := LastIndexOf(arr.AnyArray, func(elem string) bool { return elem == "CC" })
54 | require.Equal(t, idx2, 3)
55 | idx3 := FirstIndexOf(arr.AnyArray, func(elem string) bool { return elem == "EE" })
56 | require.Equal(t, idx3, -1)
57 | require.False(t, arr.Contains("EE"))
58 | }
59 |
60 | func TestAnyArray_Unmarshal(t *testing.T) {
61 | jsonStr := `
62 | ["aa", "bb","cc", "", "d d"] `
63 |
64 | var arr stringArray
65 | err := json.Unmarshal([]byte(jsonStr), &arr)
66 | require.Nil(t, err)
67 |
68 | require.Equal(t, arr.JsonString(), `["aa","bb","cc","","d d"]`)
69 | require.True(t, arr.Contains("aa"))
70 | require.True(t, arr.Contains("d d"))
71 | require.False(t, arr.Contains("xx"))
72 | }
73 |
74 | type intArray struct {
75 | AnyArray[int]
76 | }
77 |
78 | func TestIntArray(t *testing.T) {
79 | arr := intArray{}
80 | for i := 0; i < 10; i++ {
81 | arr.Append(i)
82 | }
83 | require.Equal(t, arr.Count(), 10)
84 | require.Equal(t, arr.JsonString(), `[0,1,2,3,4,5,6,7,8,9]`)
85 |
86 | idx := LastIndexOf(arr.AnyArray, func(elem int) bool { return elem == 3 })
87 | require.Equal(t, idx, 3)
88 | }
89 |
--------------------------------------------------------------------------------
/core/base/inter/any_map.go:
--------------------------------------------------------------------------------
1 | package inter
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | // AnyMap
8 | // ### Usage example for SDK
9 | //
10 | // type StringMap struct { AnyMap[string, string] }
11 | // func NewStringMap() *StringMap { return &StringMap{map[string]string{}} }
12 | //
13 | // ### Usage Done
14 | type AnyMap[K comparable, V any] map[K]V
15 |
16 | func (a AnyMap[K, V]) MarshalJSON() ([]byte, error) {
17 | var temp map[K]V = a
18 | return json.Marshal(temp)
19 | }
20 |
21 | func (a *AnyMap[K, V]) UnmarshalJSON(data []byte) error {
22 | var out map[K]V
23 | err := json.Unmarshal(data, &out)
24 | *a = out
25 | return err
26 | }
27 |
28 | func (a AnyMap[K, V]) JsonString() string {
29 | data, err := json.Marshal(a)
30 | if err != nil {
31 | return "{}"
32 | }
33 | return string(data)
34 | }
35 |
36 | func (a AnyMap[K, V]) Count() int {
37 | return len(a)
38 | }
39 |
40 | func (a AnyMap[K, V]) ValueOf(key K) V {
41 | return a[key]
42 | }
43 |
44 | func (a *AnyMap[K, V]) SetValue(value V, key K) {
45 | (*a)[key] = value
46 | }
47 |
48 | func (a *AnyMap[K, V]) Remove(key K) V {
49 | if v, ok := (*a)[key]; ok {
50 | delete((*a), key)
51 | return v
52 | }
53 | return (*a)[key]
54 | }
55 |
56 | // Deprecated: Use Contains(key) instead.
57 | func (a AnyMap[K, V]) HasKey(key K) bool {
58 | _, ok := a[key]
59 | return ok
60 | }
61 |
62 | func (a AnyMap[K, V]) Contains(key K) bool {
63 | _, ok := a[key]
64 | return ok
65 | }
66 |
67 | // Keys
68 | // 该方法的返回值无法打包到 sdk, 因此从对象方法中移出为公共方法
69 | func KeysOf[K comparable, V any](m map[K]V) []K {
70 | keys := make([]K, len(m))
71 | idx := 0
72 | for k := range m {
73 | keys[idx] = k
74 | idx++
75 | }
76 | return keys
77 | }
78 |
--------------------------------------------------------------------------------
/core/base/inter/any_map_test.go:
--------------------------------------------------------------------------------
1 | package inter
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | type stringMap struct {
11 | AnyMap[string, string]
12 | }
13 |
14 | func NewStringMap() *stringMap {
15 | return &stringMap{map[string]string{}}
16 | }
17 |
18 | func TestStringMap(t *testing.T) {
19 | m := NewStringMap()
20 | t.Log(m.JsonString())
21 |
22 | m.SetValue("saa", "name")
23 | m.SetValue("12", "age")
24 | require.Equal(t, m.Count(), 2)
25 | require.Equal(t, m.ValueOf("name"), "saa")
26 | require.Equal(t, m.ValueOf("age"), "12")
27 | require.Equal(t, m.ValueOf("invalidkey"), "")
28 | require.True(t, m.Contains("age"))
29 |
30 | res := m.Remove("age")
31 | require.Equal(t, res, "12")
32 | require.False(t, m.Contains("age"))
33 |
34 | m.SetValue("180", "height")
35 | t.Log(m.JsonString())
36 | t.Log(KeysOf(m.AnyMap))
37 | require.Equal(t, KeysOf(m.AnyMap), []string{"name", "height"})
38 | }
39 |
40 | func TestStringMap_Unmarshal(t *testing.T) {
41 | str := `{
42 | "height": "1000",
43 | "width": "200"
44 | }`
45 |
46 | var m stringMap
47 | err := json.Unmarshal([]byte(str), &m)
48 | require.Nil(t, err)
49 |
50 | t.Log(m.JsonString())
51 | }
52 |
--------------------------------------------------------------------------------
/core/base/inter/pageable.go:
--------------------------------------------------------------------------------
1 | package inter
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | // `SdkPageable` implemented wallet-SDK/base's interface `Jsonable`
8 | // If you new class `Xxx` extends it, you should implement `NewXxxWithJsonString` by your self.
9 | type SdkPageable[T any] struct {
10 | TotalCount_ int `json:"totalCount"`
11 | CurrentCount_ int `json:"currentCount"`
12 | CurrentCursor_ string `json:"currentCursor"`
13 | HasNextPage_ bool `json:"hasNextPage"`
14 |
15 | Items []T `json:"items"`
16 | }
17 |
18 | func (p *SdkPageable[T]) TotalCount() int {
19 | return p.TotalCount_
20 | }
21 |
22 | func (p *SdkPageable[T]) CurrentCount() int {
23 | p.CurrentCount_ = len(p.Items)
24 | return p.CurrentCount_
25 | }
26 |
27 | func (p *SdkPageable[T]) CurrentCursor() string {
28 | return p.CurrentCursor_
29 | }
30 |
31 | func (p *SdkPageable[T]) HasNextPage() bool {
32 | return p.HasNextPage_
33 | }
34 |
35 | func (p *SdkPageable[T]) JsonString() string {
36 | data, err := json.Marshal(p)
37 | if err != nil {
38 | return "null"
39 | }
40 | return string(data)
41 | }
42 |
43 | // It's will crash when index out of range
44 | func (p *SdkPageable[T]) ItemAt(index int) T {
45 | return p.Items[index]
46 | }
47 |
--------------------------------------------------------------------------------
/core/base/inter/pageable_test.go:
--------------------------------------------------------------------------------
1 | package inter
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | type Person struct {
8 | Name string
9 | Age int
10 | }
11 |
12 | type PersonPage struct {
13 | *SdkPageable[*Person]
14 | }
15 |
16 | func (a *PersonPage) SecondObject() *Person {
17 | return a.Items[1]
18 | }
19 |
20 | func TestSdkPageable(t *testing.T) {
21 | p := PersonPage{
22 | &SdkPageable[*Person]{
23 | Items: []*Person{
24 | {
25 | Name: "aa",
26 | Age: 123,
27 | },
28 | {
29 | Name: "bb",
30 | Age: 999,
31 | },
32 | },
33 | },
34 | }
35 | t.Log(p.ItemAt(0))
36 | t.Log(p.SecondObject())
37 |
38 | t.Log(p.TotalCount())
39 | t.Log(p.CurrentCount())
40 | }
41 |
42 | func TestFlowInterface(t *testing.T) {
43 | vv := PersonPage{
44 | &SdkPageable[*Person]{},
45 | }
46 |
47 | t.Log(vv.JsonString())
48 | }
49 |
--------------------------------------------------------------------------------
/core/base/inter/util.go:
--------------------------------------------------------------------------------
1 | package inter
2 |
3 | import (
4 | "strings"
5 | "unicode"
6 | )
7 |
8 | // IsHexString
9 | func IsHexString(str string) (valid bool, length int) {
10 | if strings.HasPrefix(str, "0x") || strings.HasPrefix(str, "0X") {
11 | str = str[2:] // remove 0x prefix
12 | }
13 | for _, ch := range []byte(str) {
14 | valid := (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
15 | if !valid {
16 | return false, 0
17 | }
18 | }
19 | return true, len(str)
20 | }
21 |
22 | func IsASCII(str string) bool {
23 | for _, c := range str {
24 | if c > unicode.MaxASCII {
25 | return false
26 | }
27 | }
28 | return true
29 | }
30 |
--------------------------------------------------------------------------------
/core/base/jsonable.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import "encoding/json"
4 |
5 | type Jsonable interface {
6 | JsonString() (*OptionalString, error)
7 |
8 | // You need to implement the following methods if your class name is Xxx
9 | // func NewXxxWithJsonString(str string) (*Xxx, error)
10 |
11 | // ====== template
12 | // func (j *Xxx) JsonString() (*base.OptionalString, error) {
13 | // return base.JsonString(j)
14 | // }
15 | // func NewXxxWithJsonString(str string) (*Xxx, error) {
16 | // var o Xxx
17 | // err := base.FromJsonString(str, &o)
18 | // return &o, err
19 | // }
20 | }
21 |
22 | func JsonString(o interface{}) (*OptionalString, error) {
23 | data, err := json.Marshal(o)
24 | if err != nil {
25 | return nil, err
26 | }
27 | return &OptionalString{Value: string(data)}, nil
28 | }
29 |
30 | func FromJsonString(jsonStr string, out interface{}) error {
31 | bytes := []byte(jsonStr)
32 | return json.Unmarshal(bytes, out)
33 | }
34 |
--------------------------------------------------------------------------------
/core/base/jsonable_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | type JsonObj struct {
10 | Name string `json:"name"`
11 | Age int `json:"age"`
12 | }
13 |
14 | func (o *JsonObj) JsonString() (*OptionalString, error) {
15 | return JsonString(o)
16 | }
17 |
18 | func NewJsonObjWithJsonString(str string) (*JsonObj, error) {
19 | var o JsonObj
20 | err := FromJsonString(str, &o)
21 | return &o, err
22 | }
23 |
24 | func NewJsonObjArrayWithJsonString(str string) (*AnyArray, error) {
25 | var o []*JsonObj
26 | err := FromJsonString(str, &o)
27 | arr := make([]*Any, len(o))
28 | for i, v := range o {
29 | arr[i] = &Any{Value: v}
30 | }
31 | return &AnyArray{AnyArray: arr}, err
32 | }
33 |
34 | func (o *JsonObj) AsAny() *Any {
35 | return &Any{o}
36 | }
37 |
38 | func TestJsonObject(t *testing.T) {
39 | o1 := JsonObj{Name: "Zhi", Age: 20}
40 | jsonStr, err := o1.JsonString()
41 | require.Nil(t, err)
42 | t.Log(jsonStr.Value)
43 |
44 | o2, err := NewJsonObjWithJsonString(jsonStr.Value)
45 | require.Nil(t, err)
46 | t.Log(o2)
47 | }
48 |
49 | func TestJsonForAny(t *testing.T) {
50 | o1 := JsonObj{Name: "Zhi", Age: 20}
51 | a1 := o1.AsAny()
52 |
53 | jsonStrO1, err := o1.JsonString()
54 | require.Nil(t, err)
55 | jsonStrA1, err := a1.JsonString()
56 | require.Nil(t, err)
57 |
58 | require.Equal(t, jsonStrO1, jsonStrA1)
59 | t.Log(jsonStrO1.Value)
60 |
61 | // ======================
62 | o2 := JsonObj{Name: "A22", Age: 17}
63 |
64 | arr1 := AnyArray{[]*Any{a1, o2.AsAny()}} // a1 is Any, o2 is JsonObj
65 | jsonStrArr1 := arr1.JsonString()
66 | t.Log(jsonStrArr1)
67 | arr2 := []JsonObj{o1, o2}
68 | jsonStrArr2, err := JsonString(arr2)
69 | require.Nil(t, err)
70 |
71 | require.Equal(t, jsonStrArr1, jsonStrArr2.Value)
72 |
73 | // ======================= new array
74 | objArray, err := NewJsonObjArrayWithJsonString(jsonStrArr1)
75 | require.Nil(t, err)
76 | t.Log(objArray.AnyArray)
77 | }
78 |
79 | func TestJsonForNestAny(t *testing.T) {
80 | o1 := JsonObj{Name: "Zhi", Age: 20}
81 |
82 | a1 := Any{Value: o1}
83 | a2 := Any{Value: a1}
84 | a3 := Any{Value: a2}
85 |
86 | jsonStr, err := a3.JsonString()
87 | require.Nil(t, err)
88 | t.Log(jsonStr.Value)
89 |
90 | // ===============
91 | arr1 := AnyArray{[]*Any{{a1}}}
92 | arr2 := AnyArray{[]*Any{{arr1}}}
93 | arr3 := AnyArray{[]*Any{{arr2}}}
94 | jsonStrArr := arr3.JsonString()
95 | t.Log(jsonStrArr)
96 | }
97 |
--------------------------------------------------------------------------------
/core/base/token.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | type TokenInfo struct {
4 | Name string
5 | Symbol string
6 | Decimal int16
7 | }
8 |
9 | type Token interface {
10 | Chain() Chain
11 |
12 | TokenInfo() (*TokenInfo, error)
13 |
14 | BalanceOfAddress(address string) (*Balance, error)
15 | BalanceOfPublicKey(publicKey string) (*Balance, error)
16 | BalanceOfAccount(account Account) (*Balance, error)
17 |
18 | BuildTransfer(sender, receiver, amount string) (txn Transaction, err error)
19 | // Before invoking this method, it is best to check `CanTransferAll()`
20 | CanTransferAll() bool
21 | BuildTransferAll(sender, receiver string) (txn Transaction, err error)
22 | }
23 |
--------------------------------------------------------------------------------
/core/base/transaction.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "encoding/json"
5 | "strings"
6 | )
7 |
8 | type Transaction interface {
9 | SignWithAccount(account Account) (signedTxn *OptionalString, err error)
10 | SignedTransactionWithAccount(account Account) (signedTxn SignedTransaction, err error)
11 | }
12 |
13 | // SignedTransaction can use chain to broadcast transactions, `chain.SendSignedTransaction(signedTxn)`
14 | type SignedTransaction interface {
15 | // Hex string can use chain to broadcast transactions, `chain.SendRawTransaction(txString)`
16 | HexString() (res *OptionalString, err error)
17 | }
18 |
19 | type TransactionStatus = SDKEnumInt
20 |
21 | const (
22 | TransactionStatusNone TransactionStatus = 0
23 | TransactionStatusPending TransactionStatus = 1
24 | TransactionStatusSuccess TransactionStatus = 2
25 | TransactionStatusFailure TransactionStatus = 3
26 | )
27 |
28 | // Transaction details that can be fetched from the chain
29 | type TransactionDetail struct {
30 | // transaction completion timestamp (s), 0 if Status is in Pending
31 | FinishTimestamp int64
32 | Status TransactionStatus
33 |
34 | // hash string on chain
35 | HashString string
36 |
37 | // transaction amount
38 | Amount string
39 |
40 | EstimateFees string
41 |
42 | // sender's address
43 | FromAddress string
44 | // receiver's address
45 | ToAddress string
46 |
47 | // failure message
48 | FailureMessage string
49 |
50 | // If this transaction is a CID transfer, its value will be the CID, otherwise it is empty
51 | CIDNumber string
52 | // If this transaction is a NFT transfer, its value will be the Token name, otherwise it is empty
53 | TokenName string
54 | }
55 |
56 | // Check the `CIDNumber` is not empty.
57 | func (d *TransactionDetail) IsCIDTransfer() bool {
58 | return strings.TrimSpace(d.CIDNumber) != ""
59 | }
60 |
61 | // Check the `TokenName` is not empty.
62 | func (d *TransactionDetail) IsNFTTransfer() bool {
63 | return strings.TrimSpace(d.TokenName) != ""
64 | }
65 |
66 | func (d *TransactionDetail) JsonString() string {
67 | b, err := json.Marshal(d)
68 | if err != nil {
69 | return ""
70 | }
71 | return string(b)
72 | }
73 |
--------------------------------------------------------------------------------
/core/base/types.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/base/inter"
7 | )
8 |
9 | type SDKEnumInt = int
10 | type SDKEnumString = string
11 |
12 | // Optional string for easy of writing iOS code
13 | type OptionalString struct {
14 | Value string
15 | }
16 |
17 | func NewOptionalString(s string) *OptionalString {
18 | return &OptionalString{Value: s}
19 | }
20 |
21 | // Optional bool for easy of writing iOS code
22 | type OptionalBool struct {
23 | Value bool
24 | }
25 |
26 | func NewOptionalBool(b bool) *OptionalBool {
27 | return &OptionalBool{Value: b}
28 | }
29 |
30 | type OptionalInt struct {
31 | Value int
32 | }
33 |
34 | func NewOptionalInt(i int) *OptionalInt {
35 | return &OptionalInt{Value: i}
36 | }
37 |
38 | type safeMap struct {
39 | sync.RWMutex
40 | Map map[interface{}]interface{}
41 | }
42 |
43 | func newSafeMap() *safeMap {
44 | return &safeMap{Map: make(map[interface{}]interface{})}
45 | }
46 |
47 | func (l *safeMap) readMap(key interface{}) (interface{}, bool) {
48 | l.RLock()
49 | value, ok := l.Map[key]
50 | l.RUnlock()
51 | return value, ok
52 | }
53 |
54 | func (l *safeMap) writeMap(key interface{}, value interface{}) {
55 | l.Lock()
56 | l.Map[key] = value
57 | l.Unlock()
58 | }
59 |
60 | // MARK - StringArray
61 |
62 | type StringArray struct {
63 | inter.AnyArray[string]
64 | }
65 |
66 | func NewStringArray() *StringArray {
67 | return &StringArray{[]string{}}
68 | }
69 |
70 | func NewStringArrayWithItem(elem string) *StringArray {
71 | return &StringArray{[]string{elem}}
72 | }
73 |
74 | func (a StringArray) Contains(value string) bool {
75 | idx := inter.FirstIndexOf(a.AnyArray, func(elem string) bool { return elem == value })
76 | return idx != -1
77 | }
78 |
79 | // MARK - StringMap
80 |
81 | type StringMap struct {
82 | inter.AnyMap[string, string]
83 | }
84 |
85 | func NewStringMap() *StringMap {
86 | return &StringMap{map[string]string{}}
87 | }
88 |
89 | func (m *StringMap) Keys() *StringArray {
90 | keys := inter.KeysOf(m.AnyMap)
91 | return &StringArray{keys}
92 | }
93 |
--------------------------------------------------------------------------------
/core/base/types_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestStringArray(t *testing.T) {
10 | arr := StringArray{}
11 |
12 | arr.Append("AAA")
13 | require.Equal(t, arr.Count(), 1)
14 |
15 | arr.Append("bbb")
16 | require.Equal(t, arr.ValueAt(0), "AAA")
17 | require.Equal(t, arr.ValueAt(1), "bbb")
18 |
19 | arr.SetValue("ccc", 1)
20 | require.Equal(t, arr.ValueAt(1), "ccc")
21 | require.Equal(t, arr.JsonString(), `["AAA","ccc"]`)
22 | require.Equal(t, arr.Count(), 2)
23 |
24 | arr.Append("ddd")
25 | arr.Remove(0)
26 | require.Equal(t, arr.Count(), 2)
27 | require.Equal(t, arr.JsonString(), `["ccc","ddd"]`)
28 | }
29 |
30 | func TestExtractNFTImageUrl(t *testing.T) {
31 | // url := "https://cdn-2.galxe.com/galaxy/images/alienswap/1667153514800858058.gif"
32 | url := "https://ipfs.rss3.page/ipfs/QmbfuMdX9qiMmKVcDiWmQHYg6sk5yfmoAh7fYbQcvWd9gd/2951.png"
33 |
34 | r, err := ExtractNFTImageUrl(url)
35 | require.Nil(t, err)
36 | t.Log(r)
37 | }
38 |
--------------------------------------------------------------------------------
/core/base/util_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "strconv"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestCatchPanic(t *testing.T) {
11 | i, err := dangerousCode()
12 | t.Log(i, err)
13 |
14 | if err != nil {
15 | t.Log("err = ", err)
16 | } else {
17 | t.Log("suc = ", i)
18 | }
19 | }
20 |
21 | func dangerousCode() (i int, e error) {
22 | defer CatchPanicAndMapToBasicError(&e)
23 |
24 | // runtime error: invalid memory address or nil pointer dereference
25 | var a Account
26 | println("......", a.Address())
27 |
28 | // panic(3432434)
29 |
30 | return 13, e
31 | }
32 |
33 | func TestMapConcurrent(t *testing.T) {
34 | nums := []interface{}{1, 2, 3, 4, 5, 6}
35 | // nums := []interface{}{"1", "2", "3", "4"}
36 | res, _ := MapListConcurrent(nums, 10, func(i interface{}) (interface{}, error) {
37 | return strconv.Itoa(i.(int) * 100), nil
38 | })
39 | t.Log(res)
40 | }
41 |
42 | func TestNFTImage(t *testing.T) {
43 | // url := "https://www.aptosnames.com/api/mainnet/v1/metadata/rolls-royce.apt" // json
44 | // url := "https://coming.chat/api/v1/metadata/2333.aptos" // image
45 | // url := "https://nft-market.coming.chat/api/v1/ipfsGateway" // no HEAD
46 | url := "https://api.github.com/users/hadley/orgs" // json but no `image` field.
47 |
48 | res, err := ExtractNFTImageUrl(url)
49 | require.Nil(t, err)
50 | t.Log(res)
51 | }
52 |
--------------------------------------------------------------------------------
/core/btc/account_derivation.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import (
4 | "github.com/btcsuite/btcd/btcec/v2"
5 | "github.com/btcsuite/btcd/btcutil/hdkeychain"
6 | "github.com/btcsuite/btcd/chaincfg"
7 | "github.com/ethereum/go-ethereum/accounts"
8 | "github.com/tyler-smith/go-bip39"
9 | )
10 |
11 | func Derivation(mnemonic string, path string) (*btcec.PrivateKey, error) {
12 | seed, err := bip39.NewSeedWithErrorChecking(mnemonic, "")
13 | if err != nil {
14 | return nil, err
15 | }
16 | masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
17 | if err != nil {
18 | return nil, err
19 | }
20 | dPath, err := accounts.ParseDerivationPath(path)
21 | if err != nil {
22 | return nil, err
23 | }
24 | key := masterKey
25 | for _, n := range dPath {
26 | key, err = key.Derive(n)
27 | if err != nil {
28 | return nil, err
29 | }
30 | }
31 | return key.ECPrivKey()
32 | }
33 |
34 | func ComingPrivateKey(mnemonic string) (*btcec.PrivateKey, error) {
35 | seed, err := bip39.NewSeedWithErrorChecking(mnemonic, "")
36 | if err != nil {
37 | return nil, err
38 | }
39 | pri, _ := btcec.PrivKeyFromBytes(seed)
40 | return pri, nil
41 | }
42 |
--------------------------------------------------------------------------------
/core/btc/balance_test.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/base"
7 | )
8 |
9 | func TestBatchQueryBalance(t *testing.T) {
10 | addresses := base.StringArray{
11 | AnyArray: []string{
12 | "123", "456", // invalid address
13 | "15MdAHnkxt9TMC2Rj595hsg8Hnv693pPBB", "bc1qa5wkzvf775vxddzaaru2hacd3mj0ehsh3g4anx",
14 | },
15 | }
16 |
17 | balances, err := BatchQueryBalance(&addresses, ChainMainnet)
18 | t.Log(balances, err)
19 | }
20 |
--------------------------------------------------------------------------------
/core/btc/brc20_inscription_test.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestFetchBrc20Inscription(t *testing.T) {
10 | owner := "bc1p65yz8hsm3antzdtjzlxd7e4z60ht5reuepk970mu8pgf2acthq5qtk8283"
11 |
12 | chain, err := NewChainWithChainnet(ChainMainnet)
13 | require.Nil(t, err)
14 | page, err := chain.FetchBrc20Inscription(owner, "0", 20)
15 | require.Nil(t, err)
16 | require.True(t, page.TotalCount() >= 1)
17 | t.Log(page.Items)
18 | t.Log(page.ItemAt(0))
19 |
20 | jsonstring := page.JsonString()
21 | rePage, err := NewBrc20InscriptionPageWithJsonString(jsonstring)
22 | require.Nil(t, err)
23 | require.Equal(t, page.TotalCount_, rePage.TotalCount_)
24 | require.Equal(t, page.Items[0], rePage.Items[0])
25 | }
26 |
27 | func TestFetchBrc20TransferableInscription(t *testing.T) {
28 | owner := "tb1p2hsjm57fsxrqcq5p42get87ttrw069kqa2ar444ma4ussquuaklqfsrknz"
29 |
30 | chain, err := NewChainWithChainnet(ChainTestnet)
31 | require.Nil(t, err)
32 | page, err := chain.FetchBrc20TransferableInscription(owner, "txtx")
33 | require.Nil(t, err)
34 | t.Log(page.Items)
35 | }
36 |
--------------------------------------------------------------------------------
/core/btc/brc20_mint_test.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/testcase"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestBrc20MintTransaction_SignAndSend(t *testing.T) {
11 | account, err := NewAccountWithMnemonic(testcase.M1, ChainTestnet, AddressTypeTaproot)
12 | require.Nil(t, err)
13 | tick := "ovvo"
14 | amount := "1000"
15 |
16 | user := account.Address()
17 | require.Nil(t, err)
18 | t.Log("address: ", user)
19 |
20 | chain, err := NewChainWithChainnet(ChainTestnet)
21 | require.Nil(t, err)
22 | balance, err := chain.BalanceOfAddress(user)
23 | require.Nil(t, err)
24 | println("balance: ", balance.Total)
25 |
26 | txn, err := chain.BuildBrc20MintTransaction(user, user, "mint", tick, amount, 1)
27 | require.Nil(t, err)
28 |
29 | signedTxn, err := txn.SignedTransactionWithAccount(account)
30 | require.Nil(t, err)
31 |
32 | if false {
33 | hash, err := chain.SendSignedTransaction(signedTxn)
34 | require.Nil(t, err)
35 | println("brc20 mint success: ", hash.Value)
36 | }
37 | }
38 |
39 | func TestFetchBrc20InscriptionList(t *testing.T) {
40 | account, err := NewAccountWithMnemonic(testcase.M1, ChainTestnet, AddressTypeTaproot)
41 | require.Nil(t, err)
42 | chain, err := NewChainWithChainnet(ChainTestnet)
43 | require.Nil(t, err)
44 | addr := account.Address()
45 |
46 | list, err := chain.FetchBrc20Inscription(addr, "", 10)
47 | require.Nil(t, err)
48 | t.Log(list.Items)
49 | }
50 |
51 | func TestNewBrc20MintTransactionWithJsonString(t *testing.T) {
52 | jsonStr := `{
53 | "commit": "abc",
54 | "reveal": ["11", "22"]
55 | }`
56 | txn, err := NewBrc20MintTransactionWithJsonString(jsonStr)
57 | require.Nil(t, err)
58 | require.Equal(t, txn.Commit, "abc")
59 | require.Equal(t, txn.Reveal.ValueAt(1), "22")
60 | }
61 |
62 | func Test_Marshal_Brc20MintTransaction(t *testing.T) {
63 | jsonStr := `
64 | {
65 | "inscription": [
66 | "9ea4ca939fb364944a1a51ca42aa5a8e99c06b2d0e7dce45bf317c601ebd4d21i0"
67 | ],
68 | "commit": "70736274ff010",
69 | "commit_custom": [
70 | "0118b014000000",
71 | "ed4fa9e028aa59e5384b219562034b8254347d591c3f667855616e8f33ab561e",
72 | "2"
73 | ],
74 | "reveal": [
75 | "0100000000010"
76 | ],
77 | "service_fee": 0,
78 | "satpoint_fee": 546,
79 | "network_fee": 70716,
80 | "commit_vsize": 154,
81 | "commit_fee": 37422
82 | }
83 | `
84 |
85 | txn, err := NewBrc20MintTransactionWithJsonString(jsonStr)
86 | require.Nil(t, err)
87 | require.Equal(t, txn.CommitCustom.BaseTx, "0118b014000000")
88 | require.Equal(t, txn.CommitCustom.Utxos.Count(), 1)
89 | require.Equal(t, txn.CommitCustom.Utxos.ValueAt(0).Index, int64(2))
90 | }
91 |
--------------------------------------------------------------------------------
/core/btc/brc20_token_test.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/coming-chat/wallet-SDK/core/base"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestBrc20Token_TokenInfo(t *testing.T) {
12 | token := NewBrc20Token("bruh", ChainMainnet)
13 | info, err := token.TokenInfo()
14 | require.Nil(t, err)
15 | require.Equal(t, info, &base.TokenInfo{
16 | Name: "BRUH",
17 | Symbol: "BRUH",
18 | Decimal: 18,
19 | })
20 | }
21 |
22 | func TestBrc20Token_FullTokenInfo(t *testing.T) {
23 | token := NewBrc20Token("sats", ChainMainnet)
24 | info, err := token.FullTokenInfo()
25 | require.Nil(t, err)
26 | require.Equal(t, info.Decimal, int16(18))
27 | // require.Equal(t, info.Max, "21000000")
28 | t.Log(info.JsonString())
29 |
30 | timeStart := time.Now().UnixMilli()
31 | info222, err := token.FullTokenInfo()
32 | timeSpent := time.Now().UnixMilli() - timeStart
33 | require.Nil(t, err)
34 | require.True(t, timeSpent < 10) // The second use of the cache should be very fast
35 | require.Equal(t, info, info222)
36 | }
37 |
38 | func TestBrc20TokenBalances(t *testing.T) {
39 | // owner := "bc1qdgflzu306s75lgskkgssmz3vscpvuawvafv3xjshyc6t73x3zzvquvtafp"
40 | // chain, err := NewChainWithChainnet(ChainMainnet)
41 |
42 | owner := "tb1p6udzumpasl8ydf2mwfxlvmjzhxh9pg2vz097sle6hq98ahra0tvs3pvvgu"
43 | chain, err := NewChainWithChainnet(ChainTestnet)
44 | require.Nil(t, err)
45 | balancePage, err := chain.FetchBrc20TokenBalance(owner, "0", 10)
46 | require.Nil(t, err)
47 | t.Log(balancePage.JsonString())
48 | }
49 |
--------------------------------------------------------------------------------
/core/btc/brc20_util.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "regexp"
8 | "strings"
9 |
10 | "github.com/coming-chat/wallet-SDK/pkg/httpUtil"
11 | )
12 |
13 | // the v5 resp.Body should like `{code: *, msg: *, data: *}`
14 | func decodeUnisatResponseV5(resp httpUtil.Res, out interface{}) error {
15 | err := responseJsonCheck(resp)
16 | if err != nil {
17 | return err
18 | }
19 | var data struct {
20 | Code int `json:"code"`
21 | Msg string `json:"msg"`
22 | Data json.RawMessage `json:"data"`
23 | }
24 | err = json.Unmarshal(resp.Body, &data)
25 | if err != nil {
26 | return err
27 | }
28 | if data.Code != 0 || data.Msg != "ok" {
29 | return fmt.Errorf("code: %v, message: %v", data.Code, data.Msg)
30 | }
31 | return json.Unmarshal(data.Data, out)
32 | }
33 |
34 | // the v4 resp.Body should like `{status: *, message: *, result: *}`
35 | func decodeUnisatResponseV4(resp httpUtil.Res, out interface{}) error {
36 | err := responseJsonCheck(resp)
37 | if err != nil {
38 | return err
39 | }
40 | var data struct {
41 | Code string `json:"status"`
42 | Msg string `json:"message"`
43 | Data json.RawMessage `json:"result"`
44 | }
45 | err = json.Unmarshal(resp.Body, &data)
46 | if err != nil {
47 | return err
48 | }
49 | if data.Code != "1" || data.Msg != "OK" {
50 | return fmt.Errorf("code: %v, message: %v", data.Code, data.Msg)
51 | }
52 | return json.Unmarshal(data.Data, out)
53 | }
54 |
55 | func responseJsonCheck(resp httpUtil.Res) error {
56 | if resp.Code != http.StatusOK {
57 | return fmt.Errorf("code: %v, body: %v", resp.Code, string(resp.Body))
58 | }
59 | contentType := resp.Header["Content-Type"]
60 | if len(contentType) <= 0 {
61 | return fmt.Errorf("response content error")
62 | }
63 | if contentType[0] == "text/html" {
64 | titleRegexp := regexp.MustCompile(`\
(.*)\`)
65 | matches := titleRegexp.FindStringSubmatch(string(resp.Body))
66 | if len(matches) >= 2 {
67 | return fmt.Errorf("error: %v", matches[1])
68 | }
69 | return fmt.Errorf("response content error")
70 | }
71 | if !strings.Contains(contentType[0], "json") {
72 | return fmt.Errorf("response content error")
73 | }
74 | return nil
75 | }
76 |
77 | func unisatRequestHeader(address string) map[string]string {
78 | return map[string]string{
79 | "x-client": "UniSat Wallet",
80 | "x-version": "1.5.4",
81 | "x-address": address,
82 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/core/btc/errs.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import "errors"
4 |
5 | var (
6 | ErrUnsupportedChain = errors.New("Unsupported BTC chainnet")
7 | ErrHttpResponseParse = errors.New("Network data parsing error")
8 |
9 | ErrDecodeAddress = errors.New("Btc cannot support decode address to public key")
10 |
11 | ErrPsbtEncode = errors.New("unsupported Psbt tx encode")
12 | ErrPsbtUnsupportedAccountType = errors.New("account psbt sign unsupported")
13 | ErrTransactionNotSigned = errors.New("transaction not signed")
14 | )
15 |
--------------------------------------------------------------------------------
/core/btc/interface_test.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Chain)(nil)
11 | // _ base.Transaction = (*Transaction)(nil)
12 |
13 | _ base.Transaction = (*Brc20MintTransaction)(nil)
14 | _ base.SignedTransaction = (*Brc20MintTransaction)(nil)
15 | _ base.Transaction = (*PsbtTransaction)(nil)
16 | _ base.SignedTransaction = (*SignedPsbtTransaction)(nil)
17 | _ base.Transaction = (*Transaction)(nil)
18 | _ base.SignedTransaction = (*SignedTransaction)(nil)
19 | )
20 |
--------------------------------------------------------------------------------
/core/btc/ordinal/ordinals.go:
--------------------------------------------------------------------------------
1 | package ordinal
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "github.com/btcsuite/btcd/txscript"
7 | )
8 |
9 | type Ord struct {
10 | Type string
11 | ContentType string
12 | Content []byte
13 | }
14 |
15 | func DecodeOrdFromWitness(witness []byte) (*Ord, error) {
16 | const scriptVersion = 0
17 | tokenizer := txscript.MakeScriptTokenizer(scriptVersion, witness)
18 | for tokenizer.Next() {
19 | if tokenizer.Opcode() != txscript.OP_FALSE || tokenizer.Done() {
20 | continue
21 | }
22 | internalTokenizer := tokenizer
23 | if !internalTokenizer.Next() {
24 | continue
25 | }
26 | if internalTokenizer.Opcode() != txscript.OP_IF {
27 | continue
28 | }
29 | if !internalTokenizer.Next() {
30 | continue
31 | }
32 | if !bytes.Equal([]byte("ord"), internalTokenizer.Data()) {
33 | continue
34 | }
35 | if !internalTokenizer.Next() {
36 | continue
37 | }
38 | if !bytes.Equal([]byte{1}, internalTokenizer.Data()) {
39 | continue
40 | }
41 | if !internalTokenizer.Next() {
42 | continue
43 | }
44 | contentType := string(internalTokenizer.Data())
45 | if !internalTokenizer.Next() {
46 | continue
47 | }
48 | if internalTokenizer.Opcode() != txscript.OP_0 {
49 | continue
50 | }
51 | if !internalTokenizer.Next() {
52 | continue
53 | }
54 | content := internalTokenizer.Data()
55 | return &Ord{
56 | Type: "ord",
57 | ContentType: contentType,
58 | Content: content,
59 | }, nil
60 | }
61 | return nil, errors.New("not found ord")
62 | }
63 |
--------------------------------------------------------------------------------
/core/btc/ordinal/ordinals_test.go:
--------------------------------------------------------------------------------
1 | package ordinal
2 |
3 | import (
4 | "encoding/hex"
5 | "github.com/stretchr/testify/require"
6 | "testing"
7 | )
8 |
9 | func TestDecodeOrdFromWitness(t *testing.T) {
10 | witness, err := hex.DecodeString("20f4df9e108244fab3e423ab3fab0bdacb9047e4443a5d68195d256ea007644ba4ac0063036f7264010118746578742f706c61696e3b636861727365743d7574662d38004c4d7b200d0a20202270223a20226272632d3230222c0d0a2020226f70223a20227472616e73666572222c0d0a2020227469636b223a2022696e7363222c0d0a202022616d74223a202232220d0a7d68")
11 | require.NoError(t, err)
12 | ord, err := DecodeOrdFromWitness(witness)
13 | require.NoError(t, err)
14 | t.Log(ord)
15 | }
16 |
--------------------------------------------------------------------------------
/core/btc/runes/cenotaph.go:
--------------------------------------------------------------------------------
1 | package runes
2 |
3 | type Cenotaph struct {
4 | Etching *Rune
5 | Flaws uint32
6 | Mint *RuneId
7 | }
8 |
9 | func (c Cenotaph) artifact() {
10 | }
11 |
--------------------------------------------------------------------------------
/core/btc/runes/edict.go:
--------------------------------------------------------------------------------
1 | package runes
2 |
3 | import (
4 | "github.com/btcsuite/btcd/wire"
5 | "math/big"
6 | )
7 |
8 | type Edict struct {
9 | Id RuneId `json:"id"`
10 | Amount big.Int `json:"amount"`
11 | Output uint32 `json:"output"`
12 | }
13 |
14 | func NewEdictFromIntegers(tx *wire.MsgTx, id RuneId, amount big.Int, output big.Int) *Edict {
15 | if output.BitLen() > 32 {
16 | return nil
17 | }
18 | if int(output.Uint64()) > len(tx.TxOut) {
19 | return nil
20 | }
21 | return &Edict{
22 | Id: id,
23 | Amount: amount,
24 | Output: uint32(output.Uint64()),
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/core/btc/runes/etching.go:
--------------------------------------------------------------------------------
1 | package runes
2 |
3 | import "math/big"
4 |
5 | type Etching struct {
6 | Divisibility *uint8 `json:"divisibility"`
7 | Premine *big.Int `json:"premine"`
8 | Rune *Rune `json:"rune"`
9 | Spacers *uint32 `json:"spacers"`
10 | Symbol *rune `json:"symbol"`
11 | Terms *Terms `json:"terms"`
12 | Turbo bool `json:"turbo"`
13 | }
14 |
15 | const (
16 | MaxDivisibility uint8 = 38
17 | MaxSpacers uint32 = 0b00000111_11111111_11111111_11111111
18 | )
19 |
20 | func (e *Etching) Supply() *big.Int {
21 | premine := big.NewInt(0)
22 | if e.Premine != nil {
23 | premine = e.Premine
24 | }
25 | cap := big.NewInt(0)
26 | if e.Terms != nil && e.Terms.Cap != nil {
27 | cap = e.Terms.Cap
28 | }
29 | amount := big.NewInt(0)
30 | if e.Terms != nil && e.Terms.Amount != nil {
31 | amount = e.Terms.Amount
32 | }
33 | mul := new(big.Int).Mul(cap, amount)
34 | if mul.BitLen() > 128 {
35 | return nil
36 | }
37 | supply := new(big.Int).Add(premine, mul)
38 | if supply.BitLen() > 128 {
39 | return nil
40 | }
41 | return supply
42 | }
43 |
--------------------------------------------------------------------------------
/core/btc/runes/flaw.go:
--------------------------------------------------------------------------------
1 | package runes
2 |
3 | type Flaw byte
4 |
5 | const (
6 | EdictOutput Flaw = iota
7 | EdictRuneId
8 | InvalidScript
9 | Opcode
10 | SupplyOverflow
11 | TrailingIntegers
12 | TruncatedField
13 | UnrecognizedEvenTag
14 | UnrecognizedFlag
15 | Varint
16 | )
17 |
18 | func (f Flaw) Flag() uint32 {
19 | return 1 << uint32(f)
20 | }
21 |
--------------------------------------------------------------------------------
/core/btc/runes/message.go:
--------------------------------------------------------------------------------
1 | package runes
2 |
3 | import (
4 | "github.com/btcsuite/btcd/wire"
5 | "github.com/coming-chat/wallet-SDK/core/btc/runes/runestone"
6 | "math/big"
7 | )
8 |
9 | type Message struct {
10 | Flaws uint32
11 | Edicts []Edict
12 | Fields map[string][]big.Int
13 | }
14 |
15 | func NewMessageFromIntegers(tx *wire.MsgTx, payload []big.Int) *Message {
16 | var edicts []Edict
17 | fields := make(map[string][]big.Int)
18 | flaws := uint32(0)
19 |
20 | for i := 0; i < len(payload); i += 2 {
21 | tag := payload[i]
22 | if tag.Cmp(runestone.Body.ToBigInt()) == 0 {
23 | id := RuneId{}
24 | for j := i + 1; j < len(payload); j += 4 {
25 | if len(payload[j:]) < 4 {
26 | flaws |= TrailingIntegers.Flag()
27 | break
28 | }
29 |
30 | next := id.Next(payload[j], payload[j+1])
31 | if next == nil {
32 | flaws |= EdictRuneId.Flag()
33 | break
34 | }
35 |
36 | edict := NewEdictFromIntegers(tx, *next, payload[j+2], payload[j+3])
37 | if edict == nil {
38 | flaws |= EdictOutput.Flag()
39 | break
40 | }
41 |
42 | id = *next
43 | edicts = append(edicts, *edict)
44 | }
45 | break
46 | }
47 |
48 | if i+1 >= len(payload) {
49 | flaws |= TruncatedField.Flag()
50 | break
51 | }
52 | value := payload[i+1]
53 |
54 | fields[tag.String()] = append(fields[tag.String()], value)
55 | }
56 | return &Message{
57 | flaws,
58 | edicts,
59 | fields,
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/core/btc/runes/rune.go:
--------------------------------------------------------------------------------
1 | package runes
2 |
3 | import (
4 | "math/big"
5 | "strings"
6 | )
7 |
8 | type Rune struct {
9 | big.Int //uint128
10 | }
11 |
12 | func (r *Rune) String() string {
13 | n := new(big.Int).Set(&r.Int)
14 | if n.Cmp(new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewInt(1))) == 0 {
15 | return "BCGDENLQRQWDSLRUGSNLBTMFIJAV"
16 | }
17 | n = n.And(n, big.NewInt(1))
18 | symbol := strings.Builder{}
19 | for n.Cmp(big.NewInt(0)) > 0 {
20 | symbol.WriteByte("ABCDEFGHIJKLMNOPQRSTUVWXYZ"[new(big.Int).Mod(n.Sub(n, big.NewInt(1)), big.NewInt(26)).Int64()])
21 | n = n.Div(n.Sub(n, big.NewInt(1)), big.NewInt(26))
22 | }
23 | return symbol.String()
24 | }
25 |
--------------------------------------------------------------------------------
/core/btc/runes/rune_id.go:
--------------------------------------------------------------------------------
1 | package runes
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "math/big"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | var (
12 | ErrIllegalRuneIdString = errors.New("illegal rune id string")
13 | )
14 |
15 | type RuneId struct {
16 | Block uint64 `json:"block"`
17 | Tx uint32 `json:"tx"`
18 | }
19 |
20 | func (r *RuneId) Next(block big.Int, tx big.Int) *RuneId {
21 | if block.BitLen() > 64 || tx.BitLen() > 32 {
22 | return nil
23 | }
24 | newBlock := r.Block + block.Uint64()
25 | var newTx uint32
26 | if newBlock == 0 {
27 | newTx = r.Tx + uint32(tx.Uint64())
28 | } else {
29 | newTx = uint32(tx.Uint64())
30 | }
31 | return &RuneId{newBlock, newTx}
32 | }
33 |
34 | func (r *RuneId) String() string {
35 | return fmt.Sprintf("%d:%d", r.Block, r.Tx)
36 | }
37 |
38 | func NewRuneIdFromStr(runeIdStr string) (*RuneId, error) {
39 | runeIdData := strings.Split(runeIdStr, ":")
40 | if len(runeIdData) != 2 {
41 | return nil, ErrIllegalRuneIdString
42 | }
43 | block, err := strconv.ParseUint(runeIdData[0], 10, 64)
44 | if err != nil {
45 | return nil, err
46 | }
47 | tx, err := strconv.ParseUint(runeIdData[1], 10, 64)
48 | if err != nil {
49 | return nil, err
50 | }
51 | return &RuneId{block, uint32(tx)}, nil
52 | }
53 |
--------------------------------------------------------------------------------
/core/btc/runes/runestone/flag.go:
--------------------------------------------------------------------------------
1 | package runestone
2 |
3 | import "math/big"
4 |
5 | type Flag uint
6 |
7 | const (
8 | Etching Flag = iota
9 | Terms
10 | Turbo
11 | // unused
12 | FlagCenotaph = iota + 124
13 | )
14 |
15 | func (f Flag) mask() *big.Int {
16 | return new(big.Int).Lsh(big.NewInt(1), uint(f))
17 | }
18 |
19 | func (f Flag) Take(flags *big.Int) bool {
20 | mask := f.mask()
21 | set := new(big.Int).And(flags, mask).Cmp(big.NewInt(0)) != 0
22 | flags.Set(new(big.Int).AndNot(flags, mask))
23 | return set
24 | }
25 |
--------------------------------------------------------------------------------
/core/btc/runes/runestone/tag.go:
--------------------------------------------------------------------------------
1 | package runestone
2 |
3 | import (
4 | "math/big"
5 | )
6 |
7 | type Tag uint64
8 |
9 | const (
10 | Body Tag = iota
11 | Divisibility
12 | Flags
13 | Spacers
14 | Rune
15 | Symbol
16 | Premine
17 | _
18 | Cap
19 | _
20 | Amount
21 | _
22 | HeightStart
23 | _
24 | HeightEnd
25 | _
26 | OffsetStart
27 | _
28 | OffsetEnd
29 | _
30 | Mint
31 | _
32 | Pointer
33 | // unused
34 | Cenotaph = iota + 103
35 | // unused
36 | Nop
37 | )
38 |
39 | func (t Tag) ToBigInt() *big.Int {
40 | return new(big.Int).SetUint64(uint64(t))
41 | }
42 |
43 | func Take[T any](tag Tag, fields map[string][]big.Int, n int, with func(...big.Int) *T) *T {
44 | field, ok := fields[tag.ToBigInt().String()]
45 | if !ok {
46 | return nil
47 | }
48 | values := make([]big.Int, n)
49 |
50 | for i := range values {
51 | if i >= len(field) {
52 | return nil
53 | }
54 | values[i] = field[i]
55 | }
56 |
57 | value := with(values...)
58 | if value == nil {
59 | return nil
60 | }
61 |
62 | field = field[n:]
63 | if len(field) == 0 {
64 | delete(fields, tag.ToBigInt().String())
65 | }
66 | return value
67 | }
68 |
--------------------------------------------------------------------------------
/core/btc/runes/terms.go:
--------------------------------------------------------------------------------
1 | package runes
2 |
3 | import "math/big"
4 |
5 | type Terms struct {
6 | Amount *big.Int
7 | Cap *big.Int
8 | Height [2]*uint64
9 | Offset [2]*uint64
10 | }
11 |
--------------------------------------------------------------------------------
/core/btc/runes/varint.go:
--------------------------------------------------------------------------------
1 | package runes
2 |
3 | import (
4 | "errors"
5 | "math/big"
6 | )
7 |
8 | var (
9 | ErrOverlong = errors.New("too long")
10 | ErrOverflow = errors.New("overflow")
11 | ErrUnterminated = errors.New("unterminated")
12 | )
13 |
14 | func Decode(buffer []byte) (*big.Int, *int) {
15 | u128, i, err := tryDecode(buffer)
16 | if err != nil {
17 | return nil, nil
18 | }
19 | return u128, &i
20 | }
21 |
22 | func tryDecode(buffer []byte) (*big.Int, int, error) {
23 | n := big.NewInt(0)
24 |
25 | for i, b := range buffer {
26 | if i > 18 {
27 | return nil, 0, ErrOverlong
28 | }
29 |
30 | value := new(big.Int).And(big.NewInt(int64(b)), big.NewInt(0b0111_1111))
31 |
32 | if i == 18 && new(big.Int).And(value, big.NewInt(0b0111_1100)).Cmp(big.NewInt(0)) != 0 {
33 | return nil, 0, ErrOverflow
34 | }
35 |
36 | n = new(big.Int).Or(n, new(big.Int).Lsh(value, uint(7*i)))
37 |
38 | if b&0b1000_0000 == 0 {
39 | return n, i + 1, nil
40 | }
41 | }
42 | return nil, 0, ErrUnterminated
43 | }
44 |
--------------------------------------------------------------------------------
/core/btc/token.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import "github.com/coming-chat/wallet-SDK/core/base"
4 |
5 | // MARK - Implement the protocol Token
6 |
7 | func (c *Chain) Chain() base.Chain {
8 | return c
9 | }
10 |
11 | func (c *Chain) TokenInfo() (*base.TokenInfo, error) {
12 | name, err := nameOf(c.Chainnet)
13 | if err != nil {
14 | return nil, err
15 | }
16 | return &base.TokenInfo{
17 | Name: name,
18 | Symbol: name,
19 | Decimal: 8,
20 | }, nil
21 | }
22 |
23 | func (t *Chain) BuildTransfer(sender, receiver, amount string) (txn base.Transaction, err error) {
24 | return nil, base.ErrUnsupportedFunction
25 | }
26 | func (t *Chain) CanTransferAll() bool {
27 | return false
28 | }
29 | func (t *Chain) BuildTransferAll(sender, receiver string) (txn base.Transaction, err error) {
30 | return nil, base.ErrUnsupportedFunction
31 | }
32 |
--------------------------------------------------------------------------------
/core/btc/transaction_psbt.go:
--------------------------------------------------------------------------------
1 | package btc
2 |
3 | import (
4 | "bytes"
5 | "encoding/hex"
6 |
7 | "github.com/btcsuite/btcd/btcutil/psbt"
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | )
10 |
11 | type PsbtTransaction struct {
12 | Packet psbt.Packet
13 | }
14 |
15 | func NewPsbtTransaction(psbtString string) (*PsbtTransaction, error) {
16 | packet, err := DecodePsbtTxToPacket(psbtString)
17 | if err != nil {
18 | return nil, err
19 | }
20 | return &PsbtTransaction{*packet}, nil
21 | }
22 |
23 | func (t *PsbtTransaction) SignWithAccount(account base.Account) (signedTxn *base.OptionalString, err error) {
24 | return nil, base.ErrUnsupportedFunction
25 | }
26 | func (t *PsbtTransaction) SignedTransactionWithAccount(account base.Account) (signedTxn base.SignedTransaction, err error) {
27 | defer base.CatchPanicAndMapToBasicError(&err)
28 |
29 | btcAccount := account.(*Account)
30 | if btcAccount == nil {
31 | return nil, base.ErrInvalidAccountType
32 | }
33 | if err = SignPSBTTx(&t.Packet, btcAccount); err != nil {
34 | return
35 | }
36 | if err = EnsurePsbtFinalize(&t.Packet); err != nil {
37 | return
38 | }
39 | return &SignedPsbtTransaction{t.Packet}, nil
40 | }
41 |
42 | type SignedPsbtTransaction struct {
43 | Packet psbt.Packet
44 | }
45 |
46 | func (t *SignedPsbtTransaction) HexString() (res *base.OptionalString, err error) {
47 | return nil, base.ErrUnsupportedFunction
48 | }
49 |
50 | func (t *SignedPsbtTransaction) PsbtHexString() (*base.OptionalString, error) {
51 | packet := t.Packet
52 | if err := EnsurePsbtFinalize(&packet); err != nil {
53 | return nil, err
54 | }
55 | var buff bytes.Buffer
56 | if err := packet.Serialize(&buff); err != nil {
57 | return nil, err
58 | }
59 | hexString := hex.EncodeToString(buff.Bytes())
60 | return &base.OptionalString{Value: hexString}, nil
61 | }
62 |
63 | func (t *SignedPsbtTransaction) PublishWithChain(c *Chain) (hashs *base.OptionalString, err error) {
64 | return c.SendSignedTransaction(t)
65 | }
66 |
--------------------------------------------------------------------------------
/core/cosmos/address_util.go:
--------------------------------------------------------------------------------
1 | package cosmos
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
8 | "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
9 | sdk "github.com/cosmos/cosmos-sdk/types"
10 | "github.com/cosmos/cosmos-sdk/types/bech32"
11 | )
12 |
13 | type Util struct {
14 | AddressPrefix string
15 | }
16 |
17 | func NewUtilWithPrefix(addressPrefix string) *Util {
18 | return &Util{addressPrefix}
19 | }
20 |
21 | // MARK - Implement the protocol wallet.Util
22 |
23 | // @param publicKey can start with 0x or not.
24 | func (u *Util) EncodePublicKeyToAddress(publicKey string) (string, error) {
25 | return EncodePublicKeyToAddress(publicKey, u.AddressPrefix)
26 | }
27 |
28 | // @throw decode address to public key is not supported
29 | func (u *Util) DecodeAddressToPublicKey(address string) (string, error) {
30 | return "", errors.New("decode address to public key is not supported")
31 | }
32 |
33 | func (u *Util) IsValidAddress(address string) bool {
34 | return IsValidAddress(address, u.AddressPrefix)
35 | }
36 |
37 | // MARK - like wallet.Util
38 |
39 | // @param publicKey can start with 0x or not.
40 | func EncodePublicKeyToAddress(publicKey string, addressPrefix string) (string, error) {
41 | pubBytes, err := types.HexDecodeString(publicKey)
42 | if err != nil {
43 | return "", err
44 | }
45 | pubKey := secp256k1.PubKey{Key: pubBytes}
46 | return Bech32FromAccAddress(pubKey.Address().Bytes(), addressPrefix)
47 | }
48 |
49 | // @param chainnet chain name
50 | func IsValidAddress(address string, addressPrefix string) bool {
51 | _, err := AccAddressFromBech32(address, addressPrefix)
52 | return err == nil
53 | }
54 |
55 | func AccAddressFromBech32(address string, addressPrefix string) (sdk.AccAddress, error) {
56 | if len(strings.TrimSpace(address)) == 0 {
57 | return nil, errors.New("empty address string is not allowed")
58 | }
59 |
60 | bz, err := sdk.GetFromBech32(address, addressPrefix)
61 | if err != nil {
62 | return nil, err
63 | }
64 |
65 | err = sdk.VerifyAddressFormat(bz)
66 | if err != nil {
67 | return nil, err
68 | }
69 |
70 | return sdk.AccAddress(bz), nil
71 | }
72 |
73 | func Bech32FromAccAddress(address []byte, addressPrefix string) (string, error) {
74 | return bech32.ConvertAndEncode(addressPrefix, address)
75 | }
76 |
--------------------------------------------------------------------------------
/core/cosmos/chain_rest.go:
--------------------------------------------------------------------------------
1 | package cosmos
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | "github.com/coming-chat/wallet-SDK/pkg/httpUtil"
10 | )
11 |
12 | type AccountInfo struct {
13 | AccountNumber string `json:"account_number"`
14 | Sequence string `json:"sequence"`
15 | }
16 |
17 | // Is aslias of Sequence
18 | func (i *AccountInfo) Nonce() string {
19 | return i.Sequence
20 | }
21 |
22 | type denomBalance struct {
23 | Denom string `json:"denom"`
24 | Amount string `json:"amount"`
25 | }
26 |
27 | func (c *Chain) AccountOf(address string) (*AccountInfo, error) {
28 | url := c.RestUrl + "/cosmos/auth/v1beta1/accounts/" + address
29 | body, err := httpUtil.Get(url, nil)
30 | if err != nil {
31 | return nil, err
32 | }
33 |
34 | account := struct {
35 | Account AccountInfo `json:"account"`
36 | }{}
37 | err = json.Unmarshal(body, &account)
38 | if err != nil {
39 | return nil, err
40 | }
41 |
42 | return &account.Account, nil
43 | }
44 |
45 | func (c *Chain) BalanceOfAddressAndDenom(address, denom string) (b *base.Balance, err error) {
46 | b = base.EmptyBalance()
47 |
48 | // url := fmt.Sprintf("%s/cosmos/bank/v1beta1/balances/%s/%s", c.RestUrl, address, denom) // the api is unusable.
49 | url := fmt.Sprintf("%s/cosmos/bank/v1beta1/balances/%s", c.RestUrl, address)
50 | body, err := httpUtil.Get(url, nil)
51 | if err != nil {
52 | return
53 | }
54 |
55 | balances := struct {
56 | Balances []denomBalance `json:"balances"`
57 | }{}
58 | err = json.Unmarshal(body, &balances)
59 | if err != nil {
60 | return b, errors.New("The balance cannot be found, the account may not exist")
61 | }
62 |
63 | if len(balances.Balances) <= 0 {
64 | return
65 | }
66 |
67 | var balance *denomBalance = nil
68 | if len(denom) <= 0 {
69 | // If no denom is specified, get the first balance
70 | balance = &balances.Balances[0]
71 | } else {
72 | for _, bal := range balances.Balances {
73 | if bal.Denom == denom {
74 | balance = &bal
75 | break
76 | }
77 | }
78 | }
79 |
80 | if balance == nil {
81 | return b, errors.New("Unmatched coin: " + denom)
82 | }
83 |
84 | b.Total = balance.Amount
85 | b.Usable = balance.Amount
86 | return b, nil
87 | }
88 |
--------------------------------------------------------------------------------
/core/cosmos/constant.go:
--------------------------------------------------------------------------------
1 | package cosmos
2 |
3 | const (
4 | // All comments are made for sdk
5 |
6 | // 0.01
7 | GasPriceLow = "0.01"
8 | // 0.025
9 | GasPriceAverage = "0.025"
10 | // 0.04
11 | GasPriceHigh = "0.04"
12 |
13 | // 100000
14 | GasLimitDefault = "100000"
15 |
16 | // 118
17 | CosmosCointype = 118
18 | // cosmos
19 | CosmosPrefix = "cosmos"
20 | // uatom
21 | CosmosAtomDenom = "uatom"
22 |
23 | // 330
24 | TerraCointype = 330
25 | // terra
26 | TerraPrefix = "terra"
27 | // uluna
28 | TerraLunaDenom = "uluna"
29 | // uusd
30 | TerraUSTDenom = "uusd"
31 |
32 | // 10
33 | TerraGasPrice = "10"
34 | // 0.25
35 | TerraGasPriceUST = "0.25"
36 | // 80000
37 | TerraGasLimitDefault = "80000"
38 | )
39 |
40 | type GradedGasPrice struct {
41 | Low string
42 | Average string
43 | High string
44 | }
45 |
46 | type KnownTokenInfo struct {
47 | Cointype int64
48 | Prefix string
49 | Denom string
50 | GasPrice *GradedGasPrice
51 | GasLimit string
52 | }
53 |
54 | var (
55 | CosmosAtom = &KnownTokenInfo{
56 | Cointype: 118,
57 | Prefix: "cosmos",
58 | Denom: "uatom",
59 | GasPrice: &GradedGasPrice{
60 | Low: "0.01",
61 | Average: "0.025",
62 | High: "0.04",
63 | },
64 | GasLimit: "100000",
65 | }
66 | TerraLunc = &KnownTokenInfo{
67 | Cointype: 330,
68 | Prefix: "terra",
69 | Denom: "uluna",
70 | GasPrice: &GradedGasPrice{"10", "10", "10"},
71 | GasLimit: "80000",
72 | }
73 | TerraUst = &KnownTokenInfo{
74 | Cointype: 330,
75 | Prefix: "terra",
76 | Denom: "uusd",
77 | GasPrice: &GradedGasPrice{"0.25", "0.25", "0.25"},
78 | GasLimit: "80000",
79 | }
80 | // TerraLuna = &KnownTokenInfo{
81 | // Cointype: 330,
82 | // Prefix: "terra",
83 | // Denom: "uluna",
84 | // GasPrice: &GradedGasPrice{"10", "10", "10"},
85 | // GasLimit: "80000",
86 | // }
87 | )
88 |
--------------------------------------------------------------------------------
/core/cosmos/interface_test.go:
--------------------------------------------------------------------------------
1 | package cosmos
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Token)(nil)
11 | // _ base.Transaction = (*Transaction)(nil)
12 | )
13 |
--------------------------------------------------------------------------------
/core/cosmos/rpc_reachability.go:
--------------------------------------------------------------------------------
1 | package cosmos
2 |
3 | import (
4 | "encoding/json"
5 | "strconv"
6 | "time"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | "github.com/coming-chat/wallet-SDK/pkg/httpUtil"
10 | )
11 |
12 | type RpcReachability struct {
13 | }
14 |
15 | func NewRpcReachability() *RpcReachability {
16 | return &RpcReachability{}
17 | }
18 |
19 | // @return latency (ms) of rpc query blockNumber. -1 means the connection failed.
20 | func (r *RpcReachability) LatencyOf(rpc string, timeout int64) (l *base.RpcLatency, err error) {
21 | l = &base.RpcLatency{
22 | RpcUrl: rpc,
23 | Latency: -1,
24 | Height: -1,
25 | }
26 |
27 | timeStart := time.Now() // Time Start
28 | params := httpUtil.RequestParams{
29 | Header: map[string]string{"Content-Type": "application/json"},
30 | Body: []byte(`{"jsonrpc":"2.0","method":"blockchain","params":{},"id":13}`),
31 | Timeout: time.Duration(timeout * int64(time.Millisecond)),
32 | }
33 | response, err := httpUtil.Post(rpc, params)
34 | if err != nil {
35 | return l, err
36 | }
37 |
38 | model := struct {
39 | Result struct {
40 | Height string `json:"last_height"`
41 | } `json:"result"`
42 | }{}
43 | err = json.Unmarshal(response, &model)
44 | if err != nil {
45 | return l, err
46 | }
47 | heightInt, err := strconv.ParseInt(model.Result.Height, 10, 64)
48 | if err != nil {
49 | heightInt = 0
50 | err = nil
51 | }
52 | timeCost := time.Since(timeStart) // Time End
53 |
54 | l.Height = heightInt
55 | l.Latency = timeCost.Milliseconds()
56 | return l, nil
57 | }
58 |
--------------------------------------------------------------------------------
/core/cosmos/rpc_reachability_test.go:
--------------------------------------------------------------------------------
1 | package cosmos
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | )
10 |
11 | type dddd struct {
12 | }
13 |
14 | func (d *dddd) ReachabilityDidReceiveNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
15 | fmt.Printf(".... delegate did receive height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
16 | }
17 |
18 | func (d *dddd) ReachabilityDidFailNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
19 | fmt.Printf(".... delegate did fail height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
20 | // tester.StopConnectivity()
21 | }
22 |
23 | func (d *dddd) ReachabilityDidFinish(tester *base.ReachMonitor, overview string) {
24 | fmt.Printf(".... delegate did finish %v\n", overview)
25 | }
26 |
27 | func TestRpcReachability_Test(t *testing.T) {
28 | reach := NewRpcReachability()
29 | monitor := base.NewReachMonitorWithReachability(reach)
30 | monitor.ReachCount = 3
31 | monitor.Delay = 3000
32 | monitor.Timeout = 1500
33 | t.Log(reach)
34 |
35 | rpcUrls := []string{rpcs.cosmosProd.rpc, rpcs.luncProd.rpc}
36 | rpcListString := strings.Join(rpcUrls, ",")
37 | // res := reach.StartConnectivitySync(rpcListString)
38 | // t.Log(res)
39 |
40 | delegate := &dddd{}
41 | monitor.StartConnectivityDelegate(rpcListString, delegate)
42 | }
43 |
--------------------------------------------------------------------------------
/core/doge/account_test.go:
--------------------------------------------------------------------------------
1 | package doge
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/testcase"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | type TestAccountCase struct {
11 | mnemonic string
12 | privateKey string
13 | publicKey string
14 | addrMainnet string
15 | addrTestnet string
16 | }
17 |
18 | var accountCase = &TestAccountCase{
19 | mnemonic: "unaware oxygen allow method allow property predict various slice travel please priority",
20 | privateKey: "0xc7fceb75bafba7aa10ffe10315352bfc523ac733f814e6a311bc736873df8923",
21 | publicKey: "0x04a721f170043daafde0fa925ab6caf5d2abcdadd2249291b1840e3d99a3f41149e13185ef52451eef2e7cc0c5fe4180b64ca2d17eb886b2328518f6aed684719a",
22 | addrMainnet: "DJhF8ahvTfGhqcLEn7sN4gJMJVVbmfwxkU",
23 | addrTestnet: "nhkJrbSqPdjRiauRowWpK5teYMstkMp4M6",
24 | }
25 | var errorCase = &TestAccountCase{
26 | mnemonic: "unaware oxygen allow method allow property predict various slice travel please check",
27 | }
28 |
29 | const (
30 | // https://shibe.technology/
31 | returnAddress = "nbMFaHF9pjNoohS4fD1jefKBgDnETK9uPu"
32 | )
33 |
34 | func TestDoge(t *testing.T) {
35 | account, err := NewAccountWithMnemonic(accountCase.mnemonic, ChainTestnet)
36 | require.Nil(t, err)
37 | t.Log(account.PrivateKeyHex())
38 | t.Log(account.PublicKeyHex())
39 | t.Log(account.Address())
40 | }
41 |
42 | func TestAccountWithPrivatekey(t *testing.T) {
43 | mnemonic := testcase.M1
44 | accountFromMnemonic, err := NewAccountWithMnemonic(mnemonic, ChainMainnet)
45 | require.Nil(t, err)
46 | privateKey, err := accountFromMnemonic.PrivateKeyHex()
47 | require.Nil(t, err)
48 |
49 | accountFromPrikey, err := AccountWithPrivateKey(privateKey, ChainMainnet)
50 | require.Nil(t, err)
51 |
52 | require.Equal(t, accountFromMnemonic.Address(), accountFromPrikey.Address())
53 | }
54 |
--------------------------------------------------------------------------------
/core/doge/account_util.go:
--------------------------------------------------------------------------------
1 | package doge
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/btcsuite/btcd/btcutil"
7 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
8 | )
9 |
10 | type Util struct {
11 | Chainnet string
12 | }
13 |
14 | func NewUtilWithChainnet(chainnet string) (*Util, error) {
15 | if isValidChain(chainnet) {
16 | return &Util{Chainnet: chainnet}, nil
17 | } else {
18 | return nil, ErrUnsupportedChain
19 | }
20 | }
21 |
22 | // MARK - Implement the protocol Util
23 |
24 | // @param publicKey can start with 0x or not.
25 | func (u *Util) EncodePublicKeyToAddress(publicKey string) (string, error) {
26 | return EncodePublicKeyToAddress(publicKey, u.Chainnet)
27 | }
28 |
29 | func (u *Util) EncodePublicDataToAddress(public []byte) (string, error) {
30 | return EncodePublicDataToAddress(public, u.Chainnet)
31 | }
32 |
33 | // Warning: Dogecoin cannot support decode address to public key
34 | func (u *Util) DecodeAddressToPublicKey(address string) (string, error) {
35 | return "", errors.New("Dogecoin cannot support decode address to public key")
36 | }
37 |
38 | func (u *Util) IsValidAddress(address string) bool {
39 | return IsValidAddress(address, u.Chainnet)
40 | }
41 |
42 | // MARK - like Util
43 |
44 | // @param publicKey can start with 0x or not.
45 | func EncodePublicKeyToAddress(publicKey string, chainnet string) (string, error) {
46 | pubData, err := types.HexDecodeString(publicKey)
47 | if err != nil {
48 | return "", err
49 | }
50 | return EncodePublicDataToAddress(pubData, chainnet)
51 | }
52 |
53 | func EncodePublicDataToAddress(public []byte, chainnet string) (string, error) {
54 | net, err := netParamsOf(chainnet)
55 | if err != nil {
56 | return "", err
57 | }
58 | address, err := btcutil.NewAddressPubKey(public, net)
59 | if err != nil {
60 | return "", err
61 | }
62 | return address.EncodeAddress(), nil
63 | }
64 |
65 | func IsValidAddress(address, chainnet string) bool {
66 | net, err := netParamsOf(chainnet)
67 | if err != nil {
68 | return false
69 | }
70 | _, err = btcutil.DecodeAddress(address, net)
71 | if err != nil {
72 | return false
73 | }
74 | return true
75 | }
76 |
--------------------------------------------------------------------------------
/core/doge/chainnet.go:
--------------------------------------------------------------------------------
1 | package doge
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/btcsuite/btcd/chaincfg"
7 | )
8 |
9 | const (
10 | ChainTestnet = "testnet"
11 | ChainMainnet = "mainnet"
12 | )
13 |
14 | var (
15 | ErrUnsupportedChain = errors.New("Unsupported Doge chainnet")
16 |
17 | // https://pkg.go.dev/github.com/renproject/multichain@v0.2.9/chain/dogecoin
18 | mainnetCfg = chaincfg.Params{
19 | Name: "mainnet",
20 | Net: 0xc0c0c0c0,
21 |
22 | PubKeyHashAddrID: 30,
23 | ScriptHashAddrID: 22,
24 | PrivateKeyID: 158,
25 |
26 | HDPrivateKeyID: [4]byte{0x02, 0xfa, 0xc3, 0x98},
27 | HDPublicKeyID: [4]byte{0x02, 0xfa, 0xca, 0xfd},
28 |
29 | Bech32HRPSegwit: "doge",
30 | }
31 | testnetCfg = chaincfg.Params{
32 | Name: "testnet",
33 | Net: 0xfcc1b7dc,
34 |
35 | PubKeyHashAddrID: 113,
36 | ScriptHashAddrID: 196,
37 | PrivateKeyID: 241,
38 |
39 | HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94},
40 | HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xcf},
41 |
42 | Bech32HRPSegwit: "doget",
43 | }
44 | )
45 |
46 | func isValidChain(chainnet string) bool {
47 | switch chainnet {
48 | case ChainTestnet, ChainMainnet:
49 | return true
50 | default:
51 | return false
52 | }
53 | }
54 |
55 | func netParamsOf(chainnet string) (*chaincfg.Params, error) {
56 | switch chainnet {
57 | case ChainTestnet:
58 | return &testnetCfg, nil
59 | case ChainMainnet:
60 | return &mainnetCfg, nil
61 | }
62 | return nil, ErrUnsupportedChain
63 | }
64 |
65 | func restUrlOf(chainnet string) (string, error) {
66 | switch chainnet {
67 | case ChainMainnet:
68 | return "https://api.blockcypher.com/v1/doge/main", nil
69 | case ChainTestnet:
70 | return "", ErrUnsupportedChain
71 | default:
72 | return "", ErrUnsupportedChain
73 | }
74 | }
75 |
76 | func nameOf(chainnet string) (string, error) {
77 | switch chainnet {
78 | case ChainTestnet:
79 | return "Doge", nil
80 | case ChainMainnet:
81 | return "Doget", nil
82 | }
83 | return "", ErrUnsupportedChain
84 | }
85 |
--------------------------------------------------------------------------------
/core/doge/interface_test.go:
--------------------------------------------------------------------------------
1 | package doge
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Chain)(nil)
11 | // _ base.Transaction = (*Transaction)(nil)
12 | )
13 |
--------------------------------------------------------------------------------
/core/doge/token.go:
--------------------------------------------------------------------------------
1 | package doge
2 |
3 | import "github.com/coming-chat/wallet-SDK/core/base"
4 |
5 | // MARK - Implement the protocol Token
6 |
7 | func (c *Chain) Chain() base.Chain {
8 | return c
9 | }
10 |
11 | func (c *Chain) TokenInfo() (*base.TokenInfo, error) {
12 | name, err := nameOf(c.Chainnet)
13 | if err != nil {
14 | return nil, err
15 | }
16 | return &base.TokenInfo{
17 | Name: name,
18 | Symbol: name,
19 | Decimal: 8,
20 | }, nil
21 | }
22 |
23 | func (t *Chain) BuildTransfer(sender, receiver, amount string) (txn base.Transaction, err error) {
24 | return nil, base.ErrUnsupportedFunction
25 | }
26 | func (t *Chain) CanTransferAll() bool {
27 | return false
28 | }
29 | func (t *Chain) BuildTransferAll(sender, receiver string) (txn base.Transaction, err error) {
30 | return nil, base.ErrUnsupportedFunction
31 | }
32 |
--------------------------------------------------------------------------------
/core/doge/transaction.go:
--------------------------------------------------------------------------------
1 | package doge
2 |
3 | import (
4 | "fmt"
5 | "math/big"
6 | "time"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | )
10 |
11 | type TransactionInput struct {
12 | Addresses []string `json:"addresses"`
13 | }
14 |
15 | type TransactionOutput struct {
16 | Value *big.Int `json:"value"`
17 | Addresses []string `json:"addresses"`
18 | }
19 |
20 | type Transaction struct {
21 | // Demo: https://api.blockcypher.com/v1/doge/main/txs/7bc313903372776e1eb81d321e3fe27c9721ce8e71a9bcfee1bde6baea31b5c2
22 | Confirmations int64 `json:"confirmations"`
23 | HashString string `json:"hash"`
24 | Total *big.Int `json:"total"`
25 | Fees *big.Int `json:"fees"`
26 | Received *time.Time `json:"received"`
27 | Confirmed *time.Time `json:"confirmed"`
28 | Inputs []*TransactionInput `json:"inputs"`
29 | Outputs []*TransactionOutput `json:"outputs"`
30 | OpReturn string `json:"data_protocol"`
31 | }
32 |
33 | func (t *Transaction) From() string {
34 | if len(t.Inputs) == 0 || len(t.Inputs[0].Addresses) == 0 {
35 | return ""
36 | }
37 | return t.Inputs[0].Addresses[0]
38 | }
39 |
40 | func (t *Transaction) ToAddressAndTransferAmount() (string, *big.Int) {
41 | if len(t.Outputs) == 0 {
42 | return "", nil
43 | }
44 | from := t.From()
45 | to := ""
46 | total := big.NewInt(0).SetBytes(t.Total.Bytes())
47 | for idx, out := range t.Outputs {
48 | if len(out.Addresses) <= 0 {
49 | continue
50 | }
51 | address := out.Addresses[0]
52 | if address == from {
53 | total.Sub(total, out.Value)
54 | continue
55 | }
56 | if idx == 0 {
57 | to = address
58 | } else {
59 | to = fmt.Sprintf("%v, %v", to, address)
60 | }
61 | }
62 | if to == "" {
63 | // If there are no other recipients, we consider the user to transfer to himself
64 | to = from
65 | }
66 |
67 | return to, total
68 | }
69 |
70 | func (t *Transaction) Status() base.TransactionStatus {
71 | if t.Confirmations >= 1 {
72 | return base.TransactionStatusSuccess
73 | } else {
74 | return base.TransactionStatusPending
75 | }
76 | }
77 |
78 | func (t *Transaction) SdkDetail() *base.TransactionDetail {
79 | to, amount := t.ToAddressAndTransferAmount()
80 | var finishTime int64
81 | if t.Confirmed != nil {
82 | finishTime = t.Confirmed.Unix()
83 | }
84 | var amountString string
85 | if amount != nil {
86 | amountString = amount.String()
87 | }
88 | return &base.TransactionDetail{
89 | HashString: t.HashString,
90 | Amount: amountString,
91 | EstimateFees: t.Fees.String(),
92 | FromAddress: t.From(),
93 | ToAddress: to,
94 | Status: t.Status(),
95 | FinishTimestamp: finishTime,
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/core/doge/utxo.go:
--------------------------------------------------------------------------------
1 | package doge
2 |
3 | import (
4 | "fmt"
5 | "math/big"
6 | )
7 |
8 | type UTXO struct {
9 | // input from like
10 | // https://api.blockcypher.com/v1/doge/main/addrs/D8aDCsK4TA9NYhmwiqw1BjZ4CP8LQ814Ea?limit=5&unspentOnly=true
11 | Txid string `json:"tx_hash"`
12 | Index int `json:"tx_output_n"`
13 | Value *big.Int `json:"value"`
14 | }
15 |
16 | func (u *UTXO) MarshalJSON() ([]byte, error) {
17 | // output to like
18 | // https://yapi.coming.chat/project/13/interface/api/578
19 | jsonString := fmt.Sprintf("{\"txid\":\"%v\",\"index\":%v,\"value\":\"%v\",\"prevTx\":\"\"}", u.Txid, u.Index, u.Value.String())
20 | return []byte(jsonString), nil
21 | }
22 |
23 | type UTXOList struct {
24 | Utxos []*UTXO `json:"txrefs"`
25 | }
26 |
27 | type SDKUTXOList struct {
28 | Txids []*UTXO `json:"txids"`
29 | FastestFee int `json:"fastestFee"`
30 | }
31 |
--------------------------------------------------------------------------------
/core/eth/abi_coder.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/ethereum/go-ethereum/accounts/abi"
7 | )
8 |
9 | func AbiCoder(abiTypes []string) (abi.Arguments, error) {
10 | if len(abiTypes) <= 0 {
11 | return nil, errors.New("no abi types")
12 | }
13 | args := make([]abi.Argument, len(abiTypes))
14 | for idx, typStr := range abiTypes {
15 | typAbi, err := abi.NewType(typStr, "", nil)
16 | if err != nil {
17 | return nil, err
18 | }
19 | args[idx] = abi.Argument{Type: typAbi}
20 | }
21 | return args, nil
22 | }
23 |
24 | // AbiCoderEncode
25 | // usage like js ethers.AbiCoder
26 | // https://docs.ethers.org/v5/api/utils/abi/coder/
27 | func AbiCoderEncode(abiTypes []string, args ...any) ([]byte, error) {
28 | coder, err := AbiCoder(abiTypes)
29 | if err != nil {
30 | return nil, err
31 | }
32 | return coder.Pack(args...)
33 | }
34 |
--------------------------------------------------------------------------------
/core/eth/abi_coder_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "encoding/hex"
5 | "math/big"
6 | "testing"
7 |
8 | "github.com/ethereum/go-ethereum/common"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestAbiCoder(t *testing.T) {
13 | tick := "BEVM"
14 | to := "0xa2cCF83EA437565a37E1F2d49940e0C4C7D7591e"
15 | amount := big.NewInt(1000)
16 |
17 | data, err := AbiCoderEncode(
18 | []string{"address", "address", "string", "string", "string", "uint256", "uint256", "uint256", "uint256", "uint16", "string"},
19 | common.HexToAddress("0x0"),
20 | common.HexToAddress(to),
21 | "src-20",
22 | "transfer",
23 | tick,
24 | big.NewInt(0),
25 | big.NewInt(0),
26 | big.NewInt(0),
27 | amount,
28 | uint16(0),
29 | "{}",
30 | )
31 | require.Nil(t, err)
32 | wantHex := "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a2ccf83ea437565a37e1f2d49940e0c4c7d7591e000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000067372632d3230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000087472616e7366657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044245564d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027b7d000000000000000000000000000000000000000000000000000000000000"
33 | require.Equal(t, "0x"+hex.EncodeToString(data), wantHex)
34 | }
35 |
--------------------------------------------------------------------------------
/core/eth/address_util.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
8 | "github.com/ethereum/go-ethereum/common"
9 | "github.com/ethereum/go-ethereum/crypto"
10 | )
11 |
12 | type Util struct {
13 | }
14 |
15 | func NewUtil() *Util {
16 | return &Util{}
17 | }
18 |
19 | // MARK - Implement the protocol wallet.Util
20 |
21 | func (u *Util) EncodePublicKeyToAddress(publicKey string) (string, error) {
22 | return EncodePublicKeyToAddress(publicKey)
23 | }
24 |
25 | // Warning: eth cannot support decode address to public key
26 | func (u *Util) DecodeAddressToPublicKey(address string) (string, error) {
27 | return "", errors.New("eth cannot support decode address to public key")
28 | }
29 |
30 | // Check if address is 40 hexadecimal characters
31 | func (u *Util) IsValidAddress(address string) bool {
32 | return IsValidAddress(address)
33 | }
34 |
35 | // MARK - like wallet.Util
36 |
37 | func EncodePublicKeyToAddress(publicKey string) (string, error) {
38 | bytes, err := types.HexDecodeString(publicKey)
39 | if err != nil {
40 | return "", err
41 | }
42 | publicKeyECDSA, err := crypto.UnmarshalPubkey(bytes)
43 | if err != nil {
44 | return "", err
45 | }
46 | address := crypto.PubkeyToAddress(*publicKeyECDSA)
47 | return address.String(), nil
48 | }
49 |
50 | // Warning: eth cannot support decode address to public key
51 | func DecodeAddressToPublicKey(address string) (string, error) {
52 | return "", errors.New("eth cannot support decode address to public key")
53 | }
54 |
55 | // Check if address is 40 hexadecimal characters
56 | func IsValidAddress(address string) bool {
57 | return common.IsHexAddress(address)
58 | }
59 |
60 | // It will check based on eip55 rules
61 | func IsValidEIP55Address(address string) bool {
62 | if !IsValidAddress(address) {
63 | return false
64 | }
65 | eip55Address := TransformEIP55Address(address)
66 | return strings.HasSuffix(eip55Address, address)
67 | }
68 |
69 | func TransformEIP55Address(address string) string {
70 | address = strings.TrimPrefix(address, "0x")
71 | addressBytes := []byte(strings.ToLower(address))
72 | checksumBytes := crypto.Keccak256(addressBytes)
73 |
74 | for i, c := range addressBytes {
75 | if c >= '0' && c <= '9' {
76 | continue
77 | } else {
78 | checksum := checksumBytes[i/2]
79 | bitcode := byte(0x80) >> ((i % 2) * 4)
80 | if checksum&bitcode > 0 { // to Upper
81 | addressBytes[i] -= 32
82 | }
83 | }
84 | }
85 |
86 | return "0x" + string(addressBytes)
87 | }
88 |
--------------------------------------------------------------------------------
/core/eth/blockscout_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestBlockScout_NFT(t *testing.T) {
10 | api := NewBlockScout(BlockScoutURLEth)
11 | owner := "0x30b31174e5FEd4598aA0dF3191EbAA5AAb48d43E" // nft very much
12 | // owner := "0x9dE416AB881b322eE0b4c189C2dE624090280cF2" // nft few
13 |
14 | page1, err := api.Nft(owner, nil)
15 | require.Nil(t, err)
16 | showPage(page1)
17 |
18 | require.Equal(t, page1.HasNextPage(), true)
19 | page2, err := api.Nft(owner, page1.NextPageParams())
20 | require.Nil(t, err)
21 | showPage(page2)
22 | }
23 |
24 | func showPage(p *BKSNFTPage) {
25 | if p == nil {
26 | println("!!! page is empty")
27 | return
28 | }
29 | println("item count: ", p.Count())
30 | f := p.ValueAt(0).ToBaseNFT()
31 | println("first item: ", f.Id, f.Name, f.Image)
32 | println("has next page: ", p.HasNextPage())
33 | println("====================")
34 | }
35 |
--------------------------------------------------------------------------------
/core/eth/chainManager.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | var chainConnections = make(map[string]*EthChain)
8 | var lock sync.RWMutex
9 |
10 | // 通过 rpcUrl, 获取 eth 的连接对象
11 | func GetConnection(rpcUrl string) (*EthChain, error) {
12 | return getConnectionWithTimeout(rpcUrl, 0)
13 | }
14 |
15 | // @param timeout time unit millisecond, zero instead use default.
16 | func getConnectionWithTimeout(rpcUrl string, timeout int64) (*EthChain, error) {
17 | chain, ok := chainConnections[rpcUrl]
18 | if ok {
19 | return chain, nil
20 | }
21 |
22 | // 通过加锁范围
23 | lock.Lock()
24 | defer lock.Unlock()
25 |
26 | // 再判断一次
27 | chain, ok = chainConnections[rpcUrl]
28 | if ok {
29 | return chain, nil
30 | }
31 |
32 | // 创建并存储
33 | chain, err := NewEthChain().CreateRemoteWithTimeout(rpcUrl, timeout)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | chainConnections[rpcUrl] = chain
39 | return chain, nil
40 | }
41 |
--------------------------------------------------------------------------------
/core/eth/chain_nft.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/coming-chat/wallet-SDK/core/base"
8 | )
9 |
10 | func (c *Chain) TransferNFT(sender, receiver string, nft *base.NFT) (*Transaction, error) {
11 | return c.TransferNFTParams(sender, receiver, nft.Id, nft.ContractAddress, nft.Standard)
12 | }
13 |
14 | // TransferNFTParams
15 | // - param nftStandard: only support erc-721 now, else throw error unsupported nft type.
16 | func (c *Chain) TransferNFTParams(sender, receiver, nftId, nftContractAddress, nftStandard string) (txn *Transaction, err error) {
17 | defer base.CatchPanicAndMapToBasicError(&err)
18 |
19 | if strings.ToLower(nftStandard) != "erc-721" {
20 | return nil, errors.New("unsupported nft type")
21 | }
22 | data, err := EncodeErc721TransferFrom(sender, receiver, nftId)
23 | if err != nil {
24 | return nil, err
25 | }
26 | gasPrice, err := c.SuggestGasPrice()
27 | if err != nil {
28 | return nil, err
29 | }
30 |
31 | msg := NewCallMsg()
32 | msg.SetFrom(sender)
33 | msg.SetTo(nftContractAddress)
34 | msg.SetValue("0")
35 | msg.SetGasPrice(gasPrice.Value)
36 | msg.SetData(data)
37 |
38 | gasLimit, err := c.EstimateGasLimit(msg)
39 | if err != nil {
40 | return nil, err
41 | }
42 | msg.SetGasLimit(gasLimit.Value)
43 |
44 | return msg.TransferToTransaction(), nil
45 | }
46 |
--------------------------------------------------------------------------------
/core/eth/chain_nft_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/testcase"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestChain_TransferNFT_Erc721(t *testing.T) {
11 | mn := testcase.M1
12 | sender, err := NewAccountWithMnemonic(mn)
13 | require.Nil(t, err)
14 |
15 | receiver := sender.Address()
16 | nftId := "0"
17 | nftContract := "0x199Dcb0132a66b05723882259832e240fF735810"
18 | nftStandard := "erc-721"
19 |
20 | chain := NewChainWithRpc("https://canary-testnet.bevm.io/")
21 |
22 | txn, err := chain.TransferNFTParams(sender.Address(), receiver,
23 | nftId, nftContract, nftStandard)
24 | require.Nil(t, err)
25 |
26 | signedTx, err := chain.BuildTransferTxWithAccount(sender, txn)
27 | require.Nil(t, err)
28 |
29 | run := false
30 | if run {
31 | txHash, err := chain.SendRawTransaction(signedTx.Value)
32 | require.Nil(t, err)
33 | t.Log(txHash)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/core/eth/chain_test_base_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/testcase"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | /// Test the chain `base`
11 |
12 | func BaseMainnetChain() *Chain {
13 | // scan https://basescan.org/
14 | return NewChainWithRpc("https://mainnet.base.org")
15 | }
16 | func BaseTestnetChain() *Chain {
17 | // scan https://goerli.basescan.org/
18 | return NewChainWithRpc("https://goerli.base.org")
19 | }
20 |
21 | func TestBaseBalance(t *testing.T) {
22 | owner := "0x8687B640546744b5338AA292fBbE881162dd5bAe"
23 | chain := BaseTestnetChain()
24 | balance, err := chain.BalanceOfAddress(owner)
25 | require.Nil(t, err)
26 | t.Log(balance)
27 | }
28 |
29 | func TestBaseErc20TokenBalance(t *testing.T) {
30 | tokenAddress := "0xDd1351a0d3BB3c9D4516aEd7adCcf8814c7A193B"
31 | chain := BaseTestnetChain()
32 | token := chain.Erc20Token(tokenAddress)
33 |
34 | tokenInfo, err := token.TokenInfo()
35 | require.Nil(t, err)
36 | t.Log(tokenInfo)
37 |
38 | owner := "0x14acba2BAB926C6BFb64239C120C466424217477"
39 | balance, err := token.BalanceOfAddress(owner)
40 | require.Nil(t, err)
41 | t.Log(balance)
42 | }
43 |
44 | func TestBaseTransfer(t *testing.T) {
45 | sender, err := NewAccountWithMnemonic(testcase.M1)
46 | require.Nil(t, err)
47 | require.Equal(t, sender.Address(), "0x8687B640546744b5338AA292fBbE881162dd5bAe")
48 |
49 | toAddress := sender.Address()
50 | amount := ETH(0.01).String()
51 |
52 | chain := BaseTestnetChain()
53 |
54 | gasPrice, err := chain.SuggestGasPrice()
55 | require.Nil(t, err)
56 |
57 | token := chain.MainEthToken()
58 | gasLimit, err := token.EstimateGasLimit(sender.Address(), toAddress, gasPrice.Value, amount)
59 | require.Nil(t, err)
60 |
61 | transaction := NewTransaction("", gasPrice.Value, gasLimit, toAddress, amount, "")
62 | signedTx, err := token.BuildTransferTxWithAccount(sender, transaction)
63 | require.Nil(t, err)
64 |
65 | if false {
66 | hash, err := chain.SendRawTransaction(signedTx.Value)
67 | require.Nil(t, err)
68 |
69 | t.Log("txn send success, hash = ", hash)
70 | }
71 | }
72 |
73 | func TestBaseFetchTransactionDetail(t *testing.T) {
74 | hash := "0x6a86326feaada152b9c125c04904122bb8bdd1c07357c384f4818ebe3bf91f8f"
75 |
76 | chain := BaseTestnetChain()
77 |
78 | detail, err := chain.FetchTransactionDetail(hash)
79 | require.Nil(t, err)
80 | require.Equal(t, detail.FromAddress, "0x8687B640546744b5338AA292fBbE881162dd5bAe")
81 | require.Equal(t, detail.Amount, ETH(0.01).String())
82 | require.Equal(t, detail.FinishTimestamp, int64(1696824474))
83 |
84 | t.Log(detail.JsonString())
85 | }
86 |
--------------------------------------------------------------------------------
/core/eth/chain_test_linea_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/testcase"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | /// Test the chain `linea`
11 |
12 | func LineaMainnetChain() *Chain {
13 | // scan https://lineascan.build
14 | return NewChainWithRpc("https://rpc.linea.build")
15 | }
16 | func LineaTestnetChain() *Chain {
17 | // scan https://goerli.lineascan.build
18 | return NewChainWithRpc("https://rpc.goerli.linea.build")
19 | }
20 |
21 | func TestLineaBalance(t *testing.T) {
22 | owner := "0x8687B640546744b5338AA292fBbE881162dd5bAe"
23 |
24 | chain := LineaTestnetChain()
25 | balance, err := chain.BalanceOfAddress(owner)
26 | require.Nil(t, err)
27 | t.Log(balance.Total)
28 | }
29 |
30 | func TestLineaErc20TokenBalance(t *testing.T) {
31 | tokenAddress := "0x83240E55e35147B095e8958103a4fd4B32700a3C"
32 | chain := LineaTestnetChain()
33 | token := chain.Erc20Token(tokenAddress)
34 |
35 | tokenInfo, err := token.TokenInfo()
36 | require.Nil(t, err)
37 | t.Log(tokenInfo)
38 |
39 | owner := "0x422f72B27819798986F41c1bede24e76114DE584"
40 | balance, err := token.BalanceOfAddress(owner)
41 | require.Nil(t, err)
42 | t.Log(balance)
43 | }
44 |
45 | func TestLineaTransfer(t *testing.T) {
46 | sender, err := NewAccountWithMnemonic(testcase.M1)
47 | require.Nil(t, err)
48 | require.Equal(t, sender.Address(), "0x8687B640546744b5338AA292fBbE881162dd5bAe")
49 |
50 | toAddress := sender.Address()
51 | amount := ETH(0.01).String()
52 |
53 | chain := LineaTestnetChain()
54 |
55 | gasPrice, err := chain.SuggestGasPrice()
56 | require.Nil(t, err)
57 |
58 | token := chain.MainEthToken()
59 | gasLimit, err := token.EstimateGasLimit(sender.Address(), toAddress, gasPrice.Value, amount)
60 | require.Nil(t, err)
61 |
62 | transaction := NewTransaction("", gasPrice.Value, gasLimit, toAddress, amount, "")
63 | signedTx, err := token.BuildTransferTxWithAccount(sender, transaction)
64 | require.Nil(t, err)
65 |
66 | if false {
67 | hash, err := chain.SendRawTransaction(signedTx.Value)
68 | require.Nil(t, err)
69 |
70 | t.Log("txn send success, hash = ", hash)
71 | }
72 | }
73 |
74 | func TestLineaFetchTransactionDetail(t *testing.T) {
75 | hash := "0x6736f02ab900694e324d963365c0200e8131f1a5d0c547c0e23a5628d5bbb3bd"
76 |
77 | chain := LineaTestnetChain()
78 |
79 | detail, err := chain.FetchTransactionDetail(hash)
80 | require.Nil(t, err)
81 | require.Equal(t, detail.FromAddress, "0x8687B640546744b5338AA292fBbE881162dd5bAe")
82 | require.Equal(t, detail.Amount, ETH(0.01).String())
83 | require.Equal(t, detail.FinishTimestamp, int64(1696836892))
84 |
85 | t.Log(detail.JsonString())
86 | }
87 |
--------------------------------------------------------------------------------
/core/eth/constants.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | const (
4 | // ERC20 交易method
5 | ERC20_METHOD_TRANSFER = "transfer"
6 | ERC20_METHOD_APPROVE = "approve"
7 | )
8 |
9 | // 默认gas limit估算失败后,21000 * 3 = 63000
10 | const (
11 | DEFAULT_CONTRACT_GAS_LIMIT = "63000"
12 | DEFAULT_ETH_GAS_LIMIT = "21000"
13 | // 当前网络 standard gas price
14 | DEFAULT_ETH_GAS_PRICE = "20000000000"
15 | )
16 |
--------------------------------------------------------------------------------
/core/eth/donut_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/testcase"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func BevmChainMainnet() *Chain {
11 | return NewChainWithRpc("https://rpc-canary-1.bevm.io")
12 | }
13 |
14 | func TestFetchDonutInscriptions(t *testing.T) {
15 | owner := "0xC91A369B080638a1e1D0cFC81f3c420414E66aEe"
16 | ins, err := FetchDonutInscriptions(owner, "")
17 | require.Nil(t, err)
18 | t.Log(ins.JsonString())
19 | }
20 |
21 | func TestBuildDonutTransfer(t *testing.T) {
22 | chain := BevmChainMainnet()
23 | sender, _ := NewAccountWithMnemonic(testcase.M1)
24 | receiver := "0xa2cCF83EA437565a37E1F2d49940e0C4C7D7591e"
25 |
26 | token := NewSrc20Token(chain, "BEVM")
27 | txn, err := token.BuildTransfer(sender.Address(), receiver, "1000")
28 | require.Nil(t, err)
29 | if sender.Address() == receiver {
30 | wantDataHex := "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a2ccf83ea437565a37e1f2d49940e0c4c7d7591e000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000067372632d3230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000087472616e7366657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044245564d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027b7d000000000000000000000000000000000000000000000000000000000000"
31 | require.Equal(t, txn.(*Transaction).Data, wantDataHex)
32 | }
33 |
34 | ethTxn := AsEthTransaction(txn)
35 | require.NotNil(t, ethTxn)
36 | signedTxn, err := chain.BuildTransferTxWithAccount(sender, ethTxn)
37 | require.Nil(t, err)
38 |
39 | send := false
40 | if send {
41 | hash, err := chain.SendRawTransaction(signedTxn.Value)
42 | require.Nil(t, err)
43 | t.Log("send success, hash = ", hash)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/core/eth/eth_common.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "encoding/hex"
5 | "errors"
6 | "strings"
7 |
8 | "github.com/ethereum/go-ethereum/accounts/abi"
9 | "github.com/ethereum/go-ethereum/crypto"
10 | )
11 |
12 | func (e *EthChain) UnpackParams(out interface{}, inputs abi.Arguments, paramsStr string) error {
13 | paramsStr = strings.TrimPrefix(paramsStr, "0x")
14 | data, err := hex.DecodeString(paramsStr)
15 | if err != nil {
16 | return err
17 | }
18 | a, err := inputs.Unpack(data)
19 | if err != nil {
20 | return err
21 | }
22 | err = inputs.Copy(out, a)
23 | if err != nil {
24 | return err
25 | }
26 | return nil
27 | }
28 |
29 | // 不带 0x 前缀
30 | func (e *EthChain) PackParams(inputs abi.Arguments, args ...interface{}) (string, error) {
31 | bytes_, err := inputs.Pack(args...)
32 | if err != nil {
33 | return "", err
34 | }
35 | return hex.EncodeToString(bytes_), nil
36 | }
37 |
38 | func (e *EthChain) MethodIdFromMethodStr(methodStr string) string {
39 | return hex.EncodeToString(crypto.Keccak256([]byte(methodStr))[:4])
40 | }
41 |
42 | func (e *EthChain) MethodFromPayload(abiStr string, payloadStr string) (*abi.Method, error) {
43 | if len(payloadStr) < 8 {
44 | return nil, errors.New("payloadStr error")
45 | }
46 |
47 | payloadStr = strings.TrimPrefix(payloadStr, "0x")
48 |
49 | parsedAbi, err := abi.JSON(strings.NewReader(abiStr))
50 | if err != nil {
51 | return nil, err
52 | }
53 | data, err := hex.DecodeString(payloadStr)
54 | if err != nil {
55 | return nil, err
56 | }
57 | method, err := parsedAbi.MethodById(data[:4])
58 | if err != nil {
59 | return nil, err
60 | }
61 | return method, err
62 | }
63 |
--------------------------------------------------------------------------------
/core/eth/ethchain.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "context"
5 | "math/big"
6 | "time"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | "github.com/ethereum/go-ethereum/ethclient"
10 | "github.com/ethereum/go-ethereum/rpc"
11 | )
12 |
13 | type EthChain struct {
14 | timeout time.Duration
15 | RemoteRpcClient *ethclient.Client
16 | chainId *big.Int
17 | rpcUrl string
18 | }
19 |
20 | func NewEthChain() *EthChain {
21 | timeout := 60 * time.Second
22 | return &EthChain{
23 | timeout: timeout,
24 | }
25 | }
26 |
27 | func (e *EthChain) CreateRemote(rpcUrl string) (chain *EthChain, err error) {
28 | return e.CreateRemoteWithTimeout(rpcUrl, 0)
29 | }
30 |
31 | // @param timeout time unit millisecond. 0 means use chain's default: 60000ms.
32 | func (e *EthChain) CreateRemoteWithTimeout(rpcUrl string, timeout int64) (chain *EthChain, err error) {
33 | defer base.CatchPanicAndMapToBasicError(&err)
34 |
35 | var t time.Duration
36 | if timeout == 0 {
37 | t = e.timeout
38 | } else {
39 | t = time.Duration(timeout * int64(time.Millisecond))
40 | }
41 | ctx, cancel := context.WithTimeout(context.Background(), t)
42 | defer cancel()
43 | rpcClient, err := rpc.DialContext(ctx, rpcUrl)
44 | if err != nil {
45 | return
46 | }
47 |
48 | remoteRpcClient := ethclient.NewClient(rpcClient)
49 | chainId, err := remoteRpcClient.ChainID(ctx)
50 | if err != nil {
51 | return
52 | }
53 | e.chainId = chainId
54 | e.RemoteRpcClient = remoteRpcClient
55 | e.rpcUrl = rpcUrl
56 | return e, nil
57 | }
58 |
59 | func (e *EthChain) ConnectRemote(rpcUrl string) error {
60 | _, err := e.CreateRemote(rpcUrl)
61 | return err
62 | }
63 |
64 | func (e *EthChain) Close() {
65 | if e.RemoteRpcClient != nil {
66 | e.RemoteRpcClient.Close()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/core/eth/ethchain_basic.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "context"
5 | "strconv"
6 |
7 | "github.com/coming-chat/wallet-SDK/core/base"
8 | "github.com/ethereum/go-ethereum/common"
9 | )
10 |
11 | // @title 主网代币余额查询
12 | // @description 返回主网代币余额,decimal为代币精度
13 | // @auth 清欢
14 | // @param (walletAddress) (string) 合约名称,钱包地址
15 | // @return (string,error) 代币余额,错误信息
16 | func (e *EthChain) Balance(address string) (string, error) {
17 | ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
18 | defer cancel()
19 | result, err := e.RemoteRpcClient.BalanceAt(ctx, common.HexToAddress(address), nil)
20 | if err != nil {
21 | return "0", base.MapAnyToBasicError(err)
22 | }
23 | return result.String(), nil
24 | }
25 |
26 | // 获取最新区块高度
27 | func (e *EthChain) LatestBlockNumber() (int64, error) {
28 | ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
29 | defer cancel()
30 | number, err := e.RemoteRpcClient.BlockNumber(ctx)
31 | if err != nil {
32 | return 0, base.MapAnyToBasicError(err)
33 | }
34 |
35 | return int64(number), nil
36 | }
37 |
38 | // 获取账户nonce
39 | func (e *EthChain) Nonce(spenderAddressHex string) (string, error) {
40 | ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
41 | defer cancel()
42 | nonce, err := e.RemoteRpcClient.PendingNonceAt(ctx, common.HexToAddress(spenderAddressHex))
43 | if err != nil {
44 | return "0", base.MapAnyToBasicError(err)
45 | }
46 | return strconv.FormatUint(nonce, 10), nil
47 | }
48 |
49 | // 获取链ID
50 | func GetChainId(e *EthChain) (string, error) {
51 | ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
52 | defer cancel()
53 | chainId, err := e.RemoteRpcClient.ChainID(ctx)
54 | if err != nil {
55 | return "0", base.MapAnyToBasicError(err)
56 | }
57 |
58 | return chainId.String(), nil
59 | }
60 |
61 | // 对交易进行广播
62 | func (e *EthChain) SendRawTransaction(txHex string) (string, error) {
63 | var hash common.Hash
64 | ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
65 | defer cancel()
66 | err := e.RemoteRpcClient.Client().CallContext(ctx, &hash, "eth_sendRawTransaction", txHex)
67 | if err != nil {
68 | return "", base.MapAnyToBasicError(err)
69 | }
70 | return hash.String(), nil
71 | }
72 |
--------------------------------------------------------------------------------
/core/eth/ethchain_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestConnect(t *testing.T) {
8 | chain, err := NewEthChain().CreateRemote(rpcs.ethereumProd.url)
9 | if err != nil {
10 | t.Fatal(err)
11 | }
12 |
13 | t.Log(chain)
14 | // for i := 0; i < 1; i++ {
15 | // time.Sleep(1 * time.Second)
16 | // address := "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee"
17 | // balance, err := chain.TokenBalance(address, address)
18 | // if err != nil {
19 | // t.Log("...... catched err", err)
20 | // } else {
21 | // t.Log("...... balance", balance)
22 | // }
23 | // }
24 |
25 | // t.Log("should successd connect", chain)
26 | }
27 |
28 | func TestBatchStatus(t *testing.T) {
29 | var ethChain, _ = NewEthChain().CreateRemote(rpcs.binanceProd.url)
30 |
31 | var hashStrings = "0x9ef27c4983b18fd25a149e737feefb952889253aa6e2cddb62c6cf80a23887c3,0x39fa91a5e34d50f373339b2a5e9102ffc2c321f497a49841c62fd213e433290d,0x2cbf78965bbddecf86d2d0fb17069fa760fa652d81ee79d9a99f0add92b05364"
32 |
33 | var statuses = ethChain.SdkBatchTransactionStatus(hashStrings)
34 |
35 | t.Log(statuses)
36 | }
37 |
38 | const (
39 | transferFromAddress = "0x8de5ff2eded4d897da535ab0f379ec1b9257ebab"
40 | transferToAddress = "0x6cd2bf22b3ceadff6b8c226487265d81164396c5"
41 | )
42 |
43 | func TestEstimateGasLimit(t *testing.T) {
44 | var ethChain, _ = NewEthChain().CreateRemote(rpcs.binanceTest.url)
45 | gasprice := "10"
46 | amount := "1"
47 | gasLimit, err := ethChain.EstimateGasLimit(transferFromAddress, transferToAddress, gasprice, amount)
48 | if err != nil {
49 | t.Fatal("gas---" + err.Error())
50 | }
51 |
52 | t.Log("TestEstimateGasLimit success", gasLimit)
53 | }
54 |
--------------------------------------------------------------------------------
/core/eth/interface_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Token)(nil)
11 | _ base.Token = (*Erc20Token)(nil)
12 | _ base.Transaction = (*Transaction)(nil)
13 |
14 | _ base.Token = (*Src20Token)(nil)
15 | )
16 |
--------------------------------------------------------------------------------
/core/eth/red_packet_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/testcase"
7 | )
8 |
9 | func TestRedPacketProcess(t *testing.T) {
10 | chain := rpcs.sherpaxProd.Chain()
11 | account, _ := NewAccountWithMnemonic(testcase.M1)
12 | // account, _ := EthAccountWithPrivateKey("0x......")
13 | contractAddress := "0x4777abDEc6D52C25b4bc55a361da495011ccDBC3"
14 |
15 | balance, err := chain.BalanceOfAddress(account.Address())
16 | t.Logf("the account's balance %v %v", balance, err)
17 |
18 | // 1.1 Create red packet
19 | erc20Token := "0xa10AF02fD7eD3B5FF107B57bB1068a3f54BcAE92" // erc20 PCX
20 | count := 3
21 | amount := "100000000"
22 | action, err := NewRedPacketActionCreate(erc20Token, count, amount)
23 |
24 | // 1.2 Open red packet
25 | // packetId := int64(0)
26 | // addresses := []string{"0x99e5f4759fC07ee8F4f1B5a017ba100EFFC0C9C0"}
27 | // amounts := []string{"50000000"}
28 | // action, err := NewRedPacketActionOpen(packetId, addresses, amounts)
29 |
30 | // 1.3 Close red packet
31 | // packetId := int64(0)
32 | // creator := "0x6334d64D5167F726d8A44f3fbCA66613708E59E7"
33 | // action, err := NewRedPacketActionClose(packetId, creator)
34 |
35 | if err != nil {
36 | t.Fatal(err)
37 | }
38 |
39 | // 2. ensure erc20 coin approved
40 | // 如果是发红包,那这一步是必须的
41 | txhash, err := action.EnsureApprovedTokens(account, chain, contractAddress, 0)
42 | if err != nil {
43 | t.Fatal(err)
44 | }
45 | t.Logf("Are there new approve? %v", txhash)
46 |
47 | // 3. make transaction
48 | transaction, err := action.TransactionFrom(account.Address(), contractAddress, chain)
49 | if err != nil {
50 | t.Fatal(err)
51 | }
52 | t.Logf("Service Fee = %v", transaction.TotalAmount())
53 |
54 | // 4.1 signing raw tx with privatekey
55 | // rawTx, err := chain.BuildTransferTx(account.PrivateKeyHex(), transaction)
56 | // 4.2 or signing raw tx with account
57 | rawTx, err := chain.BuildTransferTxWithAccount(account, transaction)
58 | if err != nil {
59 | t.Fatal(err)
60 | }
61 |
62 | // 5. send transaction
63 | txHash, err := chain.SendRawTransaction(rawTx.Value)
64 | if err != nil {
65 | t.Fatal(err)
66 | }
67 | t.Logf("Red packet process success! txHash = %v", txHash)
68 | }
69 |
70 | func TestFetchRedPacketCreationDetail(t *testing.T) {
71 | hash := "0x598ed72d6ddcc1a4b378acd9b6d1917dc0eea0eb905de4aca27ce50e61b1539c"
72 | chain := rpcs.sherpaxProd.Chain()
73 | detail, err := chain.FetchRedPacketCreationDetail(hash)
74 | if err != nil {
75 | t.Fatal(err)
76 | }
77 | t.Log(detail, detail.TransactionDetail)
78 |
79 | jsonString := detail.JsonString()
80 | t.Log("json string = ", jsonString)
81 |
82 | model2, err := NewRedPacketDetailWithJsonString(jsonString)
83 | if err != nil {
84 | t.Fatal(err)
85 | }
86 | t.Log(model2, model2.TransactionDetail)
87 | }
88 |
--------------------------------------------------------------------------------
/core/eth/rpc_reachability.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "encoding/json"
5 | "math/big"
6 | "time"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | "github.com/coming-chat/wallet-SDK/pkg/httpUtil"
10 | )
11 |
12 | type RpcReachability struct {
13 | }
14 |
15 | func NewRpcReachability() *RpcReachability {
16 | return &RpcReachability{}
17 | }
18 |
19 | // @return latency (ms) of rpc query blockNumber. -1 means the connection failed.
20 | func (r *RpcReachability) LatencyOf(rpc string, timeout int64) (l *base.RpcLatency, err error) {
21 | l = &base.RpcLatency{
22 | RpcUrl: rpc,
23 | Latency: -1,
24 | Height: -1,
25 | }
26 |
27 | timeStart := time.Now() // Time Start
28 | params := httpUtil.RequestParams{
29 | Header: map[string]string{"Content-Type": "application/json"},
30 | Body: []byte(`{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":13}`),
31 | Timeout: time.Duration(timeout * int64(time.Millisecond)),
32 | }
33 | response, err := httpUtil.Post(rpc, params)
34 | if err != nil {
35 | return l, err
36 | }
37 |
38 | model := struct {
39 | Result string `json:"result"`
40 | }{}
41 | err = json.Unmarshal(response, &model)
42 | if err != nil {
43 | return l, err
44 | }
45 | heightInt, ok := big.NewInt(0).SetString(model.Result, 0)
46 | if !ok {
47 | heightInt = big.NewInt(0)
48 | }
49 | timeCost := time.Since(timeStart) // Time End
50 |
51 | l.Height = heightInt.Int64()
52 | l.Latency = timeCost.Milliseconds()
53 | return l, nil
54 | }
55 |
--------------------------------------------------------------------------------
/core/eth/rpc_reachability_test.go:
--------------------------------------------------------------------------------
1 | package eth
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | )
10 |
11 | type dddd struct {
12 | }
13 |
14 | func (d *dddd) ReachabilityDidReceiveNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
15 | fmt.Printf(".... delegate did receive height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
16 | }
17 |
18 | func (d *dddd) ReachabilityDidFailNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
19 | fmt.Printf(".... delegate did fail height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
20 | // tester.StopConnectivity()
21 | }
22 |
23 | func (d *dddd) ReachabilityDidFinish(tester *base.ReachMonitor, overview string) {
24 | fmt.Printf(".... delegate did finish %v\n", overview)
25 | }
26 |
27 | func TestRpcReachability_Test(t *testing.T) {
28 | reach := NewRpcReachability()
29 | monitor := base.NewReachMonitorWithReachability(reach)
30 | monitor.ReachCount = 3
31 | monitor.Delay = 3000
32 | monitor.Timeout = 1500
33 | t.Log(reach)
34 |
35 | rpcUrls := []string{rpcs.ethereumProd.url, rpcs.binanceTest.url}
36 | rpcListString := strings.Join(rpcUrls, ",")
37 | // res := reach.StartConnectivitySync(rpcListString)
38 | // t.Log(res)
39 |
40 | delegate := &dddd{}
41 | monitor.StartConnectivityDelegate(rpcListString, delegate)
42 | }
43 |
--------------------------------------------------------------------------------
/core/polka/address_util.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import (
4 | "github.com/decred/base58"
5 | "github.com/itering/subscan/util/ss58"
6 | )
7 |
8 | type Util struct {
9 | Network int
10 | }
11 |
12 | func NewUtilWithNetwork(network int) *Util {
13 | return &Util{Network: network}
14 | }
15 |
16 | // MARK - Implement the protocol wallet.Util
17 |
18 | // @param publicKey can start with 0x or not.
19 | func (u *Util) EncodePublicKeyToAddress(publicKey string) (string, error) {
20 | return EncodePublicKeyToAddress(publicKey, u.Network)
21 | }
22 |
23 | // @return publicKey that will start with 0x.
24 | func (u *Util) DecodeAddressToPublicKey(address string) (string, error) {
25 | return DecodeAddressToPublicKey(address)
26 | }
27 |
28 | func (u *Util) IsValidAddress(address string) bool {
29 | return IsValidAddress(address)
30 | }
31 |
32 | // MARK - like wallet.Util
33 |
34 | // @param publicKey can start with 0x or not.
35 | func EncodePublicKeyToAddress(publicKey string, network int) (string, error) {
36 | address := ss58.Encode(publicKey, network)
37 | if len(address) == 0 {
38 | return "", ErrPublicKey
39 | }
40 | return address, nil
41 | }
42 |
43 | // @return publicKey that will start with 0x.
44 | func DecodeAddressToPublicKey(address string) (string, error) {
45 | ss58Format := base58.Decode(address)
46 | if len(ss58Format) == 0 {
47 | return "", ErrAddress
48 | }
49 | publicKey := ss58.Decode(address, int(ss58Format[0]))
50 | if len(publicKey) == 0 {
51 | return "", ErrAddress
52 | }
53 | return "0x" + publicKey, nil
54 | }
55 |
56 | // @param chainnet chain name
57 | func IsValidAddress(address string) bool {
58 | _, err := DecodeAddressToPublicKey(address)
59 | return err == nil
60 | }
61 |
--------------------------------------------------------------------------------
/core/polka/error.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import "errors"
4 |
5 | var (
6 | ErrNilKey = errors.New("no mnemonic or private key")
7 | ErrNilWallet = errors.New("no mnemonic or private key or keystore")
8 | ErrNilKeystore = errors.New("no keystore")
9 | ErrNilMetadata = errors.New("no metadata")
10 | ErrNotSigned = errors.New("transaction not signed")
11 | ErrNoPublicKey = errors.New("transaction no public key")
12 | ErrNilExtrinsic = errors.New("nil extrinsic")
13 | ErrAddress = errors.New("err address")
14 | ErrPublicKey = errors.New("err publicKey")
15 | ErrSeedOrPhrase = errors.New("invalid seed length")
16 |
17 | ErrInvalidMnemonic = errors.New("invalid mnemonic")
18 |
19 | ErrWrongMetadata = errors.New("wrong metadata")
20 | ErrNoEncrypted = errors.New("no encrypted data to decode")
21 | ErrEncryptedLength = errors.New("encrypted length is less than 24")
22 | ErrInvalidParams = errors.New("invalid injected scrypt params found")
23 | ErrSecretLength = errors.New("secret length is not 32")
24 | ErrEncoded = errors.New("encoded is nil")
25 | ErrPkcs8Header = errors.New("invalid Pkcs8 header found in body")
26 | ErrPkcs8Divider = errors.New("invalid Pkcs8 divider found in body")
27 |
28 | ErrNonPkcs8 = errors.New("unable to decode non-pkcs8 type")
29 | ErrNilPassword = errors.New("password required to decode encrypted data")
30 | ErrNoEncryptedData = errors.New("no encrypted data available to decode")
31 | ErrKeystore = errors.New("decoded public keys are not equal")
32 |
33 | ErrPassword = errors.New("password err")
34 |
35 | ErrNumber = errors.New("illegal number")
36 | ErrSign = errors.New("sign panic error")
37 | )
38 |
--------------------------------------------------------------------------------
/core/polka/estimate_fee.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/centrifuge/go-substrate-rpc-client/v4/client"
7 | "github.com/coming-chat/wallet-SDK/core/base"
8 | )
9 |
10 | func (c *Chain) EstimateTransactionFee(transaction base.Transaction) (fee *base.OptionalString, err error) {
11 | defer base.CatchPanicAndMapToBasicError(&err)
12 | txn, ok := transaction.(*Transaction)
13 | if !ok {
14 | return nil, base.ErrInvalidTransactionType
15 | }
16 |
17 | account := mockAccount()
18 | fakeHash := "0x38c5a9f6fabb8d8583ed633c469cdeefb988b0d2384937b15e10e9c0a75aa744"
19 | signData, err := txn.GetSignData(fakeHash, 0, 0, 0)
20 | if err != nil {
21 | return
22 | }
23 | signature, err := account.Sign(signData, "")
24 | if err != nil {
25 | return
26 | }
27 | sendTx, err := txn.GetTx(account.PublicKey(), signature)
28 | if err != nil {
29 | return
30 | }
31 |
32 | cl, err := getConnectedPolkaClient(c.RpcUrl)
33 | data := make(map[string]interface{})
34 | err = client.CallWithBlockHash(cl.api.Client, &data, "payment_queryInfo", nil, sendTx)
35 | if err != nil {
36 | return
37 | }
38 |
39 | estimateFee, ok := data["partialFee"].(string)
40 | if !ok {
41 | return nil, errors.New("get estimated fee result nil")
42 | }
43 |
44 | return &base.OptionalString{Value: estimateFee}, nil
45 | }
46 |
47 | func (c *Chain) EstimateTransactionFeeUsePublicKey(transaction base.Transaction, pubkey string) (fee *base.OptionalString, err error) {
48 | return c.EstimateTransactionFee(transaction)
49 | }
50 |
--------------------------------------------------------------------------------
/core/polka/interface_test.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Token)(nil)
11 | _ base.Transaction = (*Transaction)(nil)
12 | )
13 |
--------------------------------------------------------------------------------
/core/polka/metadata.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | // Load cached metadata string.
4 | // This will save a lot of network traffic to download metadata from rpcUrl.
5 | func (c *Chain) LoadCachedMetadataString(metadataString string) error {
6 | _, err := getOrCreatePolkaClient(c.RpcUrl, metadataString)
7 | return err
8 | }
9 |
10 | // Get the metadata string of the chain (if not, it will be downloaded automatically)
11 | func (c *Chain) GetMetadataString() (string, error) {
12 | client, err := getConnectedPolkaClient(c.RpcUrl)
13 | if err != nil {
14 | return "", nil
15 | }
16 | return client.MetadataString()
17 | }
18 |
19 | // Reload the latest metadata of this chain.
20 | // @return the latest metadata string
21 | func (c *Chain) ReloadMetadata() (string, error) {
22 | client, err := getConnectedPolkaClient(c.RpcUrl)
23 | if err != nil {
24 | return "", nil
25 | }
26 |
27 | err = client.ReloadMetadata()
28 | if err != nil {
29 | return "", nil
30 | }
31 |
32 | return client.MetadataString()
33 | }
34 |
--------------------------------------------------------------------------------
/core/polka/password.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import "encoding/json"
4 |
5 | func (a *Account) CheckPassword(password string) error {
6 | if a.keystore == nil {
7 | return ErrNilKeystore
8 | }
9 | if a.keystore.CheckPassword(password) != nil {
10 | return ErrPassword
11 | }
12 | return nil
13 | }
14 |
15 | func CheckKeystorePassword(keystoreJson, password string) error {
16 | var keystore keystore
17 | err := json.Unmarshal([]byte(keystoreJson), &keystore)
18 | if err != nil {
19 | return err
20 | }
21 | if keystore.CheckPassword(password) != nil {
22 | return ErrPassword
23 | }
24 | return nil
25 | }
26 |
--------------------------------------------------------------------------------
/core/polka/rpcCall.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | func (c *Chain) RpcCall(result interface{}, method string, params ...any) error {
4 | client, err := getConnectedPolkaClient(c.RpcUrl)
5 | if err != nil {
6 | return err
7 | }
8 | return client.api.Client.Call(result, method, params...)
9 | }
10 |
--------------------------------------------------------------------------------
/core/polka/rpc_reachability.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import (
4 | "encoding/json"
5 | "math/big"
6 | "time"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | "github.com/coming-chat/wallet-SDK/pkg/httpUtil"
10 | )
11 |
12 | type RpcReachability struct {
13 | }
14 |
15 | func NewRpcReachability() *RpcReachability {
16 | return &RpcReachability{}
17 | }
18 |
19 | // @return latency (ms) of rpc query blockNumber. -1 means the connection failed.
20 | func (r *RpcReachability) LatencyOf(rpc string, timeout int64) (l *base.RpcLatency, err error) {
21 | l = &base.RpcLatency{
22 | RpcUrl: rpc,
23 | Latency: -1,
24 | Height: -1,
25 | }
26 |
27 | timeStart := time.Now() // Time Start
28 | params := httpUtil.RequestParams{
29 | Header: map[string]string{"Content-Type": "application/json"},
30 | Body: []byte(`{"jsonrpc":"2.0","method":"chain_getHeader","id":13}`),
31 | Timeout: time.Duration(timeout * int64(time.Millisecond)),
32 | }
33 | response, err := httpUtil.Post(rpc, params)
34 | if err != nil {
35 | return l, err
36 | }
37 |
38 | model := struct {
39 | Result struct {
40 | Number string `json:"number"`
41 | } `json:"result"`
42 | }{}
43 | err = json.Unmarshal(response, &model)
44 | if err != nil {
45 | return l, err
46 | }
47 | heightInt, ok := big.NewInt(0).SetString(model.Result.Number, 0)
48 | if !ok {
49 | heightInt = big.NewInt(0)
50 | }
51 | timeCost := time.Since(timeStart) // Time End
52 |
53 | l.Height = heightInt.Int64()
54 | l.Latency = timeCost.Milliseconds()
55 | return l, nil
56 | }
57 |
--------------------------------------------------------------------------------
/core/polka/rpc_reachability_test.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | )
10 |
11 | type dddd struct {
12 | }
13 |
14 | func (d *dddd) ReachabilityDidReceiveNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
15 | fmt.Printf(".... delegate did receive height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
16 | }
17 |
18 | func (d *dddd) ReachabilityDidFailNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
19 | fmt.Printf(".... delegate did fail height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
20 | // tester.StopConnectivity()
21 | }
22 |
23 | func (d *dddd) ReachabilityDidFinish(tester *base.ReachMonitor, overview string) {
24 | fmt.Printf(".... delegate did finish %v\n", overview)
25 | }
26 |
27 | func TestRpcReachability_Test(t *testing.T) {
28 | reach := NewRpcReachability()
29 | monitor := base.NewReachMonitorWithReachability(reach)
30 | monitor.ReachCount = 3
31 | monitor.Delay = 3000
32 | monitor.Timeout = 1500
33 | t.Log(reach)
34 |
35 | rpcUrls := []string{rpcs.polkadot.url, rpcs.chainxProd.url}
36 | rpcListString := strings.Join(rpcUrls, ",")
37 | // res := reach.StartConnectivitySync(rpcListString)
38 | // t.Log(res)
39 |
40 | delegate := &dddd{}
41 | monitor.StartConnectivityDelegate(rpcListString, delegate)
42 | }
43 |
--------------------------------------------------------------------------------
/core/polka/token.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/base"
7 | )
8 |
9 | type Token struct {
10 | chain *Chain
11 | }
12 |
13 | func NewToken(chain *Chain) *Token {
14 | return &Token{chain}
15 | }
16 |
17 | // MARK - Implement the protocol Token
18 |
19 | func (t *Token) Chain() base.Chain {
20 | return t.chain
21 | }
22 |
23 | // Warning: polka chain is not currently supported
24 | func (t *Token) TokenInfo() (*base.TokenInfo, error) {
25 | return nil, errors.New("Polka chain is not currently supported")
26 | }
27 |
28 | func (t *Token) BalanceOfAddress(address string) (*base.Balance, error) {
29 | return t.chain.BalanceOfAddress(address)
30 | }
31 | func (t *Token) BalanceOfPublicKey(publicKey string) (*base.Balance, error) {
32 | return t.chain.BalanceOfPublicKey(publicKey)
33 | }
34 | func (t *Token) BalanceOfAccount(account base.Account) (*base.Balance, error) {
35 | return t.chain.BalanceOfAccount(account)
36 | }
37 |
38 | func (t *Token) BuildTransfer(sender, receiver, amount string) (txn base.Transaction, err error) {
39 | return nil, base.ErrUnsupportedFunction
40 | }
41 | func (t *Token) CanTransferAll() bool {
42 | return false
43 | }
44 | func (t *Token) BuildTransferAll(sender, receiver string) (txn base.Transaction, err error) {
45 | return nil, base.ErrUnsupportedFunction
46 | }
47 |
--------------------------------------------------------------------------------
/core/polka/utils.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import (
4 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
5 | "github.com/itering/subscan/util/base58"
6 | "github.com/itering/subscan/util/ss58"
7 | )
8 |
9 | func addressStringToMultiAddress(dest string) (types.MultiAddress, error) {
10 | ss58Format := base58.Decode(dest)
11 | if len(ss58Format) == 0 {
12 | return types.MultiAddress{}, ErrAddress
13 | }
14 | destPublicKey := ss58.Decode(dest, int(ss58Format[0]))
15 | if len(destPublicKey) == 0 {
16 | return types.MultiAddress{}, ErrAddress
17 | }
18 | return types.NewMultiAddressFromHexAccountID(destPublicKey)
19 | }
20 |
21 | func addressStringToAddress(dest string) (types.Address, error) {
22 | ss58Format := base58.Decode(dest)
23 | if len(ss58Format) == 0 {
24 | return types.Address{}, ErrAddress
25 | }
26 | destPublicKey := ss58.Decode(dest, int(ss58Format[0]))
27 | if len(destPublicKey) == 0 {
28 | return types.Address{}, ErrAddress
29 | }
30 | return types.NewAddressFromHexAccountID(destPublicKey)
31 | }
32 |
33 | func ByteToHex(data []byte) string {
34 | return types.HexEncodeToString(data)
35 | }
36 |
37 | func HexToByte(hex string) ([]byte, error) {
38 | return types.HexDecodeString(hex)
39 | }
40 |
--------------------------------------------------------------------------------
/core/polka/xbtc_test.go:
--------------------------------------------------------------------------------
1 | package polka
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestXBTCToken_BalanceOfAddress(t1 *testing.T) {
8 | tests := []struct {
9 | name string
10 | rpcInfo rpcInfo
11 | address string
12 | wantErr bool
13 | }{
14 | {
15 | name: "prod empty",
16 | rpcInfo: rpcs.chainxProd,
17 | address: accountCase.address44,
18 | },
19 | {
20 | name: "prod normal",
21 | rpcInfo: rpcs.chainxProd,
22 | address: "5QUEkPdwqUCdnTb7C89ay46kp7zKN4gwbaJ4cEF7caE58MKn",
23 | },
24 | {
25 | name: "test normal",
26 | rpcInfo: rpcs.chainxTest,
27 | address: "5Qeua99vLDXrv9THQyM3Rtcq95oadoXfktFsAhVggiwxTNfZ",
28 | },
29 | {
30 | name: "use XBTC at error chain",
31 | rpcInfo: rpcs.sherpaxProd,
32 | address: accountCase.address44,
33 | wantErr: true,
34 | },
35 | }
36 | for _, tt := range tests {
37 | t1.Run(tt.name, func(t1 *testing.T) {
38 | chain, _ := tt.rpcInfo.Chain()
39 | t := chain.XBTCToken()
40 | got, err := t.BalanceOfAddress(tt.address)
41 | if (err != nil) != tt.wantErr {
42 | t1.Errorf("BalanceOfAddress() error = %v, wantErr %v", err, tt.wantErr)
43 | return
44 | }
45 | if err == nil {
46 | url := tt.rpcInfo.realScan + "/account/" + tt.address
47 | t1.Log("BalanceOfAddress() result: ", got, ", Maybe you should verify via the link: ", url)
48 | }
49 | })
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/core/solana/address_util.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | import (
4 | "github.com/blocto/solana-go-sdk/common"
5 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
6 | )
7 |
8 | type Util struct {
9 | }
10 |
11 | func NewUtil() *Util {
12 | return &Util{}
13 | }
14 |
15 | // MARK - Implement the protocol wallet.Util
16 |
17 | func (u *Util) EncodePublicKeyToAddress(publicKey string) (string, error) {
18 | return EncodePublicKeyToAddress(publicKey)
19 | }
20 |
21 | // Warning: eth cannot support decode address to public key
22 | func (u *Util) DecodeAddressToPublicKey(address string) (string, error) {
23 | return DecodeAddressToPublicKey(address)
24 | }
25 |
26 | // Check if address is 40 hexadecimal characters
27 | func (u *Util) IsValidAddress(address string) bool {
28 | return IsValidAddress(address)
29 | }
30 |
31 | // MARK - like wallet.Util
32 |
33 | func EncodePublicKeyToAddress(publicKey string) (string, error) {
34 | bytes, err := types.HexDecodeString(publicKey)
35 | if err != nil {
36 | return "", err
37 | }
38 | pubKey := common.PublicKeyFromBytes(bytes)
39 | return pubKey.ToBase58(), nil
40 | }
41 |
42 | func DecodeAddressToPublicKey(address string) (string, error) {
43 | pubKey := common.PublicKeyFromString(address)
44 | return types.HexEncodeToString(pubKey.Bytes()), nil
45 | }
46 |
47 | func IsValidAddress(address string) bool {
48 | if len(address) != 44 {
49 | return false
50 | }
51 | pub := common.PublicKeyFromString(address)
52 | addr2 := pub.ToBase58()
53 | return address == addr2
54 | }
55 |
--------------------------------------------------------------------------------
/core/solana/error.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | import "errors"
4 |
5 | var (
6 | ErrNoTokenAccount = errors.New("the owner has not created the token account")
7 | )
8 |
9 | func IsNoTokenAccountError(err error) bool {
10 | return err.Error() == ErrNoTokenAccount.Error()
11 | }
12 |
--------------------------------------------------------------------------------
/core/solana/interface_test.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Token)(nil)
11 | _ base.Transaction = (*Transaction)(nil)
12 | _ base.SignedTransaction = (*SignedTransaction)(nil)
13 |
14 | _ base.Token = (*SPLToken)(nil)
15 | )
16 |
--------------------------------------------------------------------------------
/core/solana/rpc_reachability.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | import (
4 | "encoding/json"
5 | "time"
6 |
7 | "github.com/coming-chat/wallet-SDK/core/base"
8 | "github.com/coming-chat/wallet-SDK/pkg/httpUtil"
9 | )
10 |
11 | type RpcReachability struct {
12 | }
13 |
14 | func NewRpcReachability() *RpcReachability {
15 | return &RpcReachability{}
16 | }
17 |
18 | // @return latency (ms) of rpc query blockNumber. -1 means the connection failed.
19 | func (r *RpcReachability) LatencyOf(rpc string, timeout int64) (l *base.RpcLatency, err error) {
20 | l = &base.RpcLatency{
21 | RpcUrl: rpc,
22 | Latency: -1,
23 | Height: -1,
24 | }
25 |
26 | timeStart := time.Now() // Time Start
27 | params := httpUtil.RequestParams{
28 | Header: map[string]string{"Content-Type": "application/json"},
29 | Body: []byte(`{"jsonrpc":"2.0","method":"getBlockHeight","id":13}`),
30 | Timeout: time.Duration(timeout * int64(time.Millisecond)),
31 | }
32 | response, err := httpUtil.Post(rpc, params)
33 | if err != nil {
34 | return l, err
35 | }
36 |
37 | model := struct {
38 | Result int64 `json:"result"`
39 | }{}
40 | err = json.Unmarshal(response, &model)
41 | if err != nil {
42 | return l, err
43 | }
44 | timeCost := time.Since(timeStart) // Time End
45 |
46 | l.Height = model.Result
47 | l.Latency = timeCost.Milliseconds()
48 | return l, nil
49 | }
50 |
--------------------------------------------------------------------------------
/core/solana/rpc_reachability_test.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | )
10 |
11 | type dddd struct {
12 | }
13 |
14 | func (d *dddd) ReachabilityDidReceiveNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
15 | fmt.Printf(".... delegate did receive height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
16 | }
17 |
18 | func (d *dddd) ReachabilityDidFailNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
19 | fmt.Printf(".... delegate did fail height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
20 | // tester.StopConnectivity()
21 | }
22 |
23 | func (d *dddd) ReachabilityDidFinish(tester *base.ReachMonitor, overview string) {
24 | fmt.Printf(".... delegate did finish %v\n", overview)
25 | }
26 |
27 | func TestRpcReachability_Test(t *testing.T) {
28 | reach := NewRpcReachability()
29 | monitor := base.NewReachMonitorWithReachability(reach)
30 | monitor.ReachCount = 3
31 | monitor.Delay = 3000
32 | monitor.Timeout = 1500
33 | t.Log(reach)
34 |
35 | rpcUrls := []string{
36 | MainnetRPCEndpoint,
37 | TestnetRPCEndpoint,
38 | }
39 | rpcListString := strings.Join(rpcUrls, ",")
40 | // res := reach.StartConnectivitySync(rpcListString)
41 | // t.Log(res)
42 |
43 | delegate := &dddd{}
44 | monitor.StartConnectivityDelegate(rpcListString, delegate)
45 | }
46 |
--------------------------------------------------------------------------------
/core/solana/spltoken_test.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | const aTokenAddress = "38GJtbJkJJQKkR6CTZwwok4hAXoKnByU2MFAZjgTe114"
10 |
11 | func TestSPLToken_GetBalance(t *testing.T) {
12 | chain := DevnetChain()
13 | token, err := NewSPLToken(chain, "38GJtbJkJJQKkR6CTZwwok4hAXoKnByU2MFAZjgTe114")
14 | require.Nil(t, err)
15 |
16 | balance, err := token.BalanceOfAddress("4MPScMzmKwQfpzQ4MtkSaqKQbTEzGsWqovUMweNz7nFo")
17 | require.Nil(t, err)
18 | t.Log(balance)
19 | }
20 |
21 | func TestSPLToken_CreateTokenAccount(t *testing.T) {
22 | signer := M1Account(t)
23 | owner := "9HquD8jfJNm1wKuVEbeYWCr792Yt9D9zj17i1rgzC7t4"
24 |
25 | chain := DevnetChain()
26 | token, err := NewSPLToken(chain, "5PtchJqBwDiAJnvoHKYRKhXYmgurAJygj4WSL9C9xghJ")
27 | require.Nil(t, err)
28 |
29 | txn, err := token.CreateTokenAccount(owner, signer.Address())
30 | require.Nil(t, err)
31 |
32 | fee, err := chain.EstimateTransactionFee(txn)
33 | require.Nil(t, err)
34 | t.Log(fee.Value)
35 |
36 | signedTxn, err := txn.SignedTransactionWithAccount(signer)
37 | require.Nil(t, err)
38 |
39 | if false {
40 | txhash, err := chain.SendSignedTransaction(signedTxn)
41 | require.Nil(t, err)
42 | t.Log("create token account for other user success, hash = ", txhash)
43 | }
44 | }
45 |
46 | func TestSPLToken_BuildTransfer(t *testing.T) {
47 | account := M1Account(t)
48 | chain := DevnetChain()
49 |
50 | mintAddr := "38GJtbJkJJQKkR6CTZwwok4hAXoKnByU2MFAZjgTe114"
51 | token, err := NewSPLToken(chain, mintAddr)
52 | require.Nil(t, err)
53 |
54 | receiverAddr := "AfBfH4ehvcXx66Y5YZozgTYPC1nieL9A3r2yT3vCXqPY"
55 | txn, err := token.BuildTransfer(account.Address(), receiverAddr, "100")
56 | // txn, err := token.BuildTransferAll(account.Address(), receiverAddr)
57 | require.Nil(t, err)
58 |
59 | fee, err := chain.EstimateTransactionFee(txn)
60 | require.Nil(t, err)
61 | t.Log("estimate transaction fee = ", fee.Value)
62 |
63 | signedTxn, err := txn.SignedTransactionWithAccount(account)
64 | require.Nil(t, err)
65 | if false {
66 | txhash, err := chain.SendSignedTransaction(signedTxn)
67 | require.Nil(t, err)
68 | t.Log("transfer success hash = ", txhash)
69 | }
70 | }
71 |
72 | func TestSPLToken_TokenInfo(t *testing.T) {
73 | chain := MainnetChain()
74 | token, err := NewSPLToken(chain, "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB") // USDT
75 | require.Nil(t, err)
76 | info, err := token.TokenInfo()
77 | require.Nil(t, err)
78 | t.Log(info)
79 |
80 | chain = DevnetChain()
81 | token, err = NewSPLToken(chain, "38GJtbJkJJQKkR6CTZwwok4hAXoKnByU2MFAZjgTe114")
82 | require.Nil(t, err)
83 | info, err = token.TokenInfo()
84 | require.Nil(t, err)
85 | t.Log(info)
86 | }
87 |
--------------------------------------------------------------------------------
/core/solana/token_test.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/testcase"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func SOL(amount float64) testcase.Amount {
11 | return testcase.Amount{Amount: amount, Multiple: 1e9}
12 | }
13 |
14 | func TestToken_BuildTransfer_SignedTransaction(t *testing.T) {
15 | account := M1Account(t)
16 | chain := TestnetChain()
17 | token := chain.MainToken()
18 |
19 | balance, err := token.BalanceOfAddress(account.Address())
20 | require.Nil(t, err)
21 | t.Log("sender address = ", account.Address())
22 | t.Log("balance = ", balance.Usable)
23 |
24 | txn, err := token.BuildTransfer(account.Address(), account.Address(), "100")
25 | require.Nil(t, err)
26 |
27 | gasfee, err := chain.EstimateTransactionFeeUsePublicKey(txn, account.PublicKeyHex())
28 | require.Nil(t, err)
29 | t.Log("Estimate fee = ", gasfee.Value)
30 |
31 | signedTxn, err := txn.SignedTransactionWithAccount(account)
32 | require.Nil(t, err)
33 |
34 | if false {
35 | hash, err := chain.SendSignedTransaction(signedTxn)
36 | require.Nil(t, err)
37 | t.Log("Transaction hash = ", hash.Value)
38 | }
39 | }
40 |
41 | func TestTransaction_New(t *testing.T) {
42 | raw := "GuxUPtW1ioB6PShC7aBtjuVqHXzHZzE9Xo3j81FmDSNs23C4tv6tGXih6sgaVdzCnKfv5sLL5LcFn19Y5CHTnZSuDD1byvEvisjqvCNoh4APExH6baTNZatEy8AQMBpXPA74n71fXwKAGQCkcYeqDTnoWkfvkpM7iWe16rFkFkKC241cMhNs6hZAjVJpeHeBVMJSLmvVmkJgaSGdia6wMdcosAfTC84CgbAEknuvpSERBEuTz5MwPw3dVirQiShp42xvMNkYodjQkgdf6qLywBteSAaq6zHHbScQi5eQL991QBgdHy6e49NEyK7E1RVhNqM3jHNWu6gQM4qf9VMtdzdoG3VRDSKoGYq7mxJjozfxk3orNuTjPASo6ixQX163W11zR3fHw28hmc3RxxwKZ1jjhQgbAFZmNoJUVqxxjWLYKjEjx7sizWoDA9UzryZQ7BVkdB4j2VhUzu78T5yYZmLzPkgKE84yRhxMqcHC6BnKTRoD4zyz8S4cdRM4Rz2aLUx339S9NFcdonj8wW1EByPHuFsPamvoLKgA2ecMxkaaJxsSMDpz73ppBfFjCFphnEHQwKgkY5KJYZHu26T1qMr6a9Q3koumYmNKy2gHDNnHvHGKk5xnfXQtPPmrur41tBHbRuH9BnWUYniNLxjZVVQU9MvGJdvPicphjnVSrc2onEYHYC29b5QXgQZGSUjAVFp5L3t7qnZ86SwtjmPris96h5NzTLTaCq3fo7Qr4aVGYF8MSbiyhduTeLhyAZczMrWbYtLpQBkbZNxs2BtHjHSLuxmbAcxEbniJQ3Gjf5LFDHvFPbKpJeoLYXhA8mwQizP7xTSi6v33J5nYkF2BmhneCXh8aME92NZVrZ8BDaEWHfGvhfmmqAdxwg3zwkRzpzGVZUx4ixiJMA3fFhziFs4VkXpdWug4hsRVuhRT1uMGh3yn5jcvhE7jgUjXwusD53tkzq7faotpkA1UWJeRNrzYJprHfstfnR6Kh8Nk1FgwjZqkj7bzDn6ftVGN2abLQsaMbi4XN3iYnJULYDERMAHwkobJW8apt5pobHApSLwJg6pQZbw2EpBN6N2zDrGSRetMiKjdq1zhU3jU7Aws1EjnSdbWKqxJ8j5KGJ8AUeTU9L2x9mLVBBdr4c18WkXfzJ173p6WUkpbeTtKvrS2XCZsaMN3wKV3zdUT9eWcQ5QQHoeDiKVeQjtiWctFZrLs5ZAshHUEJgbLorqw1zusL5vmwAZLonzQUc5C683P5v9AcSDG6D3Hjn235fNmfXaxYEeAc9NsXzB7RLFqk469TYteG1gHrBY9N5yGf5bTkZmiN4o12UssMvHGvJHefJbtH3iXQjWaPhYUzVPGPgq23CS3FVKDpk"
43 | txn, err := NewTransaction(raw)
44 | require.Nil(t, err)
45 | require.Equal(t, txn.Message.RecentBlockHash, "BXhGCzgqK32G6wZJtzBbMUn3HxZy5nAQyJ8CvuQ9jq8x")
46 | t.Log(txn)
47 | }
48 |
--------------------------------------------------------------------------------
/core/solana/transaction.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | import (
4 | "encoding/hex"
5 |
6 | "github.com/blocto/solana-go-sdk/types"
7 | "github.com/coming-chat/wallet-SDK/core/base"
8 | "github.com/mr-tron/base58"
9 | )
10 |
11 | type Transaction struct {
12 | Message types.Message
13 | }
14 |
15 | func NewTransaction(rawBase58String string) (*Transaction, error) {
16 | data, err := base58.Decode(rawBase58String)
17 | if err != nil {
18 | return nil, err
19 | }
20 | msg, err := types.MessageDeserialize(data)
21 | if err != nil {
22 | return nil, err
23 | }
24 | return &Transaction{Message: msg}, nil
25 | }
26 |
27 | func (t *Transaction) SignWithAccount(account base.Account) (signedTxn *base.OptionalString, err error) {
28 | txn, err := t.SignedTransactionWithAccount(account)
29 | if err != nil {
30 | return nil, err
31 | }
32 | return txn.HexString()
33 | }
34 |
35 | func (t *Transaction) SignedTransactionWithAccount(account base.Account) (signedTxn base.SignedTransaction, err error) {
36 | solanaAcc := AsSolanaAccount(account)
37 | if solanaAcc == nil {
38 | return nil, base.ErrInvalidAccountType
39 | }
40 |
41 | // create tx by message + signer
42 | txn, err := types.NewTransaction(types.NewTransactionParam{
43 | Message: t.Message,
44 | Signers: []types.Account{*solanaAcc.account},
45 | })
46 | if err != nil {
47 | return nil, err
48 | }
49 | return &SignedTransaction{
50 | Transaction: txn,
51 | }, nil
52 | }
53 |
54 | type SignedTransaction struct {
55 | Transaction types.Transaction
56 | }
57 |
58 | func (txn *SignedTransaction) HexString() (res *base.OptionalString, err error) {
59 | txnBytes, err := txn.Transaction.Serialize()
60 | if err != nil {
61 | return nil, err
62 | }
63 | hexString := "0x" + hex.EncodeToString(txnBytes)
64 |
65 | return &base.OptionalString{Value: hexString}, nil
66 | }
67 |
68 | func AsSignedTransaction(txn base.SignedTransaction) *SignedTransaction {
69 | if res, ok := txn.(*SignedTransaction); ok {
70 | return res
71 | }
72 | return nil
73 | }
74 |
--------------------------------------------------------------------------------
/core/starcoin/account_test.go:
--------------------------------------------------------------------------------
1 | package starcoin
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/testcase"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestAccount(t *testing.T) {
11 | mnemonic := testcase.M1
12 | account, err := NewAccountWithMnemonic(mnemonic)
13 | require.Nil(t, err)
14 |
15 | // private key hex to account
16 | priHex, err := account.PrivateKeyHex()
17 | require.Nil(t, err)
18 | acc2, err := AccountWithPrivateKey(priHex)
19 | require.Nil(t, err)
20 | require.Equal(t, account.address, acc2.address)
21 |
22 | // public key hex to address
23 | pubHex := account.PublicKeyHex()
24 | addr2, err := EncodePublicKeyToAddress(pubHex)
25 | require.Nil(t, err)
26 | require.Equal(t, addr2, account.address)
27 | }
28 |
29 | func M1Account(t *testing.T) *Account {
30 | account, err := NewAccountWithMnemonic(testcase.M1)
31 | require.Nil(t, err)
32 | return account
33 | }
34 |
35 | func M2Account(t *testing.T) *Account {
36 | account, err := NewAccountWithMnemonic(testcase.M2)
37 | require.Nil(t, err)
38 | return account
39 | }
40 |
41 | func TestAccountWithPrivatekey(t *testing.T) {
42 | mnemonic := testcase.M1
43 | accountFromMnemonic, err := NewAccountWithMnemonic(mnemonic)
44 | require.Nil(t, err)
45 | privateKey, err := accountFromMnemonic.PrivateKeyHex()
46 | require.Nil(t, err)
47 |
48 | accountFromPrikey, err := AccountWithPrivateKey(privateKey)
49 | require.Nil(t, err)
50 |
51 | require.Equal(t, accountFromMnemonic.Address(), accountFromPrikey.Address())
52 | }
53 |
--------------------------------------------------------------------------------
/core/starcoin/address_util.go:
--------------------------------------------------------------------------------
1 | package starcoin
2 |
3 | import (
4 | "encoding/hex"
5 | "errors"
6 | "regexp"
7 |
8 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
9 | "github.com/coming-chat/wallet-SDK/core/eth"
10 | "golang.org/x/crypto/sha3"
11 | )
12 |
13 | type Util struct {
14 | }
15 |
16 | func NewUtil() (*Util, error) {
17 | return &Util{}, nil
18 | }
19 |
20 | // MARK - Implement the protocol Util
21 |
22 | // @param publicKey can start with 0x or not.
23 | func (u *Util) EncodePublicKeyToAddress(publicKey string) (string, error) {
24 | return EncodePublicKeyToAddress(publicKey)
25 | }
26 |
27 | // Warning: Starcoin cannot support decode address to public key
28 | func (u *Util) DecodeAddressToPublicKey(address string) (string, error) {
29 | return DecodeAddressToPublicKey(address)
30 | }
31 |
32 | func (u *Util) IsValidAddress(address string) bool {
33 | return IsValidAddress(address)
34 | }
35 |
36 | // MARK - like Util
37 |
38 | // @param publicKey can start with 0x or not.
39 | func EncodePublicKeyToAddress(publicKey string) (string, error) {
40 | publicBytes, err := types.HexDecodeString(publicKey)
41 | if err != nil {
42 | return "", err
43 | }
44 | publicBytes = append(publicBytes, 0x00)
45 | authKey := sha3.Sum256(publicBytes)
46 | address := hex.EncodeToString(authKey[len(authKey)-addressLength:])
47 | address = eth.TransformEIP55Address(address)
48 | return address, nil
49 | }
50 |
51 | func DecodeAddressToPublicKey(address string) (string, error) {
52 | return "", errors.New("Starcoin cannot support decode address to public key")
53 | }
54 |
55 | // @param chainnet chain name
56 | func IsValidAddress(address string) bool {
57 | reg := regexp.MustCompile(`^(0x|0X)?[0-9a-fA-F]{32}$`)
58 | return reg.MatchString(address)
59 | }
60 |
--------------------------------------------------------------------------------
/core/starcoin/chain_test.go:
--------------------------------------------------------------------------------
1 | package starcoin
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | const (
10 | BarnardRpcUrl = "https://barnard-seed.starcoin.org"
11 | MainRpcUrl = "https://main-seed.starcoin.org"
12 | )
13 |
14 | func BarnardChain(t *testing.T) *Chain {
15 | return NewChainWithRpc(BarnardRpcUrl)
16 | }
17 |
18 | func TestTransactionDetail(t *testing.T) {
19 | chain := BarnardChain(t)
20 |
21 | hash := "0x5a16ec93f46d137be51d93df374426cb212b2bb04dbe476cbb8a6b72385291fc"
22 | // hash := "0x098045bd1f817f37f7759c7f0e1dc4a6d6e0a5f33a8e0748cd8928653ed6a31a"
23 |
24 | detail, err := chain.FetchTransactionDetail(hash)
25 | require.Nil(t, err)
26 |
27 | t.Log(detail)
28 | }
29 |
--------------------------------------------------------------------------------
/core/starcoin/interface_test.go:
--------------------------------------------------------------------------------
1 | package starcoin
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Token)(nil)
11 | // _ base.Transaction = (*Transaction)(nil)
12 | )
13 |
--------------------------------------------------------------------------------
/core/starcoin/token_test.go:
--------------------------------------------------------------------------------
1 | package starcoin
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestBalance(t *testing.T) {
10 | chain := BarnardChain(t)
11 | token := chain.MainToken()
12 | account := M1Account(t)
13 |
14 | balance, err := token.BalanceOfAddress(account.Address())
15 | require.Nil(t, err)
16 |
17 | t.Log(balance)
18 | }
19 |
20 | func TestTransfer(t *testing.T) {
21 | chain := BarnardChain(t)
22 | token := NewMainToken(chain)
23 |
24 | account1 := M1Account(t)
25 | receiver := account1
26 | amount := "10000"
27 |
28 | rawTx, err := token.BuildTransferTxWithAccount(account1, receiver.Address(), amount)
29 | require.Nil(t, err)
30 |
31 | hash, err := chain.SendRawTransaction(rawTx.Value)
32 | require.Nil(t, err)
33 |
34 | t.Log(hash)
35 | }
36 |
37 | func TestEstimateFee(t *testing.T) {
38 | chain := BarnardChain(t)
39 | token := NewMainToken(chain)
40 | account := M1Account(t)
41 | gasFee, err := token.EstimateFees(account, account.Address(), "100")
42 | require.Nil(t, err)
43 | t.Log("gasfee = ", gasFee.Value)
44 | }
45 |
46 | func TestToken_BuildTransfer_SignedTransaction(t *testing.T) {
47 | account := M1Account(t)
48 | chain := BarnardChain(t)
49 | token := chain.MainToken()
50 |
51 | balance, err := token.BalanceOfAddress(account.Address())
52 | require.Nil(t, err)
53 | t.Log("sender address = ", account.Address())
54 | t.Log("balance = ", balance.Usable)
55 |
56 | txn, err := token.BuildTransfer(account.Address(), account.Address(), "100")
57 | require.Nil(t, err)
58 |
59 | gasfee, err := chain.EstimateTransactionFeeUsePublicKey(txn, account.PublicKeyHex())
60 | require.Nil(t, err)
61 | t.Log("Estimate fee = ", gasfee.Value)
62 |
63 | signedTxn, err := txn.SignedTransactionWithAccount(account)
64 | require.Nil(t, err)
65 |
66 | if false {
67 | hash, err := chain.SendSignedTransaction(signedTxn)
68 | require.Nil(t, err)
69 | t.Log("Transaction hash = ", hash.Value)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/core/starcoin/transaction.go:
--------------------------------------------------------------------------------
1 | package starcoin
2 |
3 | import (
4 | "encoding/hex"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/base"
7 | "github.com/starcoinorg/starcoin-go/client"
8 | "github.com/starcoinorg/starcoin-go/types"
9 | )
10 |
11 | type Transaction struct {
12 | Txn *types.RawUserTransaction
13 | }
14 |
15 | func (t *Transaction) SignWithAccount(account base.Account) (signedTxn *base.OptionalString, err error) {
16 | txn, err := t.SignedTransactionWithAccount(account)
17 | if err != nil {
18 | return nil, err
19 | }
20 | return txn.HexString()
21 | }
22 |
23 | func (t *Transaction) SignedTransactionWithAccount(account base.Account) (signedTxn base.SignedTransaction, err error) {
24 | starcoinAcc := AsStarcoinAccount(account)
25 | if starcoinAcc == nil {
26 | return nil, base.ErrInvalidAccountType
27 | }
28 |
29 | privateKey, err := account.PrivateKey()
30 | if err != nil {
31 | return
32 | }
33 | txn, err := client.SignRawUserTransaction(types.Ed25519PrivateKey(privateKey), t.Txn)
34 | if err != nil {
35 | return
36 | }
37 | return &SignedTransaction{
38 | Txn: txn,
39 | }, nil
40 | }
41 |
42 | type SignedTransaction struct {
43 | Txn *types.SignedUserTransaction
44 | }
45 |
46 | func (txn *SignedTransaction) HexString() (res *base.OptionalString, err error) {
47 | txnBytes, err := txn.Txn.BcsSerialize()
48 | if err != nil {
49 | return nil, err
50 | }
51 | hexString := "0x" + hex.EncodeToString(txnBytes)
52 |
53 | return &base.OptionalString{Value: hexString}, nil
54 | }
55 |
56 | func AsSignedTransaction(txn base.SignedTransaction) *SignedTransaction {
57 | if res, ok := txn.(*SignedTransaction); ok {
58 | return res
59 | }
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/core/starcoin/util.go:
--------------------------------------------------------------------------------
1 | package starcoin
2 |
3 | import (
4 | "encoding/hex"
5 | "errors"
6 | "fmt"
7 | "math/big"
8 | "strings"
9 |
10 | "github.com/novifinancial/serde-reflection/serde-generate/runtime/golang/serde"
11 | "github.com/starcoinorg/starcoin-go/types"
12 | )
13 |
14 | func NewStructTag(tag string) (*types.StructTag, error) {
15 | if strings.Contains(tag, "<") {
16 | return nil, errors.New("Not implemented")
17 | }
18 |
19 | parts := strings.Split(tag, "::")
20 | if len(parts) != 3 {
21 | return nil, errors.New("Invalid struct tag string literal.")
22 | }
23 | addr, err := NewAccountAddressFromHex(parts[0])
24 | if err != nil {
25 | return nil, err
26 | }
27 | return &types.StructTag{
28 | Address: *addr,
29 | Module: types.Identifier(parts[1]),
30 | Name: types.Identifier(parts[2]),
31 | }, nil
32 | }
33 |
34 | func NewAccountAddressFromHex(addr string) (*types.AccountAddress, error) {
35 | if strings.HasPrefix(addr, "0x") || strings.HasPrefix(addr, "0X") {
36 | addr = addr[2:]
37 | }
38 | if len(addr)%2 != 0 {
39 | addr = "0" + addr
40 | }
41 |
42 | bytes, err := hex.DecodeString(addr)
43 | if err != nil {
44 | return nil, err
45 | }
46 | if len(bytes) > addressLength {
47 | return nil, fmt.Errorf("Hex string is too long. Address's length is %v bytes.", addressLength)
48 | }
49 |
50 | res := types.AccountAddress{}
51 | copy(res[addressLength-len(bytes):], bytes[:])
52 | return &res, nil
53 | }
54 |
55 | func StructTagToString(t types.StructTag) string {
56 | return fmt.Sprintf("0x%v::%v::%v", hex.EncodeToString(t.Address[:]), t.Module, t.Name)
57 | }
58 |
59 | func NewU128FromString(number string) (*serde.Uint128, error) {
60 | n, ok := big.NewInt(0).SetString(number, 10)
61 | if !ok {
62 | return nil, errors.New("Invalid U128: not a number")
63 | }
64 | if n.Sign() < 0 {
65 | return nil, errors.New("Invalid U128: negative number")
66 | }
67 | bitLen := n.BitLen()
68 | if bitLen > 128 {
69 | return nil, errors.New("Invalid U128: too large number")
70 | }
71 | res := &serde.Uint128{}
72 | res.Low = n.Uint64()
73 | if bitLen > 64 {
74 | n = n.Rsh(n, 64)
75 | res.High = n.Uint64()
76 | }
77 | return res, nil
78 | }
79 |
--------------------------------------------------------------------------------
/core/starcoin/util_test.go:
--------------------------------------------------------------------------------
1 | package starcoin
2 |
3 | import (
4 | "math/big"
5 | "testing"
6 |
7 | "github.com/starcoinorg/starcoin-go/client"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestUint128(t *testing.T) {
12 | number := "1234567890987654321"
13 | numInt, _ := big.NewInt(0).SetString(number, 10)
14 |
15 | sdkU128, _ := client.BigIntToU128(numInt)
16 | sdkRestoreInt := client.U128ToBigInt(sdkU128)
17 | t.Log(sdkRestoreInt.String())
18 |
19 | myU128, err := NewU128FromString(number)
20 | myRestoreInt := client.U128ToBigInt(myU128)
21 | t.Log(myRestoreInt.String())
22 | if err != nil {
23 | t.Log(err)
24 | return
25 | }
26 | require.Equal(t, numInt, myRestoreInt)
27 | }
28 |
--------------------------------------------------------------------------------
/core/starknet/error.go:
--------------------------------------------------------------------------------
1 | package starknet
2 |
3 | import "regexp"
4 |
5 | func IsNotDeployedError(err error) bool {
6 | if err == nil {
7 | return false
8 | }
9 | regInsufficientGas := regexp.MustCompile(`.*UNINITIALIZED_CONTRACT.*contract address 0x[0-9a-zA-Z]+ is not deployed.*`)
10 | match := regInsufficientGas.FindAllStringSubmatch(err.Error(), -1)
11 | return len(match) > 0
12 | }
13 |
--------------------------------------------------------------------------------
/core/starknet/interface_test.go:
--------------------------------------------------------------------------------
1 | package starknet
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Token)(nil)
11 | _ base.Transaction = (*Transaction)(nil)
12 |
13 | _ base.Transaction = (*DeployAccountTransaction)(nil)
14 | _ base.SignedTransaction = (*SignedTransaction)(nil)
15 | )
16 |
--------------------------------------------------------------------------------
/core/starknet/transaction.go:
--------------------------------------------------------------------------------
1 | package starknet
2 |
3 | import (
4 | "github.com/NethermindEth/juno/core/felt"
5 | "github.com/coming-chat/wallet-SDK/core/base"
6 | "github.com/xiang-xx/starknet.go/rpc"
7 | )
8 |
9 | type Transaction struct {
10 | txnV1 rpc.InvokeTxnV1
11 | txnHash *felt.Felt
12 | }
13 |
14 | type SignedTransaction struct {
15 | // depoly Txn
16 | depolyTxn *rpc.DeployAccountTxn
17 |
18 | // Do you need to automatically deploy the contract address first when you send the transaction for the first time? default NO
19 | NeedAutoDeploy bool
20 | // invoke Txn
21 | invokeTxn *rpc.InvokeTxnV1
22 | Account *Account
23 | }
24 |
25 | func (t *Transaction) SignWithAccount(account base.Account) (signedTxn *base.OptionalString, err error) {
26 | return nil, base.ErrUnsupportedFunction
27 | }
28 |
29 | func (t *Transaction) SignedTransactionWithAccount(account base.Account) (signedTx base.SignedTransaction, err error) {
30 | starknetAccount := AsStarknetAccount(account)
31 | if starknetAccount == nil {
32 | return nil, base.ErrInvalidAccountType
33 | }
34 | t.txnV1.Signature, err = starknetAccount.SignHash(t.txnHash)
35 | if err != nil {
36 | return
37 | }
38 | return &SignedTransaction{
39 | Account: starknetAccount,
40 | invokeTxn: &t.txnV1,
41 | }, nil
42 | }
43 |
44 | func (txn *SignedTransaction) HexString() (res *base.OptionalString, err error) {
45 | return nil, base.ErrUnsupportedFunction
46 | }
47 |
48 | func (txn *SignedTransaction) ResignInvokeTransaction(chain *Chain, acc *Account) error {
49 | txnHash, err := chain.rpc.TransactionHashInvoke(*txn.invokeTxn)
50 | if err != nil {
51 | return err
52 | }
53 | txn.invokeTxn.Signature, err = acc.SignHash(txnHash)
54 | return err
55 | }
56 |
57 | func AsSignedTransaction(txn base.SignedTransaction) *SignedTransaction {
58 | if res, ok := txn.(*SignedTransaction); ok {
59 | return res
60 | }
61 | return nil
62 | }
63 |
--------------------------------------------------------------------------------
/core/starknet/transaction_deploy.go:
--------------------------------------------------------------------------------
1 | package starknet
2 |
3 | import (
4 | "math/big"
5 |
6 | "github.com/NethermindEth/juno/core/felt"
7 | "github.com/coming-chat/wallet-SDK/core/base"
8 | "github.com/xiang-xx/starknet.go/account"
9 | "github.com/xiang-xx/starknet.go/rpc"
10 | "github.com/xiang-xx/starknet.go/utils"
11 | )
12 |
13 | type DeployAccountTransaction struct {
14 | rpc.DeployAccountTxn
15 | TransactionHash *felt.Felt
16 | }
17 |
18 | func (txn *DeployAccountTransaction) SignWithAccount(account base.Account) (signedTxn *base.OptionalString, err error) {
19 | return nil, base.ErrUnsupportedFunction
20 | }
21 |
22 | func (txn *DeployAccountTransaction) SignedTransactionWithAccount(account base.Account) (signedTx base.SignedTransaction, err error) {
23 | starknetAccount := AsStarknetAccount(account)
24 | if starknetAccount == nil {
25 | return nil, base.ErrInvalidAccountType
26 | }
27 | txn.DeployAccountTxn.Signature, err = starknetAccount.SignHash(txn.TransactionHash)
28 | if err != nil {
29 | return nil, err
30 | }
31 |
32 | return &SignedTransaction{
33 | depolyTxn: &txn.DeployAccountTxn,
34 | }, nil
35 | }
36 |
37 | func NewDeployAccountTransaction(pubkey string, maxFee *big.Int, cli *account.Account, isCairo0 bool) (*DeployAccountTransaction, error) {
38 | pubFelt, err := utils.HexToFelt(pubkey)
39 | if err != nil {
40 | return nil, err
41 | }
42 | param := deployParamForArgentXWithVersion(*pubFelt, isCairo0)
43 | deployTxn := rpc.DeployAccountTxn{
44 | ContractAddressSalt: ¶m.Pubkey,
45 | ClassHash: param.ClassHash,
46 | ConstructorCalldata: param.CallData,
47 |
48 | MaxFee: utils.BigIntToFelt(maxFee),
49 | Version: rpc.TransactionV1,
50 | Type: rpc.TransactionType_DeployAccount,
51 | Nonce: &felt.Zero,
52 | Signature: nil,
53 | }
54 |
55 | contractAddress, err := param.ComputeContractAddress()
56 | if err != nil {
57 | return nil, err
58 | }
59 | txnHash, err := cli.TransactionHashDeployAccount(deployTxn, contractAddress)
60 | if err != nil {
61 | return nil, err
62 | }
63 | return &DeployAccountTransaction{
64 | DeployAccountTxn: deployTxn,
65 | TransactionHash: txnHash,
66 | }, nil
67 | }
68 |
--------------------------------------------------------------------------------
/core/starknet/utils.go:
--------------------------------------------------------------------------------
1 | package starknet
2 |
3 | import (
4 | "encoding/hex"
5 | "math/rand"
6 | "time"
7 |
8 | "github.com/NethermindEth/juno/core/felt"
9 | "github.com/xiang-xx/starknet.go/rpc"
10 | )
11 |
12 | var latestBlockId = rpc.BlockID{Tag: "latest"}
13 |
14 | func mustFelt(str string) *felt.Felt {
15 | f, _ := new(felt.Felt).SetString(str)
16 | return f
17 | }
18 |
19 | func random(max uint64) uint64 {
20 | return rand.New(rand.NewSource(time.Now().UnixNano())).Uint64() % max
21 | }
22 |
23 | // fullString
24 | func fullString(felt felt.Felt) string {
25 | bytes := felt.Bytes()
26 | return "0x" + hex.EncodeToString(bytes[:])
27 | }
28 |
--------------------------------------------------------------------------------
/core/substrate/err.go:
--------------------------------------------------------------------------------
1 | package wallet
2 |
3 | import "errors"
4 |
5 | var (
6 | ErrNilKey = errors.New("no mnemonic or private key")
7 | ErrNilWallet = errors.New("no mnemonic or private key or keystore")
8 | ErrNilKeystore = errors.New("no keystore")
9 | ErrNilMetadata = errors.New("no metadata")
10 | ErrNotSigned = errors.New("transaction not signed")
11 | ErrNoPublicKey = errors.New("transaction no public key")
12 | ErrNilExtrinsic = errors.New("nil extrinsic")
13 | ErrAddress = errors.New("err address")
14 | ErrPublicKey = errors.New("err publicKey")
15 | ErrSeedOrPhrase = errors.New("invalid seed length")
16 |
17 | ErrNoEncrypted = errors.New("no encrypted data to decode")
18 | ErrEncryptedLength = errors.New("encrypted length is less than 24")
19 | ErrInvalidParams = errors.New("invalid injected scrypt params found")
20 | ErrSecretLength = errors.New("secret length is not 32")
21 | ErrEncoded = errors.New("encoded is nil")
22 | ErrPkcs8Header = errors.New("invalid Pkcs8 header found in body")
23 | ErrPkcs8Divider = errors.New("invalid Pkcs8 divider found in body")
24 |
25 | ErrNonPkcs8 = errors.New("unable to decode non-pkcs8 type")
26 | ErrNilPassword = errors.New("password required to decode encrypted data")
27 | ErrNoEncryptedData = errors.New("no encrypted data available to decode")
28 | ErrKeystore = errors.New("decoded public keys are not equal")
29 |
30 | ErrPassword = errors.New("password err")
31 |
32 | ErrNumber = errors.New("illegal number")
33 | ErrSign = errors.New("sign panic error")
34 | )
35 |
--------------------------------------------------------------------------------
/core/substrate/types/customscale/call.go:
--------------------------------------------------------------------------------
1 | package customscale
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
7 | )
8 |
9 | type CallData struct {
10 | Method string
11 | Arg []*CallArg
12 | }
13 |
14 | type CallArg struct {
15 | FieldName string
16 | Value any
17 | }
18 |
19 | func DecodeCall(metadata *types.Metadata, call *types.Call) (*CallData, error) {
20 | variant, mod := GetCallMethodFromMetadata(metadata, call)
21 |
22 | arg := NewArgDecoder(bytes.NewReader(call.Args))
23 | callArg, err := ArgDecode(metadata, arg, variant.Fields)
24 | if err != nil {
25 | return &CallData{
26 | Method: fmt.Sprintf("%s.%s", string(mod.Name), string(variant.Name)),
27 | Arg: nil,
28 | }, err
29 | }
30 | return &CallData{
31 | Method: fmt.Sprintf("%s.%s", string(mod.Name), string(variant.Name)),
32 | Arg: callArg,
33 | }, nil
34 | }
35 |
--------------------------------------------------------------------------------
/core/substrate/types/customscale/event.go:
--------------------------------------------------------------------------------
1 | package customscale
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/centrifuge/go-substrate-rpc-client/v4/scale"
7 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
8 | )
9 |
10 | type EventRaw []byte
11 |
12 | type Event struct {
13 | Phase *types.Phase
14 | EventData *EventData
15 | Topics []*types.Hash
16 | }
17 |
18 | type EventData struct {
19 | ModuleName types.Text
20 | EventName types.Text
21 | Args []*CallArg
22 | }
23 |
24 | func (e *EventRaw) DecodeRaw(metadata *types.Metadata) ([]*Event, error) {
25 | var eventList []*Event
26 | decoder := scale.NewDecoder(bytes.NewReader(*e))
27 |
28 | // determine number of events
29 | n, err := decoder.DecodeUintCompact()
30 | if err != nil {
31 | return nil, err
32 | }
33 | for i := uint64(0); i < n.Uint64(); i++ {
34 | event := &Event{
35 | Phase: &types.Phase{},
36 | EventData: &EventData{},
37 | }
38 | // decode Phase
39 | err = decoder.Decode(event.Phase)
40 | if err != nil {
41 | return nil, fmt.Errorf("unable to decode Phase for event #%v: %v", i, err)
42 | }
43 |
44 | // decode EventID
45 | id := types.EventID{}
46 | err = decoder.Decode(&id)
47 | if err != nil {
48 | return nil, fmt.Errorf("unable to decode EventID for event #%v: %v", i, err)
49 | }
50 |
51 | var argField []types.Si1Field
52 | // ask metadata for method & event name for event
53 | event.EventData.ModuleName, event.EventData.EventName, argField, err = FindEventNamesForEventID(metadata, id)
54 | // moduleName, eventName, err := "System", "ExtrinsicSuccess", nil
55 | if err != nil {
56 | return nil, fmt.Errorf("unable to find event with EventID %v in metadata for event #%v: %s", id, i, err)
57 | }
58 |
59 | argDecoder := ArgDecoder{decoder}
60 | event.EventData.Args, err = ArgDecode(metadata, &argDecoder, argField)
61 | if err != nil {
62 | return nil, err
63 | }
64 |
65 | err = decoder.Decode(&event.Topics)
66 | if err != nil {
67 | return nil, err
68 | }
69 |
70 | eventList = append(eventList, event)
71 | }
72 | return eventList, nil
73 | }
74 |
--------------------------------------------------------------------------------
/core/substrate/types/customscale/metadata.go:
--------------------------------------------------------------------------------
1 | package customscale
2 |
3 | import (
4 | "fmt"
5 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
6 | )
7 |
8 | func GetCallMethodFromMetadata(metadata *types.Metadata, call *types.Call) (variant types.Si1Variant, mod types.PalletMetadataV14) {
9 | switch metadata.Version {
10 | case 14:
11 | for _, mod = range metadata.AsMetadataV14.Pallets {
12 | if mod.Index != types.NewU8(call.CallIndex.SectionIndex) {
13 | continue
14 | }
15 | if typ, ok := metadata.AsMetadataV14.EfficientLookup[mod.Calls.Type.Int64()]; !ok || len(typ.Def.Variant.Variants) < 0 {
16 | break
17 | } else {
18 | for _, variant = range typ.Def.Variant.Variants {
19 | if variant.Index == types.NewU8(call.CallIndex.MethodIndex) {
20 | break
21 | }
22 | }
23 | break
24 | }
25 |
26 | }
27 |
28 | }
29 | return
30 | }
31 |
32 | func FindEventNamesForEventID(metadata *types.Metadata, eventID types.EventID) (types.Text, types.Text, []types.Si1Field, error) {
33 | switch metadata.Version {
34 | case 14:
35 | for _, mod := range metadata.AsMetadataV14.Pallets {
36 | if !mod.HasEvents {
37 | continue
38 | }
39 | if mod.Index != types.NewU8(eventID[0]) {
40 | continue
41 | }
42 | eventType := mod.Events.Type.Int64()
43 | typ, ok := metadata.AsMetadataV14.EfficientLookup[eventType]
44 | if !ok {
45 | continue
46 | }
47 | if len(typ.Def.Variant.Variants) <= 0 {
48 | continue
49 | }
50 | for _, vars := range typ.Def.Variant.Variants {
51 | if uint8(vars.Index) == eventID[1] {
52 | return mod.Name, vars.Name, vars.Fields, nil
53 | }
54 | }
55 | }
56 | case 12:
57 | for _, mod := range metadata.AsMetadataV12.Modules {
58 | if !mod.HasEvents {
59 | continue
60 | }
61 | if mod.Index != eventID[0] {
62 | continue
63 | }
64 | if int(eventID[1]) >= len(mod.Events) {
65 | continue
66 | }
67 |
68 | event := mod.Events[int(eventID[1])]
69 | return mod.Name, event.Name, nil, nil
70 | }
71 | default:
72 | return "", "", nil, fmt.Errorf("module index %v out of range", eventID[0])
73 | }
74 | return "", "", nil, fmt.Errorf("module index %v out of range", eventID[0])
75 | }
76 |
77 | func GetSi1TypeFromMetadata(metadata *types.Metadata, typeId types.Si1LookupTypeID) *types.Si1Type {
78 | for _, lookupType := range metadata.AsMetadataV14.Lookup.Types {
79 | if types.Eq(lookupType.ID, typeId) {
80 | return &lookupType.Type
81 | }
82 | }
83 | return nil
84 | }
85 |
--------------------------------------------------------------------------------
/core/substrate/types/customscale/scale_test.go:
--------------------------------------------------------------------------------
1 | package customscale
2 |
3 | import (
4 | gsrpc "github.com/centrifuge/go-substrate-rpc-client/v4"
5 | "github.com/centrifuge/go-substrate-rpc-client/v4/client"
6 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
7 | "testing"
8 | )
9 |
10 | func TestDecodeExtrinsic(t *testing.T) {
11 | api, err := gsrpc.NewSubstrateAPI("wss://rpc.polkadot.io")
12 |
13 | if err != nil {
14 | t.Fatal(err)
15 | }
16 |
17 | blockHash, err := api.RPC.Chain.GetBlockHash(9337762)
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 |
22 | var res string
23 | err = client.CallWithBlockHash(api.Client, &res, "state_getMetadata", &blockHash)
24 | if err != nil {
25 | t.Fatal(err)
26 | }
27 |
28 | var metadata types.Metadata
29 |
30 | err = types.DecodeFromHexString(res, &metadata)
31 | if err != nil {
32 | t.Fatal(err)
33 | }
34 |
35 | block, err := api.RPC.Chain.GetBlock(blockHash)
36 | if err != nil {
37 | t.Fatal(err)
38 | }
39 | for _, extrinsic := range block.Block.Extrinsics {
40 | call, err := DecodeCall(&metadata, &extrinsic.Method)
41 | if err != nil {
42 | t.Fatal(err)
43 | }
44 | t.Log(call)
45 | }
46 |
47 | }
48 |
49 | func TestDecodeEvent(t *testing.T) {
50 | //eventString := "0x1c00000000000000585f8f090000000002000000010000000a066c707b1690a6b0e01b5dea252fe1887930a5afc0ec203f96705331749c37ae4a000064a7b3b6e00d000000000000000000000100000035016c707b1690a6b0e01b5dea252fe1887930a5afc0ec203f96705331749c37ae4a000001000000000000e1f5050000000000000000020000000c0303000000502334ba4a30b12b38ba5f8e1fa719ebb6420fdb360abf915d0d4b3656ae214140420f000000000000000000000000000000020000003504502334ba4a30b12b38ba5f8e1fa719ebb6420fdb360abf915d0d4b3656ae214140420f000000000000000000000000000303000000000002000000000000e1f50500000000000000"
51 | api, err := gsrpc.NewSubstrateAPI("https://mainnet.chainx.org/rpc")
52 |
53 | if err != nil {
54 | t.Fatal(err)
55 | }
56 |
57 | blockHash, err := api.RPC.Chain.GetBlockHash(12312000)
58 | if err != nil {
59 | t.Fatal(err)
60 | }
61 |
62 | var res string
63 | err = client.CallWithBlockHash(api.Client, &res, "state_getMetadata", &blockHash)
64 | if err != nil {
65 | t.Fatal(err)
66 | }
67 |
68 | var metadata types.Metadata
69 |
70 | err = types.DecodeFromHexString(res, &metadata)
71 | if err != nil {
72 | t.Fatal(err)
73 | }
74 |
75 | call, err := types.CreateStorageKey(&metadata, "System", "Events")
76 | if err != nil {
77 | t.Fatal(err)
78 | }
79 |
80 | rawData, err := api.RPC.State.GetStorageRaw(call, blockHash)
81 | if err != nil {
82 | t.Fatal(err)
83 | }
84 | eventRecord := EventRaw(*rawData)
85 | eventData, err := eventRecord.DecodeRaw(&metadata)
86 | if err != nil {
87 | t.Fatal(err)
88 | }
89 | t.Log(eventData)
90 | }
91 |
--------------------------------------------------------------------------------
/core/substrate/types/map.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/centrifuge/go-substrate-rpc-client/v4/scale"
7 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
8 | )
9 |
10 | var (
11 | k, v *types.Si1Type
12 | )
13 |
14 | type Map struct {
15 | Data map[interface{}]interface{}
16 | }
17 |
18 | func NewMap(kd, vd *types.Si1Type) Map {
19 | k = kd
20 | v = vd
21 | return Map{}
22 | }
23 |
24 | func (m *Map) Decode(decoder scale.Decoder) error {
25 | if k == nil || v == nil {
26 | return errors.New("please new map")
27 | }
28 | m.Data = map[interface{}]interface{}{}
29 | length, err := decoder.ReadOneByte()
30 | if err != nil {
31 | return err
32 | }
33 | length = length >> 2
34 | if !k.Def.IsVariant {
35 | return errors.New("not supported type")
36 | }
37 | for i := 0; uint8(i) < length; i++ {
38 | kIndex, err := decoder.ReadOneByte()
39 | if err != nil {
40 | return err
41 | }
42 | for _, variant := range k.Def.Variant.Variants {
43 | if kIndex != byte(variant.Index) {
44 | continue
45 | }
46 | var v types.U128
47 | err = decoder.Decode(&v)
48 | if err != nil {
49 | return err
50 | }
51 | m.Data[string(variant.Name)] = v
52 | break
53 | }
54 | }
55 | return nil
56 | }
57 |
--------------------------------------------------------------------------------
/core/substrate/types/map_test.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "encoding/hex"
5 | "testing"
6 |
7 | gsrpc "github.com/centrifuge/go-substrate-rpc-client/v4"
8 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
9 | "github.com/itering/subscan/util/base58"
10 | "github.com/itering/subscan/util/ss58"
11 | )
12 |
13 | func TestGetXAssertsBalance(t *testing.T) {
14 | api, err := gsrpc.NewSubstrateAPI("wss://testnet3.chainx.org")
15 | if err != nil {
16 | }
17 |
18 | metadata, err := api.RPC.State.GetMetadataLatest()
19 | if err != nil {
20 | return
21 | }
22 |
23 | ss58Format := base58.Decode("5QUEnWNMDFqsbUGpvvtgWGUgiiojnEpLf7581ELLAQyQ1xnT")
24 | publicKey, err := hex.DecodeString(ss58.Decode("5QUEnWNMDFqsbUGpvvtgWGUgiiojnEpLf7581ELLAQyQ1xnT", int(ss58Format[0])))
25 | if err != nil {
26 | return
27 | }
28 |
29 | assetId, err := types.EncodeToBytes(uint32(1))
30 | if err != nil {
31 | return
32 | }
33 |
34 | call, err := types.CreateStorageKey(metadata, "XAssets", "AssetBalance", publicKey, assetId)
35 | if err != nil {
36 | return
37 | }
38 | entryMetadata, err := metadata.FindStorageEntryMetadata("XAssets", "AssetBalance")
39 | if err != nil {
40 | return
41 | }
42 | i := entryMetadata.(types.StorageEntryMetadataV14).Type.AsMap.Value
43 | kIndex := metadata.AsMetadataV14.EfficientLookup[i.Int64()].Params[0].Type.Int64()
44 | vValue := metadata.AsMetadataV14.EfficientLookup[i.Int64()].Params[1].Type.Int64()
45 | data := NewMap(metadata.AsMetadataV14.EfficientLookup[kIndex], metadata.AsMetadataV14.EfficientLookup[vValue])
46 | //var res string
47 | //err = client.CallWithBlockHash(api.Client, &res, "state_getStorage", nil, call.Hex())
48 | //if err != nil {
49 | // return
50 | //}
51 | ok, err := api.RPC.State.GetStorageLatest(call, &data)
52 | if err != nil {
53 | t.Log(err)
54 | }
55 | t.Log(ok)
56 | t.Log(data)
57 | t.Log(data.Data["Usable"])
58 | }
59 |
--------------------------------------------------------------------------------
/core/sui/account_test.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/testcase"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestAccount(t *testing.T) {
11 | account := M1Account(t)
12 | require.Equal(t, account.Address(), "0x7e875ea78ee09f08d72e2676cf84e0f1c8ac61d94fa339cc8e37cace85bebc6e")
13 |
14 | t.Log(account.PrivateKeyHex())
15 | t.Log(account.PublicKeyHex())
16 | t.Log(account.Address())
17 | }
18 |
19 | func TestPublicKeyToAddress(t *testing.T) {
20 | account := M1Account(t)
21 |
22 | pub := account.PublicKeyHex()
23 | addr := account.Address()
24 |
25 | encodedAddr, err := EncodePublicKeyToAddress(pub)
26 | require.Nil(t, err)
27 | require.Equal(t, addr, encodedAddr)
28 | }
29 |
30 | // Account of os environment M1
31 | func M1Account(t *testing.T) *Account {
32 | account, err := NewAccountWithMnemonic(testcase.M1)
33 | require.Nil(t, err)
34 | return account
35 | }
36 |
37 | func M2Account(t *testing.T) *Account {
38 | account, err := NewAccountWithMnemonic(testcase.M2)
39 | require.Nil(t, err)
40 | return account
41 | }
42 |
43 | func M3Account(t *testing.T) *Account {
44 | account, err := NewAccountWithMnemonic(testcase.M3)
45 | require.Nil(t, err)
46 | return account
47 | }
48 |
49 | func TestAccountWithPrivatekey(t *testing.T) {
50 | mnemonic := testcase.M1
51 | accountFromMnemonic, err := NewAccountWithMnemonic(mnemonic)
52 | require.Nil(t, err)
53 | privateKey, err := accountFromMnemonic.PrivateKeyHex()
54 | require.Nil(t, err)
55 |
56 | accountFromPrikey, err := AccountWithPrivateKey(privateKey)
57 | require.Nil(t, err)
58 |
59 | require.Equal(t, accountFromMnemonic.Address(), accountFromPrikey.Address())
60 | }
61 |
62 | func Test_IsValidAddress(t *testing.T) {
63 | addr := "0xd77955e670f42c1bc5e94b9e68e5fe9bdbed9134d784f2a14dfe5fc1b24b5d9f"
64 | valid := IsValidAddress(addr)
65 | require.True(t, valid)
66 | }
67 |
--------------------------------------------------------------------------------
/core/sui/address_util.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "encoding/hex"
5 | "errors"
6 | "regexp"
7 |
8 | "github.com/centrifuge/go-substrate-rpc-client/v4/types"
9 | "golang.org/x/crypto/blake2b"
10 | )
11 |
12 | type Util struct {
13 | }
14 |
15 | func NewUtil() (*Util, error) {
16 | return &Util{}, nil
17 | }
18 |
19 | // MARK - Implement the protocol Util
20 |
21 | // @param publicKey can start with 0x or not.
22 | func (u *Util) EncodePublicKeyToAddress(publicKey string) (string, error) {
23 | return EncodePublicKeyToAddress(publicKey)
24 | }
25 |
26 | // Warning: Sui cannot support decode address to public key
27 | func (u *Util) DecodeAddressToPublicKey(address string) (string, error) {
28 | return DecodeAddressToPublicKey(address)
29 | }
30 |
31 | func (u *Util) IsValidAddress(address string) bool {
32 | return IsValidAddress(address)
33 | }
34 |
35 | // MARK - like Util
36 |
37 | // @param publicKey can start with 0x or not.
38 | func EncodePublicKeyToAddress(publicKey string) (string, error) {
39 | publicBytes, err := types.HexDecodeString(publicKey)
40 | if err != nil {
41 | return "", err
42 | }
43 |
44 | tmp := []byte{0x00}
45 | tmp = append(tmp, publicBytes...)
46 | addrBytes := blake2b.Sum256(tmp)
47 | return "0x" + hex.EncodeToString(addrBytes[:])[:64], nil
48 | }
49 |
50 | func DecodeAddressToPublicKey(address string) (string, error) {
51 | return "", errors.New("Sui cannot support decode address to public key")
52 | }
53 |
54 | // @param chainnet chain name
55 | func IsValidAddress(address string) bool {
56 | reg := regexp.MustCompile(`^(0x|0X)?[0-9a-fA-F]{1,64}$`)
57 | return reg.MatchString(address)
58 | }
59 |
--------------------------------------------------------------------------------
/core/sui/chain_nft_test.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/coming-chat/wallet-SDK/core/base"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestFetchNfts(t *testing.T) {
12 | // owner := M1Account(t).Address()
13 | owner := M3Account(t).Address()
14 | chain := MainnetChain()
15 |
16 | nfts, err := chain.FetchNFTs(owner)
17 | require.Nil(t, err)
18 | for name, group := range nfts {
19 | t.Log("=======================================")
20 | t.Logf("group: %v, count: %v", name, len(group))
21 | for idx, nft := range group {
22 | t.Logf("%4v: %v", idx, nft)
23 | }
24 | }
25 | }
26 |
27 | func TestMintNFT(t *testing.T) {
28 | account := M1Account(t)
29 | chain := DevnetChain()
30 |
31 | var (
32 | timeNow = time.Now().Format("06-01-02 15:04")
33 | nftName = "ComingChat NFT at " + timeNow
34 | nftDesc = "This is a NFT created by ComingChat"
35 | nftUrl = "https://coming.chat/favicon.ico"
36 | )
37 | txn, err := chain.MintNFT(account.Address(), nftName, nftDesc, nftUrl)
38 | require.Nil(t, err)
39 | signedTxn, err := txn.SignWithAccount(account)
40 | require.Nil(t, err)
41 | hash, err := chain.SendRawTransaction(signedTxn.Value)
42 | require.Nil(t, err)
43 | t.Log("mint nft success, hash = ", hash)
44 | }
45 |
46 | func TestTransferNFT(t *testing.T) {
47 | account := M1Account(t)
48 | receiver := M2Account(t).Address()
49 |
50 | chain := TestnetChain()
51 |
52 | nfts, err := chain.FetchNFTs(account.Address())
53 | require.Nil(t, err)
54 | var nft *base.NFT
55 | out:
56 | for _, group := range nfts {
57 | for _, n := range group {
58 | nft = n
59 | break out
60 | }
61 | }
62 | require.NotNil(t, nft)
63 |
64 | txn, err := chain.TransferNFT(account.Address(), receiver, nft.Id)
65 | require.Nil(t, err)
66 | signedTxn, err := txn.SignWithAccount(account)
67 | require.Nil(t, err)
68 | hash, err := chain.SendRawTransaction(signedTxn.Value)
69 | require.Nil(t, err)
70 | t.Log("transfer nft success, hash = ", hash)
71 | }
72 |
--------------------------------------------------------------------------------
/core/sui/chain_stake_test.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | const (
10 | ComingChatValidatorAddress = "0x520289e77c838bae8501ae92b151b99a54407288fdd20dee6e5416bfe943eb7a"
11 | ComingChatValidatorMainnet = "0x11ec7353e9e08ed4ca13b935ad930a2f937112736aec5eedd08c95cc5cd4c264"
12 | )
13 |
14 | func TestGetValidatorState(t *testing.T) {
15 | chain := MainnetChain()
16 |
17 | state, err := chain.GetValidatorState()
18 | require.Nil(t, err)
19 | for idx, v := range state.Validators.AnyArray {
20 | t.Logf("%v, %-10v, APY = %v, totalStaked=%v", idx+1, v.Name, v.APY, v.TotalStaked)
21 | }
22 | }
23 |
24 | func TestStakeEarningTimems(t *testing.T) {
25 | state := ValidatorState{
26 | Epoch: 9,
27 | EpochDurationMs: 86400000,
28 | EpochStartTimestampMs: 1681266455000,
29 | }
30 |
31 | ti := state.EarningAmountTimeAfterNowMs()
32 | t.Log(ti)
33 |
34 | delegated := DelegatedStake{
35 | RequestEpoch: 8,
36 | }
37 | ti2 := delegated.EarningAmountTimeAfterNowMs(&state)
38 | t.Log(ti2)
39 | }
40 |
41 | func TestGetDelegatedStakes(t *testing.T) {
42 | chain := TestnetChain()
43 | // address := M1Account(t).Address()
44 | address := "0xd77955e670f42c1bc5e94b9e68e5fe9bdbed9134d784f2a14dfe5fc1b24b5d9f"
45 |
46 | list, err := chain.GetDelegatedStakes(address)
47 | require.Nil(t, err)
48 | for _, v := range list.AnyArray {
49 | t.Log(v)
50 | }
51 | }
52 |
53 | func TestAddDelegation(t *testing.T) {
54 | chain := TestnetChain()
55 | acc := M1Account(t)
56 |
57 | amount := SUI(1).String()
58 | validator := ComingChatValidatorAddress
59 | txn, err := chain.AddDelegation(acc.Address(), amount, validator)
60 | require.Nil(t, err)
61 |
62 | gas, err := chain.EstimateTransactionFee(txn)
63 | require.Nil(t, err)
64 | t.Log(gas.Value)
65 |
66 | simulateTxnCheck(t, chain, txn, false)
67 | }
68 |
69 | func TestWithdrawDelegation(t *testing.T) {
70 | chain := TestnetChain()
71 | owner := "0xd77955e670f42c1bc5e94b9e68e5fe9bdbed9134d784f2a14dfe5fc1b24b5d9f"
72 |
73 | stakedArray, err := chain.GetDelegatedStakes(owner)
74 | require.Nil(t, err)
75 | require.Greater(t, stakedArray.Count(), 0)
76 |
77 | stake := stakedArray.ValueAt(0)
78 | stakeId := stake.StakeId
79 | txn, err := chain.WithdrawDelegation(owner, stakeId)
80 | require.Nil(t, err)
81 |
82 | simulateTxnCheck(t, chain, txn, false)
83 | }
84 |
--------------------------------------------------------------------------------
/core/sui/error.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/coming-chat/go-sui/v2/types"
7 | )
8 |
9 | var (
10 | ErrNoCoinsFound = types.ErrNoCoinsFound
11 | ErrInsufficientBalance = types.ErrInsufficientBalance
12 | ErrNeedMergeCoin = types.ErrNeedMergeCoin
13 | ErrNeedSplitGasCoin = types.ErrNeedSplitGasCoin
14 |
15 | ErrNoNeedMergeCoin = errors.New("existing coins exceed the target amount, no need to merge coins")
16 | ErrMergeOneCoin = errors.New("only one coin does not need to merge coins")
17 | )
18 |
19 | func IsMergeError(err error) bool {
20 | return err != nil && err.Error() == ErrNeedMergeCoin.Error()
21 | }
22 |
23 | func IsSplitError(err error) bool {
24 | return err != nil && err.Error() == ErrNeedSplitGasCoin.Error()
25 | }
26 |
--------------------------------------------------------------------------------
/core/sui/interface_test.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "github.com/coming-chat/wallet-SDK/core/base"
5 | )
6 |
7 | var (
8 | _ base.Account = (*Account)(nil)
9 | _ base.Chain = (*Chain)(nil)
10 | _ base.Token = (*Token)(nil)
11 | _ base.Transaction = (*Transaction)(nil)
12 |
13 | _ base.SignedTransaction = (*SignedTransaction)(nil)
14 | )
15 |
--------------------------------------------------------------------------------
/core/sui/merge_split_test.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coming-chat/go-sui/v2/sui_types"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestBuildMergeCoinRequest(t *testing.T) {
11 | chain := TestnetChain()
12 |
13 | owner := M1Account(t).Address()
14 | amountStr := SUI(1).String()
15 |
16 | req, err := chain.BuildMergeCoinRequest(owner, "", amountStr)
17 | require.Nil(t, err)
18 |
19 | preview, err := chain.BuildMergeCoinPreview(req)
20 | require.Nil(t, err)
21 |
22 | t.Logf("preview success %v", preview.SimulateSuccess)
23 | t.Logf("preview achieved %v", preview.WillBeAchieved)
24 | simulateCheck(t, chain, &preview.Transaction.Txn, true)
25 | }
26 |
27 | func TestBuildSplitCoinTransaction(t *testing.T) {
28 | chain := TestnetChain()
29 |
30 | owner := M1Account(t).Address()
31 | amountStr := SUI(1).String()
32 |
33 | txn, err := chain.BuildSplitCoinTransaction(owner, "", amountStr)
34 | require.Nil(t, err)
35 |
36 | simulateCheck(t, chain, &txn.Txn, true)
37 | }
38 |
39 | func TestRunableSplitCoin(t *testing.T) {
40 | chain := TestnetChain()
41 | acc := M3Account(t)
42 |
43 | owner, err := sui_types.NewAddressFromHex(acc.Address())
44 | require.Nil(t, err)
45 | amount := SUI(1).String()
46 |
47 | txn, err := chain.BuildSplitCoinTransaction(owner.String(), "", amount)
48 | require.Nil(t, err)
49 |
50 | simulateCheck(t, chain, &txn.Txn, true)
51 | // executeTransaction(t, chain, &txn.Txn, acc.account)
52 | }
53 |
--------------------------------------------------------------------------------
/core/sui/move_call.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/coming-chat/go-sui/v2/sui_types"
7 | "github.com/coming-chat/go-sui/v2/types"
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | )
10 |
11 | // @param maxGasBudget Default `MinGasBudget` if is 0.
12 | func (c *Chain) BaseMoveCall(address, packageId, module, funcName string, typArgs []string, arg []any, maxGasBudget uint64) (txn *Transaction, err error) {
13 | defer base.CatchPanicAndMapToBasicError(&err)
14 |
15 | client, err := c.Client()
16 | if err != nil {
17 | return
18 | }
19 | addr, err := sui_types.NewAddressFromHex(address)
20 | if err != nil {
21 | return
22 | }
23 | packageIdHex, err := sui_types.NewObjectIdFromHex(packageId)
24 | if err != nil {
25 | return
26 | }
27 | if maxGasBudget == 0 {
28 | maxGasBudget = MinGasBudget
29 | }
30 | return c.EstimateTransactionFeeAndRebuildTransaction(maxGasBudget, func(gasBudget uint64) (*Transaction, error) {
31 | gasInt := types.NewSafeSuiBigInt(gasBudget)
32 | tx, err := client.MoveCall(
33 | context.Background(),
34 | *addr,
35 | *packageIdHex,
36 | module,
37 | funcName,
38 | typArgs,
39 | arg,
40 | nil,
41 | gasInt,
42 | )
43 | if err != nil {
44 | return nil, err
45 | }
46 | return &Transaction{
47 | Txn: *tx,
48 | }, nil
49 | })
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/core/sui/rest_reachability.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "encoding/json"
5 | "strconv"
6 | "time"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | "github.com/coming-chat/wallet-SDK/pkg/httpUtil"
10 | )
11 |
12 | type RestReachability struct {
13 | }
14 |
15 | func NewRestReachability() *RestReachability {
16 | return &RestReachability{}
17 | }
18 |
19 | // @return latency (ms) of rpc query blockNumber. -1 means the connection failed.
20 | func (r *RestReachability) LatencyOf(rpc string, timeout int64) (l *base.RpcLatency, err error) {
21 | defer base.CatchPanicAndMapToBasicError(&err)
22 |
23 | l = &base.RpcLatency{
24 | RpcUrl: rpc,
25 | Latency: -1,
26 | Height: -1,
27 | }
28 |
29 | timeStart := time.Now() // Time Start
30 | params := httpUtil.RequestParams{
31 | Header: map[string]string{"Content-Type": "application/json"},
32 | Body: []byte(`{"jsonrpc":"2.0","method":"sui_getLatestCheckpointSequenceNumber","params":{},"id":33}`),
33 | Timeout: time.Duration(timeout * int64(time.Millisecond)),
34 | }
35 | response, err := httpUtil.Post(rpc, params)
36 | if err != nil {
37 | return l, err
38 | }
39 |
40 | model := struct {
41 | Result string `json:"result"`
42 | }{}
43 | err = json.Unmarshal(response, &model)
44 | if err != nil {
45 | return l, err
46 | }
47 | heightInt, err := strconv.ParseInt(model.Result, 10, 64)
48 | if err != nil {
49 | heightInt = 0
50 | err = nil
51 | }
52 | timeCost := time.Since(timeStart) // Time End
53 |
54 | l.Height = heightInt
55 | l.Latency = timeCost.Milliseconds()
56 | return l, nil
57 | }
58 |
--------------------------------------------------------------------------------
/core/sui/rest_reachability_test.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/coming-chat/wallet-SDK/core/base"
9 | )
10 |
11 | type dddd struct {
12 | }
13 |
14 | func (d *dddd) ReachabilityDidReceiveNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
15 | fmt.Printf(".... delegate did receive height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
16 | }
17 |
18 | func (d *dddd) ReachabilityDidFailNode(tester *base.ReachMonitor, latency *base.RpcLatency) {
19 | fmt.Printf(".... delegate did fail height %v, latency %v, node %v\n", latency.Height, latency.Latency, latency.RpcUrl)
20 | // tester.StopConnectivity()
21 | }
22 |
23 | func (d *dddd) ReachabilityDidFinish(tester *base.ReachMonitor, overview string) {
24 | fmt.Printf(".... delegate did finish %v\n", overview)
25 | }
26 |
27 | func TestRpcReachability_Test(t *testing.T) {
28 | reach := NewRestReachability()
29 | monitor := base.NewReachMonitorWithReachability(reach)
30 | monitor.ReachCount = 3
31 | monitor.Delay = 3000
32 | monitor.Timeout = 2500
33 | t.Log(reach)
34 |
35 | rpcUrls := []string{TestnetRpcUrl, DevnetRpcUrl, MainnetRpcUrl}
36 | rpcListString := strings.Join(rpcUrls, ",")
37 | // res := reach.StartConnectivitySync(rpcListString)
38 | // t.Log(res)
39 |
40 | delegate := &dddd{}
41 | monitor.StartConnectivityDelegate(rpcListString, delegate)
42 | }
43 |
--------------------------------------------------------------------------------
/core/sui/sui_cat_test.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func Test_FetchSuiCatGlobalData(t *testing.T) {
10 | chain := MainnetChain()
11 | data, err := chain.FetchSuiCatGlobalData()
12 | require.Nil(t, err)
13 | t.Log(data.JsonString())
14 | }
15 |
16 | func Test_QueryIsInSuiCatWhiteList(t *testing.T) {
17 | chain := TestnetChain()
18 |
19 | res, err := chain.QueryIsInSuiCatWhiteList("0xf9ed7d8de1a6c44d703b64318a1cc687c324fdec35454281035a53ea3ba1a95a")
20 | require.Nil(t, err)
21 | t.Log(res.Value)
22 |
23 | res, err = chain.QueryIsInSuiCatWhiteList("0x09ed7d8de1a6c44d703b64318a1cc687c324fdec35454281035a53ea3ba1a95a")
24 | require.Nil(t, err)
25 | t.Log(res.Value)
26 | }
27 |
28 | func Test_MintSuiCatNFT(t *testing.T) {
29 | acc := M3Account(t)
30 | chain := TestnetChain()
31 |
32 | txn, err := chain.MintSuiCatNFT(acc.Address(), SUI(0.9).String())
33 | require.Nil(t, err)
34 |
35 | // simulateCheck(t, chain, &txn.Txn, true)
36 | executeTransaction(t, chain, &txn.Txn, acc.account)
37 | }
38 |
--------------------------------------------------------------------------------
/core/sui/transaction.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/coming-chat/go-sui/v2/lib"
7 | "github.com/coming-chat/go-sui/v2/sui_types"
8 | "github.com/coming-chat/go-sui/v2/types"
9 | "github.com/coming-chat/wallet-SDK/core/base"
10 | )
11 |
12 | type Transaction struct {
13 | EstimateGasFee int64
14 |
15 | Txn types.TransactionBytes
16 |
17 | TxnBytes lib.Base64Data
18 | }
19 |
20 | func (t *Transaction) TransactionBytes() []byte {
21 | if t.TxnBytes != nil {
22 | return t.TxnBytes
23 | }
24 | return t.Txn.TxBytes
25 | }
26 |
27 | func (t *Transaction) SignWithAccount(account base.Account) (signedTx *base.OptionalString, err error) {
28 | signedTxn, err := t.SignedTransactionWithAccount(account)
29 | if err != nil {
30 | return
31 | }
32 | return signedTxn.HexString()
33 | }
34 |
35 | func (t *Transaction) SignedTransactionWithAccount(account base.Account) (signedTx base.SignedTransaction, err error) {
36 | acc, ok := account.(*Account)
37 | if !ok {
38 | return nil, base.ErrInvalidAccountType
39 | }
40 | txnBytes := t.TransactionBytes()
41 | signature, err := acc.account.SignSecureWithoutEncode(txnBytes, sui_types.DefaultIntent())
42 | if err != nil {
43 | return nil, err
44 | }
45 | base64data := lib.Base64Data(txnBytes)
46 | signedTxn := SignedTransaction{
47 | TxBytes: &base64data,
48 | Signature: &signature,
49 | }
50 | return &signedTxn, nil
51 | }
52 |
53 | type SignedTransaction struct {
54 | // transaction data bytes
55 | TxBytes *lib.Base64Data `json:"tx_bytes"`
56 |
57 | // transaction signature
58 | Signature *sui_types.Signature `json:"signature"`
59 | }
60 |
61 | func (txn *SignedTransaction) HexString() (res *base.OptionalString, err error) {
62 | bytes, err := json.Marshal(txn)
63 | if err != nil {
64 | return
65 | }
66 | txnString := lib.Base64Data(bytes).String()
67 |
68 | return &base.OptionalString{Value: txnString}, nil
69 | }
70 |
71 | func AsSignedTransaction(txn base.SignedTransaction) *SignedTransaction {
72 | if res, ok := txn.(*SignedTransaction); ok {
73 | return res
74 | }
75 | return nil
76 | }
77 |
--------------------------------------------------------------------------------
/core/sui/transaction_test.go:
--------------------------------------------------------------------------------
1 | package sui
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestTransaction_SignWithAccount(t *testing.T) {
10 | account := M1Account(t)
11 | chain := TestnetChain()
12 | token := NewTokenMain(chain)
13 |
14 | toAddress := M1Account(t).Address()
15 | amount := SUI(1).String()
16 | // toAddress := account.Address()
17 | // amount := SUI(4).String() // test big amount transfer
18 |
19 | txn, err := token.BuildTransferTransaction(account, toAddress, amount)
20 | require.Nil(t, err)
21 |
22 | signedTx, err := txn.SignWithAccount(account)
23 | require.Nil(t, err)
24 |
25 | t.Log(signedTx.Value)
26 | }
27 |
--------------------------------------------------------------------------------
/core/testcase/account.go:
--------------------------------------------------------------------------------
1 | package testcase
2 |
3 | import "os"
4 |
5 | type AccountCase struct {
6 | Mnemonic string
7 | Address string
8 | }
9 |
10 | type AccountGroup struct {
11 | BtcMainnet AccountCase
12 | BtcSignet AccountCase
13 | Cosmos AccountCase
14 | Terra AccountCase
15 | DogeMainnet AccountCase
16 | DogeTestnet AccountCase
17 | Ethereum AccountCase
18 | Polka0 AccountCase
19 | Polka2 AccountCase
20 | Polka44 AccountCase
21 | Solana AccountCase
22 | Aptos AccountCase
23 | Starcoin AccountCase
24 | }
25 |
26 | // 你需要在你的电脑环境中配置助记词的环境变量,并更新对应的币种地址
27 | // You need to configure the environment variable of the mnemonic phrase in your computer environment and update the corresponding currency address
28 | var M1 = os.Getenv("WalletSdkTestM1")
29 | var M2 = os.Getenv("WalletSdkTestM2")
30 | var M3 = os.Getenv("WalletSdkTestM3")
31 | var Mterra = os.Getenv("WalletSdkTestMterra")
32 |
33 | var Accounts = AccountGroup{
34 | BtcMainnet: AccountCase{M1, "bc1pe7mlvfszt45zvffl3lzctrn6t39c2v6sksu946gfeanp8zm3a3msaw8sys"},
35 | BtcSignet: AccountCase{M1, "tb1pe7mlvfszt45zvffl3lzctrn6t39c2v6sksu946gfeanp8zm3a3ms2x3l7l"},
36 | Cosmos: AccountCase{M1, "cosmos1r64ug62cytmg28leeu42hp9mzq7p442myf0s3m"},
37 | Terra: AccountCase{Mterra, "terra14tt4mzwrfxlgv7ly4xgym79jrxugjtdvgsalpj"},
38 | DogeMainnet: AccountCase{M1, "DHkgR9b8TGtFe6ZKqhAcmYmBKUeYmJ2uqg"},
39 | DogeTestnet: AccountCase{M1, "ngok9AL3PFLyX58WsWp51xMUZM2qk1RGQ6"},
40 | Ethereum: AccountCase{M1, "0x6334d64D5167F726d8A44f3fbCA66613708E59E7"},
41 | Polka0: AccountCase{M1, "12eV7FtPbXBgDG6mX4zwPaJdQKgigVtnYofSpS8mgEQbX623"},
42 | Polka2: AccountCase{M1, "EDodEyCN6w8XNuhL8kz9NqUhHyJns9pvgmi3oRNbwba5hxN"},
43 | Polka44: AccountCase{M1, "5RHWUui8WKff5quBNVhz1E1Kqfyf6ZbgrC3DtWS23ra3u4vV"},
44 | Solana: AccountCase{M1, "AfBfH4ehvcXx66Y5YZozgTYPC1nieL9A3r2yT3vCXqPY"},
45 | Aptos: AccountCase{M1, "0x6ed6f83f1891e02c00c58bf8172e3311c982b1c4fbb1be2d85a55562d4085fb1"},
46 | Starcoin: AccountCase{M1, "0x1412Ea33f5ed7a27851801eDB2fE1d3F"},
47 | }
48 |
49 | var Accounts2 = AccountGroup{
50 | DogeMainnet: AccountCase{M2, "DLGwnBwHB9FpFJw9apYato1kPXyeNYhq6H"},
51 | DogeTestnet: AccountCase{M2, "njL1WCgC77iY8HWLceC39Cc3dQMwSbXugR"},
52 | Solana: AccountCase{M2, "GDqGCNxkZK3QWcWTnXK3TDuMBV168oUPUqd5spdRN8QW"},
53 | }
54 |
55 | var EmptyMnemonic = AccountCase{Mnemonic: ""}
56 | var ErrorMnemonic = AccountCase{Mnemonic: "unaware oxygen allow method allow property predict various slice travel please error"}
57 |
--------------------------------------------------------------------------------
/core/testcase/amount.go:
--------------------------------------------------------------------------------
1 | package testcase
2 |
3 | import "strconv"
4 |
5 | type Amount struct {
6 | Amount float64
7 | Multiple float64
8 | }
9 |
10 | func (a Amount) Int64() int64 {
11 | return int64(a.Amount * a.Multiple)
12 | }
13 | func (a Amount) Uint64() uint64 {
14 | return uint64(a.Amount * a.Multiple)
15 | }
16 | func (a Amount) String() string {
17 | return strconv.FormatUint(uint64(a.Amount*a.Multiple), 10)
18 | }
19 |
--------------------------------------------------------------------------------
/core/wallet/err.go:
--------------------------------------------------------------------------------
1 | package wallet
2 |
3 | import "errors"
4 |
5 | var (
6 | // ErrNilKey = errors.New("no mnemonic or private key")
7 | // ErrNilWallet = errors.New("no mnemonic or private key or keystore")
8 | // ErrNilKeystore = errors.New("no keystore")
9 | // ErrNilMetadata = errors.New("no metadata")
10 | // ErrNotSigned = errors.New("transaction not signed")
11 | // ErrNoPublicKey = errors.New("transaction no public key")
12 | // ErrNilExtrinsic = errors.New("nil extrinsic")
13 | // ErrAddress = errors.New("err address")
14 | // ErrPublicKey = errors.New("err publicKey")
15 | // ErrSeedOrPhrase = errors.New("invalid seed length")
16 |
17 | ErrWalletInfoUnspecified = errors.New("Wallet info provider unspecified")
18 |
19 | ErrWalletInfoNotExist = errors.New("The specified wallet info does not exist")
20 |
21 | ErrUnsupportKeystore = errors.New("The chain type does not support keystore")
22 |
23 | ErrInvalidMnemonic = errors.New("Invalid mnemonic")
24 |
25 | // ErrWrongMetadata = errors.New("wrong metadata")
26 | // ErrNoEncrypted = errors.New("no encrypted data to decode")
27 | // ErrEncryptedLength = errors.New("encrypted length is less than 24")
28 | // ErrInvalidParams = errors.New("invalid injected scrypt params found")
29 | // ErrSecretLength = errors.New("secret length is not 32")
30 | // ErrEncoded = errors.New("encoded is nil")
31 | // ErrPkcs8Header = errors.New("invalid Pkcs8 header found in body")
32 | // ErrPkcs8Divider = errors.New("invalid Pkcs8 divider found in body")
33 |
34 | // ErrNonPkcs8 = errors.New("unable to decode non-pkcs8 type")
35 | // ErrNilPassword = errors.New("password required to decode encrypted data")
36 | // ErrNoEncryptedData = errors.New("no encrypted data available to decode")
37 | // ErrKeystore = errors.New("decoded public keys are not equal")
38 |
39 | // ErrPassword = errors.New("password err")
40 |
41 | // ErrNumber = errors.New("illegal number")
42 | // ErrSign = errors.New("sign panic error")
43 |
44 | // errMethodUnusable = errors.New("TODO: Method not yet implemented")
45 | )
46 |
--------------------------------------------------------------------------------
/core/wallet/mnemonic.go:
--------------------------------------------------------------------------------
1 | package wallet
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/btcsuite/btcd/btcutil/hdkeychain"
7 | "github.com/btcsuite/btcd/chaincfg"
8 | "github.com/tyler-smith/go-bip39"
9 | )
10 |
11 | func GenMnemonic() (string, error) {
12 | entropy, err := bip39.NewEntropy(128)
13 | if err != nil {
14 | return "", err
15 | }
16 | mnemonic, err := bip39.NewMnemonic(entropy)
17 | return mnemonic, err
18 | }
19 |
20 | func IsValidMnemonic(mnemonic string) bool {
21 | _, err := bip39.NewSeedWithErrorChecking(mnemonic, "")
22 | return err == nil
23 | }
24 |
25 | // ExtendMasterKey derives a master key from the given mnemonic and chain network identifier.
26 | //
27 | // Parameters:
28 | // - mnemonic: A string representing the mnemonic phrase used to generate the seed.
29 | // - chainnet: The blockchain network, which must be either
30 | // "mainnet", "testnet", "signet", "simnet" or "regtest".
31 | func ExtendMasterKey(mnemonic string, chainnet string) (string, error) {
32 | var net chaincfg.Params
33 | switch chainnet {
34 | case "mainnet", "bitcoin":
35 | net = chaincfg.MainNetParams
36 | case "testnet", "testnet3":
37 | net = chaincfg.TestNet3Params
38 | case "signet":
39 | net = chaincfg.SigNetParams
40 | case "simnet":
41 | net = chaincfg.SimNetParams
42 | case "regtest":
43 | net = chaincfg.RegressionNetParams
44 | default:
45 | return "", errors.New("invalid chainnet")
46 | }
47 | seed, err := bip39.NewSeedWithErrorChecking(mnemonic, "")
48 | if err != nil {
49 | return "", err
50 | }
51 | masterKey, err := hdkeychain.NewMaster(seed, &net)
52 | if err != nil {
53 | return "", err
54 | }
55 | return masterKey.String(), nil
56 | }
57 |
--------------------------------------------------------------------------------
/core/wallet/utils.go:
--------------------------------------------------------------------------------
1 | package wallet
2 |
3 | import "github.com/centrifuge/go-substrate-rpc-client/v4/types"
4 |
5 | func ByteToHex(data []byte) string {
6 | return types.HexEncodeToString(data)
7 | }
8 |
9 | func HexToByte(hex string) ([]byte, error) {
10 | return types.HexDecodeString(hex)
11 | }
12 |
--------------------------------------------------------------------------------
/core/wallet/watch_account.go:
--------------------------------------------------------------------------------
1 | package wallet
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/coming-chat/wallet-SDK/core/base"
7 | )
8 |
9 | type WatchAccount struct {
10 | address string
11 | }
12 |
13 | func (a *WatchAccount) PrivateKey() ([]byte, error) {
14 | return nil, errors.New("The watch wallet cannot get private key")
15 | }
16 |
17 | func (a *WatchAccount) PrivateKeyHex() (string, error) {
18 | return "", errors.New("The watch wallet cannot get private key")
19 | }
20 |
21 | func (a *WatchAccount) PublicKey() []byte {
22 | return nil
23 | }
24 |
25 | func (a *WatchAccount) PublicKeyHex() string {
26 | return ""
27 | }
28 |
29 | func (a *WatchAccount) Address() string {
30 | return a.address
31 | }
32 |
33 | func (a *WatchAccount) Sign(message []byte, password string) ([]byte, error) {
34 | return nil, errors.New("The watch wallet cannot be signed")
35 | }
36 | func (a *WatchAccount) SignHex(messageHex string, password string) (*base.OptionalString, error) {
37 | return nil, errors.New("The watch wallet cannot be signed")
38 | }
39 |
--------------------------------------------------------------------------------
/core/wallet/watch_test.go:
--------------------------------------------------------------------------------
1 | package wallet
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestChainType(t *testing.T) {
10 | address := "😁"
11 |
12 | chains := ChainTypeOfWatchAddress(address)
13 | t.Log(chains.JsonString())
14 | t.Log(chains.Count())
15 | }
16 |
17 | func TestChainTypeOfPrivateKey(t *testing.T) {
18 | tests := []struct {
19 | name string
20 | prikey string
21 | want string
22 | }{
23 | {
24 | name: "emoji",
25 | prikey: "😁",
26 | want: "null",
27 | },
28 | {
29 | name: "length 64",
30 | prikey: "0xfc0e2f9586b6ba8e4380737250824b64e7abc1d5e26d4357097809ad27e5e096",
31 | want: `["bitcoin","ethereum","polka","signet","dogecoin","cosmos","terra","aptos","sui","starcoin"]`,
32 | },
33 | {
34 | name: "length 128",
35 | prikey: "0xfc0e2f9586b6ba8e431d5e26d43537250824b64e7abc1a8e424b64e7abc97809ad27e5e096fc0e2f9586b6380737d5e26d4357080772508b097809ad27e5e096",
36 | want: `["solana"]`,
37 | },
38 | {
39 | name: "length 64, without 0x",
40 | prikey: "fc0e2f9586b6ba8e4380737250824b64e7abc1d5e26d4357097809ad27e5e096",
41 | want: `["bitcoin","ethereum","polka","signet","dogecoin","cosmos","terra","aptos","sui","starcoin"]`,
42 | },
43 | }
44 | for _, tt := range tests {
45 | t.Run(tt.name, func(t *testing.T) {
46 | got := ChainTypeOfPrivateKey(tt.prikey)
47 | t.Log(got.JsonString())
48 | require.Equal(t, got.JsonString(), tt.want)
49 | })
50 | }
51 | }
52 |
53 | func TestChainTypeOfPrivateKey_btc(t *testing.T) {
54 | prikey := "cTkZaPpb1pDdor36V5VY4uu5LE6tgzrjRADvrEXimEqWqvwRbfXY"
55 | typeArr := ChainTypeOfPrivateKey(prikey)
56 | t.Log(typeArr)
57 | }
58 |
--------------------------------------------------------------------------------
/crypto/blake2b256Hash.go:
--------------------------------------------------------------------------------
1 | package crypto
2 |
3 | import "golang.org/x/crypto/blake2b"
4 |
5 | // NewBlake2b256Hash ...
6 | // TODO: redeclared, which to use...
7 | //func NewBlake2b256Hash(input []byte) *Blake2b256Hash {
8 | //b := New(Blake2b256Hash)
9 | //copy(b[:], input)
10 | //return b
11 | //}
12 |
13 | // Value ...
14 | func (b Blake2b256Hash) Value() [blake2b.Size256]uint8 {
15 | return b
16 | }
17 |
--------------------------------------------------------------------------------
/crypto/ed25519/utils.go:
--------------------------------------------------------------------------------
1 | package ed25519
2 |
3 | import "crypto/ed25519"
4 |
5 | func IsValidSignature(publicKey, msg, signature []byte) bool {
6 | return ed25519.Verify(publicKey, msg, signature)
7 | }
8 |
--------------------------------------------------------------------------------
/crypto/ed25519/utils_test.go:
--------------------------------------------------------------------------------
1 | package ed25519
2 |
3 | import "testing"
4 |
5 | func TestIsValidSignature(t *testing.T) {
6 | type args struct {
7 | publicKey []byte
8 | msg []byte
9 | signature []byte
10 | }
11 | tests := []struct {
12 | name string
13 | args args
14 | want bool
15 | }{
16 | {
17 | name: "test 1",
18 | args: args{
19 | publicKey: []byte{0x36, 0x48, 0x6b, 0xb2, 0xa5, 0xa4, 0x83, 0xd9, 0x91, 0xa0, 0x09, 0x99, 0x45, 0xc4, 0x40, 0x31, 0x7b, 0xa2, 0xc3, 0xa7, 0x85, 0x32, 0x86, 0x22, 0xb9, 0x43, 0x1b, 0x0d, 0x1d, 0x1c, 0xa3, 0xf1},
20 | msg: []byte{0x12},
21 | signature: []byte{85, 105, 58, 89, 193, 218, 236, 85, 11, 70, 73, 13, 77, 224, 117, 53, 73, 187, 124, 125, 91, 107, 186, 177, 206, 82, 253, 216, 11, 227, 118, 38, 67, 220, 73, 54, 8, 125, 219, 63, 168, 223, 102, 161, 39, 108, 175, 97, 155, 54, 16, 29, 134, 3, 3, 220, 95, 51, 235, 205, 252, 105, 71, 13},
22 | },
23 | want: true,
24 | },
25 | }
26 | for _, tt := range tests {
27 | t.Run(tt.name, func(t *testing.T) {
28 | if got := IsValidSignature(tt.args.publicKey, tt.args.msg, tt.args.signature); got != tt.want {
29 | t.Errorf("IsValidSignature() = %v, want %v", got, tt.want)
30 | }
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/crypto/hash.go:
--------------------------------------------------------------------------------
1 | package crypto
2 |
3 | import "crypto/sha256"
4 |
5 | // marshal and unmarshal to/from hex
6 |
7 | // NewHash ...
8 | // TODO: redeclared. which to use...
9 | //func NewHash(input []byte) *Hash {
10 | //h := new(Hash)
11 | //copy(h[:], input)
12 | //return h
13 | //}
14 |
15 | // Value ...
16 | func (h Hash) Value() [sha256.Size]uint8 {
17 | return h
18 | }
19 |
--------------------------------------------------------------------------------
/crypto/secp256k1/utils.go:
--------------------------------------------------------------------------------
1 | package secp256k1
2 |
3 | import (
4 | "github.com/btcsuite/btcd/btcec/v2"
5 | "github.com/btcsuite/btcd/btcec/v2/schnorr"
6 | )
7 |
8 | func IsValidSignature(publicKey, msg, signature []byte) bool {
9 | srSignature, err := schnorr.ParseSignature(signature)
10 | if err != nil {
11 | return false
12 | }
13 | pubKey, err := btcec.ParsePubKey(publicKey)
14 | if err != nil {
15 | return false
16 | }
17 | return srSignature.Verify(msg, pubKey)
18 | }
19 |
--------------------------------------------------------------------------------
/crypto/secp256k1/utils_test.go:
--------------------------------------------------------------------------------
1 | package secp256k1
2 |
3 | import "testing"
4 |
5 | //func TestGetAccount(t *testing.T) {
6 | // "save account lyrics trick warm wrap token mutual radio return hidden holiday"
7 | //}
8 |
9 | func TestIsValidSignature(t *testing.T) {
10 | type args struct {
11 | publicKey []byte
12 | msg []byte
13 | signature []byte
14 | }
15 | tests := []struct {
16 | name string
17 | args args
18 | want bool
19 | }{
20 | {
21 | name: "test",
22 | args: args{
23 | publicKey: []byte{0x04, 0x38, 0xc3, 0x2d, 0xa7, 0xc1, 0xaf, 0x35, 0x0a, 0xe4, 0x46, 0xe5, 0x43, 0xbe, 0x1f, 0xad, 0x2e, 0x6d, 0x49, 0x79, 0x1b, 0x7c, 0x7d, 0xca, 0xef, 0xbe, 0x18, 0xd6, 0x3d, 0xdd, 0x3f, 0x42, 0x67, 0x35, 0xea, 0x5b, 0x0f, 0x28, 0xd6, 0x48, 0x9b, 0x4e, 0x4d, 0x9a, 0x38, 0xed, 0x17, 0x0c, 0xaf, 0xf1, 0xac, 0x1a, 0x3a, 0x64, 0x03, 0x33, 0xc0, 0x95, 0xb4, 0xc3, 0x62, 0x77, 0xbf, 0x08, 0xee},
24 | msg: []byte{0xdb, 0x66, 0xdc, 0x33, 0x41, 0x1f, 0xb0, 0xfd, 0x7e, 0x04, 0x4c, 0xbc, 0x22, 0xd7, 0x5c, 0x01, 0x6b, 0x7f, 0x91, 0xd1, 0xeb, 0x5d, 0x5c, 0x2a, 0x76, 0x20, 0x2d, 0xff, 0xc9, 0x75, 0x5c, 0xc9},
25 | signature: []byte{0x97, 0x0a, 0xc0, 0x04, 0x1e, 0xb5, 0xfd, 0x45, 0xef, 0xe4, 0xd2, 0x97, 0xe4, 0x53, 0x5a, 0xf0, 0x58, 0x99, 0xca, 0x9a, 0x87, 0x75, 0xbe, 0x47, 0x19, 0x3a, 0x3d, 0x10, 0x61, 0x0a, 0x69, 0x35, 0x2a, 0x9c, 0x70, 0x1a, 0x6a, 0x40, 0x5c, 0xb1, 0x1b, 0x83, 0xb6, 0x70, 0x71, 0xc5, 0x9b, 0xa7, 0xde, 0x76, 0x96, 0xb6, 0x7b, 0xc4, 0xa4, 0xbb, 0x8f, 0xe0, 0x9f, 0xed, 0xf4, 0x10, 0xf5, 0xac},
26 | },
27 | want: true,
28 | },
29 | }
30 | for _, tt := range tests {
31 | t.Run(tt.name, func(t *testing.T) {
32 | if got := IsValidSignature(tt.args.publicKey, tt.args.msg, tt.args.signature); got != tt.want {
33 | t.Errorf("IsValidSignature() = %v, want %v", got, tt.want)
34 | }
35 | })
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/crypto/sr25519/utils.go:
--------------------------------------------------------------------------------
1 | package sr25519
2 |
3 | import (
4 | "github.com/ChainSafe/go-schnorrkel"
5 | "golang.org/x/crypto/blake2b"
6 | )
7 |
8 | func IsValidSignature(publicKey, msg, signature []byte) bool {
9 | if len(msg) > 256 {
10 | h := blake2b.Sum256(msg)
11 | msg = h[:]
12 | }
13 | var (
14 | sigs [64]byte
15 | fixedPubKey [32]byte
16 | sig = new(schnorrkel.Signature)
17 | )
18 | copy(fixedPubKey[:], publicKey[:])
19 | copy(sigs[:], signature[:])
20 | pubKey := schnorrkel.NewPublicKey(fixedPubKey)
21 | if err := sig.Decode(sigs); err != nil {
22 | return false
23 | }
24 | return pubKey.Verify(sig, schnorrkel.NewSigningContext([]byte("substrate"), msg))
25 | }
26 |
--------------------------------------------------------------------------------
/crypto/sr25519/utils_test.go:
--------------------------------------------------------------------------------
1 | package sr25519
2 |
3 | import "testing"
4 |
5 | func TestIsValidSignature(t *testing.T) {
6 | type args struct {
7 | publicKey []byte
8 | msg []byte
9 | signature []byte
10 | }
11 | tests := []struct {
12 | name string
13 | args args
14 | want bool
15 | }{
16 | {
17 | name: "test 1",
18 | args: args{
19 | publicKey: []byte{0x24, 0xc5, 0x8f, 0x3b, 0x78, 0xbe, 0xe0, 0x7f, 0x93, 0xce, 0xd6, 0x8b, 0x1f, 0x74,
20 | 0x86, 0x5a, 0xbf, 0x59, 0x84, 0xd0, 0x32, 0x14, 0xbd, 0xe8, 0xcc, 0x41, 0x16, 0x5e,
21 | 0x2a, 0xc8, 0x61, 0x25},
22 | msg: []byte{0x12},
23 | signature: []byte{132, 118, 213, 103, 77, 209, 185, 218, 184, 233, 73, 27, 123, 237, 187, 25, 40, 28, 138, 254, 108, 205, 180, 137, 44, 149, 126, 197, 25, 180, 12, 61, 149, 193, 158, 103, 13, 175, 96, 1, 83, 94, 98, 118, 222, 240, 210, 43, 4, 34, 70, 93, 162, 134, 162, 146, 30, 52, 127, 35, 220, 121, 199, 131},
24 | },
25 | want: true,
26 | },
27 | }
28 | for _, tt := range tests {
29 | t.Run(tt.name, func(t *testing.T) {
30 | if got := IsValidSignature(tt.args.publicKey, tt.args.msg, tt.args.signature); got != tt.want {
31 | t.Errorf("IsValidSignature() = %v, want %v", got, tt.want)
32 | }
33 | })
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/graphql/graphql.go:
--------------------------------------------------------------------------------
1 | package graphql
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "io"
8 | "net/http"
9 | )
10 |
11 | type Params struct {
12 | Query string `json:"query"`
13 | OperationName string `json:"operationName,omitempty"`
14 | Variables map[string]any `json:"variables,omitempty"`
15 | }
16 |
17 | type Parser[RawResponse any] func(resp RawResponse, out any) error
18 |
19 | func Query[RawResponse any](params Params, graphqlUrl string, parser Parser[RawResponse], out any) (err error) {
20 | if parser == nil || out == nil {
21 | return errors.New("the parser of the query cannot be nil")
22 | }
23 | body, err := json.Marshal(params)
24 | if err != nil {
25 | return
26 | }
27 | req, err := http.NewRequest(http.MethodPost, graphqlUrl, bytes.NewBuffer(body))
28 | if err != nil {
29 | return
30 | }
31 | req.Header["Content-Type"] = []string{"application/json"}
32 |
33 | client := http.Client{}
34 | resp, err := client.Do(req)
35 | if err != nil {
36 | return
37 | }
38 | defer resp.Body.Close()
39 | respBody, err := io.ReadAll(resp.Body)
40 | if err != nil {
41 | return
42 | }
43 |
44 | var rawResp RawResponse
45 | err = json.Unmarshal(respBody, &rawResp)
46 | if err != nil {
47 | return
48 | }
49 | return parser(rawResp, out)
50 | }
51 |
52 | func QueryString[RawResponse any](query string, graphqlUrl string, parser Parser[RawResponse], out any) (err error) {
53 | return Query[RawResponse](Params{Query: query}, graphqlUrl, parser, out)
54 | }
55 |
--------------------------------------------------------------------------------
/graphql/graphql_test.go:
--------------------------------------------------------------------------------
1 | package graphql
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | type donutResp struct {
12 | Data struct {
13 | Type string `json:"@type"`
14 | Value json.RawMessage `json:"value"`
15 | } `json:"data,omitempty"`
16 | Code int `json:"code"`
17 | Msg string `json:"msg"`
18 | }
19 |
20 | func donutParser(resp donutResp, out any) error {
21 | if resp.Code != 0 {
22 | return fmt.Errorf("error code %v: %v", resp.Code, resp.Msg)
23 | }
24 | return json.Unmarshal(resp.Data.Value, out)
25 | }
26 |
27 | func TestDonutGraphql(t *testing.T) {
28 | graphUrl := "https://bc.dnt.social/v1/common/search"
29 | holder := "0xa2cCF83EA437565a37E1F2d49940e0C4C7D7591e"
30 | query := fmt.Sprintf(`{
31 | src20Balances(holder: "%v", first: 100) {
32 | edges{
33 | node{
34 | tick
35 | amount
36 | }
37 | }
38 | }
39 | }`, holder)
40 |
41 | var resp interface{}
42 | err := QueryString(query, graphUrl, donutParser, &resp)
43 | require.NoError(t, err)
44 | t.Log(resp)
45 |
46 | errQuery := "aaaa"
47 | err = QueryString(errQuery, graphUrl, donutParser, &resp)
48 | require.Error(t, err)
49 | }
50 |
--------------------------------------------------------------------------------
/util/mathutil/mathutil_test.go:
--------------------------------------------------------------------------------
1 | package mathutil
2 |
3 | import (
4 | "fmt"
5 | "math/big"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func TestPow(t *testing.T) {
11 | type input struct {
12 | i *big.Int
13 | e *big.Int
14 | }
15 | for i, tt := range []struct {
16 | in input
17 | out *big.Int
18 | }{
19 | {input{big.NewInt(16), big.NewInt(2)}, big.NewInt(256)},
20 | } {
21 | t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
22 | result := Pow(tt.in.i, tt.in.e)
23 | if result.String() != tt.out.String() {
24 | t.Fatalf("want %v; got %v", tt.out, result)
25 | }
26 | })
27 | }
28 | }
29 |
30 | func TestToUint8Slice(t *testing.T) {
31 | for i, tt := range []struct {
32 | val *big.Int
33 | le bool
34 | length int
35 | ret []uint8
36 | }{
37 | {big.NewInt(-1234), false, -1, []uint8{4, 210}},
38 | {big.NewInt(-1234), true, -1, []uint8{210, 4}},
39 | } {
40 | t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
41 | result := ToUint8Slice(tt.val, tt.le, tt.length)
42 | if !reflect.DeepEqual(result, tt.ret) {
43 | t.Fatalf("want %v; got %v", tt.ret, result)
44 | }
45 | })
46 | }
47 | }
48 |
--------------------------------------------------------------------------------