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

Integrated the swap api #225

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
124 changes: 124 additions & 0 deletions Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System;
using System.Numerics;
using System.Threading.Tasks;
using NUnit.Framework;

namespace Sequence.Marketplace
{
public class CurrencySwapTests
{
private const Chain _chain = Chain.ArbitrumOne;
private const string USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
private const string USDCe = "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8";

[Test]
public async Task GetSwapPriceTest()
{
CurrencySwap currencySwap = new CurrencySwap(_chain);
string amount = "1000";

SwapPrice swapPrice = await currencySwap.GetSwapPrice(new Address(USDC), new Address(USDCe), amount);

Assert.IsNotNull(swapPrice);
Assert.AreEqual(USDCe.ToLower(), swapPrice.currencyAddress.Value.ToLower());
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.price));
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.maxPrice));
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.transactionValue));
Assert.GreaterOrEqual(BigInteger.Parse(swapPrice.transactionValue), BigInteger.Zero);
}

[Test]
public async Task GetSwapPricesTest()
{
CurrencySwap currencySwap = new CurrencySwap(_chain);
string amount = "1000";

SwapPrice[] swapPrices = await currencySwap.GetSwapPrices(new Address("0xe8db071f698aBA1d60babaE8e08F5cBc28782108"), new Address(USDC), amount);

Assert.IsNotNull(swapPrices);
Assert.Greater(swapPrices.Length, 0);
foreach (SwapPrice swapPrice in swapPrices)
{
Assert.IsNotNull(swapPrice);
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.price));
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.maxPrice));
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.transactionValue));
Assert.GreaterOrEqual(BigInteger.Parse(swapPrice.transactionValue), BigInteger.Zero);
}
}

[Test]
public async Task GetSwapQuoteTest()
{
CurrencySwap currencySwap = new CurrencySwap(_chain);
string amount = "1000";
ChainIndexer indexer = new ChainIndexer(_chain);
Address userWallet = new Address("0xe8db071f698aBA1d60babaE8e08F5cBc28782108");

SwapQuote swapQuote = await currencySwap.GetSwapQuote(userWallet, new Address(USDC), new Address(USDCe), amount, true);
GetTokenBalancesReturn balancesReturn =
await indexer.GetTokenBalances(new GetTokenBalancesArgs(userWallet, USDCe));
TokenBalance[] balances = balancesReturn.balances;
Assert.Greater(balances.Length, 0);
TokenBalance wethBalance = null;
foreach (var balance in balances)
{
if (balance.contractAddress == USDCe)
{
wethBalance = balance;
break;
}
}
Assert.IsNotNull(wethBalance);
BigInteger wethBalanceAmount = wethBalance.balance;

Assert.IsNotNull(swapQuote);
Assert.AreEqual(USDCe.ToLower(), swapQuote.currencyAddress.Value.ToLower());
Assert.AreEqual(wethBalanceAmount, BigInteger.Parse(swapQuote.currencyBalance));
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.price));
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.maxPrice));
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.transactionData));
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.transactionValue));
Assert.GreaterOrEqual(BigInteger.Parse(swapQuote.transactionValue), BigInteger.Zero);
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.approveData));
}

[Test]
public async Task GetSwapQuoteTest_InsufficientBalance()
{
CurrencySwap currencySwap = new CurrencySwap(_chain);
string amount = "1000";
ChainIndexer indexer = new ChainIndexer(_chain);
Address userWallet = new Address("0xc683a014955b75F5ECF991d4502427c8fa1Aa249");

try
{
SwapQuote swapQuote = await currencySwap.GetSwapQuote(userWallet, new Address(USDC), new Address(USDCe), amount, true);
Assert.Fail("Exception expected but none was encountered");
}
catch (Exception e)
{
Assert.IsTrue(e.Message.Contains("Insufficient balance"));
}
}

