Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add accounts operations endpoint #166

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 144 additions & 25 deletions Tzkt.Api/Controllers/AccountsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ public async Task<ActionResult<IEnumerable<Account>>> Get(
if (kind?.Eq != null && type == null)
type = new AccountTypeParameter { Eq = 2 };
#endregion

var query = ResponseCacheService.BuildKey(Request.Path.Value,
("id", id), ("address", address), ("type", type), ("kind", kind), ("delegate", @delegate),
("id", id), ("address", address), ("type", type), ("kind", kind), ("delegate", @delegate),
("stakedPseudotokens", stakedPseudotokens), ("balance", balance), ("staked", staked), ("lastActivity", lastActivity),
("select", select), ("sort", sort), ("offset", offset), ("limit", limit));

Expand Down Expand Up @@ -151,7 +151,7 @@ public async Task<ActionResult<int>> GetCount(
#endregion

var query = ResponseCacheService.BuildKey(Request.Path.Value,
("type", type), ("kind", kind), ("balance", balance), ("staked", staked), ("firstActivity", firstActivity));
("type", type), ("kind", kind), ("balance", balance), ("staked", staked), ("firstActivity", firstActivity));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);
Expand All @@ -175,7 +175,7 @@ public async Task<ActionResult<Account>> GetByAddress(
[Required][Address] string address,
bool legacy = true)
{
var query = ResponseCacheService.BuildKey(Request.Path.Value, ("legacy", legacy));
var query = ResponseCacheService.BuildKey(Request.Path.Value, ("legacy", legacy));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);
Expand Down Expand Up @@ -207,9 +207,9 @@ public async Task<ActionResult<IEnumerable<RelatedContract>>> GetContracts(
if (sort != null && !sort.Validate("id", "balance", "creationLevel"))
return new BadRequest($"{nameof(sort)}", "Sorting by the specified field is not allowed.");
#endregion

var query = ResponseCacheService.BuildKey(Request.Path.Value,
("sort", sort), ("offset", offset), ("limit", limit));
("sort", sort), ("offset", offset), ("limit", limit));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);
Expand Down Expand Up @@ -247,10 +247,10 @@ public async Task<ActionResult<IEnumerable<Delegator>>> GetDelegators(
if (sort != null && !sort.Validate("balance", "delegationLevel"))
return new BadRequest($"{nameof(sort)}", "Sorting by the specified field is not allowed.");
#endregion

var query = ResponseCacheService.BuildKey(Request.Path.Value,
("type", type), ("balance", balance), ("delegationLevel", delegationLevel),
("sort", sort), ("offset", offset), ("limit", limit));
("sort", sort), ("offset", offset), ("limit", limit));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);
Expand All @@ -261,18 +261,18 @@ public async Task<ActionResult<IEnumerable<Delegator>>> GetDelegators(
}

/// <summary>
/// Get account operations
/// Get accounts operations
/// </summary>
/// <remarks>
/// Returns a list of operations related to the specified account.
/// Note: for better flexibility this endpoint accumulates query parameters (filters) of each `/operations/{type}` endpoint,
/// so a particular filter may affect several operation types containing this filter.
/// For example, if you specify an `initiator` it will affect all transactions, delegations and originations,
/// because all these types have an `initiator` field.
/// because all these types have an `initiator` field.
/// **NOTE: if you know in advance what operation type you want to get (e.g. transactions), prefer using `/v1/operations/{type}`
/// (e.g. [/v1/operations/transactions](#operation/Operations_GetTransactions)) instead, because it's much more efficient and way more flexible.**
/// </remarks>
/// <param name="address">Account address</param>
/// <param name="account">Account addresses. Supports only `.in` and `.eq` modes</param>
/// <param name="type">Comma separated list of operation types to return (`endorsement`, `preendorsement`, `ballot`, `proposal`, `activation`, `double_baking`,
/// `double_endorsing`, `double_preendorsing`, `nonce_revelation`, `vdf_revelation`, `delegation`, `origination`, `transaction`, `reveal`, `register_constant`,
/// `set_deposits_limit`, `increase_paid_storage`, `tx_rollup_origination`, `tx_rollup_submit_batch`, `tx_rollup_commit`, `tx_rollup_return_bond`,
Expand Down Expand Up @@ -303,9 +303,9 @@ public async Task<ActionResult<IEnumerable<Delegator>>> GetDelegators(
/// <param name="micheline">Format of the parameters, storage and diffs: `0` - JSON, `1` - JSON string, `2` - raw micheline, `3` - raw micheline string</param>
/// <param name="quote">Comma-separated list of ticker symbols to inject historical prices into response</param>
/// <returns></returns>
[HttpGet("{address}/operations")]
public async Task<ActionResult<IEnumerable<Operation>>> GetOperations(
[Required][Address] string address,
[HttpGet("operations")]
public async Task<ActionResult<IEnumerable<Operation>>> GetOperationsForMany(
[Required] AccountParameter account,
string type,
AccountParameter initiator,
AccountParameter sender,
Expand All @@ -331,6 +331,21 @@ public async Task<ActionResult<IEnumerable<Operation>>> GetOperations(
Symbols quote = Symbols.None)
{
#region validate
if (account.Ne != null)
return new BadRequest($"{nameof(account)}.ne", "This parameter doesn't support .ne mode.");

if (account.Ni != null)
return new BadRequest($"{nameof(account)}.ni", "This parameter doesn't support .ni mode.");

if (account.Null != null)
return new BadRequest($"{nameof(account)}.null", "This parameter doesn't support .null mode.");

if (account.Nex != null)
return new BadRequest($"{nameof(account)}.nex", "This parameter doesn't support .nex mode.");

if (account.Eq == -1 || account.In?.Count == 0)
return Ok(Enumerable.Empty<Operation>());

if (initiator != null)
{
if (initiator.Eqx != null)
Expand Down Expand Up @@ -440,28 +455,132 @@ public async Task<ActionResult<IEnumerable<Operation>>> GetOperations(
var _offset = lastId != null
? new OffsetParameter { Cr = lastId }
: null;


// ensures that we have a stable cache key
var accountIds = (account.In ?? new() { account.Eq.Value }).ToHashSet().OrderBy(x => x).ToList();

var query = ResponseCacheService.BuildKey(Request.Path.Value,
("account", string.Join(",", accountIds)),
("type", string.Join(",", types.OrderBy(x => x))),
("initiator", initiator), ("sender", sender), ("target", target), ("prevDelegate", prevDelegate),
("newDelegate", newDelegate), ("contractManager", contractManager), ("contractDelegate", contractDelegate),
("originatedContract", originatedContract), ("accuser", accuser), ("offender", offender), ("baker", baker),
("level", level), ("timestamp", timestamp), ("entrypoint", entrypoint), ("parameter", parameter), ("hasInternals", hasInternals),
("status", status), ("sort", sort), ("lastId", lastId), ("limit", limit), ("micheline", micheline), ("quote", quote));
("status", status), ("sort", sort), ("lastId", lastId), ("limit", limit), ("micheline", micheline), ("quote", quote));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);

var res = await Accounts.GetOperations(address, types, initiator, sender, target, prevDelegate, newDelegate, contractManager, contractDelegate, originatedContract, accuser, offender, baker, level, timestamp, entrypoint, parameter, hasInternals, status, _sort, _offset, limit, micheline, quote);
var res = await Accounts.GetOperations(accountIds, types, initiator, sender, target, prevDelegate, newDelegate, contractManager, contractDelegate, originatedContract, accuser, offender, baker, level, timestamp, entrypoint, parameter, hasInternals, status, _sort, _offset, limit, micheline, quote);
cached = ResponseCache.Set(query, res);
return this.Bytes(cached);
}

/// <summary>
/// Get account operations
/// </summary>
/// <remarks>
/// Returns a list of operations related to the specified account.
/// Note: for better flexibility this endpoint accumulates query parameters (filters) of each `/operations/{type}` endpoint,
/// so a particular filter may affect several operation types containing this filter.
/// For example, if you specify an `initiator` it will affect all transactions, delegations and originations,
/// because all these types have an `initiator` field.
/// **NOTE: if you know in advance what operation type you want to get (e.g. transactions), prefer using `/v1/operations/{type}`
/// (e.g. [/v1/operations/transactions](#operation/Operations_GetTransactions)) instead, because it's much more efficient and way more flexible.**
/// </remarks>
/// <param name="address">Account address</param>
/// <param name="type">Comma separated list of operation types to return (`endorsement`, `preendorsement`, `ballot`, `proposal`, `activation`, `double_baking`,
/// `double_endorsing`, `double_preendorsing`, `nonce_revelation`, `vdf_revelation`, `delegation`, `origination`, `transaction`, `reveal`, `register_constant`,
/// `set_deposits_limit`, `increase_paid_storage`, `tx_rollup_origination`, `tx_rollup_submit_batch`, `tx_rollup_commit`, `tx_rollup_return_bond`,
/// `tx_rollup_finalize_commitment`, `tx_rollup_remove_commitment`, `tx_rollup_rejection`, `tx_rollup_dispatch_tickets`, `transfer_ticket`, `migration`,
/// `update_consensus_key`, `drain_delegate`, `sr_add_messages`, `sr_cement`, `sr_execute`, `sr_originate`, `sr_publish`, `sr_recover_bond`, `sr_refute`,
/// `revelation_penalty`, `baking`, `endorsing_reward`). If not specified then the default set will be returned.</param>
/// <param name="initiator">Filters transactions, delegations and originations by initiator. Allowed fields for `.eqx` mode: none.</param>
/// <param name="sender">Filters transactions, delegations, originations, reveals and seed nonce revelations by sender. Allowed fields for `.eqx` mode: none.</param>
/// <param name="target">Filters transactions by target. Allowed fields for `.eqx` mode: none.</param>
/// <param name="prevDelegate">Filters delegations by prev delegate. Allowed fields for `.eqx` mode: none.</param>
/// <param name="newDelegate">Filters delegations by new delegate. Allowed fields for `.eqx` mode: none.</param>
/// <param name="contractManager">Filters origination operations by manager. Allowed fields for `.eqx` mode: none.</param>
/// <param name="contractDelegate">Filters origination operations by delegate. Allowed fields for `.eqx` mode: none.</param>
/// <param name="originatedContract">Filters origination operations by originated contract. Allowed fields for `.eqx` mode: none.</param>
/// <param name="accuser">Filters double baking and double endorsing by accuser. Allowed fields for `.eqx` mode: none.</param>
/// <param name="offender">Filters double baking and double endorsing by offender. Allowed fields for `.eqx` mode: none.</param>
/// <param name="baker">Filters seed nonce revelation operations by baker. Allowed fields for `.eqx` mode: none.</param>
/// <param name="level">Filters operations by level.</param>
/// <param name="timestamp">Filters operations by timestamp.</param>
/// <param name="entrypoint">Filters transactions by entrypoint called on the target contract.</param>
/// <param name="parameter">Filters transactions by parameter value. Note, this query parameter supports the following format: `?parameter{.path?}{.mode?}=...`,
/// so you can specify a path to a particular field to filter by, for example: `?parameter.token_id=...` or `?parameter.sigs.0.ne=...`.</param>
/// <param name="hasInternals">Filters transactions by presence of internal operations.</param>
/// <param name="status">Filters transactions, delegations, originations and reveals by operation status (`applied`, `failed`, `backtracked`, `skipped`).</param>
/// <param name="sort">Sort mode (0 - ascending, 1 - descending), operations of different types can only be sorted by ID.</param>
/// <param name="lastId">Id of the last operation received, which is used as an offset for pagination</param>
/// <param name="limit">Number of items to return</param>
/// <param name="micheline">Format of the parameters, storage and diffs: `0` - JSON, `1` - JSON string, `2` - raw micheline, `3` - raw micheline string</param>
/// <param name="quote">Comma-separated list of ticker symbols to inject historical prices into response</param>
/// <returns></returns>
[HttpGet("{address}/operations")]
public async Task<ActionResult<IEnumerable<Operation>>> GetOperations(
[Required][Address] string address,
string type,
AccountParameter initiator,
AccountParameter sender,
AccountParameter target,
AccountParameter prevDelegate,
AccountParameter newDelegate,
AccountParameter contractManager,
AccountParameter contractDelegate,
AccountParameter originatedContract,
AccountParameter accuser,
AccountParameter offender,
AccountParameter baker,
Int32Parameter level,
DateTimeParameter timestamp,
StringParameter entrypoint,
JsonParameter parameter,
BoolParameter hasInternals,
OperationStatusParameter status,
SortMode sort = SortMode.Descending,
long? lastId = null,
[Range(0, 1000)] int limit = 100,
MichelineFormat micheline = MichelineFormat.Json,
Symbols quote = Symbols.None)
{
var account = await Accounts.GetRawAsync(address);
return await GetOperationsForMany(
new () { Eq = account.Id },
type,
initiator,
sender,
target,
prevDelegate,
newDelegate,
contractManager,
contractDelegate,
originatedContract,
accuser,
offender,
baker,
level,
timestamp,
entrypoint,
parameter,
hasInternals,
status,
sort,
lastId,
limit,
micheline,
quote
);

}

[OpenApiIgnore]
[HttpGet("{address}/metadata")]
public async Task<ActionResult<RawJson>> GetMetadata([Required][Address] string address)
{
var query = ResponseCacheService.BuildKey(Request.Path.Value);
var query = ResponseCacheService.BuildKey(Request.Path.Value);

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);
Expand All @@ -482,11 +601,11 @@ public async Task<ActionResult<RawJson>> GetMetadata([Required][Address] string
[HttpGet("{address}/counter")]
public async Task<ActionResult<int>> GetCounter([Required][Address] string address)
{
var query = ResponseCacheService.BuildKey(Request.Path.Value);
var query = ResponseCacheService.BuildKey(Request.Path.Value);

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);

var res = await Accounts.GetCounterAsync(address);
cached = ResponseCache.Set(query, res);
return this.Bytes(cached);
Expand All @@ -503,7 +622,7 @@ public async Task<ActionResult<int>> GetCounter([Required][Address] string addre
[HttpGet("{address}/balance")]
public async Task<ActionResult<long>> GetBalance([Required][Address] string address)
{
var query = ResponseCacheService.BuildKey(Request.Path.Value);
var query = ResponseCacheService.BuildKey(Request.Path.Value);

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);
Expand All @@ -517,7 +636,7 @@ public async Task<ActionResult<long>> GetBalance([Required][Address] string addr
/// Get balance at level
/// </summary>
/// <remarks>
/// Returns account balance* at the specified block.
/// Returns account balance* at the specified block.
/// \* - for non-baker tz-accounts historical balances do not include staked tez,
/// because stakers do not really have staked tez on their balance, they have staking pseudotokens instead.
/// If you want to get a full historical balance, including staked tez, use the Tezos node RPC:
Expand All @@ -531,7 +650,7 @@ public async Task<ActionResult<long>> GetBalanceAtLevel(
[Required][Address] string address,
[Min(0)] int level)
{
var query = ResponseCacheService.BuildKey(Request.Path.Value);
var query = ResponseCacheService.BuildKey(Request.Path.Value);

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);
Expand All @@ -545,7 +664,7 @@ public async Task<ActionResult<long>> GetBalanceAtLevel(
/// Get balance at date
/// </summary>
/// <remarks>
/// Returns account balance* at the specified datetime.
/// Returns account balance* at the specified datetime.
/// \* - for non-baker tz-accounts historical balances do not include staked tez,
/// because stakers do not really have staked tez on their balance, they have staking pseudotokens instead.
/// If you want to get a full historical balance, including staked tez, use the Tezos node RPC:
Expand Down Expand Up @@ -573,7 +692,7 @@ public async Task<ActionResult<long>> GetBalanceAtDate(
/// Get balance history
/// </summary>
/// <remarks>
/// Returns time series with historical balances* (only changes, without duplicates).
/// Returns time series with historical balances* (only changes, without duplicates).
/// \* - for non-baker tz-accounts historical balances do not include staked tez,
/// because stakers do not really have staked tez on their balance, they have staking pseudotokens instead.
/// If you want to get a full historical balance, including staked tez, use the Tezos node RPC:
Expand Down
Loading