diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 76ab70f3c..0a2c677ce 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,22 +12,23 @@ jobs:
runs-on: windows-latest
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
steps:
- - name: Set up JDK 11
- uses: actions/setup-java@v1
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
with:
- java-version: 1.11
- - uses: actions/checkout@v2
+ java-version: 17
+ distribution: 'zulu' # Alternative distribution options are available.
+ - uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Cache SonarCloud packages
- uses: actions/cache@v1
+ uses: actions/cache@v3
with:
path: ~\sonar\cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache SonarCloud scanner
id: cache-sonar-scanner
- uses: actions/cache@v1
+ uses: actions/cache@v3
with:
path: .\.sonar\scanner
key: ${{ runner.os }}-sonar-scanner
@@ -44,6 +45,6 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
shell: powershell
run: |
- .\.sonar\scanner\dotnet-sonarscanner begin /k:"baking-bad_tzkt" /o:"baking-bad" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io"
+ .\.sonar\scanner\dotnet-sonarscanner begin /k:"baking-bad_tzkt" /o:"baking-bad" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io"
dotnet build
- .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
+ .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
diff --git a/Makefile b/Makefile
index d527d69cb..531a89df7 100644
--- a/Makefile
+++ b/Makefile
@@ -109,4 +109,7 @@ mumbai-stop:
docker-compose -f docker-compose.mumbai.yml down
mumbai-db-start:
+ docker-compose -f docker-compose.mumbai.yml up -d mumbai-db
+reset:
+ docker-compose -f docker-compose.mumbai.yml down --volumes
docker-compose -f docker-compose.mumbai.yml up -d mumbai-db
\ No newline at end of file
diff --git a/Tzkt.Api/Controllers/TicketsController.cs b/Tzkt.Api/Controllers/TicketsController.cs
new file mode 100644
index 000000000..c894915ca
--- /dev/null
+++ b/Tzkt.Api/Controllers/TicketsController.cs
@@ -0,0 +1,309 @@
+using Microsoft.AspNetCore.Mvc;
+using Tzkt.Api.Models;
+using Tzkt.Api.Repositories;
+using Tzkt.Api.Services;
+using Tzkt.Api.Services.Cache;
+
+namespace Tzkt.Api.Controllers
+{
+ [ApiController]
+ [Route("v1/tickets")]
+ public class TicketsController : ControllerBase
+ {
+ readonly TicketsRepository Tickets;
+ readonly StateCache State;
+ readonly ResponseCacheService ResponseCache;
+
+ public TicketsController(TicketsRepository tickets, StateCache state, ResponseCacheService responseCache)
+ {
+ Tickets = tickets;
+ State = state;
+ ResponseCache = responseCache;
+ }
+
+ #region tickets
+ ///
+ /// Get tickets count
+ ///
+ ///
+ /// Returns a total number of tickets.
+ ///
+ /// Filter
+ ///
+ [HttpGet("count")]
+ public async Task> GetTicketsCount([FromQuery] TicketFilter filter)
+ {
+ if (filter.Empty)
+ return Ok(State.Current.TicketsCount);
+
+ var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter));
+
+ if (ResponseCache.TryGet(query, out var cached))
+ return this.Bytes(cached);
+
+ var res = await Tickets.GetTicketsCount(filter);
+ cached = ResponseCache.Set(query, res);
+ return this.Bytes(cached);
+ }
+
+ ///
+ /// Get tickets
+ ///
+ ///
+ /// Returns a list of tickets.
+ ///
+ /// Filter
+ /// Pagination
+ /// Selection
+ ///
+ [HttpGet]
+ public async Task>> GetTickets(
+ [FromQuery] TicketFilter filter,
+ [FromQuery] Pagination pagination,
+ [FromQuery] Selection selection)
+ {
+ var query = ResponseCacheService.BuildKey(Request.Path.Value,
+ ("filter", filter), ("pagination", pagination), ("selection", selection));
+
+ if (ResponseCache.TryGet(query, out var cached))
+ return this.Bytes(cached);
+
+ object res;
+ if (selection.select == null)
+ {
+ res = await Tickets.GetTickets(filter, pagination);
+ }
+ else
+ {
+ res = new SelectionResponse
+ {
+ Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(),
+ Rows = await Tickets.GetTickets(filter, pagination, selection.select.Fields ?? selection.select.Values)
+ };
+ }
+ cached = ResponseCache.Set(query, res);
+ return this.Bytes(cached);
+ }
+ #endregion
+
+ #region ticket balances
+ ///
+ /// Get ticket balances count
+ ///
+ ///
+ /// Returns a total number of ticket balances.
+ ///
+ /// Filter
+ ///
+ [HttpGet("balances/count")]
+ public async Task> GetTicketBalancesCount([FromQuery] TicketBalanceFilter filter)
+ {
+ if (filter.Empty)
+ return Ok(State.Current.TicketBalancesCount);
+
+ #region optimizations
+ if (filter.account != null && (filter.account.Eq == -1 || filter.account.In?.Count == 0 && !filter.account.InHasNull))
+ return Ok(0);
+ #endregion
+
+ var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter));
+
+ if (ResponseCache.TryGet(query, out var cached))
+ return this.Bytes(cached);
+
+ var res = await Tickets.GetTicketBalancesCount(filter);
+ cached = ResponseCache.Set(query, res);
+ return this.Bytes(cached);
+ }
+
+ ///
+ /// Get ticket balances
+ ///
+ ///
+ /// Returns a list of ticket balances.
+ ///
+ /// Filter
+ /// Pagination
+ /// Selection
+ ///
+ [HttpGet("balances")]
+ public async Task>> GetTicketBalances(
+ [FromQuery] TicketBalanceFilter filter,
+ [FromQuery] Pagination pagination,
+ [FromQuery] Selection selection)
+ {
+ #region optimizations
+ if (filter.account != null && (filter.account.Eq == -1 || filter.account.In?.Count == 0 && !filter.account.InHasNull))
+ return Ok(Enumerable.Empty());
+ #endregion
+
+ var query = ResponseCacheService.BuildKey(Request.Path.Value,
+ ("filter", filter), ("pagination", pagination), ("selection", selection));
+
+ if (ResponseCache.TryGet(query, out var cached))
+ return this.Bytes(cached);
+
+ object res;
+ if (selection.select == null)
+ {
+ res = await Tickets.GetTicketBalances(filter, pagination);
+ }
+ else
+ {
+ res = new SelectionResponse
+ {
+ Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(),
+ Rows = await Tickets.GetTicketBalances(filter, pagination, selection.select.Fields ?? selection.select.Values)
+ };
+ }
+ cached = ResponseCache.Set(query, res);
+ return this.Bytes(cached);
+ }
+ #endregion
+
+ #region ticket transfers
+ ///
+ /// Get ticket transfers count
+ ///
+ ///
+ /// Returns the total number of ticket transfers.
+ ///
+ /// Filter
+ ///
+ [HttpGet("transfers/count")]
+ public async Task> GetTicketTransfersCount([FromQuery] TicketTransferFilter filter)
+ {
+ if (filter.Empty)
+ return Ok(State.Current.TicketTransfersCount);
+
+ #region optimizations
+ if (filter.from != null && (filter.from.Eq == -1 || filter.from.In?.Count == 0 && !filter.from.InHasNull))
+ return Ok(0);
+
+ if (filter.to != null && (filter.to.Eq == -1 || filter.to.In?.Count == 0 && !filter.to.InHasNull))
+ return Ok(0);
+
+ if (filter.anyof != null && (filter.anyof.Eq == -1 || filter.anyof.In?.Count == 0 && !filter.anyof.InHasNull))
+ return Ok(0);
+ #endregion
+
+ var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter));
+
+ if (ResponseCache.TryGet(query, out var cached))
+ return this.Bytes(cached);
+
+ var res = await Tickets.GetTicketTransfersCount(filter);
+ cached = ResponseCache.Set(query, res);
+ return this.Bytes(cached);
+ }
+
+ ///
+ /// Get ticket transfers
+ ///
+ ///
+ /// Returns a list of ticket transfers.
+ ///
+ /// Filter
+ /// Pagination
+ /// Selection
+ ///
+ [HttpGet("transfers")]
+ public async Task>> GetTicketTransfers(
+ [FromQuery] TicketTransferFilter filter,
+ [FromQuery] Pagination pagination,
+ [FromQuery] Selection selection)
+ {
+ #region optimizations
+ if (filter.from != null && (filter.from.Eq == -1 || filter.from.In?.Count == 0 && !filter.from.InHasNull))
+ return Ok(Enumerable.Empty());
+
+ if (filter.to != null && (filter.to.Eq == -1 || filter.to.In?.Count == 0 && !filter.to.InHasNull))
+ return Ok(Enumerable.Empty());
+
+ if (filter.anyof != null && (filter.anyof.Eq == -1 || filter.anyof.In?.Count == 0 && !filter.anyof.InHasNull))
+ return Ok(Enumerable.Empty());
+ #endregion
+
+ var query = ResponseCacheService.BuildKey(Request.Path.Value,
+ ("filter", filter), ("pagination", pagination), ("selection", selection));
+
+ if (ResponseCache.TryGet(query, out var cached))
+ return this.Bytes(cached);
+
+ object res;
+ if (selection.select == null)
+ {
+ res = await Tickets.GetTicketTransfers(filter, pagination);
+ }
+ else
+ {
+ res = new SelectionResponse
+ {
+ Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(),
+ Rows = await Tickets.GetTicketTransfers(filter, pagination, selection.select.Fields ?? selection.select.Values)
+ };
+ }
+ cached = ResponseCache.Set(query, res);
+ return this.Bytes(cached);
+ }
+ #endregion
+
+ #region historical balances
+ ///
+ /// Get historical ticket balances
+ ///
+ ///
+ /// Returns a list of ticket balances at the end of the specified block.
+ /// Note, this endpoint is quite heavy, therefore at least one of the filters
+ /// (`account`, `ticket.id`, `ticket.ticketer`) must be specified.
+ ///
+ /// Level of the block at the end of which historical balances must be calculated
+ /// Filter
+ /// Pagination
+ /// Selection
+ ///
+ [HttpGet("historical_balances/{level:int}")]
+ public async Task>> GetHistoricalTicketBalances(int level,
+ [FromQuery] TicketBalanceShortFilter filter,
+ [FromQuery] Pagination pagination,
+ [FromQuery] Selection selection)
+ {
+
+ if (filter.account?.Eq == null &&
+ filter.account?.In == null &&
+ filter.ticket.id?.Eq == null &&
+ filter.ticket.id?.In == null &&
+ filter.ticket.ticketer?.Eq == null &&
+ filter.ticket.ticketer?.In == null)
+ return new BadRequest("query", "At least one of the filters (`account`, `ticket.id`, `ticket.ticketer`) must be specified");
+
+ #region optimizations
+ if (filter.account != null && (filter.account.Eq == -1 || filter.account.In?.Count == 0 && !filter.account.InHasNull))
+ return Ok(Enumerable.Empty());
+ #endregion
+
+ var query = ResponseCacheService.BuildKey(Request.Path.Value,
+ ("filter", filter), ("pagination", pagination), ("selection", selection));
+
+ if (ResponseCache.TryGet(query, out var cached))
+ return this.Bytes(cached);
+
+ object res;
+ if (selection.select == null)
+ {
+ res = await Tickets.GetHistoricalTicketBalances(level, filter, pagination);
+ }
+ else
+ {
+ res = new SelectionResponse
+ {
+ Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(),
+ Rows = await Tickets.GetHistoricalTicketBalances(level, filter, pagination, selection.select.Fields ?? selection.select.Values)
+ };
+ }
+ cached = ResponseCache.Set(query, res);
+ return this.Bytes(cached);
+ }
+ #endregion
+ }
+}
diff --git a/Tzkt.Api/Extensions/ModelBindingContextExtension.cs b/Tzkt.Api/Extensions/ModelBindingContextExtension.cs
index 2cc11532c..883317f22 100644
--- a/Tzkt.Api/Extensions/ModelBindingContextExtension.cs
+++ b/Tzkt.Api/Extensions/ModelBindingContextExtension.cs
@@ -4,6 +4,7 @@
using System.Text.Json;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Netezos.Encoding;
namespace Tzkt.Api
{
@@ -189,6 +190,68 @@ public static bool TryGetInt64List(this ModelBindingContext bindingContext, stri
return true;
}
+ public static bool TryGetMicheline(this ModelBindingContext bindingContext, string name, ref bool hasValue, out IMicheline result)
+ {
+ result = null;
+ var valueObject = bindingContext.ValueProvider.GetValue(name);
+
+ if (valueObject != ValueProviderResult.None)
+ {
+ bindingContext.ModelState.SetModelValue(name, valueObject);
+ if (!string.IsNullOrEmpty(valueObject.FirstValue))
+ {
+ try
+ {
+ result = Micheline.FromJson(valueObject.FirstValue);
+ hasValue = true;
+ }
+ catch
+ {
+ bindingContext.ModelState.TryAddModelError(name, "Invalid Micheline value.");
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public static bool TryGetMichelineList(this ModelBindingContext bindingContext, string name, ref bool hasValue, out List result)
+ {
+ result = null;
+ var valueObject = bindingContext.ValueProvider.GetValue(name);
+
+ if (valueObject != ValueProviderResult.None)
+ {
+ bindingContext.ModelState.SetModelValue(name, valueObject);
+ if (!string.IsNullOrEmpty(valueObject.FirstValue))
+ {
+ try
+ {
+ var json = valueObject.FirstValue.Trim();
+ if (json[0] != '[') json = $"[{json}]";
+ var values = JsonSerializer.Deserialize>(json);
+
+ if (values.Count == 0)
+ {
+ bindingContext.ModelState.TryAddModelError(name, "List should contain at least one item.");
+ return false;
+ }
+
+ hasValue = true;
+ result = values;
+ }
+ catch
+ {
+ bindingContext.ModelState.TryAddModelError(name, "List contains invalid Micheline value(s).");
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
public static bool TryGetDateTime(this ModelBindingContext bindingContext, string name, ref bool hasValue, out DateTime? result)
{
result = null;
diff --git a/Tzkt.Api/Models/Accounts/Contract.cs b/Tzkt.Api/Models/Accounts/Contract.cs
index 1b51ac6c6..9f11fc27f 100644
--- a/Tzkt.Api/Models/Accounts/Contract.cs
+++ b/Tzkt.Api/Models/Accounts/Contract.cs
@@ -76,14 +76,14 @@ public class Contract : Account
public int NumContracts { get; set; }
///
- /// Number of account tokens with non-zero balances.
+ /// Number of tokens minted in the contract.
///
- public int ActiveTokensCount { get; set; }
+ public int TokensCount { get; set; }
///
- /// Number of tokens minted in the contract.
+ /// Number of account tokens with non-zero balances.
///
- public int TokensCount { get; set; }
+ public int ActiveTokensCount { get; set; }
///
/// Number of tokens the account ever had.
@@ -95,6 +95,26 @@ public class Contract : Account
///
public int TokenTransfersCount { get; set; }
+ ///
+ /// Number of tickets minted in the contract.
+ ///
+ public int TicketsCount { get; set; }
+
+ ///
+ /// Number of tickets the account owns.
+ ///
+ public int ActiveTicketsCount { get; set; }
+
+ ///
+ /// Number of tickets the account ever owned.
+ ///
+ public int TicketBalancesCount { get; set; }
+
+ ///
+ /// Number of ticket transfers from/to the account.
+ ///
+ public int TicketTransfersCount { get; set; }
+
///
/// Number of delegation operations of the contract.
///
diff --git a/Tzkt.Api/Models/Accounts/Delegate.cs b/Tzkt.Api/Models/Accounts/Delegate.cs
index fb69d4fff..5aefb6d8d 100644
--- a/Tzkt.Api/Models/Accounts/Delegate.cs
+++ b/Tzkt.Api/Models/Accounts/Delegate.cs
@@ -130,6 +130,21 @@ public class Delegate : Account
///
public int TokenTransfersCount { get; set; }
+ ///
+ /// Number of tickets the account owns.
+ ///
+ public int ActiveTicketsCount { get; set; }
+
+ ///
+ /// Number of tickets the account ever owned.
+ ///
+ public int TicketBalancesCount { get; set; }
+
+ ///
+ /// Number of ticket transfers from/to the account.
+ ///
+ public int TicketTransfersCount { get; set; }
+
///
/// Number of current delegators (accounts, delegated their funds) of the delegate (baker)
///
diff --git a/Tzkt.Api/Models/Accounts/Ghost.cs b/Tzkt.Api/Models/Accounts/Ghost.cs
index 0f8c5559f..5241758ee 100644
--- a/Tzkt.Api/Models/Accounts/Ghost.cs
+++ b/Tzkt.Api/Models/Accounts/Ghost.cs
@@ -40,6 +40,21 @@ public class Ghost : Account
///
public int TokenTransfersCount { get; set; }
+ ///
+ /// Number of tickets the account owns.
+ ///
+ public int ActiveTicketsCount { get; set; }
+
+ ///
+ /// Number of tickets the account ever owned.
+ ///
+ public int TicketBalancesCount { get; set; }
+
+ ///
+ /// Number of ticket transfers from/to the account.
+ ///
+ public int TicketTransfersCount { get; set; }
+
///
/// Block height at which the ghost contract appeared first time
///
diff --git a/Tzkt.Api/Models/Accounts/Rollup.cs b/Tzkt.Api/Models/Accounts/Rollup.cs
index 3f31d52c1..a4ce343c8 100644
--- a/Tzkt.Api/Models/Accounts/Rollup.cs
+++ b/Tzkt.Api/Models/Accounts/Rollup.cs
@@ -50,6 +50,21 @@ public class Rollup : Account
///
public int TokenTransfersCount { get; set; }
+ ///
+ /// Number of tickets the account owns.
+ ///
+ public int ActiveTicketsCount { get; set; }
+
+ ///
+ /// Number of tickets the account ever owned.
+ ///
+ public int TicketBalancesCount { get; set; }
+
+ ///
+ /// Number of ticket transfers from/to the account.
+ ///
+ public int TicketTransfersCount { get; set; }
+
///
/// Number of transaction operations related to the account
///
diff --git a/Tzkt.Api/Models/Accounts/SmartRollup.cs b/Tzkt.Api/Models/Accounts/SmartRollup.cs
index 0090c2c7e..1ace94ef8 100644
--- a/Tzkt.Api/Models/Accounts/SmartRollup.cs
+++ b/Tzkt.Api/Models/Accounts/SmartRollup.cs
@@ -104,6 +104,21 @@ public class SmartRollup : Account
///
public int TokenTransfersCount { get; set; }
+ ///
+ /// Number of tickets the account owns.
+ ///
+ public int ActiveTicketsCount { get; set; }
+
+ ///
+ /// Number of tickets the account ever owned.
+ ///
+ public int TicketBalancesCount { get; set; }
+
+ ///
+ /// Number of ticket transfers from/to the account.
+ ///
+ public int TicketTransfersCount { get; set; }
+
///
/// Number of transaction operations related to the account
///
diff --git a/Tzkt.Api/Models/Accounts/User.cs b/Tzkt.Api/Models/Accounts/User.cs
index 7b10d81f9..14c6ebf74 100644
--- a/Tzkt.Api/Models/Accounts/User.cs
+++ b/Tzkt.Api/Models/Accounts/User.cs
@@ -98,6 +98,21 @@ public class User : Account
///
public int TokenTransfersCount { get; set; }
+ ///
+ /// Number of tickets the account owns.
+ ///
+ public int ActiveTicketsCount { get; set; }
+
+ ///
+ /// Number of tickets the account ever owned.
+ ///
+ public int TicketBalancesCount { get; set; }
+
+ ///
+ /// Number of ticket transfers from/to the account.
+ ///
+ public int TicketTransfersCount { get; set; }
+
///
/// Number of account activation operations. Are used to activate accounts that were recommended allocations of
/// tezos tokens for donations to the Tezos Foundation’s fundraiser
diff --git a/Tzkt.Api/Models/Operations/SmartRollupExecuteOperation.cs b/Tzkt.Api/Models/Operations/SmartRollupExecuteOperation.cs
index 53388b87e..0b995288e 100644
--- a/Tzkt.Api/Models/Operations/SmartRollupExecuteOperation.cs
+++ b/Tzkt.Api/Models/Operations/SmartRollupExecuteOperation.cs
@@ -91,6 +91,11 @@ public class SmartRollupExecuteOperation : Operation
///
public List Errors { get; set; }
+ ///
+ /// Number of ticket transfers produced by the operation, or `null` if there are no transfers
+ ///
+ public int? TicketTransfersCount { get; set; }
+
#region injecting
///
/// Injected historical quote at the time of operation
diff --git a/Tzkt.Api/Models/Operations/TransactionOperation.cs b/Tzkt.Api/Models/Operations/TransactionOperation.cs
index 0945057a2..35b31d6a1 100644
--- a/Tzkt.Api/Models/Operations/TransactionOperation.cs
+++ b/Tzkt.Api/Models/Operations/TransactionOperation.cs
@@ -150,6 +150,11 @@ public class TransactionOperation : Operation
///
public int? TokenTransfersCount { get; set; }
+ ///
+ /// Number of ticket transfers produced by the operation, or `null` if there are no transfers
+ ///
+ public int? TicketTransfersCount { get; set; }
+
///
/// Number of events produced by the operation, or `null` if there are no events
///
diff --git a/Tzkt.Api/Models/Operations/TransferTicketOperation.cs b/Tzkt.Api/Models/Operations/TransferTicketOperation.cs
index 804db3c3c..95d544ec2 100644
--- a/Tzkt.Api/Models/Operations/TransferTicketOperation.cs
+++ b/Tzkt.Api/Models/Operations/TransferTicketOperation.cs
@@ -95,6 +95,11 @@ public class TransferTicketOperation : Operation
///
public string Entrypoint { get; set; }
+ ///
+ /// Number of ticket transfers produced by the operation, or `null` if there are no transfers
+ ///
+ public int? TicketTransfersCount { get; set; }
+
///
/// Micheline type of the content
///
diff --git a/Tzkt.Api/Models/Tickets/Ticket.cs b/Tzkt.Api/Models/Tickets/Ticket.cs
new file mode 100644
index 000000000..b8ba4670f
--- /dev/null
+++ b/Tzkt.Api/Models/Tickets/Ticket.cs
@@ -0,0 +1,103 @@
+using Netezos.Encoding;
+
+namespace Tzkt.Api.Models
+{
+ public class Ticket
+ {
+ ///
+ /// Internal TzKT id.
+ /// **[sortable]**
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// Contract, issued the ticket.
+ ///
+ public Alias Ticketer { get; set; }
+
+ ///
+ /// Ticket content type in Micheline format.
+ ///
+ public IMicheline RawType { get; set; }
+
+ ///
+ /// Ticket content in Micheline format.
+ ///
+ public IMicheline RawContent { get; set; }
+
+ ///
+ /// Ticket content in JSON format.
+ ///
+ public RawJson Content { get; set; }
+
+ ///
+ /// 32-bit hash of the ticket content type.
+ ///
+ public int TypeHash { get; set; }
+
+ ///
+ /// 32-bit hash of the ticket content.
+ ///
+ public int ContentHash { get; set; }
+
+ ///
+ /// Account, minted the ticket first.
+ ///
+ public Alias FirstMinter { get; set; }
+
+ ///
+ /// Level of the block where the ticket was first seen.
+ /// **[sortable]**
+ ///
+ public int FirstLevel { get; set; }
+
+ ///
+ /// Timestamp of the block where the ticket was first seen.
+ ///
+ public DateTime FirstTime { get; set; }
+
+ ///
+ /// Level of the block where the ticket was last seen.
+ /// **[sortable]**
+ ///
+ public int LastLevel { get; set; }
+
+ ///
+ /// Timestamp of the block where the ticket was last seen.
+ ///
+ public DateTime LastTime { get; set; }
+
+ ///
+ /// Total number of transfers.
+ /// **[sortable]**
+ ///
+ public int TransfersCount { get; set; }
+
+ ///
+ /// Total number of holders ever seen.
+ /// **[sortable]**
+ ///
+ public int BalancesCount { get; set; }
+
+ ///
+ /// Total number of current holders.
+ /// **[sortable]**
+ ///
+ public int HoldersCount { get; set; }
+
+ ///
+ /// Total amount minted.
+ ///
+ public string TotalMinted { get; set; }
+
+ ///
+ /// Total amount burned.
+ ///
+ public string TotalBurned { get; set; }
+
+ ///
+ /// Total amount exists.
+ ///
+ public string TotalSupply { get; set; }
+ }
+}
diff --git a/Tzkt.Api/Models/Tickets/TicketBalance.cs b/Tzkt.Api/Models/Tickets/TicketBalance.cs
new file mode 100644
index 000000000..7fa4850b8
--- /dev/null
+++ b/Tzkt.Api/Models/Tickets/TicketBalance.cs
@@ -0,0 +1,57 @@
+namespace Tzkt.Api.Models
+{
+ public class TicketBalance
+ {
+ ///
+ /// Internal TzKT id.
+ /// **[sortable]**
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// Ticket info.
+ /// Click on the field to expand more details.
+ ///
+ public TicketInfo Ticket { get; set; }
+
+ ///
+ /// Owner account.
+ /// Click on the field to expand more details.
+ ///
+ public Alias Account { get; set; }
+
+ ///
+ /// Balance.
+ /// **[sortable]**
+ ///
+ public string Balance { get; set; }
+
+ ///
+ /// Total number of transfers, affecting the ticket balance.
+ /// **[sortable]**
+ ///
+ public int TransfersCount { get; set; }
+
+ ///
+ /// Level of the block where the ticket balance was first changed.
+ /// **[sortable]**
+ ///
+ public int FirstLevel { get; set; }
+
+ ///
+ /// Timestamp of the block where the ticket balance was first changed.
+ ///
+ public DateTime FirstTime { get; set; }
+
+ ///
+ /// Level of the block where the ticket balance was last changed.
+ /// **[sortable]**
+ ///
+ public int LastLevel { get; set; }
+
+ ///
+ /// Timestamp of the block where the ticket balance was last changed.
+ ///
+ public DateTime LastTime { get; set; }
+ }
+}
diff --git a/Tzkt.Api/Models/Tickets/TicketBalanceShort.cs b/Tzkt.Api/Models/Tickets/TicketBalanceShort.cs
new file mode 100644
index 000000000..8116d6c7e
--- /dev/null
+++ b/Tzkt.Api/Models/Tickets/TicketBalanceShort.cs
@@ -0,0 +1,28 @@
+namespace Tzkt.Api.Models
+{
+ public class TicketBalanceShort
+ {
+ ///
+ /// Internal TzKT id.
+ /// **[sortable]**
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// Ticket info.
+ /// Click on the field to expand more details.
+ ///
+ public TicketInfoShort Ticket { get; set; }
+
+ ///
+ /// Owner account.
+ /// Click on the field to expand more details.
+ ///
+ public Alias Account { get; set; }
+
+ ///
+ /// Balance.
+ ///
+ public string Balance { get; set; }
+ }
+}
diff --git a/Tzkt.Api/Models/Tickets/TicketInfo.cs b/Tzkt.Api/Models/Tickets/TicketInfo.cs
new file mode 100644
index 000000000..d35fb23ae
--- /dev/null
+++ b/Tzkt.Api/Models/Tickets/TicketInfo.cs
@@ -0,0 +1,47 @@
+using Netezos.Encoding;
+
+namespace Tzkt.Api.Models
+{
+ public class TicketInfo
+ {
+ ///
+ /// Internal TzKT id.
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// Contract, issued the ticket.
+ ///
+ public Alias Ticketer { get; set; }
+
+ ///
+ /// Ticket content type in Micheline format.
+ ///
+ public IMicheline RawType { get; set; }
+
+ ///
+ /// Ticket content in Micheline format.
+ ///
+ public IMicheline RawContent { get; set; }
+
+ ///
+ /// Ticket content in JSON format.
+ ///
+ public RawJson Content { get; set; }
+
+ ///
+ /// 32-bit hash of the ticket content type.
+ ///
+ public int TypeHash { get; set; }
+
+ ///
+ /// 32-bit hash of the ticket content.
+ ///
+ public int ContentHash { get; set; }
+
+ ///
+ /// Total amount exists.
+ ///
+ public string TotalSupply { get; set; }
+ }
+}
diff --git a/Tzkt.Api/Models/Tickets/TicketInfoShort.cs b/Tzkt.Api/Models/Tickets/TicketInfoShort.cs
new file mode 100644
index 000000000..aca108074
--- /dev/null
+++ b/Tzkt.Api/Models/Tickets/TicketInfoShort.cs
@@ -0,0 +1,46 @@
+using Netezos.Encoding;
+using NJsonSchema.Annotations;
+
+namespace Tzkt.Api.Models
+{
+ public class TicketInfoShort
+ {
+ ///
+ /// Internal TzKT id.
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// Contract, issued the ticket.
+ ///
+ public Alias Ticketer { get; set; }
+
+ ///
+ /// Ticket content type in Micheline format.
+ ///
+ public IMicheline RawType { get; set; }
+
+ ///
+ /// Ticket content in Micheline format.
+ ///
+ public IMicheline RawContent { get; set; }
+
+ ///
+ /// Ticket content in JSON format.
+ ///
+ [JsonSchemaType(typeof(object), IsNullable = true)]
+ public RawJson Content { get; set; }
+
+ ///
+ /// 32-bit hash of the ticket content type.
+ /// This field can be used for searching similar tickets (which have the same type).
+ ///
+ public int TypeHash { get; set; }
+
+ ///
+ /// 32-bit hash of the ticket content.
+ /// This field can be used for searching same tickets (which have the same content).
+ ///
+ public int ContentHash { get; set; }
+ }
+}
diff --git a/Tzkt.Api/Models/Tickets/TicketTransfer.cs b/Tzkt.Api/Models/Tickets/TicketTransfer.cs
new file mode 100644
index 000000000..34713d958
--- /dev/null
+++ b/Tzkt.Api/Models/Tickets/TicketTransfer.cs
@@ -0,0 +1,61 @@
+namespace Tzkt.Api.Models
+{
+ public class TicketTransfer
+ {
+ ///
+ /// Internal TzKT id.
+ /// **[sortable]**
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// Level of the block, at which the transfer was made.
+ /// **[sortable]**
+ ///
+ public int Level { get; set; }
+
+ ///
+ /// Timestamp of the block, at which the transfer was made.
+ ///
+ public DateTime Timestamp { get; set; }
+
+ ///
+ /// Ticket info.
+ /// Click on the field to expand more details.
+ ///
+ public TicketInfo Ticket { get; set; }
+
+ ///
+ /// Sender account.
+ /// Click on the field to expand more details.
+ ///
+ public Alias From { get; set; }
+
+ ///
+ /// Recepient account.
+ /// Click on the field to expand more details.
+ ///
+ public Alias To { get; set; }
+
+ ///
+ /// Amount of tickets transferred.
+ /// **[sortable]**
+ ///
+ public string Amount { get; set; }
+
+ ///
+ /// Internal TzKT id of the transaction operation, caused the ticket transfer.
+ ///
+ public long? TransactionId { get; set; }
+
+ ///
+ /// Internal TzKT id of the transfer_ticket operation, caused the ticket transfer.
+ ///
+ public long? TransferTicketId { get; set; }
+
+ ///
+ /// Internal TzKT id of the smart_rollup_execute operation, caused the ticket transfer.
+ ///
+ public long? SmartRollupExecuteId { get; set; }
+ }
+}
diff --git a/Tzkt.Api/Parameters/Binders/MichelineBinder.cs b/Tzkt.Api/Parameters/Binders/MichelineBinder.cs
new file mode 100644
index 000000000..67789b7e4
--- /dev/null
+++ b/Tzkt.Api/Parameters/Binders/MichelineBinder.cs
@@ -0,0 +1,44 @@
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Tzkt.Api
+{
+ public class MichelineBinder : IModelBinder
+ {
+ public Task BindModelAsync(ModelBindingContext bindingContext)
+ {
+ var model = bindingContext.ModelName;
+ var hasValue = false;
+
+ if (!bindingContext.TryGetMicheline($"{model}", ref hasValue, out var value))
+ return Task.CompletedTask;
+
+ if (!bindingContext.TryGetMicheline($"{model}.eq", ref hasValue, out var eq))
+ return Task.CompletedTask;
+
+ if (!bindingContext.TryGetMicheline($"{model}.ne", ref hasValue, out var ne))
+ return Task.CompletedTask;
+
+ if (!bindingContext.TryGetMichelineList($"{model}.in", ref hasValue, out var @in))
+ return Task.CompletedTask;
+
+ if (!bindingContext.TryGetMichelineList($"{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 MichelineParameter
+ {
+ Eq = value ?? eq,
+ Ne = ne,
+ In = @in,
+ Ni = ni
+ });
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Tzkt.Api/Parameters/MichelineParameter.cs b/Tzkt.Api/Parameters/MichelineParameter.cs
new file mode 100644
index 000000000..e11152880
--- /dev/null
+++ b/Tzkt.Api/Parameters/MichelineParameter.cs
@@ -0,0 +1,71 @@
+using System.Text;
+using Microsoft.AspNetCore.Mvc;
+using Netezos.Encoding;
+using NJsonSchema.Annotations;
+
+namespace Tzkt.Api
+{
+ [ModelBinder(BinderType = typeof(MichelineBinder))]
+ [JsonSchemaExtensionData("x-tzkt-extension", "query-parameter")]
+ public class MichelineParameter : INormalizable
+ {
+ ///
+ /// **Equal** filter mode (optional, i.e. `param.eq=123` is the same as `param=123`). \
+ /// Specify Micheline JSON value to get items where the specified field is equal to the specified value.
+ ///
+ /// Example: `?type={"prim":"string"}`.
+ ///
+ public IMicheline Eq { get; set; }
+
+ ///
+ /// **Not equal** filter mode. \
+ /// Specify Micheline JSON value to get items where the specified field is not equal to the specified value.
+ ///
+ /// Example: `?balance.ne={"prim":"string"}`.
+ ///
+ public IMicheline Ne { get; set; }
+
+ ///
+ /// **In list** (any of) filter mode. \
+ /// Specify a comma-separated list of Micheline JSON values where the specified field is equal to one of the specified values.
+ ///
+ /// Example: `?type.in={"prim":"string"},{"prim":"nat"}`.
+ ///
+ public List In { get; set; }
+
+ ///
+ /// **Not in list** (none of) filter mode. \
+ /// Specify a comma-separated list of Micheline JSON values where the specified field is not equal to all the specified values.
+ ///
+ /// Example: `?type.ni={"prim":"string"},{"prim":"nat"}`.
+ ///
+ public List Ni { get; set; }
+
+ public string Normalize(string name)
+ {
+ var sb = new StringBuilder();
+
+ if (Eq != null)
+ {
+ sb.Append($"{name}.eq={Hex.Convert(Eq.ToBytes())}&");
+ }
+
+ if (Ne != null)
+ {
+ sb.Append($"{name}.ne={Hex.Convert(Ne.ToBytes())}&");
+ }
+
+ if (In?.Count > 0)
+ {
+ sb.Append($"{name}.in={string.Join(",", In.Select(x => Hex.Convert(x.ToBytes())).OrderBy(x => x))}&");
+ }
+
+ if (Ni?.Count > 0)
+ {
+ sb.Append($"{name}.ni={string.Join(",", Ni.Select(x => Hex.Convert(x.ToBytes())).OrderBy(x => x))}&");
+ }
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/Tzkt.Api/Parameters/TicketBalanceFilter.cs b/Tzkt.Api/Parameters/TicketBalanceFilter.cs
new file mode 100644
index 000000000..c5bba356a
--- /dev/null
+++ b/Tzkt.Api/Parameters/TicketBalanceFilter.cs
@@ -0,0 +1,81 @@
+using NSwag.Annotations;
+using Tzkt.Api.Services;
+
+namespace Tzkt.Api
+{
+ public class TicketBalanceFilter : INormalizable
+ {
+ ///
+ /// Filter by internal TzKT id.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int64Parameter id { get; set; }
+
+ ///
+ /// Filter by ticket.
+ /// Click on the parameter to expand more details.
+ ///
+ public TicketInfoFilter ticket { get; set; }
+
+ ///
+ /// Filter by account address.
+ /// Click on the parameter to expand more details.
+ ///
+ public AccountParameter account { get; set; }
+
+ ///
+ /// Filter by balance.
+ /// Click on the parameter to expand more details.
+ ///
+ public NatParameter balance { get; set; }
+
+ ///
+ /// Filter by number of transfers.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter transfersCount { get; set; }
+
+ ///
+ /// Filter by level of the block where the balance was first changed.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter firstLevel { get; set; }
+
+ ///
+ /// Filter by timestamp (ISO 8601) of the block where the balance was first changed.
+ /// Click on the parameter to expand more details.
+ ///
+ public TimestampParameter firstTime { get; set; }
+
+ ///
+ /// Filter by level of the block where the balance was last changed.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter lastLevel { get; set; }
+
+ ///
+ /// Filter by timestamp (ISO 8601) of the block where the balance was last changed.
+ /// Click on the parameter to expand more details.
+ ///
+ public TimestampParameter lastTime { get; set; }
+
+ [OpenApiIgnore]
+ public bool Empty =>
+ id == null &&
+ (ticket == null || ticket.Empty) &&
+ account == null &&
+ balance == null &&
+ transfersCount == null &&
+ firstLevel == null &&
+ firstTime == null &&
+ lastLevel == null &&
+ lastTime == null;
+
+ public string Normalize(string name)
+ {
+ return ResponseCacheService.BuildKey("",
+ ("id", id), ("ticket", ticket), ("account", account), ("balance", balance), ("transfersCount", transfersCount),
+ ("firstLevel", firstLevel), ("firstTime", firstTime), ("lastLevel", lastLevel), ("lastTime", lastTime));
+ }
+ }
+}
diff --git a/Tzkt.Api/Parameters/TicketBalanceShortFilter.cs b/Tzkt.Api/Parameters/TicketBalanceShortFilter.cs
new file mode 100644
index 000000000..042a647ef
--- /dev/null
+++ b/Tzkt.Api/Parameters/TicketBalanceShortFilter.cs
@@ -0,0 +1,45 @@
+using NSwag.Annotations;
+using Tzkt.Api.Services;
+
+namespace Tzkt.Api
+{
+ public class TicketBalanceShortFilter : INormalizable
+ {
+ ///
+ /// Filter by internal TzKT id.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int64Parameter id { get; set; }
+
+ ///
+ /// Filter by ticket.
+ /// Click on the parameter to expand more details.
+ ///
+ public TicketInfoShortFilter ticket { get; set; }
+
+ ///
+ /// Filter by account address.
+ /// Click on the parameter to expand more details.
+ ///
+ public AccountParameter account { get; set; }
+
+ ///
+ /// Filter by balance.
+ /// Click on the parameter to expand more details.
+ ///
+ public NatParameter balance { get; set; }
+
+ [OpenApiIgnore]
+ public bool Empty =>
+ id == null &&
+ (ticket == null || ticket.Empty) &&
+ account == null &&
+ balance == null;
+
+ public string Normalize(string name)
+ {
+ return ResponseCacheService.BuildKey("",
+ ("id", id), ("ticket", ticket), ("account", account), ("balance", balance));
+ }
+ }
+}
diff --git a/Tzkt.Api/Parameters/TicketFilter.cs b/Tzkt.Api/Parameters/TicketFilter.cs
new file mode 100644
index 000000000..a06ea9184
--- /dev/null
+++ b/Tzkt.Api/Parameters/TicketFilter.cs
@@ -0,0 +1,105 @@
+using NSwag.Annotations;
+using Tzkt.Api.Services;
+
+namespace Tzkt.Api
+{
+ public class TicketFilter : INormalizable
+ {
+ ///
+ /// Filter by internal TzKT id.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int64Parameter id { get; set; }
+
+ ///
+ /// Filter by ticketer address.
+ /// Click on the parameter to expand more details.
+ ///
+ public AccountParameter ticketer { get; set; }
+
+ ///
+ /// Filter by ticket content type in Micheline format.
+ /// Click on the parameter to expand more details.
+ ///
+ public MichelineParameter rawType { get; set; }
+
+ ///
+ /// Filter by ticket content in Micheline format.
+ /// Click on the parameter to expand more details.
+ ///
+ public MichelineParameter rawContent { get; set; }
+
+ ///
+ /// Filter by ticket content in JSON format.
+ /// Note, this parameter supports the following format: `content{.path?}{.mode?}=...`,
+ /// so you can specify a path to a particular field to filter by (for example, `?content.color.in=red,green`).
+ /// Click on the parameter to expand more details.
+ ///
+ public JsonParameter content { get; set; }
+
+ ///
+ /// Filter by 32-bit hash of ticket content type.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter typeHash { get; set; }
+
+ ///
+ /// Filter by 32-bit hash of ticket content.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter contentHash { get; set; }
+
+ ///
+ /// Filter by address of the first minter.
+ /// Click on the parameter to expand more details.
+ ///
+ public AccountParameter firstMinter { get; set; }
+
+ ///
+ /// Filter by level of the block where the ticket was first seen.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter firstLevel { get; set; }
+
+ ///
+ /// Filter by timestamp (ISO 8601) of the block where the ticket was first seen.
+ /// Click on the parameter to expand more details.
+ ///
+ public TimestampParameter firstTime { get; set; }
+
+ ///
+ /// Filter by level of the block where the ticket was last seen.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter lastLevel { get; set; }
+
+ ///
+ /// Filter by timestamp (ISO 8601) of the block where the ticket was last seen.
+ /// Click on the parameter to expand more details.
+ ///
+ public TimestampParameter lastTime { get; set; }
+
+ [OpenApiIgnore]
+ public bool Empty =>
+ id == null &&
+ ticketer == null &&
+ rawType == null &&
+ rawContent == null &&
+ content == null &&
+ typeHash == null &&
+ contentHash == null &&
+ firstMinter == null &&
+ firstLevel == null &&
+ firstTime == null &&
+ lastLevel == null &&
+ lastTime == null;
+
+ public string Normalize(string name)
+ {
+ return ResponseCacheService.BuildKey("",
+ ("id", id), ("ticketer", ticketer), ("rawType", rawType), ("rawContent", rawContent), ("content", content),
+ ("typeHash", typeHash), ("contentHash", contentHash), ("firstMinter", firstMinter), ("firstLevel", firstLevel),
+ ("firstTime", firstTime), ("lastLevel", lastLevel), ("lastTime", lastTime));
+ }
+ }
+}
diff --git a/Tzkt.Api/Parameters/TicketInfoFilter.cs b/Tzkt.Api/Parameters/TicketInfoFilter.cs
new file mode 100644
index 000000000..0c8defed4
--- /dev/null
+++ b/Tzkt.Api/Parameters/TicketInfoFilter.cs
@@ -0,0 +1,69 @@
+using NSwag.Annotations;
+using Tzkt.Api.Services;
+
+namespace Tzkt.Api
+{
+ public class TicketInfoFilter : INormalizable
+ {
+ ///
+ /// Filter by internal TzKT id.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int64Parameter id { get; set; }
+
+ ///
+ /// Filter by ticketer address.
+ /// Click on the parameter to expand more details.
+ ///
+ public AccountParameter ticketer { get; set; }
+
+ ///
+ /// Filter by ticket content type in Micheline format.
+ /// Click on the parameter to expand more details.
+ ///
+ public MichelineParameter rawType { get; set; }
+
+ ///
+ /// Filter by ticket content in Micheline format.
+ /// Click on the parameter to expand more details.
+ ///
+ public MichelineParameter rawContent { get; set; }
+
+ ///
+ /// Filter by ticket content in JSON format.
+ /// Note, this parameter supports the following format: `content{.path?}{.mode?}=...`,
+ /// so you can specify a path to a particular field to filter by (for example, `?content.color.in=red,green`).
+ /// Click on the parameter to expand more details.
+ ///
+ public JsonParameter content { get; set; }
+
+ ///
+ /// Filter by 32-bit hash of ticket content type.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter typeHash { get; set; }
+
+ ///
+ /// Filter by 32-bit hash of ticket content.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter contentHash { get; set; }
+
+ [OpenApiIgnore]
+ public bool Empty =>
+ id == null &&
+ ticketer == null &&
+ rawType == null &&
+ rawContent == null &&
+ content == null &&
+ typeHash == null &&
+ contentHash == null;
+
+ public string Normalize(string name)
+ {
+ return ResponseCacheService.BuildKey("",
+ ("id", id), ("ticketer", ticketer), ("rawType", rawType), ("rawContent", rawContent),
+ ("content", content), ("typeHash", typeHash), ("contentHash", contentHash));
+ }
+ }
+}
diff --git a/Tzkt.Api/Parameters/TicketInfoShortFilter.cs b/Tzkt.Api/Parameters/TicketInfoShortFilter.cs
new file mode 100644
index 000000000..0812f01d7
--- /dev/null
+++ b/Tzkt.Api/Parameters/TicketInfoShortFilter.cs
@@ -0,0 +1,31 @@
+using NSwag.Annotations;
+using Tzkt.Api.Services;
+
+namespace Tzkt.Api
+{
+ public class TicketInfoShortFilter : INormalizable
+ {
+ ///
+ /// Filter by internal TzKT id.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int64Parameter id { get; set; }
+
+ ///
+ /// Filter by ticketer address.
+ /// Click on the parameter to expand more details.
+ ///
+ public AccountParameter ticketer { get; set; }
+
+ [OpenApiIgnore]
+ public bool Empty =>
+ id == null &&
+ ticketer == null;
+
+ public string Normalize(string name)
+ {
+ return ResponseCacheService.BuildKey("",
+ ("id", id), ("ticketer", ticketer));
+ }
+ }
+}
diff --git a/Tzkt.Api/Parameters/TicketTransferFilter.cs b/Tzkt.Api/Parameters/TicketTransferFilter.cs
new file mode 100644
index 000000000..ec026bd50
--- /dev/null
+++ b/Tzkt.Api/Parameters/TicketTransferFilter.cs
@@ -0,0 +1,97 @@
+using NSwag.Annotations;
+using Tzkt.Api.Services;
+
+namespace Tzkt.Api
+{
+ public class TicketTransferFilter : INormalizable
+ {
+ ///
+ /// Filter by internal TzKT id.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int64Parameter id { get; set; }
+
+ ///
+ /// Filter by level of the block where the transfer was made.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int32Parameter level { get; set; }
+
+ ///
+ /// Filter by timestamp (ISO 8601) of the block where the transfer was made.
+ /// Click on the parameter to expand more details.
+ ///
+ public TimestampParameter timestamp { get; set; }
+
+ ///
+ /// Filter by ticket.
+ /// Click on the parameter to expand more details.
+ ///
+ public TicketInfoFilter ticket { get; set; }
+
+ ///
+ /// Filter by any of the specified fields (`from` or `to`).
+ /// Example: `anyof.from.to=tz1...` will return transfers where `from` OR `to` is equal to the specified value.
+ /// This parameter is useful when you need to get both incoming and outgoing transfers of the account at once.
+ /// Click on the parameter to expand more details.
+ ///
+ public AnyOfParameter anyof { get; set; }
+
+ ///
+ /// Filter by sender address.
+ /// Click on the parameter to expand more details.
+ ///
+ public AccountParameter from { get; set; }
+
+ ///
+ /// Filter by recepient address.
+ /// Click on the parameter to expand more details.
+ ///
+ public AccountParameter to { get; set; }
+
+ ///
+ /// Filter by amount.
+ /// Click on the parameter to expand more details.
+ ///
+ public NatParameter amount { get; set; }
+
+ ///
+ /// Filter by id of the transaction, caused the ticket transfer.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int64NullParameter transactionId { get; set; }
+
+ ///
+ /// Filter by id of the transfer_ticket operation, caused the ticket transfer.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int64NullParameter transferTicketId { get; set; }
+
+ ///
+ /// Filter by id of the smart_rollup_execute operation, caused the ticket transfer.
+ /// Click on the parameter to expand more details.
+ ///
+ public Int64NullParameter smartRollupExecuteId { get; set; }
+
+ [OpenApiIgnore]
+ public bool Empty =>
+ id == null &&
+ level == null &&
+ timestamp == null &&
+ (ticket == null || ticket.Empty) &&
+ anyof == null &&
+ from == null &&
+ to == null &&
+ amount == null &&
+ transactionId == null &&
+ transferTicketId == null &&
+ smartRollupExecuteId == null;
+
+ public string Normalize(string name)
+ {
+ return ResponseCacheService.BuildKey("",
+ ("id", id), ("level", level), ("timestamp", timestamp), ("ticket", ticket), ("anyof", anyof), ("from", from), ("to", to),
+ ("amount", amount), ("transactionId", transactionId), ("transferTicketId", transferTicketId), ("smartRollupExecuteId", smartRollupExecuteId));
+ }
+ }
+}
diff --git a/Tzkt.Api/Program.cs b/Tzkt.Api/Program.cs
index 5f5d68cb7..9c7877c96 100644
--- a/Tzkt.Api/Program.cs
+++ b/Tzkt.Api/Program.cs
@@ -65,6 +65,7 @@
builder.Services.AddTransient();
builder.Services.AddTransient();
builder.Services.AddTransient();
+builder.Services.AddTransient();
builder.Services.AddTransient();
builder.Services.AddTransient();
builder.Services.AddTransient();
@@ -120,6 +121,12 @@
builder.Services.AddTransient>();
builder.Services.AddTransient>();
+ builder.Services.AddTransient>();
+ builder.Services.AddTransient>();
+
+ builder.Services.AddTransient>();
+ builder.Services.AddTransient>();
+
builder.Services.AddTransient>();
builder.Services.AddTransient>();
diff --git a/Tzkt.Api/Repositories/AccountRepository.Bakers.cs b/Tzkt.Api/Repositories/AccountRepository.Bakers.cs
index 5c4ffb947..6f6b9d048 100644
--- a/Tzkt.Api/Repositories/AccountRepository.Bakers.cs
+++ b/Tzkt.Api/Repositories/AccountRepository.Bakers.cs
@@ -59,6 +59,9 @@ public partial class AccountRepository : DbConnection
ActiveTokensCount = delegat.ActiveTokensCount,
TokenBalancesCount = delegat.TokenBalancesCount,
TokenTransfersCount = delegat.TokenTransfersCount,
+ ActiveTicketsCount = delegat.ActiveTicketsCount,
+ TicketBalancesCount = delegat.TicketBalancesCount,
+ TicketTransfersCount = delegat.TicketTransfersCount,
NumDelegators = delegat.DelegatorsCount,
NumBlocks = delegat.BlocksCount,
NumDelegations = delegat.DelegationsCount,
@@ -171,6 +174,9 @@ public async Task GetDelegatesCount(BoolParameter active)
ActiveTokensCount = row.ActiveTokensCount,
TokenBalancesCount = row.TokenBalancesCount,
TokenTransfersCount = row.TokenTransfersCount,
+ ActiveTicketsCount = row.ActiveTicketsCount,
+ TicketBalancesCount = row.TicketBalancesCount,
+ TicketTransfersCount = row.TicketTransfersCount,
NumDelegators = row.DelegatorsCount,
NumBlocks = row.BlocksCount,
NumDelegations = row.DelegationsCount,
@@ -245,6 +251,9 @@ public async Task