Skip to content

Commit

Permalink
Add staking ops to the API
Browse files Browse the repository at this point in the history
  • Loading branch information
Groxan committed Feb 1, 2024
1 parent 7ae2f10 commit f2f8950
Show file tree
Hide file tree
Showing 17 changed files with 755 additions and 5 deletions.
61 changes: 59 additions & 2 deletions Tzkt.Api/Controllers/OperationsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5030,6 +5030,63 @@ public async Task<ActionResult<int>> GetSmartRollupRefuteOpsCount([FromQuery] Sr
}
#endregion

#region staking
/// <summary>
/// Get staking ops
/// </summary>
/// <remarks>
/// Returns a list of staking operations.
/// </remarks>
/// <param name="filter">Filter</param>
/// <param name="pagination">Pagination</param>
/// <param name="selection">Selection</param>
/// <param name="quote">Comma-separated list of ticker symbols to inject historical prices into response</param>
/// <returns></returns>
[HttpGet("staking")]
public async Task<ActionResult<IEnumerable<StakingOperation>>> GetStakingOps(
[FromQuery] StakingOperationFilter filter,
[FromQuery] Pagination pagination,
[FromQuery] Selection selection,
[FromQuery] Symbols quote = Symbols.None)
{
var query = ResponseCacheService.BuildKey(Request.Path.Value,
("filter", filter), ("pagination", pagination), ("selection", selection), ("quote", quote));

if (!ResponseCache.TryGet(query, out var res))
res = ResponseCache.Set(query, selection.select == null
? await Operations.GetStakingOps(filter, pagination, quote)
: new SelectionResponse
{
Cols = selection.Cols,
Rows = await Operations.GetStakingOps(filter, pagination, selection, quote)
});

return this.Bytes(res);
}

/// <summary>
/// Get staking ops count
/// </summary>
/// <remarks>
/// Returns a total number of staking operations.
/// </remarks>
/// <param name="filter">Filter</param>
/// <returns></returns>
[HttpGet("staking/count")]
public async Task<ActionResult<int>> GetStakingOpsCount([FromQuery] StakingOperationFilter filter)
{
if (filter.Empty)
return Ok(State.Current.StakingOpsCount);

var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter));

if (!ResponseCache.TryGet(query, out var res))
res = ResponseCache.Set(query, await Operations.GetStakingOpsCount(filter));

return this.Bytes(res);
}
#endregion

