Skip to content

Commit

Permalink
⚡ Improve Dex transaction API
Browse files Browse the repository at this point in the history
  • Loading branch information
GabrielePicco committed Jan 18, 2023
1 parent 621a1d5 commit 0836f35
Show file tree
Hide file tree
Showing 15 changed files with 186 additions and 191 deletions.
21 changes: 11 additions & 10 deletions src/Solana.Unity.Dex/IDex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,21 @@ public interface IDex
/// - tokenAuthority
/// </remarks>
/// <param name="whirlpoolAddress"></param>
/// <param name="amount"></param>
/// <param name="slippage"></param>
/// <param name="aToB"></param>
/// <param name="amount"></param>
/// <param name="inputTokenMintAddress"></param>
/// <param name="slippage"></param>
/// <param name="amountSpecifiedTokenType"></param>
/// <param name="tokenAuthority"></param>
/// <param name="commitment">Transaction commitment on which to base the transaction, and to use for
/// any chain queries.</param>
/// <returns>The generated Transaction instance</returns>
Task<Transaction> Swap(
PublicKey whirlpoolAddress,
ulong amount,
double slippage = 0.1,
bool aToB = true,
PublicKey tokenAuthority = null,
BigInteger amount,
PublicKey inputTokenMintAddress,
double slippage = 0.01,
TokenType amountSpecifiedTokenType = TokenType.TokenA,
PublicKey tokenAuthority = null,
Commitment commitment = Commitment.Finalized
);

