From 3ab4bb791bb7c277ccbab4660c3dd795cffa7aef Mon Sep 17 00:00:00 2001 From: Quinn Purdy Date: Mon, 21 Apr 2025 11:59:58 -0400 Subject: [PATCH 1/2] Add implementation of GetLifiChains and GetLifiTokens endpoints - these are useful to check which chains and tokens per chain are enabled by the new swap service provider, Lifi. Additionally, refactored PriceFeed.Token to a more generic class so it can be reused. --- .../Marketplace/CurrencySwapTests.cs | 87 +++++++++++++++++++ .../Mocks/MockSwapThatGivesPricesInOrder.cs | 10 +++ .../Sequence/SequenceSDK/Ethereum/Token.cs | 63 ++++++++++++++ .../SequenceSDK/Ethereum/Token.cs.meta | 3 + .../Indexer/DataTypes/GetTokenPricesArgs.cs | 4 +- .../Indexer/DataTypes/TokenPrice.cs | 4 +- .../Sequence/SequenceSDK/Indexer/PriceFeed.cs | 44 +--------- .../SequenceSDK/Marketplace/CurrencySwap.cs | 32 +++++++ .../DataTypes/GetLifiTokensRequest.cs | 30 +++++++ .../DataTypes/GetLifiTokensRequest.cs.meta | 3 + .../DataTypes/GetLifiTokensResponse.cs | 17 ++++ .../DataTypes/GetLifiTokensResponse.cs.meta | 3 + .../DataTypes/LifiSupportedChainsResponse.cs | 29 +++++++ .../LifiSupportedChainsResponse.cs.meta | 3 + .../Sequence/SequenceSDK/Marketplace/ISwap.cs | 13 +++ Packages/Sequence-Unity/package.json | 2 +- 16 files changed, 302 insertions(+), 45 deletions(-) create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Token.cs create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Token.cs.meta create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensRequest.cs create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensRequest.cs.meta create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensResponse.cs create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensResponse.cs.meta create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs.meta diff --git a/Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs b/Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs index 1e088f234..fb405b3d4 100644 --- a/Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs +++ b/Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs @@ -120,5 +120,92 @@ public async Task GetSwapQuoteTest_FailedToFetchPrice() Assert.IsTrue(e.Message.Contains("Error fetching swap quote")); } } + + [Test] + public async Task TestGetSupportedChains() + { + CurrencySwap currencySwap = new CurrencySwap(_chain); + + try + { + Chain[] supportedChains = await currencySwap.GetSupportedChains(); + Assert.IsNotNull(supportedChains); + Assert.Greater(supportedChains.Length, 0); + } + catch (Exception e) + { + Assert.Fail($"Exception encountered while fetching supported chains: {e.Message}"); + } + } + + [Test] + public async Task TestGetSupportedTokens() + { + CurrencySwap currencySwap = new CurrencySwap(_chain); + + try + { + Token[] supportedTokens = await currencySwap.GetSupportedTokens(new[] { _chain }); + Assert.IsNotNull(supportedTokens); + Assert.Greater(supportedTokens.Length, 0); + } + catch (Exception e) + { + Assert.Fail($"Exception encountered while fetching supported tokens: {e.Message}"); + } + } + + [Test] + public async Task TestGetSupportedTokens_EmptyChains() + { + CurrencySwap currencySwap = new CurrencySwap(_chain); + + try + { + Token[] supportedTokens = await currencySwap.GetSupportedTokens(new Chain[0]); + Assert.Fail("Expected exception but none was thrown"); + } + catch (Exception e) + { + Assert.IsTrue(e.Message.Contains("Error fetching supported tokens:")); + } + } + + [Test] + public async Task TestGetSupportedTokens_NullChains() + { + CurrencySwap currencySwap = new CurrencySwap(_chain); + + try + { + Token[] supportedTokens = await currencySwap.GetSupportedTokens(null); + Assert.Fail("Expected exception but none was thrown"); + } + catch (Exception e) + { + Assert.IsTrue(e.Message.Contains("Error fetching supported tokens:")); + } + } + + [Test] + public async Task TestGetSupportedTokens_AllSupportedChains() + { + CurrencySwap currencySwap = new CurrencySwap(_chain); + + try + { + Chain[] supportedChains = await currencySwap.GetSupportedChains(); + Assert.IsNotNull(supportedChains); + Assert.Greater(supportedChains.Length, 0); + + Token[] supportedTokens = await currencySwap.GetSupportedTokens(supportedChains); + Assert.IsNotNull(supportedTokens); + Assert.AreEqual(0, supportedTokens.Length); + } + catch (Exception e) + { + Assert.Fail($"Exception encountered while fetching supported tokens: {e.Message}"); + } + } } } \ No newline at end of file diff --git a/Assets/SequenceSDK/Marketplace/Mocks/MockSwapThatGivesPricesInOrder.cs b/Assets/SequenceSDK/Marketplace/Mocks/MockSwapThatGivesPricesInOrder.cs index 5f17a70ec..ab61fced7 100644 --- a/Assets/SequenceSDK/Marketplace/Mocks/MockSwapThatGivesPricesInOrder.cs +++ b/Assets/SequenceSDK/Marketplace/Mocks/MockSwapThatGivesPricesInOrder.cs @@ -48,5 +48,15 @@ public Task GetSwapQuote(Address userWallet, Address buyCurrency, Add { throw new NotImplementedException(); } + + public Task GetSupportedChains() + { + throw new NotImplementedException(); + } + + public Task GetSupportedTokens(Chain[] chains) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Token.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Token.cs new file mode 100644 index 000000000..dc1532591 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Token.cs @@ -0,0 +1,63 @@ +using System; +using System.Numerics; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using UnityEngine.Scripting; + +namespace Sequence +{ + [Serializable] + [JsonConverter(typeof(TokenConverter))] + public class Token + { + public Chain Chain; + public Address Contract; + public string TokenId; + + public Token(Chain chain, Address contract, string tokenId = null) + { + Chain = chain; + Contract = contract; + TokenId = tokenId; + } + + [Preserve] + [JsonConstructor] + public Token(BigInteger chainId, string contractAddress, string tokenId = null) + { + Chain = ChainDictionaries.ChainById[chainId.ToString()]; + Contract = new Address(contractAddress); + TokenId = tokenId; + } + } + + internal class TokenConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, Token value, JsonSerializer serializer) + { + var jsonObject = new JObject + { + ["chainId"] = ulong.Parse(ChainDictionaries.ChainIdOf[value.Chain]), + ["contractAddress"] = value.Contract.ToString(), + }; + if (value.TokenId != null) + { + jsonObject["tokenId"] = value.TokenId; + } + + jsonObject.WriteTo(writer); + } + + public override Token ReadJson(JsonReader reader, Type objectType, Token existingValue, + bool hasExistingValue, JsonSerializer serializer) + { + var jsonObject = JObject.Load(reader); + + BigInteger chainId = jsonObject["chainId"]?.Value() ?? 0; + string contractAddress = jsonObject["contractAddress"]?.Value(); + string tokenId = jsonObject["tokenId"]?.Value(); + + return new Token(chainId, contractAddress, tokenId); + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Token.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Token.cs.meta new file mode 100644 index 000000000..ea934246f --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Token.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4f7a9f677198422da3bd8cab76eaa17f +timeCreated: 1745248786 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/DataTypes/GetTokenPricesArgs.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/DataTypes/GetTokenPricesArgs.cs index ff307407e..3afa855f3 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/DataTypes/GetTokenPricesArgs.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/DataTypes/GetTokenPricesArgs.cs @@ -6,9 +6,9 @@ namespace Sequence [Serializable] internal class GetTokenPricesArgs { - public PriceFeed.Token[] tokens; + public Token[] tokens; - public GetTokenPricesArgs(PriceFeed.Token[] tokens) + public GetTokenPricesArgs(Token[] tokens) { this.tokens = tokens; } diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/DataTypes/TokenPrice.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/DataTypes/TokenPrice.cs index 9dbb27810..a1c5c76ce 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/DataTypes/TokenPrice.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/DataTypes/TokenPrice.cs @@ -6,7 +6,7 @@ namespace Sequence [Serializable] public class TokenPrice { - public PriceFeed.Token token; + public Token token; public Price price; public Price price24hChange; public Price floorPrice; @@ -15,7 +15,7 @@ public class TokenPrice public DateTime updatedAt; [Preserve] - public TokenPrice(PriceFeed.Token token, Price price, Price price24hChange, Price floorPrice, Price buyPrice, Price sellPrice, DateTime updatedAt) + public TokenPrice(Token token, Price price, Price price24hChange, Price floorPrice, Price buyPrice, Price sellPrice, DateTime updatedAt) { this.token = token; this.price = price; diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/PriceFeed.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/PriceFeed.cs index b4d7fdbc8..1e78f4543 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/PriceFeed.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Indexer/PriceFeed.cs @@ -1,58 +1,22 @@ using System; using System.Numerics; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Sequence.Config; using Sequence.Utils; -using UnityEngine.Scripting; namespace Sequence { public class PriceFeed { - [Serializable] - [JsonConverter(typeof(TokenConverter))] - public class Token + [Obsolete("Use the Token class in the Sequence namespace instead.")] + public class Token : Sequence.Token { - public Chain Chain; - public Address Contract; - - public Token(Chain chain, Address contract) - { - Chain = chain; - Contract = contract; - } - - [Preserve] - [JsonConstructor] - public Token(BigInteger chainId, string contractAddress) + public Token(Chain chain, Address contract, string tokenId = null) : base(chain, contract, tokenId) { - Chain = ChainDictionaries.ChainById[chainId.ToString()]; - Contract = new Address(contractAddress); } - } - private class TokenConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, Token value, JsonSerializer serializer) + public Token(BigInteger chainId, string contractAddress, string tokenId = null) : base(chainId, contractAddress, tokenId) { - var jsonObject = new JObject - { - ["chainId"] = ulong.Parse(ChainDictionaries.ChainIdOf[value.Chain]), - ["contractAddress"] = value.Contract.ToString(), - }; - jsonObject.WriteTo(writer); - } - - public override Token ReadJson(JsonReader reader, Type objectType, Token existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var jsonObject = JObject.Load(reader); - - BigInteger chainId = jsonObject["chainId"]?.Value() ?? 0; - string contractAddress = jsonObject["contractAddress"]?.Value(); - - return new Token(chainId, contractAddress); } } diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/CurrencySwap.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/CurrencySwap.cs index 0bef96f33..a58bc46a9 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/CurrencySwap.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/CurrencySwap.cs @@ -165,5 +165,37 @@ private async Task AssertWeHaveSufficientBalance(Address userWallet, Address buy $"Insufficient balance of {sellCurrency} to buy {buyAmount} of {buyCurrency}, have {have}, need {required}"); } } + + public async Task GetSupportedChains() + { + string url = BaseUrl.AppendTrailingSlashIfNeeded() + "GetLifiChains"; + try + { + LifiSupportedChainsResponse response = + await _client.SendRequest(url, null); + return response.GetChains(); + } + catch (Exception e) + { + string error = $"Error fetching supported chains: {e.Message}"; + throw new Exception(error); + } + } + + public async Task GetSupportedTokens(Chain[] chains) + { + string url = BaseUrl.AppendTrailingSlashIfNeeded() + "GetLifiTokens"; + try + { + GetLifiTokensResponse response = + await _client.SendRequest(url, new GetLifiTokensRequest(chains)); + return response.tokens; + } + catch (Exception e) + { + string error = $"Error fetching supported tokens: {e.Message}"; + throw new Exception(error); + } + } } } \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensRequest.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensRequest.cs new file mode 100644 index 000000000..227f5a06b --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensRequest.cs @@ -0,0 +1,30 @@ +using System; +using System.Numerics; +using Newtonsoft.Json; +using UnityEngine.Scripting; + +namespace Sequence.Marketplace +{ + [Serializable] + public class GetLifiTokensRequest + { + public BigInteger[] chainIds; + + [JsonConstructor] + [Preserve] + public GetLifiTokensRequest(BigInteger[] chainIds) + { + this.chainIds = chainIds; + } + + public GetLifiTokensRequest(Chain[] chains) + { + int length = chains.Length; + chainIds = new BigInteger[length]; + for (int i = 0; i < length; i++) + { + chainIds[i] = BigInteger.Parse(ChainDictionaries.ChainIdOf[chains[i]]); + } + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensRequest.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensRequest.cs.meta new file mode 100644 index 000000000..36c5e19fa --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensRequest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2c0c2de0e60345189236b54875874335 +timeCreated: 1745249518 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensResponse.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensResponse.cs new file mode 100644 index 000000000..f47631aaf --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensResponse.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine.Scripting; + +namespace Sequence.Marketplace +{ + [Serializable] + public class GetLifiTokensResponse + { + public Token[] tokens; + + [Preserve] + public GetLifiTokensResponse(Token[] tokens) + { + this.tokens = tokens; + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensResponse.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensResponse.cs.meta new file mode 100644 index 000000000..6dec35fec --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/GetLifiTokensResponse.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a1bffa4ea37e4ec4b0a1e0d9706f4f62 +timeCreated: 1745249557 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs new file mode 100644 index 000000000..02b50be91 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs @@ -0,0 +1,29 @@ +using System; +using System.Numerics; +using UnityEngine.Scripting; + +namespace Sequence.Marketplace +{ + [Serializable] + public class LifiSupportedChainsResponse + { + public BigInteger[] chains; + + [Preserve] + public LifiSupportedChainsResponse(BigInteger[] chains) + { + this.chains = chains; + } + + public Chain[] GetChains() + { + int length = chains.Length; + Chain[] result = new Chain[length]; + for (int i = 0; i < length; i++) + { + result[i] = ChainDictionaries.ChainById[chains[i].ToString()]; + } + return result; + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs.meta new file mode 100644 index 000000000..03166fb80 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7317e5bccdf84916b9657202923b4a4d +timeCreated: 1745249460 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/ISwap.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/ISwap.cs index 647f72c04..c21ff2f30 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/ISwap.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/ISwap.cs @@ -48,5 +48,18 @@ public interface ISwap /// public Task GetSwapQuote(Address userWallet, Address buyCurrency, Address sellCurrency, string buyAmount, bool includeApprove, uint slippagePercentage = DefaultSlippagePercentage); + + /// + /// Get a Chain[] of all supported chains for the swap provider + /// + /// + public Task GetSupportedChains(); + + /// + /// Get the supported tokens by the swap provider for a given set of chains + /// + /// + /// + public Task GetSupportedTokens(Chain[] chains); } } \ No newline at end of file diff --git a/Packages/Sequence-Unity/package.json b/Packages/Sequence-Unity/package.json index 44d3996e9..3dbf8cb32 100644 --- a/Packages/Sequence-Unity/package.json +++ b/Packages/Sequence-Unity/package.json @@ -1,6 +1,6 @@ { "name": "xyz.0xsequence.waas-unity", - "version": "4.0.3", + "version": "4.1.0", "displayName": "Sequence Embedded Wallet SDK", "description": "A Unity SDK for Sequence APIs", "unity": "2021.3", From a22f1ccd24274e6d79e8c45e56ec635dbe050579 Mon Sep 17 00:00:00 2001 From: Quinn Purdy Date: Fri, 25 Apr 2025 15:00:24 -0400 Subject: [PATCH 2/2] Fix get support chains implementation. Fix typo in test --- Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs | 2 +- .../DataTypes/LifiSupportedChainsResponse.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs b/Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs index fb405b3d4..30a7b645e 100644 --- a/Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs +++ b/Assets/SequenceSDK/Marketplace/CurrencySwapTests.cs @@ -200,7 +200,7 @@ public async Task TestGetSupportedTokens_AllSupportedChains() Token[] supportedTokens = await currencySwap.GetSupportedTokens(supportedChains); Assert.IsNotNull(supportedTokens); - Assert.AreEqual(0, supportedTokens.Length); + Assert.Greater(supportedTokens.Length, 0); } catch (Exception e) { diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs index 02b50be91..28b1c8048 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Marketplace/DataTypes/LifiSupportedChainsResponse.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Numerics; using UnityEngine.Scripting; @@ -18,12 +19,16 @@ public LifiSupportedChainsResponse(BigInteger[] chains) public Chain[] GetChains() { int length = chains.Length; - Chain[] result = new Chain[length]; + List result = new List(); for (int i = 0; i < length; i++) { - result[i] = ChainDictionaries.ChainById[chains[i].ToString()]; + string chainId = chains[i].ToString(); + if (ChainDictionaries.ChainById.TryGetValue(chainId, out Chain chain)) // There may be chains that aren't supported by Sequence + { + result.Add(chain); + } } - return result; + return result.ToArray(); } } } \ No newline at end of file