package wallet import ( "context" "go.uber.org/fx" "go.uber.org/multierr" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" ledgerwallet "github.com/filecoin-project/lotus/chain/wallet/ledger" "github.com/filecoin-project/lotus/chain/wallet/remotewallet" ) type MultiWallet struct { fx.In // "constructed" with fx.In instead of normal constructor Local *LocalWallet `optional:"true"` Remote *remotewallet.RemoteWallet `optional:"true"` Ledger *ledgerwallet.LedgerWallet `optional:"true"` } type getif interface { api.Wallet // workaround for the fact that iface(*struct(nil)) != nil Get() api.Wallet } func firstNonNil(wallets ...getif) api.Wallet { for _, w := range wallets { if w.Get() != nil { return w } } return nil } func nonNil(wallets ...getif) []api.Wallet { var out []api.Wallet for _, w := range wallets { if w.Get() == nil { continue } out = append(out, w) } return out } func (m MultiWallet) find(ctx context.Context, address address.Address, wallets ...getif) (api.Wallet, error) { ws := nonNil(wallets...) var merr error for _, w := range ws { have, err := w.WalletHas(ctx, address) merr = multierr.Append(merr, err) if err == nil && have { return w, nil } } return nil, merr } func (m MultiWallet) WalletNew(ctx context.Context, keyType types.KeyType) (address.Address, error) { var local getif = m.Local if keyType == types.KTSecp256k1Ledger { local = m.Ledger } w := firstNonNil(m.Remote, local) if w == nil { return address.Undef, xerrors.Errorf("no wallet backends supporting key type: %s", keyType) } return w.WalletNew(ctx, keyType) } func (m MultiWallet) WalletHas(ctx context.Context, address address.Address) (bool, error) { w, err := m.find(ctx, address, m.Remote, m.Ledger, m.Local) return w != nil, err } func (m MultiWallet) WalletList(ctx context.Context) ([]address.Address, error) { out := make([]address.Address, 0) seen := map[address.Address]struct{}{} ws := nonNil(m.Remote, m.Ledger, m.Local) for _, w := range ws { l, err := w.WalletList(ctx) if err != nil { return nil, err } for _, a := range l { if _, ok := seen[a]; ok { continue } seen[a] = struct{}{} out = append(out, a) } } return out, nil } func (m MultiWallet) WalletSign(ctx context.Context, signer address.Address, toSign []byte, meta api.MsgMeta) (*crypto.Signature, error) { w, err := m.find(ctx, signer, m.Remote, m.Ledger, m.Local) if err != nil { return nil, err } if w == nil { return nil, xerrors.Errorf("key not found for %s", signer) } return w.WalletSign(ctx, signer, toSign, meta) } func (m MultiWallet) WalletExport(ctx context.Context, addr address.Address) (*types.KeyInfo, error) { w, err := m.find(ctx, addr, m.Remote, m.Local) if err != nil { return nil, err } if w == nil { return nil, xerrors.Errorf("key not found for %s", addr) } return w.WalletExport(ctx, addr) } func (m MultiWallet) WalletImport(ctx context.Context, info *types.KeyInfo) (address.Address, error) { var local getif = m.Local if info.Type == types.KTSecp256k1Ledger { local = m.Ledger } w := firstNonNil(m.Remote, local) if w == nil { return address.Undef, xerrors.Errorf("no wallet backends configured") } return w.WalletImport(ctx, info) } func (m MultiWallet) WalletDelete(ctx context.Context, address address.Address) error { for { w, err := m.find(ctx, address, m.Remote, m.Ledger, m.Local) if err != nil { return err } if w == nil { return nil } if err := w.WalletDelete(ctx, address); err != nil { return err } } } var _ api.Wallet = MultiWallet{}