-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Qol improvements: added base client to be used by client libraries, a…
…dded more ser/des methods, fixed small issue in token mint resolver. (#259)
- Loading branch information
Showing
5 changed files
with
361 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
using Solnet.Programs; | ||
using Solnet.Programs.Models; | ||
using Solnet.Rpc; | ||
using Solnet.Rpc.Builders; | ||
using Solnet.Rpc.Core.Http; | ||
using Solnet.Rpc.Core.Sockets; | ||
using Solnet.Rpc.Messages; | ||
using Solnet.Rpc.Models; | ||
using Solnet.Rpc.Types; | ||
using Solnet.Wallet; | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace Solnet.Programs.Abstract | ||
{ | ||
/// <summary> | ||
/// Implements the base client | ||
/// </summary> | ||
public abstract class BaseClient | ||
{ | ||
/// <summary> | ||
/// The RPC client. | ||
/// </summary> | ||
public IRpcClient RpcClient { get; init; } | ||
|
||
/// <summary> | ||
/// The streaming RPC client. | ||
/// </summary> | ||
public IStreamingRpcClient StreamingRpcClient { get; init; } | ||
|
||
/// <summary> | ||
/// Initialize the base client with the given RPC clients. | ||
/// </summary> | ||
/// <param name="rpcClient">The RPC client instance.</param> | ||
/// <param name="streamingRpcClient">The streaming RPC client instance.</param> | ||
protected BaseClient(IRpcClient rpcClient, IStreamingRpcClient streamingRpcClient) | ||
{ | ||
RpcClient = rpcClient; | ||
StreamingRpcClient = streamingRpcClient; | ||
} | ||
|
||
/// <summary> | ||
/// Deserializes the given byte array into the specified type. | ||
/// </summary> | ||
/// <param name="data">The data to deserialize into the specified type.</param> | ||
/// <typeparam name="T">The type.</typeparam> | ||
/// <returns>An instance of the specified type or null in case it was unable to deserialize.</returns> | ||
private static T DeserializeAccount<T>(byte[] data) where T : class | ||
{ | ||
System.Reflection.MethodInfo m = typeof(T).GetMethod("Deserialize", | ||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static, | ||
null, new[] { typeof(byte[]) }, null); | ||
|
||
if (m == null) | ||
return null; | ||
return (T)m.Invoke(null, new object[] { data }); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the account info for the given account address and attempts to deserialize the account data into the specified type. | ||
/// </summary> | ||
/// <param name="programAddress">The program account address.</param> | ||
/// <param name="commitment">The commitment parameter for the RPC request.</param> | ||
/// <param name="filters">The filters to apply.</param> | ||
/// <param name="dataSize">The expected account data size.</param> | ||
/// <typeparam name="T">The specified type.</typeparam> | ||
/// <returns>A <see cref="ResultWrapper{T, T2}"/> containing the RPC response and the deserialized account if successful.</returns> | ||
protected async Task<ProgramAccountsResultWrapper<List<T>>> GetProgramAccounts<T>(string programAddress, List<MemCmp> filters, | ||
int? dataSize = null, Commitment commitment = Commitment.Finalized) where T : class | ||
{ | ||
RequestResult<List<AccountKeyPair>> res = | ||
await RpcClient.GetProgramAccountsAsync(programAddress, commitment, dataSize, filters); | ||
|
||
if (!res.WasSuccessful || !(res.Result?.Count > 0)) | ||
return new ProgramAccountsResultWrapper<List<T>>(res); | ||
|
||
List<T> resultingAccounts = new(res.Result.Count); | ||
resultingAccounts.AddRange(res.Result.Select(result => | ||
DeserializeAccount<T>(Convert.FromBase64String(result.Account.Data[0])))); | ||
|
||
return new ProgramAccountsResultWrapper<List<T>>(res, resultingAccounts); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the account info for the given account address and attempts to deserialize the account data into the specified type. | ||
/// </summary> | ||
/// <param name="accountAddresses">The list of account addresses to fetch.</param> | ||
/// <param name="commitment">The commitment parameter for the RPC request.</param> | ||
/// <typeparam name="T">The specified type.</typeparam> | ||
/// <returns>A <see cref="ResultWrapper{T, T2}"/> containing the RPC response and the deserialized account if successful.</returns> | ||
protected async Task<MultipleAccountsResultWrapper<List<T>>> GetMultipleAccounts<T>(List<string> accountAddresses, | ||
Commitment commitment = Commitment.Finalized) where T : class | ||
{ | ||
RequestResult<ResponseValue<List<AccountInfo>>> res = | ||
await RpcClient.GetMultipleAccountsAsync(accountAddresses, commitment); | ||
|
||
if (!res.WasSuccessful || !(res.Result?.Value?.Count > 0)) | ||
return new MultipleAccountsResultWrapper<List<T>>(res); | ||
|
||
List<T> resultingAccounts = new(res.Result.Value.Count); | ||
resultingAccounts.AddRange(res.Result.Value.Select(result => | ||
DeserializeAccount<T>(Convert.FromBase64String(result.Data[0])))); | ||
|
||
return new MultipleAccountsResultWrapper<List<T>>(res, resultingAccounts); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the account info for the given account address and attempts to deserialize the account data into the specified type. | ||
/// </summary> | ||
/// <param name="accountAddress">The account address.</param> | ||
/// <param name="commitment">The commitment parameter for the RPC request.</param> | ||
/// <typeparam name="T">The specified type.</typeparam> | ||
/// <returns>A <see cref="ResultWrapper{T, T2}"/> containing the RPC response and the deserialized account if successful.</returns> | ||
protected async Task<AccountResultWrapper<T>> GetAccount<T>(string accountAddress, | ||
Commitment commitment = Commitment.Finalized) where T : class | ||
{ | ||
RequestResult<ResponseValue<AccountInfo>> res = | ||
await RpcClient.GetAccountInfoAsync(accountAddress, commitment); | ||
|
||
if (res.WasSuccessful && res.Result?.Value?.Data?.Count > 0) | ||
{ | ||
return new AccountResultWrapper<T>(res, | ||
DeserializeAccount<T>(Convert.FromBase64String(res.Result.Value.Data[0]))); | ||
} | ||
|
||
return new AccountResultWrapper<T>(res); | ||
} | ||
|
||
/// <summary> | ||
/// Subscribes to notifications on changes to the given account and deserializes the account data into the specified type. | ||
/// </summary> | ||
/// <param name="accountAddress">The account address.</param> | ||
/// <param name="commitment">The commitment parameter for the RPC request.</param> | ||
/// <param name="callback">An action that is called when a notification is received</param> | ||
/// <typeparam name="T">The specified type.</typeparam> | ||
/// <returns>The subscription state.</returns> | ||
protected async Task<SubscriptionState> SubscribeAccount<T>(string accountAddress, | ||
Action<SubscriptionState, ResponseValue<AccountInfo>, T> callback, | ||
Commitment commitment = Commitment.Finalized) where T : class | ||
{ | ||
SubscriptionState res = await StreamingRpcClient.SubscribeAccountInfoAsync(accountAddress, | ||
(s, e) => | ||
{ | ||
T parsingResult = null; | ||
if (e.Value?.Data?.Count > 0) | ||
parsingResult = DeserializeAccount<T>(Convert.FromBase64String(e.Value.Data[0])); | ||
callback(s, e, parsingResult); | ||
}, commitment); | ||
|
||
return res; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
using Solnet.Rpc.Core.Http; | ||
using Solnet.Rpc.Core.Sockets; | ||
using Solnet.Rpc.Messages; | ||
using Solnet.Rpc.Models; | ||
using Solnet.Wallet; | ||
using System.Collections.Generic; | ||
|
||
namespace Solnet.Programs.Models | ||
{ | ||
/// <summary> | ||
/// Wraps a result to an RPC request. | ||
/// </summary> | ||
/// <typeparam name="T">The underlying type of the request.</typeparam> | ||
/// <typeparam name="T2">The underlying type of the request.</typeparam> | ||
public class ResultWrapper<T, T2> | ||
{ | ||
|
||
/// <summary> | ||
/// Initialize the result wrapper with the given result. | ||
/// </summary> | ||
/// <param name="result">The result of the request.</param> | ||
public ResultWrapper(RequestResult<T> result) | ||
{ | ||
OriginalRequest = result; | ||
} | ||
|
||
/// <summary> | ||
/// Initialize the result wrapper with the given result and it's parsed result type. | ||
/// </summary> | ||
/// <param name="result">The result of the request.</param> | ||
/// <param name="parsedResult">The parsed result type.</param> | ||
public ResultWrapper(RequestResult<T> result, T2 parsedResult) | ||
{ | ||
OriginalRequest = result; | ||
ParsedResult = parsedResult; | ||
} | ||
|
||
/// <summary> | ||
/// The original response to the request. | ||
/// </summary> | ||
public RequestResult<T> OriginalRequest { get; init; } | ||
|
||
/// <summary> | ||
/// The desired type of the account data. | ||
/// </summary> | ||
public T2 ParsedResult { get; set; } | ||
|
||
/// <summary> | ||
/// Whether the deserialization of the account data into the desired structure was successful. | ||
/// </summary> | ||
public bool WasDeserializationSuccessful => ParsedResult != null; | ||
|
||
/// <summary> | ||
/// Whether the original request and the deserialization of the account data into the desired structure was successful. | ||
/// </summary> | ||
public bool WasSuccessful => OriginalRequest.WasSuccessful && WasDeserializationSuccessful; | ||
} | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
public class MultipleAccountsResultWrapper<T> : ResultWrapper<ResponseValue<List<AccountInfo>>, T> | ||
{ | ||
/// <summary> | ||
/// Initialize the result wrapper with the given result. | ||
/// </summary> | ||
/// <param name="result">The result of the request.</param> | ||
public MultipleAccountsResultWrapper(RequestResult<ResponseValue<List<AccountInfo>>> result) : base(result) { } | ||
|
||
/// <summary> | ||
/// Initialize the result wrapper with the given result. | ||
/// </summary> | ||
/// <param name="result">The result of the request.</param> | ||
/// <param name="parsedResult">The parsed result type.</param> | ||
public MultipleAccountsResultWrapper(RequestResult<ResponseValue<List<AccountInfo>>> result, T parsedResult) : base(result, parsedResult) { } | ||
} | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
public class AccountResultWrapper<T> : ResultWrapper<ResponseValue<AccountInfo>, T> | ||
{ | ||
/// <summary> | ||
/// Initialize the result wrapper with the given result. | ||
/// </summary> | ||
/// <param name="result">The result of the request.</param> | ||
public AccountResultWrapper(RequestResult<ResponseValue<AccountInfo>> result) : base(result) { } | ||
|
||
/// <summary> | ||
/// Initialize the result wrapper with the given result. | ||
/// </summary> | ||
/// <param name="result">The result of the request.</param> | ||
/// <param name="parsedResult">The parsed result type.</param> | ||
public AccountResultWrapper(RequestResult<ResponseValue<AccountInfo>> result, T parsedResult) : base(result, parsedResult) { } | ||
} | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
public class ProgramAccountsResultWrapper<T> : ResultWrapper<List<AccountKeyPair>, T> | ||
{ | ||
/// <summary> | ||
/// Initialize the result wrapper with the given result. | ||
/// </summary> | ||
/// <param name="result">The result of the request.</param> | ||
public ProgramAccountsResultWrapper(RequestResult<List<AccountKeyPair>> result) : base(result) { } | ||
|
||
/// <summary> | ||
/// Initialize the result wrapper with the given result. | ||
/// </summary> | ||
/// <param name="result">The result of the request.</param> | ||
/// <param name="parsedResult">The parsed result type.</param> | ||
public ProgramAccountsResultWrapper(RequestResult<List<AccountKeyPair>> result, T parsedResult) : base(result, parsedResult) { } | ||
} | ||
|
||
/// <summary> | ||
/// Wraps the base subscription to have the underlying data of the subscription, which is sometimes needed to perform | ||
/// some logic before returning data to the subscription caller. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the subscription.</typeparam> | ||
public class SubscriptionWrapper<T> : Subscription | ||
{ | ||
/// <summary> | ||
/// The underlying data. | ||
/// </summary> | ||
public T Data; | ||
} | ||
|
||
/// <summary> | ||
/// Wraps a subscription with a generic type to hold either order book or trade events. | ||
/// </summary> | ||
public class Subscription | ||
{ | ||
/// <summary> | ||
/// The address associated with this data. | ||
/// </summary> | ||
public PublicKey Address; | ||
|
||
/// <summary> | ||
/// The underlying subscription state. | ||
/// </summary> | ||
public SubscriptionState SubscriptionState; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.