diff --git a/routes/lockups.go b/routes/lockups.go new file mode 100644 index 00000000..273d826f --- /dev/null +++ b/routes/lockups.go @@ -0,0 +1,598 @@ +package routes + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "github.com/deso-protocol/core/collections" + "github.com/deso-protocol/core/lib" + "github.com/gorilla/mux" + "github.com/holiman/uint256" + "io" + "net/http" +) + +type LockedBalanceEntryResponse struct { + HODLerPublicKeyBase58Check string + ProfilePublicKeyBase58Check string + UnlockTimestampNanoSecs int64 + VestingEndTimestampNanoSecs int64 + BalanceBaseUnits uint256.Int + ProfileEntryResponse *ProfileEntryResponse +} + +func (fes *APIServer) _lockedBalanceEntryToResponse( + lockedBalanceEntry *lib.LockedBalanceEntry, utxoView *lib.UtxoView, params *lib.DeSoParams, +) *LockedBalanceEntryResponse { + hodlerPublicKey := utxoView.GetPublicKeyForPKID(lockedBalanceEntry.HODLerPKID) + profilePublicKey := utxoView.GetPublicKeyForPKID(lockedBalanceEntry.ProfilePKID) + profileEntry := utxoView.GetProfileEntryForPKID(lockedBalanceEntry.ProfilePKID) + profileEntryResponse := fes._profileEntryToResponse(profileEntry, utxoView) + return &LockedBalanceEntryResponse{ + HODLerPublicKeyBase58Check: lib.PkToString(hodlerPublicKey, params), + ProfilePublicKeyBase58Check: lib.PkToString(profilePublicKey, params), + UnlockTimestampNanoSecs: lockedBalanceEntry.UnlockTimestampNanoSecs, + VestingEndTimestampNanoSecs: lockedBalanceEntry.VestingEndTimestampNanoSecs, + BalanceBaseUnits: lockedBalanceEntry.BalanceBaseUnits, + ProfileEntryResponse: profileEntryResponse, + } +} + +type LockupYieldCurvePointResponse struct { + ProfilePublicKeyBase58Check string + LockupDurationNanoSecs int64 + LockupYieldAPYBasisPoints uint64 + ProfileEntryResponse *ProfileEntryResponse +} + +func (fes *APIServer) _lockupYieldCurvePointToResponse( + lockupYieldCurvePoint *lib.LockupYieldCurvePoint, utxoView *lib.UtxoView, params *lib.DeSoParams, +) *LockupYieldCurvePointResponse { + profilePublicKey := utxoView.GetPublicKeyForPKID(lockupYieldCurvePoint.ProfilePKID) + profileEntry := utxoView.GetProfileEntryForPKID(lockupYieldCurvePoint.ProfilePKID) + profileEntryResponse := fes._profileEntryToResponse(profileEntry, utxoView) + return &LockupYieldCurvePointResponse{ + ProfilePublicKeyBase58Check: lib.PkToString(profilePublicKey, params), + LockupDurationNanoSecs: lockupYieldCurvePoint.LockupDurationNanoSecs, + LockupYieldAPYBasisPoints: lockupYieldCurvePoint.LockupYieldAPYBasisPoints, + ProfileEntryResponse: profileEntryResponse, + } +} + +type CoinLockupRequest struct { + TransactorPublicKeyBase58Check string `safeForLogging:"true"` + ProfilePublicKeyBase58Check string `safeForLogging:"true"` + RecipientPublicKeyBase58Check string `safeForLogging:"true"` + UnlockTimestampNanoSecs int64 `safeForLogging:"true"` + VestingEndTimestampNanoSecs int64 `safeForLogging:"true"` + LockupAmountBaseUnits *uint256.Int `safeForLogging:"true"` + ExtraData map[string]string `safeForLogging:"true"` + MinFeeRateNanosPerKB uint64 `safeForLogging:"true"` + TransactionFees []TransactionFee `safeForLogging:"true"` +} + +type UpdateCoinLockupParamsRequest struct { + TransactorPublicKeyBase58Check string `safeForLogging:"true"` + LockupYieldDurationNanoSecs int64 `safeForLogging:"true"` + LockupYieldAPYBasisPoints uint64 `safeForLogging:"true"` + RemoveYieldCurvePoint bool `safeForLogging:"true"` + NewLockupTransferRestrictions bool `safeForLogging:"true"` + LockupTransferRestrictionStatus TransferRestrictionStatusString `safeForLogging:"true"` + ExtraData map[string]string `safeForLogging:"true"` + MinFeeRateNanosPerKB uint64 `safeForLogging:"true"` + TransactionFees []TransactionFee `safeForLogging:"true"` +} + +type CoinLockupTransferRequest struct { + TransactorPublicKeyBase58Check string `safeForLogging:"true"` + ProfilePublicKeyBase58Check string `safeForLogging:"true"` + RecipientPublicKeyBase58Check string `safeForLogging:"true"` + UnlockTimestampNanoSecs int64 `safeForLogging:"true"` + LockedCoinsToTransferBaseUnits *uint256.Int `safeForLogging:"true"` + ExtraData map[string]string `safeForLogging:"true"` + MinFeeRateNanosPerKB uint64 `safeForLogging:"true"` + TransactionFees []TransactionFee `safeForLogging:"true"` +} + +type CoinUnlockRequest struct { + TransactorPublicKeyBase58Check string `safeForLogging:"true"` + ProfilePublicKeyBase58Check string `safeForLogging:"true"` + ExtraData map[string]string `safeForLogging:"true"` + MinFeeRateNanosPerKB uint64 `safeForLogging:"true"` + TransactionFees []TransactionFee `safeForLogging:"true"` +} + +type CoinLockResponse struct { + SpendAmountNanos uint64 + TotalInputNanos uint64 + ChangeAmountNanos uint64 + FeeNanos uint64 + Transaction *lib.MsgDeSoTxn + TransactionHex string + TxnHashHex string +} + +func (fes *APIServer) CoinLockup(ww http.ResponseWriter, req *http.Request) { + // Decode request body. + decoder := json.NewDecoder(io.LimitReader(req.Body, MaxRequestBodySizeBytes)) + requestData := CoinLockupRequest{} + if err := decoder.Decode(&requestData); err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockup: Problem parsing request body: %v", err)) + return + } + + // Convert TransactorPublicKeyBase58Check to TransactorPublicKeyBytes + if requestData.TransactorPublicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprint("CoinLockup: TransactorPublicKeyBase58Check is required")) + return + } + transactorPublicKeyBytes, _, err := lib.Base58CheckDecode(requestData.TransactorPublicKeyBase58Check) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockup: Problem decoding TransactorPublicKeyBase58Check %s: %v", + requestData.TransactorPublicKeyBase58Check, err)) + return + } + + // Convert ProfilePublicKeyBase58Check to ProfilePublicKeyBytes + if requestData.ProfilePublicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprint("CoinLockup: ProfilePublicKeyBase58Check is required")) + return + } + profilePublicKeyBytes, _, err := lib.Base58CheckDecode(requestData.ProfilePublicKeyBase58Check) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockup: Problem decoding ProfilePublicKeyBase58Check %s: %v", + requestData.ProfilePublicKeyBase58Check, err)) + return + + } + + // Convert RecipientPublicKeyBase58Check to RecipientPublicKeyBytes if it exists + var recipientPublicKeyBytes []byte + if requestData.RecipientPublicKeyBase58Check != "" { + recipientPublicKeyBytes, _, err = lib.Base58CheckDecode(requestData.RecipientPublicKeyBase58Check) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockup: Problem decoding RecipientPublicKeyBase58Check %s: %v", + requestData.RecipientPublicKeyBase58Check, err)) + return + } + } + + // TODO: What other validations are required? + + extraData, err := EncodeExtraDataMap(requestData.ExtraData) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockup: Problem encoding ExtraData: %v", err)) + return + } + + // Compute the additional transaction fees as specified + // by the request body and the node-level fees. + additionalOutputs, err := fes.getTransactionFee( + lib.TxnTypeCoinLockup, + transactorPublicKeyBytes, + requestData.TransactionFees, + ) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockup: specified TransactionFees are invalid: %v", err)) + return + } + + // Create transaction + txn, totalInput, changeAmount, fees, err := fes.blockchain.CreateCoinLockupTxn( + transactorPublicKeyBytes, + profilePublicKeyBytes, + recipientPublicKeyBytes, + requestData.UnlockTimestampNanoSecs, + requestData.VestingEndTimestampNanoSecs, + requestData.LockupAmountBaseUnits, + extraData, + requestData.MinFeeRateNanosPerKB, + fes.backendServer.GetMempool(), + additionalOutputs, + ) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockup: Problem creating txn: %v", err)) + return + } + + // Construct response. + txnBytes, err := txn.ToBytes(true) + if err != nil { + _AddInternalServerError(ww, fmt.Sprintf("CoinLockup: Problem serializing txn: %v", err)) + return + } + + res := CoinLockResponse{ + SpendAmountNanos: totalInput - changeAmount - fees, + TotalInputNanos: totalInput, + ChangeAmountNanos: changeAmount, + FeeNanos: fees, + Transaction: txn, + TransactionHex: hex.EncodeToString(txnBytes), + TxnHashHex: txn.Hash().String(), + } + + if err = json.NewEncoder(ww).Encode(res); err != nil { + _AddInternalServerError(ww, fmt.Sprintf("CoinLockup: Problem encoding response as JSON: %v", err)) + return + } +} + +func (fes *APIServer) UpdateCoinLockupParams(ww http.ResponseWriter, req *http.Request) { + // Decode request body. + decoder := json.NewDecoder(io.LimitReader(req.Body, MaxRequestBodySizeBytes)) + requestData := UpdateCoinLockupParamsRequest{} + if err := decoder.Decode(&requestData); err != nil { + _AddBadRequestError(ww, fmt.Sprintf("UpdateCoinLockupParams: Problem parsing request body: %v", err)) + return + } + + // Convert TransactorPublicKeyBase58Check to TransactorPublicKeyBytes + if requestData.TransactorPublicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprint("UpdateCoinLockupParams: TransactorPublicKeyBase58Check is required")) + return + } + transactorPublicKeyBytes, _, err := lib.Base58CheckDecode(requestData.TransactorPublicKeyBase58Check) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("UpdateCoinLockupParams: Problem decoding TransactorPublicKeyBase58Check %s: %v", + requestData.TransactorPublicKeyBase58Check, err)) + return + } + + var transferRestrictionStatus lib.TransferRestrictionStatus + if requestData.NewLockupTransferRestrictions { + switch requestData.LockupTransferRestrictionStatus { + case TransferRestrictionStatusStringUnrestricted: + transferRestrictionStatus = lib.TransferRestrictionStatusUnrestricted + case TransferRestrictionStatusStringProfileOwnerOnly: + transferRestrictionStatus = lib.TransferRestrictionStatusProfileOwnerOnly + case TransferRestrictionStatusStringDAOMembersOnly: + transferRestrictionStatus = lib.TransferRestrictionStatusDAOMembersOnly + case TransferRestrictionStatusStringPermanentlyUnrestricted: + transferRestrictionStatus = lib.TransferRestrictionStatusPermanentlyUnrestricted + default: + _AddBadRequestError(ww, fmt.Sprintf( + "UpdateCoinLockupParams: TransferRestrictionStatus \"%v\" not supported", + requestData.LockupTransferRestrictionStatus)) + return + } + } + + // TODO: validate LockupYieldDurationNanoSecs and LockupYieldAPYBasisPoints and anything else. + + // Parse extra data. + extraData, err := EncodeExtraDataMap(requestData.ExtraData) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("UpdateCoinLockupParams: Problem encoding ExtraData: %v", err)) + return + } + + // Compute the additional transaction fees as specified + // by the request body and the node-level fees. + additionalOutputs, err := fes.getTransactionFee( + lib.TxnTypeUpdateCoinLockupParams, + transactorPublicKeyBytes, + requestData.TransactionFees, + ) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("UpdateCoinLockupParams: specified TransactionFees are invalid: %v", err)) + return + } + + // Create transaction + txn, totalInput, changeAmount, fees, err := fes.blockchain.CreateUpdateCoinLockupParamsTxn( + transactorPublicKeyBytes, + requestData.LockupYieldDurationNanoSecs, + requestData.LockupYieldAPYBasisPoints, + requestData.RemoveYieldCurvePoint, + requestData.NewLockupTransferRestrictions, + transferRestrictionStatus, + extraData, + requestData.MinFeeRateNanosPerKB, + fes.backendServer.GetMempool(), + additionalOutputs, + ) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("UpdateCoinLockupParams: Problem creating txn: %v", err)) + return + } + + // Construct response. + txnBytes, err := txn.ToBytes(true) + if err != nil { + _AddInternalServerError(ww, fmt.Sprintf("UpdateCoinLockupParams: Problem serializing txn: %v", err)) + return + } + + res := CoinLockResponse{ + SpendAmountNanos: totalInput - changeAmount - fees, + TotalInputNanos: totalInput, + ChangeAmountNanos: changeAmount, + FeeNanos: fees, + Transaction: txn, + TransactionHex: hex.EncodeToString(txnBytes), + TxnHashHex: txn.Hash().String(), + } + + if err = json.NewEncoder(ww).Encode(res); err != nil { + _AddInternalServerError(ww, fmt.Sprintf("UpdateCoinLockupParams: Problem encoding response as JSON: %v", err)) + return + } +} + +func (fes *APIServer) CoinLockupTransfer(ww http.ResponseWriter, req *http.Request) { + // Decode request body. + decoder := json.NewDecoder(io.LimitReader(req.Body, MaxRequestBodySizeBytes)) + requestData := CoinLockupTransferRequest{} + if err := decoder.Decode(&requestData); err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockupTransfer: Problem parsing request body: %v", err)) + return + } + + // Convert TransactorPublicKeyBase58Check to TransactorPublicKeyBytes + if requestData.TransactorPublicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprint("CoinLockupTransfer: TransactorPublicKeyBase58Check is required")) + return + } + transactorPublicKeyBytes, _, err := lib.Base58CheckDecode(requestData.TransactorPublicKeyBase58Check) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf( + "CoinLockupTransfer: Problem decoding TransactorPublicKeyBase58Check %s: %v", + requestData.TransactorPublicKeyBase58Check, err)) + return + } + + // Convert ProfilePublicKeyBase58Check to ProfilePublicKeyBytes + if requestData.ProfilePublicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprint("CoinLockupTransfer: ProfilePublicKeyBase58Check is required")) + return + } + profilePublicKeyBytes, _, err := lib.Base58CheckDecode(requestData.ProfilePublicKeyBase58Check) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockupTransfer: Problem decoding ProfilePublicKeyBase58Check %s: %v", + requestData.ProfilePublicKeyBase58Check, err)) + return + } + + // Convert RecipientPublicKeyBase58Check to RecipientPublicKeyBytes + if requestData.RecipientPublicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprint("CoinLockupTransfer: RecipientPublicKeyBase58Check is required")) + return + } + recipientPublicKeyBytes, _, err := lib.Base58CheckDecode(requestData.RecipientPublicKeyBase58Check) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockupTransfer: Problem decoding RecipientPublicKeyBase58Check %s: %v", + requestData.RecipientPublicKeyBase58Check, err)) + return + } + + // TODO: validate UnlockTimestampNanoSecs, LockedCoinsToTransferBaseUnits + + // Parse extra data. + extraData, err := EncodeExtraDataMap(requestData.ExtraData) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockupTransfer: Problem encoding ExtraData: %v", err)) + return + } + + // Compute the additional transaction fees as specified + // by the request body and the node-level fees. + additionalOutputs, err := fes.getTransactionFee( + lib.TxnTypeCoinLockupTransfer, + transactorPublicKeyBytes, + requestData.TransactionFees, + ) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockupTransfer: specified TransactionFees are invalid: %v", err)) + return + } + + // Create transaction + txn, totalInput, changeAmount, fees, err := fes.blockchain.CreateCoinLockupTransferTxn( + transactorPublicKeyBytes, + profilePublicKeyBytes, + recipientPublicKeyBytes, + requestData.UnlockTimestampNanoSecs, + requestData.LockedCoinsToTransferBaseUnits, + extraData, + requestData.MinFeeRateNanosPerKB, + fes.backendServer.GetMempool(), + additionalOutputs, + ) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinLockupTransfer: Problem creating txn: %v", err)) + return + } + + // Construct response. + txnBytes, err := txn.ToBytes(true) + if err != nil { + _AddInternalServerError(ww, fmt.Sprintf("CoinLockupTransfer: Problem serializing txn: %v", err)) + return + } + + res := CoinLockResponse{ + SpendAmountNanos: totalInput - changeAmount - fees, + TotalInputNanos: totalInput, + ChangeAmountNanos: changeAmount, + FeeNanos: fees, + Transaction: txn, + TransactionHex: hex.EncodeToString(txnBytes), + TxnHashHex: txn.Hash().String(), + } + + if err = json.NewEncoder(ww).Encode(res); err != nil { + _AddInternalServerError(ww, fmt.Sprintf("CoinLockupTransfer: Problem encoding response as JSON: %v", err)) + return + } +} + +func (fes *APIServer) CoinUnlock(ww http.ResponseWriter, req *http.Request) { + // Decode request body. + decoder := json.NewDecoder(io.LimitReader(req.Body, MaxRequestBodySizeBytes)) + requestData := CoinUnlockRequest{} + if err := decoder.Decode(&requestData); err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinUnlock: Problem parsing request body: %v", err)) + return + } + + // Convert TransactorPublicKeyBase58Check to TransactorPublicKeyBytes + if requestData.TransactorPublicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprint("CoinUnlock: TransactorPublicKeyBase58Check is required")) + return + } + transactorPublicKeyBytes, _, err := lib.Base58CheckDecode(requestData.TransactorPublicKeyBase58Check) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinUnlock: Problem decoding TransactorPublicKeyBase58Check %s: %v", + requestData.TransactorPublicKeyBase58Check, err)) + return + } + + // Convert ProfilePublicKeyBase58Check to ProfilePublicKeyBytes + if requestData.ProfilePublicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprint("CoinUnlock: ProfilePublicKeyBase58Check is required")) + return + } + profilePublicKeyBytes, _, err := lib.Base58CheckDecode(requestData.ProfilePublicKeyBase58Check) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinUnlock: Problem decoding ProfilePublicKeyBase58Check %s: %v", + requestData.ProfilePublicKeyBase58Check, err)) + return + } + + // TODO: any additional validations + + // Parse extra data. + extraData, err := EncodeExtraDataMap(requestData.ExtraData) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinUnlock: Problem encoding ExtraData: %v", err)) + return + } + + // Compute the additional transaction fees as specified + // by the request body and the node-level fees. + additionalOutputs, err := fes.getTransactionFee( + lib.TxnTypeCoinUnlock, + transactorPublicKeyBytes, + requestData.TransactionFees, + ) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinUnlock: specified TransactionFees are invalid: %v", err)) + return + } + + // Create transaction + txn, totalInput, changeAmount, fees, err := fes.blockchain.CreateCoinUnlockTxn( + transactorPublicKeyBytes, + profilePublicKeyBytes, + extraData, + requestData.MinFeeRateNanosPerKB, + fes.backendServer.GetMempool(), + additionalOutputs, + ) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("CoinUnlock: Problem creating txn: %v", err)) + return + } + + // Construct response. + txnBytes, err := txn.ToBytes(true) + if err != nil { + _AddInternalServerError(ww, fmt.Sprintf("CoinUnlock: Problem serializing txn: %v", err)) + return + } + + res := CoinLockResponse{ + SpendAmountNanos: totalInput - changeAmount - fees, + TotalInputNanos: totalInput, + ChangeAmountNanos: changeAmount, + FeeNanos: fees, + Transaction: txn, + TransactionHex: hex.EncodeToString(txnBytes), + TxnHashHex: txn.Hash().String(), + } + + if err = json.NewEncoder(ww).Encode(res); err != nil { + _AddInternalServerError(ww, fmt.Sprintf("CoinUnlock: Problem encoding response as JSON: %v", err)) + return + } +} + +// TODO: GET endpoints +// GET lockup yield curve points for a profile by public key +func (fes *APIServer) LockedYieldCurvePoints(ww http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + publicKeyBase58Check := vars[publicKeyBase58CheckKey] + + if publicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprintf("LockedYieldCurvePoints: PublicKeyBase58Check is required")) + return + } + + utxoView, err := fes.backendServer.GetMempool().GetAugmentedUniversalView() + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("LockedYieldCurvePoints: Problem getting utxoView: %v", err)) + return + } + + // Decode public key + pkid, err := fes.getPKIDFromPublicKeyBase58Check(utxoView, publicKeyBase58Check) + if err != nil || pkid == nil { + _AddBadRequestError(ww, fmt.Sprintf("LockedYieldCurvePoints: Problem decoding public key: %v", err)) + return + } + + // Get locked yield curve points + yieldCurvePointsMap, err := utxoView.GetAllYieldCurvePoints(pkid) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("LockedYieldCurvePoints: Problem getting yield curve points: %v", err)) + return + } + + var allYieldCurvePoints []*LockupYieldCurvePointResponse + for _, yieldCurvePoint := range yieldCurvePointsMap { + if yieldCurvePoint.IsDeleted() { + continue + } + allYieldCurvePoints = append(allYieldCurvePoints, + fes._lockupYieldCurvePointToResponse(yieldCurvePoint, utxoView, fes.Params)) + } + + sortedYieldCurvePoints := collections.SortStable(allYieldCurvePoints, + func(ii *LockupYieldCurvePointResponse, jj *LockupYieldCurvePointResponse) bool { + return ii.LockupDurationNanoSecs < jj.LockupDurationNanoSecs + }) + + if err = json.NewEncoder(ww).Encode(sortedYieldCurvePoints); err != nil { + _AddInternalServerError(ww, fmt.Sprintf("LockedYieldCurvePoints: Problem encoding response as JSON: %v", err)) + return + } +} + +// GET all locked balances of a profile - NOTE: this is not supported by the current core indexes. + +// GET all locked balance entries held by a HODLer public key +func (fes *APIServer) LockedBalanceEntries(ww http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + publicKeyBase58Check := vars[publicKeyBase58CheckKey] + + if publicKeyBase58Check == "" { + _AddBadRequestError(ww, fmt.Sprintf("LockedBalanceEntriesHeldByPublicKey: PublicKeyBase58Check is required")) + return + } + + utxoView, err := fes.backendServer.GetMempool().GetAugmentedUniversalView() + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("LockedBalanceEntriesHeldByPublicKey: Problem getting utxoView: %v", err)) + return + } + + // Decode public key + pkid, err := fes.getPKIDFromPublicKeyBase58Check(utxoView, publicKeyBase58Check) + if err != nil || pkid == nil { + _AddBadRequestError(ww, fmt.Sprintf("LockedBalanceEntriesHeldByPublicKey: Problem decoding public key: %v", err)) + return + } + + // TODO: Get all locked balance entries. NOTE: this is not yet implemented. +} diff --git a/routes/server.go b/routes/server.go index 1d17ece5..77eddf69 100644 --- a/routes/server.go +++ b/routes/server.go @@ -322,6 +322,14 @@ const ( RoutePathUnstake = "/api/v0/unstake" RoutePathUnlockStake = "/api/v0/unlock-stake" RoutePathLockedStake = "/api/v0/locked-stake" + + // lockups.go + RoutePathCoinLockup = "/api/v0/coin-lockup" + RoutePathUpdateCoinLockupParams = "/api/v0/update-coin-lockup-params" + RoutePathCoinLockupTransfer = "/api/v0/coin-lockup-transfer" + RoutePathCoinUnlock = "/api/v0/coin-unlock" + RoutePathLockupYieldCurvePoints = "/api/v0/lockup-yield-curve-points" + RoutePathLockedBalanceEntries = "/api/v0/locked-balance-entries" ) // APIServer provides the interface between the blockchain and things like the @@ -1354,6 +1362,48 @@ func (fes *APIServer) NewRouter() *muxtrace.Router { fes.GetLockedStakesForValidatorAndStaker, PublicAccess, }, + { + "CoinLockup", + []string{"POST", "OPTIONS"}, + RoutePathCoinLockup, + fes.CoinLockup, + PublicAccess, + }, + { + "UpdateCoinLockupParams", + []string{"POST", "OPTIONS"}, + RoutePathUpdateCoinLockupParams, + fes.UpdateCoinLockupParams, + PublicAccess, + }, + { + "CoinLockupTransfer", + []string{"POST", "OPTIONS"}, + RoutePathCoinLockupTransfer, + fes.CoinLockupTransfer, + PublicAccess, + }, + { + "CoinUnlock", + []string{"POST", "OPTIONS"}, + RoutePathCoinUnlock, + fes.CoinUnlock, + PublicAccess, + }, + { + "LockedYieldCurvePoints", + []string{"GET"}, + RoutePathLockupYieldCurvePoints + "/" + makePublicKeyParamRegex(publicKeyBase58CheckKey), + fes.LockedYieldCurvePoints, + PublicAccess, + }, + { + "LockedBalanceEntries", + []string{"GET"}, + RoutePathLockedBalanceEntries + "/" + makePublicKeyParamRegex(publicKeyBase58CheckKey), + fes.LockedBalanceEntries, + PublicAccess, + }, // Jumio Routes { "JumioBegin", diff --git a/routes/stake.go b/routes/stake.go index 583280b2..891d9c74 100644 --- a/routes/stake.go +++ b/routes/stake.go @@ -109,6 +109,7 @@ const ( lockedAtEpochNumberKey = "lockedAtEpochNumber" startEpochNumberKey = "startEpochNumber" endEpochNumberKey = "endEpochNumber" + publicKeyBase58CheckKey = "publicKeyBase58Check" ) // Stake constructs a transaction that stakes a given amount of DeSo. diff --git a/routes/user.go b/routes/user.go index 62a2e24d..410a94d2 100644 --- a/routes/user.go +++ b/routes/user.go @@ -641,10 +641,11 @@ type CoinEntryResponse struct { } type DAOCoinEntryResponse struct { - NumberOfHolders uint64 - CoinsInCirculationNanos uint256.Int - MintingDisabled bool - TransferRestrictionStatus TransferRestrictionStatusString + NumberOfHolders uint64 + CoinsInCirculationNanos uint256.Int + MintingDisabled bool + TransferRestrictionStatus TransferRestrictionStatusString + LockupTransferRestrictionStatus TransferRestrictionStatusString } // GetProfiles ... @@ -1060,6 +1061,8 @@ func (fes *APIServer) _profileEntryToResponse(profileEntry *lib.ProfileEntry, ut MintingDisabled: profileEntry.DAOCoinEntry.MintingDisabled, TransferRestrictionStatus: getTransferRestrictionStatusStringFromTransferRestrictionStatus( profileEntry.DAOCoinEntry.TransferRestrictionStatus), + LockupTransferRestrictionStatus: getTransferRestrictionStatusStringFromTransferRestrictionStatus( + profileEntry.DAOCoinEntry.LockupTransferRestrictionStatus), }, CoinPriceDeSoNanos: coinPriceDeSoNanos, CoinPriceBitCloutNanos: coinPriceDeSoNanos, diff --git a/scripts/nodes/n0 b/scripts/nodes/n0 index a80aa764..7fde97a5 100755 --- a/scripts/nodes/n0 +++ b/scripts/nodes/n0 @@ -9,7 +9,7 @@ rm /tmp/main.*.log --glog-v=0 \ --glog-vmodule="*bitcoin_manager*=0,*balance*=0,*view*=0,*frontend*=0,*peer*=0,*addr*=0,*network*=0,*utils*=0,*connection*=0,*main*=0,*server*=0,*mempool*=0,*miner*=0,*blockchain*=0" \ --num-mining-threads=1 \ - --txindex=true \ + --txindex=false \ --admin-public-keys=* \ --super-admin-public-keys=* \ --miner-public-keys=BC1YLgKZyfgyNntCMXAZYExM8JooYqrYvVsrR8d8XVxorDruYFdq31p \ @@ -18,7 +18,8 @@ rm /tmp/main.*.log --block-cypher-api-key=092dae962ea44b02809a4c74408b42a1 \ --min-satoshis-for-profile=0 \ --connect-ips=deso-seed-2.io:17000 \ - --hypersync-max-queue-size=10 \ + --hypersync-max-queue-size=10000 \ + --sync-type=hypersync \ --run-hot-feed-routine=true )