diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..ebfe7db --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,7 @@ + + + 10 + enable + disable + + diff --git a/SapientFi.Cosmos.sln.DotSettings b/SapientFi.Cosmos.sln.DotSettings index babbf5c..cd92545 100644 --- a/SapientFi.Cosmos.sln.DotSettings +++ b/SapientFi.Cosmos.sln.DotSettings @@ -2,6 +2,16 @@ NEVER NEVER NEVER + True + True + True + True + True + True True True + True + True + True + True True \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index f92bc25..9834a0a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.9' services: db: - image: timescale/timescaledb:2.5.0-pg12 + image: timescale/timescaledb:2.10.0-pg15 command: ["postgres", "-c", "log_statement=all", "-c", "max_locks_per_transaction=4096"] shm_size: 1g ports: @@ -16,7 +16,7 @@ services: - .docker/postgres/data:/var/lib/postgresql/data rabbitmq: - image: masstransit/rabbitmq:3.9 + image: rabbitmq:3.11-management environment: # default user + pass is "guest" RABBITMQ_ERLANG_COOKIE: ${RABBITMQ_ERLANG_COOKIE} @@ -32,7 +32,7 @@ services: - "35433:6379" logging: - image: datalust/seq:5.1 + image: datalust/seq:latest ports: - "35435:80" environment: diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..c086269 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,19 @@ +# Configuration + +Keys marked with :exclamation: should be set when deploying. + +## General + +| Key | Type | Default | Example | Description | +|----------------------------------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|-------------| +| **General** | | | | | +| `SAPIENTFI_SERVICE_ROLES_ENABLED` | comma-separated-list | `API, BACKGROUND_WORKER` | `Foo, Bar, Baz` | | +| `SAPIENTFI_DB_CONNECTION_STRING` | string | `User ID=sapientfi_indexer_user;Password=sapientfi_indexer_user_pass;Host=localhost;Port=35432;Database=sapientfi_indexer;Pooling=true;Minimum Pool Size=10;Maximum Pool Size=100;` | | | +| `SAPIENTFI_DB_RUN_MIGRATIONS_ON_BOOT` | bool | `true` | | | +| `SAPIENTFI_NR_ELEMENTS_DEPOSITS_PR_WALLET` | int | `11` | | | +| `SAPIENTFI_API_ALLOWED_CORS_ORIGINS` | string | `http://localhost:3000` | | | +| `SAPIENTFI_TRIGGER_GATEWAY_POOL_FULL_RESYNC` | bool | `false` | | | +| `SAPIENTFI_TRIGGER_MINE_STAKING_FULL_RESYNC` | bool | `false` | | | +| `SAPIENTFI_TRIGGER_MINE_BUYBACK_FULL_RESYNC` | bool | `false` | | | +| `SAPIENTFI_MESSAGE_TRANSPORT_URI` | string | `amqp://guest:guest@localhost:5672` | | | +| `Kujira_TransactionListener_DoEnable` | bool | `true` | | | \ No newline at end of file diff --git a/src/Infrastructure/Cosmos/BusMessages/IRawCosmosTransactionAvailableAnnouncement.cs b/src/Infrastructure/Cosmos/BusMessages/IRawCosmosTransactionAvailableAnnouncement.cs new file mode 100644 index 0000000..fa3ab40 --- /dev/null +++ b/src/Infrastructure/Cosmos/BusMessages/IRawCosmosTransactionAvailableAnnouncement.cs @@ -0,0 +1,17 @@ +using System; + +namespace SapientFi.Infrastructure.Cosmos.BusMessages; + +public interface IRawCosmosTransactionAvailableAnnouncement +{ + public string TransactionHash { get; init; } + /// + /// Primary key id in the underlying database + /// + public long RawEntityId { get; init; } + + /// + /// Timestamp in the entity for it's creation date. + /// + public DateTimeOffset CreatedAt { get; init; } +} \ No newline at end of file diff --git a/src/Infrastructure/Cosmos/CosmosFactory.cs b/src/Infrastructure/Cosmos/CosmosFactory.cs new file mode 100644 index 0000000..1148cc9 --- /dev/null +++ b/src/Infrastructure/Cosmos/CosmosFactory.cs @@ -0,0 +1,31 @@ +using System.Text.Json; +using SapientFi.Infrastructure.Cosmos.Storage; +using SapientFi.Kernel.IdGeneration; +using TerraDotnet; +using TerraDotnet.TerraLcd.Messages; + +namespace SapientFi.Infrastructure.Cosmos; + +public class CosmosFactory + : ICosmosFactory + where TRawTransactionEntity : ICosmosRawTransactionEntity, new() +{ + protected readonly IdProvider IdProvider; + + public CosmosFactory(IdProvider idProvider) + { + IdProvider = idProvider; + } + + public virtual TRawTransactionEntity NewRawEntity(LcdTxResponse lcdTransaction) + { + return new() + { + Id = IdProvider.Snowflake(), + Height = lcdTransaction.HeightAsInt, + CreatedAt = lcdTransaction.CreatedAt, + TxHash = lcdTransaction.TransactionHash, + RawTx = JsonSerializer.Serialize(lcdTransaction, TerraJsonSerializerOptions.GetThem()) + }; + } +} diff --git a/src/Infrastructure/Cosmos/CosmosMarker.cs b/src/Infrastructure/Cosmos/CosmosMarker.cs new file mode 100644 index 0000000..6f4da62 --- /dev/null +++ b/src/Infrastructure/Cosmos/CosmosMarker.cs @@ -0,0 +1,3 @@ +namespace SapientFi.Infrastructure.Cosmos; + +public abstract record CosmosMarker; diff --git a/src/Infrastructure/Cosmos/ICosmosFactory.cs b/src/Infrastructure/Cosmos/ICosmosFactory.cs new file mode 100644 index 0000000..3566f45 --- /dev/null +++ b/src/Infrastructure/Cosmos/ICosmosFactory.cs @@ -0,0 +1,10 @@ +using SapientFi.Infrastructure.Cosmos.Storage; +using TerraDotnet.TerraLcd.Messages; + +namespace SapientFi.Infrastructure.Cosmos; + +public interface ICosmosFactory + where TRawTransactionEntity : ICosmosRawTransactionEntity, new() +{ + public TRawTransactionEntity NewRawEntity(LcdTxResponse lcdTransaction); +} diff --git a/src/Infrastructure/Cosmos/Indexers/Delegations/CosmosDelegationIndexer.cs b/src/Infrastructure/Cosmos/Indexers/Delegations/CosmosDelegationIndexer.cs new file mode 100644 index 0000000..36b0034 --- /dev/null +++ b/src/Infrastructure/Cosmos/Indexers/Delegations/CosmosDelegationIndexer.cs @@ -0,0 +1,205 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using MassTransit; +using Microsoft.Extensions.Logging; +using SapientFi.Infrastructure.Cosmos.BusMessages; +using SapientFi.Infrastructure.Cosmos.Indexers.Delegations.Storage; +using SapientFi.Infrastructure.Cosmos.Storage; +using SapientFi.Infrastructure.Indexing; +using SapientFi.Kernel.IdGeneration; +using TerraDotnet; +using TerraDotnet.TerraFcd.Messages; +using TerraDotnet.TerraLcd.Messages; + +namespace SapientFi.Infrastructure.Cosmos.Indexers.Delegations; + +public abstract class + CosmosDelegationIndexer< + TRawTransactionAvailableAnnouncement, + TValidatorDelegationLedgerEntity, + TRawTransactionEntity> + : IIndexer + where TRawTransactionAvailableAnnouncement : class, IRawCosmosTransactionAvailableAnnouncement + where TValidatorDelegationLedgerEntity : ICosmosValidatorDelegationLedgerEntity, new() + where TRawTransactionEntity : ICosmosRawTransactionEntity, new() +{ + protected readonly + ILogger> Logger; + protected readonly CosmosDelegationsRepository Repository; + protected readonly CosmosRawRepository RawRepository; + protected readonly IdProvider IdProvider; + + protected CosmosDelegationIndexer( + ILogger> logger, + CosmosDelegationsRepository repository, + CosmosRawRepository rawRepository, + IdProvider idProvider + ) + { + Logger = logger; + Repository = repository; + RawRepository = rawRepository; + IdProvider = idProvider; + } + + public abstract string NameOfBlockChain { get; } + public abstract string Id { get; } + public abstract string DisplayName { get; } + + public virtual async Task Consume(ConsumeContext context) + { + await DefaultConsume(context); + } + + protected async Task DefaultConsume(ConsumeContext context) + { + var rawTransaction = await RawRepository.GetByIdAndCreateOrDefaultAsync( + context.Message.RawEntityId, + context.Message.CreatedAt, + context.CancellationToken + ); + if (rawTransaction is null) + { + // TODO write to a custom metric in order to be able to monitor missing raw transactions + Logger.LogWarning( + "({BlockChain}) Unable to find raw transaction with id={RawEntityId}", + NameOfBlockChain, + context.Message.RawEntityId + ); + // throw for retry! + throw new Exception($"Unable to find raw transaction with id={context.Message.RawEntityId}") + } + + var txHash = rawTransaction.TxHash; + + // TODO: deal with re-entrance/re-processing i.e. make sure we do not make duplicate entries if we process the same TX multiple times + + var tx = JsonSerializer.Deserialize(rawTransaction.RawTx); + if (tx == default) + { + Logger.LogWarning( + "({BlockChain}) Could not deserialize transaction with hash={TxHash}", + NameOfBlockChain, + txHash + ); + } + + foreach (var txMessage in tx!.Transaction.Body.Messages) + { + var parseSuccessful = TerraMessageParser.TryParse(txMessage, out IMsg? message); + if (parseSuccessful) + { + switch (message) + { + case CosmosUndelegateMessage undelegateMsg: + { + if (undelegateMsg.Amount == default) + { + Logger.LogWarning( + "({BlockChain}) Missing amount on undelegate message (txHash={TxHash})", + NameOfBlockChain, + txHash + ); + break; + } + + var undelegateEntity = new TValidatorDelegationLedgerEntity + { + Id = IdProvider.Snowflake(), + TxHash = txHash, + At = rawTransaction.CreatedAt, + DelegatorAddress = undelegateMsg.DelegatorAddress, + ValidatorAddress = undelegateMsg.ValidatorAddress, + Amount = -long.Parse(undelegateMsg.Amount.Amount), // subtracts amount + Denominator = undelegateMsg.Amount.Denominator + }; + + await Repository.SaveAsync(undelegateEntity, context.CancellationToken); + break; + } + + case CosmosDelegateMessage delegateMsg: + { + if (delegateMsg.Amount == default) + { + Logger.LogWarning( + "({BlockChain}) Missing amount on delegate message (txHash={TxHash})", + NameOfBlockChain, + txHash + ); + break; + } + + var delegateEntity = new TValidatorDelegationLedgerEntity + { + Id = IdProvider.Snowflake(), + TxHash = txHash, + At = rawTransaction.CreatedAt, + DelegatorAddress = delegateMsg.DelegatorAddress, + ValidatorAddress = delegateMsg.ValidatorAddress, + Amount = long.Parse(delegateMsg.Amount.Amount), // adds amount + Denominator = delegateMsg.Amount.Denominator + }; + + await Repository.SaveAsync(delegateEntity, context.CancellationToken); + break; + } + + case CosmosRedelegateMessage redelegateMsg: + { + if (redelegateMsg.Amount == default) + { + Logger.LogWarning( + "({BlockChain}) Missing amount on redelegate message (txHash={TxHash})", + NameOfBlockChain, + txHash + ); + break; + } + + var entities = new[] + { + // first a ledger entry that removes the delegation + // from the original validator + new TValidatorDelegationLedgerEntity + { + Id = IdProvider.Snowflake(), + TxHash = txHash, + At = rawTransaction.CreatedAt, + DelegatorAddress = redelegateMsg.DelegatorAddress, + ValidatorAddress = redelegateMsg.ValidatorSourceAddress, + Amount = -long.Parse(redelegateMsg.Amount.Amount), // subtracts amount + Denominator = redelegateMsg.Amount.Denominator + }, + // then a ledger entry adding the delegation to the new validator + new TValidatorDelegationLedgerEntity + { + Id = IdProvider.Snowflake(), + TxHash = txHash, + At = rawTransaction.CreatedAt, + DelegatorAddress = redelegateMsg.DelegatorAddress, + ValidatorAddress = redelegateMsg.ValidatorDestinationAddress, + Amount = long.Parse(redelegateMsg.Amount.Amount), // adds amount + Denominator = redelegateMsg.Amount.Denominator + } + }; + + await Repository.SaveAllAsync(entities, context.CancellationToken); + break; + } + + default: + // we do not care about this message, so leave it be :) + break; + } + } + } + } +} diff --git a/src/Infrastructure/Cosmos/Indexers/Delegations/Storage/CosmosDelegationsRepository.cs b/src/Infrastructure/Cosmos/Indexers/Delegations/Storage/CosmosDelegationsRepository.cs new file mode 100644 index 0000000..ab65575 --- /dev/null +++ b/src/Infrastructure/Cosmos/Indexers/Delegations/Storage/CosmosDelegationsRepository.cs @@ -0,0 +1,76 @@ +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Data; +using ServiceStack.OrmLite; + +namespace SapientFi.Infrastructure.Cosmos.Indexers.Delegations.Storage; + +public abstract class CosmosDelegationsRepository + where TValidatorDelegationLedgerEntity : ICosmosValidatorDelegationLedgerEntity +{ + protected readonly IDbConnectionFactory DbFactory; + + protected CosmosDelegationsRepository(IDbConnectionFactory dbFactory) + { + DbFactory = dbFactory; + } + + public async Task SaveAsync( + TValidatorDelegationLedgerEntity entity, + CancellationToken cancellationToken = default + ) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(cancellationToken); + await db.SaveAsync(entity, token: cancellationToken); + } + + public async Task SaveAllAsync( + IEnumerable entities, + CancellationToken cancellationToken = default + ) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(cancellationToken); + await db.SaveAllAsync(entities, token: cancellationToken); + } + + public async Task SingleAsync(SqlExpression sql, CancellationToken ct = default) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(ct); + return await db.SingleAsync(sql, ct); + } + + public async Task> SelectAsync( + SqlExpression sql, + CancellationToken ct = default + ) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(ct); + return await db.SelectAsync(sql, ct); + } + + public async Task SingleByIdAsync(T2 id, CancellationToken ct = default) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(ct); + return await db.SingleByIdAsync(id, ct); + } + + public async Task> SelectByIds(IEnumerable ids, CancellationToken ct = default) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(ct); + return await db.SelectByIdsAsync(ids, ct); + } + + public async Task ScalarAsync(SqlExpression sql, CancellationToken ct = default) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(ct); + return await db.ScalarAsync(sql, ct); + } + + public async Task GetDbConnectionAsync(CancellationToken cancellationToken = default) + { + return await DbFactory.OpenDbConnectionAsync(cancellationToken); + } +} diff --git a/src/Infrastructure/Cosmos/Indexers/Delegations/Storage/ICosmosValidatorDelegationLedgerEntity.cs b/src/Infrastructure/Cosmos/Indexers/Delegations/Storage/ICosmosValidatorDelegationLedgerEntity.cs new file mode 100644 index 0000000..eed7f36 --- /dev/null +++ b/src/Infrastructure/Cosmos/Indexers/Delegations/Storage/ICosmosValidatorDelegationLedgerEntity.cs @@ -0,0 +1,38 @@ +using System; +using ServiceStack.DataAnnotations; + +namespace SapientFi.Infrastructure.Cosmos.Indexers.Delegations.Storage; + +/// +/// Represents an entry in a "ledger" of delegated +/// governance tokens for a specific validator. +/// +/// This means that when tokens are re-delegated +/// i.e. moved from validator A to validator B, +/// we have 2 entries for the same action (but with +/// inverted signs), as the tokens are leaving A +/// and going into B. +/// +public interface ICosmosValidatorDelegationLedgerEntity +{ + public long Id { get; init; } + + public DateTimeOffset At { get; init; } + + /// + /// The TxHash of the transaction that this delegation came from + /// + public string TxHash { get; init; } + + public string ValidatorAddress { get; init; } + + public string DelegatorAddress { get; init; } + + /// + /// Positive = delegation to validator + /// Negative = delegation away from validator + /// + public long Amount { get; init; } + + public string Denominator { get; init; } +} diff --git a/src/Infrastructure/Cosmos/Storage/CosmosRawRepository.cs b/src/Infrastructure/Cosmos/Storage/CosmosRawRepository.cs new file mode 100644 index 0000000..b61a0f3 --- /dev/null +++ b/src/Infrastructure/Cosmos/Storage/CosmosRawRepository.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack; +using ServiceStack.Data; +using ServiceStack.OrmLite; + +namespace SapientFi.Infrastructure.Cosmos.Storage; + +public abstract class CosmosRawRepository + where TRawTransactionEntity : ICosmosRawTransactionEntity +{ + protected readonly IDbConnectionFactory DbFactory; + + protected CosmosRawRepository(IDbConnectionFactory dbFactory) + { + DbFactory = dbFactory; + } + + public async Task SaveRawTransactionAsync( + TRawTransactionEntity entity, + CancellationToken cancellationToken = default + ) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(cancellationToken); + await db.SaveAsync(entity, token: cancellationToken); + } + + public async Task GetLatestSeenBlockHeightAsync(CancellationToken cancellationToken = default) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(cancellationToken); + return await db.ScalarAsync( + x => Sql.Max(x.Height), + _ => true, + cancellationToken + ); + } + + public async Task GetByIdOrDefaultAsync( + long id, + CancellationToken cancellationToken = default + ) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(cancellationToken); + return await db.SingleByIdAsync(id, cancellationToken); + } + + public async Task GetByIdAndCreateOrDefaultAsync( + long id, + DateTimeOffset createdAt, + CancellationToken cancellationToken = default + ) + { + using IDbConnection? db = await DbFactory.OpenDbConnectionAsync(cancellationToken); + var results = + await db.SelectAsync( + q => q.Id == id + && q.CreatedAt >= createdAt, + token: cancellationToken); + + return results.IsEmpty() ? default : results[0]; + } +} \ No newline at end of file diff --git a/src/Infrastructure/Cosmos/Storage/ICosmosRawTransactionEntity.cs b/src/Infrastructure/Cosmos/Storage/ICosmosRawTransactionEntity.cs new file mode 100644 index 0000000..7130262 --- /dev/null +++ b/src/Infrastructure/Cosmos/Storage/ICosmosRawTransactionEntity.cs @@ -0,0 +1,16 @@ +using System; + +namespace SapientFi.Infrastructure.Cosmos.Storage; + +public interface ICosmosRawTransactionEntity +{ + public long Id { get; init; } + + public DateTimeOffset CreatedAt { get; init; } + + public string TxHash { get; init; } + + public string RawTx { get; init; } + + public int Height { get; init; } +} diff --git a/src/Infrastructure/Cosmos/TransactionListener/CosmosTransactionListenerConfig.cs b/src/Infrastructure/Cosmos/TransactionListener/CosmosTransactionListenerConfig.cs new file mode 100644 index 0000000..7aa0abf --- /dev/null +++ b/src/Infrastructure/Cosmos/TransactionListener/CosmosTransactionListenerConfig.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Configuration; + +namespace SapientFi.Infrastructure.Cosmos.TransactionListener; + +public abstract class CosmosTransactionListenerConfig +{ + protected readonly IConfiguration Raw; + + protected CosmosTransactionListenerConfig(IConfiguration raw) + { + Raw = raw; + } + + public abstract bool DoEnable(); + + public abstract string LcdUri(); +} diff --git a/src/Infrastructure/Cosmos/TransactionListener/CosmosTransactionListenerHostedService.cs b/src/Infrastructure/Cosmos/TransactionListener/CosmosTransactionListenerHostedService.cs new file mode 100644 index 0000000..3d8dc30 --- /dev/null +++ b/src/Infrastructure/Cosmos/TransactionListener/CosmosTransactionListenerHostedService.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MassTransit; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Npgsql; +using SapientFi.Infrastructure.Cosmos.BusMessages; +using SapientFi.Infrastructure.Cosmos.Storage; +using TerraDotnet; +using TerraDotnet.TerraLcd.Messages; +// ReSharper disable MemberCanBePrivate.Global + +namespace SapientFi.Infrastructure.Cosmos.TransactionListener; + +public abstract class + CosmosTransactionListenerHostedService + : IHostedService + where TMarker : CosmosMarker + where TRawTransactionEntity : ICosmosRawTransactionEntity, new() + where TRawTransactionAvailableAnnouncement : IRawCosmosTransactionAvailableAnnouncement, new() +{ + protected readonly + ILogger> Logger; + protected readonly CosmosTransactionEnumerator TransactionEnumerator; + protected readonly CosmosRawRepository RawRepository; + protected readonly CosmosFactory Factory; + protected readonly IBus MassTransitBus; + + protected CancellationTokenSource? TokenSource; + protected abstract string NameOfBlockChain { get; } + + + protected CosmosTransactionListenerHostedService( + ILogger> logger, + CosmosTransactionEnumerator transactionEnumerator, + CosmosRawRepository rawRepository, + CosmosFactory factory, + IBus massTransitBus + ) + { + Logger = logger; + TransactionEnumerator = transactionEnumerator; + RawRepository = rawRepository; + Factory = factory; + MassTransitBus = massTransitBus; + } + + public virtual async Task StartAsync(CancellationToken cancellationToken) + { + Logger.LogInformation("Starting {BlockChain} transaction listener", NameOfBlockChain); + TokenSource = new CancellationTokenSource(); + + int latestSeenBlock = await RawRepository.GetLatestSeenBlockHeightAsync(cancellationToken); + IAsyncEnumerable enumeration = + TransactionEnumerator.EnumerateTransactionsAsync(latestSeenBlock, cancellationToken); + + var thread = new Thread(async () => + { + await foreach (LcdTxResponse lcdTransaction in enumeration.WithCancellation(cancellationToken)) + { + Logger.LogDebug("Got new raw transaction {TxHash} with height={TxHeight}", + lcdTransaction.TransactionHash, + lcdTransaction.HeightAsInt + ); + TRawTransactionEntity entity = Factory.NewRawEntity(lcdTransaction); + + try + { + await RawRepository.SaveRawTransactionAsync(entity, cancellationToken); + + await MassTransitBus.Publish( + new TRawTransactionAvailableAnnouncement + { + TransactionHash = entity.TxHash, + RawEntityId = entity.Id, + CreatedAt = entity.CreatedAt, + }, cancellationToken); + } + catch (PostgresException e) when (e.SqlState == PostgresErrorCodes.UniqueViolation) + { + // we already have that transaction, so do nothing else + } + } + } + ); + + Logger.LogInformation("Starting worker thread for {BlockChain}'s transaction listener", NameOfBlockChain); + thread.Start(); + } + + public virtual Task StopAsync(CancellationToken cancellationToken) + { + Logger.LogInformation("Stopping {BlockChain} transactions listener", NameOfBlockChain); + TokenSource?.Cancel(); + return Task.CompletedTask; + } +} diff --git a/src/Infrastructure/DAL/Migrations/M_20220624_1049_from_scratch.cs b/src/Infrastructure/DAL/Migrations/M_20220624_1049_from_scratch.cs new file mode 100644 index 0000000..7f7f89b --- /dev/null +++ b/src/Infrastructure/DAL/Migrations/M_20220624_1049_from_scratch.cs @@ -0,0 +1,91 @@ +using RapidCore.Migration; +using RapidCore.PostgreSql.Migration; +using ServiceStack.OrmLite.Dapper; + +namespace SapientFi.Infrastructure.Migrations; + +public class M_20220624_1049_from_scratch: MigrationBase +{ + protected override void ConfigureUpgrade(IMigrationBuilder builder) + { + var ctx = ContextAs(); + var connection = ctx.ConnectionProvider.Default(); + + /* ********************************************************************************* + */ + builder.Step("enable timescaledb", () => + { + connection.Execute("CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;"); + }); + + + /* ********************************************************************************* + */ + builder.Step("create raw terra2 transaction table", () => + { + connection.Execute(@" +create table terra2_raw_transaction_entity +( + id bigint not null, + raw_tx jsonb, + created_at timestamp with time zone not null, + tx_hash text, + height integer not null, + + constraint terra2_raw_transaction_entity_pkey + primary key (id, created_at), + + constraint terra2_raw_transaction_entity_uniqueness + unique (tx_hash, created_at) +); + +create index idx_terra2rawtransactionentity_height + on terra2_raw_transaction_entity (height); + +SELECT create_hypertable('terra2_raw_transaction_entity', 'created_at', chunk_time_interval => INTERVAL '1 day'); +"); + }); + + // TODO Why is amount using 38 digits numeric precision? It seems rather excessive, none of the + // built-in types are even that big. Ain't uluna represented as a uint64? That's only 20 digits. + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types + + /* ********************************************************************************* + */ + builder.Step("create terra2 delegations table", () => + { + connection.Execute(@" +create table terra2_validator_delegation_ledger_entity +( + id bigint not null, + at timestamp with time zone not null, + tx_hash text, + validator_address text, + delegator_address text, + amount numeric(38, 6) not null, + denominator varchar, + + constraint terra2_validator_delegation_ledger_entity_pkey + primary key (id, at) +); + +create index idx_terra2validatordelegationledgerentity_txhash + on terra2_validator_delegation_ledger_entity (tx_hash); + +create index idx_terra2validatordelegationledgerentity_valaddr + on terra2_validator_delegation_ledger_entity (validator_address); + +create index idx_terra2validatordelegationledgerentity_deladdr + on terra2_validator_delegation_ledger_entity (delegator_address); + +SELECT create_hypertable('terra2_validator_delegation_ledger_entity', 'at', chunk_time_interval => INTERVAL '1 day'); +"); + }); + } + + protected override void ConfigureDowngrade(IMigrationBuilder builder) + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Infrastructure/DAL/Migrations/M_20221006_1242_add_kujira.cs b/src/Infrastructure/DAL/Migrations/M_20221006_1242_add_kujira.cs new file mode 100644 index 0000000..12efdd2 --- /dev/null +++ b/src/Infrastructure/DAL/Migrations/M_20221006_1242_add_kujira.cs @@ -0,0 +1,83 @@ +using RapidCore.Migration; +using RapidCore.PostgreSql.Migration; +using ServiceStack.OrmLite.Dapper; + +namespace SapientFi.Infrastructure.Migrations; + +public class M_20221006_1242_add_kujira: MigrationBase +{ + protected override void ConfigureUpgrade(IMigrationBuilder builder) + { + var ctx = ContextAs(); + var connection = ctx.ConnectionProvider.Default(); + + /* ********************************************************************************* + */ + builder.Step("create raw kujira transaction table", () => + { + connection.Execute(@" +create table kujira_raw_transaction_entity +( + id bigint not null, + raw_tx jsonb, + created_at timestamp with time zone not null, + tx_hash text, + height integer not null, + + constraint kujira_raw_transaction_entity_pkey + primary key (id, created_at), + + constraint kujira_raw_transaction_entity_uniqueness + unique (tx_hash, created_at) +); + +create index idx_kujirarawtransactionentity_height + on kujira_raw_transaction_entity (height); + +SELECT create_hypertable('kujira_raw_transaction_entity', 'created_at', chunk_time_interval => INTERVAL '7 days'); +"); + }); + + // TODO Why is amount using 38 digits numeric precision? It seems rather excessive, none of the + // built-in types are even that big. Ain't uluna represented as a uint64? That's only 20 digits. + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types + + /* ********************************************************************************* + */ + builder.Step("create kujira delegations table", () => + { + connection.Execute(@" +create table kujira_validator_delegation_ledger_entity +( + id bigint not null, + at timestamp with time zone not null, + tx_hash text, + validator_address text, + delegator_address text, + amount numeric(38, 6) not null, + denominator varchar, + + constraint kujira_validator_delegation_ledger_entity_pkey + primary key (id, at) +); + +create index idx_kujiravalidatordelegationledgerentity_txhash + on kujira_validator_delegation_ledger_entity (tx_hash); + +create index idx_kujiravalidatordelegationledgerentity_valaddr + on kujira_validator_delegation_ledger_entity (validator_address); + +create index idx_kujiravalidatordelegationledgerentity_deladdr + on kujira_validator_delegation_ledger_entity (delegator_address); + +SELECT create_hypertable('kujira_validator_delegation_ledger_entity', 'at', chunk_time_interval => INTERVAL '7 days'); +"); + }); + } + + protected override void ConfigureDowngrade(IMigrationBuilder builder) + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Infrastructure/DAL/Migrations/M_202304270909_index_id.cs b/src/Infrastructure/DAL/Migrations/M_202304270909_index_id.cs new file mode 100644 index 0000000..73a49c9 --- /dev/null +++ b/src/Infrastructure/DAL/Migrations/M_202304270909_index_id.cs @@ -0,0 +1,44 @@ +using RapidCore.Migration; +using RapidCore.PostgreSql.Migration; +using ServiceStack.OrmLite.Dapper; + +namespace SapientFi.Infrastructure.Migrations; + +public class M_202304270909_index_id: MigrationBase +{ + protected override void ConfigureUpgrade(IMigrationBuilder builder) + { + var ctx = ContextAs(); + var connection = ctx.ConnectionProvider.Default(); + + /* ********************************************************************************* + */ + builder.Step("create index on id in terra raw", () => + { + connection.Execute(@" +create index terra2_raw_transaction_entity_id_index + on public.terra2_raw_transaction_entity (id); +"); + }); + + // TODO Why is amount using 38 digits numeric precision? It seems rather excessive, none of the + // built-in types are even that big. Ain't uluna represented as a uint64? That's only 20 digits. + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types + + /* ********************************************************************************* + */ + builder.Step("create index on id on kujira raw", () => + { + connection.Execute(@" +create index kujira_raw_transaction_entity_id_index + on public.kujira_raw_transaction_entity (id); +"); + }); + } + + protected override void ConfigureDowngrade(IMigrationBuilder builder) + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Infrastructure/DAL/Queriers/Terra2StakingQuerier.cs b/src/Infrastructure/DAL/Queriers/Terra2StakingQuerier.cs new file mode 100644 index 0000000..9d468c2 --- /dev/null +++ b/src/Infrastructure/DAL/Queriers/Terra2StakingQuerier.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using SapientFi.Infrastructure.Terra2.Indexers.Delegations.Storage; +using ServiceStack.OrmLite; + +namespace SapientFi.Infrastructure.DAL.Queriers; + +public class Terra2StakingQuerier +{ + private readonly Terra2DelegationsRepository _repository; + + public Terra2StakingQuerier(Terra2DelegationsRepository repository) + { + _repository = repository; + } + + public async Task> TotalStakingCurrentlyAtSpecificValidatorsAsync( + IEnumerable validatorAddresses, + CancellationToken cancellationToken = default) + { + using var db = await _repository.GetDbConnectionAsync(cancellationToken); + var result = await db.SqlListAsync<(string, long)>(db.From() + .Select(delegation => + new { + ValidatorAddr = delegation.ValidatorAddress, + Sum = Sql.Sum(delegation.Amount) + }) + .GroupBy(delegation => delegation.ValidatorAddress) + .Where(delegation => Sql.In(delegation.ValidatorAddress, validatorAddresses)), + cancellationToken + ); + + return result; + } + + public async Task> TotalStakingAtGivenTimeAtSpecificValidatorsAsync( + IEnumerable validatorAddresses, + DateTime dateTime, + CancellationToken cancellationToken = default + ) + { + using var db = await _repository.GetDbConnectionAsync(cancellationToken); + + var result = await db.SqlListAsync<(string, long)>(db.From() + .Select(delegation => + new { + ValidatorAddr = delegation.ValidatorAddress, + Sum = Sql.Sum(delegation.Amount) + }) + .GroupBy(delegation => delegation.ValidatorAddress) + .Where(delegation => Sql.In(delegation.ValidatorAddress, validatorAddresses)) + .And(validator => validator.At <= dateTime), + cancellationToken + ); + + return result; + } + + public async Task> DeltaStakingInTimePeriodAtSpecificValidators( + IEnumerable validatorAddresses, + DateTime from, + DateTime to, + CancellationToken cancellationToken = default) + { + + using var db = await _repository.GetDbConnectionAsync(cancellationToken); + + var result = await db.SqlListAsync<(string, long)>(db.From() + .Select(delegation => + new { + ValidatorAddr = delegation.ValidatorAddress, + Sum = Sql.Sum(delegation.Amount) + }) + .GroupBy(delegation => delegation.ValidatorAddress) + .Where(delegation => Sql.In(delegation.ValidatorAddress, validatorAddresses)) + .And(validator => validator.At >= from && validator.At <= to), + cancellationToken + ); + + return result; + } +} \ No newline at end of file diff --git a/src/Infrastructure/DAL/Repositories/ExchangeMarketRepository.cs b/src/Infrastructure/DAL/Repositories/ExchangeMarketRepository.cs index f793fe5..70cf1ae 100644 --- a/src/Infrastructure/DAL/Repositories/ExchangeMarketRepository.cs +++ b/src/Infrastructure/DAL/Repositories/ExchangeMarketRepository.cs @@ -1,8 +1,6 @@ +//TODO do we still need this? using Microsoft.Extensions.Logging; -using SapientFi.Kernel; -using SapientFi.Kernel.DAL.Entities.Exchanges; using ServiceStack.Data; -using ServiceStack.OrmLite; namespace SapientFi.Infrastructure.DAL.Repositories; @@ -19,24 +17,4 @@ IDbConnectionFactory dbConnectionFactory _logger = logger; _dbConnectionFactory = dbConnectionFactory; } - - public async Task GetLatestRateAsync( - string market, - Exchange exchange, - CancellationToken cancellationToken - ) - { - _logger.LogDebug("querying db for rates on market {Market} from exchange {Exchange}", market, exchange); - - using var db = await _dbConnectionFactory.OpenDbConnectionAsync(token: cancellationToken); - - var rate = await db.SingleAsync( - db.From() - .Where(q => q.Market == market && q.Exchange == exchange) - .OrderByDescending(q => q.CloseTime) - .Take(1), - token: cancellationToken); - - return rate; - } } \ No newline at end of file diff --git a/src/Infrastructure/Hosting/BackgroundWorkers/IScopedBackgroundWorker.cs b/src/Infrastructure/Hosting/BackgroundWorkers/IScopedBackgroundWorker.cs index 4d1f170..587220a 100644 --- a/src/Infrastructure/Hosting/BackgroundWorkers/IScopedBackgroundWorker.cs +++ b/src/Infrastructure/Hosting/BackgroundWorkers/IScopedBackgroundWorker.cs @@ -1,3 +1,6 @@ +using System.Threading; +using System.Threading.Tasks; + namespace SapientFi.Infrastructure.Hosting.BackgroundWorkers; public interface IScopedBackgroundServiceWorker diff --git a/src/Infrastructure/Hosting/BackgroundWorkers/ScopedBackgroundWorker.cs b/src/Infrastructure/Hosting/BackgroundWorkers/ScopedBackgroundWorker.cs index 993f71a..818b099 100644 --- a/src/Infrastructure/Hosting/BackgroundWorkers/ScopedBackgroundWorker.cs +++ b/src/Infrastructure/Hosting/BackgroundWorkers/ScopedBackgroundWorker.cs @@ -1,3 +1,6 @@ +using System; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; diff --git a/src/Infrastructure/Indexing/IIndexer.cs b/src/Infrastructure/Indexing/IIndexer.cs new file mode 100644 index 0000000..9a41b75 --- /dev/null +++ b/src/Infrastructure/Indexing/IIndexer.cs @@ -0,0 +1,10 @@ +using MassTransit; + +namespace SapientFi.Infrastructure.Indexing; + +public interface IIndexer : IConsumer where TAnnouncement : class +{ + string Id { get; } + + string DisplayName { get; } +} \ No newline at end of file diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 3eece57..9ca34c3 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -2,8 +2,6 @@ net6.0 - enable - enable SapientFi.Infrastructure SapientFi.Infrastructure @@ -27,10 +25,4 @@ - - - - - - diff --git a/src/Infrastructure/InfrastructureAssemblyMarker.cs b/src/Infrastructure/InfrastructureAssemblyMarker.cs new file mode 100644 index 0000000..9d9079c --- /dev/null +++ b/src/Infrastructure/InfrastructureAssemblyMarker.cs @@ -0,0 +1,5 @@ +namespace SapientFi.Infrastructure; + +public class InfrastructureAssemblyMarker +{ +} \ No newline at end of file diff --git a/src/Infrastructure/Kujira/BusMessages/RawKujiraTransactionAvailableAnnouncement.cs b/src/Infrastructure/Kujira/BusMessages/RawKujiraTransactionAvailableAnnouncement.cs new file mode 100644 index 0000000..30121f4 --- /dev/null +++ b/src/Infrastructure/Kujira/BusMessages/RawKujiraTransactionAvailableAnnouncement.cs @@ -0,0 +1,12 @@ +using System; +using SapientFi.Infrastructure.Cosmos.BusMessages; + +namespace SapientFi.Infrastructure.Kujira.BusMessages; + +public record RawKujiraTransactionAvailableAnnouncement : IRawCosmosTransactionAvailableAnnouncement +{ + public string TransactionHash { get; init; } = string.Empty; + public long RawEntityId { get; init; } + + public DateTimeOffset CreatedAt { get; init; } +} diff --git a/src/Infrastructure/Kujira/Indexers/Delegations/KujiraDelegationIndexer.cs b/src/Infrastructure/Kujira/Indexers/Delegations/KujiraDelegationIndexer.cs new file mode 100644 index 0000000..c1f1cb2 --- /dev/null +++ b/src/Infrastructure/Kujira/Indexers/Delegations/KujiraDelegationIndexer.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.Logging; +using SapientFi.Infrastructure.Cosmos.Indexers.Delegations; +using SapientFi.Infrastructure.Indexing; +using SapientFi.Infrastructure.Kujira.BusMessages; +using SapientFi.Infrastructure.Kujira.Indexers.Delegations.Storage; +using SapientFi.Infrastructure.Kujira.Storage; +using SapientFi.Kernel.IdGeneration; + +namespace SapientFi.Infrastructure.Kujira.Indexers.Delegations; + +public class KujiraDelegationIndexer + : CosmosDelegationIndexer< + RawKujiraTransactionAvailableAnnouncement, + KujiraValidatorDelegationLedgerEntity, + KujiraRawTransactionEntity>, + IIndexer +{ + public KujiraDelegationIndexer( + ILogger logger, + KujiraDelegationsRepository repository, + KujiraRawRepository rawRepository, + IdProvider idProvider + ) : base(logger, repository, rawRepository, idProvider) + { + } + + public override string NameOfBlockChain => "Kujira"; + public override string Id => "kujira_delegations"; + public override string DisplayName => "Kujira Delegations"; +} diff --git a/src/Infrastructure/Kujira/Indexers/Delegations/KujiraDelegationsServiceCollectionExtensions.cs b/src/Infrastructure/Kujira/Indexers/Delegations/KujiraDelegationsServiceCollectionExtensions.cs new file mode 100644 index 0000000..ce59691 --- /dev/null +++ b/src/Infrastructure/Kujira/Indexers/Delegations/KujiraDelegationsServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using SapientFi.Infrastructure.Kujira.Indexers.Delegations.Storage; + +namespace SapientFi.Infrastructure.Kujira.Indexers.Delegations; + +public static class KujiraDelegationsServiceCollectionExtensions +{ + public static IServiceCollection AddKujiraDelegationsIndexer(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + + return services; + } +} \ No newline at end of file diff --git a/src/Infrastructure/Kujira/Indexers/Delegations/Storage/KujiraDelegationsRepository.cs b/src/Infrastructure/Kujira/Indexers/Delegations/Storage/KujiraDelegationsRepository.cs new file mode 100644 index 0000000..dc9c54f --- /dev/null +++ b/src/Infrastructure/Kujira/Indexers/Delegations/Storage/KujiraDelegationsRepository.cs @@ -0,0 +1,11 @@ +using SapientFi.Infrastructure.Cosmos.Indexers.Delegations.Storage; +using ServiceStack.Data; + +namespace SapientFi.Infrastructure.Kujira.Indexers.Delegations.Storage; + +public class KujiraDelegationsRepository : CosmosDelegationsRepository +{ + public KujiraDelegationsRepository(IDbConnectionFactory dbFactory) : base(dbFactory) + { + } +} diff --git a/src/Infrastructure/Kujira/Indexers/Delegations/Storage/KujiraValidatorDelegationLedgerEntity.cs b/src/Infrastructure/Kujira/Indexers/Delegations/Storage/KujiraValidatorDelegationLedgerEntity.cs new file mode 100644 index 0000000..0d36f0f --- /dev/null +++ b/src/Infrastructure/Kujira/Indexers/Delegations/Storage/KujiraValidatorDelegationLedgerEntity.cs @@ -0,0 +1,43 @@ +using System; +using SapientFi.Infrastructure.Cosmos.Indexers.Delegations.Storage; +using ServiceStack.DataAnnotations; + +namespace SapientFi.Infrastructure.Kujira.Indexers.Delegations.Storage; + +/// +/// Represents an entry in a "ledger" of delegated +/// governance tokens for a specific validator. +/// +/// This means that when tokens are re-delegated +/// i.e. moved from validator A to validator B, +/// we have 2 entries for the same action (but with +/// inverted signs), as the tokens are leaving A +/// and going into B. +/// +public record KujiraValidatorDelegationLedgerEntity : ICosmosValidatorDelegationLedgerEntity +{ + public long Id { get; init; } + + public DateTimeOffset At { get; init; } + + /// + /// The TxHash of the transaction that this delegation + /// came from + /// + [Index] + public string TxHash { get; init; } = string.Empty; + + [Index] + public string ValidatorAddress { get; init; } = string.Empty; + + [Index] + public string DelegatorAddress { get; init; } = string.Empty; + + /// + /// Positive = delegation to validator + /// Negative = delegation away from validator + /// + public long Amount { get; init; } + + public string Denominator { get; init; } = string.Empty; +} diff --git a/src/Infrastructure/Kujira/KujiraMarker.cs b/src/Infrastructure/Kujira/KujiraMarker.cs new file mode 100644 index 0000000..472c601 --- /dev/null +++ b/src/Infrastructure/Kujira/KujiraMarker.cs @@ -0,0 +1,5 @@ +using SapientFi.Infrastructure.Cosmos; + +namespace SapientFi.Infrastructure.Kujira; + +public record KujiraMarker : CosmosMarker; diff --git a/src/Infrastructure/Kujira/KujiraServiceCollectionExtensions.cs b/src/Infrastructure/Kujira/KujiraServiceCollectionExtensions.cs new file mode 100644 index 0000000..5d3571f --- /dev/null +++ b/src/Infrastructure/Kujira/KujiraServiceCollectionExtensions.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using SapientFi.Infrastructure.Cosmos; +using SapientFi.Infrastructure.Kujira.Indexers.Delegations; +using SapientFi.Infrastructure.Kujira.Storage; +using SapientFi.Infrastructure.Kujira.TransactionListener; +using TerraDotnet; + +namespace SapientFi.Infrastructure.Kujira; + +public static class KujiraServiceCollectionExtensions +{ + public static IServiceCollection AddKujiraStack(this IServiceCollection services, IConfiguration rawConfig) + { + var config = new KujiraTransactionListenerConfig(rawConfig); + + services.AddSingleton(config); + services.AddTransient(); + services.AddKujiraDelegationsIndexer(); + services.AddTransient(); + services.AddTransient>(); + services.AddTransient(); + services.AddCosmosDotnet(config.LcdUri()); + + if (config.DoEnable()) + { + services.AddHostedService(); + } + + return services; + } +} \ No newline at end of file diff --git a/src/Infrastructure/Kujira/Storage/KujiraRawRepository.cs b/src/Infrastructure/Kujira/Storage/KujiraRawRepository.cs new file mode 100644 index 0000000..ab40add --- /dev/null +++ b/src/Infrastructure/Kujira/Storage/KujiraRawRepository.cs @@ -0,0 +1,11 @@ +using SapientFi.Infrastructure.Cosmos.Storage; +using ServiceStack.Data; + +namespace SapientFi.Infrastructure.Kujira.Storage; + +public class KujiraRawRepository : CosmosRawRepository +{ + public KujiraRawRepository(IDbConnectionFactory dbFactory) : base(dbFactory) + { + } +} diff --git a/src/Infrastructure/Kujira/Storage/KujiraRawTransactionEntity.cs b/src/Infrastructure/Kujira/Storage/KujiraRawTransactionEntity.cs new file mode 100644 index 0000000..6b0d1ee --- /dev/null +++ b/src/Infrastructure/Kujira/Storage/KujiraRawTransactionEntity.cs @@ -0,0 +1,22 @@ +using System; +using SapientFi.Infrastructure.Cosmos.Storage; +using ServiceStack.DataAnnotations; + +namespace SapientFi.Infrastructure.Kujira.Storage; + +public class KujiraRawTransactionEntity : ICosmosRawTransactionEntity +{ + [PrimaryKey] + public long Id { get; init; } + + public DateTimeOffset CreatedAt { get; init; } + + [Unique] + public string TxHash { get; init; } = string.Empty; + + [PgSqlJsonB] + public string RawTx { get; init; } = string.Empty; + + [Index] + public int Height { get; init; } +} diff --git a/src/Infrastructure/Kujira/TransactionListener/KujiraTransactionEnumerator.cs b/src/Infrastructure/Kujira/TransactionListener/KujiraTransactionEnumerator.cs new file mode 100644 index 0000000..7cba4d4 --- /dev/null +++ b/src/Infrastructure/Kujira/TransactionListener/KujiraTransactionEnumerator.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Logging; +using TerraDotnet; +using TerraDotnet.TerraLcd; + +namespace SapientFi.Infrastructure.Kujira.TransactionListener; + +public class KujiraTransactionEnumerator : CosmosTransactionEnumerator +{ + public KujiraTransactionEnumerator( + ILogger> logger, + ICosmosLcdApiClient cosmosClient + ) : base(logger, + cosmosClient, + new() + { + SecondsPerBlock = 6, + WindowBlockWidth = 2000, + PaginationLimit = 200 + } + ) + { + } +} diff --git a/src/Infrastructure/Kujira/TransactionListener/KujiraTransactionListenerConfig.cs b/src/Infrastructure/Kujira/TransactionListener/KujiraTransactionListenerConfig.cs new file mode 100644 index 0000000..d824eb1 --- /dev/null +++ b/src/Infrastructure/Kujira/TransactionListener/KujiraTransactionListenerConfig.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Configuration; +using RapidCore.Configuration; +using SapientFi.Infrastructure.Cosmos.TransactionListener; + +namespace SapientFi.Infrastructure.Kujira.TransactionListener; + +public class KujiraTransactionListenerConfig : CosmosTransactionListenerConfig +{ + public KujiraTransactionListenerConfig(IConfiguration raw) : base(raw) + { + } + + public override bool DoEnable() => Raw.Get("KUJIRA__TRANSACTION_LISTENER__DO_ENABLE", true); + + public override string LcdUri() => + Raw.Get("KUJIRA__TRANSACTION_LISTENER__LCD_URI", "https://lcd.kaiyo.kujira.setten.io"); +} diff --git a/src/Infrastructure/Kujira/TransactionListener/KujiraTransactionListenerHostedService.cs b/src/Infrastructure/Kujira/TransactionListener/KujiraTransactionListenerHostedService.cs new file mode 100644 index 0000000..33fdd31 --- /dev/null +++ b/src/Infrastructure/Kujira/TransactionListener/KujiraTransactionListenerHostedService.cs @@ -0,0 +1,28 @@ +using MassTransit; +using Microsoft.Extensions.Logging; +using SapientFi.Infrastructure.Cosmos; +using SapientFi.Infrastructure.Cosmos.TransactionListener; +using SapientFi.Infrastructure.Kujira.BusMessages; +using SapientFi.Infrastructure.Kujira.Storage; +using TerraDotnet; + +namespace SapientFi.Infrastructure.Kujira.TransactionListener; + +public class KujiraTransactionListenerHostedService + : CosmosTransactionListenerHostedService< + KujiraMarker, + KujiraRawTransactionEntity, + RawKujiraTransactionAvailableAnnouncement> +{ + public KujiraTransactionListenerHostedService( + ILogger logger, + KujiraTransactionEnumerator transactionEnumerator, + KujiraRawRepository rawRepository, + CosmosFactory factory, + IBus massTransitBus + ) : base(logger, transactionEnumerator, rawRepository, factory, massTransitBus) + { + } + + protected override string NameOfBlockChain => "Kujira"; +} diff --git a/src/Infrastructure/Oracles/ExchangeRates/Terra/LowLevel/ITerraMoneyExchangeRateApiClient.cs b/src/Infrastructure/Oracles/ExchangeRates/Terra/LowLevel/ITerraMoneyExchangeRateApiClient.cs index 7f6da48..52be008 100644 --- a/src/Infrastructure/Oracles/ExchangeRates/Terra/LowLevel/ITerraMoneyExchangeRateApiClient.cs +++ b/src/Infrastructure/Oracles/ExchangeRates/Terra/LowLevel/ITerraMoneyExchangeRateApiClient.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Threading.Tasks; using Refit; using TerraDotnet; diff --git a/src/Infrastructure/Oracles/ExchangeRates/Terra/TerraExchangeRateOracle.cs b/src/Infrastructure/Oracles/ExchangeRates/Terra/TerraExchangeRateOracle.cs index b6c5764..1bf3b86 100644 --- a/src/Infrastructure/Oracles/ExchangeRates/Terra/TerraExchangeRateOracle.cs +++ b/src/Infrastructure/Oracles/ExchangeRates/Terra/TerraExchangeRateOracle.cs @@ -1,3 +1,6 @@ +using System; +using System.Linq; +using System.Threading.Tasks; using SapientFi.Infrastructure.Oracles.ExchangeRates.Terra.LowLevel; using ServiceStack; using ServiceStack.Data; diff --git a/src/Infrastructure/Terra2/BusMessages/RawTerra2TransactionAvailableAnnouncement.cs b/src/Infrastructure/Terra2/BusMessages/RawTerra2TransactionAvailableAnnouncement.cs new file mode 100644 index 0000000..ac60f62 --- /dev/null +++ b/src/Infrastructure/Terra2/BusMessages/RawTerra2TransactionAvailableAnnouncement.cs @@ -0,0 +1,11 @@ +using System; +using SapientFi.Infrastructure.Cosmos.BusMessages; + +namespace SapientFi.Infrastructure.Terra2.BusMessages; + +public class RawTerra2TransactionAvailableAnnouncement : IRawCosmosTransactionAvailableAnnouncement +{ + public string TransactionHash { get; init; } = string.Empty; + public long RawEntityId { get; init; } + public DateTimeOffset CreatedAt { get; init; } +} \ No newline at end of file diff --git a/src/Infrastructure/Terra2/Indexers/Delegations/Storage/Terra2DelegationsRepository.cs b/src/Infrastructure/Terra2/Indexers/Delegations/Storage/Terra2DelegationsRepository.cs new file mode 100644 index 0000000..0a87358 --- /dev/null +++ b/src/Infrastructure/Terra2/Indexers/Delegations/Storage/Terra2DelegationsRepository.cs @@ -0,0 +1,11 @@ +using SapientFi.Infrastructure.Cosmos.Indexers.Delegations.Storage; +using ServiceStack.Data; + +namespace SapientFi.Infrastructure.Terra2.Indexers.Delegations.Storage; + +public class Terra2DelegationsRepository : CosmosDelegationsRepository +{ + public Terra2DelegationsRepository(IDbConnectionFactory dbFactory) : base(dbFactory) + { + } +} \ No newline at end of file diff --git a/src/Infrastructure/Terra2/Indexers/Delegations/Storage/Terra2ValidatorDelegationLedgerEntity.cs b/src/Infrastructure/Terra2/Indexers/Delegations/Storage/Terra2ValidatorDelegationLedgerEntity.cs new file mode 100644 index 0000000..5a9a099 --- /dev/null +++ b/src/Infrastructure/Terra2/Indexers/Delegations/Storage/Terra2ValidatorDelegationLedgerEntity.cs @@ -0,0 +1,43 @@ +using System; +using SapientFi.Infrastructure.Cosmos.Indexers.Delegations.Storage; +using ServiceStack.DataAnnotations; + +namespace SapientFi.Infrastructure.Terra2.Indexers.Delegations.Storage; + +/// +/// Represents an entry in a "ledger" of delegated +/// governance tokens for a specific validator. +/// +/// This means that when tokens are re-delegated +/// i.e. moved from validator A to validator B, +/// we have 2 entries for the same action (but with +/// inverted signs), as the tokens are leaving A +/// and going into B. +/// +public record Terra2ValidatorDelegationLedgerEntity : ICosmosValidatorDelegationLedgerEntity +{ + public long Id { get; init; } + + public DateTimeOffset At { get; init; } + + /// + /// The TxHash of the transaction that this delegation + /// came from + /// + [Index] + public string TxHash { get; init; } = string.Empty; + + [Index] + public string ValidatorAddress { get; init; } = string.Empty; + + [Index] + public string DelegatorAddress { get; init; } = string.Empty; + + /// + /// Positive = delegation to validator + /// Negative = delegation away from validator + /// + public long Amount { get; init; } + + public string Denominator { get; init; } = string.Empty; +} \ No newline at end of file diff --git a/src/Infrastructure/Terra2/Indexers/Delegations/Terra2DelegationIndexer.cs b/src/Infrastructure/Terra2/Indexers/Delegations/Terra2DelegationIndexer.cs new file mode 100644 index 0000000..74ae28b --- /dev/null +++ b/src/Infrastructure/Terra2/Indexers/Delegations/Terra2DelegationIndexer.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Logging; +using SapientFi.Infrastructure.Cosmos.Indexers.Delegations; +using SapientFi.Infrastructure.Indexing; +using SapientFi.Infrastructure.Terra2.BusMessages; +using SapientFi.Infrastructure.Terra2.Indexers.Delegations.Storage; +using SapientFi.Infrastructure.Terra2.Storage; +using SapientFi.Kernel.IdGeneration; + +namespace SapientFi.Infrastructure.Terra2.Indexers.Delegations; + +public class Terra2DelegationIndexer + : CosmosDelegationIndexer< + RawTerra2TransactionAvailableAnnouncement, + Terra2ValidatorDelegationLedgerEntity, + Terra2RawTransactionEntity>, + IIndexer +{ + public Terra2DelegationIndexer( + ILogger logger, + Terra2DelegationsRepository repository, + Terra2RawRepository rawRepository, + IdProvider idProvider + ) : base(logger, repository, rawRepository, idProvider) + { + } + public override string NameOfBlockChain => "Terra2"; + public override string Id => "terra2_delegations"; + public override string DisplayName => "Terra2 Delegations"; +} \ No newline at end of file diff --git a/src/Infrastructure/Terra2/Indexers/Delegations/Terra2DelegationsServiceCollectionExtensions.cs b/src/Infrastructure/Terra2/Indexers/Delegations/Terra2DelegationsServiceCollectionExtensions.cs new file mode 100644 index 0000000..15a15ea --- /dev/null +++ b/src/Infrastructure/Terra2/Indexers/Delegations/Terra2DelegationsServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using SapientFi.Infrastructure.Terra2.Indexers.Delegations.Storage; + +namespace SapientFi.Infrastructure.Terra2.Indexers.Delegations; + +public static class Terra2DelegationsServiceCollectionExtensions +{ + public static IServiceCollection AddTerra2DelegationsIndexer(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + + return services; + } +} \ No newline at end of file diff --git a/src/Infrastructure/Terra2/Storage/Terra2RawRepository.cs b/src/Infrastructure/Terra2/Storage/Terra2RawRepository.cs new file mode 100644 index 0000000..2cdcae1 --- /dev/null +++ b/src/Infrastructure/Terra2/Storage/Terra2RawRepository.cs @@ -0,0 +1,11 @@ +using SapientFi.Infrastructure.Cosmos.Storage; +using ServiceStack.Data; + +namespace SapientFi.Infrastructure.Terra2.Storage; + +public class Terra2RawRepository : CosmosRawRepository +{ + public Terra2RawRepository(IDbConnectionFactory dbFactory) : base(dbFactory) + { + } +} \ No newline at end of file diff --git a/src/Infrastructure/Terra2/Storage/Terra2RawTransactionEntity.cs b/src/Infrastructure/Terra2/Storage/Terra2RawTransactionEntity.cs new file mode 100644 index 0000000..a498051 --- /dev/null +++ b/src/Infrastructure/Terra2/Storage/Terra2RawTransactionEntity.cs @@ -0,0 +1,22 @@ +using System; +using SapientFi.Infrastructure.Cosmos.Storage; +using ServiceStack.DataAnnotations; + +namespace SapientFi.Infrastructure.Terra2.Storage; + +public class Terra2RawTransactionEntity : ICosmosRawTransactionEntity +{ + [PrimaryKey] + public long Id { get; init; } + + public DateTimeOffset CreatedAt { get; init; } + + [Unique] + public string TxHash { get; init; } = string.Empty; + + [PgSqlJsonB] + public string RawTx { get; init; } = string.Empty; + + [Index] + public int Height { get; init; } +} \ No newline at end of file diff --git a/src/Infrastructure/Terra2/Terra2Marker.cs b/src/Infrastructure/Terra2/Terra2Marker.cs new file mode 100644 index 0000000..a2f0497 --- /dev/null +++ b/src/Infrastructure/Terra2/Terra2Marker.cs @@ -0,0 +1,5 @@ +using SapientFi.Infrastructure.Cosmos; + +namespace SapientFi.Infrastructure.Terra2; + +public record Terra2Marker : CosmosMarker; \ No newline at end of file diff --git a/src/Infrastructure/Terra2/Terra2ServiceCollectionExtensions.cs b/src/Infrastructure/Terra2/Terra2ServiceCollectionExtensions.cs new file mode 100644 index 0000000..64ff643 --- /dev/null +++ b/src/Infrastructure/Terra2/Terra2ServiceCollectionExtensions.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using SapientFi.Infrastructure.Cosmos; +using SapientFi.Infrastructure.Terra2.Indexers.Delegations; +using SapientFi.Infrastructure.Terra2.Storage; +using SapientFi.Infrastructure.Terra2.TransactionListener; +using TerraDotnet; + +namespace SapientFi.Infrastructure.Terra2; + +public static class Terra2ServiceCollectionExtensions +{ + public static IServiceCollection AddTerra2Stack(this IServiceCollection services, IConfiguration rawConfig) + { + var config = new Terra2TransactionListenerConfig(rawConfig); + + services.AddSingleton(config); + services.AddTransient(); + services.AddTerra2DelegationsIndexer(); + services.AddTransient(); + services.AddTransient>(); + services.AddTransient(); + services.AddCosmosDotnet(config.LcdUri()); + + if (config.DoEnable()) + { + services.AddHostedService(); + } + + return services; + } +} \ No newline at end of file diff --git a/src/Infrastructure/Terra2/TransactionListener/Terra2TransactionEnumerator.cs b/src/Infrastructure/Terra2/TransactionListener/Terra2TransactionEnumerator.cs new file mode 100644 index 0000000..ca1d3b5 --- /dev/null +++ b/src/Infrastructure/Terra2/TransactionListener/Terra2TransactionEnumerator.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Logging; +using TerraDotnet; +using TerraDotnet.TerraLcd; + +namespace SapientFi.Infrastructure.Terra2.TransactionListener; + +public class Terra2TransactionEnumerator : CosmosTransactionEnumerator +{ + public Terra2TransactionEnumerator( + ILogger> logger, + ICosmosLcdApiClient cosmosClient + ) : base(logger, + cosmosClient, + new() + { + SecondsPerBlock = 6, + WindowBlockWidth = 1, + PaginationLimit = 200 + } + ) + { + } +} diff --git a/src/Infrastructure/Terra2/TransactionListener/Terra2TransactionListenerConfig.cs b/src/Infrastructure/Terra2/TransactionListener/Terra2TransactionListenerConfig.cs new file mode 100644 index 0000000..bc595dc --- /dev/null +++ b/src/Infrastructure/Terra2/TransactionListener/Terra2TransactionListenerConfig.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Configuration; +using RapidCore.Configuration; +using SapientFi.Infrastructure.Cosmos.TransactionListener; + +namespace SapientFi.Infrastructure.Terra2.TransactionListener; + +public class Terra2TransactionListenerConfig : CosmosTransactionListenerConfig +{ + public Terra2TransactionListenerConfig(IConfiguration raw) : base(raw) + { + } + + public override bool DoEnable() => Raw.Get("TERRA2__TRANSACTION_LISTENER__DO_ENABLE", true); + + // Other options are: + // "https://phoenix-lcd.sapient.fi" + // "https://phoenix-lcd.terra.dev/" + public override string LcdUri() => + Raw.Get("TERRA2__TRANSACTION_LISTENER__LCD_URI", "http://54.36.112.157:1317"); +} \ No newline at end of file diff --git a/src/Infrastructure/Terra2/TransactionListener/Terra2TransactionListenerHostedService.cs b/src/Infrastructure/Terra2/TransactionListener/Terra2TransactionListenerHostedService.cs new file mode 100644 index 0000000..bdd6b84 --- /dev/null +++ b/src/Infrastructure/Terra2/TransactionListener/Terra2TransactionListenerHostedService.cs @@ -0,0 +1,28 @@ +using MassTransit; +using Microsoft.Extensions.Logging; +using SapientFi.Infrastructure.Cosmos; +using SapientFi.Infrastructure.Cosmos.TransactionListener; +using SapientFi.Infrastructure.Terra2.BusMessages; +using SapientFi.Infrastructure.Terra2.Storage; +using TerraDotnet; + +namespace SapientFi.Infrastructure.Terra2.TransactionListener; + +public class Terra2TransactionListenerHostedService + : CosmosTransactionListenerHostedService< + Terra2Marker, + Terra2RawTransactionEntity, + RawTerra2TransactionAvailableAnnouncement> +{ + public Terra2TransactionListenerHostedService( + ILogger logger, + Terra2TransactionEnumerator transactionEnumerator, + Terra2RawRepository rawRepository, + CosmosFactory factory, + IBus massTransitBus + ) : base(logger, transactionEnumerator, rawRepository, factory, massTransitBus) + { + } + + protected override string NameOfBlockChain => "Terra2"; +} \ No newline at end of file diff --git a/src/Kernel/Config/ICorsConfig.cs b/src/Kernel/Config/ICorsConfig.cs index ba374b0..227486e 100644 --- a/src/Kernel/Config/ICorsConfig.cs +++ b/src/Kernel/Config/ICorsConfig.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace SapientFi.Kernel.Config; public interface ICorsConfig diff --git a/src/Kernel/Config/IDbConfig.cs b/src/Kernel/Config/IDbConfig.cs index 86795f4..025a02f 100644 --- a/src/Kernel/Config/IDbConfig.cs +++ b/src/Kernel/Config/IDbConfig.cs @@ -4,5 +4,5 @@ public interface IDbConfig { public string ConnectionString { get; } - public bool DisableMigrationsDuringBoot { get; } + public bool RunMigrationsDuringBoot { get; } } \ No newline at end of file diff --git a/src/Kernel/Config/IEnabledServiceRolesConfig.cs b/src/Kernel/Config/IEnabledServiceRolesConfig.cs index 7608637..ef8f397 100644 --- a/src/Kernel/Config/IEnabledServiceRolesConfig.cs +++ b/src/Kernel/Config/IEnabledServiceRolesConfig.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace SapientFi.Kernel.Config; public interface IEnabledServiceRolesConfig diff --git a/src/Kernel/Contracts/Exchanges/GetExchangeRate.cs b/src/Kernel/Contracts/Exchanges/GetExchangeRate.cs index 15a4d60..b9af9ef 100644 --- a/src/Kernel/Contracts/Exchanges/GetExchangeRate.cs +++ b/src/Kernel/Contracts/Exchanges/GetExchangeRate.cs @@ -1,3 +1,5 @@ +using System; + namespace SapientFi.Kernel.Contracts.Exchanges; public class GetExchangeRate diff --git a/src/Kernel/Contracts/Exchanges/GetExchangeRateResult.cs b/src/Kernel/Contracts/Exchanges/GetExchangeRateResult.cs index bd3d186..e8a40ca 100644 --- a/src/Kernel/Contracts/Exchanges/GetExchangeRateResult.cs +++ b/src/Kernel/Contracts/Exchanges/GetExchangeRateResult.cs @@ -1,3 +1,5 @@ +using System; + namespace SapientFi.Kernel.Contracts.Exchanges; public class GetExchangeRateResult diff --git a/src/Kernel/Contracts/Exchanges/GetLatestExchangeRateResult.cs b/src/Kernel/Contracts/Exchanges/GetLatestExchangeRateResult.cs index da91647..447e972 100644 --- a/src/Kernel/Contracts/Exchanges/GetLatestExchangeRateResult.cs +++ b/src/Kernel/Contracts/Exchanges/GetLatestExchangeRateResult.cs @@ -1,3 +1,5 @@ +using System; + namespace SapientFi.Kernel.Contracts.Exchanges; public record GetLatestExchangeRateResult diff --git a/src/Kernel/DAL/Entities/Exchanges/ExchangeMarketCandle.cs b/src/Kernel/DAL/Entities/Exchanges/ExchangeMarketCandle.cs index f0ccd07..d6c09b9 100644 --- a/src/Kernel/DAL/Entities/Exchanges/ExchangeMarketCandle.cs +++ b/src/Kernel/DAL/Entities/Exchanges/ExchangeMarketCandle.cs @@ -1,3 +1,6 @@ +//TODO do we still need this? + +using System; using ServiceStack.DataAnnotations; namespace SapientFi.Kernel.DAL.Entities.Exchanges; diff --git a/src/Kernel/DAL/Entities/Terra/TerraRawTransactionEntity.cs b/src/Kernel/DAL/Entities/Terra/TerraRawTransactionEntity.cs deleted file mode 100644 index 31cb08d..0000000 --- a/src/Kernel/DAL/Entities/Terra/TerraRawTransactionEntity.cs +++ /dev/null @@ -1,17 +0,0 @@ -using ServiceStack.DataAnnotations; - -namespace SapientFi.Kernel.DAL.Entities.Terra; - -public class TerraRawTransactionEntity -{ - [PrimaryKey] - public long Id { get; set; } - - public DateTimeOffset CreatedAt { get; set; } - - [Unique] - public string TxHash { get; set; } = string.Empty; - - [PgSqlJsonB] - public string RawTx { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/Kernel/DAL/Entities/Terra/TerraRewardEntity.cs b/src/Kernel/DAL/Entities/Terra/TerraRewardEntity.cs deleted file mode 100644 index 992d335..0000000 --- a/src/Kernel/DAL/Entities/Terra/TerraRewardEntity.cs +++ /dev/null @@ -1,50 +0,0 @@ -using ServiceStack.DataAnnotations; - -namespace SapientFi.Kernel.DAL.Entities.Terra -{ - public class TerraRewardEntity - { - [PrimaryKey] - public long Id { get; set; } - - [ForeignKey(typeof(TerraRawTransactionEntity))] - [Required] - public long TransactionId { get; set; } - - [Index] - public string Wallet { get; set; } = string.Empty; - - [Index] - public string FromContract { get; set; } = string.Empty; - - public decimal Amount { get; set; } - - public decimal? AmountUstAtClaim { get; set; } - - public decimal? AmountUstNow { get; set; } - public string Denominator { get; set; } = string.Empty; - - public TerraRewardType RewardType { get; set; } - public DateTimeOffset CreatedAt { get; set; } - - public DateTimeOffset UpdatedAt { get; set; } - } - - public enum TerraRewardType - { - /// - /// A token airdrop - /// - Airdrop, - - /// - /// Native token rewards for staking - /// - StakingReward, - - /// - /// LP farming reward - /// - LpFarm - } -} \ No newline at end of file diff --git a/src/Kernel/Exchange.cs b/src/Kernel/Exchange.cs index 499103e..a753b56 100644 --- a/src/Kernel/Exchange.cs +++ b/src/Kernel/Exchange.cs @@ -1,3 +1,4 @@ +//TODO do we still need this? namespace SapientFi.Kernel; public enum Exchange diff --git a/src/Kernel/Kernel.csproj b/src/Kernel/Kernel.csproj index c6e7d06..d52fcdf 100644 --- a/src/Kernel/Kernel.csproj +++ b/src/Kernel/Kernel.csproj @@ -2,8 +2,6 @@ net6.0 - enable - enable SapientFi.Kernel SapientFi.Kernel @@ -21,6 +19,14 @@ - + + + + + + + + + diff --git a/src/Kernel/Serialization/Jason.cs b/src/Kernel/Serialization/Jason.cs new file mode 100644 index 0000000..3ede6bc --- /dev/null +++ b/src/Kernel/Serialization/Jason.cs @@ -0,0 +1,31 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SapientFi.Kernel.Serialization; + +/// +/// A JSON (de)serializer, which is named like this to avoid +/// clashing with all of the other JsonSerializer classes in the world. +/// +public class Jason +{ + private readonly JsonSerializerOptions _options = new JsonSerializerOptions + { + NumberHandling = JsonNumberHandling.AllowReadingFromString, + IncludeFields = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + ReadCommentHandling = JsonCommentHandling.Skip, + DefaultIgnoreCondition = JsonIgnoreCondition.Never + }; + + public virtual string ToJson(object thing) + { + return JsonSerializer.Serialize(thing, _options); + } + + public virtual T? FromJson(string encoded) + { + return JsonSerializer.Deserialize(encoded, _options); + } +} \ No newline at end of file diff --git a/src/Kernel/Serialization/JsonSerializerOptionsExtensions.cs b/src/Kernel/Serialization/JsonSerializerOptionsExtensions.cs new file mode 100644 index 0000000..e8bc654 --- /dev/null +++ b/src/Kernel/Serialization/JsonSerializerOptionsExtensions.cs @@ -0,0 +1,13 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SapientFi.Kernel.Serialization; + +public static class JsonSerializerOptionsExtensions +{ + public static JsonSerializerOptions AddConverter(this JsonSerializerOptions options, JsonConverter converter) + { + options.Converters.Add(converter); + return options; + } +} \ No newline at end of file diff --git a/src/Kernel/Serialization/SerializationServiceCollectionExtensions.cs b/src/Kernel/Serialization/SerializationServiceCollectionExtensions.cs new file mode 100644 index 0000000..9be39b1 --- /dev/null +++ b/src/Kernel/Serialization/SerializationServiceCollectionExtensions.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace SapientFi.Kernel.Serialization; + +public static class SerializationServiceCollectionExtensions +{ + public static IServiceCollection AddSerialization(this IServiceCollection services) + { + services.AddTransient(); + + return services; + } +} \ No newline at end of file diff --git a/src/ServiceHost/CacheRefresherServiceWorker.cs b/src/ServiceHost/CacheRefresherServiceWorker.cs index 2d0b3a2..6179833 100644 --- a/src/ServiceHost/CacheRefresherServiceWorker.cs +++ b/src/ServiceHost/CacheRefresherServiceWorker.cs @@ -1,4 +1,9 @@ +using System; using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using NewRelic.Api.Agent; using SapientFi.Infrastructure.Hosting.BackgroundWorkers; using SapientFi.Kernel.Config; diff --git a/src/ServiceHost/Config/CosmosIndexerConfig.cs b/src/ServiceHost/Config/CosmosIndexerConfig.cs index d9b05b2..155bc47 100644 --- a/src/ServiceHost/Config/CosmosIndexerConfig.cs +++ b/src/ServiceHost/Config/CosmosIndexerConfig.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; using RapidCore.Configuration; using SapientFi.Kernel.Config; @@ -28,11 +30,12 @@ bool IEnabledServiceRolesConfig.IsRoleEnabled(string role) "User ID=sapientfi_indexer_user;Password=sapientfi_indexer_user_pass;Host=localhost;Port=35432;Database=sapientfi_indexer;Pooling=true;Minimum Pool Size=10;Maximum Pool Size=100;" ); - bool IDbConfig.DisableMigrationsDuringBoot => _config.Get( - "SAPIENTFI_DB_DISABLE_MIGRATIONS_ON_BOOT", - false + bool IDbConfig.RunMigrationsDuringBoot => _config.Get( + "SAPIENTFI_DB_RUN_MIGRATIONS_ON_BOOT", + true ); + //TODO: Do we still need this? int IGatewayPoolsConfig.NumberOfElementsInDepositsPrWallet => _config.Get( "SAPIENTFI_NR_ELEMENTS_DEPOSITS_PR_WALLET", 11 diff --git a/src/ServiceHost/ConfigureServices.cs b/src/ServiceHost/ConfigureServices.cs new file mode 100644 index 0000000..d693f49 --- /dev/null +++ b/src/ServiceHost/ConfigureServices.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using SapientFi.Infrastructure.Kujira; +using SapientFi.Infrastructure.Terra2; +using SapientFi.Kernel.Config; +using SapientFi.ServiceHost.Config; +using SapientFi.ServiceHost.Endpoints; +using SapientFi.ServiceHost.ServiceCollectionExtensions; + +namespace SapientFi.ServiceHost; + +public static class ConfigureServices +{ + public static void DoIt(WebApplicationBuilder builder) + { + DoIt(builder.Services, builder.Configuration); + } + + public static void DoIt(IServiceCollection services, IConfiguration configuration) + { + services.AddControllers(); + services.AddHealthChecks(); + services.AddSignalR(); + + services.AddCors(options => + { + var config = new CosmosIndexerConfig(configuration) as ICorsConfig; + + options.AddPolicy(name: "cosmos-indexer-cors", + corsPolicyBuilder => + { + corsPolicyBuilder + .WithOrigins(config.AllowedOrigins.ToArray()) + .AllowCredentials() + .WithMethods(new []{"GET", "POST", "PUT", "HEAD", "CONNECT", "OPTIONS", "TRACE"}) + .WithHeaders(new [] + { + "accept", "Access-Control-Request-Method", "Access-Control-Request-Headers", "origin", "User-agent", + "Referer", "Accept-Encoding", "Accept-Language", "connection", "host", "content-type", + "GraphQL-Tracing", "X-Apollo-Tracing", "newrelic", "traceparent", "tracestate" + }); + }); + }); + services.AddTerraMoney(configuration); + services.AddMessageBus(new CosmosIndexerConfig(configuration), configuration); + services.AddDbStack(configuration); + services.AddBackgroundJobStack(configuration); + services.AddEndpointServices(); + services.AddKujiraStack(configuration); + services.AddTerra2Stack(configuration); + + services.AddGraphQLServer() + .AddQueryType() + .AddDiagnosticEventListener() + .AddApolloTracing(); // https://chillicream.com/docs/hotchocolate/server/instrumentation#on-demand + } +} \ No newline at end of file diff --git a/src/ServiceHost/CronjobManager.cs b/src/ServiceHost/CronjobManager.cs index 5217e73..66c311a 100644 --- a/src/ServiceHost/CronjobManager.cs +++ b/src/ServiceHost/CronjobManager.cs @@ -1,5 +1,7 @@ +using System.Threading; using Hangfire; using Hangfire.Common; +using Microsoft.Extensions.Logging; using SapientFi.Kernel.Config; using SapientFi.ServiceHost.RecurringJobs; @@ -48,12 +50,7 @@ public virtual void RegisterJobsIfRequired() Job.FromExpression(job => job.DoWorkAsync(CancellationToken.None)), "13 * * * *" ); - - _jobManager.AddOrUpdate("fx-rate-download", - Job.FromExpression(job => job.DoWorkAsync(CancellationToken.None)), - "43 * * * *" - ); - + if (_featureConfig.TriggerGatewayPoolFullResync || _featureConfig.TriggerMineStakingFullResync || _featureConfig.TriggerMineBuyBackFullResync diff --git a/src/ServiceHost/Endpoints/FxRates/FxRateGraph.cs b/src/ServiceHost/Endpoints/FxRates/FxRateGraph.cs index e8f5a9f..6742121 100644 --- a/src/ServiceHost/Endpoints/FxRates/FxRateGraph.cs +++ b/src/ServiceHost/Endpoints/FxRates/FxRateGraph.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace SapientFi.ServiceHost.Endpoints.FxRates; public record FxRateGraph diff --git a/src/ServiceHost/Endpoints/FxRates/FxRatesService.cs b/src/ServiceHost/Endpoints/FxRates/FxRatesService.cs index 629ac8e..14e086e 100644 --- a/src/ServiceHost/Endpoints/FxRates/FxRatesService.cs +++ b/src/ServiceHost/Endpoints/FxRates/FxRatesService.cs @@ -1,4 +1,8 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using MassTransit; +using Microsoft.Extensions.Logging; using SapientFi.Kernel.Contracts.Exchanges; namespace SapientFi.ServiceHost.Endpoints.FxRates; diff --git a/src/ServiceHost/Endpoints/NewRelicReportingDiagnosticEventListener.cs b/src/ServiceHost/Endpoints/NewRelicReportingDiagnosticEventListener.cs index 5322247..19b4413 100644 --- a/src/ServiceHost/Endpoints/NewRelicReportingDiagnosticEventListener.cs +++ b/src/ServiceHost/Endpoints/NewRelicReportingDiagnosticEventListener.cs @@ -1,7 +1,10 @@ +using System; using System.Text; +using HotChocolate; using HotChocolate.Execution; using HotChocolate.Execution.Instrumentation; using HotChocolate.Resolvers; +using Microsoft.Extensions.Logging; namespace SapientFi.ServiceHost.Endpoints; diff --git a/src/ServiceHost/Endpoints/Query.cs b/src/ServiceHost/Endpoints/Query.cs index 37c1868..0e95034 100644 --- a/src/ServiceHost/Endpoints/Query.cs +++ b/src/ServiceHost/Endpoints/Query.cs @@ -1,3 +1,6 @@ +using System.Threading; +using System.Threading.Tasks; +using HotChocolate; using NewRelic.Api.Agent; using SapientFi.ServiceHost.Endpoints.FxRates; diff --git a/src/ServiceHost/Endpoints/Staking/ValidatorStakingGraph.cs b/src/ServiceHost/Endpoints/Staking/ValidatorStakingGraph.cs new file mode 100644 index 0000000..5b6c0bf --- /dev/null +++ b/src/ServiceHost/Endpoints/Staking/ValidatorStakingGraph.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace SapientFi.ServiceHost.Endpoints.Staking; + +public class ValidatorStakingGraph +{ + public List ValidatorStakings { get; set; } = new(); +} + +public class ValidatorStakingItemGraph +{ + public List<(string, long)> ValidatorStakingList { get; set; } = new(); + public DateTime? From { get; set; } = null; + public DateTime? To { get; set; } = null; +} diff --git a/src/ServiceHost/Endpoints/Types/TimeSeriesStatEntry.cs b/src/ServiceHost/Endpoints/Types/TimeSeriesStatEntry.cs index f2deb96..1b23646 100644 --- a/src/ServiceHost/Endpoints/Types/TimeSeriesStatEntry.cs +++ b/src/ServiceHost/Endpoints/Types/TimeSeriesStatEntry.cs @@ -1,3 +1,5 @@ +using System; + namespace SapientFi.ServiceHost.Endpoints.Types; public class TimeSeriesStatEntry diff --git a/src/ServiceHost/Program.cs b/src/ServiceHost/Program.cs index e4dd3d1..8e60caf 100644 --- a/src/ServiceHost/Program.cs +++ b/src/ServiceHost/Program.cs @@ -1,9 +1,10 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using RapidCore.Migration; using SapientFi.Kernel.Config; using SapientFi.ServiceHost; -using SapientFi.ServiceHost.Config; -using SapientFi.ServiceHost.Endpoints; -using SapientFi.ServiceHost.ServiceCollectionExtensions; using Serilog; ServiceStack.Licensing.RegisterLicense("OSS MIT 2022 https://github.com/pylonboard/dotnet-pylonboard-monolith UgSDQqrNk1uD12MhR04DQogTC/hqGtwW44By+pXTmOl/YuMFf6fINN9DVFJwQU/zoZ8AkM1phq/soc95pHgfqWBMIzAQ1XTC7mUPfHcHxzGSb9am9ps46Y//YpVGQ07mXQxKhmdzYy2nP7Z8CNffS3YaFxOmOQJQO5FZ0dH4988="); @@ -22,41 +23,9 @@ Log.Logger = loggerConfiguration.CreateLogger(); -builder.Services.AddControllers(); -builder.Services.AddHealthChecks(); -builder.Services.AddSignalR(); +ConfigureServices.DoIt(builder); -builder.Services.AddCors(options => -{ - var config = new CosmosIndexerConfig(builder.Configuration) as ICorsConfig; - - options.AddPolicy(name: "cosmos-indexer-cors", - corsPolicyBuilder => - { - corsPolicyBuilder - .WithOrigins(config.AllowedOrigins.ToArray()) - .AllowCredentials() - .WithMethods(new []{"GET", "POST", "PUT", "HEAD", "CONNECT", "OPTIONS", "TRACE"}) - .WithHeaders(new [] - { - "accept", "Access-Control-Request-Method", "Access-Control-Request-Headers", "origin", "User-agent", - "Referer", "Accept-Encoding", "Accept-Language", "connection", "host", "content-type", - "GraphQL-Tracing", "X-Apollo-Tracing", "newrelic", "traceparent", "tracestate" - }); - }); -}); -builder.Services.AddTerraMoney(builder.Configuration); -builder.Services.AddMessageBus(new CosmosIndexerConfig(builder.Configuration), builder.Configuration); -builder.Services.AddDbStack(builder.Configuration); -builder.Services.AddBackgroundJobStack(builder.Configuration); -builder.Services.AddEndpointServices(); - -builder.Services.AddGraphQLServer() - .AddQueryType() - .AddDiagnosticEventListener() - .AddApolloTracing(); // https://chillicream.com/docs/hotchocolate/server/instrumentation#on-demand - var app = builder.Build(); app.UseCors("cosmos-indexer-cors"); app.UseSerilogRequestLogging(); @@ -76,14 +45,14 @@ { var config = scope.ServiceProvider.GetRequiredService(); - if (config.DisableMigrationsDuringBoot) + if (config.RunMigrationsDuringBoot) { - logger.Warning("{Setting} is false so will not apply migrations", nameof(config.DisableMigrationsDuringBoot)); + var migrator = scope.ServiceProvider.GetRequiredService(); + await migrator.UpgradeAsync(); } else { - var migrator = scope.ServiceProvider.GetRequiredService(); - await migrator.UpgradeAsync(); + logger.Warning("{Setting} is false so will not apply migrations", nameof(config.RunMigrationsDuringBoot)); } var cronJobs = app.Services.GetRequiredService(); cronJobs.RegisterJobsIfRequired(); diff --git a/src/ServiceHost/Properties/launchSettings.json b/src/ServiceHost/Properties/launchSettings.json index 51245b6..0d7e5b0 100644 --- a/src/ServiceHost/Properties/launchSettings.json +++ b/src/ServiceHost/Properties/launchSettings.json @@ -17,7 +17,8 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "SAPIENT_SERVICE_ROLES_ENABLED": "API,BACKGROUND_WORKER", - "SAPIENT_TRIGGER_MINE_STAKING_FULL_RESYNC": "false" + "KUJIRA__TRANSACTION_LISTENER__DO_ENABLE": false, + "TERRA2__TRANSACTION_LISTENER__DO_ENABLE": true } }, "IIS Express": { diff --git a/src/ServiceHost/RecurringJobs/FxRateDownloadJob.cs b/src/ServiceHost/RecurringJobs/FxRateDownloadJob.cs deleted file mode 100644 index 970ce6b..0000000 --- a/src/ServiceHost/RecurringJobs/FxRateDownloadJob.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Data; -using SapientFi.Infrastructure.Oracles.ExchangeRates.Terra; -using SapientFi.Kernel; -using SapientFi.Kernel.Config; -using SapientFi.Kernel.DAL.Entities.Exchanges; -using SapientFi.Kernel.IdGeneration; -using ServiceStack.Data; -using ServiceStack.OrmLite; -using TerraDotnet; - -namespace SapientFi.ServiceHost.RecurringJobs; - -public class FxRateDownloadJob -{ - private readonly ILogger _logger; - private readonly IEnabledServiceRolesConfig _serviceRolesConfig; - private readonly IDbConnectionFactory _dbConnectionFactory; - private readonly TerraExchangeRateOracle _terraExchangeRateOracle; - private readonly IdProvider _idProvider; - - public FxRateDownloadJob( - ILogger logger, - IEnabledServiceRolesConfig serviceRolesConfig, - IDbConnectionFactory dbConnectionFactory, - TerraExchangeRateOracle terraExchangeRateOracle, - IdProvider idProvider - ) - { - _logger = logger; - _serviceRolesConfig = serviceRolesConfig; - _dbConnectionFactory = dbConnectionFactory; - _terraExchangeRateOracle = terraExchangeRateOracle; - _idProvider = idProvider; - } - - public async Task DoWorkAsync(CancellationToken stoppingToken) - { - if (!_serviceRolesConfig.IsRoleEnabled(ServiceRoles.BACKGROUND_WORKER)) - { - _logger.LogInformation("Background worker role not active, not starting materialized view refresher"); - return; - } - - - _logger.LogInformation("FX rate downloader is active - downloading rates"); - - using var db = await _dbConnectionFactory.OpenDbConnectionAsync(token: stoppingToken); - { - await FetchAndStoreRateAsync(TerraDenominators.Anc, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Apollo, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Glow, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Loop, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Loopr, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Luna, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.nLuna, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Mine, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Mir, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.nEth, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.bEth, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Orion, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Psi, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Sayve, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Stt, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Twd, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Vkr, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Whale, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Xdefi, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Arts, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.WCoin, TerraDenominators.Ust, db, stoppingToken); - await FetchAndStoreRateAsync(TerraDenominators.Sayve, TerraDenominators.Ust, db, stoppingToken); - } - _logger.LogInformation("FX rate downloader completed"); - } - - private async Task FetchAndStoreRateAsync(string from, string to, IDbConnection db, CancellationToken stoppingToken) - { - var (close, closeAt) = - await _terraExchangeRateOracle.GetExchangeRateAsync(from, to, DateTimeOffset.UtcNow, interval: "15m"); - await db.InsertAsync(new ExchangeMarketCandle - { - CloseTime = closeAt, - OpenTime = closeAt.AddMinutes(-15), - Close = close, - Exchange = Exchange.Terra, - Market = $"{from.ToUpperInvariant()}-{to.ToUpperInvariant()}", - Id = _idProvider.Snowflake(), - }, token: stoppingToken); - } -} \ No newline at end of file diff --git a/src/ServiceHost/RecurringJobs/MaterializedViewRefresherJob.cs b/src/ServiceHost/RecurringJobs/MaterializedViewRefresherJob.cs index 3a9fe60..b815fa3 100644 --- a/src/ServiceHost/RecurringJobs/MaterializedViewRefresherJob.cs +++ b/src/ServiceHost/RecurringJobs/MaterializedViewRefresherJob.cs @@ -1,4 +1,8 @@ +using System; using System.Data; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using SapientFi.Kernel.Config; using ServiceStack.Data; using ServiceStack.OrmLite; diff --git a/src/ServiceHost/RecurringJobs/TerraMoneyRefreshJob.cs b/src/ServiceHost/RecurringJobs/TerraMoneyRefreshJob.cs index ac28b94..139557d 100644 --- a/src/ServiceHost/RecurringJobs/TerraMoneyRefreshJob.cs +++ b/src/ServiceHost/RecurringJobs/TerraMoneyRefreshJob.cs @@ -1,4 +1,8 @@ +using System; +using System.Threading; +using System.Threading.Tasks; using Medallion.Threading; +using Microsoft.Extensions.Logging; using Polly; using SapientFi.Kernel.Config; diff --git a/src/ServiceHost/ServiceCollectionExtensions/BackgroundJobServiceCollectionExtension.cs b/src/ServiceHost/ServiceCollectionExtensions/BackgroundJobServiceCollectionExtension.cs index faf94bc..1bf36bf 100644 --- a/src/ServiceHost/ServiceCollectionExtensions/BackgroundJobServiceCollectionExtension.cs +++ b/src/ServiceHost/ServiceCollectionExtensions/BackgroundJobServiceCollectionExtension.cs @@ -2,6 +2,8 @@ using Hangfire.InMemory; using Medallion.Threading; using Medallion.Threading.Postgres; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using SapientFi.Infrastructure.Oracles.ExchangeRates.Terra; using SapientFi.Kernel.Config; using SapientFi.ServiceHost.Config; @@ -32,7 +34,6 @@ public static IServiceCollection AddBackgroundJobStack( services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/ServiceHost/ServiceCollectionExtensions/DbServiceCollectionExtensions.cs b/src/ServiceHost/ServiceCollectionExtensions/DbServiceCollectionExtensions.cs index 11af565..9e53dc4 100644 --- a/src/ServiceHost/ServiceCollectionExtensions/DbServiceCollectionExtensions.cs +++ b/src/ServiceHost/ServiceCollectionExtensions/DbServiceCollectionExtensions.cs @@ -1,4 +1,8 @@ +using System; using System.Reflection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using RapidCore.DependencyInjection; using RapidCore.Migration; using RapidCore.PostgreSql.Migration; diff --git a/src/ServiceHost/ServiceCollectionExtensions/EndpointsServiceCollectionExtensions.cs b/src/ServiceHost/ServiceCollectionExtensions/EndpointsServiceCollectionExtensions.cs index e95899f..6e69794 100644 --- a/src/ServiceHost/ServiceCollectionExtensions/EndpointsServiceCollectionExtensions.cs +++ b/src/ServiceHost/ServiceCollectionExtensions/EndpointsServiceCollectionExtensions.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.DependencyInjection; using SapientFi.ServiceHost.Endpoints.FxRates; using ServiceStack.Caching; diff --git a/src/ServiceHost/ServiceCollectionExtensions/MessageBusServiceExtensions.cs b/src/ServiceHost/ServiceCollectionExtensions/MessageBusServiceExtensions.cs index fdf71d1..d95d59a 100644 --- a/src/ServiceHost/ServiceCollectionExtensions/MessageBusServiceExtensions.cs +++ b/src/ServiceHost/ServiceCollectionExtensions/MessageBusServiceExtensions.cs @@ -1,4 +1,8 @@ +using System; using MassTransit; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using SapientFi.Infrastructure; using SapientFi.Kernel.Config; using SapientFi.ServiceHost.Config; @@ -17,20 +21,21 @@ public static IServiceCollection AddMessageBus(this IServiceCollection services, // Check this out: https://masstransit-project.com/usage/containers/#consumer-definition // Add all consumers in the specified assembly + /* + // TODO this seems weird... why would the API listen to the bus? if (enabledServiceRolesConfig.IsRoleEnabled(ServiceRoles.API)) { x.AddConsumers( typeof(MessageBusServiceExtensions).Assembly ); } + //*/ if (enabledServiceRolesConfig.IsRoleEnabled(ServiceRoles.BACKGROUND_WORKER)) { - /* x.AddConsumers( - typeof(MineStakingTransactionConsumer).Assembly + typeof(InfrastructureAssemblyMarker).Assembly ); - //*/ } x.UsingRabbitMq((context, cfg) => diff --git a/src/ServiceHost/ServiceCollectionExtensions/TerraMoneyServiceCollectionExtensions.cs b/src/ServiceHost/ServiceCollectionExtensions/TerraMoneyServiceCollectionExtensions.cs index 7b181f3..69e70ee 100644 --- a/src/ServiceHost/ServiceCollectionExtensions/TerraMoneyServiceCollectionExtensions.cs +++ b/src/ServiceHost/ServiceCollectionExtensions/TerraMoneyServiceCollectionExtensions.cs @@ -1,12 +1,16 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using RapidCore.Locking; using Refit; using SapientFi.Infrastructure.Hosting.BackgroundWorkers; using SapientFi.Infrastructure.Oracles.ExchangeRates.Terra.LowLevel; +using SapientFi.Infrastructure.Terra2; +using SapientFi.Infrastructure.Terra2.TransactionListener; using SapientFi.Kernel.Config; using SapientFi.Kernel.IdGeneration; using SapientFi.ServiceHost.Config; using TerraDotnet; -using TerraDotnet.TerraFcd; namespace SapientFi.ServiceHost.ServiceCollectionExtensions; @@ -23,9 +27,6 @@ public static IServiceCollection AddTerraMoney(this IServiceCollection services, services.AddSingleton(_ => new NoopDistributedAppLockProvider()); services.AddSingleton(_ => new IdProvider(new IdGen.IdGenerator(0))); - services.AddRefitClient() - .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://fcd.terra.dev")); - services.AddRefitClient() .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.coinhall.org")); @@ -33,7 +34,7 @@ public static IServiceCollection AddTerraMoney(this IServiceCollection services, services.AddHostedService>(); - services.AddTransient(); + services.AddTransient(); return services; diff --git a/src/ServiceHost/ServiceHost.csproj b/src/ServiceHost/ServiceHost.csproj index 780063c..854a7a1 100644 --- a/src/ServiceHost/ServiceHost.csproj +++ b/src/ServiceHost/ServiceHost.csproj @@ -2,8 +2,6 @@ net6.0 - enable - enable SapientFi.ServiceHost SapientFi.ServiceHost 0.18.0 diff --git a/src/ServiceHost/appsettings.Development.json b/src/ServiceHost/appsettings.Development.json index b3a3139..23af934 100644 --- a/src/ServiceHost/appsettings.Development.json +++ b/src/ServiceHost/appsettings.Development.json @@ -37,5 +37,11 @@ "Properties": { "Application": "Pylonboard.ServiceHost" } - } + }, + "Terra2": { + "TransactionListener": { + "DoEnable": true + } + }, + "SAPIENTFI_DB_RUN_MIGRATIONS_ON_BOOT": true } diff --git a/src/TerraDotnet/TerraFcd/Messages/Coin.cs b/src/TerraDotnet/CosmosAmount.cs similarity index 78% rename from src/TerraDotnet/TerraFcd/Messages/Coin.cs rename to src/TerraDotnet/CosmosAmount.cs index 83ce202..a63b0d0 100644 --- a/src/TerraDotnet/TerraFcd/Messages/Coin.cs +++ b/src/TerraDotnet/CosmosAmount.cs @@ -1,12 +1,11 @@ using System.Text.Json.Serialization; -namespace TerraDotnet.TerraFcd.Messages; +namespace TerraDotnet; -public record Coin +public record CosmosAmount { [JsonPropertyName("denom")] public string Denominator { get; set; } = string.Empty; - [JsonPropertyName("amount")] public string Amount { get; set; } = string.Empty; -} \ No newline at end of file +} diff --git a/src/TerraDotnet/CosmosDotnetServiceCollectionExtensions.cs b/src/TerraDotnet/CosmosDotnetServiceCollectionExtensions.cs new file mode 100644 index 0000000..1b3ce73 --- /dev/null +++ b/src/TerraDotnet/CosmosDotnetServiceCollectionExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Refit; +using TerraDotnet.TerraLcd; + +namespace TerraDotnet; + +public static class TerraDotnetServiceCollectionExtensions +{ + public static IServiceCollection AddCosmosDotnet(this IServiceCollection services, string lcdClientBaseUrl) + { + services.InternalAddLcdClient(lcdClientBaseUrl); + services.AddTransient(); + + return services; + } + + + public static IServiceCollection InternalAddLcdClient(this IServiceCollection services, string lcdClientBaseUrl) + { + services.AddRefitClient>(serviceProvider => + { + var settings = new RefitSettings(); + + settings.ContentSerializer = new SystemTextJsonContentSerializer(TerraJsonSerializerOptions.GetThem()); + + return settings; + }) + .ConfigureHttpClient(client => + { + client.BaseAddress = new Uri(lcdClientBaseUrl); + }); + + return services; + } +} \ No newline at end of file diff --git a/src/TerraDotnet/CosmosMessageParser.cs b/src/TerraDotnet/CosmosMessageParser.cs new file mode 100644 index 0000000..0258639 --- /dev/null +++ b/src/TerraDotnet/CosmosMessageParser.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Text.Json; +using TerraDotnet.Extensions; +using TerraDotnet.TerraFcd.Messages; +using TerraDotnet.TerraLcd.Messages; + +namespace TerraDotnet; + +public class TerraMessageParser +{ + public static bool TryParse(Dictionary txMessage, out IMsg? message) + { + message = null; + var parseStatus = false; + + var containsTypeProperty = txMessage.TryGetValue("@type", out var typeProperty); + if (!containsTypeProperty) + { + return parseStatus; + } + + switch (typeProperty.GetString()) + { + case "/cosmos.staking.v1beta1.MsgDelegate": + message = new CosmosDelegateMessage + { + Type = txMessage["@type"].GetString()!, + DelegatorAddress = txMessage["delegator_address"].GetString()!, + ValidatorAddress = txMessage["validator_address"].GetString()!, + Amount = txMessage["amount"].ToObject() + }; + break; + + case "/cosmos.staking.v1beta1.MsgUndelegate": + message = new CosmosUndelegateMessage + { + Type = txMessage["@type"].GetString()!, + DelegatorAddress = txMessage["delegator_address"].GetString()!, + ValidatorAddress = txMessage["validator_address"].GetString()!, + Amount = txMessage["amount"].ToObject() + }; + break; + + case "/cosmos.staking.v1beta1.MsgBeginRedelegate": + message = new CosmosRedelegateMessage + { + Type = txMessage["@type"].GetString()!, + DelegatorAddress = txMessage["delegator_address"].GetString()!, + ValidatorSourceAddress = txMessage["validator_src_address"].GetString()!, + ValidatorDestinationAddress = txMessage["validator_dst_address"].GetString()!, + Amount = txMessage["amount"].ToObject() + }; + break; + + default: + // Does not contain a message type we are interested in. + break; + } + + if (message is not null) + { + parseStatus = true; + } + + return parseStatus; + } +} \ No newline at end of file diff --git a/src/TerraDotnet/CosmosTransactionEnumerator.cs b/src/TerraDotnet/CosmosTransactionEnumerator.cs new file mode 100644 index 0000000..c405b90 --- /dev/null +++ b/src/TerraDotnet/CosmosTransactionEnumerator.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using NewRelic.Api.Agent; +using Polly; +using Refit; +using TerraDotnet.TerraLcd; +using TerraDotnet.TerraLcd.Messages; + +// ReSharper disable MemberCanBePrivate.Global + +namespace TerraDotnet; + +public struct EnumeratorOptions +{ + public EnumeratorOptions(int secondsPerBlock) + { + SecondsPerBlock = secondsPerBlock; + } + + public int SecondsPerBlock { get; init; } + public int WindowBlockWidth { get; init; } = 2000; + public int PaginationLimit { get; init; } = 200; +} + +public abstract class CosmosTransactionEnumerator +{ + protected readonly ILogger> Logger; + protected readonly ICosmosLcdApiClient CosmosClient; + protected int SecondsPerBlock { get; } + protected int WindowBlockWidth { get; } + protected int PaginationLimit { get; } + + protected CosmosTransactionEnumerator( + ILogger> logger, + ICosmosLcdApiClient cosmosClient, + EnumeratorOptions options + ) + { + Logger = logger; + CosmosClient = cosmosClient; + SecondsPerBlock = options.SecondsPerBlock; + WindowBlockWidth = options.WindowBlockWidth; + PaginationLimit = options.PaginationLimit; + } + + [Trace] + public async IAsyncEnumerable EnumerateTransactionsAsync( + int fromAndIncludingBlockHeight, + [EnumeratorCancellation] CancellationToken stoppingToken + ) + { + var queryWindow = new QueryWindow + { + WindowBlockWidth = WindowBlockWidth, + StartBlockHeight = fromAndIncludingBlockHeight, + PaginationLimit = PaginationLimit, + PaginationOffset = 0 + }; + + // Fetch the latest block in the chain before we start enumerating. Will be used later. + var latestBlockResponse = await CosmosClient.GetLatestBlockAsync(); + if (!latestBlockResponse.IsSuccessStatusCode) + { + Logger.LogCritical(latestBlockResponse.Error, "Unable to request latest block, abort"); + throw new Exception("latest block request went wrong... figure it out", latestBlockResponse.Error); + } + + int latestBlockHeight = latestBlockResponse.Content.Block.Header.HeightAsInt; + + // Fire off initial request.... + var response = await SendLcdQueryAsync(queryWindow); + while (true) + { + if (response.Pagination?.TotalAsInt == 0) + { + // now we have to figure out if we went past the + // latest block, or whether we just hit a "dead period" + if (queryWindow.EndBlockHeight >= latestBlockHeight) + { + // we should wait a bit for some blocks to be formed + await Task.Delay(TimeSpan.FromSeconds(SecondsPerBlock), stoppingToken); + // do NOT advance the query window in this scenario, as we want to + // retry the same window later + continue; + } + } + queryWindow.Advance(response?.Pagination); + // Queue up the next request for async completion while we yield the current result set + var nextResponseTask = SendLcdQueryAsync(queryWindow); + foreach (var txResponse in response.TransactionResponses) + { + yield return txResponse; + } + + // await settlement of the api query and reassign response so the restart of the loop works as expected + response = await nextResponseTask; + } + } + + private Task SendLcdQueryAsync(QueryWindow queryWindow) + { + return Policy + .Handle(exception => + exception.StatusCode is HttpStatusCode.TooManyRequests or HttpStatusCode.BadGateway + or HttpStatusCode.Gone or HttpStatusCode.GatewayTimeout or HttpStatusCode.RequestTimeout + or HttpStatusCode.ServiceUnavailable) + .WaitAndRetryAsync(10, retryCounter => TimeSpan.FromMilliseconds(Math.Pow(10, retryCounter)), + (_, span) => + { + Logger.LogWarning("Handling retry while enumerating Cosmos Transactions, waiting {Time:c}", + span); + } + ) + .ExecuteAsync( + async () => await CosmosClient.GetTransactionsMatchingQueryAsync( + queryWindow.GetConditions(), + queryWindow.PaginationLimit, + queryWindow.PaginationOffset + ) + ); + } + + + private class QueryWindow + { + public int WindowBlockWidth { get; set; } + public int StartBlockHeight { get; set; } + public int EndBlockHeight => StartBlockHeight + WindowBlockWidth; + public int PaginationLimit { get; set; } + public int PaginationOffset { get; set; } + + public string[] GetConditions() + { + return new[] + { + $"tx.height={StartBlockHeight}" + }; + } + + public void Advance(LcdPagination? pagination) + { + // did we reach the final page of this "block window"? + if (IsFinalPage(pagination)) + { + // advance to the next block window + StartBlockHeight += WindowBlockWidth; + PaginationOffset = 0; + } + else + { + // advance to the next page of transactions + // within the current block window + PaginationOffset += PaginationLimit; + } + } + + private bool IsFinalPage(LcdPagination? pagination) + { + if (pagination == null) return true; + + return pagination.TotalAsInt <= (PaginationOffset + PaginationLimit); + } + } +} \ No newline at end of file diff --git a/src/TerraDotnet/Extensions/ListExtensions.cs b/src/TerraDotnet/Extensions/ListExtensions.cs index af67066..3496ff7 100644 --- a/src/TerraDotnet/Extensions/ListExtensions.cs +++ b/src/TerraDotnet/Extensions/ListExtensions.cs @@ -1,4 +1,6 @@ -using ServiceStack; +using System; +using System.Collections.Generic; +using System.Linq; namespace TerraDotnet.Extensions; @@ -12,7 +14,7 @@ public static void EnsureUstIsLast(this List list) return; } - if (list.All(i => !i.Denominator.EqualsIgnoreCase(TerraDenominators.Ust))) return; + if (list.All(i => !i.Denominator.Equals(TerraDenominators.Ust, StringComparison.CurrentCultureIgnoreCase))) return; if (list[1].Denominator != TerraDenominators.Ust) { diff --git a/src/TerraDotnet/Extensions/SerializerExtensions.cs b/src/TerraDotnet/Extensions/SerializerExtensions.cs index ebe4fa9..d0c88c1 100644 --- a/src/TerraDotnet/Extensions/SerializerExtensions.cs +++ b/src/TerraDotnet/Extensions/SerializerExtensions.cs @@ -1,15 +1,8 @@ +using System; +using System.Collections.Generic; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; -using Serilog; -using ServiceStack; -using TerraDotnet.TerraFcd.Messages; -using TerraDotnet.TerraFcd.Messages.Bank; -using TerraDotnet.TerraFcd.Messages.Distributions; -using TerraDotnet.TerraFcd.Messages.Gov; -using TerraDotnet.TerraFcd.Messages.Market; -using TerraDotnet.TerraFcd.Messages.Staking; -using TerraDotnet.TerraFcd.Messages.Wasm; namespace TerraDotnet.Extensions; @@ -71,97 +64,6 @@ public static class SerializerExtensions { return JsonSerializer.Deserialize(Convert.FromBase64String(base64StringMessage), Options); } - - public static List? FromCoreStdTxMessage(this Dictionary? dict, - string key, - string chainId, - string txTransactionHash - ) - { - var logger = Log.ForContext(typeof(SerializerExtensions)); - if (dict == null) - { - return default; - } - - if (!dict.TryGetValue(key, out var val)) - { - return default; - } - - var intermediate = val.ToObject>(); - - var toRet = new List(); - foreach (var inter in intermediate) - { - var fromCoreStdTxMessage = inter.FromCoreStdTxMessage(chainId); - if (fromCoreStdTxMessage == default) - { - logger.Error("Unable to parse type {Type} for tx-hash {TxHash}", inter.Type, txTransactionHash); - continue; - } - toRet.Add(fromCoreStdTxMessage); - } - - return toRet; - } - - public static IMsg? FromCoreStdTxMessage(this TxMsg msg, string chainId) - { - return msg.Type switch - { - "wasm/MsgExecuteContract" => new WasmMsgExecuteContract - { - Type = msg.Type, - Value = chainId.EqualsIgnoreCase("columbus-4") - ? msg.Value.ToObject() : msg.Value.ToObject() - }, - "wasm/MsgInstantiateContract" => new WasmMsgInstantiateContract - { - Type = msg.Type, - }, - // TODO complete this type handler - "bank/MsgSend" => new BankMsgSend - { - Type = msg.Type - }, - // TODO complete this type handler - "staking/MsgUndelegate" => new StakingMsgUndelegate - { - Type = msg.Type - }, - // TODO complete this type handler - "staking/MsgBeginRedelegate" => new StakingMsgBeginRedelegate - { - Type = msg.Type, - }, - // TODO complete this type handler - "staking/MsgDelegate" => new StakingMsgDelegate - { - Type = msg.Type, - }, - // TODO complete this type handler - "gov/MsgVote" => new GovMsgVote - { - Type = msg.Type, - }, - "distribution/MsgWithdrawDelegationReward" => new DistributionMsgWithdrawDelegationReward - { - Type = msg.Type, - Value = msg.Value.ToObject() - }, - // TODO complete this type handler - "market/MsgSwap" => new MarketMsgSwap - { - Type = msg.Type - }, - "wasm/MsgMigrateContract" => new WasmMsgMigrateContract - { - Type = msg.Type - }, - _ => null, - }; - } public static bool IsBase64String(this string s) { diff --git a/src/TerraDotnet/Extensions/StringExtensions.cs b/src/TerraDotnet/Extensions/StringExtensions.cs index a7f9789..f4bfd5a 100644 --- a/src/TerraDotnet/Extensions/StringExtensions.cs +++ b/src/TerraDotnet/Extensions/StringExtensions.cs @@ -1,49 +1,11 @@ +using System; using System.Text.RegularExpressions; -using ServiceStack; using TerraDotnet.TerraFcd.Messages; namespace TerraDotnet.Extensions; public static class StringExtensions { - public static TerraStringAmount ToTerraStringAmount(this string str) - { - var amountDenomRegex = new Regex("^([0-9]+)([a-z0-9]+)$", - RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant); - - var match = amountDenomRegex.Match(str.Trim()); - - if(match.Groups.Count != 3) - { - throw new ArgumentException($"Input amount seems invalid: {str}", nameof(str)); - } - - return new TerraStringAmount - { - Amount = match.Groups[1].Value, - Denominator = match.Groups[2].Value, - }; - } - - public static TerraAmount ToTerraAmount(this string str) - { - var terraStrAmount = str.ToTerraStringAmount(); - - var amount = new TerraAmount(terraStrAmount.Amount, terraStrAmount.Denominator); - - // is this a terra address we need to convert? - if (amount.Denominator.StartsWithIgnoreCase("terra")) - { - amount.Denominator = TerraDenominators.AssetTokenAddressToDenominator[amount.Denominator]; - } - else if (amount.Denominator.IsMuDemominator()) - { - amount.Denominator = amount.Denominator.FromNativeDenomToDenom(); - } - - return amount; - } - /// /// Determines whether a given currency denominator (like uluna) is a U amount /// @@ -55,7 +17,7 @@ public static TerraAmount ToTerraAmount(this string str) /// public static bool IsMuDemominator(this string denom) { - return denom.Length >= 4 && denom.StartsWithIgnoreCase("u"); + return denom.Length >= 4 && denom.StartsWith("u", StringComparison.OrdinalIgnoreCase); } /// @@ -65,7 +27,7 @@ public static bool IsMuDemominator(this string denom) /// public static bool IsTerraDenominatorAddress(this string denom) { - return denom.StartsWithIgnoreCase("terra"); + return denom.StartsWith("terra", StringComparison.OrdinalIgnoreCase); } public static string FromNativeDenomToDenom(this string denom) diff --git a/src/TerraDotnet/Extensions/TxLogExtensions.cs b/src/TerraDotnet/Extensions/TxLogExtensions.cs deleted file mode 100644 index b74c41e..0000000 --- a/src/TerraDotnet/Extensions/TxLogExtensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -using ServiceStack; -using TerraDotnet.TerraFcd.Messages; - -namespace TerraDotnet.Extensions; - -public static class TxLogExtensions -{ - public static TxLogEventAttribute? QueryTxLogsForAttributeFirstOrDefault( - this List logs, - string txLogEventPropToFind, - string attributeKeyToFind - ) - { - return logs.QueryTxLogsForAttributeFirstOrDefault( - txLogEventPropToFind, - attribute => attribute.Key == attributeKeyToFind); - } - - public static TxLogEventAttribute? QueryTxLogsForAttributeFirstOrDefault( - this List logs, - string txLogEventPropToFind, - Func predicate - ) - { - return logs - .SelectMany(l => - l.Events) - .Where(e => e.Type.EqualsIgnoreCase(txLogEventPropToFind)) - .SelectMany(evt => evt.Attributes) - .FirstOrDefault( - predicate - ); - } - - public static TxLogEventAttribute? QueryTxLogsForAttributeLastOrDefault( - this List logs, - string txLogEventPropToFind, - string attributeKeyToFind - ) - { - return logs.QueryTxLogsForAttributeLastOrDefault( - txLogEventPropToFind, - attribute => attribute.Key == attributeKeyToFind); - } - - public static TxLogEventAttribute? QueryTxLogsForAttributeLastOrDefault( - this List logs, - string txLogEventPropToFind, - Func predicate - ) - { - return logs - .SelectMany(l => - l.Events) - .Where(e => e.Type.EqualsIgnoreCase(txLogEventPropToFind)) - .SelectMany(evt => evt.Attributes) - .LastOrDefault( - predicate - ); - } - public static IEnumerable QueryTxLogsForAttributes( - this List logs, - string txLogEventPropToFind, - Func predicate - ) - { - return logs - .SelectMany(l => - l.Events) - .Where(e => e.Type.EqualsIgnoreCase(txLogEventPropToFind)) - .SelectMany(evt => evt.Attributes) - .Where( - predicate - ); - } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/IMsg.cs b/src/TerraDotnet/IMsg.cs similarity index 100% rename from src/TerraDotnet/TerraFcd/Messages/IMsg.cs rename to src/TerraDotnet/IMsg.cs diff --git a/src/TerraDotnet/JsonNumberToStringConverter.cs b/src/TerraDotnet/JsonNumberToStringConverter.cs index ab29593..0ec620b 100644 --- a/src/TerraDotnet/JsonNumberToStringConverter.cs +++ b/src/TerraDotnet/JsonNumberToStringConverter.cs @@ -1,3 +1,4 @@ +using System; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/src/TerraDotnet/TerraAmount.cs b/src/TerraDotnet/TerraAmount.cs index 9440247..b4aa7e5 100644 --- a/src/TerraDotnet/TerraAmount.cs +++ b/src/TerraDotnet/TerraAmount.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace TerraDotnet; public record TerraAmount @@ -12,7 +14,9 @@ public TerraAmount(string amount, string denomOrToken) public decimal Divisor { get; } + [JsonPropertyName("amount")] public decimal Value { get; } - + + [JsonPropertyName("denom")] public string Denominator { get; set; } } \ No newline at end of file diff --git a/src/TerraDotnet/TerraDenominators.cs b/src/TerraDotnet/TerraDenominators.cs index 399dbc3..a39c072 100644 --- a/src/TerraDotnet/TerraDenominators.cs +++ b/src/TerraDotnet/TerraDenominators.cs @@ -1,6 +1,8 @@ // ReSharper disable InconsistentNaming // ReSharper disable IdentifierTypo +using System.Collections.Generic; + namespace TerraDotnet; public static class TerraDenominators diff --git a/src/TerraDotnet/TerraDotnet.csproj b/src/TerraDotnet/TerraDotnet.csproj index 36454a0..774d157 100644 --- a/src/TerraDotnet/TerraDotnet.csproj +++ b/src/TerraDotnet/TerraDotnet.csproj @@ -2,18 +2,17 @@ net6.0 - enable - enable TerraDotnet + + - diff --git a/src/TerraDotnet/TerraFcd/Messages/Bank/BankMsgSend.cs b/src/TerraDotnet/TerraFcd/Messages/Bank/BankMsgSend.cs deleted file mode 100644 index 0e30aee..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Bank/BankMsgSend.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Bank; - -public record BankMsgSend : IMsg -{ - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; - - [JsonPropertyName("value")] - public BankMsgSendValue Value { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Bank/BankMsgSendValue.cs b/src/TerraDotnet/TerraFcd/Messages/Bank/BankMsgSendValue.cs deleted file mode 100644 index 7cc5195..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Bank/BankMsgSendValue.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Bank; - -public record BankMsgSendValue -{ - [JsonPropertyName("amount")] - public List Amounts { get; set; } = new(); - - [JsonPropertyName("to_address")] - public string ToAddress { get; set; } = string.Empty; - - [JsonPropertyName("from_address")] - public string FromAddress { get; set; } = string.Empty; - -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/CoreStdTx.cs b/src/TerraDotnet/TerraFcd/Messages/CoreStdTx.cs deleted file mode 100644 index 06189d3..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/CoreStdTx.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages; - -public record CoreStdTx -{ - public long Id { get; set; } - public string TransactionHash { get; set; } = string.Empty; - - [JsonPropertyName("fee")] - public TxFee Fee { get; set; } = new(); - - [JsonPropertyName("msg")] - public List Messages { get; set; } = new(); - - [JsonPropertyName("memo")] - public string Memo { get; set; } = string.Empty; - - public List Logs { get; set; } = new(); - - // TODO Signatures - // TODO timeout_height -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Distributions/DistributionMsgWithdrawDelegationReward.cs b/src/TerraDotnet/TerraFcd/Messages/Distributions/DistributionMsgWithdrawDelegationReward.cs deleted file mode 100644 index b537d59..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Distributions/DistributionMsgWithdrawDelegationReward.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Distributions; - -public record DistributionMsgWithdrawDelegationReward : IMsg -{ - public string Type { get; set; } = string.Empty; - public DistributionWithdrawDelegationRewardValue Value { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Distributions/DistributionWithdrawDelegationRewardValue.cs b/src/TerraDotnet/TerraFcd/Messages/Distributions/DistributionWithdrawDelegationRewardValue.cs deleted file mode 100644 index 3b81686..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Distributions/DistributionWithdrawDelegationRewardValue.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Distributions; - -public record DistributionWithdrawDelegationRewardValue -{ - [JsonPropertyName("delegator_address")] - public string DelegatorAddress { get; set; } = string.Empty; - - [JsonPropertyName("validator_address")] - public string ValidatorAddress { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Gov/GovMsgVote.cs b/src/TerraDotnet/TerraFcd/Messages/Gov/GovMsgVote.cs deleted file mode 100644 index 6047ac7..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Gov/GovMsgVote.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Gov; - -public record GovMsgVote : IMsg -{ - public string Type { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Market/MarketMsgSwap.cs b/src/TerraDotnet/TerraFcd/Messages/Market/MarketMsgSwap.cs deleted file mode 100644 index 4dff10b..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Market/MarketMsgSwap.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Market; - -public record MarketMsgSwap : IMsg -{ - public string Type { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Staking/StakingMsgBeginRedelegate.cs b/src/TerraDotnet/TerraFcd/Messages/Staking/StakingMsgBeginRedelegate.cs deleted file mode 100644 index 72fac26..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Staking/StakingMsgBeginRedelegate.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Staking; - -public record StakingMsgBeginRedelegate : IMsg -{ - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Staking/StakingMsgDelegate.cs b/src/TerraDotnet/TerraFcd/Messages/Staking/StakingMsgDelegate.cs deleted file mode 100644 index c566cd6..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Staking/StakingMsgDelegate.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Staking; - -public record StakingMsgDelegate : IMsg -{ - public string Type { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Staking/StakingMsgUndelegate.cs b/src/TerraDotnet/TerraFcd/Messages/Staking/StakingMsgUndelegate.cs deleted file mode 100644 index c20d0fb..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Staking/StakingMsgUndelegate.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Staking; - -public record StakingMsgUndelegate : IMsg -{ - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/TerraStringAmount.cs b/src/TerraDotnet/TerraFcd/Messages/TerraStringAmount.cs deleted file mode 100644 index 223fef4..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/TerraStringAmount.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Text.Json.Serialization; -using TerraDotnet.Extensions; -using TerraDotnet.TerraFcd.Messages.Wasm; - -namespace TerraDotnet.TerraFcd.Messages; - -public record TerraStringAmount -{ - [JsonPropertyName("denom")] - public string Denominator { get; set; } = string.Empty; - - [JsonPropertyName("amount")] - public string Amount { get; set; } = string.Empty; - - public WasmExecuteMessageAsset ToWasmExecuteMessageAsset() - { - if (Denominator.IsMuDemominator()) - { - return new WasmExecuteMessageAsset - { - Amount = Amount, - Info = new WasmExecuteMessageAssetInfo - { - NativeToken = new WasmExecuteMessageAssetInfoNativeToken - { - Denominator = Denominator - } - } - }; - } - - return new WasmExecuteMessageAsset - { - Amount = Amount, - Info = new WasmExecuteMessageAssetInfo - { - Token = new WasmExecuteMessageAssetInfoToken - { - ContractAddress = Denominator, - } - } - }; - } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/TerraTxWrapper.cs b/src/TerraDotnet/TerraFcd/Messages/TerraTxWrapper.cs deleted file mode 100644 index 31868b3..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/TerraTxWrapper.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages; - -public record TerraTxWrapper -{ - /// - /// Unique id of the transaction - /// - public long Id { get; set; } - - /// - /// Id of the chain it was performed on - /// - public string ChainId { get; set; } = string.Empty; - - /// - /// A generic transaction carrier - /// - public TerraTxGeneric Tx { get; set; } = new(); - - /// - /// Block height at which this transaction happened - /// - [JsonPropertyName("height")] - public string Height { get; set; } = string.Empty; - - /// - /// Transaction hash - /// - [JsonPropertyName("txhash")] - public string TransactionHash { get; set; } = string.Empty; - - [JsonPropertyName("gas_used")] - public string GasUsed { get; set; } = string.Empty; - - [JsonPropertyName("gas_wanted")] - public string GasWanted { get; set; } = string.Empty; - - [JsonPropertyName("timestamp")] - public DateTimeOffset CreatedAt { get; set; } - - /// - /// Code 0 means all good, other codes means some kind of failure - /// - [JsonPropertyName("code")] - public int Code { get; set; } - - [JsonPropertyName("logs")] - public List Logs { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/TxFee.cs b/src/TerraDotnet/TerraFcd/Messages/TxFee.cs deleted file mode 100644 index c44ac1f..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/TxFee.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages; - -public record TxFee -{ - [JsonPropertyName("gas")] - public string Gas { get; set; } = string.Empty; - - [JsonPropertyName("amount")] - public List Amount { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/TxLog.cs b/src/TerraDotnet/TerraFcd/Messages/TxLog.cs deleted file mode 100644 index 51d2536..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/TxLog.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages; - -public record TxLog -{ - [JsonPropertyName("events")] - public List Events { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/TxLogEvent.cs b/src/TerraDotnet/TerraFcd/Messages/TxLogEvent.cs deleted file mode 100644 index 61f8e3d..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/TxLogEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages; - -public record TxLogEvent -{ - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; - - [JsonPropertyName("attributes")] - public List Attributes { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/TxLogEventAttribute.cs b/src/TerraDotnet/TerraFcd/Messages/TxLogEventAttribute.cs deleted file mode 100644 index 6f34aa0..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/TxLogEventAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages; - -public record TxLogEventAttribute -{ - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; - - [JsonPropertyName("value")] - public string Value { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/TxMsg.cs b/src/TerraDotnet/TerraFcd/Messages/TxMsg.cs deleted file mode 100644 index a8ea270..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/TxMsg.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages; - -public record TxMsg -{ - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; - - [JsonPropertyName("value")] - public JsonElement Value { get; set; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/IWasmMsgExecuteContractValue.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/IWasmMsgExecuteContractValue.cs deleted file mode 100644 index f7babed..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/IWasmMsgExecuteContractValue.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public interface IWasmMsgExecuteContractValue -{ - List Coins { get; set; } - string Sender { get; set; } - string Contract { get; set; } - WasmExecuteMessage? ExecuteMessage { get; set; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMessageAssetInfoToken.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMessageAssetInfoToken.cs deleted file mode 100644 index 4606b06..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMessageAssetInfoToken.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMessageAssetInfoToken -{ - [JsonPropertyName("contract_addr")] - public string ContractAddress { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsg.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsg.cs deleted file mode 100644 index 852da98..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsg.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMessage -{ - [JsonPropertyName("send")] - public WasmExecuteMsgSend? Send { get; set; } - - [JsonPropertyName("withdraw_voting_tokens")] - public WasmExecuteMsgWithdrawVotingTokens? WithdrawVotingTokens { get; set; } - - [JsonPropertyName("deposit")] - public WasmExecuteMsgDeposit? Deposit { get; set; } - - [JsonPropertyName("deposit_stable")] - public WasmExecuteMsgDepositStable? DepositStable { get; set; } - - [JsonPropertyName("swap")] public WasmExecuteMsgSwap? Swap { get; set; } = null; - - [JsonPropertyName("unbond")] - public WasmExecuteMsgUnbond? Unbond { get; set; } - - [JsonPropertyName("claim")] - public WasmExecuteMsgClaim? Claim { get; set; } - - [JsonPropertyName("withdraw")] - public WasmExecuteMsgWithdraw? Withdraw { get; set; } - - [JsonPropertyName("auto_stake")] - public WasmExecuteMsgProvideLiquidity? AutoStake { get; set; } - - [JsonPropertyName("provide_liquidity")] - public WasmExecuteMsgProvideLiquidity? ProvideLiquidity { get; set; } - - [JsonPropertyName("cast_vote")] - public WasmExecuteMsgCastVote? CastVote { get; set; } - - [JsonPropertyName("mint")] - public WasmExecuteMsgMint? Mint { get; set; } - - [JsonPropertyName("compound")] - public WasmExecuteMsgCompound? Compound { get; set; } - - [JsonPropertyName("sweep")] - public WasmExecuteMsgSweep? Sweep { get; set; } - - [JsonPropertyName("earn")] - public WasmExecuteMsgEarn? Earn { get; set; } - - [JsonPropertyName("update_config")] - public WasmExecuteMsgUpdateConfig? UpdateConfig { get; set; } - - [JsonPropertyName("transfer")] - public WasmExecuteMsgTransfer? Transfer { get; set; } - - [JsonPropertyName("end_poll")] - public WasmExecuteMsgEndPoll? EndPoll { get; set; } - - [JsonPropertyName("staking")] - public WasmExecuteMsgStaking? Staking { get; set; } - - [JsonPropertyName("airdrop")] - public WasmExecuteMsgAirdrop? Airdrop { get; set; } - - [JsonPropertyName("execute_swap_operations")] - public WasmExecuteMsgExecuteSwapOperations? ExecuteSwapOperations { get; set; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAirdrop.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAirdrop.cs deleted file mode 100644 index c0bdf92..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAirdrop.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgAirdrop -{ - [JsonPropertyName("claim")] - public WasmExecuteMsgAirdropClaim Claim { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAirdropClaim.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAirdropClaim.cs deleted file mode 100644 index d676cf6..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAirdropClaim.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgAirdropClaim -{ -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAsset.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAsset.cs deleted file mode 100644 index 98bfee2..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAsset.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMessageAsset -{ - [JsonPropertyName("info")] - public WasmExecuteMessageAssetInfo Info { get; set; } = new(); - - [JsonPropertyName("amount")] - public string Amount { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAssetInfo.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAssetInfo.cs deleted file mode 100644 index 413732b..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAssetInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMessageAssetInfo -{ - [JsonPropertyName("token")] - public WasmExecuteMessageAssetInfoToken Token { get; set; } = new(); - - [JsonPropertyName("native_token")] - public WasmExecuteMessageAssetInfoNativeToken NativeToken { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAssetInfoNativeToken.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAssetInfoNativeToken.cs deleted file mode 100644 index 50953c4..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgAssetInfoNativeToken.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMessageAssetInfoNativeToken -{ - [JsonPropertyName("denom")] - public string Denominator { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgCastVote.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgCastVote.cs deleted file mode 100644 index eaa06fb..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgCastVote.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgCastVote -{ - [JsonPropertyName("vote")] - public string Vote { get; set; } = string.Empty; - - [JsonPropertyName("amount")] - public string Amount { get; set; } = string.Empty; - - [JsonPropertyName("poll_id")] - public int PollId { get; set; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgClaim.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgClaim.cs deleted file mode 100644 index 584db88..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgClaim.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgClaim -{ - [JsonPropertyName("stage")] - public int Stage { get; set; } - - [JsonPropertyName("amount")] - public string Amount { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgCompound.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgCompound.cs deleted file mode 100644 index 2e774d4..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgCompound.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgCompound -{ -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgDeposit.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgDeposit.cs deleted file mode 100644 index b9137a2..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgDeposit.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgDeposit -{ - public WasmExecuteMessageAsset Asset { get; set; } = new(); - - [JsonPropertyName("amount")] - public string Amount { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgDepositStable.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgDepositStable.cs deleted file mode 100644 index 0d9177e..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgDepositStable.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgDepositStable -{ -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgEarn.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgEarn.cs deleted file mode 100644 index e103a9d..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgEarn.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgEarn -{ -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgEndPoll.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgEndPoll.cs deleted file mode 100644 index 84e2236..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgEndPoll.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgEndPoll -{ -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperations.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperations.cs deleted file mode 100644 index 4c27001..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperations.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgExecuteSwapOperations -{ - [JsonPropertyName("operations")] - public List Operations { get; set; } = new(); - - [JsonPropertyName("offer_amount")] - public string? OfferAmount { get; set; } - - [JsonPropertyName("MinimumReceive")] - public string? MinimumReceive { get; set; } - -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperationsOperation.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperationsOperation.cs deleted file mode 100644 index 4f84000..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperationsOperation.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgExecuteSwapOperationsOperation -{ - [JsonPropertyName("terra_swap")] - public WasmExecuteMsgExecuteSwapOperationsOperationTerraSwap? TerraSwap { get; set; } - - [JsonPropertyName("native_swap")] - public WasmExecuteMsgExecuteSwapOperationsOperationNativeSwap? NativeSwap { get; set; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperationsOperationNativeSwap.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperationsOperationNativeSwap.cs deleted file mode 100644 index ecbd2ea..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperationsOperationNativeSwap.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgExecuteSwapOperationsOperationNativeSwap -{ - [JsonPropertyName("offer_denom")] - public string OfferDenom { get; set; } = string.Empty; - [JsonPropertyName("ask_denom")] - public string AskDenom { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperationsOperationTerraSwap.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperationsOperationTerraSwap.cs deleted file mode 100644 index a2fa3e6..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgExecuteSwapOperationsOperationTerraSwap.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgExecuteSwapOperationsOperationTerraSwap -{ - [JsonPropertyName("ask_asset_info")] - public WasmExecuteMessageAssetInfo AskAssetInfo { get; set; } = new(); - - [JsonPropertyName("offer_asset_info")] - public WasmExecuteMessageAssetInfo OfferAssetInfo { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgMint.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgMint.cs deleted file mode 100644 index d19e063..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgMint.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgMint -{ -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgProvideLiquidity.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgProvideLiquidity.cs deleted file mode 100644 index d63015b..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgProvideLiquidity.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgProvideLiquidity -{ - [JsonPropertyName("assets")] - public List? Assets { get; set; } - - [JsonPropertyName("slippage_tolerance")] - public string? SlippageTolerance { get; set; } - - [JsonPropertyName("token_amount")] - public string? TokenAmount { get; set; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgSend.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgSend.cs deleted file mode 100644 index bf63027..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgSend.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgSend -{ - [JsonPropertyName("msg")] public string? Message { get; set; } - - [JsonPropertyName("amount")] public string? Amount { get; set; } - - [JsonPropertyName("contract")] public string? Contract { get; set; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgStaking.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgStaking.cs deleted file mode 100644 index 84a62ec..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgStaking.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgStaking -{ - [JsonPropertyName("unstake")] - public WasmExecuteMsgStakingUnstake Unstake { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgStakingUnstake.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgStakingUnstake.cs deleted file mode 100644 index 3a86048..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgStakingUnstake.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgStakingUnstake -{ - [JsonPropertyName("amount")] - public string Amount { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgSwap.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgSwap.cs deleted file mode 100644 index 0b5235d..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgSwap.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgSwap -{ - [JsonPropertyName("max_spread")] - public string MaxSpread { get; set; } = string.Empty; - - [JsonPropertyName("offer_spread")] - public WasmExecuteMessageAsset OfferAsset { get; set; } = new(); - - [JsonPropertyName("belief_price")] - public string BeliefPrice { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgSweep.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgSweep.cs deleted file mode 100644 index 6e8782e..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgSweep.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgSweep -{ -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgTransfer.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgTransfer.cs deleted file mode 100644 index b310355..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgTransfer.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgTransfer -{ -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgUnbond.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgUnbond.cs deleted file mode 100644 index 94ff7ab..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgUnbond.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgUnbond -{ - [JsonPropertyName("amount")] - public string Amount { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgUpdateConfig.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgUpdateConfig.cs deleted file mode 100644 index 5fec856..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgUpdateConfig.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgUpdateConfig -{ -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgWithdraw.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgWithdraw.cs deleted file mode 100644 index ad49241..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgWithdraw.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgWithdraw -{ - [JsonPropertyName("asset")] - public WasmExecuteMessageAsset? Asset { get; init; } - - [JsonPropertyName("amount")] - public string? Amount { get; init; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgWithdrawVotingTokens.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgWithdrawVotingTokens.cs deleted file mode 100644 index 7fe7584..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmExecuteMsgWithdrawVotingTokens.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmExecuteMsgWithdrawVotingTokens -{ - [JsonPropertyName("amount")] - public string Amount { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgExecuteContract.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgExecuteContract.cs deleted file mode 100644 index 415d7d7..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgExecuteContract.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmMsgExecuteContract : IMsg -{ - public string Type { get; set; } = string.Empty; - public IWasmMsgExecuteContractValue? Value { get; set; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgExecuteContractValueCol4.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgExecuteContractValueCol4.cs deleted file mode 100644 index 30e1520..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgExecuteContractValueCol4.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using TerraDotnet.Extensions; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmMsgExecuteContractValueCol4 : IWasmMsgExecuteContractValue -{ - [JsonPropertyName("coins")] - public List Coins { get; set; } = new(); - - [JsonPropertyName("sender")] public string Sender { get; set; } = string.Empty; - - [JsonPropertyName("contract")] public string Contract { get; set; } = string.Empty; - - WasmExecuteMessage? IWasmMsgExecuteContractValue.ExecuteMessage - { - get => ExecuteMessageRaw.ToObject(); - set => ExecuteMessageRaw = Convert.ToBase64String(JsonSerializer.SerializeToUtf8Bytes(value)); - } - - [JsonPropertyName("execute_msg")] public string ExecuteMessageRaw { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgExecuteContractValueCol5.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgExecuteContractValueCol5.cs deleted file mode 100644 index 6df6d25..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgExecuteContractValueCol5.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmMsgExecuteContractValueCol5 : IWasmMsgExecuteContractValue -{ - [JsonPropertyName("coins")] - public List Coins { get; set; } = new(); - - [JsonPropertyName("sender")] public string Sender { get; set; } = string.Empty; - - [JsonPropertyName("contract")] public string Contract { get; set; } = string.Empty; - - [JsonPropertyName("execute_msg")] public WasmExecuteMessage? ExecuteMessage { get; set; } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgInstantiateContract.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgInstantiateContract.cs deleted file mode 100644 index 9651d00..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgInstantiateContract.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmMsgInstantiateContract : IMsg -{ - public string Type { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgMigrateContract.cs b/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgMigrateContract.cs deleted file mode 100644 index c91189f..0000000 --- a/src/TerraDotnet/TerraFcd/Messages/Wasm/WasmMsgMigrateContract.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TerraDotnet.TerraFcd.Messages.Wasm; - -public record WasmMsgMigrateContract : IMsg -{ - public string Type { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/TerraMoneyFcdApiClient.cs b/src/TerraDotnet/TerraFcd/TerraMoneyFcdApiClient.cs deleted file mode 100644 index 885d66c..0000000 --- a/src/TerraDotnet/TerraFcd/TerraMoneyFcdApiClient.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Refit; - -namespace TerraDotnet.TerraFcd; - -public interface ITerraMoneyFcdApiClient -{ - // ?offset=0&limit=100&account=terra14qul6swv2p3vcfqk38fm8dvkezf0gj52m6a78k - [Get("/v1/txs")] - public Task ListTxesAsync(long offset, int limit, string account); - -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/TerraTransactionValueFactory.cs b/src/TerraDotnet/TerraFcd/TerraTransactionValueFactory.cs deleted file mode 100644 index 1dace9d..0000000 --- a/src/TerraDotnet/TerraFcd/TerraTransactionValueFactory.cs +++ /dev/null @@ -1,23 +0,0 @@ -using TerraDotnet.Extensions; -using TerraDotnet.TerraFcd.Messages; - -namespace TerraDotnet.TerraFcd; - -public static class TerraTransactionValueFactory -{ - public static CoreStdTx GetIt(TerraTxWrapper tx) - { - return tx.Tx.Type switch - { - "core/StdTx" => new CoreStdTx{ - Id = tx.Id, - TransactionHash = tx.TransactionHash, - Fee = tx.Tx.Value.ToObject("fee"), - Memo = tx.Tx.Value.ToObject("memo"), - Messages = tx.Tx.Value.FromCoreStdTxMessage("msg", tx.ChainId, tx.TransactionHash), - Logs = tx.Logs, - }, - _ => throw new ArgumentOutOfRangeException(nameof(tx.Tx.Type), $"Unable to handle tx type: {tx.Tx.Type}"), - }; - } -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/TerraTxesResponse.cs b/src/TerraDotnet/TerraFcd/TerraTxesResponse.cs deleted file mode 100644 index 28cc2bd..0000000 --- a/src/TerraDotnet/TerraFcd/TerraTxesResponse.cs +++ /dev/null @@ -1,21 +0,0 @@ -using TerraDotnet.TerraFcd.Messages; - -namespace TerraDotnet.TerraFcd; - -public record TerraTxesResponse -{ - /// - /// Used for paging, the value of Next should go in as Offset for the next call - /// - public long Next { get; set; } - - /// - /// The limit applied to the returned data - /// - public int Limit { get; set; } - - /// - /// The list of txes returned - /// - public List Txs { get; set; } = new(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraJsonSerializerOptions.cs b/src/TerraDotnet/TerraJsonSerializerOptions.cs new file mode 100644 index 0000000..611c073 --- /dev/null +++ b/src/TerraDotnet/TerraJsonSerializerOptions.cs @@ -0,0 +1,19 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace TerraDotnet; + +public class TerraJsonSerializerOptions +{ + public static JsonSerializerOptions GetThem() + { + return new JsonSerializerOptions + { + WriteIndented = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, + Converters = {new JsonNumberToStringConverter()}, + AllowTrailingCommas = true, + PropertyNameCaseInsensitive = true + }; + } +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/ICosmosLcdApiClient.cs b/src/TerraDotnet/TerraLcd/ICosmosLcdApiClient.cs new file mode 100644 index 0000000..cd06dda --- /dev/null +++ b/src/TerraDotnet/TerraLcd/ICosmosLcdApiClient.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Refit; +using TerraDotnet.TerraLcd.Messages; + +namespace TerraDotnet.TerraLcd; + +public interface ICosmosLcdApiClient +{ + [Get("/cosmos/base/tendermint/v1beta1/blocks/latest")] + public Task> GetLatestBlockAsync(); + + [Get("/cosmos/tx/v1beta1/txs")] + public Task GetTransactionsMatchingQueryAsync( + [AliasAs("events"), Query(CollectionFormat.Multi)] string[] conditions, + [AliasAs("pagination.limit"), Query("pagination.limit")] int paginationLimit = 100, + [AliasAs("pagination.offset"), Query("pagination.offset")] int paginationOffset = 0 + ); +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/ITerraMoneyLcdApiClient.cs b/src/TerraDotnet/TerraLcd/ITerraMoneyLcdApiClient.cs deleted file mode 100644 index 920be93..0000000 --- a/src/TerraDotnet/TerraLcd/ITerraMoneyLcdApiClient.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Refit; -using TerraDotnet.TerraLcd.Messages; - -namespace TerraDotnet.TerraLcd; - -public interface ITerraMoneyLcdApiClient -{ - [Get("/blocks/latest")] - public Task> GetLatestBlockAsync(); -} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/TerraBlock.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosBlock.cs similarity index 58% rename from src/TerraDotnet/TerraLcd/Messages/TerraBlock.cs rename to src/TerraDotnet/TerraLcd/Messages/CosmosBlock.cs index a918e3b..8ef8fb6 100644 --- a/src/TerraDotnet/TerraLcd/Messages/TerraBlock.cs +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosBlock.cs @@ -2,9 +2,9 @@ namespace TerraDotnet.TerraLcd.Messages; -public record TerraBlock +public record CosmosBlock { [JsonPropertyName("header")] - public TerraBlockHeader Header { get; set; } = new(); + public CosmosBlockHeader Header { get; set; } = new(); } \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/TerraBlockHeader.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosBlockHeader.cs similarity index 70% rename from src/TerraDotnet/TerraLcd/Messages/TerraBlockHeader.cs rename to src/TerraDotnet/TerraLcd/Messages/CosmosBlockHeader.cs index 4cd6a24..64ae1ed 100644 --- a/src/TerraDotnet/TerraLcd/Messages/TerraBlockHeader.cs +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosBlockHeader.cs @@ -1,8 +1,9 @@ +using System; using System.Text.Json.Serialization; namespace TerraDotnet.TerraLcd.Messages; -public record TerraBlockHeader +public record CosmosBlockHeader { [JsonPropertyName("chain_id")] public string ChainId { get; set; } = string.Empty; @@ -10,9 +11,12 @@ public record TerraBlockHeader [JsonPropertyName("height")] public string Height { get; set; } = string.Empty; + public int HeightAsInt => int.Parse(Height); + [JsonPropertyName("time")] public DateTimeOffset Time { get; set; } [JsonPropertyName("last_block_id")] - public TerraBlockId LastBlockId { get; set; } = new(); -} \ No newline at end of file + public CosmosBlockId LastBlockId { get; set; } = new(); +} + diff --git a/src/TerraDotnet/TerraLcd/Messages/TerraBlockId.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosBlockId.cs similarity index 69% rename from src/TerraDotnet/TerraLcd/Messages/TerraBlockId.cs rename to src/TerraDotnet/TerraLcd/Messages/CosmosBlockId.cs index 88be84b..d0e926a 100644 --- a/src/TerraDotnet/TerraLcd/Messages/TerraBlockId.cs +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosBlockId.cs @@ -2,11 +2,11 @@ namespace TerraDotnet.TerraLcd.Messages; -public record TerraBlockId +public record CosmosBlockId { [JsonPropertyName("hash")] public string Hash { get; set; } = string.Empty; [JsonPropertyName("parts")] - public TerraBlockIdParts Parts { get; set; } = new(); + public CosmosBlockIdParts Parts { get; set; } = new(); } \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/TerraBlockIdParts.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosBlockIdParts.cs similarity index 87% rename from src/TerraDotnet/TerraLcd/Messages/TerraBlockIdParts.cs rename to src/TerraDotnet/TerraLcd/Messages/CosmosBlockIdParts.cs index f7e9dcc..9506a29 100644 --- a/src/TerraDotnet/TerraLcd/Messages/TerraBlockIdParts.cs +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosBlockIdParts.cs @@ -2,7 +2,7 @@ namespace TerraDotnet.TerraLcd.Messages; -public record TerraBlockIdParts +public record CosmosBlockIdParts { [JsonPropertyName("total")] public int Total { get; set; } diff --git a/src/TerraDotnet/TerraLcd/Messages/TerraBlockResponse.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosBlockResponse.cs similarity index 51% rename from src/TerraDotnet/TerraLcd/Messages/TerraBlockResponse.cs rename to src/TerraDotnet/TerraLcd/Messages/CosmosBlockResponse.cs index f868df0..a473fb1 100644 --- a/src/TerraDotnet/TerraLcd/Messages/TerraBlockResponse.cs +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosBlockResponse.cs @@ -2,11 +2,11 @@ namespace TerraDotnet.TerraLcd.Messages; -public record TerraBlockResponse +public record CosmosBlockResponse { [JsonPropertyName("block_id")] - public TerraBlockId BlockId { get; set; } = new(); + public CosmosBlockId BlockId { get; set; } = new(); [JsonPropertyName("block")] - public TerraBlock Block { get; set; } = new(); + public CosmosBlock Block { get; set; } = new(); } \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/TerraClaimableRewardBody.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosClaimableRewardBody.cs similarity index 86% rename from src/TerraDotnet/TerraLcd/Messages/TerraClaimableRewardBody.cs rename to src/TerraDotnet/TerraLcd/Messages/CosmosClaimableRewardBody.cs index 959705c..cb23052 100644 --- a/src/TerraDotnet/TerraLcd/Messages/TerraClaimableRewardBody.cs +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosClaimableRewardBody.cs @@ -2,7 +2,7 @@ namespace TerraDotnet.TerraLcd.Messages; -public record TerraClaimableRewardBody +public record CosmosClaimableRewardBody { [JsonPropertyName("owner")] public string Owner { get; set; } = string.Empty; diff --git a/src/TerraDotnet/TerraLcd/Messages/TerraClaimableRewardsRequest.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosClaimableRewardsRequest.cs similarity index 51% rename from src/TerraDotnet/TerraLcd/Messages/TerraClaimableRewardsRequest.cs rename to src/TerraDotnet/TerraLcd/Messages/CosmosClaimableRewardsRequest.cs index 1e0a7e4..a7ff7f9 100644 --- a/src/TerraDotnet/TerraLcd/Messages/TerraClaimableRewardsRequest.cs +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosClaimableRewardsRequest.cs @@ -2,8 +2,8 @@ namespace TerraDotnet.TerraLcd.Messages; -public record TerraClaimableRewardsRequest +public record CosmosClaimableRewardsRequest { [JsonPropertyName("claimable_reward")] - public TerraClaimableRewardBody ClaimableReward { get; set; } = new(); + public CosmosClaimableRewardBody ClaimableReward { get; set; } = new(); } \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/CosmosDelegateMessage.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosDelegateMessage.cs new file mode 100644 index 0000000..53628e4 --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosDelegateMessage.cs @@ -0,0 +1,14 @@ +using TerraDotnet.TerraFcd.Messages; + +namespace TerraDotnet.TerraLcd.Messages; + +public record CosmosDelegateMessage : IMsg +{ + public string Type { get; set; } = string.Empty; + + public string DelegatorAddress { get; set; } = string.Empty; + + public string ValidatorAddress { get; set; } = string.Empty; + + public CosmosAmount? Amount { get; set; } +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/CosmosRedelegateMessage.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosRedelegateMessage.cs new file mode 100644 index 0000000..4493feb --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosRedelegateMessage.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using TerraDotnet.TerraFcd.Messages; + +namespace TerraDotnet.TerraLcd.Messages; + +public record CosmosRedelegateMessage : IMsg +{ + [JsonPropertyName("@type")] + public string Type { get; set; } = string.Empty; + + [JsonPropertyName("delegator_address")] + public string DelegatorAddress { get; set; } = string.Empty; + + [JsonPropertyName("validator_src_address")] + public string ValidatorSourceAddress { get; set; } = string.Empty; + + [JsonPropertyName("validator_dst_address")] + public string ValidatorDestinationAddress { get; set; } = string.Empty; + + [JsonPropertyName("amount")] + public CosmosAmount? Amount { get; set; } +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/CosmosUndelegateMessage.cs b/src/TerraDotnet/TerraLcd/Messages/CosmosUndelegateMessage.cs new file mode 100644 index 0000000..e4620c1 --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/CosmosUndelegateMessage.cs @@ -0,0 +1,5 @@ +namespace TerraDotnet.TerraLcd.Messages; + +public record CosmosUndelegateMessage : CosmosDelegateMessage +{ +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponse.cs b/src/TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponse.cs new file mode 100644 index 0000000..98ed986 --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponse.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace TerraDotnet.TerraLcd.Messages; + +public record GetTransactionsMatchingQueryResponse +{ + /// + /// These are fairly "light-weight", so what you probably want + /// is the + /// + [JsonPropertyName("txs")] + public List Transactions { get; set; } = new(); + + [JsonPropertyName("tx_responses")] + public List TransactionResponses { get; set; } = new(); + + public LcdPagination? Pagination { get; set; } = new(); +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/ILcdMessage.cs b/src/TerraDotnet/TerraLcd/Messages/ILcdMessage.cs new file mode 100644 index 0000000..98ef483 --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/ILcdMessage.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace TerraDotnet.TerraLcd.Messages; + +public interface ILcdMessage +{ + [JsonPropertyName("@type")] + public string Type { get; set; } +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/LcdError.cs b/src/TerraDotnet/TerraLcd/Messages/LcdError.cs new file mode 100644 index 0000000..5dba144 --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/LcdError.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace TerraDotnet.TerraLcd.Messages; + +public class LcdError +{ + public int Code { get; set; } + + public string Message { get; set; } = string.Empty; + + public List Details { get; set; } = new(); +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/LcdEvent.cs b/src/TerraDotnet/TerraLcd/Messages/LcdEvent.cs new file mode 100644 index 0000000..f245bca --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/LcdEvent.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace TerraDotnet.TerraLcd.Messages; + +public record LcdEvent +{ + public string Type { get; set; } = string.Empty; + + public List Attributes { get; set; } = new(); +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraFcd/Messages/TerraTxGeneric.cs b/src/TerraDotnet/TerraLcd/Messages/LcdMessageGeneric.cs similarity index 59% rename from src/TerraDotnet/TerraFcd/Messages/TerraTxGeneric.cs rename to src/TerraDotnet/TerraLcd/Messages/LcdMessageGeneric.cs index ea4ca1d..92a0725 100644 --- a/src/TerraDotnet/TerraFcd/Messages/TerraTxGeneric.cs +++ b/src/TerraDotnet/TerraLcd/Messages/LcdMessageGeneric.cs @@ -1,16 +1,19 @@ +using System.Collections.Generic; using System.Text.Json; +using System.Text.Json.Serialization; -namespace TerraDotnet.TerraFcd.Messages; +namespace TerraDotnet.TerraLcd.Messages; -public class TerraTxGeneric +public record LcdMessageGeneric { /// /// The type of the transaction - contains valuable information on how the parse the /// + [JsonPropertyName("@type")] public string Type { get; set; } = string.Empty; /// - /// Generic blobbed storage to carry arbitrary + /// Generic blobbed storage to carry arbitrary data /// public Dictionary Value { get; set; } = new(); } \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/LcdPagination.cs b/src/TerraDotnet/TerraLcd/Messages/LcdPagination.cs new file mode 100644 index 0000000..cecf548 --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/LcdPagination.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace TerraDotnet.TerraLcd.Messages; + +public record LcdPagination +{ + [JsonPropertyName("next_key")] + public string? NextKey { get; set; } + + public string Total { get; set; } = string.Empty; + + public int TotalAsInt => int.Parse(Total); +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/LcdTx.cs b/src/TerraDotnet/TerraLcd/Messages/LcdTx.cs new file mode 100644 index 0000000..6691fdb --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/LcdTx.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace TerraDotnet.TerraLcd.Messages; + +public record LcdTx +{ + [JsonPropertyName("body")] + public LcdTxBody Body { get; set; } = new(); + + // TODO auth_info + + // TODO signatures +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/LcdTxBody.cs b/src/TerraDotnet/TerraLcd/Messages/LcdTxBody.cs new file mode 100644 index 0000000..a7695e0 --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/LcdTxBody.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace TerraDotnet.TerraLcd.Messages; + +public record LcdTxBody +{ + [JsonPropertyName("messages")] + public List> Messages { get; set; } = new(); + + public string Memo { get; set; } = string.Empty; + + [JsonPropertyName("timeout_height")] + public string TimeoutHeight { get; set; } = string.Empty; + + // TODO extension_options + + // TODO non_critical_extension_options +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/LcdTxResponse.cs b/src/TerraDotnet/TerraLcd/Messages/LcdTxResponse.cs new file mode 100644 index 0000000..fd8e414 --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/LcdTxResponse.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace TerraDotnet.TerraLcd.Messages; + +public record LcdTxResponse +{ + public string Height { get; set; } = string.Empty; + + public int HeightAsInt => int.Parse(Height); + + [JsonPropertyName("txhash")] + public string TransactionHash { get; set; } = string.Empty; + + [JsonPropertyName("codespace")] + public string CodeSpace { get; set; } = string.Empty; + + public int Code { get; set; } + + public string Data { get; set; } = string.Empty; + + [JsonPropertyName("raw_log")] + public string RawLog { get; set; } = string.Empty; + + public List Logs { get; set; } + + public string Info { get; set; } = string.Empty; + + [JsonPropertyName("gas_wanted")] + public string RawGasWanted { get; set; } = string.Empty; + + public int GasWanted => int.Parse(RawGasWanted); + + [JsonPropertyName("gas_used")] + public string RawGasUsed { get; set; } = string.Empty; + + public int GasUsed => int.Parse(RawGasUsed); + + [JsonPropertyName("tx")] + public LcdTx Transaction { get; set; } = new(); + + [JsonPropertyName("timestamp")] + public DateTimeOffset CreatedAt { get; set; } + + public List Events { get; set; } = new(); +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/LcdTxResponseEventAttribute.cs b/src/TerraDotnet/TerraLcd/Messages/LcdTxResponseEventAttribute.cs new file mode 100644 index 0000000..2d4c04e --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/LcdTxResponseEventAttribute.cs @@ -0,0 +1,8 @@ +namespace TerraDotnet.TerraLcd.Messages; + +public record LcdTxResponseEventAttribute +{ + public string Key { get; set; } = string.Empty; + public string Value { get; set; } = string.Empty; + public bool? Index { get; set; } +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraLcd/Messages/LcdTxResponseLog.cs b/src/TerraDotnet/TerraLcd/Messages/LcdTxResponseLog.cs new file mode 100644 index 0000000..679e323 --- /dev/null +++ b/src/TerraDotnet/TerraLcd/Messages/LcdTxResponseLog.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace TerraDotnet.TerraLcd.Messages; + +public record LcdTxResponseLog +{ + [JsonPropertyName("msg_index")] + public int MessageIndex { get; set; } + + public string Log { get; set; } = string.Empty; + + public List Events { get; set; } = new(); + + +} \ No newline at end of file diff --git a/src/TerraDotnet/TerraStakingContracts.cs b/src/TerraDotnet/TerraStakingContracts.cs index 8628705..04288d2 100644 --- a/src/TerraDotnet/TerraStakingContracts.cs +++ b/src/TerraDotnet/TerraStakingContracts.cs @@ -1,6 +1,8 @@ // ReSharper disable InconsistentNaming // ReSharper disable IdentifierTypo +using System.Collections.Generic; + namespace TerraDotnet; // TODO update address diff --git a/src/TerraDotnet/TerraTransactionEnumerator.cs b/src/TerraDotnet/TerraTransactionEnumerator.cs deleted file mode 100644 index 61a7c8e..0000000 --- a/src/TerraDotnet/TerraTransactionEnumerator.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Diagnostics; -using System.Net; -using System.Runtime.CompilerServices; -using Microsoft.Extensions.Logging; -using NewRelic.Api.Agent; -using Polly; -using Refit; -using TerraDotnet.TerraFcd; -using TerraDotnet.TerraFcd.Messages; - -namespace TerraDotnet; - -public class TerraTransactionEnumerator -{ - private readonly ILogger _logger; - private readonly ITerraMoneyFcdApiClient _terraClient; - - public TerraTransactionEnumerator( - ILogger logger, - ITerraMoneyFcdApiClient terraClient - ) - { - _logger = logger; - _terraClient = terraClient; - } - - [Trace] - public async IAsyncEnumerable<(TerraTxWrapper tx, CoreStdTx msg)> EnumerateTransactionsAsync( - long offset, - int limit, - string contract, - [EnumeratorCancellation] CancellationToken stoppingToken - ) - { - var response = await Policy - .Handle(exception => exception.StatusCode == HttpStatusCode.TooManyRequests) - .WaitAndRetryAsync(10, retryCounter => TimeSpan.FromMilliseconds(Math.Pow(10, retryCounter)), - (_, span) => - { - _logger.LogWarning("Handling retry while enumerating Terra Transactions, waiting {Time:c}", span); - }) - .ExecuteAsync(async () => await _terraClient.ListTxesAsync(offset, limit, contract)); - var txes = response; - var next = txes.Next; - var stopwatch = new Stopwatch(); - var doContinue = true; - do - { - if (next == 0) - { - doContinue = false; - } - - _logger.LogInformation("`next` continuation token: {Next}", next); - - // NO await to perform background work - // Capture the async method as a func so we can invoke it again during retry - Func> listTxesAsync = () => _terraClient.ListTxesAsync( - txes.Next, - txes.Limit, - contract - ); - var nextTxes = listTxesAsync(); - stopwatch.Reset(); - stopwatch.Start(); - foreach (var tx in txes.Txs) - { - if (tx.Code != 0) - { - _logger.LogInformation("Skipping tx with Id {Id} since code is {Code}", tx.Id, tx.Code); - continue; - } - - _logger.LogDebug("Processing transaction with id {Id}", tx.Id); - - var msg = TerraTransactionValueFactory.GetIt(tx); - yield return (tx, msg); - } - stopwatch.Stop(); - - var expectedWallTime = 2d; - if (stopwatch.Elapsed.TotalSeconds < expectedWallTime) - { - var nappieTime = TimeSpan.FromSeconds(expectedWallTime-stopwatch.Elapsed.TotalSeconds); - _logger.LogDebug("Rate limiting, less than 2 seconds was spent on processing by the consumer. Napping for {Sleep}", nappieTime.ToString("g")); - await Task.Delay(nappieTime, stoppingToken); - } - _logger.LogDebug("waiting for next http result-set"); - - Policy.Handle(exception => exception.StatusCode == HttpStatusCode.TooManyRequests) - .WaitAndRetry(10, retryCounter => TimeSpan.FromMilliseconds(Math.Pow(10, retryCounter)), - (_, span) => - { - _logger.LogWarning("Handling retry while enumerating Terra Transactions, waiting {Time:c}", span); - nextTxes = listTxesAsync(); - }) - .Execute(() => { Task.WaitAll(new Task[] { nextTxes }, cancellationToken: stoppingToken); }); - - _logger.LogDebug("done, iterating"); - txes = nextTxes.Result; - next = nextTxes.Result.Next; - } while (doContinue); - } -} \ No newline at end of file diff --git a/test/NarrowIntegrationTests/Infrastructure/Terra2/Storage/Terra2RawRepositoryTests.cs b/test/NarrowIntegrationTests/Infrastructure/Terra2/Storage/Terra2RawRepositoryTests.cs new file mode 100644 index 0000000..ea02fd2 --- /dev/null +++ b/test/NarrowIntegrationTests/Infrastructure/Terra2/Storage/Terra2RawRepositoryTests.cs @@ -0,0 +1,62 @@ +using System.Threading; +using System.Threading.Tasks; +using SapientFi.Infrastructure.Terra2.Storage; +using Xunit; + +namespace NarrowIntegrationTests.Infrastructure.Terra2.Storage; + +public class Terra2RawRepositoryTests : IntegrationBaseTest +{ + private readonly Terra2RawRepository _repository; + + public Terra2RawRepositoryTests() + { + _repository = new Terra2RawRepository(GetDbConnectionFactory()); + } + + /* ************************************************************************************************************ + */ + [Fact] + public async Task GetLatestSeenBlockHeightAsync_returns_0_whenTableIsEmpty() + { + await EnsureEmptyAsync(); + + var actual = await _repository.GetLatestSeenBlockHeightAsync(CancellationToken.None); + + Assert.Equal(0, actual); + } + + + /* ************************************************************************************************************ + */ + [Fact] + public async Task GetLatestSeenBlockHeightAsync_returns_maxHeight_whenTableHasRows() + { + await EnsureEmptyAsync(); + + await InsertAsync(new Terra2RawTransactionEntity + { + Id = 1, + Height = 1, + TxHash = "1" + }); + + await InsertAsync(new Terra2RawTransactionEntity + { + Id = 2, + Height = 666, + TxHash = "2" + }); + + await InsertAsync(new Terra2RawTransactionEntity + { + Id = 3, + Height = 2, + TxHash = "3" + }); + + var actual = await _repository.GetLatestSeenBlockHeightAsync(CancellationToken.None); + + Assert.Equal(666, actual); + } +} \ No newline at end of file diff --git a/test/NarrowIntegrationTests/IntegrationBaseTest.cs b/test/NarrowIntegrationTests/IntegrationBaseTest.cs index d2c23e9..1d312ff 100644 --- a/test/NarrowIntegrationTests/IntegrationBaseTest.cs +++ b/test/NarrowIntegrationTests/IntegrationBaseTest.cs @@ -1,7 +1,10 @@ using System; +using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using SapientFi.ServiceHost.ServiceCollectionExtensions; +using ServiceStack.Data; +using ServiceStack.OrmLite; namespace NarrowIntegrationTests; @@ -34,9 +37,35 @@ public virtual void AddServices(IServiceCollection services) } + public virtual IDbConnectionFactory GetDbConnectionFactory() + { + return ServiceProvider.GetRequiredService(); + } + public void Dispose() { ServiceProvider.Dispose(); Scope.Dispose(); } + + + /// + /// Ensure that the table for the entity exists and is empty. + /// + /// A database Entity + protected async Task EnsureEmptyAsync() + { + using var db = await GetDbConnectionFactory().OpenDbConnectionAsync(); + db.CreateTableIfNotExists(); + await db.DeleteAllAsync(); + } + + + protected async Task InsertAsync(TEntity entity) + { + using var db = await GetDbConnectionFactory().OpenDbConnectionAsync(); + await db.InsertAsync(entity); + + return entity; + } } \ No newline at end of file diff --git a/test/NarrowIntegrationTests/NarrowIntegrationTests.csproj b/test/NarrowIntegrationTests/NarrowIntegrationTests.csproj index 989cd68..9e85750 100644 --- a/test/NarrowIntegrationTests/NarrowIntegrationTests.csproj +++ b/test/NarrowIntegrationTests/NarrowIntegrationTests.csproj @@ -26,7 +26,12 @@ - + + Always + + + Always + diff --git a/test/NarrowIntegrationTests/Oracles/TerraLcdClient/TerraMoneyFcdApiClientTests.cs b/test/NarrowIntegrationTests/Oracles/TerraLcdClient/TerraMoneyFcdApiClientTests.cs deleted file mode 100644 index 39594eb..0000000 --- a/test/NarrowIntegrationTests/Oracles/TerraLcdClient/TerraMoneyFcdApiClientTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; -using Refit; -using TerraDotnet; -using TerraDotnet.TerraFcd; -using Xunit; - -namespace NarrowIntegrationTests.Oracles.TerraLcdClient -{ - public class TerraMoneyFcdApiClientTests - { - private readonly ITerraMoneyFcdApiClient _client; - - public TerraMoneyFcdApiClientTests() - { - _client = RestService.For( - new HttpClient(new HttpClientHandler - { - // To route via Charles proxy for debugging use: - // Proxy = new WebProxy("http://localhost:8888"), - ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true, - }) - { - BaseAddress = new Uri("https://fcd.terra.dev"), - }); - } - - [Fact] - public async Task List_txs_work() - { - var txs = await _client.ListTxesAsync(121079826, 100, TerraStakingContracts.MINE_STAKING_CONTRACT); - - Assert.NotEmpty(txs.Txs); - } - } - - -} \ No newline at end of file diff --git a/test/NarrowIntegrationTests/Oracles/TerraLcdClient/TerraMoneyLcdApiClientTests.cs b/test/NarrowIntegrationTests/Oracles/TerraLcdClient/TerraMoneyLcdApiClientTests.cs index ebf6921..9740a4d 100644 --- a/test/NarrowIntegrationTests/Oracles/TerraLcdClient/TerraMoneyLcdApiClientTests.cs +++ b/test/NarrowIntegrationTests/Oracles/TerraLcdClient/TerraMoneyLcdApiClientTests.cs @@ -2,6 +2,7 @@ using System.Net.Http; using System.Threading.Tasks; using Refit; +using SapientFi.Infrastructure.Terra2; using TerraDotnet.TerraLcd; using Xunit; @@ -9,11 +10,11 @@ namespace NarrowIntegrationTests.Oracles.TerraLcdClient; public class TerraMoneyLcdApiClientTests { - private readonly ITerraMoneyLcdApiClient _client; + private readonly ICosmosLcdApiClient _client; public TerraMoneyLcdApiClientTests() { - _client = RestService.For( + _client = RestService.For>( new HttpClient(new HttpClientHandler { // To route via Charles proxy for debugging use: @@ -21,7 +22,7 @@ public TerraMoneyLcdApiClientTests() ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true, }) { - BaseAddress = new Uri("https://phoenix-lcd.terra.dev"), + BaseAddress = new Uri("https://phoenix-lcd.sapient.fi"), }); } diff --git a/test/NarrowIntegrationTests/TerraDotnet/GetTransactionsMatchingQueryResponse_01.json b/test/NarrowIntegrationTests/TerraDotnet/GetTransactionsMatchingQueryResponse_01.json new file mode 100644 index 0000000..5e69e8b --- /dev/null +++ b/test/NarrowIntegrationTests/TerraDotnet/GetTransactionsMatchingQueryResponse_01.json @@ -0,0 +1,1262 @@ +{ + "txs": [ + { + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "delegator_address": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0", + "validator_src_address": "terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx", + "validator_dst_address": "terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt", + "amount": { + "denom": "uluna", + "amount": "1932000000" + } + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AiVuv8AMDt0fGGxF8DEcDhR28sflA+j8GfH5/D59FG1L" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_LEGACY_AMINO_JSON" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "67288" + } + ], + "gas_limit": "448586", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "0sIQZiBfcQrBzGA6eJe0OrdTx3GBhClUBsKYWSOmiUEwelf5uqJC0wNiuMXxhLdxdBSxpcE8Xg2wFR6UYrf4Ww==" + ] + }, + { + "body": { + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20", + "to_address": "terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm", + "amount": [ + { + "denom": "uluna", + "amount": "1000000" + } + ] + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AgLuDN/jos126YbtrTAxvL76/JZ8SxR+F/NM9yHh8U8G" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "21237" + } + ], + "gas_limit": "141580", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "iekH9rrafxakxEmbTQbpkg9h0KsHGkgUG0eZKVFgwKUi32FoZaa/UAS+n8wnCEQ1Wa5z66r93lOicd1reZjCBg==" + ] + }, + { + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgUndelegate", + "delegator_address": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu", + "validator_address": "terravaloper1wwq9mfkkqfta334d4hcjrjau9y50a4hw37zhdl", + "amount": { + "denom": "uluna", + "amount": "32454390" + } + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "A4bnZP/9hrCbP8gW3W1vNf3pp0ClUhHoY0TAhu8tMo4R" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_LEGACY_AMINO_JSON" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "53087" + } + ], + "gas_limit": "353908", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "7YN1xYnhJhFwqC/8DZQPhUNT4fTwZzhj4EGE7wTx1dtfOZgQCeWv6emVnLAf6EV5TSqnbIUN6jsvb/6Mtv69xw==" + ] + } + ], + "tx_responses": [ + { + "height": "201", + "txhash": "74B8212F209C7F225F79B7C0CA064160CC3C9D589A4F4AB6849071A0DAFED5A3", + "codespace": "", + "code": 0, + "data": "0A3C0A2A2F636F736D6F732E7374616B696E672E763162657461312E4D7367426567696E526564656C6567617465120E0A0C08ADE0B595061095B3998502", + "raw_log": "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.staking.v1beta1.MsgBeginRedelegate\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"module\",\"value\":\"staking\"},{\"key\":\"sender\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"}]},{\"type\":\"redelegate\",\"attributes\":[{\"key\":\"source_validator\",\"value\":\"terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx\"},{\"key\":\"destination_validator\",\"value\":\"terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt\"},{\"key\":\"amount\",\"value\":\"1932000000uluna\"},{\"key\":\"completion_time\",\"value\":\"2022-06-18T06:26:53Z\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "coin_received", + "attributes": [ + { + "key": "receiver", + "value": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0" + }, + { + "key": "amount", + "value": "22551uluna" + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "spender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "22551uluna" + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "action", + "value": "/cosmos.staking.v1beta1.MsgBeginRedelegate" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "module", + "value": "staking" + }, + { + "key": "sender", + "value": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0" + } + ] + }, + { + "type": "redelegate", + "attributes": [ + { + "key": "source_validator", + "value": "terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx" + }, + { + "key": "destination_validator", + "value": "terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt" + }, + { + "key": "amount", + "value": "1932000000uluna" + }, + { + "key": "completion_time", + "value": "2022-06-18T06:26:53Z" + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "recipient", + "value": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "22551uluna" + } + ] + } + ] + } + ], + "info": "", + "gas_wanted": "448586", + "gas_used": "261855", + "tx": { + "@type": "/cosmos.tx.v1beta1.Tx", + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "delegator_address": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0", + "validator_src_address": "terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx", + "validator_dst_address": "terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt", + "amount": { + "denom": "uluna", + "amount": "1932000000" + } + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AiVuv8AMDt0fGGxF8DEcDhR28sflA+j8GfH5/D59FG1L" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_LEGACY_AMINO_JSON" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "67288" + } + ], + "gas_limit": "448586", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "0sIQZiBfcQrBzGA6eJe0OrdTx3GBhClUBsKYWSOmiUEwelf5uqJC0wNiuMXxhLdxdBSxpcE8Xg2wFR6UYrf4Ww==" + ] + }, + "timestamp": "2022-05-28T06:26:53Z", + "events": [ + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjcyODh1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjcyODh1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjcyODh1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": "NjcyODh1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzAvMA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "MHNJUVppQmZjUXJCekdBNmVKZTBPcmRUeDNHQmhDbFVCc0tZV1NPbWlVRXdlbGY1dXFKQzB3Tml1TVh4aExkeGRCU3hwY0U4WGcyd0ZSNlVZcmY0V3c9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnQmVnaW5SZWRlbGVnYXRl", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjI1NTF1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjI1NTF1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjI1NTF1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + } + ] + }, + { + "type": "redelegate", + "attributes": [ + { + "key": "c291cmNlX3ZhbGlkYXRvcg==", + "value": "dGVycmF2YWxvcGVyMWt0dTdhNndxbGs2dmx5d2Y0cnQ2d2ZjeHVwaGMwZXMyN3AwcXZ4", + "index": true + }, + { + "key": "ZGVzdGluYXRpb25fdmFsaWRhdG9y", + "value": "dGVycmF2YWxvcGVyMTBjMDR5c3o5dXpueDJta2VudWszajNlc2pjenlxaDBqNzgzbnp0", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTkzMjAwMDAwMHVsdW5h", + "index": true + }, + { + "key": "Y29tcGxldGlvbl90aW1l", + "value": "MjAyMi0wNi0xOFQwNjoyNjo1M1o=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "c3Rha2luZw==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + } + ] + } + ] + }, + { + "height": "201", + "txhash": "D084544055655650C275E74F7B4C8CEAFF2EC12A3D613467E67D5033FC3E459E", + "codespace": "", + "code": 0, + "data": "0A1E0A1C2F636F736D6F732E62616E6B2E763162657461312E4D736753656E64", + "raw_log": "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm\"},{\"key\":\"amount\",\"value\":\"1000000uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20\"},{\"key\":\"amount\",\"value\":\"1000000uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm\"},{\"key\":\"sender\",\"value\":\"terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20\"},{\"key\":\"amount\",\"value\":\"1000000uluna\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "coin_received", + "attributes": [ + { + "key": "receiver", + "value": "terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm" + }, + { + "key": "amount", + "value": "1000000uluna" + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "spender", + "value": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20" + }, + { + "key": "amount", + "value": "1000000uluna" + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "action", + "value": "/cosmos.bank.v1beta1.MsgSend" + }, + { + "key": "sender", + "value": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20" + }, + { + "key": "module", + "value": "bank" + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "recipient", + "value": "terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm" + }, + { + "key": "sender", + "value": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20" + }, + { + "key": "amount", + "value": "1000000uluna" + } + ] + } + ] + } + ], + "info": "", + "gas_wanted": "141580", + "gas_used": "97104", + "tx": { + "@type": "/cosmos.tx.v1beta1.Tx", + "body": { + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20", + "to_address": "terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm", + "amount": [ + { + "denom": "uluna", + "amount": "1000000" + } + ] + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AgLuDN/jos126YbtrTAxvL76/JZ8SxR+F/NM9yHh8U8G" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "21237" + } + ], + "gas_limit": "141580", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "iekH9rrafxakxEmbTQbpkg9h0KsHGkgUG0eZKVFgwKUi32FoZaa/UAS+n8wnCEQ1Wa5z66r93lOicd1reZjCBg==" + ] + }, + "timestamp": "2022-05-28T06:26:53Z", + "events": [ + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjEyMzd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjEyMzd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjEyMzd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": "MjEyMzd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjAvMA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "aWVrSDlycmFmeGFreEVtYlRRYnBrZzloMEtzSEdrZ1VHMGVaS1ZGZ3dLVWkzMkZvWmFhL1VBUytuOHduQ0VRMVdhNXo2NnI5M2xPaWNkMXJlWmpDQmc9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTAwMDAwMHVsdW5h", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExemp3Y2RnOW1oODB4dHUzN3ZxcXVlYWU2anV4ZnNkMGY4MmF5bG0=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTAwMDAwMHVsdW5h", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExemp3Y2RnOW1oODB4dHUzN3ZxcXVlYWU2anV4ZnNkMGY4MmF5bG0=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTAwMDAwMHVsdW5h", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + } + ] + }, + { + "height": "201", + "txhash": "C5967C255A473CE4D9D47CBE9DC1E32613ACBC5D98363EFF3754370E4F714627", + "codespace": "", + "code": 0, + "data": "0A370A252F636F736D6F732E7374616B696E672E763162657461312E4D7367556E64656C6567617465120E0A0C08ADE0B595061095B3998502", + "raw_log": "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu\"},{\"key\":\"amount\",\"value\":\"191uluna\"},{\"key\":\"receiver\",\"value\":\"terra1tygms3xhhs3yv487phx3dw4a95jn7t7l8l07dr\"},{\"key\":\"amount\",\"value\":\"32454390uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"191uluna\"},{\"key\":\"spender\",\"value\":\"terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh\"},{\"key\":\"amount\",\"value\":\"32454390uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.staking.v1beta1.MsgUndelegate\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"sender\",\"value\":\"terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh\"},{\"key\":\"module\",\"value\":\"staking\"},{\"key\":\"sender\",\"value\":\"terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"191uluna\"},{\"key\":\"recipient\",\"value\":\"terra1tygms3xhhs3yv487phx3dw4a95jn7t7l8l07dr\"},{\"key\":\"sender\",\"value\":\"terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh\"},{\"key\":\"amount\",\"value\":\"32454390uluna\"}]},{\"type\":\"unbond\",\"attributes\":[{\"key\":\"validator\",\"value\":\"terravaloper1wwq9mfkkqfta334d4hcjrjau9y50a4hw37zhdl\"},{\"key\":\"amount\",\"value\":\"32454390uluna\"},{\"key\":\"completion_time\",\"value\":\"2022-06-18T06:26:53Z\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "coin_received", + "attributes": [ + { + "key": "receiver", + "value": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu" + }, + { + "key": "amount", + "value": "191uluna" + }, + { + "key": "receiver", + "value": "terra1tygms3xhhs3yv487phx3dw4a95jn7t7l8l07dr" + }, + { + "key": "amount", + "value": "32454390uluna" + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "spender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "191uluna" + }, + { + "key": "spender", + "value": "terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh" + }, + { + "key": "amount", + "value": "32454390uluna" + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "action", + "value": "/cosmos.staking.v1beta1.MsgUndelegate" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "sender", + "value": "terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh" + }, + { + "key": "module", + "value": "staking" + }, + { + "key": "sender", + "value": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu" + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "recipient", + "value": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "191uluna" + }, + { + "key": "recipient", + "value": "terra1tygms3xhhs3yv487phx3dw4a95jn7t7l8l07dr" + }, + { + "key": "sender", + "value": "terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh" + }, + { + "key": "amount", + "value": "32454390uluna" + } + ] + }, + { + "type": "unbond", + "attributes": [ + { + "key": "validator", + "value": "terravaloper1wwq9mfkkqfta334d4hcjrjau9y50a4hw37zhdl" + }, + { + "key": "amount", + "value": "32454390uluna" + }, + { + "key": "completion_time", + "value": "2022-06-18T06:26:53Z" + } + ] + } + ] + } + ], + "info": "", + "gas_wanted": "353908", + "gas_used": "194239", + "tx": { + "@type": "/cosmos.tx.v1beta1.Tx", + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgUndelegate", + "delegator_address": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu", + "validator_address": "terravaloper1wwq9mfkkqfta334d4hcjrjau9y50a4hw37zhdl", + "amount": { + "denom": "uluna", + "amount": "32454390" + } + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "A4bnZP/9hrCbP8gW3W1vNf3pp0ClUhHoY0TAhu8tMo4R" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_LEGACY_AMINO_JSON" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "53087" + } + ], + "gas_limit": "353908", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "7YN1xYnhJhFwqC/8DZQPhUNT4fTwZzhj4EGE7wTx1dtfOZgQCeWv6emVnLAf6EV5TSqnbIUN6jsvb/6Mtv69xw==" + ] + }, + "timestamp": "2022-05-28T06:26:53Z", + "events": [ + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NTMwODd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NTMwODd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NTMwODd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": "NTMwODd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHUvMA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "N1lOMXhZbmhKaEZ3cUMvOERaUVBoVU5UNGZUd1p6aGo0RUdFN3dUeDFkdGZPWmdRQ2VXdjZlbVZuTEFmNkVWNVRTcW5iSVVONmpzdmIvNk10djY5eHc9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnVW5kZWxlZ2F0ZQ==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTkxdWx1bmE=", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTkxdWx1bmE=", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTkxdWx1bmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExZmw0OHZzbm1zZHpjdjg1cTVkMnE0ejVhamRoYTh5dTNubG4wbWg=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzI0NTQzOTB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExdHlnbXMzeGhoczN5djQ4N3BoeDNkdzRhOTVqbjd0N2w4bDA3ZHI=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzI0NTQzOTB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExdHlnbXMzeGhoczN5djQ4N3BoeDNkdzRhOTVqbjd0N2w4bDA3ZHI=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExZmw0OHZzbm1zZHpjdjg1cTVkMnE0ejVhamRoYTh5dTNubG4wbWg=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzI0NTQzOTB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExZmw0OHZzbm1zZHpjdjg1cTVkMnE0ejVhamRoYTh5dTNubG4wbWg=", + "index": true + } + ] + }, + { + "type": "unbond", + "attributes": [ + { + "key": "dmFsaWRhdG9y", + "value": "dGVycmF2YWxvcGVyMXd3cTltZmtrcWZ0YTMzNGQ0aGNqcmphdTl5NTBhNGh3Mzd6aGRs", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzI0NTQzOTB1bHVuYQ==", + "index": true + }, + { + "key": "Y29tcGxldGlvbl90aW1l", + "value": "MjAyMi0wNi0xOFQwNjoyNjo1M1o=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "c3Rha2luZw==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + } + ] + } + ] + } + ], + "pagination": { + "next_key": null, + "total": "540" + } +} \ No newline at end of file diff --git a/test/NarrowIntegrationTests/TerraDotnet/GetTransactionsMatchingQueryResponse_02.json b/test/NarrowIntegrationTests/TerraDotnet/GetTransactionsMatchingQueryResponse_02.json new file mode 100644 index 0000000..d854e6e --- /dev/null +++ b/test/NarrowIntegrationTests/TerraDotnet/GetTransactionsMatchingQueryResponse_02.json @@ -0,0 +1,465 @@ +{ + "txs": [ + { + "body": { + "messages": [ + { + "@type": "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + "delegator_address": "terra1puzp2yjqps43x7nse33svljc550xjz35jfygpe", + "validator_address": "terravaloper1puzp2yjqps43x7nse33svljc550xjz35jxg432" + }, + { + "@type": "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission", + "validator_address": "terravaloper1puzp2yjqps43x7nse33svljc550xjz35jxg432" + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AjfxAgPiOEsXq4MR3NwZOTruL168Rx4o8DlU5kLmDJ4m" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "3" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "30000" + } + ], + "gas_limit": "200000", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "iJm1PRHHyLem29CQS8xdzgw6Qp7111UKohsik4TJdCNwko0Y3reTq6QUPBTbqi4aEX3qiiZHrzmWX6CwkZ45Pg==" + ] + } + ], + "tx_responses": [ + { + "height": "24", + "txhash": "0B701285895C87AFB2F7FB01B4C68EFE804A16C8A6C294F737E760FEC654B394", + "codespace": "", + "code": 0, + "data": "0A390A372F636F736D6F732E646973747269627574696F6E2E763162657461312E4D7367576974686472617744656C656761746F725265776172640A3D0A3B2F636F736D6F732E646973747269627574696F6E2E763162657461312E4D7367576974686472617756616C696461746F72436F6D6D697373696F6E", + "raw_log": "[{\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\"},{\"key\":\"module\",\"value\":\"distribution\"},{\"key\":\"sender\",\"value\":\"terra1puzp2yjqps43x7nse33svljc550xjz35jfygpe\"}]},{\"type\":\"withdraw_rewards\",\"attributes\":[{\"key\":\"amount\"},{\"key\":\"validator\",\"value\":\"terravaloper1puzp2yjqps43x7nse33svljc550xjz35jxg432\"}]}]},{\"msg_index\":1,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1puzp2yjqps43x7nse33svljc550xjz35jfygpe\"},{\"key\":\"amount\",\"value\":\"138872uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"138872uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"module\",\"value\":\"distribution\"},{\"key\":\"sender\",\"value\":\"terravaloper1puzp2yjqps43x7nse33svljc550xjz35jxg432\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1puzp2yjqps43x7nse33svljc550xjz35jfygpe\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"138872uluna\"}]},{\"type\":\"withdraw_commission\",\"attributes\":[{\"key\":\"amount\",\"value\":\"138872uluna\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "message", + "attributes": [ + { + "key": "action", + "value": "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" + }, + { + "key": "module", + "value": "distribution" + }, + { + "key": "sender", + "value": "terra1puzp2yjqps43x7nse33svljc550xjz35jfygpe" + } + ] + }, + { + "type": "withdraw_rewards", + "attributes": [ + { + "key": "amount", + "value": "" + }, + { + "key": "validator", + "value": "terravaloper1puzp2yjqps43x7nse33svljc550xjz35jxg432" + } + ] + } + ] + }, + { + "msg_index": 1, + "log": "", + "events": [ + { + "type": "coin_received", + "attributes": [ + { + "key": "receiver", + "value": "terra1puzp2yjqps43x7nse33svljc550xjz35jfygpe" + }, + { + "key": "amount", + "value": "138872uluna" + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "spender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "138872uluna" + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "action", + "value": "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "module", + "value": "distribution" + }, + { + "key": "sender", + "value": "terravaloper1puzp2yjqps43x7nse33svljc550xjz35jxg432" + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "recipient", + "value": "terra1puzp2yjqps43x7nse33svljc550xjz35jfygpe" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "138872uluna" + } + ] + }, + { + "type": "withdraw_commission", + "attributes": [ + { + "key": "amount", + "value": "138872uluna" + } + ] + } + ] + } + ], + "info": "", + "gas_wanted": "200000", + "gas_used": "130157", + "tx": { + "@type": "/cosmos.tx.v1beta1.Tx", + "body": { + "messages": [ + { + "@type": "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + "delegator_address": "terra1puzp2yjqps43x7nse33svljc550xjz35jfygpe", + "validator_address": "terravaloper1puzp2yjqps43x7nse33svljc550xjz35jxg432" + }, + { + "@type": "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission", + "validator_address": "terravaloper1puzp2yjqps43x7nse33svljc550xjz35jxg432" + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AjfxAgPiOEsXq4MR3NwZOTruL168Rx4o8DlU5kLmDJ4m" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "3" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "30000" + } + ], + "gas_limit": "200000", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "iJm1PRHHyLem29CQS8xdzgw6Qp7111UKohsik4TJdCNwko0Y3reTq6QUPBTbqi4aEX3qiiZHrzmWX6CwkZ45Pg==" + ] + }, + "timestamp": "2022-05-28T06:07:38Z", + "events": [ + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExcHV6cDJ5anFwczQzeDduc2UzM3N2bGpjNTUweGp6MzVqZnlncGU=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzAwMDB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzAwMDB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExcHV6cDJ5anFwczQzeDduc2UzM3N2bGpjNTUweGp6MzVqZnlncGU=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzAwMDB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExcHV6cDJ5anFwczQzeDduc2UzM3N2bGpjNTUweGp6MzVqZnlncGU=", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": "MzAwMDB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "dGVycmExcHV6cDJ5anFwczQzeDduc2UzM3N2bGpjNTUweGp6MzVqZnlncGUvMw==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "aUptMVBSSEh5TGVtMjlDUVM4eGR6Z3c2UXA3MTExVUtvaHNpazRUSmRDTndrbzBZM3JlVHE2UVVQQlRicWk0YUVYM3FpaVpIcnptV1g2Q3drWjQ1UGc9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd0RlbGVnYXRvclJld2FyZA==", + "index": true + } + ] + }, + { + "type": "withdraw_rewards", + "attributes": [ + { + "key": "YW1vdW50", + "value": null, + "index": true + }, + { + "key": "dmFsaWRhdG9y", + "value": "dGVycmF2YWxvcGVyMXB1enAyeWpxcHM0M3g3bnNlMzNzdmxqYzU1MHhqejM1anhnNDMy", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "ZGlzdHJpYnV0aW9u", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExcHV6cDJ5anFwczQzeDduc2UzM3N2bGpjNTUweGp6MzVqZnlncGU=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd1ZhbGlkYXRvckNvbW1pc3Npb24=", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTM4ODcydWx1bmE=", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExcHV6cDJ5anFwczQzeDduc2UzM3N2bGpjNTUweGp6MzVqZnlncGU=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTM4ODcydWx1bmE=", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExcHV6cDJ5anFwczQzeDduc2UzM3N2bGpjNTUweGp6MzVqZnlncGU=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTM4ODcydWx1bmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + } + ] + }, + { + "type": "withdraw_commission", + "attributes": [ + { + "key": "YW1vdW50", + "value": "MTM4ODcydWx1bmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "ZGlzdHJpYnV0aW9u", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmF2YWxvcGVyMXB1enAyeWpxcHM0M3g3bnNlMzNzdmxqYzU1MHhqejM1anhnNDMy", + "index": true + } + ] + } + ] + } + ], + "pagination": { + "next_key": null, + "total": "1" + } +} \ No newline at end of file diff --git a/test/NarrowIntegrationTests/TerraDotnet/Terra2StakingQuerierTests.cs b/test/NarrowIntegrationTests/TerraDotnet/Terra2StakingQuerierTests.cs new file mode 100644 index 0000000..7c61236 --- /dev/null +++ b/test/NarrowIntegrationTests/TerraDotnet/Terra2StakingQuerierTests.cs @@ -0,0 +1,48 @@ +using System.IO; +using System.Text.Json; +using System.Threading; +using SapientFi.Infrastructure.Terra2.Indexers.Delegations.Storage; +using SapientFi.Infrastructure.Terra2.Storage; +using ServiceStack.OrmLite; +using TerraDotnet; +using TerraDotnet.TerraLcd.Messages; +using Xunit; + +namespace NarrowIntegrationTests.TerraDotnet; + +public class Terra2StakingQuerierTests : IntegrationBaseTest +{ + private readonly Terra2RawRepository _rawRepository; + private readonly Terra2DelegationsRepository _delegationsRepository; + + public Terra2StakingQuerierTests() + { + _rawRepository = new Terra2RawRepository(GetDbConnectionFactory()); + _delegationsRepository = new Terra2DelegationsRepository(GetDbConnectionFactory()); + } + + private GetTransactionsMatchingQueryResponse? GetParsedJson() + { + var json = File.ReadAllText("TerraDotnet/GetTransactionsMatchingQueryResponse_02.json"); + + return JsonSerializer.Deserialize( + json, + TerraJsonSerializerOptions.GetThem() + ); + } + + [Fact] + public async void gfjdghjd() + { + var db = await _delegationsRepository.GetDbConnectionAsync(new CancellationToken()); + var valAddr = "terravaloper1plxp55happ379eevsnk2xeuwzsrldsmqduyu36"; + + var theSum = await db.ScalarAsync( + db.From() + .Select(q => Sql.Sum(q.Amount)) + .Where(x => x.ValidatorAddress == valAddr) + ); + + Assert.Equal(0L, theSum); + } +} diff --git a/test/NarrowIntegrationTests/TerraDotnet/TerraTransactionEnumeratorTests.cs b/test/NarrowIntegrationTests/TerraDotnet/TerraTransactionEnumeratorTests.cs new file mode 100644 index 0000000..6431f94 --- /dev/null +++ b/test/NarrowIntegrationTests/TerraDotnet/TerraTransactionEnumeratorTests.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using SapientFi.Infrastructure.Terra2; +using SapientFi.Infrastructure.Terra2.TransactionListener; +using TerraDotnet; +using TerraDotnet.TerraLcd; +using Xunit; + +namespace NarrowIntegrationTests.TerraDotnet; + +public class TerraTransactionEnumeratorTests +{ + private readonly CosmosTransactionEnumerator _transactionEnumerator; + + public TerraTransactionEnumeratorTests() + { + var services = new ServiceCollection(); + services.InternalAddLcdClient("https://phoenix-lcd.sapient.fi"); + + var client = services.BuildServiceProvider().GetRequiredService>(); + + _transactionEnumerator = new Terra2TransactionEnumerator( + NullLogger>.Instance, + client + ); + } + + [Fact(Skip = "this is only meant to be an easy way for devs to debug the enumerator")] + public async Task TriggerTheEnumerator() + { + var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + + var enumeration = _transactionEnumerator.EnumerateTransactionsAsync(0, tokenSource.Token); + + await foreach (var response in enumeration.WithCancellation(tokenSource.Token)) + { + var stop = "hammer time"; + } + } +} \ No newline at end of file diff --git a/test/NarrowIntegrationTests/TerraDotnet/TerraTransactionParsingTests.cs b/test/NarrowIntegrationTests/TerraDotnet/TerraTransactionParsingTests.cs new file mode 100644 index 0000000..3b2241f --- /dev/null +++ b/test/NarrowIntegrationTests/TerraDotnet/TerraTransactionParsingTests.cs @@ -0,0 +1,206 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Threading; +using SapientFi.Infrastructure.Terra2.Storage; +using TerraDotnet; +using TerraDotnet.TerraLcd.Messages; +using Xunit; + +namespace NarrowIntegrationTests.TerraDotnet; + +public class TerraTransactionParsingTests : IntegrationBaseTest +{ + private readonly Terra2RawRepository _rawRepository; + + public TerraTransactionParsingTests() + { + _rawRepository = new Terra2RawRepository(GetDbConnectionFactory()); + } + + private GetTransactionsMatchingQueryResponse? GetParsedJson_01() + { + var json = File.ReadAllText("TerraDotnet/GetTransactionsMatchingQueryResponse_01.json"); + + return JsonSerializer.Deserialize( + json, + TerraJsonSerializerOptions.GetThem() + ); + } + + private GetTransactionsMatchingQueryResponse? GetParsedJson_02() + { + var json = File.ReadAllText("TerraDotnet/GetTransactionsMatchingQueryResponse_02.json"); + + return JsonSerializer.Deserialize( + json, + TerraJsonSerializerOptions.GetThem() + ); + } + + [Fact] + public async void SerializingAndDeserializingTransactionFromDatabaseProducesOriginalTransaction() + { + #region ___ARRANGE___ + const int ID = 2; + + await EnsureEmptyAsync(); + + var tx = GetParsedJson_02()!.TransactionResponses[0]; + var rawTxEntity = new Terra2RawTransactionEntity + { + Id = ID, + Height = tx.HeightAsInt, + CreatedAt = tx.CreatedAt, + TxHash = tx.TransactionHash, + RawTx = JsonSerializer.Serialize(tx, TerraJsonSerializerOptions.GetThem()) + }; + + #endregion + + // ************************************************************************************************ + + #region ___ACT___ + + await _rawRepository.SaveRawTransactionAsync(rawTxEntity); + var rawTxEntityFromDb = await _rawRepository.GetByIdOrDefaultAsync(ID); + + var rawTxDes = JsonSerializer.Deserialize(rawTxEntity.RawTx); + var txFromDbDes = JsonSerializer.Deserialize(rawTxEntityFromDb!.RawTx); + + #endregion + + // ************************************************************************************************ + + #region ___ASSERT___ + + Assert.Equal(2, rawTxDes!.Transaction.Body.Messages.Count); + Assert.Equal(2, txFromDbDes!.Transaction.Body.Messages.Count); + + + Assert.Equal(rawTxEntity.Id, rawTxEntityFromDb.Id); + Assert.Equal(rawTxEntity.Height, rawTxEntityFromDb.Height); + Assert.Equal(rawTxEntity.CreatedAt, rawTxEntityFromDb.CreatedAt); + Assert.Equal(rawTxEntity.TxHash, rawTxEntityFromDb.TxHash); + + // RawTx equality + var txBeforeDb = rawTxDes; + var txFromDb = txFromDbDes; + + Assert.Equal(txBeforeDb.Code, txFromDb.Code); + Assert.Equal(txBeforeDb.CodeSpace, txFromDb.CodeSpace); + Assert.Equal(txBeforeDb.Data, txFromDb.Data); + Assert.Equal(txBeforeDb.CreatedAt, txFromDb.CreatedAt); + Assert.Equal(txBeforeDb.Events.Count, txFromDb.Events.Count); + Assert.Equal(txBeforeDb.GasUsed, txFromDb.GasUsed); + Assert.Equal(txBeforeDb.GasWanted, txFromDb.GasWanted); + Assert.Equal(txBeforeDb.Height, txFromDb.Height); + Assert.Equal(txBeforeDb.HeightAsInt, txFromDb.HeightAsInt); + Assert.Equal(txBeforeDb.Info, txFromDb.Info); + Assert.Equal(txBeforeDb.Logs.Count, txFromDb.Logs.Count); + Assert.Equal(txBeforeDb.RawGasUsed, txFromDb.RawGasUsed); + Assert.Equal(txBeforeDb.RawGasWanted, txFromDb.RawGasWanted); + Assert.Equal(txBeforeDb.RawLog, txFromDb.RawLog); + Assert.Equal(txBeforeDb.TransactionHash, txFromDb.TransactionHash); + + #endregion + } + + [Fact] + public async void CanDeserialize_TxFromDb() + { + await EnsureEmptyAsync(); + var actual = GetParsedJson_01(); + + Assert.NotNull(actual); + + // transactions + Assert.Equal(3, actual!.TransactionResponses.Count); + + var tx = actual.TransactionResponses[0]; + + var rawTxEntity = new Terra2RawTransactionEntity + { + Id = 1, + Height = tx.HeightAsInt, + CreatedAt = tx.CreatedAt, + TxHash = tx.TransactionHash, + RawTx = JsonSerializer.Serialize(tx, TerraJsonSerializerOptions.GetThem()) + }; + + var ct = new CancellationToken(); + await _rawRepository.SaveRawTransactionAsync(rawTxEntity, ct); + + var txFromDb = await _rawRepository.GetByIdOrDefaultAsync(1, ct); + + Assert.NotNull(txFromDb); + Assert.Equal(201, txFromDb!.Height); + Assert.Equal("74B8212F209C7F225F79B7C0CA064160CC3C9D589A4F4AB6849071A0DAFED5A3", txFromDb.TxHash); + Assert.Equal(new DateTimeOffset(2022, 5, 28, 6, 26, 53, TimeSpan.Zero), txFromDb.CreatedAt); + + + var rawTxFromDb = JsonSerializer.Deserialize(txFromDb.RawTx)!; + + Assert.Equal("201", rawTxFromDb.Height); + Assert.Equal(201, rawTxFromDb.HeightAsInt); + Assert.Equal("74B8212F209C7F225F79B7C0CA064160CC3C9D589A4F4AB6849071A0DAFED5A3", rawTxFromDb.TransactionHash); + Assert.Empty(rawTxFromDb.CodeSpace); + Assert.Equal(0, rawTxFromDb.Code); + Assert.Equal("0A3C0A2A2F636F736D6F732E7374616B696E672E763162657461312E4D7367426567696E526564656C6567617465120E0A0C08ADE0B595061095B3998502", rawTxFromDb.Data); + Assert.Equal("[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.staking.v1beta1.MsgBeginRedelegate\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"module\",\"value\":\"staking\"},{\"key\":\"sender\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"}]},{\"type\":\"redelegate\",\"attributes\":[{\"key\":\"source_validator\",\"value\":\"terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx\"},{\"key\":\"destination_validator\",\"value\":\"terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt\"},{\"key\":\"amount\",\"value\":\"1932000000uluna\"},{\"key\":\"completion_time\",\"value\":\"2022-06-18T06:26:53Z\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]}]}]", rawTxFromDb.RawLog); + Assert.Single(rawTxFromDb.Logs); + + var log = rawTxFromDb.Logs[0]; + Assert.Equal(0, log.MessageIndex); + Assert.Empty(log.Log); + Assert.Equal(5, log.Events.Count); + + var logEvent2 = log.Events[1]; + Assert.Equal("coin_spent", logEvent2.Type); + Assert.Equal(2, logEvent2.Attributes.Count); + Assert.Equal("spender", logEvent2.Attributes[0].Key); + Assert.Equal("terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl", logEvent2.Attributes[0].Value); + Assert.Equal("amount", logEvent2.Attributes[1].Key); + Assert.Equal("22551uluna", logEvent2.Attributes[1].Value); + + Assert.Empty(rawTxFromDb.Info); + Assert.Equal("448586", rawTxFromDb.RawGasWanted); + Assert.Equal(448586, rawTxFromDb.GasWanted); + Assert.Equal("261855", rawTxFromDb.RawGasUsed); + Assert.Equal(261855, rawTxFromDb.GasUsed); + Assert.NotNull(rawTxFromDb.Transaction); + Assert.Equal(new DateTimeOffset(2022, 5, 28, 6, 26, 53, TimeSpan.Zero), rawTxFromDb.CreatedAt); + + Assert.Equal(14, rawTxFromDb.Events.Count); + + var event1 = rawTxFromDb.Events[0]; + Assert.Equal("coin_spent", event1.Type); + Assert.Equal(2, event1.Attributes.Count); + Assert.Equal("c3BlbmRlcg==", event1.Attributes[0].Key); + Assert.Equal("dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", event1.Attributes[0].Value); + Assert.True(event1.Attributes[0].Index); + Assert.Equal("YW1vdW50", event1.Attributes[1].Key); + Assert.Equal("NjcyODh1bHVuYQ==", event1.Attributes[1].Value); + Assert.True(event1.Attributes[1].Index); + + + var messages = rawTxFromDb.Transaction.Body.Messages; + Assert.Single(messages); + + var parseStatusResult = TerraMessageParser.TryParse(messages[0], out var parsedMessage); + Assert.True(parseStatusResult); + Assert.NotNull(parsedMessage); + + Assert.Equal("/cosmos.staking.v1beta1.MsgBeginRedelegate", parsedMessage!.Type); + var parsedRedelegateMessage = (CosmosRedelegateMessage)parsedMessage; + + Assert.Equal("terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0", parsedRedelegateMessage.DelegatorAddress); + Assert.Equal("terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx", parsedRedelegateMessage.ValidatorSourceAddress); + Assert.Equal("terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt", parsedRedelegateMessage.ValidatorDestinationAddress); + + Assert.NotNull(parsedRedelegateMessage.Amount); + Assert.Equal("uluna", parsedRedelegateMessage.Amount!.Denominator); + Assert.Equal("1932000000", parsedRedelegateMessage.Amount!.Amount); + //*/ + } +} diff --git a/test/UnitTests/TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponseTests.cs b/test/UnitTests/TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponseTests.cs new file mode 100644 index 0000000..a13f7e1 --- /dev/null +++ b/test/UnitTests/TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponseTests.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using SapientFi.Infrastructure.Terra2.Storage; +using TerraDotnet; +using TerraDotnet.TerraLcd.Messages; +using Xunit; + +namespace UnitTests.TerraDotnet.TerraLcd.Messages; + +public class GetTransactionsMatchingQueryResponseTests +{ + private GetTransactionsMatchingQueryResponse? GetParsedJson() + { + var json = File.ReadAllText("TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponse_01.json"); + + return JsonSerializer.Deserialize(json, + TerraJsonSerializerOptions.GetThem() + ); + } + + /* ************************************************************************************************************ + */ + [Fact] + public void CanDeserialize_txs() + { + var actual = GetParsedJson(); + + Assert.NotNull(actual); + + // transactions + Assert.Equal(3, actual!.Transactions.Count); + + var first = actual.Transactions[0]; + + Assert.NotNull(first.Body); + Assert.Single(first.Body.Messages); + Assert.Empty(first.Body.Memo); + Assert.Equal("0", first.Body.TimeoutHeight); + } + + + /* ************************************************************************************************************ + */ + [Fact] + public void CanDeserialize_tx_responses() + { + var actual = GetParsedJson(); + + Assert.NotNull(actual); + + // transactions + Assert.Equal(3, actual!.TransactionResponses.Count); + + var first = actual.TransactionResponses[0]; + + Assert.Equal("201", first.Height); + Assert.Equal(201, first.HeightAsInt); + Assert.Equal("74B8212F209C7F225F79B7C0CA064160CC3C9D589A4F4AB6849071A0DAFED5A3", first.TransactionHash); + Assert.Empty(first.CodeSpace); + Assert.Equal(0, first.Code); + Assert.Equal( + "0A3C0A2A2F636F736D6F732E7374616B696E672E763162657461312E4D7367426567696E526564656C6567617465120E0A0C08ADE0B595061095B3998502", + first.Data + ); + Assert.Equal( + "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.staking.v1beta1.MsgBeginRedelegate\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"module\",\"value\":\"staking\"},{\"key\":\"sender\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"}]},{\"type\":\"redelegate\",\"attributes\":[{\"key\":\"source_validator\",\"value\":\"terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx\"},{\"key\":\"destination_validator\",\"value\":\"terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt\"},{\"key\":\"amount\",\"value\":\"1932000000uluna\"},{\"key\":\"completion_time\",\"value\":\"2022-06-18T06:26:53Z\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]}]}]", + first.RawLog + ); + Assert.Single(first.Logs); + + var log = first.Logs[0]; + Assert.Equal(0, log.MessageIndex); + Assert.Empty(log.Log); + Assert.Equal(5, log.Events.Count); + + var logEvent2 = log.Events[1]; + Assert.Equal("coin_spent", logEvent2.Type); + Assert.Equal(2, logEvent2.Attributes.Count); + Assert.Equal("spender", logEvent2.Attributes[0].Key); + Assert.Equal("terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl", logEvent2.Attributes[0].Value); + Assert.Equal("amount", logEvent2.Attributes[1].Key); + Assert.Equal("22551uluna", logEvent2.Attributes[1].Value); + + Assert.Empty(first.Info); + Assert.Equal("448586", first.RawGasWanted); + Assert.Equal(448586, first.GasWanted); + Assert.Equal("261855", first.RawGasUsed); + Assert.Equal(261855, first.GasUsed); + Assert.NotNull(first.Transaction); + Assert.Equal(new DateTimeOffset(2022, 5, 28, 6, 26, 53, TimeSpan.Zero), first.CreatedAt); + + Assert.Equal(14, first.Events.Count); + + var event1 = first.Events[0]; + Assert.Equal("coin_spent", event1.Type); + Assert.Equal(2, event1.Attributes.Count); + Assert.Equal("c3BlbmRlcg==", event1.Attributes[0].Key); + Assert.Equal("dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", event1.Attributes[0].Value); + Assert.True(event1.Attributes[0].Index); + Assert.Equal("YW1vdW50", event1.Attributes[1].Key); + Assert.Equal("NjcyODh1bHVuYQ==", event1.Attributes[1].Value); + Assert.True(event1.Attributes[1].Index); + + + + var messages = first.Transaction.Body.Messages; + Assert.Single(messages); + + var parseStatusResult = TerraMessageParser.TryParse(messages[0], out var parsedMessage); + Assert.True(parseStatusResult); + Assert.NotNull(parsedMessage); + + Assert.Equal("/cosmos.staking.v1beta1.MsgBeginRedelegate", parsedMessage!.Type); + var parsedRedelegateMessage = (CosmosRedelegateMessage)parsedMessage; + + Assert.Equal("terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0", parsedRedelegateMessage.DelegatorAddress); + Assert.Equal("terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx", + parsedRedelegateMessage.ValidatorSourceAddress + ); + Assert.Equal("terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt", + parsedRedelegateMessage.ValidatorDestinationAddress + ); + + Assert.NotNull(parsedRedelegateMessage.Amount); + Assert.Equal("uluna", parsedRedelegateMessage.Amount!.Denominator); + Assert.Equal("1932000000", parsedRedelegateMessage.Amount!.Amount); + //*/ + } + + [Fact] + public void CanDeserialize_LcdTxResponse() + { + const int ID = 1; + var actual = GetParsedJson(); + + // transactions + Assert.Equal(3, actual!.TransactionResponses.Count); + + var tx = actual.TransactionResponses[0]; + + var rawTxEntity = new Terra2RawTransactionEntity + { + Id = ID, + Height = tx.HeightAsInt, + CreatedAt = tx.CreatedAt, + TxHash = tx.TransactionHash, + RawTx = JsonSerializer.Serialize(tx, TerraJsonSerializerOptions.GetThem()) + }; + + #region rawTransaction + var rawTransaction = rawTxEntity.RawTx; + + var deserializedTx = JsonSerializer.Deserialize(rawTransaction)!; + + Assert.Equal("201", deserializedTx.Height); + Assert.Equal(201, deserializedTx.HeightAsInt); + Assert.Equal("74B8212F209C7F225F79B7C0CA064160CC3C9D589A4F4AB6849071A0DAFED5A3", deserializedTx.TransactionHash); + Assert.Empty(deserializedTx.CodeSpace); + Assert.Equal(0, deserializedTx.Code); + Assert.Equal("0A3C0A2A2F636F736D6F732E7374616B696E672E763162657461312E4D7367426567696E526564656C6567617465120E0A0C08ADE0B595061095B3998502", deserializedTx.Data); + Assert.Equal("[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.staking.v1beta1.MsgBeginRedelegate\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"module\",\"value\":\"staking\"},{\"key\":\"sender\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"}]},{\"type\":\"redelegate\",\"attributes\":[{\"key\":\"source_validator\",\"value\":\"terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx\"},{\"key\":\"destination_validator\",\"value\":\"terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt\"},{\"key\":\"amount\",\"value\":\"1932000000uluna\"},{\"key\":\"completion_time\",\"value\":\"2022-06-18T06:26:53Z\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]}]}]", deserializedTx.RawLog); + Assert.Single(deserializedTx.Logs); + + var log = deserializedTx.Logs[0]; + Assert.Equal(0, log.MessageIndex); + Assert.Empty(log.Log); + Assert.Equal(5, log.Events.Count); + + var logEvent2 = log.Events[1]; + Assert.Equal("coin_spent", logEvent2.Type); + Assert.Equal(2, logEvent2.Attributes.Count); + Assert.Equal("spender", logEvent2.Attributes[0].Key); + Assert.Equal("terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl", logEvent2.Attributes[0].Value); + Assert.Equal("amount", logEvent2.Attributes[1].Key); + Assert.Equal("22551uluna", logEvent2.Attributes[1].Value); + + Assert.Empty(deserializedTx.Info); + Assert.Equal("448586", deserializedTx.RawGasWanted); + Assert.Equal(448586, deserializedTx.GasWanted); + Assert.Equal("261855", deserializedTx.RawGasUsed); + Assert.Equal(261855, deserializedTx.GasUsed); + Assert.NotNull(deserializedTx.Transaction); + Assert.Equal(new DateTimeOffset(2022, 5, 28, 6, 26, 53, TimeSpan.Zero), deserializedTx.CreatedAt); + + Assert.Equal(14, deserializedTx.Events.Count); + + var event1 = deserializedTx.Events[0]; + Assert.Equal("coin_spent", event1.Type); + Assert.Equal(2, event1.Attributes.Count); + Assert.Equal("c3BlbmRlcg==", event1.Attributes[0].Key); + Assert.Equal("dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", event1.Attributes[0].Value); + Assert.True(event1.Attributes[0].Index); + Assert.Equal("YW1vdW50", event1.Attributes[1].Key); + Assert.Equal("NjcyODh1bHVuYQ==", event1.Attributes[1].Value); + Assert.True(event1.Attributes[1].Index); + + List> messages = deserializedTx.Transaction.Body.Messages; + Assert.Single(messages); + + var parseStatusResult = TerraMessageParser.TryParse(messages[0], out var parsedMessage); + Assert.True(parseStatusResult); + Assert.NotNull(parsedMessage); + + Assert.Equal("/cosmos.staking.v1beta1.MsgBeginRedelegate", parsedMessage!.Type); + var parsedRedelegateMessage = (CosmosRedelegateMessage)parsedMessage; + + Assert.Equal("terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0", parsedRedelegateMessage.DelegatorAddress); + Assert.Equal("terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx", parsedRedelegateMessage.ValidatorSourceAddress); + Assert.Equal("terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt", parsedRedelegateMessage.ValidatorDestinationAddress); + + Assert.NotNull(parsedRedelegateMessage.Amount); + Assert.Equal("uluna", parsedRedelegateMessage.Amount!.Denominator); + Assert.Equal("1932000000", parsedRedelegateMessage.Amount!.Amount); + //*/ + #endregion + + /* **************************************************************** */ + + #region rawTxEntity + + Assert.Equal(201, rawTxEntity.Height); + Assert.Equal("74B8212F209C7F225F79B7C0CA064160CC3C9D589A4F4AB6849071A0DAFED5A3", rawTxEntity.TxHash); + + Assert.Equal(new DateTimeOffset(2022, 5, 28, 6, 26, 53, TimeSpan.Zero), rawTxEntity.CreatedAt); + + #endregion + } + + /* ************************************************************************************************************ + */ + [Fact] + public void CanDeserialize_pagination() + { + var actual = GetParsedJson(); + + Assert.NotNull(actual); + + Assert.NotNull(actual!.Pagination); + + Assert.Null(actual.Pagination.NextKey); + Assert.Equal("540", actual.Pagination.Total); + Assert.Equal(540, actual.Pagination.TotalAsInt); + } +} \ No newline at end of file diff --git a/test/UnitTests/TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponse_01.json b/test/UnitTests/TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponse_01.json new file mode 100644 index 0000000..5e69e8b --- /dev/null +++ b/test/UnitTests/TerraDotnet/TerraLcd/Messages/GetTransactionsMatchingQueryResponse_01.json @@ -0,0 +1,1262 @@ +{ + "txs": [ + { + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "delegator_address": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0", + "validator_src_address": "terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx", + "validator_dst_address": "terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt", + "amount": { + "denom": "uluna", + "amount": "1932000000" + } + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AiVuv8AMDt0fGGxF8DEcDhR28sflA+j8GfH5/D59FG1L" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_LEGACY_AMINO_JSON" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "67288" + } + ], + "gas_limit": "448586", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "0sIQZiBfcQrBzGA6eJe0OrdTx3GBhClUBsKYWSOmiUEwelf5uqJC0wNiuMXxhLdxdBSxpcE8Xg2wFR6UYrf4Ww==" + ] + }, + { + "body": { + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20", + "to_address": "terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm", + "amount": [ + { + "denom": "uluna", + "amount": "1000000" + } + ] + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AgLuDN/jos126YbtrTAxvL76/JZ8SxR+F/NM9yHh8U8G" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "21237" + } + ], + "gas_limit": "141580", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "iekH9rrafxakxEmbTQbpkg9h0KsHGkgUG0eZKVFgwKUi32FoZaa/UAS+n8wnCEQ1Wa5z66r93lOicd1reZjCBg==" + ] + }, + { + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgUndelegate", + "delegator_address": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu", + "validator_address": "terravaloper1wwq9mfkkqfta334d4hcjrjau9y50a4hw37zhdl", + "amount": { + "denom": "uluna", + "amount": "32454390" + } + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "A4bnZP/9hrCbP8gW3W1vNf3pp0ClUhHoY0TAhu8tMo4R" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_LEGACY_AMINO_JSON" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "53087" + } + ], + "gas_limit": "353908", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "7YN1xYnhJhFwqC/8DZQPhUNT4fTwZzhj4EGE7wTx1dtfOZgQCeWv6emVnLAf6EV5TSqnbIUN6jsvb/6Mtv69xw==" + ] + } + ], + "tx_responses": [ + { + "height": "201", + "txhash": "74B8212F209C7F225F79B7C0CA064160CC3C9D589A4F4AB6849071A0DAFED5A3", + "codespace": "", + "code": 0, + "data": "0A3C0A2A2F636F736D6F732E7374616B696E672E763162657461312E4D7367426567696E526564656C6567617465120E0A0C08ADE0B595061095B3998502", + "raw_log": "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.staking.v1beta1.MsgBeginRedelegate\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"module\",\"value\":\"staking\"},{\"key\":\"sender\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"}]},{\"type\":\"redelegate\",\"attributes\":[{\"key\":\"source_validator\",\"value\":\"terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx\"},{\"key\":\"destination_validator\",\"value\":\"terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt\"},{\"key\":\"amount\",\"value\":\"1932000000uluna\"},{\"key\":\"completion_time\",\"value\":\"2022-06-18T06:26:53Z\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"22551uluna\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "coin_received", + "attributes": [ + { + "key": "receiver", + "value": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0" + }, + { + "key": "amount", + "value": "22551uluna" + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "spender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "22551uluna" + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "action", + "value": "/cosmos.staking.v1beta1.MsgBeginRedelegate" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "module", + "value": "staking" + }, + { + "key": "sender", + "value": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0" + } + ] + }, + { + "type": "redelegate", + "attributes": [ + { + "key": "source_validator", + "value": "terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx" + }, + { + "key": "destination_validator", + "value": "terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt" + }, + { + "key": "amount", + "value": "1932000000uluna" + }, + { + "key": "completion_time", + "value": "2022-06-18T06:26:53Z" + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "recipient", + "value": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "22551uluna" + } + ] + } + ] + } + ], + "info": "", + "gas_wanted": "448586", + "gas_used": "261855", + "tx": { + "@type": "/cosmos.tx.v1beta1.Tx", + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "delegator_address": "terra1wcff43z8jweneamztk8uy74w3sy7rlmudxjdc0", + "validator_src_address": "terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx", + "validator_dst_address": "terravaloper10c04ysz9uznx2mkenuk3j3esjczyqh0j783nzt", + "amount": { + "denom": "uluna", + "amount": "1932000000" + } + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AiVuv8AMDt0fGGxF8DEcDhR28sflA+j8GfH5/D59FG1L" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_LEGACY_AMINO_JSON" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "67288" + } + ], + "gas_limit": "448586", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "0sIQZiBfcQrBzGA6eJe0OrdTx3GBhClUBsKYWSOmiUEwelf5uqJC0wNiuMXxhLdxdBSxpcE8Xg2wFR6UYrf4Ww==" + ] + }, + "timestamp": "2022-05-28T06:26:53Z", + "events": [ + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjcyODh1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjcyODh1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjcyODh1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": "NjcyODh1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzAvMA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "MHNJUVppQmZjUXJCekdBNmVKZTBPcmRUeDNHQmhDbFVCc0tZV1NPbWlVRXdlbGY1dXFKQzB3Tml1TVh4aExkeGRCU3hwY0U4WGcyd0ZSNlVZcmY0V3c9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnQmVnaW5SZWRlbGVnYXRl", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjI1NTF1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjI1NTF1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjI1NTF1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + } + ] + }, + { + "type": "redelegate", + "attributes": [ + { + "key": "c291cmNlX3ZhbGlkYXRvcg==", + "value": "dGVycmF2YWxvcGVyMWt0dTdhNndxbGs2dmx5d2Y0cnQ2d2ZjeHVwaGMwZXMyN3AwcXZ4", + "index": true + }, + { + "key": "ZGVzdGluYXRpb25fdmFsaWRhdG9y", + "value": "dGVycmF2YWxvcGVyMTBjMDR5c3o5dXpueDJta2VudWszajNlc2pjenlxaDBqNzgzbnp0", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTkzMjAwMDAwMHVsdW5h", + "index": true + }, + { + "key": "Y29tcGxldGlvbl90aW1l", + "value": "MjAyMi0wNi0xOFQwNjoyNjo1M1o=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "c3Rha2luZw==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExd2NmZjQzejhqd2VuZWFtenRrOHV5NzR3M3N5N3JsbXVkeGpkYzA=", + "index": true + } + ] + } + ] + }, + { + "height": "201", + "txhash": "D084544055655650C275E74F7B4C8CEAFF2EC12A3D613467E67D5033FC3E459E", + "codespace": "", + "code": 0, + "data": "0A1E0A1C2F636F736D6F732E62616E6B2E763162657461312E4D736753656E64", + "raw_log": "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm\"},{\"key\":\"amount\",\"value\":\"1000000uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20\"},{\"key\":\"amount\",\"value\":\"1000000uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm\"},{\"key\":\"sender\",\"value\":\"terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20\"},{\"key\":\"amount\",\"value\":\"1000000uluna\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "coin_received", + "attributes": [ + { + "key": "receiver", + "value": "terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm" + }, + { + "key": "amount", + "value": "1000000uluna" + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "spender", + "value": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20" + }, + { + "key": "amount", + "value": "1000000uluna" + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "action", + "value": "/cosmos.bank.v1beta1.MsgSend" + }, + { + "key": "sender", + "value": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20" + }, + { + "key": "module", + "value": "bank" + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "recipient", + "value": "terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm" + }, + { + "key": "sender", + "value": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20" + }, + { + "key": "amount", + "value": "1000000uluna" + } + ] + } + ] + } + ], + "info": "", + "gas_wanted": "141580", + "gas_used": "97104", + "tx": { + "@type": "/cosmos.tx.v1beta1.Tx", + "body": { + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "terra17dvkwc6srd82jk0eh96ayutf4fvwawjvrzkw20", + "to_address": "terra1zjwcdg9mh80xtu37vqqueae6juxfsd0f82aylm", + "amount": [ + { + "denom": "uluna", + "amount": "1000000" + } + ] + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AgLuDN/jos126YbtrTAxvL76/JZ8SxR+F/NM9yHh8U8G" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "21237" + } + ], + "gas_limit": "141580", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "iekH9rrafxakxEmbTQbpkg9h0KsHGkgUG0eZKVFgwKUi32FoZaa/UAS+n8wnCEQ1Wa5z66r93lOicd1reZjCBg==" + ] + }, + "timestamp": "2022-05-28T06:26:53Z", + "events": [ + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjEyMzd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjEyMzd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjEyMzd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": "MjEyMzd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjAvMA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "aWVrSDlycmFmeGFreEVtYlRRYnBrZzloMEtzSEdrZ1VHMGVaS1ZGZ3dLVWkzMkZvWmFhL1VBUytuOHduQ0VRMVdhNXo2NnI5M2xPaWNkMXJlWmpDQmc9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTAwMDAwMHVsdW5h", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExemp3Y2RnOW1oODB4dHUzN3ZxcXVlYWU2anV4ZnNkMGY4MmF5bG0=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTAwMDAwMHVsdW5h", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExemp3Y2RnOW1oODB4dHUzN3ZxcXVlYWU2anV4ZnNkMGY4MmF5bG0=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTAwMDAwMHVsdW5h", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExN2R2a3djNnNyZDgyamswZWg5NmF5dXRmNGZ2d2F3anZyemt3MjA=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + } + ] + }, + { + "height": "201", + "txhash": "C5967C255A473CE4D9D47CBE9DC1E32613ACBC5D98363EFF3754370E4F714627", + "codespace": "", + "code": 0, + "data": "0A370A252F636F736D6F732E7374616B696E672E763162657461312E4D7367556E64656C6567617465120E0A0C08ADE0B595061095B3998502", + "raw_log": "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu\"},{\"key\":\"amount\",\"value\":\"191uluna\"},{\"key\":\"receiver\",\"value\":\"terra1tygms3xhhs3yv487phx3dw4a95jn7t7l8l07dr\"},{\"key\":\"amount\",\"value\":\"32454390uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"191uluna\"},{\"key\":\"spender\",\"value\":\"terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh\"},{\"key\":\"amount\",\"value\":\"32454390uluna\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.staking.v1beta1.MsgUndelegate\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"sender\",\"value\":\"terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh\"},{\"key\":\"module\",\"value\":\"staking\"},{\"key\":\"sender\",\"value\":\"terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu\"},{\"key\":\"sender\",\"value\":\"terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl\"},{\"key\":\"amount\",\"value\":\"191uluna\"},{\"key\":\"recipient\",\"value\":\"terra1tygms3xhhs3yv487phx3dw4a95jn7t7l8l07dr\"},{\"key\":\"sender\",\"value\":\"terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh\"},{\"key\":\"amount\",\"value\":\"32454390uluna\"}]},{\"type\":\"unbond\",\"attributes\":[{\"key\":\"validator\",\"value\":\"terravaloper1wwq9mfkkqfta334d4hcjrjau9y50a4hw37zhdl\"},{\"key\":\"amount\",\"value\":\"32454390uluna\"},{\"key\":\"completion_time\",\"value\":\"2022-06-18T06:26:53Z\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "coin_received", + "attributes": [ + { + "key": "receiver", + "value": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu" + }, + { + "key": "amount", + "value": "191uluna" + }, + { + "key": "receiver", + "value": "terra1tygms3xhhs3yv487phx3dw4a95jn7t7l8l07dr" + }, + { + "key": "amount", + "value": "32454390uluna" + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "spender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "191uluna" + }, + { + "key": "spender", + "value": "terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh" + }, + { + "key": "amount", + "value": "32454390uluna" + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "action", + "value": "/cosmos.staking.v1beta1.MsgUndelegate" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "sender", + "value": "terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh" + }, + { + "key": "module", + "value": "staking" + }, + { + "key": "sender", + "value": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu" + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "recipient", + "value": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu" + }, + { + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, + { + "key": "amount", + "value": "191uluna" + }, + { + "key": "recipient", + "value": "terra1tygms3xhhs3yv487phx3dw4a95jn7t7l8l07dr" + }, + { + "key": "sender", + "value": "terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh" + }, + { + "key": "amount", + "value": "32454390uluna" + } + ] + }, + { + "type": "unbond", + "attributes": [ + { + "key": "validator", + "value": "terravaloper1wwq9mfkkqfta334d4hcjrjau9y50a4hw37zhdl" + }, + { + "key": "amount", + "value": "32454390uluna" + }, + { + "key": "completion_time", + "value": "2022-06-18T06:26:53Z" + } + ] + } + ] + } + ], + "info": "", + "gas_wanted": "353908", + "gas_used": "194239", + "tx": { + "@type": "/cosmos.tx.v1beta1.Tx", + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgUndelegate", + "delegator_address": "terra1v8n93xnsth6xlknnyprgs3wecv0j2ve7xgx8hu", + "validator_address": "terravaloper1wwq9mfkkqfta334d4hcjrjau9y50a4hw37zhdl", + "amount": { + "denom": "uluna", + "amount": "32454390" + } + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "A4bnZP/9hrCbP8gW3W1vNf3pp0ClUhHoY0TAhu8tMo4R" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_LEGACY_AMINO_JSON" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uluna", + "amount": "53087" + } + ], + "gas_limit": "353908", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "7YN1xYnhJhFwqC/8DZQPhUNT4fTwZzhj4EGE7wTx1dtfOZgQCeWv6emVnLAf6EV5TSqnbIUN6jsvb/6Mtv69xw==" + ] + }, + "timestamp": "2022-05-28T06:26:53Z", + "events": [ + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NTMwODd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NTMwODd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NTMwODd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": "NTMwODd1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHUvMA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "N1lOMXhZbmhKaEZ3cUMvOERaUVBoVU5UNGZUd1p6aGo0RUdFN3dUeDFkdGZPWmdRQ2VXdjZlbVZuTEFmNkVWNVRTcW5iSVVONmpzdmIvNk10djY5eHc9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnVW5kZWxlZ2F0ZQ==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTkxdWx1bmE=", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTkxdWx1bmE=", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTkxdWx1bmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExanY2NXMzZ3JxZjZ2NmpsM2RwNHQ2Yzl0OXJrOTljZDhwbTd1dGw=", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "dGVycmExZmw0OHZzbm1zZHpjdjg1cTVkMnE0ejVhamRoYTh5dTNubG4wbWg=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzI0NTQzOTB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "dGVycmExdHlnbXMzeGhoczN5djQ4N3BoeDNkdzRhOTVqbjd0N2w4bDA3ZHI=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzI0NTQzOTB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "dGVycmExdHlnbXMzeGhoczN5djQ4N3BoeDNkdzRhOTVqbjd0N2w4bDA3ZHI=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExZmw0OHZzbm1zZHpjdjg1cTVkMnE0ejVhamRoYTh5dTNubG4wbWg=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzI0NTQzOTB1bHVuYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "dGVycmExZmw0OHZzbm1zZHpjdjg1cTVkMnE0ejVhamRoYTh5dTNubG4wbWg=", + "index": true + } + ] + }, + { + "type": "unbond", + "attributes": [ + { + "key": "dmFsaWRhdG9y", + "value": "dGVycmF2YWxvcGVyMXd3cTltZmtrcWZ0YTMzNGQ0aGNqcmphdTl5NTBhNGh3Mzd6aGRs", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzI0NTQzOTB1bHVuYQ==", + "index": true + }, + { + "key": "Y29tcGxldGlvbl90aW1l", + "value": "MjAyMi0wNi0xOFQwNjoyNjo1M1o=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "c3Rha2luZw==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "dGVycmExdjhuOTN4bnN0aDZ4bGtubnlwcmdzM3dlY3YwajJ2ZTd4Z3g4aHU=", + "index": true + } + ] + } + ] + } + ], + "pagination": { + "next_key": null, + "total": "540" + } +} \ No newline at end of file diff --git a/test/UnitTests/TerraDotnet/TerraLcd/Messages/RawTransaction_D670_01.json b/test/UnitTests/TerraDotnet/TerraLcd/Messages/RawTransaction_D670_01.json new file mode 100644 index 0000000..dfbf6e3 --- /dev/null +++ b/test/UnitTests/TerraDotnet/TerraLcd/Messages/RawTransaction_D670_01.json @@ -0,0 +1,259 @@ +{ + "Code": 0, + "Data": "0A250A232F636F736D6F732E7374616B696E672E763162657461312E4D736744656C6567617465", + "Info": "", + "Logs": [ + { + "Log": "", + "Events": [ + { + "Type": "coin_received", + "Attributes": [ + { + "Key": "receiver", + "Value": "terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh" + }, + { + "Key": "amount", + "Value": "150000000uluna" + } + ] + }, + { + "Type": "coin_spent", + "Attributes": [ + { + "Key": "spender", + "Value": "terra187reejg4et24jpggqt55vnc47l926w0p530fkt" + }, + { + "Key": "amount", + "Value": "150000000uluna" + } + ] + }, + { + "Type": "delegate", + "Attributes": [ + { + "Key": "validator", + "Value": "terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx" + }, + { + "Key": "amount", + "Value": "150000000uluna" + }, + { + "Key": "new_shares", + "Value": "150000000.000000000000000000" + } + ] + }, + { + "Type": "message", + "Attributes": [ + { + "Key": "action", + "Value": "/cosmos.staking.v1beta1.MsgDelegate" + }, + { + "Key": "module", + "Value": "staking" + }, + { + "Key": "sender", + "Value": "terra187reejg4et24jpggqt55vnc47l926w0p530fkt" + } + ] + } + ], + "MessageIndex": 0 + } + ], + "Events": [ + { + "Type": "coin_spent", + "Attributes": [ + { + "Key": "c3BlbmRlcg==", + "Index": true, + "Value": "dGVycmExODdyZWVqZzRldDI0anBnZ3F0NTV2bmM0N2w5MjZ3MHA1MzBma3Q=" + }, + { + "Key": "YW1vdW50", + "Index": true, + "Value": "MzAwMDB1bHVuYQ==" + } + ] + }, + { + "Type": "coin_received", + "Attributes": [ + { + "Key": "cmVjZWl2ZXI=", + "Index": true, + "Value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=" + }, + { + "Key": "YW1vdW50", + "Index": true, + "Value": "MzAwMDB1bHVuYQ==" + } + ] + }, + { + "Type": "transfer", + "Attributes": [ + { + "Key": "cmVjaXBpZW50", + "Index": true, + "Value": "dGVycmExN3hwZnZha20yYW1nOTYyeWxzNmY4NHoza2VsbDhjNWxrYWVxZmE=" + }, + { + "Key": "c2VuZGVy", + "Index": true, + "Value": "dGVycmExODdyZWVqZzRldDI0anBnZ3F0NTV2bmM0N2w5MjZ3MHA1MzBma3Q=" + }, + { + "Key": "YW1vdW50", + "Index": true, + "Value": "MzAwMDB1bHVuYQ==" + } + ] + }, + { + "Type": "message", + "Attributes": [ + { + "Key": "c2VuZGVy", + "Index": true, + "Value": "dGVycmExODdyZWVqZzRldDI0anBnZ3F0NTV2bmM0N2w5MjZ3MHA1MzBma3Q=" + } + ] + }, + { + "Type": "tx", + "Attributes": [ + { + "Key": "ZmVl", + "Index": true, + "Value": "MzAwMDB1bHVuYQ==" + } + ] + }, + { + "Type": "tx", + "Attributes": [ + { + "Key": "YWNjX3NlcQ==", + "Index": true, + "Value": "dGVycmExODdyZWVqZzRldDI0anBnZ3F0NTV2bmM0N2w5MjZ3MHA1MzBma3QvMA==" + } + ] + }, + { + "Type": "tx", + "Attributes": [ + { + "Key": "c2lnbmF0dXJl", + "Index": true, + "Value": "d2EvQldHRUhOWUsxQTFuNGRIMlZGYmg4aEMwMzBKYkUxR0FsSFh5bGNBUXNvVmFIM0ZxTlBweStScHV6REY1bFJEQXQ3VG5qOXcyOUNDOUc3Wlk3bVE9PQ==" + } + ] + }, + { + "Type": "message", + "Attributes": [ + { + "Key": "YWN0aW9u", + "Index": true, + "Value": "L2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGU=" + } + ] + }, + { + "Type": "coin_spent", + "Attributes": [ + { + "Key": "c3BlbmRlcg==", + "Index": true, + "Value": "dGVycmExODdyZWVqZzRldDI0anBnZ3F0NTV2bmM0N2w5MjZ3MHA1MzBma3Q=" + }, + { + "Key": "YW1vdW50", + "Index": true, + "Value": "MTUwMDAwMDAwdWx1bmE=" + } + ] + }, + { + "Type": "coin_received", + "Attributes": [ + { + "Key": "cmVjZWl2ZXI=", + "Index": true, + "Value": "dGVycmExZmw0OHZzbm1zZHpjdjg1cTVkMnE0ejVhamRoYTh5dTNubG4wbWg=" + }, + { + "Key": "YW1vdW50", + "Index": true, + "Value": "MTUwMDAwMDAwdWx1bmE=" + } + ] + }, + { + "Type": "delegate", + "Attributes": [ + { + "Key": "dmFsaWRhdG9y", + "Index": true, + "Value": "dGVycmF2YWxvcGVyMWt0dTdhNndxbGs2dmx5d2Y0cnQ2d2ZjeHVwaGMwZXMyN3AwcXZ4" + }, + { + "Key": "YW1vdW50", + "Index": true, + "Value": "MTUwMDAwMDAwdWx1bmE=" + }, + { + "Key": "bmV3X3NoYXJlcw==", + "Index": true, + "Value": "MTUwMDAwMDAwLjAwMDAwMDAwMDAwMDAwMDAwMA==" + } + ] + }, + { + "Type": "message", + "Attributes": [ + { + "Key": "bW9kdWxl", + "Index": true, + "Value": "c3Rha2luZw==" + }, + { + "Key": "c2VuZGVy", + "Index": true, + "Value": "dGVycmExODdyZWVqZzRldDI0anBnZ3F0NTV2bmM0N2w5MjZ3MHA1MzBma3Q=" + } + ] + } + ], + "Height": "2", + "RawLog": "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"terra1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3nln0mh\"},{\"key\":\"amount\",\"value\":\"150000000uluna\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"terra187reejg4et24jpggqt55vnc47l926w0p530fkt\"},{\"key\":\"amount\",\"value\":\"150000000uluna\"}]},{\"type\":\"delegate\",\"attributes\":[{\"key\":\"validator\",\"value\":\"terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx\"},{\"key\":\"amount\",\"value\":\"150000000uluna\"},{\"key\":\"new_shares\",\"value\":\"150000000.000000000000000000\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.staking.v1beta1.MsgDelegate\"},{\"key\":\"module\",\"value\":\"staking\"},{\"key\":\"sender\",\"value\":\"terra187reejg4et24jpggqt55vnc47l926w0p530fkt\"}]}]}]", + "GasUsed": 162215, + "CodeSpace": "", + "CreatedAt": "/Date(1653717617000)/", + "GasWanted": 200000, + "RawGasUsed": "162215", + "HeightAsInt": 2, + "Transaction": { + "Body": { + "Memo": "", + "Messages": [ + "{\n \"@type\": \"/cosmos.staking.v1beta1.MsgDelegate\",\n \"delegator_address\": \"terra187reejg4et24jpggqt55vnc47l926w0p530fkt\",\n \"validator_address\": \"terravaloper1ktu7a6wqlk6vlywf4rt6wfcxuphc0es27p0qvx\",\n \"amount\": {\n \"denom\": \"uluna\",\n \"amount\": \"150000000\"\n }\n }" + ], + "TimeoutHeight": "0" + } + }, + "RawGasWanted": "200000", + "TransactionHash": "D6704E3E53C0514A3AAF2C4359CFD3EA33F62E3223C66D7098672F524F1DB644" +} \ No newline at end of file diff --git a/test/UnitTests/TerraDotnet/TerraLcd/Transactions/RawTransactionIndexerTests.cs b/test/UnitTests/TerraDotnet/TerraLcd/Transactions/RawTransactionIndexerTests.cs new file mode 100644 index 0000000..7c421fd --- /dev/null +++ b/test/UnitTests/TerraDotnet/TerraLcd/Transactions/RawTransactionIndexerTests.cs @@ -0,0 +1,32 @@ +using System.IO; +using System.Text.Json; +using MassTransit; +using TerraDotnet; +using TerraDotnet.TerraLcd.Messages; + +namespace UnitTests.TerraDotnet.TerraLcd.Transactions; + +public class RawTransactionIndexerTests +{ + private readonly IBus _massTransitBus; + + public RawTransactionIndexerTests( + IBus massTransitBus + ) + { + _massTransitBus = massTransitBus; + } + + private GetTransactionsMatchingQueryResponse? GetParsedJson() + { + var json = File.ReadAllText("TerraDotnet/TerraLcd/Messages/RawTransaction_D670_01.json"); + + return JsonSerializer.Deserialize( + json, + TerraJsonSerializerOptions.GetThem() + ); + } + + + +} \ No newline at end of file diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 761dd82..444e7a2 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -70,6 +70,9 @@ Always + + Always + diff --git a/thunder-tests/thunderclient.json b/thunder-tests/thunderclient.json index 5018fd5..919f074 100644 --- a/thunder-tests/thunderclient.json +++ b/thunder-tests/thunderclient.json @@ -4,13 +4,127 @@ "colId": "6404331f-4dc7-4360-8741-960c7fb00bc1", "containerId": "", "name": "Transactions from specific block", - "url": "{{terra2BaseUrl}}/cosmos/tx/v1beta1/txs/block/352708", + "url": "{{terra2BaseUrl}}/cosmos/tx/v1beta1/txs/block/141", "method": "GET", "sortNum": 10000, "created": "2022-06-21T12:35:09.514Z", - "modified": "2022-06-21T12:35:44.158Z", + "modified": "2022-06-24T13:26:58.804Z", "headers": [], "params": [], "tests": [] + }, + { + "_id": "c7bcf9a9-78f0-4b92-95c2-e8c6ee38cb44", + "colId": "6404331f-4dc7-4360-8741-960c7fb00bc1", + "containerId": "", + "name": "Transactions before block", + "url": "{{terra2BaseUrl}}/cosmos/tx/v1beta1/txs?events=tx.height<=100", + "method": "GET", + "sortNum": 20000, + "created": "2022-06-22T08:23:40.974Z", + "modified": "2022-06-22T08:24:17.802Z", + "headers": [], + "params": [ + { + "name": "events", + "value": "tx.height<=100", + "isPath": false + } + ], + "tests": [] + }, + { + "_id": "467681eb-c185-486c-9b27-0569f8c89102", + "colId": "6404331f-4dc7-4360-8741-960c7fb00bc1", + "containerId": "", + "name": "Transactions in block window", + "url": "{{terra2BaseUrl}}/cosmos/tx/v1beta1/txs?events=tx.height>=100&events=tx.height<=200&pagination.limit=100&pagination.offset=0", + "method": "GET", + "sortNum": 30000, + "created": "2022-06-22T08:24:27.911Z", + "modified": "2022-06-24T14:35:56.000Z", + "headers": [], + "params": [ + { + "name": "events", + "value": "tx.height>=100", + "isPath": false + }, + { + "name": "events", + "value": "tx.height<=200", + "isPath": false + }, + { + "name": "pagination.limit", + "value": "100", + "isPath": false + }, + { + "name": "pagination.offset", + "value": "0", + "isPath": false + }, + { + "name": "pagination.key", + "value": "", + "isDisabled": true, + "isPath": false + }, + { + "name": "order_by", + "value": "ORDER_BY_ASC", + "isDisabled": true, + "isPath": false + } + ], + "tests": [] + }, + { + "_id": "9023d2b9-d47a-4e44-957e-18951a4d73ee", + "colId": "6404331f-4dc7-4360-8741-960c7fb00bc1", + "containerId": "", + "name": "Transactions in using single block window", + "url": "{{terra2BaseUrl}}/cosmos/tx/v1beta1/txs?events=tx.height>=24&events=tx.height<=24&pagination.limit=100&pagination.offset=0", + "method": "GET", + "sortNum": 40000, + "created": "2022-06-24T14:37:25.407Z", + "modified": "2022-07-01T08:31:47.336Z", + "headers": [], + "params": [ + { + "name": "events", + "value": "tx.height>=24", + "isPath": false + }, + { + "name": "events", + "value": "tx.height<=24", + "isPath": false + }, + { + "name": "pagination.limit", + "value": "100", + "isPath": false + }, + { + "name": "pagination.offset", + "value": "0", + "isPath": false + }, + { + "name": "pagination.key", + "value": "", + "isDisabled": true, + "isPath": false + }, + { + "name": "order_by", + "value": "ORDER_BY_ASC", + "isDisabled": true, + "isPath": false + } + ], + "tests": [] } ] \ No newline at end of file