From da9756534b0424071dd1ef91217a99d7e980096c Mon Sep 17 00:00:00 2001
From: Peterjah
Date: Tue, 17 Dec 2024 15:44:30 +0100
Subject: [PATCH 01/10] add global config file manager
---
cmd/massa-wallet/massa-wallet.go | 3 ++
go.mod | 10 +++-
go.sum | 20 ++++++-
pkg/config/config.go | 91 ++++++++++++++++++++++++++++++++
4 files changed, 121 insertions(+), 3 deletions(-)
create mode 100644 pkg/config/config.go
diff --git a/cmd/massa-wallet/massa-wallet.go b/cmd/massa-wallet/massa-wallet.go
index e0452bf3c..a42c441cd 100644
--- a/cmd/massa-wallet/massa-wallet.go
+++ b/cmd/massa-wallet/massa-wallet.go
@@ -9,6 +9,7 @@ import (
"github.com/massalabs/station-massa-wallet/internal/handler"
walletApp "github.com/massalabs/station-massa-wallet/pkg/app"
"github.com/massalabs/station-massa-wallet/pkg/assets"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
"github.com/massalabs/station/pkg/logger"
@@ -20,6 +21,8 @@ func StartServer(app *walletApp.WalletApp) {
LRU().
Build()
+ config.Load()
+
massaClient := network.NewNodeFetcher()
var promptApp prompt.WalletPrompterInterface = prompt.NewWalletPrompter(app)
diff --git a/go.mod b/go.mod
index d233adf00..dc0786340 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,9 @@ require (
github.com/bluele/gcache v0.0.2
github.com/go-openapi/runtime v0.25.0
github.com/jessevdk/go-flags v1.5.0
+ github.com/knadh/koanf/parsers/json v0.1.0
+ github.com/knadh/koanf/providers/file v1.1.2
+ github.com/knadh/koanf/v2 v2.1.2
github.com/massalabs/station v0.6.6-0.20250122093330-de0ab4bbb76c
github.com/massalabs/station-massa-hello-world v0.0.11-0.20240503070604-6b14a27fcdff
github.com/pkg/errors v0.9.1
@@ -22,11 +25,14 @@ require (
github.com/bep/debounce v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
+ github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
+ github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/labstack/echo/v4 v4.10.2 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
@@ -35,6 +41,8 @@ require (
github.com/leaanthony/u v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
+ github.com/mitchellh/copystructure v1.2.0 // indirect
+ github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
@@ -74,7 +82,7 @@ require (
github.com/oklog/ulid v1.3.1 // indirect
go.mongodb.org/mongo-driver v1.11.3 // indirect
golang.org/x/net v0.25.0
- golang.org/x/sys v0.20.0 // indirect
+ golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.1.7
diff --git a/go.sum b/go.sum
index a5697c39e..db41a175d 100644
--- a/go.sum
+++ b/go.sum
@@ -35,6 +35,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
+github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
@@ -66,6 +68,8 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+
github.com/go-openapi/validate v0.22.0 h1:b0QecH6VslW/TxtpKgzpO1SNG7GU2FsaqKdP1E2T50Y=
github.com/go-openapi/validate v0.22.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
+github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
@@ -119,6 +123,14 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
+github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
+github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU=
+github.com/knadh/koanf/parsers/json v0.1.0/go.mod h1:ll2/MlXcZ2BfXD6YJcjVFzhG9P0TdJ207aIBKQhV2hY=
+github.com/knadh/koanf/providers/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w=
+github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
+github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
+github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -163,10 +175,14 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
+github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
+github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
@@ -300,8 +316,8 @@ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
diff --git a/pkg/config/config.go b/pkg/config/config.go
new file mode 100644
index 000000000..6f4b49811
--- /dev/null
+++ b/pkg/config/config.go
@@ -0,0 +1,91 @@
+package config
+
+import (
+ jsonStd "encoding/json"
+ "log"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/knadh/koanf/parsers/json"
+ "github.com/knadh/koanf/providers/file"
+ "github.com/knadh/koanf/v2"
+ "github.com/massalabs/station-massa-wallet/pkg/wallet"
+)
+
+type SignRule struct {
+ Contract string `json:"contract"`
+ PasswordPrompt bool `json:"passwordPrompt"`
+ ConfirmationBypass bool `json:"confirmationBypass"`
+}
+
+type Account struct {
+ SignRules []SignRule `json:"signRules"`
+}
+
+type Config struct {
+ Accounts map[string]Account `json:"accounts"`
+}
+
+var (
+ cfg *Config
+ once sync.Once
+ k = koanf.New(".")
+)
+
+const (
+ fileName = "wallet_config.json"
+)
+
+func Load() *Config {
+ once.Do(func() {
+ configDir, err := wallet.Path()
+ if err != nil {
+ log.Fatalf("Failed to get wallet config directory: %v", err)
+ }
+
+ path := filepath.Join(configDir, fileName)
+
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ log.Printf("config file not found. Creating default config at %s", path)
+ createDefaultConfigFile(path, defaultConfig())
+ }
+
+ if err := k.Load(file.Provider(path), json.Parser()); err != nil {
+ log.Fatalf("Error loading config: %v", err)
+ }
+
+ cfg = &Config{}
+ if err := k.Unmarshal("", cfg); err != nil {
+ log.Fatalf("Error unmarshaling config: %v", err)
+ }
+ })
+ return cfg
+}
+
+func Get() *Config {
+ if cfg == nil {
+ log.Fatal("Config not loaded. Call Load() first.")
+ }
+ return cfg
+}
+
+func defaultConfig() *Config {
+ return &Config{
+ Accounts: map[string]Account{},
+ }
+}
+
+func createDefaultConfigFile(filePath string, defaultConfig *Config) {
+ file, err := os.Create(filePath)
+ if err != nil {
+ log.Fatalf("failed to create config file: %v", err)
+ }
+ defer file.Close()
+
+ encoder := jsonStd.NewEncoder(file)
+ encoder.SetIndent("", " ") // Pretty-print JSON
+ if err := encoder.Encode(defaultConfig); err != nil {
+ log.Fatalf("failed to write default config to file: %v", err)
+ }
+}
From 2f59437cfaca2071c37b063f24a4b614dc69cb69 Mon Sep 17 00:00:00 2001
From: Peterjah
Date: Tue, 17 Dec 2024 16:14:05 +0100
Subject: [PATCH 02/10] fix linter
---
api/server/restapi/configure_massa_wallet.go | 2 ++
internal/handler/wallet/backup.go | 1 +
internal/handler/wallet/export.go | 2 ++
internal/handler/wallet/get_all_assets.go | 3 +++
internal/handler/wallet/import_test.go | 2 ++
internal/handler/wallet/sign_test.go | 1 -
pkg/assets/dollar.go | 1 +
pkg/config/config.go | 3 +++
pkg/prompt/import.go | 5 +++--
pkg/prompt/prompt.go | 4 ++--
pkg/types/address_test.go | 2 ++
pkg/wallet/wallet.go | 2 ++
pkg/wallet/wallet_test.go | 1 -
13 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/api/server/restapi/configure_massa_wallet.go b/api/server/restapi/configure_massa_wallet.go
index 67f9c9b27..8c5c455a2 100644
--- a/api/server/restapi/configure_massa_wallet.go
+++ b/api/server/restapi/configure_massa_wallet.go
@@ -97,8 +97,10 @@ func webAppMiddleware(handler http.Handler) http.Handler {
responder := html.HandleWebApp(params)
// Handle the successful response
responder.WriteResponse(w, runtime.JSONProducer())
+
return
}
+
handler.ServeHTTP(w, r)
})
}
diff --git a/internal/handler/wallet/backup.go b/internal/handler/wallet/backup.go
index eea31b31c..60b2aa56c 100644
--- a/internal/handler/wallet/backup.go
+++ b/internal/handler/wallet/backup.go
@@ -62,6 +62,7 @@ func (w *walletBackupAccount) Handle(params operations.BackupAccountParams) midd
} else {
password, _ := promptOutput.(*memguard.LockedBuffer)
guardedPrivateKey, err := acc.PrivateKeyTextInClear(password)
+ //nolint:wsl
if err != nil {
return newErrorResponse(err.Error(), errorGetWallets, http.StatusInternalServerError)
}
diff --git a/internal/handler/wallet/export.go b/internal/handler/wallet/export.go
index bf544c032..d2b8c0719 100644
--- a/internal/handler/wallet/export.go
+++ b/internal/handler/wallet/export.go
@@ -46,8 +46,10 @@ func (w *walletExportFile) Handle(params operations.ExportAccountFileParams) mid
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
+
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filepath.Base(file.Name())))
+
if _, err := io.Copy(w, file); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
diff --git a/internal/handler/wallet/get_all_assets.go b/internal/handler/wallet/get_all_assets.go
index 124b0f6bf..ab6851794 100644
--- a/internal/handler/wallet/get_all_assets.go
+++ b/internal/handler/wallet/get_all_assets.go
@@ -62,6 +62,7 @@ func (g *getAllAssets) Handle(params operations.GetAllAssetsParams) middleware.R
if assetsWithBalance[i].AssetInfo.Symbol == "MAS" {
return true
}
+
if assetsWithBalance[j].AssetInfo.Symbol == "MAS" {
return false
}
@@ -72,9 +73,11 @@ func (g *getAllAssets) Handle(params operations.GetAllAssetsParams) middleware.R
if (valueI == nil || *valueI == 0) && (valueJ == nil || *valueJ == 0) {
return assetsWithBalance[i].AssetInfo.Symbol < assetsWithBalance[j].AssetInfo.Symbol
}
+
if valueI == nil || *valueI == 0 {
return false
}
+
if valueJ == nil || *valueJ == 0 {
return true
}
diff --git a/internal/handler/wallet/import_test.go b/internal/handler/wallet/import_test.go
index 545c44b6d..162c0db8b 100644
--- a/internal/handler/wallet/import_test.go
+++ b/internal/handler/wallet/import_test.go
@@ -87,6 +87,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
data := []byte(walletFile)
err = os.WriteFile(filePath, data, 0o644)
assert.NoError(t, err)
+
defer os.Remove(filePath)
testResult := make(chan walletapp.EventData)
@@ -131,6 +132,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
data := []byte(walletFile)
err = os.WriteFile(filePath, data, 0o644)
assert.NoError(t, err)
+
defer os.Remove(filePath)
testResult := make(chan walletapp.EventData)
diff --git a/internal/handler/wallet/sign_test.go b/internal/handler/wallet/sign_test.go
index 1ccf12ccb..3e2da4dbe 100644
--- a/internal/handler/wallet/sign_test.go
+++ b/internal/handler/wallet/sign_test.go
@@ -234,7 +234,6 @@ func Test_walletSign_Handle(t *testing.T) {
resp = signTransaction(t, api, nickname, transactionDataBatch)
verifyStatusCode(t, resp, http.StatusOK) // remove this line when enabling batch signing
-
// Uncomment the code below when enabling batch signing:
// var bodyError operations.SignInternalServerError
// err = json.Unmarshal(resp.Body.Bytes(), &bodyError)
diff --git a/pkg/assets/dollar.go b/pkg/assets/dollar.go
index 9c817fa74..c468b3854 100644
--- a/pkg/assets/dollar.go
+++ b/pkg/assets/dollar.go
@@ -24,6 +24,7 @@ func DollarValue(balance, MEXCSymbol, symbol string, decimals int64) (*float64,
} else {
err := error(nil)
price, err = DollarPrice(MEXCSymbol)
+ //nolint:wsl
if err != nil {
return nil, fmt.Errorf("Error getting dollar price: %s\n", err)
}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 6f4b49811..9136b7751 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -60,6 +60,7 @@ func Load() *Config {
log.Fatalf("Error unmarshaling config: %v", err)
}
})
+
return cfg
}
@@ -67,6 +68,7 @@ func Get() *Config {
if cfg == nil {
log.Fatal("Config not loaded. Call Load() first.")
}
+
return cfg
}
@@ -85,6 +87,7 @@ func createDefaultConfigFile(filePath string, defaultConfig *Config) {
encoder := jsonStd.NewEncoder(file)
encoder.SetIndent("", " ") // Pretty-print JSON
+
if err := encoder.Encode(defaultConfig); err != nil {
log.Fatalf("failed to write default config to file: %v", err)
}
diff --git a/pkg/prompt/import.go b/pkg/prompt/import.go
index a46f37be9..20e063bd9 100644
--- a/pkg/prompt/import.go
+++ b/pkg/prompt/import.go
@@ -1,6 +1,7 @@
package prompt
import (
+ "errors"
"fmt"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
@@ -39,7 +40,7 @@ func handleImportFile(prompterApp WalletPrompterInterface, filePath string) (*ac
prompterApp.EmitEvent(walletapp.PromptResultEvent,
walletapp.EventData{Success: false, CodeMessage: utils.WailsErrorCode(err)})
- return nil, false, fmt.Errorf(msg)
+ return nil, false, errors.New(msg)
}
return acc, false, nil
@@ -62,7 +63,7 @@ func handleImportPrivateKey(prompterApp WalletPrompterInterface, walletInfo wall
prompterApp.EmitEvent(walletapp.PromptResultEvent,
walletapp.EventData{Success: false, CodeMessage: utils.WailsErrorCode(err)})
- return nil, false, fmt.Errorf(msg)
+ return nil, false, errors.New(msg)
}
return acc, false, nil
diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go
index 607d7cb45..4ad1e3cfc 100644
--- a/pkg/prompt/prompt.go
+++ b/pkg/prompt/prompt.go
@@ -2,7 +2,7 @@ package prompt
import (
"context"
- "fmt"
+ "errors"
"math/rand"
"strconv"
@@ -62,7 +62,7 @@ func WakeUpPrompt(
) (interface{}, error) {
if prompterApp.IsListening() {
logger.Warn(AlreadyListeningErr)
- return nil, fmt.Errorf(AlreadyListeningErr)
+ return nil, errors.New(AlreadyListeningErr)
}
prompterApp.Lock()
diff --git a/pkg/types/address_test.go b/pkg/types/address_test.go
index fe2159421..b81356a49 100644
--- a/pkg/types/address_test.go
+++ b/pkg/types/address_test.go
@@ -45,6 +45,7 @@ func TestAddress_UnmarshalText(t *testing.T) {
if err := a.UnmarshalText(tt.text); err != tt.expectedErr {
t.Errorf("Address.UnmarshalText() error = %v, wantErr %v", err, tt.expectedErr)
}
+
assert.Equal(t, tt.expectedType, a.Kind, "Should be %s, got %s", tt.expectedType, a.Kind)
assert.Equal(t, tt.expectedData, a.Data)
})
@@ -229,6 +230,7 @@ func TestAddress_UnmarshalBinary(t *testing.T) {
if err := a.UnmarshalBinary(tt.data); err != tt.expectedErr {
t.Errorf("Address.UnmarshalBinary() error = %v, wantErr %v", err, tt.expectedErr)
}
+
assert.Equal(t, tt.expectedType, a.Kind, "Should be %s, got %s", tt.expectedType, a.Kind)
assert.Equal(t, tt.expectedData, a.Data)
})
diff --git a/pkg/wallet/wallet.go b/pkg/wallet/wallet.go
index 639f6650b..70b61e86f 100644
--- a/pkg/wallet/wallet.go
+++ b/pkg/wallet/wallet.go
@@ -200,6 +200,7 @@ func (w *Wallet) GetAccountFromAddress(needle string) (*account.Account, error)
return false
}
}
+
return true
})
@@ -243,6 +244,7 @@ func (w *Wallet) AllAccounts() []*account.Account {
if ok {
accounts = append(accounts, acc)
}
+
return true
})
diff --git a/pkg/wallet/wallet_test.go b/pkg/wallet/wallet_test.go
index 943ffc644..a1956e490 100644
--- a/pkg/wallet/wallet_test.go
+++ b/pkg/wallet/wallet_test.go
@@ -141,7 +141,6 @@ func TestWallet(t *testing.T) {
t.Run("Get Account: new file added manually", func(t *testing.T) {
// User can add an account file in the account folder by its own,
// we want to wallet to be able to manage this account.
-
accountPath, err := w.AccountPath(sampleNicknameUnitTest)
assert.NoError(t, err)
From d3f0bdeeda5a08f93ad7d2982e1df72012ea1868 Mon Sep 17 00:00:00 2001
From: Peterjah
Date: Wed, 18 Dec 2024 13:11:26 +0100
Subject: [PATCH 03/10] remove batch & correlationId
---
api/server/models/correlation_id.go | 56 -----------
api/server/models/sign_request.go | 57 +----------
api/server/models/sign_response.go | 52 ----------
api/server/restapi/embedded_spec.go | 42 ++-------
api/server/restapi/operations/sign.go | 2 +-
.../restapi/operations/sign_responses.go | 4 +-
api/walletApi-V0.yml | 17 +---
documentation/wallet.adoc | 13 +--
internal/handler/wallet/api_test.go | 4 +
internal/handler/wallet/backup_test.go | 18 ++--
internal/handler/wallet/create_test.go | 2 +-
internal/handler/wallet/delete_test.go | 33 +------
internal/handler/wallet/errors.go | 2 -
internal/handler/wallet/get_test.go | 2 +-
internal/handler/wallet/import_test.go | 8 +-
internal/handler/wallet/rolls_test.go | 4 +-
internal/handler/wallet/sign.go | 94 +++++++------------
internal/handler/wallet/sign_test.go | 83 +---------------
internal/handler/wallet/testsUtils.go | 3 +-
internal/handler/wallet/transfer_test.go | 2 +-
pkg/app/app.go | 16 ++--
pkg/app/events.go | 18 +---
pkg/config/config.go | 24 +++--
pkg/network/operation.go | 3 +-
pkg/prompt/env.go | 4 +-
pkg/prompt/prompt.go | 22 +----
pkg/utils/errors.go | 12 +--
wails-frontend/src/events/events.tsx | 1 -
.../pages/PasswordPromptHandler/Delete.tsx | 2 +-
.../src/pages/PasswordPromptHandler/Sign.tsx | 2 +-
wails-frontend/src/pages/backupKeyPairs.tsx | 2 +-
wails-frontend/src/pages/backupMethods.tsx | 4 +-
wails-frontend/src/pages/confirmDelete.tsx | 2 +-
wails-frontend/src/pages/importFile.tsx | 2 +-
wails-frontend/src/pages/newPassword.tsx | 9 +-
.../wailsjs/go/walletapp/WalletApp.d.ts | 6 +-
.../wailsjs/go/walletapp/WalletApp.js | 12 +--
37 files changed, 144 insertions(+), 495 deletions(-)
delete mode 100644 api/server/models/correlation_id.go
diff --git a/api/server/models/correlation_id.go b/api/server/models/correlation_id.go
deleted file mode 100644
index 6907fd8c0..000000000
--- a/api/server/models/correlation_id.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Code generated by go-swagger; DO NOT EDIT.
-
-package models
-
-// This file was generated by the swagger tool.
-// Editing this file might prove futile when you re-run the swagger generate command
-
-import (
- "context"
-
- "github.com/go-openapi/strfmt"
- "github.com/go-openapi/swag"
-)
-
-// CorrelationID Correlation id of the operation batch
-//
-// swagger:model CorrelationId
-type CorrelationID strfmt.Base64
-
-// UnmarshalJSON sets a CorrelationID value from JSON input
-func (m *CorrelationID) UnmarshalJSON(b []byte) error {
- return ((*strfmt.Base64)(m)).UnmarshalJSON(b)
-}
-
-// MarshalJSON retrieves a CorrelationID value as JSON output
-func (m CorrelationID) MarshalJSON() ([]byte, error) {
- return (strfmt.Base64(m)).MarshalJSON()
-}
-
-// Validate validates this correlation Id
-func (m CorrelationID) Validate(formats strfmt.Registry) error {
- return nil
-}
-
-// ContextValidate validates this correlation Id based on context it is used
-func (m CorrelationID) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
- return nil
-}
-
-// MarshalBinary interface implementation
-func (m *CorrelationID) MarshalBinary() ([]byte, error) {
- if m == nil {
- return nil, nil
- }
- return swag.WriteJSON(m)
-}
-
-// UnmarshalBinary interface implementation
-func (m *CorrelationID) UnmarshalBinary(b []byte) error {
- var res CorrelationID
- if err := swag.ReadJSON(b, &res); err != nil {
- return err
- }
- *m = res
- return nil
-}
diff --git a/api/server/models/sign_request.go b/api/server/models/sign_request.go
index 091038d00..25f6a2fa3 100644
--- a/api/server/models/sign_request.go
+++ b/api/server/models/sign_request.go
@@ -19,17 +19,10 @@ import (
// swagger:model SignRequest
type SignRequest struct {
- // A boolean property that indicates whether the sign operation is part of a batch of operations. Set to true if this operation is part of a batch, otherwise set to false.
- Batch bool `json:"batch,omitempty"`
-
// The chain id of the network to which the operation will be sent.
// Required: true
ChainID *int64 `json:"chainId"`
- // correlation Id
- // Format: byte
- CorrelationID CorrelationID `json:"correlationId,omitempty"`
-
// Description text of what is being signed (optional)
// Max Length: 280
Description string `json:"description,omitempty"`
@@ -48,10 +41,6 @@ func (m *SignRequest) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
- if err := m.validateCorrelationID(formats); err != nil {
- res = append(res, err)
- }
-
if err := m.validateDescription(formats); err != nil {
res = append(res, err)
}
@@ -75,23 +64,6 @@ func (m *SignRequest) validateChainID(formats strfmt.Registry) error {
return nil
}
-func (m *SignRequest) validateCorrelationID(formats strfmt.Registry) error {
- if swag.IsZero(m.CorrelationID) { // not required
- return nil
- }
-
- if err := m.CorrelationID.Validate(formats); err != nil {
- if ve, ok := err.(*errors.Validation); ok {
- return ve.ValidateName("correlationId")
- } else if ce, ok := err.(*errors.CompositeError); ok {
- return ce.ValidateName("correlationId")
- }
- return err
- }
-
- return nil
-}
-
func (m *SignRequest) validateDescription(formats strfmt.Registry) error {
if swag.IsZero(m.Description) { // not required
return nil
@@ -113,35 +85,8 @@ func (m *SignRequest) validateOperation(formats strfmt.Registry) error {
return nil
}
-// ContextValidate validate this sign request based on the context it is used
+// ContextValidate validates this sign request based on context it is used
func (m *SignRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
- var res []error
-
- if err := m.contextValidateCorrelationID(ctx, formats); err != nil {
- res = append(res, err)
- }
-
- if len(res) > 0 {
- return errors.CompositeValidationError(res...)
- }
- return nil
-}
-
-func (m *SignRequest) contextValidateCorrelationID(ctx context.Context, formats strfmt.Registry) error {
-
- if swag.IsZero(m.CorrelationID) { // not required
- return nil
- }
-
- if err := m.CorrelationID.ContextValidate(ctx, formats); err != nil {
- if ve, ok := err.(*errors.Validation); ok {
- return ve.ValidateName("correlationId")
- } else if ce, ok := err.(*errors.CompositeError); ok {
- return ce.ValidateName("correlationId")
- }
- return err
- }
-
return nil
}
diff --git a/api/server/models/sign_response.go b/api/server/models/sign_response.go
index 4d91f1f69..07f7edbf3 100644
--- a/api/server/models/sign_response.go
+++ b/api/server/models/sign_response.go
@@ -19,10 +19,6 @@ import (
// swagger:model SignResponse
type SignResponse struct {
- // correlation Id
- // Format: byte
- CorrelationID CorrelationID `json:"correlationId,omitempty"`
-
// The modified operation (usr can change the fees).
// Read Only: true
// Format: byte
@@ -40,32 +36,6 @@ type SignResponse struct {
// Validate validates this sign response
func (m *SignResponse) Validate(formats strfmt.Registry) error {
- var res []error
-
- if err := m.validateCorrelationID(formats); err != nil {
- res = append(res, err)
- }
-
- if len(res) > 0 {
- return errors.CompositeValidationError(res...)
- }
- return nil
-}
-
-func (m *SignResponse) validateCorrelationID(formats strfmt.Registry) error {
- if swag.IsZero(m.CorrelationID) { // not required
- return nil
- }
-
- if err := m.CorrelationID.Validate(formats); err != nil {
- if ve, ok := err.(*errors.Validation); ok {
- return ve.ValidateName("correlationId")
- } else if ce, ok := err.(*errors.CompositeError); ok {
- return ce.ValidateName("correlationId")
- }
- return err
- }
-
return nil
}
@@ -73,10 +43,6 @@ func (m *SignResponse) validateCorrelationID(formats strfmt.Registry) error {
func (m *SignResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
- if err := m.contextValidateCorrelationID(ctx, formats); err != nil {
- res = append(res, err)
- }
-
if err := m.contextValidateOperation(ctx, formats); err != nil {
res = append(res, err)
}
@@ -95,24 +61,6 @@ func (m *SignResponse) ContextValidate(ctx context.Context, formats strfmt.Regis
return nil
}
-func (m *SignResponse) contextValidateCorrelationID(ctx context.Context, formats strfmt.Registry) error {
-
- if swag.IsZero(m.CorrelationID) { // not required
- return nil
- }
-
- if err := m.CorrelationID.ContextValidate(ctx, formats); err != nil {
- if ve, ok := err.(*errors.Validation); ok {
- return ve.ValidateName("correlationId")
- } else if ce, ok := err.(*errors.CompositeError); ok {
- return ce.ValidateName("correlationId")
- }
- return err
- }
-
- return nil
-}
-
func (m *SignResponse) contextValidateOperation(ctx context.Context, formats strfmt.Registry) error {
if err := validate.ReadOnly(ctx, "operation", "body", strfmt.Base64(m.Operation)); err != nil {
diff --git a/api/server/restapi/embedded_spec.go b/api/server/restapi/embedded_spec.go
index 8d5345e0d..c642b353f 100644
--- a/api/server/restapi/embedded_spec.go
+++ b/api/server/restapi/embedded_spec.go
@@ -590,7 +590,7 @@ func init() {
},
"/api/accounts/{nickname}/sign": {
"post": {
- "description": "Sign an operation or a message using the account associated with the provided nickname in the path. If no correlationId is provided, the user will be prompted to enter their account password.",
+ "description": "Sign an operation or a message using the account associated with the provided nickname in the path.",
"produces": [
"application/json"
],
@@ -619,7 +619,7 @@ func init() {
],
"responses": {
"200": {
- "description": "Returns the signature, public key, and correlationId (if provided).",
+ "description": "Returns the signature, public key.",
"schema": {
"$ref": "#/definitions/SignResponse"
}
@@ -631,7 +631,7 @@ func init() {
}
},
"401": {
- "description": "Unauthorized - The request requires user authentication. Only if no correlationId is provided.",
+ "description": "Unauthorized - The request requires user authentication.",
"schema": {
"$ref": "#/definitions/Error"
}
@@ -904,11 +904,6 @@ func init() {
}
]
},
- "CorrelationId": {
- "description": "Correlation id of the operation batch",
- "type": "string",
- "format": "byte"
- },
"Error": {
"description": "Error object.",
"type": "object",
@@ -1029,17 +1024,10 @@ func init() {
"chainId"
],
"properties": {
- "batch": {
- "description": "A boolean property that indicates whether the sign operation is part of a batch of operations. Set to true if this operation is part of a batch, otherwise set to false.",
- "type": "boolean"
- },
"chainId": {
"description": "The chain id of the network to which the operation will be sent.",
"type": "integer"
},
- "correlationId": {
- "$ref": "#/definitions/CorrelationId"
- },
"description": {
"description": "Description text of what is being signed (optional)",
"type": "string",
@@ -1056,9 +1044,6 @@ func init() {
"description": "Signature of a sent operation.",
"type": "object",
"properties": {
- "correlationId": {
- "$ref": "#/definitions/CorrelationId"
- },
"operation": {
"description": "The modified operation (usr can change the fees).",
"type": "string",
@@ -1757,7 +1742,7 @@ func init() {
},
"/api/accounts/{nickname}/sign": {
"post": {
- "description": "Sign an operation or a message using the account associated with the provided nickname in the path. If no correlationId is provided, the user will be prompted to enter their account password.",
+ "description": "Sign an operation or a message using the account associated with the provided nickname in the path.",
"produces": [
"application/json"
],
@@ -1791,7 +1776,7 @@ func init() {
],
"responses": {
"200": {
- "description": "Returns the signature, public key, and correlationId (if provided).",
+ "description": "Returns the signature, public key.",
"schema": {
"$ref": "#/definitions/SignResponse"
}
@@ -1803,7 +1788,7 @@ func init() {
}
},
"401": {
- "description": "Unauthorized - The request requires user authentication. Only if no correlationId is provided.",
+ "description": "Unauthorized - The request requires user authentication.",
"schema": {
"$ref": "#/definitions/Error"
}
@@ -2088,11 +2073,6 @@ func init() {
}
]
},
- "CorrelationId": {
- "description": "Correlation id of the operation batch",
- "type": "string",
- "format": "byte"
- },
"Error": {
"description": "Error object.",
"type": "object",
@@ -2213,17 +2193,10 @@ func init() {
"chainId"
],
"properties": {
- "batch": {
- "description": "A boolean property that indicates whether the sign operation is part of a batch of operations. Set to true if this operation is part of a batch, otherwise set to false.",
- "type": "boolean"
- },
"chainId": {
"description": "The chain id of the network to which the operation will be sent.",
"type": "integer"
},
- "correlationId": {
- "$ref": "#/definitions/CorrelationId"
- },
"description": {
"description": "Description text of what is being signed (optional)",
"type": "string",
@@ -2240,9 +2213,6 @@ func init() {
"description": "Signature of a sent operation.",
"type": "object",
"properties": {
- "correlationId": {
- "$ref": "#/definitions/CorrelationId"
- },
"operation": {
"description": "The modified operation (usr can change the fees).",
"type": "string",
diff --git a/api/server/restapi/operations/sign.go b/api/server/restapi/operations/sign.go
index b326b445c..2ea5a3742 100644
--- a/api/server/restapi/operations/sign.go
+++ b/api/server/restapi/operations/sign.go
@@ -32,7 +32,7 @@ func NewSign(ctx *middleware.Context, handler SignHandler) *Sign {
/*
Sign swagger:route POST /api/accounts/{nickname}/sign sign
-Sign an operation or a message using the account associated with the provided nickname in the path. If no correlationId is provided, the user will be prompted to enter their account password.
+Sign an operation or a message using the account associated with the provided nickname in the path.
*/
type Sign struct {
Context *middleware.Context
diff --git a/api/server/restapi/operations/sign_responses.go b/api/server/restapi/operations/sign_responses.go
index 5fe444d6b..eea9bae73 100644
--- a/api/server/restapi/operations/sign_responses.go
+++ b/api/server/restapi/operations/sign_responses.go
@@ -16,7 +16,7 @@ import (
const SignOKCode int = 200
/*
-SignOK Returns the signature, public key, and correlationId (if provided).
+SignOK Returns the signature, public key.
swagger:response signOK
*/
@@ -106,7 +106,7 @@ func (o *SignBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.
const SignUnauthorizedCode int = 401
/*
-SignUnauthorized Unauthorized - The request requires user authentication. Only if no correlationId is provided.
+SignUnauthorized Unauthorized - The request requires user authentication.
swagger:response signUnauthorized
*/
diff --git a/api/walletApi-V0.yml b/api/walletApi-V0.yml
index 78373fb61..8906f647c 100644
--- a/api/walletApi-V0.yml
+++ b/api/walletApi-V0.yml
@@ -235,7 +235,7 @@ paths:
/api/accounts/{nickname}/sign:
post:
operationId: Sign
- description: Sign an operation or a message using the account associated with the provided nickname in the path. If no correlationId is provided, the user will be prompted to enter their account password.
+ description: Sign an operation or a message using the account associated with the provided nickname in the path.
parameters:
- $ref: "#/parameters/nickname"
- in: body
@@ -255,7 +255,7 @@ paths:
- application/json
responses:
200:
- description: Returns the signature, public key, and correlationId (if provided).
+ description: Returns the signature, public key.
schema:
$ref: "#/definitions/SignResponse"
400:
@@ -263,7 +263,7 @@ paths:
schema:
$ref: "#/definitions/Error"
401:
- description: Unauthorized - The request requires user authentication. Only if no correlationId is provided.
+ description: Unauthorized - The request requires user authentication.
schema:
$ref: "#/definitions/Error"
404:
@@ -565,11 +565,6 @@ definitions:
description: Serialized attributes of the operation to be signed with the key pair corresponding to the given nickname.
type: string
format: byte
- batch:
- description: A boolean property that indicates whether the sign operation is part of a batch of operations. Set to true if this operation is part of a batch, otherwise set to false.
- type: boolean
- correlationId:
- $ref: "#/definitions/CorrelationId"
chainId:
type: integer
description: The chain id of the network to which the operation will be sent.
@@ -659,12 +654,6 @@ definitions:
format: byte
x-nullable: false
readOnly: true
- correlationId:
- $ref: "#/definitions/CorrelationId"
- CorrelationId:
- description: Correlation id of the operation batch
- type: string
- format: byte
Nickname:
description: Account's short name.
type: string
diff --git a/documentation/wallet.adoc b/documentation/wallet.adoc
index 28913aae3..6b099a106 100644
--- a/documentation/wallet.adoc
+++ b/documentation/wallet.adoc
@@ -666,11 +666,7 @@ NOTE: For hands-on testing and examples, refer to the provided Swagger documenta
The 'sign' endpoint serves the purpose of signing all five operation
types, including Transaction, Roll buy/sell, CallSC, and
-ExecuteSC. It provides the capability to batch operation requests by
-utilizing the correlationID parameter. No user interaction is required once first operation processing is underway.
-Batching operation requests in Massa Wallet does not entail processing the operations as a group.
-Instead, it enhances the user experience by allowing users to, sequentially, sign multiple operations
-after being prompted to provide a password once.
+ExecuteSC.
===== Parameters
@@ -680,23 +676,17 @@ after being prompted to provide a password once.
| `nickname` | Identifier for the account. | Required | String
| `description` | Text describing the signing intent. | Optional | String (Default: "", Max: 280 characters)
| `operation` | Serialized attributes for signing. | Required | Byte array
-| `batch` | Specifies if the operation is part of a batch. | Optional | Boolean (Default: `false`)
-| `correlationId` | Identifier for correlating multiple requests. | Optional | String (Default: "")
|===
-IMPORTANT: To ensure security, the `correlationId` should be sent in the request body, not in the query string.
-
===== Response
-The correlationId will be included if the batch parameter is set to true.
[%header,cols="1,3,1"]
|===
| Name | Description | Format
| `signature` | The signature of the signed message | String
| `publicKey` | The public key of the account | String
-| `correlationId` | Correlation id of the operation batch | String
|===
@@ -726,7 +716,6 @@ calls with the same data will initiate separate executions.
| Name | Description | Format
| `signature` | The signature of the signed message | String
| `publicKey` | The public key of the account | String
-| `correlationId` | Correlation id of the operation batch | String
|===
==== Create Account
diff --git a/internal/handler/wallet/api_test.go b/internal/handler/wallet/api_test.go
index befa01613..cb6d788ac 100644
--- a/internal/handler/wallet/api_test.go
+++ b/internal/handler/wallet/api_test.go
@@ -15,6 +15,7 @@ import (
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
"github.com/massalabs/station-massa-wallet/pkg/assets"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
"github.com/massalabs/station-massa-wallet/pkg/wallet"
@@ -72,6 +73,9 @@ func MockAPI() (*operations.MassaWalletAPI, prompt.WalletPrompterInterface, *ass
massaNodeMock := NewNodeFetcherMock()
+ // Load config file
+ config.Load()
+
// Set wallet API endpoints
AppendEndpoints(massaWalletAPI, prompterApp, massaNodeMock, AssetsStore, gcache.New(20).LRU().Build())
diff --git a/internal/handler/wallet/backup_test.go b/internal/handler/wallet/backup_test.go
index 10d173acc..d407585ff 100644
--- a/internal/handler/wallet/backup_test.go
+++ b/internal/handler/wallet/backup_test.go
@@ -51,7 +51,7 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
go func() {
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.YamlFileBackup),
}
testResult <- (<-resChan)
@@ -69,7 +69,7 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
go func() {
// send backup method
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.PrivateKeyBackup),
}
prompterApp.App().CtrlChan <- walletapp.Cancel
@@ -85,12 +85,12 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
go func() {
// send backup method
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.PrivateKeyBackup),
}
// send password
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: "wrong password",
}
@@ -100,7 +100,7 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
// send password
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: password,
}
testResult <- (<-resChan)
@@ -127,12 +127,12 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
go func() {
// send backup method
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.PrivateKeyBackup),
}
// send password
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: "wrong password",
}
@@ -153,12 +153,12 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
go func() {
// send backup method
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.PrivateKeyBackup),
}
// send password
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: password,
}
diff --git a/internal/handler/wallet/create_test.go b/internal/handler/wallet/create_test.go
index cb1043cd9..5399a1402 100644
--- a/internal/handler/wallet/create_test.go
+++ b/internal/handler/wallet/create_test.go
@@ -44,7 +44,7 @@ func Test_walletCreate_Handle(t *testing.T) {
prompterApp.App().CtrlChan <- walletapp.Cancel
} else {
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: test.password,
}
// forward test result to test goroutine
diff --git a/internal/handler/wallet/delete_test.go b/internal/handler/wallet/delete_test.go
index 527d7577a..dc90e2ad0 100644
--- a/internal/handler/wallet/delete_test.go
+++ b/internal/handler/wallet/delete_test.go
@@ -41,7 +41,7 @@ func Test_walletDelete_Handle(t *testing.T) {
// Send password to prompter app and wait for result
go func() {
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: "invalid password",
}
// forward test result to test goroutine
@@ -61,7 +61,7 @@ func Test_walletDelete_Handle(t *testing.T) {
go func() {
// Send wrong password to prompter app and wait for result
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: "this is not the password",
}
// forward test result to test goroutine
@@ -81,37 +81,11 @@ func Test_walletDelete_Handle(t *testing.T) {
assert.NoError(t, err)
})
- t.Run("invalid prompt correlation id", func(t *testing.T) {
- // first send a invalid prompt correlation id and then the correct one
- go func() {
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: "666"},
- Message: password,
- }
-
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
- Message: password,
- }
- // forward test result to test goroutine
- testResult <- (<-resChan)
- }()
-
- resp := deleteWallet(t, api, nickname)
- verifyStatusCode(t, resp, http.StatusNoContent)
- result := <-testResult
- checkResultChannel(t, result, true, "")
- _, err = prompterApp.App().Wallet.GetAccount(nickname)
- assert.Error(t, err, "Wallet should have been deleted")
- })
-
t.Run("delete success", func(t *testing.T) {
- createAccount(password, nickname, t, prompterApp)
-
// Send password to prompter app and wait for result
go func() {
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: password,
}
// forward test result to test goroutine
@@ -127,6 +101,7 @@ func Test_walletDelete_Handle(t *testing.T) {
checkResultChannel(t, result, true, "")
_, err = prompterApp.App().Wallet.GetAccount(nickname)
+
assert.Error(t, err, "Wallet should have been deleted")
})
}
diff --git a/internal/handler/wallet/errors.go b/internal/handler/wallet/errors.go
index 4ccbd2bb0..664eeebd0 100644
--- a/internal/handler/wallet/errors.go
+++ b/internal/handler/wallet/errors.go
@@ -16,8 +16,6 @@ const (
errorSignDecodeMessage
errorSign
errorSignLoadCache
- errorSignCorrelationIDNotFound
- errorSignGenerateCorrelationID
errorImportWalletCanceled
errorImportNickNameAlreadyTaken
errorImportWallet
diff --git a/internal/handler/wallet/get_test.go b/internal/handler/wallet/get_test.go
index ed9959747..32ec9f49f 100644
--- a/internal/handler/wallet/get_test.go
+++ b/internal/handler/wallet/get_test.go
@@ -97,7 +97,7 @@ func Test_getWallet_handler(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: password,
}
// forward test result to test goroutine
diff --git a/internal/handler/wallet/import_test.go b/internal/handler/wallet/import_test.go
index 162c0db8b..7f7ee3969 100644
--- a/internal/handler/wallet/import_test.go
+++ b/internal/handler/wallet/import_test.go
@@ -59,7 +59,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
// Send filepath to prompter app and wait for result
go func(res chan walletapp.EventData) {
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: filePath,
}
// forward test result to test goroutine
@@ -96,7 +96,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
go func(res chan walletapp.EventData) {
// Send invalid file to prompter app and wait for result
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: filePath,
}
failRes := <-resChan
@@ -141,7 +141,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
go func(res chan walletapp.EventData) {
// Send invalid filename to prompter app and wait for result
prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Message: filePath,
}
failRes := <-resChan
@@ -202,7 +202,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
go func(res chan walletapp.EventData) {
prompterApp.App().PromptInput <- &walletapp.ImportPKeyPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
PrivateKey: tt.privateKey,
Nickname: tt.nickname,
Password: tt.password,
diff --git a/internal/handler/wallet/rolls_test.go b/internal/handler/wallet/rolls_test.go
index aba4e22cf..2d598ba81 100644
--- a/internal/handler/wallet/rolls_test.go
+++ b/internal/handler/wallet/rolls_test.go
@@ -85,7 +85,7 @@ func Test_traderolls_handler(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
}
@@ -112,7 +112,7 @@ func Test_traderolls_handler(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
}
diff --git a/internal/handler/wallet/sign.go b/internal/handler/wallet/sign.go
index fcb1723af..f7f94e61c 100644
--- a/internal/handler/wallet/sign.go
+++ b/internal/handler/wallet/sign.go
@@ -2,10 +2,8 @@ package wallet
import (
"crypto/ed25519"
- cryptorand "crypto/rand"
"encoding/binary"
"fmt"
- "io"
"net/http"
"os"
"strconv"
@@ -34,8 +32,9 @@ import (
)
const (
- defaultExpirationTime = time.Second * 60 * 10
+ defaultExpirationTime = time.Hour * 24 * 30
RollPrice = 100
+ cacheKeyPrefix = "pkey"
)
type PromptRequestSignData struct {
@@ -83,18 +82,17 @@ func (w *walletSign) Handle(params operations.SignParams) middleware.Responder {
return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorSignDecodeMessage, http.StatusBadRequest)
}
- var correlationID *memguard.LockedBuffer
var privateKey *memguard.LockedBuffer
- const disableBatchSigning = true
+ // accountCfg, err := config.GetAccountConfig(params.Nickname)
- if params.Body.CorrelationID != nil && !disableBatchSigning {
- correlationID = memguard.NewBufferFromBytes(params.Body.CorrelationID)
-
- pk, err := w.privateKeyFromCache(acc, correlationID)
+ if err != nil {
+ // todo
+ // todo: handle cache expired error
+ pk, err := w.privateKeyFromCache(acc)
if err != nil {
- if errors.Is(err, utils.ErrCorrelationIDNotFound) {
- return newErrorResponse(err.Error(), errorSignCorrelationIDNotFound, http.StatusNotFound)
+ if errors.Is(err, errors.New("errtodo")) {
+ return newErrorResponse(err.Error(), "errtodo", http.StatusNotFound)
}
return newErrorResponse(err.Error(), errorSign, http.StatusInternalServerError)
@@ -121,14 +119,11 @@ func (w *walletSign) Handle(params operations.SignParams) middleware.Responder {
privateKey = pk
- if params.Body.Batch {
- cID, err := w.CacheAccount(acc, privateKey)
+ if false { // todo
+ err := w.CacheAccount(acc, privateKey)
if err != nil {
- return newErrorResponse(err.Error(), errorSignCorrelationIDNotFound, http.StatusInternalServerError)
+ return newErrorResponse(err.Error(), "errtodo", http.StatusInternalServerError)
}
- correlationID = cID
- } else {
- correlationID = memguard.NewBufferFromBytes([]byte{})
}
}
@@ -143,7 +138,7 @@ func (w *walletSign) Handle(params operations.SignParams) middleware.Responder {
return newErrorResponse("Error: signature verification failed", "errorSignVerifySignature", http.StatusInternalServerError)
}
- return w.Success(acc, signature, correlationID, operation)
+ return w.Success(acc, signature, operation)
}
func (w *walletSign) PromptPassword(acc *account.Account, promptRequest *prompt.PromptRequest) (*walletapp.SignPromptOutput, error) {
@@ -163,17 +158,14 @@ func (w *walletSign) PromptPassword(acc *account.Account, promptRequest *prompt.
return output, nil
}
-func (w *walletSign) CacheAccount(acc *account.Account, privateKey *memguard.LockedBuffer) (*memguard.LockedBuffer, error) {
- correlationID, err := generateCorrelationID()
- if err != nil {
- return nil, fmt.Errorf("Error cannot generate correlation id: %w", err)
- }
+func (w *walletSign) CacheAccount(acc *account.Account, privateKey *memguard.LockedBuffer) error {
+ cacheKey := cacheKeyPrefix + acc.Nickname
- key := CacheKey(correlationID.Bytes())
+ key := KeyHash([]byte(cacheKey))
- cipheredPrivateKey, err := Xor(privateKey, correlationID)
+ cipheredPrivateKey, err := Xor(privateKey, key)
if err != nil {
- return nil, fmt.Errorf("Error cannot XOR correlation id: %w", err)
+ return fmt.Errorf("Error cannot XOR private key: %w", err)
}
cacheValue := make([]byte, ed25519.PrivateKeySize)
@@ -182,13 +174,13 @@ func (w *walletSign) CacheAccount(acc *account.Account, privateKey *memguard.Loc
err = w.gc.SetWithExpire(key, cacheValue, expirationDuration())
if err != nil {
- return nil, fmt.Errorf("Error set correlation id in cache: %w", err)
+ return fmt.Errorf("Error set private key in cache: %w", err)
}
- return correlationID, nil
+ return nil
}
-func (w *walletSign) Success(acc *account.Account, signature []byte, correlationId *memguard.LockedBuffer, operation []byte) middleware.Responder {
+func (w *walletSign) Success(acc *account.Account, signature []byte, operation []byte) middleware.Responder {
publicKeyBytes, err := acc.PublicKey.MarshalText()
if err != nil {
return newErrorResponse(err.Error(), errorGetAccount, http.StatusInternalServerError)
@@ -196,10 +188,9 @@ func (w *walletSign) Success(acc *account.Account, signature []byte, correlation
return operations.NewSignOK().WithPayload(
&models.SignResponse{
- PublicKey: string(publicKeyBytes),
- Signature: signature,
- CorrelationID: correlationId.Bytes(),
- Operation: operation,
+ PublicKey: string(publicKeyBytes),
+ Signature: signature,
+ Operation: operation,
})
}
@@ -384,14 +375,14 @@ func (w *walletSign) getTransactionPromptData(
// privateKeyFromCache return the private key from the cache or an error.
func (w *walletSign) privateKeyFromCache(
acc *account.Account,
- correlationID *memguard.LockedBuffer,
) (*memguard.LockedBuffer, error) {
- key := CacheKey(correlationID.Bytes())
+ cacheKey := cacheKeyPrefix + acc.Nickname
+ keyHash := KeyHash([]byte(cacheKey))
- value, err := w.gc.Get(key)
+ value, err := w.gc.Get(keyHash)
if err != nil {
if err.Error() == gcache.KeyNotFoundError.Error() {
- return nil, fmt.Errorf("%w: %w", utils.ErrCorrelationIDNotFound, err)
+ return nil, fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
}
return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
@@ -411,35 +402,33 @@ func (w *walletSign) privateKeyFromCache(
cipheredPrivateKey := memguard.NewBufferFromBytes(cacheValue)
- privateKey, err := Xor(cipheredPrivateKey, correlationID)
+ privateKey, err := Xor(cipheredPrivateKey, keyHash)
if err != nil {
return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
}
cipheredPrivateKey.Destroy()
- correlationID.Destroy()
return privateKey, nil
}
-func Xor(bufferA, bufferB *memguard.LockedBuffer) (*memguard.LockedBuffer, error) {
- a := bufferA.Bytes()
- b := bufferB.Bytes()
+func Xor(pkey *memguard.LockedBuffer, cacheKeyHash [32]byte) (*memguard.LockedBuffer, error) {
+ a := pkey.Bytes()
- if len(a) != len(b) {
- return nil, fmt.Errorf("length of two arrays must be same, %d and %d", len(a), len(b))
+ if len(a) != len(cacheKeyHash) {
+ return nil, fmt.Errorf("length of two arrays must be same, %d and %d", len(a), len(cacheKeyHash))
}
result := make([]byte, len(a))
for i := 0; i < len(a); i++ {
- result[i] = a[i] ^ b[i]
+ result[i] = a[i] ^ cacheKeyHash[i]
}
return memguard.NewBufferFromBytes(result), nil
}
-func CacheKey(correlationID models.CorrelationID) [32]byte {
- return blake3.Sum256(correlationID)
+func KeyHash(cacheKeyHash []byte) [32]byte {
+ return blake3.Sum256(cacheKeyHash)
}
func expirationDuration() time.Duration {
@@ -457,17 +446,6 @@ func expirationDuration() time.Duration {
return duration
}
-func generateCorrelationID() (*memguard.LockedBuffer, error) {
- rand := cryptorand.Reader
-
- correlationId := make([]byte, ed25519.PrivateKeySize)
- if _, err := io.ReadFull(rand, correlationId); err != nil {
- return nil, err
- }
-
- return memguard.NewBufferFromBytes(correlationId), nil
-}
-
func convertAssetsToModel(assetsWithBalance []*assets.AssetInfoWithBalances) []models.AssetInfo {
result := make([]models.AssetInfo, 0)
diff --git a/internal/handler/wallet/sign_test.go b/internal/handler/wallet/sign_test.go
index 3e2da4dbe..59525db7c 100644
--- a/internal/handler/wallet/sign_test.go
+++ b/internal/handler/wallet/sign_test.go
@@ -1,15 +1,12 @@
package wallet
import (
- "encoding/base64"
- "encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"testing"
- "github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
"github.com/massalabs/station-massa-wallet/pkg/utils"
@@ -72,7 +69,7 @@ func Test_walletSign_Handle(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "14400",
}
@@ -94,7 +91,7 @@ func Test_walletSign_Handle(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
}
@@ -121,7 +118,7 @@ func Test_walletSign_Handle(t *testing.T) {
go func(res chan walletapp.EventData) {
// Send wrong password to prompter app and wait for result
prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Password: "this is not the password",
Fees: "1000",
}
@@ -132,7 +129,7 @@ func Test_walletSign_Handle(t *testing.T) {
// Send password to prompter app to unlock the handler
prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
}
@@ -154,7 +151,7 @@ func Test_walletSign_Handle(t *testing.T) {
go func() {
// Send wrong password to prompter app and wait for result
prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Password: "this is not the password",
Fees: "1000",
}
@@ -170,74 +167,4 @@ func Test_walletSign_Handle(t *testing.T) {
resp := signTransaction(t, api, nickname, transactionData)
verifyStatusCode(t, resp, http.StatusUnauthorized)
})
-
- t.Run("sign transaction batch", func(t *testing.T) {
- transactionDataBatch := fmt.Sprintf(`{"chainId": `+strconv.FormatUint(ChainIDUnitTests, 10)+`, "operation":"%s","batch":true}`, callSCString)
- testResult := make(chan walletapp.EventData)
-
- // Send password to prompter app and wait for result
- go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
- Password: password,
- Fees: "1000",
- }
- // forward test result to test goroutine
- res <- (<-resChan)
- }(testResult)
-
- resp := signTransaction(t, api, nickname, transactionDataBatch)
- verifyStatusCode(t, resp, http.StatusOK)
-
- result := <-testResult
-
- checkResultChannel(t, result, true, "")
-
- var body models.SignResponse
- err = json.Unmarshal(resp.Body.Bytes(), &body)
- assert.NoError(t, err)
-
- correlationId := base64.StdEncoding.EncodeToString(body.CorrelationID)
-
- transactionDataBatch = fmt.Sprintf(`{"chainId": `+strconv.FormatUint(ChainIDUnitTests, 10)+`, "operation":"%s","correlationId":"%s"}`, callSCString, correlationId)
- // Send new transaction without password prompt
- // ---
- // No, we disable batch signing, so send password prompt:
- go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
- Password: password,
- Fees: "1000",
- }
- // forward test result to test goroutine
- res <- (<-resChan)
- }(testResult)
- // ---
- resp = signTransaction(t, api, nickname, transactionDataBatch)
- verifyStatusCode(t, resp, http.StatusOK)
-
- // Send new transaction with incorrect correlation id
- correlationId = base64.StdEncoding.EncodeToString([]byte("wrong correlation id"))
- transactionDataBatch = fmt.Sprintf(`{"chainId": `+strconv.FormatUint(ChainIDUnitTests, 10)+`, "operation":"%s","correlationId":"%s"}`, callSCString, correlationId)
-
- // We disable batch signing, so send password prompt:
- go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
- Password: password,
- Fees: "1000",
- }
- // forward test result to test goroutine
- res <- (<-resChan)
- }(testResult)
-
- resp = signTransaction(t, api, nickname, transactionDataBatch)
-
- verifyStatusCode(t, resp, http.StatusOK) // remove this line when enabling batch signing
- // Uncomment the code below when enabling batch signing:
- // var bodyError operations.SignInternalServerError
- // err = json.Unmarshal(resp.Body.Bytes(), &bodyError)
- // assert.NoError(t, err)
- // verifyStatusCode(t, resp, http.StatusNotFound)
- })
}
diff --git a/internal/handler/wallet/testsUtils.go b/internal/handler/wallet/testsUtils.go
index 404c2662a..b24d03d0f 100644
--- a/internal/handler/wallet/testsUtils.go
+++ b/internal/handler/wallet/testsUtils.go
@@ -13,8 +13,7 @@ import (
)
const (
- ChainIDUnitTests = 77658366 // Buildnet
- PromptCorrelationTestId = "1"
+ ChainIDUnitTests = 77658366 // Buildnet
)
func createAccount(password, nickname string, t *testing.T, prompterApp prompt.WalletPrompterInterface) *account.Account {
diff --git a/internal/handler/wallet/transfer_test.go b/internal/handler/wallet/transfer_test.go
index 62e52bad0..04439252c 100644
--- a/internal/handler/wallet/transfer_test.go
+++ b/internal/handler/wallet/transfer_test.go
@@ -64,7 +64,7 @@ func Test_transfer_handler(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
- BaseMessage: walletapp.BaseMessage{CorrelationID: PromptCorrelationTestId},
+ BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
}
diff --git a/pkg/app/app.go b/pkg/app/app.go
index 4bea18e2b..74e024937 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -17,7 +17,7 @@ import (
type WalletApp struct {
Ctx context.Context
CtrlChan chan PromptCtrl
- PromptInput chan CorrelationIdentifier
+ PromptInput chan EventInterface
Wallet *wallet.Wallet
Shutdown bool
IsListening bool
@@ -37,7 +37,7 @@ func (a *WalletApp) cleanExit() {
func NewWalletApp(wallet *wallet.Wallet) *WalletApp {
app := &WalletApp{
CtrlChan: make(chan PromptCtrl),
- PromptInput: make(chan CorrelationIdentifier),
+ PromptInput: make(chan EventInterface),
Wallet: wallet,
Shutdown: false,
IsListening: false,
@@ -78,25 +78,25 @@ func (a *WalletApp) BeforeClose(ctx context.Context) bool {
// Functions to send user input to the backend from the wails frontend
-func (a *WalletApp) SendPromptInput(input string, correlationID string) {
+func (a *WalletApp) SendPromptInput(input string) {
if !a.IsListening {
logger.Warn("Not listening (in SendPromptInput)")
return
}
- a.PromptInput <- &StringPromptInput{BaseMessage: BaseMessage{CorrelationID: correlationID}, Message: input}
+ a.PromptInput <- &StringPromptInput{BaseMessage: BaseMessage{}, Message: input}
}
-func (a *WalletApp) SendSignPromptInput(password string, fees string, correlationID string) {
+func (a *WalletApp) SendSignPromptInput(password string, fees string) {
if !a.IsListening {
logger.Warn("Not listening (in SendSignPromptInput)")
return
}
- a.PromptInput <- &SignPromptInput{BaseMessage: BaseMessage{CorrelationID: correlationID}, Password: password, Fees: fees}
+ a.PromptInput <- &SignPromptInput{BaseMessage: BaseMessage{}, Password: password, Fees: fees}
}
-func (a *WalletApp) SendPKeyPromptInput(privateKeyText string, nickname string, password string, correlationID string) {
+func (a *WalletApp) SendPKeyPromptInput(privateKeyText string, nickname string, password string) {
if !a.IsListening {
logger.Warn("Not listening (in SendPKeyPromptInput)")
return
@@ -105,7 +105,7 @@ func (a *WalletApp) SendPKeyPromptInput(privateKeyText string, nickname string,
guardedPrivateKey := memguard.NewBufferFromBytes([]byte(privateKeyText))
a.PromptInput <- &ImportPKeyPromptInput{
- BaseMessage: BaseMessage{CorrelationID: correlationID},
+ BaseMessage: BaseMessage{},
PrivateKey: guardedPrivateKey,
Nickname: nickname,
Password: memguard.NewBufferFromBytes([]byte(password)),
diff --git a/pkg/app/events.go b/pkg/app/events.go
index e898bd13e..a063a3a28 100644
--- a/pkg/app/events.go
+++ b/pkg/app/events.go
@@ -36,20 +36,12 @@ const (
// User input interfaces for the channel
-// CorrelationIdentifier interface that all message types will implement
-type CorrelationIdentifier interface {
- GetCorrelationID() string
-}
-
-// BaseMessage contains the common CorrelationID attribute
-type BaseMessage struct {
- CorrelationID string
-}
+// EventInterface interface that all message types will implement
+// Unused for now
+type EventInterface interface{}
-// GetCorrelationID retrieves the CorrelationID from the BaseMessage struct
-func (m *BaseMessage) GetCorrelationID() string {
- return m.CorrelationID
-}
+// BaseMessage contains the common attribute
+type BaseMessage struct{}
type StringPromptInput struct {
BaseMessage
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 9136b7751..0843a0853 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -2,6 +2,7 @@ package config
import (
jsonStd "encoding/json"
+ "fmt"
"log"
"os"
"path/filepath"
@@ -14,17 +15,17 @@ import (
)
type SignRule struct {
- Contract string `json:"contract"`
- PasswordPrompt bool `json:"passwordPrompt"`
- ConfirmationBypass bool `json:"confirmationBypass"`
+ Contract string `json:"contract"`
+ PasswordPrompt bool `json:"passwordPrompt"`
+ AutoSign bool `json:"autoSign"`
}
-type Account struct {
+type AccountCfg struct {
SignRules []SignRule `json:"signRules"`
}
type Config struct {
- Accounts map[string]Account `json:"accounts"`
+ Accounts map[string]AccountCfg `json:"accounts"`
}
var (
@@ -72,9 +73,20 @@ func Get() *Config {
return cfg
}
+func GetAccountConfig(accountName string) (*AccountCfg, error) {
+ cfg := Get()
+
+ account, exists := cfg.Accounts[accountName]
+ if !exists {
+ return nil, fmt.Errorf("account '%s' not found in configuration", accountName)
+ }
+
+ return &account, nil
+}
+
func defaultConfig() *Config {
return &Config{
- Accounts: map[string]Account{},
+ Accounts: map[string]AccountCfg{},
}
}
diff --git a/pkg/network/operation.go b/pkg/network/operation.go
index 9bf52d32b..8419f4923 100644
--- a/pkg/network/operation.go
+++ b/pkg/network/operation.go
@@ -44,7 +44,6 @@ func SendOperation(
operationDataToSign := utils.PrepareSignData(uint64(chainID), append(publicKey, operationData...))
- // TODO: we do not implement the handling of the correlation id for now
signature, err := acc.Sign(password, operationDataToSign)
if err != nil {
return nil, fmt.Errorf("Error while signing operation: %w", err)
@@ -62,5 +61,5 @@ func SendOperation(
return nil, fmt.Errorf("Error during RPC call: %w", err)
}
- return &sendOperation.OperationResponse{CorrelationID: "", OperationID: resp[0]}, nil
+ return &sendOperation.OperationResponse{OperationID: resp[0]}, nil
}
diff --git a/pkg/prompt/env.go b/pkg/prompt/env.go
index daa7647d1..59f0817f3 100644
--- a/pkg/prompt/env.go
+++ b/pkg/prompt/env.go
@@ -17,9 +17,9 @@ func (e *envPrompter) PromptRequest(req PromptRequest) {
switch req.Action {
case walletapp.Sign:
- e.PromptApp.PromptInput <- &walletapp.SignPromptInput{BaseMessage: walletapp.BaseMessage{CorrelationID: req.CorrelationID}, Password: password, Fees: "10000000"}
+ e.PromptApp.PromptInput <- &walletapp.SignPromptInput{Password: password, Fees: "10000000"}
case walletapp.Delete, walletapp.NewPassword, walletapp.Unprotect:
- e.PromptApp.PromptInput <- &walletapp.StringPromptInput{BaseMessage: walletapp.BaseMessage{CorrelationID: req.CorrelationID}, Message: password}
+ e.PromptApp.PromptInput <- &walletapp.StringPromptInput{Message: password}
}
}()
}
diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go
index 4ad1e3cfc..5a1f0674a 100644
--- a/pkg/prompt/prompt.go
+++ b/pkg/prompt/prompt.go
@@ -3,8 +3,6 @@ package prompt
import (
"context"
"errors"
- "math/rand"
- "strconv"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
"github.com/massalabs/station-massa-wallet/pkg/utils"
@@ -15,11 +13,10 @@ import (
)
type PromptRequest struct {
- Action walletapp.PromptRequestAction
- Msg string
- Data interface{}
- CodeMessage string
- CorrelationID string
+ Action walletapp.PromptRequestAction
+ Msg string
+ Data interface{}
+ CodeMessage string
}
// WalletPrompter is a struct that wraps a Wails GUI application and implements the WalletPrompterInterface interface.
@@ -68,8 +65,6 @@ func WakeUpPrompt(
prompterApp.Lock()
defer prompterApp.Unlock()
- correlationId := strconv.FormatUint(rand.Uint64(), 10)
- req.CorrelationID = correlationId
prompterApp.PromptRequest(req)
ctxTimeout, cancel := context.WithTimeout(context.Background(), TIMEOUT)
@@ -80,15 +75,6 @@ func WakeUpPrompt(
for {
select {
case input := <-prompterApp.App().PromptInput:
- receivedCorrelationId := input.GetCorrelationID()
- // In test environnement we can't provide the correlation id,
- // so here we continue only if the correlation id is not the same as the one we sent,
- // and if the correlation id is not 1 (which is the test value for correlation id).
- if receivedCorrelationId != "1" && receivedCorrelationId != correlationId {
- logger.Warn("Received a prompt input with a different correlation id")
- continue
- }
-
var keepListening bool
var err error
diff --git a/pkg/utils/errors.go b/pkg/utils/errors.go
index f53edb0e4..1518ef06f 100644
--- a/pkg/utils/errors.go
+++ b/pkg/utils/errors.go
@@ -34,12 +34,12 @@ const (
// Sentinel errors
var (
- ErrCorrelationIDNotFound = errors.New("Correlation ID not found")
- ErrCache = errors.New("Error loading cache")
- ErrWrongPassword = errors.New("wrong password")
- ErrActionCanceled = errors.New("Action canceled by user")
- ErrInvalidInputType = errors.New("invalid prompt input type")
- ErrTimeout = errors.New("Password prompt reached timeout")
+ ErrPrivateKeyCache = errors.New("Private key not found in cache")
+ ErrCache = errors.New("Error loading cache")
+ ErrWrongPassword = errors.New("wrong password")
+ ErrActionCanceled = errors.New("Action canceled by user")
+ ErrInvalidInputType = errors.New("invalid prompt input type")
+ ErrTimeout = errors.New("Password prompt reached timeout")
)
func WailsErrorCode(err error) string {
diff --git a/wails-frontend/src/events/events.tsx b/wails-frontend/src/events/events.tsx
index 1c3f5111c..6d6b33eb9 100644
--- a/wails-frontend/src/events/events.tsx
+++ b/wails-frontend/src/events/events.tsx
@@ -20,7 +20,6 @@ export type promptRequest = {
Msg: string;
Data: any;
CodeMessage: string;
- CorrelationID: string;
};
export const events = {
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/Delete.tsx b/wails-frontend/src/pages/PasswordPromptHandler/Delete.tsx
index 727678d22..7b2cf8952 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/Delete.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/Delete.tsx
@@ -35,7 +35,7 @@ export function Delete() {
EventsOnce(events.promptResult, handleResult);
- SendPromptInput(password, req.CorrelationID);
+ SendPromptInput(password);
}
function handleResult(result: promptResult) {
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/Sign.tsx b/wails-frontend/src/pages/PasswordPromptHandler/Sign.tsx
index 7a0e94b30..00fe2d704 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/Sign.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/Sign.tsx
@@ -98,7 +98,7 @@ export function Sign() {
EventsOnce(events.promptResult, handleResult);
- SendSignPromptInput(password, fees.toString(), req.CorrelationID);
+ SendSignPromptInput(password, fees.toString());
}
function handleResult(result: promptResult) {
diff --git a/wails-frontend/src/pages/backupKeyPairs.tsx b/wails-frontend/src/pages/backupKeyPairs.tsx
index f08011225..45758da54 100644
--- a/wails-frontend/src/pages/backupKeyPairs.tsx
+++ b/wails-frontend/src/pages/backupKeyPairs.tsx
@@ -97,7 +97,7 @@ function BackupKeyPairs() {
const { password } = formObject;
EventsOnce(events.promptResult, handleResult);
- await SendPromptInput(password, req.CorrelationID);
+ await SendPromptInput(password);
}
return (
diff --git a/wails-frontend/src/pages/backupMethods.tsx b/wails-frontend/src/pages/backupMethods.tsx
index 644723e7e..f740bd4f6 100644
--- a/wails-frontend/src/pages/backupMethods.tsx
+++ b/wails-frontend/src/pages/backupMethods.tsx
@@ -26,11 +26,11 @@ function BackupMethods() {
handleApplyResult(navigate, req, setErrorMsg, true),
);
- await SendPromptInput(backupMethods.ymlFileBackup, req.CorrelationID);
+ await SendPromptInput(backupMethods.ymlFileBackup);
}
async function handleKeyPairs() {
- await SendPromptInput(backupMethods.privateKeyBackup, req.CorrelationID);
+ await SendPromptInput(backupMethods.privateKeyBackup);
navigate('/backup-pkey', { state: { req } });
}
diff --git a/wails-frontend/src/pages/confirmDelete.tsx b/wails-frontend/src/pages/confirmDelete.tsx
index 0a064d101..04f7ec252 100644
--- a/wails-frontend/src/pages/confirmDelete.tsx
+++ b/wails-frontend/src/pages/confirmDelete.tsx
@@ -17,7 +17,7 @@ function ConfirmDelete() {
function handleConfirm() {
EventsOnce(events.promptResult, handleApplyResult(navigate, req));
- SendPromptInput(password, req.CorrelationID);
+ SendPromptInput(password);
}
return (
diff --git a/wails-frontend/src/pages/importFile.tsx b/wails-frontend/src/pages/importFile.tsx
index 161d31da9..07bef45a6 100644
--- a/wails-frontend/src/pages/importFile.tsx
+++ b/wails-frontend/src/pages/importFile.tsx
@@ -53,7 +53,7 @@ const ImportFile = () => {
events.promptResult,
handleApplyResult(nav, req, setErrorMsg, true),
);
- await SendPromptInput(account.filePath, req.CorrelationID);
+ await SendPromptInput(account.filePath);
}
};
diff --git a/wails-frontend/src/pages/newPassword.tsx b/wails-frontend/src/pages/newPassword.tsx
index d02c90987..562eac872 100644
--- a/wails-frontend/src/pages/newPassword.tsx
+++ b/wails-frontend/src/pages/newPassword.tsx
@@ -88,13 +88,8 @@ function NewPassword() {
);
return isImportAction
- ? SendPKeyPromptInput(
- state.privateKey,
- state.nickname,
- password,
- req.CorrelationID,
- )
- : SendPromptInput(password, req.CorrelationID);
+ ? SendPKeyPromptInput(state.privateKey, state.nickname, password)
+ : SendPromptInput(password);
}
async function handleSubmit(e: SyntheticEvent) {
diff --git a/wails-frontend/wailsjs/go/walletapp/WalletApp.d.ts b/wails-frontend/wailsjs/go/walletapp/WalletApp.d.ts
index e3c59f5b8..b7c5da0a8 100755
--- a/wails-frontend/wailsjs/go/walletapp/WalletApp.d.ts
+++ b/wails-frontend/wailsjs/go/walletapp/WalletApp.d.ts
@@ -12,10 +12,10 @@ export function IsNicknameValid(arg1:string):Promise;
export function SelectAccountFile():Promise;
-export function SendPKeyPromptInput(arg1:string,arg2:string,arg3:string,arg4:string):Promise;
+export function SendPKeyPromptInput(arg1:string,arg2:string,arg3:string):Promise;
-export function SendPromptInput(arg1:string,arg2:string):Promise;
+export function SendPromptInput(arg1:string):Promise;
-export function SendSignPromptInput(arg1:string,arg2:string,arg3:string):Promise;
+export function SendSignPromptInput(arg1:string,arg2:string):Promise;
export function Show():Promise;
diff --git a/wails-frontend/wailsjs/go/walletapp/WalletApp.js b/wails-frontend/wailsjs/go/walletapp/WalletApp.js
index 8ba213d34..19787c800 100755
--- a/wails-frontend/wailsjs/go/walletapp/WalletApp.js
+++ b/wails-frontend/wailsjs/go/walletapp/WalletApp.js
@@ -22,16 +22,16 @@ export function SelectAccountFile() {
return window['go']['walletapp']['WalletApp']['SelectAccountFile']();
}
-export function SendPKeyPromptInput(arg1, arg2, arg3, arg4) {
- return window['go']['walletapp']['WalletApp']['SendPKeyPromptInput'](arg1, arg2, arg3, arg4);
+export function SendPKeyPromptInput(arg1, arg2, arg3) {
+ return window['go']['walletapp']['WalletApp']['SendPKeyPromptInput'](arg1, arg2, arg3);
}
-export function SendPromptInput(arg1, arg2) {
- return window['go']['walletapp']['WalletApp']['SendPromptInput'](arg1, arg2);
+export function SendPromptInput(arg1) {
+ return window['go']['walletapp']['WalletApp']['SendPromptInput'](arg1);
}
-export function SendSignPromptInput(arg1, arg2, arg3) {
- return window['go']['walletapp']['WalletApp']['SendSignPromptInput'](arg1, arg2, arg3);
+export function SendSignPromptInput(arg1, arg2) {
+ return window['go']['walletapp']['WalletApp']['SendSignPromptInput'](arg1, arg2);
}
export function Show() {
From de6692d022a1108a645f089f48972aa1fdbec12c Mon Sep 17 00:00:00 2001
From: Peterjah
Date: Fri, 20 Dec 2024 14:45:57 +0100
Subject: [PATCH 04/10] add getConfig endpoint
---
api/server/models/account_config.go | 124 +++++++++++++++
api/server/models/config.go | 111 ++++++++++++++
api/server/models/sign_rule.go | 105 +++++++++++++
api/server/restapi/embedded_spec.go | 142 ++++++++++++++++++
api/server/restapi/operations/get_config.go | 56 +++++++
.../operations/get_config_parameters.go | 46 ++++++
.../operations/get_config_responses.go | 103 +++++++++++++
.../operations/get_config_urlbuilder.go | 84 +++++++++++
.../restapi/operations/massa_wallet_api.go | 12 ++
api/walletApi-V0.yml | 51 +++++++
internal/handler/wallet/api.go | 1 +
internal/handler/wallet/errors.go | 1 +
internal/handler/wallet/get_config.go | 51 +++++++
13 files changed, 887 insertions(+)
create mode 100644 api/server/models/account_config.go
create mode 100644 api/server/models/config.go
create mode 100644 api/server/models/sign_rule.go
create mode 100644 api/server/restapi/operations/get_config.go
create mode 100644 api/server/restapi/operations/get_config_parameters.go
create mode 100644 api/server/restapi/operations/get_config_responses.go
create mode 100644 api/server/restapi/operations/get_config_urlbuilder.go
create mode 100644 internal/handler/wallet/get_config.go
diff --git a/api/server/models/account_config.go b/api/server/models/account_config.go
new file mode 100644
index 000000000..1442dcb2c
--- /dev/null
+++ b/api/server/models/account_config.go
@@ -0,0 +1,124 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+ "strconv"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// AccountConfig Wallet Account Config.
+//
+// swagger:model AccountConfig
+type AccountConfig struct {
+
+ // sign rules
+ // Required: true
+ SignRules []*SignRule `json:"signRules"`
+}
+
+// Validate validates this account config
+func (m *AccountConfig) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.validateSignRules(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *AccountConfig) validateSignRules(formats strfmt.Registry) error {
+
+ if err := validate.Required("signRules", "body", m.SignRules); err != nil {
+ return err
+ }
+
+ for i := 0; i < len(m.SignRules); i++ {
+ if swag.IsZero(m.SignRules[i]) { // not required
+ continue
+ }
+
+ if m.SignRules[i] != nil {
+ if err := m.SignRules[i].Validate(formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("signRules" + "." + strconv.Itoa(i))
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("signRules" + "." + strconv.Itoa(i))
+ }
+ return err
+ }
+ }
+
+ }
+
+ return nil
+}
+
+// ContextValidate validate this account config based on the context it is used
+func (m *AccountConfig) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.contextValidateSignRules(ctx, formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *AccountConfig) contextValidateSignRules(ctx context.Context, formats strfmt.Registry) error {
+
+ for i := 0; i < len(m.SignRules); i++ {
+
+ if m.SignRules[i] != nil {
+
+ if swag.IsZero(m.SignRules[i]) { // not required
+ return nil
+ }
+
+ if err := m.SignRules[i].ContextValidate(ctx, formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("signRules" + "." + strconv.Itoa(i))
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("signRules" + "." + strconv.Itoa(i))
+ }
+ return err
+ }
+ }
+
+ }
+
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *AccountConfig) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *AccountConfig) UnmarshalBinary(b []byte) error {
+ var res AccountConfig
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/models/config.go b/api/server/models/config.go
new file mode 100644
index 000000000..e721cd724
--- /dev/null
+++ b/api/server/models/config.go
@@ -0,0 +1,111 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// Config Wallet Config.
+//
+// swagger:model Config
+type Config struct {
+
+ // accounts
+ Accounts map[string]AccountConfig `json:"accounts,omitempty"`
+}
+
+// Validate validates this config
+func (m *Config) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.validateAccounts(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *Config) validateAccounts(formats strfmt.Registry) error {
+ if swag.IsZero(m.Accounts) { // not required
+ return nil
+ }
+
+ for k := range m.Accounts {
+
+ if err := validate.Required("accounts"+"."+k, "body", m.Accounts[k]); err != nil {
+ return err
+ }
+ if val, ok := m.Accounts[k]; ok {
+ if err := val.Validate(formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("accounts" + "." + k)
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("accounts" + "." + k)
+ }
+ return err
+ }
+ }
+
+ }
+
+ return nil
+}
+
+// ContextValidate validate this config based on the context it is used
+func (m *Config) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.contextValidateAccounts(ctx, formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *Config) contextValidateAccounts(ctx context.Context, formats strfmt.Registry) error {
+
+ for k := range m.Accounts {
+
+ if val, ok := m.Accounts[k]; ok {
+ if err := val.ContextValidate(ctx, formats); err != nil {
+ return err
+ }
+ }
+
+ }
+
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *Config) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *Config) UnmarshalBinary(b []byte) error {
+ var res Config
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/models/sign_rule.go b/api/server/models/sign_rule.go
new file mode 100644
index 000000000..19cf03d75
--- /dev/null
+++ b/api/server/models/sign_rule.go
@@ -0,0 +1,105 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// SignRule Account Sign rule.
+//
+// swagger:model SignRule
+type SignRule struct {
+
+ // auto sign
+ // Required: true
+ AutoSign *bool `json:"autoSign"`
+
+ // contract
+ // Required: true
+ Contract *string `json:"contract"`
+
+ // password prompt
+ // Required: true
+ PasswordPrompt *bool `json:"passwordPrompt"`
+}
+
+// Validate validates this sign rule
+func (m *SignRule) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.validateAutoSign(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateContract(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validatePasswordPrompt(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *SignRule) validateAutoSign(formats strfmt.Registry) error {
+
+ if err := validate.Required("autoSign", "body", m.AutoSign); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *SignRule) validateContract(formats strfmt.Registry) error {
+
+ if err := validate.Required("contract", "body", m.Contract); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *SignRule) validatePasswordPrompt(formats strfmt.Registry) error {
+
+ if err := validate.Required("passwordPrompt", "body", m.PasswordPrompt); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ContextValidate validates this sign rule based on context it is used
+func (m *SignRule) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *SignRule) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *SignRule) UnmarshalBinary(b []byte) error {
+ var res SignRule
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/restapi/embedded_spec.go b/api/server/restapi/embedded_spec.go
index c642b353f..76769667e 100644
--- a/api/server/restapi/embedded_spec.go
+++ b/api/server/restapi/embedded_spec.go
@@ -779,6 +779,29 @@ func init() {
}
}
},
+ "/api/config": {
+ "get": {
+ "description": "Get wallet config",
+ "produces": [
+ "application/json"
+ ],
+ "operationId": "GetConfig",
+ "responses": {
+ "200": {
+ "description": "Config retrieved.",
+ "schema": {
+ "$ref": "#/definitions/Config"
+ }
+ },
+ "500": {
+ "description": "Internal Server Error - The server has encountered a situation it does not know how to handle.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
"/web-app/{resource}": {
"get": {
"description": "Route for the ReactJS front-end web application (in /web-frontend)",
@@ -851,6 +874,21 @@ func init() {
}
}
},
+ "AccountConfig": {
+ "description": "Wallet Account Config.",
+ "type": "object",
+ "required": [
+ "signRules"
+ ],
+ "properties": {
+ "signRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/SignRule"
+ }
+ }
+ }
+ },
"Address": {
"description": "Account's address.",
"type": "string",
@@ -904,6 +942,19 @@ func init() {
}
]
},
+ "Config": {
+ "description": "Wallet Config.",
+ "type": "object",
+ "properties": {
+ "accounts": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/AccountConfig"
+ }
+ }
+ },
+ "x-nullable": false
+ },
"Error": {
"description": "Error object.",
"type": "object",
@@ -1066,6 +1117,26 @@ func init() {
}
}
},
+ "SignRule": {
+ "description": "Account Sign rule.",
+ "type": "object",
+ "required": [
+ "contract",
+ "passwordPrompt",
+ "autoSign"
+ ],
+ "properties": {
+ "autoSign": {
+ "type": "boolean"
+ },
+ "contract": {
+ "type": "string"
+ },
+ "passwordPrompt": {
+ "type": "boolean"
+ }
+ }
+ },
"TransferRequest": {
"type": "object",
"required": [
@@ -1946,6 +2017,29 @@ func init() {
}
}
},
+ "/api/config": {
+ "get": {
+ "description": "Get wallet config",
+ "produces": [
+ "application/json"
+ ],
+ "operationId": "GetConfig",
+ "responses": {
+ "200": {
+ "description": "Config retrieved.",
+ "schema": {
+ "$ref": "#/definitions/Config"
+ }
+ },
+ "500": {
+ "description": "Internal Server Error - The server has encountered a situation it does not know how to handle.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
"/web-app/{resource}": {
"get": {
"description": "Route for the ReactJS front-end web application (in /web-frontend)",
@@ -2018,6 +2112,21 @@ func init() {
}
}
},
+ "AccountConfig": {
+ "description": "Wallet Account Config.",
+ "type": "object",
+ "required": [
+ "signRules"
+ ],
+ "properties": {
+ "signRules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/SignRule"
+ }
+ }
+ }
+ },
"Address": {
"description": "Account's address.",
"type": "string",
@@ -2073,6 +2182,19 @@ func init() {
}
]
},
+ "Config": {
+ "description": "Wallet Config.",
+ "type": "object",
+ "properties": {
+ "accounts": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/AccountConfig"
+ }
+ }
+ },
+ "x-nullable": false
+ },
"Error": {
"description": "Error object.",
"type": "object",
@@ -2235,6 +2357,26 @@ func init() {
}
}
},
+ "SignRule": {
+ "description": "Account Sign rule.",
+ "type": "object",
+ "required": [
+ "contract",
+ "passwordPrompt",
+ "autoSign"
+ ],
+ "properties": {
+ "autoSign": {
+ "type": "boolean"
+ },
+ "contract": {
+ "type": "string"
+ },
+ "passwordPrompt": {
+ "type": "boolean"
+ }
+ }
+ },
"TransferRequest": {
"type": "object",
"required": [
diff --git a/api/server/restapi/operations/get_config.go b/api/server/restapi/operations/get_config.go
new file mode 100644
index 000000000..393016af2
--- /dev/null
+++ b/api/server/restapi/operations/get_config.go
@@ -0,0 +1,56 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime/middleware"
+)
+
+// GetConfigHandlerFunc turns a function with the right signature into a get config handler
+type GetConfigHandlerFunc func(GetConfigParams) middleware.Responder
+
+// Handle executing the request and returning a response
+func (fn GetConfigHandlerFunc) Handle(params GetConfigParams) middleware.Responder {
+ return fn(params)
+}
+
+// GetConfigHandler interface for that can handle valid get config params
+type GetConfigHandler interface {
+ Handle(GetConfigParams) middleware.Responder
+}
+
+// NewGetConfig creates a new http.Handler for the get config operation
+func NewGetConfig(ctx *middleware.Context, handler GetConfigHandler) *GetConfig {
+ return &GetConfig{Context: ctx, Handler: handler}
+}
+
+/*
+ GetConfig swagger:route GET /api/config getConfig
+
+Get wallet config
+*/
+type GetConfig struct {
+ Context *middleware.Context
+ Handler GetConfigHandler
+}
+
+func (o *GetConfig) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ route, rCtx, _ := o.Context.RouteInfo(r)
+ if rCtx != nil {
+ *r = *rCtx
+ }
+ var Params = NewGetConfigParams()
+ if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
+ o.Context.Respond(rw, r, route.Produces, route, err)
+ return
+ }
+
+ res := o.Handler.Handle(Params) // actually handle the request
+ o.Context.Respond(rw, r, route.Produces, route, res)
+
+}
diff --git a/api/server/restapi/operations/get_config_parameters.go b/api/server/restapi/operations/get_config_parameters.go
new file mode 100644
index 000000000..7501ecf5e
--- /dev/null
+++ b/api/server/restapi/operations/get_config_parameters.go
@@ -0,0 +1,46 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/runtime/middleware"
+)
+
+// NewGetConfigParams creates a new GetConfigParams object
+//
+// There are no default values defined in the spec.
+func NewGetConfigParams() GetConfigParams {
+
+ return GetConfigParams{}
+}
+
+// GetConfigParams contains all the bound params for the get config operation
+// typically these are obtained from a http.Request
+//
+// swagger:parameters GetConfig
+type GetConfigParams struct {
+
+ // HTTP Request Object
+ HTTPRequest *http.Request `json:"-"`
+}
+
+// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
+// for simple values it will use straight method calls.
+//
+// To ensure default values, the struct must have been initialized with NewGetConfigParams() beforehand.
+func (o *GetConfigParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
+ var res []error
+
+ o.HTTPRequest = r
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
diff --git a/api/server/restapi/operations/get_config_responses.go b/api/server/restapi/operations/get_config_responses.go
new file mode 100644
index 000000000..c805e7c5d
--- /dev/null
+++ b/api/server/restapi/operations/get_config_responses.go
@@ -0,0 +1,103 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+)
+
+// GetConfigOKCode is the HTTP code returned for type GetConfigOK
+const GetConfigOKCode int = 200
+
+/*
+GetConfigOK Config retrieved.
+
+swagger:response getConfigOK
+*/
+type GetConfigOK struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Config `json:"body,omitempty"`
+}
+
+// NewGetConfigOK creates GetConfigOK with default headers values
+func NewGetConfigOK() *GetConfigOK {
+
+ return &GetConfigOK{}
+}
+
+// WithPayload adds the payload to the get config o k response
+func (o *GetConfigOK) WithPayload(payload *models.Config) *GetConfigOK {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the get config o k response
+func (o *GetConfigOK) SetPayload(payload *models.Config) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *GetConfigOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(200)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// GetConfigInternalServerErrorCode is the HTTP code returned for type GetConfigInternalServerError
+const GetConfigInternalServerErrorCode int = 500
+
+/*
+GetConfigInternalServerError Internal Server Error - The server has encountered a situation it does not know how to handle.
+
+swagger:response getConfigInternalServerError
+*/
+type GetConfigInternalServerError struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewGetConfigInternalServerError creates GetConfigInternalServerError with default headers values
+func NewGetConfigInternalServerError() *GetConfigInternalServerError {
+
+ return &GetConfigInternalServerError{}
+}
+
+// WithPayload adds the payload to the get config internal server error response
+func (o *GetConfigInternalServerError) WithPayload(payload *models.Error) *GetConfigInternalServerError {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the get config internal server error response
+func (o *GetConfigInternalServerError) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *GetConfigInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(500)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
diff --git a/api/server/restapi/operations/get_config_urlbuilder.go b/api/server/restapi/operations/get_config_urlbuilder.go
new file mode 100644
index 000000000..9025f7d48
--- /dev/null
+++ b/api/server/restapi/operations/get_config_urlbuilder.go
@@ -0,0 +1,84 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "errors"
+ "net/url"
+ golangswaggerpaths "path"
+)
+
+// GetConfigURL generates an URL for the get config operation
+type GetConfigURL struct {
+ _basePath string
+}
+
+// WithBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *GetConfigURL) WithBasePath(bp string) *GetConfigURL {
+ o.SetBasePath(bp)
+ return o
+}
+
+// SetBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *GetConfigURL) SetBasePath(bp string) {
+ o._basePath = bp
+}
+
+// Build a url path and query string
+func (o *GetConfigURL) Build() (*url.URL, error) {
+ var _result url.URL
+
+ var _path = "/api/config"
+
+ _basePath := o._basePath
+ _result.Path = golangswaggerpaths.Join(_basePath, _path)
+
+ return &_result, nil
+}
+
+// Must is a helper function to panic when the url builder returns an error
+func (o *GetConfigURL) Must(u *url.URL, err error) *url.URL {
+ if err != nil {
+ panic(err)
+ }
+ if u == nil {
+ panic("url can't be nil")
+ }
+ return u
+}
+
+// String returns the string representation of the path with query string
+func (o *GetConfigURL) String() string {
+ return o.Must(o.Build()).String()
+}
+
+// BuildFull builds a full url with scheme, host, path and query string
+func (o *GetConfigURL) BuildFull(scheme, host string) (*url.URL, error) {
+ if scheme == "" {
+ return nil, errors.New("scheme is required for a full url on GetConfigURL")
+ }
+ if host == "" {
+ return nil, errors.New("host is required for a full url on GetConfigURL")
+ }
+
+ base, err := o.Build()
+ if err != nil {
+ return nil, err
+ }
+
+ base.Scheme = scheme
+ base.Host = host
+ return base, nil
+}
+
+// StringFull returns the string representation of a complete url
+func (o *GetConfigURL) StringFull(scheme, host string) string {
+ return o.Must(o.BuildFull(scheme, host)).String()
+}
diff --git a/api/server/restapi/operations/massa_wallet_api.go b/api/server/restapi/operations/massa_wallet_api.go
index c478a5bfd..22e4307bc 100644
--- a/api/server/restapi/operations/massa_wallet_api.go
+++ b/api/server/restapi/operations/massa_wallet_api.go
@@ -83,6 +83,9 @@ func NewMassaWalletAPI(spec *loads.Document) *MassaWalletAPI {
GetAllAssetsHandler: GetAllAssetsHandlerFunc(func(params GetAllAssetsParams) middleware.Responder {
return middleware.NotImplemented("operation GetAllAssets has not yet been implemented")
}),
+ GetConfigHandler: GetConfigHandlerFunc(func(params GetConfigParams) middleware.Responder {
+ return middleware.NotImplemented("operation GetConfig has not yet been implemented")
+ }),
ImportAccountHandler: ImportAccountHandlerFunc(func(params ImportAccountParams) middleware.Responder {
return middleware.NotImplemented("operation ImportAccount has not yet been implemented")
}),
@@ -177,6 +180,8 @@ type MassaWalletAPI struct {
GetAccountHandler GetAccountHandler
// GetAllAssetsHandler sets the operation handler for the get all assets operation
GetAllAssetsHandler GetAllAssetsHandler
+ // GetConfigHandler sets the operation handler for the get config operation
+ GetConfigHandler GetConfigHandler
// ImportAccountHandler sets the operation handler for the import account operation
ImportAccountHandler ImportAccountHandler
// SignHandler sets the operation handler for the sign operation
@@ -312,6 +317,9 @@ func (o *MassaWalletAPI) Validate() error {
if o.GetAllAssetsHandler == nil {
unregistered = append(unregistered, "GetAllAssetsHandler")
}
+ if o.GetConfigHandler == nil {
+ unregistered = append(unregistered, "GetConfigHandler")
+ }
if o.ImportAccountHandler == nil {
unregistered = append(unregistered, "ImportAccountHandler")
}
@@ -472,6 +480,10 @@ func (o *MassaWalletAPI) initHandlerCache() {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/api/accounts/{nickname}/assets"] = NewGetAllAssets(o.context, o.GetAllAssetsHandler)
+ if o.handlers["GET"] == nil {
+ o.handlers["GET"] = make(map[string]http.Handler)
+ }
+ o.handlers["GET"]["/api/config"] = NewGetConfig(o.context, o.GetConfigHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
diff --git a/api/walletApi-V0.yml b/api/walletApi-V0.yml
index 8906f647c..1eca8438e 100644
--- a/api/walletApi-V0.yml
+++ b/api/walletApi-V0.yml
@@ -42,6 +42,21 @@ paths:
description: Resource not found.
schema:
$ref: "#/definitions/Error"
+ /api/config:
+ get:
+ operationId: GetConfig
+ description: Get wallet config
+ produces:
+ - application/json
+ responses:
+ 200:
+ description: Config retrieved.
+ schema:
+ $ref: "#/definitions/Config"
+ 500:
+ description: Internal Server Error - The server has encountered a situation it does not know how to handle.
+ schema:
+ $ref: "#/definitions/Error"
/api/accounts:
get:
operationId: AccountList
@@ -749,3 +764,39 @@ definitions:
type: boolean
dollarValue:
type: string
+
+ SignRule:
+ type: object
+ description: Account Sign rule.
+ properties:
+ contract:
+ type: string
+ passwordPrompt:
+ type: boolean
+ autoSign:
+ type: boolean
+ required:
+ - contract
+ - passwordPrompt
+ - autoSign
+
+ AccountConfig:
+ type: object
+ description: Wallet Account Config.
+ properties:
+ signRules:
+ type: array
+ items:
+ $ref: "#/definitions/SignRule"
+ required:
+ - signRules
+
+ Config:
+ type: object
+ description: Wallet Config.
+ properties:
+ accounts:
+ type: object
+ additionalProperties:
+ $ref: "#/definitions/AccountConfig"
+ x-nullable: false
diff --git a/internal/handler/wallet/api.go b/internal/handler/wallet/api.go
index 1207fd441..3fd8c1709 100644
--- a/internal/handler/wallet/api.go
+++ b/internal/handler/wallet/api.go
@@ -37,6 +37,7 @@ func AppendEndpoints(api *operations.MassaWalletAPI, prompterApp prompt.WalletPr
api.AddAssetHandler = NewAddAsset(AssetsStore, massaClient)
api.GetAllAssetsHandler = NewGetAllAssets(prompterApp.App().Wallet, AssetsStore, massaClient)
api.DeleteAssetHandler = NewDeleteAsset(AssetsStore)
+ api.GetConfigHandler = NewGetConfig()
}
// loadAccount loads a wallet from the file system or returns an error.
diff --git a/internal/handler/wallet/errors.go b/internal/handler/wallet/errors.go
index 664eeebd0..36e710de0 100644
--- a/internal/handler/wallet/errors.go
+++ b/internal/handler/wallet/errors.go
@@ -31,4 +31,5 @@ const (
errorDeleteAssetJSON
errorAssetNotFound
errorTradeRoll
+ internalError
)
diff --git a/internal/handler/wallet/get_config.go b/internal/handler/wallet/get_config.go
new file mode 100644
index 000000000..952b68321
--- /dev/null
+++ b/internal/handler/wallet/get_config.go
@@ -0,0 +1,51 @@
+package wallet
+
+import (
+ "github.com/go-openapi/runtime/middleware"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+ "github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
+)
+
+func NewGetConfig() operations.GetConfigHandler {
+ return &getConfig{}
+}
+
+type getConfig struct{}
+
+func (w *getConfig) Handle(_ operations.GetConfigParams) middleware.Responder {
+ cfg := config.Get()
+ modelConfig, err := newConfigModel(cfg)
+ //nolint:wsl
+ if err != nil {
+ return operations.NewGetConfigInternalServerError().WithPayload(
+ &models.Error{
+ Code: internalError,
+ Message: "Unable to create config model",
+ })
+ }
+
+ return operations.NewGetConfigOK().WithPayload(modelConfig)
+}
+
+func newConfigModel(cfg *config.Config) (*models.Config, error) {
+ modelAccounts := make(map[string]models.AccountConfig)
+
+ for nickname, accountConfig := range cfg.Accounts {
+ modelSignRules := make([]*models.SignRule, len(accountConfig.SignRules))
+ for i, rule := range accountConfig.SignRules {
+ modelSignRules[i] = &models.SignRule{
+ Contract: &rule.Contract,
+ PasswordPrompt: &rule.PasswordPrompt,
+ AutoSign: &rule.AutoSign,
+ }
+ }
+ modelAccounts[nickname] = models.AccountConfig{
+ SignRules: modelSignRules,
+ }
+ }
+
+ return &models.Config{
+ Accounts: modelAccounts,
+ }, nil
+}
From 83243c3da3bc71774fb772708cb449e9761d534b Mon Sep 17 00:00:00 2001
From: Peterjah
Date: Thu, 9 Jan 2025 13:54:33 +0100
Subject: [PATCH 05/10] add signRule endpoints
---
.nvmrc | 1 +
api/server/models/add_sign_rule.go | 160 +++++
api/server/models/add_sign_rule_response.go | 71 +++
.../models/delete_sign_rule_response.go | 50 ++
api/server/models/request.go | 126 ++++
api/server/models/request_response.go | 127 ++++
api/server/models/request_type.go | 78 +++
api/server/models/rule_type.go | 78 +++
api/server/models/sign_rule.go | 62 +-
api/server/models/update_sign_rule.go | 160 +++++
.../models/update_sign_rule_response.go | 71 +++
api/server/restapi/embedded_spec.go | 599 +++++++++++++++++-
.../restapi/operations/add_sign_rule.go | 56 ++
.../operations/add_sign_rule_parameters.go | 108 ++++
.../operations/add_sign_rule_responses.go | 283 +++++++++
.../operations/add_sign_rule_urlbuilder.go | 96 +++
.../restapi/operations/delete_sign_rule.go | 56 ++
.../operations/delete_sign_rule_parameters.go | 95 +++
.../operations/delete_sign_rule_responses.go | 218 +++++++
.../operations/delete_sign_rule_urlbuilder.go | 104 +++
.../restapi/operations/massa_wallet_api.go | 36 ++
api/server/restapi/operations/request.go | 56 ++
.../restapi/operations/request_parameters.go | 108 ++++
.../restapi/operations/request_responses.go | 283 +++++++++
.../restapi/operations/request_urlbuilder.go | 96 +++
.../restapi/operations/update_sign_rule.go | 56 ++
.../operations/update_sign_rule_parameters.go | 132 ++++
.../operations/update_sign_rule_responses.go | 283 +++++++++
.../operations/update_sign_rule_urlbuilder.go | 104 +++
api/walletApi-V0.yml | 206 +++++-
cmd/massa-wallet/massa-wallet.go | 10 +-
go.mod | 2 +-
go.sum | 8 +-
internal/handler/api.go | 6 +-
internal/handler/wallet/add_assets_test.go | 8 +-
internal/handler/wallet/api.go | 22 +-
internal/handler/wallet/api_test.go | 33 +-
internal/handler/wallet/backup_test.go | 28 +-
internal/handler/wallet/cache.go | 155 +++++
internal/handler/wallet/create_test.go | 8 +-
internal/handler/wallet/delete_asset_test.go | 4 +-
internal/handler/wallet/delete_test.go | 18 +-
internal/handler/wallet/errors.go | 5 +
internal/handler/wallet/export_test.go | 5 +-
.../handler/wallet/get_all_assets_test.go | 6 +-
internal/handler/wallet/get_config.go | 6 +-
internal/handler/wallet/get_test.go | 16 +-
internal/handler/wallet/import_test.go | 14 +-
internal/handler/wallet/prompt_utils.go | 45 ++
internal/handler/wallet/rolls.go | 2 +-
internal/handler/wallet/rolls_test.go | 8 +-
internal/handler/wallet/sign.go | 259 ++------
internal/handler/wallet/sign_message.go | 25 +-
internal/handler/wallet/sign_rule_add.go | 110 ++++
internal/handler/wallet/sign_rule_delete.go | 85 +++
internal/handler/wallet/sign_rule_test.go | 107 ++++
internal/handler/wallet/sign_rule_update.go | 103 +++
internal/handler/wallet/sign_test.go | 358 ++++++++++-
internal/handler/wallet/transfer.go | 18 +-
internal/handler/wallet/transfer_test.go | 6 +-
internal/handler/wallet/update_test.go | 6 +-
pkg/app/events.go | 11 +-
pkg/assets/asset.go | 16 +-
pkg/assets/asset_test.go | 20 +-
pkg/cache/cache.go | 172 +++++
pkg/config/config.go | 126 ++--
pkg/config/config_test.go | 185 ++++++
pkg/config/rules.go | 261 ++++++++
pkg/config/types.go | 30 +
pkg/prompt/prompt.go | 13 +-
pkg/prompt/sign.go | 44 +-
pkg/types/address.go | 12 +
pkg/utils/errors.go | 8 +-
web-frontend/src/i18n/en_US.json | 2 +-
74 files changed, 5882 insertions(+), 463 deletions(-)
create mode 100644 .nvmrc
create mode 100644 api/server/models/add_sign_rule.go
create mode 100644 api/server/models/add_sign_rule_response.go
create mode 100644 api/server/models/delete_sign_rule_response.go
create mode 100644 api/server/models/request.go
create mode 100644 api/server/models/request_response.go
create mode 100644 api/server/models/request_type.go
create mode 100644 api/server/models/rule_type.go
create mode 100644 api/server/models/update_sign_rule.go
create mode 100644 api/server/models/update_sign_rule_response.go
create mode 100644 api/server/restapi/operations/add_sign_rule.go
create mode 100644 api/server/restapi/operations/add_sign_rule_parameters.go
create mode 100644 api/server/restapi/operations/add_sign_rule_responses.go
create mode 100644 api/server/restapi/operations/add_sign_rule_urlbuilder.go
create mode 100644 api/server/restapi/operations/delete_sign_rule.go
create mode 100644 api/server/restapi/operations/delete_sign_rule_parameters.go
create mode 100644 api/server/restapi/operations/delete_sign_rule_responses.go
create mode 100644 api/server/restapi/operations/delete_sign_rule_urlbuilder.go
create mode 100644 api/server/restapi/operations/request.go
create mode 100644 api/server/restapi/operations/request_parameters.go
create mode 100644 api/server/restapi/operations/request_responses.go
create mode 100644 api/server/restapi/operations/request_urlbuilder.go
create mode 100644 api/server/restapi/operations/update_sign_rule.go
create mode 100644 api/server/restapi/operations/update_sign_rule_parameters.go
create mode 100644 api/server/restapi/operations/update_sign_rule_responses.go
create mode 100644 api/server/restapi/operations/update_sign_rule_urlbuilder.go
create mode 100644 internal/handler/wallet/cache.go
create mode 100644 internal/handler/wallet/prompt_utils.go
create mode 100644 internal/handler/wallet/sign_rule_add.go
create mode 100644 internal/handler/wallet/sign_rule_delete.go
create mode 100644 internal/handler/wallet/sign_rule_test.go
create mode 100644 internal/handler/wallet/sign_rule_update.go
create mode 100644 pkg/cache/cache.go
create mode 100644 pkg/config/config_test.go
create mode 100644 pkg/config/rules.go
create mode 100644 pkg/config/types.go
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 000000000..209e3ef4b
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+20
diff --git a/api/server/models/add_sign_rule.go b/api/server/models/add_sign_rule.go
new file mode 100644
index 000000000..b199f22df
--- /dev/null
+++ b/api/server/models/add_sign_rule.go
@@ -0,0 +1,160 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// AddSignRule add sign rule
+//
+// swagger:model AddSignRule
+type AddSignRule struct {
+
+ // The contract to which the rule applies.
+ // Required: true
+ Contract *string `json:"contract"`
+
+ // Description text of what is being done (optional)
+ // Max Length: 280
+ Description string `json:"description,omitempty"`
+
+ // Whether the rule is enabled or not.
+ // Required: true
+ Enabled *bool `json:"enabled"`
+
+ // The name of the rule.
+ Name string `json:"name,omitempty"`
+
+ // rule type
+ // Required: true
+ RuleType RuleType `json:"ruleType"`
+}
+
+// Validate validates this add sign rule
+func (m *AddSignRule) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.validateContract(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateDescription(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateEnabled(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateRuleType(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *AddSignRule) validateContract(formats strfmt.Registry) error {
+
+ if err := validate.Required("contract", "body", m.Contract); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *AddSignRule) validateDescription(formats strfmt.Registry) error {
+ if swag.IsZero(m.Description) { // not required
+ return nil
+ }
+
+ if err := validate.MaxLength("description", "body", m.Description, 280); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *AddSignRule) validateEnabled(formats strfmt.Registry) error {
+
+ if err := validate.Required("enabled", "body", m.Enabled); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *AddSignRule) validateRuleType(formats strfmt.Registry) error {
+
+ if err := validate.Required("ruleType", "body", RuleType(m.RuleType)); err != nil {
+ return err
+ }
+
+ if err := m.RuleType.Validate(formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("ruleType")
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("ruleType")
+ }
+ return err
+ }
+
+ return nil
+}
+
+// ContextValidate validate this add sign rule based on the context it is used
+func (m *AddSignRule) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.contextValidateRuleType(ctx, formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *AddSignRule) contextValidateRuleType(ctx context.Context, formats strfmt.Registry) error {
+
+ if err := m.RuleType.ContextValidate(ctx, formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("ruleType")
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("ruleType")
+ }
+ return err
+ }
+
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *AddSignRule) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *AddSignRule) UnmarshalBinary(b []byte) error {
+ var res AddSignRule
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/models/add_sign_rule_response.go b/api/server/models/add_sign_rule_response.go
new file mode 100644
index 000000000..fc1d39a0e
--- /dev/null
+++ b/api/server/models/add_sign_rule_response.go
@@ -0,0 +1,71 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// AddSignRuleResponse add sign rule response
+//
+// swagger:model AddSignRuleResponse
+type AddSignRuleResponse struct {
+
+ // id
+ // Read Only: true
+ ID string `json:"id,omitempty"`
+}
+
+// Validate validates this add sign rule response
+func (m *AddSignRuleResponse) Validate(formats strfmt.Registry) error {
+ return nil
+}
+
+// ContextValidate validate this add sign rule response based on the context it is used
+func (m *AddSignRuleResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.contextValidateID(ctx, formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *AddSignRuleResponse) contextValidateID(ctx context.Context, formats strfmt.Registry) error {
+
+ if err := validate.ReadOnly(ctx, "id", "body", string(m.ID)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *AddSignRuleResponse) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *AddSignRuleResponse) UnmarshalBinary(b []byte) error {
+ var res AddSignRuleResponse
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/models/delete_sign_rule_response.go b/api/server/models/delete_sign_rule_response.go
new file mode 100644
index 000000000..3b594eae3
--- /dev/null
+++ b/api/server/models/delete_sign_rule_response.go
@@ -0,0 +1,50 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+)
+
+// DeleteSignRuleResponse delete sign rule response
+//
+// swagger:model DeleteSignRuleResponse
+type DeleteSignRuleResponse struct {
+
+ // Confirmation message for the deletion.
+ Message string `json:"message,omitempty"`
+}
+
+// Validate validates this delete sign rule response
+func (m *DeleteSignRuleResponse) Validate(formats strfmt.Registry) error {
+ return nil
+}
+
+// ContextValidate validates this delete sign rule response based on context it is used
+func (m *DeleteSignRuleResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *DeleteSignRuleResponse) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *DeleteSignRuleResponse) UnmarshalBinary(b []byte) error {
+ var res DeleteSignRuleResponse
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/models/request.go b/api/server/models/request.go
new file mode 100644
index 000000000..250e43ab9
--- /dev/null
+++ b/api/server/models/request.go
@@ -0,0 +1,126 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// Request request
+//
+// swagger:model Request
+type Request struct {
+
+ // Arbitrary JSON data associated with the request.
+ Data interface{} `json:"data,omitempty"`
+
+ // Description text of what is being done (optional)
+ // Max Length: 280
+ Description string `json:"description,omitempty"`
+
+ // request type
+ // Required: true
+ RequestType RequestType `json:"requestType"`
+}
+
+// Validate validates this request
+func (m *Request) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.validateDescription(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateRequestType(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *Request) validateDescription(formats strfmt.Registry) error {
+ if swag.IsZero(m.Description) { // not required
+ return nil
+ }
+
+ if err := validate.MaxLength("description", "body", m.Description, 280); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *Request) validateRequestType(formats strfmt.Registry) error {
+
+ if err := validate.Required("requestType", "body", RequestType(m.RequestType)); err != nil {
+ return err
+ }
+
+ if err := m.RequestType.Validate(formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("requestType")
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("requestType")
+ }
+ return err
+ }
+
+ return nil
+}
+
+// ContextValidate validate this request based on the context it is used
+func (m *Request) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.contextValidateRequestType(ctx, formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *Request) contextValidateRequestType(ctx context.Context, formats strfmt.Registry) error {
+
+ if err := m.RequestType.ContextValidate(ctx, formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("requestType")
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("requestType")
+ }
+ return err
+ }
+
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *Request) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *Request) UnmarshalBinary(b []byte) error {
+ var res Request
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/models/request_response.go b/api/server/models/request_response.go
new file mode 100644
index 000000000..2bccf88cb
--- /dev/null
+++ b/api/server/models/request_response.go
@@ -0,0 +1,127 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// RequestResponse request response
+//
+// swagger:model RequestResponse
+type RequestResponse struct {
+
+ // result
+ // Read Only: true
+ // Enum: ["approved","denied","error"]
+ Result string `json:"result,omitempty"`
+}
+
+// Validate validates this request response
+func (m *RequestResponse) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.validateResult(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+var requestResponseTypeResultPropEnum []interface{}
+
+func init() {
+ var res []string
+ if err := json.Unmarshal([]byte(`["approved","denied","error"]`), &res); err != nil {
+ panic(err)
+ }
+ for _, v := range res {
+ requestResponseTypeResultPropEnum = append(requestResponseTypeResultPropEnum, v)
+ }
+}
+
+const (
+
+ // RequestResponseResultApproved captures enum value "approved"
+ RequestResponseResultApproved string = "approved"
+
+ // RequestResponseResultDenied captures enum value "denied"
+ RequestResponseResultDenied string = "denied"
+
+ // RequestResponseResultError captures enum value "error"
+ RequestResponseResultError string = "error"
+)
+
+// prop value enum
+func (m *RequestResponse) validateResultEnum(path, location string, value string) error {
+ if err := validate.EnumCase(path, location, value, requestResponseTypeResultPropEnum, true); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (m *RequestResponse) validateResult(formats strfmt.Registry) error {
+ if swag.IsZero(m.Result) { // not required
+ return nil
+ }
+
+ // value enum
+ if err := m.validateResultEnum("result", "body", m.Result); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ContextValidate validate this request response based on the context it is used
+func (m *RequestResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.contextValidateResult(ctx, formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *RequestResponse) contextValidateResult(ctx context.Context, formats strfmt.Registry) error {
+
+ if err := validate.ReadOnly(ctx, "result", "body", string(m.Result)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *RequestResponse) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *RequestResponse) UnmarshalBinary(b []byte) error {
+ var res RequestResponse
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/models/request_type.go b/api/server/models/request_type.go
new file mode 100644
index 000000000..5cf7cf6dc
--- /dev/null
+++ b/api/server/models/request_type.go
@@ -0,0 +1,78 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/validate"
+)
+
+// RequestType An enumeration of allowed request types.
+//
+// swagger:model RequestType
+type RequestType string
+
+func NewRequestType(value RequestType) *RequestType {
+ return &value
+}
+
+// Pointer returns a pointer to a freshly-allocated RequestType.
+func (m RequestType) Pointer() *RequestType {
+ return &m
+}
+
+const (
+
+ // RequestTypeAddSignRule captures enum value "addSignRule"
+ RequestTypeAddSignRule RequestType = "addSignRule"
+
+ // RequestTypeRemoveSignRule captures enum value "removeSignRule"
+ RequestTypeRemoveSignRule RequestType = "removeSignRule"
+)
+
+// for schema
+var requestTypeEnum []interface{}
+
+func init() {
+ var res []RequestType
+ if err := json.Unmarshal([]byte(`["addSignRule","removeSignRule"]`), &res); err != nil {
+ panic(err)
+ }
+ for _, v := range res {
+ requestTypeEnum = append(requestTypeEnum, v)
+ }
+}
+
+func (m RequestType) validateRequestTypeEnum(path, location string, value RequestType) error {
+ if err := validate.EnumCase(path, location, value, requestTypeEnum, true); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Validate validates this request type
+func (m RequestType) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ // value enum
+ if err := m.validateRequestTypeEnum("", "body", m); err != nil {
+ return err
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+// ContextValidate validates this request type based on context it is used
+func (m RequestType) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ return nil
+}
diff --git a/api/server/models/rule_type.go b/api/server/models/rule_type.go
new file mode 100644
index 000000000..d237b1801
--- /dev/null
+++ b/api/server/models/rule_type.go
@@ -0,0 +1,78 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/validate"
+)
+
+// RuleType An enumeration of the different types of rules.
+//
+// swagger:model RuleType
+type RuleType string
+
+func NewRuleType(value RuleType) *RuleType {
+ return &value
+}
+
+// Pointer returns a pointer to a freshly-allocated RuleType.
+func (m RuleType) Pointer() *RuleType {
+ return &m
+}
+
+const (
+
+ // RuleTypeDisablePasswordPrompt captures enum value "disable_password_prompt"
+ RuleTypeDisablePasswordPrompt RuleType = "disable_password_prompt"
+
+ // RuleTypeAutoSign captures enum value "auto_sign"
+ RuleTypeAutoSign RuleType = "auto_sign"
+)
+
+// for schema
+var ruleTypeEnum []interface{}
+
+func init() {
+ var res []RuleType
+ if err := json.Unmarshal([]byte(`["disable_password_prompt","auto_sign"]`), &res); err != nil {
+ panic(err)
+ }
+ for _, v := range res {
+ ruleTypeEnum = append(ruleTypeEnum, v)
+ }
+}
+
+func (m RuleType) validateRuleTypeEnum(path, location string, value RuleType) error {
+ if err := validate.EnumCase(path, location, value, ruleTypeEnum, true); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Validate validates this rule type
+func (m RuleType) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ // value enum
+ if err := m.validateRuleTypeEnum("", "body", m); err != nil {
+ return err
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+// ContextValidate validates this rule type based on context it is used
+func (m RuleType) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ return nil
+}
diff --git a/api/server/models/sign_rule.go b/api/server/models/sign_rule.go
index 19cf03d75..40bc7d779 100644
--- a/api/server/models/sign_rule.go
+++ b/api/server/models/sign_rule.go
@@ -7,6 +7,7 @@ package models
import (
"context"
+ "encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -19,32 +20,23 @@ import (
// swagger:model SignRule
type SignRule struct {
- // auto sign
- // Required: true
- AutoSign *bool `json:"autoSign"`
-
// contract
- // Required: true
- Contract *string `json:"contract"`
+ Contract *string `json:"contract,omitempty"`
- // password prompt
+ // enabled
+ Enabled *bool `json:"enabled,omitempty"`
+
+ // rule type
// Required: true
- PasswordPrompt *bool `json:"passwordPrompt"`
+ // Enum: ["password_prompt","auto_sign"]
+ RuleType *string `json:"ruleType"`
}
// Validate validates this sign rule
func (m *SignRule) Validate(formats strfmt.Registry) error {
var res []error
- if err := m.validateAutoSign(formats); err != nil {
- res = append(res, err)
- }
-
- if err := m.validateContract(formats); err != nil {
- res = append(res, err)
- }
-
- if err := m.validatePasswordPrompt(formats); err != nil {
+ if err := m.validateRuleType(formats); err != nil {
res = append(res, err)
}
@@ -54,27 +46,43 @@ func (m *SignRule) Validate(formats strfmt.Registry) error {
return nil
}
-func (m *SignRule) validateAutoSign(formats strfmt.Registry) error {
+var signRuleTypeRuleTypePropEnum []interface{}
- if err := validate.Required("autoSign", "body", m.AutoSign); err != nil {
- return err
+func init() {
+ var res []string
+ if err := json.Unmarshal([]byte(`["password_prompt","auto_sign"]`), &res); err != nil {
+ panic(err)
+ }
+ for _, v := range res {
+ signRuleTypeRuleTypePropEnum = append(signRuleTypeRuleTypePropEnum, v)
}
-
- return nil
}
-func (m *SignRule) validateContract(formats strfmt.Registry) error {
+const (
+
+ // SignRuleRuleTypePasswordPrompt captures enum value "password_prompt"
+ SignRuleRuleTypePasswordPrompt string = "password_prompt"
+
+ // SignRuleRuleTypeAutoSign captures enum value "auto_sign"
+ SignRuleRuleTypeAutoSign string = "auto_sign"
+)
- if err := validate.Required("contract", "body", m.Contract); err != nil {
+// prop value enum
+func (m *SignRule) validateRuleTypeEnum(path, location string, value string) error {
+ if err := validate.EnumCase(path, location, value, signRuleTypeRuleTypePropEnum, true); err != nil {
return err
}
-
return nil
}
-func (m *SignRule) validatePasswordPrompt(formats strfmt.Registry) error {
+func (m *SignRule) validateRuleType(formats strfmt.Registry) error {
+
+ if err := validate.Required("ruleType", "body", m.RuleType); err != nil {
+ return err
+ }
- if err := validate.Required("passwordPrompt", "body", m.PasswordPrompt); err != nil {
+ // value enum
+ if err := m.validateRuleTypeEnum("ruleType", "body", *m.RuleType); err != nil {
return err
}
diff --git a/api/server/models/update_sign_rule.go b/api/server/models/update_sign_rule.go
new file mode 100644
index 000000000..4a7f8353e
--- /dev/null
+++ b/api/server/models/update_sign_rule.go
@@ -0,0 +1,160 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// UpdateSignRule update sign rule
+//
+// swagger:model UpdateSignRule
+type UpdateSignRule struct {
+
+ // The contract to which the rule applies. Use wildcard (*) to apply the rule for contracts.
+ // Required: true
+ Contract *string `json:"contract"`
+
+ // Description text of what is being updated (optional)
+ // Max Length: 280
+ Description string `json:"description,omitempty"`
+
+ // Whether the rule is enabled or not.
+ // Required: true
+ Enabled *bool `json:"enabled"`
+
+ // The name of the rule.
+ Name string `json:"name,omitempty"`
+
+ // rule type
+ // Required: true
+ RuleType RuleType `json:"ruleType"`
+}
+
+// Validate validates this update sign rule
+func (m *UpdateSignRule) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.validateContract(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateDescription(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateEnabled(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateRuleType(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *UpdateSignRule) validateContract(formats strfmt.Registry) error {
+
+ if err := validate.Required("contract", "body", m.Contract); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *UpdateSignRule) validateDescription(formats strfmt.Registry) error {
+ if swag.IsZero(m.Description) { // not required
+ return nil
+ }
+
+ if err := validate.MaxLength("description", "body", m.Description, 280); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *UpdateSignRule) validateEnabled(formats strfmt.Registry) error {
+
+ if err := validate.Required("enabled", "body", m.Enabled); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *UpdateSignRule) validateRuleType(formats strfmt.Registry) error {
+
+ if err := validate.Required("ruleType", "body", RuleType(m.RuleType)); err != nil {
+ return err
+ }
+
+ if err := m.RuleType.Validate(formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("ruleType")
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("ruleType")
+ }
+ return err
+ }
+
+ return nil
+}
+
+// ContextValidate validate this update sign rule based on the context it is used
+func (m *UpdateSignRule) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.contextValidateRuleType(ctx, formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *UpdateSignRule) contextValidateRuleType(ctx context.Context, formats strfmt.Registry) error {
+
+ if err := m.RuleType.ContextValidate(ctx, formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("ruleType")
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("ruleType")
+ }
+ return err
+ }
+
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *UpdateSignRule) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *UpdateSignRule) UnmarshalBinary(b []byte) error {
+ var res UpdateSignRule
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/models/update_sign_rule_response.go b/api/server/models/update_sign_rule_response.go
new file mode 100644
index 000000000..de9effc58
--- /dev/null
+++ b/api/server/models/update_sign_rule_response.go
@@ -0,0 +1,71 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// UpdateSignRuleResponse update sign rule response
+//
+// swagger:model UpdateSignRuleResponse
+type UpdateSignRuleResponse struct {
+
+ // id
+ // Read Only: true
+ ID string `json:"id,omitempty"`
+}
+
+// Validate validates this update sign rule response
+func (m *UpdateSignRuleResponse) Validate(formats strfmt.Registry) error {
+ return nil
+}
+
+// ContextValidate validate this update sign rule response based on the context it is used
+func (m *UpdateSignRuleResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.contextValidateID(ctx, formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *UpdateSignRuleResponse) contextValidateID(ctx context.Context, formats strfmt.Registry) error {
+
+ if err := validate.ReadOnly(ctx, "id", "body", string(m.ID)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *UpdateSignRuleResponse) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *UpdateSignRuleResponse) UnmarshalBinary(b []byte) error {
+ var res UpdateSignRuleResponse
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/api/server/restapi/embedded_spec.go b/api/server/restapi/embedded_spec.go
index 76769667e..7620731dc 100644
--- a/api/server/restapi/embedded_spec.go
+++ b/api/server/restapi/embedded_spec.go
@@ -175,7 +175,6 @@ func init() {
"$ref": "#/parameters/nickname"
},
{
- "x-nullable": false,
"name": "body",
"in": "body",
"required": true,
@@ -718,6 +717,173 @@ func init() {
}
}
},
+ "/api/accounts/{nickname}/signrules": {
+ "post": {
+ "description": "Create a new sign rule for the account associated with the provided nickname.",
+ "produces": [
+ "application/json"
+ ],
+ "operationId": "AddSignRule",
+ "parameters": [
+ {
+ "$ref": "#/parameters/nickname"
+ },
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/AddSignRule"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "signRule Id.",
+ "schema": {
+ "$ref": "#/definitions/AddSignRuleResponse"
+ }
+ },
+ "400": {
+ "description": "Bad request.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "401": {
+ "description": "Unauthorized - The request requires user authentication.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "404": {
+ "description": "Account Not found.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "422": {
+ "description": "Unprocessable Entity - syntax is correct, but the server was unable to process the contained instructions.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "500": {
+ "description": "Internal Server Error - The server has encountered a situation it does not know how to handle.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
+ "/api/accounts/{nickname}/signrules/{ruleId}": {
+ "put": {
+ "description": "Update an existing sign rule for the account associated with the provided nickname by its ID.",
+ "produces": [
+ "application/json"
+ ],
+ "operationId": "UpdateSignRule",
+ "parameters": [
+ {
+ "$ref": "#/parameters/nickname"
+ },
+ {
+ "$ref": "#/parameters/ruleId"
+ },
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/UpdateSignRule"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Sign rule updated successfully.",
+ "schema": {
+ "$ref": "#/definitions/UpdateSignRuleResponse"
+ }
+ },
+ "400": {
+ "description": "Bad request.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "401": {
+ "description": "Unauthorized - The request requires user authentication.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "404": {
+ "description": "Account or Rule Not found.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "422": {
+ "description": "Unprocessable Entity - syntax is correct, but the server was unable to process the contained instructions.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "500": {
+ "description": "Internal Server Error - The server has encountered a situation it does not know how to handle.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ },
+ "delete": {
+ "description": "Delete a sign rule for the account associated with the provided nickname by its ID.",
+ "produces": [
+ "application/json"
+ ],
+ "operationId": "DeleteSignRule",
+ "parameters": [
+ {
+ "$ref": "#/parameters/nickname"
+ },
+ {
+ "$ref": "#/parameters/ruleId"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Sign rule deleted successfully."
+ },
+ "400": {
+ "description": "Bad request.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "401": {
+ "description": "Unauthorized - The request requires user authentication.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "404": {
+ "description": "Account or Rule Not found.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "500": {
+ "description": "Internal Server Error - The server has encountered a situation it does not know how to handle.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
"/api/accounts/{nickname}/transfer": {
"post": {
"description": "Transfer coins from the account associated with the provided nickname in the path. Will ask the user to enter its account password.",
@@ -889,6 +1055,46 @@ func init() {
}
}
},
+ "AddSignRule": {
+ "type": "object",
+ "required": [
+ "ruleType",
+ "contract",
+ "enabled"
+ ],
+ "properties": {
+ "contract": {
+ "description": "The contract to which the rule applies.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description text of what is being done (optional)",
+ "type": "string",
+ "maxLength": 280
+ },
+ "enabled": {
+ "description": "Whether the rule is enabled or not.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "The name of the rule.",
+ "type": "string"
+ },
+ "ruleType": {
+ "$ref": "#/definitions/RuleType"
+ }
+ }
+ },
+ "AddSignRuleResponse": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "x-nullable": false,
+ "readOnly": true
+ }
+ }
+ },
"Address": {
"description": "Account's address.",
"type": "string",
@@ -1049,6 +1255,15 @@ func init() {
}
}
},
+ "RuleType": {
+ "description": "An enumeration of the different types of rules.",
+ "type": "string",
+ "enum": [
+ "disable_password_prompt",
+ "auto_sign"
+ ],
+ "x-nullable": false
+ },
"SignMessageRequest": {
"type": "object",
"properties": {
@@ -1121,19 +1336,23 @@ func init() {
"description": "Account Sign rule.",
"type": "object",
"required": [
- "contract",
- "passwordPrompt",
- "autoSign"
+ "ruleType"
],
"properties": {
- "autoSign": {
- "type": "boolean"
- },
"contract": {
- "type": "string"
+ "type": "string",
+ "x-nullable": true
},
- "passwordPrompt": {
- "type": "boolean"
+ "enabled": {
+ "type": "boolean",
+ "default": true
+ },
+ "ruleType": {
+ "type": "string",
+ "enum": [
+ "password_prompt",
+ "auto_sign"
+ ]
}
}
},
@@ -1179,6 +1398,46 @@ func init() {
"$ref": "#/definitions/Nickname"
}
}
+ },
+ "UpdateSignRule": {
+ "type": "object",
+ "required": [
+ "ruleType",
+ "contract",
+ "enabled"
+ ],
+ "properties": {
+ "contract": {
+ "description": "The contract to which the rule applies. Use wildcard (*) to apply the rule for contracts.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description text of what is being updated (optional)",
+ "type": "string",
+ "maxLength": 280
+ },
+ "enabled": {
+ "description": "Whether the rule is enabled or not.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "The name of the rule.",
+ "type": "string"
+ },
+ "ruleType": {
+ "$ref": "#/definitions/RuleType"
+ }
+ }
+ },
+ "UpdateSignRuleResponse": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "x-nullable": false,
+ "readOnly": true
+ }
+ }
}
},
"parameters": {
@@ -1197,6 +1456,14 @@ func init() {
"name": "nickname",
"in": "path",
"required": true
+ },
+ "ruleId": {
+ "type": "string",
+ "x-nullable": false,
+ "description": "The ID of the sign rule",
+ "name": "ruleId",
+ "in": "path",
+ "required": true
}
}
}`))
@@ -1373,7 +1640,6 @@ func init() {
"required": true
},
{
- "x-nullable": false,
"name": "body",
"in": "body",
"required": true,
@@ -1951,6 +2217,198 @@ func init() {
}
}
},
+ "/api/accounts/{nickname}/signrules": {
+ "post": {
+ "description": "Create a new sign rule for the account associated with the provided nickname.",
+ "produces": [
+ "application/json"
+ ],
+ "operationId": "AddSignRule",
+ "parameters": [
+ {
+ "type": "string",
+ "x-nullable": false,
+ "description": "Account's short name.",
+ "name": "nickname",
+ "in": "path",
+ "required": true
+ },
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/AddSignRule"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "signRule Id.",
+ "schema": {
+ "$ref": "#/definitions/AddSignRuleResponse"
+ }
+ },
+ "400": {
+ "description": "Bad request.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "401": {
+ "description": "Unauthorized - The request requires user authentication.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "404": {
+ "description": "Account Not found.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "422": {
+ "description": "Unprocessable Entity - syntax is correct, but the server was unable to process the contained instructions.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "500": {
+ "description": "Internal Server Error - The server has encountered a situation it does not know how to handle.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
+ "/api/accounts/{nickname}/signrules/{ruleId}": {
+ "put": {
+ "description": "Update an existing sign rule for the account associated with the provided nickname by its ID.",
+ "produces": [
+ "application/json"
+ ],
+ "operationId": "UpdateSignRule",
+ "parameters": [
+ {
+ "type": "string",
+ "x-nullable": false,
+ "description": "Account's short name.",
+ "name": "nickname",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "x-nullable": false,
+ "description": "The ID of the sign rule",
+ "name": "ruleId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/UpdateSignRule"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Sign rule updated successfully.",
+ "schema": {
+ "$ref": "#/definitions/UpdateSignRuleResponse"
+ }
+ },
+ "400": {
+ "description": "Bad request.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "401": {
+ "description": "Unauthorized - The request requires user authentication.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "404": {
+ "description": "Account or Rule Not found.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "422": {
+ "description": "Unprocessable Entity - syntax is correct, but the server was unable to process the contained instructions.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "500": {
+ "description": "Internal Server Error - The server has encountered a situation it does not know how to handle.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ },
+ "delete": {
+ "description": "Delete a sign rule for the account associated with the provided nickname by its ID.",
+ "produces": [
+ "application/json"
+ ],
+ "operationId": "DeleteSignRule",
+ "parameters": [
+ {
+ "type": "string",
+ "x-nullable": false,
+ "description": "Account's short name.",
+ "name": "nickname",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "x-nullable": false,
+ "description": "The ID of the sign rule",
+ "name": "ruleId",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Sign rule deleted successfully."
+ },
+ "400": {
+ "description": "Bad request.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "401": {
+ "description": "Unauthorized - The request requires user authentication.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "404": {
+ "description": "Account or Rule Not found.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ },
+ "500": {
+ "description": "Internal Server Error - The server has encountered a situation it does not know how to handle.",
+ "schema": {
+ "$ref": "#/definitions/Error"
+ }
+ }
+ }
+ }
+ },
"/api/accounts/{nickname}/transfer": {
"post": {
"description": "Transfer coins from the account associated with the provided nickname in the path. Will ask the user to enter its account password.",
@@ -2127,6 +2585,46 @@ func init() {
}
}
},
+ "AddSignRule": {
+ "type": "object",
+ "required": [
+ "ruleType",
+ "contract",
+ "enabled"
+ ],
+ "properties": {
+ "contract": {
+ "description": "The contract to which the rule applies.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description text of what is being done (optional)",
+ "type": "string",
+ "maxLength": 280
+ },
+ "enabled": {
+ "description": "Whether the rule is enabled or not.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "The name of the rule.",
+ "type": "string"
+ },
+ "ruleType": {
+ "$ref": "#/definitions/RuleType"
+ }
+ }
+ },
+ "AddSignRuleResponse": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "x-nullable": false,
+ "readOnly": true
+ }
+ }
+ },
"Address": {
"description": "Account's address.",
"type": "string",
@@ -2289,6 +2787,15 @@ func init() {
}
}
},
+ "RuleType": {
+ "description": "An enumeration of the different types of rules.",
+ "type": "string",
+ "enum": [
+ "disable_password_prompt",
+ "auto_sign"
+ ],
+ "x-nullable": false
+ },
"SignMessageRequest": {
"type": "object",
"properties": {
@@ -2361,19 +2868,23 @@ func init() {
"description": "Account Sign rule.",
"type": "object",
"required": [
- "contract",
- "passwordPrompt",
- "autoSign"
+ "ruleType"
],
"properties": {
- "autoSign": {
- "type": "boolean"
- },
"contract": {
- "type": "string"
+ "type": "string",
+ "x-nullable": true
},
- "passwordPrompt": {
- "type": "boolean"
+ "enabled": {
+ "type": "boolean",
+ "default": true
+ },
+ "ruleType": {
+ "type": "string",
+ "enum": [
+ "password_prompt",
+ "auto_sign"
+ ]
}
}
},
@@ -2419,6 +2930,46 @@ func init() {
"$ref": "#/definitions/Nickname"
}
}
+ },
+ "UpdateSignRule": {
+ "type": "object",
+ "required": [
+ "ruleType",
+ "contract",
+ "enabled"
+ ],
+ "properties": {
+ "contract": {
+ "description": "The contract to which the rule applies. Use wildcard (*) to apply the rule for contracts.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Description text of what is being updated (optional)",
+ "type": "string",
+ "maxLength": 280
+ },
+ "enabled": {
+ "description": "Whether the rule is enabled or not.",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "The name of the rule.",
+ "type": "string"
+ },
+ "ruleType": {
+ "$ref": "#/definitions/RuleType"
+ }
+ }
+ },
+ "UpdateSignRuleResponse": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "x-nullable": false,
+ "readOnly": true
+ }
+ }
}
},
"parameters": {
@@ -2437,6 +2988,14 @@ func init() {
"name": "nickname",
"in": "path",
"required": true
+ },
+ "ruleId": {
+ "type": "string",
+ "x-nullable": false,
+ "description": "The ID of the sign rule",
+ "name": "ruleId",
+ "in": "path",
+ "required": true
}
}
}`))
diff --git a/api/server/restapi/operations/add_sign_rule.go b/api/server/restapi/operations/add_sign_rule.go
new file mode 100644
index 000000000..f19e22e19
--- /dev/null
+++ b/api/server/restapi/operations/add_sign_rule.go
@@ -0,0 +1,56 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime/middleware"
+)
+
+// AddSignRuleHandlerFunc turns a function with the right signature into a add sign rule handler
+type AddSignRuleHandlerFunc func(AddSignRuleParams) middleware.Responder
+
+// Handle executing the request and returning a response
+func (fn AddSignRuleHandlerFunc) Handle(params AddSignRuleParams) middleware.Responder {
+ return fn(params)
+}
+
+// AddSignRuleHandler interface for that can handle valid add sign rule params
+type AddSignRuleHandler interface {
+ Handle(AddSignRuleParams) middleware.Responder
+}
+
+// NewAddSignRule creates a new http.Handler for the add sign rule operation
+func NewAddSignRule(ctx *middleware.Context, handler AddSignRuleHandler) *AddSignRule {
+ return &AddSignRule{Context: ctx, Handler: handler}
+}
+
+/*
+ AddSignRule swagger:route POST /api/accounts/{nickname}/signrules addSignRule
+
+Create a new sign rule for the account associated with the provided nickname.
+*/
+type AddSignRule struct {
+ Context *middleware.Context
+ Handler AddSignRuleHandler
+}
+
+func (o *AddSignRule) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ route, rCtx, _ := o.Context.RouteInfo(r)
+ if rCtx != nil {
+ *r = *rCtx
+ }
+ var Params = NewAddSignRuleParams()
+ if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
+ o.Context.Respond(rw, r, route.Produces, route, err)
+ return
+ }
+
+ res := o.Handler.Handle(Params) // actually handle the request
+ o.Context.Respond(rw, r, route.Produces, route, res)
+
+}
diff --git a/api/server/restapi/operations/add_sign_rule_parameters.go b/api/server/restapi/operations/add_sign_rule_parameters.go
new file mode 100644
index 000000000..d13af654d
--- /dev/null
+++ b/api/server/restapi/operations/add_sign_rule_parameters.go
@@ -0,0 +1,108 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "io"
+ "net/http"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/runtime"
+ "github.com/go-openapi/runtime/middleware"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/validate"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+)
+
+// NewAddSignRuleParams creates a new AddSignRuleParams object
+//
+// There are no default values defined in the spec.
+func NewAddSignRuleParams() AddSignRuleParams {
+
+ return AddSignRuleParams{}
+}
+
+// AddSignRuleParams contains all the bound params for the add sign rule operation
+// typically these are obtained from a http.Request
+//
+// swagger:parameters AddSignRule
+type AddSignRuleParams struct {
+
+ // HTTP Request Object
+ HTTPRequest *http.Request `json:"-"`
+
+ /*
+ Required: true
+ In: body
+ */
+ Body *models.AddSignRule
+ /*Account's short name.
+ Required: true
+ In: path
+ */
+ Nickname string
+}
+
+// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
+// for simple values it will use straight method calls.
+//
+// To ensure default values, the struct must have been initialized with NewAddSignRuleParams() beforehand.
+func (o *AddSignRuleParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
+ var res []error
+
+ o.HTTPRequest = r
+
+ if runtime.HasBody(r) {
+ defer r.Body.Close()
+ var body models.AddSignRule
+ if err := route.Consumer.Consume(r.Body, &body); err != nil {
+ if err == io.EOF {
+ res = append(res, errors.Required("body", "body", ""))
+ } else {
+ res = append(res, errors.NewParseError("body", "body", "", err))
+ }
+ } else {
+ // validate body object
+ if err := body.Validate(route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ ctx := validate.WithOperationRequest(r.Context())
+ if err := body.ContextValidate(ctx, route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) == 0 {
+ o.Body = &body
+ }
+ }
+ } else {
+ res = append(res, errors.Required("body", "body", ""))
+ }
+
+ rNickname, rhkNickname, _ := route.Params.GetOK("nickname")
+ if err := o.bindNickname(rNickname, rhkNickname, route.Formats); err != nil {
+ res = append(res, err)
+ }
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+// bindNickname binds and validates parameter Nickname from path.
+func (o *AddSignRuleParams) bindNickname(rawData []string, hasKey bool, formats strfmt.Registry) error {
+ var raw string
+ if len(rawData) > 0 {
+ raw = rawData[len(rawData)-1]
+ }
+
+ // Required: true
+ // Parameter is provided by construction from the route
+ o.Nickname = raw
+
+ return nil
+}
diff --git a/api/server/restapi/operations/add_sign_rule_responses.go b/api/server/restapi/operations/add_sign_rule_responses.go
new file mode 100644
index 000000000..92bf4a8a8
--- /dev/null
+++ b/api/server/restapi/operations/add_sign_rule_responses.go
@@ -0,0 +1,283 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+)
+
+// AddSignRuleOKCode is the HTTP code returned for type AddSignRuleOK
+const AddSignRuleOKCode int = 200
+
+/*
+AddSignRuleOK signRule Id.
+
+swagger:response addSignRuleOK
+*/
+type AddSignRuleOK struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.AddSignRuleResponse `json:"body,omitempty"`
+}
+
+// NewAddSignRuleOK creates AddSignRuleOK with default headers values
+func NewAddSignRuleOK() *AddSignRuleOK {
+
+ return &AddSignRuleOK{}
+}
+
+// WithPayload adds the payload to the add sign rule o k response
+func (o *AddSignRuleOK) WithPayload(payload *models.AddSignRuleResponse) *AddSignRuleOK {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the add sign rule o k response
+func (o *AddSignRuleOK) SetPayload(payload *models.AddSignRuleResponse) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *AddSignRuleOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(200)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// AddSignRuleBadRequestCode is the HTTP code returned for type AddSignRuleBadRequest
+const AddSignRuleBadRequestCode int = 400
+
+/*
+AddSignRuleBadRequest Bad request.
+
+swagger:response addSignRuleBadRequest
+*/
+type AddSignRuleBadRequest struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewAddSignRuleBadRequest creates AddSignRuleBadRequest with default headers values
+func NewAddSignRuleBadRequest() *AddSignRuleBadRequest {
+
+ return &AddSignRuleBadRequest{}
+}
+
+// WithPayload adds the payload to the add sign rule bad request response
+func (o *AddSignRuleBadRequest) WithPayload(payload *models.Error) *AddSignRuleBadRequest {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the add sign rule bad request response
+func (o *AddSignRuleBadRequest) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *AddSignRuleBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(400)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// AddSignRuleUnauthorizedCode is the HTTP code returned for type AddSignRuleUnauthorized
+const AddSignRuleUnauthorizedCode int = 401
+
+/*
+AddSignRuleUnauthorized Unauthorized - The request requires user authentication.
+
+swagger:response addSignRuleUnauthorized
+*/
+type AddSignRuleUnauthorized struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewAddSignRuleUnauthorized creates AddSignRuleUnauthorized with default headers values
+func NewAddSignRuleUnauthorized() *AddSignRuleUnauthorized {
+
+ return &AddSignRuleUnauthorized{}
+}
+
+// WithPayload adds the payload to the add sign rule unauthorized response
+func (o *AddSignRuleUnauthorized) WithPayload(payload *models.Error) *AddSignRuleUnauthorized {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the add sign rule unauthorized response
+func (o *AddSignRuleUnauthorized) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *AddSignRuleUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(401)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// AddSignRuleNotFoundCode is the HTTP code returned for type AddSignRuleNotFound
+const AddSignRuleNotFoundCode int = 404
+
+/*
+AddSignRuleNotFound Account Not found.
+
+swagger:response addSignRuleNotFound
+*/
+type AddSignRuleNotFound struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewAddSignRuleNotFound creates AddSignRuleNotFound with default headers values
+func NewAddSignRuleNotFound() *AddSignRuleNotFound {
+
+ return &AddSignRuleNotFound{}
+}
+
+// WithPayload adds the payload to the add sign rule not found response
+func (o *AddSignRuleNotFound) WithPayload(payload *models.Error) *AddSignRuleNotFound {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the add sign rule not found response
+func (o *AddSignRuleNotFound) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *AddSignRuleNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(404)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// AddSignRuleUnprocessableEntityCode is the HTTP code returned for type AddSignRuleUnprocessableEntity
+const AddSignRuleUnprocessableEntityCode int = 422
+
+/*
+AddSignRuleUnprocessableEntity Unprocessable Entity - syntax is correct, but the server was unable to process the contained instructions.
+
+swagger:response addSignRuleUnprocessableEntity
+*/
+type AddSignRuleUnprocessableEntity struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewAddSignRuleUnprocessableEntity creates AddSignRuleUnprocessableEntity with default headers values
+func NewAddSignRuleUnprocessableEntity() *AddSignRuleUnprocessableEntity {
+
+ return &AddSignRuleUnprocessableEntity{}
+}
+
+// WithPayload adds the payload to the add sign rule unprocessable entity response
+func (o *AddSignRuleUnprocessableEntity) WithPayload(payload *models.Error) *AddSignRuleUnprocessableEntity {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the add sign rule unprocessable entity response
+func (o *AddSignRuleUnprocessableEntity) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *AddSignRuleUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(422)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// AddSignRuleInternalServerErrorCode is the HTTP code returned for type AddSignRuleInternalServerError
+const AddSignRuleInternalServerErrorCode int = 500
+
+/*
+AddSignRuleInternalServerError Internal Server Error - The server has encountered a situation it does not know how to handle.
+
+swagger:response addSignRuleInternalServerError
+*/
+type AddSignRuleInternalServerError struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewAddSignRuleInternalServerError creates AddSignRuleInternalServerError with default headers values
+func NewAddSignRuleInternalServerError() *AddSignRuleInternalServerError {
+
+ return &AddSignRuleInternalServerError{}
+}
+
+// WithPayload adds the payload to the add sign rule internal server error response
+func (o *AddSignRuleInternalServerError) WithPayload(payload *models.Error) *AddSignRuleInternalServerError {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the add sign rule internal server error response
+func (o *AddSignRuleInternalServerError) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *AddSignRuleInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(500)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
diff --git a/api/server/restapi/operations/add_sign_rule_urlbuilder.go b/api/server/restapi/operations/add_sign_rule_urlbuilder.go
new file mode 100644
index 000000000..a0244b8c4
--- /dev/null
+++ b/api/server/restapi/operations/add_sign_rule_urlbuilder.go
@@ -0,0 +1,96 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "errors"
+ "net/url"
+ golangswaggerpaths "path"
+ "strings"
+)
+
+// AddSignRuleURL generates an URL for the add sign rule operation
+type AddSignRuleURL struct {
+ Nickname string
+
+ _basePath string
+ // avoid unkeyed usage
+ _ struct{}
+}
+
+// WithBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *AddSignRuleURL) WithBasePath(bp string) *AddSignRuleURL {
+ o.SetBasePath(bp)
+ return o
+}
+
+// SetBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *AddSignRuleURL) SetBasePath(bp string) {
+ o._basePath = bp
+}
+
+// Build a url path and query string
+func (o *AddSignRuleURL) Build() (*url.URL, error) {
+ var _result url.URL
+
+ var _path = "/api/accounts/{nickname}/signrules"
+
+ nickname := o.Nickname
+ if nickname != "" {
+ _path = strings.Replace(_path, "{nickname}", nickname, -1)
+ } else {
+ return nil, errors.New("nickname is required on AddSignRuleURL")
+ }
+
+ _basePath := o._basePath
+ _result.Path = golangswaggerpaths.Join(_basePath, _path)
+
+ return &_result, nil
+}
+
+// Must is a helper function to panic when the url builder returns an error
+func (o *AddSignRuleURL) Must(u *url.URL, err error) *url.URL {
+ if err != nil {
+ panic(err)
+ }
+ if u == nil {
+ panic("url can't be nil")
+ }
+ return u
+}
+
+// String returns the string representation of the path with query string
+func (o *AddSignRuleURL) String() string {
+ return o.Must(o.Build()).String()
+}
+
+// BuildFull builds a full url with scheme, host, path and query string
+func (o *AddSignRuleURL) BuildFull(scheme, host string) (*url.URL, error) {
+ if scheme == "" {
+ return nil, errors.New("scheme is required for a full url on AddSignRuleURL")
+ }
+ if host == "" {
+ return nil, errors.New("host is required for a full url on AddSignRuleURL")
+ }
+
+ base, err := o.Build()
+ if err != nil {
+ return nil, err
+ }
+
+ base.Scheme = scheme
+ base.Host = host
+ return base, nil
+}
+
+// StringFull returns the string representation of a complete url
+func (o *AddSignRuleURL) StringFull(scheme, host string) string {
+ return o.Must(o.BuildFull(scheme, host)).String()
+}
diff --git a/api/server/restapi/operations/delete_sign_rule.go b/api/server/restapi/operations/delete_sign_rule.go
new file mode 100644
index 000000000..cb8fbb793
--- /dev/null
+++ b/api/server/restapi/operations/delete_sign_rule.go
@@ -0,0 +1,56 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime/middleware"
+)
+
+// DeleteSignRuleHandlerFunc turns a function with the right signature into a delete sign rule handler
+type DeleteSignRuleHandlerFunc func(DeleteSignRuleParams) middleware.Responder
+
+// Handle executing the request and returning a response
+func (fn DeleteSignRuleHandlerFunc) Handle(params DeleteSignRuleParams) middleware.Responder {
+ return fn(params)
+}
+
+// DeleteSignRuleHandler interface for that can handle valid delete sign rule params
+type DeleteSignRuleHandler interface {
+ Handle(DeleteSignRuleParams) middleware.Responder
+}
+
+// NewDeleteSignRule creates a new http.Handler for the delete sign rule operation
+func NewDeleteSignRule(ctx *middleware.Context, handler DeleteSignRuleHandler) *DeleteSignRule {
+ return &DeleteSignRule{Context: ctx, Handler: handler}
+}
+
+/*
+ DeleteSignRule swagger:route DELETE /api/accounts/{nickname}/signrules/{ruleId} deleteSignRule
+
+Delete a sign rule for the account associated with the provided nickname by its ID.
+*/
+type DeleteSignRule struct {
+ Context *middleware.Context
+ Handler DeleteSignRuleHandler
+}
+
+func (o *DeleteSignRule) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ route, rCtx, _ := o.Context.RouteInfo(r)
+ if rCtx != nil {
+ *r = *rCtx
+ }
+ var Params = NewDeleteSignRuleParams()
+ if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
+ o.Context.Respond(rw, r, route.Produces, route, err)
+ return
+ }
+
+ res := o.Handler.Handle(Params) // actually handle the request
+ o.Context.Respond(rw, r, route.Produces, route, res)
+
+}
diff --git a/api/server/restapi/operations/delete_sign_rule_parameters.go b/api/server/restapi/operations/delete_sign_rule_parameters.go
new file mode 100644
index 000000000..8dda7e43f
--- /dev/null
+++ b/api/server/restapi/operations/delete_sign_rule_parameters.go
@@ -0,0 +1,95 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/runtime/middleware"
+ "github.com/go-openapi/strfmt"
+)
+
+// NewDeleteSignRuleParams creates a new DeleteSignRuleParams object
+//
+// There are no default values defined in the spec.
+func NewDeleteSignRuleParams() DeleteSignRuleParams {
+
+ return DeleteSignRuleParams{}
+}
+
+// DeleteSignRuleParams contains all the bound params for the delete sign rule operation
+// typically these are obtained from a http.Request
+//
+// swagger:parameters DeleteSignRule
+type DeleteSignRuleParams struct {
+
+ // HTTP Request Object
+ HTTPRequest *http.Request `json:"-"`
+
+ /*Account's short name.
+ Required: true
+ In: path
+ */
+ Nickname string
+ /*The ID of the sign rule
+ Required: true
+ In: path
+ */
+ RuleID string
+}
+
+// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
+// for simple values it will use straight method calls.
+//
+// To ensure default values, the struct must have been initialized with NewDeleteSignRuleParams() beforehand.
+func (o *DeleteSignRuleParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
+ var res []error
+
+ o.HTTPRequest = r
+
+ rNickname, rhkNickname, _ := route.Params.GetOK("nickname")
+ if err := o.bindNickname(rNickname, rhkNickname, route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ rRuleID, rhkRuleID, _ := route.Params.GetOK("ruleId")
+ if err := o.bindRuleID(rRuleID, rhkRuleID, route.Formats); err != nil {
+ res = append(res, err)
+ }
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+// bindNickname binds and validates parameter Nickname from path.
+func (o *DeleteSignRuleParams) bindNickname(rawData []string, hasKey bool, formats strfmt.Registry) error {
+ var raw string
+ if len(rawData) > 0 {
+ raw = rawData[len(rawData)-1]
+ }
+
+ // Required: true
+ // Parameter is provided by construction from the route
+ o.Nickname = raw
+
+ return nil
+}
+
+// bindRuleID binds and validates parameter RuleID from path.
+func (o *DeleteSignRuleParams) bindRuleID(rawData []string, hasKey bool, formats strfmt.Registry) error {
+ var raw string
+ if len(rawData) > 0 {
+ raw = rawData[len(rawData)-1]
+ }
+
+ // Required: true
+ // Parameter is provided by construction from the route
+ o.RuleID = raw
+
+ return nil
+}
diff --git a/api/server/restapi/operations/delete_sign_rule_responses.go b/api/server/restapi/operations/delete_sign_rule_responses.go
new file mode 100644
index 000000000..3535ba75f
--- /dev/null
+++ b/api/server/restapi/operations/delete_sign_rule_responses.go
@@ -0,0 +1,218 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+)
+
+// DeleteSignRuleOKCode is the HTTP code returned for type DeleteSignRuleOK
+const DeleteSignRuleOKCode int = 200
+
+/*
+DeleteSignRuleOK Sign rule deleted successfully.
+
+swagger:response deleteSignRuleOK
+*/
+type DeleteSignRuleOK struct {
+}
+
+// NewDeleteSignRuleOK creates DeleteSignRuleOK with default headers values
+func NewDeleteSignRuleOK() *DeleteSignRuleOK {
+
+ return &DeleteSignRuleOK{}
+}
+
+// WriteResponse to the client
+func (o *DeleteSignRuleOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
+
+ rw.WriteHeader(200)
+}
+
+// DeleteSignRuleBadRequestCode is the HTTP code returned for type DeleteSignRuleBadRequest
+const DeleteSignRuleBadRequestCode int = 400
+
+/*
+DeleteSignRuleBadRequest Bad request.
+
+swagger:response deleteSignRuleBadRequest
+*/
+type DeleteSignRuleBadRequest struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewDeleteSignRuleBadRequest creates DeleteSignRuleBadRequest with default headers values
+func NewDeleteSignRuleBadRequest() *DeleteSignRuleBadRequest {
+
+ return &DeleteSignRuleBadRequest{}
+}
+
+// WithPayload adds the payload to the delete sign rule bad request response
+func (o *DeleteSignRuleBadRequest) WithPayload(payload *models.Error) *DeleteSignRuleBadRequest {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the delete sign rule bad request response
+func (o *DeleteSignRuleBadRequest) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *DeleteSignRuleBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(400)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// DeleteSignRuleUnauthorizedCode is the HTTP code returned for type DeleteSignRuleUnauthorized
+const DeleteSignRuleUnauthorizedCode int = 401
+
+/*
+DeleteSignRuleUnauthorized Unauthorized - The request requires user authentication.
+
+swagger:response deleteSignRuleUnauthorized
+*/
+type DeleteSignRuleUnauthorized struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewDeleteSignRuleUnauthorized creates DeleteSignRuleUnauthorized with default headers values
+func NewDeleteSignRuleUnauthorized() *DeleteSignRuleUnauthorized {
+
+ return &DeleteSignRuleUnauthorized{}
+}
+
+// WithPayload adds the payload to the delete sign rule unauthorized response
+func (o *DeleteSignRuleUnauthorized) WithPayload(payload *models.Error) *DeleteSignRuleUnauthorized {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the delete sign rule unauthorized response
+func (o *DeleteSignRuleUnauthorized) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *DeleteSignRuleUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(401)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// DeleteSignRuleNotFoundCode is the HTTP code returned for type DeleteSignRuleNotFound
+const DeleteSignRuleNotFoundCode int = 404
+
+/*
+DeleteSignRuleNotFound Account or Rule Not found.
+
+swagger:response deleteSignRuleNotFound
+*/
+type DeleteSignRuleNotFound struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewDeleteSignRuleNotFound creates DeleteSignRuleNotFound with default headers values
+func NewDeleteSignRuleNotFound() *DeleteSignRuleNotFound {
+
+ return &DeleteSignRuleNotFound{}
+}
+
+// WithPayload adds the payload to the delete sign rule not found response
+func (o *DeleteSignRuleNotFound) WithPayload(payload *models.Error) *DeleteSignRuleNotFound {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the delete sign rule not found response
+func (o *DeleteSignRuleNotFound) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *DeleteSignRuleNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(404)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// DeleteSignRuleInternalServerErrorCode is the HTTP code returned for type DeleteSignRuleInternalServerError
+const DeleteSignRuleInternalServerErrorCode int = 500
+
+/*
+DeleteSignRuleInternalServerError Internal Server Error - The server has encountered a situation it does not know how to handle.
+
+swagger:response deleteSignRuleInternalServerError
+*/
+type DeleteSignRuleInternalServerError struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewDeleteSignRuleInternalServerError creates DeleteSignRuleInternalServerError with default headers values
+func NewDeleteSignRuleInternalServerError() *DeleteSignRuleInternalServerError {
+
+ return &DeleteSignRuleInternalServerError{}
+}
+
+// WithPayload adds the payload to the delete sign rule internal server error response
+func (o *DeleteSignRuleInternalServerError) WithPayload(payload *models.Error) *DeleteSignRuleInternalServerError {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the delete sign rule internal server error response
+func (o *DeleteSignRuleInternalServerError) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *DeleteSignRuleInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(500)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
diff --git a/api/server/restapi/operations/delete_sign_rule_urlbuilder.go b/api/server/restapi/operations/delete_sign_rule_urlbuilder.go
new file mode 100644
index 000000000..2d9786ba9
--- /dev/null
+++ b/api/server/restapi/operations/delete_sign_rule_urlbuilder.go
@@ -0,0 +1,104 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "errors"
+ "net/url"
+ golangswaggerpaths "path"
+ "strings"
+)
+
+// DeleteSignRuleURL generates an URL for the delete sign rule operation
+type DeleteSignRuleURL struct {
+ Nickname string
+ RuleID string
+
+ _basePath string
+ // avoid unkeyed usage
+ _ struct{}
+}
+
+// WithBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *DeleteSignRuleURL) WithBasePath(bp string) *DeleteSignRuleURL {
+ o.SetBasePath(bp)
+ return o
+}
+
+// SetBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *DeleteSignRuleURL) SetBasePath(bp string) {
+ o._basePath = bp
+}
+
+// Build a url path and query string
+func (o *DeleteSignRuleURL) Build() (*url.URL, error) {
+ var _result url.URL
+
+ var _path = "/api/accounts/{nickname}/signrules/{ruleId}"
+
+ nickname := o.Nickname
+ if nickname != "" {
+ _path = strings.Replace(_path, "{nickname}", nickname, -1)
+ } else {
+ return nil, errors.New("nickname is required on DeleteSignRuleURL")
+ }
+
+ ruleID := o.RuleID
+ if ruleID != "" {
+ _path = strings.Replace(_path, "{ruleId}", ruleID, -1)
+ } else {
+ return nil, errors.New("ruleId is required on DeleteSignRuleURL")
+ }
+
+ _basePath := o._basePath
+ _result.Path = golangswaggerpaths.Join(_basePath, _path)
+
+ return &_result, nil
+}
+
+// Must is a helper function to panic when the url builder returns an error
+func (o *DeleteSignRuleURL) Must(u *url.URL, err error) *url.URL {
+ if err != nil {
+ panic(err)
+ }
+ if u == nil {
+ panic("url can't be nil")
+ }
+ return u
+}
+
+// String returns the string representation of the path with query string
+func (o *DeleteSignRuleURL) String() string {
+ return o.Must(o.Build()).String()
+}
+
+// BuildFull builds a full url with scheme, host, path and query string
+func (o *DeleteSignRuleURL) BuildFull(scheme, host string) (*url.URL, error) {
+ if scheme == "" {
+ return nil, errors.New("scheme is required for a full url on DeleteSignRuleURL")
+ }
+ if host == "" {
+ return nil, errors.New("host is required for a full url on DeleteSignRuleURL")
+ }
+
+ base, err := o.Build()
+ if err != nil {
+ return nil, err
+ }
+
+ base.Scheme = scheme
+ base.Host = host
+ return base, nil
+}
+
+// StringFull returns the string representation of a complete url
+func (o *DeleteSignRuleURL) StringFull(scheme, host string) string {
+ return o.Must(o.BuildFull(scheme, host)).String()
+}
diff --git a/api/server/restapi/operations/massa_wallet_api.go b/api/server/restapi/operations/massa_wallet_api.go
index 22e4307bc..ea8df3d54 100644
--- a/api/server/restapi/operations/massa_wallet_api.go
+++ b/api/server/restapi/operations/massa_wallet_api.go
@@ -62,6 +62,9 @@ func NewMassaWalletAPI(spec *loads.Document) *MassaWalletAPI {
AddAssetHandler: AddAssetHandlerFunc(func(params AddAssetParams) middleware.Responder {
return middleware.NotImplemented("operation AddAsset has not yet been implemented")
}),
+ AddSignRuleHandler: AddSignRuleHandlerFunc(func(params AddSignRuleParams) middleware.Responder {
+ return middleware.NotImplemented("operation AddSignRule has not yet been implemented")
+ }),
BackupAccountHandler: BackupAccountHandlerFunc(func(params BackupAccountParams) middleware.Responder {
return middleware.NotImplemented("operation BackupAccount has not yet been implemented")
}),
@@ -74,6 +77,9 @@ func NewMassaWalletAPI(spec *loads.Document) *MassaWalletAPI {
DeleteAssetHandler: DeleteAssetHandlerFunc(func(params DeleteAssetParams) middleware.Responder {
return middleware.NotImplemented("operation DeleteAsset has not yet been implemented")
}),
+ DeleteSignRuleHandler: DeleteSignRuleHandlerFunc(func(params DeleteSignRuleParams) middleware.Responder {
+ return middleware.NotImplemented("operation DeleteSignRule has not yet been implemented")
+ }),
ExportAccountFileHandler: ExportAccountFileHandlerFunc(func(params ExportAccountFileParams) middleware.Responder {
return middleware.NotImplemented("operation ExportAccountFile has not yet been implemented")
}),
@@ -104,6 +110,9 @@ func NewMassaWalletAPI(spec *loads.Document) *MassaWalletAPI {
UpdateAccountHandler: UpdateAccountHandlerFunc(func(params UpdateAccountParams) middleware.Responder {
return middleware.NotImplemented("operation UpdateAccount has not yet been implemented")
}),
+ UpdateSignRuleHandler: UpdateSignRuleHandlerFunc(func(params UpdateSignRuleParams) middleware.Responder {
+ return middleware.NotImplemented("operation UpdateSignRule has not yet been implemented")
+ }),
DefaultPageHandler: DefaultPageHandlerFunc(func(params DefaultPageParams) middleware.Responder {
return middleware.NotImplemented("operation DefaultPage has not yet been implemented")
}),
@@ -166,6 +175,8 @@ type MassaWalletAPI struct {
AccountListHandler AccountListHandler
// AddAssetHandler sets the operation handler for the add asset operation
AddAssetHandler AddAssetHandler
+ // AddSignRuleHandler sets the operation handler for the add sign rule operation
+ AddSignRuleHandler AddSignRuleHandler
// BackupAccountHandler sets the operation handler for the backup account operation
BackupAccountHandler BackupAccountHandler
// CreateAccountHandler sets the operation handler for the create account operation
@@ -174,6 +185,8 @@ type MassaWalletAPI struct {
DeleteAccountHandler DeleteAccountHandler
// DeleteAssetHandler sets the operation handler for the delete asset operation
DeleteAssetHandler DeleteAssetHandler
+ // DeleteSignRuleHandler sets the operation handler for the delete sign rule operation
+ DeleteSignRuleHandler DeleteSignRuleHandler
// ExportAccountFileHandler sets the operation handler for the export account file operation
ExportAccountFileHandler ExportAccountFileHandler
// GetAccountHandler sets the operation handler for the get account operation
@@ -194,6 +207,8 @@ type MassaWalletAPI struct {
TransferCoinHandler TransferCoinHandler
// UpdateAccountHandler sets the operation handler for the update account operation
UpdateAccountHandler UpdateAccountHandler
+ // UpdateSignRuleHandler sets the operation handler for the update sign rule operation
+ UpdateSignRuleHandler UpdateSignRuleHandler
// DefaultPageHandler sets the operation handler for the default page operation
DefaultPageHandler DefaultPageHandler
// WebAppHandler sets the operation handler for the web app operation
@@ -296,6 +311,9 @@ func (o *MassaWalletAPI) Validate() error {
if o.AddAssetHandler == nil {
unregistered = append(unregistered, "AddAssetHandler")
}
+ if o.AddSignRuleHandler == nil {
+ unregistered = append(unregistered, "AddSignRuleHandler")
+ }
if o.BackupAccountHandler == nil {
unregistered = append(unregistered, "BackupAccountHandler")
}
@@ -308,6 +326,9 @@ func (o *MassaWalletAPI) Validate() error {
if o.DeleteAssetHandler == nil {
unregistered = append(unregistered, "DeleteAssetHandler")
}
+ if o.DeleteSignRuleHandler == nil {
+ unregistered = append(unregistered, "DeleteSignRuleHandler")
+ }
if o.ExportAccountFileHandler == nil {
unregistered = append(unregistered, "ExportAccountFileHandler")
}
@@ -338,6 +359,9 @@ func (o *MassaWalletAPI) Validate() error {
if o.UpdateAccountHandler == nil {
unregistered = append(unregistered, "UpdateAccountHandler")
}
+ if o.UpdateSignRuleHandler == nil {
+ unregistered = append(unregistered, "UpdateSignRuleHandler")
+ }
if o.DefaultPageHandler == nil {
unregistered = append(unregistered, "DefaultPageHandler")
}
@@ -455,6 +479,10 @@ func (o *MassaWalletAPI) initHandlerCache() {
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
+ o.handlers["POST"]["/api/accounts/{nickname}/signrules"] = NewAddSignRule(o.context, o.AddSignRuleHandler)
+ if o.handlers["POST"] == nil {
+ o.handlers["POST"] = make(map[string]http.Handler)
+ }
o.handlers["POST"]["/api/accounts/{nickname}/backup"] = NewBackupAccount(o.context, o.BackupAccountHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
@@ -468,6 +496,10 @@ func (o *MassaWalletAPI) initHandlerCache() {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
o.handlers["DELETE"]["/api/accounts/{nickname}/assets"] = NewDeleteAsset(o.context, o.DeleteAssetHandler)
+ if o.handlers["DELETE"] == nil {
+ o.handlers["DELETE"] = make(map[string]http.Handler)
+ }
+ o.handlers["DELETE"]["/api/accounts/{nickname}/signrules/{ruleId}"] = NewDeleteSignRule(o.context, o.DeleteSignRuleHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
@@ -508,6 +540,10 @@ func (o *MassaWalletAPI) initHandlerCache() {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/api/accounts/{nickname}"] = NewUpdateAccount(o.context, o.UpdateAccountHandler)
+ if o.handlers["PUT"] == nil {
+ o.handlers["PUT"] = make(map[string]http.Handler)
+ }
+ o.handlers["PUT"]["/api/accounts/{nickname}/signrules/{ruleId}"] = NewUpdateSignRule(o.context, o.UpdateSignRuleHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
diff --git a/api/server/restapi/operations/request.go b/api/server/restapi/operations/request.go
new file mode 100644
index 000000000..83dc9e4ec
--- /dev/null
+++ b/api/server/restapi/operations/request.go
@@ -0,0 +1,56 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime/middleware"
+)
+
+// RequestHandlerFunc turns a function with the right signature into a request handler
+type RequestHandlerFunc func(RequestParams) middleware.Responder
+
+// Handle executing the request and returning a response
+func (fn RequestHandlerFunc) Handle(params RequestParams) middleware.Responder {
+ return fn(params)
+}
+
+// RequestHandler interface for that can handle valid request params
+type RequestHandler interface {
+ Handle(RequestParams) middleware.Responder
+}
+
+// NewRequest creates a new http.Handler for the request operation
+func NewRequest(ctx *middleware.Context, handler RequestHandler) *Request {
+ return &Request{Context: ctx, Handler: handler}
+}
+
+/*
+ Request swagger:route POST /api/accounts/{nickname}/request request
+
+Request user approval through UI.
+*/
+type Request struct {
+ Context *middleware.Context
+ Handler RequestHandler
+}
+
+func (o *Request) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ route, rCtx, _ := o.Context.RouteInfo(r)
+ if rCtx != nil {
+ *r = *rCtx
+ }
+ var Params = NewRequestParams()
+ if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
+ o.Context.Respond(rw, r, route.Produces, route, err)
+ return
+ }
+
+ res := o.Handler.Handle(Params) // actually handle the request
+ o.Context.Respond(rw, r, route.Produces, route, res)
+
+}
diff --git a/api/server/restapi/operations/request_parameters.go b/api/server/restapi/operations/request_parameters.go
new file mode 100644
index 000000000..0005e60bc
--- /dev/null
+++ b/api/server/restapi/operations/request_parameters.go
@@ -0,0 +1,108 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "io"
+ "net/http"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/runtime"
+ "github.com/go-openapi/runtime/middleware"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/validate"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+)
+
+// NewRequestParams creates a new RequestParams object
+//
+// There are no default values defined in the spec.
+func NewRequestParams() RequestParams {
+
+ return RequestParams{}
+}
+
+// RequestParams contains all the bound params for the request operation
+// typically these are obtained from a http.Request
+//
+// swagger:parameters Request
+type RequestParams struct {
+
+ // HTTP Request Object
+ HTTPRequest *http.Request `json:"-"`
+
+ /*
+ Required: true
+ In: body
+ */
+ Body *models.Request
+ /*Account's short name.
+ Required: true
+ In: path
+ */
+ Nickname string
+}
+
+// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
+// for simple values it will use straight method calls.
+//
+// To ensure default values, the struct must have been initialized with NewRequestParams() beforehand.
+func (o *RequestParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
+ var res []error
+
+ o.HTTPRequest = r
+
+ if runtime.HasBody(r) {
+ defer r.Body.Close()
+ var body models.Request
+ if err := route.Consumer.Consume(r.Body, &body); err != nil {
+ if err == io.EOF {
+ res = append(res, errors.Required("body", "body", ""))
+ } else {
+ res = append(res, errors.NewParseError("body", "body", "", err))
+ }
+ } else {
+ // validate body object
+ if err := body.Validate(route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ ctx := validate.WithOperationRequest(r.Context())
+ if err := body.ContextValidate(ctx, route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) == 0 {
+ o.Body = &body
+ }
+ }
+ } else {
+ res = append(res, errors.Required("body", "body", ""))
+ }
+
+ rNickname, rhkNickname, _ := route.Params.GetOK("nickname")
+ if err := o.bindNickname(rNickname, rhkNickname, route.Formats); err != nil {
+ res = append(res, err)
+ }
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+// bindNickname binds and validates parameter Nickname from path.
+func (o *RequestParams) bindNickname(rawData []string, hasKey bool, formats strfmt.Registry) error {
+ var raw string
+ if len(rawData) > 0 {
+ raw = rawData[len(rawData)-1]
+ }
+
+ // Required: true
+ // Parameter is provided by construction from the route
+ o.Nickname = raw
+
+ return nil
+}
diff --git a/api/server/restapi/operations/request_responses.go b/api/server/restapi/operations/request_responses.go
new file mode 100644
index 000000000..f5e0e5591
--- /dev/null
+++ b/api/server/restapi/operations/request_responses.go
@@ -0,0 +1,283 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+)
+
+// RequestOKCode is the HTTP code returned for type RequestOK
+const RequestOKCode int = 200
+
+/*
+RequestOK Success.
+
+swagger:response requestOK
+*/
+type RequestOK struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.RequestResponse `json:"body,omitempty"`
+}
+
+// NewRequestOK creates RequestOK with default headers values
+func NewRequestOK() *RequestOK {
+
+ return &RequestOK{}
+}
+
+// WithPayload adds the payload to the request o k response
+func (o *RequestOK) WithPayload(payload *models.RequestResponse) *RequestOK {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the request o k response
+func (o *RequestOK) SetPayload(payload *models.RequestResponse) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *RequestOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(200)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// RequestBadRequestCode is the HTTP code returned for type RequestBadRequest
+const RequestBadRequestCode int = 400
+
+/*
+RequestBadRequest Bad request.
+
+swagger:response requestBadRequest
+*/
+type RequestBadRequest struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewRequestBadRequest creates RequestBadRequest with default headers values
+func NewRequestBadRequest() *RequestBadRequest {
+
+ return &RequestBadRequest{}
+}
+
+// WithPayload adds the payload to the request bad request response
+func (o *RequestBadRequest) WithPayload(payload *models.Error) *RequestBadRequest {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the request bad request response
+func (o *RequestBadRequest) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *RequestBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(400)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// RequestUnauthorizedCode is the HTTP code returned for type RequestUnauthorized
+const RequestUnauthorizedCode int = 401
+
+/*
+RequestUnauthorized Unauthorized - The request requires user authentication.
+
+swagger:response requestUnauthorized
+*/
+type RequestUnauthorized struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewRequestUnauthorized creates RequestUnauthorized with default headers values
+func NewRequestUnauthorized() *RequestUnauthorized {
+
+ return &RequestUnauthorized{}
+}
+
+// WithPayload adds the payload to the request unauthorized response
+func (o *RequestUnauthorized) WithPayload(payload *models.Error) *RequestUnauthorized {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the request unauthorized response
+func (o *RequestUnauthorized) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *RequestUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(401)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// RequestNotFoundCode is the HTTP code returned for type RequestNotFound
+const RequestNotFoundCode int = 404
+
+/*
+RequestNotFound Account Not found.
+
+swagger:response requestNotFound
+*/
+type RequestNotFound struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewRequestNotFound creates RequestNotFound with default headers values
+func NewRequestNotFound() *RequestNotFound {
+
+ return &RequestNotFound{}
+}
+
+// WithPayload adds the payload to the request not found response
+func (o *RequestNotFound) WithPayload(payload *models.Error) *RequestNotFound {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the request not found response
+func (o *RequestNotFound) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *RequestNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(404)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// RequestUnprocessableEntityCode is the HTTP code returned for type RequestUnprocessableEntity
+const RequestUnprocessableEntityCode int = 422
+
+/*
+RequestUnprocessableEntity Unprocessable Entity - syntax is correct, but the server was unable to process the contained instructions.
+
+swagger:response requestUnprocessableEntity
+*/
+type RequestUnprocessableEntity struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewRequestUnprocessableEntity creates RequestUnprocessableEntity with default headers values
+func NewRequestUnprocessableEntity() *RequestUnprocessableEntity {
+
+ return &RequestUnprocessableEntity{}
+}
+
+// WithPayload adds the payload to the request unprocessable entity response
+func (o *RequestUnprocessableEntity) WithPayload(payload *models.Error) *RequestUnprocessableEntity {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the request unprocessable entity response
+func (o *RequestUnprocessableEntity) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *RequestUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(422)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// RequestInternalServerErrorCode is the HTTP code returned for type RequestInternalServerError
+const RequestInternalServerErrorCode int = 500
+
+/*
+RequestInternalServerError Internal Server Error - The server has encountered a situation it does not know how to handle.
+
+swagger:response requestInternalServerError
+*/
+type RequestInternalServerError struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewRequestInternalServerError creates RequestInternalServerError with default headers values
+func NewRequestInternalServerError() *RequestInternalServerError {
+
+ return &RequestInternalServerError{}
+}
+
+// WithPayload adds the payload to the request internal server error response
+func (o *RequestInternalServerError) WithPayload(payload *models.Error) *RequestInternalServerError {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the request internal server error response
+func (o *RequestInternalServerError) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *RequestInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(500)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
diff --git a/api/server/restapi/operations/request_urlbuilder.go b/api/server/restapi/operations/request_urlbuilder.go
new file mode 100644
index 000000000..946a252f1
--- /dev/null
+++ b/api/server/restapi/operations/request_urlbuilder.go
@@ -0,0 +1,96 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "errors"
+ "net/url"
+ golangswaggerpaths "path"
+ "strings"
+)
+
+// RequestURL generates an URL for the request operation
+type RequestURL struct {
+ Nickname string
+
+ _basePath string
+ // avoid unkeyed usage
+ _ struct{}
+}
+
+// WithBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *RequestURL) WithBasePath(bp string) *RequestURL {
+ o.SetBasePath(bp)
+ return o
+}
+
+// SetBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *RequestURL) SetBasePath(bp string) {
+ o._basePath = bp
+}
+
+// Build a url path and query string
+func (o *RequestURL) Build() (*url.URL, error) {
+ var _result url.URL
+
+ var _path = "/api/accounts/{nickname}/request"
+
+ nickname := o.Nickname
+ if nickname != "" {
+ _path = strings.Replace(_path, "{nickname}", nickname, -1)
+ } else {
+ return nil, errors.New("nickname is required on RequestURL")
+ }
+
+ _basePath := o._basePath
+ _result.Path = golangswaggerpaths.Join(_basePath, _path)
+
+ return &_result, nil
+}
+
+// Must is a helper function to panic when the url builder returns an error
+func (o *RequestURL) Must(u *url.URL, err error) *url.URL {
+ if err != nil {
+ panic(err)
+ }
+ if u == nil {
+ panic("url can't be nil")
+ }
+ return u
+}
+
+// String returns the string representation of the path with query string
+func (o *RequestURL) String() string {
+ return o.Must(o.Build()).String()
+}
+
+// BuildFull builds a full url with scheme, host, path and query string
+func (o *RequestURL) BuildFull(scheme, host string) (*url.URL, error) {
+ if scheme == "" {
+ return nil, errors.New("scheme is required for a full url on RequestURL")
+ }
+ if host == "" {
+ return nil, errors.New("host is required for a full url on RequestURL")
+ }
+
+ base, err := o.Build()
+ if err != nil {
+ return nil, err
+ }
+
+ base.Scheme = scheme
+ base.Host = host
+ return base, nil
+}
+
+// StringFull returns the string representation of a complete url
+func (o *RequestURL) StringFull(scheme, host string) string {
+ return o.Must(o.BuildFull(scheme, host)).String()
+}
diff --git a/api/server/restapi/operations/update_sign_rule.go b/api/server/restapi/operations/update_sign_rule.go
new file mode 100644
index 000000000..ac298024c
--- /dev/null
+++ b/api/server/restapi/operations/update_sign_rule.go
@@ -0,0 +1,56 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime/middleware"
+)
+
+// UpdateSignRuleHandlerFunc turns a function with the right signature into a update sign rule handler
+type UpdateSignRuleHandlerFunc func(UpdateSignRuleParams) middleware.Responder
+
+// Handle executing the request and returning a response
+func (fn UpdateSignRuleHandlerFunc) Handle(params UpdateSignRuleParams) middleware.Responder {
+ return fn(params)
+}
+
+// UpdateSignRuleHandler interface for that can handle valid update sign rule params
+type UpdateSignRuleHandler interface {
+ Handle(UpdateSignRuleParams) middleware.Responder
+}
+
+// NewUpdateSignRule creates a new http.Handler for the update sign rule operation
+func NewUpdateSignRule(ctx *middleware.Context, handler UpdateSignRuleHandler) *UpdateSignRule {
+ return &UpdateSignRule{Context: ctx, Handler: handler}
+}
+
+/*
+ UpdateSignRule swagger:route PUT /api/accounts/{nickname}/signrules/{ruleId} updateSignRule
+
+Update an existing sign rule for the account associated with the provided nickname by its ID.
+*/
+type UpdateSignRule struct {
+ Context *middleware.Context
+ Handler UpdateSignRuleHandler
+}
+
+func (o *UpdateSignRule) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ route, rCtx, _ := o.Context.RouteInfo(r)
+ if rCtx != nil {
+ *r = *rCtx
+ }
+ var Params = NewUpdateSignRuleParams()
+ if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
+ o.Context.Respond(rw, r, route.Produces, route, err)
+ return
+ }
+
+ res := o.Handler.Handle(Params) // actually handle the request
+ o.Context.Respond(rw, r, route.Produces, route, res)
+
+}
diff --git a/api/server/restapi/operations/update_sign_rule_parameters.go b/api/server/restapi/operations/update_sign_rule_parameters.go
new file mode 100644
index 000000000..47d8a4976
--- /dev/null
+++ b/api/server/restapi/operations/update_sign_rule_parameters.go
@@ -0,0 +1,132 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "io"
+ "net/http"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/runtime"
+ "github.com/go-openapi/runtime/middleware"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/validate"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+)
+
+// NewUpdateSignRuleParams creates a new UpdateSignRuleParams object
+//
+// There are no default values defined in the spec.
+func NewUpdateSignRuleParams() UpdateSignRuleParams {
+
+ return UpdateSignRuleParams{}
+}
+
+// UpdateSignRuleParams contains all the bound params for the update sign rule operation
+// typically these are obtained from a http.Request
+//
+// swagger:parameters UpdateSignRule
+type UpdateSignRuleParams struct {
+
+ // HTTP Request Object
+ HTTPRequest *http.Request `json:"-"`
+
+ /*
+ Required: true
+ In: body
+ */
+ Body *models.UpdateSignRule
+ /*Account's short name.
+ Required: true
+ In: path
+ */
+ Nickname string
+ /*The ID of the sign rule
+ Required: true
+ In: path
+ */
+ RuleID string
+}
+
+// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
+// for simple values it will use straight method calls.
+//
+// To ensure default values, the struct must have been initialized with NewUpdateSignRuleParams() beforehand.
+func (o *UpdateSignRuleParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
+ var res []error
+
+ o.HTTPRequest = r
+
+ if runtime.HasBody(r) {
+ defer r.Body.Close()
+ var body models.UpdateSignRule
+ if err := route.Consumer.Consume(r.Body, &body); err != nil {
+ if err == io.EOF {
+ res = append(res, errors.Required("body", "body", ""))
+ } else {
+ res = append(res, errors.NewParseError("body", "body", "", err))
+ }
+ } else {
+ // validate body object
+ if err := body.Validate(route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ ctx := validate.WithOperationRequest(r.Context())
+ if err := body.ContextValidate(ctx, route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) == 0 {
+ o.Body = &body
+ }
+ }
+ } else {
+ res = append(res, errors.Required("body", "body", ""))
+ }
+
+ rNickname, rhkNickname, _ := route.Params.GetOK("nickname")
+ if err := o.bindNickname(rNickname, rhkNickname, route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ rRuleID, rhkRuleID, _ := route.Params.GetOK("ruleId")
+ if err := o.bindRuleID(rRuleID, rhkRuleID, route.Formats); err != nil {
+ res = append(res, err)
+ }
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+// bindNickname binds and validates parameter Nickname from path.
+func (o *UpdateSignRuleParams) bindNickname(rawData []string, hasKey bool, formats strfmt.Registry) error {
+ var raw string
+ if len(rawData) > 0 {
+ raw = rawData[len(rawData)-1]
+ }
+
+ // Required: true
+ // Parameter is provided by construction from the route
+ o.Nickname = raw
+
+ return nil
+}
+
+// bindRuleID binds and validates parameter RuleID from path.
+func (o *UpdateSignRuleParams) bindRuleID(rawData []string, hasKey bool, formats strfmt.Registry) error {
+ var raw string
+ if len(rawData) > 0 {
+ raw = rawData[len(rawData)-1]
+ }
+
+ // Required: true
+ // Parameter is provided by construction from the route
+ o.RuleID = raw
+
+ return nil
+}
diff --git a/api/server/restapi/operations/update_sign_rule_responses.go b/api/server/restapi/operations/update_sign_rule_responses.go
new file mode 100644
index 000000000..230de184c
--- /dev/null
+++ b/api/server/restapi/operations/update_sign_rule_responses.go
@@ -0,0 +1,283 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+)
+
+// UpdateSignRuleOKCode is the HTTP code returned for type UpdateSignRuleOK
+const UpdateSignRuleOKCode int = 200
+
+/*
+UpdateSignRuleOK Sign rule updated successfully.
+
+swagger:response updateSignRuleOK
+*/
+type UpdateSignRuleOK struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.UpdateSignRuleResponse `json:"body,omitempty"`
+}
+
+// NewUpdateSignRuleOK creates UpdateSignRuleOK with default headers values
+func NewUpdateSignRuleOK() *UpdateSignRuleOK {
+
+ return &UpdateSignRuleOK{}
+}
+
+// WithPayload adds the payload to the update sign rule o k response
+func (o *UpdateSignRuleOK) WithPayload(payload *models.UpdateSignRuleResponse) *UpdateSignRuleOK {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the update sign rule o k response
+func (o *UpdateSignRuleOK) SetPayload(payload *models.UpdateSignRuleResponse) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *UpdateSignRuleOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(200)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// UpdateSignRuleBadRequestCode is the HTTP code returned for type UpdateSignRuleBadRequest
+const UpdateSignRuleBadRequestCode int = 400
+
+/*
+UpdateSignRuleBadRequest Bad request.
+
+swagger:response updateSignRuleBadRequest
+*/
+type UpdateSignRuleBadRequest struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewUpdateSignRuleBadRequest creates UpdateSignRuleBadRequest with default headers values
+func NewUpdateSignRuleBadRequest() *UpdateSignRuleBadRequest {
+
+ return &UpdateSignRuleBadRequest{}
+}
+
+// WithPayload adds the payload to the update sign rule bad request response
+func (o *UpdateSignRuleBadRequest) WithPayload(payload *models.Error) *UpdateSignRuleBadRequest {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the update sign rule bad request response
+func (o *UpdateSignRuleBadRequest) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *UpdateSignRuleBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(400)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// UpdateSignRuleUnauthorizedCode is the HTTP code returned for type UpdateSignRuleUnauthorized
+const UpdateSignRuleUnauthorizedCode int = 401
+
+/*
+UpdateSignRuleUnauthorized Unauthorized - The request requires user authentication.
+
+swagger:response updateSignRuleUnauthorized
+*/
+type UpdateSignRuleUnauthorized struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewUpdateSignRuleUnauthorized creates UpdateSignRuleUnauthorized with default headers values
+func NewUpdateSignRuleUnauthorized() *UpdateSignRuleUnauthorized {
+
+ return &UpdateSignRuleUnauthorized{}
+}
+
+// WithPayload adds the payload to the update sign rule unauthorized response
+func (o *UpdateSignRuleUnauthorized) WithPayload(payload *models.Error) *UpdateSignRuleUnauthorized {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the update sign rule unauthorized response
+func (o *UpdateSignRuleUnauthorized) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *UpdateSignRuleUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(401)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// UpdateSignRuleNotFoundCode is the HTTP code returned for type UpdateSignRuleNotFound
+const UpdateSignRuleNotFoundCode int = 404
+
+/*
+UpdateSignRuleNotFound Account or Rule Not found.
+
+swagger:response updateSignRuleNotFound
+*/
+type UpdateSignRuleNotFound struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewUpdateSignRuleNotFound creates UpdateSignRuleNotFound with default headers values
+func NewUpdateSignRuleNotFound() *UpdateSignRuleNotFound {
+
+ return &UpdateSignRuleNotFound{}
+}
+
+// WithPayload adds the payload to the update sign rule not found response
+func (o *UpdateSignRuleNotFound) WithPayload(payload *models.Error) *UpdateSignRuleNotFound {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the update sign rule not found response
+func (o *UpdateSignRuleNotFound) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *UpdateSignRuleNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(404)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// UpdateSignRuleUnprocessableEntityCode is the HTTP code returned for type UpdateSignRuleUnprocessableEntity
+const UpdateSignRuleUnprocessableEntityCode int = 422
+
+/*
+UpdateSignRuleUnprocessableEntity Unprocessable Entity - syntax is correct, but the server was unable to process the contained instructions.
+
+swagger:response updateSignRuleUnprocessableEntity
+*/
+type UpdateSignRuleUnprocessableEntity struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewUpdateSignRuleUnprocessableEntity creates UpdateSignRuleUnprocessableEntity with default headers values
+func NewUpdateSignRuleUnprocessableEntity() *UpdateSignRuleUnprocessableEntity {
+
+ return &UpdateSignRuleUnprocessableEntity{}
+}
+
+// WithPayload adds the payload to the update sign rule unprocessable entity response
+func (o *UpdateSignRuleUnprocessableEntity) WithPayload(payload *models.Error) *UpdateSignRuleUnprocessableEntity {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the update sign rule unprocessable entity response
+func (o *UpdateSignRuleUnprocessableEntity) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *UpdateSignRuleUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(422)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
+
+// UpdateSignRuleInternalServerErrorCode is the HTTP code returned for type UpdateSignRuleInternalServerError
+const UpdateSignRuleInternalServerErrorCode int = 500
+
+/*
+UpdateSignRuleInternalServerError Internal Server Error - The server has encountered a situation it does not know how to handle.
+
+swagger:response updateSignRuleInternalServerError
+*/
+type UpdateSignRuleInternalServerError struct {
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewUpdateSignRuleInternalServerError creates UpdateSignRuleInternalServerError with default headers values
+func NewUpdateSignRuleInternalServerError() *UpdateSignRuleInternalServerError {
+
+ return &UpdateSignRuleInternalServerError{}
+}
+
+// WithPayload adds the payload to the update sign rule internal server error response
+func (o *UpdateSignRuleInternalServerError) WithPayload(payload *models.Error) *UpdateSignRuleInternalServerError {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the update sign rule internal server error response
+func (o *UpdateSignRuleInternalServerError) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *UpdateSignRuleInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(500)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
diff --git a/api/server/restapi/operations/update_sign_rule_urlbuilder.go b/api/server/restapi/operations/update_sign_rule_urlbuilder.go
new file mode 100644
index 000000000..5dabd7d4a
--- /dev/null
+++ b/api/server/restapi/operations/update_sign_rule_urlbuilder.go
@@ -0,0 +1,104 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "errors"
+ "net/url"
+ golangswaggerpaths "path"
+ "strings"
+)
+
+// UpdateSignRuleURL generates an URL for the update sign rule operation
+type UpdateSignRuleURL struct {
+ Nickname string
+ RuleID string
+
+ _basePath string
+ // avoid unkeyed usage
+ _ struct{}
+}
+
+// WithBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *UpdateSignRuleURL) WithBasePath(bp string) *UpdateSignRuleURL {
+ o.SetBasePath(bp)
+ return o
+}
+
+// SetBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *UpdateSignRuleURL) SetBasePath(bp string) {
+ o._basePath = bp
+}
+
+// Build a url path and query string
+func (o *UpdateSignRuleURL) Build() (*url.URL, error) {
+ var _result url.URL
+
+ var _path = "/api/accounts/{nickname}/signrules/{ruleId}"
+
+ nickname := o.Nickname
+ if nickname != "" {
+ _path = strings.Replace(_path, "{nickname}", nickname, -1)
+ } else {
+ return nil, errors.New("nickname is required on UpdateSignRuleURL")
+ }
+
+ ruleID := o.RuleID
+ if ruleID != "" {
+ _path = strings.Replace(_path, "{ruleId}", ruleID, -1)
+ } else {
+ return nil, errors.New("ruleId is required on UpdateSignRuleURL")
+ }
+
+ _basePath := o._basePath
+ _result.Path = golangswaggerpaths.Join(_basePath, _path)
+
+ return &_result, nil
+}
+
+// Must is a helper function to panic when the url builder returns an error
+func (o *UpdateSignRuleURL) Must(u *url.URL, err error) *url.URL {
+ if err != nil {
+ panic(err)
+ }
+ if u == nil {
+ panic("url can't be nil")
+ }
+ return u
+}
+
+// String returns the string representation of the path with query string
+func (o *UpdateSignRuleURL) String() string {
+ return o.Must(o.Build()).String()
+}
+
+// BuildFull builds a full url with scheme, host, path and query string
+func (o *UpdateSignRuleURL) BuildFull(scheme, host string) (*url.URL, error) {
+ if scheme == "" {
+ return nil, errors.New("scheme is required for a full url on UpdateSignRuleURL")
+ }
+ if host == "" {
+ return nil, errors.New("host is required for a full url on UpdateSignRuleURL")
+ }
+
+ base, err := o.Build()
+ if err != nil {
+ return nil, err
+ }
+
+ base.Scheme = scheme
+ base.Host = host
+ return base, nil
+}
+
+// StringFull returns the string representation of a complete url
+func (o *UpdateSignRuleURL) StringFull(scheme, host string) string {
+ return o.Must(o.BuildFull(scheme, host)).String()
+}
diff --git a/api/walletApi-V0.yml b/api/walletApi-V0.yml
index 1eca8438e..b9d075c38 100644
--- a/api/walletApi-V0.yml
+++ b/api/walletApi-V0.yml
@@ -196,7 +196,6 @@ paths:
- in: body
name: body
required: true
- x-nullable: false
schema:
$ref: "#/definitions/UpdateAccountRequest"
produces:
@@ -247,6 +246,114 @@ paths:
description: Internal Server Error - The server has encountered a situation it does not know how to handle.
schema:
$ref: "#/definitions/Error"
+
+ /api/accounts/{nickname}/signrules:
+ post:
+ operationId: AddSignRule
+ description: Create a new sign rule for the account associated with the provided nickname.
+ parameters:
+ - $ref: "#/parameters/nickname"
+ - in: body
+ name: body
+ required: true
+ schema:
+ $ref: "#/definitions/AddSignRule"
+ produces:
+ - application/json
+ responses:
+ 200:
+ description: signRule Id.
+ schema:
+ $ref: "#/definitions/AddSignRuleResponse"
+ 400:
+ description: Bad request.
+ schema:
+ $ref: "#/definitions/Error"
+ 401:
+ description: Unauthorized - The request requires user authentication.
+ schema:
+ $ref: "#/definitions/Error"
+ 404:
+ description: Account Not found.
+ schema:
+ $ref: "#/definitions/Error"
+ 422:
+ description: Unprocessable Entity - syntax is correct, but the server was unable to process the contained instructions.
+ schema:
+ $ref: "#/definitions/Error"
+ 500:
+ description: Internal Server Error - The server has encountered a situation it does not know how to handle.
+ schema:
+ $ref: "#/definitions/Error"
+
+ /api/accounts/{nickname}/signrules/{ruleId}:
+ delete:
+ operationId: DeleteSignRule
+ description: Delete a sign rule for the account associated with the provided nickname by its ID.
+ parameters:
+ - $ref: "#/parameters/nickname"
+ - $ref: "#/parameters/ruleId"
+ produces:
+ - application/json
+ responses:
+ 200:
+ description: Sign rule deleted successfully.
+ 400:
+ description: Bad request.
+ schema:
+ $ref: "#/definitions/Error"
+ 401:
+ description: Unauthorized - The request requires user authentication.
+ schema:
+ $ref: "#/definitions/Error"
+ 404:
+ description: Account or Rule Not found.
+ schema:
+ $ref: "#/definitions/Error"
+ 500:
+ description: Internal Server Error - The server has encountered a situation it does not know how to handle.
+ schema:
+ $ref: "#/definitions/Error"
+
+ put:
+ operationId: UpdateSignRule
+ description: Update an existing sign rule for the account associated with the provided nickname by its ID.
+ parameters:
+ - $ref: "#/parameters/nickname"
+ - $ref: "#/parameters/ruleId"
+ - in: body
+ name: body
+ required: true
+ schema:
+ $ref: "#/definitions/UpdateSignRule"
+ produces:
+ - application/json
+ responses:
+ 200:
+ description: Sign rule updated successfully.
+ schema:
+ $ref: "#/definitions/UpdateSignRuleResponse"
+ 400:
+ description: Bad request.
+ schema:
+ $ref: "#/definitions/Error"
+ 401:
+ description: Unauthorized - The request requires user authentication.
+ schema:
+ $ref: "#/definitions/Error"
+ 404:
+ description: Account or Rule Not found.
+ schema:
+ $ref: "#/definitions/Error"
+ 422:
+ description: Unprocessable Entity - syntax is correct, but the server was unable to process the contained instructions.
+ schema:
+ $ref: "#/definitions/Error"
+ 500:
+ description: Internal Server Error - The server has encountered a situation it does not know how to handle.
+ schema:
+ $ref: "#/definitions/Error"
+
/api/accounts/{nickname}/sign:
post:
operationId: Sign
@@ -267,7 +374,7 @@ paths:
description: Whether to allow user to edit the fee value in the sign prompt.
x-nullable: true
produces:
- - application/json
+ - application/json
responses:
200:
description: Returns the signature, public key.
@@ -501,6 +608,7 @@ paths:
description: Internal Server Error - The server has encountered a situation it does not know how to handle.
schema:
$ref: "#/definitions/Error"
+
delete:
operationId: DeleteAsset
description: Delete token information from an account.
@@ -550,6 +658,14 @@ parameters:
default: true
description: whether to return the data ciphered or not
x-nullable: true
+ ruleId:
+ name: ruleId
+ in: path
+ required: true
+ type: string
+ description: The ID of the sign rule
+ x-nullable: false
+
definitions:
Error:
type: object
@@ -584,6 +700,76 @@ definitions:
type: integer
description: The chain id of the network to which the operation will be sent.
+ AddSignRule:
+ type: object
+ required:
+ - ruleType
+ - contract
+ - enabled
+ properties:
+ description:
+ type: string
+ maxLength: 280
+ description: Description text of what is being done (optional)
+ ruleType:
+ $ref: "#/definitions/RuleType"
+ name:
+ description: "The name of the rule."
+ type: "string"
+ contract:
+ description: "The contract to which the rule applies."
+ type: "string"
+ enabled:
+ description: "Whether the rule is enabled or not."
+ type: "boolean"
+
+ RuleType:
+ description: "An enumeration of the different types of rules."
+ type: string
+ enum:
+ - disable_password_prompt
+ - auto_sign
+ x-nullable: false
+
+ AddSignRuleResponse:
+ type: object
+ properties:
+ id:
+ type: string
+ x-nullable: false
+ readOnly: true
+
+ UpdateSignRule:
+ type: object
+ required:
+ - ruleType
+ - contract
+ - enabled
+ properties:
+ description:
+ type: string
+ maxLength: 280
+ description: Description text of what is being updated (optional)
+ ruleType:
+ $ref: "#/definitions/RuleType"
+ name:
+ description: "The name of the rule."
+ type: "string"
+ contract:
+ description: "The contract to which the rule applies. Use wildcard (*) to apply the rule for contracts."
+ type: "string"
+ enabled:
+ description: "Whether the rule is enabled or not."
+ type: "boolean"
+
+ UpdateSignRuleResponse:
+ type: object
+ properties:
+ id:
+ type: string
+ x-nullable: false
+ readOnly: true
+
SignMessageRequest:
type: object
properties:
@@ -669,6 +855,7 @@ definitions:
format: byte
x-nullable: false
readOnly: true
+
Nickname:
description: Account's short name.
type: string
@@ -771,14 +958,17 @@ definitions:
properties:
contract:
type: string
- passwordPrompt:
- type: boolean
- autoSign:
+ x-nullable: true
+ enabled:
type: boolean
+ default: true
+ ruleType:
+ type: string
+ enum:
+ - password_prompt
+ - auto_sign
required:
- - contract
- - passwordPrompt
- - autoSign
+ - ruleType
AccountConfig:
type: object
diff --git a/cmd/massa-wallet/massa-wallet.go b/cmd/massa-wallet/massa-wallet.go
index a42c441cd..36698ecae 100644
--- a/cmd/massa-wallet/massa-wallet.go
+++ b/cmd/massa-wallet/massa-wallet.go
@@ -3,7 +3,6 @@ package app
import (
"os"
- "github.com/bluele/gcache"
"github.com/massalabs/station-massa-hello-world/pkg/plugin"
"github.com/massalabs/station-massa-wallet/api/server/restapi"
"github.com/massalabs/station-massa-wallet/internal/handler"
@@ -16,11 +15,6 @@ import (
)
func StartServer(app *walletApp.WalletApp) {
- // Initialize cache
- gc := gcache.New(20).
- LRU().
- Build()
-
config.Load()
massaClient := network.NewNodeFetcher()
@@ -30,7 +24,7 @@ func StartServer(app *walletApp.WalletApp) {
promptApp = prompt.NewEnvPrompter(app)
}
- AssetsStore, err := assets.NewAssetsStore("", massaClient)
+ _, err := assets.InitAssetsStore("", massaClient)
if err != nil {
logger.Fatalf("Failed to create AssetsStore: %v", err)
}
@@ -39,8 +33,6 @@ func StartServer(app *walletApp.WalletApp) {
massaWalletAPI, err := handler.InitializeAPI(
promptApp,
massaClient,
- AssetsStore,
- gc,
)
if err != nil {
logger.Fatalf("Failed to initialize API: %v", err)
diff --git a/go.mod b/go.mod
index dc0786340..ac98b0410 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/rs/cors v1.8.3
github.com/shopspring/decimal v1.3.1
- github.com/stretchr/testify v1.8.4
+ github.com/stretchr/testify v1.10.0
github.com/wailsapp/wails/v2 v2.9.1
golang.org/x/crypto v0.23.0
gopkg.in/yaml.v2 v2.4.0
diff --git a/go.sum b/go.sum
index db41a175d..15ea93f06 100644
--- a/go.sum
+++ b/go.sum
@@ -221,16 +221,16 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
diff --git a/internal/handler/api.go b/internal/handler/api.go
index 08aba18d9..67f2cbf7e 100644
--- a/internal/handler/api.go
+++ b/internal/handler/api.go
@@ -1,19 +1,17 @@
package handler
import (
- "github.com/bluele/gcache"
"github.com/go-openapi/loads"
"github.com/massalabs/station-massa-wallet/api/server/restapi"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
"github.com/massalabs/station-massa-wallet/internal/handler/html"
walletHandler "github.com/massalabs/station-massa-wallet/internal/handler/wallet"
- "github.com/massalabs/station-massa-wallet/pkg/assets"
"github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
)
// InitializeAPI initializes the API handlers
-func InitializeAPI(prompterApp prompt.WalletPrompterInterface, massaClient network.NodeFetcherInterface, AssetsStore *assets.AssetsStore, gc gcache.Cache) (*operations.MassaWalletAPI, error) {
+func InitializeAPI(prompterApp prompt.WalletPrompterInterface, massaClient network.NodeFetcherInterface) (*operations.MassaWalletAPI, error) {
// Load the Swagger specification
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
@@ -27,7 +25,7 @@ func InitializeAPI(prompterApp prompt.WalletPrompterInterface, massaClient netwo
html.AppendEndpoints(api)
// Set wallet API endpoints
- walletHandler.AppendEndpoints(api, prompterApp, massaClient, AssetsStore, gc)
+ walletHandler.AppendEndpoints(api, prompterApp, massaClient)
return api, nil
}
diff --git a/internal/handler/wallet/add_assets_test.go b/internal/handler/wallet/add_assets_test.go
index 6f7742fd4..5103353ae 100644
--- a/internal/handler/wallet/add_assets_test.go
+++ b/internal/handler/wallet/add_assets_test.go
@@ -3,7 +3,6 @@ package wallet
import (
"encoding/json"
"fmt"
- "log"
"net/http"
"testing"
@@ -11,16 +10,11 @@ import (
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
"github.com/massalabs/station-massa-wallet/pkg/wallet/account"
- "github.com/massalabs/station/pkg/logger"
"github.com/stretchr/testify/assert"
)
func TestAddAssetHandler(t *testing.T) {
- if err := logger.InitializeGlobal("./unit-test.log"); err != nil {
- log.Fatalf("while initializing global logger: %s", err.Error())
- }
-
- api, _, _, _, err := MockAPI()
+ api, _, err := MockAPI()
assert.NoError(t, err)
nickname := "GoodNickname"
diff --git a/internal/handler/wallet/api.go b/internal/handler/wallet/api.go
index 3fd8c1709..d73d7ff6e 100644
--- a/internal/handler/wallet/api.go
+++ b/internal/handler/wallet/api.go
@@ -5,15 +5,14 @@ import (
"fmt"
"net/http"
- "github.com/bluele/gcache"
"github.com/go-openapi/runtime/middleware"
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
"github.com/massalabs/station-massa-wallet/pkg/assets"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
"github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/massalabs/station-massa-wallet/pkg/openapi"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
- "github.com/massalabs/station-massa-wallet/pkg/wallet"
walletpkg "github.com/massalabs/station-massa-wallet/pkg/wallet"
"github.com/massalabs/station-massa-wallet/pkg/wallet/account"
"github.com/massalabs/station/pkg/logger"
@@ -21,28 +20,33 @@ import (
// AppendEndpoints appends wallet endpoints to the API
// Note: the password prompter is mandatory for sign endpoint
-func AppendEndpoints(api *operations.MassaWalletAPI, prompterApp prompt.WalletPrompterInterface, massaClient network.NodeFetcherInterface, AssetsStore *assets.AssetsStore, gc gcache.Cache) {
+func AppendEndpoints(api *operations.MassaWalletAPI, prompterApp prompt.WalletPrompterInterface, massaClient network.NodeFetcherInterface) {
+ gc := cache.Get()
+ assetStore := assets.Store
api.CreateAccountHandler = NewCreateAccount(prompterApp, massaClient)
api.DeleteAccountHandler = NewDelete(prompterApp, massaClient)
api.ImportAccountHandler = NewImport(prompterApp, massaClient)
api.AccountListHandler = NewGetAll(prompterApp.App().Wallet, massaClient)
- api.SignHandler = NewSign(prompterApp, gc, AssetsStore)
+ api.SignHandler = NewSign(prompterApp, gc, assetStore)
api.SignMessageHandler = NewSignMessage(prompterApp, gc)
api.GetAccountHandler = NewGet(prompterApp, massaClient)
api.ExportAccountFileHandler = NewWalletExportFile(prompterApp.App().Wallet)
- api.TransferCoinHandler = NewTransferCoin(prompterApp, massaClient)
+ api.TransferCoinHandler = NewTransferCoin(prompterApp, massaClient, gc)
api.TradeRollsHandler = NewTradeRolls(prompterApp, massaClient)
api.BackupAccountHandler = NewBackupAccount(prompterApp)
api.UpdateAccountHandler = NewUpdateAccount(prompterApp, massaClient)
- api.AddAssetHandler = NewAddAsset(AssetsStore, massaClient)
- api.GetAllAssetsHandler = NewGetAllAssets(prompterApp.App().Wallet, AssetsStore, massaClient)
- api.DeleteAssetHandler = NewDeleteAsset(AssetsStore)
+ api.AddAssetHandler = NewAddAsset(assetStore, massaClient)
+ api.GetAllAssetsHandler = NewGetAllAssets(prompterApp.App().Wallet, assetStore, massaClient)
+ api.DeleteAssetHandler = NewDeleteAsset(assetStore)
api.GetConfigHandler = NewGetConfig()
+ api.AddSignRuleHandler = NewAddSignRuleHandler(prompterApp, gc)
+ api.DeleteSignRuleHandler = NewDeleteSignRuleHandler(prompterApp, gc)
+ api.UpdateSignRuleHandler = NewUpdateSignRuleHandler(prompterApp, gc)
}
// loadAccount loads a wallet from the file system or returns an error.
// Here it is acceptable to return a middleware.Responder to simplify the code.
-func loadAccount(wallet *wallet.Wallet, nickname string) (*account.Account, middleware.Responder) {
+func loadAccount(wallet *walletpkg.Wallet, nickname string) (*account.Account, middleware.Responder) {
acc, err := wallet.GetAccount(nickname)
if err == nil {
return acc, nil
diff --git a/internal/handler/wallet/api_test.go b/internal/handler/wallet/api_test.go
index cb6d788ac..edf7bdb52 100644
--- a/internal/handler/wallet/api_test.go
+++ b/internal/handler/wallet/api_test.go
@@ -6,6 +6,7 @@ import (
"net/http"
"net/http/httptest"
"os"
+ "path/filepath"
"strings"
"testing"
@@ -15,10 +16,11 @@ import (
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
"github.com/massalabs/station-massa-wallet/pkg/assets"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
"github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/network"
- "github.com/massalabs/station-massa-wallet/pkg/prompt"
"github.com/massalabs/station-massa-wallet/pkg/wallet"
+ "github.com/massalabs/station/pkg/logger"
"github.com/stretchr/testify/assert"
)
@@ -37,15 +39,21 @@ type PrivateKeyPrompt struct {
Err error
}
+var (
+ prompterAppMock *walletPrompterMock
+ testAssetStore *assets.AssetsStore
+ testCache gcache.Cache
+)
+
// MockAPI mocks the wallet API.
// All the wallet endpoints are mocked. You can use the Prompt channel to drive the password entry expected values.
-func MockAPI() (*operations.MassaWalletAPI, prompt.WalletPrompterInterface, *assets.AssetsStore, chan walletapp.EventData, error) {
+func MockAPI() (*operations.MassaWalletAPI, chan walletapp.EventData, error) {
os.Setenv("STANDALONE", "1")
// Load the Swagger specification
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, nil, err
}
// Create a new MassaWalletAPI instance
massaWalletAPI := operations.NewMassaWalletAPI(swaggerSpec)
@@ -59,31 +67,38 @@ func MockAPI() (*operations.MassaWalletAPI, prompt.WalletPrompterInterface, *ass
wallet, err := wallet.New(walletPath)
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, nil, err
}
- prompterApp := NewWalletPrompterMock(walletapp.NewWalletApp(wallet), resultChannel)
+ if err := logger.InitializeGlobal(filepath.Join(walletPath, "unit-test.log")); err != nil {
+ log.Fatalf("while initializing global logger: %s", err.Error())
+ }
+
+ prompterAppMock = NewWalletPrompterMock(walletapp.NewWalletApp(wallet), resultChannel)
nodeFetcher := network.NewNodeFetcher()
- AssetsStore, err := assets.NewAssetsStore(walletPath, nodeFetcher)
+ testAssetStore, err = assets.InitAssetsStore(walletPath, nodeFetcher)
if err != nil {
log.Fatalf("Failed to create AssetsStore: %v", err)
}
massaNodeMock := NewNodeFetcherMock()
- // Load config file
+ // Load config file with config file path override
+ config.SetConfigFileDirOverride(walletPath)
config.Load()
+ testCache = cache.Get()
+
// Set wallet API endpoints
- AppendEndpoints(massaWalletAPI, prompterApp, massaNodeMock, AssetsStore, gcache.New(20).LRU().Build())
+ AppendEndpoints(massaWalletAPI, prompterAppMock, massaNodeMock)
// instantiates the server configure its API.
server := restapi.NewServer(massaWalletAPI)
server.ConfigureAPI()
- return massaWalletAPI, prompterApp, AssetsStore, resultChannel, err
+ return massaWalletAPI, resultChannel, err
}
// processHTTPRequest simulates the processing of an HTTP request on the given API.
diff --git a/internal/handler/wallet/backup_test.go b/internal/handler/wallet/backup_test.go
index d407585ff..5e5641e8c 100644
--- a/internal/handler/wallet/backup_test.go
+++ b/internal/handler/wallet/backup_test.go
@@ -25,12 +25,12 @@ func backupWallet(t *testing.T, api *operations.MassaWalletAPI, nickname string)
}
func Test_walletBackupAccount_Handle(t *testing.T) {
- api, prompterApp, _, resChan, err := MockAPI()
+ api, resChan, err := MockAPI()
assert.NoError(t, err)
nickname := "walletToBackup"
password := "zePassword"
- acc := createAccount(password, nickname, t, prompterApp)
+ acc := createAccount(password, nickname, t, prompterAppMock)
t.Run("invalid nickname", func(t *testing.T) {
resp := backupWallet(t, api, "toto")
@@ -39,7 +39,7 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
t.Run("export canceled by user", func(t *testing.T) {
go func() {
- prompterApp.App().CtrlChan <- walletapp.Cancel
+ prompterAppMock.App().CtrlChan <- walletapp.Cancel
}()
resp := backupWallet(t, api, nickname)
@@ -50,7 +50,7 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
testResult := make(chan walletapp.EventData)
go func() {
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.YamlFileBackup),
}
@@ -68,11 +68,11 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
t.Run("chose private backup then cancel", func(t *testing.T) {
go func() {
// send backup method
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.PrivateKeyBackup),
}
- prompterApp.App().CtrlChan <- walletapp.Cancel
+ prompterAppMock.App().CtrlChan <- walletapp.Cancel
}()
resp := backupWallet(t, api, nickname)
@@ -84,12 +84,12 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
go func() {
// send backup method
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.PrivateKeyBackup),
}
// send password
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: "wrong password",
}
@@ -99,7 +99,7 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
checkResultChannel(t, result, false, utils.WrongPassword)
// send password
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: password,
}
@@ -126,12 +126,12 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
t.Run("backup private key, wrong password and cancel", func(t *testing.T) {
go func() {
// send backup method
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.PrivateKeyBackup),
}
// send password
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: "wrong password",
}
@@ -140,7 +140,7 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
checkResultChannel(t, result, false, utils.WrongPassword)
- prompterApp.App().CtrlChan <- walletapp.Cancel
+ prompterAppMock.App().CtrlChan <- walletapp.Cancel
}()
resp := backupWallet(t, api, nickname)
@@ -152,12 +152,12 @@ func Test_walletBackupAccount_Handle(t *testing.T) {
go func() {
// send backup method
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: string(prompt.PrivateKeyBackup),
}
// send password
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: password,
}
diff --git a/internal/handler/wallet/cache.go b/internal/handler/wallet/cache.go
new file mode 100644
index 000000000..a19c900d9
--- /dev/null
+++ b/internal/handler/wallet/cache.go
@@ -0,0 +1,155 @@
+package wallet
+
+import (
+ "crypto/ed25519"
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/awnumar/memguard"
+ "github.com/bluele/gcache"
+ "github.com/massalabs/station-massa-wallet/pkg/utils"
+ "github.com/massalabs/station-massa-wallet/pkg/wallet/account"
+ "lukechampine.com/blake3"
+)
+
+const (
+ defaultExpirationTime = time.Hour * 24 * 30
+ cacheKeyPrefix = "pkey"
+ notFoundMsg = "private key not found in cache"
+)
+
+func CachePrivateKeyFromPassword(gc gcache.Cache, account *account.Account, password *memguard.LockedBuffer) error {
+ privateKey, err := account.PrivateKeyBytesInClear(password)
+ if err != nil {
+ return fmt.Errorf("error caching private key: %w", err)
+ }
+
+ err = CachePrivateKey(gc, account, privateKey)
+ if err != nil {
+ return fmt.Errorf("error caching private key: %w", err)
+ }
+
+ return nil
+}
+
+func CachePrivateKey(gc gcache.Cache, account *account.Account, privateKey *memguard.LockedBuffer) error {
+ cacheKey, err := privateKeyCacheKey(account)
+ if err != nil {
+ return fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
+ }
+
+ key := KeyHash([]byte(cacheKey))
+
+ // Concatenate the key with itself to make it 64 bytes long
+ var extendedKey [64]byte
+ copy(extendedKey[:], append(key[:], key[:]...))
+
+ cipheredPrivateKey, err := Xor(privateKey, extendedKey)
+ if err != nil {
+ return fmt.Errorf("%w: %w", utils.ErrCache, err)
+ }
+
+ cacheValue := make([]byte, ed25519.PrivateKeySize)
+ copy(cacheValue, cipheredPrivateKey.Bytes())
+ cipheredPrivateKey.Destroy()
+
+ err = gc.SetWithExpire(key, cacheValue, expirationDuration())
+ if err != nil {
+ return fmt.Errorf("error set private key in cache: %w", err)
+ }
+
+ return nil
+}
+
+// privateKeyFromCache return the private key from the cache or an error.
+func privateKeyFromCache(
+ gc gcache.Cache,
+ acc *account.Account,
+) (*memguard.LockedBuffer, error) {
+ cacheKey, err := privateKeyCacheKey(acc)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
+ }
+
+ keyHash := KeyHash([]byte(cacheKey))
+
+ value, err := gc.Get(keyHash)
+ if err != nil {
+ if err.Error() == gcache.KeyNotFoundError.Error() {
+ return nil, fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
+ }
+
+ return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
+ }
+
+ if value == nil {
+ return nil, fmt.Errorf("%w: %s", utils.ErrCache, notFoundMsg)
+ }
+
+ byteValue, ok := value.([]byte)
+ if !ok {
+ return nil, fmt.Errorf("%w: %s", utils.ErrCache, "value is not a byte array")
+ }
+
+ cacheValue := make([]byte, ed25519.PrivateKeySize)
+ copy(cacheValue, byteValue)
+
+ cipheredPrivateKey := memguard.NewBufferFromBytes(cacheValue)
+
+ // Concatenate the key with itself to make it 64 bytes long
+ var extendedKey [64]byte
+ copy(extendedKey[:], append(keyHash[:], keyHash[:]...))
+
+ privateKey, err := Xor(cipheredPrivateKey, extendedKey)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
+ }
+
+ cipheredPrivateKey.Destroy()
+
+ return privateKey, nil
+}
+
+func Xor(pkey *memguard.LockedBuffer, cacheKeyHash [64]byte) (*memguard.LockedBuffer, error) {
+ a := pkey.Bytes()
+
+ if len(a) != len(cacheKeyHash) {
+ return nil, fmt.Errorf("length of two arrays must be same, %d and %d", len(a), len(cacheKeyHash))
+ }
+ result := make([]byte, len(a))
+
+ for i := 0; i < len(a); i++ {
+ result[i] = a[i] ^ cacheKeyHash[i]
+ }
+
+ return memguard.NewBufferFromBytes(result), nil
+}
+
+func privateKeyCacheKey(account *account.Account) ([]byte, error) {
+ address, err := account.Address.String()
+ if err != nil {
+ return []byte(""), fmt.Errorf("err: %w", err)
+ }
+
+ return []byte(cacheKeyPrefix + address), nil
+}
+
+func KeyHash(cacheKeyHash []byte) [32]byte {
+ return blake3.Sum256(cacheKeyHash)
+}
+
+func expirationDuration() time.Duration {
+ fromEnv := os.Getenv("PKEY_CACHE_EXPIRATION_TIME")
+
+ if fromEnv == "" {
+ return defaultExpirationTime
+ }
+
+ duration, err := time.ParseDuration(fromEnv)
+ if err != nil {
+ return defaultExpirationTime
+ }
+
+ return duration
+}
diff --git a/internal/handler/wallet/create_test.go b/internal/handler/wallet/create_test.go
index 5399a1402..fa9a6a7b2 100644
--- a/internal/handler/wallet/create_test.go
+++ b/internal/handler/wallet/create_test.go
@@ -14,7 +14,7 @@ import (
)
func Test_walletCreate_Handle(t *testing.T) {
- api, prompterApp, _, resChan, err := MockAPI()
+ api, resChan, err := MockAPI()
if err != nil {
panic(err)
}
@@ -41,9 +41,9 @@ func Test_walletCreate_Handle(t *testing.T) {
go func(res chan walletapp.EventData) {
if test.password == "cancel" {
// Send cancel to prompter app to unlock the handler
- prompterApp.App().CtrlChan <- walletapp.Cancel
+ prompterAppMock.App().CtrlChan <- walletapp.Cancel
} else {
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: test.password,
}
@@ -60,7 +60,7 @@ func Test_walletCreate_Handle(t *testing.T) {
checkResultChannel(t, result, true, "")
- assertWallet(t, prompterApp.App().Wallet, nickname)
+ assertWallet(t, prompterAppMock.App().Wallet, nickname)
}
}
}
diff --git a/internal/handler/wallet/delete_asset_test.go b/internal/handler/wallet/delete_asset_test.go
index ecef7d688..98a5d0cae 100644
--- a/internal/handler/wallet/delete_asset_test.go
+++ b/internal/handler/wallet/delete_asset_test.go
@@ -10,12 +10,12 @@ import (
)
func TestAddGetDeleteAsset(t *testing.T) {
- api, prompterApp, _, _, err := MockAPI()
+ api, _, err := MockAPI()
assert.NoError(t, err)
nickname := "GoodNickname"
password := "zePassword"
- createAccount(password, nickname, t, prompterApp)
+ createAccount(password, nickname, t, prompterAppMock)
// Create the test wallet first
diff --git a/internal/handler/wallet/delete_test.go b/internal/handler/wallet/delete_test.go
index dc90e2ad0..b0fe362fd 100644
--- a/internal/handler/wallet/delete_test.go
+++ b/internal/handler/wallet/delete_test.go
@@ -23,14 +23,14 @@ func deleteWallet(t *testing.T, api *operations.MassaWalletAPI, nickname string)
}
func Test_walletDelete_Handle(t *testing.T) {
- api, prompterApp, _, resChan, err := MockAPI()
+ api, resChan, err := MockAPI()
assert.NoError(t, err)
testResult := make(chan walletapp.EventData)
nickname := "walletToDelete"
password := "zePassword"
- createAccount(password, nickname, t, prompterApp)
+ createAccount(password, nickname, t, prompterAppMock)
t.Run("invalid nickname", func(t *testing.T) {
resp := deleteWallet(t, api, "toto")
@@ -40,7 +40,7 @@ func Test_walletDelete_Handle(t *testing.T) {
t.Run("invalid password", func(t *testing.T) {
// Send password to prompter app and wait for result
go func() {
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: "invalid password",
}
@@ -50,7 +50,7 @@ func Test_walletDelete_Handle(t *testing.T) {
checkResultChannel(t, failRes, false, utils.WrongPassword)
// Send cancel to prompter app to unlock the handler
- prompterApp.App().CtrlChan <- walletapp.Cancel
+ prompterAppMock.App().CtrlChan <- walletapp.Cancel
}()
resp := deleteWallet(t, api, nickname)
@@ -60,7 +60,7 @@ func Test_walletDelete_Handle(t *testing.T) {
t.Run("canceled by user", func(t *testing.T) {
go func() {
// Send wrong password to prompter app and wait for result
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: "this is not the password",
}
@@ -70,21 +70,21 @@ func Test_walletDelete_Handle(t *testing.T) {
checkResultChannel(t, failRes, false, utils.WrongPassword)
// Send cancel to prompter app to unlock the handler
- prompterApp.App().CtrlChan <- walletapp.Cancel
+ prompterAppMock.App().CtrlChan <- walletapp.Cancel
}()
resp := deleteWallet(t, api, nickname)
verifyStatusCode(t, resp, http.StatusUnauthorized)
- _, err = prompterApp.App().Wallet.GetAccount(nickname)
+ _, err = prompterAppMock.App().Wallet.GetAccount(nickname)
assert.NoError(t, err)
})
t.Run("delete success", func(t *testing.T) {
// Send password to prompter app and wait for result
go func() {
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: password,
}
@@ -100,7 +100,7 @@ func Test_walletDelete_Handle(t *testing.T) {
checkResultChannel(t, result, true, "")
- _, err = prompterApp.App().Wallet.GetAccount(nickname)
+ _, err = prompterAppMock.App().Wallet.GetAccount(nickname)
assert.Error(t, err, "Wallet should have been deleted")
})
diff --git a/internal/handler/wallet/errors.go b/internal/handler/wallet/errors.go
index 36e710de0..6f170fa0a 100644
--- a/internal/handler/wallet/errors.go
+++ b/internal/handler/wallet/errors.go
@@ -32,4 +32,9 @@ const (
errorAssetNotFound
errorTradeRoll
internalError
+ errorAddSignRule
+ errorDeleteSignRule
+ errorUpdateSignRule
+ errorCachePrivateKey
+ errorGetAccountConfig
)
diff --git a/internal/handler/wallet/export_test.go b/internal/handler/wallet/export_test.go
index b803e50c1..2d731397c 100644
--- a/internal/handler/wallet/export_test.go
+++ b/internal/handler/wallet/export_test.go
@@ -10,7 +10,8 @@ import (
)
func Test_exportFileWallet_handler(t *testing.T) {
- api, prompterApp, _, _, err := MockAPI()
+ api, _, err := MockAPI()
+
assert.NoError(t, err)
handler, exist := api.HandlerFor("get", "/api/accounts/{nickname}/exportFile")
@@ -27,7 +28,7 @@ func Test_exportFileWallet_handler(t *testing.T) {
t.Run("Export file of created wallet", func(t *testing.T) {
nickname := "trololol"
password := "zePassword"
- createAccount(password, nickname, t, prompterApp)
+ createAccount(password, nickname, t, prompterAppMock)
resp, err := handleHTTPRequest(handler, "GET", fmt.Sprintf("/api/accounts/%s/exportFile", nickname), "")
assert.NoError(t, err)
diff --git a/internal/handler/wallet/get_all_assets_test.go b/internal/handler/wallet/get_all_assets_test.go
index 88c31898b..c8df4c46d 100644
--- a/internal/handler/wallet/get_all_assets_test.go
+++ b/internal/handler/wallet/get_all_assets_test.go
@@ -15,12 +15,12 @@ import (
)
func TestGetAllAssetsHandler(t *testing.T) {
- api, prompterApp, _, _, err := MockAPI()
+ api, _, err := MockAPI()
assert.NoError(t, err)
nickname := "GoodNickname"
password := "zePassword"
- createAccount(password, nickname, t, prompterApp)
+ createAccount(password, nickname, t, prompterAppMock)
// Get the assetsWithBalance
assetsWithBalance := getAssets(t, api, nickname)
@@ -66,7 +66,7 @@ func getExpectedAssetsCount(t *testing.T) int {
tempDir, err := os.MkdirTemp(os.TempDir(), "*-wallet-dir")
assert.NoError(t, err)
nodeFetcher := network.NewNodeFetcher()
- store, err := assets.NewAssetsStore(tempDir, nodeFetcher)
+ store, err := assets.InitAssetsStore(tempDir, nodeFetcher)
assert.NoError(t, err)
defaultAssets, err := store.Default()
assert.NoError(t, err)
diff --git a/internal/handler/wallet/get_config.go b/internal/handler/wallet/get_config.go
index 952b68321..5f4769cd5 100644
--- a/internal/handler/wallet/get_config.go
+++ b/internal/handler/wallet/get_config.go
@@ -35,9 +35,9 @@ func newConfigModel(cfg *config.Config) (*models.Config, error) {
modelSignRules := make([]*models.SignRule, len(accountConfig.SignRules))
for i, rule := range accountConfig.SignRules {
modelSignRules[i] = &models.SignRule{
- Contract: &rule.Contract,
- PasswordPrompt: &rule.PasswordPrompt,
- AutoSign: &rule.AutoSign,
+ Contract: &rule.Contract,
+ Enabled: &rule.Enabled,
+ RuleType: (*string)(&rule.RuleType),
}
}
modelAccounts[nickname] = models.AccountConfig{
diff --git a/internal/handler/wallet/get_test.go b/internal/handler/wallet/get_test.go
index 32ec9f49f..1bb2100b7 100644
--- a/internal/handler/wallet/get_test.go
+++ b/internal/handler/wallet/get_test.go
@@ -14,10 +14,10 @@ import (
)
func Test_getWallets_handler(t *testing.T) {
- api, prompterApp, _, _, err := MockAPI()
+ api, _, err := MockAPI()
assert.NoError(t, err)
- wallet.ClearAccounts(t, prompterApp.App().Wallet.WalletPath)
+ wallet.ClearAccounts(t, prompterAppMock.App().Wallet.WalletPath)
// test empty configuration first.
t.Run("Get empty list", func(t *testing.T) {
@@ -38,7 +38,7 @@ func Test_getWallets_handler(t *testing.T) {
nicknames := []string{"account1", "account2", "account3"}
for _, nickname := range nicknames {
- createAccount(password, nickname, t, prompterApp)
+ createAccount(password, nickname, t, prompterAppMock)
}
t.Run("Get multiple accounts", func(t *testing.T) {
@@ -58,17 +58,17 @@ func Test_getWallets_handler(t *testing.T) {
assertAccountsBody(t, resp, true)
})
- wallet.ClearAccounts(t, prompterApp.App().Wallet.WalletPath)
+ wallet.ClearAccounts(t, prompterAppMock.App().Wallet.WalletPath)
}
func Test_getWallet_handler(t *testing.T) {
nickname := "trololol"
password := "zePassword"
- api, prompterApp, _, resChan, err := MockAPI()
+ api, resChan, err := MockAPI()
assert.NoError(t, err)
- createAccount(password, nickname, t, prompterApp)
+ createAccount(password, nickname, t, prompterAppMock)
handler, exist := api.HandlerFor("get", "/api/accounts/{nickname}")
assert.True(t, exist, "Endpoint doesn't exist")
@@ -88,7 +88,7 @@ func Test_getWallet_handler(t *testing.T) {
assertAccountBody(t, resp, nickname, true)
- wallet.ClearAccounts(t, prompterApp.App().Wallet.WalletPath)
+ wallet.ClearAccounts(t, prompterAppMock.App().Wallet.WalletPath)
})
// test with un-ciphered data.
@@ -96,7 +96,7 @@ func Test_getWallet_handler(t *testing.T) {
testResult := make(chan walletapp.EventData)
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: password,
}
diff --git a/internal/handler/wallet/import_test.go b/internal/handler/wallet/import_test.go
index 7f7ee3969..bcde3f7e2 100644
--- a/internal/handler/wallet/import_test.go
+++ b/internal/handler/wallet/import_test.go
@@ -27,7 +27,7 @@ func importWallet(t *testing.T, api *operations.MassaWalletAPI) *httptest.Respon
}
func Test_walletImport_Handle(t *testing.T) {
- api, prompterApp, _, resChan, err := MockAPI()
+ api, resChan, err := MockAPI()
assert.NoError(t, err)
t.Run("import wallet file", func(t *testing.T) {
@@ -58,7 +58,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
// Send filepath to prompter app and wait for result
go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: filePath,
}
@@ -74,7 +74,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
checkResultChannel(t, result, true, "")
- assertWallet(t, prompterApp.App().Wallet, nickname)
+ assertWallet(t, prompterAppMock.App().Wallet, nickname)
os.Remove(filePath)
})
@@ -95,7 +95,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
// Send filepath to prompter app and wait for result
go func(res chan walletapp.EventData) {
// Send invalid file to prompter app and wait for result
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: filePath,
}
@@ -140,7 +140,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
// Send filepath to prompter app and wait for result
go func(res chan walletapp.EventData) {
// Send invalid filename to prompter app and wait for result
- prompterApp.App().PromptInput <- &walletapp.StringPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
BaseMessage: walletapp.BaseMessage{},
Message: filePath,
}
@@ -201,7 +201,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
testResult := make(chan walletapp.EventData)
go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.ImportPKeyPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.ImportPKeyPromptInput{
BaseMessage: walletapp.BaseMessage{},
PrivateKey: tt.privateKey,
Nickname: tt.nickname,
@@ -217,7 +217,7 @@ PublicKey: [0, 164, 243, 44, 155, 204, 6, 20, 131, 218, 97, 32, 58, 224, 189, 41
checkResultChannel(t, result, tt.wantResult.Success, tt.wantResult.CodeMessage)
if tt.wantResult.Success {
- assertWallet(t, prompterApp.App().Wallet, tt.nickname)
+ assertWallet(t, prompterAppMock.App().Wallet, tt.nickname)
}
})
}
diff --git a/internal/handler/wallet/prompt_utils.go b/internal/handler/wallet/prompt_utils.go
new file mode 100644
index 000000000..dbbeb3086
--- /dev/null
+++ b/internal/handler/wallet/prompt_utils.go
@@ -0,0 +1,45 @@
+package wallet
+
+import (
+ "fmt"
+
+ "github.com/awnumar/memguard"
+ walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/prompt"
+ "github.com/massalabs/station-massa-wallet/pkg/utils"
+ "github.com/massalabs/station-massa-wallet/pkg/wallet/account"
+)
+
+func PromptPassword(prompterApp prompt.WalletPrompterInterface, acc *account.Account, promptRequest *prompt.PromptRequest) (*memguard.LockedBuffer, error) {
+ promptOutput, err := prompt.WakeUpPrompt(prompterApp, *promptRequest, acc)
+ if err != nil {
+ return nil, fmt.Errorf("prompting password: %w", err)
+ }
+
+ output, ok := promptOutput.(*memguard.LockedBuffer)
+ if !ok {
+ return nil, fmt.Errorf("prompting password: %s", utils.ErrInvalidInputType.Error())
+ }
+
+ prompterApp.EmitEvent(walletapp.PromptResultEvent,
+ walletapp.EventData{Success: true})
+
+ return output, nil
+}
+
+func PromptForOperation(prompterApp prompt.WalletPrompterInterface, acc *account.Account, promptRequest *prompt.PromptRequest) (*walletapp.SignPromptOutput, error) {
+ promptOutput, err := prompt.WakeUpPrompt(prompterApp, *promptRequest, acc)
+ if err != nil {
+ return nil, fmt.Errorf("prompting password: %w", err)
+ }
+
+ output, ok := promptOutput.(*walletapp.SignPromptOutput)
+ if !ok {
+ return nil, fmt.Errorf("prompting password: %s", utils.ErrInvalidInputType.Error())
+ }
+
+ prompterApp.EmitEvent(walletapp.PromptResultEvent,
+ walletapp.EventData{Success: true})
+
+ return output, nil
+}
diff --git a/internal/handler/wallet/rolls.go b/internal/handler/wallet/rolls.go
index 7e183dc17..e32d40ffc 100644
--- a/internal/handler/wallet/rolls.go
+++ b/internal/handler/wallet/rolls.go
@@ -66,7 +66,7 @@ func (t *tradeRolls) Handle(params operations.TradeRollsParams) middleware.Respo
promptRequest := prompt.PromptRequest{
Action: walletapp.Sign,
- Data: PromptRequestSignData{
+ Data: prompt.PromptRequestSignData{
Fees: strconv.FormatUint(fee, 10),
MinFees: minimalFees,
WalletAddress: string(addressBytes),
diff --git a/internal/handler/wallet/rolls_test.go b/internal/handler/wallet/rolls_test.go
index 2d598ba81..37d57c3c0 100644
--- a/internal/handler/wallet/rolls_test.go
+++ b/internal/handler/wallet/rolls_test.go
@@ -10,7 +10,7 @@ import (
)
func Test_traderolls_handler(t *testing.T) {
- api, prompterApp, _, resChan, err := MockAPI()
+ api, resChan, err := MockAPI()
assert.NoError(t, err)
handler, exist := api.HandlerFor("post", "/api/accounts/{nickname}/rolls")
@@ -18,7 +18,7 @@ func Test_traderolls_handler(t *testing.T) {
nickname := "wallet1"
password := "password"
- createAccount(password, nickname, t, prompterApp)
+ createAccount(password, nickname, t, prompterAppMock)
t.Run("Trade rolls with unprocessable entity", func(t *testing.T) {
resp, err := handleHTTPRequest(handler, "POST", fmt.Sprintf("/api/accounts/%s/rolls", nickname), "")
@@ -84,7 +84,7 @@ func Test_traderolls_handler(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
@@ -111,7 +111,7 @@ func Test_traderolls_handler(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
diff --git a/internal/handler/wallet/sign.go b/internal/handler/wallet/sign.go
index f7f94e61c..88195a2a3 100644
--- a/internal/handler/wallet/sign.go
+++ b/internal/handler/wallet/sign.go
@@ -1,13 +1,10 @@
package wallet
import (
- "crypto/ed25519"
"encoding/binary"
"fmt"
"net/http"
- "os"
"strconv"
- "time"
"github.com/awnumar/memguard"
"github.com/bluele/gcache"
@@ -16,10 +13,13 @@ import (
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
"github.com/massalabs/station-massa-wallet/pkg/assets"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
"github.com/massalabs/station-massa-wallet/pkg/utils"
"github.com/massalabs/station-massa-wallet/pkg/wallet/account"
+ "github.com/massalabs/station/pkg/logger"
"github.com/massalabs/station/pkg/node/sendoperation"
"github.com/massalabs/station/pkg/node/sendoperation/buyrolls"
"github.com/massalabs/station/pkg/node/sendoperation/callsc"
@@ -28,39 +28,12 @@ import (
"github.com/massalabs/station/pkg/node/sendoperation/transaction"
onchain "github.com/massalabs/station/pkg/onchain"
"github.com/pkg/errors"
- "lukechampine.com/blake3"
)
const (
- defaultExpirationTime = time.Hour * 24 * 30
- RollPrice = 100
- cacheKeyPrefix = "pkey"
+ RollPrice = 100
)
-type PromptRequestSignData struct {
- Description string
- Fees string
- MinFees string
- OperationType int
- Coins string
- Address string
- Function string
- MaxCoins string // for ExecuteSC
- WalletAddress string
- Nickname string
- RollCount uint64
- RecipientAddress string
- RecipientNickname string
- Amount string
- PlainText string
- AllowFeeEdition bool
- ChainID int64
- Assets []models.AssetInfo
- Parameters []byte
- DeployedByteCodeSize uint // for executeSC of type deploySC
- DeployedCoins uint64 // for executeSC of type DeploySC; the number of coins sent to the deployed contract
-}
-
func NewSign(prompterApp prompt.WalletPrompterInterface, gc gcache.Cache, AssetsStore *assets.AssetsStore) operations.SignHandler {
return &walletSign{gc: gc, prompterApp: prompterApp, AssetsStore: AssetsStore}
}
@@ -77,30 +50,43 @@ func (w *walletSign) Handle(params operations.SignParams) middleware.Responder {
return errResp
}
- promptRequest, fees, err := w.getPromptRequest(params, acc, params.Body.Description, *params.Body.ChainID)
+ promptRequest, fees, err := w.getPromptRequest(params, acc)
if err != nil {
return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorSignDecodeMessage, http.StatusBadRequest)
}
- var privateKey *memguard.LockedBuffer
+ cfg := config.Get()
- // accountCfg, err := config.GetAccountConfig(params.Nickname)
+ var contract *string
- if err != nil {
- // todo
- // todo: handle cache expired error
- pk, err := w.privateKeyFromCache(acc)
- if err != nil {
- if errors.Is(err, errors.New("errtodo")) {
- return newErrorResponse(err.Error(), "errtodo", http.StatusNotFound)
- }
+ promptData, ok := promptRequest.Data.(prompt.PromptRequestSignData)
+ if ok {
+ contract = &promptData.Address
+ }
+
+ enabledRule := cfg.GetEnabledRuleForContract(acc.Nickname, contract)
+
+ var privateKey *memguard.LockedBuffer
- return newErrorResponse(err.Error(), errorSign, http.StatusInternalServerError)
+ if enabledRule != nil {
+ // at this point, we have a rule enabled for the contract, if private key is cached, we don't need to prompt for password
+ privateKey, err = cache.PrivateKeyFromCache(w.gc, acc)
+ if err != nil {
+ logger.Warn("error retriving private key from cache: ", err)
+ } else if privateKey != nil {
+ // If privatekey is cached, we don't need to prompt for password
+ promptRequest.PasswordRequired = false
}
- privateKey = pk
- } else {
- output, err := w.PromptPassword(acc, promptRequest)
+ }
+
+ isDisablePasswordRuleEnabled := enabledRule != nil && *enabledRule == config.RuleTypeDisablePasswordPrompt
+
+ if promptRequest.PasswordRequired || isDisablePasswordRuleEnabled {
+ promptData.EnabledSignRule = enabledRule
+ promptRequest.Data = promptData
+
+ output, err := PromptForOperation(w.prompterApp, acc, promptRequest)
if err != nil {
msg := fmt.Sprintf("Unable to unprotect wallet: %s", err.Error())
if errors.Is(err, utils.ErrWrongPassword) || errors.Is(err, utils.ErrActionCanceled) {
@@ -112,17 +98,19 @@ func (w *walletSign) Handle(params operations.SignParams) middleware.Responder {
fees = output.Fees
- pk, err := acc.PrivateKeyBytesInClear(output.Password)
- if err != nil {
- return newErrorResponse(err.Error(), errorWrongPassword, http.StatusInternalServerError)
+ if privateKey == nil {
+ if output.Password != nil {
+ privateKey, err = acc.PrivateKeyBytesInClear(output.Password)
+ if err != nil {
+ return newErrorResponse(err.Error(), errorWrongPassword, http.StatusInternalServerError)
+ }
+ }
}
- privateKey = pk
-
- if false { // todo
- err := w.CacheAccount(acc, privateKey)
+ if cfg.HasEnabledRule(acc.Nickname) {
+ err = cache.CachePrivateKey(w.gc, acc, privateKey)
if err != nil {
- return newErrorResponse(err.Error(), "errtodo", http.StatusInternalServerError)
+ return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
}
}
}
@@ -141,45 +129,6 @@ func (w *walletSign) Handle(params operations.SignParams) middleware.Responder {
return w.Success(acc, signature, operation)
}
-func (w *walletSign) PromptPassword(acc *account.Account, promptRequest *prompt.PromptRequest) (*walletapp.SignPromptOutput, error) {
- promptOutput, err := prompt.WakeUpPrompt(w.prompterApp, *promptRequest, acc)
- if err != nil {
- return nil, fmt.Errorf("prompting password: %w", err)
- }
-
- output, ok := promptOutput.(*walletapp.SignPromptOutput)
- if !ok {
- return nil, fmt.Errorf("prompting password for sign: %s", utils.ErrInvalidInputType.Error())
- }
-
- w.prompterApp.EmitEvent(walletapp.PromptResultEvent,
- walletapp.EventData{Success: true})
-
- return output, nil
-}
-
-func (w *walletSign) CacheAccount(acc *account.Account, privateKey *memguard.LockedBuffer) error {
- cacheKey := cacheKeyPrefix + acc.Nickname
-
- key := KeyHash([]byte(cacheKey))
-
- cipheredPrivateKey, err := Xor(privateKey, key)
- if err != nil {
- return fmt.Errorf("Error cannot XOR private key: %w", err)
- }
-
- cacheValue := make([]byte, ed25519.PrivateKeySize)
- copy(cacheValue, cipheredPrivateKey.Bytes())
- cipheredPrivateKey.Destroy()
-
- err = w.gc.SetWithExpire(key, cacheValue, expirationDuration())
- if err != nil {
- return fmt.Errorf("Error set private key in cache: %w", err)
- }
-
- return nil
-}
-
func (w *walletSign) Success(acc *account.Account, signature []byte, operation []byte) middleware.Responder {
publicKeyBytes, err := acc.PublicKey.MarshalText()
if err != nil {
@@ -214,7 +163,7 @@ func prepareOperation(acc *account.Account, fees uint64, operationB64 string, ch
publicKey, err := acc.PublicKey.MarshalBinary()
if err != nil {
- return nil, nil, fmt.Errorf("Unable to marshal public key: %w", err)
+ return nil, nil, fmt.Errorf("unable to marshal public key: %w", err)
}
msgToSign = utils.PrepareSignData(uint64(chainID), append(publicKey, msgToSign...))
@@ -222,7 +171,7 @@ func prepareOperation(acc *account.Account, fees uint64, operationB64 string, ch
return operation, msgToSign, nil
}
-func (w *walletSign) getPromptRequest(params operations.SignParams, acc *account.Account, description string, chainID int64) (*prompt.PromptRequest, uint64, error) {
+func (w *walletSign) getPromptRequest(params operations.SignParams, acc *account.Account) (*prompt.PromptRequest, uint64, error) {
msgToSign := params.Body.Operation.String()
decodedMsg, fees, _, err := sendoperation.DecodeMessage64(msgToSign)
@@ -235,7 +184,7 @@ func (w *walletSign) getPromptRequest(params operations.SignParams, acc *account
return nil, 0, fmt.Errorf("failed to decode operation ID: %w", err)
}
- var data PromptRequestSignData
+ var data prompt.PromptRequestSignData
switch opType {
case transaction.OpType:
@@ -258,9 +207,9 @@ func (w *walletSign) getPromptRequest(params operations.SignParams, acc *account
return nil, 0, fmt.Errorf("failed to decode message of operation type: %d: %w", opType, err)
}
- addressBytes, err := acc.Address.MarshalText()
+ address, err := acc.Address.String()
if err != nil {
- return nil, 0, fmt.Errorf("failed to marshal address: %w", err)
+ return nil, 0, fmt.Errorf("err: %w", err)
}
_, minimalFees, err := network.GetNodeInfo()
@@ -268,19 +217,20 @@ func (w *walletSign) getPromptRequest(params operations.SignParams, acc *account
minimalFees = "0"
}
- data.Description = description
+ data.Description = params.Body.Description
data.Fees = strconv.FormatUint(fees, 10)
data.MinFees = minimalFees
- data.WalletAddress = string(addressBytes)
+ data.WalletAddress = address
data.Nickname = acc.Nickname
data.OperationType = int(opType)
data.AllowFeeEdition = *params.AllowFeeEdition
- data.ChainID = chainID
- data.Assets = convertAssetsToModel(w.AssetsStore.All(acc.Nickname, int(chainID)))
+ data.ChainID = *params.Body.ChainID
+ data.Assets = convertAssetsToModel(w.AssetsStore.All(acc.Nickname, int(data.ChainID)))
promptRequest := prompt.PromptRequest{
- Action: walletapp.Sign,
- Data: data,
+ Action: walletapp.Sign,
+ Data: data,
+ PasswordRequired: true,
}
return &promptRequest, fees, nil
@@ -288,13 +238,13 @@ func (w *walletSign) getPromptRequest(params operations.SignParams, acc *account
func getCallSCPromptData(
decodedMsg []byte,
-) (PromptRequestSignData, error) {
+) (prompt.PromptRequestSignData, error) {
msg, err := callsc.DecodeMessage(decodedMsg)
if err != nil {
- return PromptRequestSignData{}, err
+ return prompt.PromptRequestSignData{}, err
}
- return PromptRequestSignData{
+ return prompt.PromptRequestSignData{
Coins: strconv.FormatUint(msg.Coins, 10),
Address: msg.Address,
Function: msg.Function,
@@ -304,25 +254,24 @@ func getCallSCPromptData(
func getExecuteSCPromptData(
decodedMsg []byte,
-) (PromptRequestSignData, error) {
+) (prompt.PromptRequestSignData, error) {
msg, err := executesc.DecodeMessage(decodedMsg)
if err != nil {
- return PromptRequestSignData{}, err
+ return prompt.PromptRequestSignData{}, err
}
- promptReq := PromptRequestSignData{
+ promptReq := prompt.PromptRequestSignData{
MaxCoins: strconv.FormatUint(msg.MaxCoins, 10),
}
// Check the datastore to know whether the ExecuteSC is a DeploySC or not
-
if msg.DataStore == nil { // the executeSC is not a deploySC
return promptReq, nil
}
dataStore, err := onchain.DeSerializeDatastore(msg.DataStore)
if err != nil {
- return PromptRequestSignData{}, err
+ return prompt.PromptRequestSignData{}, err
}
deployedContract, isDeployDatastore := onchain.DatastoreToDeployedContract(dataStore)
@@ -336,13 +285,13 @@ func getExecuteSCPromptData(
func getRollPromptData(
decodedMsg []byte,
-) (PromptRequestSignData, error) {
+) (prompt.PromptRequestSignData, error) {
msg, err := sendoperation.RollDecodeMessage(decodedMsg)
if err != nil {
- return PromptRequestSignData{}, err
+ return prompt.PromptRequestSignData{}, err
}
- return PromptRequestSignData{
+ return prompt.PromptRequestSignData{
RollCount: msg.RollCount,
Coins: strconv.FormatUint(msg.RollCount*RollPrice, 10),
}, nil
@@ -350,10 +299,10 @@ func getRollPromptData(
func (w *walletSign) getTransactionPromptData(
decodedMsg []byte,
-) (PromptRequestSignData, error) {
+) (prompt.PromptRequestSignData, error) {
msg, err := transaction.DecodeMessage(decodedMsg)
if err != nil {
- return PromptRequestSignData{}, err
+ return prompt.PromptRequestSignData{}, err
}
var recipientNickname string
@@ -365,87 +314,13 @@ func (w *walletSign) getTransactionPromptData(
recipientNickname = recipientAcc.Nickname
}
- return PromptRequestSignData{
+ return prompt.PromptRequestSignData{
RecipientAddress: msg.RecipientAddress,
RecipientNickname: recipientNickname,
Amount: strconv.FormatUint(msg.Amount, 10),
}, nil
}
-// privateKeyFromCache return the private key from the cache or an error.
-func (w *walletSign) privateKeyFromCache(
- acc *account.Account,
-) (*memguard.LockedBuffer, error) {
- cacheKey := cacheKeyPrefix + acc.Nickname
- keyHash := KeyHash([]byte(cacheKey))
-
- value, err := w.gc.Get(keyHash)
- if err != nil {
- if err.Error() == gcache.KeyNotFoundError.Error() {
- return nil, fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
- }
-
- return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
- }
-
- if value == nil {
- return nil, fmt.Errorf("%w: %s", utils.ErrCache, "value is nil")
- }
-
- byteValue, ok := value.([]byte)
- if !ok {
- return nil, fmt.Errorf("%w: %s", utils.ErrCache, "value is not a byte array")
- }
-
- cacheValue := make([]byte, ed25519.PrivateKeySize)
- copy(cacheValue, byteValue)
-
- cipheredPrivateKey := memguard.NewBufferFromBytes(cacheValue)
-
- privateKey, err := Xor(cipheredPrivateKey, keyHash)
- if err != nil {
- return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
- }
-
- cipheredPrivateKey.Destroy()
-
- return privateKey, nil
-}
-
-func Xor(pkey *memguard.LockedBuffer, cacheKeyHash [32]byte) (*memguard.LockedBuffer, error) {
- a := pkey.Bytes()
-
- if len(a) != len(cacheKeyHash) {
- return nil, fmt.Errorf("length of two arrays must be same, %d and %d", len(a), len(cacheKeyHash))
- }
- result := make([]byte, len(a))
-
- for i := 0; i < len(a); i++ {
- result[i] = a[i] ^ cacheKeyHash[i]
- }
-
- return memguard.NewBufferFromBytes(result), nil
-}
-
-func KeyHash(cacheKeyHash []byte) [32]byte {
- return blake3.Sum256(cacheKeyHash)
-}
-
-func expirationDuration() time.Duration {
- fromEnv := os.Getenv("BATCH_EXPIRATION_TIME")
-
- if fromEnv == "" {
- return defaultExpirationTime
- }
-
- duration, err := time.ParseDuration(fromEnv)
- if err != nil {
- return defaultExpirationTime
- }
-
- return duration
-}
-
func convertAssetsToModel(assetsWithBalance []*assets.AssetInfoWithBalances) []models.AssetInfo {
result := make([]models.AssetInfo, 0)
diff --git a/internal/handler/wallet/sign_message.go b/internal/handler/wallet/sign_message.go
index 956534655..a357213cc 100644
--- a/internal/handler/wallet/sign_message.go
+++ b/internal/handler/wallet/sign_message.go
@@ -10,6 +10,7 @@ import (
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
"github.com/massalabs/station-massa-wallet/pkg/utils"
"github.com/massalabs/station-massa-wallet/pkg/wallet/account"
@@ -49,8 +50,7 @@ func (w *walletSignMessage) Handle(params operations.SignMessageParams) middlewa
return newErrorResponse(err.Error(), errorSignDecodeMessage, http.StatusBadRequest)
}
- // Use the prompt-based logic to sign the message
- promptOutput, err := prompt.WakeUpPrompt(w.prompterApp, *promptRequest, acc)
+ output, err := PromptForOperation(w.prompterApp, acc, promptRequest)
if err != nil {
msg := fmt.Sprintf("Unable to unprotect wallet: %s", err.Error())
if errors.Is(err, utils.ErrWrongPassword) || errors.Is(err, utils.ErrActionCanceled) {
@@ -60,17 +60,7 @@ func (w *walletSignMessage) Handle(params operations.SignMessageParams) middlewa
return newErrorResponse(msg, errorGetWallets, http.StatusInternalServerError)
}
- output, ok := promptOutput.(*walletapp.SignPromptOutput)
- if !ok {
- return newErrorResponse(fmt.Sprintf("prompting password for message: %v", utils.ErrInvalidInputType.Error()), utils.ErrInvalidInputType.Error(), http.StatusInternalServerError)
- }
-
- w.prompterApp.EmitEvent(walletapp.PromptResultEvent,
- walletapp.EventData{Success: true})
-
- password := output.Password
-
- signature, err := acc.Sign(password, []byte(params.Body.Message))
+ signature, err := acc.Sign(output.Password, []byte(params.Body.Message))
if err != nil {
return newErrorResponse(fmt.Sprintf("unable to sign message: %s", err.Error()), errorGetWallets, http.StatusInternalServerError)
}
@@ -80,6 +70,15 @@ func (w *walletSignMessage) Handle(params operations.SignMessageParams) middlewa
return newErrorResponse(err.Error(), errorGetAccount, http.StatusInternalServerError)
}
+ cfg := config.Get()
+
+ if cfg.HasEnabledRule(acc.Nickname) {
+ err = CachePrivateKey(w.gc, acc, output.Password)
+ if err != nil {
+ return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
+ }
+ }
+
// Return the signature and public key as the response
return operations.NewSignMessageOK().WithPayload(
&models.SignResponse{
diff --git a/internal/handler/wallet/sign_rule_add.go b/internal/handler/wallet/sign_rule_add.go
new file mode 100644
index 000000000..9938b0637
--- /dev/null
+++ b/internal/handler/wallet/sign_rule_add.go
@@ -0,0 +1,110 @@
+package wallet
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/bluele/gcache"
+ "github.com/go-openapi/runtime/middleware"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+ "github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
+ walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
+ "github.com/massalabs/station-massa-wallet/pkg/prompt"
+ "github.com/massalabs/station-massa-wallet/pkg/utils"
+ "github.com/massalabs/station-massa-wallet/pkg/wallet/account"
+ "github.com/pkg/errors"
+)
+
+type SignRulePromptData struct {
+ Nickname string
+ WalletAddress string
+ Description string
+ SignRule config.SignRule
+}
+
+func NewAddSignRuleHandler(prompterApp prompt.WalletPrompterInterface, gc gcache.Cache) operations.AddSignRuleHandler {
+ return &addSignRuleHandler{gc: gc, prompterApp: prompterApp}
+}
+
+type addSignRuleHandler struct {
+ prompterApp prompt.WalletPrompterInterface
+ gc gcache.Cache
+}
+
+func (w *addSignRuleHandler) Handle(params operations.AddSignRuleParams) middleware.Responder {
+ acc, errResp := loadAccount(w.prompterApp.App().Wallet, params.Nickname)
+ if errResp != nil {
+ return errResp
+ }
+
+ promptRequest, err := w.getPromptRequest(params, acc)
+ if err != nil {
+ return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorAddSignRule, http.StatusBadRequest)
+ }
+
+ password, err := PromptPassword(w.prompterApp, acc, promptRequest)
+ if err != nil {
+ msg := fmt.Sprintf("Unable to unprotect wallet: %s", err.Error())
+ if errors.Is(err, utils.ErrWrongPassword) || errors.Is(err, utils.ErrActionCanceled) {
+ return newErrorResponse(msg, errorGetWallets, http.StatusUnauthorized)
+ }
+
+ return newErrorResponse(msg, errorGetWallets, http.StatusInternalServerError)
+ }
+
+ cfg := config.Get()
+
+ newRule := config.SignRule{
+ Name: params.Body.Name,
+ Contract: *params.Body.Contract,
+ RuleType: config.RuleType(params.Body.RuleType),
+ Enabled: *params.Body.Enabled,
+ }
+
+ ruleID, err := cfg.AddSignRule(acc.Nickname, newRule)
+ if err != nil {
+ return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorAddSignRule, http.StatusInternalServerError)
+ }
+
+ if cfg.HasEnabledRule(acc.Nickname) {
+ err = CachePrivateKeyFromPassword(w.gc, acc, password)
+ if err != nil {
+ return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
+ }
+ }
+
+ return w.Success(ruleID)
+}
+
+func (w *addSignRuleHandler) Success(ruleID string) middleware.Responder {
+ return operations.NewAddSignRuleOK().WithPayload(
+ &models.AddSignRuleResponse{
+ ID: ruleID,
+ })
+}
+
+func (w *addSignRuleHandler) getPromptRequest(params operations.AddSignRuleParams, acc *account.Account) (*prompt.PromptRequest, error) {
+ address, err := acc.Address.String()
+ if err != nil {
+ return nil, fmt.Errorf("failed to stringify address: %w", err)
+ }
+
+ promptData := SignRulePromptData{
+ WalletAddress: address,
+ Nickname: acc.Nickname,
+ Description: params.Body.Description,
+ SignRule: config.SignRule{
+ Contract: *params.Body.Contract,
+ RuleType: config.RuleType(params.Body.RuleType),
+ Enabled: *params.Body.Enabled,
+ },
+ }
+
+ promptRequest := prompt.PromptRequest{
+ Action: walletapp.AddSignRule,
+ Data: promptData,
+ }
+
+ return &promptRequest, nil
+}
diff --git a/internal/handler/wallet/sign_rule_delete.go b/internal/handler/wallet/sign_rule_delete.go
new file mode 100644
index 000000000..5bc9d0de0
--- /dev/null
+++ b/internal/handler/wallet/sign_rule_delete.go
@@ -0,0 +1,85 @@
+package wallet
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/bluele/gcache"
+ "github.com/go-openapi/runtime/middleware"
+ "github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
+ walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
+ "github.com/massalabs/station-massa-wallet/pkg/prompt"
+ "github.com/massalabs/station-massa-wallet/pkg/utils"
+ "github.com/massalabs/station-massa-wallet/pkg/wallet/account"
+ "github.com/pkg/errors"
+)
+
+func NewDeleteSignRuleHandler(prompterApp prompt.WalletPrompterInterface, gc gcache.Cache) operations.DeleteSignRuleHandler {
+ return &deleteSignRuleHandler{gc: gc, prompterApp: prompterApp}
+}
+
+type deleteSignRuleHandler struct {
+ prompterApp prompt.WalletPrompterInterface
+ gc gcache.Cache
+}
+
+func (w *deleteSignRuleHandler) Handle(params operations.DeleteSignRuleParams) middleware.Responder {
+ acc, errResp := loadAccount(w.prompterApp.App().Wallet, params.Nickname)
+ if errResp != nil {
+ return errResp
+ }
+
+ cfg := config.Get()
+
+ signRule, err := cfg.GetSignRule(acc.Nickname, params.RuleID)
+ if err != nil {
+ return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorDeleteSignRule, http.StatusInternalServerError)
+ }
+
+ promptRequest, err := w.getPromptRequest(acc, signRule)
+ if err != nil {
+ return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorDeleteSignRule, http.StatusBadRequest)
+ }
+
+ _, err = PromptPassword(w.prompterApp, acc, promptRequest)
+ if err != nil {
+ msg := fmt.Sprintf("Unable to unprotect wallet: %s", err.Error())
+ if errors.Is(err, utils.ErrWrongPassword) || errors.Is(err, utils.ErrActionCanceled) {
+ return newErrorResponse(msg, errorGetWallets, http.StatusUnauthorized)
+ }
+
+ return newErrorResponse(msg, errorGetWallets, http.StatusInternalServerError)
+ }
+
+ err = cfg.DeleteSignRule(acc.Nickname, params.RuleID)
+ if err != nil {
+ return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorDeleteSignRule, http.StatusInternalServerError)
+ }
+
+ return w.Success()
+}
+
+func (w *deleteSignRuleHandler) Success() middleware.Responder {
+ return operations.NewDeleteSignRuleOK()
+}
+
+func (w *deleteSignRuleHandler) getPromptRequest(acc *account.Account, signRule *config.SignRule) (*prompt.PromptRequest, error) {
+ address, err := acc.Address.String()
+ if err != nil {
+ return nil, fmt.Errorf("failed to stringify address: %w", err)
+ }
+
+ promptData := SignRulePromptData{
+ WalletAddress: address,
+ Nickname: acc.Nickname,
+ SignRule: *signRule,
+ }
+
+ promptRequest := prompt.PromptRequest{
+ Action: walletapp.DeleteSignRule,
+ Data: promptData,
+ }
+
+ return &promptRequest, nil
+}
diff --git a/internal/handler/wallet/sign_rule_test.go b/internal/handler/wallet/sign_rule_test.go
new file mode 100644
index 000000000..ad0dd5c04
--- /dev/null
+++ b/internal/handler/wallet/sign_rule_test.go
@@ -0,0 +1,107 @@
+package wallet
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+ walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ assertLib "github.com/stretchr/testify/assert"
+)
+
+func Test_signrule_Handlers(t *testing.T) {
+ assert := assertLib.New(t)
+
+ api, resChan, err := MockAPI()
+ assert.NoError(err)
+
+ addhandler, exist := api.HandlerFor("post", "/api/accounts/{nickname}/signrules")
+ assert.True(exist)
+
+ nickname := "zeWahLetName"
+ password := "zePassword"
+ account := createAccount(password, nickname, t, prompterAppMock)
+
+ t.Run("Add sign rule", func(t *testing.T) {
+ body := `{
+ "name": "Test Rule",
+ "contract": "Test Contract",
+ "ruleType": "auto_sign",
+ "enabled": true,
+ "description": "Test Description"
+ }`
+
+ testResult := make(chan walletapp.EventData)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Message: password,
+ }
+
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ resp, err := handleHTTPRequest(addhandler, "POST", fmt.Sprintf("/api/accounts/%s/signrules", nickname), body)
+ assert.NoError(err)
+ fmt.Println(resp)
+
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result := <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ var addRuleResponse models.AddSignRuleResponse
+ err = json.Unmarshal(resp.Body.Bytes(), &addRuleResponse)
+ assert.NoError(err)
+ assert.NotEmpty(addRuleResponse.ID)
+
+ // check that privateKey is cached
+ pkey, err := privateKeyFromCache(testCache, account)
+ assert.NoError(err)
+ assert.NotNil(pkey)
+ })
+}
+
+// func TestDeleteSignRule(t *testing.T) {
+// api, _, _, resultChannel, err := MockAPI()
+// assert.NoError(t, err)
+
+// body := `{
+// "nickname": "Test Nickname",
+// "ruleID": "Test Rule ID"
+// }`
+
+// resp, err := processHTTPRequest(api, "DELETE", "/sign-rule", body)
+// assert.NoError(t, err)
+// assert.Equal(t, http.StatusOK, resp.Code)
+
+// result := <-resultChannel
+// checkResultChannel(t, result, true, "Sign rule deleted successfully")
+// }
+
+// func TestUpdateSignRule(t *testing.T) {
+// api, _, _, resultChannel, err := MockAPI()
+// assert.NoError(t, err)
+
+// body := `{
+// "nickname": "Test Nickname",
+// "ruleID": "Test Rule ID",
+// "name": "Updated Rule",
+// "contract": "Updated Contract",
+// "ruleType": "Updated Type",
+// "enabled": false
+// }`
+
+// resp, err := processHTTPRequest(api, "PUT", "/sign-rule", body)
+// assert.NoError(t, err)
+// assert.Equal(t, http.StatusOK, resp.Code)
+
+// result := <-resultChannel
+// checkResultChannel(t, result, true, "Sign rule updated successfully")
+// }
diff --git a/internal/handler/wallet/sign_rule_update.go b/internal/handler/wallet/sign_rule_update.go
new file mode 100644
index 000000000..ca3e703ed
--- /dev/null
+++ b/internal/handler/wallet/sign_rule_update.go
@@ -0,0 +1,103 @@
+package wallet
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/bluele/gcache"
+ "github.com/go-openapi/runtime/middleware"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
+ "github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
+ walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
+ "github.com/massalabs/station-massa-wallet/pkg/prompt"
+ "github.com/massalabs/station-massa-wallet/pkg/utils"
+ "github.com/massalabs/station-massa-wallet/pkg/wallet/account"
+ "github.com/pkg/errors"
+)
+
+func NewUpdateSignRuleHandler(prompterApp prompt.WalletPrompterInterface, gc gcache.Cache) operations.UpdateSignRuleHandler {
+ return &updateSignRuleHandler{gc: gc, prompterApp: prompterApp}
+}
+
+type updateSignRuleHandler struct {
+ prompterApp prompt.WalletPrompterInterface
+ gc gcache.Cache
+}
+
+func (w *updateSignRuleHandler) Handle(params operations.UpdateSignRuleParams) middleware.Responder {
+ acc, errResp := loadAccount(w.prompterApp.App().Wallet, params.Nickname)
+ if errResp != nil {
+ return errResp
+ }
+
+ cfg := config.Get()
+
+ signRule, err := cfg.GetSignRule(acc.Nickname, params.RuleID)
+ if err != nil {
+ return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorUpdateSignRule, http.StatusInternalServerError)
+ }
+
+ promptRequest, err := w.getPromptRequest(acc, signRule)
+ if err != nil {
+ return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorUpdateSignRule, http.StatusBadRequest)
+ }
+
+ password, err := PromptPassword(w.prompterApp, acc, promptRequest)
+ if err != nil {
+ msg := fmt.Sprintf("Unable to unprotect wallet: %s", err.Error())
+ if errors.Is(err, utils.ErrWrongPassword) || errors.Is(err, utils.ErrActionCanceled) {
+ return newErrorResponse(msg, errorGetWallets, http.StatusUnauthorized)
+ }
+
+ return newErrorResponse(msg, errorGetWallets, http.StatusInternalServerError)
+ }
+
+ newRule := config.SignRule{
+ Name: params.Body.Name,
+ Contract: *params.Body.Contract,
+ RuleType: config.RuleType(params.Body.RuleType),
+ Enabled: *params.Body.Enabled,
+ }
+
+ ruleID, err := cfg.UpdateSignRule(acc.Nickname, params.RuleID, newRule)
+ if err != nil {
+ return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorUpdateSignRule, http.StatusInternalServerError)
+ }
+
+ if cfg.HasEnabledRule(acc.Nickname) {
+ err = CachePrivateKeyFromPassword(w.gc, acc, password)
+ if err != nil {
+ return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
+ }
+ }
+
+ return w.Success(ruleID)
+}
+
+func (w *updateSignRuleHandler) Success(ruleID string) middleware.Responder {
+ return operations.NewUpdateSignRuleOK().WithPayload(
+ &models.UpdateSignRuleResponse{
+ ID: ruleID,
+ })
+}
+
+func (w *updateSignRuleHandler) getPromptRequest(acc *account.Account, signRule *config.SignRule) (*prompt.PromptRequest, error) {
+ address, err := acc.Address.String()
+ if err != nil {
+ return nil, fmt.Errorf("failed to stringify address: %w", err)
+ }
+
+ promptData := SignRulePromptData{
+ WalletAddress: address,
+ Nickname: acc.Nickname,
+ SignRule: *signRule,
+ }
+
+ promptRequest := prompt.PromptRequest{
+ Action: walletapp.UpdateSignRule,
+ Data: promptData,
+ }
+
+ return &promptRequest, nil
+}
diff --git a/internal/handler/wallet/sign_test.go b/internal/handler/wallet/sign_test.go
index 59525db7c..2328b5b74 100644
--- a/internal/handler/wallet/sign_test.go
+++ b/internal/handler/wallet/sign_test.go
@@ -1,20 +1,25 @@
package wallet
import (
+ "encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"testing"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/utils"
- "github.com/massalabs/station-massa-wallet/pkg/wallet/account"
"github.com/stretchr/testify/assert"
)
-const callSCString = "AKT4CASAzuTNAqCNBgEAXBwUw39NBQYix8Ovph0TUiJuDDEnlFYUPgsbeMbrA4cLZm9yd2FyZEJ1cm7FAQDgfY7fLW7qpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAweGZDRERBRTI1MTAwNjIxYTViQzg4MTlkQzlEMzg0MjUzNEQ3QmY0NzYAAAAANQAAAEFTMTJUUm9TY01kd0xLOFlwdDZOQkFwcHl6Q0Z3N1FlRzVlM3hGdnhwQ0FuQW5ZTGZ1TVVUKgAAADB4NTM4NDRGOTU3N0MyMzM0ZTU0MUFlYzdEZjcxNzRFQ2U1ZEYxZkNmMKc2qgAAAAAA"
+const (
+ callSCString = "AKT4CASAzuTNAqCNBgEAXBwUw39NBQYix8Ovph0TUiJuDDEnlFYUPgsbeMbrA4cLZm9yd2FyZEJ1cm7FAQDgfY7fLW7qpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAweGZDRERBRTI1MTAwNjIxYTViQzg4MTlkQzlEMzg0MjUzNEQ3QmY0NzYAAAAANQAAAEFTMTJUUm9TY01kd0xLOFlwdDZOQkFwcHl6Q0Z3N1FlRzVlM3hGdnhwQ0FuQW5ZTGZ1TVVUKgAAADB4NTM4NDRGOTU3N0MyMzM0ZTU0MUFlYzdEZjcxNzRFQ2U1ZEYxZkNmMKc2qgAAAAAA"
+ contract = "AS1hZpUH6TPiRxHtTKqAfXDmZ7Afa7UfS4rtYN7NxVwAaSAphCET"
+)
func signTransaction(t *testing.T, api *operations.MassaWalletAPI, nickname string, body string) *httptest.ResponseRecorder {
handler, exist := api.HandlerFor("post", "/api/accounts/{nickname}/sign")
@@ -36,27 +41,23 @@ func signMessage(t *testing.T, api *operations.MassaWalletAPI, nickname string,
return resp
}
-func TestPrepareOperation(t *testing.T) {
- acc := account.NewAccount(t)
- fees := uint64(1000)
- operationB64 := "AKT4CASAzuTNAqCNBgEAXBwUw39NBQYix8Ovph0TUiJuDDEnlFYUPgsbeMbrA4cLZm9yd2FyZEJ1cm7FAQDgfY7fLW7qpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAweGZDRERBRTI1MTAwNjIxYTViQzg4MTlkQzlEMzg0MjUzNEQ3QmY0NzYAAAAANQAAAEFTMTJUUm9TY01kd0xLOFlwdDZOQkFwcHl6Q0Z3N1FlRzVlM3hGdnhwQ0FuQW5ZTGZ1TVVUKgAAADB4NTM4NDRGOTU3N0MyMzM0ZTU0MUFlYzdEZjcxNzRFQ2U1ZEYxZkNmMKc2qgAAAAAA"
-
- operation, msgToSign, err := prepareOperation(acc, fees, operationB64, ChainIDUnitTests)
+func verifySignResponse(t *testing.T, resp *httptest.ResponseRecorder) {
+ var signResponse models.SignResponse
+ err := json.Unmarshal(resp.Body.Bytes(), &signResponse)
assert.NoError(t, err)
- expected := []byte{0xe8, 0x7, 0xa4, 0xf8, 0x8, 0x4, 0x80, 0xce, 0xe4, 0xcd, 0x2, 0xa0, 0x8d, 0x6, 0x1, 0x0, 0x5c, 0x1c, 0x14, 0xc3, 0x7f, 0x4d, 0x5, 0x6, 0x22, 0xc7, 0xc3, 0xaf, 0xa6, 0x1d, 0x13, 0x52, 0x22, 0x6e, 0xc, 0x31, 0x27, 0x94, 0x56, 0x14, 0x3e, 0xb, 0x1b, 0x78, 0xc6, 0xeb, 0x3, 0x87, 0xb, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x42, 0x75, 0x72, 0x6e, 0xc5, 0x1, 0x0, 0xe0, 0x7d, 0x8e, 0xdf, 0x2d, 0x6e, 0xea, 0xa7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x30, 0x78, 0x66, 0x43, 0x44, 0x44, 0x41, 0x45, 0x32, 0x35, 0x31, 0x30, 0x30, 0x36, 0x32, 0x31, 0x61, 0x35, 0x62, 0x43, 0x38, 0x38, 0x31, 0x39, 0x64, 0x43, 0x39, 0x44, 0x33, 0x38, 0x34, 0x32, 0x35, 0x33, 0x34, 0x44, 0x37, 0x42, 0x66, 0x34, 0x37, 0x36, 0x0, 0x0, 0x0, 0x0, 0x35, 0x0, 0x0, 0x0, 0x41, 0x53, 0x31, 0x32, 0x54, 0x52, 0x6f, 0x53, 0x63, 0x4d, 0x64, 0x77, 0x4c, 0x4b, 0x38, 0x59, 0x70, 0x74, 0x36, 0x4e, 0x42, 0x41, 0x70, 0x70, 0x79, 0x7a, 0x43, 0x46, 0x77, 0x37, 0x51, 0x65, 0x47, 0x35, 0x65, 0x33, 0x78, 0x46, 0x76, 0x78, 0x70, 0x43, 0x41, 0x6e, 0x41, 0x6e, 0x59, 0x4c, 0x66, 0x75, 0x4d, 0x55, 0x54, 0x2a, 0x0, 0x0, 0x0, 0x30, 0x78, 0x35, 0x33, 0x38, 0x34, 0x34, 0x46, 0x39, 0x35, 0x37, 0x37, 0x43, 0x32, 0x33, 0x33, 0x34, 0x65, 0x35, 0x34, 0x31, 0x41, 0x65, 0x63, 0x37, 0x44, 0x66, 0x37, 0x31, 0x37, 0x34, 0x45, 0x43, 0x65, 0x35, 0x64, 0x46, 0x31, 0x66, 0x43, 0x66, 0x30, 0xa7, 0x36, 0xaa, 0x0, 0x0, 0x0, 0x0, 0x0}
- assert.Equal(t, expected, operation)
- expectedMsg := []byte{0x0, 0x0, 0x0, 0x0, 0x4, 0xa0, 0xf8, 0xfe, 0x0, 0x2d, 0x96, 0xbc, 0xda, 0xcb, 0xbe, 0x41, 0x38, 0x2c, 0xa2, 0x3e, 0x52, 0xe3, 0xd2, 0x19, 0x6c, 0xba, 0x65, 0xe7, 0xa1, 0xac, 0xd2, 0x9, 0xdf, 0xc9, 0x5c, 0x6b, 0x32, 0xb6, 0xa1, 0x8a, 0x93, 0xe8, 0x7, 0xa4, 0xf8, 0x8, 0x4, 0x80, 0xce, 0xe4, 0xcd, 0x2, 0xa0, 0x8d, 0x6, 0x1, 0x0, 0x5c, 0x1c, 0x14, 0xc3, 0x7f, 0x4d, 0x5, 0x6, 0x22, 0xc7, 0xc3, 0xaf, 0xa6, 0x1d, 0x13, 0x52, 0x22, 0x6e, 0xc, 0x31, 0x27, 0x94, 0x56, 0x14, 0x3e, 0xb, 0x1b, 0x78, 0xc6, 0xeb, 0x3, 0x87, 0xb, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x42, 0x75, 0x72, 0x6e, 0xc5, 0x1, 0x0, 0xe0, 0x7d, 0x8e, 0xdf, 0x2d, 0x6e, 0xea, 0xa7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x30, 0x78, 0x66, 0x43, 0x44, 0x44, 0x41, 0x45, 0x32, 0x35, 0x31, 0x30, 0x30, 0x36, 0x32, 0x31, 0x61, 0x35, 0x62, 0x43, 0x38, 0x38, 0x31, 0x39, 0x64, 0x43, 0x39, 0x44, 0x33, 0x38, 0x34, 0x32, 0x35, 0x33, 0x34, 0x44, 0x37, 0x42, 0x66, 0x34, 0x37, 0x36, 0x0, 0x0, 0x0, 0x0, 0x35, 0x0, 0x0, 0x0, 0x41, 0x53, 0x31, 0x32, 0x54, 0x52, 0x6f, 0x53, 0x63, 0x4d, 0x64, 0x77, 0x4c, 0x4b, 0x38, 0x59, 0x70, 0x74, 0x36, 0x4e, 0x42, 0x41, 0x70, 0x70, 0x79, 0x7a, 0x43, 0x46, 0x77, 0x37, 0x51, 0x65, 0x47, 0x35, 0x65, 0x33, 0x78, 0x46, 0x76, 0x78, 0x70, 0x43, 0x41, 0x6e, 0x41, 0x6e, 0x59, 0x4c, 0x66, 0x75, 0x4d, 0x55, 0x54, 0x2a, 0x0, 0x0, 0x0, 0x30, 0x78, 0x35, 0x33, 0x38, 0x34, 0x34, 0x46, 0x39, 0x35, 0x37, 0x37, 0x43, 0x32, 0x33, 0x33, 0x34, 0x65, 0x35, 0x34, 0x31, 0x41, 0x65, 0x63, 0x37, 0x44, 0x66, 0x37, 0x31, 0x37, 0x34, 0x45, 0x43, 0x65, 0x35, 0x64, 0x46, 0x31, 0x66, 0x43, 0x66, 0x30, 0xa7, 0x36, 0xaa, 0x0, 0x0, 0x0, 0x0, 0x0}
- assert.Equal(t, expectedMsg, msgToSign)
+ assert.NotEmpty(t, signResponse.Signature)
+ assert.NotEmpty(t, signResponse.Operation)
+ assert.NotEmpty(t, signResponse.PublicKey)
}
func Test_walletSign_Handle(t *testing.T) {
- api, prompterApp, _, resChan, err := MockAPI()
+ api, resChan, err := MockAPI()
assert.NoError(t, err)
transactionData := fmt.Sprintf(`{"chainId": `+strconv.FormatUint(ChainIDUnitTests, 10)+`, "operation":"%s"}`, callSCString)
nickname := "walletToDelete"
password := "zePassword"
- createAccount(password, nickname, t, prompterApp)
+ account := createAccount(password, nickname, t, prompterAppMock)
t.Run("invalid nickname", func(t *testing.T) {
resp := signTransaction(t, api, "Johnny", transactionData)
@@ -68,7 +69,7 @@ func Test_walletSign_Handle(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "14400",
@@ -83,6 +84,7 @@ func Test_walletSign_Handle(t *testing.T) {
result := <-testResult
checkResultChannel(t, result, true, "")
+ verifySignResponse(t, resp)
})
t.Run("sign a plain text message", func(t *testing.T) {
@@ -90,7 +92,7 @@ func Test_walletSign_Handle(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
@@ -108,6 +110,12 @@ func Test_walletSign_Handle(t *testing.T) {
result := <-testResult
checkResultChannel(t, result, true, "")
+
+ var signResponse models.SignResponse
+ err := json.Unmarshal(resp.Body.Bytes(), &signResponse)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, signResponse.Signature)
+ assert.NotEmpty(t, signResponse.PublicKey)
})
// The handler will not return until a the good password is sent or the action is canceled
@@ -117,7 +125,7 @@ func Test_walletSign_Handle(t *testing.T) {
//nolint:staticcheck
go func(res chan walletapp.EventData) {
// Send wrong password to prompter app and wait for result
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
BaseMessage: walletapp.BaseMessage{},
Password: "this is not the password",
Fees: "1000",
@@ -128,7 +136,7 @@ func Test_walletSign_Handle(t *testing.T) {
checkResultChannel(t, failRes, false, utils.WrongPassword)
// Send password to prompter app to unlock the handler
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
@@ -144,13 +152,14 @@ func Test_walletSign_Handle(t *testing.T) {
result := <-testResult
checkResultChannel(t, result, true, "")
+ verifySignResponse(t, resp)
})
t.Run("invalid password try, then action canceled by user", func(t *testing.T) {
//nolint:staticcheck
go func() {
// Send wrong password to prompter app and wait for result
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
BaseMessage: walletapp.BaseMessage{},
Password: "this is not the password",
Fees: "1000",
@@ -161,10 +170,319 @@ func Test_walletSign_Handle(t *testing.T) {
checkResultChannel(t, failRes, false, utils.WrongPassword)
// Send cancel to prompter app to unlock the handler
- prompterApp.App().CtrlChan <- walletapp.Cancel
+ prompterAppMock.App().CtrlChan <- walletapp.Cancel
}()
resp := signTransaction(t, api, nickname, transactionData)
verifyStatusCode(t, resp, http.StatusUnauthorized)
})
+
+ t.Run("Auto Sign", func(t *testing.T) {
+ cfg := config.Get()
+ ruleId, err := cfg.AddSignRule(nickname, config.SignRule{
+ Name: "test",
+ Contract: contract,
+ RuleType: config.RuleTypeAutoSign,
+ Enabled: true,
+ })
+
+ assert.NoError(t, err)
+
+ assert.True(t, cfg.HasEnabledRule(nickname))
+
+ testResult := make(chan walletapp.EventData)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Password: password,
+ Fees: "14400",
+ }
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ resp := signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result := <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ // check that privateKey is cached
+ pkey, err := privateKeyFromCache(testCache, account)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkey)
+
+ // sign again, should not prompt for password
+ resp = signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ verifySignResponse(t, resp)
+
+ err = cfg.DeleteSignRule(nickname, ruleId)
+ assert.NoError(t, err)
+ })
+
+ t.Run("Auto Sign with enabled rule", func(t *testing.T) {
+ // Clean cache
+ testCache.Purge()
+
+ cfg := config.Get()
+ ruleId, err := cfg.AddSignRule(nickname, config.SignRule{
+ Name: "test",
+ Contract: "AnotherContract",
+ RuleType: config.RuleTypeAutoSign,
+ Enabled: true,
+ })
+
+ assert.NoError(t, err)
+
+ assert.True(t, cfg.HasEnabledRule(nickname))
+
+ testResult := make(chan walletapp.EventData)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Password: password,
+ Fees: "14400",
+ }
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ resp := signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result := <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ // check that privateKey is cached
+ pkey, err := privateKeyFromCache(testCache, account)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkey)
+
+ // sign again, should prompt for password
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Password: password,
+ Fees: "14400",
+ }
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ resp = signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result = <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ verifySignResponse(t, resp)
+
+ err = cfg.DeleteSignRule(nickname, ruleId)
+ assert.NoError(t, err)
+ })
+
+ t.Run("Disable Password Prompt for contract", func(t *testing.T) {
+ // Clean cache
+ testCache.Purge()
+
+ cfg := config.Get()
+ ruleId, err := cfg.AddSignRule(nickname, config.SignRule{
+ Name: "test",
+ Contract: contract,
+ RuleType: config.RuleTypeDisablePasswordPrompt,
+ Enabled: true,
+ })
+
+ assert.NoError(t, err)
+
+ assert.True(t, cfg.HasEnabledRule(nickname))
+
+ testResult := make(chan walletapp.EventData)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Password: password,
+ Fees: "14400",
+ }
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ fmt.Println(">>>>Sign transaction password needed")
+ resp := signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result := <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ // check that privateKey is cached
+ pkey, err := privateKeyFromCache(testCache, account)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkey)
+
+ // sign again, should prompt for user confirmation, no password needed
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Fees: "12345",
+ }
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ fmt.Println(">>>>Sign transaction password NOT needed")
+
+ resp = signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result = <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ verifySignResponse(t, resp)
+
+ err = cfg.DeleteSignRule(nickname, ruleId)
+ assert.NoError(t, err)
+ })
+
+ t.Run("Disable Password Prompt enabled for another contract", func(t *testing.T) {
+ // Clean cache
+ testCache.Purge()
+
+ cfg := config.Get()
+ ruleId, err := cfg.AddSignRule(nickname, config.SignRule{
+ Name: "test",
+ Contract: "DummyContract",
+ RuleType: config.RuleTypeDisablePasswordPrompt,
+ Enabled: true,
+ })
+
+ assert.NoError(t, err)
+
+ assert.True(t, cfg.HasEnabledRule(nickname))
+
+ testResult := make(chan walletapp.EventData)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Password: password,
+ Fees: "14400",
+ }
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ resp := signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result := <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ // check that privateKey is cached
+ pkey, err := privateKeyFromCache(testCache, account)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkey)
+
+ // sign again, should prompt for user confirmation, password is needed
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Password: password,
+ Fees: "14400",
+ }
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ resp = signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result = <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ verifySignResponse(t, resp)
+
+ err = cfg.DeleteSignRule(nickname, ruleId)
+ assert.NoError(t, err)
+ })
+
+ t.Run("Disable Password Prompt enabled for all contract.", func(t *testing.T) {
+ // Clean cache
+ testCache.Purge()
+
+ cfg := config.Get()
+ ruleId, err := cfg.AddSignRule(nickname, config.SignRule{
+ Name: "test",
+ Contract: "*",
+ RuleType: config.RuleTypeDisablePasswordPrompt,
+ Enabled: true,
+ })
+
+ assert.NoError(t, err)
+
+ assert.True(t, cfg.HasEnabledRule(nickname))
+
+ testResult := make(chan walletapp.EventData)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Password: password,
+ Fees: "14400",
+ }
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ resp := signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result := <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ // check that privateKey is cached
+ pkey, err := privateKeyFromCache(testCache, account)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkey)
+
+ // sign again, should prompt for user confirmation, password is not needed
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Fees: "14400",
+ }
+ // forward test result to test goroutine
+ res <- (<-resChan)
+ }(testResult)
+
+ resp = signTransaction(t, api, nickname, transactionData)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ result = <-testResult
+
+ checkResultChannel(t, result, true, "")
+
+ verifySignResponse(t, resp)
+
+ err = cfg.DeleteSignRule(nickname, ruleId)
+ assert.NoError(t, err)
+ })
}
diff --git a/internal/handler/wallet/transfer.go b/internal/handler/wallet/transfer.go
index bfbb13409..44abf93a2 100644
--- a/internal/handler/wallet/transfer.go
+++ b/internal/handler/wallet/transfer.go
@@ -6,10 +6,12 @@ import (
"strconv"
"github.com/awnumar/memguard"
+ "github.com/bluele/gcache"
"github.com/go-openapi/runtime/middleware"
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
"github.com/massalabs/station-massa-wallet/pkg/utils"
@@ -18,13 +20,14 @@ import (
"github.com/massalabs/station/pkg/node/sendoperation/transaction"
)
-func NewTransferCoin(prompterApp prompt.WalletPrompterInterface, massaClient network.NodeFetcherInterface) operations.TransferCoinHandler {
- return &transferCoin{prompterApp: prompterApp, massaClient: massaClient}
+func NewTransferCoin(prompterApp prompt.WalletPrompterInterface, massaClient network.NodeFetcherInterface, gc gcache.Cache) operations.TransferCoinHandler {
+ return &transferCoin{prompterApp: prompterApp, massaClient: massaClient, gc: gc}
}
type transferCoin struct {
prompterApp prompt.WalletPrompterInterface
massaClient network.NodeFetcherInterface
+ gc gcache.Cache
}
func (t *transferCoin) Handle(params operations.TransferCoinParams) middleware.Responder {
@@ -68,7 +71,7 @@ func (t *transferCoin) Handle(params operations.TransferCoinParams) middleware.R
promptRequest := prompt.PromptRequest{
Action: walletapp.Sign,
- Data: PromptRequestSignData{
+ Data: prompt.PromptRequestSignData{
Fees: strconv.FormatUint(fee, 10),
MinFees: minimalFees,
WalletAddress: address,
@@ -107,6 +110,15 @@ func (t *transferCoin) Handle(params operations.TransferCoinParams) middleware.R
t.prompterApp.EmitEvent(walletapp.PromptResultEvent,
walletapp.EventData{Success: true})
+ cfg := config.Get()
+
+ if cfg.HasEnabledRule(acc.Nickname) {
+ err = CachePrivateKey(t.gc, acc, output.Password)
+ if err != nil {
+ return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
+ }
+ }
+
return operations.NewTransferCoinOK().WithPayload(
&models.OperationResponse{
OperationID: operation.OperationID,
diff --git a/internal/handler/wallet/transfer_test.go b/internal/handler/wallet/transfer_test.go
index 04439252c..5344cf68a 100644
--- a/internal/handler/wallet/transfer_test.go
+++ b/internal/handler/wallet/transfer_test.go
@@ -10,7 +10,7 @@ import (
)
func Test_transfer_handler(t *testing.T) {
- api, prompterApp, _, resChan, err := MockAPI()
+ api, resChan, err := MockAPI()
assert.NoError(t, err)
handler, exist := api.HandlerFor("post", "/api/accounts/{nickname}/transfer")
@@ -18,7 +18,7 @@ func Test_transfer_handler(t *testing.T) {
nickname := "wallet1"
password := "password"
- createAccount(password, nickname, t, prompterApp)
+ createAccount(password, nickname, t, prompterAppMock)
t.Run("Transfer with unprocessable entity", func(t *testing.T) {
resp, err := handleHTTPRequest(handler, "POST", fmt.Sprintf("/api/accounts/%s/transfer", "nobody"), "")
@@ -63,7 +63,7 @@ func Test_transfer_handler(t *testing.T) {
// Send password to prompter app and wait for result
go func(res chan walletapp.EventData) {
- prompterApp.App().PromptInput <- &walletapp.SignPromptInput{
+ prompterAppMock.App().PromptInput <- &walletapp.SignPromptInput{
BaseMessage: walletapp.BaseMessage{},
Password: password,
Fees: "1000",
diff --git a/internal/handler/wallet/update_test.go b/internal/handler/wallet/update_test.go
index b8a042e16..db22aef69 100644
--- a/internal/handler/wallet/update_test.go
+++ b/internal/handler/wallet/update_test.go
@@ -11,14 +11,14 @@ import (
)
func Test_ModifyWallets_handler(t *testing.T) {
- api, prompterApp, _, _, err := MockAPI()
+ api, _, err := MockAPI()
assert.NoError(t, err)
// Create account
nickname := "trololol-old"
password := "zePassword"
- createAccount(password, nickname, t, prompterApp)
- assert.Equal(t, prompterApp.App().Wallet.GetAccountCount(), 1, "there should be only one wallet")
+ createAccount(password, nickname, t, prompterAppMock)
+ assert.Equal(t, prompterAppMock.App().Wallet.GetAccountCount(), 1, "there should be only one wallet")
handler, exist := api.HandlerFor("put", "/api/accounts/{nickname}")
assert.True(t, exist, "Endpoint doesn't exist")
diff --git a/pkg/app/events.go b/pkg/app/events.go
index a063a3a28..5b9c02e07 100644
--- a/pkg/app/events.go
+++ b/pkg/app/events.go
@@ -32,6 +32,9 @@ const (
Backup
TradeRolls
Unprotect
+ AddSignRule
+ DeleteSignRule
+ UpdateSignRule
)
// User input interfaces for the channel
@@ -63,7 +66,11 @@ type ImportPKeyPromptInput struct {
// Output interface for the caller of the prompt
-type SignPromptOutput struct {
+type PasswordPromptOutput struct {
Password *memguard.LockedBuffer
- Fees uint64
+}
+
+type SignPromptOutput struct {
+ PasswordPromptOutput
+ Fees uint64
}
diff --git a/pkg/assets/asset.go b/pkg/assets/asset.go
index 70a140027..d9fcc7851 100644
--- a/pkg/assets/asset.go
+++ b/pkg/assets/asset.go
@@ -55,10 +55,12 @@ type assetData struct {
ChainID int64 `json:"chainID"`
}
+var Store *AssetsStore
+
// NewAssetsStore creates and initializes a new instance of AssetsStore.
// If assetsJSONDir is empty, it will use the default wallet path.
-func NewAssetsStore(assetsJSONDir string, massaClient *network.NodeFetcher) (*AssetsStore, error) {
- store := &AssetsStore{
+func InitAssetsStore(assetsJSONDir string, massaClient *network.NodeFetcher) (*AssetsStore, error) {
+ Store = &AssetsStore{
Assets: make(map[string]Assets),
massaClient: massaClient,
}
@@ -68,21 +70,21 @@ func NewAssetsStore(assetsJSONDir string, massaClient *network.NodeFetcher) (*As
if err != nil {
return nil, errors.Wrap(err, "Failed to get AssetsStore JSON file")
}
- store.assetsJSONDir = assetsJSONDir
+ Store.assetsJSONDir = assetsJSONDir
} else {
- store.assetsJSONDir = assetsJSONDir
+ Store.assetsJSONDir = assetsJSONDir
}
- if err := store.loadAccountsStore(); err != nil {
+ if err := Store.loadAccountsStore(); err != nil {
return nil, errors.Wrap(err, "failed to create AssetsStore")
}
- err := store.InitDefault()
+ err := Store.InitDefault()
if err != nil {
return nil, errors.Wrap(err, "failed to create AssetsStore")
}
- return store, nil
+ return Store, nil
}
// loadAccountsStore loads the data from the assets JSON file into the AssetsStore.
diff --git a/pkg/assets/asset_test.go b/pkg/assets/asset_test.go
index e0a05fe52..81cd6127e 100644
--- a/pkg/assets/asset_test.go
+++ b/pkg/assets/asset_test.go
@@ -42,13 +42,13 @@ func TestLoadAccountsStore(t *testing.T) {
// Create a new instance of AssetsStore and load data from the testing file
nodeFetcher := network.NewNodeFetcher()
- store, err := NewAssetsStore(tempDir, nodeFetcher)
+ _, err = InitAssetsStore(tempDir, nodeFetcher)
assert.NoError(t, err)
// Validate the loaded data
expectedAccountName := "dummyAccount"
expectedContractAddress := "0x1234567890abcdef"
- asset, ok := store.Assets[expectedAccountName].ContractAssets[expectedContractAddress]
+ asset, ok := Store.Assets[expectedAccountName].ContractAssets[expectedContractAddress]
assert.True(t, ok)
assert.Equal(t, asset.Name, "DummyToken")
@@ -64,20 +64,20 @@ func TestAssetExists(t *testing.T) {
// Create a new instance of AssetsStore and load data from the testing file
nodeFetcher := network.NewNodeFetcher()
- store, err := NewAssetsStore(tempDir, nodeFetcher)
+ _, err = InitAssetsStore(tempDir, nodeFetcher)
assert.NoError(t, err)
// Test case 1: Check for an existing asset
existingNickname := "dummyAccount"
existingContractAddress := "0x1234567890abcdef"
- exists := store.AssetExists(existingNickname, existingContractAddress)
+ exists := Store.AssetExists(existingNickname, existingContractAddress)
assert.True(t, exists, "Expected asset to exist ")
// Test case 2: Check for a non-existing asset
nonExistingNickname := "nonExistingAccount"
nonExistingContractAddress := "0xabcdefabcdefabcdef"
- notExists := store.AssetExists(nonExistingNickname, nonExistingContractAddress)
+ notExists := Store.AssetExists(nonExistingNickname, nonExistingContractAddress)
assert.False(t, notExists, "Expected asset to not exist ")
}
@@ -109,7 +109,7 @@ func TestAddAndDeleteAsset(t *testing.T) {
// Create a new instance of AssetsStore and load data from the testing file
nodeFetcher := network.NewNodeFetcher()
- store, err := NewAssetsStore(tempDir, nodeFetcher)
+ _, err = InitAssetsStore(tempDir, nodeFetcher)
assert.NoError(t, err)
// Test case 1: Add an asset and check if it's saved to JSON
@@ -126,16 +126,16 @@ func TestAddAndDeleteAsset(t *testing.T) {
*assetInfo.Decimals = 18
// Add the asset
- err = store.AddAsset(nickname, assetInfo)
+ err = Store.AddAsset(nickname, assetInfo)
assert.NoError(t, err)
// Check if the added asset exists
- assert.True(t, store.AssetExists(nickname, assetAddress), "Added asset not found in the store")
+ assert.True(t, Store.AssetExists(nickname, assetAddress), "Added asset not found in the store")
// Test case 2: Delete the added asset and check if it's removed from JSON
- err = store.DeleteAsset(nickname, assetAddress)
+ err = Store.DeleteAsset(nickname, assetAddress)
assert.NoError(t, err)
// Check if the deleted asset no longer exists
- assert.False(t, store.AssetExists(nickname, assetAddress), "Deleted asset still found in the store")
+ assert.False(t, Store.AssetExists(nickname, assetAddress), "Deleted asset still found in the store")
}
diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go
new file mode 100644
index 000000000..354ade26e
--- /dev/null
+++ b/pkg/cache/cache.go
@@ -0,0 +1,172 @@
+package cache
+
+import (
+ "crypto/ed25519"
+ "fmt"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/awnumar/memguard"
+ "github.com/bluele/gcache"
+ "github.com/massalabs/station-massa-wallet/pkg/utils"
+ "github.com/massalabs/station-massa-wallet/pkg/wallet/account"
+ "lukechampine.com/blake3"
+)
+
+var (
+ cache gcache.Cache
+ once sync.Once
+)
+
+func Get() gcache.Cache {
+ once.Do(func() {
+ cache = gcache.New(cacheSize).
+ LRU().
+ Build()
+ })
+
+ return cache
+}
+
+const (
+ defaultExpirationTime = time.Hour * 24 * 30
+ cacheKeyPrefix = "pkey"
+ notFoundMsg = "private key not found in cache"
+ cacheSize = 100
+)
+
+func CachePrivateKeyFromPassword(gc gcache.Cache, account *account.Account, password *memguard.LockedBuffer) error {
+ privateKey, err := account.PrivateKeyBytesInClear(password)
+ if err != nil {
+ return fmt.Errorf("error caching private key: %w", err)
+ }
+
+ err = CachePrivateKey(gc, account, privateKey)
+ if err != nil {
+ return fmt.Errorf("error caching private key: %w", err)
+ }
+
+ return nil
+}
+
+func CachePrivateKey(gc gcache.Cache, account *account.Account, privateKey *memguard.LockedBuffer) error {
+ cacheKey, err := privateKeyCacheKey(account)
+ if err != nil {
+ return fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
+ }
+
+ key := KeyHash([]byte(cacheKey))
+
+ // Concatenate the key with itself to make it 64 bytes long
+ var extendedKey [64]byte
+ copy(extendedKey[:], append(key[:], key[:]...))
+
+ cipheredPrivateKey, err := xor(privateKey, extendedKey)
+ if err != nil {
+ return fmt.Errorf("%w: %w", utils.ErrCache, err)
+ }
+
+ cacheValue := make([]byte, ed25519.PrivateKeySize)
+ copy(cacheValue, cipheredPrivateKey.Bytes())
+ cipheredPrivateKey.Destroy()
+
+ err = gc.SetWithExpire(key, cacheValue, expirationDuration())
+ if err != nil {
+ return fmt.Errorf("error set private key in cache: %w", err)
+ }
+
+ return nil
+}
+
+// privateKeyFromCache return the private key from the cache or an error.
+func PrivateKeyFromCache(
+ gc gcache.Cache,
+ acc *account.Account,
+) (*memguard.LockedBuffer, error) {
+ cacheKey, err := privateKeyCacheKey(acc)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
+ }
+
+ keyHash := KeyHash([]byte(cacheKey))
+
+ value, err := gc.Get(keyHash)
+ if err != nil {
+ if err.Error() == gcache.KeyNotFoundError.Error() {
+ return nil, fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
+ }
+
+ return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
+ }
+
+ if value == nil {
+ return nil, fmt.Errorf("%w: %s", utils.ErrCache, notFoundMsg)
+ }
+
+ byteValue, ok := value.([]byte)
+ if !ok {
+ return nil, fmt.Errorf("%w: %s", utils.ErrCache, "value is not a byte array")
+ }
+
+ cacheValue := make([]byte, ed25519.PrivateKeySize)
+ copy(cacheValue, byteValue)
+
+ cipheredPrivateKey := memguard.NewBufferFromBytes(cacheValue)
+
+ // Concatenate the key with itself to make it 64 bytes long
+ var extendedKey [64]byte
+ copy(extendedKey[:], append(keyHash[:], keyHash[:]...))
+
+ privateKey, err := xor(cipheredPrivateKey, extendedKey)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
+ }
+
+ cipheredPrivateKey.Destroy()
+
+ return privateKey, nil
+}
+
+func xor(pkey *memguard.LockedBuffer, cacheKeyHash [64]byte) (*memguard.LockedBuffer, error) {
+ a := pkey.Bytes()
+
+ if len(a) != len(cacheKeyHash) {
+ return nil, fmt.Errorf("length of two arrays must be same, %d and %d", len(a), len(cacheKeyHash))
+ }
+ result := make([]byte, len(a))
+
+ for i := 0; i < len(a); i++ {
+ result[i] = a[i] ^ cacheKeyHash[i]
+ }
+
+ return memguard.NewBufferFromBytes(result), nil
+}
+
+func privateKeyCacheKey(account *account.Account) ([]byte, error) {
+ address, err := account.Address.String()
+ if err != nil {
+ return []byte(""), fmt.Errorf("err: %w", err)
+ }
+
+ return []byte(cacheKeyPrefix + address), nil
+}
+
+func KeyHash(cacheKeyHash []byte) [32]byte {
+ return blake3.Sum256(cacheKeyHash)
+}
+
+func expirationDuration() time.Duration {
+ fromEnv := os.Getenv("PKEY_CACHE_EXPIRATION_TIME")
+
+ if fromEnv == "" {
+ return defaultExpirationTime
+ }
+
+ duration, err := time.ParseDuration(fromEnv)
+ if err != nil {
+ return defaultExpirationTime
+ }
+
+ return duration
+}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 0843a0853..b3ebe8eda 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -14,73 +14,85 @@ import (
"github.com/massalabs/station-massa-wallet/pkg/wallet"
)
-type SignRule struct {
- Contract string `json:"contract"`
- PasswordPrompt bool `json:"passwordPrompt"`
- AutoSign bool `json:"autoSign"`
-}
-
-type AccountCfg struct {
- SignRules []SignRule `json:"signRules"`
-}
-
-type Config struct {
- Accounts map[string]AccountCfg `json:"accounts"`
-}
-
var (
- cfg *Config
- once sync.Once
- k = koanf.New(".")
+ configManager *ConfigManager
+ once sync.Once
+ k = koanf.New(".")
)
const (
- fileName = "wallet_config.json"
+ fileName = "wallet_config.json"
+ nanoIDLength = 10 // Length of NanoID
)
-func Load() *Config {
- once.Do(func() {
- configDir, err := wallet.Path()
- if err != nil {
- log.Fatalf("Failed to get wallet config directory: %v", err)
- }
+var configFileDirOverride string
- path := filepath.Join(configDir, fileName)
+func Load() *ConfigManager {
+ once.Do(func() {
+ path := configFilePath()
if _, err := os.Stat(path); os.IsNotExist(err) {
- log.Printf("config file not found. Creating default config at %s", path)
- createDefaultConfigFile(path, defaultConfig())
+ log.Printf("Config file not found. Creating default config at %s", path)
+
+ if err := saveConfigUnsafe(defaultConfig()); err != nil {
+ log.Fatalf("Error saving config: %v", err)
+ }
}
if err := k.Load(file.Provider(path), json.Parser()); err != nil {
log.Fatalf("Error loading config: %v", err)
}
- cfg = &Config{}
- if err := k.Unmarshal("", cfg); err != nil {
+ configManager = &ConfigManager{}
+
+ var cfg Config
+ if err := k.Unmarshal("", &cfg); err != nil {
log.Fatalf("Error unmarshaling config: %v", err)
}
+
+ configManager.Config = &cfg
+
+ // Validate all rule IDs
+ if err := configManager.Config.validateAllRuleIDs(); err != nil {
+ log.Fatalf("Invalid rule IDs found: %v", err)
+ }
+
+ if err := saveConfigUnsafe(configManager.Config); err != nil {
+ log.Fatalf("Error saving config: %v", err)
+ }
})
- return cfg
+ return configManager
+}
+
+// Used by unit test
+func SetConfigFileDirOverride(path string) {
+ configFileDirOverride = path
}
func Get() *Config {
- if cfg == nil {
+ if configManager == nil {
log.Fatal("Config not loaded. Call Load() first.")
}
- return cfg
+ configManager.mu.RLock()
+ defer configManager.mu.RUnlock()
+
+ return configManager.Config
}
func GetAccountConfig(accountName string) (*AccountCfg, error) {
- cfg := Get()
-
- account, exists := cfg.Accounts[accountName]
- if !exists {
+ // Use koanf's in-memory structure to get the account configuration
+ key := fmt.Sprintf("accounts.%s", accountName)
+ if !k.Exists(key) {
return nil, fmt.Errorf("account '%s' not found in configuration", accountName)
}
+ var account AccountCfg
+ if err := k.Unmarshal(key, &account); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal account configuration for '%s': %v", accountName, err)
+ }
+
return &account, nil
}
@@ -90,17 +102,49 @@ func defaultConfig() *Config {
}
}
-func createDefaultConfigFile(filePath string, defaultConfig *Config) {
- file, err := os.Create(filePath)
+func configFilePath() string {
+ if configFileDirOverride != "" {
+ return filepath.Join(configFileDirOverride, fileName)
+ }
+
+ configDir, err := wallet.Path()
+ if err != nil {
+ log.Fatalf("Failed to get wallet config directory: %v", err)
+ }
+
+ return filepath.Join(configDir, fileName)
+}
+
+// Public method for saving config
+func SaveConfig(config *Config) error {
+ configManager.mu.Lock()
+ defer configManager.mu.Unlock()
+
+ return saveConfigUnsafe(config)
+}
+
+// saveConfigUnsafe saves the config without locking.
+// It should only be called from methods that already hold the lock.
+func saveConfigUnsafe(config *Config) error {
+ // Check for duplicate IDs
+ if err := config.ensureUniqueRuleIDs(); err != nil {
+ return fmt.Errorf("duplicate SignRule IDs found: %v", err)
+ }
+
+ path := configFilePath()
+
+ file, err := os.Create(path)
if err != nil {
- log.Fatalf("failed to create config file: %v", err)
+ return fmt.Errorf("failed to create or save config file: %v", err)
}
defer file.Close()
encoder := jsonStd.NewEncoder(file)
encoder.SetIndent("", " ") // Pretty-print JSON
- if err := encoder.Encode(defaultConfig); err != nil {
- log.Fatalf("failed to write default config to file: %v", err)
+ if err := encoder.Encode(config); err != nil {
+ return fmt.Errorf("failed to write config to file: %v", err)
}
+
+ return nil
}
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
new file mode 100644
index 000000000..1e509d9f1
--- /dev/null
+++ b/pkg/config/config_test.go
@@ -0,0 +1,185 @@
+package config
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var cfg *Config
+
+const accountName = "test_account"
+
+func TestMain(m *testing.M) {
+ tempDir, err := os.MkdirTemp(os.TempDir(), "*-wallet-dir")
+ if err != nil {
+ log.Fatalf("while creating temporary wallet directory: %s", err.Error())
+ }
+
+ // Load config file with config file path override
+ SetConfigFileDirOverride(tempDir)
+ manager := Load()
+ cfg = manager.Config
+
+ os.Exit(m.Run())
+}
+
+func TestAddSignRule(t *testing.T) {
+ rule := SignRule{
+ Name: "Test Rule",
+ Contract: "test_contract",
+ RuleType: RuleTypeAutoSign,
+ Enabled: true,
+ }
+
+ ruleID, err := cfg.AddSignRule(accountName, rule)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, ruleID)
+
+ // Verify the rule was added
+ account, exists := cfg.Accounts[accountName]
+ assert.True(t, exists)
+ assert.Equal(t, 1, len(account.SignRules))
+ assert.Equal(t, ruleID, account.SignRules[0].ID)
+
+ // Add another rule
+ rule2 := SignRule{
+ Name: "Test Rule 2",
+ Contract: "test_contract_2",
+ RuleType: RuleTypeDisablePasswordPrompt,
+ Enabled: false,
+ }
+
+ ruleID, err = cfg.AddSignRule(accountName, rule2)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, ruleID)
+
+ // Verify the rule was added
+ account, exists = cfg.Accounts[accountName]
+ assert.True(t, exists)
+ assert.Equal(t, 2, len(account.SignRules))
+ assert.Equal(t, ruleID, account.SignRules[1].ID)
+
+ // Add the same rule again and verify it fails (duplicate rule)
+ _, err = cfg.AddSignRule(accountName, rule)
+ assert.Error(t, err)
+
+ _, err = cfg.AddSignRule(accountName, rule2)
+ assert.Error(t, err)
+
+ // check there is no duplicate rule
+ err = cfg.ensureUniqueRuleIDs()
+ assert.NoError(t, err)
+}
+
+func TestDeleteSignRule(t *testing.T) {
+ contract := "test_delete_contract"
+
+ rule := SignRule{
+ Name: "Test Rule",
+ Contract: contract,
+ RuleType: RuleTypeAutoSign,
+ Enabled: true,
+ }
+
+ fmt.Println("TestDeleteSignRule: adding rule", rule)
+
+ ruleID, err := cfg.AddSignRule(accountName, rule)
+ assert.NoError(t, err)
+
+ err = cfg.DeleteSignRule(accountName, ruleID)
+ assert.NoError(t, err)
+
+ // Verify the rule was deleted
+ deletedRule, err := cfg.GetSignRule(accountName, ruleID)
+ assert.Error(t, err, "rule not found")
+ assert.Nil(t, deletedRule)
+}
+
+func TestUpdateSignRule(t *testing.T) {
+ accountName := "test_account"
+ contract := "test_update_contract"
+ rule := SignRule{
+ Name: "Test Rule",
+ Contract: contract,
+ RuleType: RuleTypeAutoSign,
+ Enabled: true,
+ }
+
+ ruleID, err := cfg.AddSignRule(accountName, rule)
+ assert.NoError(t, err)
+
+ newRule := SignRule{
+ Name: "Updated Rule",
+ Contract: "updated_contract",
+ RuleType: RuleTypeDisablePasswordPrompt,
+ Enabled: false,
+ }
+
+ newRuleID, err := cfg.UpdateSignRule(accountName, ruleID, newRule)
+ assert.NoError(t, err)
+ assert.NotEqual(t, ruleID, newRuleID)
+
+ // Verify the rule was updated
+ updatedRule, err := cfg.GetSignRule(accountName, newRuleID)
+ assert.NoError(t, err)
+ assert.Equal(t, newRuleID, updatedRule.ID)
+ assert.Equal(t, newRule.Name, updatedRule.Name)
+ assert.Equal(t, newRule.Contract, updatedRule.Contract)
+ assert.Equal(t, newRule.RuleType, updatedRule.RuleType)
+ assert.Equal(t, newRule.Enabled, updatedRule.Enabled)
+
+ // Verify previous rule is deleted
+ deletedRule, err := cfg.GetSignRule(accountName, ruleID)
+ assert.Error(t, err, "rule not found")
+ assert.Nil(t, deletedRule)
+}
+
+func TestValidateRuleID(t *testing.T) {
+ rule := SignRule{
+ Name: "Test Rule",
+ Contract: "test_contract",
+ RuleType: RuleTypeAutoSign,
+ Enabled: true,
+ }
+
+ rule.ID = generateRuleID(accountName, rule)
+ err := ValidateRuleID(accountName, rule)
+ assert.NoError(t, err)
+
+ // Test with an invalid ID
+ rule.ID = "invalid_id"
+ err = ValidateRuleID(accountName, rule)
+ assert.Error(t, err)
+}
+
+func TestHasEnabledRule(t *testing.T) {
+ accountName := "test_account"
+ contract := "test_hasEnable_contract"
+ rule := SignRule{
+ Name: "Test Rule",
+ Contract: contract,
+ RuleType: RuleTypeAutoSign,
+ Enabled: true,
+ }
+
+ ruleId, err := cfg.AddSignRule(accountName, rule)
+ assert.NoError(t, err)
+
+ hasEnabled := cfg.HasEnabledRule(accountName)
+ assert.True(t, hasEnabled)
+
+ rulePtr := cfg.GetEnabledRuleForContract(accountName, &contract)
+ assert.NotNil(t, rulePtr)
+
+ // Disable the rule and check again
+ rule.Enabled = false
+ _, err = cfg.UpdateSignRule(accountName, ruleId, rule)
+ assert.NoError(t, err)
+
+ rulePtr = cfg.GetEnabledRuleForContract(accountName, &contract)
+ assert.Nil(t, rulePtr)
+}
diff --git a/pkg/config/rules.go b/pkg/config/rules.go
new file mode 100644
index 000000000..105c69f30
--- /dev/null
+++ b/pkg/config/rules.go
@@ -0,0 +1,261 @@
+package config
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+)
+
+func (c *Config) AddSignRule(accountName string, rule SignRule) (string, error) {
+ if err := validateRule(rule); err != nil {
+ return "", fmt.Errorf("invalid rule: %v", err)
+ }
+
+ rule.ID = generateRuleID(accountName, rule)
+
+ configManager.mu.Lock()
+ defer configManager.mu.Unlock()
+
+ // Check if the account exists
+ account, exists := c.Accounts[accountName]
+ if !exists {
+ account = AccountCfg{SignRules: []SignRule{}}
+ c.Accounts[accountName] = account
+ }
+
+ // check if the rule already exists
+ _, err := c.getSignRuleUnsafe(accountName, rule.ID)
+ if err == nil {
+ return "", fmt.Errorf("rule %s already exists", rule.ID)
+ }
+
+ // Add the new rule
+ account.SignRules = append(account.SignRules, rule)
+ c.Accounts[accountName] = account
+
+ err = saveConfigUnsafe(c)
+ if err != nil {
+ return "", fmt.Errorf("error saving configuration: %v", err)
+ }
+
+ return rule.ID, nil
+}
+
+func (c *Config) DeleteSignRule(accountName, ruleID string) error {
+ configManager.mu.Lock()
+ defer configManager.mu.Unlock()
+
+ // Check if the account exists
+ account, exists := c.Accounts[accountName]
+ if !exists {
+ return fmt.Errorf("account not found: %s", accountName)
+ }
+
+ // Find the index of the rule to delete
+ index := -1
+
+ for i, rule := range account.SignRules {
+ if rule.ID == ruleID {
+ index = i
+ break
+ }
+ }
+
+ if index == -1 {
+ return fmt.Errorf("rule not found: %s", ruleID)
+ }
+
+ // Remove the rule from the slice
+ account.SignRules = append(account.SignRules[:index], account.SignRules[index+1:]...)
+ c.Accounts[accountName] = account
+
+ err := saveConfigUnsafe(c)
+ if err != nil {
+ return fmt.Errorf("error saving configuration: %v", err)
+ }
+
+ return nil
+}
+
+func (c *Config) UpdateSignRule(accountName, ruleID string, newRule SignRule) (string, error) {
+ configManager.mu.Lock()
+ defer configManager.mu.Unlock()
+
+ // Check if the account exists
+ account, exists := c.Accounts[accountName]
+ if !exists {
+ return "", fmt.Errorf("account not found: %s", accountName)
+ }
+
+ // Find the index of the rule to update
+ index := -1
+
+ for i, rule := range account.SignRules {
+ if rule.ID == ruleID {
+ index = i
+ break
+ }
+ }
+
+ if index == -1 {
+ return "", fmt.Errorf("rule not found: %s", ruleID)
+ }
+
+ // Delete the existing rule
+ account.SignRules = append(account.SignRules[:index], account.SignRules[index+1:]...)
+ c.Accounts[accountName] = account
+
+ // Add the new rule
+ newRule.ID = generateRuleID(accountName, newRule)
+ account.SignRules = append(account.SignRules, newRule)
+ c.Accounts[accountName] = account
+
+ err := saveConfigUnsafe(c)
+ if err != nil {
+ return "", fmt.Errorf("error saving configuration: %v", err)
+ }
+
+ return newRule.ID, nil
+}
+
+func validateRule(rule SignRule) error {
+ switch rule.RuleType {
+ case RuleTypeDisablePasswordPrompt:
+ return nil
+
+ case RuleTypeAutoSign:
+ if rule.Contract == "*" {
+ return fmt.Errorf("RuleTypeAutoSign cannot have a wildcard contract")
+ }
+
+ return nil
+
+ default:
+ return fmt.Errorf("invalid ruleType: %s", rule.RuleType)
+ }
+}
+
+// Two rules with the same SignRule will always generate the same ID.
+func generateRuleID(accountName string, rule SignRule) string {
+ // Create a string that combines all rule parameters
+ ruleString := fmt.Sprintf("%s:%s:%s", accountName, rule.Contract, string(rule.RuleType))
+
+ hash := sha256.Sum256([]byte(ruleString))
+
+ // Convert the first 10 bytes of the hash to a hex string
+ return hex.EncodeToString(hash[:5])
+}
+
+func (c *Config) ensureUniqueRuleIDs() error {
+ idMap := make(map[string]bool)
+
+ for _, account := range c.Accounts {
+ for _, rule := range account.SignRules {
+ if idMap[rule.ID] {
+ return fmt.Errorf("duplicate ID found: %s", rule.ID)
+ }
+
+ idMap[rule.ID] = true
+ }
+ }
+
+ return nil
+}
+
+func ValidateRuleID(accountName string, rule SignRule) error {
+ // Generate the expected ID based on the rule's parameters
+ expectedID := generateRuleID(accountName, rule)
+
+ // Compare the expected ID with the actual ID
+ if rule.ID != expectedID {
+ return fmt.Errorf("rule ID mismatch: expected %s, got %s", expectedID, rule.ID)
+ }
+
+ return nil
+}
+
+func (c *Config) validateAllRuleIDs() error {
+ for accountName, account := range c.Accounts {
+ for i, rule := range account.SignRules {
+ if err := ValidateRuleID(accountName, rule); err != nil {
+ return fmt.Errorf("invalid rule ID in account '%s', rule index %d: %v", accountName, i, err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (c *Config) getSignRuleUnsafe(accountName, ruleID string) (*SignRule, error) {
+ // Check if the account exists
+ account, exists := c.Accounts[accountName]
+ if !exists {
+ return nil, fmt.Errorf("account not found: %s", accountName)
+ }
+
+ // Search for the rule by ID
+ for _, rule := range account.SignRules {
+ if rule.ID == ruleID {
+ return &rule, nil
+ }
+ }
+
+ return nil, fmt.Errorf("rule not found: %s", ruleID)
+}
+
+func (c *Config) GetSignRule(accountName, ruleID string) (*SignRule, error) {
+ configManager.mu.RLock()
+ defer configManager.mu.RUnlock()
+
+ return c.getSignRuleUnsafe(accountName, ruleID)
+}
+
+func (c *Config) HasEnabledRule(accountName string) bool {
+ configManager.mu.RLock()
+ defer configManager.mu.RUnlock()
+
+ // Check if the account exists
+ account, exists := c.Accounts[accountName]
+ if !exists {
+ return false
+ }
+
+ // Check if there is any enabled rule
+ for _, rule := range account.SignRules {
+ if rule.Enabled {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (c *Config) GetEnabledRuleForContract(accountName string, contract *string) *RuleType {
+ configManager.mu.RLock()
+ defer configManager.mu.RUnlock()
+
+ // Check if the account exists
+ account, exists := c.Accounts[accountName]
+ if !exists {
+ return nil
+ }
+
+ // Check if there is any enabled rule that applies to the contract
+ for _, rule := range account.SignRules {
+ if rule.Enabled {
+ switch rule.RuleType {
+ case RuleTypeAutoSign:
+ if contract != nil && rule.Contract == *contract {
+ return &rule.RuleType
+ }
+
+ case RuleTypeDisablePasswordPrompt:
+ if rule.Contract == "*" || (contract != nil && rule.Contract == *contract) {
+ return &rule.RuleType
+ }
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/config/types.go b/pkg/config/types.go
new file mode 100644
index 000000000..c8068f9fd
--- /dev/null
+++ b/pkg/config/types.go
@@ -0,0 +1,30 @@
+package config
+
+import "sync"
+
+type RuleType string
+
+const (
+ RuleTypeDisablePasswordPrompt RuleType = "disable_password_prompt"
+ RuleTypeAutoSign RuleType = "auto_sign"
+)
+
+type SignRule struct {
+ Name string `koanf:"name"`
+ ID string `koanf:"id"`
+ Contract string `koanf:"contract"`
+ RuleType RuleType `koanf:"ruleType"`
+ Enabled bool `koanf:"enabled"`
+}
+
+type AccountCfg struct {
+ SignRules []SignRule `koanf:"signRules"`
+}
+
+type Config struct {
+ Accounts map[string]AccountCfg `koanf:"accounts"`
+}
+type ConfigManager struct {
+ mu sync.RWMutex
+ Config *Config
+}
diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go
index 5a1f0674a..ed7417181 100644
--- a/pkg/prompt/prompt.go
+++ b/pkg/prompt/prompt.go
@@ -13,10 +13,11 @@ import (
)
type PromptRequest struct {
- Action walletapp.PromptRequestAction
- Msg string
- Data interface{}
- CodeMessage string
+ Action walletapp.PromptRequestAction
+ Msg string
+ Data interface{}
+ CodeMessage string
+ PasswordRequired bool
}
// WalletPrompter is a struct that wraps a Wails GUI application and implements the WalletPrompterInterface interface.
@@ -79,10 +80,10 @@ func WakeUpPrompt(
var err error
switch req.Action {
- case walletapp.Delete, walletapp.Unprotect:
+ case walletapp.Delete, walletapp.Unprotect, walletapp.AddSignRule, walletapp.UpdateSignRule, walletapp.DeleteSignRule:
output, keepListening, err = handlePasswordPrompt(prompterApp, input, acc)
case walletapp.Sign:
- output, keepListening, err = handleSignPrompt(prompterApp, input, acc)
+ output, keepListening, err = handleSignPrompt(prompterApp, req, input, acc)
case walletapp.NewPassword:
output, keepListening, err = handleNewPasswordPrompt(prompterApp, input)
case walletapp.Import:
diff --git a/pkg/prompt/sign.go b/pkg/prompt/sign.go
index 4a172c40f..69643aad8 100644
--- a/pkg/prompt/sign.go
+++ b/pkg/prompt/sign.go
@@ -5,13 +5,40 @@ import (
"strconv"
"github.com/awnumar/memguard"
+ "github.com/massalabs/station-massa-wallet/api/server/models"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/utils"
"github.com/massalabs/station-massa-wallet/pkg/wallet/account"
)
+type PromptRequestSignData struct {
+ Description string
+ Fees string
+ MinFees string
+ OperationType int
+ Coins string
+ Address string
+ Function string
+ MaxCoins string
+ WalletAddress string
+ Nickname string
+ RollCount uint64
+ RecipientAddress string
+ RecipientNickname string
+ Amount string
+ PlainText string
+ AllowFeeEdition bool
+ ChainID int64
+ Assets []models.AssetInfo
+ Parameters []byte
+ DeployedByteCodeSize uint // for executeSC of type deploySC
+ DeployedCoins uint64 // for executeSC of type DeploySC; the number of coins sent to the deployed contract
+ EnabledSignRule *config.RuleType
+}
+
// handleSignPrompt returns the password as a LockedBuffer, or an error if the input is not a string.
-func handleSignPrompt(prompterApp WalletPrompterInterface, input interface{}, acc *account.Account) (*walletapp.SignPromptOutput, bool, error) {
+func handleSignPrompt(prompterApp WalletPrompterInterface, req PromptRequest, input interface{}, acc *account.Account) (*walletapp.SignPromptOutput, bool, error) {
inputObject, ok := input.(*walletapp.SignPromptInput)
if !ok {
return nil, true, InputTypeError(prompterApp)
@@ -25,6 +52,15 @@ func handleSignPrompt(prompterApp WalletPrompterInterface, input interface{}, ac
return nil, true, fmt.Errorf("failed to parse fees: %w", err)
}
+ data, ok := req.Data.(PromptRequestSignData)
+
+ if ok && data.EnabledSignRule != nil && !req.PasswordRequired {
+ // if sign rule is enabled, we don't need to check password
+ return &walletapp.SignPromptOutput{
+ Fees: fees,
+ }, false, nil
+ }
+
inputString := inputObject.Password
password := memguard.NewBufferFromBytes([]byte(inputString))
@@ -42,8 +78,10 @@ func handleSignPrompt(prompterApp WalletPrompterInterface, input interface{}, ac
}
output := &walletapp.SignPromptOutput{
- Password: passwordReturned,
- Fees: fees,
+ PasswordPromptOutput: walletapp.PasswordPromptOutput{
+ Password: passwordReturned,
+ },
+ Fees: fees,
}
return output, false, nil
diff --git a/pkg/types/address.go b/pkg/types/address.go
index 1f1f18f8f..6ddd49e28 100644
--- a/pkg/types/address.go
+++ b/pkg/types/address.go
@@ -1,6 +1,8 @@
package types
import (
+ "fmt"
+
"github.com/massalabs/station-massa-wallet/pkg/types/object"
"lukechampine.com/blake3"
)
@@ -56,6 +58,16 @@ func (a *Address) MarshalText() ([]byte, error) {
return a.Object.MarshalText()
}
+// String returns the Address as a string by marshaling it to text.
+func (a *Address) String() (string, error) {
+ data, err := a.MarshalText()
+ if err != nil {
+ return "", fmt.Errorf("failed to marshal address: %w", err)
+ }
+
+ return string(data), nil
+}
+
// UnmarshalText overloads the TextUnmarshaler interface for Address.
func (a *Address) UnmarshalText(text []byte) error {
if a.Object == nil {
diff --git a/pkg/utils/errors.go b/pkg/utils/errors.go
index 1518ef06f..0301dc227 100644
--- a/pkg/utils/errors.go
+++ b/pkg/utils/errors.go
@@ -34,12 +34,12 @@ const (
// Sentinel errors
var (
- ErrPrivateKeyCache = errors.New("Private key not found in cache")
- ErrCache = errors.New("Error loading cache")
+ ErrPrivateKeyCache = errors.New("private key not found in cache")
+ ErrCache = errors.New("error loading cache")
ErrWrongPassword = errors.New("wrong password")
- ErrActionCanceled = errors.New("Action canceled by user")
+ ErrActionCanceled = errors.New("action canceled by user")
ErrInvalidInputType = errors.New("invalid prompt input type")
- ErrTimeout = errors.New("Password prompt reached timeout")
+ ErrTimeout = errors.New("password prompt reached timeout")
)
func WailsErrorCode(err error) string {
diff --git a/web-frontend/src/i18n/en_US.json b/web-frontend/src/i18n/en_US.json
index e62f11c25..5092157ac 100644
--- a/web-frontend/src/i18n/en_US.json
+++ b/web-frontend/src/i18n/en_US.json
@@ -44,7 +44,7 @@
"placeholder": "Token Address",
"import-button": "Import token",
"import-title": "Import a Fungible Token",
- "import-subtitle": "Input the FT1 token’s address",
+ "import-subtitle": "Input the MRC20 token’s address",
"success": "Token successfully added",
"success-screen": {
"success-message": "{name} ({symbol}) token is added to your wallet.",
From c8e8cf44f2983bba903b304417cdc50c0b901b09 Mon Sep 17 00:00:00 2001
From: Peterjah
Date: Tue, 21 Jan 2025 10:47:12 +0100
Subject: [PATCH 06/10] Use singleton for cache and datastore
---
internal/handler/wallet/add_asset.go | 8 +-
internal/handler/wallet/api.go | 22 ++-
internal/handler/wallet/cache.go | 155 --------------------
internal/handler/wallet/delete_asset.go | 14 +-
internal/handler/wallet/get_all_assets.go | 6 +-
internal/handler/wallet/sign.go | 13 +-
internal/handler/wallet/sign_message.go | 9 +-
internal/handler/wallet/sign_rule_add.go | 9 +-
internal/handler/wallet/sign_rule_delete.go | 6 +-
internal/handler/wallet/sign_rule_test.go | 3 +-
internal/handler/wallet/sign_rule_update.go | 9 +-
internal/handler/wallet/sign_test.go | 14 +-
internal/handler/wallet/transfer.go | 9 +-
pkg/cache/cache.go | 11 +-
14 files changed, 55 insertions(+), 233 deletions(-)
delete mode 100644 internal/handler/wallet/cache.go
diff --git a/internal/handler/wallet/add_asset.go b/internal/handler/wallet/add_asset.go
index 948c66c3e..3379a88c5 100644
--- a/internal/handler/wallet/add_asset.go
+++ b/internal/handler/wallet/add_asset.go
@@ -13,15 +13,13 @@ type AssetInfoListResponse struct {
Assets []models.AssetInfo `json:"assets"`
}
-func NewAddAsset(AssetsStore *assets.AssetsStore, massaClient network.NodeFetcherInterface) operations.AddAssetHandler {
+func NewAddAsset(massaClient network.NodeFetcherInterface) operations.AddAssetHandler {
return &addAsset{
- AssetsStore: AssetsStore,
massaClient: massaClient,
}
}
type addAsset struct {
- AssetsStore *assets.AssetsStore
massaClient network.NodeFetcherInterface
}
@@ -41,7 +39,7 @@ func (a *addAsset) Handle(params operations.AddAssetParams) middleware.Responder
}
// Check if the address exists in the loaded JSON
- if a.AssetsStore.AssetExists(params.Nickname, params.AssetAddress) {
+ if assets.Store.AssetExists(params.Nickname, params.AssetAddress) {
// Return that the asset already exists
errorMsg := "Asset with the provided address already exists."
return operations.NewAddAssetBadRequest().WithPayload(&models.Error{Code: errorAssetExists, Message: errorMsg})
@@ -56,7 +54,7 @@ func (a *addAsset) Handle(params operations.AddAssetParams) middleware.Responder
}
// Add Asset and persist in JSON file.
- if err := a.AssetsStore.AddAsset(params.Nickname, *assetInfoFromSC); err != nil {
+ if err := assets.Store.AddAsset(params.Nickname, *assetInfoFromSC); err != nil {
// Return error occurred while persisting the asset
errorMsg := "Failed to add the asset to the JSON file."
return operations.NewAddAssetInternalServerError().WithPayload(&models.Error{Code: errorAddAssetJSON, Message: errorMsg})
diff --git a/internal/handler/wallet/api.go b/internal/handler/wallet/api.go
index d73d7ff6e..87e4fe9e3 100644
--- a/internal/handler/wallet/api.go
+++ b/internal/handler/wallet/api.go
@@ -8,8 +8,6 @@ import (
"github.com/go-openapi/runtime/middleware"
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
- "github.com/massalabs/station-massa-wallet/pkg/assets"
- "github.com/massalabs/station-massa-wallet/pkg/cache"
"github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/massalabs/station-massa-wallet/pkg/openapi"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
@@ -21,27 +19,25 @@ import (
// AppendEndpoints appends wallet endpoints to the API
// Note: the password prompter is mandatory for sign endpoint
func AppendEndpoints(api *operations.MassaWalletAPI, prompterApp prompt.WalletPrompterInterface, massaClient network.NodeFetcherInterface) {
- gc := cache.Get()
- assetStore := assets.Store
api.CreateAccountHandler = NewCreateAccount(prompterApp, massaClient)
api.DeleteAccountHandler = NewDelete(prompterApp, massaClient)
api.ImportAccountHandler = NewImport(prompterApp, massaClient)
api.AccountListHandler = NewGetAll(prompterApp.App().Wallet, massaClient)
- api.SignHandler = NewSign(prompterApp, gc, assetStore)
- api.SignMessageHandler = NewSignMessage(prompterApp, gc)
+ api.SignHandler = NewSign(prompterApp)
+ api.SignMessageHandler = NewSignMessage(prompterApp)
api.GetAccountHandler = NewGet(prompterApp, massaClient)
api.ExportAccountFileHandler = NewWalletExportFile(prompterApp.App().Wallet)
- api.TransferCoinHandler = NewTransferCoin(prompterApp, massaClient, gc)
+ api.TransferCoinHandler = NewTransferCoin(prompterApp, massaClient)
api.TradeRollsHandler = NewTradeRolls(prompterApp, massaClient)
api.BackupAccountHandler = NewBackupAccount(prompterApp)
api.UpdateAccountHandler = NewUpdateAccount(prompterApp, massaClient)
- api.AddAssetHandler = NewAddAsset(assetStore, massaClient)
- api.GetAllAssetsHandler = NewGetAllAssets(prompterApp.App().Wallet, assetStore, massaClient)
- api.DeleteAssetHandler = NewDeleteAsset(assetStore)
+ api.AddAssetHandler = NewAddAsset(massaClient)
+ api.GetAllAssetsHandler = NewGetAllAssets(prompterApp.App().Wallet, massaClient)
+ api.DeleteAssetHandler = NewDeleteAsset()
api.GetConfigHandler = NewGetConfig()
- api.AddSignRuleHandler = NewAddSignRuleHandler(prompterApp, gc)
- api.DeleteSignRuleHandler = NewDeleteSignRuleHandler(prompterApp, gc)
- api.UpdateSignRuleHandler = NewUpdateSignRuleHandler(prompterApp, gc)
+ api.AddSignRuleHandler = NewAddSignRuleHandler(prompterApp)
+ api.DeleteSignRuleHandler = NewDeleteSignRuleHandler(prompterApp)
+ api.UpdateSignRuleHandler = NewUpdateSignRuleHandler(prompterApp)
}
// loadAccount loads a wallet from the file system or returns an error.
diff --git a/internal/handler/wallet/cache.go b/internal/handler/wallet/cache.go
deleted file mode 100644
index a19c900d9..000000000
--- a/internal/handler/wallet/cache.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package wallet
-
-import (
- "crypto/ed25519"
- "fmt"
- "os"
- "time"
-
- "github.com/awnumar/memguard"
- "github.com/bluele/gcache"
- "github.com/massalabs/station-massa-wallet/pkg/utils"
- "github.com/massalabs/station-massa-wallet/pkg/wallet/account"
- "lukechampine.com/blake3"
-)
-
-const (
- defaultExpirationTime = time.Hour * 24 * 30
- cacheKeyPrefix = "pkey"
- notFoundMsg = "private key not found in cache"
-)
-
-func CachePrivateKeyFromPassword(gc gcache.Cache, account *account.Account, password *memguard.LockedBuffer) error {
- privateKey, err := account.PrivateKeyBytesInClear(password)
- if err != nil {
- return fmt.Errorf("error caching private key: %w", err)
- }
-
- err = CachePrivateKey(gc, account, privateKey)
- if err != nil {
- return fmt.Errorf("error caching private key: %w", err)
- }
-
- return nil
-}
-
-func CachePrivateKey(gc gcache.Cache, account *account.Account, privateKey *memguard.LockedBuffer) error {
- cacheKey, err := privateKeyCacheKey(account)
- if err != nil {
- return fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
- }
-
- key := KeyHash([]byte(cacheKey))
-
- // Concatenate the key with itself to make it 64 bytes long
- var extendedKey [64]byte
- copy(extendedKey[:], append(key[:], key[:]...))
-
- cipheredPrivateKey, err := Xor(privateKey, extendedKey)
- if err != nil {
- return fmt.Errorf("%w: %w", utils.ErrCache, err)
- }
-
- cacheValue := make([]byte, ed25519.PrivateKeySize)
- copy(cacheValue, cipheredPrivateKey.Bytes())
- cipheredPrivateKey.Destroy()
-
- err = gc.SetWithExpire(key, cacheValue, expirationDuration())
- if err != nil {
- return fmt.Errorf("error set private key in cache: %w", err)
- }
-
- return nil
-}
-
-// privateKeyFromCache return the private key from the cache or an error.
-func privateKeyFromCache(
- gc gcache.Cache,
- acc *account.Account,
-) (*memguard.LockedBuffer, error) {
- cacheKey, err := privateKeyCacheKey(acc)
- if err != nil {
- return nil, fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
- }
-
- keyHash := KeyHash([]byte(cacheKey))
-
- value, err := gc.Get(keyHash)
- if err != nil {
- if err.Error() == gcache.KeyNotFoundError.Error() {
- return nil, fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
- }
-
- return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
- }
-
- if value == nil {
- return nil, fmt.Errorf("%w: %s", utils.ErrCache, notFoundMsg)
- }
-
- byteValue, ok := value.([]byte)
- if !ok {
- return nil, fmt.Errorf("%w: %s", utils.ErrCache, "value is not a byte array")
- }
-
- cacheValue := make([]byte, ed25519.PrivateKeySize)
- copy(cacheValue, byteValue)
-
- cipheredPrivateKey := memguard.NewBufferFromBytes(cacheValue)
-
- // Concatenate the key with itself to make it 64 bytes long
- var extendedKey [64]byte
- copy(extendedKey[:], append(keyHash[:], keyHash[:]...))
-
- privateKey, err := Xor(cipheredPrivateKey, extendedKey)
- if err != nil {
- return nil, fmt.Errorf("%w: %w", utils.ErrCache, err)
- }
-
- cipheredPrivateKey.Destroy()
-
- return privateKey, nil
-}
-
-func Xor(pkey *memguard.LockedBuffer, cacheKeyHash [64]byte) (*memguard.LockedBuffer, error) {
- a := pkey.Bytes()
-
- if len(a) != len(cacheKeyHash) {
- return nil, fmt.Errorf("length of two arrays must be same, %d and %d", len(a), len(cacheKeyHash))
- }
- result := make([]byte, len(a))
-
- for i := 0; i < len(a); i++ {
- result[i] = a[i] ^ cacheKeyHash[i]
- }
-
- return memguard.NewBufferFromBytes(result), nil
-}
-
-func privateKeyCacheKey(account *account.Account) ([]byte, error) {
- address, err := account.Address.String()
- if err != nil {
- return []byte(""), fmt.Errorf("err: %w", err)
- }
-
- return []byte(cacheKeyPrefix + address), nil
-}
-
-func KeyHash(cacheKeyHash []byte) [32]byte {
- return blake3.Sum256(cacheKeyHash)
-}
-
-func expirationDuration() time.Duration {
- fromEnv := os.Getenv("PKEY_CACHE_EXPIRATION_TIME")
-
- if fromEnv == "" {
- return defaultExpirationTime
- }
-
- duration, err := time.ParseDuration(fromEnv)
- if err != nil {
- return defaultExpirationTime
- }
-
- return duration
-}
diff --git a/internal/handler/wallet/delete_asset.go b/internal/handler/wallet/delete_asset.go
index f31b642bb..c9fce6f57 100644
--- a/internal/handler/wallet/delete_asset.go
+++ b/internal/handler/wallet/delete_asset.go
@@ -8,15 +8,11 @@ import (
"github.com/massalabs/station-massa-wallet/pkg/utils"
)
-func NewDeleteAsset(AssetsStore *assets.AssetsStore) operations.DeleteAssetHandler {
- return &deleteAsset{
- AssetsStore: AssetsStore,
- }
+func NewDeleteAsset() operations.DeleteAssetHandler {
+ return &deleteAsset{}
}
-type deleteAsset struct {
- AssetsStore *assets.AssetsStore
-}
+type deleteAsset struct{}
func (d *deleteAsset) Handle(params operations.DeleteAssetParams) middleware.Responder {
// Check if the address is valid
@@ -27,14 +23,14 @@ func (d *deleteAsset) Handle(params operations.DeleteAssetParams) middleware.Res
}
// Check if the asset exists in the loaded JSON
- if !d.AssetsStore.AssetExists(params.Nickname, params.AssetAddress) {
+ if !assets.Store.AssetExists(params.Nickname, params.AssetAddress) {
// Return an error indicating that the asset does not exist
errorMsg := "Asset with the provided address does not exist."
return operations.NewDeleteAssetBadRequest().WithPayload(&models.Error{Code: errorAssetNotExists, Message: errorMsg})
}
// Delete Asset From the JSON file.
- if err := d.AssetsStore.DeleteAsset(params.Nickname, params.AssetAddress); err != nil {
+ if err := assets.Store.DeleteAsset(params.Nickname, params.AssetAddress); err != nil {
// Return error occurred while persisting the asset
errorMsg := "Failed to delete the asset from the JSON file."
return operations.NewDeleteAssetInternalServerError().WithPayload(&models.Error{Code: errorDeleteAssetJSON, Message: errorMsg})
diff --git a/internal/handler/wallet/get_all_assets.go b/internal/handler/wallet/get_all_assets.go
index ab6851794..588dcb87a 100644
--- a/internal/handler/wallet/get_all_assets.go
+++ b/internal/handler/wallet/get_all_assets.go
@@ -15,17 +15,15 @@ import (
"github.com/massalabs/station/pkg/logger"
)
-func NewGetAllAssets(wallet *wallet.Wallet, AssetsStore *assets.AssetsStore, massaClient network.NodeFetcherInterface) operations.GetAllAssetsHandler {
+func NewGetAllAssets(wallet *wallet.Wallet, massaClient network.NodeFetcherInterface) operations.GetAllAssetsHandler {
return &getAllAssets{
wallet: wallet,
- AssetsStore: AssetsStore,
massaClient: massaClient,
}
}
type getAllAssets struct {
wallet *wallet.Wallet
- AssetsStore *assets.AssetsStore
massaClient network.NodeFetcherInterface
}
@@ -124,7 +122,7 @@ func (g *getAllAssets) getMASAsset(acc *account.Account) (*assets.AssetInfoWithB
// getAssetsData fetches the balance and dollar value for each asset in the account.
// If user has asset that are deployed on another network, it will not be included.
func (g *getAllAssets) getAssetsData(acc *account.Account, chainID int) []*assets.AssetInfoWithBalances {
- assetsInfo := g.AssetsStore.All(acc.Nickname, chainID)
+ assetsInfo := assets.Store.All(acc.Nickname, chainID)
assetsWithBalance := make([]*assets.AssetInfoWithBalances, 0)
var wg sync.WaitGroup
diff --git a/internal/handler/wallet/sign.go b/internal/handler/wallet/sign.go
index 88195a2a3..460d40e65 100644
--- a/internal/handler/wallet/sign.go
+++ b/internal/handler/wallet/sign.go
@@ -7,7 +7,6 @@ import (
"strconv"
"github.com/awnumar/memguard"
- "github.com/bluele/gcache"
"github.com/go-openapi/runtime/middleware"
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
@@ -34,14 +33,12 @@ const (
RollPrice = 100
)
-func NewSign(prompterApp prompt.WalletPrompterInterface, gc gcache.Cache, AssetsStore *assets.AssetsStore) operations.SignHandler {
- return &walletSign{gc: gc, prompterApp: prompterApp, AssetsStore: AssetsStore}
+func NewSign(prompterApp prompt.WalletPrompterInterface) operations.SignHandler {
+ return &walletSign{prompterApp: prompterApp}
}
type walletSign struct {
prompterApp prompt.WalletPrompterInterface
- gc gcache.Cache
- AssetsStore *assets.AssetsStore
}
func (w *walletSign) Handle(params operations.SignParams) middleware.Responder {
@@ -70,7 +67,7 @@ func (w *walletSign) Handle(params operations.SignParams) middleware.Responder {
if enabledRule != nil {
// at this point, we have a rule enabled for the contract, if private key is cached, we don't need to prompt for password
- privateKey, err = cache.PrivateKeyFromCache(w.gc, acc)
+ privateKey, err = cache.PrivateKeyFromCache(acc)
if err != nil {
logger.Warn("error retriving private key from cache: ", err)
} else if privateKey != nil {
@@ -108,7 +105,7 @@ func (w *walletSign) Handle(params operations.SignParams) middleware.Responder {
}
if cfg.HasEnabledRule(acc.Nickname) {
- err = cache.CachePrivateKey(w.gc, acc, privateKey)
+ err = cache.CachePrivateKey(acc, privateKey)
if err != nil {
return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
}
@@ -225,7 +222,7 @@ func (w *walletSign) getPromptRequest(params operations.SignParams, acc *account
data.OperationType = int(opType)
data.AllowFeeEdition = *params.AllowFeeEdition
data.ChainID = *params.Body.ChainID
- data.Assets = convertAssetsToModel(w.AssetsStore.All(acc.Nickname, int(data.ChainID)))
+ data.Assets = convertAssetsToModel(assets.Store.All(acc.Nickname, int(data.ChainID)))
promptRequest := prompt.PromptRequest{
Action: walletapp.Sign,
diff --git a/internal/handler/wallet/sign_message.go b/internal/handler/wallet/sign_message.go
index a357213cc..91f3e58ce 100644
--- a/internal/handler/wallet/sign_message.go
+++ b/internal/handler/wallet/sign_message.go
@@ -5,11 +5,11 @@ import (
"fmt"
"net/http"
- "github.com/bluele/gcache"
"github.com/go-openapi/runtime/middleware"
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
"github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
"github.com/massalabs/station-massa-wallet/pkg/utils"
@@ -29,13 +29,12 @@ type PromptRequestSignMessageData struct {
DisplayData bool
}
-func NewSignMessage(prompterApp prompt.WalletPrompterInterface, gc gcache.Cache) operations.SignMessageHandler {
- return &walletSignMessage{gc: gc, prompterApp: prompterApp}
+func NewSignMessage(prompterApp prompt.WalletPrompterInterface) operations.SignMessageHandler {
+ return &walletSignMessage{prompterApp: prompterApp}
}
type walletSignMessage struct {
prompterApp prompt.WalletPrompterInterface
- gc gcache.Cache
}
func (w *walletSignMessage) Handle(params operations.SignMessageParams) middleware.Responder {
@@ -73,7 +72,7 @@ func (w *walletSignMessage) Handle(params operations.SignMessageParams) middlewa
cfg := config.Get()
if cfg.HasEnabledRule(acc.Nickname) {
- err = CachePrivateKey(w.gc, acc, output.Password)
+ err = cache.CachePrivateKey(acc, output.Password)
if err != nil {
return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
}
diff --git a/internal/handler/wallet/sign_rule_add.go b/internal/handler/wallet/sign_rule_add.go
index 9938b0637..9d4436760 100644
--- a/internal/handler/wallet/sign_rule_add.go
+++ b/internal/handler/wallet/sign_rule_add.go
@@ -4,11 +4,11 @@ import (
"fmt"
"net/http"
- "github.com/bluele/gcache"
"github.com/go-openapi/runtime/middleware"
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
"github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
"github.com/massalabs/station-massa-wallet/pkg/utils"
@@ -23,13 +23,12 @@ type SignRulePromptData struct {
SignRule config.SignRule
}
-func NewAddSignRuleHandler(prompterApp prompt.WalletPrompterInterface, gc gcache.Cache) operations.AddSignRuleHandler {
- return &addSignRuleHandler{gc: gc, prompterApp: prompterApp}
+func NewAddSignRuleHandler(prompterApp prompt.WalletPrompterInterface) operations.AddSignRuleHandler {
+ return &addSignRuleHandler{prompterApp: prompterApp}
}
type addSignRuleHandler struct {
prompterApp prompt.WalletPrompterInterface
- gc gcache.Cache
}
func (w *addSignRuleHandler) Handle(params operations.AddSignRuleParams) middleware.Responder {
@@ -68,7 +67,7 @@ func (w *addSignRuleHandler) Handle(params operations.AddSignRuleParams) middlew
}
if cfg.HasEnabledRule(acc.Nickname) {
- err = CachePrivateKeyFromPassword(w.gc, acc, password)
+ err = cache.CachePrivateKeyFromPassword(acc, password)
if err != nil {
return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
}
diff --git a/internal/handler/wallet/sign_rule_delete.go b/internal/handler/wallet/sign_rule_delete.go
index 5bc9d0de0..ae6bda25e 100644
--- a/internal/handler/wallet/sign_rule_delete.go
+++ b/internal/handler/wallet/sign_rule_delete.go
@@ -4,7 +4,6 @@ import (
"fmt"
"net/http"
- "github.com/bluele/gcache"
"github.com/go-openapi/runtime/middleware"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
@@ -15,13 +14,12 @@ import (
"github.com/pkg/errors"
)
-func NewDeleteSignRuleHandler(prompterApp prompt.WalletPrompterInterface, gc gcache.Cache) operations.DeleteSignRuleHandler {
- return &deleteSignRuleHandler{gc: gc, prompterApp: prompterApp}
+func NewDeleteSignRuleHandler(prompterApp prompt.WalletPrompterInterface) operations.DeleteSignRuleHandler {
+ return &deleteSignRuleHandler{prompterApp: prompterApp}
}
type deleteSignRuleHandler struct {
prompterApp prompt.WalletPrompterInterface
- gc gcache.Cache
}
func (w *deleteSignRuleHandler) Handle(params operations.DeleteSignRuleParams) middleware.Responder {
diff --git a/internal/handler/wallet/sign_rule_test.go b/internal/handler/wallet/sign_rule_test.go
index ad0dd5c04..e11ecce38 100644
--- a/internal/handler/wallet/sign_rule_test.go
+++ b/internal/handler/wallet/sign_rule_test.go
@@ -8,6 +8,7 @@ import (
"github.com/massalabs/station-massa-wallet/api/server/models"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
assertLib "github.com/stretchr/testify/assert"
)
@@ -62,7 +63,7 @@ func Test_signrule_Handlers(t *testing.T) {
assert.NotEmpty(addRuleResponse.ID)
// check that privateKey is cached
- pkey, err := privateKeyFromCache(testCache, account)
+ pkey, err := cache.PrivateKeyFromCache(account)
assert.NoError(err)
assert.NotNil(pkey)
})
diff --git a/internal/handler/wallet/sign_rule_update.go b/internal/handler/wallet/sign_rule_update.go
index ca3e703ed..3f1a63a9f 100644
--- a/internal/handler/wallet/sign_rule_update.go
+++ b/internal/handler/wallet/sign_rule_update.go
@@ -4,11 +4,11 @@ import (
"fmt"
"net/http"
- "github.com/bluele/gcache"
"github.com/go-openapi/runtime/middleware"
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
"github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
"github.com/massalabs/station-massa-wallet/pkg/utils"
@@ -16,13 +16,12 @@ import (
"github.com/pkg/errors"
)
-func NewUpdateSignRuleHandler(prompterApp prompt.WalletPrompterInterface, gc gcache.Cache) operations.UpdateSignRuleHandler {
- return &updateSignRuleHandler{gc: gc, prompterApp: prompterApp}
+func NewUpdateSignRuleHandler(prompterApp prompt.WalletPrompterInterface) operations.UpdateSignRuleHandler {
+ return &updateSignRuleHandler{prompterApp: prompterApp}
}
type updateSignRuleHandler struct {
prompterApp prompt.WalletPrompterInterface
- gc gcache.Cache
}
func (w *updateSignRuleHandler) Handle(params operations.UpdateSignRuleParams) middleware.Responder {
@@ -66,7 +65,7 @@ func (w *updateSignRuleHandler) Handle(params operations.UpdateSignRuleParams) m
}
if cfg.HasEnabledRule(acc.Nickname) {
- err = CachePrivateKeyFromPassword(w.gc, acc, password)
+ err = cache.CachePrivateKeyFromPassword(acc, password)
if err != nil {
return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
}
diff --git a/internal/handler/wallet/sign_test.go b/internal/handler/wallet/sign_test.go
index 2328b5b74..5d26fa681 100644
--- a/internal/handler/wallet/sign_test.go
+++ b/internal/handler/wallet/sign_test.go
@@ -11,6 +11,7 @@ import (
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
"github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/utils"
"github.com/stretchr/testify/assert"
@@ -211,7 +212,7 @@ func Test_walletSign_Handle(t *testing.T) {
checkResultChannel(t, result, true, "")
// check that privateKey is cached
- pkey, err := privateKeyFromCache(testCache, account)
+ pkey, err := cache.PrivateKeyFromCache(account)
assert.NoError(t, err)
assert.NotNil(t, pkey)
@@ -262,7 +263,7 @@ func Test_walletSign_Handle(t *testing.T) {
checkResultChannel(t, result, true, "")
// check that privateKey is cached
- pkey, err := privateKeyFromCache(testCache, account)
+ pkey, err := cache.PrivateKeyFromCache(account)
assert.NoError(t, err)
assert.NotNil(t, pkey)
@@ -319,7 +320,6 @@ func Test_walletSign_Handle(t *testing.T) {
res <- (<-resChan)
}(testResult)
- fmt.Println(">>>>Sign transaction password needed")
resp := signTransaction(t, api, nickname, transactionData)
verifyStatusCode(t, resp, http.StatusOK)
@@ -328,7 +328,7 @@ func Test_walletSign_Handle(t *testing.T) {
checkResultChannel(t, result, true, "")
// check that privateKey is cached
- pkey, err := privateKeyFromCache(testCache, account)
+ pkey, err := cache.PrivateKeyFromCache(account)
assert.NoError(t, err)
assert.NotNil(t, pkey)
@@ -342,8 +342,6 @@ func Test_walletSign_Handle(t *testing.T) {
res <- (<-resChan)
}(testResult)
- fmt.Println(">>>>Sign transaction password NOT needed")
-
resp = signTransaction(t, api, nickname, transactionData)
verifyStatusCode(t, resp, http.StatusOK)
@@ -394,7 +392,7 @@ func Test_walletSign_Handle(t *testing.T) {
checkResultChannel(t, result, true, "")
// check that privateKey is cached
- pkey, err := privateKeyFromCache(testCache, account)
+ pkey, err := cache.PrivateKeyFromCache(account)
assert.NoError(t, err)
assert.NotNil(t, pkey)
@@ -459,7 +457,7 @@ func Test_walletSign_Handle(t *testing.T) {
checkResultChannel(t, result, true, "")
// check that privateKey is cached
- pkey, err := privateKeyFromCache(testCache, account)
+ pkey, err := cache.PrivateKeyFromCache(account)
assert.NoError(t, err)
assert.NotNil(t, pkey)
diff --git a/internal/handler/wallet/transfer.go b/internal/handler/wallet/transfer.go
index 44abf93a2..91f91e703 100644
--- a/internal/handler/wallet/transfer.go
+++ b/internal/handler/wallet/transfer.go
@@ -6,11 +6,11 @@ import (
"strconv"
"github.com/awnumar/memguard"
- "github.com/bluele/gcache"
"github.com/go-openapi/runtime/middleware"
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
"github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
@@ -20,14 +20,13 @@ import (
"github.com/massalabs/station/pkg/node/sendoperation/transaction"
)
-func NewTransferCoin(prompterApp prompt.WalletPrompterInterface, massaClient network.NodeFetcherInterface, gc gcache.Cache) operations.TransferCoinHandler {
- return &transferCoin{prompterApp: prompterApp, massaClient: massaClient, gc: gc}
+func NewTransferCoin(prompterApp prompt.WalletPrompterInterface, massaClient network.NodeFetcherInterface) operations.TransferCoinHandler {
+ return &transferCoin{prompterApp: prompterApp, massaClient: massaClient}
}
type transferCoin struct {
prompterApp prompt.WalletPrompterInterface
massaClient network.NodeFetcherInterface
- gc gcache.Cache
}
func (t *transferCoin) Handle(params operations.TransferCoinParams) middleware.Responder {
@@ -113,7 +112,7 @@ func (t *transferCoin) Handle(params operations.TransferCoinParams) middleware.R
cfg := config.Get()
if cfg.HasEnabledRule(acc.Nickname) {
- err = CachePrivateKey(t.gc, acc, output.Password)
+ err = cache.CachePrivateKey(acc, output.Password)
if err != nil {
return newErrorResponse(err.Error(), errorCachePrivateKey, http.StatusInternalServerError)
}
diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go
index 354ade26e..f6fdecc0f 100644
--- a/pkg/cache/cache.go
+++ b/pkg/cache/cache.go
@@ -36,13 +36,13 @@ const (
cacheSize = 100
)
-func CachePrivateKeyFromPassword(gc gcache.Cache, account *account.Account, password *memguard.LockedBuffer) error {
+func CachePrivateKeyFromPassword(account *account.Account, password *memguard.LockedBuffer) error {
privateKey, err := account.PrivateKeyBytesInClear(password)
if err != nil {
return fmt.Errorf("error caching private key: %w", err)
}
- err = CachePrivateKey(gc, account, privateKey)
+ err = CachePrivateKey(account, privateKey)
if err != nil {
return fmt.Errorf("error caching private key: %w", err)
}
@@ -50,7 +50,7 @@ func CachePrivateKeyFromPassword(gc gcache.Cache, account *account.Account, pass
return nil
}
-func CachePrivateKey(gc gcache.Cache, account *account.Account, privateKey *memguard.LockedBuffer) error {
+func CachePrivateKey(account *account.Account, privateKey *memguard.LockedBuffer) error {
cacheKey, err := privateKeyCacheKey(account)
if err != nil {
return fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
@@ -71,7 +71,7 @@ func CachePrivateKey(gc gcache.Cache, account *account.Account, privateKey *memg
copy(cacheValue, cipheredPrivateKey.Bytes())
cipheredPrivateKey.Destroy()
- err = gc.SetWithExpire(key, cacheValue, expirationDuration())
+ err = cache.SetWithExpire(key, cacheValue, expirationDuration())
if err != nil {
return fmt.Errorf("error set private key in cache: %w", err)
}
@@ -81,7 +81,6 @@ func CachePrivateKey(gc gcache.Cache, account *account.Account, privateKey *memg
// privateKeyFromCache return the private key from the cache or an error.
func PrivateKeyFromCache(
- gc gcache.Cache,
acc *account.Account,
) (*memguard.LockedBuffer, error) {
cacheKey, err := privateKeyCacheKey(acc)
@@ -91,7 +90,7 @@ func PrivateKeyFromCache(
keyHash := KeyHash([]byte(cacheKey))
- value, err := gc.Get(keyHash)
+ value, err := cache.Get(keyHash)
if err != nil {
if err.Error() == gcache.KeyNotFoundError.Error() {
return nil, fmt.Errorf("%w: %w", utils.ErrPrivateKeyCache, err)
From af30a139e77231a5d2fa1334046ee7628f85e8d8 Mon Sep 17 00:00:00 2001
From: Peterjah
Date: Tue, 21 Jan 2025 13:38:02 +0100
Subject: [PATCH 07/10] add signrule endpoints tests
---
cmd/massa-wallet/massa-wallet.go | 5 +-
internal/handler/wallet/api_test.go | 6 +-
.../handler/wallet/get_all_assets_test.go | 14 +-
internal/handler/wallet/sign_rule_test.go | 226 ++++++++++++++----
pkg/assets/asset.go | 18 +-
pkg/assets/asset_test.go | 15 +-
pkg/cache/cache.go | 2 +-
pkg/config/rules.go | 11 +-
8 files changed, 227 insertions(+), 70 deletions(-)
diff --git a/cmd/massa-wallet/massa-wallet.go b/cmd/massa-wallet/massa-wallet.go
index 36698ecae..802f10142 100644
--- a/cmd/massa-wallet/massa-wallet.go
+++ b/cmd/massa-wallet/massa-wallet.go
@@ -8,6 +8,7 @@ import (
"github.com/massalabs/station-massa-wallet/internal/handler"
walletApp "github.com/massalabs/station-massa-wallet/pkg/app"
"github.com/massalabs/station-massa-wallet/pkg/assets"
+ "github.com/massalabs/station-massa-wallet/pkg/cache"
"github.com/massalabs/station-massa-wallet/pkg/config"
"github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/massalabs/station-massa-wallet/pkg/prompt"
@@ -24,7 +25,9 @@ func StartServer(app *walletApp.WalletApp) {
promptApp = prompt.NewEnvPrompter(app)
}
- _, err := assets.InitAssetsStore("", massaClient)
+ cache.Init()
+
+ _, err := assets.InitAssetsStore(massaClient)
if err != nil {
logger.Fatalf("Failed to create AssetsStore: %v", err)
}
diff --git a/internal/handler/wallet/api_test.go b/internal/handler/wallet/api_test.go
index edf7bdb52..3771c561b 100644
--- a/internal/handler/wallet/api_test.go
+++ b/internal/handler/wallet/api_test.go
@@ -78,7 +78,9 @@ func MockAPI() (*operations.MassaWalletAPI, chan walletapp.EventData, error) {
nodeFetcher := network.NewNodeFetcher()
- testAssetStore, err = assets.InitAssetsStore(walletPath, nodeFetcher)
+ assets.SetFileDirOverride(walletPath)
+
+ testAssetStore, err = assets.InitAssetsStore(nodeFetcher)
if err != nil {
log.Fatalf("Failed to create AssetsStore: %v", err)
}
@@ -89,7 +91,7 @@ func MockAPI() (*operations.MassaWalletAPI, chan walletapp.EventData, error) {
config.SetConfigFileDirOverride(walletPath)
config.Load()
- testCache = cache.Get()
+ testCache = cache.Init()
// Set wallet API endpoints
AppendEndpoints(massaWalletAPI, prompterAppMock, massaNodeMock)
diff --git a/internal/handler/wallet/get_all_assets_test.go b/internal/handler/wallet/get_all_assets_test.go
index c8df4c46d..a34ad4ebf 100644
--- a/internal/handler/wallet/get_all_assets_test.go
+++ b/internal/handler/wallet/get_all_assets_test.go
@@ -4,13 +4,11 @@ import (
"encoding/json"
"fmt"
"net/http"
- "os"
"testing"
"github.com/massalabs/station-massa-wallet/api/server/models"
"github.com/massalabs/station-massa-wallet/api/server/restapi/operations"
"github.com/massalabs/station-massa-wallet/pkg/assets"
- "github.com/massalabs/station-massa-wallet/pkg/network"
"github.com/stretchr/testify/assert"
)
@@ -63,12 +61,12 @@ func assertAssetInfoWithBalanceEqual(t *testing.T, actual, expected *models.Asse
}
func getExpectedAssetsCount(t *testing.T) int {
- tempDir, err := os.MkdirTemp(os.TempDir(), "*-wallet-dir")
- assert.NoError(t, err)
- nodeFetcher := network.NewNodeFetcher()
- store, err := assets.InitAssetsStore(tempDir, nodeFetcher)
- assert.NoError(t, err)
- defaultAssets, err := store.Default()
+ // tempDir, err := os.MkdirTemp(os.TempDir(), "*-wallet-dir")
+ // assert.NoError(t, err)
+ // nodeFetcher := network.NewNodeFetcher()
+ // store, err := assets.InitAssetsStore(tempDir, nodeFetcher)
+ // assert.NoError(t, err)
+ defaultAssets, err := assets.Store.Default()
assert.NoError(t, err)
counter := 0
diff --git a/internal/handler/wallet/sign_rule_test.go b/internal/handler/wallet/sign_rule_test.go
index e11ecce38..d2ec49ab3 100644
--- a/internal/handler/wallet/sign_rule_test.go
+++ b/internal/handler/wallet/sign_rule_test.go
@@ -9,11 +9,12 @@ import (
"github.com/massalabs/station-massa-wallet/api/server/models"
walletapp "github.com/massalabs/station-massa-wallet/pkg/app"
"github.com/massalabs/station-massa-wallet/pkg/cache"
- assertLib "github.com/stretchr/testify/assert"
+ "github.com/massalabs/station-massa-wallet/pkg/config"
+ "github.com/stretchr/testify/assert"
)
func Test_signrule_Handlers(t *testing.T) {
- assert := assertLib.New(t)
+ assert := assert.New(t)
api, resChan, err := MockAPI()
assert.NoError(err)
@@ -21,18 +22,30 @@ func Test_signrule_Handlers(t *testing.T) {
addhandler, exist := api.HandlerFor("post", "/api/accounts/{nickname}/signrules")
assert.True(exist)
+ updatehandler, exist := api.HandlerFor("put", "/api/accounts/{nickname}/signrules/{ruleId}")
+ assert.True(exist)
+
+ deletehandler, exist := api.HandlerFor("delete", "/api/accounts/{nickname}/signrules/{ruleId}")
+ assert.True(exist)
+
nickname := "zeWahLetName"
password := "zePassword"
account := createAccount(password, nickname, t, prompterAppMock)
+ cfg := config.Get()
+
t.Run("Add sign rule", func(t *testing.T) {
- body := `{
- "name": "Test Rule",
- "contract": "Test Contract",
- "ruleType": "auto_sign",
- "enabled": true,
+ contract := "TestAddContract"
+ ruleType := config.RuleTypeAutoSign
+ ruleName := "Test Rule"
+ enabled := true
+ body := fmt.Sprintf(`{
+ "name": "%s",
+ "contract": "%s",
+ "ruleType": "%s",
+ "enabled": %t,
"description": "Test Description"
- }`
+ }`, ruleName, contract, ruleType, enabled)
testResult := make(chan walletapp.EventData)
@@ -43,7 +56,6 @@ func Test_signrule_Handlers(t *testing.T) {
Message: password,
}
- // forward test result to test goroutine
res <- (<-resChan)
}(testResult)
@@ -62,47 +74,167 @@ func Test_signrule_Handlers(t *testing.T) {
assert.NoError(err)
assert.NotEmpty(addRuleResponse.ID)
+ // check rule is added
+ rulePtr := cfg.GetEnabledRuleForContract(account.Nickname, &contract)
+ assert.NotNil(rulePtr)
+ assert.Equal(*rulePtr, ruleType)
+
+ hasRule := cfg.HasEnabledRule(account.Nickname)
+ assert.True(hasRule)
+
+ // check rule contains expected fields
+ rule, err := cfg.GetSignRule(account.Nickname, addRuleResponse.ID)
+ assert.NoError(err)
+ assert.Equal(rule.Name, ruleName)
+ assert.Equal(rule.Contract, contract)
+ assert.Equal(rule.RuleType, ruleType)
+ assert.Equal(rule.Enabled, enabled)
+
// check that privateKey is cached
pkey, err := cache.PrivateKeyFromCache(account)
assert.NoError(err)
assert.NotNil(pkey)
})
-}
-// func TestDeleteSignRule(t *testing.T) {
-// api, _, _, resultChannel, err := MockAPI()
-// assert.NoError(t, err)
-
-// body := `{
-// "nickname": "Test Nickname",
-// "ruleID": "Test Rule ID"
-// }`
-
-// resp, err := processHTTPRequest(api, "DELETE", "/sign-rule", body)
-// assert.NoError(t, err)
-// assert.Equal(t, http.StatusOK, resp.Code)
-
-// result := <-resultChannel
-// checkResultChannel(t, result, true, "Sign rule deleted successfully")
-// }
-
-// func TestUpdateSignRule(t *testing.T) {
-// api, _, _, resultChannel, err := MockAPI()
-// assert.NoError(t, err)
-
-// body := `{
-// "nickname": "Test Nickname",
-// "ruleID": "Test Rule ID",
-// "name": "Updated Rule",
-// "contract": "Updated Contract",
-// "ruleType": "Updated Type",
-// "enabled": false
-// }`
-
-// resp, err := processHTTPRequest(api, "PUT", "/sign-rule", body)
-// assert.NoError(t, err)
-// assert.Equal(t, http.StatusOK, resp.Code)
-
-// result := <-resultChannel
-// checkResultChannel(t, result, true, "Sign rule updated successfully")
-// }
+ t.Run("Update sign rule", func(t *testing.T) {
+ contract := "*"
+ ruleType := config.RuleTypeDisablePasswordPrompt
+ ruleName := "Test Rule"
+ enabled := true
+ body := fmt.Sprintf(`{
+ "name": "%s",
+ "contract": "%s",
+ "ruleType": "%s",
+ "enabled": %t,
+ "description": "Test Description"
+ }`, ruleName, contract, ruleType, enabled)
+
+ testResult := make(chan walletapp.EventData)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Message: password,
+ }
+
+ res <- (<-resChan)
+ }(testResult)
+
+ // Add a sign rule first
+ resp, err := handleHTTPRequest(addhandler, "POST", fmt.Sprintf("/api/accounts/%s/signrules", nickname), body)
+ assert.NoError(err)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ var addRuleResponse models.AddSignRuleResponse
+ err = json.Unmarshal(resp.Body.Bytes(), &addRuleResponse)
+ assert.NoError(err)
+ assert.NotEmpty(addRuleResponse.ID)
+
+ // Update the sign rule
+ updatedRuleName := "Updated Test Rule"
+ enabled = false
+ updatedBody := fmt.Sprintf(`{
+ "name": "%s",
+ "contract": "%s",
+ "ruleType": "%s",
+ "enabled": %t,
+ "description": "Updated Test Description"
+ }`, updatedRuleName, contract, ruleType, enabled)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Message: password,
+ }
+
+ res <- (<-resChan)
+ }(testResult)
+
+ resp, err = handleHTTPRequest(updatehandler, "PUT", fmt.Sprintf("/api/accounts/%s/signrules/%s", nickname, addRuleResponse.ID), updatedBody)
+ assert.NoError(err)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ var updateRuleResponse models.UpdateSignRuleResponse
+ err = json.Unmarshal(resp.Body.Bytes(), &updateRuleResponse)
+ assert.NoError(err)
+ assert.NotEmpty(updateRuleResponse.ID)
+
+ // Check rule is updated
+ rule, err := cfg.GetSignRule(account.Nickname, updateRuleResponse.ID)
+ assert.NoError(err)
+ assert.Equal(rule.Name, updatedRuleName)
+ assert.Equal(rule.Contract, contract)
+ assert.Equal(rule.RuleType, ruleType)
+ assert.Equal(rule.Enabled, enabled)
+
+ // Check that privateKey is still cached
+ pkey, err := cache.PrivateKeyFromCache(account)
+ assert.NoError(err)
+ assert.NotNil(pkey)
+ })
+
+ t.Run("Delete sign rule", func(t *testing.T) {
+ contract := "TestDeleteContract"
+ ruleType := config.RuleTypeAutoSign
+ ruleName := "Test Rule"
+ enabled := true
+ body := fmt.Sprintf(`{
+ "name": "%s",
+ "contract": "%s",
+ "ruleType": "%s",
+ "enabled": %t,
+ "description": "Test Description"
+ }`, ruleName, contract, ruleType, enabled)
+
+ testResult := make(chan walletapp.EventData)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Message: password,
+ }
+
+ res <- (<-resChan)
+ }(testResult)
+
+ // Add a sign rule first
+ resp, err := handleHTTPRequest(addhandler, "POST", fmt.Sprintf("/api/accounts/%s/signrules", nickname), body)
+ assert.NoError(err)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ var addRuleResponse models.AddSignRuleResponse
+ err = json.Unmarshal(resp.Body.Bytes(), &addRuleResponse)
+ assert.NoError(err)
+ assert.NotEmpty(addRuleResponse.ID)
+
+ // Send password to prompter app and wait for result
+ go func(res chan walletapp.EventData) {
+ prompterAppMock.App().PromptInput <- &walletapp.StringPromptInput{
+ BaseMessage: walletapp.BaseMessage{},
+ Message: password,
+ }
+
+ res <- (<-resChan)
+ }(testResult)
+
+ // Now delete the sign rule
+ resp, err = handleHTTPRequest(deletehandler, "DELETE", fmt.Sprintf("/api/accounts/%s/signrules/%s", nickname, addRuleResponse.ID), "")
+ assert.NoError(err)
+ verifyStatusCode(t, resp, http.StatusOK)
+
+ // Check rule is deleted
+ rulePtr := cfg.GetEnabledRuleForContract(account.Nickname, &contract)
+ assert.Nil(rulePtr)
+
+ hasRule := cfg.HasEnabledRule(account.Nickname)
+ assert.True(hasRule)
+
+ // Check that privateKey is still cached
+ pkey, err := cache.PrivateKeyFromCache(account)
+ assert.NoError(err)
+ assert.NotNil(pkey)
+ })
+}
diff --git a/pkg/assets/asset.go b/pkg/assets/asset.go
index d9fcc7851..57fffd8f3 100644
--- a/pkg/assets/asset.go
+++ b/pkg/assets/asset.go
@@ -55,24 +55,27 @@ type assetData struct {
ChainID int64 `json:"chainID"`
}
-var Store *AssetsStore
+var (
+ Store *AssetsStore
+ filePathOverride string
+)
// NewAssetsStore creates and initializes a new instance of AssetsStore.
// If assetsJSONDir is empty, it will use the default wallet path.
-func InitAssetsStore(assetsJSONDir string, massaClient *network.NodeFetcher) (*AssetsStore, error) {
+func InitAssetsStore(massaClient *network.NodeFetcher) (*AssetsStore, error) {
Store = &AssetsStore{
Assets: make(map[string]Assets),
massaClient: massaClient,
}
- if assetsJSONDir == "" {
+ if filePathOverride != "" {
+ Store.assetsJSONDir = filePathOverride
+ } else {
assetsJSONDir, err := wallet.Path()
if err != nil {
return nil, errors.Wrap(err, "Failed to get AssetsStore JSON file")
}
Store.assetsJSONDir = assetsJSONDir
- } else {
- Store.assetsJSONDir = assetsJSONDir
}
if err := Store.loadAccountsStore(); err != nil {
@@ -87,6 +90,11 @@ func InitAssetsStore(assetsJSONDir string, massaClient *network.NodeFetcher) (*A
return Store, nil
}
+// Used by unit test
+func SetFileDirOverride(path string) {
+ filePathOverride = path
+}
+
// loadAccountsStore loads the data from the assets JSON file into the AssetsStore.
func (s *AssetsStore) loadAccountsStore() error {
// Check if the file exists, and if not, create a new one with an empty object
diff --git a/pkg/assets/asset_test.go b/pkg/assets/asset_test.go
index 81cd6127e..3b8a304bc 100644
--- a/pkg/assets/asset_test.go
+++ b/pkg/assets/asset_test.go
@@ -42,7 +42,10 @@ func TestLoadAccountsStore(t *testing.T) {
// Create a new instance of AssetsStore and load data from the testing file
nodeFetcher := network.NewNodeFetcher()
- _, err = InitAssetsStore(tempDir, nodeFetcher)
+
+ SetFileDirOverride(tempDir)
+
+ _, err = InitAssetsStore(nodeFetcher)
assert.NoError(t, err)
// Validate the loaded data
@@ -64,7 +67,10 @@ func TestAssetExists(t *testing.T) {
// Create a new instance of AssetsStore and load data from the testing file
nodeFetcher := network.NewNodeFetcher()
- _, err = InitAssetsStore(tempDir, nodeFetcher)
+
+ SetFileDirOverride(tempDir)
+
+ _, err = InitAssetsStore(nodeFetcher)
assert.NoError(t, err)
// Test case 1: Check for an existing asset
@@ -109,7 +115,10 @@ func TestAddAndDeleteAsset(t *testing.T) {
// Create a new instance of AssetsStore and load data from the testing file
nodeFetcher := network.NewNodeFetcher()
- _, err = InitAssetsStore(tempDir, nodeFetcher)
+
+ SetFileDirOverride(tempDir)
+
+ _, err = InitAssetsStore(nodeFetcher)
assert.NoError(t, err)
// Test case 1: Add an asset and check if it's saved to JSON
diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go
index f6fdecc0f..2ab081c04 100644
--- a/pkg/cache/cache.go
+++ b/pkg/cache/cache.go
@@ -19,7 +19,7 @@ var (
once sync.Once
)
-func Get() gcache.Cache {
+func Init() gcache.Cache {
once.Do(func() {
cache = gcache.New(cacheSize).
LRU().
diff --git a/pkg/config/rules.go b/pkg/config/rules.go
index 105c69f30..188f59788 100644
--- a/pkg/config/rules.go
+++ b/pkg/config/rules.go
@@ -240,22 +240,27 @@ func (c *Config) GetEnabledRuleForContract(accountName string, contract *string)
return nil
}
+ var ruleType *RuleType = nil
+
// Check if there is any enabled rule that applies to the contract
for _, rule := range account.SignRules {
if rule.Enabled {
switch rule.RuleType {
case RuleTypeAutoSign:
if contract != nil && rule.Contract == *contract {
- return &rule.RuleType
+ ruleType = &rule.RuleType
}
case RuleTypeDisablePasswordPrompt:
if rule.Contract == "*" || (contract != nil && rule.Contract == *contract) {
- return &rule.RuleType
+ if ruleType == nil {
+ // If there are multiple rules that apply to the contract, the rule with the highest priority is used (AutoSign)
+ ruleType = &rule.RuleType
+ }
}
}
}
}
- return nil
+ return ruleType
}
From f3b819df7693cb317c7e0ec54599d222877d92ee Mon Sep 17 00:00:00 2001
From: Peterjah
Date: Tue, 21 Jan 2025 15:12:12 +0100
Subject: [PATCH 08/10] factorize findIndex rule
---
api/server/models/sign_rule.go | 62 +++++++++++++--------------
api/server/restapi/embedded_spec.go | 12 +-----
api/walletApi-V0.yml | 5 +--
internal/handler/wallet/get_config.go | 2 +-
pkg/config/rules.go | 60 +++++++++++---------------
5 files changed, 59 insertions(+), 82 deletions(-)
diff --git a/api/server/models/sign_rule.go b/api/server/models/sign_rule.go
index 40bc7d779..674efa30e 100644
--- a/api/server/models/sign_rule.go
+++ b/api/server/models/sign_rule.go
@@ -7,7 +7,6 @@ package models
import (
"context"
- "encoding/json"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -28,8 +27,7 @@ type SignRule struct {
// rule type
// Required: true
- // Enum: ["password_prompt","auto_sign"]
- RuleType *string `json:"ruleType"`
+ RuleType RuleType `json:"ruleType"`
}
// Validate validates this sign rule
@@ -46,51 +44,49 @@ func (m *SignRule) Validate(formats strfmt.Registry) error {
return nil
}
-var signRuleTypeRuleTypePropEnum []interface{}
+func (m *SignRule) validateRuleType(formats strfmt.Registry) error {
-func init() {
- var res []string
- if err := json.Unmarshal([]byte(`["password_prompt","auto_sign"]`), &res); err != nil {
- panic(err)
- }
- for _, v := range res {
- signRuleTypeRuleTypePropEnum = append(signRuleTypeRuleTypePropEnum, v)
+ if err := validate.Required("ruleType", "body", RuleType(m.RuleType)); err != nil {
+ return err
}
-}
-
-const (
-
- // SignRuleRuleTypePasswordPrompt captures enum value "password_prompt"
- SignRuleRuleTypePasswordPrompt string = "password_prompt"
- // SignRuleRuleTypeAutoSign captures enum value "auto_sign"
- SignRuleRuleTypeAutoSign string = "auto_sign"
-)
-
-// prop value enum
-func (m *SignRule) validateRuleTypeEnum(path, location string, value string) error {
- if err := validate.EnumCase(path, location, value, signRuleTypeRuleTypePropEnum, true); err != nil {
+ if err := m.RuleType.Validate(formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("ruleType")
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("ruleType")
+ }
return err
}
+
return nil
}
-func (m *SignRule) validateRuleType(formats strfmt.Registry) error {
+// ContextValidate validate this sign rule based on the context it is used
+func (m *SignRule) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
- if err := validate.Required("ruleType", "body", m.RuleType); err != nil {
- return err
+ if err := m.contextValidateRuleType(ctx, formats); err != nil {
+ res = append(res, err)
}
- // value enum
- if err := m.validateRuleTypeEnum("ruleType", "body", *m.RuleType); err != nil {
- return err
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
}
-
return nil
}
-// ContextValidate validates this sign rule based on context it is used
-func (m *SignRule) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+func (m *SignRule) contextValidateRuleType(ctx context.Context, formats strfmt.Registry) error {
+
+ if err := m.RuleType.ContextValidate(ctx, formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("ruleType")
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("ruleType")
+ }
+ return err
+ }
+
return nil
}
diff --git a/api/server/restapi/embedded_spec.go b/api/server/restapi/embedded_spec.go
index 7620731dc..e11d29490 100644
--- a/api/server/restapi/embedded_spec.go
+++ b/api/server/restapi/embedded_spec.go
@@ -1348,11 +1348,7 @@ func init() {
"default": true
},
"ruleType": {
- "type": "string",
- "enum": [
- "password_prompt",
- "auto_sign"
- ]
+ "$ref": "#/definitions/RuleType"
}
}
},
@@ -2880,11 +2876,7 @@ func init() {
"default": true
},
"ruleType": {
- "type": "string",
- "enum": [
- "password_prompt",
- "auto_sign"
- ]
+ "$ref": "#/definitions/RuleType"
}
}
},
diff --git a/api/walletApi-V0.yml b/api/walletApi-V0.yml
index b9d075c38..34ed432a8 100644
--- a/api/walletApi-V0.yml
+++ b/api/walletApi-V0.yml
@@ -963,10 +963,7 @@ definitions:
type: boolean
default: true
ruleType:
- type: string
- enum:
- - password_prompt
- - auto_sign
+ $ref: "#/definitions/RuleType"
required:
- ruleType
diff --git a/internal/handler/wallet/get_config.go b/internal/handler/wallet/get_config.go
index 5f4769cd5..efb3cfb3c 100644
--- a/internal/handler/wallet/get_config.go
+++ b/internal/handler/wallet/get_config.go
@@ -37,7 +37,7 @@ func newConfigModel(cfg *config.Config) (*models.Config, error) {
modelSignRules[i] = &models.SignRule{
Contract: &rule.Contract,
Enabled: &rule.Enabled,
- RuleType: (*string)(&rule.RuleType),
+ RuleType: (models.RuleType)(rule.RuleType),
}
}
modelAccounts[nickname] = models.AccountConfig{
diff --git a/pkg/config/rules.go b/pkg/config/rules.go
index 188f59788..140921c02 100644
--- a/pkg/config/rules.go
+++ b/pkg/config/rules.go
@@ -41,35 +41,36 @@ func (c *Config) AddSignRule(accountName string, rule SignRule) (string, error)
return rule.ID, nil
}
+func (c *Config) findRuleIndex(accountConfig AccountCfg, ruleID string) (int, error) {
+ for i, rule := range accountConfig.SignRules {
+ if rule.ID == ruleID {
+ return i, nil
+ }
+ }
+
+ return -1, fmt.Errorf("rule not found: %s", ruleID)
+}
+
func (c *Config) DeleteSignRule(accountName, ruleID string) error {
configManager.mu.Lock()
defer configManager.mu.Unlock()
// Check if the account exists
- account, exists := c.Accounts[accountName]
+ accountConfig, exists := c.Accounts[accountName]
if !exists {
return fmt.Errorf("account not found: %s", accountName)
}
- // Find the index of the rule to delete
- index := -1
-
- for i, rule := range account.SignRules {
- if rule.ID == ruleID {
- index = i
- break
- }
- }
-
- if index == -1 {
- return fmt.Errorf("rule not found: %s", ruleID)
+ index, err := c.findRuleIndex(accountConfig, ruleID)
+ if err != nil {
+ return fmt.Errorf("deleting sign rule: %w", err)
}
// Remove the rule from the slice
- account.SignRules = append(account.SignRules[:index], account.SignRules[index+1:]...)
- c.Accounts[accountName] = account
+ accountConfig.SignRules = append(accountConfig.SignRules[:index], accountConfig.SignRules[index+1:]...)
+ c.Accounts[accountName] = accountConfig
- err := saveConfigUnsafe(c)
+ err = saveConfigUnsafe(c)
if err != nil {
return fmt.Errorf("error saving configuration: %v", err)
}
@@ -82,35 +83,26 @@ func (c *Config) UpdateSignRule(accountName, ruleID string, newRule SignRule) (s
defer configManager.mu.Unlock()
// Check if the account exists
- account, exists := c.Accounts[accountName]
+ accountConfig, exists := c.Accounts[accountName]
if !exists {
return "", fmt.Errorf("account not found: %s", accountName)
}
- // Find the index of the rule to update
- index := -1
-
- for i, rule := range account.SignRules {
- if rule.ID == ruleID {
- index = i
- break
- }
- }
-
- if index == -1 {
- return "", fmt.Errorf("rule not found: %s", ruleID)
+ index, err := c.findRuleIndex(accountConfig, ruleID)
+ if err != nil {
+ return "", fmt.Errorf("updating sign rule: %w", err)
}
// Delete the existing rule
- account.SignRules = append(account.SignRules[:index], account.SignRules[index+1:]...)
- c.Accounts[accountName] = account
+ accountConfig.SignRules = append(accountConfig.SignRules[:index], accountConfig.SignRules[index+1:]...)
+ c.Accounts[accountName] = accountConfig
// Add the new rule
newRule.ID = generateRuleID(accountName, newRule)
- account.SignRules = append(account.SignRules, newRule)
- c.Accounts[accountName] = account
+ accountConfig.SignRules = append(accountConfig.SignRules, newRule)
+ c.Accounts[accountName] = accountConfig
- err := saveConfigUnsafe(c)
+ err = saveConfigUnsafe(c)
if err != nil {
return "", fmt.Errorf("error saving configuration: %v", err)
}
From a103dae04617cd93d1f3dd2111fb250bd1b806b8 Mon Sep 17 00:00:00 2001
From: Peterjah
Date: Tue, 21 Jan 2025 15:42:39 +0100
Subject: [PATCH 09/10] refactor InitAssetsStore
---
cmd/massa-wallet/massa-wallet.go | 5 +----
internal/handler/wallet/api_test.go | 5 +----
pkg/assets/asset.go | 10 +++++-----
pkg/assets/asset_test.go | 9 +++------
4 files changed, 10 insertions(+), 19 deletions(-)
diff --git a/cmd/massa-wallet/massa-wallet.go b/cmd/massa-wallet/massa-wallet.go
index 802f10142..179596820 100644
--- a/cmd/massa-wallet/massa-wallet.go
+++ b/cmd/massa-wallet/massa-wallet.go
@@ -27,10 +27,7 @@ func StartServer(app *walletApp.WalletApp) {
cache.Init()
- _, err := assets.InitAssetsStore(massaClient)
- if err != nil {
- logger.Fatalf("Failed to create AssetsStore: %v", err)
- }
+ assets.InitAssetsStore(massaClient)
// Initializes API
massaWalletAPI, err := handler.InitializeAPI(
diff --git a/internal/handler/wallet/api_test.go b/internal/handler/wallet/api_test.go
index 3771c561b..38806b861 100644
--- a/internal/handler/wallet/api_test.go
+++ b/internal/handler/wallet/api_test.go
@@ -80,10 +80,7 @@ func MockAPI() (*operations.MassaWalletAPI, chan walletapp.EventData, error) {
assets.SetFileDirOverride(walletPath)
- testAssetStore, err = assets.InitAssetsStore(nodeFetcher)
- if err != nil {
- log.Fatalf("Failed to create AssetsStore: %v", err)
- }
+ testAssetStore = assets.InitAssetsStore(nodeFetcher)
massaNodeMock := NewNodeFetcherMock()
diff --git a/pkg/assets/asset.go b/pkg/assets/asset.go
index 57fffd8f3..e8ec52ba0 100644
--- a/pkg/assets/asset.go
+++ b/pkg/assets/asset.go
@@ -62,7 +62,7 @@ var (
// NewAssetsStore creates and initializes a new instance of AssetsStore.
// If assetsJSONDir is empty, it will use the default wallet path.
-func InitAssetsStore(massaClient *network.NodeFetcher) (*AssetsStore, error) {
+func InitAssetsStore(massaClient *network.NodeFetcher) *AssetsStore {
Store = &AssetsStore{
Assets: make(map[string]Assets),
massaClient: massaClient,
@@ -73,21 +73,21 @@ func InitAssetsStore(massaClient *network.NodeFetcher) (*AssetsStore, error) {
} else {
assetsJSONDir, err := wallet.Path()
if err != nil {
- return nil, errors.Wrap(err, "Failed to get AssetsStore JSON file")
+ logger.Fatalf("Failed to create AssetsStore: Failed to get AssetsStore JSON file", err)
}
Store.assetsJSONDir = assetsJSONDir
}
if err := Store.loadAccountsStore(); err != nil {
- return nil, errors.Wrap(err, "failed to create AssetsStore")
+ logger.Fatalf("Failed to create AssetsStore:", err)
}
err := Store.InitDefault()
if err != nil {
- return nil, errors.Wrap(err, "failed to create AssetsStore")
+ logger.Fatalf("Failed to create AssetsStore:", err)
}
- return Store, nil
+ return Store
}
// Used by unit test
diff --git a/pkg/assets/asset_test.go b/pkg/assets/asset_test.go
index 3b8a304bc..1aac0a63e 100644
--- a/pkg/assets/asset_test.go
+++ b/pkg/assets/asset_test.go
@@ -45,8 +45,7 @@ func TestLoadAccountsStore(t *testing.T) {
SetFileDirOverride(tempDir)
- _, err = InitAssetsStore(nodeFetcher)
- assert.NoError(t, err)
+ InitAssetsStore(nodeFetcher)
// Validate the loaded data
expectedAccountName := "dummyAccount"
@@ -70,8 +69,7 @@ func TestAssetExists(t *testing.T) {
SetFileDirOverride(tempDir)
- _, err = InitAssetsStore(nodeFetcher)
- assert.NoError(t, err)
+ InitAssetsStore(nodeFetcher)
// Test case 1: Check for an existing asset
existingNickname := "dummyAccount"
@@ -118,8 +116,7 @@ func TestAddAndDeleteAsset(t *testing.T) {
SetFileDirOverride(tempDir)
- _, err = InitAssetsStore(nodeFetcher)
- assert.NoError(t, err)
+ InitAssetsStore(nodeFetcher)
// Test case 1: Add an asset and check if it's saved to JSON
nickname := "dummyAccount"
From bf376782d68d2e643ca71f088d8c75f57cc84a61 Mon Sep 17 00:00:00 2001
From: Pierre Seznec <34547263+peterjah@users.noreply.github.com>
Date: Fri, 24 Jan 2025 17:01:40 +0100
Subject: [PATCH 10/10] Sign rules prompts (#987)
* add signature management prompters
---
internal/handler/wallet/add_asset.go | 4 +-
internal/handler/wallet/sign_rule_add.go | 27 ++-
internal/handler/wallet/sign_rule_delete.go | 6 +-
internal/handler/wallet/sign_rule_test.go | 43 ++++-
internal/handler/wallet/sign_rule_update.go | 25 +--
internal/handler/wallet/sign_test.go | 4 +-
internal/handler/wallet/transfer.go | 2 +-
pkg/app/enums.go | 51 +++++
pkg/app/events.go | 21 --
pkg/config/config.go | 2 +-
pkg/config/config_test.go | 27 ++-
pkg/config/rules.go | 43 +++--
pkg/utils/address.go | 20 +-
pkg/wails/wails.go | 4 +
wails-frontend/package-lock.json | 8 +-
wails-frontend/package.json | 2 +-
wails-frontend/package.json.md5 | 2 +-
wails-frontend/src/app.tsx | 31 +--
wails-frontend/src/events/index.ts | 1 +
.../src/events/{events.tsx => types.tsx} | 20 +-
wails-frontend/src/i18n/en_US.json | 34 +++-
.../pages/ImportPrivateKey/PromptNickname.tsx | 2 +-
.../ImportPrivateKey/PromptPrivateKey.tsx | 2 +-
.../BuySellRoll/BuySellRoll.tsx | 3 +-
.../CallSC/FTTransferInfo.tsx | 3 +-
.../pages/PasswordPromptHandler/Delete.tsx | 7 +-
.../ExecuteSC.tsx/ExecuteSc.tsx | 3 +-
.../PasswordPromptHandler.tsx | 15 +-
.../src/pages/PasswordPromptHandler/Sign.tsx | 7 +-
.../SignComponentUtils/From.tsx | 5 +-
.../SignComponentUtils/FromTo.tsx | 13 +-
.../SignComponentUtils/OperationCost.tsx | 2 +-
.../Transaction/Transaction.tsx | 3 +-
.../components/account.tsx | 28 +++
.../components/clipBoardCopy.tsx | 14 ++
.../pages/SignRuleHandler/signRulePrompt.tsx | 180 ++++++++++++++++++
.../src/pages/SignRuleHandler/types.tsx | 24 +++
wails-frontend/src/pages/backupKeyPairs.tsx | 7 +-
wails-frontend/src/pages/backupMethods.tsx | 10 +-
wails-frontend/src/pages/confirmDelete.tsx | 7 +-
wails-frontend/src/pages/failure.tsx | 2 +-
wails-frontend/src/pages/importFile.tsx | 9 +-
wails-frontend/src/pages/importMethods.tsx | 2 +-
wails-frontend/src/pages/newPassword.tsx | 13 +-
wails-frontend/src/pages/success.tsx | 15 +-
.../src/utils/handleApplyResult.tsx | 2 +-
wails-frontend/wailsjs/go/models.ts | 17 ++
47 files changed, 582 insertions(+), 190 deletions(-)
create mode 100644 pkg/app/enums.go
create mode 100644 wails-frontend/src/events/index.ts
rename wails-frontend/src/events/{events.tsx => types.tsx} (52%)
create mode 100644 wails-frontend/src/pages/PasswordPromptHandler/components/account.tsx
create mode 100644 wails-frontend/src/pages/PasswordPromptHandler/components/clipBoardCopy.tsx
create mode 100644 wails-frontend/src/pages/SignRuleHandler/signRulePrompt.tsx
create mode 100644 wails-frontend/src/pages/SignRuleHandler/types.tsx
diff --git a/internal/handler/wallet/add_asset.go b/internal/handler/wallet/add_asset.go
index 3379a88c5..2694b4abd 100644
--- a/internal/handler/wallet/add_asset.go
+++ b/internal/handler/wallet/add_asset.go
@@ -24,9 +24,7 @@ type addAsset struct {
}
func (a *addAsset) Handle(params operations.AddAssetParams) middleware.Responder {
- // Check if the address is valid
- if !utils.IsValidAddress(params.AssetAddress) {
- // Return an error indicating the address is not valid
+ if !utils.IsValidContract(params.AssetAddress) {
errorMsg := "Invalid address format"
return operations.NewAddAssetUnprocessableEntity().WithPayload(&models.Error{Code: errorInvalidAssetAddress, Message: errorMsg})
}
diff --git a/internal/handler/wallet/sign_rule_add.go b/internal/handler/wallet/sign_rule_add.go
index 9d4436760..9f2a0947b 100644
--- a/internal/handler/wallet/sign_rule_add.go
+++ b/internal/handler/wallet/sign_rule_add.go
@@ -37,6 +37,23 @@ func (w *addSignRuleHandler) Handle(params operations.AddSignRuleParams) middlew
return errResp
}
+ newRule := config.SignRule{
+ Name: params.Body.Name,
+ Contract: *params.Body.Contract,
+ RuleType: config.RuleType(params.Body.RuleType),
+ Enabled: *params.Body.Enabled,
+ }
+
+ cfg := config.Get()
+
+ if exists := cfg.IsExistingRule(acc.Nickname, newRule); exists {
+ return newErrorResponse("Rule already exists", errorAddSignRule, http.StatusBadRequest)
+ }
+
+ if err := config.ValidateRule(newRule); err != nil {
+ return operations.NewAddSignRuleBadRequest().WithPayload(&models.Error{Code: errorInvalidAssetAddress, Message: err.Error()})
+ }
+
promptRequest, err := w.getPromptRequest(params, acc)
if err != nil {
return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorAddSignRule, http.StatusBadRequest)
@@ -52,15 +69,6 @@ func (w *addSignRuleHandler) Handle(params operations.AddSignRuleParams) middlew
return newErrorResponse(msg, errorGetWallets, http.StatusInternalServerError)
}
- cfg := config.Get()
-
- newRule := config.SignRule{
- Name: params.Body.Name,
- Contract: *params.Body.Contract,
- RuleType: config.RuleType(params.Body.RuleType),
- Enabled: *params.Body.Enabled,
- }
-
ruleID, err := cfg.AddSignRule(acc.Nickname, newRule)
if err != nil {
return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorAddSignRule, http.StatusInternalServerError)
@@ -94,6 +102,7 @@ func (w *addSignRuleHandler) getPromptRequest(params operations.AddSignRuleParam
Nickname: acc.Nickname,
Description: params.Body.Description,
SignRule: config.SignRule{
+ Name: params.Body.Name,
Contract: *params.Body.Contract,
RuleType: config.RuleType(params.Body.RuleType),
Enabled: *params.Body.Enabled,
diff --git a/internal/handler/wallet/sign_rule_delete.go b/internal/handler/wallet/sign_rule_delete.go
index ae6bda25e..0150758fc 100644
--- a/internal/handler/wallet/sign_rule_delete.go
+++ b/internal/handler/wallet/sign_rule_delete.go
@@ -30,9 +30,9 @@ func (w *deleteSignRuleHandler) Handle(params operations.DeleteSignRuleParams) m
cfg := config.Get()
- signRule, err := cfg.GetSignRule(acc.Nickname, params.RuleID)
- if err != nil {
- return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorDeleteSignRule, http.StatusInternalServerError)
+ signRule := cfg.GetSignRule(acc.Nickname, params.RuleID)
+ if signRule == nil {
+ return newErrorResponse(fmt.Sprintf("Rule ID %s not found", params.RuleID), errorDeleteSignRule, http.StatusInternalServerError)
}
promptRequest, err := w.getPromptRequest(acc, signRule)
diff --git a/internal/handler/wallet/sign_rule_test.go b/internal/handler/wallet/sign_rule_test.go
index d2ec49ab3..df3e4f5cb 100644
--- a/internal/handler/wallet/sign_rule_test.go
+++ b/internal/handler/wallet/sign_rule_test.go
@@ -34,8 +34,34 @@ func Test_signrule_Handlers(t *testing.T) {
cfg := config.Get()
+ t.Run("Add sign rule with invalid contract", func(t *testing.T) {
+ contract := "Invalid Contract"
+ ruleType := config.RuleTypeAutoSign
+ ruleName := "Test Rule"
+ enabled := true
+ body := fmt.Sprintf(`{
+ "name": "%s",
+ "contract": "%s",
+ "ruleType": "%s",
+ "enabled": %t,
+ "description": "Test Description"
+ }`, ruleName, contract, ruleType, enabled)
+
+ resp, err := handleHTTPRequest(addhandler, "POST", fmt.Sprintf("/api/accounts/%s/signrules", nickname), body)
+ assert.NoError(err)
+
+ verifyStatusCode(t, resp, http.StatusBadRequest)
+
+ // check rule is not added
+ rulePtr := cfg.GetEnabledRuleForContract(account.Nickname, &contract)
+ assert.Nil(rulePtr)
+
+ hasRule := cfg.HasEnabledRule(account.Nickname)
+ assert.False(hasRule)
+ })
+
t.Run("Add sign rule", func(t *testing.T) {
- contract := "TestAddContract"
+ contract := "AS12UMSUxgpRBB6ArZDJ19arHoxNkkpdfofQGekAiAJqsuE6PEFJy"
ruleType := config.RuleTypeAutoSign
ruleName := "Test Rule"
enabled := true
@@ -61,7 +87,6 @@ func Test_signrule_Handlers(t *testing.T) {
resp, err := handleHTTPRequest(addhandler, "POST", fmt.Sprintf("/api/accounts/%s/signrules", nickname), body)
assert.NoError(err)
- fmt.Println(resp)
verifyStatusCode(t, resp, http.StatusOK)
@@ -83,8 +108,8 @@ func Test_signrule_Handlers(t *testing.T) {
assert.True(hasRule)
// check rule contains expected fields
- rule, err := cfg.GetSignRule(account.Nickname, addRuleResponse.ID)
- assert.NoError(err)
+ rule := cfg.GetSignRule(account.Nickname, addRuleResponse.ID)
+ assert.NotNil(rule)
assert.Equal(rule.Name, ruleName)
assert.Equal(rule.Contract, contract)
assert.Equal(rule.RuleType, ruleType)
@@ -162,8 +187,8 @@ func Test_signrule_Handlers(t *testing.T) {
assert.NotEmpty(updateRuleResponse.ID)
// Check rule is updated
- rule, err := cfg.GetSignRule(account.Nickname, updateRuleResponse.ID)
- assert.NoError(err)
+ rule := cfg.GetSignRule(account.Nickname, updateRuleResponse.ID)
+ assert.NotNil(rule)
assert.Equal(rule.Name, updatedRuleName)
assert.Equal(rule.Contract, contract)
assert.Equal(rule.RuleType, ruleType)
@@ -176,7 +201,7 @@ func Test_signrule_Handlers(t *testing.T) {
})
t.Run("Delete sign rule", func(t *testing.T) {
- contract := "TestDeleteContract"
+ contract := "AS1hCJXjndR4c9vekLWsXGnrdigp4AaZ7uYG3UKFzzKnWVsrNLPJ"
ruleType := config.RuleTypeAutoSign
ruleName := "Test Rule"
enabled := true
@@ -184,8 +209,7 @@ func Test_signrule_Handlers(t *testing.T) {
"name": "%s",
"contract": "%s",
"ruleType": "%s",
- "enabled": %t,
- "description": "Test Description"
+ "enabled": %t
}`, ruleName, contract, ruleType, enabled)
testResult := make(chan walletapp.EventData)
@@ -203,6 +227,7 @@ func Test_signrule_Handlers(t *testing.T) {
// Add a sign rule first
resp, err := handleHTTPRequest(addhandler, "POST", fmt.Sprintf("/api/accounts/%s/signrules", nickname), body)
assert.NoError(err)
+
verifyStatusCode(t, resp, http.StatusOK)
var addRuleResponse models.AddSignRuleResponse
diff --git a/internal/handler/wallet/sign_rule_update.go b/internal/handler/wallet/sign_rule_update.go
index 3f1a63a9f..295a3cf5d 100644
--- a/internal/handler/wallet/sign_rule_update.go
+++ b/internal/handler/wallet/sign_rule_update.go
@@ -30,14 +30,24 @@ func (w *updateSignRuleHandler) Handle(params operations.UpdateSignRuleParams) m
return errResp
}
+ newRule := config.SignRule{
+ Name: params.Body.Name,
+ Contract: *params.Body.Contract,
+ RuleType: config.RuleType(params.Body.RuleType),
+ Enabled: *params.Body.Enabled,
+ }
+
cfg := config.Get()
- signRule, err := cfg.GetSignRule(acc.Nickname, params.RuleID)
- if err != nil {
- return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorUpdateSignRule, http.StatusInternalServerError)
+ if signRule := cfg.GetSignRule(acc.Nickname, params.RuleID); signRule == nil {
+ return newErrorResponse(fmt.Sprintf("Rule ID %s not found", params.RuleID), errorUpdateSignRule, http.StatusInternalServerError)
}
- promptRequest, err := w.getPromptRequest(acc, signRule)
+ if err := config.ValidateRule(newRule); err != nil {
+ return operations.NewUpdateSignRuleBadRequest().WithPayload(&models.Error{Code: errorInvalidAssetAddress, Message: err.Error()})
+ }
+
+ promptRequest, err := w.getPromptRequest(acc, &newRule)
if err != nil {
return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorUpdateSignRule, http.StatusBadRequest)
}
@@ -52,13 +62,6 @@ func (w *updateSignRuleHandler) Handle(params operations.UpdateSignRuleParams) m
return newErrorResponse(msg, errorGetWallets, http.StatusInternalServerError)
}
- newRule := config.SignRule{
- Name: params.Body.Name,
- Contract: *params.Body.Contract,
- RuleType: config.RuleType(params.Body.RuleType),
- Enabled: *params.Body.Enabled,
- }
-
ruleID, err := cfg.UpdateSignRule(acc.Nickname, params.RuleID, newRule)
if err != nil {
return newErrorResponse(fmt.Sprintf("Error: %v", err.Error()), errorUpdateSignRule, http.StatusInternalServerError)
diff --git a/internal/handler/wallet/sign_test.go b/internal/handler/wallet/sign_test.go
index 5d26fa681..bda815b62 100644
--- a/internal/handler/wallet/sign_test.go
+++ b/internal/handler/wallet/sign_test.go
@@ -233,7 +233,7 @@ func Test_walletSign_Handle(t *testing.T) {
cfg := config.Get()
ruleId, err := cfg.AddSignRule(nickname, config.SignRule{
Name: "test",
- Contract: "AnotherContract",
+ Contract: "AS1ZGF1upwp9kPRvDKLxFAKRebgg7b3RWDnhgV7VvdZkZsUL7Nuv",
RuleType: config.RuleTypeAutoSign,
Enabled: true,
})
@@ -362,7 +362,7 @@ func Test_walletSign_Handle(t *testing.T) {
cfg := config.Get()
ruleId, err := cfg.AddSignRule(nickname, config.SignRule{
Name: "test",
- Contract: "DummyContract",
+ Contract: "AS12LKs9txoSSy8JgFJgV96m8k5z9pgzjYMYSshwN67mFVuj3bdUV",
RuleType: config.RuleTypeDisablePasswordPrompt,
Enabled: true,
})
diff --git a/internal/handler/wallet/transfer.go b/internal/handler/wallet/transfer.go
index 91f91e703..ab77f13f6 100644
--- a/internal/handler/wallet/transfer.go
+++ b/internal/handler/wallet/transfer.go
@@ -134,7 +134,7 @@ func doTransfer(
) (*sendOperation.OperationResponse, error) {
operation, err := transaction.New(recipientAddress, amount)
if err != nil {
- return nil, fmt.Errorf("Error during transaction creation: %w", err)
+ return nil, fmt.Errorf("error during transaction creation: %w", err)
}
return network.SendOperation(acc, password, massaClient, operation, fee, chainID)
diff --git a/pkg/app/enums.go b/pkg/app/enums.go
new file mode 100644
index 000000000..5ffa67259
--- /dev/null
+++ b/pkg/app/enums.go
@@ -0,0 +1,51 @@
+package walletapp
+
+// Enum Bindings
+
+type EventType string
+
+const (
+ PromptResultEvent string = "PROMPT_RESULT"
+ PromptDataEvent string = "PROMPT_DATA"
+ PromptRequestEvent string = "PROMPT_REQUEST"
+)
+
+var EventTypes = []struct {
+ Value EventType
+ TSName string
+}{
+ {EventType(PromptResultEvent), "promptResult"},
+ {EventType(PromptDataEvent), "promptData"},
+ {EventType(PromptRequestEvent), "promptRequest"},
+}
+
+type PromptRequestAction string
+
+const (
+ Delete PromptRequestAction = "DELETE_ACCOUNT"
+ NewPassword PromptRequestAction = "CREATE_PASSWORD"
+ Sign PromptRequestAction = "SIGN"
+ Import PromptRequestAction = "IMPORT_ACCOUNT"
+ Backup PromptRequestAction = "BACKUP_ACCOUNT"
+ TradeRolls PromptRequestAction = "TRADE_ROLLS"
+ Unprotect PromptRequestAction = "UNPROTECT"
+ AddSignRule PromptRequestAction = "ADD_SIGN_RULE"
+ DeleteSignRule PromptRequestAction = "DELETE_SIGN_RULE"
+ UpdateSignRule PromptRequestAction = "UPDATE_SIGN_RULE"
+)
+
+var PromptRequest = []struct {
+ Value PromptRequestAction
+ TSName string
+}{
+ {Delete, "delete"},
+ {NewPassword, "newPassword"},
+ {Sign, "sign"},
+ {Import, "import"},
+ {Backup, "backup"},
+ {TradeRolls, "tradeRolls"},
+ {Unprotect, "unprotect"},
+ {AddSignRule, "addSignRule"},
+ {DeleteSignRule, "deleteSignRule"},
+ {UpdateSignRule, "updateSignRule"},
+}
diff --git a/pkg/app/events.go b/pkg/app/events.go
index 5b9c02e07..2cdf8a878 100644
--- a/pkg/app/events.go
+++ b/pkg/app/events.go
@@ -2,12 +2,6 @@ package walletapp
import "github.com/awnumar/memguard"
-const (
- PromptResultEvent string = "promptResult"
- PromptDataEvent string = "promptData"
- PromptRequestEvent string = "promptRequest"
-)
-
// Events
type EventData struct {
@@ -22,21 +16,6 @@ const (
Cancel PromptCtrl = iota
)
-type PromptRequestAction int
-
-const (
- Delete PromptRequestAction = iota
- NewPassword
- Sign
- Import
- Backup
- TradeRolls
- Unprotect
- AddSignRule
- DeleteSignRule
- UpdateSignRule
-)
-
// User input interfaces for the channel
// EventInterface interface that all message types will implement
diff --git a/pkg/config/config.go b/pkg/config/config.go
index b3ebe8eda..a193f75fb 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -128,7 +128,7 @@ func SaveConfig(config *Config) error {
func saveConfigUnsafe(config *Config) error {
// Check for duplicate IDs
if err := config.ensureUniqueRuleIDs(); err != nil {
- return fmt.Errorf("duplicate SignRule IDs found: %v", err)
+ return err
}
path := configFilePath()
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 1e509d9f1..a5af7d6d5 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -1,7 +1,6 @@
package config
import (
- "fmt"
"log"
"os"
"testing"
@@ -30,7 +29,7 @@ func TestMain(m *testing.M) {
func TestAddSignRule(t *testing.T) {
rule := SignRule{
Name: "Test Rule",
- Contract: "test_contract",
+ Contract: "AS12U4TZfNK7qoLyEERBBRDMu8nm5MKoRzPXDXans4v9wdATZedz9",
RuleType: RuleTypeAutoSign,
Enabled: true,
}
@@ -48,7 +47,7 @@ func TestAddSignRule(t *testing.T) {
// Add another rule
rule2 := SignRule{
Name: "Test Rule 2",
- Contract: "test_contract_2",
+ Contract: "AS133eqPPaPttJ6hJnk3sfoG5cjFFqBDi1VGxdo2wzWkq8AfZnan",
RuleType: RuleTypeDisablePasswordPrompt,
Enabled: false,
}
@@ -76,7 +75,7 @@ func TestAddSignRule(t *testing.T) {
}
func TestDeleteSignRule(t *testing.T) {
- contract := "test_delete_contract"
+ contract := "AS12UMSUxgpRBB6ArZDJ19arHoxNkkpdfofQGekAiAJqsuE6PEFJy"
rule := SignRule{
Name: "Test Rule",
@@ -85,8 +84,6 @@ func TestDeleteSignRule(t *testing.T) {
Enabled: true,
}
- fmt.Println("TestDeleteSignRule: adding rule", rule)
-
ruleID, err := cfg.AddSignRule(accountName, rule)
assert.NoError(t, err)
@@ -94,14 +91,13 @@ func TestDeleteSignRule(t *testing.T) {
assert.NoError(t, err)
// Verify the rule was deleted
- deletedRule, err := cfg.GetSignRule(accountName, ruleID)
- assert.Error(t, err, "rule not found")
+ deletedRule := cfg.GetSignRule(accountName, ruleID)
assert.Nil(t, deletedRule)
}
func TestUpdateSignRule(t *testing.T) {
accountName := "test_account"
- contract := "test_update_contract"
+ contract := "AS12UMSUxgpRBB6ArZDJ19arHoxNkkpdfofQGekAiAJqsuE6PEFJy"
rule := SignRule{
Name: "Test Rule",
Contract: contract,
@@ -114,7 +110,7 @@ func TestUpdateSignRule(t *testing.T) {
newRule := SignRule{
Name: "Updated Rule",
- Contract: "updated_contract",
+ Contract: "AS125oPLYRTtfVjpWisPZVTLjBhCFfQ1jDsi75XNtRm1NZux54eCj",
RuleType: RuleTypeDisablePasswordPrompt,
Enabled: false,
}
@@ -124,8 +120,8 @@ func TestUpdateSignRule(t *testing.T) {
assert.NotEqual(t, ruleID, newRuleID)
// Verify the rule was updated
- updatedRule, err := cfg.GetSignRule(accountName, newRuleID)
- assert.NoError(t, err)
+ updatedRule := cfg.GetSignRule(accountName, newRuleID)
+ assert.NotNil(t, updatedRule)
assert.Equal(t, newRuleID, updatedRule.ID)
assert.Equal(t, newRule.Name, updatedRule.Name)
assert.Equal(t, newRule.Contract, updatedRule.Contract)
@@ -133,15 +129,14 @@ func TestUpdateSignRule(t *testing.T) {
assert.Equal(t, newRule.Enabled, updatedRule.Enabled)
// Verify previous rule is deleted
- deletedRule, err := cfg.GetSignRule(accountName, ruleID)
- assert.Error(t, err, "rule not found")
+ deletedRule := cfg.GetSignRule(accountName, ruleID)
assert.Nil(t, deletedRule)
}
func TestValidateRuleID(t *testing.T) {
rule := SignRule{
Name: "Test Rule",
- Contract: "test_contract",
+ Contract: "AS1hCJXjndR4c9vekLWsXGnrdigp4AaZ7uYG3UKFzzKnWVsrNLPJ",
RuleType: RuleTypeAutoSign,
Enabled: true,
}
@@ -158,7 +153,7 @@ func TestValidateRuleID(t *testing.T) {
func TestHasEnabledRule(t *testing.T) {
accountName := "test_account"
- contract := "test_hasEnable_contract"
+ contract := "AS124vf3YfAJCSCQVYKczzuWWpXrximFpbTmX4rheLs5uNSftiiRY"
rule := SignRule{
Name: "Test Rule",
Contract: contract,
diff --git a/pkg/config/rules.go b/pkg/config/rules.go
index 140921c02..7d3324b0c 100644
--- a/pkg/config/rules.go
+++ b/pkg/config/rules.go
@@ -4,10 +4,12 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
+
+ "github.com/massalabs/station-massa-wallet/pkg/utils"
)
func (c *Config) AddSignRule(accountName string, rule SignRule) (string, error) {
- if err := validateRule(rule); err != nil {
+ if err := ValidateRule(rule); err != nil {
return "", fmt.Errorf("invalid rule: %v", err)
}
@@ -24,8 +26,8 @@ func (c *Config) AddSignRule(accountName string, rule SignRule) (string, error)
}
// check if the rule already exists
- _, err := c.getSignRuleUnsafe(accountName, rule.ID)
- if err == nil {
+ existingRule := c.getSignRuleUnsafe(accountName, rule.ID)
+ if existingRule != nil {
return "", fmt.Errorf("rule %s already exists", rule.ID)
}
@@ -33,8 +35,7 @@ func (c *Config) AddSignRule(accountName string, rule SignRule) (string, error)
account.SignRules = append(account.SignRules, rule)
c.Accounts[accountName] = account
- err = saveConfigUnsafe(c)
- if err != nil {
+ if err := saveConfigUnsafe(c); err != nil {
return "", fmt.Errorf("error saving configuration: %v", err)
}
@@ -79,6 +80,10 @@ func (c *Config) DeleteSignRule(accountName, ruleID string) error {
}
func (c *Config) UpdateSignRule(accountName, ruleID string, newRule SignRule) (string, error) {
+ if err := ValidateRule(newRule); err != nil {
+ return "", fmt.Errorf("invalid rule: %v", err)
+ }
+
configManager.mu.Lock()
defer configManager.mu.Unlock()
@@ -110,7 +115,11 @@ func (c *Config) UpdateSignRule(accountName, ruleID string, newRule SignRule) (s
return newRule.ID, nil
}
-func validateRule(rule SignRule) error {
+func ValidateRule(rule SignRule) error {
+ if rule.Contract != "*" && !utils.IsValidContract(rule.Contract) {
+ return fmt.Errorf("invalid contract address: %s", rule.Contract)
+ }
+
switch rule.RuleType {
case RuleTypeDisablePasswordPrompt:
return nil
@@ -178,24 +187,24 @@ func (c *Config) validateAllRuleIDs() error {
return nil
}
-func (c *Config) getSignRuleUnsafe(accountName, ruleID string) (*SignRule, error) {
+func (c *Config) getSignRuleUnsafe(accountName, ruleID string) *SignRule {
// Check if the account exists
account, exists := c.Accounts[accountName]
if !exists {
- return nil, fmt.Errorf("account not found: %s", accountName)
+ return nil
}
// Search for the rule by ID
for _, rule := range account.SignRules {
if rule.ID == ruleID {
- return &rule, nil
+ return &rule
}
}
- return nil, fmt.Errorf("rule not found: %s", ruleID)
+ return nil
}
-func (c *Config) GetSignRule(accountName, ruleID string) (*SignRule, error) {
+func (c *Config) GetSignRule(accountName, ruleID string) *SignRule {
configManager.mu.RLock()
defer configManager.mu.RUnlock()
@@ -256,3 +265,15 @@ func (c *Config) GetEnabledRuleForContract(accountName string, contract *string)
return ruleType
}
+
+func (c *Config) IsExistingRule(accountName string, rule SignRule) bool {
+ configManager.mu.RLock()
+ defer configManager.mu.RUnlock()
+
+ // Generate the expected ID based on the rule's parameters
+ expectedID := generateRuleID(accountName, rule)
+
+ rulePtr := c.getSignRuleUnsafe(accountName, expectedID)
+
+ return rulePtr != nil
+}
diff --git a/pkg/utils/address.go b/pkg/utils/address.go
index de9b0e938..398fb0280 100644
--- a/pkg/utils/address.go
+++ b/pkg/utils/address.go
@@ -2,21 +2,23 @@ package utils
import "github.com/massalabs/station/pkg/node/base58"
-// IsValidAddress checks if the address is valid based on the prefix rule, non-empty rule, and successful decoding.
func IsValidAddress(addr string) bool {
- if addr == "" {
+ if len(addr) <= 2 {
return false
}
- addressPrefix := addr[:2]
addressWithoutPrefix := addr[2:]
- if addressPrefix == "AS" && len(addressWithoutPrefix) > 0 {
- _, _, err := base58.VersionedCheckDecode(addressWithoutPrefix)
- if err == nil {
- return true
- }
+ _, _, err := base58.VersionedCheckDecode(addressWithoutPrefix)
+
+ return err == nil
+}
+
+func IsValidContract(addr string) bool {
+ if !IsValidAddress(addr) {
+ return false
}
+ addressPrefix := addr[:2]
- return false
+ return addressPrefix == "AS"
}
diff --git a/pkg/wails/wails.go b/pkg/wails/wails.go
index d44930b0a..88a3bc198 100644
--- a/pkg/wails/wails.go
+++ b/pkg/wails/wails.go
@@ -24,5 +24,9 @@ func NewWailsApp(app *wApp.WalletApp, assets embed.FS) *application.Application
Bind: []interface{}{
app,
},
+ EnumBind: []interface{}{
+ wApp.PromptRequest,
+ wApp.EventTypes,
+ },
})
}
diff --git a/wails-frontend/package-lock.json b/wails-frontend/package-lock.json
index b7a8c9747..4a8d63640 100644
--- a/wails-frontend/package-lock.json
+++ b/wails-frontend/package-lock.json
@@ -9,7 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@massalabs/massa-web3": "^5.1.0",
- "@massalabs/react-ui-kit": "^1.0.2",
+ "@massalabs/react-ui-kit": "^1.0.3-dev.20250122150606",
"currency.js": "^2.0.4",
"dot-object": "^2.1.4",
"esbuild": "^0.17.19",
@@ -2032,9 +2032,9 @@
}
},
"node_modules/@massalabs/react-ui-kit": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@massalabs/react-ui-kit/-/react-ui-kit-1.0.2.tgz",
- "integrity": "sha512-+HzUCUBaOdJrYoTvt3xL/heW1OVOvrnHVclzPZPk8SZ27cZEbncJeq3q+LB0KFlEb4r7TUVp72osxkBfltqHAQ==",
+ "version": "1.0.3-dev.20250122150606",
+ "resolved": "https://registry.npmjs.org/@massalabs/react-ui-kit/-/react-ui-kit-1.0.3-dev.20250122150606.tgz",
+ "integrity": "sha512-uBNoNtfU8zEAwyb3g0K/g3SpZHOwNw6WprBDH2maGyQh6+DRId/PmlHBYeISGR9uxiBEbNnMTnNNnv7dk+KJ0Q==",
"dependencies": {
"@headlessui/react": "^1.7.15",
"@massalabs/massa-web3": "^5.1.1",
diff --git a/wails-frontend/package.json b/wails-frontend/package.json
index bfa4f94bc..6883624f8 100644
--- a/wails-frontend/package.json
+++ b/wails-frontend/package.json
@@ -18,7 +18,7 @@
},
"dependencies": {
"@massalabs/massa-web3": "^5.1.0",
- "@massalabs/react-ui-kit": "^1.0.2",
+ "@massalabs/react-ui-kit": "^1.0.3-dev.20250122150606",
"currency.js": "^2.0.4",
"dot-object": "^2.1.4",
"esbuild": "^0.17.19",
diff --git a/wails-frontend/package.json.md5 b/wails-frontend/package.json.md5
index 76fdba50e..316d51841 100755
--- a/wails-frontend/package.json.md5
+++ b/wails-frontend/package.json.md5
@@ -1 +1 @@
-01bf439a1eae333a806df63eb95be49c
\ No newline at end of file
+c65c84ad1d9819f5c36e51ecb5a2c005
\ No newline at end of file
diff --git a/wails-frontend/src/app.tsx b/wails-frontend/src/app.tsx
index 6e73c39a6..45c7c9834 100644
--- a/wails-frontend/src/app.tsx
+++ b/wails-frontend/src/app.tsx
@@ -1,44 +1,45 @@
import { EventsOn } from '@wailsjs/runtime/runtime';
import { useNavigate } from 'react-router-dom';
-import {
- events,
- promptAction,
- promptRequest,
- promptResult,
-} from './events/events';
+import { promptRequest, promptResult } from './events';
import Intl from './i18n/i18n';
import { Loading } from './pages/loading';
import { useConfigStore } from './store/store';
import { ErrorCode } from './utils';
+import { walletapp } from '../wailsjs/go/models';
export function App() {
const navigate = useNavigate();
+ const { PromptRequestAction, EventType } = walletapp;
+
const handlePromptRequest = (req: promptRequest) => {
clearTimeout(useConfigStore.getState().timeoutId);
switch (req.Action) {
- case promptAction.deleteReq:
- case promptAction.signReq:
- case promptAction.tradeRollsReq:
- case promptAction.unprotectReq:
+ case PromptRequestAction.delete:
+ case PromptRequestAction.sign:
+ case PromptRequestAction.tradeRolls:
+ case PromptRequestAction.unprotect:
+ case PromptRequestAction.addSignRule:
+ case PromptRequestAction.deleteSignRule:
+ case PromptRequestAction.updateSignRule:
navigate('/password', { state: { req } });
return;
- case promptAction.newPasswordReq:
+ case PromptRequestAction.newPassword:
navigate('/new-password', { state: { req } });
return;
- case promptAction.importReq:
+ case PromptRequestAction.import:
navigate('/import-methods', { state: { req } });
return;
- case promptAction.backupReq:
+ case PromptRequestAction.backup:
navigate('/backup-methods', { state: { req } });
return;
default:
}
};
- EventsOn(events.promptResult, (result: promptResult) => {
+ EventsOn(EventType.promptResult, (result: promptResult) => {
if (!result.Success && result.CodeMessage === ErrorCode.Timeout) {
const errorMessage = Intl.t(`errors.${result.CodeMessage}`);
navigate('/failure', {
@@ -47,7 +48,7 @@ export function App() {
}
});
- EventsOn(events.promptRequest, handlePromptRequest);
+ EventsOn(EventType.promptRequest, handlePromptRequest);
return ;
}
diff --git a/wails-frontend/src/events/index.ts b/wails-frontend/src/events/index.ts
new file mode 100644
index 000000000..fcb073fef
--- /dev/null
+++ b/wails-frontend/src/events/index.ts
@@ -0,0 +1 @@
+export * from './types';
diff --git a/wails-frontend/src/events/events.tsx b/wails-frontend/src/events/types.tsx
similarity index 52%
rename from wails-frontend/src/events/events.tsx
rename to wails-frontend/src/events/types.tsx
index 6d6b33eb9..f80b45c8b 100644
--- a/wails-frontend/src/events/events.tsx
+++ b/wails-frontend/src/events/types.tsx
@@ -1,3 +1,5 @@
+import { walletapp } from '@wailsjs/go/models';
+
/* eslint-disable @typescript-eslint/no-explicit-any */
export type promptResult = {
Success: boolean;
@@ -5,29 +7,13 @@ export type promptResult = {
Data: T;
};
-export enum promptAction {
- deleteReq = 0,
- newPasswordReq = 1,
- signReq = 2,
- importReq = 3,
- backupReq = 4,
- tradeRollsReq = 6,
- unprotectReq = 7,
-}
-
export type promptRequest = {
- Action: promptAction;
+ Action: walletapp.PromptRequestAction;
Msg: string;
Data: any;
CodeMessage: string;
};
-export const events = {
- promptResult: 'promptResult',
- promptData: 'promptData',
- promptRequest: 'promptRequest',
-};
-
export const backupMethods = {
ymlFileBackup: 'yaml',
privateKeyBackup: 'privateKey',
diff --git a/wails-frontend/src/i18n/en_US.json b/wails-frontend/src/i18n/en_US.json
index 62ef24f26..6333bc3d9 100644
--- a/wails-frontend/src/i18n/en_US.json
+++ b/wails-frontend/src/i18n/en_US.json
@@ -14,7 +14,33 @@
"password": "Password"
}
},
+ "signRule": {
+ "addRule": {
+ "title": "Add a signature rule"
+ },
+ "updateRule": {
+ "title": "Update a signature rule"
+ },
+ "deleteRule": {
+ "title": "Delete a signature rule"
+ },
+ "behavior": {
+ "disable_password_prompt": "You are allowing the wallet to remember your password and enable one click sign for the selected contract(s)",
+ "auto_sign": "You are allowing the wallet to sign transactions without asking for confirmation for the selected contract"
+ },
+ "description": "Description from the requesting dApp",
+ "details": "Rule details",
+ "contract": "Contract",
+ "name": "Name",
+ "ruleType": "Rule type",
+ "enabled": "Enable",
+ "rulesTypeDesc": {
+ "auto_sign": "Auto sign",
+ "disable_password_prompt": "Remember password"
+ }
+ },
"password-prompt": {
+ "account": "Account:",
"transfer": {
"from": "from: ",
"to": "to: ",
@@ -160,7 +186,11 @@
"Wallet-0012": "Failed to sign: invalid operation",
"Wallet-0021": "Failed to sign",
"transaction": "Failed to process transaction",
- "delete": "Failed to process delete operation",
- "default": "Failed to process operation"
+ "delete": "Failed to delete account",
+ "default": "Failed to process operation",
+ "addRule": "Failed to add signature rule",
+ "updateRule": "Failed to update signature rule",
+ "deleteRule": "Failed to delete signature rule",
+ "unknownRuleRequest": "Unknown signature rule request"
}
}
diff --git a/wails-frontend/src/pages/ImportPrivateKey/PromptNickname.tsx b/wails-frontend/src/pages/ImportPrivateKey/PromptNickname.tsx
index 68aab3d3a..76c87fc3c 100644
--- a/wails-frontend/src/pages/ImportPrivateKey/PromptNickname.tsx
+++ b/wails-frontend/src/pages/ImportPrivateKey/PromptNickname.tsx
@@ -8,7 +8,7 @@ import {
import { useLocation, useNavigate } from 'react-router-dom';
import { IMPORT_STEPS } from '@/const/stepper';
-import { promptRequest } from '@/events/events';
+import { promptRequest } from '@/events';
import Intl from '@/i18n/i18n';
import { Layout } from '@/layouts/Layout/Layout';
import { IErrorObject, parseForm, handleCancel } from '@/utils';
diff --git a/wails-frontend/src/pages/ImportPrivateKey/PromptPrivateKey.tsx b/wails-frontend/src/pages/ImportPrivateKey/PromptPrivateKey.tsx
index e0f86436e..ed9b5af8c 100644
--- a/wails-frontend/src/pages/ImportPrivateKey/PromptPrivateKey.tsx
+++ b/wails-frontend/src/pages/ImportPrivateKey/PromptPrivateKey.tsx
@@ -4,7 +4,7 @@ import { Password, Button, Stepper } from '@massalabs/react-ui-kit';
import { useLocation, useNavigate } from 'react-router-dom';
import { IMPORT_STEPS } from '@/const/stepper';
-import { promptRequest } from '@/events/events';
+import { promptRequest } from '@/events';
import Intl from '@/i18n/i18n';
import { Layout } from '@/layouts/Layout/Layout';
import { IErrorObject, parseForm, handleCancel } from '@/utils';
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/BuySellRoll/BuySellRoll.tsx b/wails-frontend/src/pages/PasswordPromptHandler/BuySellRoll/BuySellRoll.tsx
index bb743134c..a4cab467f 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/BuySellRoll/BuySellRoll.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/BuySellRoll/BuySellRoll.tsx
@@ -1,5 +1,4 @@
-import { formatAmount } from '@massalabs/react-ui-kit';
-import { massaToken } from '@massalabs/react-ui-kit/src/lib/massa-react/utils/const';
+import { formatAmount, massaToken } from '@massalabs/react-ui-kit';
import { OPER_BUY_ROLL } from '@/const/operations';
import Intl from '@/i18n/i18n';
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/CallSC/FTTransferInfo.tsx b/wails-frontend/src/pages/PasswordPromptHandler/CallSC/FTTransferInfo.tsx
index 10dc9f984..2ed098e58 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/CallSC/FTTransferInfo.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/CallSC/FTTransferInfo.tsx
@@ -1,6 +1,5 @@
import { Args } from '@massalabs/massa-web3';
-import { formatAmount } from '@massalabs/react-ui-kit';
-import { maskAddress } from '@massalabs/react-ui-kit/src/lib/massa-react/utils';
+import { formatAmount, maskAddress } from '@massalabs/react-ui-kit';
import { LogPrint } from '@wailsjs/runtime/runtime';
import Intl from '@/i18n/i18n';
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/Delete.tsx b/wails-frontend/src/pages/PasswordPromptHandler/Delete.tsx
index 7b2cf8952..168472849 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/Delete.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/Delete.tsx
@@ -1,12 +1,13 @@
import { SyntheticEvent, useRef, useState } from 'react';
import { Button, Password } from '@massalabs/react-ui-kit';
+import { walletapp } from '@wailsjs/go/models';
import { SendPromptInput } from '@wailsjs/go/walletapp/WalletApp';
import { EventsOnce } from '@wailsjs/runtime/runtime';
import { FiTrash2 } from 'react-icons/fi';
import { useLocation, useNavigate } from 'react-router-dom';
-import { events, promptRequest, promptResult } from '@/events/events';
+import { promptRequest, promptResult } from '@/events';
import Intl from '@/i18n/i18n';
import { Layout } from '@/layouts/Layout/Layout';
import { PromptRequestDeleteData } from '@/pages/PasswordPromptHandler/PasswordPromptHandler';
@@ -29,11 +30,13 @@ export function Delete() {
const req: promptRequest = state.req;
const data: PromptRequestDeleteData = req.Data;
+ const { EventType } = walletapp;
+
function save(e: SyntheticEvent) {
const form = parseForm(e);
const { password } = form;
- EventsOnce(events.promptResult, handleResult);
+ EventsOnce(EventType.promptResult, handleResult);
SendPromptInput(password);
}
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/ExecuteSC.tsx/ExecuteSc.tsx b/wails-frontend/src/pages/PasswordPromptHandler/ExecuteSC.tsx/ExecuteSc.tsx
index 421c7e44e..752a95f63 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/ExecuteSC.tsx/ExecuteSc.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/ExecuteSC.tsx/ExecuteSc.tsx
@@ -1,5 +1,4 @@
-import { Tooltip, formatAmount } from '@massalabs/react-ui-kit';
-import { massaToken } from '@massalabs/react-ui-kit/src/lib/massa-react/utils/const';
+import { Tooltip, formatAmount, massaToken } from '@massalabs/react-ui-kit';
import { FiAlertTriangle, FiInfo } from 'react-icons/fi';
import Intl from '@/i18n/i18n';
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/PasswordPromptHandler.tsx b/wails-frontend/src/pages/PasswordPromptHandler/PasswordPromptHandler.tsx
index 1adb5dd69..5d84fc20a 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/PasswordPromptHandler.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/PasswordPromptHandler.tsx
@@ -1,8 +1,10 @@
+import { walletapp } from '@wailsjs/go/models';
import { useLocation } from 'react-router-dom';
import { Delete } from './Delete';
import { Sign } from './Sign';
-import { promptAction, promptRequest } from '@/events/events';
+import { SignRule } from '../SignRuleHandler/signRulePrompt';
+import { promptRequest } from '@/events';
export interface PromptRequestDeleteData {
Nickname: string;
@@ -12,16 +14,21 @@ export interface PromptRequestDeleteData {
export default function PasswordPromptHandler() {
const { state } = useLocation();
const req: promptRequest = state.req;
- const { deleteReq, signReq } = promptAction;
+
+ const { PromptRequestAction } = walletapp;
return (
<>
{(() => {
switch (req.Action) {
- case deleteReq:
+ case PromptRequestAction.delete:
return ;
- case signReq:
+ case PromptRequestAction.sign:
return ;
+ case PromptRequestAction.addSignRule:
+ case PromptRequestAction.updateSignRule:
+ case PromptRequestAction.deleteSignRule:
+ return ;
}
})()}
>
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/Sign.tsx b/wails-frontend/src/pages/PasswordPromptHandler/Sign.tsx
index 00fe2d704..ea96b5d3c 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/Sign.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/Sign.tsx
@@ -2,6 +2,7 @@ import { SyntheticEvent, useRef, useState } from 'react';
import { Mas } from '@massalabs/massa-web3';
import { Button, Password } from '@massalabs/react-ui-kit';
+import { walletapp } from '@wailsjs/go/models';
import { SendSignPromptInput } from '@wailsjs/go/walletapp/WalletApp';
import { EventsOnce, WindowSetSize } from '@wailsjs/runtime/runtime';
import { FiLock } from 'react-icons/fi';
@@ -22,7 +23,7 @@ import {
OPER_SELL_ROLL,
OPER_TRANSACTION,
} from '@/const/operations';
-import { events, promptRequest, promptResult } from '@/events/events';
+import { promptRequest, promptResult } from '@/events';
import Intl from '@/i18n/i18n';
import { SignLayout } from '@/layouts/Layout/SignLayout';
import {
@@ -92,11 +93,13 @@ export function Sign() {
const [fees, setFees] = useState(BigInt(signData.Fees || 0));
const [isEditing, setIsEditing] = useState(false);
+ const { EventType } = walletapp;
+
function save(e: SyntheticEvent) {
const form = parseForm(e);
const { password } = form;
- EventsOnce(events.promptResult, handleResult);
+ EventsOnce(EventType.promptResult, handleResult);
SendSignPromptInput(password, fees.toString());
}
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/From.tsx b/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/From.tsx
index 4cf84a293..79431ea25 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/From.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/From.tsx
@@ -1,7 +1,4 @@
-import {
- maskAddress,
- maskNickname,
-} from '@massalabs/react-ui-kit/src/lib/massa-react/utils';
+import { maskAddress, maskNickname } from '@massalabs/react-ui-kit';
import Intl from '@/i18n/i18n';
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/FromTo.tsx b/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/FromTo.tsx
index 501b44b4a..f6fe24b4f 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/FromTo.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/FromTo.tsx
@@ -1,9 +1,7 @@
-import {
- maskAddress,
- maskNickname,
-} from '@massalabs/react-ui-kit/src/lib/massa-react/utils';
+import { maskAddress, maskNickname } from '@massalabs/react-ui-kit';
import { FiArrowRight } from 'react-icons/fi';
+import { CopyClip } from '../components/clipBoardCopy';
import Intl from '@/i18n/i18n';
interface FromToProps {
@@ -46,7 +44,12 @@ export function FromTo(props: FromToProps) {
) : null}
- {maskAddress(recipientAddress)}
+
+
{maskAddress(recipientAddress)}
+
+
+
+
);
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/OperationCost.tsx b/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/OperationCost.tsx
index 878531057..94ca9e21c 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/OperationCost.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/SignComponentUtils/OperationCost.tsx
@@ -13,8 +13,8 @@ import {
InlineMoney,
Tooltip,
formatAmount,
+ massaToken,
} from '@massalabs/react-ui-kit';
-import { massaToken } from '@massalabs/react-ui-kit/src/lib/massa-react/utils/const';
import {
FiCheck,
FiChevronDown,
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/Transaction/Transaction.tsx b/wails-frontend/src/pages/PasswordPromptHandler/Transaction/Transaction.tsx
index 425a392c8..7e367e2e4 100644
--- a/wails-frontend/src/pages/PasswordPromptHandler/Transaction/Transaction.tsx
+++ b/wails-frontend/src/pages/PasswordPromptHandler/Transaction/Transaction.tsx
@@ -1,5 +1,4 @@
-import { formatAmount } from '@massalabs/react-ui-kit';
-import { massaToken } from '@massalabs/react-ui-kit/src/lib/massa-react/utils/const';
+import { formatAmount, massaToken } from '@massalabs/react-ui-kit';
import Intl from '@/i18n/i18n';
import { AmountBox } from '@/pages/PasswordPromptHandler/AmountBox';
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/components/account.tsx b/wails-frontend/src/pages/PasswordPromptHandler/components/account.tsx
new file mode 100644
index 000000000..9787a4a94
--- /dev/null
+++ b/wails-frontend/src/pages/PasswordPromptHandler/components/account.tsx
@@ -0,0 +1,28 @@
+import { maskAddress, maskNickname } from '@massalabs/react-ui-kit';
+
+import { CopyClip } from './clipBoardCopy';
+import Intl from '@/i18n/i18n';
+
+interface FromProps {
+ nickname: string;
+ walletAddress: string;
+}
+
+export function Account(props: FromProps) {
+ const { nickname, walletAddress } = props;
+
+ return (
+
+
{Intl.t('password-prompt.account')}
+
+
{maskNickname(nickname)}
+
+
{maskAddress(walletAddress)}
+
+
+
+
+
+
+ );
+}
diff --git a/wails-frontend/src/pages/PasswordPromptHandler/components/clipBoardCopy.tsx b/wails-frontend/src/pages/PasswordPromptHandler/components/clipBoardCopy.tsx
new file mode 100644
index 000000000..ada50177b
--- /dev/null
+++ b/wails-frontend/src/pages/PasswordPromptHandler/components/clipBoardCopy.tsx
@@ -0,0 +1,14 @@
+import { ClipboardSetText } from '@wailsjs/runtime';
+import { FiCopy } from 'react-icons/fi';
+
+interface CopyProps {
+ data: string;
+}
+
+export function CopyClip({ data }: CopyProps) {
+ async function handleCopy() {
+ await ClipboardSetText(data);
+ }
+
+ return ;
+}
diff --git a/wails-frontend/src/pages/SignRuleHandler/signRulePrompt.tsx b/wails-frontend/src/pages/SignRuleHandler/signRulePrompt.tsx
new file mode 100644
index 000000000..75e4ec3b8
--- /dev/null
+++ b/wails-frontend/src/pages/SignRuleHandler/signRulePrompt.tsx
@@ -0,0 +1,180 @@
+import { SyntheticEvent, useRef, useState } from 'react';
+
+import { Button, Password, maskAddress } from '@massalabs/react-ui-kit';
+import { walletapp } from '@wailsjs/go/models';
+import { SendPromptInput } from '@wailsjs/go/walletapp/WalletApp';
+import { EventsOnce, WindowSetSize } from '@wailsjs/runtime/runtime';
+import { FiAlertTriangle } from 'react-icons/fi';
+import { useLocation, useNavigate } from 'react-router-dom';
+
+import { ruleRequestData, signRuleActionStr } from './types';
+import { Account } from '../PasswordPromptHandler/components/account';
+import { CopyClip } from '../PasswordPromptHandler/components/clipBoardCopy';
+import { promptRequest, promptResult } from '@/events';
+import Intl from '@/i18n/i18n';
+import { Layout } from '@/layouts/Layout/Layout';
+import { validate } from '@/pages/PasswordPromptHandler/Sign';
+import {
+ ErrorCode,
+ IErrorObject,
+ handleApplyResult,
+ handleCancel,
+ parseForm,
+} from '@/utils';
+
+export function SignRule() {
+ const [error, setError] = useState(null);
+ const [errorMessage, setErrorMessage] = useState('');
+
+ const navigate = useNavigate();
+ const form = useRef(null);
+ const { state } = useLocation();
+ const req: promptRequest = state.req;
+ const data: ruleRequestData = req.Data;
+
+ let action = '';
+ let showWarning = false;
+
+ const { addSignRule, updateSignRule, deleteSignRule } =
+ walletapp.PromptRequestAction;
+
+ const { EventType } = walletapp;
+
+ switch (req.Action) {
+ case addSignRule:
+ action = signRuleActionStr.addSignRule;
+ showWarning = data.SignRule.Enabled;
+ break;
+ case updateSignRule:
+ action = signRuleActionStr.updateSignRule;
+ showWarning = data.SignRule.Enabled;
+ break;
+ case deleteSignRule:
+ action = signRuleActionStr.deleteSignRule;
+ break;
+ default:
+ setErrorMessage(Intl.t(`errors.unknownRuleRequest`));
+ }
+
+ const winWidth = 460;
+ const winHeight = showWarning ? 650 : 540;
+
+ WindowSetSize(winWidth, winHeight);
+
+ function handleResult(result: promptResult) {
+ let { Success, CodeMessage } = result;
+ setError(null);
+ setErrorMessage('');
+
+ if (!Success) {
+ if (CodeMessage === ErrorCode.WrongPassword) {
+ setError({ password: Intl.t(`errors.${CodeMessage}`) });
+ } else {
+ setErrorMessage(Intl.t(`errors.${action}`));
+ }
+ return;
+ }
+ handleApplyResult(navigate, req, setError, false)(result);
+ }
+
+ function submitPassword(e: SyntheticEvent) {
+ const form = parseForm(e);
+ const { password } = form;
+
+ EventsOnce(EventType.promptResult, handleResult);
+
+ SendPromptInput(password);
+ }
+
+ async function handleSubmitPassword(e: SyntheticEvent) {
+ e.preventDefault();
+ if (!validate(e, setError)) return;
+ submitPassword(e);
+ }
+
+ const isAllContract = data.SignRule.Contract === '*';
+
+ const renderContract = () => {
+ if (isAllContract) {
+ return 'All';
+ }
+ return maskAddress(data.SignRule.Contract, 10);
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/wails-frontend/src/pages/SignRuleHandler/types.tsx b/wails-frontend/src/pages/SignRuleHandler/types.tsx
new file mode 100644
index 000000000..e88dc7474
--- /dev/null
+++ b/wails-frontend/src/pages/SignRuleHandler/types.tsx
@@ -0,0 +1,24 @@
+export enum RuleType {
+ disablePasswordPrompt = 'disable_password_prompt',
+ autoSign = 'auto_sign',
+}
+
+// Used in i18n
+export enum signRuleActionStr {
+ addSignRule = 'addRule',
+ deleteSignRule = 'deleteRule',
+ updateSignRule = 'updateRule',
+}
+
+export interface ruleRequestData {
+ WalletAddress: string;
+ Nickname: string;
+ Description: string;
+ SignRule: {
+ Name: string;
+ Contract: string;
+ RuleType: RuleType;
+ Enabled: boolean;
+ ID: string;
+ };
+}
diff --git a/wails-frontend/src/pages/backupKeyPairs.tsx b/wails-frontend/src/pages/backupKeyPairs.tsx
index 45758da54..48bc7d3d9 100644
--- a/wails-frontend/src/pages/backupKeyPairs.tsx
+++ b/wails-frontend/src/pages/backupKeyPairs.tsx
@@ -1,12 +1,13 @@
import { useState, useRef, SyntheticEvent } from 'react';
import { Password, Button, Clipboard } from '@massalabs/react-ui-kit';
+import { walletapp } from '@wailsjs/go/models';
import { SendPromptInput } from '@wailsjs/go/walletapp/WalletApp';
import { ClipboardSetText, EventsOnce } from '@wailsjs/runtime';
import { FiCopy, FiArrowRight } from 'react-icons/fi';
import { useLocation, useNavigate } from 'react-router-dom';
-import { events, promptRequest, promptResult } from '@/events/events';
+import { promptRequest, promptResult } from '@/events';
import Intl from '@/i18n/i18n';
import { Layout } from '@/layouts/Layout/Layout';
import { ErrorCode, IErrorObject, handleCancel } from '@/utils';
@@ -59,6 +60,8 @@ function BackupKeyPairs() {
const [error, setError] = useState(null);
+ const { EventType } = walletapp;
+
function validate(e: SyntheticEvent) {
const formObject = parseForm(e);
const { password } = formObject;
@@ -96,7 +99,7 @@ function BackupKeyPairs() {
const formObject = parseForm(e);
const { password } = formObject;
- EventsOnce(events.promptResult, handleResult);
+ EventsOnce(EventType.promptResult, handleResult);
await SendPromptInput(password);
}
diff --git a/wails-frontend/src/pages/backupMethods.tsx b/wails-frontend/src/pages/backupMethods.tsx
index f740bd4f6..367e60dc9 100644
--- a/wails-frontend/src/pages/backupMethods.tsx
+++ b/wails-frontend/src/pages/backupMethods.tsx
@@ -1,12 +1,12 @@
import { useState } from 'react';
-import { Button } from '@massalabs/react-ui-kit';
-import { maskNickname } from '@massalabs/react-ui-kit/src/lib/massa-react/utils';
+import { Button, maskNickname } from '@massalabs/react-ui-kit';
+import { walletapp } from '@wailsjs/go/models';
import { SendPromptInput } from '@wailsjs/go/walletapp/WalletApp';
import { EventsOnce } from '@wailsjs/runtime/runtime';
import { useLocation, useNavigate } from 'react-router-dom';
-import { backupMethods, events, promptRequest } from '@/events/events';
+import { backupMethods, promptRequest } from '@/events';
import Intl from '@/i18n/i18n';
import { Layout } from '@/layouts/Layout/Layout';
import { handleApplyResult } from '@/utils';
@@ -20,9 +20,11 @@ function BackupMethods() {
const req: promptRequest = state.req;
const walletName: string = req.Msg;
+ const { EventType } = walletapp;
+
async function handleDownloadYaml() {
EventsOnce(
- events.promptResult,
+ EventType.promptResult,
handleApplyResult(navigate, req, setErrorMsg, true),
);
diff --git a/wails-frontend/src/pages/confirmDelete.tsx b/wails-frontend/src/pages/confirmDelete.tsx
index 04f7ec252..4b12a9fbe 100644
--- a/wails-frontend/src/pages/confirmDelete.tsx
+++ b/wails-frontend/src/pages/confirmDelete.tsx
@@ -1,10 +1,11 @@
import { Button } from '@massalabs/react-ui-kit';
+import { walletapp } from '@wailsjs/go/models';
import { SendPromptInput } from '@wailsjs/go/walletapp/WalletApp';
import { EventsOnce } from '@wailsjs/runtime/runtime';
import { FiAlertTriangle, FiTrash2 } from 'react-icons/fi';
import { useLocation, useNavigate } from 'react-router-dom';
-import { events, promptRequest } from '@/events/events';
+import { promptRequest } from '@/events';
import { Layout } from '@/layouts/Layout/Layout';
import { handleApplyResult, handleCancel } from '@/utils';
@@ -14,8 +15,10 @@ function ConfirmDelete() {
const req: promptRequest = state.req;
const password = state.password;
+ const { EventType } = walletapp;
+
function handleConfirm() {
- EventsOnce(events.promptResult, handleApplyResult(navigate, req));
+ EventsOnce(EventType.promptResult, handleApplyResult(navigate, req));
SendPromptInput(password);
}
diff --git a/wails-frontend/src/pages/failure.tsx b/wails-frontend/src/pages/failure.tsx
index 46cd97fcc..221946aa6 100644
--- a/wails-frontend/src/pages/failure.tsx
+++ b/wails-frontend/src/pages/failure.tsx
@@ -1,7 +1,7 @@
import { FiX } from 'react-icons/fi';
import { useLocation } from 'react-router';
-import { promptRequest } from '@/events/events';
+import { promptRequest } from '@/events';
import { Layout } from '@/layouts/Layout/Layout';
const Failure = () => {
diff --git a/wails-frontend/src/pages/importFile.tsx b/wails-frontend/src/pages/importFile.tsx
index 07bef45a6..6be23a29f 100644
--- a/wails-frontend/src/pages/importFile.tsx
+++ b/wails-frontend/src/pages/importFile.tsx
@@ -1,7 +1,6 @@
import { useState } from 'react';
-import { Button } from '@massalabs/react-ui-kit';
-import { maskNickname } from '@massalabs/react-ui-kit/src/lib/massa-react/utils';
+import { Button, maskNickname } from '@massalabs/react-ui-kit';
import { walletapp } from '@wailsjs/go/models';
import {
SendPromptInput,
@@ -10,7 +9,7 @@ import {
import { EventsOnce } from '@wailsjs/runtime/runtime';
import { useLocation, useNavigate } from 'react-router-dom';
-import { events, promptRequest } from '@/events/events';
+import { promptRequest } from '@/events';
import Intl from '@/i18n/i18n';
import { Layout } from '@/layouts/Layout/Layout';
import { handleApplyResult, handleCancel } from '@/utils';
@@ -26,6 +25,8 @@ const ImportFile = () => {
const { state } = useLocation();
const req: promptRequest = state.req;
+ const { EventType } = walletapp;
+
const accountStyleSuccess = 'mas-body text-s-success pb-4';
const accountStyleNormal = 'mas-body text-neutral pb-4';
@@ -50,7 +51,7 @@ const ImportFile = () => {
setAccount(res);
} else {
EventsOnce(
- events.promptResult,
+ EventType.promptResult,
handleApplyResult(nav, req, setErrorMsg, true),
);
await SendPromptInput(account.filePath);
diff --git a/wails-frontend/src/pages/importMethods.tsx b/wails-frontend/src/pages/importMethods.tsx
index eaa8bc6c4..b08e7a5a8 100644
--- a/wails-frontend/src/pages/importMethods.tsx
+++ b/wails-frontend/src/pages/importMethods.tsx
@@ -1,7 +1,7 @@
import { Button } from '@massalabs/react-ui-kit';
import { useLocation, useNavigate } from 'react-router-dom';
-import { promptRequest } from '@/events/events';
+import { promptRequest } from '@/events';
import { Layout } from '@/layouts/Layout/Layout';
const ImportMethods = () => {
diff --git a/wails-frontend/src/pages/newPassword.tsx b/wails-frontend/src/pages/newPassword.tsx
index 562eac872..13ed2dc31 100644
--- a/wails-frontend/src/pages/newPassword.tsx
+++ b/wails-frontend/src/pages/newPassword.tsx
@@ -1,6 +1,7 @@
import { useState, useRef, SyntheticEvent } from 'react';
import { Password, Button, Stepper } from '@massalabs/react-ui-kit';
+import { walletapp } from '@wailsjs/go/models';
import {
SendPKeyPromptInput,
SendPromptInput,
@@ -9,7 +10,7 @@ import { EventsOnce } from '@wailsjs/runtime';
import { FiLock } from 'react-icons/fi';
import { useLocation, useNavigate } from 'react-router-dom';
-import { events, promptAction, promptRequest } from '@/events/events';
+import { promptRequest } from '@/events';
import { Layout } from '@/layouts/Layout/Layout';
import {
parseForm,
@@ -26,12 +27,14 @@ function NewPassword() {
const { state } = useLocation();
const req: promptRequest = state.req;
- const { newPasswordReq, importReq } = promptAction;
+ const { EventType } = walletapp;
+
+ const { newPassword, import: importReq } = walletapp.PromptRequestAction;
const isImportAction = req.Action === importReq;
function getButtonLabel() {
switch (req.Action) {
- case newPasswordReq:
+ case newPassword:
return 'Define';
case importReq:
return 'Define and import';
@@ -42,7 +45,7 @@ function NewPassword() {
function getSubtitle() {
switch (req.Action) {
- case newPasswordReq:
+ case newPassword:
return 'Enter a secure password';
case importReq:
return 'Define a new password';
@@ -83,7 +86,7 @@ function NewPassword() {
const { password } = form;
EventsOnce(
- events.promptResult,
+ EventType.promptResult,
handleApplyResult(navigate, req, setError, isImportAction),
);
diff --git a/wails-frontend/src/pages/success.tsx b/wails-frontend/src/pages/success.tsx
index d115404f9..a928947d7 100644
--- a/wails-frontend/src/pages/success.tsx
+++ b/wails-frontend/src/pages/success.tsx
@@ -1,7 +1,8 @@
+import { walletapp } from '@wailsjs/go/models';
import { FiCheck } from 'react-icons/fi';
import { useLocation } from 'react-router';
-import { promptAction, promptRequest } from '@/events/events';
+import { promptRequest } from '@/events';
import Intl from '@/i18n/i18n';
import { Layout } from '@/layouts/Layout/Layout';
@@ -9,17 +10,19 @@ function Success() {
const { state } = useLocation();
const { req } = state;
+ const { PromptRequestAction } = walletapp;
+
function successMsg(req: promptRequest) {
switch (req.Action) {
- case promptAction.deleteReq:
+ case PromptRequestAction.delete:
return Intl.t('success.delete');
- case promptAction.newPasswordReq:
+ case PromptRequestAction.newPassword:
return Intl.t('success.new-password');
- case promptAction.importReq:
+ case PromptRequestAction.import:
return Intl.t('success.import');
- case promptAction.signReq:
+ case PromptRequestAction.sign:
return Intl.t('success.sign');
- case promptAction.backupReq:
+ case PromptRequestAction.backup:
return Intl.t('success.backup');
default:
return Intl.t('success.success');
diff --git a/wails-frontend/src/utils/handleApplyResult.tsx b/wails-frontend/src/utils/handleApplyResult.tsx
index c373edfa7..424e35882 100644
--- a/wails-frontend/src/utils/handleApplyResult.tsx
+++ b/wails-frontend/src/utils/handleApplyResult.tsx
@@ -3,7 +3,7 @@ import { WindowReloadApp } from '@wailsjs/runtime/runtime';
import { NavigateFunction } from 'react-router-dom';
import { IErrorObject } from './errors';
-import { promptResult, promptRequest } from '@/events/events';
+import { promptResult, promptRequest } from '@/events';
import Intl from '@/i18n/i18n';
import { useConfigStore } from '@/store/store';
diff --git a/wails-frontend/wailsjs/go/models.ts b/wails-frontend/wailsjs/go/models.ts
index af2ec967d..c83e807f0 100755
--- a/wails-frontend/wailsjs/go/models.ts
+++ b/wails-frontend/wailsjs/go/models.ts
@@ -1,5 +1,22 @@
export namespace walletapp {
+ export enum PromptRequestAction {
+ delete = "DELETE_ACCOUNT",
+ newPassword = "CREATE_PASSWORD",
+ sign = "SIGN",
+ import = "IMPORT_ACCOUNT",
+ backup = "BACKUP_ACCOUNT",
+ tradeRolls = "TRADE_ROLLS",
+ unprotect = "UNPROTECT",
+ addSignRule = "ADD_SIGN_RULE",
+ deleteSignRule = "DELETE_SIGN_RULE",
+ updateSignRule = "UPDATE_SIGN_RULE",
+ }
+ export enum EventType {
+ promptResult = "PROMPT_RESULT",
+ promptData = "PROMPT_DATA",
+ promptRequest = "PROMPT_REQUEST",
+ }
export class selectFileResult {
err: string;
codeMessage: string;