diff --git a/.dockerignore b/.dockerignore index 9801cc1..72e9d78 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ .git web_wallet/node_modules -vm/tests/e2e/e2e.test +tests/e2e/e2e.test +build \ No newline at end of file diff --git a/.env.example b/.env.example index f9e891d..42feb5c 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,3 @@ -SERVE_DOMAIN=mysuperdomain.com \ No newline at end of file +FAUCET_PRIVATE_KEY_HEX="" +PREFUND_ADDRESS="" +SERVE_DOMAIN="" diff --git a/.gitignore b/.gitignore index f26b895..3ce8077 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env -vm/tests/e2e/e2e.test +tests/e2e/e2e.test +build \ No newline at end of file diff --git a/Dockerfile.devnet b/Dockerfile.devnet index 8b9de8e..ed51d55 100644 --- a/Dockerfile.devnet +++ b/Dockerfile.devnet @@ -18,13 +18,13 @@ COPY --from=avalanchego /avalanchego/build/avalanchego /root/.hypersdk/avalanche ENV GOMODCACHE /go/pkg/mod -WORKDIR / +WORKDIR /app COPY ./go.mod ./go.sum ./ -COPY ./vm ./vm +COPY ./ ./ -WORKDIR /vm +WORKDIR /app ENTRYPOINT ["/bin/bash", "-c", "./scripts/stop.sh; ./scripts/run.sh && echo 'Devnet started' && tail -f /dev/null"] diff --git a/Dockerfile.faucet b/Dockerfile.faucet index a16e2eb..2541017 100644 --- a/Dockerfile.faucet +++ b/Dockerfile.faucet @@ -7,12 +7,12 @@ FROM golang:1.22-bookworm AS faucet-builder WORKDIR /build COPY ./go.mod ./go.sum ./ -COPY ./vm ./vm +COPY ./ ./ ENV GOMODCACHE /go/pkg/mod RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ - go build -o build/faucet ./vm/cmd/faucet + go build -o build/faucet ./cmd/faucet ##### # Final layer with faucet, VM and avalanchego @@ -20,5 +20,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \ FROM debian:bookworm-slim COPY --from=faucet-builder /build/build/faucet /faucet +ENV RPC_ENDPOINT=http://devnet:9650 + ENTRYPOINT ["/faucet"] diff --git a/actions/hi.go b/actions/hi.go new file mode 100644 index 0000000..9745d18 --- /dev/null +++ b/actions/hi.go @@ -0,0 +1,84 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package actions + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/ids" + + "github.com/ava-labs/hypersdk-starter/storage" + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/state" +) + +const ( + HiComputeUnits = 1 + MaxNameSize = 256 +) + +var ( + ErrNameTooLarge = fmt.Errorf("name is too large") + _ chain.Action = (*Hi)(nil) +) + +type Hi struct { + Name string `serialize:"true" json:"name"` +} + +type HiResult struct { + Greeting string `serialize:"true" json:"greeting"` + Balance uint64 `serialize:"true" json:"balance"` +} + +func (*Hi) GetTypeID() uint8 { + return 1 +} + +func (h *Hi) StateKeys(actor codec.Address, _ ids.ID) state.Keys { + return state.Keys{ + string(storage.BalanceKey(actor)): state.Read, + } +} + +func (*Hi) StateKeysMaxChunks() []uint16 { + return []uint16{storage.BalanceChunks} +} + +func (h *Hi) Execute( + ctx context.Context, + _ chain.Rules, + mu state.Mutable, + _ int64, + actor codec.Address, + _ ids.ID, +) ([][]byte, error) { + if len(h.Name) > MaxNameSize { + return nil, ErrNameTooLarge + } + + balance, err := storage.GetBalance(ctx, mu, actor) + if err != nil { + return nil, err + } + + greeting := fmt.Sprintf("Hi, %s", h.Name) + + bytes, err := codec.Marshal(HiResult{ + Greeting: greeting, + Balance: balance, + }) + + return [][]byte{bytes}, err +} + +func (*Hi) ComputeUnits(chain.Rules) uint64 { + return HiComputeUnits +} + +func (*Hi) ValidRange(chain.Rules) (int64, int64) { + return -1, -1 +} diff --git a/vm/actions/transfer.go b/actions/transfer.go similarity index 68% rename from vm/actions/transfer.go rename to actions/transfer.go index 3c0ca9c..372c6c5 100644 --- a/vm/actions/transfer.go +++ b/actions/transfer.go @@ -9,13 +9,10 @@ import ( "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk-starter/vm/storage" + "github.com/ava-labs/hypersdk-starter/storage" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/state" - - mconsts "github.com/ava-labs/hypersdk-starter/vm/consts" - consts "github.com/ava-labs/hypersdk/consts" ) const ( @@ -37,11 +34,16 @@ type Transfer struct { Value uint64 `serialize:"true" json:"value"` // Optional message to accompany transaction. - Memo codec.StringAsBytes `serialize:"true" json:"memo"` + Memo codec.Bytes `serialize:"true" json:"memo"` +} + +type TransferResult struct { + SenderBalance uint64 `serialize:"true" json:"sender_balance"` + ReceiverBalance uint64 `serialize:"true" json:"receiver_balance"` } func (*Transfer) GetTypeID() uint8 { - return mconsts.TransferID + return 0 } func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys { @@ -75,7 +77,22 @@ func (t *Transfer) Execute( if err := storage.AddBalance(ctx, mu, t.To, t.Value, true); err != nil { return nil, err } - return nil, nil + + senderBalance, err := storage.GetBalance(ctx, mu, actor) + if err != nil { + return nil, err + } + receiverBalance, err := storage.GetBalance(ctx, mu, t.To) + if err != nil { + return nil, err + } + + bytes, err := codec.Marshal(TransferResult{ + SenderBalance: senderBalance, + ReceiverBalance: receiverBalance, + }) + + return [][]byte{bytes}, err } func (*Transfer) ComputeUnits(chain.Rules) uint64 { @@ -86,24 +103,3 @@ func (*Transfer) ValidRange(chain.Rules) (int64, int64) { // Returning -1, -1 means that the action is always valid. return -1, -1 } - -// Implementing chain.Marshaler is optional but can be used to optimize performance when hitting TPS limits -var _ chain.Marshaler = (*Transfer)(nil) - -func (t *Transfer) Size() int { - return codec.AddressLen + consts.Uint64Len + codec.BytesLen(t.Memo) -} - -func (t *Transfer) Marshal(p *codec.Packer) { - p.PackAddress(t.To) - p.PackLong(t.Value) - p.PackBytes(t.Memo) -} - -func UnmarshalTransfer(p *codec.Packer) (chain.Action, error) { - var transfer Transfer - p.UnpackAddress(&transfer.To) - transfer.Value = p.UnpackUint64(true) - p.UnpackBytes(MaxMemoSize, false, (*[]byte)(&transfer.Memo)) - return &transfer, p.Err() -} diff --git a/vm/actions/transfer_test.go b/actions/transfer_test.go similarity index 91% rename from vm/actions/transfer_test.go rename to actions/transfer_test.go index 5ff050d..2e87a5a 100644 --- a/vm/actions/transfer_test.go +++ b/actions/transfer_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/ava-labs/hypersdk-starter/vm/storage" + "github.com/ava-labs/hypersdk-starter/storage" "github.com/ava-labs/hypersdk/chain/chaintest" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/codec/codectest" @@ -79,6 +79,12 @@ func TestTransferAction(t *testing.T) { require.NoError(t, err) require.Equal(t, balance, uint64(1)) }, + ExpectedOutputs: [][]byte{ + codec.MustMarshal(TransferResult{ + SenderBalance: 1, + ReceiverBalance: 1, + }), + }, }, { Name: "OverflowBalance", @@ -117,6 +123,12 @@ func TestTransferAction(t *testing.T) { require.NoError(t, err) require.Equal(t, senderBalance, uint64(0)) }, + ExpectedOutputs: [][]byte{ + codec.MustMarshal(TransferResult{ + SenderBalance: 0, + ReceiverBalance: 1, + }), + }, }, } diff --git a/vm/cmd/faucet/faucet.go b/cmd/faucet/faucet.go similarity index 68% rename from vm/cmd/faucet/faucet.go rename to cmd/faucet/faucet.go index 715547f..ad0de50 100644 --- a/vm/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -17,9 +17,9 @@ import ( "github.com/rs/cors" "golang.org/x/time/rate" - "github.com/ava-labs/hypersdk-starter/vm/actions" - "github.com/ava-labs/hypersdk-starter/vm/consts" - "github.com/ava-labs/hypersdk-starter/vm/vm" + "github.com/ava-labs/hypersdk-starter/actions" + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk-starter/vm" "github.com/ava-labs/hypersdk/api/jsonrpc" "github.com/ava-labs/hypersdk/auth" "github.com/ava-labs/hypersdk/chain" @@ -28,13 +28,18 @@ import ( "github.com/ava-labs/hypersdk/utils" ) -const amtStr = "10.00" +const ( + amtStr = "10.00" + faucetServerPort = "8765" +) var ( priv ed25519.PrivateKey factory chain.AuthFactory hyperVMRPC *vm.JSONRPCClient hyperSDKRPC *jsonrpc.JSONRPCClient + isReady bool + healthMu sync.RWMutex ) func init() { @@ -51,11 +56,10 @@ func init() { rpcEndpoint := os.Getenv("RPC_ENDPOINT") if rpcEndpoint == "" { - rpcEndpoint = "http://localhost:9650" + log.Fatalf("RPC_ENDPOINT is not set") } url := fmt.Sprintf("%s/ext/bc/%s", rpcEndpoint, consts.Name) hyperVMRPC = vm.NewJSONRPCClient(url) - hyperSDKRPC = jsonrpc.NewJSONRPCClient(url) } @@ -74,12 +78,11 @@ func transferCoins(to string) (string, error) { if err != nil { return "", fmt.Errorf("failed to get balance: %w", err) } - fmt.Printf("Balance before: %s\n", utils.FormatBalance(balanceBefore, consts.Decimals)) + log.Printf("Balance before: %s\n", utils.FormatBalance(balanceBefore, consts.Decimals)) - // Check if balance is greater than 1.000 threshold, _ := utils.ParseBalance("1.000", consts.Decimals) if balanceBefore > threshold { - fmt.Printf("Balance is already greater than 1.000, no transfer needed\n") + log.Printf("Balance is already greater than 1.000, no transfer needed\n") return "Balance is already greater than 1.000, no transfer needed", nil } @@ -101,70 +104,106 @@ func transferCoins(to string) (string, error) { return "", fmt.Errorf("failed to generate transaction: %w", err) } - err = submit(context.TODO()) - if err != nil { + if err := submit(context.TODO()); err != nil { return "", fmt.Errorf("failed to submit transaction: %w", err) } - err = hyperVMRPC.WaitForBalance(context.TODO(), to, amt) - if err != nil { + if err := hyperVMRPC.WaitForBalance(context.TODO(), to, amt); err != nil { return "", fmt.Errorf("failed to wait for balance: %w", err) } return "Coins transferred successfully", nil } - func main() { r := mux.NewRouter() r.HandleFunc("/faucet/{address}", handleFaucetRequest).Methods("GET", "POST") + r.HandleFunc("/readyz", handleReadyCheck).Methods("GET") - // Create a new CORS handler c := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "POST", "OPTIONS"}, AllowedHeaders: []string{"*"}, }) - // Wrap the router with the CORS handler handler := c.Handler(r) - zeroAddressBech32 := codec.MustAddressBech32(consts.HRP, codec.Address{1, 2, 3}) - fmt.Printf("Starting faucet server on port 8765\nOpen http://localhost:8765/faucet/%s to test the transfer\n", zeroAddressBech32) + performInitialTransfer() srv := &http.Server{ - Addr: ":8765", + Addr: ":" + faucetServerPort, Handler: handler, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } + log.Printf("Starting faucet server on port %s\n", faucetServerPort) + log.Printf("Ready check endpoint: http://localhost:%s/readyz\n", faucetServerPort) + log.Printf("Faucet endpoint: http://localhost:%s/faucet/{address}\n", faucetServerPort) + if err := srv.ListenAndServe(); err != nil { log.Fatal(err) } } +func performInitialTransfer() { + zeroAddressBech32 := codec.MustAddressBech32(consts.HRP, codec.Address{1, 2, 3}) + log.Println("Performing initial transfer to ready check address...") + + for i := 0; i < 10; i++ { + message, err := transferCoins(zeroAddressBech32) + if err == nil { + log.Printf("Initial transfer result: %s\n", message) + setReady(true) + log.Println("Faucet is now healthy and ready to serve requests") + return + } + log.Printf("Attempt %d failed to perform initial transfer: %v\n", i+1, err) + time.Sleep(time.Duration(i+1) * time.Second) + } + + log.Fatal("Faucet initialization failed after 5 attempts. Exiting.") +} + +func setReady(status bool) { + healthMu.Lock() + defer healthMu.Unlock() + isReady = status +} + +func getReadyStatus() bool { + healthMu.RLock() + defer healthMu.RUnlock() + return isReady +} + +func handleReadyCheck(w http.ResponseWriter, r *http.Request) { + if getReadyStatus() { + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, "Faucet is healthy") + } else { + w.WriteHeader(http.StatusServiceUnavailable) + fmt.Fprint(w, "Faucet is not yet healthy") + } +} + var ( ipLimiters = make(map[string]*rate.Limiter) mu sync.Mutex ) func handleFaucetRequest(w http.ResponseWriter, r *http.Request) { - // Set CORS headers for all responses w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "*") - // Handle preflight requests if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } - // Get client IP clientIP := r.RemoteAddr - // Check rate limit if !getRateLimiter(clientIP).Allow() { http.Error(w, "Rate limit exceeded. Please try again later.", http.StatusTooManyRequests) return @@ -179,7 +218,7 @@ func handleFaucetRequest(w http.ResponseWriter, r *http.Request) { message, err := transferCoins(address) if err != nil { - fmt.Printf("Failed to transfer coins: %v\n", err) + log.Printf("Failed to transfer coins: %v\n", err) http.Error(w, fmt.Sprintf("Failed to transfer coins: %v", err), http.StatusInternalServerError) return } diff --git a/cmd/keygen/gen.go b/cmd/keygen/gen.go new file mode 100644 index 0000000..4d74da7 --- /dev/null +++ b/cmd/keygen/gen.go @@ -0,0 +1,66 @@ +package main + +import ( + "encoding/hex" + "fmt" + "os" + + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk/auth" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/crypto/ed25519" + "github.com/joho/godotenv" +) + +func main() { + priv, err := ed25519.GeneratePrivateKey() + if err != nil { + panic(err) + } + + pub := priv.PublicKey() + addrStr := codec.MustAddressBech32(consts.HRP, auth.NewED25519Address(pub)) + + privKeyHex := hex.EncodeToString(priv[:]) + + // Check if .env file exists, if not copy from .env.example + if _, err := os.Stat(".env"); os.IsNotExist(err) { + err = copyFile(".env.example", ".env") + if err != nil { + panic(err) + } + fmt.Println(".env file created from .env.example") + } + + // Load existing .env file + envMap, err := godotenv.Read(".env") + if err != nil { + panic(err) + } + + // Always update FAUCET_PRIVATE_KEY_HEX and PREFUND_ADDRESS + envMap["FAUCET_PRIVATE_KEY_HEX"] = privKeyHex + envMap["PREFUND_ADDRESS"] = addrStr + + // Write to .env file + err = godotenv.Write(envMap, ".env") + if err != nil { + panic(err) + } + + fmt.Println("Private key and address have been written to .env file") +} + +func copyFile(src, dst string) error { + input, err := os.ReadFile(src) + if err != nil { + return err + } + + err = os.WriteFile(dst, input, 0644) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/morpheus-cli/cmd/action.go b/cmd/morpheus-cli/cmd/action.go new file mode 100644 index 0000000..a637884 --- /dev/null +++ b/cmd/morpheus-cli/cmd/action.go @@ -0,0 +1,69 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/ava-labs/hypersdk-starter/actions" + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/cli/prompt" + "github.com/ava-labs/hypersdk/codec" +) + +var actionCmd = &cobra.Command{ + Use: "action", + RunE: func(*cobra.Command, []string) error { + return ErrMissingSubcommand + }, +} + +var transferCmd = &cobra.Command{ + Use: "transfer", + RunE: func(*cobra.Command, []string) error { + ctx := context.Background() + _, priv, factory, cli, bcli, ws, err := handler.DefaultActor() + if err != nil { + return err + } + + // Get balance info + balance, err := handler.GetBalance(ctx, bcli, priv.Address) + if balance == 0 || err != nil { + return err + } + + // Select recipient + receipientStr, err := prompt.String("recipient", 0, 200) + if err != nil { + return err + } + recipient, err := codec.ParseAddressBech32(consts.HRP, receipientStr) + if err != nil { + return err + } + + // Select amount + amount, err := prompt.Amount("amount", consts.Decimals, balance, nil) + if err != nil { + return err + } + + // Confirm action + cont, err := prompt.Continue() + if !cont || err != nil { + return err + } + + // Generate transaction + _, _, err = sendAndWait(ctx, []chain.Action{&actions.Transfer{ + To: recipient, + Value: amount, + }}, cli, bcli, ws, factory, true) + return err + }, +} diff --git a/cmd/morpheus-cli/cmd/chain.go b/cmd/morpheus-cli/cmd/chain.go new file mode 100644 index 0000000..0869bf3 --- /dev/null +++ b/cmd/morpheus-cli/cmd/chain.go @@ -0,0 +1,43 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import ( + "github.com/spf13/cobra" +) + +var chainCmd = &cobra.Command{ + Use: "chain", + RunE: func(*cobra.Command, []string) error { + return ErrMissingSubcommand + }, +} + +var importChainCmd = &cobra.Command{ + Use: "import", + RunE: func(_ *cobra.Command, _ []string) error { + return handler.Root().ImportChain() + }, +} + +var setChainCmd = &cobra.Command{ + Use: "set", + RunE: func(*cobra.Command, []string) error { + return handler.Root().SetDefaultChain() + }, +} + +var chainInfoCmd = &cobra.Command{ + Use: "info", + RunE: func(_ *cobra.Command, _ []string) error { + return handler.Root().PrintChainInfo() + }, +} + +var watchChainCmd = &cobra.Command{ + Use: "watch", + RunE: func(_ *cobra.Command, args []string) error { + return handler.Root().WatchChain(hideTxs) + }, +} diff --git a/cmd/morpheus-cli/cmd/errors.go b/cmd/morpheus-cli/cmd/errors.go new file mode 100644 index 0000000..11e9bf4 --- /dev/null +++ b/cmd/morpheus-cli/cmd/errors.go @@ -0,0 +1,13 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import "errors" + +var ( + ErrInvalidArgs = errors.New("invalid args") + ErrMissingSubcommand = errors.New("must specify a subcommand") + ErrInvalidAddress = errors.New("invalid address") + ErrInvalidKeyType = errors.New("invalid key type") +) diff --git a/cmd/morpheus-cli/cmd/genesis.go b/cmd/morpheus-cli/cmd/genesis.go new file mode 100644 index 0000000..3183bd2 --- /dev/null +++ b/cmd/morpheus-cli/cmd/genesis.go @@ -0,0 +1,79 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import ( + "encoding/json" + "os" + + "github.com/fatih/color" + "github.com/spf13/cobra" + + "github.com/ava-labs/hypersdk/fees" + "github.com/ava-labs/hypersdk/genesis" +) + +var genesisCmd = &cobra.Command{ + Use: "genesis", + RunE: func(*cobra.Command, []string) error { + return ErrMissingSubcommand + }, +} + +var genGenesisCmd = &cobra.Command{ + Use: "generate [custom allocates file] [options]", + Short: "Creates a new genesis in the default location", + PreRunE: func(_ *cobra.Command, args []string) error { + if len(args) != 1 { + return ErrInvalidArgs + } + return nil + }, + RunE: func(_ *cobra.Command, args []string) error { + a, err := os.ReadFile(args[0]) + if err != nil { + return err + } + var allocs []*genesis.CustomAllocation + if err := json.Unmarshal(a, &allocs); err != nil { + return err + } + genesis := genesis.NewDefaultGenesis(allocs) + if len(minUnitPrice) > 0 { + d, err := fees.ParseDimensions(minUnitPrice) + if err != nil { + return err + } + genesis.Rules.MinUnitPrice = d + } + if len(maxBlockUnits) > 0 { + d, err := fees.ParseDimensions(maxBlockUnits) + if err != nil { + return err + } + genesis.Rules.MaxBlockUnits = d + } + if len(windowTargetUnits) > 0 { + d, err := fees.ParseDimensions(windowTargetUnits) + if err != nil { + return err + } + genesis.Rules.WindowTargetUnits = d + } + if minBlockGap >= 0 { + genesis.Rules.MinBlockGap = minBlockGap + } + + b, err := json.Marshal(genesis) + if err != nil { + return err + } + if err := os.WriteFile(genesisFile, b, fsModeWrite); err != nil { + return err + } + + color.Green("created genesis and saved to %s", genesisFile) + return nil + }, +} diff --git a/cmd/morpheus-cli/cmd/handler.go b/cmd/morpheus-cli/cmd/handler.go new file mode 100644 index 0000000..207e1ff --- /dev/null +++ b/cmd/morpheus-cli/cmd/handler.go @@ -0,0 +1,152 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import ( + "context" + + "github.com/ava-labs/avalanchego/ids" + + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk-starter/vm" + "github.com/ava-labs/hypersdk/api/jsonrpc" + "github.com/ava-labs/hypersdk/api/ws" + "github.com/ava-labs/hypersdk/auth" + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/cli" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/crypto/bls" + "github.com/ava-labs/hypersdk/crypto/ed25519" + "github.com/ava-labs/hypersdk/crypto/secp256r1" + "github.com/ava-labs/hypersdk/utils" +) + +var _ cli.Controller = (*Controller)(nil) + +type Handler struct { + h *cli.Handler +} + +func NewHandler(h *cli.Handler) *Handler { + return &Handler{h} +} + +func (h *Handler) Root() *cli.Handler { + return h.h +} + +func (h *Handler) DefaultActor() ( + ids.ID, *cli.PrivateKey, chain.AuthFactory, + *jsonrpc.JSONRPCClient, *vm.JSONRPCClient, *ws.WebSocketClient, error, +) { + addr, priv, err := h.h.GetDefaultKey(true) + if err != nil { + return ids.Empty, nil, nil, nil, nil, nil, err + } + var factory chain.AuthFactory + switch addr[0] { + case auth.ED25519ID: + factory = auth.NewED25519Factory(ed25519.PrivateKey(priv)) + case auth.SECP256R1ID: + factory = auth.NewSECP256R1Factory(secp256r1.PrivateKey(priv)) + case auth.BLSID: + p, err := bls.PrivateKeyFromBytes(priv) + if err != nil { + return ids.Empty, nil, nil, nil, nil, nil, err + } + factory = auth.NewBLSFactory(p) + default: + return ids.Empty, nil, nil, nil, nil, nil, ErrInvalidAddress + } + chainID, uris, err := h.h.GetDefaultChain(true) + if err != nil { + return ids.Empty, nil, nil, nil, nil, nil, err + } + jcli := jsonrpc.NewJSONRPCClient(uris[0]) + if err != nil { + return ids.Empty, nil, nil, nil, nil, nil, err + } + ws, err := ws.NewWebSocketClient(uris[0], ws.DefaultHandshakeTimeout, 1024, 1024*1024) + if err != nil { + return ids.Empty, nil, nil, nil, nil, nil, err + } + // For [defaultActor], we always send requests to the first returned URI. + return chainID, &cli.PrivateKey{ + Address: addr, + Bytes: priv, + }, factory, jcli, + vm.NewJSONRPCClient( + uris[0], + ), ws, nil +} + +func (*Handler) GetBalance( + ctx context.Context, + cli *vm.JSONRPCClient, + addr codec.Address, +) (uint64, error) { + saddr, err := codec.AddressBech32(consts.HRP, addr) + if err != nil { + return 0, err + } + balance, err := cli.Balance(ctx, saddr) + if err != nil { + return 0, err + } + if balance == 0 { + utils.Outf("{{red}}balance:{{/}} 0 %s\n", consts.Symbol) + utils.Outf("{{red}}please send funds to %s{{/}}\n", saddr) + utils.Outf("{{red}}exiting...{{/}}\n") + return 0, nil + } + utils.Outf( + "{{yellow}}balance:{{/}} %s %s\n", + utils.FormatBalance(balance, consts.Decimals), + consts.Symbol, + ) + return balance, nil +} + +type Controller struct { + databasePath string +} + +func NewController(databasePath string) *Controller { + return &Controller{databasePath} +} + +func (c *Controller) DatabasePath() string { + return c.databasePath +} + +func (*Controller) Symbol() string { + return consts.Symbol +} + +func (*Controller) Decimals() uint8 { + return consts.Decimals +} + +func (*Controller) Address(addr codec.Address) string { + return codec.MustAddressBech32(consts.HRP, addr) +} + +func (*Controller) ParseAddress(addr string) (codec.Address, error) { + return codec.ParseAddressBech32(consts.HRP, addr) +} + +func (*Controller) GetParser(uri string) (chain.Parser, error) { + cli := vm.NewJSONRPCClient(uri) + return cli.Parser(context.TODO()) +} + +func (*Controller) HandleTx(tx *chain.Transaction, result *chain.Result) { + handleTx(tx, result) +} + +func (*Controller) LookupBalance(address string, uri string) (uint64, error) { + cli := vm.NewJSONRPCClient(uri) + balance, err := cli.Balance(context.TODO(), address) + return balance, err +} diff --git a/cmd/morpheus-cli/cmd/key.go b/cmd/morpheus-cli/cmd/key.go new file mode 100644 index 0000000..d76afde --- /dev/null +++ b/cmd/morpheus-cli/cmd/key.go @@ -0,0 +1,184 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk/auth" + "github.com/ava-labs/hypersdk/cli" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/crypto/bls" + "github.com/ava-labs/hypersdk/crypto/ed25519" + "github.com/ava-labs/hypersdk/crypto/secp256r1" + "github.com/ava-labs/hypersdk/utils" +) + +const ( + ed25519Key = "ed25519" + secp256r1Key = "secp256r1" + blsKey = "bls" +) + +func checkKeyType(k string) error { + switch k { + case ed25519Key, secp256r1Key, blsKey: + return nil + default: + return fmt.Errorf("%w: %s", ErrInvalidKeyType, k) + } +} + +func generatePrivateKey(k string) (*cli.PrivateKey, error) { + switch k { + case ed25519Key: + p, err := ed25519.GeneratePrivateKey() + if err != nil { + return nil, err + } + return &cli.PrivateKey{ + Address: auth.NewED25519Address(p.PublicKey()), + Bytes: p[:], + }, nil + case secp256r1Key: + p, err := secp256r1.GeneratePrivateKey() + if err != nil { + return nil, err + } + return &cli.PrivateKey{ + Address: auth.NewSECP256R1Address(p.PublicKey()), + Bytes: p[:], + }, nil + case blsKey: + p, err := bls.GeneratePrivateKey() + if err != nil { + return nil, err + } + return &cli.PrivateKey{ + Address: auth.NewBLSAddress(bls.PublicFromPrivateKey(p)), + Bytes: bls.PrivateKeyToBytes(p), + }, nil + default: + return nil, ErrInvalidKeyType + } +} + +func loadPrivateKey(k string, path string) (*cli.PrivateKey, error) { + switch k { + case ed25519Key: + p, err := utils.LoadBytes(path, ed25519.PrivateKeyLen) + if err != nil { + return nil, err + } + pk := ed25519.PrivateKey(p) + return &cli.PrivateKey{ + Address: auth.NewED25519Address(pk.PublicKey()), + Bytes: p, + }, nil + case secp256r1Key: + p, err := utils.LoadBytes(path, secp256r1.PrivateKeyLen) + if err != nil { + return nil, err + } + pk := secp256r1.PrivateKey(p) + return &cli.PrivateKey{ + Address: auth.NewSECP256R1Address(pk.PublicKey()), + Bytes: p, + }, nil + case blsKey: + p, err := utils.LoadBytes(path, bls.PrivateKeyLen) + if err != nil { + return nil, err + } + + privKey, err := bls.PrivateKeyFromBytes(p) + if err != nil { + return nil, err + } + return &cli.PrivateKey{ + Address: auth.NewBLSAddress(bls.PublicFromPrivateKey(privKey)), + Bytes: p, + }, nil + default: + return nil, ErrInvalidKeyType + } +} + +var keyCmd = &cobra.Command{ + Use: "key", + RunE: func(*cobra.Command, []string) error { + return ErrMissingSubcommand + }, +} + +var genKeyCmd = &cobra.Command{ + Use: "generate [ed25519/secp256r1/bls]", + PreRunE: func(_ *cobra.Command, args []string) error { + if len(args) != 1 { + return ErrInvalidArgs + } + return checkKeyType(args[0]) + }, + RunE: func(_ *cobra.Command, args []string) error { + priv, err := generatePrivateKey(args[0]) + if err != nil { + return err + } + if err := handler.h.StoreKey(priv); err != nil { + return err + } + if err := handler.h.StoreDefaultKey(priv.Address); err != nil { + return err + } + utils.Outf( + "{{green}}created address:{{/}} %s", + codec.MustAddressBech32(consts.HRP, priv.Address), + ) + return nil + }, +} + +var importKeyCmd = &cobra.Command{ + Use: "import [type] [path]", + PreRunE: func(_ *cobra.Command, args []string) error { + if len(args) != 2 { + return ErrInvalidArgs + } + return checkKeyType(args[0]) + }, + RunE: func(_ *cobra.Command, args []string) error { + priv, err := loadPrivateKey(args[0], args[1]) + if err != nil { + return err + } + if err := handler.h.StoreKey(priv); err != nil { + return err + } + if err := handler.h.StoreDefaultKey(priv.Address); err != nil { + return err + } + utils.Outf( + "{{green}}imported address:{{/}} %s", + codec.MustAddressBech32(consts.HRP, priv.Address), + ) + return nil + }, +} + +var setKeyCmd = &cobra.Command{ + Use: "set", + RunE: func(*cobra.Command, []string) error { + return handler.Root().SetKey() + }, +} + +var balanceKeyCmd = &cobra.Command{ + Use: "balance", + RunE: func(*cobra.Command, []string) error { + return handler.Root().Balance(checkAllChains) + }, +} diff --git a/cmd/morpheus-cli/cmd/prometheus.go b/cmd/morpheus-cli/cmd/prometheus.go new file mode 100644 index 0000000..81fbcc3 --- /dev/null +++ b/cmd/morpheus-cli/cmd/prometheus.go @@ -0,0 +1,23 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +//nolint:lll +package cmd + +import ( + "github.com/spf13/cobra" +) + +var prometheusCmd = &cobra.Command{ + Use: "prometheus", + RunE: func(*cobra.Command, []string) error { + return ErrMissingSubcommand + }, +} + +var generatePrometheusCmd = &cobra.Command{ + Use: "generate", + RunE: func(_ *cobra.Command, args []string) error { + return handler.Root().GeneratePrometheus(prometheusBaseURI, prometheusOpenBrowser, startPrometheus, prometheusFile, prometheusData) + }, +} diff --git a/cmd/morpheus-cli/cmd/resolutions.go b/cmd/morpheus-cli/cmd/resolutions.go new file mode 100644 index 0000000..2d62542 --- /dev/null +++ b/cmd/morpheus-cli/cmd/resolutions.go @@ -0,0 +1,100 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import ( + "context" + "fmt" + "reflect" + + "github.com/ava-labs/avalanchego/ids" + + "github.com/ava-labs/hypersdk-starter/actions" + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk-starter/vm" + "github.com/ava-labs/hypersdk/api/jsonrpc" + "github.com/ava-labs/hypersdk/api/ws" + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/utils" +) + +// sendAndWait may not be used concurrently +func sendAndWait( + ctx context.Context, actions []chain.Action, cli *jsonrpc.JSONRPCClient, + bcli *vm.JSONRPCClient, ws *ws.WebSocketClient, factory chain.AuthFactory, printStatus bool, +) (bool, ids.ID, error) { + parser, err := bcli.Parser(ctx) + if err != nil { + return false, ids.Empty, err + } + _, tx, _, err := cli.GenerateTransaction(ctx, parser, actions, factory) + if err != nil { + return false, ids.Empty, err + } + if err := ws.RegisterTx(tx); err != nil { + return false, ids.Empty, err + } + var result *chain.Result + for { + txID, txErr, txResult, err := ws.ListenTx(ctx) + if err != nil { + return false, ids.Empty, err + } + if txErr != nil { + return false, ids.Empty, txErr + } + if txID == tx.ID() { + result = txResult + break + } + utils.Outf("{{yellow}}skipping unexpected transaction:{{/}} %s\n", tx.ID()) + } + if printStatus { + status := "❌" + if result.Success { + status = "✅" + } + utils.Outf("%s {{yellow}}txID:{{/}} %s\n", status, tx.ID()) + } + return result.Success, tx.ID(), nil +} + +func handleTx(tx *chain.Transaction, result *chain.Result) { + actor := tx.Auth.Actor() + if !result.Success { + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}error:{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + "❌", + tx.ID(), + codec.MustAddressBech32(consts.HRP, actor), + result.Error, + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, consts.Decimals), + consts.Symbol, + result.Units, + ) + return + } + + for _, action := range tx.Actions { + var summaryStr string + switch act := action.(type) { //nolint:gocritic + case *actions.Transfer: + summaryStr = fmt.Sprintf("%s %s -> %s\n", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) + } + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + "✅", + tx.ID(), + codec.MustAddressBech32(consts.HRP, actor), + reflect.TypeOf(action), + summaryStr, + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, consts.Decimals), + consts.Symbol, + result.Units, + ) + } +} diff --git a/cmd/morpheus-cli/cmd/root.go b/cmd/morpheus-cli/cmd/root.go new file mode 100644 index 0000000..6f58834 --- /dev/null +++ b/cmd/morpheus-cli/cmd/root.go @@ -0,0 +1,188 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + + "github.com/ava-labs/hypersdk/cli" + "github.com/ava-labs/hypersdk/utils" +) + +const ( + fsModeWrite = 0o600 + defaultDatabase = ".morpheus-cli" + defaultGenesis = "genesis.json" +) + +var ( + handler *Handler + + dbPath string + genesisFile string + minUnitPrice []string + maxBlockUnits []string + windowTargetUnits []string + minBlockGap int64 + hideTxs bool + checkAllChains bool + prometheusBaseURI string + prometheusOpenBrowser bool + prometheusFile string + prometheusData string + startPrometheus bool + + rootCmd = &cobra.Command{ + Use: "morpheus-cli", + Short: "MorpheusVM CLI", + SuggestFor: []string{"morpheus-cli", "morpheuscli"}, + } +) + +func init() { + cobra.EnablePrefixMatching = true + rootCmd.AddCommand( + genesisCmd, + keyCmd, + chainCmd, + actionCmd, + spamCmd, + prometheusCmd, + ) + rootCmd.PersistentFlags().StringVar( + &dbPath, + "database", + defaultDatabase, + "path to database (will create it missing)", + ) + rootCmd.PersistentPreRunE = func(*cobra.Command, []string) error { + utils.Outf("{{yellow}}database:{{/}} %s\n", dbPath) + controller := NewController(dbPath) + root, err := cli.New(controller) + if err != nil { + return err + } + handler = NewHandler(root) + return err + } + rootCmd.PersistentPostRunE = func(*cobra.Command, []string) error { + return handler.Root().CloseDatabase() + } + rootCmd.SilenceErrors = true + + // genesis + genGenesisCmd.PersistentFlags().StringVar( + &genesisFile, + "genesis-file", + defaultGenesis, + "genesis file path", + ) + genGenesisCmd.PersistentFlags().StringSliceVar( + &minUnitPrice, + "min-unit-price", + []string{}, + "minimum price", + ) + genGenesisCmd.PersistentFlags().StringSliceVar( + &maxBlockUnits, + "max-block-units", + []string{}, + "max block units", + ) + genGenesisCmd.PersistentFlags().StringSliceVar( + &windowTargetUnits, + "window-target-units", + []string{}, + "window target units", + ) + genGenesisCmd.PersistentFlags().Int64Var( + &minBlockGap, + "min-block-gap", + -1, + "minimum block gap (ms)", + ) + genesisCmd.AddCommand( + genGenesisCmd, + ) + + // key + balanceKeyCmd.PersistentFlags().BoolVar( + &checkAllChains, + "check-all-chains", + false, + "check all chains", + ) + keyCmd.AddCommand( + genKeyCmd, + importKeyCmd, + setKeyCmd, + balanceKeyCmd, + ) + + // chain + watchChainCmd.PersistentFlags().BoolVar( + &hideTxs, + "hide-txs", + false, + "hide txs", + ) + chainCmd.AddCommand( + importChainCmd, + setChainCmd, + chainInfoCmd, + watchChainCmd, + ) + + // actions + actionCmd.AddCommand( + transferCmd, + ) + + // spam + spamCmd.AddCommand( + runSpamCmd, + ) + + // prometheus + generatePrometheusCmd.PersistentFlags().StringVar( + &prometheusBaseURI, + "prometheus-base-uri", + "http://localhost:9090", + "prometheus server location", + ) + generatePrometheusCmd.PersistentFlags().BoolVar( + &prometheusOpenBrowser, + "prometheus-open-browser", + true, + "open browser to prometheus dashboard", + ) + generatePrometheusCmd.PersistentFlags().StringVar( + &prometheusFile, + "prometheus-file", + "/tmp/prometheus.yaml", + "prometheus file location", + ) + generatePrometheusCmd.PersistentFlags().StringVar( + &prometheusData, + "prometheus-data", + fmt.Sprintf("/tmp/prometheus-%d", time.Now().Unix()), + "prometheus data location", + ) + generatePrometheusCmd.PersistentFlags().BoolVar( + &startPrometheus, + "prometheus-start", + true, + "start local prometheus server", + ) + prometheusCmd.AddCommand( + generatePrometheusCmd, + ) +} + +func Execute() error { + return rootCmd.Execute() +} diff --git a/cmd/morpheus-cli/cmd/spam.go b/cmd/morpheus-cli/cmd/spam.go new file mode 100644 index 0000000..6aa1044 --- /dev/null +++ b/cmd/morpheus-cli/cmd/spam.go @@ -0,0 +1,107 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/ava-labs/hypersdk-starter/actions" + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk-starter/vm" + "github.com/ava-labs/hypersdk/api/ws" + "github.com/ava-labs/hypersdk/auth" + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/cli" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/crypto/bls" + "github.com/ava-labs/hypersdk/crypto/ed25519" + "github.com/ava-labs/hypersdk/crypto/secp256r1" + "github.com/ava-labs/hypersdk/utils" +) + +type SpamHelper struct { + keyType string + cli *vm.JSONRPCClient + ws *ws.WebSocketClient +} + +func (sh *SpamHelper) CreateAccount() (*cli.PrivateKey, error) { + return generatePrivateKey(sh.keyType) +} + +func (*SpamHelper) GetFactory(pk *cli.PrivateKey) (chain.AuthFactory, error) { + switch pk.Address[0] { + case auth.ED25519ID: + return auth.NewED25519Factory(ed25519.PrivateKey(pk.Bytes)), nil + case auth.SECP256R1ID: + return auth.NewSECP256R1Factory(secp256r1.PrivateKey(pk.Bytes)), nil + case auth.BLSID: + p, err := bls.PrivateKeyFromBytes(pk.Bytes) + if err != nil { + return nil, err + } + return auth.NewBLSFactory(p), nil + default: + return nil, ErrInvalidKeyType + } +} + +func (sh *SpamHelper) CreateClient(uri string) error { + sh.cli = vm.NewJSONRPCClient(uri) + ws, err := ws.NewWebSocketClient(uri, ws.DefaultHandshakeTimeout, 1024, 1024*1024) + if err != nil { + return err + } + sh.ws = ws + return nil +} + +func (sh *SpamHelper) GetParser(ctx context.Context) (chain.Parser, error) { + return sh.cli.Parser(ctx) +} + +func (sh *SpamHelper) LookupBalance(choice int, address string) (uint64, error) { + balance, err := sh.cli.Balance(context.TODO(), address) + if err != nil { + return 0, err + } + utils.Outf( + "%d) {{cyan}}address:{{/}} %s {{cyan}}balance:{{/}} %s %s\n", + choice, + address, + utils.FormatBalance(balance, consts.Decimals), + consts.Symbol, + ) + return balance, err +} + +func (*SpamHelper) GetTransfer(address codec.Address, amount uint64, memo []byte) []chain.Action { + return []chain.Action{&actions.Transfer{ + To: address, + Value: amount, + Memo: memo, + }} +} + +var spamCmd = &cobra.Command{ + Use: "spam", + RunE: func(*cobra.Command, []string) error { + return ErrMissingSubcommand + }, +} + +var runSpamCmd = &cobra.Command{ + Use: "run [ed25519/secp256r1/bls]", + PreRunE: func(_ *cobra.Command, args []string) error { + if len(args) != 1 { + return ErrInvalidArgs + } + return checkKeyType(args[0]) + }, + RunE: func(_ *cobra.Command, args []string) error { + return handler.Root().Spam(&SpamHelper{keyType: args[0]}) + }, +} diff --git a/cmd/morpheus-cli/main.go b/cmd/morpheus-cli/main.go new file mode 100644 index 0000000..500e511 --- /dev/null +++ b/cmd/morpheus-cli/main.go @@ -0,0 +1,20 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// "morpheus-cli" implements morpheusvm client operation interface. +package main + +import ( + "os" + + "github.com/ava-labs/hypersdk-starter/cmd/morpheus-cli/cmd" + "github.com/ava-labs/hypersdk/utils" +) + +func main() { + if err := cmd.Execute(); err != nil { + utils.Outf("{{red}}morpheus-cli exited with error:{{/}} %+v\n", err) + os.Exit(1) + } + os.Exit(0) +} diff --git a/vm/cmd/morpheusvm/main.go b/cmd/morpheusvm/main.go similarity index 90% rename from vm/cmd/morpheusvm/main.go rename to cmd/morpheusvm/main.go index d1a622c..3db30e4 100644 --- a/vm/cmd/morpheusvm/main.go +++ b/cmd/morpheusvm/main.go @@ -13,8 +13,8 @@ import ( "github.com/ava-labs/avalanchego/vms/rpcchainvm" "github.com/spf13/cobra" - "github.com/ava-labs/hypersdk-starter/vm/cmd/morpheusvm/version" - "github.com/ava-labs/hypersdk-starter/vm/vm" + "github.com/ava-labs/hypersdk-starter/cmd/morpheusvm/version" + "github.com/ava-labs/hypersdk-starter/vm" ) var rootCmd = &cobra.Command{ diff --git a/vm/cmd/morpheusvm/version/version.go b/cmd/morpheusvm/version/version.go similarity index 91% rename from vm/cmd/morpheusvm/version/version.go rename to cmd/morpheusvm/version/version.go index 5a44367..20d253f 100644 --- a/vm/cmd/morpheusvm/version/version.go +++ b/cmd/morpheusvm/version/version.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/cobra" - "github.com/ava-labs/hypersdk-starter/vm/consts" + "github.com/ava-labs/hypersdk-starter/consts" ) func init() { diff --git a/compose.yml b/compose.yml index d1aaa98..73b5aa1 100644 --- a/compose.yml +++ b/compose.yml @@ -13,6 +13,9 @@ services: timeout: 5s retries: 5 start_period: 10s + depends_on: + faucet: + condition: service_healthy devnet: container_name: devnet @@ -34,6 +37,7 @@ services: retries: 5 start_period: 300s + faucet: container_name: faucet build: @@ -41,10 +45,15 @@ services: context: ./ environment: - FAUCET_PRIVATE_KEY_HEX=323b1d8f4eed5f0da9da93071b034f2dce9d2d22692c172f3cb252a64ddfafd01b057de320297c29ad0c1f589ea216869cf1938d88c9fbd70d6748323dbf2fa7 - - RPC_ENDPOINT=http://devnet:9650 restart: always ports: - "127.0.0.1:8765:8765" + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:8765/readyz" ] + interval: 10s + timeout: 5s + retries: 20 + start_period: 10s depends_on: devnet: condition: service_healthy diff --git a/vm/consts/consts.go b/consts/consts.go similarity index 100% rename from vm/consts/consts.go rename to consts/consts.go diff --git a/go.mod b/go.mod index d76197b..a6af08e 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,10 @@ go 1.22.5 require ( github.com/ava-labs/avalanchego v1.11.11-0.20240827034238-fc892827880a - github.com/ava-labs/hypersdk v0.0.17-0.20240910022752-6b206001ceee + github.com/ava-labs/hypersdk v0.0.17-0.20240917054515-c6ece0719906 + github.com/fatih/color v1.13.0 github.com/gorilla/mux v1.8.0 + github.com/joho/godotenv v1.5.1 github.com/onsi/ginkgo/v2 v2.13.1 github.com/rs/cors v1.7.0 github.com/spf13/cobra v1.7.0 @@ -25,6 +27,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect @@ -78,6 +81,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect @@ -92,6 +96,7 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pires/go-proxyproto v0.6.2 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.16.0 // indirect diff --git a/go.sum b/go.sum index 1c3bcd1..50a7a3f 100644 --- a/go.sum +++ b/go.sum @@ -62,12 +62,8 @@ github.com/ava-labs/avalanchego v1.11.11-0.20240827034238-fc892827880a h1:TMhT+m github.com/ava-labs/avalanchego v1.11.11-0.20240827034238-fc892827880a/go.mod h1:9e0UPXJboybmgFjeTj+SFbK4ugbrdG4t68VdiUW5oQ8= github.com/ava-labs/coreth v0.13.8-0.20240802110637-b3e5088d062d h1:klPTcKVvqfA2KSKaRvQAO56Pd4XAqGhwgMTQ6/W+w7w= github.com/ava-labs/coreth v0.13.8-0.20240802110637-b3e5088d062d/go.mod h1:tXDujonxXFOF6oK5HS2EmgtSXJK3Gy6RpZxb5WzR9rM= -github.com/ava-labs/hypersdk v0.0.17-0.20240830103554-e3b49767b75d h1:pQKSwkrBU4W5yOsLfC522SPfUg/qwOkOEa+7+HU18EE= -github.com/ava-labs/hypersdk v0.0.17-0.20240830103554-e3b49767b75d/go.mod h1:MQ28FET3w+38FDGvRDG6rTfKVUnh23d3WSzeK16bIj4= -github.com/ava-labs/hypersdk v0.0.17-0.20240908014321-65a796120e94 h1:G9oFOWXIsQfOLWiiTEymUZiWVN4AQkq2JvuHJ8pcvQM= -github.com/ava-labs/hypersdk v0.0.17-0.20240908014321-65a796120e94/go.mod h1:KOWaq62YxTpDExjNo/dsQvg/+WYQ+KYu13xxVUv6OnA= -github.com/ava-labs/hypersdk v0.0.17-0.20240910022752-6b206001ceee h1:GGcp0Ckf9zIHOjD+FGsY0rPElYUkszrpqLqtsVT2uak= -github.com/ava-labs/hypersdk v0.0.17-0.20240910022752-6b206001ceee/go.mod h1:KOWaq62YxTpDExjNo/dsQvg/+WYQ+KYu13xxVUv6OnA= +github.com/ava-labs/hypersdk v0.0.17-0.20240917054515-c6ece0719906 h1:G0+VPoHidAz8xIvVJst6NLzfdnZtS6Pvl6IuE4+qd/U= +github.com/ava-labs/hypersdk v0.0.17-0.20240917054515-c6ece0719906/go.mod h1:8e7qwjiXRgdzgQayJedu0BpU19BGwYjQdhDhJNknSsM= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -106,10 +102,16 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -181,6 +183,8 @@ github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1 github.com/ethereum/go-ethereum v1.13.8 h1:1od+thJel3tM52ZUNQwvpYOeRHlbkVFZ5S8fhi0Lgsg= github.com/ethereum/go-ethereum v1.13.8/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= @@ -362,6 +366,8 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -401,8 +407,11 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -484,6 +493,8 @@ github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -759,6 +770,7 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -808,6 +820,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/kurtosis.yml b/kurtosis.yml deleted file mode 100644 index af31247..0000000 --- a/kurtosis.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: github.com/example-org/example-package -description: |- - # github.com/example-org/example-package - Enter description Markdown here. -replace: {} diff --git a/main.star b/main.star deleted file mode 100644 index 98085a1..0000000 --- a/main.star +++ /dev/null @@ -1,70 +0,0 @@ -DEVNET_PORT_ID = "devnet" -FAUCET_PORT_ID = "faucet" -FRONTEND_PORT_ID = "frontend" -# FIXME: do we need those? - -def run(plan): - # TODO: generate an address and private key - # docker build -f ./Dockerfile.keygen -t keygen . - # const [privateKey, address] = $(docker run keygen).split("\n") - # run container once, grab output - - # TODO: set env to the address to prefund - devnet = plan.add_service( - name = "devnet", - config = ServiceConfig( - image = ImageBuildSpec( - image_name="devnet", - build_context_dir="./", - build_file="Dockerfile.devnet", - build_args={}, - ), - ports = { - DEVNET_PORT_ID: PortSpec(9650, application_protocol = "http"), - }, - env_vars = { - "HELLO": "WORLD", - "PREFUND_ADDRESS":"morpheus1qrzvk4zlwj9zsacqgtufx7zvapd3quufqpxk5rsdd4633m4wz2fdjk97rwu", - }, - # TODO: add healthcheck - ), - ) - # TODO: wait till devnet is healthy - # [ "CMD", "curl", "-X", "POST", "http://localhost:9650/ext/bc/hypervm/coreapi", "-H", "Content-Type: application/json", "-d", "{\"jsonrpc\":\"2.0\",\"method\":\"hypersdk.network\",\"params\":{},\"id\":1}" ] - - faucet = plan.add_service( - name = "faucet", - config = ServiceConfig( - image = ImageBuildSpec( - image_name="faucet", - build_context_dir="./", - build_file="Dockerfile.faucet", - build_args={}, - ), - ports = { - FAUCET_PORT_ID: PortSpec(8765, application_protocol = "http"), - }, - env_vars = { - # TODO: generate a random private key and address - "FAUCET_PRIVATE_KEY_HEX": "323b1d8f4eed5f0da9da93071b034f2dce9d2d22692c172f3cb252a64ddfafd01b057de320297c29ad0c1f589ea216869cf1938d88c9fbd70d6748323dbf2fa7", - "RPC_ENDPOINT": "http://devnet:9650",#FIXME: is host devnet correct? - }, - ), - ) - - frontend = plan.add_service( - name = "frontend", - config = ServiceConfig( - image = ImageBuildSpec( - image_name="frontend", - build_context_dir="./", - build_file="Dockerfile.frontend", - build_args={}, - ), - ports = { - FRONTEND_PORT_ID: PortSpec(5173, application_protocol = "http"), - }, - # TODO: add healthcheck - ), - ) - \ No newline at end of file diff --git a/scripts/build.release.sh b/scripts/build.release.sh new file mode 100755 index 0000000..4024846 --- /dev/null +++ b/scripts/build.release.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +# See the file LICENSE for licensing terms. + +set -o errexit +set -o nounset +set -o pipefail + +if ! [[ "$0" =~ scripts/build.release.sh ]]; then + echo "must be run from morpheusvm root" + exit 255 +fi + +# shellcheck source=/scripts/constants.sh +source ../../scripts/constants.sh +# shellcheck source=/scripts/common/utils.sh +source ../../scripts/common/utils.sh + +# https://goreleaser.com/install/ +go install -v github.com/goreleaser/goreleaser@latest + +# alert the user if they do not have $GOPATH properly configured +check_command goreleaser + +# e.g., +# git tag 1.0.0 +goreleaser release \ +--config .goreleaser.yml \ +--skip-announce \ +--skip-publish diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..2d24956 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +# See the file LICENSE for licensing terms. + +set -o errexit +set -o nounset +set -o pipefail + +# Get the directory of the script, even if sourced from another directory +SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) + +# shellcheck source=/scripts/common/build.sh +source "$SCRIPT_DIR"/../../../scripts/common/build.sh +# shellcheck source=/scripts/constants.sh +source "$SCRIPT_DIR"/../../../scripts/constants.sh +# Construct the correct path to morpheusvm directory +MORPHEUSVM_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) + +build_project "$MORPHEUSVM_PATH" "morpheusvm" "pkEmJQuTUic3dxzg8EYnktwn4W7uCHofNcwiYo458vodAUbY7" diff --git a/scripts/common/build.sh b/scripts/common/build.sh new file mode 100644 index 0000000..cefee93 --- /dev/null +++ b/scripts/common/build.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +# See the file LICENSE for licensing terms. + +set -o errexit +set -o nounset +set -o pipefail + +realpath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" +} + +build_project() { + local project_path + project_path=$(realpath "$1") + local project_name=$2 + + local binary_path + if [[ $# -eq 3 ]]; then + local binary_dir + local binary_name + # Ensure binary_dir is an absolute path + binary_dir=$(realpath "$project_path/build/$(dirname "$3")") + binary_name=$(basename "$3") + binary_path=$binary_dir/$binary_name + else + # Set default binary directory location + binary_path=$project_path/build/$project_name + fi + + cd "$project_path" + echo "Building $project_name in $binary_path" + mkdir -p "$(dirname "$binary_path")" + + go build -o "$binary_path" ./cmd/"$project_name" +} diff --git a/vm/scripts/utils.sh b/scripts/common/utils.sh similarity index 95% rename from vm/scripts/utils.sh rename to scripts/common/utils.sh index 6291275..0551123 100644 --- a/vm/scripts/utils.sh +++ b/scripts/common/utils.sh @@ -58,5 +58,5 @@ function add_license_headers() { echo "${action} license headers" # Find and process files with the specified extensions - find . -type f \( -name "*.go" -o -name "*.rs" -o -name "*.sh" \) -print0 | xargs -0 addlicense "${args[@]}" + find . -type f \( -name "*.go" -o -name "*.rs" -o -name "*.sh" \) -not -path "./target/*" -print0 | xargs -0 addlicense "${args[@]}" } diff --git a/vm/scripts/constants.sh b/scripts/constants.sh similarity index 100% rename from vm/scripts/constants.sh rename to scripts/constants.sh diff --git a/scripts/fix.lint.sh b/scripts/fix.lint.sh new file mode 100755 index 0000000..f5284c5 --- /dev/null +++ b/scripts/fix.lint.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +# See the file LICENSE for licensing terms. + +set -o errexit +set -o pipefail +set -e + +if ! [[ "$0" =~ scripts/fix.lint.sh ]]; then + echo "must be run from morpheusvm root" + exit 255 +fi + +../../scripts/fix.lint.sh diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 0000000..8a787c3 --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +# See the file LICENSE for licensing terms. + +set -o errexit +set -o pipefail +set -e + +if ! [[ "$0" =~ scripts/lint.sh ]]; then + echo "must be run from morpheusvm root" + exit 255 +fi + +# Specify the version of golangci-lint. Should be upgraded after linting issues are resolved. +export GOLANGCI_LINT_VERSION="v1.51.2" +../../scripts/lint.sh diff --git a/vm/scripts/run.sh b/scripts/run.sh similarity index 98% rename from vm/scripts/run.sh rename to scripts/run.sh index 900d109..1618865 100755 --- a/vm/scripts/run.sh +++ b/scripts/run.sh @@ -15,7 +15,7 @@ fi # shellcheck source=/scripts/constants.sh source ./scripts/constants.sh # shellcheck source=/scripts/common/utils.sh -source ./scripts/utils.sh +source ./scripts/common/utils.sh VERSION=d729e5c7ef9f008c3e89cd7131148ad3acda2e34 diff --git a/vm/scripts/stop.sh b/scripts/stop.sh similarity index 100% rename from vm/scripts/stop.sh rename to scripts/stop.sh diff --git a/scripts/tests.integration.sh b/scripts/tests.integration.sh new file mode 100755 index 0000000..9e7f260 --- /dev/null +++ b/scripts/tests.integration.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +# See the file LICENSE for licensing terms. + +set -e + +if ! [[ "$0" =~ scripts/tests.integration.sh ]]; then + echo "must be run from morpheusvm root" + exit 255 +fi + +# shellcheck source=/scripts/common/utils.sh +source ../../scripts/common/utils.sh +# shellcheck source=/scripts/constants.sh +source ../../scripts/constants.sh + +rm_previous_cov_reports +prepare_ginkgo + +# run with 3 embedded VMs +ACK_GINKGO_RC=true ginkgo \ +run \ +-v \ +--fail-fast \ +-cover \ +-covermode=atomic \ +-coverpkg=github.com/ava-labs/hypersdk/... \ +-coverprofile=integration.coverage.out \ +./tests/integration \ +--vms 3 + +# output generate coverage html +go tool cover -html=integration.coverage.out -o=integration.coverage.html diff --git a/scripts/tests.unit.sh b/scripts/tests.unit.sh new file mode 100755 index 0000000..c2f0a48 --- /dev/null +++ b/scripts/tests.unit.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +# See the file LICENSE for licensing terms. + +set -e + +if ! [[ "$0" =~ scripts/tests.unit.sh ]]; then + echo "must be run from morpheusvm root" + exit 255 +fi + +# shellcheck source=/scripts/common/utils.sh +source ../../scripts/common/utils.sh +# shellcheck source=/scripts/constants.sh +source ../../scripts/constants.sh + +# Provision of the list of tests requires word splitting, so disable the shellcheck +# shellcheck disable=SC2046 +go test -race -timeout="10m" -coverprofile="coverage.out" -covermode="atomic" $(find . -name "*.go" | grep -v "./cmd" | grep -v "./tests" | xargs -n1 dirname | sort -u | xargs) diff --git a/vm/storage/errors.go b/storage/errors.go similarity index 100% rename from vm/storage/errors.go rename to storage/errors.go diff --git a/vm/storage/state_manager.go b/storage/state_manager.go similarity index 100% rename from vm/storage/state_manager.go rename to storage/state_manager.go diff --git a/vm/storage/storage.go b/storage/storage.go similarity index 98% rename from vm/storage/storage.go rename to storage/storage.go index abc716a..b9f1e03 100644 --- a/vm/storage/storage.go +++ b/storage/storage.go @@ -16,7 +16,7 @@ import ( "github.com/ava-labs/hypersdk/state" smath "github.com/ava-labs/avalanchego/utils/math" - mconsts "github.com/ava-labs/hypersdk-starter/vm/consts" + mconsts "github.com/ava-labs/hypersdk-starter/consts" ) type ReadState func(context.Context, [][]byte) ([][]byte, []error) diff --git a/vm/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go similarity index 86% rename from vm/tests/e2e/e2e_test.go rename to tests/e2e/e2e_test.go index 0eee52d..1b313ff 100644 --- a/vm/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -10,9 +10,9 @@ import ( "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/stretchr/testify/require" - "github.com/ava-labs/hypersdk-starter/vm/consts" - "github.com/ava-labs/hypersdk-starter/vm/tests/workload" - "github.com/ava-labs/hypersdk-starter/vm/vm" + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk-starter/tests/workload" + "github.com/ava-labs/hypersdk-starter/vm" "github.com/ava-labs/hypersdk/abi" "github.com/ava-labs/hypersdk/tests/fixture" @@ -42,7 +42,7 @@ var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { genesisBytes, err := json.Marshal(gen) require.NoError(err) - expectedABI, err := abi.GetVMABI(vm.ActionParser.GetRegisteredTypes()) + expectedABI, err := abi.NewABI(vm.ActionParser.GetRegisteredTypes()) require.NoError(err) // Import HyperSDK e2e test coverage and inject MorpheusVM name diff --git a/vm/tests/integration/integration_test.go b/tests/integration/integration_test.go similarity index 85% rename from vm/tests/integration/integration_test.go rename to tests/integration/integration_test.go index 8a6acad..7a6d66c 100644 --- a/vm/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -9,13 +9,13 @@ import ( "github.com/stretchr/testify/require" - "github.com/ava-labs/hypersdk-starter/vm/vm" + "github.com/ava-labs/hypersdk-starter/vm" "github.com/ava-labs/hypersdk/auth" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/tests/integration" - lconsts "github.com/ava-labs/hypersdk-starter/vm/consts" - morpheusWorkload "github.com/ava-labs/hypersdk-starter/vm/tests/workload" + lconsts "github.com/ava-labs/hypersdk-starter/consts" + morpheusWorkload "github.com/ava-labs/hypersdk-starter/tests/workload" ginkgo "github.com/onsi/ginkgo/v2" ) diff --git a/vm/tests/workload/workload.go b/tests/workload/workload.go similarity index 87% rename from vm/tests/workload/workload.go rename to tests/workload/workload.go index 38b714d..e46098b 100644 --- a/vm/tests/workload/workload.go +++ b/tests/workload/workload.go @@ -5,15 +5,15 @@ package workload import ( "context" - "os" + "math" "time" "github.com/ava-labs/avalanchego/ids" "github.com/stretchr/testify/require" - "github.com/ava-labs/hypersdk-starter/vm/actions" - "github.com/ava-labs/hypersdk-starter/vm/consts" - "github.com/ava-labs/hypersdk-starter/vm/vm" + "github.com/ava-labs/hypersdk-starter/actions" + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk-starter/vm" "github.com/ava-labs/hypersdk/api/indexer" "github.com/ava-labs/hypersdk/api/jsonrpc" "github.com/ava-labs/hypersdk/auth" @@ -22,6 +22,7 @@ import ( "github.com/ava-labs/hypersdk/crypto/bls" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/crypto/secp256r1" + "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/genesis" "github.com/ava-labs/hypersdk/tests/workload" ) @@ -66,11 +67,6 @@ type workloadFactory struct { func New(minBlockGap int64) (*genesis.DefaultGenesis, workload.TxWorkloadFactory, error) { customAllocs := make([]*genesis.CustomAllocation, 0, len(ed25519AddrStrs)) - - if prefundAddress := os.Getenv("PREFUND_ADDRESS"); prefundAddress != "" { - ed25519AddrStrs = append(ed25519AddrStrs, prefundAddress) - } - for _, prefundedAddrStr := range ed25519AddrStrs { customAllocs = append(customAllocs, &genesis.CustomAllocation{ Address: prefundedAddrStr, @@ -79,6 +75,11 @@ func New(minBlockGap int64) (*genesis.DefaultGenesis, workload.TxWorkloadFactory } genesis := genesis.NewDefaultGenesis(customAllocs) + // Set WindowTargetUnits to MaxUint64 for all dimensions to iterate full mempool during block building. + genesis.Rules.WindowTargetUnits = fees.Dimensions{math.MaxUint64, math.MaxUint64, math.MaxUint64, math.MaxUint64, math.MaxUint64} + // Set all limits to MaxUint64 to avoid limiting block size for all dimensions except bandwidth. Must limit bandwidth to avoid building + // a block that exceeds the maximum size allowed by AvalancheGo. + genesis.Rules.MaxBlockUnits = fees.Dimensions{1800000, math.MaxUint64, math.MaxUint64, math.MaxUint64, math.MaxUint64} genesis.Rules.MinBlockGap = minBlockGap return genesis, &workloadFactory{ @@ -145,6 +146,7 @@ func (g *simpleTxWorkload) GenerateTxWithAssertion(ctx context.Context) (*chain. balance, err := lcli.Balance(ctx, aotherStr) require.NoError(err) require.Equal(uint64(1), balance) + // TODO: check transaction output (not currently available via API) }, nil } @@ -242,6 +244,7 @@ func (g *mixedAuthWorkload) GenerateTxWithAssertion(ctx context.Context) (*chain balance, err := lcli.Balance(ctx, receiverAddrStr) require.NoError(err) require.Equal(expectedBalance, balance) - // TODO check tx fee + units (not currently available via API) + // TODO: check tx fee + units (not currently available via API) + // TODO: check transaction output (not currently available via API) }, nil } diff --git a/vm/vm/client.go b/vm/client.go similarity index 93% rename from vm/vm/client.go rename to vm/client.go index bb3fa5e..3c37b64 100644 --- a/vm/vm/client.go +++ b/vm/client.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "github.com/ava-labs/hypersdk-starter/vm/consts" - "github.com/ava-labs/hypersdk-starter/vm/storage" + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk-starter/storage" "github.com/ava-labs/hypersdk/api/jsonrpc" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/genesis" @@ -105,7 +105,7 @@ func (p *Parser) Rules(_ int64) chain.Rules { return p.genesis.Rules } -func (*Parser) Registry() (chain.ActionRegistry, chain.AuthRegistry) { +func (*Parser) Registry() (*chain.ActionRegistry, *chain.AuthRegistry) { return ActionParser, AuthParser } diff --git a/vm/cmd/keygen/gen.go b/vm/cmd/keygen/gen.go deleted file mode 100644 index a6cc295..0000000 --- a/vm/cmd/keygen/gen.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "encoding/hex" - "fmt" - - "github.com/ava-labs/hypersdk-starter/vm/consts" - "github.com/ava-labs/hypersdk/auth" - "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/crypto/ed25519" -) - -func main() { - priv, err := ed25519.GeneratePrivateKey() - if err != nil { - panic(err) - } - - pub := priv.PublicKey() - addrStr := codec.MustAddressBech32(consts.HRP, auth.NewED25519Address(pub)) - - fmt.Println(hex.EncodeToString(priv[:])) - fmt.Println(addrStr) -} diff --git a/vm/consts/types.go b/vm/consts/types.go deleted file mode 100644 index da50b94..0000000 --- a/vm/consts/types.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package consts - -const ( - // Action TypeIDs - TransferID uint8 = 0 -) diff --git a/vm/vm/option.go b/vm/option.go similarity index 100% rename from vm/vm/option.go rename to vm/option.go diff --git a/vm/package-lock.json b/vm/package-lock.json deleted file mode 100644 index bfe70b6..0000000 --- a/vm/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "vm", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/vm/vm/server.go b/vm/server.go similarity index 93% rename from vm/vm/server.go rename to vm/server.go index f19d247..bd6f967 100644 --- a/vm/vm/server.go +++ b/vm/server.go @@ -6,8 +6,8 @@ package vm import ( "net/http" - "github.com/ava-labs/hypersdk-starter/vm/consts" - "github.com/ava-labs/hypersdk-starter/vm/storage" + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk-starter/storage" "github.com/ava-labs/hypersdk/api" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/genesis" diff --git a/vm/vm/vm.go b/vm/vm.go similarity index 73% rename from vm/vm/vm.go rename to vm/vm.go index 97a90c6..a2976a0 100644 --- a/vm/vm/vm.go +++ b/vm/vm.go @@ -6,32 +6,32 @@ package vm import ( "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/hypersdk-starter/vm/actions" - "github.com/ava-labs/hypersdk-starter/vm/consts" - "github.com/ava-labs/hypersdk-starter/vm/storage" + "github.com/ava-labs/hypersdk-starter/actions" + "github.com/ava-labs/hypersdk-starter/consts" + "github.com/ava-labs/hypersdk-starter/storage" "github.com/ava-labs/hypersdk/auth" "github.com/ava-labs/hypersdk/chain" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/genesis" "github.com/ava-labs/hypersdk/vm" "github.com/ava-labs/hypersdk/vm/defaultvm" ) var ( - ActionParser *codec.TypeParser[chain.Action] - AuthParser *codec.TypeParser[chain.Auth] + ActionParser *chain.ActionRegistry + AuthParser *chain.AuthRegistry ) // Setup types func init() { - ActionParser = codec.NewTypeParser[chain.Action]() - AuthParser = codec.NewTypeParser[chain.Auth]() + ActionParser = chain.NewActionRegistry() + AuthParser = chain.NewAuthRegistry() errs := &wrappers.Errs{} errs.Add( // When registering new actions, ALWAYS make sure to append at the end. // Pass nil as second argument if manual marshalling isn't needed (if in doubt, you probably don't) - ActionParser.Register(&actions.Transfer{}, actions.UnmarshalTransfer), + ActionParser.Register(&actions.Transfer{}, actions.TransferResult{}, nil), + ActionParser.Register(&actions.Hi{}, actions.HiResult{}, nil), // When registering new auth, ALWAYS make sure to append at the end. AuthParser.Register(&auth.ED25519{}, auth.UnmarshalED25519), diff --git a/web_wallet/package-lock.json b/web_wallet/package-lock.json index b551854..7646c11 100644 --- a/web_wallet/package-lock.json +++ b/web_wallet/package-lock.json @@ -13,7 +13,7 @@ "@metamask/sdk": "^0.26.5", "@noble/curves": "^1.4.2", "@scure/base": "^1.1.7", - "hypersdk-client": "^0.3.0", + "hypersdk-client": "^0.4.0", "lossless-json": "^4.0.1", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -3433,24 +3433,24 @@ } }, "node_modules/@metamask/snaps-sdk": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@metamask/snaps-sdk/-/snaps-sdk-6.3.0.tgz", - "integrity": "sha512-aq8glZBYw8T0tyd9cUsj5bgs/uLxKjqEXoq8eyeJc/4yp0SVvE6mscwa/na0ONK0SZoSCisMHLvRStzoOXOz5g==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@metamask/snaps-sdk/-/snaps-sdk-6.5.0.tgz", + "integrity": "sha512-MHNbCQ/4+HwIyWAHlrLbokoO3CGNTod696FGi3WjYHlKC/1fLE06q8jE/NzuixY11Q6wkShBBk2u7MBGLyLp0w==", "dependencies": { "@metamask/key-tree": "^9.1.2", "@metamask/providers": "^17.1.2", "@metamask/rpc-errors": "^6.3.1", "@metamask/superstruct": "^3.1.0", - "@metamask/utils": "^9.1.0" + "@metamask/utils": "^9.2.1" }, "engines": { "node": "^18.16 || >=20" } }, "node_modules/@metamask/snaps-sdk/node_modules/@metamask/json-rpc-engine": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-9.0.2.tgz", - "integrity": "sha512-wteoGUDhiqCgyO6Gdjnm6n+7raoRS+dRHOIsTc7LK2zpezAynav9BIK7QWPcJZeieMTSG5HuYrQf+epLbcdB/g==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-9.0.3.tgz", + "integrity": "sha512-efeRXW7KaL0BJcAeudSGhzu6sD3hMpxx9nl3V+Yemm1bsyc66yVUhYPR+XH+Y6ZvB2p05ywgvd1Ev5PBwFzr/g==", "license": "ISC", "dependencies": { "@metamask/rpc-errors": "^6.3.1", @@ -3462,12 +3462,12 @@ } }, "node_modules/@metamask/snaps-sdk/node_modules/@metamask/json-rpc-middleware-stream": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-8.0.2.tgz", - "integrity": "sha512-8z7GAUYj3EFVkxiTjaND2x9Q8iURoKhwIyzLMZChLTiJ/XKRJwvtJ9cG2KIeL3GnNsPkVH97xngRmTaYAM6yDg==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-8.0.3.tgz", + "integrity": "sha512-x0rh4EzzLtkpBi7adrAZ2qSAXBwk4knARZdR1j5YOyXYN7r0AeoTiTgmw7pfrUIF62x2si+WAOMm9R1hWNteGw==", "license": "ISC", "dependencies": { - "@metamask/json-rpc-engine": "^9.0.2", + "@metamask/json-rpc-engine": "^9.0.3", "@metamask/safe-event-emitter": "^3.0.0", "@metamask/utils": "^9.1.0", "readable-stream": "^3.6.2" @@ -8394,13 +8394,13 @@ } }, "node_modules/hypersdk-client": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/hypersdk-client/-/hypersdk-client-0.3.0.tgz", - "integrity": "sha512-Wl2zjlNd3NnqFNw1r8ZN6gYQgYOneEPX2+LLXUb8jyyckYqI7MPvzLPZk8PXFf4Kk2UzylP/hBrmhIhOUmhpow==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/hypersdk-client/-/hypersdk-client-0.4.0.tgz", + "integrity": "sha512-UQIZYgbe38BiLbo1qyZmSdCXVoLxAjafuKL0RPD3mDAvfFnCUCaG7o8A+K1MqnyG4lb9VfZUDhYtzcKJYcQwvQ==", "license": "NOT_DECIDED_YET_TODO_FIX", "dependencies": { "@metamask/sdk": "^0.28.2", - "@metamask/snaps-sdk": "^6.1.1", + "@metamask/snaps-sdk": "^6.5.0", "@noble/curves": "^1.6.0", "@scure/base": "^1.1.8", "buffer": "^6.0.3", @@ -8412,9 +8412,9 @@ } }, "node_modules/hypersdk-client/node_modules/@metamask/sdk": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@metamask/sdk/-/sdk-0.28.2.tgz", - "integrity": "sha512-pylk1uJAZYyO3HcNW/TNfII3+T+Yx6qrFYaC/HmuSIuRJeXsdZuExSbNQ236iQocIy3L7JjI+GQKbv3TbN+HQQ==", + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@metamask/sdk/-/sdk-0.28.4.tgz", + "integrity": "sha512-RjWBKPNesjeua2SXIDF9IvYALOSsOQyqHv5DPPK0Voskytk7y+2n/33ocbC1BH5hTLI4hDPH+BuCpXJRWs3/Yg==", "dependencies": { "@metamask/onboarding": "^1.0.1", "@metamask/providers": "16.1.0", diff --git a/web_wallet/package.json b/web_wallet/package.json index fb4c49d..1b94276 100644 --- a/web_wallet/package.json +++ b/web_wallet/package.json @@ -16,7 +16,7 @@ "@metamask/sdk": "^0.26.5", "@noble/curves": "^1.4.2", "@scure/base": "^1.1.7", - "hypersdk-client": "^0.3.0", + "hypersdk-client": "^0.4.0", "lossless-json": "^4.0.1", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/web_wallet/src/VMClient.ts b/web_wallet/src/VMClient.ts index e685abe..b0c63d1 100644 --- a/web_wallet/src/VMClient.ts +++ b/web_wallet/src/VMClient.ts @@ -1,18 +1,18 @@ import { API_HOST, FAUCET_HOST } from "./const"; import { HyperSDKBaseClient } from "hypersdk-client/src/client" import { ActionData } from 'hypersdk-client/src/snap' -import { base64 } from '@scure/base' + class VMClient extends HyperSDKBaseClient { public readonly COIN_SYMBOL = 'RED'; - public readonly HRP = 'morpheus' constructor(apiHost: string, private readonly faucetHost: string) { const vmName = 'morpheusvm'; const vmRPCPrefix = 'morpheusapi'; const decimals = 9; - super(apiHost, vmName, vmRPCPrefix, decimals); + const HRP = 'morpheus' + super(apiHost, vmName, vmRPCPrefix, HRP, decimals); } public async getBalance(address: string): Promise { diff --git a/web_wallet/src/screens/ConnectWallet.tsx b/web_wallet/src/screens/ConnectWallet.tsx index c9ceb28..8f06481 100644 --- a/web_wallet/src/screens/ConnectWallet.tsx +++ b/web_wallet/src/screens/ConnectWallet.tsx @@ -1,20 +1,20 @@ import { useState } from 'react' import { vmClient } from '../VMClient' +type SignerType = "metamask-snap" | "ephemeral"; + export default function ConnectWallet() { const [loading, setLoading] = useState(0) const [errors, setErrors] = useState([]) - async function connectWallet(signerType: "metamask-snap" | "ephemeral") { + async function connectWallet(signerType: SignerType, snapSource: "npm" | "local" = "npm") { try { setLoading((prevLoading) => prevLoading + 1); if (signerType === "metamask-snap") { - await vmClient.connect({ - type: "metamask-snap", - // snapId: "local:http://localhost:8080" - }) + const snapId = snapSource === "local" ? "local:http://localhost:8989" : undefined; + await vmClient.connect({ type: "metamask-snap", snapId }); } else { - await vmClient.connect({ type: "ephemeral" }) + await vmClient.connect({ type: "ephemeral" }); } } catch (e) { console.error(e); @@ -48,24 +48,33 @@ export default function ConnectWallet() { } return (
-
+

