centrifuge / go-substrate-rpc-client

Substrate RPC client for go aka GSRPC
Apache License 2.0
204 stars 179 forks source link

Can sign and send transactions to moonbeam? #399

Open YuXiaoCoder opened 3 months ago

YuXiaoCoder commented 3 months ago

How do I sign and send a transaction to Moonbeam, I understand Polkadot's method!

// Transfer 转账 https://kusama.subscan.io/extrinsic?module=Balances&call=transfer_keep_alive
func (service *Service) Transfer(signer Signer, to types.AccountID, amount uint64) *types.Hash {
    // 获取元数据信息
    metadata := service.GetMetadata()
    if metadata == nil {
        return nil
    }

    toMultiAddress, err := types.NewMultiAddressFromAccountID(to.ToBytes())
    if err != nil {
        zap.S().Warnf("failed to get to [%v] multi address, err: %v", to.ToHexString(), err)
        return nil
    }

    // Transfer Call
    transferCall, err := types.NewCall(metadata, "Balances.transfer_keep_alive", toMultiAddress, types.NewUCompactFromUInt(amount))
    if err != nil {
        zap.S().Warnf("failed to new Balances.transfer_keep_alive call, err: %v", err)
        return nil
    }

    // Extrinsic
    extrinsic := types.NewExtrinsic(transferCall)

    if signer.IsProxy {
        proxyType := signer.ProxyType
        realAccount := signer.RealAccountId

        // 获取被代理账户的账户 ID
        realMultiAddress, err := types.NewMultiAddressFromAccountID(realAccount.ToBytes())
        if err != nil {
            zap.S().Warnf("failed to get real [%v] multi address, err: %v", realAccount.ToHexString(), err)
            return nil
        }

        call, err := types.NewCall(metadata, "Proxy.proxy", realMultiAddress, types.NewOption[byte](byte(proxyType)), transferCall)
        if err != nil {
            zap.S().Warnf("failed to new Proxy.proxy call, err: %v", err)
            return nil
        }

        // Extrinsic
        extrinsic = types.NewExtrinsic(call)
    }
    extrinsicByte, err := extrinsic.MarshalJSON()
    if err != nil {
        zap.S().Warnf("failed to unmarshal extrinsic, err: %v", err)
        return nil
    }
    zap.S().Infof("singer: %v, real account: %v, extrinsic: %v\n", signer.Origin.Address, signer.RealAccountId.ToHexString(), FormatExtrinsic(extrinsicByte))

    // 获取签名配置
    options := service.GetSignatureOptions(signer.Origin.PublicKey)
    if options == nil {
        return nil
    }

    // 对交易体签名
    err = extrinsic.Sign(signer.Origin, *options)
    if err != nil {
        zap.S().Warnf("failed to sign extrinsic, err: %v", err)
        return nil
    }

    // 提交交易并返回交易哈希
    txHash, err := service.Api.RPC.Author.SubmitExtrinsic(extrinsic)
    if err != nil {
        zap.S().Warnf("failed to submit extrinsic, err: %v", err)
        return nil
    }
    return &txHash
}

// 从助记词派生出密钥对
keypair, err := signature.KeyringPairFromSecret(mnemonic+derivationPath, uint16(networkId))
if err != nil {
        zap.S().Warnf("failed to derive key pair: %v", err)
        return nil
}
cdamian commented 3 months ago

@YuXiaoCoder Please check the following issue for more details about signing and submitting transactions: https://github.com/centrifuge/go-substrate-rpc-client/pull/389

YuXiaoCoder commented 3 months ago

@YuXiaoCoder Please check the following issue for more details about signing and submitting transactions: #389