#region migrations
/// <summary>
/// Get migrations
Expand Down Expand Up @@ -5626,7 +5683,7 @@ public async Task<ActionResult<int>> GetEndorsingRewardsCount(
/// <returns></returns>
[HttpGet("autostaking")]
public async Task<ActionResult<IEnumerable<AutostakingOperation>>> GetAutostakingOps(
[FromQuery] AutostakingOpFilter filter,
[FromQuery] AutostakingOperationFilter filter,
[FromQuery] Pagination pagination,
[FromQuery] Selection selection,
[FromQuery] Symbols quote = Symbols.None)
Expand Down Expand Up @@ -5655,7 +5712,7 @@ public async Task<ActionResult<IEnumerable<AutostakingOperation>>> GetAutostakin
/// <param name="filter">Filter</param>
/// <returns></returns>
[HttpGet("autostaking/count")]
public async Task<ActionResult<int>> GetAutostakingOpsCount([FromQuery] AutostakingOpFilter filter)
public async Task<ActionResult<int>> GetAutostakingOpsCount([FromQuery] AutostakingOperationFilter filter)
{
if (filter.Empty)
return Ok(State.Current.AutostakingOpsCount);
Expand Down
60 changes: 60 additions & 0 deletions Tzkt.Api/Extensions/ModelBindingContextExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,66 @@ public static bool TryGetAutostakingActionsList(this ModelBindingContext binding
return true;
}

public static bool TryGetStakingOperationKind(this ModelBindingContext bindingContext, string name, ref bool hasValue, out int? result)
{
result = null;
var valueObject = bindingContext.ValueProvider.GetValue(name);

if (valueObject != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(name, valueObject);
if (!string.IsNullOrEmpty(valueObject.FirstValue))
{
if (!StakingOperationKinds.TryParse(valueObject.FirstValue, out var value))
{
bindingContext.ModelState.TryAddModelError(name, "Invalid staking operation kind.");
return false;
}
hasValue = true;
result = value;
}
}

return true;
}

public static bool TryGetStakingOperationKindsList(this ModelBindingContext bindingContext, string name, ref bool hasValue, out List<int> result)
{
result = null;
var valueObject = bindingContext.ValueProvider.GetValue(name);

if (valueObject != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(name, valueObject);
if (!string.IsNullOrEmpty(valueObject.FirstValue))
{
var rawValues = valueObject.FirstValue.Split(',', StringSplitOptions.RemoveEmptyEntries);

if (rawValues.Length == 0)
{
bindingContext.ModelState.TryAddModelError(name, "List should contain at least one item.");
return false;
}

hasValue = true;
result = new List<int>(rawValues.Length);

foreach (var rawValue in rawValues)
{
if (!StakingOperationKinds.TryParse(rawValue, out var value))
{
bindingContext.ModelState.TryAddModelError(name, "List contains invalid staking operation kind.");
return false;
}
hasValue = true;
result.Add(value);
}
}
}

return true;
}

public static bool TryGetSrMessageType(this ModelBindingContext bindingContext, string name, ref bool hasValue, out int? result)
{
result = null;
Expand Down
5 changes: 5 additions & 0 deletions Tzkt.Api/Models/Block.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ public class Block
/// </summary>
public IEnumerable<SmartRollupRefuteOperation> SrRefuteOps { get; set; }

/// <summary>
/// List of staking operations, included in the block
/// </summary>
public IEnumerable<StakingOperation> StakingOps { get; set; }

/// <summary>
/// List of migration operations, implicitly applied at the end of the block
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions Tzkt.Api/Models/Operations/Base/Operation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace Tzkt.Api.Models
[KnownType(typeof(SmartRollupRecoverBondOperation))]
[KnownType(typeof(SmartRollupRefuteOperation))]
[KnownType(typeof(AutostakingOperation))]
[KnownType(typeof(StakingOperation))]
public abstract class Operation
{
/// <summary>
Expand Down Expand Up @@ -184,6 +185,9 @@ public override string GetDiscriminatorValue(Type type)
if (type == typeof(AutostakingOperation))
return OpTypes.Autostaking;

if (type == typeof(StakingOperation))
return OpTypes.Staking;

return base.GetDiscriminatorValue(type);
}
}
Expand Down
116 changes: 116 additions & 0 deletions Tzkt.Api/Models/Operations/StakingOperation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
namespace Tzkt.Api.Models
{
public class StakingOperation : Operation
{
/// <summary>
/// Type of the operation, `staking`
/// </summary>
public override string Type => OpTypes.Staking;

/// <summary>
/// Internal TzKT ID.
/// **[sortable]**
/// </summary>
public override long Id { get; set; }

/// <summary>
/// Height of the block from the genesis
/// </summary>
public int Level { get; set; }

/// <summary>
/// Datetime at which the block is claimed to have been created (ISO 8601, e.g. `2020-02-20T02:40:57Z`)
/// </summary>
public DateTime Timestamp { get; set; }

/// <summary>
/// Hash of the operation
/// </summary>
public string Hash { get; set; }

/// <summary>
/// Information about the account who has sent the operation
/// </summary>
public Alias Sender { get; set; }

/// <summary>
/// An account nonce which is used to prevent operation replay
/// </summary>
public int Counter { get; set; }

/// <summary>
/// A cap on the amount of gas a given operation can consume
/// </summary>
public int GasLimit { get; set; }

/// <summary>
/// Amount of gas, consumed by the operation
/// </summary>
public int GasUsed { get; set; }

/// <summary>
/// A cap on the amount of storage a given operation can consume
/// </summary>
public int StorageLimit { get; set; }

/// <summary>
/// Fee to the baker, produced block, in which the operation was included (micro tez)
/// </summary>
public long BakerFee { get; set; }

/// <summary>
/// Staking operation kind (`stake`, `unstake`, `finalize`, `set_parameters`)
/// </summary>
public string Kind { get; set; }

/// <summary>
/// Information about the baker
/// </summary>
public Alias Baker { get; set; }

/// <summary>
/// Amount (micro tez)
/// </summary>
public long? Amount { get; set; }

/// <summary>
/// Pseudotokens
/// </summary>
public long? Pseudotokens { get; set; }

/// <summary>
/// This parameter determines the maximum portion (millionth) of external stake by stakers over the baker's own staked funds.
/// </summary>
public long? LimitOfStakingOverBaking { get; set; }

/// <summary>
/// This parameter determines the fraction (billionth) of the rewards that accrue to the baker's liquid spendable balance — the remainder accrues to frozen stakes.
/// </summary>
public long? EdgeOfBakingOverStaking { get; set; }

/// <summary>
/// Cycle from which the specified staking parameters are activated
/// </summary>
public int? ActivationCycle { get; set; }

/// <summary>
/// Operation status (`applied` - an operation applied by the node and successfully added to the blockchain,
/// `failed` - an operation which failed with some particular error (not enough balance, gas limit, etc),
/// `backtracked` - an operation which was successful but reverted due to one of the following operations in the same operation group was failed,
/// `skipped` - all operations after the failed one in an operation group)
/// </summary>
public string Status { get; set; }

/// <summary>
/// List of errors provided by the node, injected the operation to the blockchain. `null` if there is no errors
/// </summary>
public List<OperationError> Errors { get; set; }

#region injecting
/// <summary>
/// Injected historical quote at the time of operation
/// </summary>
public QuoteShort Quote { get; set; }
#endregion
}
}
44 changes: 44 additions & 0 deletions Tzkt.Api/Parameters/Binders/StakingOperationKindBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Tzkt.Api
{
public class StakingOperationKindBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var model = bindingContext.ModelName;
var hasValue = false;

if (!bindingContext.TryGetStakingOperationKind($"{model}", ref hasValue, out var value))
return Task.CompletedTask;

if (!bindingContext.TryGetStakingOperationKind($"{model}.eq", ref hasValue, out var eq))
return Task.CompletedTask;

if (!bindingContext.TryGetStakingOperationKind($"{model}.ne", ref hasValue, out var ne))
return Task.CompletedTask;

if (!bindingContext.TryGetStakingOperationKindsList($"{model}.in", ref hasValue, out var @in))
return Task.CompletedTask;

if (!bindingContext.TryGetStakingOperationKindsList($"{model}.ni", ref hasValue, out var ni))
return Task.CompletedTask;

if (!hasValue)
{
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}

bindingContext.Result = ModelBindingResult.Success(new StakingOperationKindParameter
{
Eq = value ?? eq,
Ne = ne,
In = @in,
Ni = ni
});

return Task.CompletedTask;
}
}
}
41 changes: 41 additions & 0 deletions Tzkt.Api/Parameters/StakingOperationFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Tzkt.Api.Services;

