diff --git a/Assets/SequenceSDK/Adapter.meta b/Assets/SequenceSDK/Adapter.meta new file mode 100644 index 000000000..74ff581c9 --- /dev/null +++ b/Assets/SequenceSDK/Adapter.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c80db533ce3049719d92ba7d1b9a6fe6 +timeCreated: 1750361202 \ No newline at end of file diff --git a/Assets/SequenceSDK/Adapter/Tests.meta b/Assets/SequenceSDK/Adapter/Tests.meta new file mode 100644 index 000000000..3f6ad50c2 --- /dev/null +++ b/Assets/SequenceSDK/Adapter/Tests.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c7fe56b63fed4a80a6017040e7404806 +timeCreated: 1750361210 \ No newline at end of file diff --git a/Assets/SequenceSDK/Adapter/Tests/AdapterTests.cs b/Assets/SequenceSDK/Adapter/Tests/AdapterTests.cs new file mode 100644 index 000000000..91aea060c --- /dev/null +++ b/Assets/SequenceSDK/Adapter/Tests/AdapterTests.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Sequence.Pay.Tests.Transak +{ + public class AdapterTests + { + private const string TokenId = "0"; + + private static readonly Address TokenAddress = new (""); + private static readonly Address CurrencyAddress = new (""); + private static readonly Address SellCurrencyAddress = new (""); + private static readonly Address RecipientAddress = new (""); + + [Test] + public async Task UnifiedEndToEndTest() + { + var sequenceUnified = new Adapter.Sequence(Chain.TestnetArbitrumSepolia); + await sequenceUnified.TryRecoverWalletFromStorage(); + + var recovered = await sequenceUnified.TryRecoverWalletFromStorage(); + if (!recovered) + await sequenceUnified.GuestLogin(); + + await sequenceUnified.GetIdToken(); + + var nativeTokenBalance= await sequenceUnified.GetMyNativeTokenBalance(); + var tokenBalance = await sequenceUnified.GetMyTokenBalance(TokenAddress); + + await sequenceUnified.SendToken(TokenAddress, RecipientAddress, TokenId, 1); + await sequenceUnified.SwapToken(SellCurrencyAddress, CurrencyAddress, 1000); + + await sequenceUnified.CreateListingOnMarketplace(TokenAddress, CurrencyAddress, TokenId, + 1, 1000, DateTime.MaxValue); + + var listings = await sequenceUnified.GetAllListingsFromMarketplace(TokenAddress); + await sequenceUnified.PurchaseOrderFromMarketplace(listings[0].order, 1); + } + } +} \ No newline at end of file diff --git a/Assets/SequenceSDK/Adapter/Tests/AdapterTests.cs.meta b/Assets/SequenceSDK/Adapter/Tests/AdapterTests.cs.meta new file mode 100644 index 000000000..ae9034cf3 --- /dev/null +++ b/Assets/SequenceSDK/Adapter/Tests/AdapterTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1be23cc916d046b69e5b0c2e9c905be7 +timeCreated: 1750361251 \ No newline at end of file diff --git a/Assets/SequenceSDK/Adapter/Tests/SequenceAdapterTests.asmdef b/Assets/SequenceSDK/Adapter/Tests/SequenceAdapterTests.asmdef new file mode 100644 index 000000000..cb8f400a5 --- /dev/null +++ b/Assets/SequenceSDK/Adapter/Tests/SequenceAdapterTests.asmdef @@ -0,0 +1,18 @@ +{ + "name": "SequenceUnifiedTests", + "rootNamespace": "", + "references": ["GUID:03d0ed9c50cfd4cb28a56d2d06580ffa","GUID:f7fd4ba36aabd1d499450c174865e70b","GUID:19b9eb7db56cc47349571a4fbb0dd677","GUID:403077141e1554429a890cbc129df651","GUID:4e0a798abbda240658187632ae443a67","GUID:f78a27d6a73d94c4baf04337e0add4dc"], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/SequenceSDK/Adapter/Tests/SequenceAdapterTests.asmdef.meta b/Assets/SequenceSDK/Adapter/Tests/SequenceAdapterTests.asmdef.meta new file mode 100644 index 000000000..875fd8229 --- /dev/null +++ b/Assets/SequenceSDK/Adapter/Tests/SequenceAdapterTests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 10f7025d30db4b42b66b8d746923d16f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SequenceSDK/Authentication/Tests/MockLogin.cs b/Assets/SequenceSDK/Authentication/Tests/MockLogin.cs index d6d49c9a3..0af1e5914 100644 --- a/Assets/SequenceSDK/Authentication/Tests/MockLogin.cs +++ b/Assets/SequenceSDK/Authentication/Tests/MockLogin.cs @@ -91,7 +91,7 @@ public void TryToRestoreSession() } - public void GuestLogin() + public Task GuestLogin() { throw new System.NotImplementedException(); } diff --git a/Assets/SequenceSDK/Authentication/Tests/MockLoginCustomEventTiming.cs b/Assets/SequenceSDK/Authentication/Tests/MockLoginCustomEventTiming.cs index 717b38dc3..2a6cd6895 100644 --- a/Assets/SequenceSDK/Authentication/Tests/MockLoginCustomEventTiming.cs +++ b/Assets/SequenceSDK/Authentication/Tests/MockLoginCustomEventTiming.cs @@ -80,7 +80,7 @@ public void TryToRestoreSession() } - public void GuestLogin() + public Task GuestLogin() { throw new System.NotImplementedException(); } diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter.meta new file mode 100644 index 000000000..784342f5a --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2fbda8d3e94840b497881679b1ee831b +timeCreated: 1750351968 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/ISequence.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/ISequence.cs new file mode 100644 index 000000000..3263bb496 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/ISequence.cs @@ -0,0 +1,133 @@ +using System; +using System.Numerics; +using System.Threading.Tasks; +using Sequence.EmbeddedWallet; +using Sequence.Marketplace; + +namespace Sequence.Adapter +{ + public interface ISequence + { + /// + /// The underlying Sequence Embedded Wallet reference. Use it for more control, such as transaction batches. + /// + public IWallet Wallet { get; } + + // ONBOARDING + + /// + /// Recover a wallet from secure storage. + /// + /// 'true' if a stored wallet was found or 'false' if not or if session recovery is disabled in your SequenceConfig file. + Task TryRecoverWalletFromStorage(); + + /// + /// Login using Email OTP. You will receive a code to your email. Use it with the 'ConfirmEmailCode' function to finish the login process. + /// + /// Email of the given user. + /// + Task EmailLogin(string email); + + /// + /// You receive a code after calling 'EmailLogin'. Use it with this function to complete the login process. + /// + /// + /// + /// + Task ConfirmEmailCode(string email, string code); + + /// + /// Login as a guest. When the user uninstall the application, they lose access to a guest wallet, unless they use our Account Federation feature to link the guest wallet to another login option. + /// + /// + Task GuestLogin(); + + /// + /// Sign In with Google. The user is redirected to an external browser. + /// + void GoogleLogin(); + + /// + /// Sign In with Apple. The user is redirected to an external browser. On iOS, this function uses the native Sign In SDK. + /// + void AppleLogin(); + + /// + /// Get an id token as a JWT you can use to verify the user on your backend. Use any JWKS library to verify this token. + /// + /// JWT Id Token + Task GetIdToken(); + + // POWER + + /// + /// Get the native token balance for your local user. For example, the native token on the Ethereum Mainnet is the amount of 'ETH' + /// + /// Balance in wei. + Task GetMyNativeTokenBalance(); + + /// + /// Get the token balance for your local user. + /// + /// Address of your token contract. This could be an ERC20, ERC1155, or ERC721 contract you deployed on https//sequence.build/ + /// Balance in wei and the token metadata. + Task<(BigInteger Balance, TokenMetadata TokenMetadata)> GetMyTokenBalance(Address tokenAddress); + + /// + /// + /// + /// + /// + Task GetTokenSupplies(Address tokenAddress); + + /// + /// Send any ERC20, ERC1155 or ERC721 token to another wallet. + /// + /// The address of the wallet you want to send the token to. + /// The address of your token. For example one you have deployed through https://sequence.build/ + /// Leave it blank for ERC20 contracts. + /// Leave it blank for ERC721 contracts. + /// Transaction receipt used to check transaction information onchain. + /// + Task SendToken(Address recipientAddress, Address tokenAddress, string tokenId, BigInteger amount); + + // MONETIZATION + + /// + /// Swap one of your tokens to another one. Make sure you have configured enough liquidity on a DEX such as UniSwap. + /// + /// + /// + /// + /// + Task SwapToken(Address sellToken, Address buyToken, BigInteger buyAmount); + + /// + /// Get all listings from your Marketplace on a given collection. Please make sure you have configured your Marketplace on https://sequence.build/ + /// + /// + /// + Task GetAllListingsFromMarketplace(Address collectionAddress); + + /// + /// Create a listing for a given token you own. Please make sure you have configured your Marketplace on https://sequence.build/ + /// + /// + /// + /// + /// + /// + /// + /// + Task CreateListingOnMarketplace(Address contractAddress, Address currencyAddress, + string tokenId, BigInteger amount, BigInteger pricePerToken, DateTime expiry); + + /// + /// Purchase an order from your Marketplace. Please make sure you have configured your Marketplace on https://sequence.build/ + /// + /// The order as returned from the 'GetAllListingsFromMarketplace' function. + /// The amount of orders you wish to purchase. + /// + Task PurchaseOrderFromMarketplace(Order order, BigInteger amount); + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/ISequence.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/ISequence.cs.meta new file mode 100644 index 000000000..c935552eb --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/ISequence.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 56129c265d66491ba5529d572d6ac2e0 +timeCreated: 1750401684 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/Sequence.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/Sequence.cs new file mode 100644 index 000000000..c62295487 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/Sequence.cs @@ -0,0 +1,193 @@ +using System; +using System.Numerics; +using System.Threading.Tasks; +using NUnit.Framework; +using Sequence.EmbeddedWallet; +using Sequence.Marketplace; + +namespace Sequence.Adapter +{ + public class Sequence : ISequence, IDisposable + { + private IWallet _wallet; + public IWallet Wallet + { + get + { + EnsureWalletReferenceExists(); + return _wallet; + } + set => _wallet = value; + } + + private SequenceLogin _loginHandler; + private SequenceLogin LoginHandler + { + get + { + _loginHandler ??= SequenceLogin.GetInstance(); + return _loginHandler; + } + } + + private readonly Chain _chain; + private readonly ChainIndexer _indexer; + private readonly CurrencySwap _swap; + private readonly MarketplaceReader _marketplace; + private Checkout _checkout; + + public Sequence(Chain chain) + { + _chain = chain; + _indexer = new ChainIndexer(_chain); + _swap = new CurrencySwap(_chain); + _marketplace = new MarketplaceReader(_chain); + + SequenceWallet.OnWalletCreated += UpdateWallet; + } + + public void Dispose() + { + SequenceWallet.OnWalletCreated -= UpdateWallet; + } + + public async Task TryRecoverWalletFromStorage() + { + var result = await _loginHandler.TryToRestoreSessionAsync(); + if (!result.StorageEnabled || result.Wallet == null) + return false; + + Wallet = result.Wallet; + return true; + } + + public async Task EmailLogin(string email) + { + await _loginHandler.Login(email); + } + + public async Task ConfirmEmailCode(string email, string code) + { + await _loginHandler.Login(email, code); + } + + public async Task GuestLogin() + { + await LoginHandler.GuestLogin(); + } + + public void GoogleLogin() + { + LoginHandler.GoogleLogin(); + } + + public void AppleLogin() + { + LoginHandler.AppleLogin(); + } + + public async Task GetIdToken() + { + var response = await Wallet.GetIdToken(); + return response.IdToken; + } + + public async Task GetMyNativeTokenBalance() + { + var result = await _indexer.GetNativeTokenBalance(Wallet.GetWalletAddress()); + return result.balanceWei; + } + + public async Task<(BigInteger Balance, TokenMetadata TokenMetadata)> GetMyTokenBalance(Address tokenAddress) + { + var args = new GetTokenBalancesArgs(Wallet.GetWalletAddress(), tokenAddress, true); + var result = await _indexer.GetTokenBalances(args); + var balance = result.balances[0]; + return (balance.balance, balance.tokenMetadata); + } + + public async Task GetTokenSupplies(Address tokenAddress) + { + var args = new GetTokenSuppliesArgs(tokenAddress, true); + var result = await _indexer.GetTokenSupplies(args); + return result.tokenIDs; + } + + public async Task SendToken(Address recipientAddress, Address tokenAddress, string tokenId, BigInteger amount) + { + EnsureWalletReferenceExists(); + var supplies = await _indexer.GetTokenSupplies(new GetTokenSuppliesArgs(tokenAddress, true)); + + Transaction transaction = supplies.contractType switch + { + ContractType.ERC20 => new SendERC20(tokenAddress, recipientAddress, amount.ToString()), + ContractType.ERC1155 => new SendERC1155(tokenAddress, recipientAddress, + new SendERC1155Values[] { new(tokenId, amount.ToString()) }), + ContractType.ERC721 => new SendERC721(tokenAddress, recipientAddress, tokenId), + _ => throw new Exception("Unknown contract type") + }; + + return await SendTransaction(new[] { transaction }); + } + + public async Task SwapToken(Address sellToken, Address buyToken, BigInteger buyAmount) + { + var walletAddress = Wallet.GetWalletAddress(); + var quote = await _swap.GetSwapQuote(walletAddress, buyToken, + sellToken, buyAmount.ToString(), true); + + await SendTransaction(new Transaction[] + { + new RawTransaction(sellToken, string.Empty, quote.approveData), + new RawTransaction(quote.to, quote.transactionValue, quote.transactionData), + } + ); + } + + public async Task GetAllListingsFromMarketplace(Address collectionAddress) + { + return await _marketplace.ListAllCollectibleListingsWithLowestPricedListingsFirst(collectionAddress); + } + + public async Task CreateListingOnMarketplace(Address contractAddress, Address currencyAddress, + string tokenId, BigInteger amount, BigInteger pricePerToken, DateTime expiry) + { + EnsureWalletReferenceExists(); + var steps = await _checkout.GenerateListingTransaction(contractAddress, tokenId, amount, + Marketplace.ContractType.ERC20, currencyAddress, pricePerToken, expiry); + + var transactions = steps.AsTransactionArray(); + return await SendTransaction(transactions); + } + + public async Task PurchaseOrderFromMarketplace(Order order, BigInteger amount) + { + EnsureWalletReferenceExists(); + var steps = await _checkout.GenerateBuyTransaction(order, amount); + var transactions = steps.AsTransactionArray(); + return await SendTransaction(transactions); + } + + private async Task SendTransaction(Transaction[] transactions) + { + var transactionResult = await Wallet.SendTransaction(_chain, transactions); + return transactionResult switch + { + SuccessfulTransactionReturn success => success.receipt.txnReceipt, + FailedTransactionReturn failed => throw new Exception($"Failed transaction {failed.error}"), + _ => throw new Exception("Unknown error while sending transaction.") + }; + } + + private void UpdateWallet(IWallet wallet) + { + Wallet = wallet; + _checkout = new Checkout(wallet, _chain); + } + + private void EnsureWalletReferenceExists() + { + Assert.IsNotNull(Wallet, "Please sign in first. For example, call 'new SequenceQuickstart().GuestLogin();'"); + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/Sequence.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/Sequence.cs.meta new file mode 100644 index 000000000..3f665db39 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/Sequence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da7ded03c99be413e8cc8f4cb9456807 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/SequenceAdapter.asmdef b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/SequenceAdapter.asmdef new file mode 100644 index 000000000..3e434670a --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/SequenceAdapter.asmdef @@ -0,0 +1,24 @@ +{ + "name": "SequenceAdapter", + "rootNamespace": "Sequence.Adapter", + "references": [ + "GUID:a35e3a53d4439435f8b36ed2c6158cd8", + "GUID:4e0a798abbda240658187632ae443a67", + "GUID:f7fd4ba36aabd1d499450c174865e70b", + "GUID:b4f9c0f8f363f439b9e337f79050f189", + "GUID:f78a27d6a73d94c4baf04337e0add4dc", + "GUID:403077141e1554429a890cbc129df651", + "GUID:19b9eb7db56cc47349571a4fbb0dd677", + "GUID:a67bc3d548bec4971b914c7b64c9e959", + "GUID:94b67778c44684afdab21990eebf60aa" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/SequenceAdapter.asmdef.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/SequenceAdapter.asmdef.meta new file mode 100644 index 000000000..566183831 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Adapter/SequenceAdapter.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 03d0ed9c50cfd4cb28a56d2d06580ffa +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/ILogin.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/ILogin.cs index 927ab6cee..2763f4c30 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/ILogin.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/ILogin.cs @@ -95,7 +95,7 @@ public interface ILogin /// /// Login as a guest /// - public void GuestLogin(); + public Task GuestLogin(); /// /// Login with PlayFab diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceLogin.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceLogin.cs index f68b4a2a1..ebff2f4a1 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceLogin.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/EmbeddedWallet/SequenceLogin.cs @@ -229,9 +229,9 @@ public void TryToRestoreSession() TryToLoginWithStoredSessionWallet(); } - public void GuestLogin() + public async Task GuestLogin() { - ConnectToWaaSAsGuest(); + await ConnectToWaaSAsGuest(); } private void SetupAuthenticatorAndListeners()