Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add ThirdwebInsight.GetTransactions API #134

Merged
merged 1 commit into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions Thirdweb.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@

#region Indexer

// // Create a ThirdwebInsight instance
// var insight = await ThirdwebInsight.Create(client);
// Create a ThirdwebInsight instance
var insight = await ThirdwebInsight.Create(client);

// // Setup some filters
// var address = await Utils.GetAddressFromENS(client, "vitalik.eth");
Expand Down Expand Up @@ -78,6 +78,17 @@
// );
// Console.WriteLine($"Events: {JsonConvert.SerializeObject(events, Formatting.Indented)}");

// // Fetch transactions (great amount of optional filters available)
// var transactions = await insight.GetTransactions(
// chainIds: new BigInteger[] { 1 }, // ethereum
// contractAddress: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", // bored apes
// fromTimestamp: Utils.GetUnixTimeStampNow() - 3600, // last hour
// sortBy: SortBy.TransactionIndex, // block number, block timestamp or transaction index
// sortOrder: SortOrder.Desc, // latest first
// limit: 5 // last 5 transactions
// );
// Console.WriteLine($"Transactions: {JsonConvert.SerializeObject(transactions, Formatting.Indented)}");

#endregion

#region AI
Expand Down
96 changes: 95 additions & 1 deletion Thirdweb/Thirdweb.Indexer/ThirdwebInsight.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ public class Token

public class Token_ERC20 : Token { }

public class Token_ERC721 : Token { }
public class Token_ERC721 : Token
{
[JsonProperty("tokenId", Required = Required.Always)]
public BigInteger TokenId { get; set; }
}

public class Token_ERC1155 : Token
{
Expand Down Expand Up @@ -81,6 +85,96 @@ public class Event
public Decoded Decoded { get; set; } = null!;
}

public class Transaction
{
[JsonProperty("chain_id", Required = Required.Always)]
public BigInteger ChainId { get; set; }

[JsonProperty("block_number", Required = Required.Always)]
public string BlockNumber { get; set; } = null!;

[JsonProperty("block_hash", Required = Required.Always)]
public string BlockHash { get; set; } = null!;

[JsonProperty("block_timestamp", Required = Required.Always)]
public string BlockTimestamp { get; set; } = null!;

[JsonProperty("hash", Required = Required.Always)]
public string Hash { get; set; } = null!;

[JsonProperty("nonce", Required = Required.Always)]
public BigInteger Nonce { get; set; }

[JsonProperty("transaction_index", Required = Required.Always)]
public BigInteger TransactionIndex { get; set; }

[JsonProperty("from_address", Required = Required.Always)]
public string FromAddress { get; set; } = null!;

[JsonProperty("to_address", Required = Required.Always)]
public string ToAddress { get; set; } = null!;

[JsonProperty("value", Required = Required.Always)]
public BigInteger Value { get; set; }

[JsonProperty("gas_price", Required = Required.Always)]
public BigInteger GasPrice { get; set; }

[JsonProperty("gas", Required = Required.Always)]
public BigInteger Gas { get; set; }

[JsonProperty("function_selector", Required = Required.Always)]
public string FunctionSelector { get; set; } = null!;

[JsonProperty("data", Required = Required.Always)]
public string Data { get; set; } = null!;

[JsonProperty("max_fee_per_gas", Required = Required.Always)]
public BigInteger MaxFeePerGas { get; set; }

[JsonProperty("max_priority_fee_per_gas", Required = Required.Always)]
public BigInteger MaxPriorityFeePerGas { get; set; }

[JsonProperty("transaction_type", Required = Required.Always)]
public BigInteger TransactionType { get; set; }

[JsonProperty("r", Required = Required.Always)]
public BigInteger R { get; set; }

[JsonProperty("s", Required = Required.Always)]
public BigInteger S { get; set; }

[JsonProperty("v", Required = Required.Always)]
public BigInteger V { get; set; }

[JsonProperty("access_list_json")]
public string AccessListJson { get; set; }

[JsonProperty("contract_address")]
public string ContractAddress { get; set; }

[JsonProperty("gas_used")]
public BigInteger? GasUsed { get; set; }

[JsonProperty("cumulative_gas_used")]
public BigInteger? CumulativeGasUsed { get; set; }

[JsonProperty("effective_gas_price")]
public BigInteger? EffectiveGasPrice { get; set; }

[JsonProperty("blob_gas_used")]
public BigInteger? BlobGasUsed { get; set; }

[JsonProperty("blob_gas_price")]
public BigInteger? BlobGasPrice { get; set; }

[JsonProperty("logs_bloom")]
public string LogsBloom { get; set; }

[JsonProperty("status")]
public BigInteger? Status { get; set; }
}

