From a7b440e7f0f0c3ab28eac5f2f21c2006460d2ffd Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Fri, 24 May 2024 11:04:00 -0400 Subject: [PATCH 1/3] move wallet/keychain. use sdk keychain in wallet --- avalanche/keychain.go | 18 ------------------ keychain/keychain.go | 18 ++++++++++++++++++ subnet/deploy_subnet.go | 5 +++-- subnet/subnet_test.go | 7 ++++--- {subnet => wallet}/wallet.go | 15 +++++++-------- 5 files changed, 32 insertions(+), 31 deletions(-) delete mode 100644 avalanche/keychain.go create mode 100644 keychain/keychain.go rename {subnet => wallet}/wallet.go (83%) diff --git a/avalanche/keychain.go b/avalanche/keychain.go deleted file mode 100644 index fd1741c..0000000 --- a/avalanche/keychain.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package avalanche - -import "github.com/ava-labs/avalanchego/utils/crypto/keychain" - -type Keychain struct { - Network Network - - Keychain keychain.Keychain - - Ledger keychain.Ledger - - UsesLedger bool - - LedgerIndices []uint32 -} diff --git a/keychain/keychain.go b/keychain/keychain.go new file mode 100644 index 0000000..122a3b0 --- /dev/null +++ b/keychain/keychain.go @@ -0,0 +1,18 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package keychain + +import ( + "avalanche-tooling-sdk-go/avalanche" + + "github.com/ava-labs/avalanchego/utils/crypto/keychain" +) + +type Keychain struct { + keychain.Keychain + Network avalanche.Network + Ledger keychain.Ledger + UsesLedger bool + LedgerIndices []uint32 +} diff --git a/subnet/deploy_subnet.go b/subnet/deploy_subnet.go index d27a9de..93816fc 100644 --- a/subnet/deploy_subnet.go +++ b/subnet/deploy_subnet.go @@ -8,6 +8,7 @@ import ( "fmt" "avalanche-tooling-sdk-go/multisig" + "avalanche-tooling-sdk-go/wallet" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/formatting/address" @@ -16,7 +17,7 @@ import ( ) // CreateSubnetTx creates uncommitted createSubnet transaction -func (c *Subnet) CreateSubnetTx(wallet Wallet) (*multisig.Multisig, error) { +func (c *Subnet) CreateSubnetTx(wallet wallet.Wallet) (*multisig.Multisig, error) { addrs, err := address.ParseToIDs(c.ControlKeys) if err != nil { return nil, fmt.Errorf("failure parsing control keys: %w", err) @@ -40,7 +41,7 @@ func (c *Subnet) CreateSubnetTx(wallet Wallet) (*multisig.Multisig, error) { } // CreateBlockchainTx creates uncommitted createBlockchain transaction -func (c *Subnet) CreateBlockchainTx(wallet Wallet) (*multisig.Multisig, error) { +func (c *Subnet) CreateBlockchainTx(wallet wallet.Wallet) (*multisig.Multisig, error) { wallet.SetSubnetAuthMultisig(c.SubnetAuthKeys) // create tx fxIDs := make([]ids.ID, 0) diff --git a/subnet/subnet_test.go b/subnet/subnet_test.go index 30b7275..f418581 100644 --- a/subnet/subnet_test.go +++ b/subnet/subnet_test.go @@ -5,6 +5,7 @@ package subnet import ( "avalanche-tooling-sdk-go/avalanche" + "avalanche-tooling-sdk-go/wallet" "context" "fmt" "testing" @@ -13,7 +14,7 @@ import ( "github.com/ava-labs/avalanchego/wallet/subnet/primary" ) -func TestSubnetDeploy(t *testing.T) { +func TestSubnetDeploy(_ *testing.T) { baseApp := avalanche.New(avalanche.DefaultLeveledLogger) subnetParams := SubnetParams{ SubnetEVM: SubnetEVMParams{ @@ -28,7 +29,7 @@ func TestSubnetDeploy(t *testing.T) { } newSubnet := New(baseApp, &subnetParams) ctx := context.Background() - wallet, _ := NewWallet( + wallet, _ := wallet.New( ctx, &primary.WalletConfig{ URI: "", @@ -37,7 +38,7 @@ func TestSubnetDeploy(t *testing.T) { PChainTxsToFetch: nil, }, ) - //deploy Subnet returns tx ID and error + // deploy Subnet returns multisig and error deploySubnetTx, _ := newSubnet.CreateSubnetTx(wallet) fmt.Printf("deploySubnetTx %s", deploySubnetTx) } diff --git a/subnet/wallet.go b/wallet/wallet.go similarity index 83% rename from subnet/wallet.go rename to wallet/wallet.go index 9fca0a9..8b8b70e 100644 --- a/subnet/wallet.go +++ b/wallet/wallet.go @@ -1,13 +1,12 @@ // Copyright (C) 2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. - -package subnet +package wallet import ( + "avalanche-tooling-sdk-go/keychain" "context" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/wallet/subnet/primary" @@ -20,14 +19,16 @@ type Wallet struct { options []common.Option } -func NewWallet(ctx context.Context, config *primary.WalletConfig) (Wallet, error) { +func New(ctx context.Context, config *primary.WalletConfig) (Wallet, error) { wallet, err := primary.MakeWallet( ctx, config, ) return Wallet{ - Wallet: wallet, - keychain: config.AVAXKeychain, + Wallet: wallet, + keychain: keychain.Keychain{ + Keychain: config.AVAXKeychain, + }, }, err } @@ -41,7 +42,6 @@ func (w *Wallet) SecureWalletIsChangeOwner() { Threshold: 1, Addrs: []ids.ShortID{changeAddr}, } - // TODO: avoid continuosly adding the options in succesive calls w.options = append(w.options, common.WithChangeOwner(changeOwner)) w.Wallet = primary.NewWalletWithOptions(w.Wallet, w.options...) } @@ -52,7 +52,6 @@ func (w *Wallet) SetAuthKeys(authKeys []ids.ShortID) { addrsSet := set.Set[ids.ShortID]{} addrsSet.Add(addrs...) addrsSet.Add(authKeys...) - // TODO: avoid continuosly adding the options in succesive calls w.options = append(w.options, common.WithCustomAddresses(addrsSet)) w.Wallet = primary.NewWalletWithOptions(w.Wallet, w.options...) } From 431f124a27731cd1416ca189b5eb61d7a080e2f4 Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Tue, 28 May 2024 10:39:15 -0400 Subject: [PATCH 2/3] adding ledger package --- avalanche/network.go | 21 ++++++-- go.mod | 12 ++++- go.sum | 18 +++++++ keychain/keychain.go | 119 ++++++++++++++++++++++++++++++++++++++++--- ledger/ledger.go | 108 +++++++++++++++++++++++++++++++++++++++ utils/utils.go | 62 ++++++++++++++++++++++ 6 files changed, 328 insertions(+), 12 deletions(-) create mode 100644 ledger/ledger.go create mode 100644 utils/utils.go diff --git a/avalanche/network.go b/avalanche/network.go index 76f09df..688a218 100644 --- a/avalanche/network.go +++ b/avalanche/network.go @@ -3,12 +3,25 @@ package avalanche +import "github.com/ava-labs/avalanchego/utils/constants" + type NetworkKind int64 type Network struct { - Kind NetworkKind - - ID uint32 - + Kind NetworkKind + ID uint32 Endpoint string } + +func (n Network) HRP() string { + switch n.ID { + case constants.LocalID: + return constants.LocalHRP + case constants.FujiID: + return constants.FujiHRP + case constants.MainnetID: + return constants.MainnetHRP + default: + return constants.FallbackHRP + } +} diff --git a/go.mod b/go.mod index 05bc006..b1da20f 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,19 @@ go 1.21.9 toolchain go1.21.10 -require github.com/ava-labs/avalanchego v1.11.5 +require ( + github.com/ava-labs/avalanchego v1.11.5 + golang.org/x/exp v0.0.0-20231127185646-65229373498e +) require ( github.com/DataDog/zstd v1.5.2 // indirect + github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect + github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/VictoriaMetrics/fastcache v1.10.0 // indirect github.com/ava-labs/coreth v0.13.3-rc.2 // indirect + github.com/ava-labs/ledger-avalanche/go v0.0.0-20231102202641-ae2ebdaeac34 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -88,10 +94,13 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tyler-smith/go-bip32 v1.0.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/urfave/cli/v2 v2.25.7 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect go.opentelemetry.io/otel v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect @@ -104,7 +113,6 @@ require ( go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect diff --git a/go.sum b/go.sum index 00fa081..f8e60ff 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,10 @@ github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3 github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc= +github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw= +github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc= +github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= @@ -22,6 +26,8 @@ github.com/ava-labs/avalanchego v1.11.5 h1:3HCE6I1/9BOSrIvwXXuOhVuMGMi926jKhQ+sg github.com/ava-labs/avalanchego v1.11.5/go.mod h1:XPNUEh0ezPEW1IV6yo6AwvqjTZSG1twGskDb7dVcIRA= github.com/ava-labs/coreth v0.13.3-rc.2 h1:lhyQwln6at1DTs1O586dMSAtGtSfQWlt2WH+Z2kgYdQ= github.com/ava-labs/coreth v0.13.3-rc.2/go.mod h1:4l15XGak3FklhIb7CtlC/1YVwGAfMl83R2zd2N0hNE0= +github.com/ava-labs/ledger-avalanche/go v0.0.0-20231102202641-ae2ebdaeac34 h1:mg9Uw6oZFJKytJxgxnl3uxZOs/SB8CVHg6Io4Tf99Zc= +github.com/ava-labs/ledger-avalanche/go v0.0.0-20231102202641-ae2ebdaeac34/go.mod h1:pJxaT9bUgeRNVmNRgtCHb7sFDIRKy7CzTQVi8gGNT6g= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -66,6 +72,8 @@ github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86c github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw= +github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877 h1:1MLK4YpFtIEo3ZtMA5C795Wtv5VuUnrXX7mQG+aHg6o= @@ -400,6 +408,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -419,6 +428,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tyler-smith/go-bip32 v1.0.0 h1:sDR9juArbUgX+bO/iblgZnMPeWY1KZMUC2AFUJdv5KE= +github.com/tyler-smith/go-bip32 v1.0.0/go.mod h1:onot+eHknzV4BVPwrzqY5OoVpyCvnwD7lMawL5aQupE= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -449,6 +460,10 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= @@ -473,6 +488,7 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -676,5 +692,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/keychain/keychain.go b/keychain/keychain.go index 122a3b0..907b8a0 100644 --- a/keychain/keychain.go +++ b/keychain/keychain.go @@ -1,18 +1,125 @@ // Copyright (C) 2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. - package keychain import ( - "avalanche-tooling-sdk-go/avalanche" + "fmt" "github.com/ava-labs/avalanchego/utils/crypto/keychain" + + "avalanche-tooling-sdk-go/avalanche" + "avalanche-tooling-sdk-go/ledger" + "avalanche-tooling-sdk-go/utils" + + "golang.org/x/exp/maps" ) type Keychain struct { keychain.Keychain - Network avalanche.Network - Ledger keychain.Ledger - UsesLedger bool - LedgerIndices []uint32 + network avalanche.Network + ledgerDevice *ledger.LedgerDevice + ledgerIndices []uint32 +} + +func (kc *Keychain) P() ([]string, error) { + return utils.P(kc.network.HRP(), kc.Addresses().List()) +} + +func (kc *Keychain) LedgerEnabled() bool { + return kc.ledgerDevice != nil +} + +func (kc *Keychain) AddLedgerIndices(indices []uint32) error { + if kc.LedgerEnabled() { + kc.ledgerIndices = utils.Unique(append(kc.ledgerIndices, indices...)) + utils.Uint32Sort(kc.ledgerIndices) + newKc, err := keychain.NewLedgerKeychainFromIndices(kc.ledgerDevice, kc.ledgerIndices) + if err != nil { + return err + } + kc.Keychain = newKc + return nil + } + return fmt.Errorf("keychain is not ledger enabled") +} + +func (kc *Keychain) AddLedgerAddresses(addresses []string) error { + if kc.LedgerEnabled() { + indices, err := kc.ledgerDevice.FindAddresses(addresses, 0) + if err != nil { + return err + } + return kc.AddLedgerIndices(maps.Values(indices)) + } + return fmt.Errorf("keychain is not ledger enabled") +} + +func (kc *Keychain) AddLedgerFunds(amount uint64) error { + if kc.LedgerEnabled() { + indices, err := kc.ledgerDevice.FindFunds(kc.network, amount, 0) + if err != nil { + return err + } + return kc.AddLedgerIndices(indices) + } + return fmt.Errorf("keychain is not ledger enabled") +} + +func GetKeychain( + app *application.Avalanche, + useEwoq bool, + useLedger bool, + ledgerAddresses []string, + network models.Network, + requiredFunds uint64, +) (*Keychain, error) { + // get keychain accessor + if useLedger { + ledgerDevice, err := ledger.New() + if err != nil { + return nil, err + } + // always have index 0, for change + ledgerIndices := []uint32{0} + if requiredFunds > 0 { + ledgerIndicesAux, err := searchForFundedLedgerIndices(network, ledgerDevice, requiredFunds) + if err != nil { + return nil, err + } + ledgerIndices = append(ledgerIndices, ledgerIndicesAux...) + } + if len(ledgerAddresses) > 0 { + ledgerIndicesAux, err := getLedgerIndices(ledgerDevice, ledgerAddresses) + if err != nil { + return nil, err + } + ledgerIndices = append(ledgerIndices, ledgerIndicesAux...) + } + ledgerIndicesSet := set.Set[uint32]{} + ledgerIndicesSet.Add(ledgerIndices...) + ledgerIndices = ledgerIndicesSet.List() + utils.SortUint32(ledgerIndices) + if err := showLedgerAddresses(network, ledgerDevice, ledgerIndices); err != nil { + return nil, err + } + kc, err := keychain.NewLedgerKeychainFromIndices(ledgerDevice, ledgerIndices) + if err != nil { + return nil, err + } + return NewKeychain(network, kc, ledgerDevice, ledgerIndices), nil + } + if useEwoq { + sf, err := app.GetKey("ewoq", network, false) + if err != nil { + return nil, err + } + kc := sf.KeyChain() + return NewKeychain(network, kc, nil, nil), nil + } + sf, err := app.GetKey(keyName, network, false) + if err != nil { + return nil, err + } + kc := sf.KeyChain() + return NewKeychain(network, kc, nil, nil), nil } diff --git a/ledger/ledger.go b/ledger/ledger.go new file mode 100644 index 0000000..1502c9f --- /dev/null +++ b/ledger/ledger.go @@ -0,0 +1,108 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package ledger + +import ( + "avalanche-tooling-sdk-go/avalanche" + "avalanche-tooling-sdk-go/utils" + "fmt" + + "github.com/ava-labs/avalanchego/utils/crypto/keychain" + "github.com/ava-labs/avalanchego/utils/crypto/ledger" + "github.com/ava-labs/avalanchego/utils/formatting/address" + "github.com/ava-labs/avalanchego/vms/platformvm" +) + +const ( + maxIndexToSearch = 1000 + maxIndexToSearchForBalance = 100 +) + +type LedgerDevice struct { + keychain.Ledger +} + +func New() (*LedgerDevice, error) { + avagoDev, err := ledger.New() + if err != nil { + return nil, err + } + dev := LedgerDevice{ + Ledger: avagoDev, + } + return &dev, nil +} + +func (dev *LedgerDevice) P(network avalanche.Network, indices []uint32) ([]string, error) { + addresses, err := dev.Addresses(indices) + if err != nil { + return nil, err + } + return utils.P(network.HRP(), addresses) +} + +func (dev *LedgerDevice) FindAddresses(addresses []string, maxIndex uint32) (map[string]uint32, error) { + addressesIDs, err := address.ParseToIDs(addresses) + if err != nil { + return nil, fmt.Errorf("failure parsing ledger addresses: %w", err) + } + // for all ledger indices to search for, find if the ledger address belongs to the input + // addresses and, if so, add an index association to indexMap. + // breaks the loop if all addresses were found + if maxIndex == 0 { + maxIndex = maxIndexToSearch + } + indices := map[string]uint32{} + for index := uint32(0); index < maxIndex; index++ { + ledgerAddress, err := dev.Addresses([]uint32{index}) + if err != nil { + return nil, err + } + for addressIndex, addr := range addressesIDs { + if addr == ledgerAddress[0] { + indices[addresses[addressIndex]] = index + } + } + if len(indices) == len(addresses) { + break + } + } + return indices, nil +} + +// search for a set of indices that pay a given amount +func (dev *LedgerDevice) FindFunds( + network avalanche.Network, + amount uint64, + maxIndex uint32, +) ([]uint32, error) { + pClient := platformvm.NewClient(network.Endpoint) + totalBalance := uint64(0) + indices := []uint32{} + if maxIndex == 0 { + maxIndex = maxIndexToSearchForBalance + } + for index := uint32(0); index < maxIndex; index++ { + ledgerAddress, err := dev.Addresses([]uint32{index}) + if err != nil { + return []uint32{}, err + } + ctx, cancel := utils.GetAPIContext() + resp, err := pClient.GetBalance(ctx, ledgerAddress) + cancel() + if err != nil { + return nil, err + } + if resp.Balance > 0 { + totalBalance += uint64(resp.Balance) + indices = append(indices, index) + } + if totalBalance >= amount { + break + } + } + if totalBalance < amount { + return nil, fmt.Errorf("not enough funds on ledger") + } + return indices, nil +} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..a1bde4d --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,62 @@ +// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package utils + +import ( + "context" + "sort" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/formatting/address" +) + +const ( + APIRequestTimeout = 30 * time.Second +) + +func MapE[T, U any](input []T, f func(T) (U, error)) ([]U, error) { + output := make([]U, 0, len(input)) + for _, e := range input { + o, err := f(e) + if err != nil { + return nil, err + } + output = append(output, o) + } + return output, nil +} + +// Unique returns a new slice containing only the unique elements from the input slice. +func Unique[T comparable](arr []T) []T { + visited := map[T]bool{} + unique := []T{} + for _, e := range arr { + if !visited[e] { + unique = append(unique, e) + visited[e] = true + } + } + return unique +} + +func Uint32Sort(arr []uint32) { + sort.Slice(arr, func(i, j int) bool { return arr[i] < arr[j] }) +} + +// Context for API requests +func GetAPIContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), APIRequestTimeout) +} + +func P( + networkHRP string, + addresses []ids.ShortID, +) ([]string, error) { + return MapE( + addresses, + func(addr ids.ShortID) (string, error) { + return address.Format("P", networkHRP, addr[:]) + }, + ) +} From b3d15dbaebe75fda9e9c5f4151262e8a5bf453a0 Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Tue, 28 May 2024 14:02:11 -0400 Subject: [PATCH 3/3] add ledger key --- keychain/keychain.go | 66 +++++++++++++++++----------------- multisig/multisig.go | 4 +++ subnet/add_validator_subnet.go | 2 +- subnet/join_subnet.go | 2 +- subnet/subnet.go | 3 +- utils/utils.go | 13 ++++++- 6 files changed, 53 insertions(+), 37 deletions(-) diff --git a/keychain/keychain.go b/keychain/keychain.go index 907b8a0..3cd761c 100644 --- a/keychain/keychain.go +++ b/keychain/keychain.go @@ -5,12 +5,13 @@ package keychain import ( "fmt" - "github.com/ava-labs/avalanchego/utils/crypto/keychain" - "avalanche-tooling-sdk-go/avalanche" + "avalanche-tooling-sdk-go/key" "avalanche-tooling-sdk-go/ledger" "avalanche-tooling-sdk-go/utils" + "github.com/ava-labs/avalanchego/utils/crypto/keychain" + "golang.org/x/exp/maps" ) @@ -65,61 +66,60 @@ func (kc *Keychain) AddLedgerFunds(amount uint64) error { return fmt.Errorf("keychain is not ledger enabled") } -func GetKeychain( - app *application.Avalanche, +func NewKeychain( + network avalanche.Network, + keyPath string, useEwoq bool, useLedger bool, ledgerAddresses []string, - network models.Network, requiredFunds uint64, ) (*Keychain, error) { // get keychain accessor if useLedger { - ledgerDevice, err := ledger.New() + dev, err := ledger.New() if err != nil { return nil, err } - // always have index 0, for change - ledgerIndices := []uint32{0} + kc := Keychain{ + ledgerDevice: dev, + network: network, + } + if err := kc.AddLedgerIndices([]uint32{0}); err != nil { + return nil, err + } if requiredFunds > 0 { - ledgerIndicesAux, err := searchForFundedLedgerIndices(network, ledgerDevice, requiredFunds) - if err != nil { + if err := kc.AddLedgerFunds(requiredFunds); err != nil { return nil, err } - ledgerIndices = append(ledgerIndices, ledgerIndicesAux...) } if len(ledgerAddresses) > 0 { - ledgerIndicesAux, err := getLedgerIndices(ledgerDevice, ledgerAddresses) - if err != nil { + if err := kc.AddLedgerAddresses(ledgerAddresses); err != nil { return nil, err } - ledgerIndices = append(ledgerIndices, ledgerIndicesAux...) } - ledgerIndicesSet := set.Set[uint32]{} - ledgerIndicesSet.Add(ledgerIndices...) - ledgerIndices = ledgerIndicesSet.List() - utils.SortUint32(ledgerIndices) - if err := showLedgerAddresses(network, ledgerDevice, ledgerIndices); err != nil { - return nil, err - } - kc, err := keychain.NewLedgerKeychainFromIndices(ledgerDevice, ledgerIndices) + return &kc, nil + } + if useEwoq { + sf, err := key.LoadEwoq() if err != nil { return nil, err } - return NewKeychain(network, kc, ledgerDevice, ledgerIndices), nil + kc := Keychain{ + Keychain: sf.KeyChain(), + network: network, + } + return &kc, nil } - if useEwoq { - sf, err := app.GetKey("ewoq", network, false) + if keyPath != "" { + sf, err := key.LoadSoft(keyPath) if err != nil { return nil, err } - kc := sf.KeyChain() - return NewKeychain(network, kc, nil, nil), nil - } - sf, err := app.GetKey(keyName, network, false) - if err != nil { - return nil, err + kc := Keychain{ + Keychain: sf.KeyChain(), + network: network, + } + return &kc, nil } - kc := sf.KeyChain() - return NewKeychain(network, kc, nil, nil), nil + return nil, fmt.Errorf("not keychain option defined") } diff --git a/multisig/multisig.go b/multisig/multisig.go index b5d8daf..259d85b 100644 --- a/multisig/multisig.go +++ b/multisig/multisig.go @@ -23,6 +23,10 @@ func New(_ *txs.Tx) *Multisig { return nil } +func (*Multisig) String() string { + return "" +} + func (*Multisig) ToBytes() ([]byte, error) { return nil, nil } diff --git a/subnet/add_validator_subnet.go b/subnet/add_validator_subnet.go index de96c71..9ec953b 100644 --- a/subnet/add_validator_subnet.go +++ b/subnet/add_validator_subnet.go @@ -4,6 +4,6 @@ package subnet // AddValidator adds validator to subnet -func AddValidator(subnet Subnet) error { +func AddValidator(_ Subnet) error { return nil } diff --git a/subnet/join_subnet.go b/subnet/join_subnet.go index 1dbb839..e493d6b 100644 --- a/subnet/join_subnet.go +++ b/subnet/join_subnet.go @@ -4,6 +4,6 @@ package subnet // JoinSubnet configures validator node to begin validating a new Subnet -func JoinSubnet(subnet Subnet) error { +func JoinSubnet(_ Subnet) error { return nil } diff --git a/subnet/subnet.go b/subnet/subnet.go index af08336..8bec4e5 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -5,6 +5,7 @@ package subnet import ( "avalanche-tooling-sdk-go/avalanche" + "github.com/ava-labs/avalanchego/ids" ) @@ -118,7 +119,7 @@ type Subnet struct { Logger avalanche.LeveledLoggerInterface } -func New(client *avalanche.BaseApp, subnetParams *SubnetParams) *Subnet { +func New(client *avalanche.BaseApp, _ *SubnetParams) *Subnet { subnet := Subnet{ Logger: client.Logger, } diff --git a/utils/utils.go b/utils/utils.go index a1bde4d..6527c4f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,6 +4,7 @@ package utils import ( "context" + "os" "sort" "time" @@ -12,7 +13,8 @@ import ( ) const ( - APIRequestTimeout = 30 * time.Second + APIRequestTimeout = 30 * time.Second + WriteReadUserOnlyPerms = 0o600 ) func MapE[T, U any](input []T, f func(T) (U, error)) ([]U, error) { @@ -60,3 +62,12 @@ func P( }, ) } + +// FileExists checks if a file exists. +func FileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +}