func (this *Ledger) Populate(ctx context.Context, row db.LedgerRecord) { this.ID = row.LedgerHash this.PT = row.PagingToken() this.Hash = row.LedgerHash this.PrevHash = row.PreviousLedgerHash.String this.Sequence = row.Sequence this.TransactionCount = row.TransactionCount this.OperationCount = row.OperationCount this.ClosedAt = row.ClosedAt this.TotalCoins = amount.String(xdr.Int64(row.TotalCoins)) this.FeePool = amount.String(xdr.Int64(row.FeePool)) this.BaseFee = row.BaseFee this.BaseReserve = amount.String(xdr.Int64(row.BaseReserve)) this.MaxTxSetSize = row.MaxTxSetSize self := fmt.Sprintf("/ledgers/%d", row.Sequence) lb := hal.LinkBuilder{httpx.BaseURL(ctx)} this.Links.Self = lb.Link(self) this.Links.Transactions = lb.PagedLink(self, "transactions") this.Links.Operations = lb.PagedLink(self, "operations") this.Links.Payments = lb.PagedLink(self, "payments") this.Links.Effects = lb.PagedLink(self, "effects") return }
func (this *Balance) Populate(ctx context.Context, row core.Trustline) (err error) { this.Type, err = assets.String(row.Assettype) if err != nil { return } this.Balance = amount.String(row.Balance) this.Limit = amount.String(row.Tlimit) this.Issuer = row.Issuer this.Code = row.Assetcode return }
func TestString(t *testing.T) { for _, v := range Tests { o := amount.String(v.I) if o != v.S { t.Errorf("%d stringified to %s, not %s", v.I, o, v.S) } } }
func (this *Path) Populate(ctx context.Context, q paths.Query, p paths.Path) (err error) { this.DestinationAmount = amount.String(q.DestinationAmount) cost, err := p.Cost(q.DestinationAmount) if err != nil { return } this.SourceAmount = amount.String(cost) err = p.Source().Extract( &this.SourceAssetType, &this.SourceAssetCode, &this.SourceAssetIssuer) if err != nil { return } err = p.Destination().Extract( &this.DestinationAssetType, &this.DestinationAssetCode, &this.DestinationAssetIssuer) if err != nil { return } path := p.Path() this.Path = make([]Asset, len(path)) for i, a := range path { err = a.Extract( &this.Path[i].Type, &this.Path[i].Code, &this.Path[i].Issuer) if err != nil { return } } return }
func (this *Balance) PopulateNative(stroops xdr.Int64) (err error) { this.Type, err = assets.String(xdr.AssetTypeAssetTypeNative) if err != nil { return } this.Balance = amount.String(stroops) this.Limit = "" this.Issuer = "" this.Code = "" return }
func (is *Session) tradeDetails(buyer, seller xdr.AccountId, claim xdr.ClaimOfferAtom) (bd map[string]interface{}, sd map[string]interface{}) { bd = map[string]interface{}{ "offer_id": claim.OfferId, "seller": seller.Address(), "bought_amount": amount.String(claim.AmountSold), "sold_amount": amount.String(claim.AmountBought), } is.assetDetails(bd, claim.AssetSold, "bought_") is.assetDetails(bd, claim.AssetBought, "sold_") sd = map[string]interface{}{ "offer_id": claim.OfferId, "seller": buyer.Address(), "bought_amount": amount.String(claim.AmountBought), "sold_amount": amount.String(claim.AmountSold), } is.assetDetails(sd, claim.AssetBought, "bought_") is.assetDetails(sd, claim.AssetSold, "sold_") return }
func (this *Offer) Populate(ctx context.Context, row db.CoreOfferRecord) { this.ID = row.OfferID this.PT = row.PagingToken() this.Seller = row.SellerID this.Amount = amount.String(row.Amount) this.PriceR.N = row.Pricen this.PriceR.D = row.Priced this.Price = row.PriceAsString() this.Buying = Asset{ Type: assets.MustString(row.BuyingAssetType), Code: row.BuyingAssetCode.String, Issuer: row.BuyingIssuer.String, } this.Selling = Asset{ Type: assets.MustString(row.SellingAssetType), Code: row.SellingAssetCode.String, Issuer: row.SellingIssuer.String, } lb := hal.LinkBuilder{httpx.BaseURL(ctx)} this.Links.Self = lb.Linkf("/offers/%d", row.OfferID) this.Links.OfferMaker = lb.Linkf("/accounts/%s", row.SellerID) return }
// AmountAsString returns the amount as a string, formatted using // the amount.String() utility from go-stellar-base. func (p *PriceLevel) AmountAsString() string { return amount.String(xdr.Int64(p.Amount)) }
type HomeDomain string // MemoHash is a mutator that sets a memo on the mutated transaction of type // MEMO_HASH. type MemoHash struct { Value xdr.Hash } // Limit is a mutator that sets a limit on the change_trust operation type Limit Amount // MasterWeight is a mutator that sets account's master weight type MasterWeight uint32 // MaxLimit represents the maximum value that can be passed as trutline Limit var MaxLimit = Limit(amount.String(math.MaxInt64)) // MemoID is a mutator that sets a memo on the mutated transaction of type // MEMO_ID. type MemoID struct { Value uint64 } // MemoReturn is a mutator that sets a memo on the mutated transaction of type // MEMO_RETURN. type MemoReturn struct { Value xdr.Hash } // MemoText is a mutator that sets a memo on the mutated transaction of type // MEMO_TEXT.
// HandlerAuth implements authorize endpoint func (rh *RequestHandler) HandlerAuth(c web.C, w http.ResponseWriter, r *http.Request) { authreq := &compliance.AuthRequest{} authreq.FromRequest(r) err := authreq.Validate() if err != nil { errorResponse := err.(*protocols.ErrorResponse) log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } var authData compliance.AuthData err = json.Unmarshal([]byte(authreq.Data), &authData) if err != nil { errorResponse := protocols.NewInvalidParameterError("data", authreq.Data) log.WithFields(errorResponse.LogData).Warn(errorResponse.Error()) server.Write(w, errorResponse) return } senderStellarToml, err := rh.StellarTomlResolver.GetStellarTomlByAddress(authData.Sender) if err != nil { log.WithFields(log.Fields{"err": err, "sender": authData.Sender}).Warn("Cannot get stellar.toml of sender") server.Write(w, protocols.InvalidParameterError) return } if senderStellarToml.SigningKey == "" { errorResponse := protocols.NewInvalidParameterError("data.sender", authData.Sender) log.WithFields(errorResponse.LogData).Warn("No SIGNING_KEY in stellar.toml of sender") server.Write(w, errorResponse) return } // Verify signature signatureBytes, err := base64.StdEncoding.DecodeString(authreq.Signature) if err != nil { errorResponse := protocols.NewInvalidParameterError("sig", authreq.Signature) log.WithFields(errorResponse.LogData).Warn("Error decoding signature") server.Write(w, errorResponse) return } err = rh.SignatureSignerVerifier.Verify(senderStellarToml.SigningKey, []byte(authreq.Data), signatureBytes) if err != nil { log.WithFields(log.Fields{ "signing_key": senderStellarToml.SigningKey, "data": authreq.Data, "sig": authreq.Signature, }).Warn("Invalid signature") errorResponse := protocols.NewInvalidParameterError("sig", authreq.Signature) server.Write(w, errorResponse) return } b64r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(authData.Tx)) var tx xdr.Transaction _, err = xdr.Unmarshal(b64r, &tx) if err != nil { errorResponse := protocols.NewInvalidParameterError("data.tx", authData.Tx) log.WithFields(log.Fields{ "err": err, "tx": authData.Tx, }).Warn("Error decoding Transaction XDR") server.Write(w, errorResponse) return } if tx.Memo.Hash == nil { errorResponse := protocols.NewInvalidParameterError("data.tx", authData.Tx) log.WithFields(log.Fields{"tx": authData.Tx}).Warn("Transaction does not contain Memo.Hash") server.Write(w, errorResponse) return } // Validate memo preimage hash memoPreimageHashBytes := sha256.Sum256([]byte(authData.Memo)) memoBytes := [32]byte(*tx.Memo.Hash) if memoPreimageHashBytes != memoBytes { errorResponse := protocols.NewInvalidParameterError("data.tx", authData.Tx) h := xdr.Hash(memoPreimageHashBytes) tx.Memo.Hash = &h var txBytes bytes.Buffer _, err = xdr.Marshal(&txBytes, tx) if err != nil { log.Error("Error mashaling transaction") server.Write(w, protocols.InternalServerError) return } expectedTx := base64.StdEncoding.EncodeToString(txBytes.Bytes()) log.WithFields(log.Fields{"tx": authData.Tx, "expected_tx": expectedTx}).Warn("Memo preimage hash does not equal tx Memo.Hash") server.Write(w, errorResponse) return } var memoPreimage memo.Memo err = json.Unmarshal([]byte(authData.Memo), &memoPreimage) if err != nil { errorResponse := protocols.NewInvalidParameterError("data.memo", authData.Memo) log.WithFields(log.Fields{ "err": err, "memo": authData.Memo, }).Warn("Cannot unmarshal memo preimage") server.Write(w, errorResponse) return } transactionHash, err := submitter.TransactionHash(&tx, rh.Config.NetworkPassphrase) if err != nil { log.WithFields(log.Fields{"err": err}).Warn("Error calculating tx hash") server.Write(w, protocols.InternalServerError) return } response := compliance.AuthResponse{} // Sanctions check if rh.Config.Callbacks.Sanctions == "" { response.TxStatus = compliance.AuthStatusOk } else { resp, err := rh.Client.PostForm( rh.Config.Callbacks.Sanctions, url.Values{"sender": {memoPreimage.Transaction.SenderInfo}}, ) if err != nil { log.WithFields(log.Fields{ "sanctions": rh.Config.Callbacks.Sanctions, "err": err, }).Error("Error sending request to sanctions server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Error reading sanctions server response") server.Write(w, protocols.InternalServerError) return } switch resp.StatusCode { case http.StatusOK: // AuthStatusOk response.TxStatus = compliance.AuthStatusOk case http.StatusAccepted: // AuthStatusPending response.TxStatus = compliance.AuthStatusPending var pendingResponse compliance.PendingResponse err := json.Unmarshal(body, &pendingResponse) if err != nil { // Set default value response.Pending = 600 } else { response.Pending = pendingResponse.Pending } case http.StatusForbidden: // AuthStatusDenied response.TxStatus = compliance.AuthStatusDenied default: log.WithFields(log.Fields{ "status": resp.StatusCode, "body": string(body), }).Error("Error response from sanctions server") server.Write(w, protocols.InternalServerError) return } } // User info if authData.NeedInfo { if rh.Config.Callbacks.AskUser == "" { response.InfoStatus = compliance.AuthStatusDenied // Check AllowedFi tokens := strings.Split(authData.Sender, "*") if len(tokens) != 2 { log.WithFields(log.Fields{ "sender": authData.Sender, }).Warn("Invalid stellar address") server.Write(w, protocols.InternalServerError) return } allowedFi, err := rh.Repository.GetAllowedFiByDomain(tokens[1]) if err != nil { log.WithFields(log.Fields{"err": err}).Error("Error getting AllowedFi from DB") server.Write(w, protocols.InternalServerError) return } if allowedFi == nil { // FI not found check AllowedUser allowedUser, err := rh.Repository.GetAllowedUserByDomainAndUserID(tokens[1], tokens[0]) if err != nil { log.WithFields(log.Fields{"err": err}).Error("Error getting AllowedUser from DB") server.Write(w, protocols.InternalServerError) return } if allowedUser != nil { response.InfoStatus = compliance.AuthStatusOk } } else { response.InfoStatus = compliance.AuthStatusOk } } else { // Ask user var amount, assetType, assetCode, assetIssuer string if len(tx.Operations) > 0 { operationBody := tx.Operations[0].Body if operationBody.Type == xdr.OperationTypePayment { amount = baseAmount.String(operationBody.PaymentOp.Amount) operationBody.PaymentOp.Asset.Extract(&assetType, &assetCode, &assetIssuer) } else if operationBody.Type == xdr.OperationTypePathPayment { amount = baseAmount.String(operationBody.PathPaymentOp.DestAmount) operationBody.PathPaymentOp.DestAsset.Extract(&assetType, &assetCode, &assetIssuer) } } resp, err := rh.Client.PostForm( rh.Config.Callbacks.AskUser, url.Values{ "amount": {amount}, "asset_code": {assetCode}, "asset_issuer": {assetIssuer}, "sender": {memoPreimage.Transaction.SenderInfo}, "note": {memoPreimage.Transaction.Note}, }, ) if err != nil { log.WithFields(log.Fields{ "ask_user": rh.Config.Callbacks.AskUser, "err": err, }).Error("Error sending request to ask_user server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Error reading ask_user server response") server.Write(w, protocols.InternalServerError) return } switch resp.StatusCode { case http.StatusOK: // AuthStatusOk response.InfoStatus = compliance.AuthStatusOk case http.StatusAccepted: // AuthStatusPending response.InfoStatus = compliance.AuthStatusPending var pendingResponse compliance.PendingResponse err := json.Unmarshal(body, &pendingResponse) if err != nil { // Set default value response.Pending = 600 } else { response.Pending = pendingResponse.Pending } case http.StatusForbidden: // AuthStatusDenied response.InfoStatus = compliance.AuthStatusDenied default: log.WithFields(log.Fields{ "status": resp.StatusCode, "body": string(body), }).Error("Error response from ask_user server") server.Write(w, protocols.InternalServerError) return } } if response.InfoStatus == compliance.AuthStatusOk { // Fetch Info fetchInfoRequest := compliance.FetchInfoRequest{Address: memoPreimage.Transaction.Route} resp, err := rh.Client.PostForm( rh.Config.Callbacks.FetchInfo, fetchInfoRequest.ToValues(), ) if err != nil { log.WithFields(log.Fields{ "fetch_info": rh.Config.Callbacks.FetchInfo, "err": err, }).Error("Error sending request to fetch_info server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.WithFields(log.Fields{ "fetch_info": rh.Config.Callbacks.FetchInfo, "err": err, }).Error("Error reading fetch_info server response") server.Write(w, protocols.InternalServerError) return } if resp.StatusCode != http.StatusOK { log.WithFields(log.Fields{ "fetch_info": rh.Config.Callbacks.FetchInfo, "status": resp.StatusCode, "body": string(body), }).Error("Error response from fetch_info server") server.Write(w, protocols.InternalServerError) return } response.DestInfo = string(body) } } else { response.InfoStatus = compliance.AuthStatusOk } if response.TxStatus == compliance.AuthStatusOk && response.InfoStatus == compliance.AuthStatusOk { authorizedTransaction := &entities.AuthorizedTransaction{ TransactionID: hex.EncodeToString(transactionHash[:]), Memo: base64.StdEncoding.EncodeToString(memoBytes[:]), TransactionXdr: authData.Tx, AuthorizedAt: time.Now(), Data: authreq.Data, } err = rh.EntityManager.Persist(authorizedTransaction) if err != nil { log.WithFields(log.Fields{"err": err}).Warn("Error persisting AuthorizedTransaction") server.Write(w, protocols.InternalServerError) return } } server.Write(w, &response) }
// Payment implements /payment endpoint func (rh *RequestHandler) Payment(w http.ResponseWriter, r *http.Request) { request := &bridge.PaymentRequest{} request.FromRequest(r) err := request.Validate() if err != nil { errorResponse := err.(*protocols.ErrorResponse) log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } if request.Source == "" { request.Source = rh.Config.Accounts.BaseSeed } sourceKeypair, _ := keypair.Parse(request.Source) var submitResponse horizon.SubmitTransactionResponse var submitError error if request.ExtraMemo != "" && rh.Config.Compliance != "" { // Compliance server part sendRequest := request.ToComplianceSendRequest() resp, err := rh.Client.PostForm( rh.Config.Compliance+"/send", sendRequest.ToValues(), ) if err != nil { log.WithFields(log.Fields{"err": err}).Error("Error sending request to compliance server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Error reading compliance server response") server.Write(w, protocols.InternalServerError) return } if resp.StatusCode != 200 { log.WithFields(log.Fields{ "status": resp.StatusCode, "body": string(body), }).Error("Error response from compliance server") server.Write(w, protocols.InternalServerError) return } var complianceSendResponse compliance.SendResponse err = json.Unmarshal(body, &complianceSendResponse) if err != nil { log.Error("Error unmarshalling from compliance server") server.Write(w, protocols.InternalServerError) return } if complianceSendResponse.AuthResponse.InfoStatus == compliance.AuthStatusPending || complianceSendResponse.AuthResponse.TxStatus == compliance.AuthStatusPending { log.WithFields(log.Fields{"response": complianceSendResponse}).Info("Compliance response pending") server.Write(w, bridge.NewPaymentPendingError(complianceSendResponse.AuthResponse.Pending)) return } if complianceSendResponse.AuthResponse.InfoStatus == compliance.AuthStatusDenied || complianceSendResponse.AuthResponse.TxStatus == compliance.AuthStatusDenied { log.WithFields(log.Fields{"response": complianceSendResponse}).Info("Compliance response denied") server.Write(w, bridge.PaymentDenied) return } var tx xdr.Transaction err = xdr.SafeUnmarshalBase64(complianceSendResponse.TransactionXdr, &tx) if err != nil { log.Error("Error unmarshalling transaction returned by compliance server") server.Write(w, protocols.InternalServerError) return } submitResponse, submitError = rh.TransactionSubmitter.SignAndSubmitRawTransaction(request.Source, &tx) } else { // Payment without compliance server destinationObject, _, err := rh.FederationResolver.Resolve(request.Destination) if err != nil { log.WithFields(log.Fields{"destination": request.Destination, "err": err}).Print("Cannot resolve address") server.Write(w, bridge.PaymentCannotResolveDestination) return } _, err = keypair.Parse(destinationObject.AccountID) if err != nil { log.WithFields(log.Fields{"AccountId": destinationObject.AccountID}).Print("Invalid AccountId in destination") server.Write(w, protocols.NewInvalidParameterError("destination", request.Destination)) return } var payWithMutator *b.PayWithPath if request.SendMax != "" { // Path payment var sendAsset b.Asset if request.SendAssetCode == "" && request.SendAssetIssuer == "" { sendAsset = b.NativeAsset() } else { sendAsset = b.CreditAsset(request.SendAssetCode, request.SendAssetIssuer) } payWith := b.PayWith(sendAsset, request.SendMax) for i := 0; ; i++ { codeFieldName := fmt.Sprintf("path[%d][asset_code]", i) issuerFieldName := fmt.Sprintf("path[%d][asset_issuer]", i) // If the element does not exist in PostForm break the loop if _, exists := r.PostForm[codeFieldName]; !exists { break } code := r.PostFormValue(codeFieldName) issuer := r.PostFormValue(issuerFieldName) if code == "" && issuer == "" { payWith = payWith.Through(b.NativeAsset()) } else { payWith = payWith.Through(b.CreditAsset(code, issuer)) } } payWithMutator = &payWith } var operationBuilder interface{} if request.AssetCode != "" && request.AssetIssuer != "" { mutators := []interface{}{ b.Destination{destinationObject.AccountID}, b.CreditAmount{request.AssetCode, request.AssetIssuer, request.Amount}, } if payWithMutator != nil { mutators = append(mutators, *payWithMutator) } operationBuilder = b.Payment(mutators...) } else { mutators := []interface{}{ b.Destination{destinationObject.AccountID}, b.NativeAmount{request.Amount}, } if payWithMutator != nil { mutators = append(mutators, *payWithMutator) } // Check if destination account exist _, err = rh.Horizon.LoadAccount(destinationObject.AccountID) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Error loading account") operationBuilder = b.CreateAccount(mutators...) } else { operationBuilder = b.Payment(mutators...) } } memoType := request.MemoType memo := request.Memo if destinationObject.MemoType != "" { if request.MemoType != "" { log.Print("Memo given in request but federation returned memo fields.") server.Write(w, bridge.PaymentCannotUseMemo) return } memoType = destinationObject.MemoType memo = destinationObject.Memo } var memoMutator interface{} switch { case memoType == "": break case memoType == "id": id, err := strconv.ParseUint(memo, 10, 64) if err != nil { log.WithFields(log.Fields{"memo": memo}).Print("Cannot convert memo_id value to uint64") server.Write(w, protocols.NewInvalidParameterError("memo", request.Memo)) return } memoMutator = b.MemoID{id} case memoType == "text": memoMutator = &b.MemoText{memo} case memoType == "hash": memoBytes, err := hex.DecodeString(memo) if err != nil || len(memoBytes) != 32 { log.WithFields(log.Fields{"memo": memo}).Print("Cannot decode hash memo value") server.Write(w, protocols.NewInvalidParameterError("memo", request.Memo)) return } var b32 [32]byte copy(b32[:], memoBytes[0:32]) hash := xdr.Hash(b32) memoMutator = &b.MemoHash{hash} default: log.Print("Not supported memo type: ", memoType) server.Write(w, protocols.NewInvalidParameterError("memo", request.Memo)) return } accountResponse, err := rh.Horizon.LoadAccount(sourceKeypair.Address()) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot load source account") server.Write(w, bridge.PaymentSourceNotExist) return } sequenceNumber, err := strconv.ParseUint(accountResponse.SequenceNumber, 10, 64) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot convert SequenceNumber") server.Write(w, protocols.InternalServerError) return } transactionMutators := []b.TransactionMutator{ b.SourceAccount{request.Source}, b.Sequence{sequenceNumber + 1}, b.Network{rh.Config.NetworkPassphrase}, operationBuilder.(b.TransactionMutator), } if memoMutator != nil { transactionMutators = append(transactionMutators, memoMutator.(b.TransactionMutator)) } tx := b.Transaction(transactionMutators...) if tx.Err != nil { log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") // TODO when build.OperationBuilder interface is ready check for // create_account and payment errors separately switch { case tx.Err.Error() == "Asset code length is invalid": server.Write( w, protocols.NewInvalidParameterError("asset_code", request.AssetCode), ) case strings.Contains(tx.Err.Error(), "cannot parse amount"): server.Write( w, protocols.NewInvalidParameterError("amount", request.Amount), ) default: log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") server.Write(w, protocols.InternalServerError) } return } txe := tx.Sign(request.Source) txeB64, err := txe.Base64() if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot encode transaction envelope") server.Write(w, protocols.InternalServerError) return } submitResponse, submitError = rh.Horizon.SubmitTransaction(txeB64) } if submitError != nil { log.WithFields(log.Fields{"error": submitError}).Error("Error submitting transaction") server.Write(w, protocols.InternalServerError) return } errorResponse := bridge.ErrorFromHorizonResponse(submitResponse) if errorResponse != nil { log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } // Path payment send amount if submitResponse.ResultXdr != nil { var transactionResult xdr.TransactionResult reader := strings.NewReader(*submitResponse.ResultXdr) b64r := base64.NewDecoder(base64.StdEncoding, reader) _, err := xdr.Unmarshal(b64r, &transactionResult) if err == nil && transactionResult.Result.Code == xdr.TransactionResultCodeTxSuccess { operationResult := (*transactionResult.Result.Results)[0] if operationResult.Tr.PathPaymentResult != nil { sendAmount := operationResult.Tr.PathPaymentResult.SendAmount() submitResponse.SendAmount = amount.String(sendAmount) } } } server.Write(w, &submitResponse) }
func (is *Session) ingestEffects() { if is.Err != nil { return } effects := &EffectIngestion{ Dest: is.Ingestion, Accounts: is.accountCache, OperationID: is.Cursor.OperationID(), } source := is.Cursor.OperationSourceAccount() opbody := is.Cursor.Operation().Body switch is.Cursor.OperationType() { case xdr.OperationTypeCreateAccount: op := opbody.MustCreateAccountOp() effects.Add(op.Destination, history.EffectAccountCreated, map[string]interface{}{ "starting_balance": amount.String(op.StartingBalance), }, ) effects.Add(source, history.EffectAccountDebited, map[string]interface{}{ "asset_type": "native", "amount": amount.String(op.StartingBalance), }, ) effects.Add(op.Destination, history.EffectSignerCreated, map[string]interface{}{ "public_key": op.Destination.Address(), "weight": keypair.DefaultSignerWeight, }, ) case xdr.OperationTypePayment: op := opbody.MustPaymentOp() dets := map[string]interface{}{"amount": amount.String(op.Amount)} is.assetDetails(dets, op.Asset, "") effects.Add(op.Destination, history.EffectAccountCredited, dets) effects.Add(source, history.EffectAccountDebited, dets) case xdr.OperationTypePathPayment: op := opbody.MustPathPaymentOp() dets := map[string]interface{}{"amount": amount.String(op.DestAmount)} is.assetDetails(dets, op.DestAsset, "") effects.Add(op.Destination, history.EffectAccountCredited, dets) result := is.Cursor.OperationResult().MustPathPaymentResult() dets = map[string]interface{}{"amount": amount.String(result.SendAmount())} is.assetDetails(dets, op.SendAsset, "") effects.Add(source, history.EffectAccountDebited, dets) is.ingestTrades(effects, source, result.MustSuccess().Offers) case xdr.OperationTypeManageOffer: result := is.Cursor.OperationResult().MustManageOfferResult().MustSuccess() is.ingestTrades(effects, source, result.OffersClaimed) case xdr.OperationTypeCreatePassiveOffer: claims := []xdr.ClaimOfferAtom{} result := is.Cursor.OperationResult() // KNOWN ISSUE: stellar-core creates results for CreatePassiveOffer operations // with the wrong result arm set. if result.Type == xdr.OperationTypeManageOffer { claims = result.MustManageOfferResult().MustSuccess().OffersClaimed } else { claims = result.MustCreatePassiveOfferResult().MustSuccess().OffersClaimed } is.ingestTrades(effects, source, claims) case xdr.OperationTypeSetOptions: op := opbody.MustSetOptionsOp() if op.HomeDomain != nil { effects.Add(source, history.EffectAccountHomeDomainUpdated, map[string]interface{}{ "home_domain": string(*op.HomeDomain), }, ) } thresholdDetails := map[string]interface{}{} if op.LowThreshold != nil { thresholdDetails["low_threshold"] = *op.LowThreshold } if op.MedThreshold != nil { thresholdDetails["med_threshold"] = *op.MedThreshold } if op.HighThreshold != nil { thresholdDetails["high_threshold"] = *op.HighThreshold } if len(thresholdDetails) > 0 { effects.Add(source, history.EffectAccountThresholdsUpdated, thresholdDetails) } flagDetails := map[string]bool{} is.effectFlagDetails(flagDetails, op.SetFlags, true) is.effectFlagDetails(flagDetails, op.ClearFlags, false) if len(flagDetails) > 0 { effects.Add(source, history.EffectAccountFlagsUpdated, flagDetails) } is.ingestSignerEffects(effects, op) case xdr.OperationTypeChangeTrust: op := opbody.MustChangeTrustOp() dets := map[string]interface{}{"limit": amount.String(op.Limit)} key := xdr.LedgerKey{} effect := history.EffectType(0) is.assetDetails(dets, op.Line, "") key.SetTrustline(source, op.Line) before, after, err := is.Cursor.BeforeAndAfter(key) // NOTE: when an account trusts itself, the transaction is successful but // no ledger entries are actually modified, leading to an "empty meta" // situation. We simply continue on to the next operation in that scenario. if err == meta.ErrMetaNotFound { return } if err != nil { is.Err = err return } switch { case before == nil && after != nil: effect = history.EffectTrustlineCreated case before != nil && after == nil: effect = history.EffectTrustlineRemoved case before != nil && after != nil: effect = history.EffectTrustlineUpdated default: panic("Invalid before-and-after state") } effects.Add(source, effect, dets) case xdr.OperationTypeAllowTrust: op := opbody.MustAllowTrustOp() asset := op.Asset.ToAsset(source) dets := map[string]interface{}{ "trustor": op.Trustor.Address(), } is.assetDetails(dets, asset, "") if op.Authorize { effects.Add(source, history.EffectTrustlineAuthorized, dets) } else { effects.Add(source, history.EffectTrustlineDeauthorized, dets) } case xdr.OperationTypeAccountMerge: dest := opbody.MustDestination() result := is.Cursor.OperationResult().MustAccountMergeResult() dets := map[string]interface{}{ "amount": amount.String(result.MustSourceAccountBalance()), "asset_type": "native", } effects.Add(source, history.EffectAccountDebited, dets) effects.Add(dest, history.EffectAccountCredited, dets) effects.Add(source, history.EffectAccountRemoved, map[string]interface{}{}) case xdr.OperationTypeInflation: payouts := is.Cursor.OperationResult().MustInflationResult().MustPayouts() for _, payout := range payouts { effects.Add(payout.Destination, history.EffectAccountCredited, map[string]interface{}{ "amount": amount.String(payout.Amount), "asset_type": "native", }, ) } case xdr.OperationTypeManageData: op := opbody.MustManageDataOp() dets := map[string]interface{}{"name": op.DataName} key := xdr.LedgerKey{} effect := history.EffectType(0) key.SetData(source, string(op.DataName)) before, after, err := is.Cursor.BeforeAndAfter(key) if err != nil { is.Err = err return } if after != nil { raw := after.Data.MustData().DataValue dets["value"] = base64.StdEncoding.EncodeToString(raw) } switch { case before == nil && after != nil: effect = history.EffectDataCreated case before != nil && after == nil: effect = history.EffectDataRemoved case before != nil && after != nil: effect = history.EffectDataUpdated default: panic("Invalid before-and-after state") } effects.Add(source, effect, dets) default: is.Err = fmt.Errorf("Unknown operation type: %s", is.Cursor.OperationType()) return } is.Err = effects.Finish() }
// operationDetails returns the details regarding the current operation, suitable // for ingestion into a history_operation row func (is *Session) operationDetails() map[string]interface{} { details := map[string]interface{}{} c := is.Cursor source := c.OperationSourceAccount() switch c.OperationType() { case xdr.OperationTypeCreateAccount: op := c.Operation().Body.MustCreateAccountOp() details["funder"] = source.Address() details["account"] = op.Destination.Address() details["starting_balance"] = amount.String(op.StartingBalance) case xdr.OperationTypePayment: op := c.Operation().Body.MustPaymentOp() details["from"] = source.Address() details["to"] = op.Destination.Address() details["amount"] = amount.String(op.Amount) is.assetDetails(details, op.Asset, "") case xdr.OperationTypePathPayment: op := c.Operation().Body.MustPathPaymentOp() details["from"] = source.Address() details["to"] = op.Destination.Address() result := c.OperationResult().MustPathPaymentResult() details["amount"] = amount.String(op.DestAmount) details["source_amount"] = amount.String(result.SendAmount()) details["source_max"] = amount.String(op.SendMax) is.assetDetails(details, op.DestAsset, "") is.assetDetails(details, op.SendAsset, "source_") var path = make([]map[string]interface{}, len(op.Path)) for i := range op.Path { path[i] = make(map[string]interface{}) is.assetDetails(path[i], op.Path[i], "") } details["path"] = path case xdr.OperationTypeManageOffer: op := c.Operation().Body.MustManageOfferOp() details["offer_id"] = op.OfferId details["amount"] = amount.String(op.Amount) details["price"] = op.Price.String() details["price_r"] = map[string]interface{}{ "n": op.Price.N, "d": op.Price.D, } is.assetDetails(details, op.Buying, "buying_") is.assetDetails(details, op.Selling, "selling_") case xdr.OperationTypeCreatePassiveOffer: op := c.Operation().Body.MustCreatePassiveOfferOp() details["amount"] = amount.String(op.Amount) details["price"] = op.Price.String() details["price_r"] = map[string]interface{}{ "n": op.Price.N, "d": op.Price.D, } is.assetDetails(details, op.Buying, "buying_") is.assetDetails(details, op.Selling, "selling_") case xdr.OperationTypeSetOptions: op := c.Operation().Body.MustSetOptionsOp() if op.InflationDest != nil { details["inflation_dest"] = op.InflationDest.Address() } if op.SetFlags != nil && *op.SetFlags > 0 { is.operationFlagDetails(details, int32(*op.SetFlags), "set") } if op.ClearFlags != nil && *op.ClearFlags > 0 { is.operationFlagDetails(details, int32(*op.ClearFlags), "clear") } if op.MasterWeight != nil { details["master_key_weight"] = *op.MasterWeight } if op.LowThreshold != nil { details["low_threshold"] = *op.LowThreshold } if op.MedThreshold != nil { details["med_threshold"] = *op.MedThreshold } if op.HighThreshold != nil { details["high_threshold"] = *op.HighThreshold } if op.HomeDomain != nil { details["home_domain"] = *op.HomeDomain } if op.Signer != nil { details["signer_key"] = op.Signer.PubKey.Address() details["signer_weight"] = op.Signer.Weight } case xdr.OperationTypeChangeTrust: op := c.Operation().Body.MustChangeTrustOp() is.assetDetails(details, op.Line, "") details["trustor"] = source.Address() details["trustee"] = details["asset_issuer"] details["limit"] = amount.String(op.Limit) case xdr.OperationTypeAllowTrust: op := c.Operation().Body.MustAllowTrustOp() is.assetDetails(details, op.Asset.ToAsset(source), "") details["trustee"] = source.Address() details["trustor"] = op.Trustor.Address() details["authorize"] = op.Authorize case xdr.OperationTypeAccountMerge: aid := c.Operation().Body.MustDestination() details["account"] = source.Address() details["into"] = aid.Address() case xdr.OperationTypeInflation: // no inflation details, presently case xdr.OperationTypeManageData: op := c.Operation().Body.MustManageDataOp() details["name"] = string(op.DataName) if op.DataValue != nil { details["value"] = base64.StdEncoding.EncodeToString(*op.DataValue) } else { details["value"] = nil } default: panic(fmt.Errorf("Unknown operation type: %s", c.OperationType())) } return details }