public class Decoded
{
[JsonProperty("name")]
Expand Down
92 changes: 92 additions & 0 deletions Thirdweb/Thirdweb.Indexer/ThirdwebInsight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public class InsightEvents
public Meta Meta { get; set; }
}

public class InsightTransactions
{
public Transaction[] Transactions { get; set; }
public Meta Meta { get; set; }
}

public class ThirdwebInsight
{
private readonly IThirdwebHttpClient _httpClient;
Expand Down Expand Up @@ -234,6 +240,92 @@ public async Task<InsightEvents> GetEvents(
return new InsightEvents { Events = result.Data, Meta = result.Meta, };
}

/// <summary>
/// Get transactions, optionally filtered by contract address, signature, and more.
/// </summary>
/// <param name="chainIds">The chain IDs to get the transactions from.</param>
/// <param name="contractAddress">The contract address to get the transactions from. (Optional)</param>
/// <param name="signature">The signature to filter transactions by. (Optional)</param>
/// <param name="fromBlock">The starting block number to get the transactions from. (Optional, if provided, said block is included in query)</param>
/// <param name="toBlock">The ending block number to get the transactions from. (Optional, if provided, said block is included in query)</param>
/// <param name="fromTimestamp">The starting block timestamp to get the transactions from. (Optional, if provided, said block is included in query)</param>
/// <param name="toTimestamp">The ending block timestamp to get the transactions from. (Optional, if provided, said block is included in query)</param>
/// <param name="sortBy">The field to sort the transactions by. (Default: BlockNumber)</param>
/// <param name="sortOrder">The order to sort the transactions by. (Default: Desc)</param>
/// <param name="limit">The number of transactions to return. (Default: 20)</param>
/// <param name="page">The page number to return. (Default: 0)</param>
/// <param name="decode">Whether to decode the transactions. (Default: true)</param>
/// <returns>The transactions and metadata as an instance of <see cref="InsightTransactions"/>.</returns>
/// <exception cref="ArgumentException">Thrown when a signature is provided without a contract address.</exception>
/// /// <exception cref="ArgumentException">Thrown when no chain IDs are provided.</exception>
public async Task<InsightTransactions> GetTransactions(
BigInteger[] chainIds,
string contractAddress = null,
string signature = null,
BigInteger? fromBlock = null,
BigInteger? toBlock = null,
BigInteger? fromTimestamp = null,
BigInteger? toTimestamp = null,
SortBy sortBy = SortBy.BlockNumber,
SortOrder sortOrder = SortOrder.Desc,
int limit = 20,
int page = 0,
bool decode = true
)
{
if (!string.IsNullOrEmpty(signature) && string.IsNullOrEmpty(contractAddress))
{
throw new ArgumentException("Contract address must be provided when signature is provided.");
}

if (chainIds.Length == 0)
{
throw new ArgumentException("At least one chain ID must be provided.", nameof(chainIds));
}

var baseUrl = $"{Constants.INSIGHT_API_URL}/v1/transactions";
var url = AppendChains(
!string.IsNullOrEmpty(contractAddress)
? !string.IsNullOrEmpty(signature)
? $"{baseUrl}/{contractAddress}/{signature}"
: $"{baseUrl}/{contractAddress}"
: baseUrl,
chainIds
);

url += $"&sort_by={SortByToString(sortBy)}";
url += $"&sort_order={SortOrderToString(sortOrder)}";
url += $"&limit={limit}";
url += $"&page={page}";
url += $"&decode={decode}";

if (fromBlock.HasValue)
{
url += $"&filter_block_number_gte={fromBlock}";
}

if (toBlock.HasValue)
{
url += $"&filter_block_number_lte={toBlock}";
}

if (fromTimestamp.HasValue)
{
url += $"&filter_block_timestamp_gte={fromTimestamp}";
}

if (toTimestamp.HasValue)
{
url += $"&filter_block_timestamp_lte={toTimestamp}";
}

var response = await this._httpClient.GetAsync(url).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject<ResponseModel<Transaction>>(responseContent);
return new InsightTransactions { Transactions = result.Data, Meta = result.Meta, };
}

private static string AppendChains(string url, BigInteger[] chainIds)
{
return $"{url}?chain={string.Join("&chain=", chainIds)}";
Expand Down