@cdamian

  1. How should I generate the KeyringPair for the moonbeam network, should I continue to use sr25519.Scheme{} or ecdsa..Scheme{}
  2. How should the derived path be written to correspond to the EVM address?('/m/44'/60'/0'/0/0')
  3. The account id for the moonbeam network is 20 bytes long, not 32, so it runs exceptionally: panic: invalid account ID bytes
  4. I'm a beginner, thanks a lot for being able to answer the stupid questions I'm asking, and if it's feasible, could you give me a full example? (including keypair generation, and example of signing and sending a transaction)
package main

import (
    "fmt"
    "math/big"

    gsrpc "github.com/centrifuge/go-substrate-rpc-client/v4"
    "github.com/centrifuge/go-substrate-rpc-client/v4/signature"
    "github.com/centrifuge/go-substrate-rpc-client/v4/types"
    "github.com/centrifuge/go-substrate-rpc-client/v4/types/codec"
    "github.com/centrifuge/go-substrate-rpc-client/v4/types/extrinsic"
    "github.com/centrifuge/go-substrate-rpc-client/v4/types/extrinsic/extensions"
    "go.uber.org/zap"
)

func main() {
    mnemonic := "XXXXXX"

    // 从助记词派生出密钥对
    keypair, err := signature.KeyringPairFromSecret(mnemonic+"/m/44'/60'/0'/0/0", uint16(1284))
    if err != nil {
        zap.S().Warnf("failed to derive key pair: %v", err)
        return
    }
    fmt.Println(keypair.Address)

    // Instantiate the API
    api, err := gsrpc.NewSubstrateAPI("wss://moonriver-rpc.dwellir.com")
    if err != nil {
        panic(err)
    }

    meta, err := api.RPC.State.GetMetadataLatest()
    if err != nil {
        panic(err)
    }

    // Create a call, transferring 1e10 units to Bob
    bob, err := types.NewMultiAddressFromHexAccountID("0x80893554E58bBCCa39faA3d88573e88f94145d86")
    if err != nil {
        panic(err)
    }

    // 1 unit of transfer
    bal, ok := new(big.Int).SetString("1000000000000", 10)
    if !ok {
        panic(fmt.Errorf("failed to convert balance"))
    }

    c, err := types.NewCall(meta, "Balances.transfer_keep_alive", bob, types.NewUCompact(bal))
    if err != nil {
        panic(err)
    }

    ext := extrinsic.NewDynamicExtrinsic(&c)

    genesisHash, err := api.RPC.Chain.GetBlockHash(0)
    if err != nil {
        panic(err)
    }

    rv, err := api.RPC.State.GetRuntimeVersionLatest()
    if err != nil {
        panic(err)
    }

    key, err := types.CreateStorageKey(meta, "System", "Account", keypair.PublicKey)
    if err != nil {
        panic(err)
    }

    var accountInfo types.AccountInfo
    ok, err = api.RPC.State.GetStorageLatest(key, &accountInfo)
    if err != nil || !ok {
        panic(err)
    }

    err = ext.Sign(
        keypair,
        meta,
        extrinsic.WithEra(types.ExtrinsicEra{IsImmortalEra: true}, genesisHash),
        extrinsic.WithNonce(types.NewUCompactFromUInt(uint64(accountInfo.Nonce))),
        extrinsic.WithTip(types.NewUCompactFromUInt(0)),
        extrinsic.WithSpecVersion(rv.SpecVersion),
        extrinsic.WithTransactionVersion(rv.TransactionVersion),
        extrinsic.WithGenesisHash(genesisHash),
        extrinsic.WithMetadataMode(extensions.CheckMetadataModeDisabled, extensions.CheckMetadataHash{Hash: types.NewEmptyOption[types.H256]()}),
        extrinsic.WithAssetID(types.NewEmptyOption[types.AssetID]()),
    )

    if err != nil {
        panic(err)
    }

    encodedExt, err := codec.EncodeToHex(ext)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Ext - %s\n", encodedExt)

    sub, err := api.RPC.Author.SubmitAndWatchDynamicExtrinsic(ext)
    if err != nil {
        panic(err)
    }
    defer sub.Unsubscribe()

    for {
        select {
        case st := <-sub.Chan():
            extStatus, _ := st.MarshalJSON()
            fmt.Printf("Status for transaction - %s\n", string(extStatus))
        case err := <-sub.Err():
            panic(err)
        }
    }
}