From ff8fb693d735022d0474da45b2a60d03e3d2fcd4 Mon Sep 17 00:00:00 2001 From: ZeneDeLuca Date: Fri, 17 Jan 2025 08:16:52 +0000 Subject: [PATCH] feat: Add fee settlement functionality and update configuration for Sonic Grid --- hypergrid-aide/.hypergrid-aide.yaml | 3 + hypergrid-aide/main.go | 73 ++++++++++++++++++++++ hypergrid-aide/tools/cosmos.go | 96 +++++++++++++++++++++++++++++ hypergrid-aide/tools/solana.go | 91 +++++++++++++++++++++++++++ 4 files changed, 263 insertions(+) diff --git a/hypergrid-aide/.hypergrid-aide.yaml b/hypergrid-aide/.hypergrid-aide.yaml index 1c765da..ed72918 100644 --- a/hypergrid-aide/.hypergrid-aide.yaml +++ b/hypergrid-aide/.hypergrid-aide.yaml @@ -3,6 +3,9 @@ solana: baselayer_rpc: https://api.testnet.solana.com private_key: ~/.config/solana/id.json inbox_program_id: FG8P631H9q5b53qsVM9aD71GZTWBKvujtqeWUGstpeka + sonic_grid_rpc: http://api.grid-sonic.sonic.game + fee_program_id: SonicFeeSet1ement11111111111111111111111111 + fee_data_account_id: SonicFeeSet1ementData1111111111111111111112 cosmos: rpc: https://api.testnet.hssn.sonic.game/ address_prefix: cosmos diff --git a/hypergrid-aide/main.go b/hypergrid-aide/main.go index 81ea9af..213ebcf 100644 --- a/hypergrid-aide/main.go +++ b/hypergrid-aide/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "io" "log" @@ -21,8 +22,11 @@ import ( // Default values for the global variables var SOLANA_RPC_ENDPOINT = "http://localhost:8899" var SOLANA_BASELAYER_RPC = "https://api.testnet.solana.com" +var SOLANA_SONIC_GRID_RPC = "http://api.grid-sonic.sonic.game" var SOLANA_PRIVATE_KEY = "~/.config/solana/id.json" var SOLANA_InboxProgramID = "FG8P631H9q5b53qsVM9aD71GZTWBKvujtqeWUGstpeka" +var SonicFeeProgramID = "SonicFeeSet1ement11111111111111111111111111" +var SonicFeeDataAccountID = "SonicFeeSet1ementData1111111111111111111112" var COSMOS_RPC_ENDPOINT = "http://localhost:26657" var COSMOS_ADDRESS_PREFIX = "cosmos" var COSMOS_HOME = "~/.hypergrid-ssn" @@ -78,6 +82,9 @@ func readVariablesFromYaml(filename string) { SOLANA_BASELAYER_RPC = solana_params["baselayer_rpc"].(string) SOLANA_PRIVATE_KEY = solana_params["private_key"].(string) SOLANA_InboxProgramID = solana_params["inbox_program_id"].(string) + SOLANA_SONIC_GRID_RPC = solana_params["sonic_grid_rpc"].(string) + SonicFeeProgramID = solana_params["fee_program_id"].(string) + SonicFeeDataAccountID = solana_params["fee_data_account_id"].(string) cosmos_params := params["cosmos"].(map[string]interface{}) COSMOS_RPC_ENDPOINT = cosmos_params["rpc"].(string) @@ -163,6 +170,60 @@ func SyncStateAccount(cosmos tools.CosmosClient, account cosmosaccount.Account, log.Println(res) } +func SettleFeeBill(cosmos tools.CosmosClient, account cosmosaccount.Account) { + fromId := uint64(0) + endId := uint64(0) + + res1, err1 := cosmos.QueryLastFeeSettlementBill() + if err1 != nil { + log.Fatal(err1) + } + if len(res1.FeeSettlementBill) > 0 { + // Id = res1.FeeSettlementBill[0].Id + fromId = res1.FeeSettlementBill[0].EndId + } + + res2, err2 := cosmos.QueryLastGridBlockFee() + if err2 != nil { + log.Fatal(err1) + } + if len(res2.GridBlockFee) > 0 { + // Id = res1.FeeSettlementBill[0].Id + endId = res2.GridBlockFee[0].Id + } + + if fromId == 0 && endId == 0 { + log.Fatal("fromId or endId is 0") + return + } + + res, err := cosmos.SettleFeeBill(account, fromId, endId) + if err != nil { + log.Fatal(err) + } + log.Print("SettleFeeBill:\n\n") + log.Println(res) + + res3, err3 := cosmos.QueryLastFeeSettlementBill() + if err3 != nil { + log.Fatal(err3) + } + + bills := res3.FeeSettlementBill[0].Bill + //convert bills to bytes + // billsBytes := []byte(bills) + var Bills map[string]uint64 + json.Unmarshal([]byte(bills), &Bills) + + res4, err4 := tools.SendTxFeeSettlement(SOLANA_PRIVATE_KEY, SOLANA_SONIC_GRID_RPC, SonicFeeProgramID, SonicFeeDataAccountID, res3.FeeSettlementBill[0].FromId, res3.FeeSettlementBill[0].EndId, Bills) + if err4 != nil { + log.Fatal(err4) + } + + log.Print("SendTxFeeSettlement:\n\n") + log.Println(res4) +} + func main() { log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate) //get program arguments @@ -238,6 +299,18 @@ func main() { } SendGridBlockFees(*cosmos, *solana, account, gridId, limit) // break + case "settle": + cosmos := tools.NewCosmosClient( + cosmosclient.WithNodeAddress(COSMOS_RPC_ENDPOINT), + cosmosclient.WithAddressPrefix(COSMOS_ADDRESS_PREFIX), + cosmosclient.WithHome(COSMOS_HOME), + cosmosclient.WithGas(strconv.FormatUint(COSMOS_GAS, 10)), + ) + account, err := cosmos.Account(COSMOS_KEY) + if err != nil { + log.Fatal(err) + } + SettleFeeBill(*cosmos, account) default: fmt.Println("Usage: hypergrid-aide ") } diff --git a/hypergrid-aide/tools/cosmos.go b/hypergrid-aide/tools/cosmos.go index 94221ec..001b487 100644 --- a/hypergrid-aide/tools/cosmos.go +++ b/hypergrid-aide/tools/cosmos.go @@ -7,6 +7,7 @@ import ( "strconv" // Importing the general purpose Cosmos blockchain client + "github.com/cosmos/cosmos-sdk/types/query" "github.com/ignite/cli/v28/ignite/pkg/cosmosaccount" "github.com/ignite/cli/v28/ignite/pkg/cosmosclient" // Importing the types package of your blog blockchain @@ -156,6 +157,28 @@ func (c *CosmosClient) QueryAllGridBlockFees() (*types.QueryAllGridBlockFeeRespo return queryResp, err } +func (c *CosmosClient) QueryLastGridBlockFee() (*types.QueryAllGridBlockFeeResponse, error) { + // Instantiate a query client for your `blog` blockchain + queryClient := types.NewQueryClient(c.Client.Context()) + + // Query the blockchain using the client's `PostAll` method + // to get all posts store all posts in queryResp + // return queryClient.GridBlockFeeAll(c.Context, &types.QueryAllGridBlockFeeRequest{}) + queryResp, err := queryClient.GridBlockFeeAll(c.Context, &types.QueryAllGridBlockFeeRequest{Pagination: &query.PageRequest{ + Limit: 1, + Reverse: true, + }}) + if err != nil { + log.Fatal(err) + return nil, err + } + + // Print response from querying all the posts + log.Print("\n\nAll grid tx fee:\n\n") + log.Println(queryResp) + return queryResp, err +} + func (c *CosmosClient) QueryGridBlockFee(_id uint64) (*types.QueryGetGridBlockFeeResponse, error) { // Instantiate a query client for your `blog` blockchain queryClient := types.NewQueryClient(c.Client.Context()) @@ -190,3 +213,76 @@ func (c *CosmosClient) QueryAllHypergridNodes() (*types.QueryAllHypergridNodeRes log.Println(queryResp) return queryResp, err } + +func (c *CosmosClient) SettleFeeBill(account cosmosaccount.Account, fromId uint64, endId uint64) (*cosmosclient.Response, error) { + address, err := account.Address(COSMOS_ADDRESS_PREFIX) + if err != nil { + log.Fatal(err) + } + log.Println("Account: ", address) + + // Define a message to create a grid inbox + msg := types.MsgCreateFeeSettlementBill{ + Creator: address, + FromId: fromId, + EndId: endId, + } + + // Broadcast a transaction from account with the message + // to create a post store response in txResp + txResp, err := c.Client.BroadcastTx(c.Context, account, &msg) + if err != nil { + log.Fatal(err) + return nil, err + } + // Print response from broadcasting a transaction + log.Print("MsgCreateFeeSettlementBill:\n\n") + log.Println(txResp) + + _, err1 := c.Client.WaitForTx(c.Context, txResp.TxHash) + if err1 != nil { + log.Fatal(err1) + return nil, err1 + } + + return &txResp, nil +} + +func (c *CosmosClient) QueryLastFeeSettlementBill() (*types.QueryAllFeeSettlementBillResponse, error) { + // Instantiate a query client for your `blog` blockchain + queryClient := types.NewQueryClient(c.Client.Context()) + + // Query the blockchain using the client's `PostAll` method + // to get all posts store all posts in queryResp + queryResp, err := queryClient.FeeSettlementBillAll(c.Context, &types.QueryAllFeeSettlementBillRequest{Pagination: &query.PageRequest{ + Limit: 1, + Reverse: true, + }}) + if err != nil { + log.Fatal(err) + return nil, err + } + + // Print response from querying all the posts + log.Print("\n\nAll grid tx fee:\n\n") + log.Println(queryResp) + return queryResp, err +} + +func (c *CosmosClient) QueryGetFeeSettlementBill(_id uint64) (*types.QueryGetFeeSettlementBillResponse, error) { + // Instantiate a query client for your `blog` blockchain + queryClient := types.NewQueryClient(c.Client.Context()) + + // Query the blockchain using the client's `PostAll` method + // to get all posts store all posts in queryResp + queryResp, err := queryClient.FeeSettlementBill(c.Context, &types.QueryGetFeeSettlementBillRequest{Id: _id}) + if err != nil { + log.Fatal(err) + return nil, err + } + + // Print response from querying all the posts + log.Print("\n\nGet grid tx fee:\n\n") + log.Println(queryResp) + return queryResp, err +} diff --git a/hypergrid-aide/tools/solana.go b/hypergrid-aide/tools/solana.go index 4eac921..cd443e7 100644 --- a/hypergrid-aide/tools/solana.go +++ b/hypergrid-aide/tools/solana.go @@ -1,11 +1,14 @@ package tools import ( + "bytes" "context" "crypto/sha256" + "encoding/binary" "encoding/hex" "fmt" "log" + "sort" "strings" "github.com/davecgh/go-spew/spew" @@ -371,3 +374,91 @@ func SendTxInbox(localPrivateKey string, rpcUrl string, programId string, slot u return sig, nil } + +type SettlementBillParam struct { + Key solana.PublicKey + Amount uint64 +} + +type SettleFeeBillParams struct { + Instruction uint32 + FromID uint64 + EndID uint64 + Bills []SettlementBillParam +} + +// BorshEncode encodes the InstructionData using Borsh +func (d *SettleFeeBillParams) BorshEncode() ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.LittleEndian, d.Instruction) + if err != nil { + return nil, err + } + err = binary.Write(buf, binary.LittleEndian, d.FromID) + if err != nil { + return nil, err + } + err = binary.Write(buf, binary.LittleEndian, d.EndID) + if err != nil { + return nil, err + } + billCount := uint64(len(d.Bills)) + err = binary.Write(buf, binary.LittleEndian, billCount) + if err != nil { + return nil, err + } + for _, bill := range d.Bills { + err = binary.Write(buf, binary.LittleEndian, bill.Key[:]) + if err != nil { + return nil, err + } + err = binary.Write(buf, binary.LittleEndian, bill.Amount) + if err != nil { + return nil, err + } + } + return buf.Bytes(), nil +} + +func SendTxFeeSettlement(localPrivateKey string, rpcUrl string, SonicFeeProgramID string, SonicFeeDataAccountID string, FromId uint64, EndID uint64, bills map[string]uint64) (*solana.Signature, error) { + Bills := []SettlementBillParam{} + // convert bills to []SettlementBillParam + for key, value := range bills { + Bills = append(Bills, SettlementBillParam{ + Key: solana.MustPublicKeyFromBase58(key), + Amount: value, + }) + } + + //sort bills by key + sort.Slice(Bills, func(i, j int) bool { + return Bills[i].Key.String() < Bills[j].Key.String() + }) + + instructionData := SettleFeeBillParams{ + Instruction: 1, + FromID: FromId, + EndID: EndID, + Bills: Bills, + } + + // Serialize to bytes using Borsh + serializedData, err := instructionData.BorshEncode() // borsh.Serialize(instructionData) + if err != nil { + // panic(err) + return nil, err + } + + accounts := solana.AccountMetaSlice{ + solana.NewAccountMeta(solana.MustPublicKeyFromBase58(SonicFeeDataAccountID), true, false), + } + + signer, err := getLocalPrivateKey(localPrivateKey) + if err != nil { + // panic(err) + return nil, err + } + + signers := []solana.PrivateKey{signer} + return sendSonicTx(rpcUrl, SonicFeeProgramID, accounts, serializedData, signers) +}