Skip to content

Commit

Permalink
Minor Batch Enhancements (#368) + readme update (#376)
Browse files Browse the repository at this point in the history
* Load TokenWallet via Batch RPC
* Overload WithMint to accept TokenDef on TokenWalletFilterList
* Fix bug when flushing an empty batch
* Update README to fix sending SPL token via TokenWallet example (#376)
* Version bump to 6.0.9
  • Loading branch information
liquizard authored May 2, 2022
1 parent d49bf31 commit a8fdbe6
Show file tree
Hide file tree
Showing 11 changed files with 510 additions and 10 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,16 +405,22 @@ Console.WriteLine();
### Sending an SPL token

```c#
var wallet = new Wallet(MnemonicWords);
var ownerAccount = wallet.GetAccount(10); // fee payer
// load wallet and its token accounts
var client = ClientFactory.GetClient(Cluster.MainNet, logger);
var tokens = New TokenMintResolver();
var wallet = TokenWallet.Load(client, tokens, feePayer);
var tokenDefs = new TokenMintResolver();
var tokenWallet = TokenWallet.Load(client, tokenDefs, ownerAccount);

// find source of funds
var source = wallet.TokenAccounts.ForToken(WellKnownTokens.Serum).WithAtLeast(12.75D).FirstOrDefault();
var source = tokenWallet.TokenAccounts().ForToken(WellKnownTokens.Serum).WithAtLeast(12.75M).FirstOrDefault();

// single-line SPL send - sends 12.75 SRM to target wallet ATA
// if required, ATA will be created funded by feePayer
var sig = wallet.Send(source, 12.75D, target, feePayer);
// if required, ATA will be created funded by ownerAccount.
// transaction is signed by you in the txBuilder callback to avoid passing your private keys out of this scope
var sig = tokenWallet.Send(source, 12.75M, target, txBuilder => txBuilder.Build(ownerAccount));
Console.WriteLine($"tx: {sig}");
```

## Support
Expand Down
2 changes: 1 addition & 1 deletion SharedBuildProperties.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Solnet</Product>
<Version>6.0.8</Version>
<Version>6.0.9</Version>
<Copyright>Copyright 2022 &#169; Solnet</Copyright>
<Authors>blockmountain</Authors>
<PublisherName>blockmountain</PublisherName>
Expand Down
11 changes: 11 additions & 0 deletions src/Solnet.Extensions/Models/TokenWallet/TokenWalletFilterList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ public TokenWalletFilterList WithMint(string mint)
return new TokenWalletFilterList(_list.Where(x => x.TokenMint == mint));
}

/// <summary>
/// Uses the TokenDef TokenMint to keep all matching accounts.
/// </summary>
/// <param name="tokenDef">A TokenDef instance.</param>
/// <returns>A filtered list of accounts for the given mint.</returns>
public TokenWalletFilterList WithMint(TokenDef tokenDef)
{
if (tokenDef == null) throw new ArgumentNullException(nameof(tokenDef));
return WithMint(tokenDef.TokenMint);
}

/// <summary>
/// Keeps all accounts with at least the supplied minimum balance.
/// </summary>
Expand Down
112 changes: 112 additions & 0 deletions src/Solnet.Extensions/TokenWallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ private TokenWallet(ITokenWalletRpcProxy client, ITokenMintResolver mintResolver
_ataCache = new Dictionary<string, PublicKey>();
}

/// <summary>
/// Private constructor, get your instances via Load methods
/// </summary>
private TokenWallet(ITokenMintResolver mintResolver, PublicKey publicKey)
{
if (mintResolver is null) throw new ArgumentNullException(nameof(mintResolver));
if (publicKey is null) throw new ArgumentNullException(nameof(publicKey));
MintResolver = mintResolver;
PublicKey = publicKey;
_ataCache = new Dictionary<string, PublicKey>();
}

#region Overloaded Load methods

/// <summary>
Expand Down Expand Up @@ -181,6 +193,106 @@ public async static Task<TokenWallet> LoadAsync(ITokenWalletRpcProxy client,
return output;
}


/// <summary>
/// Creates and loads a TokenWallet instance using an existing RPC batch call.
/// </summary>
/// <param name="batch">An instance of SolanaRpcBatchWithCallbacks</param>
/// <param name="mintResolver">An instance of a mint resolver.</param>
/// <param name="publicKey">The account public key.</param>
/// <param name="commitment">The state commitment to consider when querying the ledger state.</param>
/// <returns>A TokenWallet task that will trigger once the batch has executed.</returns>
public static Task<TokenWallet> LoadAsync(SolanaRpcBatchWithCallbacks batch,
ITokenMintResolver mintResolver,
PublicKey publicKey,
Commitment commitment = Commitment.Finalized)
{
if (publicKey == null) throw new ArgumentNullException(nameof(publicKey));
if (!publicKey.IsOnCurve()) throw new ArgumentException("PublicKey not valid - check this is native wallet address (not an ATA, PDA or aux account)");
return LoadAsync(batch, mintResolver, publicKey.Key, commitment);
}

/// <summary>
/// Creates and loads a TokenWallet instance using an existing RPC batch call.
/// </summary>
/// <param name="batch">An instance of SolanaRpcBatchWithCallbacks</param>
/// <param name="mintResolver">An instance of a mint resolver.</param>
/// <param name="publicKey">The account public key.</param>
/// <param name="commitment">The state commitment to consider when querying the ledger state.</param>
/// <returns>A TokenWallet task that will trigger once the batch has executed.</returns>
public static Task<TokenWallet> LoadAsync(SolanaRpcBatchWithCallbacks batch,
ITokenMintResolver mintResolver,
string publicKey,
Commitment commitment = Commitment.Finalized)
{
if (batch == null) throw new ArgumentNullException(nameof(batch));
if (mintResolver == null) throw new ArgumentNullException(nameof(mintResolver));
if (publicKey == null) throw new ArgumentNullException(nameof(publicKey));

// create the task source
var taskSource = new TaskCompletionSource<TokenWallet>();
var success = 0;
var fail = 0;
ulong lamports = 0;
List<TokenAccount> tokenAccounts = null;

// function to create a token wallet when both callbacks have responsed (in any order)
Action<Exception> wrapUp = ex =>
{
if (success == 2)
{
var tokenWallet = new TokenWallet(mintResolver, new PublicKey(publicKey));
tokenWallet.Lamports = lamports;
tokenWallet._tokenAccounts = tokenAccounts;
taskSource.SetResult(tokenWallet);
}
else if (fail + success == 2)
taskSource.SetException(new ApplicationException("Failed to load TokenWallet via Batch"));
};

// get sol balance
batch.GetBalance(publicKey, commitment, callback: (x, ex) =>
{
// handle balance response
lock (taskSource)
{
if (x != null)
{
lamports = x.Value;
success += 1;
}
else
fail += 1;
}

// finished?
wrapUp.Invoke(ex);
});

// load token accounts
batch.GetTokenAccountsByOwner(publicKey, null, TokenProgram.ProgramIdKey, commitment, callback: (x, ex) =>
{
// handle token list response
lock (taskSource)
{
if (x != null)
{
tokenAccounts = x.Value;
success += 1;
}
else
fail += 1;
}

// finished?
wrapUp.Invoke(ex);
});

// return the task
return taskSource.Task;

}

#endregion

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Solnet.Rpc/SolanaRpcBatchWithCallbacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public void AutoExecute(BatchAutoExecuteMode mode, int batchSizeTrigger)
/// </summary>
public void Flush()
{
_composer.Flush();
if (_composer.Count > 0)
_composer.Flush();
}

#region RPC Methods
Expand Down
5 changes: 4 additions & 1 deletion src/Solnet.Rpc/Solnet.Rpc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Solnet.Rpc.Test</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Solnet.Extensions.Test</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>DynamicProxyGenAssembly2</_Parameter1>
</AssemblyAttribute>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"method":"getBalance","params":["9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5"],"jsonrpc":"2.0","id":0},{"method":"getTokenAccountsByOwner","params":["9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5",{"programId":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"},{"encoding":"jsonParsed"}],"jsonrpc":"2.0","id":1}]
Loading

0 comments on commit a8fdbe6

Please sign in to comment.