diff --git a/cardano-cli/cli.go b/cardano-cli/cli.go index da0e1cd..9110ab7 100644 --- a/cardano-cli/cli.go +++ b/cardano-cli/cli.go @@ -1,7 +1,7 @@ package cardanocli import ( - "bytes" + "context" "encoding/hex" "encoding/json" "errors" @@ -12,11 +12,44 @@ import ( "strconv" "strings" + flag "github.com/spf13/pflag" + "github.com/echovl/cardano-go" ) +var socketPath, fallbackSocketPath string + +func AddFlags(fs *flag.FlagSet) { + fs.StringVar(&socketPath, "cardano-node-socket-path", "", "") + fs.StringVar(&fallbackSocketPath, "fallback-cardano-node-socket-path", "", "") +} + +func availableAsSocket(fn string) bool { + fi, err := os.Stat(fn) + if err != nil { + return false + } + if (fi.Mode().Type() & os.ModeSocket) != os.ModeSocket { + return false + } + return true +} + +func getSocketPathToUse() string { + sp := socketPath + if sp != "" && availableAsSocket(sp) { + return sp + } + sp = fallbackSocketPath + if sp != "" && availableAsSocket(sp) { + return sp + } + return os.Getenv("CARDANO_NODE_SOCKET_PATH") +} + // CardanoCli implements Node using cardano-cli and a local node. type CardanoCli struct { + ctx context.Context network cardano.Network } @@ -35,12 +68,15 @@ type cliTx struct { } // NewNode returns a new instance of CardanoCli. -func NewNode(network cardano.Network) cardano.Node { +func NewNode(network cardano.Network, rest ...any) cardano.Node { + if len(rest) > 0 { + return &CardanoCli{network: network, ctx: rest[0].(context.Context)} + } return &CardanoCli{network: network} } -func (c *CardanoCli) runCommand(args ...string) ([]byte, error) { - out := &bytes.Buffer{} +func (c *CardanoCli) runCommand(args ...string) (string, error) { + out := &strings.Builder{} if c.network == cardano.Mainnet { args = append(args, "--mainnet") @@ -48,14 +84,23 @@ func (c *CardanoCli) runCommand(args ...string) ([]byte, error) { args = append(args, "--testnet-magic", strconv.Itoa(cardano.ProtocolMagic)) } - cmd := exec.Command("cardano-cli", args...) + var cmd *exec.Cmd + if c.ctx == nil { + cmd = exec.Command("cardano-cli", args...) + } else { + cmd = exec.CommandContext(c.ctx, "cardano-cli", args...) + } cmd.Stdout = out cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { - return nil, err + return "", err } - return out.Bytes(), nil + return out.String(), nil +} + +func (c *CardanoCli) DoCommand(args ...string) (string, error) { + return c.runCommand(args...) } func (c *CardanoCli) UTxOs(addr cardano.Address) ([]cardano.UTxO, error) { @@ -65,7 +110,7 @@ func (c *CardanoCli) UTxOs(addr cardano.Address) ([]cardano.UTxO, error) { } utxos := []cardano.UTxO{} - lines := strings.Split(string(out), "\n") + lines := strings.Split(out, "\n") if len(lines) < 3 { return utxos, nil @@ -142,7 +187,7 @@ func (c *CardanoCli) Tip() (*cardano.NodeTip, error) { } cliTip := &tip{} - if err = json.Unmarshal(out, cliTip); err != nil { + if err = json.Unmarshal([]byte(out), cliTip); err != nil { return nil, err } @@ -171,7 +216,7 @@ func (c *CardanoCli) SubmitTx(tx *cardano.Tx) (*cardano.Hash32, error) { out, err := c.runCommand("transaction", "submit", "--tx-file", txFile.Name()) if err != nil { - return nil, errors.New(string(out)) + return nil, errors.New(out) } txHash, err := tx.Hash() @@ -191,11 +236,11 @@ type protocolParameters struct { func (c *CardanoCli) ProtocolParams() (*cardano.ProtocolParams, error) { out, err := c.runCommand("query", "protocol-parameters") if err != nil { - return nil, errors.New(string(out)) + return nil, errors.New(out) } var cparams protocolParameters - if err := json.Unmarshal(out, &cparams); err != nil { + if err := json.Unmarshal([]byte(out), &cparams); err != nil { return nil, err } diff --git a/go.mod b/go.mod index 2825464..43c1116 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,12 @@ go 1.18 require ( filippo.io/edwards25519 v1.0.0 github.com/blockfrost/blockfrost-go v0.1.0 + github.com/cardano-community/koios-go-client/v2 v2.0.2 github.com/dgraph-io/badger/v3 v3.2103.2 github.com/echovl/ed25519 v0.2.0 github.com/matoous/go-nanoid/v2 v2.0.0 github.com/spf13/cobra v1.4.0 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 github.com/tyler-smith/go-bip39 v1.1.0 github.com/x448/float16 v0.8.4 @@ -37,17 +39,18 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.3.0 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2ee6f79..6f80dcc 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/blockfrost/blockfrost-go v0.1.0 h1:s9+kk1L2pM+GEZZCZxX7z6+1eNYyyFFX0lUVp6fUgx8= github.com/blockfrost/blockfrost-go v0.1.0/go.mod h1:TYp7iHyuEm87IrTziSUA2+UaAor8a1lGGR499YyfPO4= +github.com/cardano-community/koios-go-client/v2 v2.0.2 h1:/9x8w5KO/xRr8QwWlqMGkYpXi4w2VimcqUW7YBKA9d8= +github.com/cardano-community/koios-go-client/v2 v2.0.2/go.mod h1:l6n5BGaZq8pj6iQ/cPsEi4DDNAea++MZBCi8qIm5/gs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -212,6 +214,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -239,8 +243,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= @@ -408,6 +412,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -564,8 +570,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/koios/client.go b/koios/client.go new file mode 100644 index 0000000..b199f7c --- /dev/null +++ b/koios/client.go @@ -0,0 +1,119 @@ +package koios + +import ( + "context" + + "github.com/cardano-community/koios-go-client/v2" + "github.com/echovl/cardano-go" +) + +type KoiosCli struct { + *koios.Client + ctx context.Context + network cardano.Network +} + +func NewNode(network cardano.Network, ctx context.Context, opts ...koios.Option) cardano.Node { + if ctx == nil { + ctx = context.Background() + } + if network == cardano.Testnet { + opts = append(opts, koios.Host("testnet.koios.rest")) + } + + c, err := koios.New(opts...) + if err != nil { + panic(err) + } + return &KoiosCli{Client: c, ctx: ctx, network: network} +} + +// // utility function to know if continue pagination or not +// // simply check if the "end of content range" is matching the PageSize +// func isResponseComplete(res koios.Response) bool { +// fields := strings.FieldsFunc(res.ContentRange, func(r rune) bool { return r == '-' || r == '/' }) +// if len(fields) != 3 { +// return false +// } +// if endSide_, err := strconv.ParseUint(fields[1], 10, 16); err == nil { +// return (uint16(endSide_) % uint16(koios.PageSize)) < uint16(koios.PageSize-1) +// } +// return false +// } + +func (kc *KoiosCli) UTxOs(a cardano.Address) ([]cardano.UTxO, error) { + utxos := []cardano.UTxO{} + opts := kc.NewRequestOptions() + opts.QuerySet("select", "utxo_set") + res, err := kc.GetAddressInfo(kc.ctx, koios.Address(a.Bech32()), opts) + if err != nil || res.Data == nil { + return nil, err + } + + for _, utxo := range res.Data.UTxOs { + utxo_ := cardano.UTxO{ + TxHash: cardano.Hash32(utxo.TxHash), + Index: uint64(utxo.TxIndex), + Spender: a, + Amount: cardano.NewValue( + cardano.Coin(utxo.Value.IntPart())), + } + if len(utxo.AssetList) > 0 { + ma := utxo_.Amount.MultiAsset + for _, as := range utxo.AssetList { + asset := cardano.NewAssets() + policyId := cardano.NewPolicyIDFromHash( + cardano.Hash28(as.PolicyID.String())) + asset.Set(cardano.NewAssetName(string(as.AssetName)), + cardano.BigNum(as.Quantity.IntPart())) + ma.Set(policyId, asset) + } + } + utxos = append(utxos, utxo_) + } + return utxos, nil +} + +// Tip returns the node's current tip +func (kc *KoiosCli) Tip() (*cardano.NodeTip, error) { + opts := kc.NewRequestOptions() + opts.QuerySet("select", "block_no,epoch_no,abs_slot") + res, err := kc.GetTip(kc.ctx, opts) + if err != nil { + return nil, err + } + return &cardano.NodeTip{ + Epoch: uint64(res.Data.EpochNo), + Block: uint64(res.Data.BlockNo), + Slot: uint64(res.Data.AbsSlot), + }, nil +} + +// SubmitTx submits a transaction to the node using cbor encoding +func (kc *KoiosCli) SubmitTx(*cardano.Tx) (*cardano.Hash32, error) { + return nil, nil +} + +// ProtocolParams returns the Node's Protocol Parameters +func (kc *KoiosCli) ProtocolParams() (*cardano.ProtocolParams, error) { + opts := kc.NewRequestOptions() + opts.QuerySet("limit", "1") + res, err := kc.GetEpochParams(kc.ctx, nil, opts) + if err != nil { + return nil, err + } + ep := res.Data[0] + return &cardano.ProtocolParams{ + MinFeeA: cardano.Coin(ep.MinFeeA.IntPart()), + MinFeeB: cardano.Coin(ep.MinFeeB.IntPart()), + MaxBlockBodySize: uint(ep.MaxBlockSize), + MaxTxSize: uint(ep.MaxTxSize), + // ... + }, nil + +} + +// Network returns the node's current network type +func (kc *KoiosCli) Network() cardano.Network { + return kc.network +}