mirror of
https://github.com/galacticship/terra.git
synced 2024-12-04 13:51:25 +00:00
initial commit
This commit is contained in:
commit
c8099af2c9
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
*.exe
|
||||
go.work
|
||||
.idea/
|
||||
cmd/
|
||||
Dockerfile
|
||||
Dockerfile.*
|
||||
.dockerignore
|
21
LICENCE
Normal file
21
LICENCE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 galacticship
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
88
asset_info.go
Normal file
88
asset_info.go
Normal file
@ -0,0 +1,88 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type AssetInfo interface {
|
||||
IsNative() bool
|
||||
Id() string
|
||||
}
|
||||
|
||||
func GetTokenFromAssetInfo(ctx context.Context, querier *Querier, ai AssetInfo) (Token, error) {
|
||||
var res Token
|
||||
if ai.IsNative() {
|
||||
return NativeTokenFromDenom(ai.Id()), nil
|
||||
}
|
||||
var err error
|
||||
res, err = Cw20TokenFromAddress(ctx, querier, ai.Id())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid token %s", ai.Id())
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type AssetInfoFactory interface {
|
||||
DecodeFromJson(raw json.RawMessage) (AssetInfo, error)
|
||||
NewFromToken(token Token) AssetInfo
|
||||
}
|
||||
|
||||
type assetInfoFactory struct {
|
||||
}
|
||||
|
||||
func (a assetInfoFactory) NewFromToken(token Token) AssetInfo {
|
||||
var res StandardAssetInfo
|
||||
if token.IsNative() {
|
||||
res.NativeToken = &nativeTokenAssetInfo{
|
||||
Denom: token.Id(),
|
||||
}
|
||||
} else {
|
||||
res.Token = &cw20TokenAssetInfo{
|
||||
ContractAddr: token.Id(),
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (a assetInfoFactory) DecodeFromJson(raw json.RawMessage) (AssetInfo, error) {
|
||||
var res StandardAssetInfo
|
||||
err := json.Unmarshal(raw, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NewAssetInfoFactory() AssetInfoFactory {
|
||||
return &assetInfoFactory{}
|
||||
}
|
||||
|
||||
type nativeTokenAssetInfo struct {
|
||||
Denom string `json:"denom"`
|
||||
}
|
||||
|
||||
type cw20TokenAssetInfo struct {
|
||||
ContractAddr string `json:"contract_addr"`
|
||||
}
|
||||
|
||||
type StandardAssetInfo struct {
|
||||
Token *cw20TokenAssetInfo `json:"token,omitempty"`
|
||||
NativeToken *nativeTokenAssetInfo `json:"native_token,omitempty"`
|
||||
}
|
||||
|
||||
func (ai StandardAssetInfo) IsNative() bool {
|
||||
return ai.NativeToken != nil
|
||||
}
|
||||
|
||||
func (ai StandardAssetInfo) Id() string {
|
||||
if ai.IsNative() {
|
||||
return ai.NativeToken.Denom
|
||||
} else if ai.Token != nil {
|
||||
return ai.Token.ContractAddr
|
||||
} else {
|
||||
panic(errors.New("invalid asset info"))
|
||||
}
|
||||
}
|
64
contract.go
Normal file
64
contract.go
Normal file
@ -0,0 +1,64 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/galacticship/terra/terra"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Contract struct {
|
||||
q *Querier
|
||||
contractAddress cosmos.AccAddress
|
||||
}
|
||||
|
||||
func NewContract(querier *Querier, contractAddress string) (*Contract, error) {
|
||||
accAddress, err := cosmos.AccAddressFromBech32(contractAddress)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "validating address")
|
||||
}
|
||||
return &Contract{
|
||||
q: querier,
|
||||
contractAddress: accAddress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Contract) Querier() *Querier {
|
||||
return c.q
|
||||
}
|
||||
|
||||
func (c *Contract) Address() cosmos.AccAddress {
|
||||
return c.contractAddress
|
||||
}
|
||||
|
||||
func (c *Contract) QueryStore(ctx context.Context, query interface{}, result interface{}) error {
|
||||
var envelope struct {
|
||||
QueryResult json.RawMessage `json:"query_result"`
|
||||
}
|
||||
queryBytes, err := json.Marshal(query)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling query execmsg")
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Set("query_msg", base64.StdEncoding.EncodeToString(queryBytes))
|
||||
err = c.q.GET(ctx, fmt.Sprintf("terra/wasm/v1beta1/contracts/%s/store", c.contractAddress.String()), params, &envelope)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "executing get request")
|
||||
}
|
||||
if result != nil {
|
||||
err = json.Unmarshal(envelope.QueryResult, result)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unmarshalling query result")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Contract) NewMsgExecuteContract(sender cosmos.AccAddress, execMsg interface{}) (*terra.MsgExecuteContract, error) {
|
||||
return terra.NewMsgExecuteContract(sender, c.Address(), execMsg, nil)
|
||||
}
|
18
cosmos/address.go
Normal file
18
cosmos/address.go
Normal file
@ -0,0 +1,18 @@
|
||||
package cosmos
|
||||
|
||||
import sdktypes "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
type (
|
||||
AccAddress = sdktypes.AccAddress
|
||||
ValAddress = sdktypes.ValAddress
|
||||
ConsAddress = sdktypes.ConsAddress
|
||||
)
|
||||
|
||||
var (
|
||||
AccAddressFromBech32 = sdktypes.AccAddressFromBech32
|
||||
AccAddressFromHex = sdktypes.AccAddressFromHex
|
||||
ValAddressFromBech32 = sdktypes.ValAddressFromBech32
|
||||
ValAddressFromHex = sdktypes.ValAddressFromHex
|
||||
ConsAddressFromBech32 = sdktypes.ConsAddressFromBech32
|
||||
ConsAddressFromHex = sdktypes.ConsAddressFromHex
|
||||
)
|
19
cosmos/coin.go
Normal file
19
cosmos/coin.go
Normal file
@ -0,0 +1,19 @@
|
||||
package cosmos
|
||||
|
||||
import sdktypes "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
type (
|
||||
Coin = sdktypes.Coin
|
||||
Coins = sdktypes.Coins
|
||||
DecCoin = sdktypes.DecCoin
|
||||
DecCoins = sdktypes.DecCoins
|
||||
)
|
||||
|
||||
var (
|
||||
NewCoin = sdktypes.NewCoin
|
||||
NewInt64Coin = sdktypes.NewInt64Coin
|
||||
NewCoins = sdktypes.NewCoins
|
||||
NewDecCoin = sdktypes.NewDecCoin
|
||||
NewInt64DecCoin = sdktypes.NewInt64DecCoin
|
||||
NewDecCoins = sdktypes.NewDecCoins
|
||||
)
|
17
cosmos/msg.go
Normal file
17
cosmos/msg.go
Normal file
@ -0,0 +1,17 @@
|
||||
package cosmos
|
||||
|
||||
import (
|
||||
sdktypes "github.com/cosmos/cosmos-sdk/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
)
|
||||
|
||||
type (
|
||||
Msg = sdktypes.Msg
|
||||
Send = banktypes.MsgSend
|
||||
MultiSend = banktypes.MsgMultiSend
|
||||
)
|
||||
|
||||
var (
|
||||
NewMsgSend = banktypes.NewMsgSend
|
||||
NewMsgMultiSend = banktypes.NewMsgMultiSend
|
||||
)
|
48
cosmos/signing.go
Normal file
48
cosmos/signing.go
Normal file
@ -0,0 +1,48 @@
|
||||
package cosmos
|
||||
|
||||
import (
|
||||
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
)
|
||||
|
||||
type (
|
||||
// SignerData is the specific information needed to sign a transaction that generally
|
||||
// isn't included in the transaction body itself
|
||||
SignerData = authsigning.SignerData
|
||||
|
||||
// SignMode represents a signing mode with its own security guarantees.
|
||||
SignMode = signing.SignMode
|
||||
|
||||
// SignatureV2 is a convenience type that is easier to use in application logic
|
||||
// than the protobuf SignerInfo's and raw signature bytes. It goes beyond the
|
||||
// first sdk.Signature types by supporting sign modes and explicitly nested
|
||||
// multi-signatures. It is intended to be used for both building and verifying
|
||||
// signatures.
|
||||
SignatureV2 = signing.SignatureV2
|
||||
SingleSignatureData = signing.SingleSignatureData
|
||||
|
||||
// Tx defines a transaction interface that supports all standard message, signature
|
||||
// fee, memo, and auxiliary interfaces.
|
||||
Tx = authsigning.Tx
|
||||
)
|
||||
|
||||
const (
|
||||
// SignModeUnspecified specifies an unknown signing mode and will be
|
||||
// rejected
|
||||
SignModeUnspecified SignMode = 0
|
||||
// SignModeDirect specifies a signing mode which uses SignDoc and is
|
||||
// verified with raw bytes from Tx
|
||||
SignModeDirect SignMode = 1
|
||||
// SignModeTexture is a future signing mode that will verify some
|
||||
// human-readable textual representation on top of the binary representation
|
||||
// from SIGN_MODE_DIRECT
|
||||
SignModeTexture SignMode = 2
|
||||
// SignModeLegacyAminoJSON is a backwards compatibility mode which uses
|
||||
// Amino JSON and will be removed in the future
|
||||
SignModeLegacyAminoJSON SignMode = 127
|
||||
)
|
||||
|
||||
var (
|
||||
SignWithPrivKey = clienttx.SignWithPrivKey
|
||||
)
|
42
cosmos/tx.go
Normal file
42
cosmos/tx.go
Normal file
@ -0,0 +1,42 @@
|
||||
package cosmos
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
)
|
||||
|
||||
type (
|
||||
TxBuilder = client.TxBuilder
|
||||
TxConfig = client.TxConfig
|
||||
|
||||
SimulateRequest = txtypes.SimulateRequest
|
||||
SimulateResponse = txtypes.SimulateResponse
|
||||
|
||||
BroadcastTxRequest = txtypes.BroadcastTxRequest
|
||||
BroadcastTxResponse = txtypes.BroadcastTxResponse
|
||||
|
||||
TxResponse = types.TxResponse
|
||||
|
||||
BroadcastMode = txtypes.BroadcastMode
|
||||
)
|
||||
|
||||
const (
|
||||
BroadcastModeUnspecified BroadcastMode = 0
|
||||
BroadcastModeBlock BroadcastMode = 1
|
||||
BroadcastModeSync BroadcastMode = 2
|
||||
BroadcastModeAsync BroadcastMode = 3
|
||||
)
|
||||
|
||||
func NewBroadcastTxRequest(txBytes []byte, broadcastMode BroadcastMode) *BroadcastTxRequest {
|
||||
return &BroadcastTxRequest{
|
||||
TxBytes: txBytes,
|
||||
Mode: broadcastMode,
|
||||
}
|
||||
}
|
||||
|
||||
func NewSimulateRequest(txBytes []byte) *SimulateRequest {
|
||||
return &SimulateRequest{
|
||||
TxBytes: txBytes,
|
||||
}
|
||||
}
|
25
cosmos/types.go
Normal file
25
cosmos/types.go
Normal file
@ -0,0 +1,25 @@
|
||||
package cosmos
|
||||
|
||||
import sdktypes "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
type (
|
||||
Int = sdktypes.Int
|
||||
Dec = sdktypes.Dec
|
||||
)
|
||||
|
||||
var (
|
||||
NewInt = sdktypes.NewInt
|
||||
NewIntFromBigInt = sdktypes.NewIntFromBigInt
|
||||
NewIntFromString = sdktypes.NewIntFromString
|
||||
NewIntFromUint64 = sdktypes.NewIntFromUint64
|
||||
NewIntWithDecimal = sdktypes.NewIntWithDecimal
|
||||
NewDec = sdktypes.NewDec
|
||||
NewDecCoinFromCoin = sdktypes.NewDecCoinFromCoin
|
||||
NewDecCoinFromDec = sdktypes.NewDecCoinFromDec
|
||||
NewDecFromBigInt = sdktypes.NewDecFromBigInt
|
||||
NewDecFromBigIntWithPrec = sdktypes.NewDecFromBigIntWithPrec
|
||||
NewDecFromInt = sdktypes.NewDecFromInt
|
||||
NewDecFromIntWithPrec = sdktypes.NewDecFromIntWithPrec
|
||||
NewDecFromStr = sdktypes.NewDecFromStr
|
||||
NewDecWithPrec = sdktypes.NewDecWithPrec
|
||||
)
|
59
crypto/mnemonic.go
Normal file
59
crypto/mnemonic.go
Normal file
@ -0,0 +1,59 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"github.com/cosmos/go-bip39"
|
||||
)
|
||||
|
||||
type (
|
||||
PubKey = secp256k1.PubKey
|
||||
PrivKey = types.PrivKey
|
||||
)
|
||||
|
||||
// CreateMnemonic - create new mnemonic
|
||||
func CreateMnemonic() (string, error) {
|
||||
// Default number of words (24): This generates a mnemonic directly from the
|
||||
// number of words by reading system entropy.
|
||||
entropy, err := bip39.NewEntropy(256)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return bip39.NewMnemonic(entropy)
|
||||
}
|
||||
|
||||
// CreateHDPath returns BIP 44 object from account and index parameters.
|
||||
func CreateHDPath(account uint32, index uint32) string {
|
||||
return hd.CreateHDPath(330, account, index).String()
|
||||
}
|
||||
|
||||
// DerivePrivKeyBz - derive private key bytes
|
||||
func DerivePrivKeyBz(mnemonic string, hdPath string) ([]byte, error) {
|
||||
if !bip39.IsMnemonicValid(mnemonic) {
|
||||
return nil, errors.New("invalid mnemonic")
|
||||
}
|
||||
|
||||
algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyring.SigningAlgoList{hd.Secp256k1})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create master key and derive first key for keyring
|
||||
return algo.Derive()(mnemonic, "", hdPath)
|
||||
}
|
||||
|
||||
// PrivKeyGen is the default PrivKeyGen function in the keybase.
|
||||
// For now, it only supports Secp256k1
|
||||
func PrivKeyGen(bz []byte) (types.PrivKey, error) {
|
||||
algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyring.SigningAlgoList{hd.Secp256k1})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return algo.Generate()(bz), nil
|
||||
}
|
80
factory.go
Normal file
80
factory.go
Normal file
@ -0,0 +1,80 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Factory struct {
|
||||
*Contract
|
||||
}
|
||||
|
||||
func NewFactory(querier *Querier, contractAddress string) (*Factory, error) {
|
||||
contract, err := NewContract(querier, contractAddress)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating base contract")
|
||||
}
|
||||
|
||||
return &Factory{
|
||||
contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *Factory) Pairs(ctx context.Context) ([]string, error) {
|
||||
const limit = 30
|
||||
var res []string
|
||||
var startAfter []StandardAssetInfo
|
||||
cpt := 1
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context canceled")
|
||||
default:
|
||||
pagePairs, lastAssets, err := f.pairsPage(ctx, startAfter, limit)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "querying page %d", cpt)
|
||||
}
|
||||
if pagePairs == nil {
|
||||
return res, nil
|
||||
}
|
||||
res = append(res, pagePairs...)
|
||||
startAfter = lastAssets
|
||||
cpt++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Factory) pairsPage(ctx context.Context, startAfter []StandardAssetInfo, limit int) ([]string, []StandardAssetInfo, error) {
|
||||
var q struct {
|
||||
Pairs struct {
|
||||
StartAfter []StandardAssetInfo `json:"start_after,omitempty"`
|
||||
Limit *int `json:"limit,omitempty"`
|
||||
} `json:"pairs"`
|
||||
}
|
||||
q.Pairs.StartAfter = startAfter
|
||||
q.Pairs.Limit = &limit
|
||||
|
||||
type response struct {
|
||||
Pairs []struct {
|
||||
AssetInfos []StandardAssetInfo `json:"asset_infos"`
|
||||
ContractAddr string `json:"contract_addr"`
|
||||
} `json:"pairs"`
|
||||
}
|
||||
var resp response
|
||||
err := f.QueryStore(ctx, q, &resp)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
|
||||
var res []string
|
||||
var lastAssets []StandardAssetInfo
|
||||
for _, pair := range resp.Pairs {
|
||||
res = append(res, pair.ContractAddr)
|
||||
fmt.Println(pair.ContractAddr)
|
||||
lastAssets = pair.AssetInfos
|
||||
}
|
||||
fmt.Println("")
|
||||
|
||||
return res, lastAssets, nil
|
||||
}
|
117
go.mod
Normal file
117
go.mod
Normal file
@ -0,0 +1,117 @@
|
||||
module github.com/galacticship/terra
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/cosmos/cosmos-sdk v0.44.5
|
||||
github.com/cosmos/go-bip39 v1.0.0
|
||||
github.com/gogo/protobuf v1.3.3
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
github.com/terra-money/core v0.5.17
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0-beta.2 // indirect
|
||||
github.com/99designs/keyring v1.1.6 // indirect
|
||||
github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
|
||||
github.com/CosmWasm/wasmvm v0.16.3 // indirect
|
||||
github.com/DataDog/zstd v1.4.5 // indirect
|
||||
github.com/armon/go-metrics v0.3.9 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/btcsuite/btcd v0.22.0-beta // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/confio/ics23/go v0.6.6 // indirect
|
||||
github.com/cosmos/btcutil v1.0.4 // indirect
|
||||
github.com/cosmos/iavl v0.17.3 // indirect
|
||||
github.com/cosmos/ibc-go v1.1.5 // indirect
|
||||
github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect
|
||||
github.com/cosmos/ledger-go v0.9.2 // indirect
|
||||
github.com/danieljoos/wincred v1.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/badger/v2 v2.2007.2 // indirect
|
||||
github.com/dgraph-io/ristretto v0.0.3 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/go-kit/kit v0.10.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.0 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||
github.com/gtank/merlin v0.1.1 // indirect
|
||||
github.com/gtank/ristretto255 v0.1.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jmhodges/levigo v1.0.0 // indirect
|
||||
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect
|
||||
github.com/libp2p/go-buffer-pool v0.0.2 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.29.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
|
||||
github.com/regen-network/cosmos-proto v0.3.1 // indirect
|
||||
github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.2.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.8.1 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca // indirect
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
|
||||
github.com/tendermint/btcd v0.1.1 // indirect
|
||||
github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect
|
||||
github.com/tendermint/go-amino v0.16.0 // indirect
|
||||
github.com/tendermint/tendermint v0.34.14 // indirect
|
||||
github.com/tendermint/tm-db v0.6.6 // indirect
|
||||
github.com/zondax/hid v0.9.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 // indirect
|
||||
google.golang.org/grpc v1.42.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/ini.v1 v1.63.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76
|
||||
github.com/cosmos/cosmos-sdk => github.com/terra-money/cosmos-sdk v0.44.5-terra.2
|
||||
github.com/cosmos/ledger-cosmos-go => github.com/terra-money/ledger-terra-go v0.11.2
|
||||
github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
|
||||
github.com/tecbot/gorocksdb => github.com/cosmos/gorocksdb v1.2.0
|
||||
github.com/tendermint/tendermint => github.com/terra-money/tendermint v0.34.14-terra.2
|
||||
google.golang.org/grpc => google.golang.org/grpc v1.33.2
|
||||
)
|
310
pair.go
Normal file
310
pair.go
Normal file
@ -0,0 +1,310 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Pair interface {
|
||||
CommissionRate() decimal.Decimal
|
||||
LpToken() Cw20Token
|
||||
Token1() Token
|
||||
Token2() Token
|
||||
ContractAddressString() string
|
||||
ContractAddress() cosmos.AccAddress
|
||||
|
||||
PoolInfo(ctx context.Context) (PoolInfo, error)
|
||||
NewWithdrawLiquidityMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error)
|
||||
Share(ctx context.Context, lpAmount decimal.Decimal) (token1Amount decimal.Decimal, token2Amount decimal.Decimal, err error)
|
||||
NewSimpleSwapMessage(sender cosmos.AccAddress, offerToken Token, amount decimal.Decimal) (cosmos.Msg, error)
|
||||
NewSwapMessage(sender cosmos.AccAddress, offerToken Token, amount decimal.Decimal, spread string, beliefPrice decimal.Decimal) (cosmos.Msg, error)
|
||||
SimulateSwap(ctx context.Context, offer Token, amount decimal.Decimal) (decimal.Decimal, decimal.Decimal, decimal.Decimal, error)
|
||||
Equals(pair Pair) bool
|
||||
String() string
|
||||
}
|
||||
|
||||
type BasePair struct {
|
||||
*Contract
|
||||
|
||||
token1 Token
|
||||
token2 Token
|
||||
lpToken Cw20Token
|
||||
commissionRate decimal.Decimal
|
||||
|
||||
aiFactory AssetInfoFactory
|
||||
}
|
||||
|
||||
func NewBasePair(querier *Querier, contractAddress string, token1 Token, token2 Token, lpToken Cw20Token, commissionRate decimal.Decimal, aiFactory AssetInfoFactory) (*BasePair, error) {
|
||||
contract, err := NewContract(querier, contractAddress)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
p := &BasePair{
|
||||
Contract: contract,
|
||||
commissionRate: commissionRate,
|
||||
aiFactory: aiFactory,
|
||||
token1: token1,
|
||||
token2: token2,
|
||||
lpToken: lpToken,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p BasePair) CommissionRate() decimal.Decimal {
|
||||
return p.commissionRate
|
||||
}
|
||||
|
||||
func (p BasePair) LpToken() Cw20Token {
|
||||
return p.lpToken
|
||||
}
|
||||
|
||||
func (p BasePair) Token1() Token {
|
||||
return p.token1
|
||||
}
|
||||
|
||||
func (p BasePair) Token2() Token {
|
||||
return p.token2
|
||||
}
|
||||
|
||||
func (p BasePair) ContractAddressString() string {
|
||||
return p.contractAddress.String()
|
||||
}
|
||||
func (p BasePair) ContractAddress() cosmos.AccAddress {
|
||||
return p.contractAddress
|
||||
}
|
||||
|
||||
func (p *BasePair) SetToken1(token Token) {
|
||||
p.token1 = token
|
||||
}
|
||||
func (p *BasePair) SetToken2(token Token) {
|
||||
p.token2 = token
|
||||
}
|
||||
func (p *BasePair) SetLpToken(token Cw20Token) {
|
||||
p.lpToken = token
|
||||
}
|
||||
|
||||
func (p *BasePair) Config(ctx context.Context) (tokens []Token, lpToken Token, err error) {
|
||||
var q struct {
|
||||
Pair struct {
|
||||
} `json:"pair"`
|
||||
}
|
||||
type response struct {
|
||||
AssetInfos []json.RawMessage `json:"asset_infos"`
|
||||
ContractAddr string `json:"contract_addr"`
|
||||
LiquidityToken string `json:"liquidity_token"`
|
||||
}
|
||||
var r response
|
||||
err = p.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
|
||||
var assets []AssetInfo
|
||||
for _, info := range r.AssetInfos {
|
||||
a, err := p.aiFactory.DecodeFromJson(info)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "decoding asset json")
|
||||
}
|
||||
assets = append(assets, a)
|
||||
}
|
||||
|
||||
if len(assets) < 2 {
|
||||
return nil, nil, errors.Errorf("not enough token in pair config: %d", len(assets))
|
||||
}
|
||||
t1, err := GetTokenFromAssetInfo(ctx, p.Contract.Querier(), assets[0])
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "getting token1 from asset info")
|
||||
}
|
||||
|
||||
t2, err := GetTokenFromAssetInfo(ctx, p.Contract.Querier(), assets[1])
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "getting token2 from asset info")
|
||||
}
|
||||
|
||||
lptoken, err := Cw20TokenFromAddress(ctx, p.Contract.Querier(), r.LiquidityToken)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "getting lp token: %s", r.LiquidityToken)
|
||||
}
|
||||
|
||||
return []Token{t1, t2}, lptoken, nil
|
||||
}
|
||||
|
||||
type PoolInfo map[string]decimal.Decimal
|
||||
|
||||
func (p BasePair) PoolInfo(ctx context.Context) (PoolInfo, error) {
|
||||
type query struct {
|
||||
Pool struct {
|
||||
} `json:"pool"`
|
||||
}
|
||||
type response struct {
|
||||
Assets []struct {
|
||||
Info json.RawMessage `json:"info"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
} `json:"assets"`
|
||||
TotalShare decimal.Decimal `json:"total_share"`
|
||||
}
|
||||
var r response
|
||||
err := p.QueryStore(ctx, query{}, &r)
|
||||
if err != nil {
|
||||
return PoolInfo{}, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
res := make(map[string]decimal.Decimal)
|
||||
for _, a := range r.Assets {
|
||||
ai, err := p.aiFactory.DecodeFromJson(a.Info)
|
||||
if err != nil {
|
||||
return PoolInfo{}, errors.Wrap(err, "decoding asset info")
|
||||
}
|
||||
if p.token1.Id() == ai.Id() {
|
||||
res[ai.Id()] = p.token1.ValueFromTerra(a.Amount)
|
||||
} else if p.token2.Id() == ai.Id() {
|
||||
res[ai.Id()] = p.token2.ValueFromTerra(a.Amount)
|
||||
} else {
|
||||
return PoolInfo{}, errors.New("asset unknown")
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (p BasePair) NewWithdrawLiquidityMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
type query struct {
|
||||
WithdrawLiquidity struct {
|
||||
} `json:"withdraw_liquidity"`
|
||||
}
|
||||
var q query
|
||||
return p.lpToken.NewMsgSendExecute(sender, p.Contract, amount, q)
|
||||
}
|
||||
|
||||
func (p BasePair) Share(ctx context.Context, lpAmount decimal.Decimal) (token1Amount decimal.Decimal, token2Amount decimal.Decimal, err error) {
|
||||
type query struct {
|
||||
Share struct {
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
} `json:"share"`
|
||||
}
|
||||
type response []struct {
|
||||
Info json.RawMessage `json:"info"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
}
|
||||
var q query
|
||||
q.Share.Amount = p.lpToken.ValueToTerra(lpAmount)
|
||||
var r response
|
||||
err = p.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return decimal.Zero, decimal.Zero, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
if len(r) < 2 {
|
||||
return decimal.Zero, decimal.Zero, errors.Errorf("not enough token in share response: %d", len(r))
|
||||
}
|
||||
|
||||
for _, a := range r {
|
||||
ai, err := p.aiFactory.DecodeFromJson(a.Info)
|
||||
if err != nil {
|
||||
return decimal.Zero, decimal.Zero, errors.Wrap(err, "decoding asset info")
|
||||
}
|
||||
if p.token1.Id() == ai.Id() {
|
||||
token1Amount = p.token1.ValueFromTerra(a.Amount)
|
||||
} else if p.token2.Id() == ai.Id() {
|
||||
token2Amount = p.token2.ValueFromTerra(a.Amount)
|
||||
} else {
|
||||
return decimal.Zero, decimal.Zero, errors.New("asset unknown")
|
||||
}
|
||||
}
|
||||
return token1Amount, token2Amount, nil
|
||||
|
||||
}
|
||||
|
||||
func (p BasePair) NewSimpleSwapMessage(sender cosmos.AccAddress, offerToken Token, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
type query struct {
|
||||
Swap struct {
|
||||
OfferAsset struct {
|
||||
Info AssetInfo `json:"info"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
} `json:"offer_asset"`
|
||||
} `json:"swap"`
|
||||
}
|
||||
|
||||
var q query
|
||||
q.Swap.OfferAsset.Amount = offerToken.ValueToTerra(amount)
|
||||
q.Swap.OfferAsset.Info = p.aiFactory.NewFromToken(offerToken)
|
||||
return offerToken.NewMsgSendExecute(sender, p.Contract, amount, q)
|
||||
}
|
||||
|
||||
func (p BasePair) NewSwapMessage(sender cosmos.AccAddress, offerToken Token, amount decimal.Decimal, spread string, beliefPrice decimal.Decimal) (cosmos.Msg, error) {
|
||||
type query struct {
|
||||
Swap struct {
|
||||
MaxSpread string `json:"max_spread"`
|
||||
OfferAsset struct {
|
||||
Info AssetInfo `json:"info"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
} `json:"offer_asset"`
|
||||
BeliefPrice decimal.Decimal `json:"belief_price"`
|
||||
} `json:"swap"`
|
||||
}
|
||||
|
||||
var q query
|
||||
q.Swap.OfferAsset.Info = p.aiFactory.NewFromToken(offerToken)
|
||||
q.Swap.OfferAsset.Amount = offerToken.ValueToTerra(amount)
|
||||
q.Swap.MaxSpread = spread
|
||||
q.Swap.BeliefPrice = beliefPrice
|
||||
return offerToken.NewMsgSendExecute(sender, p.Contract, amount, q)
|
||||
}
|
||||
|
||||
func ComputeConstantProductSwap(offerPool decimal.Decimal, askPool decimal.Decimal, offerAmount decimal.Decimal, commissionRate decimal.Decimal) (decimal.Decimal, decimal.Decimal, decimal.Decimal, decimal.Decimal) {
|
||||
if offerPool.Equals(decimal.Zero) || askPool.Equals(decimal.Zero) || offerAmount.Equals(decimal.Zero) {
|
||||
return decimal.Zero, decimal.Zero, decimal.Zero, decimal.Zero
|
||||
}
|
||||
cp := offerPool.Mul(askPool)
|
||||
returnAmount := (askPool.Sub(cp.Div(offerPool.Add(offerAmount)))).Truncate(0)
|
||||
spread := offerAmount.Mul(askPool).Div(offerPool).Sub(returnAmount).Truncate(0)
|
||||
commissionAmount := returnAmount.Mul(commissionRate).Truncate(0)
|
||||
beliefPrice := offerAmount.Div(returnAmount)
|
||||
returnAmount = returnAmount.Sub(commissionAmount)
|
||||
return returnAmount, spread, commissionAmount, beliefPrice
|
||||
}
|
||||
|
||||
func (p BasePair) SimulateSwap(ctx context.Context, offer Token, amount decimal.Decimal) (decimal.Decimal, decimal.Decimal, decimal.Decimal, error) {
|
||||
type query struct {
|
||||
Simulation struct {
|
||||
OfferAsset struct {
|
||||
Info AssetInfo `json:"info"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
} `json:"offer_asset"`
|
||||
} `json:"simulation"`
|
||||
}
|
||||
var q query
|
||||
q.Simulation.OfferAsset.Info = p.aiFactory.NewFromToken(offer)
|
||||
q.Simulation.OfferAsset.Amount = offer.ValueToTerra(amount)
|
||||
type response struct {
|
||||
ReturnAmount decimal.Decimal `json:"return_amount"`
|
||||
SpreadAmount decimal.Decimal `json:"spread_amount"`
|
||||
CommissionAmount decimal.Decimal `json:"commission_amount"`
|
||||
}
|
||||
var r response
|
||||
err := p.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return decimal.Zero, decimal.Zero, decimal.Zero, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
|
||||
return r.ReturnAmount, r.SpreadAmount, r.CommissionAmount, nil
|
||||
|
||||
}
|
||||
|
||||
func (p *BasePair) Equals(pair Pair) bool {
|
||||
return p.contractAddress.String() == pair.ContractAddressString()
|
||||
}
|
||||
|
||||
func (p *BasePair) String() string {
|
||||
t1symbol := ""
|
||||
if p.token1 != nil {
|
||||
t1symbol = p.token1.Symbol()
|
||||
}
|
||||
t2symbol := ""
|
||||
if p.token2 != nil {
|
||||
t2symbol = p.token2.Symbol()
|
||||
}
|
||||
return fmt.Sprintf("%s / %s", t1symbol, t2symbol)
|
||||
}
|
90
price_service.go
Normal file
90
price_service.go
Normal file
@ -0,0 +1,90 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type PriceService struct {
|
||||
routers []Router
|
||||
cache map[string]cachedPrice
|
||||
cacheMutex *sync.Mutex
|
||||
cacheTimeout time.Duration
|
||||
maxRouteLength int
|
||||
}
|
||||
|
||||
type cachedPrice struct {
|
||||
price decimal.Decimal
|
||||
updatedTime time.Time
|
||||
}
|
||||
|
||||
type PriceServiceOption func(s *PriceService) *PriceService
|
||||
|
||||
func WithCacheTimeout(timeout time.Duration) PriceServiceOption {
|
||||
return func(s *PriceService) *PriceService {
|
||||
s.cacheTimeout = timeout
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaxRouteLength(maxRouteLenght int) PriceServiceOption {
|
||||
return func(s *PriceService) *PriceService {
|
||||
s.maxRouteLength = maxRouteLenght
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func NewPriceService(options ...PriceServiceOption) *PriceService {
|
||||
p := &PriceService{
|
||||
routers: nil,
|
||||
cache: make(map[string]cachedPrice),
|
||||
cacheMutex: &sync.Mutex{},
|
||||
cacheTimeout: 30 * time.Second,
|
||||
maxRouteLength: 2,
|
||||
}
|
||||
for _, option := range options {
|
||||
p = option(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *PriceService) AddRouter(router Router) {
|
||||
s.routers = append(s.routers, router)
|
||||
}
|
||||
|
||||
func (s *PriceService) GetPriceCached(ctx context.Context, token Token) (decimal.Decimal, error) {
|
||||
s.cacheMutex.Lock()
|
||||
defer s.cacheMutex.Unlock()
|
||||
if p, ok := s.cache[token.Id()]; ok && p.updatedTime.Add(s.cacheTimeout).After(time.Now()) {
|
||||
return p.price, nil
|
||||
}
|
||||
p, err := s.getCurrentPrice(ctx, token)
|
||||
if err != nil {
|
||||
return decimal.Zero, errors.Wrap(err, "getting current price")
|
||||
}
|
||||
s.cache[token.Id()] = cachedPrice{
|
||||
price: p,
|
||||
updatedTime: time.Now(),
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (s *PriceService) getCurrentPrice(ctx context.Context, token Token) (decimal.Decimal, error) {
|
||||
var bestprice decimal.Decimal
|
||||
var bestroute Route
|
||||
for _, router := range s.routers {
|
||||
newprice, newroute, err := router.SimulateSwap(ctx, token, UST, decimal.NewFromInt(1), s.maxRouteLength)
|
||||
if err != nil && err != ErrNoRouteFund {
|
||||
return decimal.Zero, errors.Wrapf(err, "simulating swap with router %s", router)
|
||||
}
|
||||
if newprice.GreaterThan(bestprice) || (newprice.Equals(bestprice) && len(newroute) < len(bestroute)) {
|
||||
bestprice = newprice
|
||||
bestroute = newroute
|
||||
}
|
||||
}
|
||||
return bestprice, nil
|
||||
}
|
65
protocols/anchor/anchor.go
Normal file
65
protocols/anchor/anchor.go
Normal file
@ -0,0 +1,65 @@
|
||||
package anchor
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Anchor struct {
|
||||
Market *Market
|
||||
Overseer *Overseer
|
||||
PriceOracle *PriceOracle
|
||||
BLUNACustody *BLUNACustody
|
||||
}
|
||||
|
||||
func NewAnchor(querier *terra.Querier) (*Anchor, error) {
|
||||
market, err := NewMarket(querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating market")
|
||||
}
|
||||
overseer, err := NewOverseer(querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating overseer")
|
||||
}
|
||||
priceOracle, err := NewPriceOracle(querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating price oracle")
|
||||
}
|
||||
blunaCustody, err := NewBLUNACustody(querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating bluna custody")
|
||||
}
|
||||
|
||||
return &Anchor{
|
||||
Market: market,
|
||||
Overseer: overseer,
|
||||
PriceOracle: priceOracle,
|
||||
BLUNACustody: blunaCustody,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *Anchor) NewProvideBLUNAMessages(sender cosmos.AccAddress, amount decimal.Decimal) ([]cosmos.Msg, error) {
|
||||
m1, err := a.BLUNACustody.NewDepositCollateralMessage(sender, amount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating deposit collateral to BLUNA custody message")
|
||||
}
|
||||
m2, err := a.Overseer.NewLockCollateralMessage(sender, terra.BLUNA, amount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating lock collateral from overseer message")
|
||||
}
|
||||
return []cosmos.Msg{m1, m2}, nil
|
||||
}
|
||||
|
||||
func (a *Anchor) NewWithdrawBLUNAMessages(sender cosmos.AccAddress, amount decimal.Decimal) ([]cosmos.Msg, error) {
|
||||
m1, err := a.Overseer.NewUnlockCollateralMessage(sender, terra.BLUNA, amount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating unlock collateral from overseer message")
|
||||
}
|
||||
m2, err := a.BLUNACustody.NewWithdrawCollateralMessage(sender, amount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating withdraw collateral from BLUNA custody message")
|
||||
}
|
||||
return []cosmos.Msg{m1, m2}, nil
|
||||
}
|
40
protocols/anchor/blunacustody.go
Normal file
40
protocols/anchor/blunacustody.go
Normal file
@ -0,0 +1,40 @@
|
||||
package anchor
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type BLUNACustody struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewBLUNACustody(querier *terra.Querier) (*BLUNACustody, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1ptjp2vfjrwh0j0faj9r6katm640kgjxnwwq9kn")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
return &BLUNACustody{
|
||||
Contract: contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *BLUNACustody) NewDepositCollateralMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
DepositCollateral struct {
|
||||
} `json:"deposit_collateral"`
|
||||
}
|
||||
return terra.BLUNA.NewMsgSendExecute(sender, c.Contract, amount, q)
|
||||
}
|
||||
|
||||
func (c *BLUNACustody) NewWithdrawCollateralMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
WithdrawCollateral struct {
|
||||
Amount decimal.Decimal
|
||||
} `json:"withdraw_collateral"`
|
||||
}
|
||||
q.WithdrawCollateral.Amount = terra.BLUNA.ValueToTerra(amount)
|
||||
return c.NewMsgExecuteContract(sender, q)
|
||||
}
|
98
protocols/anchor/market.go
Normal file
98
protocols/anchor/market.go
Normal file
@ -0,0 +1,98 @@
|
||||
package anchor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Market struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewMarket(querier *terra.Querier) (*Market, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
return &Market{
|
||||
contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type BorrowerInfo struct {
|
||||
InterestIndex decimal.Decimal
|
||||
RewardIndex decimal.Decimal
|
||||
LoanAmount decimal.Decimal
|
||||
PendingRewards decimal.Decimal
|
||||
}
|
||||
|
||||
func (m Market) BorrowerInfo(ctx context.Context, borrower cosmos.AccAddress) (BorrowerInfo, error) {
|
||||
type query struct {
|
||||
BorrowerInfo struct {
|
||||
Borrower string `json:"borrower"`
|
||||
} `json:"borrower_info"`
|
||||
}
|
||||
var q query
|
||||
q.BorrowerInfo.Borrower = borrower.String()
|
||||
|
||||
type response struct {
|
||||
Borrower string `json:"borrower"`
|
||||
InterestIndex decimal.Decimal `json:"interest_index"`
|
||||
RewardIndex decimal.Decimal `json:"reward_index"`
|
||||
LoanAmount decimal.Decimal `json:"loan_amount"`
|
||||
PendingRewards decimal.Decimal `json:"pending_rewards"`
|
||||
}
|
||||
var r response
|
||||
err := m.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return BorrowerInfo{}, errors.Wrap(err, "querying store")
|
||||
}
|
||||
return BorrowerInfo{
|
||||
InterestIndex: r.InterestIndex,
|
||||
RewardIndex: r.RewardIndex,
|
||||
LoanAmount: terra.UST.ValueFromTerra(r.LoanAmount),
|
||||
PendingRewards: terra.ANC.ValueFromTerra(r.PendingRewards),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m Market) NewDepositUSTMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
DepositStable struct{} `json:"deposit_stable"`
|
||||
}
|
||||
return terra.UST.NewMsgSendExecute(sender, m.Contract, amount, q)
|
||||
}
|
||||
|
||||
func (m Market) NewRedeemAUSTMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
RedeemStable struct{} `json:"redeem_stable"`
|
||||
}
|
||||
return terra.AUST.NewMsgSendExecute(sender, m.Contract, amount, q)
|
||||
}
|
||||
|
||||
func (m Market) NewClaimRewardsMessage(sender cosmos.AccAddress) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
ClaimRewards struct{} `json:"claim_rewards"`
|
||||
}
|
||||
return m.NewMsgExecuteContract(sender, q)
|
||||
}
|
||||
|
||||
func (m Market) NewBorrowStableMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
BorrowStable struct {
|
||||
BorrowAmount decimal.Decimal `json:"borrow_amount"`
|
||||
} `json:"borrow_stable"`
|
||||
}
|
||||
q.BorrowStable.BorrowAmount = terra.UST.ValueToTerra(amount)
|
||||
return m.NewMsgExecuteContract(sender, q)
|
||||
}
|
||||
|
||||
func (m Market) NewRepayStableMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
RepayStable struct{} `json:"repay_stable"`
|
||||
}
|
||||
return terra.UST.NewMsgSendExecute(sender, m.Contract, amount, q)
|
||||
}
|
125
protocols/anchor/overseer.go
Normal file
125
protocols/anchor/overseer.go
Normal file
@ -0,0 +1,125 @@
|
||||
package anchor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Overseer struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewOverseer(querier *terra.Querier) (*Overseer, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1tmnqgvg567ypvsvk6rwsga3srp7e3lg6u0elp8")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
return &Overseer{
|
||||
Contract: contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *Overseer) BorrowLimit(ctx context.Context, borrower cosmos.AccAddress) (decimal.Decimal, error) {
|
||||
type query struct {
|
||||
BorrowLimit struct {
|
||||
Borrower string `json:"borrower"`
|
||||
} `json:"borrow_limit"`
|
||||
}
|
||||
var q query
|
||||
q.BorrowLimit.Borrower = borrower.String()
|
||||
|
||||
type response struct {
|
||||
Borrower string `json:"borrower"`
|
||||
BorrowLimit decimal.Decimal `json:"borrow_limit"`
|
||||
}
|
||||
var r response
|
||||
err := o.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return decimal.Zero, errors.Wrap(err, "querying store")
|
||||
}
|
||||
return terra.UST.ValueFromTerra(r.BorrowLimit), nil
|
||||
}
|
||||
|
||||
func (o *Overseer) Collaterals(ctx context.Context, borrower cosmos.AccAddress) (map[string]decimal.Decimal, error) {
|
||||
type query struct {
|
||||
Collaterals struct {
|
||||
Borrower string `json:"borrower"`
|
||||
} `json:"collaterals"`
|
||||
}
|
||||
var q query
|
||||
q.Collaterals.Borrower = borrower.String()
|
||||
type response struct {
|
||||
Borrower string `json:"borrower"`
|
||||
Collaterals [][2]string `json:"collaterals"`
|
||||
}
|
||||
var r response
|
||||
err := o.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "querying store")
|
||||
}
|
||||
res := make(map[string]decimal.Decimal)
|
||||
for _, collateral := range r.Collaterals {
|
||||
token, err := terra.Cw20TokenFromAddress(ctx, o.Contract.Querier(), collateral[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
value, err := decimal.NewFromString(collateral[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
res[collateral[0]] = token.ValueFromTerra(value)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (o *Overseer) Collateral(ctx context.Context, borrower cosmos.AccAddress, token terra.Token) (decimal.Decimal, error) {
|
||||
collaterals, err := o.Collaterals(ctx, borrower)
|
||||
if err != nil {
|
||||
return decimal.Zero, errors.Wrap(err, "getting collaterals")
|
||||
}
|
||||
res := decimal.Zero
|
||||
found := false
|
||||
for s, d := range collaterals {
|
||||
if s == token.Address().String() {
|
||||
found = true
|
||||
res = d
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return decimal.Zero, errors.Errorf("No collateral found for %s", token.Symbol())
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (o *Overseer) NewUnlockCollateralMessage(sender cosmos.AccAddress, token terra.Cw20Token, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
type query struct {
|
||||
UnlockCollateral struct {
|
||||
Collaterals [][2]string `json:"collaterals"`
|
||||
} `json:"unlock_collateral"`
|
||||
}
|
||||
var q query
|
||||
q.UnlockCollateral.Collaterals = append(q.UnlockCollateral.Collaterals, [2]string{
|
||||
token.Address().String(),
|
||||
token.ValueToTerra(amount).String(),
|
||||
})
|
||||
return o.NewMsgExecuteContract(sender, q)
|
||||
}
|
||||
|
||||
func (o *Overseer) NewLockCollateralMessage(sender cosmos.AccAddress, token terra.Cw20Token, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
type query struct {
|
||||
LockCollateral struct {
|
||||
Collaterals [][2]string `json:"collaterals"`
|
||||
} `json:"lock_collateral"`
|
||||
}
|
||||
var q query
|
||||
q.LockCollateral.Collaterals = append(q.LockCollateral.Collaterals, [2]string{
|
||||
token.Address().String(),
|
||||
token.ValueToTerra(amount).String(),
|
||||
})
|
||||
return o.NewMsgExecuteContract(sender, q)
|
||||
}
|
70
protocols/anchor/priceoracle.go
Normal file
70
protocols/anchor/priceoracle.go
Normal file
@ -0,0 +1,70 @@
|
||||
package anchor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type PriceOracle struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewPriceOracle(querier *terra.Querier) (*PriceOracle, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1cgg6yef7qcdm070qftghfulaxmllgmvk77nc7t")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
return &PriceOracle{
|
||||
Contract: contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *PriceOracle) Prices(ctx context.Context) (map[string]decimal.Decimal, error) {
|
||||
var q struct {
|
||||
Prices struct {
|
||||
} `json:"prices"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Prices []struct {
|
||||
Asset string `json:"asset"`
|
||||
Price decimal.Decimal `json:"price"`
|
||||
LastUpdatedTime int `json:"last_updated_time"`
|
||||
} `json:"prices"`
|
||||
}
|
||||
var r response
|
||||
err := o.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "querying store")
|
||||
}
|
||||
|
||||
res := make(map[string]decimal.Decimal)
|
||||
for _, price := range r.Prices {
|
||||
res[price.Asset] = price.Price
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (o *PriceOracle) Price(ctx context.Context, token terra.Token) (decimal.Decimal, error) {
|
||||
prices, err := o.Prices(ctx)
|
||||
if err != nil {
|
||||
return decimal.Zero, errors.Wrap(err, "getting prices")
|
||||
}
|
||||
|
||||
res := decimal.Zero
|
||||
found := false
|
||||
for s, d := range prices {
|
||||
if s == token.Address().String() {
|
||||
found = true
|
||||
res = d
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return decimal.Zero, errors.Errorf("price for %s not found in Price Oracle prices", token.Symbol())
|
||||
}
|
||||
return res, nil
|
||||
}
|
123
protocols/astroport/bootstrap_auction.go
Normal file
123
protocols/astroport/bootstrap_auction.go
Normal file
@ -0,0 +1,123 @@
|
||||
package astroport
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type BootstrapAuction struct {
|
||||
*terra.Contract
|
||||
astroustpair terra.Pair
|
||||
}
|
||||
|
||||
func (b *BootstrapAuction) RewardPair() terra.Pair {
|
||||
return b.astroustpair
|
||||
}
|
||||
|
||||
func NewBootstrapAuction(ctx context.Context, querier *terra.Querier) (*BootstrapAuction, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1tvld5k6pus2yh7pcu7xuwyjedn7mjxfkkkjjap")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
b := &BootstrapAuction{
|
||||
Contract: contract,
|
||||
}
|
||||
cfg, err := b.Config(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting config")
|
||||
}
|
||||
p, err := NewXykPair(querier, cfg.AstroUstPoolAddress, terra.ASTRO, terra.UST, terra.ASTRO_ASTROUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting pool pair object")
|
||||
}
|
||||
b.astroustpair = p
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type BootstrapAuctionUserInfo struct {
|
||||
WithdrawableLpShares decimal.Decimal `json:"withdrawable_lp_shares"`
|
||||
}
|
||||
|
||||
func (b *BootstrapAuction) UserInfo(ctx context.Context, address cosmos.AccAddress) (BootstrapAuctionUserInfo, error) {
|
||||
type query struct {
|
||||
UserInfo struct {
|
||||
Address string `json:"address"`
|
||||
} `json:"user_info"`
|
||||
}
|
||||
var q query
|
||||
q.UserInfo.Address = address.String()
|
||||
type response struct {
|
||||
WithdrawableLpShares decimal.Decimal `json:"withdrawable_lp_shares"`
|
||||
}
|
||||
|
||||
var r response
|
||||
err := b.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return BootstrapAuctionUserInfo{}, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
return BootstrapAuctionUserInfo{
|
||||
WithdrawableLpShares: b.astroustpair.LpToken().ValueFromTerra(r.WithdrawableLpShares),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type BootstrapAuctionConfig struct {
|
||||
AstroUstPoolAddress string
|
||||
}
|
||||
|
||||
func (b *BootstrapAuction) Config(ctx context.Context) (BootstrapAuctionConfig, error) {
|
||||
var q struct {
|
||||
Config struct {
|
||||
} `json:"config"`
|
||||
}
|
||||
type response struct {
|
||||
Owner string `json:"owner"`
|
||||
AstroTokenAddress string `json:"astro_token_address"`
|
||||
AirdropContractAddress string `json:"airdrop_contract_address"`
|
||||
LockdropContractAddress string `json:"lockdrop_contract_address"`
|
||||
PoolInfo struct {
|
||||
AstroUstPoolAddress string `json:"astro_ust_pool_address"`
|
||||
AstroUstLpTokenAddress string `json:"astro_ust_lp_token_address"`
|
||||
} `json:"pool_info"`
|
||||
GeneratorContract string `json:"generator_contract"`
|
||||
AstroIncentiveAmount string `json:"astro_incentive_amount"`
|
||||
LpTokensVestingDuration int `json:"lp_tokens_vesting_duration"`
|
||||
InitTimestamp int `json:"init_timestamp"`
|
||||
DepositWindow int `json:"deposit_window"`
|
||||
WithdrawalWindow int `json:"withdrawal_window"`
|
||||
}
|
||||
|
||||
var r response
|
||||
err := b.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return BootstrapAuctionConfig{}, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
return BootstrapAuctionConfig{
|
||||
AstroUstPoolAddress: r.PoolInfo.AstroUstPoolAddress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *BootstrapAuction) NewClaimAllRewardsMessage(ctx context.Context, sender cosmos.AccAddress) (cosmos.Msg, decimal.Decimal, error) {
|
||||
r, err := b.UserInfo(ctx, sender)
|
||||
if err != nil {
|
||||
return nil, decimal.Zero, errors.Wrap(err, "getting user info")
|
||||
}
|
||||
msg, err := b.NewClaimRewardsMessage(sender, r.WithdrawableLpShares)
|
||||
if err != nil {
|
||||
return nil, decimal.Zero, errors.Wrap(err, "creating message")
|
||||
}
|
||||
return msg, r.WithdrawableLpShares, nil
|
||||
}
|
||||
|
||||
func (b *BootstrapAuction) NewClaimRewardsMessage(sender cosmos.AccAddress, withdrawLpShares decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
ClaimRewards struct {
|
||||
WithdrawLpShares decimal.Decimal `json:"withdraw_lp_shares"`
|
||||
} `json:"claim_rewards"`
|
||||
}
|
||||
q.ClaimRewards.WithdrawLpShares = b.astroustpair.LpToken().ValueToTerra(withdrawLpShares)
|
||||
return b.NewMsgExecuteContract(sender, q)
|
||||
}
|
37
protocols/astroport/lockdrop.go
Normal file
37
protocols/astroport/lockdrop.go
Normal file
@ -0,0 +1,37 @@
|
||||
package astroport
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Lockdrop struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewLockdrop(querier *terra.Querier) (*Lockdrop, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1627ldjvxatt54ydd3ns6xaxtd68a2vtyu7kakj")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
|
||||
return &Lockdrop{
|
||||
Contract: contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *Lockdrop) NewClaimRewardsMessage(sender cosmos.AccAddress, duration int, withdrawLpStake bool, terraswapLpToken string) (cosmos.Msg, error) {
|
||||
type query struct {
|
||||
Claim struct {
|
||||
Duration int `json:"duration"`
|
||||
WithdrawLpStake bool `json:"withdraw_lp_stake"`
|
||||
TerraswapLpToken string `json:"terraswap_lp_token"`
|
||||
} `json:"claim_rewards_and_optionally_unlock"`
|
||||
}
|
||||
var q query
|
||||
q.Claim.Duration = duration
|
||||
q.Claim.WithdrawLpStake = withdrawLpStake
|
||||
q.Claim.TerraswapLpToken = terraswapLpToken
|
||||
return l.NewMsgExecuteContract(sender, q)
|
||||
}
|
34
protocols/astroport/pair.go
Normal file
34
protocols/astroport/pair.go
Normal file
@ -0,0 +1,34 @@
|
||||
package astroport
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type XykPair struct {
|
||||
*terra.BasePair
|
||||
}
|
||||
|
||||
func NewXykPair(querier *terra.Querier, contractAddress string, token1 terra.Token, token2 terra.Token, lpToken terra.Cw20Token) (*XykPair, error) {
|
||||
bp, err := terra.NewBasePair(querier, contractAddress, token1, token2, lpToken, decimal.NewFromFloat(0.003), terra.NewAssetInfoFactory())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &XykPair{
|
||||
bp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type StablePair struct {
|
||||
*terra.BasePair
|
||||
}
|
||||
|
||||
func NewStablePair(querier *terra.Querier, contractAddress string, token1 terra.Token, token2 terra.Token, lpToken terra.Cw20Token) (*StablePair, error) {
|
||||
bp, err := terra.NewBasePair(querier, contractAddress, token1, token2, lpToken, decimal.NewFromFloat(0.0005), terra.NewAssetInfoFactory())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &StablePair{
|
||||
bp,
|
||||
}, nil
|
||||
}
|
106
protocols/astroport/router.go
Normal file
106
protocols/astroport/router.go
Normal file
@ -0,0 +1,106 @@
|
||||
package astroport
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type router struct {
|
||||
*terra.BaseRouter
|
||||
}
|
||||
|
||||
func NewRouter(querier *terra.Querier) (terra.Router, error) {
|
||||
r, err := terra.NewBaseRouter(querier, "terra16t7dpwwgx9n3lq6l6te3753lsjqwhxwpday9zx", terra.NewAssetInfoFactory(), newOperation)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating base router")
|
||||
}
|
||||
|
||||
LUNAUST, err := NewXykPair(querier, "terra1m6ywlgn6wrjuagcmmezzz2a029gtldhey5k552", terra.LUNA, terra.UST, terra.ASTRO_LUNAUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LUNAUST pair")
|
||||
}
|
||||
BLUNAUST, err := NewXykPair(querier, "terra1wdwg06ksy3dfvkys32yt4yqh9gm6a9f7qmsh37", terra.BLUNA, terra.UST, terra.ASTRO_BLUNAUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init BLUNAUST pair")
|
||||
}
|
||||
ANCUST, err := NewXykPair(querier, "terra1qr2k6yjjd5p2kaewqvg93ag74k6gyjr7re37fs", terra.ANC, terra.UST, terra.ASTRO_ANCUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ANCUST pair")
|
||||
}
|
||||
MIRUST, err := NewXykPair(querier, "terra143xxfw5xf62d5m32k3t4eu9s82ccw80lcprzl9", terra.MIR, terra.UST, terra.ASTRO_MIRUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init MIRUST pair")
|
||||
}
|
||||
MINEUST, err := NewXykPair(querier, "terra134m8n2epp0n40qr08qsvvrzycn2zq4zcpmue48", terra.MINE, terra.UST, terra.ASTRO_MINEUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init MINEUST pair")
|
||||
}
|
||||
SKUJIKUJI, err := NewXykPair(querier, "terra1hlq6ye6km5sq2pcnmrvlf784gs9zygt0akwvsu", terra.SKUJI, terra.KUJI, terra.ASTRO_SKUJIKUJILP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init SKUJIKUJI pair")
|
||||
}
|
||||
MARSUST, err := NewXykPair(querier, "terra19wauh79y42u5vt62c5adt2g5h4exgh26t3rpds", terra.MARS, terra.UST, terra.ASTRO_MARSUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init MARSUST pair")
|
||||
}
|
||||
ASTROUST, err := NewXykPair(querier, "terra1l7xu2rl3c7qmtx3r5sd2tz25glf6jh8ul7aag7", terra.ASTRO, terra.UST, terra.ASTRO_ASTROUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ASTROUST pair")
|
||||
}
|
||||
ASTROLUNA, err := NewXykPair(querier, "terra1nujm9zqa4hpaz9s8wrhrp86h3m9xwprjt9kmf9", terra.ASTRO, terra.LUNA, terra.ASTRO_ASTROLUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ASTROLUNA pair")
|
||||
}
|
||||
|
||||
LUNABLUNA, err := NewStablePair(querier, "terra1j66jatn3k50hjtg2xemnjm8s7y8dws9xqa5y8w", terra.LUNA, terra.BLUNA, terra.ASTRO_LUNABLUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LUNABLUNA pair")
|
||||
}
|
||||
|
||||
r.SetPairs(
|
||||
LUNAUST,
|
||||
BLUNAUST,
|
||||
ANCUST,
|
||||
MIRUST,
|
||||
MINEUST,
|
||||
SKUJIKUJI,
|
||||
MARSUST,
|
||||
ASTROUST,
|
||||
ASTROLUNA,
|
||||
LUNABLUNA,
|
||||
//{terra.VKR, terra.UST},
|
||||
//{terra.APOLLO, terra.UST},
|
||||
//{terra.ORION, terra.UST},
|
||||
//{terra.BLUNA, terra.LUNA},
|
||||
//{terra.STLUNA, terra.LUNA},
|
||||
//{terra.STT, terra.UST},
|
||||
//{terra.PSI, terra.UST},
|
||||
//{terra.PSI, terra.NLUNA},
|
||||
//{terra.WEWSTETH, terra.UST},
|
||||
//{terra.PSI, terra.NETH},
|
||||
//{terra.XDEFI, terra.UST},
|
||||
//{terra.LUART, terra.UST},
|
||||
//{terra.ORNE, terra.UST},
|
||||
//{terra.HALO, terra.UST},
|
||||
)
|
||||
|
||||
return &router{r}, nil
|
||||
}
|
||||
|
||||
type operation struct {
|
||||
Swap struct {
|
||||
OfferAssetInfo terra.AssetInfo `json:"offer_asset_info"`
|
||||
AskAssetInfo terra.AssetInfo `json:"ask_asset_info"`
|
||||
} `json:"astro_swap"`
|
||||
}
|
||||
|
||||
func newOperation(aiFactory terra.AssetInfoFactory, offer terra.Token, ask terra.Token) interface{} {
|
||||
var res operation
|
||||
res.Swap.OfferAssetInfo = aiFactory.NewFromToken(offer)
|
||||
res.Swap.AskAssetInfo = aiFactory.NewFromToken(ask)
|
||||
return res
|
||||
}
|
||||
|
||||
func (r router) String() string {
|
||||
return "astroport"
|
||||
}
|
37
protocols/astroport/staking.go
Normal file
37
protocols/astroport/staking.go
Normal file
@ -0,0 +1,37 @@
|
||||
package astroport
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Staking struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewStaking(querier *terra.Querier) (*Staking, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1f68wt2ch3cx2g62dxtc8v68mkdh5wchdgdjwz7")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
|
||||
return &Staking{
|
||||
Contract: contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Staking) NewEnterMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
Enter struct{} `json:"enter"`
|
||||
}
|
||||
return terra.ASTRO.NewMsgSendExecute(sender, s.Contract, amount, q)
|
||||
}
|
||||
|
||||
func (s *Staking) NewLeaveMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
Leave struct{} `json:"leave"`
|
||||
}
|
||||
return terra.XASTRO.NewMsgSendExecute(sender, s.Contract, amount, q)
|
||||
}
|
20
protocols/loop/pair.go
Normal file
20
protocols/loop/pair.go
Normal file
@ -0,0 +1,20 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Pair struct {
|
||||
*terra.BasePair
|
||||
}
|
||||
|
||||
func NewPair(querier *terra.Querier, contractAddress string, token1 terra.Token, token2 terra.Token, lpToken terra.Cw20Token) (*Pair, error) {
|
||||
bp, err := terra.NewBasePair(querier, contractAddress, token1, token2, lpToken, decimal.NewFromFloat(0.003), terra.NewAssetInfoFactory())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Pair{
|
||||
bp,
|
||||
}, nil
|
||||
}
|
116
protocols/mars/bootstrap.go
Normal file
116
protocols/mars/bootstrap.go
Normal file
@ -0,0 +1,116 @@
|
||||
package mars
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/galacticship/terra/protocols/astroport"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Bootstrap struct {
|
||||
*terra.Contract
|
||||
|
||||
marsustpair terra.Pair
|
||||
}
|
||||
|
||||
func (b *Bootstrap) RewardPair() terra.Pair {
|
||||
return b.marsustpair
|
||||
}
|
||||
|
||||
func NewBootstrap(ctx context.Context, querier *terra.Querier) (*Bootstrap, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1hgyamk2kcy3stqx82wrnsklw9aq7rask5dxfds")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
b := &Bootstrap{
|
||||
Contract: contract,
|
||||
}
|
||||
cfg, err := b.Config(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting config")
|
||||
}
|
||||
p, err := astroport.NewXykPair(querier, cfg.MarsUstPoolAddress, terra.MARS, terra.UST, terra.ASTRO_MARSUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting pool pair object")
|
||||
}
|
||||
b.marsustpair = p
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type BootstrapConfig struct {
|
||||
MarsUstPoolAddress string
|
||||
}
|
||||
|
||||
func (b *Bootstrap) Config(ctx context.Context) (BootstrapConfig, error) {
|
||||
var q struct {
|
||||
Config struct {
|
||||
} `json:"config"`
|
||||
}
|
||||
type response struct {
|
||||
Owner string `json:"owner"`
|
||||
MarsTokenAddress string `json:"mars_token_address"`
|
||||
AstroTokenAddress string `json:"astro_token_address"`
|
||||
AirdropContractAddress string `json:"airdrop_contract_address"`
|
||||
LockdropContractAddress string `json:"lockdrop_contract_address"`
|
||||
AstroportLpPool string `json:"astroport_lp_pool"`
|
||||
LpTokenAddress string `json:"lp_token_address"`
|
||||
MarsLpStakingContract string `json:"mars_lp_staking_contract"`
|
||||
GeneratorContract string `json:"generator_contract"`
|
||||
MarsRewards string `json:"mars_rewards"`
|
||||
MarsVestingDuration int `json:"mars_vesting_duration"`
|
||||
LpTokensVestingDuration int `json:"lp_tokens_vesting_duration"`
|
||||
InitTimestamp int `json:"init_timestamp"`
|
||||
MarsDepositWindow int `json:"mars_deposit_window"`
|
||||
UstDepositWindow int `json:"ust_deposit_window"`
|
||||
WithdrawalWindow int `json:"withdrawal_window"`
|
||||
}
|
||||
|
||||
var r response
|
||||
err := b.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return BootstrapConfig{}, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
return BootstrapConfig{
|
||||
MarsUstPoolAddress: r.AstroportLpPool,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type BootstrapUserInfo struct {
|
||||
WithdrawableLpShares decimal.Decimal `json:"withdrawable_lp_shares"`
|
||||
}
|
||||
|
||||
func (b *Bootstrap) UserInfo(ctx context.Context, address cosmos.AccAddress) (BootstrapUserInfo, error) {
|
||||
type query struct {
|
||||
UserInfo struct {
|
||||
Address string `json:"address"`
|
||||
} `json:"user_info"`
|
||||
}
|
||||
var q query
|
||||
q.UserInfo.Address = address.String()
|
||||
type response struct {
|
||||
WithdrawableLpShares decimal.Decimal `json:"withdrawable_lp_shares"`
|
||||
}
|
||||
|
||||
var r response
|
||||
err := b.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return BootstrapUserInfo{}, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
return BootstrapUserInfo{
|
||||
WithdrawableLpShares: b.marsustpair.LpToken().ValueFromTerra(r.WithdrawableLpShares),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Bootstrap) NewClaimRewardsMessage(sender cosmos.AccAddress, withDrawUnlockedShares bool) (cosmos.Msg, error) {
|
||||
type query struct {
|
||||
ClaimRewards struct {
|
||||
WithdrawUnlockedShares bool `json:"withdraw_unlocked_shares"`
|
||||
} `json:"claim_rewards"`
|
||||
}
|
||||
var q query
|
||||
q.ClaimRewards.WithdrawUnlockedShares = withDrawUnlockedShares
|
||||
return b.NewMsgExecuteContract(sender, q)
|
||||
}
|
65
protocols/mars/field.go
Normal file
65
protocols/mars/field.go
Normal file
@ -0,0 +1,65 @@
|
||||
package mars
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Field struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewANCUSTField(querier *terra.Querier) (*Field, error) {
|
||||
return NewField(querier, "terra1vapq79y9cqghqny7zt72g4qukndz282uvqwtz6")
|
||||
}
|
||||
|
||||
func NewField(querier *terra.Querier, address string) (*Field, error) {
|
||||
contract, err := terra.NewContract(querier, address)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
return &Field{
|
||||
contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type FieldSnapshot struct {
|
||||
PositionLpValue decimal.Decimal
|
||||
DebtValue decimal.Decimal
|
||||
Ltv decimal.Decimal
|
||||
}
|
||||
|
||||
func (f *Field) Snapshot(ctx context.Context, address string) (FieldSnapshot, error) {
|
||||
type query struct {
|
||||
Snapshot struct {
|
||||
User string `json:"user"`
|
||||
} `json:"snapshot"`
|
||||
}
|
||||
type response struct {
|
||||
Time int `json:"time"`
|
||||
Height int `json:"height"`
|
||||
Position struct {
|
||||
BondUnits decimal.Decimal `json:"bond_units"`
|
||||
DebtUnits decimal.Decimal `json:"debt_units"`
|
||||
} `json:"position"`
|
||||
Health struct {
|
||||
BondAmount decimal.Decimal `json:"bond_amount"`
|
||||
BondValue decimal.Decimal `json:"bond_value"`
|
||||
DebtAmount decimal.Decimal `json:"debt_amount"`
|
||||
DebtValue decimal.Decimal `json:"debt_value"`
|
||||
Ltv decimal.Decimal `json:"ltv"`
|
||||
} `json:"health"`
|
||||
}
|
||||
var q query
|
||||
q.Snapshot.User = address
|
||||
var r response
|
||||
err := f.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return FieldSnapshot{}, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
res := FieldSnapshot{}
|
||||
return res, nil
|
||||
}
|
29
protocols/mars/governance.go
Normal file
29
protocols/mars/governance.go
Normal file
@ -0,0 +1,29 @@
|
||||
package mars
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Governance struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewGovernance(querier *terra.Querier) (*Governance, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1y8wwr5q24msk55x9smwn0ptyt24fxpwm4l7tjl")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
return &Governance{
|
||||
Contract: contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *Governance) NewStakeMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
Stake struct{} `json:"stake"`
|
||||
}
|
||||
return terra.MARS.NewMsgSendExecute(sender, g.Contract, amount, q)
|
||||
}
|
35
protocols/mars/mars.go
Normal file
35
protocols/mars/mars.go
Normal file
@ -0,0 +1,35 @@
|
||||
package mars
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Mars struct {
|
||||
Governance *Governance
|
||||
Bootstrap *Bootstrap
|
||||
ANCUSTField *Field
|
||||
}
|
||||
|
||||
func NewMars(ctx context.Context, querier *terra.Querier) (*Mars, error) {
|
||||
governance, err := NewGovernance(querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating governance")
|
||||
}
|
||||
bootstrap, err := NewBootstrap(ctx, querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating bootstrap")
|
||||
}
|
||||
ancustfield, err := NewANCUSTField(querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating ancust field")
|
||||
}
|
||||
|
||||
return &Mars{
|
||||
Governance: governance,
|
||||
Bootstrap: bootstrap,
|
||||
ANCUSTField: ancustfield,
|
||||
}, nil
|
||||
}
|
79
protocols/prism/amps.go
Normal file
79
protocols/prism/amps.go
Normal file
@ -0,0 +1,79 @@
|
||||
package prism
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Amps struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewAmps(querier *terra.Querier) (*Amps, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1pa4amk66q8punljptzmmftf6ylq3ezyzx6kl9m")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
return &Amps{
|
||||
Contract: contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type BoostInfo struct {
|
||||
AmountBonded decimal.Decimal
|
||||
Boost decimal.Decimal
|
||||
LastUpdatedTime time.Time
|
||||
BoostAccrualStartTime time.Time
|
||||
}
|
||||
|
||||
func (a *Amps) GetBoost(ctx context.Context, user cosmos.AccAddress) (BoostInfo, error) {
|
||||
type query struct {
|
||||
GetBoost struct {
|
||||
User string `json:"user"`
|
||||
} `json:"get_boost"`
|
||||
}
|
||||
type response struct {
|
||||
AmtBonded decimal.Decimal `json:"amt_bonded"`
|
||||
TotalBoost decimal.Decimal `json:"total_boost"`
|
||||
LastUpdated int64 `json:"last_updated"`
|
||||
BoostAccrualStartTime int64 `json:"boost_accrual_start_time"`
|
||||
}
|
||||
var q query
|
||||
q.GetBoost.User = user.String()
|
||||
var r response
|
||||
err := a.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return BoostInfo{}, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
return BoostInfo{
|
||||
AmountBonded: terra.XPRISM.ValueFromTerra(r.AmtBonded),
|
||||
Boost: r.TotalBoost.Shift(-6),
|
||||
LastUpdatedTime: time.Unix(r.LastUpdated, 0),
|
||||
BoostAccrualStartTime: time.Unix(r.BoostAccrualStartTime, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *Amps) NewBondMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
type query struct {
|
||||
Bond struct {
|
||||
} `json:"bond"`
|
||||
}
|
||||
var q query
|
||||
return terra.XPRISM.NewMsgSendExecute(sender, a.Contract, amount, q)
|
||||
}
|
||||
|
||||
func (a *Amps) NewUnbondMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
type query struct {
|
||||
Unbond struct {
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
} `json:"unbond"`
|
||||
}
|
||||
var q query
|
||||
q.Unbond.Amount = terra.XPRISM.ValueToTerra(amount)
|
||||
return a.NewMsgExecuteContract(sender, q)
|
||||
}
|
54
protocols/prism/asset_info.go
Normal file
54
protocols/prism/asset_info.go
Normal file
@ -0,0 +1,54 @@
|
||||
package prism
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
)
|
||||
|
||||
//goland:noinspection GoNameStartsWithPackageName
|
||||
type PrismAssetInfo struct {
|
||||
Token string `json:"cw20,omitempty"`
|
||||
NativeToken string `json:"native,omitempty"`
|
||||
}
|
||||
|
||||
func (ai PrismAssetInfo) IsNative() bool {
|
||||
return ai.NativeToken != ""
|
||||
}
|
||||
|
||||
func (ai PrismAssetInfo) Id() string {
|
||||
if ai.IsNative() {
|
||||
return ai.NativeToken
|
||||
} else if ai.Token != "" {
|
||||
return ai.Token
|
||||
} else {
|
||||
panic(errors.New("invalid asset info"))
|
||||
}
|
||||
}
|
||||
|
||||
type assetInfoFactory struct {
|
||||
}
|
||||
|
||||
func (a assetInfoFactory) NewFromToken(token terra.Token) terra.AssetInfo {
|
||||
var res PrismAssetInfo
|
||||
if token.IsNative() {
|
||||
res.NativeToken = token.Id()
|
||||
} else {
|
||||
res.Token = token.Id()
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (a assetInfoFactory) DecodeFromJson(raw json.RawMessage) (terra.AssetInfo, error) {
|
||||
var res PrismAssetInfo
|
||||
err := json.Unmarshal(raw, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NewAssetInfoFactory() terra.AssetInfoFactory {
|
||||
return &assetInfoFactory{}
|
||||
}
|
63
protocols/prism/farm.go
Normal file
63
protocols/prism/farm.go
Normal file
@ -0,0 +1,63 @@
|
||||
package prism
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Farm struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewFarm(querier *terra.Querier) (*Farm, error) {
|
||||
c, err := terra.NewContract(querier, "terra1ns5nsvtdxu53dwdthy3yxs6x3w2hf3fclhzllc")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init base contract")
|
||||
}
|
||||
return &Farm{
|
||||
c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *Farm) NewBondMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
Bond struct{} `json:"bond"`
|
||||
}
|
||||
return terra.YLUNA.NewMsgSendExecute(sender, f.Contract, amount, q)
|
||||
}
|
||||
|
||||
func (f *Farm) NewUnBondMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
Unbond struct {
|
||||
Amount decimal.Decimal
|
||||
} `json:"unbond"`
|
||||
}
|
||||
q.Unbond.Amount = terra.YLUNA.ValueToTerra(amount)
|
||||
return f.NewMsgExecuteContract(sender, q)
|
||||
}
|
||||
|
||||
func (f *Farm) NewActivateBoostMessage(sender cosmos.AccAddress) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
ActivateBoost struct{} `json:"activate_boost"`
|
||||
}
|
||||
return f.NewMsgExecuteContract(sender, q)
|
||||
}
|
||||
|
||||
func (f *Farm) newClaimWithdrawnRewardsMessage(sender cosmos.AccAddress, claimType string) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
ClaimWithdrawnRewards struct {
|
||||
ClaimType string `json:"claim_type"`
|
||||
} `json:"claim_withdrawn_rewards"`
|
||||
}
|
||||
q.ClaimWithdrawnRewards.ClaimType = claimType
|
||||
return f.NewMsgExecuteContract(sender, q)
|
||||
}
|
||||
|
||||
func (f *Farm) NewClaimWithdrawnRewardsMessage(sender cosmos.AccAddress) (cosmos.Msg, error) {
|
||||
return f.newClaimWithdrawnRewardsMessage(sender, "Prism")
|
||||
}
|
||||
func (f *Farm) NewClaimAndPledgeWithdrawnRewardsMessage(sender cosmos.AccAddress) (cosmos.Msg, error) {
|
||||
return f.newClaimWithdrawnRewardsMessage(sender, "Amps")
|
||||
}
|
20
protocols/prism/pair.go
Normal file
20
protocols/prism/pair.go
Normal file
@ -0,0 +1,20 @@
|
||||
package prism
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Pair struct {
|
||||
*terra.BasePair
|
||||
}
|
||||
|
||||
func NewPair(querier *terra.Querier, contractAddress string, token1 terra.Token, token2 terra.Token, lpToken terra.Cw20Token) (*Pair, error) {
|
||||
bp, err := terra.NewBasePair(querier, contractAddress, token1, token2, lpToken, decimal.NewFromFloat(0.003), NewAssetInfoFactory())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Pair{
|
||||
bp,
|
||||
}, nil
|
||||
}
|
33
protocols/prism/prism.go
Normal file
33
protocols/prism/prism.go
Normal file
@ -0,0 +1,33 @@
|
||||
package prism
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Prism struct {
|
||||
Amps *Amps
|
||||
YLUNAStaking *YLUNAStaking
|
||||
Farm *Farm
|
||||
}
|
||||
|
||||
func NewPrism(querier *terra.Querier) (*Prism, error) {
|
||||
amps, err := NewAmps(querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating amps")
|
||||
}
|
||||
ylunastaking, err := NewYLUNAStaking(querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating yluna staking")
|
||||
}
|
||||
farm, err := NewFarm(querier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating farm")
|
||||
}
|
||||
|
||||
return &Prism{
|
||||
Amps: amps,
|
||||
YLUNAStaking: ylunastaking,
|
||||
Farm: farm,
|
||||
}, nil
|
||||
}
|
73
protocols/prism/router.go
Normal file
73
protocols/prism/router.go
Normal file
@ -0,0 +1,73 @@
|
||||
package prism
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
*terra.BaseRouter
|
||||
}
|
||||
|
||||
func NewRouter(querier *terra.Querier) (terra.Router, error) {
|
||||
r, err := terra.NewBaseRouter(querier, "terra1yrc0zpwhuqezfnhdgvvh7vs5svqtgyl7pu3n6c", NewAssetInfoFactory(), newOperation)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating base router")
|
||||
}
|
||||
|
||||
PRISMUST, err := NewPair(querier, "terra19d2alknajcngdezrdhq40h6362k92kz23sz62u", terra.PRISM, terra.UST, terra.PRISM_PRISMUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PRISMUST pair")
|
||||
}
|
||||
PRISMLUNA, err := NewPair(querier, "terra1r38qlqt69lez4nja5h56qwf4drzjpnu8gz04jd", terra.PRISM, terra.LUNA, terra.PRISM_PRISMLUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PRISMLUNA pair")
|
||||
}
|
||||
PRISMPLUNA, err := NewPair(querier, "terra1persuahr6f8fm6nyup0xjc7aveaur89nwgs5vs", terra.PRISM, terra.PLUNA, terra.PRISM_PRISMPLUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PRISMPLUNA pair")
|
||||
}
|
||||
PRISMXPRISM, err := NewPair(querier, "terra1czynvm64nslq2xxavzyrrhau09smvana003nrf", terra.PRISM, terra.XPRISM, terra.PRISM_PRISMXPRISMLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PRISMXPRISM pair")
|
||||
}
|
||||
PRISMCLUNA, err := NewPair(querier, "terra1yxgq5y6mw30xy9mmvz9mllneddy9jaxndrphvk", terra.PRISM, terra.CLUNA, terra.PRISM_PRISMCLUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PRISMCLUNA pair")
|
||||
}
|
||||
PRISMYLUNA, err := NewPair(querier, "terra1kqc65n5060rtvcgcktsxycdt2a4r67q2zlvhce", terra.PRISM, terra.YLUNA, terra.PRISM_PRISMYLUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PRISMYLUNA pair")
|
||||
}
|
||||
|
||||
r.SetPairs(
|
||||
PRISMUST,
|
||||
PRISMLUNA,
|
||||
PRISMPLUNA,
|
||||
PRISMXPRISM,
|
||||
PRISMCLUNA,
|
||||
PRISMYLUNA,
|
||||
)
|
||||
|
||||
return &Router{
|
||||
BaseRouter: r,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type operation struct {
|
||||
Swap struct {
|
||||
OfferAssetInfo terra.AssetInfo `json:"offer_asset_info"`
|
||||
AskAssetInfo terra.AssetInfo `json:"ask_asset_info"`
|
||||
} `json:"prism_swap"`
|
||||
}
|
||||
|
||||
func newOperation(aiFactory terra.AssetInfoFactory, offer terra.Token, ask terra.Token) interface{} {
|
||||
var res operation
|
||||
res.Swap.OfferAssetInfo = aiFactory.NewFromToken(offer)
|
||||
res.Swap.AskAssetInfo = aiFactory.NewFromToken(ask)
|
||||
return res
|
||||
}
|
||||
|
||||
func (r Router) String() string {
|
||||
return "prism"
|
||||
}
|
78
protocols/prism/yluna_staking.go
Normal file
78
protocols/prism/yluna_staking.go
Normal file
@ -0,0 +1,78 @@
|
||||
package prism
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type YLUNAStaking struct {
|
||||
*terra.Contract
|
||||
}
|
||||
|
||||
func NewYLUNAStaking(querier *terra.Querier) (*YLUNAStaking, error) {
|
||||
contract, err := terra.NewContract(querier, "terra1p7jp8vlt57cf8qwazjg58qngwvarmszsamzaru")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init contract object")
|
||||
}
|
||||
return &YLUNAStaking{
|
||||
Contract: contract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type RewardInfo struct {
|
||||
StakedAmount decimal.Decimal
|
||||
Rewards map[string]decimal.Decimal
|
||||
}
|
||||
|
||||
func (y *YLUNAStaking) GetRewardInfo(ctx context.Context, stakerAddress cosmos.AccAddress) (RewardInfo, error) {
|
||||
type query struct {
|
||||
RewardInfo struct {
|
||||
StakerAddr string `json:"staker_addr"`
|
||||
} `json:"reward_info"`
|
||||
}
|
||||
type response struct {
|
||||
StakerAddr string `json:"staker_addr"`
|
||||
StakedAmount decimal.Decimal `json:"staked_amount"`
|
||||
Rewards []struct {
|
||||
Info struct {
|
||||
Cw20 string `json:"cw20"`
|
||||
} `json:"info"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
} `json:"rewards"`
|
||||
}
|
||||
var q query
|
||||
q.RewardInfo.StakerAddr = stakerAddress.String()
|
||||
var r response
|
||||
err := y.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return RewardInfo{}, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
res := RewardInfo{
|
||||
StakedAmount: terra.YLUNA.ValueFromTerra(r.StakedAmount),
|
||||
Rewards: make(map[string]decimal.Decimal),
|
||||
}
|
||||
for _, reward := range r.Rewards {
|
||||
token, err := terra.Cw20TokenFromAddress(ctx, y.Contract.Querier(), reward.Info.Cw20)
|
||||
if err != nil {
|
||||
return RewardInfo{}, errors.Wrapf(err, "getting token %s", reward.Info.Cw20)
|
||||
}
|
||||
res.Rewards[reward.Info.Cw20] = token.ValueFromTerra(reward.Amount)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (y *YLUNAStaking) NewClaimAndConvertRewardMessage(sender cosmos.AccAddress, claimToken terra.Cw20Token) (cosmos.Msg, error) {
|
||||
var q struct {
|
||||
ConvertAndClaimRewards struct {
|
||||
ClaimAsset struct {
|
||||
Cw20 string `json:"cw20"`
|
||||
} `json:"claim_asset"`
|
||||
} `json:"convert_and_claim_rewards"`
|
||||
}
|
||||
q.ConvertAndClaimRewards.ClaimAsset.Cw20 = claimToken.Address().String()
|
||||
return y.NewMsgExecuteContract(sender, q)
|
||||
}
|
20
protocols/terraswap/pair.go
Normal file
20
protocols/terraswap/pair.go
Normal file
@ -0,0 +1,20 @@
|
||||
package terraswap
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Pair struct {
|
||||
*terra.BasePair
|
||||
}
|
||||
|
||||
func NewPair(querier *terra.Querier, contractAddress string, token1 terra.Token, token2 terra.Token, lpToken terra.Cw20Token) (*Pair, error) {
|
||||
bp, err := terra.NewBasePair(querier, contractAddress, token1, token2, lpToken, decimal.NewFromFloat(0.003), terra.NewAssetInfoFactory())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Pair{
|
||||
bp,
|
||||
}, nil
|
||||
}
|
252
protocols/terraswap/router.go
Normal file
252
protocols/terraswap/router.go
Normal file
@ -0,0 +1,252 @@
|
||||
package terraswap
|
||||
|
||||
import (
|
||||
"github.com/galacticship/terra"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
*terra.BaseRouter
|
||||
}
|
||||
|
||||
func NewRouter(querier *terra.Querier) (terra.Router, error) {
|
||||
r, err := terra.NewBaseRouter(querier, "terra19qx5xe6q9ll4w0890ux7lv2p4mf3csd4qvt3ex", terra.NewAssetInfoFactory(), newOperation)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating base router")
|
||||
}
|
||||
|
||||
LUNAUST, err := NewPair(querier, "terra1tndcaqxkpc5ce9qee5ggqf430mr2z3pefe5wj6", terra.LUNA, terra.UST, terra.TERRASWAP_LUNAUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LUNAUST pair")
|
||||
}
|
||||
BLUNALUNA, err := NewPair(querier, "terra1jxazgm67et0ce260kvrpfv50acuushpjsz2y0p", terra.BLUNA, terra.LUNA, terra.TERRASWAP_BLUNALUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init BLUNALUNA pair")
|
||||
}
|
||||
LUNALUNAX, err := NewPair(querier, "terra1zrzy688j8g6446jzd88vzjzqtywh6xavww92hy", terra.LUNAX, terra.LUNA, terra.TERRASWAP_LUNAXLUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LUNALUNAX pair")
|
||||
}
|
||||
LUNAXBLUNA, err := NewPair(querier, "terra1x8h5gan6vey5cz2xfyst74mtqsj7746fqj2hze", terra.LUNAX, terra.BLUNA, terra.TERRASWAP_LUNAXBLUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LUNAXBLUNA pair")
|
||||
}
|
||||
LUNAXUST, err := NewPair(querier, "terra1llhpkqd5enjfflt27u3jx0jcp5pdn6s9lfadx3", terra.LUNAX, terra.UST, terra.TERRASWAP_LUNAXUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LUNAXUST pair")
|
||||
}
|
||||
BLUNAUST, err := NewPair(querier, "terra1qpd9n7afwf45rkjlpujrrdfh83pldec8rpujgn", terra.BLUNA, terra.UST, terra.TERRASWAP_BLUNAUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init BLUNAUST pair")
|
||||
}
|
||||
KUJIUST, err := NewPair(querier, "terra1zkyrfyq7x9v5vqnnrznn3kvj35az4f6jxftrl2", terra.KUJI, terra.UST, terra.TERRASWAP_KUJIUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init KUJIUST pair")
|
||||
}
|
||||
PLUNAUST, err := NewPair(querier, "terra1hngzkju6egu78eyzzw2fn8el9dnjk3rr704z2f", terra.PLUNA, terra.UST, terra.TERRASWAP_PLUNAUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PLUNAUST pair")
|
||||
}
|
||||
STLUNAUST, err := NewPair(querier, "terra1de8xa55xm83s3ke0s20fc5pxy7p3cpndmmm7zk", terra.STLUNA, terra.UST, terra.TERRASWAP_STLUNAUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init STLUNAUST pair")
|
||||
}
|
||||
ANCUST, err := NewPair(querier, "terra1gm5p3ner9x9xpwugn9sp6gvhd0lwrtkyrecdn3", terra.ANC, terra.UST, terra.TERRASWAP_ANCUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ANCUST pair")
|
||||
}
|
||||
MIRUST, err := NewPair(querier, "terra1amv303y8kzxuegvurh0gug2xe9wkgj65enq2ux", terra.MIR, terra.UST, terra.TERRASWAP_MIRUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init MIRUST pair")
|
||||
}
|
||||
LOOPUST, err := NewPair(querier, "terra10k7y9qw63tfwj7e3x4uuzru2u9kvtd4ureajhd", terra.LOOP, terra.UST, terra.TERRASWAP_LOOPUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LOOPUST pair")
|
||||
}
|
||||
LOOPRUST, err := NewPair(querier, "terra18raj59xx32kuz66sfg82kqta6q0aslfs3m8s4r", terra.LOOPR, terra.UST, terra.TERRASWAP_LOOPRUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LOOPRUST pair")
|
||||
}
|
||||
MINEUST, err := NewPair(querier, "terra178jydtjvj4gw8earkgnqc80c3hrmqj4kw2welz", terra.MINE, terra.UST, terra.TERRASWAP_MINEUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init MINEUST pair")
|
||||
}
|
||||
SKUJIKUJI, err := NewPair(querier, "terra1g8kjs70d5r68j9507s3gwymzc30yaur5j2ccfr", terra.SKUJI, terra.KUJI, terra.TERRASWAP_SKUJIKUJILP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init SKUJIKUJI pair")
|
||||
}
|
||||
MARSUST, err := NewPair(querier, "terra15sut89ms4lts4dd5yrcuwcpctlep3hdgeu729f", terra.MARS, terra.UST, terra.TERRASWAP_MARSUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init MARSUST pair")
|
||||
}
|
||||
PRISMXPRISM, err := NewPair(querier, "terra1urt608par6rkcancsjzm76472phptfwq397gpm", terra.PRISM, terra.XPRISM, terra.TERRASWAP_PRISMXPRISMLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PRISMXPRISM pair")
|
||||
}
|
||||
PRISMUST, err := NewPair(querier, "terra1ag6fqvxz33nqg78830k5c27n32mmqlcrcgqejl", terra.PRISM, terra.UST, terra.TERRASWAP_PRISMUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PRISMUST pair")
|
||||
}
|
||||
CLUNALUNA, err := NewPair(querier, "terra1ejyqwcemr5kda5pxwz27t2ja784j3d0nj0v6lh", terra.CLUNA, terra.LUNA, terra.TERRASWAP_CLUNALUNALP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init CLUNALUNA pair")
|
||||
}
|
||||
ASTROUST, err := NewPair(querier, "terra1pufczag48fwqhsmekfullmyu02f93flvfc9a25", terra.ASTRO, terra.UST, terra.TERRASWAP_ASTROUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ASTROUST pair")
|
||||
}
|
||||
AUSTUST, err := NewPair(querier, "terra1z50zu7j39s2dls8k9xqyxc89305up0w7f7ec3n", terra.AUST, terra.UST, terra.TERRASWAP_AUSTUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init AUSTUST pair")
|
||||
}
|
||||
AUSTVUST, err := NewPair(querier, "terra1gkdudgg2a5wt70cneyx5rtehjls4dvhhcmlptv", terra.AUST, terra.VUST, terra.TERRASWAP_AUSTVUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init AUSTVUST pair")
|
||||
}
|
||||
WHALEVUST, err := NewPair(querier, "terra12arl49w7t4xpq7krtv43t3dg6g8kn2xxyaav56", terra.WHALE, terra.VUST, terra.TERRASWAP_WHALEVUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init WHALEVUST pair")
|
||||
}
|
||||
BETHUST, err := NewPair(querier, "terra1c0afrdc5253tkp5wt7rxhuj42xwyf2lcre0s7c", terra.BETH, terra.UST, terra.TERRASWAP_BETHUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init BETHUST pair")
|
||||
}
|
||||
WHALEUST, err := NewPair(querier, "terra1v4kpj65uq63m4x0mqzntzm27ecpactt42nyp5c", terra.WHALE, terra.UST, terra.TERRASWAP_WHALEUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init WHALEUST pair")
|
||||
}
|
||||
SPECUST, err := NewPair(querier, "terra1tn8ejzw8kpuc87nu42f6qeyen4c7qy35tl8t20", terra.SPEC, terra.UST, terra.TERRASWAP_SPECUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init SPECUST pair")
|
||||
}
|
||||
STTUST, err := NewPair(querier, "terra19pg6d7rrndg4z4t0jhcd7z9nhl3p5ygqttxjll", terra.STT, terra.UST, terra.TERRASWAP_STTUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init STTUST pair")
|
||||
}
|
||||
TWDUST, err := NewPair(querier, "terra1etdkg9p0fkl8zal6ecp98kypd32q8k3ryced9d", terra.TWD, terra.UST, terra.TERRASWAP_TWDUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init TWDUST pair")
|
||||
}
|
||||
PSIUST, err := NewPair(querier, "terra163pkeeuwxzr0yhndf8xd2jprm9hrtk59xf7nqf", terra.PSI, terra.UST, terra.TERRASWAP_PSIUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PSIUST pair")
|
||||
}
|
||||
PLYUST, err := NewPair(querier, "terra19fjaurx28dq4wgnf9fv3qg0lwldcln3jqafzm6", terra.PLY, terra.UST, terra.TERRASWAP_PLYUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init PLYUST pair")
|
||||
}
|
||||
LOTAUST, err := NewPair(querier, "terra1pn20mcwnmeyxf68vpt3cyel3n57qm9mp289jta", terra.LOTA, terra.UST, terra.TERRASWAP_LOTAUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LOTAUST pair")
|
||||
}
|
||||
APOLLOUST, err := NewPair(querier, "terra1xj2w7w8mx6m2nueczgsxy2gnmujwejjeu2xf78", terra.APOLLO, terra.UST, terra.TERRASWAP_APOLLOUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init APOLLOUST pair")
|
||||
}
|
||||
VKRUST, err := NewPair(querier, "terra1e59utusv5rspqsu8t37h5w887d9rdykljedxw0", terra.VKR, terra.UST, terra.TERRASWAP_VKRUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init VKRUST pair")
|
||||
}
|
||||
ORIONUST, err := NewPair(querier, "terra1z6tp0ruxvynsx5r9mmcc2wcezz9ey9pmrw5r8g", terra.ORION, terra.UST, terra.TERRASWAP_ORIONUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ORIONUST pair")
|
||||
}
|
||||
ATLOUST, err := NewPair(querier, "terra1ycp5lnn0qu4sq4wq7k63zax9f05852xt9nu3yc", terra.ATLO, terra.UST, terra.TERRASWAP_ATLOUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ATLOUST pair")
|
||||
}
|
||||
GLOWUST, err := NewPair(querier, "terra1p44kn7l233p7gcj0v3mzury8k7cwf4zt6gsxs5", terra.GLOW, terra.UST, terra.TERRASWAP_GLOWUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init GLOWUST pair")
|
||||
}
|
||||
TNSUST, err := NewPair(querier, "terra1hqnk9expq3k4la2ruzdnyapgndntec4fztdyln", terra.TNS, terra.UST, terra.TERRASWAP_TNSUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init TNSUST pair")
|
||||
}
|
||||
LUVUST, err := NewPair(querier, "terra1hmcd4kwafyydd4mjv2rzhcuuwnfuqc2prkmlhj", terra.LUV, terra.UST, terra.TERRASWAP_LUVUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init LUVUST pair")
|
||||
}
|
||||
ROBOUST, err := NewPair(querier, "terra1sprg4sv9dwnk78ahxdw78asslj8upyv9lerjhm", terra.ROBO, terra.UST, terra.TERRASWAP_ROBOUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ROBOUST pair")
|
||||
}
|
||||
XSDWHSD, err := NewPair(querier, "terra1cmehvqwvglg08clmqn66zfuv5cuxgxwrt3jz2u", terra.XSD, terra.WHSD, terra.TERRASWAP_XSDWHSDLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init XSDWHSD pair")
|
||||
}
|
||||
WHSDUST, err := NewPair(querier, "terra1upuslwv5twc8l7hrwlka4wju9z97q8ju63a6jt", terra.WHSD, terra.UST, terra.TERRASWAP_WHSDUSTLP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init WHSDUST pair")
|
||||
}
|
||||
NLUNAPSI, err := NewPair(querier, "terra1zvn8z6y8u2ndwvsjhtpsjsghk6pa6ugwzxp6vx", terra.NLUNA, terra.PSI, terra.TERRASWAP_NLUNAPSILP)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init NLUNAPSI pair")
|
||||
}
|
||||
|
||||
r.SetPairs(
|
||||
LUNAUST,
|
||||
BLUNALUNA,
|
||||
LUNALUNAX,
|
||||
LUNAXBLUNA,
|
||||
LUNAXUST,
|
||||
BLUNAUST,
|
||||
KUJIUST,
|
||||
PLUNAUST,
|
||||
STLUNAUST,
|
||||
ANCUST,
|
||||
MIRUST,
|
||||
LOOPUST,
|
||||
LOOPRUST,
|
||||
MINEUST,
|
||||
SKUJIKUJI,
|
||||
MARSUST,
|
||||
PRISMXPRISM,
|
||||
CLUNALUNA,
|
||||
ASTROUST,
|
||||
AUSTUST,
|
||||
AUSTVUST,
|
||||
WHALEVUST,
|
||||
BETHUST,
|
||||
WHALEUST,
|
||||
SPECUST,
|
||||
STTUST,
|
||||
TWDUST,
|
||||
PSIUST,
|
||||
PLYUST,
|
||||
LOTAUST,
|
||||
APOLLOUST,
|
||||
VKRUST,
|
||||
ORIONUST,
|
||||
ATLOUST,
|
||||
GLOWUST,
|
||||
TNSUST,
|
||||
LUVUST,
|
||||
ROBOUST,
|
||||
XSDWHSD,
|
||||
WHSDUST,
|
||||
PRISMUST,
|
||||
NLUNAPSI,
|
||||
)
|
||||
|
||||
return &Router{r}, nil
|
||||
}
|
||||
|
||||
type swap struct {
|
||||
OfferAssetInfo terra.AssetInfo `json:"offer_asset_info"`
|
||||
AskAssetInfo terra.AssetInfo `json:"ask_asset_info"`
|
||||
}
|
||||
type operation struct {
|
||||
Swap swap `json:"terra_swap"`
|
||||
}
|
||||
|
||||
func newOperation(aiFactory terra.AssetInfoFactory, offer terra.Token, ask terra.Token) interface{} {
|
||||
var res operation
|
||||
res.Swap.OfferAssetInfo = aiFactory.NewFromToken(offer)
|
||||
res.Swap.AskAssetInfo = aiFactory.NewFromToken(ask)
|
||||
return res
|
||||
}
|
||||
|
||||
func (r Router) String() string {
|
||||
return "terraswap"
|
||||
}
|
160
querier.go
Normal file
160
querier.go
Normal file
@ -0,0 +1,160 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
terraappparams "github.com/terra-money/core/app/params"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
)
|
||||
|
||||
type Querier struct {
|
||||
url string
|
||||
httpClient *http.Client
|
||||
encodingConfig terraappparams.EncodingConfig
|
||||
chainId string
|
||||
}
|
||||
|
||||
type QuerierOption func(q *Querier) *Querier
|
||||
|
||||
func WithChainId(chainId string) QuerierOption {
|
||||
return func(q *Querier) *Querier {
|
||||
q.chainId = chainId
|
||||
return q
|
||||
}
|
||||
}
|
||||
|
||||
func NewQuerier(httpClient *http.Client, url string) *Querier {
|
||||
return &Querier{
|
||||
url: url,
|
||||
httpClient: httpClient,
|
||||
encodingConfig: terraappparams.MakeEncodingConfig(),
|
||||
chainId: "columbus-5",
|
||||
}
|
||||
}
|
||||
|
||||
func (q Querier) ChainId() string {
|
||||
return q.chainId
|
||||
}
|
||||
|
||||
func (q Querier) POST(ctx context.Context, method string, payload interface{}, result interface{}) error {
|
||||
u, err := url.Parse(q.url)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "parsing lcd URL %s", q.url)
|
||||
}
|
||||
u.Path = path.Join(u.Path, method)
|
||||
|
||||
reqBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling payload to json")
|
||||
}
|
||||
|
||||
resp, err := ctxhttp.Post(ctx, q.httpClient, u.String(), "application/json", bytes.NewBuffer(reqBytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "executing http post request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
out, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading response")
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("non-200 response code %d: %s", resp.StatusCode, string(out))
|
||||
}
|
||||
if result != nil {
|
||||
err = json.Unmarshal(out, &result)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unmarshalling response from json")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q Querier) POSTProto(ctx context.Context, method string, payload proto.Message, result proto.Message) error {
|
||||
u, err := url.Parse(q.url)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "parsing lcd URL %s", q.url)
|
||||
}
|
||||
u.Path = path.Join(u.Path, method)
|
||||
|
||||
reqBytes, err := q.encodingConfig.Marshaler.MarshalJSON(payload)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling payload to json")
|
||||
}
|
||||
|
||||
resp, err := ctxhttp.Post(ctx, q.httpClient, u.String(), "application/json", bytes.NewBuffer(reqBytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "executing http post request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
out, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading response")
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("non-200 response code %d: %s", resp.StatusCode, string(out))
|
||||
}
|
||||
if result != nil {
|
||||
err = q.encodingConfig.Marshaler.UnmarshalJSON(out, result)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unmarshalling response from json")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q Querier) GET(ctx context.Context, method string, params url.Values, res interface{}) error {
|
||||
u, err := url.Parse(q.url)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "parsing lcd URL %s", q.url)
|
||||
}
|
||||
u.Path = path.Join(u.Path, method)
|
||||
if params != nil {
|
||||
u.RawQuery = params.Encode()
|
||||
}
|
||||
resp, err := ctxhttp.Get(ctx, q.httpClient, u.String())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "http requesting %s", u.String())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
out, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading response")
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("non-200 response code %d: %s", resp.StatusCode, string(out))
|
||||
}
|
||||
if res != nil {
|
||||
err = json.Unmarshal(out, &res)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unmarshalling response from json")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Querier) LatestBlockInfo(ctx context.Context) (int64, time.Time, error) {
|
||||
var res struct {
|
||||
Block struct {
|
||||
Header struct {
|
||||
Height int64 `json:"height,string"`
|
||||
Time time.Time `json:"time"`
|
||||
} `json:"header"`
|
||||
} `json:"block"`
|
||||
}
|
||||
err := q.GET(ctx, "blocks/latest", nil, &res)
|
||||
if err != nil {
|
||||
return 0, time.Time{}, errors.Wrap(err, "executing get request")
|
||||
}
|
||||
return res.Block.Header.Height, res.Block.Header.Time, nil
|
||||
}
|
141
route.go
Normal file
141
route.go
Normal file
@ -0,0 +1,141 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type RoutePair struct {
|
||||
Pair Pair
|
||||
IsToken1First bool
|
||||
}
|
||||
|
||||
func NewRoutePair(pair Pair, token1first bool) RoutePair {
|
||||
return RoutePair{
|
||||
Pair: pair,
|
||||
IsToken1First: token1first,
|
||||
}
|
||||
}
|
||||
|
||||
func (p RoutePair) FirstToken() Token {
|
||||
if p.IsToken1First {
|
||||
return p.Pair.Token1()
|
||||
} else {
|
||||
return p.Pair.Token2()
|
||||
}
|
||||
}
|
||||
|
||||
func (p RoutePair) SecondToken() Token {
|
||||
if p.IsToken1First {
|
||||
return p.Pair.Token2()
|
||||
} else {
|
||||
return p.Pair.Token1()
|
||||
}
|
||||
}
|
||||
|
||||
type Route []RoutePair
|
||||
|
||||
func NewRoute(pairs ...RoutePair) Route {
|
||||
return Route(pairs)
|
||||
}
|
||||
|
||||
func (r Route) Last() RoutePair {
|
||||
return r[len(r)-1]
|
||||
}
|
||||
func (r Route) First() RoutePair {
|
||||
return r[0]
|
||||
}
|
||||
|
||||
func (r Route) Contains(pair Pair) bool {
|
||||
for _, t := range r {
|
||||
if t.Pair.Equals(pair) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r Route) Copy() Route {
|
||||
res := make(Route, len(r))
|
||||
copy(res, r)
|
||||
return res
|
||||
}
|
||||
|
||||
func (r Route) String() string {
|
||||
var res string
|
||||
for _, pair := range r {
|
||||
if pair.IsToken1First {
|
||||
res += fmt.Sprintf("[%s-%s] ", pair.Pair.Token1().Symbol(), pair.Pair.Token2().Symbol())
|
||||
} else {
|
||||
res += fmt.Sprintf("[%s-%s] ", pair.Pair.Token2().Symbol(), pair.Pair.Token1().Symbol())
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r Route) OfferToken() Token {
|
||||
if r.First().IsToken1First {
|
||||
return r.First().Pair.Token1()
|
||||
} else {
|
||||
return r.First().Pair.Token2()
|
||||
}
|
||||
}
|
||||
|
||||
func (r Route) AskToken() Token {
|
||||
if r.Last().IsToken1First {
|
||||
return r.Last().Pair.Token2()
|
||||
} else {
|
||||
return r.Last().Pair.Token1()
|
||||
}
|
||||
}
|
||||
|
||||
func (r Route) CopyAndAdd(pair RoutePair) Route {
|
||||
return append(r.Copy(), pair)
|
||||
}
|
||||
|
||||
func (r Route) SimulateSwap(offerAmount decimal.Decimal, pools map[Pair]PoolInfo) decimal.Decimal {
|
||||
current := r.OfferToken().ValueToTerra(offerAmount)
|
||||
for _, pair := range r {
|
||||
pool := pools[pair.Pair]
|
||||
ra, _, _, _ := ComputeConstantProductSwap(pair.FirstToken().ValueToTerra(pool[pair.FirstToken().Id()]), pair.SecondToken().ValueToTerra(pool[pair.SecondToken().Id()]), current, pair.Pair.CommissionRate())
|
||||
current = ra
|
||||
}
|
||||
return r.AskToken().ValueFromTerra(current)
|
||||
}
|
||||
|
||||
func (r Route) GenerateArbitrageMessages(sender cosmos.AccAddress, offerAmount decimal.Decimal, pools map[Pair]PoolInfo) ([]cosmos.Msg, error) {
|
||||
current := r.OfferToken().ValueToTerra(offerAmount)
|
||||
var msgs []cosmos.Msg
|
||||
for _, pair := range r {
|
||||
pool := pools[pair.Pair]
|
||||
ra, _, _, bp := ComputeConstantProductSwap(pair.FirstToken().ValueToTerra(pool[pair.FirstToken().Id()]), pair.SecondToken().ValueToTerra(pool[pair.SecondToken().Id()]), current, pair.Pair.CommissionRate())
|
||||
newmsg, err := pair.Pair.NewSwapMessage(sender, pair.FirstToken(), current, "0.0", bp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "generating message for pair %s", pair.Pair)
|
||||
}
|
||||
msgs = append(msgs, newmsg)
|
||||
current = ra
|
||||
}
|
||||
return msgs, nil
|
||||
}
|
||||
|
||||
func (r Route) Pairs() []Pair {
|
||||
var res []Pair
|
||||
for _, pair := range r {
|
||||
contains := false
|
||||
for _, re := range res {
|
||||
if re.Equals(pair.Pair) {
|
||||
contains = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if contains {
|
||||
continue
|
||||
}
|
||||
res = append(res, pair.Pair)
|
||||
}
|
||||
return res
|
||||
}
|
75
route_service.go
Normal file
75
route_service.go
Normal file
@ -0,0 +1,75 @@
|
||||
package terra
|
||||
|
||||
type RouteService interface {
|
||||
RegisterPairs(pairs ...Pair)
|
||||
FindRoutes(offer Token, ask Token, maxDepth int) []Route
|
||||
FindArbitrages(token Token, maxDepth int) []Route
|
||||
GetAllArbitrages(maxDepth int) []Route
|
||||
}
|
||||
|
||||
type routeService struct {
|
||||
pairs []Pair
|
||||
}
|
||||
|
||||
func NewRouteService() RouteService {
|
||||
return &routeService{}
|
||||
}
|
||||
|
||||
func (s *routeService) RegisterPairs(pairs ...Pair) {
|
||||
s.pairs = append(s.pairs, pairs...)
|
||||
}
|
||||
|
||||
func (s *routeService) walkRoute(route Route, ask Token, depth, maxdepth int) []Route {
|
||||
var res []Route
|
||||
depth++
|
||||
if depth > maxdepth {
|
||||
return res
|
||||
}
|
||||
if route.AskToken().Equals(ask) {
|
||||
res = append(res, route)
|
||||
return res
|
||||
}
|
||||
for _, pair := range s.pairs {
|
||||
if route.Contains(pair) {
|
||||
continue
|
||||
}
|
||||
var newroute Route
|
||||
if route.Last().SecondToken().Equals(pair.Token1()) {
|
||||
newroute = route.CopyAndAdd(NewRoutePair(pair, true))
|
||||
}
|
||||
if route.Last().SecondToken().Equals(pair.Token2()) {
|
||||
newroute = route.CopyAndAdd(NewRoutePair(pair, false))
|
||||
}
|
||||
if newroute == nil {
|
||||
continue
|
||||
}
|
||||
res = append(res, s.walkRoute(newroute, ask, depth, maxdepth)...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *routeService) FindRoutes(offer Token, ask Token, maxDepth int) []Route {
|
||||
var res []Route
|
||||
for _, pair := range s.pairs {
|
||||
if pair.Token1().Equals(offer) {
|
||||
res = append(res, s.walkRoute(NewRoute(NewRoutePair(pair, true)), ask, 0, maxDepth)...)
|
||||
}
|
||||
if pair.Token2().Equals(offer) {
|
||||
res = append(res, s.walkRoute(NewRoute(NewRoutePair(pair, false)), ask, 0, maxDepth)...)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *routeService) FindArbitrages(token Token, maxDepth int) []Route {
|
||||
return s.FindRoutes(token, token, maxDepth)
|
||||
}
|
||||
|
||||
func (s *routeService) GetAllArbitrages(maxDepth int) []Route {
|
||||
var res []Route
|
||||
for _, pair := range s.pairs {
|
||||
res = append(res, s.walkRoute(NewRoute(NewRoutePair(pair, true)), pair.Token1(), 0, maxDepth)...)
|
||||
res = append(res, s.walkRoute(NewRoute(NewRoutePair(pair, false)), pair.Token2(), 0, maxDepth)...)
|
||||
}
|
||||
return res
|
||||
}
|
138
router.go
Normal file
138
router.go
Normal file
@ -0,0 +1,138 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
String() string
|
||||
SimulateSwapWithRoute(ctx context.Context, amount decimal.Decimal, route Route) (decimal.Decimal, error)
|
||||
SimulateSwap(ctx context.Context, offer Token, ask Token, amount decimal.Decimal, maxRouteLength int) (decimal.Decimal, Route, error)
|
||||
FindAllRoutes(offer Token, ask Token, maxLength int) []Route
|
||||
|
||||
NewSwapMessageWithRoute(sender cosmos.AccAddress, route Route, offerAmount decimal.Decimal, askExpectedAmount decimal.Decimal, maxSpread float64) (cosmos.Msg, error)
|
||||
NewSwapMessageWithBestRoute(ctx context.Context, sender cosmos.AccAddress, offer Token, ask Token, offerAmount decimal.Decimal, maxRouteLength int, maxSpread float64) (cosmos.Msg, error)
|
||||
}
|
||||
|
||||
type BaseRouter struct {
|
||||
*Contract
|
||||
routeService RouteService
|
||||
|
||||
operationFactory func(aiFactory AssetInfoFactory, offer Token, ask Token) interface{}
|
||||
aiFactory AssetInfoFactory
|
||||
}
|
||||
|
||||
func NewBaseRouter(querier *Querier, contractAddress string, aiFactory AssetInfoFactory, operationFactory func(aiFactory AssetInfoFactory, offer Token, ask Token) interface{}) (*BaseRouter, error) {
|
||||
contract, err := NewContract(querier, contractAddress)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating base contract")
|
||||
}
|
||||
return &BaseRouter{
|
||||
Contract: contract,
|
||||
routeService: NewRouteService(),
|
||||
operationFactory: operationFactory,
|
||||
aiFactory: aiFactory,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r BaseRouter) ContractAddress() cosmos.AccAddress {
|
||||
return r.contractAddress
|
||||
}
|
||||
|
||||
func (r *BaseRouter) SetPairs(pairs ...Pair) {
|
||||
r.routeService.RegisterPairs(pairs...)
|
||||
}
|
||||
|
||||
func (r BaseRouter) FindAllRoutes(offer Token, ask Token, maxLength int) []Route {
|
||||
return r.routeService.FindRoutes(offer, ask, maxLength)
|
||||
}
|
||||
|
||||
var ErrNoRouteFund = errors.New("no route found")
|
||||
|
||||
func (r BaseRouter) SimulateSwap(ctx context.Context, offer Token, ask Token, amount decimal.Decimal, maxRouteLength int) (decimal.Decimal, Route, error) {
|
||||
var resValue decimal.Decimal
|
||||
var resRoute Route
|
||||
routes := r.FindAllRoutes(offer, ask, maxRouteLength)
|
||||
for _, route := range routes {
|
||||
|
||||
tmpValue, err := r.SimulateSwapWithRoute(ctx, amount, route)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if resValue.LessThan(tmpValue) || (resValue.Equals(tmpValue) && len(resRoute) > len(route)) {
|
||||
resValue = tmpValue
|
||||
resRoute = route
|
||||
}
|
||||
}
|
||||
if resRoute == nil {
|
||||
return decimal.Zero, nil, ErrNoRouteFund
|
||||
}
|
||||
return resValue, resRoute, nil
|
||||
}
|
||||
|
||||
func (r BaseRouter) SimulateSwapWithRoute(ctx context.Context, amount decimal.Decimal, route Route) (decimal.Decimal, error) {
|
||||
|
||||
if len(route) < 1 {
|
||||
return decimal.Zero, errors.Errorf("route length must be greater than 1")
|
||||
}
|
||||
type query struct {
|
||||
SimulateSwapOperations struct {
|
||||
OfferAmount decimal.Decimal `json:"offer_amount"`
|
||||
Operations []interface{} `json:"operations"`
|
||||
} `json:"simulate_swap_operations"`
|
||||
}
|
||||
var q query
|
||||
q.SimulateSwapOperations.OfferAmount = route[0].FirstToken().ValueToTerra(amount)
|
||||
for _, pair := range route {
|
||||
q.SimulateSwapOperations.Operations = append(q.SimulateSwapOperations.Operations,
|
||||
r.operationFactory(r.aiFactory, pair.FirstToken(), pair.SecondToken()))
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
}
|
||||
var resp response
|
||||
err := r.QueryStore(ctx, q, &resp)
|
||||
if err != nil {
|
||||
return decimal.Zero, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
|
||||
return route[len(route)-1].SecondToken().ValueFromTerra(resp.Amount), nil
|
||||
}
|
||||
|
||||
func (r BaseRouter) NewSwapMessageWithRoute(sender cosmos.AccAddress, route Route, offerAmount decimal.Decimal, askExpectedAmount decimal.Decimal, maxSpread float64) (cosmos.Msg, error) {
|
||||
askExpectedAmount = route.AskToken().ValueToTerra(askExpectedAmount)
|
||||
minimumReceived := askExpectedAmount.Sub(askExpectedAmount.Mul(decimal.NewFromFloat(maxSpread).Div(decimal.NewFromInt(100)))).Truncate(0)
|
||||
|
||||
type query struct {
|
||||
ExecuteSwapOperations struct {
|
||||
OfferAmount decimal.Decimal `json:"offer_amount"`
|
||||
MinimumReceive decimal.Decimal `json:"minimum_receive"`
|
||||
Operations []interface{} `json:"operations"`
|
||||
} `json:"execute_swap_operations"`
|
||||
}
|
||||
var q query
|
||||
q.ExecuteSwapOperations.OfferAmount = route.OfferToken().ValueToTerra(offerAmount)
|
||||
q.ExecuteSwapOperations.MinimumReceive = minimumReceived
|
||||
for _, pair := range route {
|
||||
q.ExecuteSwapOperations.Operations = append(q.ExecuteSwapOperations.Operations,
|
||||
r.operationFactory(r.aiFactory, pair.FirstToken(), pair.SecondToken()))
|
||||
}
|
||||
res, err := route.OfferToken().NewMsgSendExecute(sender, r.Contract, offerAmount, q)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "generating MsgSendExecute")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (r BaseRouter) NewSwapMessageWithBestRoute(ctx context.Context, sender cosmos.AccAddress, offer Token, ask Token, offerAmount decimal.Decimal, maxRouteLength int, maxSpread float64) (cosmos.Msg, error) {
|
||||
askExpected, bestRoute, err := r.SimulateSwap(ctx, offer, ask, offerAmount, maxRouteLength)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "simulating swaps to find best route")
|
||||
}
|
||||
return r.NewSwapMessageWithRoute(sender, bestRoute, offerAmount, askExpected, maxSpread)
|
||||
}
|
17
terra/init.go
Normal file
17
terra/init.go
Normal file
@ -0,0 +1,17 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
core "github.com/terra-money/core/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sdkConfig := sdk.GetConfig()
|
||||
sdkConfig.SetCoinType(core.CoinType)
|
||||
sdkConfig.SetPurpose(44)
|
||||
sdkConfig.SetBech32PrefixForAccount(core.Bech32PrefixAccAddr, core.Bech32PrefixAccPub)
|
||||
sdkConfig.SetBech32PrefixForValidator(core.Bech32PrefixValAddr, core.Bech32PrefixValPub)
|
||||
sdkConfig.SetBech32PrefixForConsensusNode(core.Bech32PrefixConsAddr, core.Bech32PrefixConsPub)
|
||||
sdkConfig.SetAddressVerifier(core.AddressVerifier)
|
||||
sdkConfig.Seal()
|
||||
}
|
35
terra/msg.go
Normal file
35
terra/msg.go
Normal file
@ -0,0 +1,35 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
markettypes "github.com/terra-money/core/x/market/types"
|
||||
wasmtypes "github.com/terra-money/core/x/wasm/types"
|
||||
)
|
||||
|
||||
type (
|
||||
MsgSwap = markettypes.MsgSwap
|
||||
MsgSwapSend = markettypes.MsgSwapSend
|
||||
MsgStoreCode = wasmtypes.MsgStoreCode
|
||||
MsgMigrateCode = wasmtypes.MsgMigrateCode
|
||||
MsgInstantiateContract = wasmtypes.MsgInstantiateContract
|
||||
MsgExecuteContract = wasmtypes.MsgExecuteContract
|
||||
MsgMigrateContract = wasmtypes.MsgMigrateContract
|
||||
)
|
||||
|
||||
var (
|
||||
NewMsgSwap = markettypes.NewMsgSwap
|
||||
NewMsgSwapSend = markettypes.NewMsgSwapSend
|
||||
NewMsgStoreCode = wasmtypes.NewMsgStoreCode
|
||||
NewMsgMigrateCode = wasmtypes.NewMsgMigrateCode
|
||||
NewMsgInstantiateContract = wasmtypes.NewMsgInstantiateContract
|
||||
NewMsgMigrateContract = wasmtypes.NewMsgMigrateContract
|
||||
NewMsgExecuteContract = func(sender cosmos.AccAddress, contract cosmos.AccAddress, execMsg interface{}, coins cosmos.Coins) (*MsgExecuteContract, error) {
|
||||
jsonq, err := json.Marshal(execMsg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wasmtypes.NewMsgExecuteContract(sender, contract, jsonq, coins), nil
|
||||
}
|
||||
)
|
5
terra/params.go
Normal file
5
terra/params.go
Normal file
@ -0,0 +1,5 @@
|
||||
package terra
|
||||
|
||||
import "github.com/terra-money/core/app/params"
|
||||
|
||||
var EncodingConfig = params.MakeEncodingConfig()
|
22
terra/tx.go
Normal file
22
terra/tx.go
Normal file
@ -0,0 +1,22 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
customauthtx "github.com/terra-money/core/custom/auth/tx"
|
||||
)
|
||||
|
||||
type (
|
||||
ComputeTaxRequest struct {
|
||||
customauthtx.ComputeTaxRequest
|
||||
}
|
||||
ComputeTaxResponse struct {
|
||||
customauthtx.ComputeTaxResponse
|
||||
}
|
||||
)
|
||||
|
||||
func NewComputeTaxRequest(txBytes []byte) *ComputeTaxRequest {
|
||||
return &ComputeTaxRequest{
|
||||
customauthtx.ComputeTaxRequest{
|
||||
TxBytes: txBytes,
|
||||
},
|
||||
}
|
||||
}
|
264
token.go
Normal file
264
token.go
Normal file
@ -0,0 +1,264 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/galacticship/terra/terra"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type tokenInfo struct {
|
||||
Name string `json:"name"`
|
||||
Symbol string `json:"symbol"`
|
||||
Decimals uint8 `json:"decimals"`
|
||||
TotalSupply decimal.Decimal `json:"total_supply"`
|
||||
}
|
||||
|
||||
type Token interface {
|
||||
Id() string
|
||||
Address() cosmos.AccAddress
|
||||
Symbol() string
|
||||
Decimals() uint8
|
||||
Balance(context.Context, *Querier, cosmos.AccAddress) (decimal.Decimal, error)
|
||||
|
||||
IsNative() bool
|
||||
|
||||
ValueFromTerra(value decimal.Decimal) decimal.Decimal
|
||||
ValueToTerra(value decimal.Decimal) decimal.Decimal
|
||||
|
||||
NewMsgSendExecute(sender cosmos.AccAddress, contract *Contract, amount decimal.Decimal, execMsg interface{}) (cosmos.Msg, error)
|
||||
|
||||
Equals(Token) bool
|
||||
|
||||
String() string
|
||||
}
|
||||
|
||||
type Cw20Token struct {
|
||||
address cosmos.AccAddress
|
||||
symbol string
|
||||
decimals uint8
|
||||
}
|
||||
|
||||
func NewCw20Token(contractAddress string, symbol string, decimals uint8) (Cw20Token, error) {
|
||||
accaddress, err := cosmos.AccAddressFromBech32(contractAddress)
|
||||
if err != nil {
|
||||
return Cw20Token{}, errors.Wrap(err, "validating token contract address")
|
||||
}
|
||||
return Cw20Token{
|
||||
address: accaddress,
|
||||
symbol: symbol,
|
||||
decimals: decimals,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func Cw20TokenFromAddress(ctx context.Context, querier *Querier, contractAddress string) (Cw20Token, error) {
|
||||
if t, ok := Cw20TokenMap[contractAddress]; ok {
|
||||
return t, nil
|
||||
}
|
||||
accaddress, err := cosmos.AccAddressFromBech32(contractAddress)
|
||||
if err != nil {
|
||||
return Cw20Token{}, errors.Wrap(err, "validating token contract address")
|
||||
}
|
||||
t := Cw20Token{
|
||||
address: accaddress,
|
||||
}
|
||||
ti, err := t.getTokenInfo(ctx, querier)
|
||||
if err != nil {
|
||||
return Cw20Token{}, errors.Wrap(err, "getting token info")
|
||||
}
|
||||
t.symbol = ti.Symbol
|
||||
t.decimals = ti.Decimals
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t Cw20Token) getTokenInfo(ctx context.Context, querier *Querier) (tokenInfo, error) {
|
||||
var ti tokenInfo
|
||||
query := struct {
|
||||
TokenInfo struct{} `json:"token_info"`
|
||||
}{}
|
||||
contract, err := NewContract(querier, t.address.String())
|
||||
if err != nil {
|
||||
return tokenInfo{}, errors.Wrap(err, "creating contract object")
|
||||
}
|
||||
err = contract.QueryStore(ctx, query, &ti)
|
||||
if err != nil {
|
||||
return tokenInfo{}, errors.Wrap(err, "calling token_info contract method")
|
||||
}
|
||||
return ti, nil
|
||||
}
|
||||
|
||||
func (t Cw20Token) Id() string {
|
||||
return t.Address().String()
|
||||
}
|
||||
|
||||
func (t Cw20Token) Decimals() uint8 {
|
||||
return t.decimals
|
||||
}
|
||||
|
||||
func (t Cw20Token) DecimalsAsInt32() int32 {
|
||||
return int32(t.decimals)
|
||||
}
|
||||
|
||||
func (t Cw20Token) Symbol() string {
|
||||
return t.symbol
|
||||
}
|
||||
|
||||
func (t Cw20Token) Address() cosmos.AccAddress {
|
||||
return t.address
|
||||
}
|
||||
|
||||
func (t Cw20Token) ValueFromTerra(value decimal.Decimal) decimal.Decimal {
|
||||
return value.Shift(-t.DecimalsAsInt32())
|
||||
}
|
||||
|
||||
func (t Cw20Token) ValueToTerra(value decimal.Decimal) decimal.Decimal {
|
||||
return value.Shift(t.DecimalsAsInt32())
|
||||
}
|
||||
|
||||
func (t Cw20Token) Balance(ctx context.Context, querier *Querier, address cosmos.AccAddress) (decimal.Decimal, error) {
|
||||
type query struct {
|
||||
Balance struct {
|
||||
Address string `json:"address"`
|
||||
} `json:"balance"`
|
||||
}
|
||||
type response struct {
|
||||
Balance decimal.Decimal `json:"balance"`
|
||||
}
|
||||
var q query
|
||||
q.Balance.Address = address.String()
|
||||
var r response
|
||||
contract, err := NewContract(querier, t.address.String())
|
||||
if err != nil {
|
||||
return decimal.Zero, errors.Wrap(err, "creating contract object")
|
||||
}
|
||||
err = contract.QueryStore(ctx, q, &r)
|
||||
if err != nil {
|
||||
return decimal.Zero, errors.Wrap(err, "querying contract store")
|
||||
}
|
||||
return t.ValueFromTerra(r.Balance), nil
|
||||
}
|
||||
|
||||
func (t Cw20Token) IsNative() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t Cw20Token) Equals(token Token) bool {
|
||||
if !token.IsNative() && t.symbol == token.Symbol() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t Cw20Token) String() string {
|
||||
return t.symbol
|
||||
}
|
||||
|
||||
func (t Cw20Token) NewMsgSendExecute(sender cosmos.AccAddress, contract *Contract, amount decimal.Decimal, execMsg interface{}) (cosmos.Msg, error) {
|
||||
jsonexecmsg, err := json.Marshal(execMsg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshalling execMsg")
|
||||
}
|
||||
type query struct {
|
||||
Send struct {
|
||||
Contract string `json:"contract"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
Message interface{} `json:"msg"`
|
||||
} `json:"send"`
|
||||
}
|
||||
var q query
|
||||
q.Send.Contract = contract.Address().String()
|
||||
q.Send.Amount = t.ValueToTerra(amount)
|
||||
q.Send.Message = jsonexecmsg
|
||||
return terra.NewMsgExecuteContract(sender, t.Address(), q, nil)
|
||||
}
|
||||
|
||||
type NativeToken struct {
|
||||
symbol string
|
||||
denom string
|
||||
}
|
||||
|
||||
func NewNativeToken(symbol string, denom string) NativeToken {
|
||||
return NativeToken{
|
||||
denom: denom,
|
||||
symbol: symbol,
|
||||
}
|
||||
}
|
||||
|
||||
func NativeTokenFromDenom(denom string) NativeToken {
|
||||
if t, ok := NativeTokenMap[denom]; ok {
|
||||
return t
|
||||
}
|
||||
return NewNativeToken("", denom)
|
||||
}
|
||||
|
||||
func (n NativeToken) Id() string {
|
||||
return n.denom
|
||||
}
|
||||
|
||||
func (n NativeToken) Address() cosmos.AccAddress {
|
||||
return cosmos.AccAddress{}
|
||||
}
|
||||
|
||||
func (n NativeToken) Symbol() string {
|
||||
return n.symbol
|
||||
}
|
||||
func (n NativeToken) Denom() string {
|
||||
return n.denom
|
||||
}
|
||||
|
||||
func (n NativeToken) Decimals() uint8 {
|
||||
return 6
|
||||
}
|
||||
|
||||
func (n NativeToken) Balance(ctx context.Context, querier *Querier, address cosmos.AccAddress) (decimal.Decimal, error) {
|
||||
var response struct {
|
||||
Balance struct {
|
||||
Denom string `json:"denom"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
} `json:"balance"`
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Set("denom", n.Denom())
|
||||
err := querier.GET(ctx, fmt.Sprintf("cosmos/bank/v1beta1/balances/%s/by_denom", address.String()), params, &response)
|
||||
if err != nil {
|
||||
return decimal.Zero, errors.Wrap(err, "executing get request")
|
||||
}
|
||||
return n.ValueFromTerra(response.Balance.Amount), nil
|
||||
}
|
||||
|
||||
func (n NativeToken) DecimalsAsInt32() int32 {
|
||||
return int32(n.Decimals())
|
||||
}
|
||||
|
||||
func (n NativeToken) ValueFromTerra(value decimal.Decimal) decimal.Decimal {
|
||||
return value.Shift(-n.DecimalsAsInt32())
|
||||
}
|
||||
|
||||
func (n NativeToken) ValueToTerra(value decimal.Decimal) decimal.Decimal {
|
||||
return value.Shift(n.DecimalsAsInt32())
|
||||
}
|
||||
|
||||
func (n NativeToken) IsNative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (n NativeToken) Equals(token Token) bool {
|
||||
if token.IsNative() && n.symbol == token.Symbol() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n NativeToken) String() string {
|
||||
return n.symbol
|
||||
}
|
||||
|
||||
func (n NativeToken) NewMsgSendExecute(sender cosmos.AccAddress, contract *Contract, amount decimal.Decimal, execMsg interface{}) (cosmos.Msg, error) {
|
||||
return terra.NewMsgExecuteContract(sender, contract.contractAddress, execMsg, cosmos.NewCoins(cosmos.NewInt64Coin(n.Id(), n.ValueToTerra(amount).IntPart())))
|
||||
}
|
170
tokens.go
Normal file
170
tokens.go
Normal file
@ -0,0 +1,170 @@
|
||||
package terra
|
||||
|
||||
var (
|
||||
LUNA = NewNativeToken("LUNA", "uluna")
|
||||
UST = NewNativeToken("UST", "uusd")
|
||||
)
|
||||
|
||||
var (
|
||||
SKUJI, _ = NewCw20Token("terra188w26t95tf4dz77raftme8p75rggatxjxfeknw", "sKUJI", 6)
|
||||
KUJI, _ = NewCw20Token("terra1xfsdgcemqwxp4hhnyk4rle6wr22sseq7j07dnn", "KUJI", 6)
|
||||
XPRISM, _ = NewCw20Token("terra1042wzrwg2uk6jqxjm34ysqquyr9esdgm5qyswz", "xPRISM", 6)
|
||||
YLUNA, _ = NewCw20Token("terra17wkadg0tah554r35x6wvff0y5s7ve8npcjfuhz", "yLUNA", 6)
|
||||
PLUNA, _ = NewCw20Token("terra1tlgelulz9pdkhls6uglfn5lmxarx7f2gxtdzh2", "pLUNA", 6)
|
||||
PRISM, _ = NewCw20Token("terra1dh9478k2qvqhqeajhn75a2a7dsnf74y5ukregw", "PRISM", 6)
|
||||
CLUNA, _ = NewCw20Token("terra13zaagrrrxj47qjwczsczujlvnnntde7fdt0mau", "cLUNA", 6)
|
||||
ASTRO, _ = NewCw20Token("terra1xj49zyqrwpv5k928jwfpfy2ha668nwdgkwlrg3", "ASTRO", 6)
|
||||
XASTRO, _ = NewCw20Token("terra1f68wt2ch3cx2g62dxtc8v68mkdh5wchdgdjwz7", "xASTRO", 6)
|
||||
APOLLO, _ = NewCw20Token("terra100yeqvww74h4yaejj6h733thgcafdaukjtw397", "APOLLO", 6)
|
||||
ANC, _ = NewCw20Token("terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", "ANC", 6)
|
||||
BLUNA, _ = NewCw20Token("terra1kc87mu460fwkqte29rquh4hc20m54fxwtsx7gp", "bLUNA", 6)
|
||||
AUST, _ = NewCw20Token("terra1hzh9vpxhsk8253se0vv5jj6etdvxu3nv8z07zu", "aUST", 6)
|
||||
BETH, _ = NewCw20Token("terra1dzhzukyezv0etz22ud940z7adyv7xgcjkahuun", "bETH", 6)
|
||||
MIR, _ = NewCw20Token("terra15gwkyepfc6xgca5t5zefzwy42uts8l2m4g40k6", "MIR", 6)
|
||||
MINE, _ = NewCw20Token("terra1kcthelkax4j9x8d3ny6sdag0qmxxynl3qtcrpy", "MINE", 6)
|
||||
STT, _ = NewCw20Token("terra13xujxcrc9dqft4p9a8ls0w3j0xnzm6y2uvve8n", "STT", 6)
|
||||
PSI, _ = NewCw20Token("terra12897djskt9rge8dtmm86w654g7kzckkd698608", "PSI", 6)
|
||||
VKR, _ = NewCw20Token("terra1dy9kmlm4anr92e42mrkjwzyvfqwz66un00rwr5", "VKR", 6)
|
||||
SPEC, _ = NewCw20Token("terra1s5eczhe0h0jutf46re52x5z4r03c8hupacxmdr", "SPEC", 6)
|
||||
ORION, _ = NewCw20Token("terra1mddcdx0ujx89f38gu7zspk2r2ffdl5enyz2u03", "ORION", 8)
|
||||
GLOW, _ = NewCw20Token("terra13zx49nk8wjavedjzu8xkk95r3t0ta43c9ptul7", "GLOW", 6)
|
||||
HALO, _ = NewCw20Token("terra1w8kvd6cqpsthupsk4l0clwnmek4l3zr7c84kwq", "HALO", 6)
|
||||
LOOP, _ = NewCw20Token("terra1nef5jf6c7js9x6gkntlehgywvjlpytm7pcgkn4", "LOOP", 6)
|
||||
PLY, _ = NewCw20Token("terra13awdgcx40tz5uygkgm79dytez3x87rpg4uhnvu", "PLY", 6)
|
||||
WHALE, _ = NewCw20Token("terra1php5m8a6qd68z02t3zpw4jv2pj4vgw4wz0t8mz", "WHALE", 6)
|
||||
MARS, _ = NewCw20Token("terra12hgwnpupflfpuual532wgrxu2gjp0tcagzgx4n", "MARS", 6)
|
||||
ATLO, _ = NewCw20Token("terra1cl7whtrqmz5ldr553q69qahck8xvk80fm33qjx", "ATLO", 6)
|
||||
LOTA, _ = NewCw20Token("terra1ez46kxtulsdv07538fh5ra5xj8l68mu8eg24vr", "LOTA", 6)
|
||||
TWD, _ = NewCw20Token("terra19djkaepjjswucys4npd5ltaxgsntl7jf0xz7w6", "TWD", 6)
|
||||
LUNAX, _ = NewCw20Token("terra17y9qkl8dfkeg4py7n0g5407emqnemc3yqk5rup", "LunaX", 6)
|
||||
VUST, _ = NewCw20Token("terra1w0p5zre38ecdy3ez8efd5h9fvgum5s206xknrg", "vUST", 6)
|
||||
STLUNA, _ = NewCw20Token("terra1yg3j2s986nyp5z7r2lvt0hx3r0lnd7kwvwwtsc", "stLUNA", 6)
|
||||
NLUNA, _ = NewCw20Token("terra10f2mt82kjnkxqj2gepgwl637u2w4ue2z5nhz5j", "nLUNA", 6)
|
||||
WEWSTETH, _ = NewCw20Token("terra133chr09wu8sakfte5v7vd8qzq9vghtkv4tn0ur", "wewstETH", 8)
|
||||
NETH, _ = NewCw20Token("terra178v546c407pdnx5rer3hu8s2c0fc924k74ymnn", "nETH", 6)
|
||||
XDEFI, _ = NewCw20Token("terra169edevav3pdrtjcx35j6pvzuv54aevewar4nlh", "XDEFI", 8)
|
||||
LUART, _ = NewCw20Token("terra1vwz7t30q76s7xx6qgtxdqnu6vpr3ak3vw62ygk", "XDEFI", 6)
|
||||
ORNE, _ = NewCw20Token("terra1hnezwjqlhzawcrfysczcxs6xqxu2jawn729kkf", "ORNE", 6)
|
||||
LOOPR, _ = NewCw20Token("terra1jx4lmmke2srcvpjeereetc9hgegp4g5j0p9r2q", "LOOPR", 6)
|
||||
TNS, _ = NewCw20Token("terra14vz4v8adanzph278xyeggll4tfww7teh0xtw2y", "TNS", 6)
|
||||
LUV, _ = NewCw20Token("terra15k5r9r8dl8r7xlr29pry8a9w7sghehcnv5mgp6", "LUV", 6)
|
||||
ROBO, _ = NewCw20Token("terra1f62tqesptvmhtzr8sudru00gsdtdz24srgm7wp", "ROBO", 6)
|
||||
XSD, _ = NewCw20Token("terra1ln2z938phz0nc2wepxpzfkwp6ezn9yrz9zv9ep", "XSD", 8)
|
||||
WHSD, _ = NewCw20Token("terra1ustvnmngueq0p4jd7gfnutgvdc6ujpsjhsjd02", "WHSD", 8)
|
||||
)
|
||||
|
||||
var (
|
||||
ASTRO_LUNAUSTLP, _ = NewCw20Token("terra1m24f7k4g66gnh9f7uncp32p722v0kyt3q4l3u5", "uLP", 6)
|
||||
ASTRO_BLUNAUSTLP, _ = NewCw20Token("terra1aaqmlv4ajsg9043zrhsd44lk8dqnv2hnakjv97", "uLP", 6)
|
||||
ASTRO_ANCUSTLP, _ = NewCw20Token("terra1wmaty65yt7mjw6fjfymkd9zsm6atsq82d9arcd", "uLP", 6)
|
||||
ASTRO_MIRUSTLP, _ = NewCw20Token("terra17trxzqjetl0q6xxep0s2w743dhw2cay0x47puc", "uLP", 6)
|
||||
ASTRO_MINEUSTLP, _ = NewCw20Token("terra16unvjel8vvtanxjpw49ehvga5qjlstn8c826qe", "uLP", 6)
|
||||
ASTRO_SKUJIKUJILP, _ = NewCw20Token("terra1kp4n4tms5w4tvvypya7589zswssqqahtjxy6da", "uLP", 6)
|
||||
ASTRO_MARSUSTLP, _ = NewCw20Token("terra1ww6sqvfgmktp0afcmvg78st6z89x5zr3tmvpss", "uLP", 6)
|
||||
ASTRO_ASTROUSTLP, _ = NewCw20Token("terra17n5sunn88hpy965mzvt3079fqx3rttnplg779g", "uLP", 6)
|
||||
ASTRO_ASTROLUNALP, _ = NewCw20Token("terra1ryxkslm6p04q0nl046quwz8ctdd5llkjnaccpa", "uLP", 6)
|
||||
ASTRO_LUNABLUNALP, _ = NewCw20Token("terra1htw7hm40ch0hacm8qpgd24sus4h0tq3hsseatl", "uLP", 6)
|
||||
|
||||
TERRASWAP_LUNAUSTLP, _ = NewCw20Token("terra17dkr9rnmtmu7x4azrpupukvur2crnptyfvsrvr", "uLP", 6)
|
||||
TERRASWAP_BLUNALUNALP, _ = NewCw20Token("terra1nuy34nwnsh53ygpc4xprlj263cztw7vc99leh2", "uLP", 6)
|
||||
TERRASWAP_LUNAXLUNALP, _ = NewCw20Token("terra1halhfnaul7c0u9t5aywj430jnlu2hgauftdvdq", "uLP", 6)
|
||||
TERRASWAP_LUNAXBLUNALP, _ = NewCw20Token("terra1spagsh9rgcpdgl5pj6lfyftmhtz9elugurfl92", "uLP", 6)
|
||||
TERRASWAP_LUNAXUSTLP, _ = NewCw20Token("terra1ah6vn794y3fjvn5jvcv0pzrzky7gar3tp8zuyu", "uLP", 6)
|
||||
TERRASWAP_BLUNAUSTLP, _ = NewCw20Token("terra1qmr8j3m9x53dhws0yxhymzsvnkjq886yk8k93m", "uLP", 6)
|
||||
TERRASWAP_KUJIUSTLP, _ = NewCw20Token("terra1cmqv3sjew8kcm3j907x2026e4n0ejl2jackxlx", "uLP", 6)
|
||||
TERRASWAP_PLUNAUSTLP, _ = NewCw20Token("terra1t5tg7jrmsk6mj9xs3fk0ey092wfkqqlapuevwr", "uLP", 6)
|
||||
TERRASWAP_STLUNAUSTLP, _ = NewCw20Token("terra1cksuxx4ryfyhkk2c6lw3mpnn4hahkxlkml82rp", "uLP", 6)
|
||||
TERRASWAP_ANCUSTLP, _ = NewCw20Token("terra1gecs98vcuktyfkrve9czrpgtg0m3aq586x6gzm", "uLP", 6)
|
||||
TERRASWAP_MIRUSTLP, _ = NewCw20Token("terra17gjf2zehfvnyjtdgua9p9ygquk6gukxe7ucgwh", "uLP", 6)
|
||||
TERRASWAP_LOOPUSTLP, _ = NewCw20Token("terra12v03at235nxnmsyfg09akh4tp02mr60ne6flry", "uLP", 6)
|
||||
TERRASWAP_LOOPRUSTLP, _ = NewCw20Token("terra17mau5a2q453vf4e33ffaa4cvtn0twle5vm0zuf", "uLP", 6)
|
||||
TERRASWAP_MINEUSTLP, _ = NewCw20Token("terra1rqkyau9hanxtn63mjrdfhpnkpddztv3qav0tq2", "uLP", 6)
|
||||
TERRASWAP_SKUJIKUJILP, _ = NewCw20Token("terra1qf5xuhns225e6xr3mnjv3z8qwlpzyzf2c8we82", "uLP", 6)
|
||||
TERRASWAP_MARSUSTLP, _ = NewCw20Token("terra175xghpferetqhnx0hlp3e0um0wyfknxzv8h42q", "uLP", 6)
|
||||
TERRASWAP_PRISMXPRISMLP, _ = NewCw20Token("terra1pc6fvx7vzk220uj840kmkrnyjhjwxcrneuffnk", "uLP", 6)
|
||||
TERRASWAP_PRISMUSTLP, _ = NewCw20Token("terra1tragr8vkyx52rzy9f8n42etl6la42zylhcfkwa", "uLP", 6)
|
||||
TERRASWAP_CLUNALUNALP, _ = NewCw20Token("terra18cul84v9tt4nmxmmyxm2z74vpgjmrj6py73pus", "uLP", 6)
|
||||
TERRASWAP_ASTROUSTLP, _ = NewCw20Token("terra1xwyhu8geetx2mv8429a3z5dyzr0ajqnmmn4rtr", "uLP", 6)
|
||||
TERRASWAP_AUSTUSTLP, _ = NewCw20Token("terra1umup8qvslkayek0af8u7x2r3r5ndhk2fwhdxz5", "uLP", 6)
|
||||
TERRASWAP_AUSTVUSTLP, _ = NewCw20Token("terra14d33ndaanjc802ural7uq8ck3n6smsy2e4r0rt", "uLP", 6)
|
||||
TERRASWAP_WHALEVUSTLP, _ = NewCw20Token("terra1hg3tr0gx2jfaw38m80s83c7khr4wgfvzyh5uak", "uLP", 6)
|
||||
TERRASWAP_BETHUSTLP, _ = NewCw20Token("terra1jvewsf7922dm47wr872crumps7ktxd7srwcgte", "uLP", 6)
|
||||
TERRASWAP_WHALEUSTLP, _ = NewCw20Token("terra17pqpurglgfqnvkhypask28c3llnf69cstaquke", "uLP", 6)
|
||||
TERRASWAP_SPECUSTLP, _ = NewCw20Token("terra1y9kxxm97vu4ex3uy0rgdr5h2vt7aze5sqx7jyl", "uLP", 6)
|
||||
TERRASWAP_STTUSTLP, _ = NewCw20Token("terra1uwhf02zuaw7grj6gjs7pxt5vuwm79y87ct5p70", "uLP", 6)
|
||||
TERRASWAP_TWDUSTLP, _ = NewCw20Token("terra1c9wr85y8p8989tr58flz5gjkqp8q2r6murwpm9", "uLP", 6)
|
||||
TERRASWAP_PSIUSTLP, _ = NewCw20Token("terra1q6r8hfdl203htfvpsmyh8x689lp2g0m7856fwd", "uLP", 6)
|
||||
TERRASWAP_PLYUSTLP, _ = NewCw20Token("terra1h69kvcmg8jnq7ph2r45k6md4afkl96ugg73amc", "uLP", 6)
|
||||
TERRASWAP_LOTAUSTLP, _ = NewCw20Token("terra1t4xype7nzjxrzttuwuyh9sglwaaeszr8l78u6e", "uLP", 6)
|
||||
TERRASWAP_APOLLOUSTLP, _ = NewCw20Token("terra1n3gt4k3vth0uppk0urche6m3geu9eqcyujt88q", "uLP", 6)
|
||||
TERRASWAP_VKRUSTLP, _ = NewCw20Token("terra17fysmcl52xjrs8ldswhz7n6mt37r9cmpcguack", "uLP", 6)
|
||||
TERRASWAP_ORIONUSTLP, _ = NewCw20Token("terra14ffp0waxcck733a9jfd58d86h9rac2chf5xhev", "uLP", 6)
|
||||
TERRASWAP_ATLOUSTLP, _ = NewCw20Token("terra1l0wqwge0vtfmukx028pluxsr7ee2vk8gl5mlxr", "uLP", 6)
|
||||
TERRASWAP_GLOWUSTLP, _ = NewCw20Token("terra1khm4az2cjlzl76885x2n7re48l9ygckjuye0mt", "uLP", 6)
|
||||
TERRASWAP_TNSUSTLP, _ = NewCw20Token("terra1kg9vmu4e43d3pz0dfsdg9vzwgnnuf6uf3z9jwj", "uLP", 6)
|
||||
TERRASWAP_LUVUSTLP, _ = NewCw20Token("terra1qq6g0kds9zn97lvrukf2qxf6w4sjt0k9jhcdty", "uLP", 6)
|
||||
TERRASWAP_ROBOUSTLP, _ = NewCw20Token("terra19ryu7a586s75ncw3ddc8julkszjht4ahwd7zja", "uLP", 6)
|
||||
TERRASWAP_XSDWHSDLP, _ = NewCw20Token("terra1z0vaks4wkehncztu2a3j2z4fj2gjsnyk2ng9xu", "uLP", 6)
|
||||
TERRASWAP_WHSDUSTLP, _ = NewCw20Token("terra13m7t5z9zvx2phtpa0k6lxht3qtjjhj68u0t0jz", "uLP", 6)
|
||||
TERRASWAP_NLUNAPSILP, _ = NewCw20Token("terra1tuw46dwfvahpcwf3ulempzsn9a0vhazut87zec", "uLP", 6)
|
||||
|
||||
PRISM_PRISMUSTLP, _ = NewCw20Token("terra1wkv9htanake4yerrrjz8p5n40lyrjg9md28tg3", "uLP", 6)
|
||||
PRISM_PRISMLUNALP, _ = NewCw20Token("terra1af7hyx4ek8vqr8asmtujsyv7s3z6py3jgtsgh8", "uLP", 6)
|
||||
PRISM_PRISMPLUNALP, _ = NewCw20Token("terra1rjm3ca2xh2cfm6l6nsnvs6dqzed0lgzdydy7wf", "uLP", 6)
|
||||
PRISM_PRISMXPRISMLP, _ = NewCw20Token("terra1zuv05w52xvtn3td2lpfl3q9jj807533ew54f0x", "uLP", 6)
|
||||
PRISM_PRISMCLUNALP, _ = NewCw20Token("terra1vn5c4yf70aasrq50k2xdy3vn2s8vm40wmngljh", "uLP", 6)
|
||||
PRISM_PRISMYLUNALP, _ = NewCw20Token("terra1argcazqn3ukpyp0vmldxnf9qymnm6vfjaar94g", "uLP", 6)
|
||||
)
|
||||
|
||||
var (
|
||||
Cw20TokenMap = map[string]Cw20Token{
|
||||
"terra188w26t95tf4dz77raftme8p75rggatxjxfeknw": SKUJI,
|
||||
"terra1xfsdgcemqwxp4hhnyk4rle6wr22sseq7j07dnn": KUJI,
|
||||
"terra1042wzrwg2uk6jqxjm34ysqquyr9esdgm5qyswz": XPRISM,
|
||||
"terra17wkadg0tah554r35x6wvff0y5s7ve8npcjfuhz": YLUNA,
|
||||
"terra1tlgelulz9pdkhls6uglfn5lmxarx7f2gxtdzh2": PLUNA,
|
||||
"terra1dh9478k2qvqhqeajhn75a2a7dsnf74y5ukregw": PRISM,
|
||||
"terra13zaagrrrxj47qjwczsczujlvnnntde7fdt0mau": CLUNA,
|
||||
"terra1xj49zyqrwpv5k928jwfpfy2ha668nwdgkwlrg3": ASTRO,
|
||||
"terra1f68wt2ch3cx2g62dxtc8v68mkdh5wchdgdjwz7": XASTRO,
|
||||
"terra100yeqvww74h4yaejj6h733thgcafdaukjtw397": APOLLO,
|
||||
"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76": ANC,
|
||||
"terra1kc87mu460fwkqte29rquh4hc20m54fxwtsx7gp": BLUNA,
|
||||
"terra1hzh9vpxhsk8253se0vv5jj6etdvxu3nv8z07zu": AUST,
|
||||
"terra1dzhzukyezv0etz22ud940z7adyv7xgcjkahuun": BETH,
|
||||
"terra15gwkyepfc6xgca5t5zefzwy42uts8l2m4g40k6": MIR,
|
||||
"terra1kcthelkax4j9x8d3ny6sdag0qmxxynl3qtcrpy": MINE,
|
||||
"terra13xujxcrc9dqft4p9a8ls0w3j0xnzm6y2uvve8n": STT,
|
||||
"terra12897djskt9rge8dtmm86w654g7kzckkd698608": PSI,
|
||||
"terra1dy9kmlm4anr92e42mrkjwzyvfqwz66un00rwr5": VKR,
|
||||
"terra1s5eczhe0h0jutf46re52x5z4r03c8hupacxmdr": SPEC,
|
||||
"terra1mddcdx0ujx89f38gu7zspk2r2ffdl5enyz2u03": ORION,
|
||||
"terra13zx49nk8wjavedjzu8xkk95r3t0ta43c9ptul7": GLOW,
|
||||
"terra1w8kvd6cqpsthupsk4l0clwnmek4l3zr7c84kwq": HALO,
|
||||
"terra1nef5jf6c7js9x6gkntlehgywvjlpytm7pcgkn4": LOOP,
|
||||
"terra13awdgcx40tz5uygkgm79dytez3x87rpg4uhnvu": PLY,
|
||||
"terra1php5m8a6qd68z02t3zpw4jv2pj4vgw4wz0t8mz": WHALE,
|
||||
"terra12hgwnpupflfpuual532wgrxu2gjp0tcagzgx4n": MARS,
|
||||
"terra1cl7whtrqmz5ldr553q69qahck8xvk80fm33qjx": ATLO,
|
||||
"terra1ez46kxtulsdv07538fh5ra5xj8l68mu8eg24vr": LOTA,
|
||||
"terra19djkaepjjswucys4npd5ltaxgsntl7jf0xz7w6": TWD,
|
||||
"terra17y9qkl8dfkeg4py7n0g5407emqnemc3yqk5rup": LUNAX,
|
||||
"terra1w0p5zre38ecdy3ez8efd5h9fvgum5s206xknrg": VUST,
|
||||
"terra1yg3j2s986nyp5z7r2lvt0hx3r0lnd7kwvwwtsc": STLUNA,
|
||||
"terra133chr09wu8sakfte5v7vd8qzq9vghtkv4tn0ur": WEWSTETH,
|
||||
"terra178v546c407pdnx5rer3hu8s2c0fc924k74ymnn": NETH,
|
||||
"terra169edevav3pdrtjcx35j6pvzuv54aevewar4nlh": XDEFI,
|
||||
"terra1vwz7t30q76s7xx6qgtxdqnu6vpr3ak3vw62ygk": LUART,
|
||||
"terra1hnezwjqlhzawcrfysczcxs6xqxu2jawn729kkf": ORNE,
|
||||
"terra1jx4lmmke2srcvpjeereetc9hgegp4g5j0p9r2q": LOOPR,
|
||||
"terra14vz4v8adanzph278xyeggll4tfww7teh0xtw2y": TNS,
|
||||
"terra15k5r9r8dl8r7xlr29pry8a9w7sghehcnv5mgp6": LUV,
|
||||
"terra1f62tqesptvmhtzr8sudru00gsdtdz24srgm7wp": ROBO,
|
||||
"terra1ln2z938phz0nc2wepxpzfkwp6ezn9yrz9zv9ep": XSD,
|
||||
"terra1ustvnmngueq0p4jd7gfnutgvdc6ujpsjhsjd02": WHSD,
|
||||
}
|
||||
NativeTokenMap = map[string]NativeToken{
|
||||
"uusd": UST,
|
||||
"uluna": LUNA,
|
||||
}
|
||||
)
|
318
transaction.go
Normal file
318
transaction.go
Normal file
@ -0,0 +1,318 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/galacticship/terra/crypto"
|
||||
"github.com/galacticship/terra/terra"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Transaction struct {
|
||||
builder cosmos.TxBuilder
|
||||
config cosmos.TxConfig
|
||||
|
||||
q *Querier
|
||||
errors *multierror.Error
|
||||
|
||||
gasLimit uint64
|
||||
feeAmount cosmos.Coins
|
||||
signMode cosmos.SignMode
|
||||
accountNumber uint64
|
||||
sequence uint64
|
||||
messages []cosmos.Msg
|
||||
}
|
||||
|
||||
func NewTransaction(q *Querier) *Transaction {
|
||||
return &Transaction{
|
||||
builder: terra.EncodingConfig.TxConfig.NewTxBuilder(),
|
||||
config: terra.EncodingConfig.TxConfig,
|
||||
q: q,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transaction) Error() error {
|
||||
return t.errors
|
||||
}
|
||||
|
||||
func (t *Transaction) Message(message func() (cosmos.Msg, error)) *Transaction {
|
||||
m, err := message()
|
||||
if err != nil {
|
||||
t.errors = multierror.Append(t.errors, errors.Wrap(err, "generating message"))
|
||||
}
|
||||
t.messages = append(t.messages, m)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transaction) Memo(memo string) *Transaction {
|
||||
t.builder.SetMemo(memo)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transaction) FeeGranter(feeGranter cosmos.AccAddress) *Transaction {
|
||||
t.builder.SetFeeGranter(feeGranter)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transaction) TimeoutHeight(timeoutHeight uint64) *Transaction {
|
||||
t.builder.SetTimeoutHeight(timeoutHeight)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transaction) GasLimit(gasLimit uint64) *Transaction {
|
||||
t.gasLimit = gasLimit
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transaction) FeeAmount(feeAmount cosmos.Coins) *Transaction {
|
||||
t.feeAmount = feeAmount
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transaction) SignMode(signMode cosmos.SignMode) *Transaction {
|
||||
t.signMode = signMode
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transaction) AccountNumber(accountNumber uint64) *Transaction {
|
||||
t.accountNumber = accountNumber
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transaction) Sequence(sequence uint64) *Transaction {
|
||||
t.sequence = sequence
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transaction) simulate(ctx context.Context) (*cosmos.SimulateResponse, error) {
|
||||
sig := cosmos.SignatureV2{
|
||||
PubKey: &crypto.PubKey{},
|
||||
Data: &cosmos.SingleSignatureData{
|
||||
SignMode: t.signMode,
|
||||
},
|
||||
Sequence: t.sequence,
|
||||
}
|
||||
if err := t.builder.SetSignatures(sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txBytes, err := t.GetTxBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res cosmos.SimulateResponse
|
||||
err = t.q.POSTProto(ctx, "cosmos/tx/v1beta1/simulate", cosmos.NewSimulateRequest(txBytes), &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "querying")
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (t *Transaction) computeTax(ctx context.Context) (*terra.ComputeTaxResponse, error) {
|
||||
txBytes, err := t.GetTxBytes()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting transaction bytes")
|
||||
}
|
||||
var res terra.ComputeTaxResponse
|
||||
err = t.q.POSTProto(ctx, "terra/tx/v1beta1/compute_tax", terra.NewComputeTaxRequest(txBytes), &res)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "querying")
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (t *Transaction) validate(ctx context.Context, wallet *Wallet) error {
|
||||
err := t.builder.SetMsgs(t.messages...)
|
||||
if err != nil {
|
||||
t.errors = multierror.Append(t.errors, errors.Wrap(err, "setting messages"))
|
||||
}
|
||||
|
||||
if t.errors.ErrorOrNil() != nil {
|
||||
return t.errors.ErrorOrNil()
|
||||
}
|
||||
|
||||
if t.accountNumber == 0 || t.sequence == 0 {
|
||||
state, err := wallet.State(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting wallet state")
|
||||
}
|
||||
t.accountNumber = state.AccountNumber
|
||||
t.sequence = state.Sequence
|
||||
}
|
||||
|
||||
if t.signMode == cosmos.SignModeUnspecified {
|
||||
t.signMode = cosmos.SignModeDirect
|
||||
}
|
||||
|
||||
gasLimit := int64(t.gasLimit)
|
||||
if gasLimit == 0 {
|
||||
simulateRes, err := t.simulate(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "simulating transaction for gas limit")
|
||||
}
|
||||
gasLimit = wallet.GasAdjustment().MulInt64(int64(simulateRes.GasInfo.GasUsed)).Ceil().RoundInt64()
|
||||
}
|
||||
t.builder.SetGasLimit(uint64(gasLimit))
|
||||
|
||||
feeAmount := t.feeAmount
|
||||
if feeAmount.IsZero() {
|
||||
//computeTaxRes, err := t.computeTax(ctx)
|
||||
//if err != nil {
|
||||
// return errors.Wrap(err, "computing taxes to determine feeAmount")
|
||||
//}
|
||||
gasPrice := wallet.GasPrice()
|
||||
feeAmount = cosmos.NewCoins(cosmos.NewCoin(gasPrice.Denom, gasPrice.Amount.MulInt64(gasLimit).Ceil().RoundInt()))
|
||||
}
|
||||
t.builder.SetFeeAmount(feeAmount)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Transaction) broadcast(ctx context.Context) (*cosmos.TxResponse, error) {
|
||||
txBytes, err := t.GetTxBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res cosmos.BroadcastTxResponse
|
||||
err = t.q.POSTProto(ctx, "cosmos/tx/v1beta1/txs", cosmos.NewBroadcastTxRequest(txBytes, cosmos.BroadcastModeAsync), &res)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "querying")
|
||||
}
|
||||
txResponse := res.TxResponse
|
||||
if txResponse.Code != 0 {
|
||||
return txResponse, errors.Errorf("tx failed with code %d: %s", txResponse.Code, txResponse.RawLog)
|
||||
}
|
||||
return txResponse, nil
|
||||
}
|
||||
|
||||
func (t *Transaction) ExecuteAndWaitFor(ctx context.Context, wallet *Wallet) error {
|
||||
wallet.lock()
|
||||
defer wallet.unlock()
|
||||
err := t.validate(ctx, wallet)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "validating transaction")
|
||||
}
|
||||
err = wallet.SignTransaction(t)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "signing transaction")
|
||||
}
|
||||
transresp, err := t.broadcast(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "broadcasting transaction")
|
||||
}
|
||||
tick := time.NewTicker(2 * time.Second)
|
||||
notfoundmax := 10
|
||||
notfoundcount := 0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("context canceled")
|
||||
case <-tick.C:
|
||||
var res struct {
|
||||
TxResponse struct {
|
||||
Height int64 `json:"height,string"`
|
||||
Txhash string `json:"txhash"`
|
||||
Code int `json:"code"`
|
||||
} `json:"tx_response"`
|
||||
}
|
||||
err := t.q.GET(ctx, fmt.Sprintf("cosmos/tx/v1beta1/txs/%s", transresp.TxHash), nil, &res)
|
||||
if err != nil {
|
||||
if notfoundcount < notfoundmax {
|
||||
notfoundcount++
|
||||
continue
|
||||
}
|
||||
return errors.Wrapf(err, "retrieving transaction state for hash %s", transresp.TxHash)
|
||||
}
|
||||
if res.TxResponse.Code != 0 {
|
||||
return errors.Errorf("transaction %s failed with code %d", transresp.TxHash, res.TxResponse.Code)
|
||||
}
|
||||
t.waitForBlock(ctx, res.TxResponse.Height)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transaction) waitForBlock(ctx context.Context, height int64) {
|
||||
checkBlock := func(height int64) error {
|
||||
latestBlockHeight, _, err := t.q.LatestBlockInfo(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "querying latest block")
|
||||
}
|
||||
if latestBlockHeight < height {
|
||||
return errors.Wrap(err, "latest block height is less than asked height")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := checkBlock(height); err != nil {
|
||||
tickHeight := time.NewTicker(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-tickHeight.C:
|
||||
err = checkBlock(height)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transaction) sign(
|
||||
signMode cosmos.SignMode, signerData cosmos.SignerData,
|
||||
privKey crypto.PrivKey, overwriteSig bool) error {
|
||||
|
||||
sigData := cosmos.SingleSignatureData{
|
||||
SignMode: signMode,
|
||||
Signature: nil,
|
||||
}
|
||||
sig := cosmos.SignatureV2{
|
||||
PubKey: privKey.PubKey(),
|
||||
Data: &sigData,
|
||||
Sequence: signerData.Sequence,
|
||||
}
|
||||
|
||||
var err error
|
||||
var prevSignatures []cosmos.SignatureV2
|
||||
if !overwriteSig {
|
||||
prevSignatures, err = t.builder.GetTx().GetSignaturesV2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.builder.SetSignatures(sig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signature, err := cosmos.SignWithPrivKey(
|
||||
signMode,
|
||||
signerData,
|
||||
t.builder,
|
||||
privKey,
|
||||
t.config,
|
||||
signerData.Sequence,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if overwriteSig {
|
||||
return t.builder.SetSignatures(signature)
|
||||
}
|
||||
prevSignatures = append(prevSignatures, signature)
|
||||
return t.builder.SetSignatures(prevSignatures...)
|
||||
}
|
||||
|
||||
// GetTxBytes return tx bytes for broadcast
|
||||
func (t Transaction) GetTxBytes() ([]byte, error) {
|
||||
return t.config.TxEncoder()(t.builder.GetTx())
|
||||
}
|
111
wallet.go
Normal file
111
wallet.go
Normal file
@ -0,0 +1,111 @@
|
||||
package terra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/galacticship/terra/cosmos"
|
||||
"github.com/galacticship/terra/crypto"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Wallet struct {
|
||||
q *Querier
|
||||
privKey crypto.PrivKey
|
||||
transactionLock *sync.Mutex
|
||||
|
||||
gasAdjustment cosmos.Dec
|
||||
gasPrice cosmos.DecCoin
|
||||
}
|
||||
|
||||
type WalletOption func(w *Wallet) *Wallet
|
||||
|
||||
func WithGasAdjustment(gasAdjustment cosmos.Dec) WalletOption {
|
||||
return func(w *Wallet) *Wallet {
|
||||
w.gasAdjustment = gasAdjustment
|
||||
return w
|
||||
}
|
||||
}
|
||||
|
||||
func WithGasPrice(gasPrice cosmos.DecCoin) WalletOption {
|
||||
return func(w *Wallet) *Wallet {
|
||||
w.gasPrice = gasPrice
|
||||
return w
|
||||
}
|
||||
}
|
||||
|
||||
func NewWalletFromMnemonic(querier *Querier, mnemonic string, account uint32, index uint32, options ...WalletOption) (*Wallet, error) {
|
||||
privKeyBz, err := crypto.DerivePrivKeyBz(mnemonic, crypto.CreateHDPath(account, index))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "deriving private key bytes")
|
||||
}
|
||||
privKey, err := crypto.PrivKeyGen(privKeyBz)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "generating private key")
|
||||
}
|
||||
|
||||
return NewWalletFromPrivateKey(querier, privKey, options...), nil
|
||||
}
|
||||
|
||||
func NewWalletFromPrivateKey(querier *Querier, privateKey crypto.PrivKey, options ...WalletOption) *Wallet {
|
||||
w := &Wallet{
|
||||
q: querier,
|
||||
privKey: privateKey,
|
||||
transactionLock: &sync.Mutex{},
|
||||
|
||||
gasAdjustment: cosmos.NewDecFromIntWithPrec(cosmos.NewInt(14), 1),
|
||||
gasPrice: cosmos.NewDecCoinFromDec("uusd", cosmos.NewDecFromIntWithPrec(cosmos.NewInt(15), 2)),
|
||||
}
|
||||
for _, option := range options {
|
||||
w = option(w)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (a Wallet) GasAdjustment() cosmos.Dec {
|
||||
return a.gasAdjustment
|
||||
}
|
||||
func (a Wallet) GasPrice() cosmos.DecCoin {
|
||||
return a.gasPrice
|
||||
}
|
||||
|
||||
func (a Wallet) Address() cosmos.AccAddress {
|
||||
return cosmos.AccAddress(a.privKey.PubKey().Address())
|
||||
}
|
||||
|
||||
type WalletState struct {
|
||||
AccountNumber uint64 `json:"account_number,string"`
|
||||
Sequence uint64 `json:"sequence,string"`
|
||||
}
|
||||
|
||||
func (a Wallet) State(ctx context.Context) (WalletState, error) {
|
||||
var response struct {
|
||||
AccountInfo WalletState `json:"account"`
|
||||
}
|
||||
err := a.q.GET(ctx, fmt.Sprintf("cosmos/auth/v1beta1/accounts/%s", a.Address().String()), nil, &response)
|
||||
if err != nil {
|
||||
return WalletState{}, errors.Wrap(err, "querying lcd")
|
||||
}
|
||||
return response.AccountInfo, nil
|
||||
}
|
||||
|
||||
func (a Wallet) SignTransaction(transaction *Transaction) error {
|
||||
err := transaction.sign(transaction.signMode, cosmos.SignerData{
|
||||
AccountNumber: transaction.accountNumber,
|
||||
ChainID: a.q.ChainId(),
|
||||
Sequence: transaction.sequence,
|
||||
}, a.privKey, true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "signing transaction")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a Wallet) lock() {
|
||||
a.transactionLock.Lock()
|
||||
}
|
||||
|
||||
func (a Wallet) unlock() {
|
||||
a.transactionLock.Unlock()
|
||||
}
|
Loading…
Reference in New Issue
Block a user