@@ -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 | |||
Asset string `json:"asset"` | |||
FundWallet string `json:"fundWallet"` | |||
FundSecret string `json:"fundSecret"` | |||
SellingWallet string `json:"sellingWallet"` | |||
IssuerWallet string `json:"issuerWallet"` | |||
Memo string `json:"memo"` | |||
@@ -57,6 +58,7 @@ type Contribution struct { | |||
ModelBase | |||
Wallet string `json:"wallet"` | |||
Amount float64 `gorm:"type:decimal(19,7)" json:"amount"` | |||
Submitted bool `gorm:"type:boolean" json:"submitted"` | |||
TransactionID string `json:"transactionID"` | |||
RewardFundID uint `json:"rewardFundID"` | |||
} | |||
@@ -38,8 +38,7 @@ func NearlyCompleteFunds(w http.ResponseWriter, r *http.Request) { | |||
Db.Table("contributions"). | |||
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"). | |||
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) | |||
err = json.NewEncoder(w).Encode(resp) | |||
if err != nil { | |||
@@ -6,6 +6,7 @@ import ( | |||
"net/http" | |||
"strings" | |||
"github.com/imosed/signet/client" | |||
. "github.com/imosed/signet/data" | |||
"github.com/rs/zerolog/log" | |||
"github.com/stellar/go/clients/horizonclient" | |||
@@ -44,9 +45,9 @@ func Contribute(resp http.ResponseWriter, req *http.Request) { | |||
source := keypair.MustParseFull(cont.PrivateKey) | |||
sourceReq := horizonclient.AccountRequest{AccountID: source.Address()} | |||
var sourceAcct horizon.Account | |||
sourceAcct, err = client.AccountDetail(sourceReq) | |||
sourceAcct, err = client.SignetClient.AccountDetail(sourceReq) | |||
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 | |||
} | |||
@@ -80,15 +81,13 @@ func Contribute(resp http.ResponseWriter, req *http.Request) { | |||
} | |||
var response horizon.Transaction | |||
response, err = client.SubmitTransaction(tx) | |||
response, err = client.SignetClient.SubmitTransaction(tx) | |||
if err != nil { | |||
log.Error().Err(err).Msg("Could not submit contribution transaction") | |||
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 | |||
result.Success = response.Successful && err == nil | |||
@@ -6,6 +6,7 @@ import ( | |||
"strings" | |||
"github.com/gorilla/websocket" | |||
"github.com/imosed/signet/client" | |||
. "github.com/imosed/signet/data" | |||
"github.com/rs/zerolog/log" | |||
"github.com/spf13/viper" | |||
@@ -52,13 +53,16 @@ func InitializeContributionStreams() { | |||
Db.Table("reward_funds").Select("fund_wallet").Scan(&wallets) | |||
contributionUpdateHandler := func(op operations.Operation) { | |||
if op.GetBase().GetTypeI() == 6 || op.GetBase().GetTypeI() == 12 { | |||
return | |||
} | |||
payment := op.(operations.Payment) | |||
var tx horizon.Transaction | |||
var amt float64 | |||
var fund RewardFund | |||
tx, err = client.TransactionDetail(payment.GetTransactionHash()) | |||
tx, err = client.SignetClient.TransactionDetail(payment.GetTransactionHash()) | |||
if err != nil { | |||
log.Error().Err(err).Msg("Could not get transaction from hash") | |||
return | |||
@@ -104,9 +108,9 @@ func InitializeContributionStreams() { | |||
opReq.SetOperationsEndpoint() | |||
ctx, cancellation := context.WithCancel(context.Background()) | |||
cancellations = append(cancellations, cancellation) | |||
err = client.StreamOperations(ctx, opReq, contributionUpdateHandler) | |||
err = client.SignetClient.StreamOperations(ctx, opReq, contributionUpdateHandler) | |||
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 ( | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"net/http" | |||
"strconv" | |||
"github.com/imosed/signet/auth" | |||
. "github.com/imosed/signet/data" | |||
"github.com/imosed/signet/utils" | |||
"github.com/rs/zerolog/log" | |||
"github.com/stellar/go/clients/horizonclient" | |||
"github.com/stellar/go/protocols/horizon" | |||
) | |||
type CreateRewardFundRequest struct { | |||
@@ -66,7 +64,7 @@ func CreateRewardFund(resp http.ResponseWriter, req *http.Request) { | |||
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}) | |||
if err != nil { | |||
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) | |||
} | |||
} | |||
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" | |||
"strconv" | |||
"github.com/imosed/signet/client" | |||
"github.com/rs/zerolog/log" | |||
"github.com/stellar/go/clients/horizonclient" | |||
"github.com/stellar/go/keypair" | |||
@@ -38,7 +39,7 @@ func GetBalance(w http.ResponseWriter, r *http.Request) { | |||
AccountID: kp.Address(), | |||
} | |||
var acct horizon.Account | |||
acct, err = client.AccountDetail(acctReq) | |||
acct, err = client.SignetClient.AccountDetail(acctReq) | |||
if err != nil { | |||
log.Error().Err(err).Msg("Could not get account data from public key") | |||
return | |||
@@ -5,16 +5,22 @@ import ( | |||
"fmt" | |||
"net/http" | |||
"github.com/imosed/signet/client" | |||
. "github.com/imosed/signet/data" | |||
"github.com/imosed/signet/utils" | |||
"github.com/rs/zerolog/log" | |||
"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/txnbuild" | |||
"github.com/stellar/go/xdr" | |||
"gorm.io/gorm/clause" | |||
) | |||
var client = horizonclient.DefaultTestNetClient | |||
type SubmitFundRequest struct { | |||
FundID uint `json:"fundId"` | |||
FundID uint `json:"fundID"` | |||
Submit bool `json:"submit"` | |||
} | |||
func SubmitFund(w http.ResponseWriter, r *http.Request) { | |||
@@ -25,69 +31,108 @@ func SubmitFund(w http.ResponseWriter, r *http.Request) { | |||
} | |||
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{ | |||
Seller: fund.SellingWallet, | |||
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) | |||
if err != nil { | |||
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("/CreateRewardFund", endpoints.CreateRewardFund) | |||
router.HandleFunc("/CloseRewardFund", endpoints.CloseRewardFund) | |||
router.HandleFunc("/SubmitRewardFund", endpoints.SubmitFund) | |||
// router.HandleFunc("/SubmitFund", endpoints.SubmitFund) | |||
router.HandleFunc("/GetBalance", endpoints.GetBalance) | |||
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 | |||
} | |||
} |