Merge pull request #73 from lbryio/feature/wallet
Add wallet commands to JSON-RPC client
This commit is contained in:
commit
9c278d131d
5 changed files with 245 additions and 30 deletions
|
@ -137,11 +137,17 @@ func (d *Client) SetRPCTimeout(timeout time.Duration) {
|
|||
//============================================
|
||||
// NEW SDK
|
||||
//============================================
|
||||
|
||||
func (d *Client) AccountList() (*AccountListResponse, error) {
|
||||
response := new(AccountListResponse)
|
||||
return response, d.call(response, "account_list", map[string]interface{}{})
|
||||
}
|
||||
|
||||
func (d *Client) AccountListForWallet(walletID string) (*AccountListResponse, error) {
|
||||
response := new(AccountListResponse)
|
||||
return response, d.call(response, "account_list", map[string]interface{}{"wallet_id": walletID})
|
||||
}
|
||||
|
||||
func (d *Client) SingleAccountList(accountID string) (*Account, error) {
|
||||
response := new(Account)
|
||||
return response, d.call(response, "account_list", map[string]interface{}{"account_id": accountID})
|
||||
|
@ -211,7 +217,7 @@ func (d *Client) AddressUnused(account *string) (*AddressUnusedResponse, error)
|
|||
})
|
||||
}
|
||||
|
||||
func (d *Client) ChannelList(account *string, page uint64, pageSize uint64) (*ChannelListResponse, error) {
|
||||
func (d *Client) ChannelList(account *string, page uint64, pageSize uint64, wid *string) (*ChannelListResponse, error) {
|
||||
if page == 0 {
|
||||
return nil, errors.Err("pages start from 1")
|
||||
}
|
||||
|
@ -221,6 +227,7 @@ func (d *Client) ChannelList(account *string, page uint64, pageSize uint64) (*Ch
|
|||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"include_protobuf": true,
|
||||
"wallet_id": wid,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -521,6 +528,14 @@ func (d *Client) ChannelExport(channelClaimID string, channelName, accountID *st
|
|||
})
|
||||
}
|
||||
|
||||
func (d *Client) ChannelImport(key string, walletID *string) (*ChannelImportResponse, error) {
|
||||
response := new(ChannelImportResponse)
|
||||
return response, d.call(response, "channel_import", map[string]interface{}{
|
||||
"channel_data": key,
|
||||
"wallet_id": walletID,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Client) SupportList(accountID *string, page uint64, pageSize uint64) (*SupportListResponse, error) {
|
||||
response := new(SupportListResponse)
|
||||
return response, d.call(response, "support_list", map[string]interface{}{
|
||||
|
@ -598,3 +613,39 @@ func (d *Client) AccountAdd(accountName string, seed *string, privateKey *string
|
|||
structs.DefaultTagName = "json"
|
||||
return response, d.call(response, "account_add", structs.Map(args))
|
||||
}
|
||||
|
||||
type WalletCreateOpts struct {
|
||||
ID string `json:"wallet_id"`
|
||||
SkipOnStartup bool `json:"skip_on_startup,omitempty"`
|
||||
CreateAccount bool `json:"create_account,omitempty"`
|
||||
SingleKey bool `json:"single_key,omitempty"`
|
||||
}
|
||||
|
||||
func (d *Client) WalletCreate(id string, opts *WalletCreateOpts) (*Wallet, error) {
|
||||
response := new(Wallet)
|
||||
if opts == nil {
|
||||
opts = &WalletCreateOpts{}
|
||||
}
|
||||
opts.ID = id
|
||||
structs.DefaultTagName = "json"
|
||||
return response, d.call(response, "wallet_create", structs.Map(opts))
|
||||
}
|
||||
|
||||
func (d *Client) WalletAdd(id string) (*Wallet, error) {
|
||||
response := new(Wallet)
|
||||
return response, d.call(response, "wallet_add", map[string]interface{}{"wallet_id": id})
|
||||
}
|
||||
|
||||
func (d *Client) WalletList(id string) (*WalletList, error) {
|
||||
response := new(WalletList)
|
||||
params := map[string]interface{}{}
|
||||
if id != "" {
|
||||
params["wallet_id"] = id
|
||||
}
|
||||
return response, d.call(response, "wallet_list", params)
|
||||
}
|
||||
|
||||
func (d *Client) WalletRemove(id string) (*Wallet, error) {
|
||||
response := new(Wallet)
|
||||
return response, d.call(response, "wallet_remove", map[string]interface{}{"wallet_id": id})
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package jsonrpc
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -21,6 +22,12 @@ func prettyPrint(i interface{}) {
|
|||
fmt.Println(string(s))
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestClient_AccountFund(t *testing.T) {
|
||||
d := NewClient("")
|
||||
accounts, err := d.AccountList()
|
||||
|
@ -59,17 +66,20 @@ func TestClient_AccountList(t *testing.T) {
|
|||
|
||||
func TestClient_SingleAccountList(t *testing.T) {
|
||||
d := NewClient("")
|
||||
createdAccount, err := d.AccountCreate("test"+fmt.Sprintf("%d", time.Now().Unix())+"@lbry.com", false)
|
||||
name := "test" + fmt.Sprintf("%d", rand.Int()) + "@lbry.com"
|
||||
createdAccount, err := d.AccountCreate(name, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
account, err := d.SingleAccountList(createdAccount.ID)
|
||||
prettyPrint(*createdAccount)
|
||||
prettyPrint(*account)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
prettyPrint(*account)
|
||||
if account.Name != name {
|
||||
t.Fatalf("account name mismatch: %v != %v", account.Name, name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_AccountBalance(t *testing.T) {
|
||||
|
@ -94,7 +104,7 @@ func TestClient_AddressUnused(t *testing.T) {
|
|||
|
||||
func TestClient_ChannelList(t *testing.T) {
|
||||
d := NewClient("")
|
||||
got, err := d.ChannelList(nil, 1, 50)
|
||||
got, err := d.ChannelList(nil, 1, 50, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -433,7 +443,7 @@ func TestClient_AccountSet(t *testing.T) {
|
|||
|
||||
func TestClient_AccountCreate(t *testing.T) {
|
||||
d := NewClient("")
|
||||
name := "test" + fmt.Sprintf("%d", time.Now().Unix()) + "@lbry.com"
|
||||
name := "lbry#user#id:" + fmt.Sprintf("%d", rand.Int())
|
||||
account, err := d.AccountCreate(name, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -468,7 +478,8 @@ func TestClient_AccountAdd(t *testing.T) {
|
|||
|
||||
func TestClient_AccountRemove(t *testing.T) {
|
||||
d := NewClient("")
|
||||
createdAccount, err := d.AccountCreate("test"+fmt.Sprintf("%d", time.Now().Unix())+"@lbry.com", false)
|
||||
name := "lbry#user#id:" + fmt.Sprintf("%d", rand.Int())
|
||||
createdAccount, err := d.AccountCreate(name, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
|
@ -488,9 +499,7 @@ func TestClient_AccountRemove(t *testing.T) {
|
|||
prettyPrint(*removedAccount)
|
||||
return
|
||||
}
|
||||
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Error("account was not removed")
|
||||
prettyPrint(*account)
|
||||
|
@ -507,3 +516,160 @@ func TestClient_ChannelExport(t *testing.T) {
|
|||
}
|
||||
t.Log("Export:", *response)
|
||||
}
|
||||
|
||||
func TestClient_ChannelImport(t *testing.T) {
|
||||
d := NewClient("")
|
||||
|
||||
// A channel created just for automated testing purposes
|
||||
channelName := "@LbryAutomatedTestChannel"
|
||||
channelkey := "7943FWPBHZES4dUcMXSpDYwoM5a2tsyJT1R8V54QoUhekGcqmeH3hbzDXoLLQ8" +
|
||||
"oKkfb99PgGK5efrZeYqaxg4X5XRJMJ6gKC8hqKcnwhYkmKDXmoBDNgd2ccZ9jhP8z" +
|
||||
"HG3NJorAN9Hh4XMyBc5goBLZYYvC9MYvBmT3Fcteb5saqMvmQxFURv74NqXLQZC1t" +
|
||||
"p6iRZKfTj77Pd5gsBsCYAbVmCqzbm5m1hHkUmfFEZVGcQNTYCDwZn543xSMYvSPnJ" +
|
||||
"zt8tRYCJWaPdj713uENZZMo3gxuAMb1NwSnx8tbwETp7WPkpFLL6HZ9jKpB8BURHM" +
|
||||
"F1RFD1PRyqbC6YezPyPQ2oninKKHdBduvXZG5KF2G2Q3ixsuE2ntifBBo1f5PotRk" +
|
||||
"UanXKEafWxvXAayJjpsmZ4bFt7n6Xg4438WZXBiZKCPobLJAiHfe72n618kE6PCNU" +
|
||||
"77cyU5Rk8J3CuY6QzZPzwuiXz2GLfkUMCYd9jGT6g53XbE6SwCsmGnd9NJkBAaJf5" +
|
||||
"1FAYRURrhHnp79PAoHftEWtZEuU8MCPMdSRjzxYMRS4ScUzg5viDMTAkE8frsfCVZ" +
|
||||
"hxsFwGUyNNno8eiqrrYmpbJGEwwK3S4437JboAUEFPdMNn8zNQWZcLLVrK9KyQeKM" +
|
||||
"XpKkf4zJV6sZJ7gBMpzvPL18ULEgXTy7VsNBKmsfC1rM4WVG9ri1UixEcLDS79foC" +
|
||||
"Jb3FnSr1T4MRKESeN3W"
|
||||
response, err := d.ChannelImport(channelkey, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
channels, err := d.ChannelList(nil, 1, 50, nil)
|
||||
seen := false
|
||||
for _, c := range channels.Items {
|
||||
if c.Name == channelName {
|
||||
seen = true
|
||||
}
|
||||
}
|
||||
if !seen {
|
||||
t.Error("couldn't find imported channel")
|
||||
}
|
||||
t.Log("Response:", *response)
|
||||
}
|
||||
|
||||
func TestClient_ChannelImportWithWalletID(t *testing.T) {
|
||||
d := NewClient("")
|
||||
|
||||
id := "lbry#wallet#id:" + fmt.Sprintf("%d", rand.Int())
|
||||
wallet, err := d.WalletCreate(id, nil)
|
||||
|
||||
// A channel created just for automated testing purposes
|
||||
channelName := "@LbryAutomatedTestChannel"
|
||||
channelkey := "7943FWPBHZES4dUcMXSpDYwoM5a2tsyJT1R8V54QoUhekGcqmeH3hbzDXoLLQ8" +
|
||||
"oKkfb99PgGK5efrZeYqaxg4X5XRJMJ6gKC8hqKcnwhYkmKDXmoBDNgd2ccZ9jhP8z" +
|
||||
"HG3NJorAN9Hh4XMyBc5goBLZYYvC9MYvBmT3Fcteb5saqMvmQxFURv74NqXLQZC1t" +
|
||||
"p6iRZKfTj77Pd5gsBsCYAbVmCqzbm5m1hHkUmfFEZVGcQNTYCDwZn543xSMYvSPnJ" +
|
||||
"zt8tRYCJWaPdj713uENZZMo3gxuAMb1NwSnx8tbwETp7WPkpFLL6HZ9jKpB8BURHM" +
|
||||
"F1RFD1PRyqbC6YezPyPQ2oninKKHdBduvXZG5KF2G2Q3ixsuE2ntifBBo1f5PotRk" +
|
||||
"UanXKEafWxvXAayJjpsmZ4bFt7n6Xg4438WZXBiZKCPobLJAiHfe72n618kE6PCNU" +
|
||||
"77cyU5Rk8J3CuY6QzZPzwuiXz2GLfkUMCYd9jGT6g53XbE6SwCsmGnd9NJkBAaJf5" +
|
||||
"1FAYRURrhHnp79PAoHftEWtZEuU8MCPMdSRjzxYMRS4ScUzg5viDMTAkE8frsfCVZ" +
|
||||
"hxsFwGUyNNno8eiqrrYmpbJGEwwK3S4437JboAUEFPdMNn8zNQWZcLLVrK9KyQeKM" +
|
||||
"XpKkf4zJV6sZJ7gBMpzvPL18ULEgXTy7VsNBKmsfC1rM4WVG9ri1UixEcLDS79foC" +
|
||||
"Jb3FnSr1T4MRKESeN3W"
|
||||
response, err := d.ChannelImport(channelkey, &wallet.ID)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
channels, err := d.ChannelList(nil, 1, 50, &wallet.ID)
|
||||
seen := false
|
||||
for _, c := range channels.Items {
|
||||
if c.Name == channelName {
|
||||
seen = true
|
||||
}
|
||||
}
|
||||
if !seen {
|
||||
t.Error("couldn't find imported channel")
|
||||
}
|
||||
t.Log("Response:", *response)
|
||||
}
|
||||
|
||||
func TestClient_WalletCreate(t *testing.T) {
|
||||
d := NewClient("")
|
||||
|
||||
id := "lbry#wallet#id:" + fmt.Sprintf("%d", rand.Int())
|
||||
wallet, err := d.WalletCreate(id, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if wallet.ID != id {
|
||||
prettyPrint(*wallet)
|
||||
t.Fatalf("wallet ID mismatch, expected %q, got %q", id, wallet.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_WalletCreateWithOpts(t *testing.T) {
|
||||
d := NewClient("")
|
||||
|
||||
id := "lbry#wallet#id:" + fmt.Sprintf("%d", rand.Int())
|
||||
wallet, err := d.WalletCreate(id, &WalletCreateOpts{CreateAccount: true, SingleKey: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
accounts, err := d.AccountListForWallet(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prettyPrint(wallet)
|
||||
prettyPrint(accounts)
|
||||
if accounts.LBCMainnet[0].Name == "" {
|
||||
t.Fatalf("account name is empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_WalletList(t *testing.T) {
|
||||
d := NewClient("")
|
||||
|
||||
id := "lbry#wallet#id:" + fmt.Sprintf("%d", rand.Int())
|
||||
wList, err := d.WalletList(id)
|
||||
if err == nil {
|
||||
t.Fatalf("wallet %v was unexpectedly found", id)
|
||||
}
|
||||
if err.Error() != fmt.Sprintf("Error in daemon: Couldn't find wallet: %v.", id) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = d.WalletCreate(id, &WalletCreateOpts{CreateAccount: true, SingleKey: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wList, err = d.WalletList(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(*wList) < 1 {
|
||||
t.Fatal("wallet list is empty")
|
||||
}
|
||||
if (*wList)[0].ID != id {
|
||||
t.Fatalf("wallet ID mismatch, expected %q, got %q", id, (*wList)[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_WalletRemoveWalletAdd(t *testing.T) {
|
||||
d := NewClient("")
|
||||
|
||||
id := "lbry#wallet#id:" + fmt.Sprintf("%d", rand.Int())
|
||||
wallet, err := d.WalletCreate(id, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = d.WalletRemove(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
addedWallet, err := d.WalletAdd(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if addedWallet.ID != wallet.ID {
|
||||
prettyPrint(*addedWallet)
|
||||
t.Fatalf("wallet ID mismatch, expected %q, got %q", wallet.ID, addedWallet.Name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -271,7 +271,9 @@ type AddressListResponse []struct {
|
|||
Pubkey string `json:"pubkey"`
|
||||
UsedTimes uint64 `json:"used_times"`
|
||||
}
|
||||
|
||||
type ChannelExportResponse string
|
||||
type ChannelImportResponse string
|
||||
|
||||
type ChannelListResponse struct {
|
||||
Items []Transaction `json:"items"`
|
||||
|
@ -538,3 +540,10 @@ type NumClaimsInChannelResponse map[string]struct {
|
|||
}
|
||||
|
||||
type ClaimShowResponse *Claim
|
||||
|
||||
type Wallet struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type WalletList []Wallet
|
||||
|
|
23
go.mod
23
go.mod
|
@ -1,50 +1,35 @@
|
|||
module github.com/lbryio/lbry.go
|
||||
module github.com/lbryio/lbry.go/v2
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf // indirect
|
||||
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32
|
||||
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/go-ini/ini v1.38.2
|
||||
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible // indirect
|
||||
github.com/golang/protobuf v1.3.0
|
||||
github.com/google/go-cmp v0.2.0 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/gorilla/rpc v1.1.0
|
||||
github.com/gorilla/websocket v1.2.0 // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/lbryio/lbry.go v1.1.2
|
||||
github.com/lbryio/lbryschema.go v0.0.0-20190602173230-6d2f69a36f46
|
||||
github.com/lbryio/ozzo-validation v0.0.0-20170323141101-d1008ad1fd04
|
||||
github.com/lbryio/types v0.0.0-20190422033210-321fb2abda9c
|
||||
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
|
||||
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect
|
||||
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675
|
||||
github.com/nlopes/slack v0.5.0
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/sebdah/goldie v0.0.0-20180424091453-8784dd1ab561
|
||||
github.com/sergi/go-diff v1.0.0
|
||||
github.com/shopspring/decimal v0.0.0-20180607144847-19e3cb6c2930
|
||||
github.com/sirupsen/logrus v1.2.0
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||
github.com/spf13/cast v1.2.0
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/uber-go/atomic v1.3.2
|
||||
github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d
|
||||
go.uber.org/atomic v1.3.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||
golang.org/x/sys v0.0.0-20190520201301-c432e742b0af // indirect
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c
|
||||
google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f // indirect
|
||||
google.golang.org/grpc v1.17.0
|
||||
gopkg.in/ini.v1 v1.41.0 // indirect
|
||||
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
)
|
||||
|
||||
go 1.12
|
||||
|
|
4
go.sum
4
go.sum
|
@ -66,6 +66,10 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfM
|
|||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c/go.mod h1:muH7wpUqE8hRA3OrYYosw9+Sl681BF9cwcjzE+OCNK8=
|
||||
github.com/lbryio/lbry.go v1.1.2 h1:Dyxc+glT/rVWJwHfIf7vjlPYYbjzrQz5ARmJd5Hp69c=
|
||||
github.com/lbryio/lbry.go v1.1.2/go.mod h1:JtyI30bU51rm0LZ/po3mQuzf++14OWb6kR/6mMRAmKU=
|
||||
github.com/lbryio/lbryschema.go v0.0.0-20190428231007-c54836bca002/go.mod h1:dAzPCBj3CKKWBGYBZxK6tKBP5SCgY2tqd9SnQd/OyKo=
|
||||
github.com/lbryio/lbryschema.go v0.0.0-20190602173230-6d2f69a36f46 h1:LemfR+rMxhf7nnOrzy2HqS7Me7SZ5gEwOcNFzKC8ySQ=
|
||||
github.com/lbryio/lbryschema.go v0.0.0-20190602173230-6d2f69a36f46/go.mod h1:dAzPCBj3CKKWBGYBZxK6tKBP5SCgY2tqd9SnQd/OyKo=
|
||||
github.com/lbryio/ozzo-validation v0.0.0-20170323141101-d1008ad1fd04 h1:Nze+C2HbeKvhjI/kVn+9Poj/UuEW5sOQxcsxqO7L3GI=
|
||||
|
|
Loading…
Reference in a new issue