Skip to content

Commit d88c252

Browse files
Integrated the swap api (#225)
1 parent 566ab21 commit d88c252

27 files changed

Lines changed: 807 additions & 5 deletions
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System;
2+
using System.Numerics;
3+
using System.Threading.Tasks;
4+
using NUnit.Framework;
5+
6+
namespace Sequence.Marketplace
7+
{
8+
public class CurrencySwapTests
9+
{
10+
private const Chain _chain = Chain.ArbitrumOne;
11+
private const string USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
12+
private const string USDCe = "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8";
13+
14+
[Test]
15+
public async Task GetSwapPriceTest()
16+
{
17+
CurrencySwap currencySwap = new CurrencySwap(_chain);
18+
string amount = "1000";
19+
20+
SwapPrice swapPrice = await currencySwap.GetSwapPrice(new Address(USDC), new Address(USDCe), amount);
21+
22+
Assert.IsNotNull(swapPrice);
23+
Assert.AreEqual(USDCe.ToLower(), swapPrice.currencyAddress.Value.ToLower());
24+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.price));
25+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.maxPrice));
26+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.transactionValue));
27+
Assert.GreaterOrEqual(BigInteger.Parse(swapPrice.transactionValue), BigInteger.Zero);
28+
}
29+
30+
[Test]
31+
public async Task GetSwapPricesTest()
32+
{
33+
CurrencySwap currencySwap = new CurrencySwap(_chain);
34+
string amount = "1000";
35+
36+
SwapPrice[] swapPrices = await currencySwap.GetSwapPrices(new Address("0xe8db071f698aBA1d60babaE8e08F5cBc28782108"), new Address(USDC), amount);
37+
38+
Assert.IsNotNull(swapPrices);
39+
Assert.Greater(swapPrices.Length, 0);
40+
foreach (SwapPrice swapPrice in swapPrices)
41+
{
42+
Assert.IsNotNull(swapPrice);
43+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.price));
44+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.maxPrice));
45+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapPrice.transactionValue));
46+
Assert.GreaterOrEqual(BigInteger.Parse(swapPrice.transactionValue), BigInteger.Zero);
47+
}
48+
}
49+
50+
[Test]
51+
public async Task GetSwapQuoteTest()
52+
{
53+
CurrencySwap currencySwap = new CurrencySwap(_chain);
54+
string amount = "1000";
55+
ChainIndexer indexer = new ChainIndexer(_chain);
56+
Address userWallet = new Address("0xe8db071f698aBA1d60babaE8e08F5cBc28782108");
57+
58+
SwapQuote swapQuote = await currencySwap.GetSwapQuote(userWallet, new Address(USDC), new Address(USDCe), amount, true);
59+
GetTokenBalancesReturn balancesReturn =
60+
await indexer.GetTokenBalances(new GetTokenBalancesArgs(userWallet, USDCe));
61+
TokenBalance[] balances = balancesReturn.balances;
62+
Assert.Greater(balances.Length, 0);
63+
TokenBalance wethBalance = null;
64+
foreach (var balance in balances)
65+
{
66+
if (balance.contractAddress == USDCe)
67+
{
68+
wethBalance = balance;
69+
break;
70+
}
71+
}
72+
Assert.IsNotNull(wethBalance);
73+
BigInteger wethBalanceAmount = wethBalance.balance;
74+
75+
Assert.IsNotNull(swapQuote);
76+
Assert.AreEqual(USDCe.ToLower(), swapQuote.currencyAddress.Value.ToLower());
77+
Assert.AreEqual(wethBalanceAmount, BigInteger.Parse(swapQuote.currencyBalance));
78+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.price));
79+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.maxPrice));
80+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.transactionData));
81+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.transactionValue));
82+
Assert.GreaterOrEqual(BigInteger.Parse(swapQuote.transactionValue), BigInteger.Zero);
83+
Assert.IsFalse(string.IsNullOrWhiteSpace(swapQuote.approveData));
84+
}
85+
86+
[Test]
87+
public async Task GetSwapQuoteTest_InsufficientBalance()
88+
{
89+
CurrencySwap currencySwap = new CurrencySwap(_chain);
90+
string amount = "1000";
91+
ChainIndexer indexer = new ChainIndexer(_chain);
92+
Address userWallet = new Address("0xc683a014955b75F5ECF991d4502427c8fa1Aa249");
93+
94+
try
95+
{
96+
SwapQuote swapQuote = await currencySwap.GetSwapQuote(userWallet, new Address(USDC), new Address(USDCe), amount, true);
97+
Assert.Fail("Exception expected but none was encountered");
98+
}
99+
catch (Exception e)
100+
{
101+
Assert.IsTrue(e.Message.Contains("Insufficient balance"));
102+
}
103+
}
104+
105+
[Test]
106+
public async Task GetSwapQuoteTest_FailedToFetchPrice()
107+
{
108+
CurrencySwap currencySwap = new CurrencySwap(_chain);
109+
string amount = "1000000000000000000000000000000000000";
110+
ChainIndexer indexer = new ChainIndexer(_chain);
111+
Address userWallet = new Address("0xc683a014955b75F5ECF991d4502427c8fa1Aa249");
112+
113+
try
114+
{
115+
SwapQuote swapQuote = await currencySwap.GetSwapQuote(userWallet, new Address(USDC), new Address(USDCe), amount, true);
116+
Assert.Fail("Exception expected but none was encountered");
117+
}
118+
catch (Exception e)
119+
{
120+
Assert.IsTrue(e.Message.Contains("Error fetching swap price"));
121+
}
122+
}
123+
}
124+
}

Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/SequenceSDK/Marketplace/SequenceMarketPlaceTests.asmdef

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,16 @@
44
"references": [
55
"GUID:0acc523941302664db1f4e527237feb3",
66
"GUID:27619889b8ba8c24980f49ee34dbb44a",
7-
"GUID:19b9eb7db56cc47349571a4fbb0dd677",
8-
"GUID:f7fd4ba36aabd1d499450c174865e70b"
7+
"GUID:f7fd4ba36aabd1d499450c174865e70b",
8+
"SequenceIntegrations",
9+
"SequenceUtils",
10+
"SequenceMarketplace",
11+
"GUID:403077141e1554429a890cbc129df651",
12+
"SequenceEmbeddedWallet",
13+
"GUID:040286810a82b46ed9acd6d70bfbbfd4",
14+
"GUID:f78a27d6a73d94c4baf04337e0add4dc",
15+
"GUID:68e161619428e430bba22d7d0a9548ab",
16+
"GUID:0da6d172d18a54e4389d0dce1e1ffdf2"
917
],
1018
"includePlatforms": [
1119
"Editor"

Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Address/Address.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using System;
2+
using Newtonsoft.Json;
23
using Sequence.Utils;
34

45
namespace Sequence {
6+
7+
[JsonConverter(typeof(AddressJsonConverter))]
58
public class Address {
69
public readonly string Value;
710

@@ -40,4 +43,43 @@ public override bool Equals(object obj)
4043
return this.Value == address.Value;
4144
}
4245
}
46+
47+
public class AddressJsonConverter : JsonConverter
48+
{
49+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
50+
{
51+
if (value is Address address)
52+
{
53+
writer.WriteValue(address.Value);
54+
}
55+
else
56+
{
57+
throw new JsonSerializationException("Expected Address object.");
58+
}
59+
}
60+
61+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
62+
{
63+
if (reader.TokenType == JsonToken.String)
64+
{
65+
string addressString = (string)reader.Value;
66+
67+
try
68+
{
69+
return new Address(addressString);
70+
}
71+
catch (ArgumentOutOfRangeException)
72+
{
73+
throw new JsonSerializationException("Invalid address format.");
74+
}
75+
}
76+
77+
throw new JsonSerializationException("Expected a string value.");
78+
}
79+
80+
public override bool CanConvert(Type objectType)
81+
{
82+
return true;
83+
}
84+
}
4385
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
using System;
2+
using System.Numerics;
3+
using System.Threading.Tasks;
4+
using Sequence.Utils;
5+
6+
namespace Sequence.Marketplace
7+
{
8+
public class CurrencySwap : ISwap
9+
{
10+
private Chain _chain;
11+
private IHttpClient _client;
12+
private const string BaseUrl = "https://api.sequence.app/rpc/API";
13+
private IIndexer _indexer;
14+
15+
public CurrencySwap(Chain chain, IHttpClient client = null)
16+
{
17+
_chain = chain;
18+
if (client == null)
19+
{
20+
client = new HttpClient();
21+
}
22+
_client = client;
23+
_indexer = new ChainIndexer(_chain);
24+
}
25+
26+
public event Action<SwapPrice> OnSwapPriceReturn;
27+
public event Action<string> OnSwapPriceError;
28+
29+
public async Task<SwapPrice> GetSwapPrice(Address buyCurrency, Address sellCurrency, string buyAmount,
30+
uint slippagePercent = ISwap.DefaultSlippagePercentage)
31+
{
32+
GetSwapPriceRequest args = new GetSwapPriceRequest(buyCurrency, sellCurrency, buyAmount, _chain,
33+
slippagePercent);
34+
string url = BaseUrl.AppendTrailingSlashIfNeeded() + "GetSwapPrice";
35+
try
36+
{
37+
GetSwapPriceResponse response =
38+
await _client.SendRequest<GetSwapPriceRequest, GetSwapPriceResponse>(url, args);
39+
OnSwapPriceReturn?.Invoke(response.swapPrice);
40+
return response.swapPrice;
41+
}
42+
catch (Exception e)
43+
{
44+
string error =
45+
$"Error fetching swap price for {buyCurrency} and {sellCurrency} with {nameof(buyAmount)} {buyAmount}: {e.Message}";
46+
OnSwapPriceError?.Invoke(error);
47+
throw new Exception(error);
48+
}
49+
}
50+
51+
public event Action<SwapPrice[]> OnSwapPricesReturn;
52+
public event Action<string> OnSwapPricesError;
53+
54+
public async Task<SwapPrice[]> GetSwapPrices(Address userWallet, Address buyCurrency, string buyAmount,
55+
uint slippagePercentage = ISwap.DefaultSlippagePercentage)
56+
{
57+
GetSwapPricesRequest args = new GetSwapPricesRequest(userWallet, buyCurrency, buyAmount, _chain,
58+
slippagePercentage);
59+
string url = BaseUrl.AppendTrailingSlashIfNeeded() + "GetSwapPrices";
60+
try
61+
{
62+
GetSwapPricesResponse response =
63+
await _client.SendRequest<GetSwapPricesRequest, GetSwapPricesResponse>(url, args);
64+
OnSwapPricesReturn?.Invoke(response.swapPrices);
65+
return response.swapPrices;
66+
}
67+
catch (Exception e)
68+
{
69+
string error =
70+
$"Error fetching swap prices for {buyCurrency} with {nameof(buyAmount)} {buyAmount}: {e.Message}";
71+
OnSwapPricesError?.Invoke(error);
72+
throw new Exception(error);
73+
}
74+
}
75+
76+
public event Action<SwapQuote> OnSwapQuoteReturn;
77+
public event Action<string> OnSwapQuoteError;
78+
79+
public async Task<SwapQuote> GetSwapQuote(Address userWallet, Address buyCurrency, Address sellCurrency,
80+
string buyAmount, bool includeApprove,
81+
uint slippagePercentage = ISwap.DefaultSlippagePercentage)
82+
{
83+
try
84+
{
85+
await AssertWeHaveSufficientBalance(userWallet, buyCurrency, sellCurrency, buyAmount,
86+
slippagePercentage);
87+
}
88+
catch (Exception e)
89+
{
90+
string error = $"Error fetching swap quote for buying {buyAmount} of {buyCurrency} with {sellCurrency}: {e.Message}";
91+
OnSwapQuoteError?.Invoke(error);
92+
throw new Exception(error);
93+
}
94+
95+
GetSwapQuoteRequest args = new GetSwapQuoteRequest(userWallet, buyCurrency, sellCurrency, buyAmount, _chain,
96+
slippagePercentage, includeApprove);
97+
string url = BaseUrl.AppendTrailingSlashIfNeeded() + "GetSwapQuote";
98+
try
99+
{
100+
GetSwapQuoteResponse response =
101+
await _client.SendRequest<GetSwapQuoteRequest, GetSwapQuoteResponse>(url, args);
102+
if (response.swapQuote == null)
103+
{
104+
string error = $"Error fetching swap quote for buying {buyAmount} of {buyCurrency} with {sellCurrency}: Unknown error - swap API has returned a null response";
105+
OnSwapQuoteError?.Invoke(error);
106+
throw new Exception(error);
107+
}
108+
OnSwapQuoteReturn?.Invoke(response.swapQuote);
109+
return response.swapQuote;
110+
}
111+
catch (Exception e)
112+
{
113+
string error =
114+
$"Error fetching swap quote for buying {buyAmount} of {buyCurrency} with {sellCurrency}: {e.Message}";
115+
OnSwapQuoteError?.Invoke(error);
116+
throw new Exception(error);
117+
}
118+
}
119+
120+
private async Task AssertWeHaveSufficientBalance(Address userWallet, Address buyCurrency, Address sellCurrency,
121+
string buyAmount, uint slippagePercentage = ISwap.DefaultSlippagePercentage)
122+
{
123+
BigInteger required, have;
124+
try
125+
{
126+
SwapPrice price = await GetSwapPrice(buyCurrency, sellCurrency, buyAmount, slippagePercentage);
127+
required = BigInteger.Parse(price.maxPrice);
128+
}
129+
catch (Exception e)
130+
{
131+
throw new Exception($"Error fetching swap price for buying {buyAmount} of {buyCurrency} with {sellCurrency}: {e.Message}");
132+
}
133+
134+
TokenBalance[] sellCurrencyBalances;
135+
try
136+
{
137+
GetTokenBalancesReturn balanceResponse = await _indexer.GetTokenBalances(new GetTokenBalancesArgs(userWallet, sellCurrency));
138+
sellCurrencyBalances = balanceResponse.balances;
139+
}
140+
catch (Exception e)
141+
{
142+
throw new Exception($"Error fetching token balance of {sellCurrency}: {e.Message}");
143+
}
144+
145+
if (sellCurrencyBalances == null || sellCurrencyBalances.Length == 0)
146+
{
147+
have = 0;
148+
}
149+
else
150+
{
151+
have = sellCurrencyBalances[0].balance;
152+
}
153+
154+
if (have < required)
155+
{
156+
throw new Exception(
157+
$"Insufficient balance of {sellCurrency} to buy {buyAmount} of {buyCurrency}, have {have}, need {required}");
158+
}
159+
}
160+
}
161+
}

Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/CurrencySwap.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)