diff --git a/README.md b/README.md
index bce3a04..f83f984 100644
--- a/README.md
+++ b/README.md
@@ -7,12 +7,8 @@ Kadena Cabinet is a Community Advisory Board (CAB) where KDA token holders come
- **Chainweb Node Instance:**
- An instance of the [Chainweb Node](https://github.com/kadena-io/chainweb-node) with the `allowReadsInLocal: true` parameter set.
-- **One of the Following:**
- - A [Kadena GraphQL](https://github.com/kadena-community/kadena.js/tree/main/packages/apps/graph) server.
- - Access to the `events` endpoint from [Chainweb Data](https://github.com/kadena-io/chainweb-data).
- - SQL access to the PostgreSQL database from [Chainweb Data](https://github.com/kadena-io/chainweb-data).
-
-
+- A **[Kadena GraphQL](https://github.com/kadena-community/kadena.js/tree/main/packages/apps/graph)** server
+
## Project Structure
- `pact/`: Smart contracts and related files for on-chain governance.
diff --git a/backend/API/Interfaces/IBondService.cs b/backend/API/Interfaces/IBondService.cs
index 79373ba..ef40425 100644
--- a/backend/API/Interfaces/IBondService.cs
+++ b/backend/API/Interfaces/IBondService.cs
@@ -9,6 +9,7 @@ public interface IBondService
Task> GetAllLockups(bool ignoreCache = false);
Task> GetAllLockupEvents(bool ignoreCache = false);
Task> GetAllClaimEvents(bool ignoreCache = false);
+ Task> GetAllVoteEvents(bool ignoreCache = false);
Task GetLockup(string lockupId, bool ignoreCache = false);
Task> GetAccountLockups(string account, bool ignoreCache = false);
Task> GetAllLockupsFromBond(string bondId, bool ignoreCache = false);
diff --git a/backend/API/Interfaces/IChainwebDataRetriever.cs b/backend/API/Interfaces/IChainwebDataRetriever.cs
deleted file mode 100644
index 4573389..0000000
--- a/backend/API/Interfaces/IChainwebDataRetriever.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Dab.API.Models.Events;
-
-namespace Dab.API.Interfaces;
-
-public interface IChainwebDataRetriever
-{
- Task> RetrieveLockData();
- Task> RetrieveClaimData();
-}
diff --git a/backend/API/Models/BondDashboard.cs b/backend/API/Models/BondDashboard.cs
index 00a2c84..f855ade 100644
--- a/backend/API/Models/BondDashboard.cs
+++ b/backend/API/Models/BondDashboard.cs
@@ -1,3 +1,5 @@
+using Dab.API.Models.Events;
+
namespace Dab.API.Models.Dashboard;
public class LockDTO
@@ -17,6 +19,17 @@ public LockDTO(EventDTO evt)
RequestKey = evt.RequestKey;
LockTime = DateTime.TryParse(evt.BlockTime, out var lockTime) ? lockTime : default;
}
+
+ public LockDTO(LockEvent evt)
+ {
+ BondId = evt.BondId;
+ Account = evt.Account;
+ LockedAmount = evt.Amount;
+ MaxRewards = evt.Rewards;
+ RequestKey = evt.RequestKey;
+ LockTime = evt.Timestamp;
+ }
+
}
public class ClaimDTO
@@ -36,6 +49,16 @@ public ClaimDTO(EventDTO evt)
RequestKey = evt.RequestKey;
ClaimTime = DateTime.TryParse(evt.BlockTime, out var lockTime) ? lockTime : default;
}
+ public ClaimDTO(ClaimEvent evt)
+ {
+ BondId = evt.BondId;
+ Account = evt.Account;
+ Amount = evt.OriginalAmount;
+ TotalAmount = evt.TotalAmount;
+ RequestKey = evt.RequestKey;
+ ClaimTime = evt.Timestamp;
+ }
+
}
public class PollVoteEventDTO
@@ -47,12 +70,21 @@ public class PollVoteEventDTO
public string RequestKey { get; set; } = "";
public PollVoteEventDTO(EventDTO evt)
{
- PollId = evt.Params[0]?.ToString();
- Account = evt.Params[1]?.ToString();
+ PollId = evt.Params[1]?.ToString();
+ Account = evt.Params[0]?.ToString();
Action = evt.Params[2]?.ToString();
RequestKey = evt.RequestKey;
VoteTime = DateTime.TryParse(evt.BlockTime, out var lockTime) ? lockTime : default;
}
+ public PollVoteEventDTO(VoteEvent evt)
+ {
+ PollId = evt.PollId;
+ Account = evt.Account;
+ Action = evt.Action;
+ RequestKey = evt.RequestKey;
+ VoteTime = evt.Timestamp;
+ }
+
}
@@ -84,6 +116,7 @@ public class BondDashboard
public List LatestLocks { get; set; } = new();
public List LatestClaims { get; set; } = new();
public List LatestVotes { get; set; } = new();
+ public bool ActivePool {get; set;}
}
public class VoteDistributionDTO
diff --git a/backend/API/Models/Bonder.cs b/backend/API/Models/Bonder.cs
index 2a8f269..57b0fd3 100644
--- a/backend/API/Models/Bonder.cs
+++ b/backend/API/Models/Bonder.cs
@@ -4,8 +4,6 @@
namespace Dab.API.Models.Bonder;
-//FIXME these types require more testing (not handled {decimal:})
-
public class LockupOption
{
[JsonPropertyName("option-name")]
diff --git a/backend/API/Models/Cache.cs b/backend/API/Models/Cache.cs
index 5903c01..6837a01 100644
--- a/backend/API/Models/Cache.cs
+++ b/backend/API/Models/Cache.cs
@@ -77,6 +77,8 @@ public class CacheKeys
public static string AllVotes() => $"all-votes";
+ public static string AllVoteEvents() => $"all-vote-events";
+
public static string ApiAnalytics() => $"api-dashboard";
}
}
diff --git a/backend/API/Models/Events.cs b/backend/API/Models/Events.cs
index 6209562..722c982 100644
--- a/backend/API/Models/Events.cs
+++ b/backend/API/Models/Events.cs
@@ -37,3 +37,12 @@ public class ClaimEvent : IBondEvent
public decimal Amount => TotalAmount;
}
+
+public class VoteEvent
+{
+ public string Account { get; set; } = "";
+ public string PollId { get; set; } = "";
+ public string Action { get; set; } = "";
+ public DateTime Timestamp { get; set; }
+ public string RequestKey { get; set; } = "";
+}
diff --git a/backend/API/Program.cs b/backend/API/Program.cs
index 5e32f9b..6c2ed76 100644
--- a/backend/API/Program.cs
+++ b/backend/API/Program.cs
@@ -98,7 +98,6 @@
// Business logic services
// builder.Services.AddSingleton();
builder.Services.AddSingleton();
-builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
diff --git a/backend/API/Services/AnalyticsService.cs b/backend/API/Services/AnalyticsService.cs
index 9d625c7..9d2833e 100644
--- a/backend/API/Services/AnalyticsService.cs
+++ b/backend/API/Services/AnalyticsService.cs
@@ -68,13 +68,9 @@ private async Task> CacheEventSearch(string evt, bool ignoreCache
public async Task> GetLatestLocks(bool ignoreCache = false)
{
- var lockupsInContract = await _bondService.GetAllLockups();
- if (!lockupsInContract.Any()) return new List();
+ var lockupsInContract = await _bondService.GetAllLockupEvents();
- var evt = $"{ns}.bonder.LOCK";
- var query = await CacheEventSearch(evt, ignoreCache);
-
- return query
+ return lockupsInContract
.Select(evt => new LockDTO(evt))
.OrderByDescending(dto => dto.LockTime)
.Take(3)
@@ -83,13 +79,9 @@ public async Task> GetLatestLocks(bool ignoreCache = false)
public async Task> GetLatestClaims(bool ignoreCache = false)
{
- var lockupsInContract = await _bondService.GetAllLockups();
- if (!lockupsInContract.Any()) return new List();
-
- var evt = $"{ns}.bonder.CLAIM";
- var query = await CacheEventSearch(evt, ignoreCache);
+ var claimsInContract = await _bondService.GetAllClaimEvents();
- return query
+ return claimsInContract
.Select(evt => new ClaimDTO(evt))
.OrderByDescending(dto => dto.ClaimTime)
.Take(3)
@@ -98,13 +90,9 @@ public async Task> GetLatestClaims(bool ignoreCache = false)
public async Task> GetLatestVotes(bool ignoreCache = false)
{
- var lockupsInContract = await _bondService.GetAllLockups();
- if (!lockupsInContract.Any()) return new List();
-
- var evt = $"{ns}.poller.VOTE";
- var query = await CacheEventSearch(evt, ignoreCache);
+ var votesInContract = await _bondService.GetAllVoteEvents();
- return query
+ return votesInContract
.Select(evt => new PollVoteEventDTO(evt))
.OrderByDescending(dto => dto.VoteTime)
.Take(3)
@@ -240,7 +228,8 @@ public async Task GetApiAnalytics(bool ignoreCache = false)
MostVotedPoll = await SafeExecute(async () => await GetMostVotedPoll(ignoreCache), ""),
AverageLockup = await SafeExecute(async () => await GetAverageLockup(ignoreCache), ""),
MaxReturnRate = await SafeExecute(async () => await GetMaxReturnRate(ignoreCache), 0m),
- TotalLockedAmount = await SafeExecute(async () => (await _bondService.GetAllLockupEvents(ignoreCache)).Sum(x => x.Amount + x.Rewards), 0m)
+ TotalLockedAmount = await SafeExecute(async () => (await _bondService.GetAllLockupEvents(ignoreCache)).Sum(x => x.Amount + x.Rewards), 0m),
+ ActivePool = await SafeExecute(async () => (await IsPollActive(ignoreCache)), false)
};
var jsonResult = Utils.JsonPrettify(ret);
@@ -268,6 +257,19 @@ private async Task SafeExecute(Func> func, T defaultValue)
}
}
+ private async Task IsPollActive(bool ignoreCache = false)
+ {
+
+ try
+ {
+ var pools = await _pollService.GetActivePolls(ignoreCache);
+ return pools.Any();
+ }
+ catch
+ {
+ return false;
+ }
+ }
private async Task GetGlobalGivenRewards(bool ignoreCache = false)
{
diff --git a/backend/API/Services/BondService.cs b/backend/API/Services/BondService.cs
index a3de5dd..c204a3d 100644
--- a/backend/API/Services/BondService.cs
+++ b/backend/API/Services/BondService.cs
@@ -13,19 +13,17 @@ public class BondService : IBondService
private readonly ILogger _logger;
private readonly ICacheService _cacheService;
private readonly IPactService _pactService;
- private readonly IChainwebDataRetriever _dataRetriever;
private readonly ChainwebGraphQLRetriever _graphRetriever;
private readonly DabContractConfig _dabConfig;
private readonly string chain;
private readonly string ns;
private readonly int expirySeconds = 20;
- public BondService(ILogger logger, ICacheService cacheService, IPactService pactService, IConfiguration configuration, IChainwebDataRetriever dataRetriever, ChainwebGraphQLRetriever graphRetriever)
+ public BondService(ILogger logger, ICacheService cacheService, IPactService pactService, IConfiguration configuration, ChainwebGraphQLRetriever graphRetriever)
{
_logger = logger;
_cacheService = cacheService;
_pactService = pactService;
- _dataRetriever = dataRetriever;
_graphRetriever = graphRetriever;
_dabConfig = (configuration.GetSection("DabContractConfig").Get() ??
@@ -34,47 +32,55 @@ public BondService(ILogger logger, ICacheService cacheService, IPac
ns = _dabConfig.Namespace;
}
-public async Task> GetAllLockupEvents(bool ignoreCache = false)
-{
- // var locksInContract = await GetAllLockups(ignoreCache);
+ public async Task> GetAllLockupEvents(bool ignoreCache = false)
+ {
+ var cacheKey = CacheKeys.AllLockupEvents();
+
+ if (!ignoreCache && await _cacheService.HasItem(cacheKey))
+ {
+ var cached = await _cacheService.GetItem(cacheKey);
+ return JsonSerializer.Deserialize>(cached) ?? new();
+ }
+
+ var lockEvents = (await _graphRetriever.RetrieveLockData())
+ .GroupBy(e => e.RequestKey)
+ .Select(g => g.First())
+ .ToList();
- // if (!locksInContract.Any()) return new List();
+ var ret = Utils.JsonPrettify(lockEvents);
- var cacheKey = CacheKeys.AllLockupEvents();
+ await _cacheService.SetItem(cacheKey, ret, 30 * expirySeconds);
+ return lockEvents;
+ }
- if (!ignoreCache && await _cacheService.HasItem(cacheKey))
+ public async Task> GetAllVoteEvents(bool ignoreCache = false)
{
- var cached = await _cacheService.GetItem(cacheKey);
- return JsonSerializer.Deserialize>(cached) ?? new();
+ var cacheKey = CacheKeys.AllVoteEvents();
+
+ if (!ignoreCache && await _cacheService.HasItem(cacheKey))
+ {
+ var cached = await _cacheService.GetItem(cacheKey);
+ return JsonSerializer.Deserialize>(cached) ?? new();
+ }
+
+ var voteEvents = (await _graphRetriever.RetrieveVoteData())
+ .GroupBy(e => e.RequestKey)
+ .Select(g => g.First())
+ .ToList();
+
+
+ var ret = Utils.JsonPrettify(voteEvents);
+
+ await _cacheService.SetItem(cacheKey, ret, 30 * expirySeconds);
+ return voteEvents;
}
- // Temporarily commented out
- var lockEvents = await _graphRetriever.RetrieveLockData();
-
- // var lockEvents = locksInContract.Select(lockup => new LockEvent
- // {
- // BondId = lockup.BondId,
- // Account = lockup.Account,
- // Amount = lockup.KdaLocked,
- // Rewards = lockup.MaxKdaRewards,
- // LockupLength = lockup.LockupOption.OptionLength,
- // Timestamp = lockup.LockupStartTime.Date,
- // RequestKey = "", //lockup.LockupId,
- // Type = "Lock"
- // }).ToList();
-
- var ret = Utils.JsonPrettify(lockEvents);
-
- await _cacheService.SetItem(cacheKey, ret, 30 * expirySeconds);
- return lockEvents;
-}
public async Task> GetAllClaimEvents(bool ignoreCache = false)
{
-
var cacheKey = CacheKeys.AllClaimEvents();
if (!ignoreCache && await _cacheService.HasItem(cacheKey))
@@ -83,7 +89,11 @@ public async Task> GetAllClaimEvents(bool ignoreCache = false)
return JsonSerializer.Deserialize>(cached) ?? new();
}
- var claimEvents = await _graphRetriever.RetrieveClaimData();
+ var claimEvents = (await _graphRetriever.RetrieveClaimData())
+ .GroupBy(e => e.RequestKey)
+ .Select(g => g.First())
+ .ToList();
+
var ret = Utils.JsonPrettify(claimEvents);
diff --git a/backend/API/Services/DataRetriever.cs b/backend/API/Services/DataRetriever.cs
deleted file mode 100644
index 5f693ac..0000000
--- a/backend/API/Services/DataRetriever.cs
+++ /dev/null
@@ -1,270 +0,0 @@
-using Npgsql;
-using Dab.API.Interfaces;
-using Dab.API.Models.Events;
-using System.Text.Json;
-using Dab.API.Models.Cache;
-using Dab.API.Models.Dashboard;
-
-namespace Dab.API.Services
-{
- public class ChainwebDataRetriever : IChainwebDataRetriever
- {
- private readonly ILogger _logger;
- private readonly ICacheService _cacheService;
- private readonly string _connectionString;
- private readonly string _namespace;
- private NpgsqlDataSource? dataSource;
- private readonly string ns;
- private readonly string chainwebDataUrl;
- private readonly int expirySeconds = 120;
-
- public ChainwebDataRetriever(IConfiguration configuration, ILogger logger, ICacheService cacheService)
- {
- _logger = logger;
- _connectionString = configuration.GetConnectionString("DefaultConnection");
- _namespace = configuration.GetSection("DabContractConfig").GetValue("Namespace") ?? throw new Exception("Namespace not defined in configuration.");
- _cacheService = cacheService;
- chainwebDataUrl = configuration.GetSection("DabContractConfig").GetValue("ChainwebDataUrl") ?? throw new Exception("Chainweb Data URL not defined in configuration.");
- ns = _namespace;
- }
-
- private void OpenSqlConnection()
- {
- if (!string.IsNullOrEmpty(_connectionString))
- {
- dataSource = NpgsqlDataSource.Create(_connectionString);
- }
- }
-
- public async Task> RetrieveLockData()
- {
- if (!string.IsNullOrEmpty(_connectionString))
- {
- try
- {
- _logger.LogInformation($"Retrieving LOCK event data from SQL via \n {_connectionString}");
-
- OpenSqlConnection();
- var sql = $@"SELECT params ->> 0 as ""BondId"",
- params ->> 1 as ""Account"",
- coalesce(params -> 2 ->> 'decimal', params ->> 2) as ""Amount"",
- coalesce(params -> 3 ->> 'decimal', params ->> 3) as ""Rewards"",
- coalesce(params -> 4 ->> 'int', params ->> 4) as ""LockupLength"",
- replace(translate(to_char(creationtime, 'YYYY-MM-DD HH:MI:SS.MSOF'), ' ', 'T'), '+00', 'Z') as ""Timestamp"",
- requestkey as ""RequestKey""
- FROM events as a
- JOIN blocks as b
- on a.block = b.hash
- WHERE a.qualname = '{_namespace}.bonder.LOCK'
- ORDER BY creationtime ASC;";
-
- await using var cmd = dataSource.CreateCommand(sql);
- await using var rdr = await cmd.ExecuteReaderAsync();
-
- var list = new List();
-
- while (await rdr.ReadAsync())
- {
- var lockEvent = new LockEvent
- {
- BondId = rdr.GetString(0),
- Account = rdr.GetString(1),
- Amount = decimal.Parse(rdr.GetString(2)),
- Rewards = decimal.Parse(rdr.GetString(3)),
- LockupLength = decimal.Parse(rdr.GetString(4)),
- Timestamp = DateTime.Parse(rdr.GetString(5)),
- RequestKey = rdr.GetString(6)
- };
-
- list.Add(lockEvent);
- }
-
- return list;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "SQL retrieval failed, falling back to ChainwebData API");
- }
- }
-
- // Fallback to ChainwebData API
- return new(); // await GetAllLocks();
- }
-
- public async Task> RetrieveClaimData()
- {
- if (!string.IsNullOrEmpty(_connectionString))
- {
- try
- {
- _logger.LogInformation("Retrieving CLAIM event data from SQL");
-
- OpenSqlConnection();
- var sql = $@"SELECT params ->> 0 as ""BondId"",
- params ->> 1 as ""Account"",
- coalesce(params -> 2 ->> 'decimal', params ->> 2) as ""OriginalAmount"",
- coalesce(params -> 3 ->> 'decimal', params ->> 3) as ""TotalAmount"",
- replace(translate(to_char(creationtime, 'YYYY-MM-DD HH:MI:SS.MSOF'), ' ', 'T'), '+00', 'Z') as ""Timestamp"",
- requestkey as ""RequestKey""
- FROM events as a
- JOIN blocks as b
- on a.block = b.hash
- WHERE a.qualname = '{_namespace}.bonder.CLAIM'
- ORDER BY creationtime ASC;";
-
- await using var cmd = dataSource.CreateCommand(sql);
- await using var rdr = await cmd.ExecuteReaderAsync();
-
- var list = new List();
-
- while (await rdr.ReadAsync())
- {
- _logger.LogInformation(rdr.ToString());
- var claimEvent = new ClaimEvent
- {
- BondId = rdr.GetString(0),
- Account = rdr.GetString(1),
- OriginalAmount = decimal.Parse(rdr.GetString(2)),
- TotalAmount = decimal.Parse(rdr.GetString(3)),
- Timestamp = DateTime.Parse(rdr.GetString(4)),
- RequestKey = rdr.GetString(5)
- };
-
- list.Add(claimEvent);
- }
-
- return list;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "SQL retrieval failed, falling back to ChainwebData API");
- }
- }
-
- // Fallback to ChainwebData API
- return await GetAllClaims();
- }
-
-
- private int ParseLockupLength(object lockupLengthParam)
- {
- if (lockupLengthParam is JsonElement jsonElement && jsonElement.ValueKind == JsonValueKind.Object)
- {
- // Assuming the "int" field is present in the JSON object
- if (jsonElement.TryGetProperty("int", out var intElement))
- {
- return intElement.GetInt32();
- }
- }
-
- // Fallback for direct int values or if the object is not in expected format
- return int.TryParse(lockupLengthParam?.ToString(), out var result) ? result : 0;
- }
-
- public async Task> GetAllLocks(bool ignoreCache = false)
- {
- var evt = $"{ns}.bonder.LOCK";
- var query = await CacheEventSearchAll(evt, ignoreCache);
-
- return query
- .Select(e => new LockEvent
- {
- BondId = e.Params[0]?.ToString() ?? "",
- Account = e.Params[1]?.ToString() ?? "",
- Amount = decimal.Parse(e.Params[2]?.ToString() ?? "0"),
- Rewards = decimal.Parse(e.Params[3]?.ToString() ?? "0"),
- LockupLength = ParseLockupLength(e.Params[4]),
- Timestamp = DateTime.Parse(e.BlockTime),
- RequestKey = e.RequestKey
- })
- .OrderByDescending(le => le.Timestamp)
- .ToList();
- }
-
- public async Task> GetAllClaims(bool ignoreCache = false)
- {
- var evt = $"{ns}.bonder.CLAIM";
- var query = await CacheEventSearchAll(evt, ignoreCache);
-
- return query
- .Select(e => new ClaimEvent
- {
- BondId = e.Params[0]?.ToString() ?? "",
- Account = e.Params[1]?.ToString() ?? "",
- OriginalAmount = decimal.Parse(e.Params[2]?.ToString() ?? "0"),
- TotalAmount = decimal.Parse(e.Params[3]?.ToString() ?? "0"),
- Timestamp = DateTime.Parse(e.BlockTime),
- RequestKey = e.RequestKey
- })
- .OrderByDescending(ce => ce.Timestamp)
- .ToList();
- }
-
- private async Task> EventSearchAll(string evt)
- {
- List allResults = new();
- string nextToken = null;
-
- do
- {
- var result = await EventSearchWithToken(evt, nextToken);
-
- // Add the items to the allResults list if there are any
- if (result.Items != null && result.Items.Count > 0)
- {
- allResults.AddRange(result.Items);
- }
-
- if (!result.Items.Any()) break;
-
- // Update nextToken to determine if the loop should continue
- nextToken = result.NextToken ?? String.Empty;
-
- } while (!string.IsNullOrEmpty(nextToken)); // Continue if nextToken is not null or empty
-
- return allResults;
- }
-
- private async Task<(List Items, string? NextToken)> EventSearchWithToken(string evt, string nextToken = null)
- {
- var queryUri = $"{chainwebDataUrl}/txs/events?search={evt}";
- if (!string.IsNullOrEmpty(nextToken))
- {
- queryUri += $"&next={nextToken}";
- }
-
- _logger.LogDebug($"Querying {queryUri} for {evt}");
- using (HttpClient client = new())
- {
- using var request = new HttpRequestMessage(HttpMethod.Get, queryUri);
- using var response = await client.SendAsync(request);
- response.EnsureSuccessStatusCode();
-
- var events = await response.Content.ReadFromJsonAsync>();
- var next = response.Headers.Contains("Chainweb-Next")
- ? response.Headers.GetValues("Chainweb-Next").FirstOrDefault()
- : null;
-
- return (events ?? new List(), next);
- }
- }
-
- private async Task> CacheEventSearchAll(string evt, bool ignoreCache = false)
- {
- var cacheKey = CacheKeys.EventSearch(evt);
- if (!ignoreCache && await _cacheService.HasItem(cacheKey))
- {
- var cached = await _cacheService.GetItem(cacheKey);
- return JsonSerializer.Deserialize>(cached) ?? new();
- }
-
- var evs = await EventSearchAll(evt);
- var ret = Utils.JsonPrettify(evs);
-
- _logger.LogDebug($"Event Search: {ret}");
-
- await _cacheService.SetItem(cacheKey, ret, expirySeconds);
- return evs;
- }
- }
-}
diff --git a/backend/API/Services/GraphQLRetriever.cs b/backend/API/Services/GraphQLRetriever.cs
index b1d9292..b046b90 100644
--- a/backend/API/Services/GraphQLRetriever.cs
+++ b/backend/API/Services/GraphQLRetriever.cs
@@ -5,7 +5,7 @@
namespace Dab.API.Services
{
- public class ChainwebGraphQLRetriever : IChainwebDataRetriever
+ public class ChainwebGraphQLRetriever
{
private readonly ILogger _logger;
private readonly ICacheService _cacheService;
@@ -21,9 +21,9 @@ public ChainwebGraphQLRetriever(IConfiguration configuration, ILogger("GraphQLEndpoint") ?? "https://graph.kadena.network/graphql";
}
- public async Task> RetrieveLockData()
+ public async Task> RetrieveEventDataAsync(string qualifiedEventName, Func parseFunction)
{
- var allLockEvents = new List();
+ var allEvents = new List();
string endCursor = null;
bool hasNextPage = true;
@@ -31,12 +31,12 @@ public async Task> RetrieveLockData()
{
try
{
- _logger.LogInformation("Retrieving LOCK event data via GraphQL");
+ _logger.LogInformation($"Retrieving {qualifiedEventName} event data via GraphQL");
var query = $@"
- query LockEventSearch($after: String) {{
+ query EventSearch($after: String) {{
events(
- qualifiedEventName: ""{_namespace}.bonder.LOCK""
+ qualifiedEventName: ""{qualifiedEventName}""
first: 1000
after: $after
) {{
@@ -59,19 +59,27 @@ query LockEventSearch($after: String) {{
var variables = new { after = endCursor };
- var graphQLResponse = await ExecuteGraphQLQueryAsync(query, "LockEventSearch", variables);
+ var graphQLResponse = await ExecuteGraphQLQueryAsync(query, "EventSearch", variables);
- var lockEvents = ParseLockEvents(graphQLResponse);
-
- allLockEvents.AddRange(lockEvents);
-
- // Update pagination info
+ // Parse events using the provided parse function
if (graphQLResponse.TryGetProperty("data", out var dataElement) &&
dataElement.TryGetProperty("events", out var eventsElement) &&
- eventsElement.TryGetProperty("pageInfo", out var pageInfoElement))
+ eventsElement.TryGetProperty("edges", out var edgesElement))
{
- hasNextPage = pageInfoElement.GetProperty("hasNextPage").GetBoolean();
- endCursor = pageInfoElement.GetProperty("endCursor").GetString();
+ foreach (var edge in edgesElement.EnumerateArray())
+ {
+ if (edge.TryGetProperty("node", out var nodeElement))
+ {
+ var parsedEvent = parseFunction(nodeElement);
+ allEvents.Add(parsedEvent);
+ }
+ }
+
+ if (eventsElement.TryGetProperty("pageInfo", out var pageInfoElement))
+ {
+ hasNextPage = pageInfoElement.GetProperty("hasNextPage").GetBoolean();
+ endCursor = pageInfoElement.GetProperty("endCursor").GetString();
+ }
}
else
{
@@ -85,84 +93,26 @@ query LockEventSearch($after: String) {{
}
}
- return allLockEvents;
+ return allEvents;
}
- public async Task> RetrieveClaimData()
- {
- var allClaimEvents = new List();
- string endCursor = null;
- bool hasNextPage = true;
-
- while (hasNextPage)
- {
- try
- {
- _logger.LogInformation("Retrieving CLAIM event data via GraphQL");
+ public async Task> RetrieveLockData() =>
+ await RetrieveEventDataAsync($"{_namespace}.bonder.LOCK", ParseLockEvent);
- var query = $@"
- query ClaimEventSearch($after: String) {{
- events(
- qualifiedEventName: ""{_namespace}.bonder.CLAIM""
- first: 1000
- after: $after
- ) {{
- edges {{
- cursor
- node {{
- parameters
- requestKey
- block {{
- creationTime
- }}
- }}
- }}
- pageInfo {{
- hasNextPage
- endCursor
- }}
- }}
- }}";
-
- var variables = new { after = endCursor };
-
- var graphQLResponse = await ExecuteGraphQLQueryAsync(query, "ClaimEventSearch", variables);
-
- var claimEvents = ParseClaimEvents(graphQLResponse);
-
- allClaimEvents.AddRange(claimEvents);
-
- // Update pagination info
- if (graphQLResponse.TryGetProperty("data", out var dataElement) &&
- dataElement.TryGetProperty("events", out var eventsElement) &&
- eventsElement.TryGetProperty("pageInfo", out var pageInfoElement))
- {
- hasNextPage = pageInfoElement.GetProperty("hasNextPage").GetBoolean();
- endCursor = pageInfoElement.GetProperty("endCursor").GetString();
- }
- else
- {
- hasNextPage = false;
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "GraphQL retrieval failed");
- break;
- }
- }
+ public async Task> RetrieveClaimData() =>
+ await RetrieveEventDataAsync($"{_namespace}.bonder.CLAIM", ParseClaimEvent);
- return allClaimEvents;
- }
+ public async Task> RetrieveVoteData() =>
+ await RetrieveEventDataAsync($"{_namespace}.poller.VOTE", ParseVoteEvent);
private async Task ExecuteGraphQLQueryAsync(string query, string operationName, object variables)
{
using var client = new HttpClient();
var requestBody = new
{
- query = query,
- operationName = operationName,
- variables = variables,
+ query,
+ operationName,
+ variables,
extensions = new { }
};
@@ -180,82 +130,47 @@ private async Task ExecuteGraphQLQueryAsync(string query, string op
using var jsonDocument = JsonDocument.Parse(responseString);
- return jsonDocument.RootElement.Clone(); // Clone to prevent disposal issues
+ return jsonDocument.RootElement.Clone();
}
- private List ParseLockEvents(JsonElement root)
- {
- var lockEvents = new List();
-
- if (root.TryGetProperty("data", out var dataElement) &&
- dataElement.TryGetProperty("events", out var eventsElement) &&
- eventsElement.TryGetProperty("edges", out var edgesElement))
+ private LockEvent ParseLockEvent(JsonElement nodeElement) =>
+ ParseEvent(nodeElement, parameters => new LockEvent
{
- foreach (var edge in edgesElement.EnumerateArray())
- {
- if (edge.TryGetProperty("node", out var nodeElement))
- {
- var parametersString = nodeElement.GetProperty("parameters").GetString();
- var parametersJson = JsonSerializer.Deserialize(parametersString);
-
- var lockEvent = new LockEvent
- {
- BondId = parametersJson.Length > 0 ? parametersJson[0].GetString() : "",
- Account = parametersJson.Length > 1 ? parametersJson[1].GetString() : "",
- Amount = parametersJson.Length > 2 ? decimal.Parse(parametersJson[2].GetRawText()) : 0,
- Rewards = parametersJson.Length > 3 ? decimal.Parse(parametersJson[3].GetRawText()) : 0,
- LockupLength = parametersJson.Length > 4 ? ParseLockupLength(parametersJson[4]) : 0,
- Timestamp = DateTime.Parse(nodeElement.GetProperty("block").GetProperty("creationTime").GetString()),
- RequestKey = nodeElement.GetProperty("requestKey").GetString() ?? ""
- };
-
- lockEvents.Add(lockEvent);
- }
- }
- }
- else
+ BondId = parameters.Length > 0 ? parameters[0].GetString() : "",
+ Account = parameters.Length > 1 ? parameters[1].GetString() : "",
+ Amount = parameters.Length > 2 ? decimal.Parse(parameters[2].GetRawText()) : 0,
+ Rewards = parameters.Length > 3 ? decimal.Parse(parameters[3].GetRawText()) : 0,
+ LockupLength = parameters.Length > 4 ? ParseLockupLength(parameters[4]) : 0,
+ Timestamp = DateTime.Parse(nodeElement.GetProperty("block").GetProperty("creationTime").GetString()),
+ RequestKey = nodeElement.GetProperty("requestKey").GetString() ?? ""
+ });
+
+ private ClaimEvent ParseClaimEvent(JsonElement nodeElement) =>
+ ParseEvent(nodeElement, parameters => new ClaimEvent
{
- _logger.LogError("Unexpected JSON structure in ParseLockEvents.");
- }
-
- return lockEvents;
- }
-
- private List ParseClaimEvents(JsonElement root)
- {
- var claimEvents = new List();
-
- if (root.TryGetProperty("data", out var dataElement) &&
- dataElement.TryGetProperty("events", out var eventsElement) &&
- eventsElement.TryGetProperty("edges", out var edgesElement))
+ BondId = parameters.Length > 0 ? parameters[0].GetString() : "",
+ Account = parameters.Length > 1 ? parameters[1].GetString() : "",
+ OriginalAmount = parameters.Length > 2 ? decimal.Parse(parameters[2].GetRawText()) : 0,
+ TotalAmount = parameters.Length > 3 ? decimal.Parse(parameters[3].GetRawText()) : 0,
+ Timestamp = DateTime.Parse(nodeElement.GetProperty("block").GetProperty("creationTime").GetString()),
+ RequestKey = nodeElement.GetProperty("requestKey").GetString() ?? ""
+ });
+
+ private VoteEvent ParseVoteEvent(JsonElement nodeElement) =>
+ ParseEvent(nodeElement, parameters => new VoteEvent
{
- foreach (var edge in edgesElement.EnumerateArray())
- {
- if (edge.TryGetProperty("node", out var nodeElement))
- {
- var parametersString = nodeElement.GetProperty("parameters").GetString();
- var parametersJson = JsonSerializer.Deserialize(parametersString);
-
- var claimEvent = new ClaimEvent
- {
- BondId = parametersJson.Length > 0 ? parametersJson[0].GetString() : "",
- Account = parametersJson.Length > 1 ? parametersJson[1].GetString() : "",
- OriginalAmount = parametersJson.Length > 2 ? decimal.Parse(parametersJson[2].GetRawText()) : 0,
- TotalAmount = parametersJson.Length > 3 ? decimal.Parse(parametersJson[3].GetRawText()) : 0,
- Timestamp = DateTime.Parse(nodeElement.GetProperty("block").GetProperty("creationTime").GetString()),
- RequestKey = nodeElement.GetProperty("requestKey").GetString() ?? ""
- };
-
- claimEvents.Add(claimEvent);
- }
- }
- }
- else
- {
- _logger.LogError("Unexpected JSON structure in ParseClaimEvents.");
- }
-
- return claimEvents;
+ Account = parameters.Length > 0 ? parameters[0].GetString() : "",
+ PollId = parameters.Length > 1 ? parameters[1].GetString() : "",
+ Action = parameters.Length > 2 ? parameters[2].GetString() : "",
+ Timestamp = DateTime.Parse(nodeElement.GetProperty("block").GetProperty("creationTime").GetString()),
+ RequestKey = nodeElement.GetProperty("requestKey").GetString() ?? ""
+ });
+
+ private T ParseEvent(JsonElement nodeElement, Func createEvent)
+ {
+ var parametersString = nodeElement.GetProperty("parameters").GetString();
+ var parameters = JsonSerializer.Deserialize(parametersString);
+ return createEvent(parameters);
}
private int ParseLockupLength(JsonElement lockupLengthParam)
@@ -277,7 +192,5 @@ private int ParseLockupLength(JsonElement lockupLengthParam)
return 0;
}
}
-
- // Implement other methods from IChainwebDataRetriever as needed
}
}
diff --git a/backend/README.md b/backend/README.md
index 4740591..54df1a4 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -7,10 +7,7 @@ Cabinet Backend API serves as the middle layer consumed by the Cabinet UI. It is
To run the Cabinet Backend API, ensure you have the following:
- **Chainweb Node Instance**: An instance of the [Chainweb Node](https://github.com/kadena-io/chainweb-node) with the parameter `allowReadsInLocal: true` set for local reads.
-- **One of the Following**:
- - A [Kadena GraphQL](https://github.com/kadena-community/kadena.js/tree/main/packages/apps/graph) server.
- - Access to the `events` endpoint from [Chainweb Data](https://github.com/kadena-io/chainweb-data).
- - SQL access to the PostgreSQL database from [Chainweb Data](https://github.com/kadena-io/chainweb-data).
+- A **[Kadena GraphQL](https://github.com/kadena-community/kadena.js/tree/main/packages/apps/graph)** server.
- **.NET SDK**: Version 7.0 or 8.0 with all required dependencies installed.
- **Redis**: A running Redis instance for caching purposes.
- **Docker & Docker Compose**: Alternatively, you can deploy the entire stack using Docker Compose.
@@ -45,11 +42,7 @@ To run the Cabinet Backend API, ensure you have the following:
"DabContractConfig": {
"ContractChain": "0",
"Namespace": "n_yournamespace",
- "ChainwebDataUrl": "http://127.0.0.1:8080",
"GraphQLEndpoint": "https://127.0.0.1:4000/graph"
- },
- "ConnectionStrings": {
- "DefaultConnection": "Host=127.0.0.1;Port=5432;Database=devnet;Username=devnet;Password=;" # only if you exposed port 5432
}
}
```
@@ -81,10 +74,7 @@ To run the Cabinet Backend API, ensure you have the following:
- **DabContractConfig**:
- `ContractChain`: The specific chain ID for contract interactions.
- `Namespace`: Namespace used for the contracts.
- - `ChainwebDataUrl`: URL for accessing Chainweb Data API.
- `GraphQLEndpoint`: The endpoint for Kadena's GraphQL server.
-- **ConnectionStrings**:
- - `DefaultConnection`: PostgreSQL connection string for accessing historical data directly from the database.
## Getting Started
@@ -114,9 +104,5 @@ Here's a concise breakdown of the folder names and their specific purposes:
## Data Sources
-The API fetches and processes blockchain historical data from the following sources:
-
-1. **Chainweb Data**: Retrieves transaction and event history directly from Chainweb’s API endpoints.
-2. **PostgreSQL Database**: Queries historical data stored locally for faster access and caching.
-3. **GraphQL**: Pulls data using GraphQL queries from Kadena’s testnet or mainnet.
+The API fetches and processes blockchain historical data using **GraphQL** event queries from Kadena’s testnet or mainnet.
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 2a6489d..e9c1937 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,4 +1,3 @@
-version: "3"
services:
cache:
image: redis
diff --git a/web/package.json b/web/package.json
index 2be243c..9486757 100644
--- a/web/package.json
+++ b/web/package.json
@@ -9,28 +9,28 @@
"lint": "next lint"
},
"dependencies": {
- "@kadena/client": "^1.12.0",
+ "@kadena/client": "^1.15.0",
"@reach/dialog": "^0.18.0",
"@react-hook/window-scroll": "^1.3.0",
"@rebass/forms": "^4.0.6",
- "@reduxjs/toolkit": "^2.2.1",
+ "@reduxjs/toolkit": "^2.3.0",
"@svgr/webpack": "^8.1.0",
- "@tailwindcss/typography": "^0.5.12",
- "@types/rebass": "^4.0.14",
+ "@tailwindcss/typography": "^0.5.15",
+ "@types/rebass": "^4.0.15",
"@types/ua-parser-js": "^0.7.39",
- "@vercel/analytics": "^1.3.1",
+ "@vercel/analytics": "^1.3.2",
"@walletconnect/client": "^1.8.0",
- "@walletconnect/modal": "^2.6.2",
+ "@walletconnect/modal": "^2.7.0",
"@walletconnect/qrcode-modal": "^1.8.0",
- "@walletconnect/types": "^2.13.3",
- "@walletconnect/utils": "^2.13.3",
- "axios": "^1.6.8",
+ "@walletconnect/types": "^2.17.1",
+ "@walletconnect/utils": "^2.17.1",
+ "axios": "^1.7.7",
"babel-plugin-styled-components": "^2.1.4",
"camelcase-keys": "^9.1.3",
"copy-to-clipboard": "^3.3.3",
- "date-fns": "^3.6.0",
- "lucide-react": "^0.428.0",
- "next": "14.1.3",
+ "date-fns": "^4.1.0",
+ "lucide-react": "^0.454.0",
+ "next": "15.0.2",
"next-redux-wrapper": "^8.1.0",
"next-svg": "^1.0.7",
"next-svgr": "^0.0.2",
@@ -40,33 +40,32 @@
"react-debounce-input": "^3.3.0",
"react-dom": "^18",
"react-feather": "^2.0.10",
- "react-icons": "^5.2.1",
- "react-spring": "^9.7.3",
+ "react-icons": "^5.3.0",
+ "react-spring": "^9.7.4",
"react-use-gesture": "^9.1.3",
"rebass": "^4.0.7",
- "recharts": "^2.12.7",
- "sharp": "^0.33.4",
- "styled-components": "^6.1.8",
+ "recharts": "^2.13.2",
+ "sharp": "^0.33.5",
+ "styled-components": "^6.1.13",
"svgo": "^3.2.0",
- "tailwind-merge": "^2.4.0",
- "ua-parser-js": "^1.0.37",
- "web-vitals": "^3.5.2"
+ "tailwind-merge": "^2.5.4",
+ "ua-parser-js": "^2.0.0-rc.1"
},
"devDependencies": {
- "@types/node": "^20",
- "@types/react": "^18",
- "@types/react-dom": "^18",
- "autoprefixer": "^10.0.1",
- "eslint": "^8",
- "eslint-config-next": "14.1.3",
+ "@types/node": "^22.8.5",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.13.0",
+ "eslint-config-next": "15.0.2",
"pact-lang-api": "^4.3.6",
- "postcss": "^8",
+ "postcss": "^8.4.47",
"reac": "^0.0.0",
"react": "^18.2.0",
"react-redux": "^9.1.0",
- "react-router-dom": "^6.22.3",
- "tailwindcss": "^3.3.0",
- "typescript": "^5",
+ "react-router-dom": "^6.27.0",
+ "tailwindcss": "^3.4.14",
+ "typescript": "^5.6.3",
"zustand": "^4.5.2"
}
}
diff --git a/web/src/features/bond/AllBondLockups.tsx b/web/src/features/bond/AllBondLockups.tsx
index ed37241..26bc0aa 100644
--- a/web/src/features/bond/AllBondLockups.tsx
+++ b/web/src/features/bond/AllBondLockups.tsx
@@ -127,7 +127,7 @@ const AllBondLockupsComponent: React.FC<{ bondId: string }> = ({ bondId }) => {
Status
Amount + MAX rewards
Date
- {/* Explorer*/}
+ Explorer
{bondLockups.length === 0 ? (
No results found
@@ -161,7 +161,7 @@ const AllBondLockupsComponent: React.FC<{ bondId: string }> = ({ bondId }) => {
{new Date(lockup.timestamp).toLocaleString()}
- {/*
))
)}
diff --git a/web/src/features/bond/BondDetailsModal.tsx b/web/src/features/bond/BondDetailsModal.tsx
index 12358fc..a9f70b5 100644
--- a/web/src/features/bond/BondDetailsModal.tsx
+++ b/web/src/features/bond/BondDetailsModal.tsx
@@ -32,7 +32,10 @@ const BondDetailsModal: React.FC = ({
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
- if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
+ if (
+ modalRef.current &&
+ !modalRef.current.contains(event.target as Node)
+ ) {
onClose();
}
};
@@ -53,16 +56,16 @@ const BondDetailsModal: React.FC = ({
}
function formatLockupId(id: string) {
- const regex = /^LOCKUP_SALE-(\d+)$/;
- const match = id.match(regex);
+ const regex = /^LOCKUP_SALE-(\d+)$/;
+ const match = id.match(regex);
- if (match) {
- const number = match[1];
- return `Lockup ${number}`;
- }
+ if (match) {
+ const number = match[1];
+ return `Lockup ${number}`;
+ }
- // Return the original id if it doesn't match the pattern
- return id;
+ // Return the original id if it doesn't match the pattern
+ return id;
}
const handleToggle = () => {
@@ -81,69 +84,70 @@ const BondDetailsModal: React.FC = ({
alignItems: "center",
}}
>
-
-
-
-
{formatLockupId(bond.bondId)} Details
-
-
+
+
+
+
+ {formatLockupId(bond.bondId)} Details
+
+
+
-
-
-
Min Amount
-
{formatNumber(bond.minAmount)}
-
-
-
Max Amount
-
{formatNumber(bond.maxAmount)}
-
-
-
Creator
-
-
{shortenKAddress(bond.creator)}
-
+
+
+
Min Amount
+
{formatNumber(bond.minAmount)}
+
+
+
Max Amount
+
{formatNumber(bond.maxAmount)}
+
+
+
Creator
+
+
{shortenKAddress(bond.creator)}
+
+
+
+
+
+
Active Cabinet Members
+
{bond.activeBonders}
+
+
+
Total Polls
+
{bond.totalPolls}
+
+
+
Total Rewards
+
{formatNumber(bond.totalRewards)}
+
-
-
-
Active Cabinet Members
-
{bond.activeBonders}
-
-
-
Total Polls
-
{bond.totalPolls}
-
-
-
Total Rewards
-
{formatNumber(bond.totalRewards)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
);
};
diff --git a/web/src/features/bond/BondRewardsRadialBarChart.tsx b/web/src/features/bond/BondRewardsRadialBarChart.tsx
index b4508a4..e6c7b6f 100644
--- a/web/src/features/bond/BondRewardsRadialBarChart.tsx
+++ b/web/src/features/bond/BondRewardsRadialBarChart.tsx
@@ -18,8 +18,7 @@ const COLORS = ["#4A9079", "#E41968", "#FF8042"];
interface CustomTooltipProps extends TooltipProps
{
active?: boolean;
payload?: any;
- label?: string;
-}
+ label?: string; }
const BondRewardsHorizontalBarChart: React.FC<{ bond: any }> = ({ bond }) => {
const { totalRewards, lockedRewards, givenRewards } = bond;
diff --git a/web/src/features/bond/LockupDensityChart.tsx b/web/src/features/bond/LockupDensityChart.tsx
index 0a8b881..8579abf 100644
--- a/web/src/features/bond/LockupDensityChart.tsx
+++ b/web/src/features/bond/LockupDensityChart.tsx
@@ -23,6 +23,12 @@ const COLORS: string[] = [
"#0B1D2E",
];
+
+interface CustomTooltipProps extends TooltipProps {
+ active?: boolean;
+ payload?: any;
+ label?: string; }
+
const LockupDensityChart: React.FC = () => {
const lockupDensity = useAppSelector(selectLockupDensity) || [];
//console.log(lockupDensity);
@@ -31,17 +37,11 @@ const LockupDensityChart: React.FC = () => {
return Loading...
;
}
- interface CustomTooltipProps extends TooltipProps {
- active?: boolean;
- payload?: any;
- label?: string;
- }
-
const CustomTooltip: React.FC = ({
- active,
- payload,
- label,
- }) => {
+ active,
+ payload,
+ label,
+}) => {
if (active && payload && payload.length) {
return (
diff --git a/web/src/features/bond/LockupStatusChart.tsx b/web/src/features/bond/LockupStatusChart.tsx
index 647abaa..054c2b4 100644
--- a/web/src/features/bond/LockupStatusChart.tsx
+++ b/web/src/features/bond/LockupStatusChart.tsx
@@ -27,12 +27,11 @@ interface BondLockupDistributionBarChartProps {
interface CustomTooltipProps extends TooltipProps
{
active?: boolean;
payload?: any;
- label?: string;
-}
+ label?: string; }
-const BondLockupDistributionBarChart: React.FC = ({
- bond,
-}) => {
+ const BondLockupDistributionBarChart: React.FC = ({
+ bond,
+ }) => {
const { lockedCount, claimedCount } = bond;
diff --git a/web/src/features/bond/LockupsOverTimeBarChart.tsx b/web/src/features/bond/LockupsOverTimeBarChart.tsx
index 89c5bec..b322cd0 100644
--- a/web/src/features/bond/LockupsOverTimeBarChart.tsx
+++ b/web/src/features/bond/LockupsOverTimeBarChart.tsx
@@ -5,51 +5,51 @@ import {
XAxis,
YAxis,
Tooltip,
- ResponsiveContainer,
TooltipProps,
+ ResponsiveContainer,
} from "recharts";
import { useAppSelector } from "@/app/hooks";
import { selectLockupSummaryBar, selectDisplayAmount } from "./bondSlice";
import styles from "@/styles/main.module.css";
import { LockupDailyAmount } from "./types";
-import {AppLoader} from '@/features/components/Loader';
+import { AppLoader } from "@/features/components/Loader";
const COLORS = {
amount: "#4A9079",
lockupCount: "#E41968",
};
-
interface CustomTooltipProps extends TooltipProps {
active?: boolean;
payload?: any;
label?: string;
}
-const CustomTooltip: React.FC = ({
- active,
- payload,
- label,
-}) => {
- if (active && payload && payload.length) {
- return (
-
-
{`${label} : ${payload[0].value.toLocaleString()} KDA`}
-
- );
- }
- return null;
-};
-
-
-
const LockupsOverTimeBarChart: React.FC = () => {
const lockupSummaryBar = useAppSelector(selectLockupSummaryBar) || [];
const displayAmount = useAppSelector(selectDisplayAmount);
+ const CustomTooltip: React.FC = ({
+ active,
+ payload,
+ label,
+ }) => {
+ if (active && payload && payload.length) {
+ return (
+
+
{`${label} : ${payload[0].value.toLocaleString()} ${displayAmount ? "KDA" : "lockups"} `}
+
+ );
+ }
+ return null;
+ };
+
if (!lockupSummaryBar) {
- return
-;
+ return (
+
+ );
}
const data = lockupSummaryBar.map((lockup: LockupDailyAmount) => ({
@@ -72,14 +72,20 @@ const LockupsOverTimeBarChart: React.FC = () => {
data={sortedData}
margin={{ top: 20, right: 30, left: 20, bottom: 20 }}
>
-
} />
{
const dispatch = useAppDispatch();
const { account } = useKadenaReact();
- if (!account) return null;
const isCoreMember = useAppSelector((state: RootState) =>
selectIsCoreMember(state),
@@ -24,7 +23,7 @@ const CreateBondComponent: React.FC = () => {
}, [dispatch, account]);
const handleSubmit = useCreateBond();
-
+ const safeAccount = account ? account.account : "";
const [lockupOptions, setLockupOptions] = useState([]);
const [newBond, setNewBond] = useState({
startTime: "",
@@ -34,7 +33,7 @@ const CreateBondComponent: React.FC = () => {
maxAmount: 100000.0,
minAmount: 1000.0,
totalRewards: 10000.0,
- creator: account?.account,
+ creator: safeAccount,
});
const handleLockupOptionChange = (
@@ -75,7 +74,7 @@ const CreateBondComponent: React.FC = () => {
setLockupOptions((prevOptions) => {
const newOption: PactLockupOption = {
"option-name": "10 minutes",
- "option-length": 600,
+ "option-length": { int: 600 }, // Initialize as an object with "int"
"time-multiplier": 1.1,
"poller-max-boost": 1.0,
"polling-power-multiplier": 1.0,
@@ -99,20 +98,22 @@ const CreateBondComponent: React.FC = () => {
}
};
+ if (!account) return;
+
if (!isCoreMember) {
return (
- You must be a core member to create a lockup oportunity.
+ You must be a core member to create a lockup.
);
}
return (