├── .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 | --------------------------------------------------------------------------------