[Test]
public async Task GetSwapQuoteTest_FailedToFetchPrice()
{
CurrencySwap currencySwap = new CurrencySwap(_chain);
string amount = "1000000000000000000000000000000000000";
ChainIndexer indexer = new ChainIndexer(_chain);
Address userWallet = new Address("0xc683a014955b75F5ECF991d4502427c8fa1Aa249");

try
{
SwapQuote swapQuote = await currencySwap.GetSwapQuote(userWallet, new Address(USDC), new Address(USDCe), amount, true);
Assert.Fail("Exception expected but none was encountered");
}
catch (Exception e)
{
Assert.IsTrue(e.Message.Contains("Error fetching swap price"));
}
}
}
}
11 changes: 11 additions & 0 deletions Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions Assets/SequenceSDK/Marketplace/SequenceMarketPlaceTests.asmdef
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@
"references": [
"GUID:0acc523941302664db1f4e527237feb3",
"GUID:27619889b8ba8c24980f49ee34dbb44a",
"GUID:19b9eb7db56cc47349571a4fbb0dd677",
"GUID:f7fd4ba36aabd1d499450c174865e70b"
"GUID:f7fd4ba36aabd1d499450c174865e70b",
"SequenceIntegrations",
"SequenceUtils",
"SequenceMarketplace",
"GUID:403077141e1554429a890cbc129df651",
"SequenceEmbeddedWallet",
"GUID:040286810a82b46ed9acd6d70bfbbfd4",
"GUID:f78a27d6a73d94c4baf04337e0add4dc",
"GUID:68e161619428e430bba22d7d0a9548ab",
"GUID:0da6d172d18a54e4389d0dce1e1ffdf2"
],
"includePlatforms": [
"Editor"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System;
using Newtonsoft.Json;
using Sequence.Utils;

namespace Sequence {

[JsonConverter(typeof(AddressJsonConverter))]
public class Address {
public readonly string Value;

Expand Down Expand Up @@ -40,4 +43,43 @@ public override bool Equals(object obj)
return this.Value == address.Value;
}
}

public class AddressJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is Address address)
{
writer.WriteValue(address.Value);
}
else
{
throw new JsonSerializationException("Expected Address object.");
}
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
string addressString = (string)reader.Value;

try
{
return new Address(addressString);
}
catch (ArgumentOutOfRangeException)
{
throw new JsonSerializationException("Invalid address format.");
}
}

throw new JsonSerializationException("Expected a string value.");
}