HyperSDK e2e demo

Connect with Metamask Flask development signer via a Snap, or create a signer in memory.

-
- +
+
+ + +
diff --git a/web_wallet/src/screens/Wallet.tsx b/web_wallet/src/screens/Wallet.tsx index 640db36..b8fb145 100644 --- a/web_wallet/src/screens/Wallet.tsx +++ b/web_wallet/src/screens/Wallet.tsx @@ -2,7 +2,7 @@ import { ArrowPathIcon } from '@heroicons/react/20/solid' import { useState, useCallback, useEffect } from 'react' import { ActionData } from 'hypersdk-client/src/snap' import { vmClient } from '../VMClient' - +import { stringify } from 'lossless-json' const otherWalletAddress = "morpheus1qypqxqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmrmag4" export default function Wallet({ myAddr }: { myAddr: string }) { @@ -76,6 +76,22 @@ export default function Wallet({ myAddr }: { myAddr: string }) { } } + async function executeReadonlyAction() { + setLogText("") + try { + log("info", "Executing readonly action: Hi") + setLoading(counter => counter + 1) + const result = await vmClient.executeReadonlyAction({ actionName: "Hi", data: { name: "Luigi" } }) + console.log(result) + log("success", `Readonly action result: ${stringify(result)}`) + } catch (e: unknown) { + log("error", `Readonly action failed: ${(e as { message?: string })?.message || String(e)}`); + console.error(e) + } finally { + setLoading(counter => counter - 1) + } + } + return (
0 ? "animate-pulse" : ""}> @@ -99,7 +115,12 @@ export default function Wallet({ myAddr }: { myAddr: string }) { > Send 1 RED - +