Expand Down Expand Up @@ -81,8 +83,7 @@ Task<Transaction> SwapWithQuote(
/// <param name="tickLowerIndex"></param>
/// <param name="tickUpperIndex"></param>
/// <param name="withMetadata">True if position metadata is to be added to the position token.</param>
/// <param name="funderAddress">Position liquidity funder (optional, if different from
/// <paramref name="account">account</paramref>)</param>
/// <param name="funderAddress">Position liquidity funder (optional, if different from the wallet).</param>
/// <param name="commitment">Transaction commitment on which to base the transaction, and to use for
/// any chain queries.</param>
/// <returns>The generated Transaction instance</returns>
Expand Down Expand Up @@ -442,7 +443,7 @@ Task<IncreaseLiquidityQuote> GetIncreaseLiquidityQuote(
/// <returns>A quote to decrease liquidity.</returns>
Task<DecreaseLiquidityQuote> GetDecreaseLiquidityQuote(
PublicKey positionAddress,
ulong liquidityAmount,
BigInteger liquidityAmount,
double slippageTolerance,
Commitment commitment
);
Expand Down
116 changes: 71 additions & 45 deletions src/Solana.Unity.Dex/Orca/Orca/OrcaDex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,47 +61,21 @@ public OrcaDex(
/// <exception cref="System.Exception">Thrown if the specified whirlpool doesn't exist.</exception>
public override async Task<Transaction> Swap(
PublicKey whirlpoolAddress,
ulong amount,
double slippage = 0.1,
bool aToB = true,
BigInteger amount,
PublicKey inputTokenMintAddress,
double slippage = 0.01,
TokenType amountSpecifiedTokenType = TokenType.TokenA,
PublicKey tokenAuthority = null,
Commitment commitment = Commitment.Finalized
)
{
PublicKey account = _context.WalletPubKey;

//get the specified whirlpool
Whirlpool whirlpool = await InstructionUtil.TryGetWhirlpoolAsync(
_context, whirlpoolAddress, commitment
);

var tb = new TransactionBuilder();

//instruction to create token account A
await CreateAssociatedTokenAccountInstructionIfNotExtant(
account, whirlpool.TokenMintA, tb, commitment
);

//instruction to create token account B
await CreateAssociatedTokenAccountInstructionIfNotExtant(
account, whirlpool.TokenMintB, tb, commitment
);

//generate the transaction
tb.AddInstruction(
await OrcaInstruction.GenerateSwapInstruction(
_context,
whirlpool,
whirlpoolAddress,
tokenAuthority: tokenAuthority,
amount,
slippage,
aToB: aToB
)
);

//set fee payer and recent blockhash
return await PrepareTransaction(tb, account, RpcClient, commitment);
SwapQuote swapQuote = await GetSwapQuoteFromWhirlpool(
whirlpoolAddress,
amount,
inputTokenMintAddress,
slippage,
commitment: commitment);
return await SwapWithQuote(whirlpoolAddress, swapQuote);
}

/// <inheritdoc />
Expand All @@ -114,23 +88,34 @@ public override async Task<Transaction> SwapWithQuote(
{
PublicKey account = _context.WalletPubKey;

//get the specified whirlpool
// get the specified whirlpool
Whirlpool whirlpool = await InstructionUtil.TryGetWhirlpoolAsync(
_context, whirlpoolAddress, commitment
);

var tb = new TransactionBuilder();

//instruction to create token account A
await CreateAssociatedTokenAccountInstructionIfNotExtant(
// instruction to create token account A
var ataA = await CreateAssociatedTokenAccountInstructionIfNotExtant(
account, whirlpool.TokenMintA, tb, commitment
);

//instruction to create token account B
await CreateAssociatedTokenAccountInstructionIfNotExtant(
// instruction to create token account B
var ataB = await CreateAssociatedTokenAccountInstructionIfNotExtant(
account, whirlpool.TokenMintB, tb, commitment
);

// Wrap to wSOL if necessary
if (whirlpool.TokenMintA.Equals(AddressConstants.NATIVE_MINT_PUBKEY) && swapQuote.AtoB)
{
SyncIfNative(account, whirlpool.TokenMintA, swapQuote.Amount, tb);
}

if (whirlpool.TokenMintB.Equals(AddressConstants.NATIVE_MINT_PUBKEY) && swapQuote.AtoB)
{
SyncIfNative(account, whirlpool.TokenMintB, swapQuote.Amount, tb);
}

//generate the transaction
tb.AddInstruction(
await OrcaInstruction.GenerateSwapInstruction(
Expand All @@ -139,6 +124,28 @@ await OrcaInstruction.GenerateSwapInstruction(
swapQuote
)
);

// Close wrapped sol account
if (whirlpool.TokenMintA.Equals(AddressConstants.NATIVE_MINT_PUBKEY))
{
tb.AddInstruction(TokenProgram.CloseAccount(
ataA,
account,
account,
TokenProgram.ProgramIdKey)
);
}

// Close wrapped sol account
if (whirlpool.TokenMintB.Equals(AddressConstants.NATIVE_MINT_PUBKEY))
{
tb.AddInstruction(TokenProgram.CloseAccount(
ataB,
account,
account,
TokenProgram.ProgramIdKey)
);
}

//set fee payer and recent blockhash
return await PrepareTransaction(tb, account, RpcClient, commitment);
Expand Down Expand Up @@ -690,7 +697,7 @@ await SwapQuoteUtils.SwapQuoteByToken(

/// <inheritdoc />
/// <exception cref="System.Exception">Thrown if the specified whirlpool doesn't exist.</exception>
public async override Task<IncreaseLiquidityQuote> GetIncreaseLiquidityQuote(
public override async Task<IncreaseLiquidityQuote> GetIncreaseLiquidityQuote(
PublicKey whirlpoolAddress,
PublicKey inputTokenMintAddress,
double inputTokenAmount,
Expand All @@ -717,7 +724,7 @@ public async override Task<IncreaseLiquidityQuote> GetIncreaseLiquidityQuote(
/// <exception cref="System.Exception">Thrown if the specified position doesn't exist.</exception>
public override async Task<DecreaseLiquidityQuote> GetDecreaseLiquidityQuote(
PublicKey positionAddress,
ulong liquidityAmount,
BigInteger liquidityAmount,
double slippageTolerance,
Commitment commitment = Commitment.Finalized
)
Expand Down Expand Up @@ -775,10 +782,29 @@ Commitment commitment
)
);
}

return AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(ownerAddress, mintAddress);
}

private void SyncIfNative(
PublicKey ownerAddress,
PublicKey mintAddress,
BigInteger amount,
TransactionBuilder builder)
{
if (mintAddress.Equals(AddressConstants.NATIVE_MINT_PUBKEY))
{
var ata = AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(ownerAddress, mintAddress);
var transfer = SystemProgram.Transfer(
ownerAddress,
ata,
(ulong)(amount));
var nativeSync = TokenProgram.SyncNative(ata);

builder.AddInstruction(transfer);
builder.AddInstruction(nativeSync);
}
}

/// <summary>
/// Determines whether an appropriate tick array has been initialized for the given characteristics,
/// and adds an instruction to create one to the given TransactionBuilder, if not.
Expand Down
68 changes: 0 additions & 68 deletions src/Solana.Unity.Dex/Orca/Orca/OrcaInstruction.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using Orca;
using Solana.Unity.Dex.Math;
using System;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;

Expand All @@ -14,11 +11,9 @@
using Solana.Unity.Dex.Orca.Core.Accounts;
using Solana.Unity.Dex.Orca.Core.Program;
using Solana.Unity.Dex.Orca.Core.Types;
using Solana.Unity.Dex.Orca.Math;
using Solana.Unity.Dex.Orca.Swap;
using Solana.Unity.Dex.Orca.Ticks;
using Solana.Unity.Dex.Quotes;
using System.Collections.Generic;

namespace Solana.Unity.Dex.Orca.Orca
{
Expand All @@ -27,69 +22,6 @@ namespace Solana.Unity.Dex.Orca.Orca
/// </summary>
internal static class OrcaInstruction
{
/// <summary>
/// Generates an instruction for a swap.
/// </summary>
/// <param name="context">Whirlpool context object.</param>
/// <param name="whirlpool">Whirlpool object representing the pool on which to swap.</param>
/// <param name="whirlpoolAddress">Address of the pool on which to swap.</param>
/// <param name="tokenAuthority">Optional; if null, the context wallet public key is used.</param>
/// <param name="amount">Amount of input token to swap.</param>
/// <param name="slippage">The slippage tolerance</param>
/// <param name="aToB">The swap direction.</param>
/// <returns>A TransactionInstruction object.</returns>
public static async Task<TransactionInstruction> GenerateSwapInstruction(
IWhirlpoolContext context,
Whirlpool whirlpool,
PublicKey whirlpoolAddress,
PublicKey tokenAuthority,
ulong amount,
Percentage slippage,
bool aToB
)
{
//get tick arrays
IList<TickArrayContainer> tickArrays = await SwapUtils.GetTickArrays(
context,
whirlpool.TickCurrentIndex,
whirlpool.TickSpacing,
aToB,
AddressConstants.WHIRLPOOLS_PUBKEY,
whirlpoolAddress
);

//prune tick arrays that are empty
tickArrays = tickArrays.Where(t => t.Data != null).ToList();

//there must be tick arrays initialized
if (tickArrays.Count == 0)
throw new Exception("No tickarrays are initialized for the specified whirlpool");

//duplicate if count < 3
while (tickArrays.Count > 0 && tickArrays.Count < 3)
{
tickArrays.Add(tickArrays[tickArrays.Count - 1]);
}

//calculate sqrt price
BigInteger sqrtPriceLimit = SwapUtils.GetDefaultSqrtPriceLimit(aToB);

ulong otherAmountThreshold = (ulong)TokenMath.AdjustForSlippage(amount, slippage, true);

var swapQuote = new SwapQuote()
{
Amount = amount,
OtherAmountThreshold = otherAmountThreshold,
SqrtPriceLimit = sqrtPriceLimit,
AmountSpecifiedIsInput = true,
TickArray0 = tickArrays[0].Address,
TickArray1 = tickArrays[1].Address,
TickArray2 = tickArrays[2].Address,
AtoB = aToB,
};

return await GenerateSwapInstruction(context, whirlpool, swapQuote, tokenAuthority);
}

/// <summary>
/// Generates an instruction for a swap.
Expand Down
5 changes: 1 addition & 4 deletions src/Solana.Unity.Dex/Orca/Orca/Tokens.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Solana.Unity.Dex.Models;
using Solana.Unity.Rpc;
using Solana.Unity.Rpc.Core.Http;
using Solana.Unity.Wallet;
using System;

namespace Orca
Expand Down Expand Up @@ -40,11 +39,9 @@ public static async Task<IList<TokenData>> GetTokens(Cluster cluster = Cluster.M
string url = cluster == Cluster.DevNet ? DevnetUrl : MainnetUrl;
if (_tokens == null || forceRefresh)
{
HttpClient client = new();
using var client = new HttpClient();
using var httpReq = new HttpRequestMessage(HttpMethod.Get, url);

string response = await CrossHttpClient.SendAsyncRequest(client, httpReq).Result.Content.ReadAsStringAsync();

TokensDocument tokensDocument = new JsonSerializer().Deserialize<TokensDocument>(
new JsonTextReader(
new StringReader(response)
Expand Down
9 changes: 5 additions & 4 deletions src/Solana.Unity.Dex/Orca/TxApi/Dex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ protected Dex(IWhirlpoolContext context)
/// <inheritdoc />
public abstract Task<Transaction> Swap(
PublicKey whirlpoolAddress,
ulong amount,
double slippage = 0.1,
bool aToB = true,
BigInteger amount,
PublicKey inputTokenMintAddress,
double slippage = 0.01,
TokenType amountSpecifiedTokenType = TokenType.TokenA,
PublicKey tokenAuthority = null,
Commitment commitment = Commitment.Finalized
);
Expand Down Expand Up @@ -290,7 +291,7 @@ Commitment commitment
/// <inheritdoc />
public abstract Task<DecreaseLiquidityQuote> GetDecreaseLiquidityQuote(
PublicKey positionAddress,
ulong liquidityAmount,
BigInteger liquidityAmount,
double slippageTolerance,
Commitment commitment
);
Expand Down
9 changes: 5 additions & 4 deletions src/Solana.Unity.Extensions/TokenMintResolver.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Solana.Unity.Extensions.TokenMint;
using Solana.Unity.Rpc.Core.Http;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace Solana.Unity.Extensions
Expand Down Expand Up @@ -88,9 +88,10 @@ public static async Task<TokenMintResolver> LoadAsync()
/// <returns>A task that will result in an instance of the TokenMintResolver populated with Solana token list definitions.</returns>
public static async Task<TokenMintResolver> LoadAsync(string url)
{
using (var wc = new WebClient())
using (var client = new HttpClient())
{
var json = await wc.DownloadStringTaskAsync(url);
using var httpReq = new HttpRequestMessage(HttpMethod.Get, url);
string json = await CrossHttpClient.SendAsyncRequest(client, httpReq).Result.Content.ReadAsStringAsync();
return ParseTokenList(json);
}
}
Expand Down
Loading

0 comments on commit 0836f35

Please sign in to comment.