Last active
December 23, 2020 18:14
-
-
Save ChinmayPatel/8372362ebbaec2edf0d7942985bea7b5 to your computer and use it in GitHub Desktop.
Thorchain Go Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ¤t) | |
} | |
} | |
// // Find the correct Args Type | |
// var current types.Args | |
// err = types.DecodeFromBytes(ext.Method.Args, ¤t); | |
// 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