Skip to content

Instantly share code, notes, and snippets.

@ChinmayPatel
Last active December 23, 2020 18:14
Show Gist options
  • Save ChinmayPatel/8372362ebbaec2edf0d7942985bea7b5 to your computer and use it in GitHub Desktop.
Save ChinmayPatel/8372362ebbaec2edf0d7942985bea7b5 to your computer and use it in GitHub Desktop.
Thorchain Go Script
package main
import (
// "bytes"
// "github.com/centrifuge/go-substrate-rpc-client/scale"
"encoding/json"
"fmt"
"strings"
gsrpc "github.com/centrifuge/go-substrate-rpc-client"
"github.com/centrifuge/go-substrate-rpc-client/config"
"github.com/centrifuge/go-substrate-rpc-client/signature"
"github.com/centrifuge/go-substrate-rpc-client/types"
// Itering Imports
iScale "github.com/itering/scale.go"
iTypes "github.com/itering/scale.go/types"
iUtil "github.com/itering/subscan/util"
iMetadata "github.com/itering/substrate-api-rpc/metadata"
iRPC "github.com/itering/substrate-api-rpc/rpc"
iWspool "github.com/itering/substrate-api-rpc/websocket"
iSS58 "github.com/itering/subscan/util/ss58"
)
func main() {
// createTransaction();
// createRemark();
// listenToBlocks();
// readABlockUsingCentrifuge();
// readABlockUsingItering();
createUtilityBatchTx();
}
func createRemark(){
// Instantiate the API
api, err := gsrpc.NewSubstrateAPI(config.Default().RPCURL)
if err != nil {
panic(err)
}
opts := types.SerDeOptions{NoPalletIndices: true}
types.SetSerDeOptions(opts)
meta, err := api.RPC.State.GetMetadataLatest()
if err != nil {
panic(err)
}
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", signature.TestKeyringPairAlice.PublicKey, nil)
if err != nil {
panic(err)
}
var accountInfo types.AccountInfo
ok, err := api.RPC.State.GetStorageLatest(key, &accountInfo)
if err != nil || !ok {
panic(err)
}
nonce := uint32(accountInfo.Nonce)
o := types.SignatureOptions{
BlockHash: genesisHash,
Era: types.ExtrinsicEra{IsMortalEra: false},
GenesisHash: genesisHash,
Nonce: types.NewUCompactFromUInt(uint64(nonce)),
SpecVersion: rv.SpecVersion,
Tip: types.NewUCompactFromUInt(0),
TransactionVersion: rv.TransactionVersion,
}
call, err := types.NewCall(meta, "System.remark", "Just adding a remark here. Nothing to see." )
if err != nil {
panic(err)
}
// Create the extrinsic
ext := types.NewExtrinsic(call)
// Sign the transaction using Alice's default account
err = ext.Sign(signature.TestKeyringPairAlice, o)
if err != nil {
panic(err)
}
// Send the extrinsic
hash, err := api.RPC.Author.SubmitExtrinsic(ext)
if err != nil {
panic(err)
}
fmt.Printf("Transfer sent with hash %#x\n", hash)
}
func createTransaction() {
// This sample shows how to create a transaction to make a transfer from one an account to another.
// Instantiate the API
api, err := gsrpc.NewSubstrateAPI(config.Default().RPCURL)
if err != nil {
panic(err)
}
opts := types.SerDeOptions{NoPalletIndices: true}
types.SetSerDeOptions(opts)
meta, err := api.RPC.State.GetMetadataLatest()
if err != nil {
panic(err)
}
// Create a call, transferring 12345 units to Bob
bob, err := types.NewAddressFromHexAccountID("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48")
if err != nil {
panic(err)
}
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", signature.TestKeyringPairAlice.PublicKey, nil)
if err != nil {
panic(err)
}
var accountInfo types.AccountInfo
ok, err := api.RPC.State.GetStorageLatest(key, &accountInfo)
if err != nil || !ok {
panic(err)
}
nonce := uint32(accountInfo.Nonce)
o := types.SignatureOptions{
BlockHash: genesisHash,
Era: types.ExtrinsicEra{IsMortalEra: false},
GenesisHash: genesisHash,
Nonce: types.NewUCompactFromUInt(uint64(nonce)),
SpecVersion: rv.SpecVersion,
Tip: types.NewUCompactFromUInt(0),
TransactionVersion: rv.TransactionVersion,
}
call, err := types.NewCall(meta, "Balances.transfer", bob, types.NewUCompactFromUInt(10000000000000))
if err != nil {
panic(err)
}
// Create the extrinsic
ext := types.NewExtrinsic(call)
// Sign the transaction using Alice's default account
err = ext.Sign(signature.TestKeyringPairAlice, o)
if err != nil {
panic(err)
}
// Send the extrinsic
hash, err := api.RPC.Author.SubmitExtrinsic(ext)
if err != nil {
panic(err)
}
fmt.Printf("Transfer sent with hash %#x\n", hash)
}
func createUtilityBatchTx() {
// This sample shows how to create a transaction to make a transfer from one an account to another.
// Instantiate the API
api, err := gsrpc.NewSubstrateAPI(config.Default().RPCURL)
if err != nil {
panic(err)
}
opts := types.SerDeOptions{NoPalletIndices: true}
types.SetSerDeOptions(opts)
meta, err := api.RPC.State.GetMetadataLatest()
if err != nil {
panic(err)
}
// Create a call, transferring 12345 units to Bob
bob, err := types.NewAddressFromHexAccountID("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48")
if err != nil {
panic(err)
}
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", signature.TestKeyringPairAlice.PublicKey, nil)
if err != nil {
panic(err)
}
var accountInfo types.AccountInfo
ok, err := api.RPC.State.GetStorageLatest(key, &accountInfo)
if err != nil || !ok {
panic(err)
}
nonce := uint32(accountInfo.Nonce)
o := types.SignatureOptions{
BlockHash: genesisHash,
Era: types.ExtrinsicEra{IsMortalEra: false},
GenesisHash: genesisHash,
Nonce: types.NewUCompactFromUInt(uint64(nonce)),
SpecVersion: rv.SpecVersion,
Tip: types.NewUCompactFromUInt(0),
TransactionVersion: rv.TransactionVersion,
}
transferCall, err := types.NewCall(meta, "Balances.transfer", bob, types.NewUCompactFromUInt(10000000000000))
if err != nil {
panic(err)
}
remarkCall, err := types.NewCall(meta, "System.remark", []byte("Something is happening"))
if err != nil {
panic(err)
}
call, err := types.NewCall(meta, "Utility.batch", []types.Call{transferCall, remarkCall})
// Create the extrinsic
ext := types.NewExtrinsic(call)
// Sign the transaction using Alice's default account
err = ext.Sign(signature.TestKeyringPairAlice, o)
if err != nil {
panic(err)
}
// Send the extrinsic
hash, err := api.RPC.Author.SubmitExtrinsic(ext)
if err != nil {
panic(err)
}
fmt.Printf("Transfer sent with hash %#x\n", hash)
}
func listenToBlocks(){
// This example shows how to subscribe to new blocks.
//
// It displays the block number every time a new block is seen by the node you are connected to.
//
// NOTE: The example runs until 10 blocks are received or until you stop it with CTRL+C
api, err := gsrpc.NewSubstrateAPI(config.Default().RPCURL)
if err != nil {
panic(err)
}
sub, err := api.RPC.Chain.SubscribeNewHeads()
if err != nil {
panic(err)
}
defer sub.Unsubscribe()
count := 0
for {
head := <-sub.Chan()
fmt.Printf("Chain is at block: #%v\n", head.Number)
if err != nil {
panic(err)
}
blockHash, err := api.RPC.Chain.GetBlockHashLatest()
if(err != nil){
panic(err)
}
fmt.Println("The latest block hash is %{}", blockHash.Hex() );
// Get the block
block, err := api.RPC.Chain.GetBlock(blockHash)
if(err != nil){
panic(err)
}
// Go through each Extrinsics
for i, ext := range block.Block.Extrinsics {
// signDec, err := types.Decode
fmt.Println("EXT # ", i)
// fmt.Println("EXT ", ext)
// fmt.Println("Signer is ", ext.Signature.Signer)
fmt.Println("Signature is ", ext.Signature)
}
count++
if count == 10 {
sub.Unsubscribe()
break
}
}
}
func readABlockUsingCentrifuge(){
// api, err := gsrpc.NewSubstrateAPI(config.Default().RPCURL)
api, err := gsrpc.NewSubstrateAPI("https://westend-rpc.polkadot.io")
if err != nil {
panic(err)
}
// var blockHashString = "0xd7a1c8e7fb89dcf72895a03d0a64b0aeb36e11aa7ab3e9bf036975b2d3b4a2c7";
var blockHashString = "0x39718cb67ed41fb088ecfa3b7e5fe775d6b4867b38f67bc5be291b36ede18d8b" // Utility Batch on Westend
blockHash, err := types.NewHashFromHexString(blockHashString)
if(err != nil) {
panic(err)
}
types.SetSerDeOptions(types.SerDeOptions{NoPalletIndices: true})
// Get the block
block, err := api.RPC.Chain.GetBlock(blockHash)
if(err != nil){
panic(err)
}
meta, err := api.RPC.State.GetMetadata(blockHash)
if err != nil {
panic(err)
}
// Go through each Extrinsics
for i, ext := range block.Block.Extrinsics {
// i++
fmt.Println("EXT # ", i , " --> ", ext.Method.CallIndex)
for j, mod := range meta.AsMetadataV12.Modules{
j++
if (mod.Index == ext.Method.CallIndex.SectionIndex){
fmt.Println("Current EXT is : ", mod.Name, ".", mod.Calls[ext.Method.CallIndex.MethodIndex].Name);
fmt.Println("Args: ", ext.Method.Args);
var current types.EventAssetTransferred
types.DecodeFromBytes(ext.Method.Args, &current)
}
}
// // Find the correct Args Type
// var current types.Args
// err = types.DecodeFromBytes(ext.Method.Args, &current);
// if (err!= nil) {
// panic(err)
// }
// // ext.Decode()
}
// fmt.Println("meta is {}", meta)
}
func readABlockUsingItering(){
// TODO : Convert WSS calls into HTTP
iWspool.SetEndpoint("wss://westend-rpc.polkadot.io")
blockHash := "0x39718cb67ed41fb088ecfa3b7e5fe775d6b4867b38f67bc5be291b36ede18d8b";
/*
* TODO : Listen to the block
*/
/*
* Get Latest Metadata
*/
codedMetadataAtHash, _ := iRPC.GetMetadataByHash(nil, blockHash);
// metadataInBytes := iUtil.HexToBytes(metadata)
// m := iScale.MetadataDecoder{};
// m.Init(metadataInBytes);
// m.Process();
iMetadata.Latest(&iMetadata.RuntimeRaw{
Spec: 12,
Raw: strings.TrimPrefix(codedMetadataAtHash, "0x"),
})
currentMetadata := iMetadata.RuntimeMetadata[12]
/*
* Get Block Data
*/
v := &iRPC.JsonRpcResult{}
err := iWspool.SendWsRequest(nil, v, iRPC.ChainGetBlock(0, blockHash));
if (err != nil) {
fmt.Println("Could not read the block", err);
}
rpcBlock := v.ToBlock()
fmt.Println(rpcBlock)
/*
* Parse Extrinsics
*/
decodedExtrinsics, _ := decodeExtrinsics(rpcBlock.Block.Extrinsics, currentMetadata, 12)
// for _, ext := range rpcBlock.Block.Extrinsics {
// e := iScale.ExtrinsicDecoder{}
// option := iTypes.ScaleDecoderOption{Metadata: &m.Metadata}
// newExt := iUtil.HexToBytes(ext)
// e.Init(iTypes.ScaleBytes{Data: newExt}, &option)
// e.Process();
/*
* TODO
Check if the extrinsic is "utility.batch"
Check if the remark is of Thorchain format.
Parse the Thorchain format.
Get the address & value from the send transaction.
Check the events for "success" and make sure ther are no "EventInrerupted" thrown.
If any of these fails, send the failed message.
*/
/*
* TODO: Check if there is anything similar to `batch_all()
*/
for _, e := range decodedExtrinsics {
err := printExtrinsic(&e)
if err != nil {
fmt.Println(err)
}
}
// fmt.Println("decodedExtrinsics ", decodedExtrinsics);
// }
}
func decodeExtrinsics(list []string, metadata *iMetadata.Instant, spec int) (r []iScale.ExtrinsicDecoder, err error) {
defer func() {
if fatal := recover(); fatal != nil {
err = fmt.Errorf("Recovering from panic in DecodeExtrinsic: %v", fatal)
}
}()
m := iTypes.MetadataStruct(*metadata)
for _, extrinsicRaw := range list {
e := iScale.ExtrinsicDecoder{}
option := iTypes.ScaleDecoderOption{Metadata: &m, Spec: spec}
e.Init(iTypes.ScaleBytes{Data: iUtil.HexToBytes(extrinsicRaw)}, &option)
e.Process();
r = append(r, e)
}
return r, nil
}
type UtilityBatchDataForThorchain struct {
dest string `json:"dest"`
value string `json:"amount"`
memo string `json:"memo"`
}
func NewUtilityBatchDataForThorchain (dest string, value string, memo string) UtilityBatchDataForThorchain {
ub := UtilityBatchDataForThorchain {dest,value,memo}
return ub;
}
type UtilityBatchCall struct {
CallIndex string `json:"call_index"`
CallFunction string `json:"call_function"`
CallModule string `json:"call_module"`
CallArgs []iTypes.ExtrinsicParam `json:"call_args"`
}
func printExtrinsic(e *iScale.ExtrinsicDecoder) error {
parseUtilityBatchForThorchain(e);
// j, _ := json.MarshalIndent(e.Params, "", " ")
// fmt.Println("extrinsic ", e.CallModule.Name, " : ", e.Call.Name," decoded, params", e.Params)
// if e.CallModule.Name == "Utility" && e.Call.Name == "batch" {
// calls := &[]UtilityBatchCall{}
// err := unmarshalAny(calls, e.Params[0].Value)
// if err != nil {
// return fmt.Errorf("unable to decode utility batch calls: %v", e.Params[0].Value)
// }
// for _, c := range *calls {
// var stringifyArgs strings.Builder
// for _, a := range c.CallArgs {
// stringifyArgs.WriteString(
// fmt.Sprintf(" %s(%s)=%s", a.Name, a.Type, a.Value.(string)),
// )
// }
// fmt.Println("%s.%s%s", c.CallModule, c.CallFunction, stringifyArgs.String())
// }
// }
return nil
}
func parseUtilityBatchForThorchain(e *iScale.ExtrinsicDecoder) (error) {
if e.CallModule.Name == "Utility" && e.Call.Name == "batch" {
calls := &[]UtilityBatchCall{}
err := unmarshalAny(calls, e.Params[0].Value)
if err != nil {
return fmt.Errorf("unable to decode utility batch calls: %v", e.Params[0].Value)
}
fromAddress := iSS58.Encode(e.Address, iUtil.StringToInt("42"))
dest := ""
value := ""
memo := ""
for _, c := range *calls {
for _, a := range c.CallArgs {
switch a.Name {
case "dest":
dest = a.Value.(string)
dest = iSS58.Encode(dest, iUtil.StringToInt("42"))
break;
case "value":
value = a.Value.(string)
break;
case "_remark":
memo = a.Value.(string)
break;
}
}
}
ub := NewUtilityBatchDataForThorchain(dest, value, memo)
fmt.Println(ub)
fmt.Println(fromAddress)
}
return nil;
}
func unmarshalAny(r interface{}, raw interface{}) error {
j, err := json.Marshal(raw)
if err != nil {
return err
}
return json.Unmarshal(j, r)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment