initial commit

This commit is contained in:
Laurent Le Houerou 2022-04-07 10:19:24 +04:00
commit c8099af2c9
52 changed files with 5416 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.exe
go.work
.idea/
cmd/
Dockerfile
Dockerfile.*
.dockerignore

21
LICENCE Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
)

1283
go.sum Normal file

File diff suppressed because it is too large Load Diff

310
pair.go Normal file
View 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
View 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
}

View 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
}

View 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)
}

View 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)
}

View 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)
}

View 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
}

View 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)
}

View 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)
}

View 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
}

View 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"
}

View 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
View 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
View 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
View 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
}

View 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
View 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
View 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)
}

View 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
View 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
View 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
View 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
View 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"
}

View 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)
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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()
}