namespace Tzkt.Api
{
public class StakingOperationFilter : ManagerOperationFilter
{
/// <summary>
/// Filter by any of the specified fields (`sender`, or `baker`).
/// Example: `anyof.sender.baker=tz1...` will return operations where `sender` OR `baker` is equal to the specified value.
/// This parameter is useful when you need to get all operations somehow related to the account in a single request.
/// Click on the parameter to expand more details.
/// </summary>
public AnyOfParameter anyof { get; set; }

/// <summary>
/// Filter by baker address.
/// Click on the parameter to expand more details.
/// </summary>
public AccountParameter baker { get; set; }

/// <summary>
/// Filter by operation kind (`stake`, `unstake`, `finalize`, or `set_parameters`).
/// Click on the parameter to expand more details.
/// </summary>
public StakingOperationKindParameter kind { get; set; }

public override bool Empty =>
base.Empty &&
anyof == null &&
baker == null &&
kind == null;

public override string Normalize(string name)
{
return ResponseCacheService.BuildKey("",
("id", id), ("hash", hash), ("counter", counter), ("level", level),
("timestamp", timestamp), ("status", status), ("sender", sender),
("anyof", anyof), ("baker", baker), ("kind", kind));
}
}
}
Loading

0 comments on commit f2f8950

Please sign in to comment.