@@ -0,0 +1,5 @@ | |||||
package client | |||||
import "github.com/stellar/go/clients/horizonclient" | |||||
var SignetClient = horizonclient.DefaultTestNetClient |
@@ -34,6 +34,7 @@ type RewardFund struct { | |||||
ModelBase | ModelBase | ||||
Asset string `json:"asset"` | Asset string `json:"asset"` | ||||
FundWallet string `json:"fundWallet"` | FundWallet string `json:"fundWallet"` | ||||
FundSecret string `json:"fundSecret"` | |||||
SellingWallet string `json:"sellingWallet"` | SellingWallet string `json:"sellingWallet"` | ||||
IssuerWallet string `json:"issuerWallet"` | IssuerWallet string `json:"issuerWallet"` | ||||
Memo string `json:"memo"` | Memo string `json:"memo"` | ||||
@@ -57,6 +58,7 @@ type Contribution struct { | |||||
ModelBase | ModelBase | ||||
Wallet string `json:"wallet"` | Wallet string `json:"wallet"` | ||||
Amount float64 `gorm:"type:decimal(19,7)" json:"amount"` | Amount float64 `gorm:"type:decimal(19,7)" json:"amount"` | ||||
Submitted bool `gorm:"type:boolean" json:"submitted"` | |||||
TransactionID string `json:"transactionID"` | TransactionID string `json:"transactionID"` | ||||
RewardFundID uint `json:"rewardFundID"` | RewardFundID uint `json:"rewardFundID"` | ||||
} | } | ||||
@@ -38,8 +38,7 @@ func NearlyCompleteFunds(w http.ResponseWriter, r *http.Request) { | |||||
Db.Table("contributions"). | Db.Table("contributions"). | ||||
Select("rf.id", "asset", "min_contribution", "amount_available", "memo", "fund_wallet", "sum(amount) as raised"). | Select("rf.id", "asset", "min_contribution", "amount_available", "memo", "fund_wallet", "sum(amount) as raised"). | ||||
Joins("inner join reward_funds rf on rf.id = contributions.reward_fund_id"). | Joins("inner join reward_funds rf on rf.id = contributions.reward_fund_id"). | ||||
Group("asset, rf.id, min_contribution, amount_available, memo, fund_wallet"). | |||||
Having("sum(amount) between (rf.amount_available * ?) and rf.amount_available", req.Threshold/100). | |||||
Where("rf.amount_available < ?", req.Threshold). | |||||
Scan(&resp.Funds) | Scan(&resp.Funds) | ||||
err = json.NewEncoder(w).Encode(resp) | err = json.NewEncoder(w).Encode(resp) | ||||
if err != nil { | if err != nil { | ||||
@@ -6,6 +6,7 @@ import ( | |||||
"net/http" | "net/http" | ||||
"strings" | "strings" | ||||
"github.com/imosed/signet/client" | |||||
. "github.com/imosed/signet/data" | . "github.com/imosed/signet/data" | ||||
"github.com/rs/zerolog/log" | "github.com/rs/zerolog/log" | ||||
"github.com/stellar/go/clients/horizonclient" | "github.com/stellar/go/clients/horizonclient" | ||||
@@ -44,9 +45,9 @@ func Contribute(resp http.ResponseWriter, req *http.Request) { | |||||
source := keypair.MustParseFull(cont.PrivateKey) | source := keypair.MustParseFull(cont.PrivateKey) | ||||
sourceReq := horizonclient.AccountRequest{AccountID: source.Address()} | sourceReq := horizonclient.AccountRequest{AccountID: source.Address()} | ||||
var sourceAcct horizon.Account | var sourceAcct horizon.Account | ||||
sourceAcct, err = client.AccountDetail(sourceReq) | |||||
sourceAcct, err = client.SignetClient.AccountDetail(sourceReq) | |||||
if err != nil { | if err != nil { | ||||
log.Error().Err(err).Msg("Could not get account details from Horizon client") | |||||
log.Error().Err(err).Msg("Could not get account details from Horizon SignetClient") | |||||
return | return | ||||
} | } | ||||
@@ -80,15 +81,13 @@ func Contribute(resp http.ResponseWriter, req *http.Request) { | |||||
} | } | ||||
var response horizon.Transaction | var response horizon.Transaction | ||||
response, err = client.SubmitTransaction(tx) | |||||
response, err = client.SignetClient.SubmitTransaction(tx) | |||||
if err != nil { | if err != nil { | ||||
log.Error().Err(err).Msg("Could not submit contribution transaction") | log.Error().Err(err).Msg("Could not submit contribution transaction") | ||||
return | return | ||||
} | } | ||||
fmt.Println("Successful Transaction:") | |||||
fmt.Println("Ledger:", response.Ledger) | |||||
fmt.Println("Hash:", response.Hash) | |||||
log.Info().Msg(fmt.Sprintf("Successful Transaction: { Ledger: %d, Hash: %s }", response.Ledger, response.Hash)) | |||||
var result SuccessResponse | var result SuccessResponse | ||||
result.Success = response.Successful && err == nil | result.Success = response.Successful && err == nil | ||||
@@ -6,6 +6,7 @@ import ( | |||||
"strings" | "strings" | ||||
"github.com/gorilla/websocket" | "github.com/gorilla/websocket" | ||||
"github.com/imosed/signet/client" | |||||
. "github.com/imosed/signet/data" | . "github.com/imosed/signet/data" | ||||
"github.com/rs/zerolog/log" | "github.com/rs/zerolog/log" | ||||
"github.com/spf13/viper" | "github.com/spf13/viper" | ||||
@@ -52,13 +53,16 @@ func InitializeContributionStreams() { | |||||
Db.Table("reward_funds").Select("fund_wallet").Scan(&wallets) | Db.Table("reward_funds").Select("fund_wallet").Scan(&wallets) | ||||
contributionUpdateHandler := func(op operations.Operation) { | contributionUpdateHandler := func(op operations.Operation) { | ||||
if op.GetBase().GetTypeI() == 6 || op.GetBase().GetTypeI() == 12 { | |||||
return | |||||
} | |||||
payment := op.(operations.Payment) | payment := op.(operations.Payment) | ||||
var tx horizon.Transaction | var tx horizon.Transaction | ||||
var amt float64 | var amt float64 | ||||
var fund RewardFund | var fund RewardFund | ||||
tx, err = client.TransactionDetail(payment.GetTransactionHash()) | |||||
tx, err = client.SignetClient.TransactionDetail(payment.GetTransactionHash()) | |||||
if err != nil { | if err != nil { | ||||
log.Error().Err(err).Msg("Could not get transaction from hash") | log.Error().Err(err).Msg("Could not get transaction from hash") | ||||
return | return | ||||
@@ -104,9 +108,9 @@ func InitializeContributionStreams() { | |||||
opReq.SetOperationsEndpoint() | opReq.SetOperationsEndpoint() | ||||
ctx, cancellation := context.WithCancel(context.Background()) | ctx, cancellation := context.WithCancel(context.Background()) | ||||
cancellations = append(cancellations, cancellation) | cancellations = append(cancellations, cancellation) | ||||
err = client.StreamOperations(ctx, opReq, contributionUpdateHandler) | |||||
err = client.SignetClient.StreamOperations(ctx, opReq, contributionUpdateHandler) | |||||
if err != nil { | if err != nil { | ||||
log.Error().Err(err).Msg("Failed to stream contributions from Horizon client") | |||||
log.Error().Err(err).Msg("Failed to stream contributions from Horizon SignetClient") | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -2,16 +2,14 @@ package endpoints | |||||
import ( | import ( | ||||
"encoding/json" | "encoding/json" | ||||
"errors" | |||||
"fmt" | "fmt" | ||||
"net/http" | "net/http" | ||||
"strconv" | |||||
"github.com/imosed/signet/auth" | "github.com/imosed/signet/auth" | ||||
. "github.com/imosed/signet/data" | . "github.com/imosed/signet/data" | ||||
"github.com/imosed/signet/utils" | |||||
"github.com/rs/zerolog/log" | "github.com/rs/zerolog/log" | ||||
"github.com/stellar/go/clients/horizonclient" | "github.com/stellar/go/clients/horizonclient" | ||||
"github.com/stellar/go/protocols/horizon" | |||||
) | ) | ||||
type CreateRewardFundRequest struct { | type CreateRewardFundRequest struct { | ||||
@@ -66,7 +64,7 @@ func CreateRewardFund(resp http.ResponseWriter, req *http.Request) { | |||||
Order: horizonclient.OrderDesc, | Order: horizonclient.OrderDesc, | ||||
} | } | ||||
if err, ok := FindOffer(offerReq, &rewardFund); !ok { | |||||
if err, ok := utils.FindOffer(offerReq, &rewardFund); !ok { | |||||
err = json.NewEncoder(resp).Encode(&SuccessResponse{Success: ok}) | err = json.NewEncoder(resp).Encode(&SuccessResponse{Success: ok}) | ||||
if err != nil { | if err != nil { | ||||
log.Error().Err(err).Msg("Could not deliver response after failing to find issuer offer") | log.Error().Err(err).Msg("Could not deliver response after failing to find issuer offer") | ||||
@@ -108,53 +106,3 @@ func CreateRewardFund(resp http.ResponseWriter, req *http.Request) { | |||||
resp.WriteHeader(403) | resp.WriteHeader(403) | ||||
} | } | ||||
} | } | ||||
func FindOffer(offerReq horizonclient.OfferRequest, rewardFund *RewardFund) (error, bool) { | |||||
op, err := client.Offers(offerReq) | |||||
if err != nil { | |||||
return errors.New("could not get offers"), false | |||||
} | |||||
offers := op.Embedded.Records | |||||
var price float64 | |||||
var amt float64 | |||||
if len(offers) == 1 { | |||||
price, err = strconv.ParseFloat(op.Embedded.Records[0].Price, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse single offer price to float"), false | |||||
} | |||||
amt, err = strconv.ParseFloat(op.Embedded.Records[0].Amount, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse single offer amount to float"), false | |||||
} | |||||
rewardFund.Price = price | |||||
rewardFund.AmountAvailable = amt | |||||
return nil, true | |||||
} else if len(offers) > 1 { | |||||
var maxOffers float64 = 0 | |||||
var correctOffer horizon.Offer | |||||
for _, o := range op.Embedded.Records { | |||||
parsedAmt, err := strconv.ParseFloat(o.Amount, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse amount from offer slice to float"), false | |||||
} | |||||
if parsedAmt > maxOffers { | |||||
correctOffer = o | |||||
maxOffers = parsedAmt | |||||
} | |||||
} | |||||
price, err = strconv.ParseFloat(correctOffer.Price, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse correct offer price to float"), false | |||||
} | |||||
rewardFund.Price = price | |||||
amt, err = strconv.ParseFloat(correctOffer.Amount, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse correct offer amount to float"), false | |||||
} | |||||
rewardFund.AmountAvailable = amt | |||||
return nil, true | |||||
} else { | |||||
return nil, false // no offers shouldn't error | |||||
} | |||||
} |
@@ -5,6 +5,7 @@ import ( | |||||
"net/http" | "net/http" | ||||
"strconv" | "strconv" | ||||
"github.com/imosed/signet/client" | |||||
"github.com/rs/zerolog/log" | "github.com/rs/zerolog/log" | ||||
"github.com/stellar/go/clients/horizonclient" | "github.com/stellar/go/clients/horizonclient" | ||||
"github.com/stellar/go/keypair" | "github.com/stellar/go/keypair" | ||||
@@ -38,7 +39,7 @@ func GetBalance(w http.ResponseWriter, r *http.Request) { | |||||
AccountID: kp.Address(), | AccountID: kp.Address(), | ||||
} | } | ||||
var acct horizon.Account | var acct horizon.Account | ||||
acct, err = client.AccountDetail(acctReq) | |||||
acct, err = client.SignetClient.AccountDetail(acctReq) | |||||
if err != nil { | if err != nil { | ||||
log.Error().Err(err).Msg("Could not get account data from public key") | log.Error().Err(err).Msg("Could not get account data from public key") | ||||
return | return | ||||
@@ -5,16 +5,22 @@ import ( | |||||
"fmt" | "fmt" | ||||
"net/http" | "net/http" | ||||
"github.com/imosed/signet/client" | |||||
. "github.com/imosed/signet/data" | . "github.com/imosed/signet/data" | ||||
"github.com/imosed/signet/utils" | |||||
"github.com/rs/zerolog/log" | "github.com/rs/zerolog/log" | ||||
"github.com/stellar/go/clients/horizonclient" | "github.com/stellar/go/clients/horizonclient" | ||||
"github.com/stellar/go/keypair" | |||||
"github.com/stellar/go/network" | |||||
"github.com/stellar/go/protocols/horizon" | "github.com/stellar/go/protocols/horizon" | ||||
"github.com/stellar/go/txnbuild" | |||||
"github.com/stellar/go/xdr" | |||||
"gorm.io/gorm/clause" | |||||
) | ) | ||||
var client = horizonclient.DefaultTestNetClient | |||||
type SubmitFundRequest struct { | type SubmitFundRequest struct { | ||||
FundID uint `json:"fundId"` | |||||
FundID uint `json:"fundID"` | |||||
Submit bool `json:"submit"` | |||||
} | } | ||||
func SubmitFund(w http.ResponseWriter, r *http.Request) { | func SubmitFund(w http.ResponseWriter, r *http.Request) { | ||||
@@ -25,69 +31,108 @@ func SubmitFund(w http.ResponseWriter, r *http.Request) { | |||||
} | } | ||||
var fund RewardFund | var fund RewardFund | ||||
Db.Find(&fund, req.FundID) | |||||
Db.Preload(clause.Associations).Find(&fund, req.FundID) | |||||
// source := keypair.MustParseFull(fund.FundWallet) | |||||
// sourceReq := horizonclient.AccountRequest{AccountID: source.Address()} | |||||
// var sourceAcct horizon.Account | |||||
// sourceAcct, err = client.AccountDetail(sourceReq) | |||||
var resp SuccessResponse | |||||
if !req.Submit { | |||||
json.NewEncoder(w).Encode(&SuccessResponse{Success: false}) | |||||
return | |||||
} | |||||
source := keypair.MustParseFull(fund.FundSecret) | |||||
sourceReq := horizonclient.AccountRequest{AccountID: source.Address()} | |||||
var sourceAcct horizon.Account | |||||
sourceAcct, err = client.SignetClient.AccountDetail(sourceReq) | |||||
offerReq := horizonclient.OfferRequest{ | offerReq := horizonclient.OfferRequest{ | ||||
Seller: fund.SellingWallet, | |||||
Selling: fmt.Sprintf("%s:%s", fund.Asset, fund.IssuerWallet), | Selling: fmt.Sprintf("%s:%s", fund.Asset, fund.IssuerWallet), | ||||
Seller: fund.IssuerWallet, | |||||
Cursor: "0", | |||||
Order: horizonclient.OrderDesc, | |||||
} | } | ||||
var offers horizon.OffersPage | |||||
offers, err = client.Offers(offerReq) | |||||
for _, o := range offers.Embedded.Records { | |||||
if float64(o.PriceR.N)/float64(o.PriceR.D) == fund.Price { | |||||
fmt.Println(o.PriceR.N) | |||||
fmt.Println(o.Amount) | |||||
if err, ok := utils.FindOffer(offerReq, &fund); !ok { | |||||
err = json.NewEncoder(w).Encode(&SuccessResponse{Success: ok}) | |||||
if err != nil { | |||||
log.Error().Err(err).Msg("Could not deliver response after failing to find issuer offer in submission") | |||||
} | } | ||||
return | |||||
} | } | ||||
// var tx *txnbuild.Transaction | |||||
// tx, err = txnbuild.NewTransaction( | |||||
// txnbuild.TransactionParams{ | |||||
// SourceAccount: &sourceAcct, | |||||
// IncrementSequenceNum: true, | |||||
// Operations: []txnbuild.Operation{ | |||||
// &txnbuild.ManageBuyOffer{ | |||||
// Selling: txnbuild.NativeAsset{}, | |||||
// Buying: txnbuild.CreditAsset{ | |||||
// Code: fund.Asset, | |||||
// Issuer: fund.IssuerWallet, | |||||
// }, | |||||
// Amount: fmt.Sprintf("%f", SumContributions(fund.Contributions)), | |||||
// Price: xdr.Price{}, // TODO: get price | |||||
// OfferID: 0, | |||||
// SourceAccount: "", | |||||
// }, | |||||
// }, | |||||
// BaseFee: txnbuild.MinBaseFee, | |||||
// Memo: txnbuild.Memo(txnbuild.MemoText(strconv.Itoa(int(fund.Model.ID)))), | |||||
// Preconditions: txnbuild.Preconditions{ | |||||
// TimeBounds: txnbuild.NewInfiniteTimeout(), // TODO: change from infinite | |||||
// }, | |||||
// }) | |||||
// if err != nil { | |||||
// log.Error().Err(err).Msg("Could not submit reward fund") | |||||
// } | |||||
// | |||||
// tx, err = tx.Sign(network.TestNetworkPassphrase, source) | |||||
// if err != nil { | |||||
// log.Error().Err(err).Msg("Could not submit fund") | |||||
// } | |||||
// | |||||
// var response horizon.Transaction | |||||
// response, err = client.SubmitTransaction(tx) | |||||
var currentContributions = fund.Contributions | |||||
var submissionAmount = SumContributions(currentContributions) | |||||
var resp SuccessResponse | |||||
// resp.Success = response.Successful | |||||
tr := Db.Begin() | |||||
tr.Table("contributions"). | |||||
Where("reward_fund_id = ? and submitted is null or submitted = false", req.FundID). | |||||
Updates(Contribution{Submitted: true}) | |||||
var tx *txnbuild.Transaction | |||||
tx, err = txnbuild.NewTransaction( | |||||
txnbuild.TransactionParams{ | |||||
SourceAccount: &sourceAcct, | |||||
IncrementSequenceNum: true, | |||||
Operations: []txnbuild.Operation{ | |||||
&txnbuild.ChangeTrust{ | |||||
Line: txnbuild.CreditAsset{ | |||||
Code: fund.Asset, | |||||
Issuer: fund.IssuerWallet, | |||||
}.MustToChangeTrustAsset(), | |||||
SourceAccount: fund.FundWallet, | |||||
}, | |||||
&txnbuild.ManageBuyOffer{ | |||||
Selling: txnbuild.NativeAsset{}, | |||||
Buying: txnbuild.CreditAsset{ | |||||
Code: fund.Asset, | |||||
Issuer: fund.IssuerWallet, | |||||
}, | |||||
Amount: fmt.Sprintf("%f", submissionAmount), | |||||
Price: xdr.Price{N: 1, D: xdr.Int32(fund.Price)}, | |||||
OfferID: 0, | |||||
SourceAccount: fund.FundWallet, | |||||
}, | |||||
}, | |||||
BaseFee: txnbuild.MinBaseFee, | |||||
Memo: txnbuild.Memo(nil), | |||||
Preconditions: txnbuild.Preconditions{ | |||||
TimeBounds: txnbuild.NewInfiniteTimeout(), // TODO: change from infinite | |||||
}, | |||||
}) | |||||
if err != nil { | |||||
log.Error().Err(err).Msg("Could not build submission transaction") | |||||
tr.Rollback() | |||||
return | |||||
} | |||||
tx, err = tx.Sign(network.TestNetworkPassphrase, source) | |||||
if err != nil { | |||||
log.Error().Err(err).Msg("Could not sign submission transaction") | |||||
tr.Rollback() | |||||
return | |||||
} | |||||
var response horizon.Transaction | |||||
response, err = client.SignetClient.SubmitTransaction(tx) | |||||
if err != nil { | |||||
log.Error().Err(err).Msg("Could not submit transaction") | |||||
tr.Rollback() | |||||
return | |||||
} | |||||
tr.Commit() | |||||
resp.Success = response.Successful | |||||
err = json.NewEncoder(w).Encode(resp) | err = json.NewEncoder(w).Encode(resp) | ||||
if err != nil { | if err != nil { | ||||
log.Error().Err(err).Msg("Could not deliver response in SubmitFund call") | log.Error().Err(err).Msg("Could not deliver response in SubmitFund call") | ||||
} | } | ||||
} | } | ||||
func SumContributions(contributions []Contribution) float64 { | |||||
var total float64 = 0 | |||||
for _, contribution := range contributions { | |||||
if !contribution.Submitted { | |||||
total += contribution.Amount | |||||
} | |||||
} | |||||
return total | |||||
} |
@@ -40,6 +40,7 @@ func main() { | |||||
router.HandleFunc("/EditQueue", endpoints.EditQueue) | router.HandleFunc("/EditQueue", endpoints.EditQueue) | ||||
router.HandleFunc("/CreateRewardFund", endpoints.CreateRewardFund) | router.HandleFunc("/CreateRewardFund", endpoints.CreateRewardFund) | ||||
router.HandleFunc("/CloseRewardFund", endpoints.CloseRewardFund) | router.HandleFunc("/CloseRewardFund", endpoints.CloseRewardFund) | ||||
router.HandleFunc("/SubmitRewardFund", endpoints.SubmitFund) | |||||
// router.HandleFunc("/SubmitFund", endpoints.SubmitFund) | // router.HandleFunc("/SubmitFund", endpoints.SubmitFund) | ||||
router.HandleFunc("/GetBalance", endpoints.GetBalance) | router.HandleFunc("/GetBalance", endpoints.GetBalance) | ||||
router.HandleFunc("/Contribute", endpoints.Contribute) | router.HandleFunc("/Contribute", endpoints.Contribute) | ||||
@@ -0,0 +1,60 @@ | |||||
package utils | |||||
import ( | |||||
"errors" | |||||
"strconv" | |||||
"github.com/imosed/signet/client" | |||||
"github.com/imosed/signet/data" | |||||
"github.com/stellar/go/clients/horizonclient" | |||||
"github.com/stellar/go/protocols/horizon" | |||||
) | |||||
func FindOffer(offerReq horizonclient.OfferRequest, rewardFund *data.RewardFund) (error, bool) { | |||||
op, err := client.SignetClient.Offers(offerReq) | |||||
if err != nil { | |||||
return errors.New("could not get offers"), false | |||||
} | |||||
offers := op.Embedded.Records | |||||
var price float64 | |||||
var amt float64 | |||||
if len(offers) == 1 { | |||||
price, err = strconv.ParseFloat(op.Embedded.Records[0].Price, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse single offer price to float"), false | |||||
} | |||||
amt, err = strconv.ParseFloat(op.Embedded.Records[0].Amount, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse single offer amount to float"), false | |||||
} | |||||
rewardFund.Price = price | |||||
rewardFund.AmountAvailable = amt | |||||
return nil, true | |||||
} else if len(offers) > 1 { | |||||
var maxOffers float64 = 0 | |||||
var correctOffer horizon.Offer | |||||
for _, o := range op.Embedded.Records { | |||||
parsedAmt, err := strconv.ParseFloat(o.Amount, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse amount from offer slice to float"), false | |||||
} | |||||
if parsedAmt > maxOffers { | |||||
correctOffer = o | |||||
maxOffers = parsedAmt | |||||
} | |||||
} | |||||
price, err = strconv.ParseFloat(correctOffer.Price, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse correct offer price to float"), false | |||||
} | |||||
amt, err = strconv.ParseFloat(correctOffer.Amount, 64) | |||||
if err != nil { | |||||
return errors.New("could not parse correct offer amount to float"), false | |||||
} | |||||
rewardFund.Price = price | |||||
rewardFund.AmountAvailable = amt | |||||
return nil, true | |||||
} else { | |||||
return nil, false // no offers shouldn't error | |||||
} | |||||
} |