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