public override bool CanConvert(Type objectType)
{
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System;
using System.Numerics;
using System.Threading.Tasks;
using Sequence.Utils;

namespace Sequence.Marketplace
{
public class CurrencySwap : ISwap
{
private Chain _chain;
private IHttpClient _client;
private const string BaseUrl = "https://api.sequence.app/rpc/API";
private IIndexer _indexer;

public CurrencySwap(Chain chain, IHttpClient client = null)
{
_chain = chain;
if (client == null)
{
client = new HttpClient();
}
_client = client;
_indexer = new ChainIndexer(_chain);
}

public event Action<SwapPrice> OnSwapPriceReturn;
public event Action<string> OnSwapPriceError;

public async Task<SwapPrice> GetSwapPrice(Address buyCurrency, Address sellCurrency, string buyAmount,
uint slippagePercent = ISwap.DefaultSlippagePercentage)
{
GetSwapPriceRequest args = new GetSwapPriceRequest(buyCurrency, sellCurrency, buyAmount, _chain,
slippagePercent);
string url = BaseUrl.AppendTrailingSlashIfNeeded() + "GetSwapPrice";
try
{
GetSwapPriceResponse response =
await _client.SendRequest<GetSwapPriceRequest, GetSwapPriceResponse>(url, args);
OnSwapPriceReturn?.Invoke(response.swapPrice);
return response.swapPrice;
}
catch (Exception e)
{
string error =
$"Error fetching swap price for {buyCurrency} and {sellCurrency} with {nameof(buyAmount)} {buyAmount}: {e.Message}";
OnSwapPriceError?.Invoke(error);
throw new Exception(error);
}
}

public event Action<SwapPrice[]> OnSwapPricesReturn;
public event Action<string> OnSwapPricesError;

public async Task<SwapPrice[]> GetSwapPrices(Address userWallet, Address buyCurrency, string buyAmount,
uint slippagePercentage = ISwap.DefaultSlippagePercentage)
{
GetSwapPricesRequest args = new GetSwapPricesRequest(userWallet, buyCurrency, buyAmount, _chain,
slippagePercentage);
string url = BaseUrl.AppendTrailingSlashIfNeeded() + "GetSwapPrices";
try
{
GetSwapPricesResponse response =
await _client.SendRequest<GetSwapPricesRequest, GetSwapPricesResponse>(url, args);
OnSwapPricesReturn?.Invoke(response.swapPrices);
return response.swapPrices;
}
catch (Exception e)
{
string error =
$"Error fetching swap prices for {buyCurrency} with {nameof(buyAmount)} {buyAmount}: {e.Message}";
OnSwapPricesError?.Invoke(error);
throw new Exception(error);
}
}

public event Action<SwapQuote> OnSwapQuoteReturn;
public event Action<string> OnSwapQuoteError;

public async Task<SwapQuote> GetSwapQuote(Address userWallet, Address buyCurrency, Address sellCurrency,
string buyAmount, bool includeApprove,
uint slippagePercentage = ISwap.DefaultSlippagePercentage)
{
try
{
await AssertWeHaveSufficientBalance(userWallet, buyCurrency, sellCurrency, buyAmount,
slippagePercentage);
}
catch (Exception e)
{
string error = $"Error fetching swap quote for buying {buyAmount} of {buyCurrency} with {sellCurrency}: {e.Message}";
OnSwapQuoteError?.Invoke(error);
throw new Exception(error);
}

GetSwapQuoteRequest args = new GetSwapQuoteRequest(userWallet, buyCurrency, sellCurrency, buyAmount, _chain,
slippagePercentage, includeApprove);
string url = BaseUrl.AppendTrailingSlashIfNeeded() + "GetSwapQuote";
try
{
GetSwapQuoteResponse response =
await _client.SendRequest<GetSwapQuoteRequest, GetSwapQuoteResponse>(url, args);
if (response.swapQuote == null)
{
string error = $"Error fetching swap quote for buying {buyAmount} of {buyCurrency} with {sellCurrency}: Unknown error - swap API has returned a null response";
OnSwapQuoteError?.Invoke(error);
throw new Exception(error);
}
OnSwapQuoteReturn?.Invoke(response.swapQuote);
return response.swapQuote;
}
catch (Exception e)
{
string error =
$"Error fetching swap quote for buying {buyAmount} of {buyCurrency} with {sellCurrency}: {e.Message}";
OnSwapQuoteError?.Invoke(error);
throw new Exception(error);
}
}

private async Task AssertWeHaveSufficientBalance(Address userWallet, Address buyCurrency, Address sellCurrency,
string buyAmount, uint slippagePercentage = ISwap.DefaultSlippagePercentage)
{
BigInteger required, have;
try
{
SwapPrice price = await GetSwapPrice(buyCurrency, sellCurrency, buyAmount, slippagePercentage);
required = BigInteger.Parse(price.maxPrice);
}
catch (Exception e)
{
throw new Exception($"Error fetching swap price for buying {buyAmount} of {buyCurrency} with {sellCurrency}: {e.Message}");
}

TokenBalance[] sellCurrencyBalances;
try
{
GetTokenBalancesReturn balanceResponse = await _indexer.GetTokenBalances(new GetTokenBalancesArgs(userWallet, sellCurrency));
sellCurrencyBalances = balanceResponse.balances;
}
catch (Exception e)
{
throw new Exception($"Error fetching token balance of {sellCurrency}: {e.Message}");
}

if (sellCurrencyBalances == null || sellCurrencyBalances.Length == 0)
{
have = 0;
}
else
{
have = sellCurrencyBalances[0].balance;
}

if (have < required)
{
throw new Exception(
$"Insufficient balance of {sellCurrency} to buy {buyAmount} of {buyCurrency}, have {have}, need {required}");
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading