mirror of
https://github.com/galacticship/terra.git
synced 2024-11-12 19:27:52 +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