From 0f98e01c4b30501526504460b66f2deeaea11c52 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Sat, 27 Sep 2025 23:55:19 +0700 Subject: [PATCH 1/2] [Portal] .NET v3 & Unity v6 Documentation Closes BLD-349 --- apps/portal/redirects.mjs | 2 +- .../src/app/dotnet/api/quickstart/page.mdx | 55 ++ .../app/dotnet/contracts/extensions/page.mdx | 6 +- .../src/app/dotnet/contracts/prepare/page.mdx | 51 -- .../src/app/dotnet/getting-started/page.mdx | 8 +- .../app/dotnet/insight/quickstart/page.mdx | 98 ---- .../src/app/dotnet/nebula/quickstart/page.mdx | 100 ---- apps/portal/src/app/dotnet/sidebar.tsx | 103 +--- .../app/dotnet/transactions/create/page.mdx | 7 +- .../app/dotnet/transactions/prepare/page.mdx | 83 +++ .../universal-bridge/quickstart/page.mdx | 171 ------ apps/portal/src/app/dotnet/utils/page.mdx | 512 +++++------------- .../account-abstraction/page.mdx | 0 .../providers/ecosystem-wallet/page.mdx | 351 ------------ .../wallets/providers/engine-wallet/page.mdx | 71 --- .../wallets/providers/in-app-wallet/page.mdx | 144 ----- .../wallets/providers/private-key/page.mdx | 62 --- .../app/dotnet/wallets/server-wallet/page.mdx | 123 +++++ .../app/dotnet/wallets/user-wallet/page.mdx | 226 ++++++++ .../app/unity/v6/build-instructions/page.mdx | 65 +++ .../src/app/unity/v6/contracts/page.mdx | 34 ++ .../src/app/unity/v6/getting-started/page.mdx | 75 +++ apps/portal/src/app/unity/v6/layout.tsx | 26 + .../src/app/unity/v6/migration-guide/page.mdx | 162 ++++++ apps/portal/src/app/unity/v6/page.mdx | 75 +++ apps/portal/src/app/unity/v6/sidebar.tsx | 93 ++++ .../src/app/unity/v6/thirdwebmanager/page.mdx | 138 +++++ .../thirdwebmanager_client.png | Bin 0 -> 27236 bytes .../thirdwebmanager/thirdwebmanager_debug.png | Bin 0 -> 27435 bytes .../thirdwebmanager/thirdwebmanager_misc.png | Bin 0 -> 31476 bytes .../thirdwebmanager_preferences.png | Bin 0 -> 24452 bytes .../v6/wallets/account-abstraction/page.mdx | 65 +++ .../v6/wallets/ecosystem-wallet/page.mdx | 214 ++++++++ .../unity/v6/wallets/in-app-wallet/page.mdx | 191 +++++++ .../src/app/unity/v6/wallets/reown/page.mdx | 102 ++++ 35 files changed, 1906 insertions(+), 1507 deletions(-) create mode 100644 apps/portal/src/app/dotnet/api/quickstart/page.mdx delete mode 100644 apps/portal/src/app/dotnet/contracts/prepare/page.mdx delete mode 100644 apps/portal/src/app/dotnet/insight/quickstart/page.mdx delete mode 100644 apps/portal/src/app/dotnet/nebula/quickstart/page.mdx create mode 100644 apps/portal/src/app/dotnet/transactions/prepare/page.mdx delete mode 100644 apps/portal/src/app/dotnet/universal-bridge/quickstart/page.mdx rename apps/portal/src/app/dotnet/wallets/{providers => }/account-abstraction/page.mdx (100%) delete mode 100644 apps/portal/src/app/dotnet/wallets/providers/ecosystem-wallet/page.mdx delete mode 100644 apps/portal/src/app/dotnet/wallets/providers/engine-wallet/page.mdx delete mode 100644 apps/portal/src/app/dotnet/wallets/providers/in-app-wallet/page.mdx delete mode 100644 apps/portal/src/app/dotnet/wallets/providers/private-key/page.mdx create mode 100644 apps/portal/src/app/dotnet/wallets/server-wallet/page.mdx create mode 100644 apps/portal/src/app/dotnet/wallets/user-wallet/page.mdx create mode 100644 apps/portal/src/app/unity/v6/build-instructions/page.mdx create mode 100644 apps/portal/src/app/unity/v6/contracts/page.mdx create mode 100644 apps/portal/src/app/unity/v6/getting-started/page.mdx create mode 100644 apps/portal/src/app/unity/v6/layout.tsx create mode 100644 apps/portal/src/app/unity/v6/migration-guide/page.mdx create mode 100644 apps/portal/src/app/unity/v6/page.mdx create mode 100644 apps/portal/src/app/unity/v6/sidebar.tsx create mode 100644 apps/portal/src/app/unity/v6/thirdwebmanager/page.mdx create mode 100644 apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_client.png create mode 100644 apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_debug.png create mode 100644 apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_misc.png create mode 100644 apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_preferences.png create mode 100644 apps/portal/src/app/unity/v6/wallets/account-abstraction/page.mdx create mode 100644 apps/portal/src/app/unity/v6/wallets/ecosystem-wallet/page.mdx create mode 100644 apps/portal/src/app/unity/v6/wallets/in-app-wallet/page.mdx create mode 100644 apps/portal/src/app/unity/v6/wallets/reown/page.mdx diff --git a/apps/portal/redirects.mjs b/apps/portal/redirects.mjs index a6d9fba5838..6f6232a8621 100644 --- a/apps/portal/redirects.mjs +++ b/apps/portal/redirects.mjs @@ -414,7 +414,7 @@ const reactNativeRedirects = { const unityRedirects = { // top level - "/unity": "/unity/v5", + "/unity": "/unity/v6", // blocks "/unity/blocks/getblock": "/unity/v4/blocks/getblock", "/unity/blocks/getblockwithtransactions": diff --git a/apps/portal/src/app/dotnet/api/quickstart/page.mdx b/apps/portal/src/app/dotnet/api/quickstart/page.mdx new file mode 100644 index 00000000000..77ef917498b --- /dev/null +++ b/apps/portal/src/app/dotnet/api/quickstart/page.mdx @@ -0,0 +1,55 @@ +import { Details, createMetadata } from "@doc"; + +export const metadata = createMetadata({ + title: "ThirdwebApi | Thirdweb .NET SDK", + description: + "Low-level interaction with the Thirdweb API for enhanced SDK functionality and extensions.", +}); + +# [ThirdwebApi](/reference) .NET Integration +`ThirdwebApi` is the low-level HTTP client that powers the higher-level Thirdweb .NET SDK. Reach for it when you need to call https://api.thirdweb.com directly, build custom extensions, or inspect raw API payloads. The client works everywhere .NET runs, including Unity and MAUI. + +## What you unlock with ThirdwebApi + +| Capability | Why teams use it | +| --- | --- | +| **Authentication** | Orchestrate SMS/email codes, SIWE, OAuth redirects, and passkeys from your own backend before handing off JWTs to wallets. | +| **Wallet services** | Look up user/server wallets, enumerate tokens/NFTs, stream transaction history, sign messages, or send value programmatically. | +| **Smart contracts** | Batch read/write invocations, fetch metadata & signatures, deploy bytecode, and capture emitted events with pagination. | +| **Transactions pipeline** | Queue, monitor, and introspect Engine-backed transactions with execution metadata and status tracking. | +| **Tokens & bridge** | Launch ERC20s, list circulating assets, price fiat ↔ crypto, or bridge/swap liquidity across chains. | +| **Payments & x402** | Spin up hosted purchases, reconcile payment history, and prepare X402 machine-payable requests. | +| **AI assistant** | Embed natural-language orchestration that can read state, prepare contract calls, swap assets, or route transactions for end-users. + +> **Backend first** — All of the above accept your project secret via `x-secret-key`. Client-side use cases can opt into `x-client-id` + user JWT flows where supported. + +## When to reach for ThirdwebApi +- You need endpoints that aren't yet wrapped by the higher-level SDK helpers. +- You're building backend services that must authenticate with a Thirdweb secret key. +- You want full visibility into contract metadata, ABI definitions, and compilation artifacts. +- You plan to orchestrate auth, payments, or cross-chain flows without managing disparate providers. + +## Quickstart workflow + +### Instantiate an authenticated client +Create a reusable `ThirdwebClient` instance with your backend secret key. This unlocks every raw API surface exposed by Thirdweb. + +```csharp +// Program.cs / dependency container registration +var client = ThirdwebClient.Create(secretKey: Environment.GetEnvironmentVariable("THIRDWEB_SECRET_KEY")); +``` + +### Example operation +Contract metadata is the canonical example for low-level reads. The same pattern applies to other namespaces like `/v1/wallets/*`, `/v1/payments/*`, or `/ai/chat`—swap the method call but keep your authenticated client. + +```csharp +// using Newtonsoft.Json; + +var metadata = await client.Api.GetContractMetadataAsync( + chainId: 1, + address: "0xBd3531dA5CF5857e7CfAA92426877b022e612cf8" +); + +Console.WriteLine($"ABI: {JsonConvert.SerializeObject(metadata.Result.Output.Abi, Formatting.Indented)}"); +Console.WriteLine($"Compiler version: {metadata.Result.Compiler.Version}"); +``` \ No newline at end of file diff --git a/apps/portal/src/app/dotnet/contracts/extensions/page.mdx b/apps/portal/src/app/dotnet/contracts/extensions/page.mdx index c4551440c70..df2dcbb440c 100644 --- a/apps/portal/src/app/dotnet/contracts/extensions/page.mdx +++ b/apps/portal/src/app/dotnet/contracts/extensions/page.mdx @@ -36,8 +36,4 @@ var calldata = contract.CreateCallData("myFunction", param1, param2); ## Available Contract Extensions -Please refer to the `ThirdwebExtensions` [full reference](https://thirdweb-dev.github.io/dotnet/docs/Thirdweb.ThirdwebExtensions.html) for a complete list of available contract extensions. - -If you are using our [Marketplace](https://thirdweb.com/thirdweb.eth/MarketplaceV3) contract, check out our [Marketplace-specific](https://thirdweb-dev.github.io/dotnet/docs/Thirdweb.ThirdwebMarketplaceExtensions.html) extensions. - - +Please refer to the `ThirdwebExtensions` [full reference](https://thirdweb-dev.github.io/dotnet/docs/Thirdweb.ThirdwebExtensions.html) for a complete list of available contract extensions. \ No newline at end of file diff --git a/apps/portal/src/app/dotnet/contracts/prepare/page.mdx b/apps/portal/src/app/dotnet/contracts/prepare/page.mdx deleted file mode 100644 index a4a8156043a..00000000000 --- a/apps/portal/src/app/dotnet/contracts/prepare/page.mdx +++ /dev/null @@ -1,51 +0,0 @@ -import { Details, createMetadata } from "@doc"; - -export const metadata = createMetadata({ - title: "ThirdwebContract.Prepare | Thirdweb .NET SDK", - description: - "Instantiate a low level Transaction object from a contract prepare call.", -}); - -# ThirdwebContract.Prepare - -Instantiate a low level [Transaction](/dotnet/transactions/create) object from a contract prepare call. - -Useful for preparing, simulating and manipulating transactions before sending them to the blockchain. - -## Usage - -```csharp -ThirdwebTransaction transaction = await contract.Prepare(wallet, contract, "methodName", weiValue, parameters); -``` - -
- -### wallet (required) - -An instance of `IThirdwebWallet`. This represents the signer of the transaction, which can be any type of wallet provider. - -### contract (required) - -An instance of `ThirdwebContract`. Represents the smart contract you wish to interact with. - -### methodName (required) - -The name of the smart contract method you intend to call. Must be a `string`. - -### weiValue (required) - -The amount of Wei to send with payable transactions as a `BigInteger`. This is often set to 0 for methods that don't require sending Ether. - -### parameters (optional) - -The parameters to pass to the smart contract method, if any. Provide these as a comma-separated list after weiValue. - -
- -
- -### Transaction - -Returns an instance of `ThirdwebTransaction` configured with the provided parameters, ready to be sent to the blockchain. - -
diff --git a/apps/portal/src/app/dotnet/getting-started/page.mdx b/apps/portal/src/app/dotnet/getting-started/page.mdx index c4d4c96f619..45ff8a2f90b 100644 --- a/apps/portal/src/app/dotnet/getting-started/page.mdx +++ b/apps/portal/src/app/dotnet/getting-started/page.mdx @@ -77,15 +77,15 @@ Check the SDK documentation for more advanced features like interacting with sma diff --git a/apps/portal/src/app/dotnet/insight/quickstart/page.mdx b/apps/portal/src/app/dotnet/insight/quickstart/page.mdx deleted file mode 100644 index e95d29e933f..00000000000 --- a/apps/portal/src/app/dotnet/insight/quickstart/page.mdx +++ /dev/null @@ -1,98 +0,0 @@ -import { Details, createMetadata } from "@doc"; - -export const metadata = createMetadata({ - title: "ThirdwebInsight | Thirdweb .NET SDK", - description: - "Instantiate Insight to fetch all the blockchain data you can fathom.", -}); - -# [Insight](https://thirdweb.com/insight) Indexer .NET Integration -Insight is an extremely useful and performant tool to query blockchain data and can decorate it quite nicely too. - -Did you know that vitalik, on Ethereum, Polygon and Arbitrum alone owns 7811 ERC20s, 34,362 ERC721s and 2,818 ERC1155s? -Let's go through some examples! - -### Instantiation -```csharp -using Thirdweb.Indexer; - -// Create a ThirdwebInsight instance -var insight = await ThirdwebInsight.Create(client); -``` - -### Simple Filters -```csharp -// Setup some filters -var address = await Utils.GetAddressFromENS(client, "vitalik.eth"); -var chains = new BigInteger[] { 1, 137, 42161 }; -``` - -### Fetching all tokens owned by a given address -```csharp -// Fetch all token types -var tokens = await insight.GetTokens(address, chains); -Console.WriteLine($"ERC20 Count: {tokens.erc20Tokens.Length} | ERC721 Count: {tokens.erc721Tokens.Length} | ERC1155 Count: {tokens.erc1155Tokens.Length}"); -``` - -### Fetching a specific type of token owned by a given address -```csharp -// Fetch ERC20s only -var erc20Tokens = await insight.GetTokens_ERC20(address, chains); -Console.WriteLine($"ERC20 Tokens: {JsonConvert.SerializeObject(erc20Tokens, Formatting.Indented)}"); - -// Fetch ERC721s only -var erc721Tokens = await insight.GetTokens_ERC721(address, chains); -Console.WriteLine($"ERC721 Tokens: {JsonConvert.SerializeObject(erc721Tokens, Formatting.Indented)}"); - -// Fetch ERC1155s only -var erc1155Tokens = await insight.GetTokens_ERC1155(address, chains); -Console.WriteLine($"ERC1155 Tokens: {JsonConvert.SerializeObject(erc1155Tokens, Formatting.Indented)}"); -``` - -### Fetching Events -Note: most of these parameters are optional (and some are not showcased here) -```csharp -// Fetch events (great amount of optional filters available) -var events = await insight.GetEvents( - chainIds: new BigInteger[] { 1 }, // ethereum - contractAddress: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", // bored apes - eventSignature: "Transfer(address,address,uint256)", // transfer event - fromTimestamp: Utils.GetUnixTimeStampNow() - 3600, // last hour - sortBy: SortBy.TransactionIndex, // block number, block timestamp or transaction index - sortOrder: SortOrder.Desc, // latest first - limit: 5 // last 5 transfers -); -Console.WriteLine($"Events: {JsonConvert.SerializeObject(events, Formatting.Indented)}"); -``` - -### Fetching Transactions -```csharp -// Fetch transactions (great amount of optional filters available) -var transactions = await insight.GetTransactions( - chainIds: new BigInteger[] { 1 }, // ethereum - contractAddress: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", // bored apes - fromTimestamp: Utils.GetUnixTimeStampNow() - 3600, // last hour - sortBy: SortBy.TransactionIndex, // block number, block timestamp or transaction index - sortOrder: SortOrder.Desc, // latest first - limit: 5 // last 5 transactions -); -Console.WriteLine($"Transactions: {JsonConvert.SerializeObject(transactions, Formatting.Indented)}"); -``` - -### Fetching NFTs with Metadata -Insight can return rich NFT Metadata. Extensions can turn Insight results into familiar NFT objects. - -```csharp -// Fetch ERC721s with extra metadata returned -var erc721Tokens = await insight.GetTokens_ERC721(address, chains, withMetadata: true); - -// Use ToNFT or ToNFTList extensions -var convertedNft = erc721Tokens[0].ToNFT(); -var convertedNfts = erc721Tokens.ToNFTList(); - -// Use NFT Extensions (GetNFTImageBytes, or GetNFTSprite in Unity) -var imageBytes = await convertedNft.GetNFTImageBytes(client); -var pathToSave = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "nft.png"); -await File.WriteAllBytesAsync(pathToSave, imageBytes); -Console.WriteLine($"NFT image saved to: {pathToSave}"); -``` \ No newline at end of file diff --git a/apps/portal/src/app/dotnet/nebula/quickstart/page.mdx b/apps/portal/src/app/dotnet/nebula/quickstart/page.mdx deleted file mode 100644 index fbb5dbac692..00000000000 --- a/apps/portal/src/app/dotnet/nebula/quickstart/page.mdx +++ /dev/null @@ -1,100 +0,0 @@ -import { Details, createMetadata } from "@doc"; - -export const metadata = createMetadata({ - title: "ThirdwebNebula | Thirdweb .NET SDK", - description: - "Instantiate Nebula to interact with a blockchain-powered AI assistant.", -}); - -# [Nebula AI](https://thirdweb.com/nebula) .NET Integration -The last piece of the puzzle required to create .NET apps and games leveraging a blockchain-powered AI assistant or NPC that also has the power to fully execute transactions. - -## Read, Write, Reason. -The best way to understand is to look at examples. - -We'll prepare some context for the AI - here, we'll generate a basic `PrivateKeyWallet` and upgrade it to a `SmartWallet` on Sepolia. -```csharp -// Prepare some context -var myChain = 11155111; // Sepolia -var mySigner = await PrivateKeyWallet.Generate(client); -var myWallet = await SmartWallet.Create(mySigner, myChain); -var myContractAddress = "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8"; // DropERC1155 -var usdcAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; // Sepolia USDC -``` - -A one liner creates a Nebula session. -```csharp -// Create a Nebula session -var nebula = await ThirdwebNebula.Create(client); -``` - -You can **Chat** with Nebula. The Chat method accepts any `IThirdwebWallet` as an optional parameter, context will automatically be updated. -```csharp -// Chat, passing wallet context -var response1 = await nebula.Chat(message: "What is my wallet address?", wallet: myWallet); -Console.WriteLine($"Response 1: {response1.Message}"); -``` - -You may also pass it smart contract context. -```csharp -// Chat, passing contract context -var response2 = await nebula.Chat( - message: "What's the total supply of token id 0 for this contract?", - context: new NebulaContext(contractAddresses: new List { myContractAddress }, chainIds: new List { myChain }) -); -Console.WriteLine($"Response 2: {response2.Message}"); -``` - -You can have a full on conversation with it with our **Chat** override accepting multiple messages. -```csharp -// Chat, passing multiple messages and context -var response3 = await nebula.Chat( - messages: new List - { - new($"Tell me the name of this contract: {myContractAddress}", NebulaChatRole.User), - new("The name of the contract is CatDrop", NebulaChatRole.Assistant), - new("What's the symbol of this contract?", NebulaChatRole.User), - }, - context: new NebulaContext(contractAddresses: new List { myContractAddress }, chainIds: new List { myChain }) -); -Console.WriteLine($"Response 3: {response3.Message}"); -``` - -Chatting is cool, but what if I just want it to _**do**_ things and stop talking so much? - -You can **Execute** transactions directly with a simple prompt. -```csharp -// Execute, this directly sends transactions -var executionResult = await nebula.Execute("Approve 1 USDC to vitalik.eth", wallet: myWallet, context: new NebulaContext(contractAddresses: new List() { usdcAddress })); -if (executionResult.TransactionReceipts != null && executionResult.TransactionReceipts.Count > 0) -{ - Console.WriteLine($"Receipt: {executionResult.TransactionReceipts[0]}"); -} -else -{ - Console.WriteLine($"Message: {executionResult.Message}"); -} -``` - -Similarly, **Execute** can take in multiple messages. -```csharp -// Batch execute -var batchExecutionResult = await nebula.Execute( - new List - { - new("What's the address of vitalik.eth", NebulaChatRole.User), - new("The address of vitalik.eth is 0xd8dA6BF26964aF8E437eEa5e3616511D7G3a3298", NebulaChatRole.Assistant), - new("Approve 1 USDC to them", NebulaChatRole.User), - }, - wallet: myWallet, - context: new NebulaContext(contractAddresses: new List() { usdcAddress }) -); -if (batchExecutionResult.TransactionReceipts != null && batchExecutionResult.TransactionReceipts.Count > 0) -{ - Console.WriteLine($"Receipts: {JsonConvert.SerializeObject(batchExecutionResult.TransactionReceipts, Formatting.Indented)}"); -} -else -{ - Console.WriteLine($"Message: {batchExecutionResult.Message}"); -} -``` \ No newline at end of file diff --git a/apps/portal/src/app/dotnet/sidebar.tsx b/apps/portal/src/app/dotnet/sidebar.tsx index 793bd19f9f5..565443b9049 100644 --- a/apps/portal/src/app/dotnet/sidebar.tsx +++ b/apps/portal/src/app/dotnet/sidebar.tsx @@ -2,35 +2,6 @@ import { CodeIcon, ExternalLinkIcon, ZapIcon } from "lucide-react"; import type { SideBar } from "@/components/Layouts/DocLayout"; import type { SidebarLink } from "@/components/others/Sidebar"; -const walletProviders: SidebarLink = (() => { - const parentSlug = "/dotnet/wallets/providers"; - return { - links: [ - { - href: `${parentSlug}/in-app-wallet`, - name: "In App Wallet", - }, - { - href: `${parentSlug}/ecosystem-wallet`, - name: "Ecosystem Wallet", - }, - { - href: `${parentSlug}/account-abstraction`, - name: "Account Abstraction", - }, - { - href: `${parentSlug}/private-key`, - name: "Private Key Wallet", - }, - { - href: `${parentSlug}/engine-wallet`, - name: "Engine Wallet", - }, - ], - name: "Wallet Providers", - }; -})(); - const walletActions: SidebarLink = (() => { const parentSlug = "/dotnet/wallets/actions"; return { @@ -179,15 +150,12 @@ const walletActions: SidebarLink = (() => { })(); const contracts: SidebarLink = { + isCollapsible: false, links: [ { href: "/dotnet/contracts/create", name: "Create Contract", }, - { - href: "/dotnet/contracts/extensions", - name: "Contract Extensions", - }, { href: "/dotnet/contracts/read", name: "Read Contract", @@ -197,15 +165,20 @@ const contracts: SidebarLink = { name: "Write Contract", }, { - href: "/dotnet/contracts/prepare", - name: "Prepare Transaction", + href: "/dotnet/contracts/extensions", + name: "Contract Extensions", }, ], - name: "Contract Interactions", + name: "Contracts", }; const transactions: SidebarLink = { + isCollapsible: false, links: [ + { + href: "/dotnet/transactions/prepare", + name: "Prepare Contract Transaction", + }, { href: "/dotnet/transactions/create", name: "Create Transaction", @@ -219,7 +192,7 @@ const transactions: SidebarLink = { name: "Static Methods", }, ], - name: "Transaction Builder", + name: "Transactions", }; export const sidebar: SideBar = { @@ -245,7 +218,7 @@ export const sidebar: SideBar = { isCollapsible: true, links: [ { - href: "/unity/v5", + href: "/unity", name: "Unity", }, { @@ -283,69 +256,43 @@ export const sidebar: SideBar = { { separator: true, }, - { - isCollapsible: false, - links: [walletProviders, walletActions], - name: "Wallets", - }, - { - separator: true, - }, - { - isCollapsible: false, - links: [contracts, transactions], - name: "Transactions", - }, - { - separator: true, - }, { isCollapsible: false, links: [ { - href: "/dotnet/universal-bridge/quickstart", - name: "Quickstart", + href: `/dotnet/wallets/user-wallet`, + name: "User Wallet", }, { - href: "https://thirdweb-dev.github.io/dotnet/docs/Thirdweb.Bridge.ThirdwebBridge.html", - name: "Full Reference", - }, - ], - name: "Payments", - }, - { - separator: true, - }, - { - isCollapsible: false, - links: [ - { - href: "/dotnet/insight/quickstart", - name: "Quickstart", + href: `/dotnet/wallets/server-wallet`, + name: "Server Wallet", }, { - href: "https://thirdweb-dev.github.io/dotnet/docs/Thirdweb.Indexer.ThirdwebInsight.html", - name: "Insight Full Reference", + href: `/dotnet/wallets/account-abstraction`, + name: "Account Abstraction", }, + walletActions, ], - name: "Indexer", + name: "Wallets", }, { separator: true, }, + contracts, + transactions, { isCollapsible: false, links: [ { - href: "/dotnet/nebula/quickstart", + href: "/dotnet/api/quickstart", name: "Quickstart", }, { - href: "https://thirdweb-dev.github.io/dotnet/docs/Thirdweb.AI.ThirdwebNebula.html", - name: "Nebula Full Reference", + href: "/reference", + name: "API Full Reference", }, ], - name: "AI", + name: "API", }, { separator: true, diff --git a/apps/portal/src/app/dotnet/transactions/create/page.mdx b/apps/portal/src/app/dotnet/transactions/create/page.mdx index 1e3fd3393c3..e65bcb4a196 100644 --- a/apps/portal/src/app/dotnet/transactions/create/page.mdx +++ b/apps/portal/src/app/dotnet/transactions/create/page.mdx @@ -8,9 +8,10 @@ export const metadata = createMetadata({ # ThirdwebTransaction.Create - - If you are interacting with a contract, it is best to use - [ThirdwebContract.Prepare](/dotnet/contracts/prepare) to create a transaction. + + When you're starting from a contract method, use + [ThirdwebContract.Prepare](/dotnet/transactions/prepare) to build the transaction + before customizing it here. The `ThirdwebTransaction.Create` method allows advanced users to manually create transactions using specific wallet credentials and transaction inputs. This method is ideal for users who need precise control over the transaction details. diff --git a/apps/portal/src/app/dotnet/transactions/prepare/page.mdx b/apps/portal/src/app/dotnet/transactions/prepare/page.mdx new file mode 100644 index 00000000000..6b793e4be27 --- /dev/null +++ b/apps/portal/src/app/dotnet/transactions/prepare/page.mdx @@ -0,0 +1,83 @@ +import { Details, Callout, createMetadata } from "@doc"; + +export const metadata = createMetadata({ + title: "ThirdwebContract.Prepare | Thirdweb .NET SDK", + description: + "Generate a ThirdwebTransaction from a contract call before customizing or sending it.", +}); + +# ThirdwebContract.Prepare + +`ThirdwebContract.Prepare` bridges contract interactions and the transaction tooling within the Thirdweb .NET SDK. Use it when you want to start from a contract method, but need fine-grained control over the resulting `ThirdwebTransaction` before dispatching it. + + +Looking for the contract APIs themselves? Check out the [Contracts guides](/dotnet/contracts/create) for creating, reading, and writing before you promote a call into a transaction. + + +## Why prepare first? + +- **Simulation & inspection** – Prepare the transaction and run [`ThirdwebTransaction.Simulate`](/dotnet/transactions/static) before sending. +- **Customization** – Chain the [`ThirdwebTransaction` instance methods](/dotnet/transactions/instance) to adjust calldata, value, gas, or paymaster options. +- **Wallet-agnostic** – Works with any `IThirdwebWallet`, letting you bridge user wallets, smart wallets, or server wallets into the same flow. + +## Usage + +```csharp +ThirdwebTransaction transaction = await contract.Prepare( + wallet, + contract, + "methodName", + weiValue, + parameters +); +``` + +Once prepared, you can tweak or send the transaction: + +```csharp +// Optional: override calldata, value, or other fields +transaction = transaction + .SetValue(BigInteger.Zero) + .SetGasLimit(250000); + +// Optional: simulate or estimate costs +await ThirdwebTransaction.Simulate(transaction); +var totalCosts = await ThirdwebTransaction.EstimateTotalCosts(transaction); + +// Finally: send the transaction and wait for the receipt +TransactionReceipt receipt = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(transaction); +``` + +## Parameters + +
+ +### wallet (required) + +An instance of `IThirdwebWallet`. This signer provides the context for populating sender-specific defaults and will ultimately authorize the transaction. + +### contract (required) + +An instance of `ThirdwebContract`. Represents the smart contract whose method you're calling. + +### methodName (required) + +The name of the contract method to invoke. Must be a `string`. + +### weiValue (required) + +The amount of Wei to send with the transaction, as a `BigInteger`. Use `BigInteger.Zero` when no value transfer is needed. + +### parameters (optional) + +The arguments to forward to the contract method. Provide them in the same order as the ABI definition. + +
+ +## Return value + +
+ +Returns a fully populated `ThirdwebTransaction` that mirrors the contract invocation. You can send it immediately or pipe it through additional configuration helpers before execution. + +
diff --git a/apps/portal/src/app/dotnet/universal-bridge/quickstart/page.mdx b/apps/portal/src/app/dotnet/universal-bridge/quickstart/page.mdx deleted file mode 100644 index e096496bd09..00000000000 --- a/apps/portal/src/app/dotnet/universal-bridge/quickstart/page.mdx +++ /dev/null @@ -1,171 +0,0 @@ -import { Details, createMetadata } from "@doc"; - -export const metadata = createMetadata({ - title: "Payments | Thirdweb .NET SDK", - description: - "Enable your users to trade assets across any supported chain using any token.", -}); - -# Payments .NET Integration - -Payments enables your users to trade assets across any supported chain using any token. - -To streamline onchain asset trading, we've added .NET extensions that work seamlessly with any `IThirdwebWallet` nicely. - -## Core APIs - -The design is akin to letting us know what your intent is. -- Buy: "I want to buy x USDC on y Chain using z Token" -- Sell: "I want to sell x USDC on y Chain for z Token" -- Transfer: "Just transfer all my money to vitalik" -- Onramp: "I want to buy x USDC on y Chain by paying with card" - -We will return the transactions needed to achieve whatever you desire. -You may then handle execution yourself or use our extensions. - -### Instantiation -```csharp -using Thirdweb.Bridge; - -// Create a ThirdwebBridge instance -var bridge = await ThirdwebBridge.Create(client); -``` - -### Buy - Get a quote for buying a specific amount of tokens -```csharp -var buyQuote = await bridge.Buy_Quote( - originChainId: 1, - originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum - destinationChainId: 324, - destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync - buyAmountWei: BigInteger.Parse("0.1".ToWei()) -); -Console.WriteLine($"Buy quote: {JsonConvert.SerializeObject(buyQuote, Formatting.Indented)}"); -``` - -### Buy - Get an executable set of transactions (alongside a quote) for buying a specific amount of tokens -```csharp -var preparedBuy = await bridge.Buy_Prepare( - originChainId: 1, - originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum - destinationChainId: 324, - destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync - buyAmountWei: BigInteger.Parse("0.1".ToWei()), - sender: await Utils.GetAddressFromENS(client, "vitalik.eth"), - receiver: await myWallet.GetAddress() -); -Console.WriteLine($"Prepared Buy contains {preparedBuy.Transactions.Count} transaction(s)!"); -``` - -### Sell - Get a quote for selling a specific amount of tokens -```csharp -var sellQuote = await bridge.Sell_Quote( - originChainId: 324, - originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync - destinationChainId: 1, - destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum - sellAmountWei: BigInteger.Parse("0.1".ToWei()) -); -Console.WriteLine($"Sell quote: {JsonConvert.SerializeObject(sellQuote, Formatting.Indented)}"); -``` - -### Sell - Get an executable set of transactions (alongside a quote) for selling a specific amount of tokens -```csharp -var preparedSell = await bridge.Sell_Prepare( - originChainId: 324, - originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync - destinationChainId: 1, - destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum - sellAmountWei: BigInteger.Parse("0.1".ToWei()), - sender: await Utils.GetAddressFromENS(client, "vitalik.eth"), - receiver: await myWallet.GetAddress() -); -Console.WriteLine($"Prepared Sell contains {preparedSell.Transactions.Count} transaction(s)!"); -``` - -### Transfer - Get an executable transaction for transferring a specific amount of tokens -_Why not just transfer with the SDK? Stay tuned for webhooks, think direct payments!_ -```csharp -var preparedTransfer = await bridge.Transfer_Prepare( - chainId: 137, - tokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync - transferAmountWei: BigInteger.Parse("0.1".ToWei()), - sender: await Utils.GetAddressFromENS(client, "vitalik.eth"), - receiver: await myWallet.GetAddress() -); -Console.WriteLine($"Prepared Transfer: {JsonConvert.SerializeObject(preparedTransfer, Formatting.Indented)}"); -``` - -## Manual Execution -_This is not production code, we're just showcasing some of the APIs that would help you execute and poll status here._ -```csharp -// You may use our extensions to execute yourself... -var myTx = await preparedTransfer.Transactions[0].ToThirdwebTransaction(myWallet); -var myHash = await ThirdwebTransaction.Send(myTx); - -// ...and poll for the status... -var status = await bridge.Status(transactionHash: myHash, chainId: 1); -var isComplete = status.StatusType == StatusType.COMPLETED; -Console.WriteLine($"Status: {JsonConvert.SerializeObject(status, Formatting.Indented)}"); - -// Or use our Execute extensions directly to handle everything for you! -``` - -## Managed Execution -The SDK comes with some extensions that simplify the use of `ThirdwebBridge`. - -```csharp -// Execute a prepared Buy -var buyResult = await bridge.Execute(myWallet, preparedBuy); -var buyHashes = buyResult.Select(receipt => receipt.TransactionHash).ToList(); -Console.WriteLine($"Buy hashes: {JsonConvert.SerializeObject(buyHashes, Formatting.Indented)}"); - -// Execute a prepared Sell -var sellResult = await bridge.Execute(myWallet, preparedSell); -var sellHashes = sellResult.Select(receipt => receipt.TransactionHash).ToList(); -Console.WriteLine($"Sell hashes: {JsonConvert.SerializeObject(sellHashes, Formatting.Indented)}"); - -// Execute a prepared Transfer -var transferResult = await bridge.Execute(myWallet, preparedTransfer); -var transferHashes = transferResult.Select(receipt => receipt.TransactionHash).ToList(); -Console.WriteLine($"Transfer hashes: {JsonConvert.SerializeObject(transferHashes, Formatting.Indented)}"); -``` - -## Onramping with Fiat -The onramp flow will return a link for you to display/open as you please. You may poll the status of that onramp by its ID. -In some cases, you may receive an additional set of onchain steps required to get to your destination token post on-ramp, in such cases, you may use our extension `IsSwapRequiredPostOnramp` to check, and if a swap is indeed required, you may use our `Execute` extensions to execute the transactions, or manually execute them by going through each `Step`. - -```csharp -// Onramp - Get a quote for buying crypto with Fiat -var preparedOnramp = await bridge.Onramp_Prepare( - onramp: OnrampProvider.Coinbase, - chainId: 8453, - tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base - amount: "10000000", - receiver: await myWallet.GetAddress() -); -Console.WriteLine($"Onramp link: {preparedOnramp.Link}"); -Console.WriteLine($"Full onramp quote and steps data: {JsonConvert.SerializeObject(preparedOnramp, Formatting.Indented)}"); - -while (true) -{ - var onrampStatus = await bridge.Onramp_Status(id: preparedOnramp.Id); - Console.WriteLine($"Full Onramp Status: {JsonConvert.SerializeObject(onrampStatus, Formatting.Indented)}"); - if (onrampStatus.StatusType is StatusType.COMPLETED or StatusType.FAILED) - { - break; - } - await ThirdwebTask.Delay(5000); -} - -if (preparedOnramp.IsSwapRequiredPostOnramp()) -{ - // Execute additional steps that are required post-onramp to get to your token, manually or via the Execute extension - var receipts = await bridge.Execute(myWallet, preparedOnramp); - Console.WriteLine($"Onramp receipts: {JsonConvert.SerializeObject(receipts, Formatting.Indented)}"); -} -else -{ - Console.WriteLine("No additional steps required post-onramp, you can use the tokens directly!"); -} -``` \ No newline at end of file diff --git a/apps/portal/src/app/dotnet/utils/page.mdx b/apps/portal/src/app/dotnet/utils/page.mdx index 44c1ddabc08..0c90ba4b72c 100644 --- a/apps/portal/src/app/dotnet/utils/page.mdx +++ b/apps/portal/src/app/dotnet/utils/page.mdx @@ -1,534 +1,310 @@ -import { Details, createMetadata } from "@doc"; +import { Callout, Details, createMetadata } from "@doc"; export const metadata = createMetadata({ - title: "Common Utils | Thirdweb .NET SDK", - description: "Useful utilities offered by thirdweb.", + title: "Common Utils | Thirdweb .NET SDK", + description: "Frequently used helpers for working with the Thirdweb .NET SDK.", }); # Utils -A collection of common utility functions that are useful for interacting with the Thirdweb .NET SDK or the blockchain in general. Full list available [here](https://thirdweb-dev.github.io/dotnet/docs/Thirdweb.Utils.html). +`Utils` bundles the SDK's most handy helpers for formatting values, working with addresses, and preparing typed data. If you need an API not covered here, check the [full reference](https://thirdweb-dev.github.io/dotnet/docs/Thirdweb.Utils.html). -## Constants.ADDRESS_ZERO +## Useful constants -A `string` representing the zero address `0x0000000000000000000000000000000000000000`. - -Useful for passing the zero address to various functions. - -## Constants.NATIVE_TOKEN_ADDRESS - -A `string` representing the native token address `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`. +```csharp +Constants.ADDRESS_ZERO // 0x0000…0000 (zero address) +Constants.NATIVE_TOKEN_ADDRESS // 0xEeee…EEeE (native token sentinel) +Constants.DECIMALS_18 // 1e18 as a double (decimal scaling helper) -Useful for passing the native token address (ETH or equivalent) to various functions. +Constants.IERC20_INTERFACE_ID // ERC-165 ID for ERC-20 +Constants.IERC721_INTERFACE_ID // ERC-165 ID for ERC-721 +Constants.IERC1155_INTERFACE_ID // ERC-165 ID for ERC-1155 -## Constants.ENTRYPOINT_ADDRESS_V06 +Constants.ENTRYPOINT_ADDRESS_V06 // Default ERC-4337 entry point v0.6 +Constants.ENTRYPOINT_ADDRESS_V07 // Default ERC-4337 entry point v0.7 -A `string` representing the entrypoint address for Account Abstraction 0.7.0 `0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789`. +Constants.DEFAULT_FACTORY_ADDRESS_V06 // Default Thirdweb factory v0.6 +Constants.DEFAULT_FACTORY_ADDRESS_V07 // Default Thirdweb factory v0.7 -## Constants.ENTRYPOINT_ADDRESS_V07 +Constants.EIP_1271_MAGIC_VALUE // Expected return value for EIP-1271 signature validation +Constants.ERC_6492_MAGIC_VALUE // Magic value appended to ERC-6492 signatures +``` -A `string` representing the entrypoint address for Account Abstraction 0.7.0 `0x0000000071727De22E5E9d8BAf0edAc6f37da032`. +## Chain helpers -## GetSocialProfiles +### Utils.GetChainMetadata -Fetches the social profiles for the given address or ENS using a Thirdweb client. +Fetch metadata (native token, RPCs, explorers, stack type, etc.) for a given chain ID. Results are cached in-memory. ```csharp -var socialProfiles = await Utils.GetSocialProfiles(client, addressOrEns); +ThirdwebChainData data = await Utils.GetChainMetadata(client, chainId); ```
### client -A `ThirdwebClient` object representing the client to use for fetching social profiles. +An initialized `ThirdwebClient`. -### addressOrEns +### chainId -A `string` representing the wallet address or ENS to fetch social profiles for. +`BigInteger` chain identifier.
-
- -### socialProfiles +
-A `SocialProfiles` object containing the fetched social profiles. +`ThirdwebChainData` describing the chain.
-## GetENSFromAddress +### Utils.IsZkSync -Fetches the ENS name for the given address using a Thirdweb client. +Quickly check if a chain uses zkSync infrastructure. Falls back to chain metadata when the ID is not one of the known zkSync networks. ```csharp -var ensName = await Utils.GetENSFromAddress(client, address); +bool isZkSync = await Utils.IsZkSync(client, chainId); ```
### client -A `ThirdwebClient` object representing the client to use for fetching the ENS name. +`ThirdwebClient` used for metadata lookups. -### address +### chainId -A `string` representing the wallet address to fetch the ENS name for. +`BigInteger` chain identifier.
-
- -### ensName +
-A `string` representing the fetched ENS name. +`true` if the chain is zkSync-based.
-## GetAddressFromENS +## Address & identity helpers + +### Utils.ToChecksumAddress -Fetches the address for the given ENS name using a Thirdweb client. +Normalize any hex address to its EIP-55 checksum representation. ```csharp -var address = await Utils.GetAddressFromENS(client, ensName); +string checksum = address.ToChecksumAddress(); ``` -
- -### client - -A `ThirdwebClient` object representing the client to use for fetching the address. - -### ensName - -A `string` representing the ENS name to fetch the address for. - -
- -
- -### address +### Utils.IsValidAddress -A `string` representing the fetched address. - -
- -## GetChainMetadata - -Fetches chain data for a specified chain ID using a Thirdweb client. +Lightweight validation for EVM addresses. ```csharp -var chainData = await Utils.GetChainMetadata (client, chainId); +if (!input.IsValidAddress()) { + throw new ArgumentException("Expected an EVM address"); +} ``` -
+### Utils.GetENSFromAddress / Utils.GetAddressFromENS -### client - -A `ThirdwebClient` object representing the client to use for fetching chain data. - -### chainId - -A `BigInteger` representing the chain ID to fetch data for. - -
- -
- -### chainData - -A `ThirdwebChainData` object containing the fetched chain data such as native token name, symbol, decimals, etc. - -
- -## HexConcat - -Concatenates the given hex strings into a single hex string. +Resolve between ENS names and addresses. Lookups are cached per process. ```csharp -var concatenatedHex = Utils.HexConcat("0x1234", "0x5678"); +string ens = await Utils.GetENSFromAddress(client, address); +string addr = await Utils.GetAddressFromENS(client, ensName); ``` -
- -### hexStrings - -An array of hexadecimal `string` values to concatenate. - -
- -
+### Utils.GetSocialProfiles -### concatenatedHex - -The concatenated hexadecimal `string`. - -
- -## HashPrefixedMessage - -Hashes the given message bytes with a prefixed message for Ethereum signing. +Pull public social metadata (Lens, Farcaster, etc.) for an address or ENS. ```csharp -var hashedMessage = messageBytes.HashPrefixedMessage(); +SocialProfiles profiles = await Utils.GetSocialProfiles(client, "vitalik.eth"); ``` -
- -### messageBytes - -A `byte[]` representing the message to hash. - -
+## Encoding & hashing -
+### Utils.HexConcat -### hashedMessage - -A `byte[]` representing the hashed message. - -
- -## HashMessage - -Hashes the given message using SHA3 Keccak hashing. +Join multiple hex strings (with `0x` prefixes) into a single blob. ```csharp -var hashedMessage = message.HashMessage(); +string callData = Utils.HexConcat("0x1234", "0x5566"); ``` -
- -### message - -A `string` representing the message to hash. - -
- -
- -### hashedMessage - -A `string` representing the hashed message in hexadecimal format. - -
- -## BytesToHex +### BytesToHex / HexToBytes -Converts a byte array to a hexadecimal string. +Extension helpers for toggling between byte arrays and hex. ```csharp -var hexString = byteArray.BytesToHex(); +byte[] bytes = hex.HexToBytes(); +string hexAgain = bytes.BytesToHex(); ``` -
- -### bytes - -A `byte[]` representing the byte array to convert. - -
- -
+### HexToNumber / NumberToHex -### hexString - -A `string` representing the converted hexadecimal string. - -
- -## HexToBytes - -Converts a hexadecimal string to a byte array. +Convert between hex strings and `BigInteger` values. ```csharp -var byteArray = hexString.HexToBytes(); +BigInteger gas = "0x5208".HexToNumber(); +string hexValue = gas.NumberToHex(); ``` -
- -### hex +### StringToHex / HexToString -A `string` representing the hexadecimal string to convert. - -
- -
- -### byteArray - -A `byte[]` representing the converted byte array. - -
- -## StringToHex - -Converts a regular string to a hexadecimal string. +Encode or decode UTF-8 strings as hex. ```csharp -var hexString = "Hello".StringToHex(); +string payload = "Hello".StringToHex(); +string message = payload.HexToString(); ``` -
- -### str - -A `string` representing the regular string to convert. +### HashPrefixedMessage & HashMessage -
- -
- -### hexString - -A `string` representing the converted hexadecimal string. - -
- -## GetUnixTimeStampNow - -Gets the current Unix timestamp in seconds. +Generate Ethereum-style prefixed hashes or raw Keccak hashes. ```csharp -var timestampNow = Utils.GetUnixTimeStampNow(); +byte[] prefixed = messageBytes.HashPrefixedMessage(); +string keccak = message.HashMessage(); ``` -
- -### timestampNow - -A `long` representing the current Unix timestamp. - -
- -## GetUnixTimeStampIn10Years +### Utils.HexToBytes32 -Gets the Unix timestamp for 10 years from now. +Left-pad a hex string so it fits into exactly 32 bytes. ```csharp -var timestampIn10Years = Utils.GetUnixTimeStampIn10Years(); +byte[] padded = someHex.HexToBytes32(); ``` -
+### Utils.TrimZeroes -### timestampIn10Years - -A `long` representing the Unix timestamp 10 years from now. - -
- -## ReplaceIPFS - -Replaces an IPFS URI with a specified gateway. +Strip leading zero bytes—handy before ABI encoding. ```csharp -var gatewayUri = ipfsUri.ReplaceIPFS("https://ipfs.io/ipfs/"); +byte[] trimmed = signature.TrimZeroes(); ``` -
- -### uri - -A `string` representing the IPFS URI to replace. - -### gateway - -A `string` representing the gateway to use. Default is `Constants.FALLBACK_IPFS_GATEWAY`. - -
- -
+### Utils.PreprocessTypedDataJson -### gatewayUri - -A `string` representing the replaced URI with the specified gateway. - -
- -## ToWei - -Converts Ether values to Wei. +Stringify large integers inside a typed-data payload before sending it to browsers or wallets. ```csharp -var weiValue = ethValue.ToWei(); +string normalizedJson = Utils.PreprocessTypedDataJson(rawJson); ``` -
+### Utils.ToJsonExternalWalletFriendly -### eth - -A `string` representing the Ether value to convert. - -
- -
- -### weiValue - -A `string` representing the converted Wei value. - -
- -## ToEth - -Converts Wei values to Ether. +Convert strongly typed EIP-712 data structures into wallet-ready JSON. ```csharp -var ethValue = weiValue.ToEth(); +string json = Utils.ToJsonExternalWalletFriendly(typedData, message); ``` -
- -### wei - -A `string` representing the Wei value to convert. - -### decimalsToDisplay - -An `int` specifying the number of decimals to display. Default is `4`. - -### addCommas - -A `bool` indicating whether to add commas to the output. Default is `false`. - -
- -
- -### ethValue - -A `string` representing the converted Ether value. - -
- -## GenerateSIWE +### Utils.SerializeErc6492Signature -Generates a Sign-In With Ethereum (SIWE) message. +Wrap signatures for ERC-6492 (counterfactual ERC-4337 accounts) with the required magic value. ```csharp -var siweMessage = Utils.GenerateSIWE(loginPayloadData); +string wrapped = Utils.SerializeErc6492Signature(factoryAddress, deploymentCalldata, signatureBytes); ``` -
- -### loginPayloadData +## Value & time helpers -A `LoginPayloadData` object containing the payload data for generating the SIWE message. - -
+### Utils.ToWei / Utils.ToEth / Utils.FormatERC20 -
- -### siweMessage - -A `string` representing the generated SIWE message. - -
- -## IsZkSync - -Checks if a given chain ID corresponds to zkSync. +Convert between human-readable token amounts and Wei. ```csharp -var isZkSync = Utils.IsZkSync(chainId); +string wei = "0.05".ToWei(); +string eth = wei.ToEth(decimalsToDisplay: 6, addCommas: true); +string usdc = Utils.FormatERC20(usdcRawValue, decimalsToDisplay: 2, decimals: 6); ``` -
- -### chainId +### Utils.AdjustDecimals -A `BigInteger` representing the chain ID to check. - -
- -
- -### isZkSync - -A `bool` indicating whether the chain ID corresponds to zkSync. - -
- -## ToChecksumAddress - -Converts an Ethereum address to its checksum format. +Scale a `BigInteger` between two decimal systems—useful when toggling between token decimals. ```csharp -var checksummedAddress = address.ToChecksumAddress(); +BigInteger stableValue = amount.AdjustDecimals(18, 6); ``` -
- -### address - -A `string` representing the Ethereum address to convert. +### Utils.GenerateSIWE -
+Assemble a Sign-In With Ethereum message from a `LoginPayloadData` object. Throws if required fields are missing to help surface misconfigurations early. -
+```csharp +string messageToSign = Utils.GenerateSIWE(payload); +``` -### checksummedAddress +### Utils.GetUnixTimeStampNow / Utils.GetUnixTimeStampIn10Years -A `string` representing the checksummed Ethereum address. +Timestamp helpers for authentication windows or scheduled expirations. -
+```csharp +long now = Utils.GetUnixTimeStampNow(); +long farFuture = Utils.GetUnixTimeStampIn10Years(); +``` -## AdjustDecimals +### Utils.ReplaceIPFS -Adjusts the decimal places of a value. +Swap `ipfs://` URIs for a gateway of your choice (defaults to the Thirdweb fallback gateway). ```csharp -var adjustedValue = value.AdjustDecimals(18, 6); +string httpUrl = ipfsUri.ReplaceIPFS("https://w3s.link/ipfs/"); ``` -
+## Gas & network costs -### value +### Utils.FetchGasPrice / Utils.FetchGasFees -A `BigInteger` representing the value to adjust. +Retrieve legacy gas prices or EIP-1559 fee suggestions with optional safety bumps. Polygon, Celo, and Arbitrum chains get tailor-made logic. -### fromDecimals - -An `int` specifying the original number of decimals. +```csharp +BigInteger gasPrice = await Utils.FetchGasPrice(client, chainId); +(BigInteger maxFee, BigInteger maxPriority) = await Utils.FetchGasFees(client, chainId); +``` -### toDecimals +### Utils.IsEip155Enforced / Utils.IsEip1559Supported -An `int` specifying the target number of decimals. +Runtime checks that tell you whether a chain requires EIP-155 signatures and whether it supports EIP-1559 style fees. -
+```csharp +bool needsProtectedTx = await Utils.IsEip155Enforced(client, chainId); +bool supports1559 = Utils.IsEip1559Supported(chainId.ToString()); +``` -
+## Transactions & monitoring -### adjustedValue +### Utils.WaitForTransactionReceipt -A `BigInteger` representing the value adjusted to the new decimals. +Poll for a transaction receipt with built-in timeouts from the client's fetch options. -
+```csharp +var receipt = await Utils.WaitForTransactionReceipt(client, chainId, txHash); +``` -## ToJsonExternalWalletFriendly +### Utils.IsDeployed -Converts typed data into a JSON string suitable for external wallet interaction. +Check whether bytecode exists at an address. ```csharp -var jsonString = Utils.ToJsonExternalWalletFriendly(typedData, message); +bool deployed = await Utils.IsDeployed(client, chainId, contractAddress); ``` -
- -### typedData - -A `TypedData` object representing the typed data to convert. +### Utils.IsDelegatedAccount -### message +Detect delegated accounts (EIP-7702 style) by comparing on-chain code against the delegation contract. -A `TMessage` object representing the message to include in the JSON. +```csharp +bool delegated = await Utils.IsDelegatedAccount(client, chainId, address, delegationContract); +``` -
+## IP & social enrichment -
+### Utils.PacketToBytes -### jsonString +Encode ENS-style DNS packets. Exposed for advanced ENS tooling; most integrations can rely on the higher-level ENS helpers above. -A `string` representing the JSON formatted data. +```csharp +byte[] packet = Utils.PacketToBytes("subdomain.example.eth"); +``` -
diff --git a/apps/portal/src/app/dotnet/wallets/providers/account-abstraction/page.mdx b/apps/portal/src/app/dotnet/wallets/account-abstraction/page.mdx similarity index 100% rename from apps/portal/src/app/dotnet/wallets/providers/account-abstraction/page.mdx rename to apps/portal/src/app/dotnet/wallets/account-abstraction/page.mdx diff --git a/apps/portal/src/app/dotnet/wallets/providers/ecosystem-wallet/page.mdx b/apps/portal/src/app/dotnet/wallets/providers/ecosystem-wallet/page.mdx deleted file mode 100644 index aeebe7b9c0b..00000000000 --- a/apps/portal/src/app/dotnet/wallets/providers/ecosystem-wallet/page.mdx +++ /dev/null @@ -1,351 +0,0 @@ -import { Details, Callout, createMetadata } from "@doc"; - -export const metadata = createMetadata({ - title: "EcosystemWallet.Create | Thirdweb .NET SDK", - description: - "Instantiate an EcosystemWallet for secure user authentication and interaction.", -}); - -# EcosystemWallet.Create - -Create an instance of `EcosystemWallet` using a user's email, phone number or OAuth. This wallet type facilitates secure user authentication through OTP verification, making it suitable for client-facing applications where handling private keys directly is not ideal. - -## Login Methods - -Ecosystem Wallets support a variety of login methods: -- Email (OTP Login) -- Phone (OTP Login) -- Socials (Google, Apple, Facebook, Telegram, Farcaster, Line, Github, Twitch, Steam, TikTok etc.) -- Custom Auth (OIDC Compatible) -- Custom Auth (Generic Auth Endpoint) -- Guest (Onboard easily, link other accounts later) -- Backend (Server Wallets) -- Siwe (Login with a separate wallet supported by the SDK) -- SiweExternal (Login with an external wallet that only supports web using a browser loading a static thirdweb React page temporarily) - -## Usage - -```csharp -// NOTE: All creation examples below may take in an `ecosystemPartnerId` if you are the ecosystem partner integrating with a third-party ecosystem - -// Email -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", email: "userEmail"); -// Phone -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", phoneNumber: "+1234567890"); -// Google, Apple, Facebook, etc. -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Google); -// Custom Auth - JWT -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.JWT); -// Custom Auth - AuthEndpoint -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.AuthEndpoint); -// Guest Login -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Guest); -// Server Login -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Backend, walletSecret: "very-secret"); -// Siwe -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Siwe, siweSigner: anyExternalWallet); -// SiweExternal -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.SiweExternal); - -// Session resuming supported for all methods -var isConnected = await wallet.IsConnected(); - -// If not connected, initiate login flow based on the auth provider you are using - -// Email & Phone (OTP) -await wallet.SendOTP(); // and fetch the otp -var address = await wallet.LoginWithOtp("userEnteredOTP"); // try catch and retry if needed - -// Socials (OAuth) -var address = await wallet.LoginWithOauth( - // Windows console app example, adaptable to any runtime - isMobile: false, - browserOpenAction: (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - }, - mobileRedirectScheme: "myBundleId://" -); - -// Custom Auth (JWT) -var address = await wallet.LoginWithCustomAuth(jwt: "myjwt"); - -// Custom Auth (AuthEndpoint) -var address = await wallet.LoginWithAuthEndpoint(payload: "mypayload"); - -// Guest Login (Easy onboarding) -var address = await wallet.LoginWithGuest(); - -// Backend (Server Wallets) -var address = await wallet.LoginWithBackend(); - -// SIWE (Wallet) -var address = await siweWallet.LoginWithSiwe(chainId: 1); - -// SiweExternal (React-only wallet) -var address = await wallet.LoginWithSiweExternal( - // Windows console app example, adaptable to any runtime - isMobile: false, - browserOpenAction: (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - }, - forceWalletIds: new List { "io.metamask", "com.coinbase.wallet", "xyz.abs" } -); -``` - -
- -### client (required) - -An instance of `ThirdwebClient`. - -### ecosystemId (required) - -The ID of the ecosystem wallet you created on the dashboard. - -### ecosystemPartnerId (optional) - -The partner ID you were provided by the Ecosystem owner. - -### email (optional) - -The user's email address. Required if `phoneNumber` is not provided. - -### phoneNumber (optional) - -The user's phone number. Required if `email` is not provided. - -### authProvider (optional) - -The OAuth provider to use for authentication. Supported values are `AuthProvider.Google`, `AuthProvider.Apple`, `AuthProvider.Facebook`. - -### storageDirectoryPath (optional) - -The path to the directory where the wallet data is stored. Defaults to the application's data directory. - -### siweSigner (optional) - -An external wallet instance to use for SIWE authentication. - -### legacyEncryptionKey (optional) - -The encryption key that is no longer required but was used in the past. Only pass this if you had used custom auth before this was deprecated. - -### walletSecret (optional) - -The secret identifier to use when creating server-side wallets with backend authentication. - -
- -
- -### EcosystemWallet - -Returns an instance of EcosystemWallet, initialized for the user based on the provided email or phone number. This wallet is ready for OTP authentication and further blockchain interactions. - -
- -## OTP Authentication Flow - -The OTP authentication flow involves sending an OTP to the user's email or phone and then verifying the OTP to complete authentication: - -**Send OTP:** Initiate the login process by calling SendOTP on the EcosystemWallet instance. This sends an OTP to the user's email or phone number. - -```csharp -await wallet.SendOTP(); -``` - -**Submit OTP:** Once the user receives the OTP, they submit it back to the application, which then calls LoginWithOtp on the EcosystemWallet instance to verify the OTP and complete the login process. - -```csharp -var address = await wallet.LoginWithOtp("userEnteredOTP"); -// If this fails, feel free to catch and take in another OTP and retry the login process -``` - -## Example - -Here's an example of creating an `EcosystemWallet` with a user's email and completing the OTP authentication flow: - -```csharp -// Create EcosystemWallet wallet as signer to unlock web2 auth -var ecosystemWallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", email: "email@email.com"); // or email: null, phoneNumber: "+1234567890" - -// Resume session (if `EcosystemWallet` wallet was not logged in) -if (!await ecosystemWallet.IsConnected()) -{ - await ecosystemWallet.SendOTP(); - Console.WriteLine("Please submit the OTP."); - var otp = Console.ReadLine(); - var ecosystemWalletAddress = await ecosystemWallet.LoginWithOtp(otp); // try catch and retry if needed -} - -Console.WriteLine($"EcosystemWallet address: {await ecosystemWallet.GetAddress()}"); - -// Sign a message -var message = "Hello, Thirdweb!"; -var signature = await wallet.PersonalSign(message); -Console.WriteLine($"Signature: {signature}"); -``` - -**Note:** EcosystemWallet leverages the security of OTP-based authentication to ensure a secure and user-friendly experience in blockchain applications. - -## OAuth Authentication Flow - -**LoginWithOauth:** Initiate the login process by calling LoginWithOauth on the EcosystemWallet instance. This redirects the user to the OAuth provider's login page. - -```csharp -// Windows console app example -var address = await ecosystemWallet.LoginWithOauth( - isMobile: false, - browserOpenAction: (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - }, -); -// Godot standalone example -var address = await ThirdwebManager.Instance.EcosystemWallet.LoginWithOauth( - isMobile: OS.GetName() == "Android" || OS.GetName() == "iOS", - browserOpenAction: (url) => OS.ShellOpen(url), - mobileRedirectScheme: "thirdweb://" -); -``` - -
- -### isMobile - -A `bool` indicating whether the application is running on a mobile platform. - -### browserOpenAction - -An `Action` that opens the OAuth provider's login page in a browser. - -### mobileRedirectScheme - -The redirect scheme to use for mobile platforms. Defaults to `"thirdweb://"`. - -### browser - -An instance of `IThirdwebBrowser` to use for the OAuth login process. Defaults to `null`. - -### cancellationToken - -A `CancellationToken` to cancel the operation. Defaults to `default`. - -
- -
- -### string - -The EcosystemWallet address as a hexadecimal `string`. - -
- -## Example - -Here's an example of creating an `EcosystemWallet` using OAuth. - -```csharp -// Create EcosystemWallet wallet as signer to unlock web2 auth -var ecosystemWallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Google); - -// Resume session (if `EcosystemWallet` wallet was not logged in) -if (!await ecosystemWallet.IsConnected()) -{ - try { - var address = await ecosystemWallet.LoginWithOauth( - isMobile: false, - browserOpenAction: (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - }, - ); - Console.WriteLine($"OAuth login successful. EcosystemWallet address: {address}"); - } catch (Exception ex) { - Console.WriteLine($"OAuth login failed: {ex.Message}"); - return; - } -} -``` - -**Note:** The `LoginWithOauth` API allows for custom browser handling, making it suitable for various application types and platforms. - -## External Wallet Auth (Siwe & SiweExternal) - -**LoginWithSiwe:** Initiate the login process by calling LoginWithSiwe on the EcosystemWallet instance. This will prompt the external wallet to sign a message instantly. - -```csharp -var address = await siweWallet.LoginWithSiwe(chainId: 1); -``` - -**LoginWithSiweExternal:** Initiate the login process by calling LoginWithSiweExternal on the EcosystemWallet instance. This will initiate a browser-based login flow for external wallets that only support web platforms. - -```csharp -var address = await wallet.LoginWithSiweExternal( - // Windows console app example, adaptable to any runtime - isMobile: false, - browserOpenAction: (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - }, - forceWalletIds: new List { "io.metamask", "com.coinbase.wallet", "xyz.abs" } -); -``` - -Note: The parameters are similar to the OAuth flow, with the addition of `forceWalletIds` to specify the wallet IDs to force the user to use. Using a single wallet id will skip the wallet selection screen and directly open the wallet. - -## Unified Identity - Account Linking - -EcosystemWallet supports linking multiple authentication methods to a single user account. This feature enables users to access their account using different authentication methods, such as email, phone, or OAuth, without creating separate accounts for each method. - -### Linking Accounts - -```csharp -var ecosystemWalletMain = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Google); -if (!await ecosystemWalletMain.IsConnected()) -{ - _ = await ecosystemWalletMain.LoginWithOauth( - isMobile: false, - (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - }, - "thirdweb://", - new EcosystemWalletBrowser() - ); -} -Console.WriteLine($"Main EcosystemWallet address: {await ecosystemWalletMain.GetAddress()}"); - -// Prepare Telegram -var socialWallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Telegram); -// Link Telegram -_ = await ecosystemWalletMain.LinkAccount(walletToLink: socialWallet,); - -// Prepare Phone -var phoneWallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", phoneNumber: "+1234567890"); -_ = await phoneWallet.SendOTP(); -var otp = Console.ReadLine(); -// Link Phone -_ = await ecosystemWalletMain.LinkAccount(walletToLink: phoneWallet, otp: otp); -``` - -### Getting Linked Accounts - -```csharp -List linkedAccounts = await ecosystemWalletMain.GetLinkedAccounts(); -``` - -### Unlinking Accounts - -```csharp -List linkedAccounts = await ecosystemWallet.GetLinkedAccounts(); -List linkedAccountsAfterUnlinking = await ecosystemWallet.UnlinkAccount(linkedAccounts[0]); -``` diff --git a/apps/portal/src/app/dotnet/wallets/providers/engine-wallet/page.mdx b/apps/portal/src/app/dotnet/wallets/providers/engine-wallet/page.mdx deleted file mode 100644 index 770c398db72..00000000000 --- a/apps/portal/src/app/dotnet/wallets/providers/engine-wallet/page.mdx +++ /dev/null @@ -1,71 +0,0 @@ -import { Details, Callout, createMetadata } from "@doc"; - -export const metadata = createMetadata({ - title: "EngineWallet.Create | Thirdweb .NET SDK", - description: - "Instantiate a EngineWallet to sign transactions and messages.", -}); - -# EngineWallet.Create - -A .NET integration of thirdweb Transactions. - -Instantiate a `Engine` with a given private key. This wallet is capable of signing transactions and messages, interacting with smart contracts, and performing other blockchain operations. **This wallet type is intended for backend applications only,** due to the sensitive nature of private keys. - -## Usage - -```csharp -// EngineWallet is compatible with IThirdwebWallet and can be used with any SDK method/extension -var engineWallet = EngineWallet.Create( - client: client, - engineUrl: Environment.GetEnvironmentVariable("ENGINE_URL"), - authToken: Environment.GetEnvironmentVariable("ENGINE_ACCESS_TOKEN"), - walletAddress: Environment.GetEnvironmentVariable("ENGINE_BACKEND_WALLET_ADDRESS"), - timeoutSeconds: null, // no timeout - additionalHeaders: null // can set things like x-account-address if using basic session keys -); - -// Simple self transfer -var receipt = await engineWallet.Transfer(chainId: 11155111, toAddress: await engineWallet.GetAddress(), weiAmount: 0); -Console.WriteLine($"Receipt: {receipt}"); -``` - - - This method involves using an access token, it is meant to be used in a secure backend environment. Never expose access tokens in client-side code or store them in an insecure manner. Ideally, use environment variables or secure vaults to manage access tokens in backend services. - - -
- -### client (required) - -An instance of `ThirdwebClient`. - -### engineUrl (required) - -The URL of the Thirdweb Engine instance, get it from your Engine dashboard. - -### authToken (required) - -The access token for the Engine instance, get it from your Engine dashboard. - -### walletAddress (required) - -The address of the backend wallet that will be used to sign transactions. You can create a backend wallet using the Engine dashboard. - -### timeoutSeconds (optional) - -Enforce a timeout for requests to the Engine instance, in seconds. - -### additionalHeaders (optional) - -Additional headers to include in requests to the Engine instance, such as `x-account-address` for Account Abstraction/Session Key flows. - -
- -
- -### EngineWallet - -Returns an instance of `EngineWallet`, ready to sign transactions and messages with the provided access token. - -
\ No newline at end of file diff --git a/apps/portal/src/app/dotnet/wallets/providers/in-app-wallet/page.mdx b/apps/portal/src/app/dotnet/wallets/providers/in-app-wallet/page.mdx deleted file mode 100644 index 26455d9bd69..00000000000 --- a/apps/portal/src/app/dotnet/wallets/providers/in-app-wallet/page.mdx +++ /dev/null @@ -1,144 +0,0 @@ -import { Details, Callout, createMetadata } from "@doc"; - -export const metadata = createMetadata({ - title: "InAppWallet.Create | Thirdweb .NET SDK", - description: - "Instantiate an InAppWallet for secure user authentication and interaction.", -}); - -# InAppWallet.Create - -Create an instance of `InAppWallet` using a user's email, phone number or OAuth. This wallet type facilitates secure user authentication through OTP verification, making it suitable for client-facing applications where handling private keys directly is not ideal. - -## Login Methods - -In-App Wallets support a variety of login methods: -- Email (OTP Login) -- Phone (OTP Login) -- Socials (Google, Apple, Facebook, Telegram, Farcaster, Line, Github, Twitch, Steam, TikTok etc.) -- Custom Auth (OIDC Compatible) -- Custom Auth (Generic Auth Endpoint) -- Guest (Onboard easily, link other accounts later) -- Backend (Server Wallets) -- Siwe (Login with a separate wallet supported by the SDK) -- SiweExternal (Login with an external wallet that only supports web using a browser loading a static thirdweb React page temporarily) - -## Usage - -```csharp -// Email -var wallet = await InAppWallet.Create(client: client, email: "userEmail"); -// Phone -var wallet = await InAppWallet.Create(client: client, phoneNumber: "+1234567890"); -// Google, Apple, Facebook, etc. -var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Google); -// Custom Auth - JWT -var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.JWT); -// Custom Auth - AuthEndpoint -var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.AuthEndpoint); -// Guest Login -var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Guest); -// Server Login -var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Backend, walletSecret: "very-secret"); -// Siwe -var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Siwe, siweSigner: anyExternalWallet); -// SiweExternal -var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.SiweExternal); - -// Session resuming supported for all methods -var isConnected = await wallet.IsConnected(); - -// If not connected, initiate login flow based on the auth provider you are using - -// Email & Phone (OTP) -await wallet.SendOTP(); // and fetch the otp -var address = await wallet.LoginWithOtp("userEnteredOTP"); // try catch and retry if needed - -// Socials (OAuth) -var address = await wallet.LoginWithOauth( - // Windows console app example, adaptable to any runtime - isMobile: false, - browserOpenAction: (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - }, - mobileRedirectScheme: "myBundleId://" -); - -// Custom Auth (JWT) -var address = await wallet.LoginWithCustomAuth(jwt: "myjwt"); - -// Custom Auth (AuthEndpoint) -var address = await wallet.LoginWithAuthEndpoint(payload: "mypayload"); - -// Guest Login (Easy onboarding) -var address = await wallet.LoginWithGuest(); - -// Backend (Server Wallets) -var address = await wallet.LoginWithBackend(); - -// SIWE (Wallet) -var address = await siweWallet.LoginWithSiwe(chainId: 1); - -// SiweExternal (React-only wallet) -var address = await wallet.LoginWithSiweExternal( - // Windows console app example, adaptable to any runtime - isMobile: false, - browserOpenAction: (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - }, - forceWalletIds: new List { "io.metamask", "com.coinbase.wallet", "xyz.abs" } -); -``` - -
- -### client (required) - -An instance of `ThirdwebClient`. - -### email (optional) - -The user's email address. Required if `phoneNumber` is not provided. - -### phoneNumber (optional) - -The user's phone number. Required if `email` is not provided. - -### authProvider (optional) - -The OAuth provider to use for authentication. Supported values are `AuthProvider.Google`, `AuthProvider.Apple`, `AuthProvider.Facebook`. - -### storageDirectoryPath (optional) - -The path to the directory where the wallet data is stored. Defaults to the application's data directory. - -### siweSigner (optional) - -An external wallet instance to use for SIWE authentication. - -### legacyEncryptionKey (optional) - -The encryption key that is no longer required but was used in the past. Only pass this if you had used custom auth before this was deprecated. - -### walletSecret (optional) - -The secret identifier to use when creating server-side wallets with backend authentication. - -
- -
- -### InAppWallet - -Returns an instance of InAppWallet, initialized for the user based on the provided email or phone number. This wallet is ready for OTP authentication and further blockchain interactions. - -
- -## Authentication, Linking and more. - -`InAppWallet` extends `EcosystemWallet`, refer to the [EcosystemWallet](/dotnet/wallets/providers/ecosystem-wallet) documentation for information about authentication, linking multiple auth providers to the same wallet, and more. All functionality outside of creation and third-party integrations is the same as `EcosystemWallet`. - diff --git a/apps/portal/src/app/dotnet/wallets/providers/private-key/page.mdx b/apps/portal/src/app/dotnet/wallets/providers/private-key/page.mdx deleted file mode 100644 index 5c5dc09096c..00000000000 --- a/apps/portal/src/app/dotnet/wallets/providers/private-key/page.mdx +++ /dev/null @@ -1,62 +0,0 @@ -import { Details, Callout, createMetadata } from "@doc"; - -export const metadata = createMetadata({ - title: "PrivateKeyWallet.Create | Thirdweb .NET SDK", - description: - "Instantiate a PrivateKeyWallet to sign transactions and messages.", -}); - -# PrivateKeyWallet.Create - -Instantiate a `PrivateKeyWallet` with a given private key. This wallet is capable of signing transactions and messages, interacting with smart contracts, and performing other blockchain operations. **This wallet type is intended for backend applications only,** due to the sensitive nature of private keys. - -## Usage - -```csharp -var wallet = await PrivateKeyWallet.Create(client, "yourPrivateKeyHex"); -``` - - - This method involves using a raw private key, which should be handled with the - utmost care. Never expose private keys in client-side code or store them in an - insecure manner. Ideally, use environment variables or secure vaults to manage - private keys in backend services. - - -
- -### client (required) - -An instance of `ThirdwebClient`. - -### privateKeyHex (required) - -The private key of the wallet, provided as a hexadecimal `string`. This key grants full control over the wallet's funds and assets on the blockchain. - -
- -
-### PrivateKeyWallet - -Returns an instance of `PrivateKeyWallet`, ready to sign transactions and messages with the provided private key. - -
- -## Example - -Here is an example of creating a PrivateKeyWallet to sign a simple message. This example assumes you have a ThirdwebClient instance already created: - -```csharp -var client = ThirdwebClient.Create("yourClientId"); -var privateKeyHex = "yourPrivateKeyHex"; // Should be securely stored and accessed -var wallet = await PrivateKeyWallet.Create(client, privateKeyHex); - -Console.WriteLine($"PrivateKeyWallet address: {await wallet.GetAddress()}"); - -// Sign a message -var message = "Hello, Thirdweb!"; -var signature = await wallet.PersonalSign(message); -Console.WriteLine($"Signature: {signature}"); -``` - -**Note**: The `PrivateKeyWallet` is intended for use in secure, backend environments. Ensure private keys are never exposed or hard-coded in client-side applications. Use `InAppWallet` and `SmartWallet` for client-side applications. diff --git a/apps/portal/src/app/dotnet/wallets/server-wallet/page.mdx b/apps/portal/src/app/dotnet/wallets/server-wallet/page.mdx new file mode 100644 index 00000000000..c326d5d6fcc --- /dev/null +++ b/apps/portal/src/app/dotnet/wallets/server-wallet/page.mdx @@ -0,0 +1,123 @@ +import { Callout, Details, createMetadata } from "@doc"; + +export const metadata = createMetadata({ + title: "ServerWallet.Create | Thirdweb .NET SDK", + description: "Provision a vault-backed server wallet from your Thirdweb dashboard label.", +}); + +# ServerWallet.Create + +`ServerWallet` lets you operate a vault-secured signer that lives inside your Thirdweb project. Use it whenever you need backend signing with the same controls and monitoring offered in the dashboard. + + +Before you instantiate a server wallet from code, create it inside the dashboard (Transactions → Server wallets) and note the label you assign. The SDK searches for that label when you call `Create`. + + +## Quick start + +```csharp +var client = ThirdwebClient.Create(secretKey: Environment.GetEnvironmentVariable("THIRDWEB_SECRET_KEY")); + +var wallet = await ServerWallet.Create( + client: client, + label: "production-deployer" +); + +Console.WriteLine($"Server wallet ready at {await wallet.GetAddress()}."); +``` + +This call: + +1. Reuses your client credentials to talk to the Thirdweb Engine API. +2. Finds the server wallet whose label matches `production-deployer`. +3. Auto-configures execution options that track the signer address and idempotency key. + +## Execution options + +The optional `executionOptions` argument lets you control how the wallet submits transactions. Pass one of the SDK-provided option types to enable advanced flows: + +```csharp +// Auto (default) – SDK picks the right strategy per chain +var autoOptions = new AutoExecutionOptions +{ + IdempotencyKey = Guid.NewGuid().ToString(), +}; + +// EOA – force a direct signer submission +var eoaOptions = new EOAExecutionOptions(); + +// ERC-4337 – sign and send as a smart account +var erc4337Options = new ERC4337ExecutionOptions( + chainId: new BigInteger(8453), + signerAddress: "0xYourSigner" +) +{ + EntrypointAddress = "0xEntryPoint", +}; + +// EIP-7702 – delegate to an authorized contract +var eip7702Options = new EIP7702ExecutionOptions(); + +var advancedWallet = await ServerWallet.Create( + client: client, + label: "production-deployer", + executionOptions: erc4337Options +); +``` + +`ServerWallet.Create` will populate any missing `From`, `SignerAddress`, or smart-account data at runtime. If you pass an unsupported execution option, the call throws an `InvalidOperationException` with guidance on the allowed types. + +## Self-managed vault access + +If you configured the wallet as self-managed, include the Vault Access Token issued by Thirdweb so your backend can sign requests on behalf of the vault. + +```csharp +var wallet = await ServerWallet.Create( + client: client, + label: "self-managed", + vaultAccessToken: Environment.GetEnvironmentVariable("THIRDWEB_VAULT_ACCESS_TOKEN") +); +``` + +The method injects the token as an `X-Vault-Access-Token` header for all Engine API calls. + +## Handling lookup failures + +Creation will throw descriptive exceptions when it cannot find the target wallet: + +- `ArgumentNullException` if you omit the `client` or `label`. +- `InvalidOperationException` when no server wallets exist or the label does not match (the error lists available labels). + +Wrap your call in a retry or surface the labels to operators to resolve quickly. + +## Next steps + +A server wallet implements `IThirdwebWallet`, so you can reuse it anywhere you expect a wallet, including: + +- Contract writes via `await contract.Write(wallet, ...)` +- Prepared transactions with `await contract.Prepare(wallet, ...)` +- Direct Engine interactions through `wallet.SendTransaction` + +For end-user wallets, see the [User Wallets](/dotnet/user-wallets) guide. + +## API reference + +
+ +### Required + +- `client`: `ThirdwebClient` — Initialized with your project secret key. +- `label`: `string` — Matches the label from the dashboard. + +### Optional + +- `executionOptions`: `ExecutionOptions` — One of `AutoExecutionOptions`, `EOAExecutionOptions`, `EIP7702ExecutionOptions`, or `ERC4337ExecutionOptions`. +- `vaultAccessToken`: `string` — Required when the vault is self-managed. + +
+ +
+ +Returns an initialized `ServerWallet` instance tracked by the SDK connection manager. Use its methods to read the address, sign messages, or send transactions from your backend. + +
diff --git a/apps/portal/src/app/dotnet/wallets/user-wallet/page.mdx b/apps/portal/src/app/dotnet/wallets/user-wallet/page.mdx new file mode 100644 index 00000000000..51da388b5bf --- /dev/null +++ b/apps/portal/src/app/dotnet/wallets/user-wallet/page.mdx @@ -0,0 +1,226 @@ +import { Details, Callout, createMetadata } from "@doc"; + +export const metadata = createMetadata({ + title: "User Wallets | Thirdweb .NET SDK", + description: + "Learn how to work with InAppWallet and EcosystemWallet using the unified user wallet APIs.", +}); + +# User Wallets + +`InAppWallet` and `EcosystemWallet` provide the same runtime capabilities for user authentication and signing in the Thirdweb .NET SDK. The only material differences are in how you create each wallet. Use this guide anytime you need an OTP-backed wallet experience for end users, regardless of whether you are building on the default thirdweb ecosystem or operating your own partner ecosystem. + +## Choosing a wallet type + +- **InAppWallet** — best for applications that rely on the default thirdweb infrastructure. Creation does not require an `ecosystemId`. +- **EcosystemWallet** — required when you run your own ecosystem. Creation requires the `ecosystemId` that was issued in the dashboard (and optionally an `ecosystemPartnerId`). + +Once created, both wallets expose identical methods for login, session management, and blockchain interactions. + +## Creating wallets + +### InAppWallet + +```csharp +// Email +var wallet = await InAppWallet.Create(client: client, email: "userEmail"); +// Phone +var wallet = await InAppWallet.Create(client: client, phoneNumber: "+1234567890"); +// Social login +var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Google); +// Custom auth (JWT) +var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.JWT); +// Guest login +var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Guest); +// Backend (server wallet) +var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Backend, walletSecret: "very-secret"); +// SIWE +var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Siwe, siweSigner: anyExternalWallet); +// SIWE External +var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.SiweExternal); +``` + +### EcosystemWallet + +```csharp +// NOTE: Add ecosystemPartnerId if you are an approved partner within the ecosystem + +// Email +var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", email: "userEmail"); +// Phone +var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", phoneNumber: "+1234567890"); +// Social login +var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Google); +// Custom auth (JWT) +var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.JWT); +// Custom auth (auth endpoint) +var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.AuthEndpoint); +// Guest login +var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Guest); +// Backend (server wallet) +var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Backend, walletSecret: "very-secret"); +// SIWE +var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Siwe, siweSigner: anyExternalWallet); +// SIWE External +var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.SiweExternal); +``` + +Once a wallet is created, you can always resume the session: + +```csharp +var isConnected = await wallet.IsConnected(); +``` + +If the wallet is not connected, initiate the login flow that corresponds to the auth provider you selected. + +## Supported login methods + +Both wallet types support the same login surfaces: + +- Email or phone (OTP-based) +- Social providers (Google, Apple, Facebook, Telegram, Farcaster, Line, GitHub, Twitch, Steam, TikTok, and more) +- Custom auth (JWT or generic auth endpoint) +- Guest onboarding +- Backend/server wallets +- Sign-In With Ethereum (SIWE) +- SIWE External (web-only wallets via a browser prompt) + +## OTP authentication flow + +```csharp +await wallet.SendOTP(); +var address = await wallet.LoginWithOtp("userEnteredOTP"); // retry on failure if needed +``` + +Catch exceptions to allow retries when the user enters an incorrect code. + +## OAuth flow + +```csharp +var address = await wallet.LoginWithOauth( + isMobile: false, + browserOpenAction: (url) => + { + var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; + _ = Process.Start(psi); + }, + mobileRedirectScheme: "myBundleId://" +); +``` + +Adjust `isMobile`, `browserOpenAction`, and `mobileRedirectScheme` based on your target runtime. For Godot or other engines, reuse the existing platform-specific handlers you already have in place. + +## Custom auth examples + +```csharp +// Custom Auth (JWT) +var address = await wallet.LoginWithCustomAuth(jwt: "myjwt"); + +// Custom Auth (AuthEndpoint) +var address = await wallet.LoginWithAuthEndpoint(payload: "mypayload"); +``` + +## Guest and backend flows + +```csharp +// Guest +var guestAddress = await wallet.LoginWithGuest(); + +// Backend (server wallet) +var backendAddress = await wallet.LoginWithBackend(); +``` + +## External wallet authentication + +```csharp +// SIWE +var address = await wallet.LoginWithSiwe(chainId: 1); + +// SIWE External +var address = await wallet.LoginWithSiweExternal( + isMobile: false, + browserOpenAction: (url) => + { + var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; + _ = Process.Start(psi); + }, + forceWalletIds: new List { "io.metamask", "com.coinbase.wallet", "xyz.abs" } +); +``` + +Limiting `forceWalletIds` to a single value skips the wallet picker and deep-links into that specific wallet provider. + +## Unified identity and account linking + +Link multiple login methods to the same user identity regardless of the wallet type that initiated the session. + +```csharp +var primaryWallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Google); +if (!await primaryWallet.IsConnected()) +{ + _ = await primaryWallet.LoginWithOauth( + isMobile: false, + (url) => + { + var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; + _ = Process.Start(psi); + }, + "thirdweb://" + ); +} + +var telegramWallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Telegram); +_ = await primaryWallet.LinkAccount(walletToLink: telegramWallet); + +var phoneWallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", phoneNumber: "+1234567890"); +_ = await phoneWallet.SendOTP(); +var otp = Console.ReadLine(); +_ = await primaryWallet.LinkAccount(walletToLink: phoneWallet, otp: otp); + +List linkedAccounts = await primaryWallet.GetLinkedAccounts(); +List linkedAccountsAfterUnlinking = await primaryWallet.UnlinkAccount(linkedAccounts[0]); +``` + +## Parameter reference + +
+ +### Required + +- `client`: `ThirdwebClient` + +### Optional + +- `email`: string — required if `phoneNumber` is not provided. +- `phoneNumber`: string — required if `email` is not provided. +- `authProvider`: `AuthProvider` +- `storageDirectoryPath`: string +- `siweSigner`: external wallet instance for SIWE. +- `walletSecret`: string — required for backend/server wallet creation. + +
+ +
+ +### Required + +- `client`: `ThirdwebClient` +- `ecosystemId`: string — your ecosystem identifier from the dashboard. + +### Optional + +- `ecosystemPartnerId`: string — partner identifier if supplied by the ecosystem owner. +- `email`: string — required if `phoneNumber` is not provided. +- `phoneNumber`: string — required if `email` is not provided. +- `authProvider`: `AuthProvider` +- `storageDirectoryPath`: string +- `siweSigner`: external wallet instance for SIWE. +- `walletSecret`: string — required for backend/server wallet creation. + +
+ +
+ +Both `Create` methods return an initialized instance of either `InAppWallet` or `EcosystemWallet`. These instances are ready for OTP verification, OAuth, or any other supported login flow and expose the same wallet interaction APIs thereafter. + +
diff --git a/apps/portal/src/app/unity/v6/build-instructions/page.mdx b/apps/portal/src/app/unity/v6/build-instructions/page.mdx new file mode 100644 index 00000000000..31f09eb9aa6 --- /dev/null +++ b/apps/portal/src/app/unity/v6/build-instructions/page.mdx @@ -0,0 +1,65 @@ +import { Callout, createMetadata } from "@doc"; + +export const metadata = createMetadata({ + title: "Unity SDK v6 Build Instructions | thirdweb Unity SDK", + description: + "Learn how to build your Unity project with the thirdweb Unity SDK.", +}); + +# Build Instructions + +## All Target Platforms + +- **Player Settings:** Use IL2CPP over Mono when available. +- **Build Settings:** Use `Smaller (faster) Builds` / `Shorter Build Time` for IL2CPP Code Generation. +- **Stripping Level:** Set `Managed Stripping Level` to `Minimal` (`Player Settings` > `Other Settings` > `Optimization`). (Generally not a hard requirement unless using external wallets.) +- **Strip Engine Code:** We recommend turning this off. + +## WebGL + +- **WebGL Template:** None enforced, feel free to customize! +- **Compression Format:** Set to `Disabled` (`Player Settings` > `Publishing Settings`) for final builds. +- **Testing WebGL Social Login Locally:** Host the build or run it locally with `Cross-Origin-Opener-Policy` set to `same-origin-allow-popups`. + +Example setup for testing In-App or Ecosystem Wallet Social Login locally: + +```javascript +// YourWebGLOutputFolder/server.js +const express = require("express"); +const app = express(); +const port = 8000; + +app.use((req, res, next) => { + res.header("Cross-Origin-Opener-Policy", "same-origin-allow-popups"); + next(); +}); + +app.use(express.static(".")); +app.listen(port, () => + console.log(`Server running on http://localhost:${port}`), +); + +// run it with `node server.js` +``` + +No action needed for hosted builds. + +## Mobile + +- **EDM4U:** Comes with the package, resolves dependencies at runtime. Use `Force Resolve` from `Assets` > `External Dependency Manager` > `Android Resolver`. +- **Redirect Schemes:** Set custom schemes matching your bundle ID in `Plugins/Android/AndroidManifest.xml` or equivalent to ensure OAuth redirects if using social login. +```xml + + + +``` + +## Troubleshooting OAuth Redirects + +If you notice issues with redirects on mobile, for instance hanging after selecting an account when logging in with Google, you did not setup OAuth properly. +Best practice is to use a lowercase bundle id that is the same as your Unity project's application identifier, it must be present in the following places: +- Your `ThirdwebManager` prefab `Bundle ID` field. +- Your `Plugins/AndroidManifest.xml` scheme field as mentioned above (it doesn't have to be our default `AndroidManifest.xml`, it can be in your custom one, just add the scheme). +- Your thirdweb dashboard project settings as one of: + - `Bundle ID Allowlist` field (those automatically added to allowed redirect URIs). + - In-App Wallet Configuration `Allowed redirect URIs` field. \ No newline at end of file diff --git a/apps/portal/src/app/unity/v6/contracts/page.mdx b/apps/portal/src/app/unity/v6/contracts/page.mdx new file mode 100644 index 00000000000..587165ea5c7 --- /dev/null +++ b/apps/portal/src/app/unity/v6/contracts/page.mdx @@ -0,0 +1,34 @@ +import { Details, Callout, createMetadata, ArticleIconCard } from "@doc"; +import { GraduationCap } from "lucide-react"; + +export const metadata = createMetadata({ + title: "ThirdwebContract | Thirdweb Unity SDK", + description: + "Instantiate a PrivateKeyWallet to sign transactions and messages.", +}); + +# ThirdwebContract + +`ThirdwebContract` is a wrapper around any EVM-compatible smart contract. It allows you to interact with the contract's functions easily, as well as prepare transactions. + +### Instantiating a ThirdwebContract + +```csharp +var contract = await ThirdwebManager.Instance.GetContract( + address: "contract-address", + chainId: 1, + abi: "optional-abi" +); +``` + +That's it! You can now interact with the contract using the `ThirdwebContract`. + +It contains `Read`, `Write` and `Prepare` methods to respectively read contract state, write to the contract and prepare transactions. + +Furthermore, it contains shorthands such as `ThirdwebContract.ERC20_BalanceOf` to easily interact with common contract interfaces. + + \ No newline at end of file diff --git a/apps/portal/src/app/unity/v6/getting-started/page.mdx b/apps/portal/src/app/unity/v6/getting-started/page.mdx new file mode 100644 index 00000000000..e3f1daa5bb3 --- /dev/null +++ b/apps/portal/src/app/unity/v6/getting-started/page.mdx @@ -0,0 +1,75 @@ +import { + Callout, + Steps, + Step, + DocImage, + ArticleIconCard, + createMetadata, +} from "@doc"; +import { GraduationCap } from "lucide-react"; + +export const metadata = createMetadata({ + title: "Interacting with Contracts | thirdweb Unity SDK", + description: + "Learn how to interact with smart contracts using the thirdweb Unity SDK.", +}); + +# Getting Started + + + +To get started with the Unity SDK, you will need to have a [thirdweb API Key](https://thirdweb.com/create-api-key) created. Make sure you allowlist a bundle ID to use within Unity native applications. + + + +To get started, you'll need to [download and install the Unity Hub and Unity Editor](https://unity.com/download). + +We recommend using the 2022 LTS version of Unity. + +## Integration Steps + + + + + +Download the latest thirdweb Unity SDK package from the [releases page](https://github.com/thirdweb-dev/unity/releases). + + + + + +Drag and drop the package into your Unity project. + + + + + +Play around in our example `Scene_Playground` to test out the SDK! + + + + + +Refer to the [Build Instructions](/unity/v6/build-instructions), pick a target platform and build your scene! + +We support all platforms. + + + + + +## Thirdweb Manager + +Now you're ready to use the SDK in your own scene! + +We recommend adding a [ThirdwebManager](/unity/v6/thirdwebmanager) prefab to your scene. + +Navigate to `Thirdweb` > `Runtime` > `Unity` > `Prefabs` and drag the `ThirdwebManager` into your scene. + +Alternatively, use our handy quickstart installer from the top level `Tools > Thirdweb` menu. + + diff --git a/apps/portal/src/app/unity/v6/layout.tsx b/apps/portal/src/app/unity/v6/layout.tsx new file mode 100644 index 00000000000..c5fd47ccc08 --- /dev/null +++ b/apps/portal/src/app/unity/v6/layout.tsx @@ -0,0 +1,26 @@ +import { createMetadata } from "@/components/Document"; +import { DocLayout } from "@/components/Layouts/DocLayout"; +import { PlatformSelector } from "../../../components/others/PlatformSelector"; +import { sidebar } from "./sidebar"; + +export default async function Layout(props: { children: React.ReactNode }) { + return ( + } + > + {props.children} + + ); +} + +export const metadata = createMetadata({ + description: + "Connect to user's wallets, interact with smart contracts, sign messages, and utilize common standards such as tokens, NFTs, marketplaces; all with built-in RPC URLs, IPFS gateways, and more.", + image: { + icon: "unity", + title: "thirdweb Unity SDK", + }, + title: "thirdweb Unity SDK", +}); diff --git a/apps/portal/src/app/unity/v6/migration-guide/page.mdx b/apps/portal/src/app/unity/v6/migration-guide/page.mdx new file mode 100644 index 00000000000..2f6f3a30305 --- /dev/null +++ b/apps/portal/src/app/unity/v6/migration-guide/page.mdx @@ -0,0 +1,162 @@ + +import { + Callout, + Details, + createMetadata, +} from "@doc"; + +export const metadata = createMetadata({ + title: "Unity SDK v5 to v6 Migration Guide | thirdweb Unity SDK", + description: + "Upgrade notes that map the thirdweb Unity SDK v5 API surface to v6, highlighting the exact code changes in ThirdwebManagerBase and wallet providers.", +}); + +# Unity SDK v5 → v6 Migration Guide + +Unity SDK v6 modernises wallet flows, trims unused integrations, and keeps the .NET interoperability you relied on in v5. This document compares the two `ThirdwebManagerBase` implementations and lists the code edits required to upgrade an existing project. + +## Quick reference: what changed? + +- **Wallet providers** – The `WalletProvider` enum now only exposes `InAppWallet`, `EcosystemWallet`, and `ReownWallet`. `PrivateKeyWallet`, `WalletConnectWallet`, and `MetaMaskWallet` handlers were removed from the manager. +- **Reown integration** – External wallets are unified under the optional `ReownWallet` provider. Support is wrapped in `#if THIRDWEB_REOWN` guards and requires Reown AppKit in your project. +- **Option models** – `EcosystemWalletOptions` and `InAppWalletOptions` no longer accept `legacyEncryptionKey`. `WalletOptions` gained a `ReownOptions` payload that is filled with sensible defaults when omitted. +- **Manager internals** – `SupportedChains`, `IncludedWalletIds`, and the `_walletMapping` accessors were deleted. `ConnectWallet` now sets `ActiveWallet` directly and only persists the most recent wallet for auto-connect. +- **Version + diagnostics** – `THIRDWEB_UNITY_SDK_VERSION` jumped to `6.0.0`, and the boot sequence logs an explicit error if `THIRDWEB_REOWN` is defined but the AppKit prefab is missing from the scene. + + +Before updating, branch or clone your project. The v6 package deletes v5 scripts and prefabs; keeping a clean comparison helps when you reapply custom UI. + + +## 1. Install v6 and remove v5 assets + +1. Delete the v5 Thirdweb folder. +2. Import the v6 Unity package. +3. Remove any outdated assembly definition references. + + +After deleting old assemblies, run a domain reload (enter/exit Play mode) so Unity forgets the stale DLLs before you open scenes. + + +## 2. Replace `ThirdwebManager` prefabs or components + +The serialized shape of `ThirdwebManagerBase` changed between branches. Drop the new prefab into your scenes (or update custom subclasses) and clear orphaned fields. + +```csharp +// v5 +public enum WalletProvider { + PrivateKeyWallet, + InAppWallet, + WalletConnectWallet, + MetaMaskWallet, + EcosystemWallet, +} + +// v6 +public enum WalletProvider { + InAppWallet, + EcosystemWallet, + ReownWallet, +} +``` + +Key property changes in the component: + +| Removed (v5) | Replacement in v6 | +| --- | --- | +| `ulong[] SupportedChains` | Provide supported chain IDs when you call `WalletConnectWallet` (now Reown) or manage them in code. | +| `string[] IncludedWalletIds` | Use `ReownOptions.IncludedWalletIds` when invoking `ReownWallet`. | +| `Dictionary _walletMapping` and helpers (`GetWallet`, `AddWallet`, `RemoveWallet`) | Maintain your own cache if you relied on these lookups. `ConnectWallet` assigns `ActiveWallet` without tracking every address. | +| `legacyEncryptionKey` on wallet option classes | Remove the property from any serialized scriptable objects or constructor calls. | + +
+`SetAutoConnectOptions` used to ignore `WalletConnectWallet`. In v6 the guard is gone, so every provider you connect (including Reown) will save its options into `PlayerPrefs`. If you prefer to opt out, disable `AutoConnectLastWallet` or clear `PlayerPrefs` after connecting. +
+ +## 3. Add (or skip) Reown support + +Reown replaces both MetaMask and WalletConnect. The v6 manager checks for the AppKit prefab whenever the `THIRDWEB_REOWN` scripting define symbol is set. + +```csharp +#if THIRDWEB_REOWN +var reownModalExists = FindObjectOfType(); +if (!reownModalExists) { + ThirdwebDebug.LogError("Reown AppKit not found in scene …"); +} +#endif +``` + +To enable Reown: + +1. Install the [Reown AppKit Unity package](https://docs.reown.com/appkit/unity/core/installation). +2. Add `THIRDWEB_REOWN` to **Player Settings → Other Settings → Scripting Define Symbols** for every build target that uses external wallets. +3. Drop the `Reown AppKit` prefab into the scenes that should surface the modal. + +If you’re shipping only in-app or ecosystem wallets, **do not** set the scripting define and remove any references to `WalletProvider.ReownWallet`. + +## 4. Update wallet connection code + +Search the project for the removed enum values and update the call sites. + +```csharp +// v5 WalletConnect +await ThirdwebManager.Instance.ConnectWallet(new WalletOptions( + provider: WalletProvider.WalletConnectWallet, + chainId: new BigInteger(421614) +)); + +// v6 Reown +await ThirdwebManager.Instance.ConnectWallet(new WalletOptions( + provider: WalletProvider.ReownWallet, + chainId: new BigInteger(421614), + reownOptions: new ReownOptions(projectId: "YOUR_REOWN_PROJECT_ID") +)); +``` + +Other call-site adjustments: + +- Remove any arguments named `legacyEncryptionKey` when instantiating `InAppWalletOptions` or `EcosystemWalletOptions`. +- Replace checks for `WalletProvider.PrivateKeyWallet` with direct usage of the .NET `PrivateKeyWallet` helpers, e.g. `await PrivateKeyWallet.Generate(client)`. +- When you need to filter wallets in Reown, set `IncludedWalletIds`/`ExcludedWalletIds` inside the `ReownOptions` object instead of relying on the removed `IncludedWalletIds` array. + +## 5. Refresh smart wallet upgrades and linking logic + +`SmartWalletOptions` retained the same fields, so your upgrade code should compile unchanged. What does change is the surrounding manager state: + +- `UpgradeToSmartWallet` no longer adds the smart wallet to a shared dictionary; it simply returns the new wallet and sets `ActiveWallet`. +- `LinkAccount` still exists but now reads the redirect settings from `ThirdwebManagerBase`’s remaining fields. Confirm any login UI that referenced `RedirectPageHtmlOverride` still points to a valid asset. + +```csharp +var personalWallet = await PrivateKeyWallet.Generate(client); +var smartWallet = await ThirdwebManager.Instance.UpgradeToSmartWallet( + personalWallet, + chainId: new BigInteger(421614), + smartWalletOptions: new SmartWalletOptions(sponsorGas: true) +); +``` + +## 6. Clean up project assets + +- Delete deprecated prefabs (`WalletConnectModal`, old MetaMask UI, etc.). +- Remove scripts that referenced the dropped enum values so Unity stops warning about missing behaviours. +- Clear serialized ScriptableObjects that kept `legacyEncryptionKey`, `SupportedChains`, or `IncludedWalletIds`. +- Update internal docs to mention Reown and the new optional dependency model. + + +If Unity reports missing MonoBehaviours after the upgrade, open the affected prefab or scene, remove the broken component, and rewire it using the v6 APIs. + + +## 7. Validate the migration + +1. Open Play mode and confirm each wallet flow works (In-App, Ecosystem, and optionally Reown). +2. Test the auto-connect flow to verify that `PlayerPrefs` stores the new `WalletOptions` payload. +3. Build for every platform you support; Reown builds will fail if the AppKit prefab or define is missing. +4. Run any automated gameplay/contract tests to ensure async interactions behave identically. + +## Troubleshooting + +| Issue | Explanation | Resolution | +| --- | --- | --- | +| `NotSupportedException: Wallet provider WalletConnectWallet is not supported.` | A v5 enum value remained in code. | Replace it with `WalletProvider.ReownWallet` or remove the branch. | +| `Reown AppKit not found in scene …` | `THIRDWEB_REOWN` is defined but the prefab is absent. | Install AppKit and add the prefab, or remove the scripting define. | +| `legacyEncryptionKey` compiler errors | Constructors in v6 dropped the argument. | Delete the parameter from your calls or ScriptableObject assets. | +| Missing script warnings on UI prefabs | Old WalletConnect/MetaMask components were removed. | Delete the component and rebuild the UI against Reown or in-app wallet scripts. | diff --git a/apps/portal/src/app/unity/v6/page.mdx b/apps/portal/src/app/unity/v6/page.mdx new file mode 100644 index 00000000000..f438e8c67d4 --- /dev/null +++ b/apps/portal/src/app/unity/v6/page.mdx @@ -0,0 +1,75 @@ +import { OpenSourceCard, ArticleIconCard, ArticleCard, Grid } from "@doc"; +import { GraduationCap } from "lucide-react"; + +# Unity SDK + +The best Unity SDK to build cross-platform games on any EVM blockchain. + +Connect to user’s wallets, interact with smart contracts, sign +messages, and utilize common standards such as tokens, NFTs, marketplaces; all with built-in RPC URLs, IPFS gateways, Account Abstraction and more. + + + +## Installation + +The Unity SDK is distributed as an [Asset Package](https://docs.unity3d.com/Manual/AssetPackages.html) allowing you to view and edit the source code. + +It also wraps our open-source gaming-focused [.NET SDK](https://github.com/thirdweb-dev/dotnet) making version updates extremely simple containing a single DLL filechange. + +You can download the latest version of the SDK from our [GitHub releases page](https://github.com/thirdweb-dev/unity-sdk/releases). + +## Get Started + +Check out the [getting started](/unity/v6/getting-started) guide to learn how to use the SDK in less than 2 minutes. + + + +## Examples + + + + + + + + + + + + + + + + diff --git a/apps/portal/src/app/unity/v6/sidebar.tsx b/apps/portal/src/app/unity/v6/sidebar.tsx new file mode 100644 index 00000000000..14f44eedc1c --- /dev/null +++ b/apps/portal/src/app/unity/v6/sidebar.tsx @@ -0,0 +1,93 @@ +import { CodeIcon, ZapIcon } from "lucide-react"; +import type { SideBar } from "@/components/Layouts/DocLayout"; + +const sdkSlug = "/unity/v6"; +const walletProvidersSlug = `${sdkSlug}/wallets`; + +export const sidebar: SideBar = { + links: [ + { separator: true }, + { + href: sdkSlug, + name: "Overview", + }, + { + href: `${sdkSlug}/getting-started`, + icon: , + name: "Getting Started", + }, + { + href: "/dotnet", + icon: , + isCollapsible: false, + name: "API Reference", + }, + { + separator: true, + }, + { + isCollapsible: false, + links: [ + { + href: `${sdkSlug}/thirdwebmanager`, + name: "Thirdweb Manager", + }, + { + href: `${sdkSlug}/build-instructions`, + name: "Build Instructions", + }, + ], + name: "Core", + }, + { + separator: true, + }, + { + isCollapsible: false, + links: [ + { + href: `${walletProvidersSlug}/in-app-wallet`, + name: "In-App Wallet", + }, + { + href: `${walletProvidersSlug}/ecosystem-wallet`, + name: "Ecosystem Wallet", + }, + { + href: `${walletProvidersSlug}/account-abstraction`, + name: "Account Abstraction", + }, + { + href: `${walletProvidersSlug}/reown`, + name: "External Wallet (Reown)", + }, + ], + name: "Onboarding Users", + }, + { + separator: true, + }, + { + isCollapsible: false, + links: [ + { + href: `${sdkSlug}/contracts`, + name: "Interacting with Contracts", + }, + { + href: "/dotnet/contracts/extensions", + name: "Contract Extensions", + }, + ], + name: "Onchain Interactions", + }, + { + separator: true, + }, + { + href: `${sdkSlug}/migration-guide`, + name: "Migrate from v5", + }, + ], + name: "Unity SDK", +}; diff --git a/apps/portal/src/app/unity/v6/thirdwebmanager/page.mdx b/apps/portal/src/app/unity/v6/thirdwebmanager/page.mdx new file mode 100644 index 00000000000..73236f535ad --- /dev/null +++ b/apps/portal/src/app/unity/v6/thirdwebmanager/page.mdx @@ -0,0 +1,138 @@ + +import { DocImage, createMetadata } from "@doc"; +import thirdwebmanager_client from "./thirdwebmanager_client.png"; +import thirdwebmanager_preferences from "./thirdwebmanager_preferences.png"; +import thirdwebmanager_misc from "./thirdwebmanager_misc.png"; +import thirdwebmanager_debug from "./thirdwebmanager_debug.png"; + +export const metadata = createMetadata({ + title: "Thirdweb Manager | thirdweb Unity SDK", + description: + "ThirdwebManager is a prefab that provides a convenient way to instantiate the ThirdwebClient, and contains helper functions to create contracts and wallets. Add the prefab to your scene and the SDK will persist throughout your game's lifecycle.", +}); + +# Thirdweb Manager + +The `ThirdwebManager` is a prefab that provides a convenient way to instantiate the [ThirdwebClient](/dotnet/client), and contains helper functions to create contracts and wallets. +Add the prefab to your scene and the client will persist throughout your game's lifecycle, keeping track of your connected wallets. + +It is entirely optional, and you can opt to use the [.NET SDK](/dotnet) directly if you prefer to do so. + +If you are wrapping the SDK, we recommend making your own Manager inspired by `ThirdwebManager.cs` that extends `ThirdwebManagerBase.cs`, specifically for `ThirdwebClient` initialization. Every method and property is virtual and can be overridden. + +## Configuration + +Configure `ThirdwebManager` through the Unity Inspector window. + +Below is a list of all the settings you can adjust. + +### Client + + + +This section configures the `ThirdwebClient` instance that the manager creates at runtime: + +- `Client ID`: Required thirdweb [API Key](https://thirdweb.com/create-api-key). Without it, `ThirdwebManager` refuses to initialize and logs an error. +- `Bundle ID`: Optional identifier used for native redirect URIs. If left blank, the manager falls back to `Application.identifier` (or `com..` when no identifier is set). +- `Create API Key`: Opens the dashboard so you can generate a fresh API key. + +### Preferences + + + +Use these toggles to shape how the manager boots and reports information: + +- `Initialize On Awake`: Automatically calls `Initialize()` during `Awake`. Disable it if you need to delay initialization or swap credentials at runtime. +- `Show Debug Logs`: Mirrors `ThirdwebDebug.IsEnabled`, enabling verbose diagnostics during development. +- `Auto-Connect Last Wallet`: Persists the most recent `WalletOptions` to `PlayerPrefs` and reconnects on startup (including smart-wallet upgrades). Errors in this flow are swallowed and logged. + +### Miscellaneous + + + +Fine-tune the runtime behaviour of authentication and networking here: + +- `RPC Overrides`: Per-chain RPC endpoints stored as `ChainId` → `URL` pairs. When populated, these replace the default thirdweb RPCs for the matching chains. +- `OAuth Redirect Page HTML Override`: Multiline HTML snippet used by the in-app and ecosystem OAuth flows (including external browser handoffs). Provide custom markup only if you know the full redirect requirements. + +### Debug + + + +Quick utilities exposed by the custom inspector: + +- `Log Active Wallet Info`: Available in Play Mode; prints the current `ActiveWallet` type and address to the Unity Console. +- `Open Documentation`: Launches the hosted Unity SDK documentation in your default browser. + +## Interacting with the ThirdwebManager + +Once your `ThirdwebManager` is set up, you can interact with it using the following methods: + +### Initialize (If not set to `Initialize On Awake`) + +```csharp +ThirdwebManager.Instance.Initialize(); +``` + +Initializes the SDK with the settings specified in the Unity Inspector. + +### ConnectWallet + +```csharp +var walletOptions = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + inAppWalletOptions: new InAppWalletOptions(authprovider: AuthProvider.Guest) +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(walletOptions); +var address = await wallet.GetAddress(); +ThirdwebDebug.Log($"Connected wallet address: {address}"); +``` + +Connects a wallet based on the specified `WalletOptions` and returns an `IThirdwebWallet` instance that can be used to interact with the blockchain. + +### UpgradeToSmartWallet + +```csharp +var smartWallet = await ThirdwebManager.Instance.UpgradeToSmartWallet(wallet, chainId, smartWalletOptions); +``` + +Upgrades the specified wallet to a `SmartWallet`, returning a `SmartWallet` instance. + +### ActiveWallet + +```csharp +var activeWallet = ThirdwebManager.Instance.ActiveWallet; +var address = await activeWallet.GetAddress(); +``` + +Returns the last connected wallet as an `IThirdwebWallet` instance, or `null` if no wallet is connected. + +### GetContract + +```csharp +var contract = await ThirdwebManager.Instance.GetContract("contract-address", chainId, "optional-contract-abi"); +var result = await contract.Read("name"); +``` + +Returns a [ThirdwebContract](/dotnet/contracts/create) instance that can be used to interact with a smart contract. + +### LinkAccount + +```csharp +var linkedAccounts = await ThirdwebManager.Instance.LinkAccount(mainWallet, walletToLink, otp, chainId, jwtOrPayload); +``` + +Links another `InAppWallet` or `EcosystemWallet` account to the main wallet and returns a list of linked accounts, allowing you to login with either authentication method later. + +## Child Prefabs + +### DefaultOTPModal + +The helper modal that is displayed when using the `ThirdwebManager`'s `ConnectWallet` function with an auth method that requires an OTP, such as Email or Phone login. + +It can be replaced with a custom modal that extends `AbstractOTPVerifyModal` to customize the OTP verification process. + +## What Now? + +Explore the [.NET SDK](/dotnet) to learn more about interacting with smart contracts, wallets, storage, RPC, account abstraction, and more. diff --git a/apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_client.png b/apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_client.png new file mode 100644 index 0000000000000000000000000000000000000000..4b987ba073a1d42dddfb90906ac2bfc36df953c5 GIT binary patch literal 27236 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 literal 0 HcmV?d00001 diff --git a/apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_debug.png b/apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_debug.png new file mode 100644 index 0000000000000000000000000000000000000000..1c5599683a3408704ff0d133e5e3cf0c8f895774 GIT binary patch literal 27435 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 literal 0 HcmV?d00001 diff --git a/apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_preferences.png b/apps/portal/src/app/unity/v6/thirdwebmanager/thirdwebmanager_preferences.png new file mode 100644 index 0000000000000000000000000000000000000000..b1bd2b3863d383a083130734be85fa61a31ff76a GIT binary patch literal 24452 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 literal 0 HcmV?d00001 diff --git a/apps/portal/src/app/unity/v6/wallets/account-abstraction/page.mdx b/apps/portal/src/app/unity/v6/wallets/account-abstraction/page.mdx new file mode 100644 index 00000000000..7d8003268a6 --- /dev/null +++ b/apps/portal/src/app/unity/v6/wallets/account-abstraction/page.mdx @@ -0,0 +1,65 @@ +import { Details, Callout, createMetadata, ArticleIconCard } from "@doc"; +import { GraduationCap } from "lucide-react"; + +export const metadata = createMetadata({ + title: "SmartWallet | Thirdweb Unity SDK", + description: + "Instantiate a SmartWallet to sign transactions and messages.", +}); + +# Native Account Abstraction (via EIP-7702 Smart EOAs) +Native Account Abstraction is a system that allows you to set code to an EOA, unlocking a world of possibilities to enhance their functionality. It is available since the Pectra upgrade on various chains. + +Enabling it is as simple as creating an `InAppWallet` and passing the `ExecutionMode.EIP7702Sponsored` flag during creation. + +```csharp +// Turn your boring EOAs into Smart EOAs! +var smartIaw = await ConnectWallet( + new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 11155111, // Sepolia supports EIP-7702 + inAppWalletOptions: new InAppWalletOptions( + authprovider: AuthProvider.Google, + executionMode: ExecutionMode.EIP7702Sponsored // new! + ) + ) +); +ThirdwebDebug.Log("Connected to InAppWallet: " + await smartIaw.GetAddress()); + +// Execute a transaction as usual, execution is managed by thirdweb seamlessly! +var receipt = await smartIaw.Transfer(11155111, await smartIaw.GetAddress(), 0); +ThirdwebDebug.Log($"Transfer receipt: https://sepolia.etherscan.io/tx/{receipt.TransactionHash}"); +``` + +# SmartWallet (via EIP-4337 Bundlers) + +Instantiate or upgrade any other wallet to a `SmartWallet` to enable advanced blockchain interactions, including gasless transactions through Account Abstraction (ERC4337 as well as ZkSync Native AA). + +Account Abstraction is a system that turns any personal wallet into a smart wallet, allowing you to sponsor gas for your users and unlocking advanced permissioning features that allow for seamless user onboarding, gasless transactions, automation and more. + +We recommend using Smart Wallets as the primary wallet type for your users. + +### Connecting to a Smart Wallet directly + +```csharp +var smartWalletOptions = new SmartWalletOptions(sponsorGas: true) +var options = new WalletOptions(..., smartWalletOptions: smartWalletOptions); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +### Upgrading an existing wallet to a Smart Wallet + +```csharp +var smartWalletOptions = new SmartWalletOptions(sponsorGas: true) +var smartWallet = await ThirdwebManager.Instance.UpgradeToSmartWallet( + personalWallet: myExistingIThirdwebWallet, + chainId: 1, + smartWalletOptions: smartWalletOptions +); +``` + + \ No newline at end of file diff --git a/apps/portal/src/app/unity/v6/wallets/ecosystem-wallet/page.mdx b/apps/portal/src/app/unity/v6/wallets/ecosystem-wallet/page.mdx new file mode 100644 index 00000000000..59ccc75a165 --- /dev/null +++ b/apps/portal/src/app/unity/v6/wallets/ecosystem-wallet/page.mdx @@ -0,0 +1,214 @@ +import { Details, Callout, createMetadata, ArticleIconCard } from "@doc"; +import { GraduationCap } from "lucide-react"; + +export const metadata = createMetadata({ + title: "EcosystemWallet | Thirdweb Unity SDK", + description: + "Instantiate an EcosystemWallet to sign transactions and messages.", +}); + +# EcosystemWallet + +`EcosystemWallet` is the ultimate persistent wallet provider option for your game. It supports email, phone, social and custom authentication schemes, and will persist across devices, platforms, and other SDKs. + +It makes for a fantastic [SmartWallet](/unity/v6/wallets/account-abstraction) admin/signer and will make sure your users can have the same wallet address across all your games, apps and blockchains. + +Ecosystem Wallets have a very similar API to the [In-App Wallet](/unity/v6/wallets/in-app-wallet) but with the added benefit of being able to share your wallets with ecosystem partners through special identifiers that they can use, preserving the user's identity across not only your apps and games, but other ecosystem partners' as well. It is secure, easy to use, and use enclave technology to protect your user's data. All examples below can take in an `ecosystemPartnerId` if you are the ecosystem partner integrating with a third-party ecosystem. + +## Login Methods + +Ecosystem Wallets support a variety of login methods: +- Email (OTP Login) +- Phone (OTP Login) +- Socials (Google, Apple, Facebook, Telegram, Farcaster, Line, Github, Twitch, Steam, TikTok etc.) +- Custom Auth (OIDC Compatible) +- Custom Auth (Generic Auth Endpoint) +- Guest (Onboard easily, link other accounts later) +- Backend (Server Wallets) +- Siwe (Login with a separate wallet supported by the SDK) +- SiweExternal (Login with an external wallet that only supports web using a browser loading a static thirdweb React page temporarily) + +### Login with Email + +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions( + ecosystemId: "ecosystem.your-ecosystem", + email: "myepicemail@domain.id" +); +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + ecosystemWalletOptions: ecosystemWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +Will instantiate `EcosystemWalletModal` or resume the session - a simple prefab that will verify the user OTP. + +### Login with Phone + +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions( + ecosystemId: "ecosystem.your-ecosystem", + phoneNumber: "+1234567890" +); +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + ecosystemWalletOptions: ecosystemWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +Will instantiate `EcosystemWalletModal` or resume the session - a simple prefab that will verify the user OTP. + +### Login with Socials (Google, Apple, Facebook, etc.) + +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions( + ecosystemId: "ecosystem.your-ecosystem", + authprovider: AuthProvider.Google +); +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + ecosystemWalletOptions: ecosystemWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +Will open a native browser or oauth session to authenticate the user and redirect back to the game. + +### Login with SIWE + +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions( + ecosystemId: "ecosystem.your-ecosystem", + authprovider: AuthProvider.Siwe, + siweSigner: anyExternalWallet +); +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + ecosystemWalletOptions: ecosystemWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +Will use the external wallet to sign a message and login to the EcosystemWallet. + +### Login with SiweExternal + +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions(ecosystemId: "ecosystem.your-ecosystem", authprovider: AuthProvider.SiweExternal) +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 421614, + ecosystemWalletOptions: ecosystemWalletOptions +); +var wallet = await ConnectWallet(options); +``` + +Will open a browser and load a static thirdweb React page to authenticate the user and redirect back to the game. +You can pass `forceSiweExternalWalletIds` to force the page to use one or more wallets. + +Example: +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions( + ecosystemId: "ecosystem.your-ecosystem", + authprovider: AuthProvider.SiweExternal, + forceSiweExternalWalletIds: new List { "xyz.abs" }); +``` + +### Login with Custom Auth - OIDC Compatible + +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions( + ecosystemId: "ecosystem.your-ecosystem", + authprovider: AuthProvider.JWT, + jwtOrPayload: "myjwt" +); +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + ecosystemWalletOptions: ecosystemWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +### Login with Custom Auth - Generic Auth Endpoint + +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions( + ecosystemId: "ecosystem.your-ecosystem", + authprovider: AuthProvider.AuthEndpoint, + jwtOrPayload: "mypayload" +); +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + ecosystemWalletOptions: ecosystemWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +### Login with Guest - Onboard easily, link other accounts later + +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions( + ecosystemId: "ecosystem.your-ecosystem", + authprovider: AuthProvider.Guest +); +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + ecosystemWalletOptions: ecosystemWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +### Login with Backend - Server Wallets + +```csharp +var ecosystemWalletOptions = new EcosystemWalletOptions( + ecosystemId: "ecosystem.your-ecosystem", + authprovider: AuthProvider.Backend, + walletSecret: "very-secret" +); +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + ecosystemWalletOptions: ecosystemWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +## Account Linking + +EcosystemWallets support linking multiple authentication methods to a single wallet, for instance linking Google to your Email-based In-App-Wallet. This is useful to have a unified identity across platforms. + +```csharp +// Your main EcosystemWallet account, already authenticated and connected +EcosystemWallet mainEcosystemWallet = ... + +// An EcosystemWallet with a new auth provider to be linked to the main account, not connected +EcosystemWallet walletToLink = await EcosystemWallet.Create(client: Client, ecosystemId: "ecosystem.your-ecosystem", authProvider: AuthProvider.Telegram); + +// Link Account - Headless version +var linkedAccounts = await mainEcosystemWallet.LinkAccount(walletToLink: walletToLink); + +// Link Account - Unity wrapper version +var linkedAccounts = await ThirdwebManager.Instance.LinkAccount(mainEcosystemWallet, walletToLink); + +// You can also fetch linked accounts at any time +List linkedAccounts = await mainEcosystemWallet.GetLinkedAccounts(); + +// Unlink an account +List linkedAccounts = await mainEcosystemWallet.UnlinkAccount(linkedAccounts[0]); +``` + + diff --git a/apps/portal/src/app/unity/v6/wallets/in-app-wallet/page.mdx b/apps/portal/src/app/unity/v6/wallets/in-app-wallet/page.mdx new file mode 100644 index 00000000000..ccc59e06ce4 --- /dev/null +++ b/apps/portal/src/app/unity/v6/wallets/in-app-wallet/page.mdx @@ -0,0 +1,191 @@ +import { Details, Callout, createMetadata, ArticleIconCard } from "@doc"; +import { GraduationCap } from "lucide-react"; + +export const metadata = createMetadata({ + title: "InAppWallet | Thirdweb Unity SDK", + description: + "Instantiate an InAppWallet to sign transactions and messages.", +}); + +# InAppWallet + +`InAppWallet` is the ultimate persistent wallet provider option for your game. It supports email, phone, social and custom authentication schemes, and will persist across devices, platforms, and other SDKs. + +It makes for a fantastic [SmartWallet](/unity/v6/wallets/account-abstraction) admin/signer and will make sure your users can have the same wallet address across all your games, apps and blockchains. + +## Login Methods + +In-App Wallets support a variety of login methods: +- Email (OTP Login) +- Phone (OTP Login) +- Socials (Google, Apple, Facebook, Telegram, Farcaster, Line, Github, Twitch, Steam, TikTok etc.) +- Custom Auth (OIDC Compatible) +- Custom Auth (Generic Auth Endpoint) +- Guest (Onboard easily, link other accounts later) +- Backend (Server Wallets) +- Siwe (Login with a separate wallet supported by the SDK) +- SiweExternal (Login with an external wallet that only supports web using a browser loading a static thirdweb React page temporarily) + +### Login with Email + +```csharp +var inAppWalletOptions = new InAppWalletOptions(email: "myepicemail@domain.id"); +var options = new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 1, + inAppWalletOptions: inAppWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +Will instantiate `InAppWalletModal` or resume the session - a simple prefab that will verify the user OTP. + +### Login with Phone + +```csharp +var inAppWalletOptions = new InAppWalletOptions(phoneNumber: "+1234567890"); +var options = new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 1, + inAppWalletOptions: inAppWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +Will instantiate `InAppWalletModal` or resume the session - a simple prefab that will verify the user OTP. + +### Login with Socials (Google, Apple, Facebook, etc.) + +```csharp +var inAppWalletOptions = new InAppWalletOptions(authprovider: AuthProvider.Google); +var options = new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 1, + inAppWalletOptions: inAppWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +Will open a native browser or oauth session to authenticate the user and redirect back to the game. + +### Login with Siwe + +```csharp +var inAppWalletOptions = new InAppWalletOptions(authprovider: AuthProvider.Siwe, siweSigner: sdkSupportedExternalWallet); +var options = new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 1, + inAppWalletOptions: inAppWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +Will use a separate wallet to sign a message and login to the InAppWallet. + +### Login with SiweExternal + +```csharp +var inAppWalletOptions = new InAppWalletOptions(authprovider: AuthProvider.SiweExternal) +var options = new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 421614, + inAppWalletOptions: inAppWalletOptions +); +var wallet = await ConnectWallet(inAppWalletOptions); +``` + +Will open a browser and load a static thirdweb React page to authenticate the user and redirect back to the game. +You can pass `forceSiweExternalWalletIds` to force the page to use one or more wallets. + +Example: +```csharp +var inAppWalletOptions = new InAppWalletOptions( + authprovider: AuthProvider.SiweExternal, + forceSiweExternalWalletIds: new List { "xyz.abs" }); +``` + +### Login with Custom Auth - OIDC Compatible + +```csharp +var inAppWalletOptions = new InAppWalletOptions(authprovider: AuthProvider.JWT, jwtOrPayload: "myjwt"); +var options = new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 1, + inAppWalletOptions: inAppWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +### Login with Custom Auth - Generic Auth Endpoint + +```csharp +var inAppWalletOptions = new InAppWalletOptions( + authprovider: AuthProvider.AuthEndpoint, + jwtOrPayload: "mypayload" +); +var options = new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 1, + inAppWalletOptions: inAppWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +### Login with Guest - Onboard easily, link other accounts later + +```csharp +var inAppWalletOptions = new InAppWalletOptions( + authprovider: AuthProvider.Guest +); +var options = new WalletOptions( + provider: WalletProvider.EcosystemWallet, + chainId: 1, + inAppWalletOptions: inAppWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +### Login with Backend - Server Wallets + +```csharp +var inAppWalletOptions = new InAppWalletOptions( + authprovider: AuthProvider.Backend, + walletSecret: "very-secret" +); +var options = new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 1, + inAppWalletOptions: inAppWalletOptions +); +var wallet = await ThirdwebManager.Instance.ConnectWallet(options); +``` + +## Account Linking + +InAppWallets support linking multiple authentication methods to a single wallet, for instance linking Google to your Email-based In-App-Wallet. This is useful to have a unified identity across platforms. + +```csharp +// Your main InAppWallet account, already authenticated and connected +InAppWallet mainInAppWallet = ... + +// An InAppWallet with a new auth provider to be linked to the main account, not connected +InAppWallet walletToLink = await InAppWallet.Create(client: Client, authProvider: AuthProvider.Telegram); + +// Link Account - Headless version +var linkedAccounts = await mainInAppWallet.LinkAccount(walletToLink: walletToLink); + +// Link Account - Unity wrapper version +var linkedAccounts = await ThirdwebManager.Instance.LinkAccount(mainInAppWallet, walletToLink); + +// You can also fetch linked accounts at any time +List linkedAccounts = await mainInAppWallet.GetLinkedAccounts(); + +// Unlink an account +List linkedAccounts = await mainInAppWallet.UnlinkAccount(linkedAccounts[0]); +``` + + diff --git a/apps/portal/src/app/unity/v6/wallets/reown/page.mdx b/apps/portal/src/app/unity/v6/wallets/reown/page.mdx new file mode 100644 index 00000000000..1a00d7ec772 --- /dev/null +++ b/apps/portal/src/app/unity/v6/wallets/reown/page.mdx @@ -0,0 +1,102 @@ +import { Details, Callout, createMetadata, ArticleIconCard } from "@doc"; +import { GraduationCap } from "lucide-react"; + +export const metadata = createMetadata({ + title: "ReownWallet | Thirdweb Unity SDK", + description: + "Connect external wallets in Unity using Reown AppKit and Thirdweb.", +}); + +# ReownWallet + +`ReownWallet` replaces the legacy WalletConnect integration. It wraps the official [Reown AppKit](https://docs.reown.com/appkit/unity) to surface 400+ wallets inside your Unity project. + + + - Install the **Reown AppKit Unity** package (via UPM Git URL `https://github.com/reown-com/appkit-unity.git`). + - Add the **"Reown AppKit"** prefab to your scene. + - Define the scripting symbol **`THIRDWEB_REOWN`** (Project Settings → Player → Other Settings → Scripting Define Symbols). + + Without the define symbol you'll hit a `NotSupportedException` with guidance to enable Reown support. + + +## Setup checklist + +1. **Add Reown prefab** – drag the `Reown AppKit` prefab from `Packages/Reown.AppKit.Unity/Prefabs` into your scene. +2. **Plan your connect call** – you choose the chain and branding when you construct `WalletOptions` and `ReownOptions` before calling `ConnectWallet`. +3. **Set the script define** – add `THIRDWEB_REOWN` to every build target that will use Reown. +4. **Provide metadata** – Reown needs a WalletConnect **projectId** plus branding details (name, description, URL, icon) in the options you pass at runtime. + +If the prefab is missing, the SDK logs an error at runtime telling you to either remove the define or add the prefab. + +## Connecting with Reown + +```csharp +var reownOptions = new ReownOptions( + projectId: "YOUR_REOWN_PROJECT_ID", + name: "My Game", + description: "thirdweb powered experience", + url: "https://mygame.example", + iconUrl: "https://mygame.example/icon.png", + includedWalletIds: new[] { "eip155:1:metamask" }, + excludedWalletIds: null +); + +var walletOptions = new WalletOptions( + provider: WalletProvider.ReownWallet, + chainId: 1, + reownOptions: reownOptions +); + +var wallet = await ThirdwebManager.Instance.ConnectWallet(walletOptions); +``` + +The connection flow will: + +1. Initialize Reown AppKit (once per session) with your metadata. +2. Attempt to resume a previous session before showing the modal. +3. Open the Reown modal for up to 120 seconds if no session is found. +4. Ensure the connected chain matches `walletOptions.ChainId`. + +If the selected chain is not part of Reown's built-in list, the SDK automatically injects the chain metadata using Thirdweb's chain APIs and logs a warning so you know it was added on the fly. + +## API surface + +`ReownWallet.Create` accepts the following arguments: + +
+ +### client (required) + +`ThirdwebClient` instance obtained from your `ThirdwebManager`. + +### activeChainId (required) + +`BigInteger` chain ID the wallet should target on connect. + +### projectId (required) + +Reown WalletConnect project ID. + +### name, description, url, iconUrl (optional) + +Branding metadata used inside the modal. Defaults mirror the `ReownOptions` constructor. + +### includedWalletIds / excludedWalletIds (optional) + +Filter the wallet list presented by Reown. Use WalletConnect wallet IDs (e.g. `eip155:1:metamask`). + +
+ +> **Limitations:** Reown currently doesn't support raw `eth_sign`, account linking, or EIP-7702 authorizations. The SDK throws descriptive exceptions if you call those APIs. + +## Troubleshooting + +- **"Reown AppKit not found"** – add the prefab or remove `THIRDWEB_REOWN` if you don't need Reown. +- **NotSupportedException on connect** – ensure the define symbol is added for the active build target. +- **Connection timed out** – the modal stays open for 120 seconds; confirm the wallet scanned the QR code or resumed the session. + + \ No newline at end of file From 9acb116c5a696175608b7b0b01325c4231e84884 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Sun, 28 Sep 2025 00:19:48 +0700 Subject: [PATCH 2/2] address reviews & remove SiweExternal --- apps/portal/src/app/dotnet/godot/page.mdx | 2 +- .../app/dotnet/wallets/user-wallet/page.mdx | 15 ------------ .../src/app/unity/v6/contracts/page.mdx | 2 +- .../src/app/unity/v6/getting-started/page.mdx | 4 ++-- .../v6/wallets/account-abstraction/page.mdx | 4 ++-- .../v6/wallets/ecosystem-wallet/page.mdx | 24 ------------------- .../unity/v6/wallets/in-app-wallet/page.mdx | 23 ------------------ 7 files changed, 6 insertions(+), 68 deletions(-) diff --git a/apps/portal/src/app/dotnet/godot/page.mdx b/apps/portal/src/app/dotnet/godot/page.mdx index a8146f82423..472d545c80f 100644 --- a/apps/portal/src/app/dotnet/godot/page.mdx +++ b/apps/portal/src/app/dotnet/godot/page.mdx @@ -24,7 +24,7 @@ To add the Thirdweb .NET SDK to your Godot project, you will need to manually ed ```xml - + ``` diff --git a/apps/portal/src/app/dotnet/wallets/user-wallet/page.mdx b/apps/portal/src/app/dotnet/wallets/user-wallet/page.mdx index 51da388b5bf..abc6bd38124 100644 --- a/apps/portal/src/app/dotnet/wallets/user-wallet/page.mdx +++ b/apps/portal/src/app/dotnet/wallets/user-wallet/page.mdx @@ -36,8 +36,6 @@ var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Backend, walletSecret: "very-secret"); // SIWE var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Siwe, siweSigner: anyExternalWallet); -// SIWE External -var wallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.SiweExternal); ``` ### EcosystemWallet @@ -61,8 +59,6 @@ var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosyste var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Backend, walletSecret: "very-secret"); // SIWE var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.Siwe, siweSigner: anyExternalWallet); -// SIWE External -var wallet = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.my-ecosystem", authProvider: AuthProvider.SiweExternal); ``` Once a wallet is created, you can always resume the session: @@ -135,17 +131,6 @@ var backendAddress = await wallet.LoginWithBackend(); ```csharp // SIWE var address = await wallet.LoginWithSiwe(chainId: 1); - -// SIWE External -var address = await wallet.LoginWithSiweExternal( - isMobile: false, - browserOpenAction: (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - }, - forceWalletIds: new List { "io.metamask", "com.coinbase.wallet", "xyz.abs" } -); ``` Limiting `forceWalletIds` to a single value skips the wallet picker and deep-links into that specific wallet provider. diff --git a/apps/portal/src/app/unity/v6/contracts/page.mdx b/apps/portal/src/app/unity/v6/contracts/page.mdx index 587165ea5c7..7a9b4b000ea 100644 --- a/apps/portal/src/app/unity/v6/contracts/page.mdx +++ b/apps/portal/src/app/unity/v6/contracts/page.mdx @@ -4,7 +4,7 @@ import { GraduationCap } from "lucide-react"; export const metadata = createMetadata({ title: "ThirdwebContract | Thirdweb Unity SDK", description: - "Instantiate a PrivateKeyWallet to sign transactions and messages.", + "Interact with any EVM smart contract through the ThirdwebContract wrapper in the Unity SDK.", }); # ThirdwebContract diff --git a/apps/portal/src/app/unity/v6/getting-started/page.mdx b/apps/portal/src/app/unity/v6/getting-started/page.mdx index e3f1daa5bb3..277fa60145a 100644 --- a/apps/portal/src/app/unity/v6/getting-started/page.mdx +++ b/apps/portal/src/app/unity/v6/getting-started/page.mdx @@ -9,9 +9,9 @@ import { import { GraduationCap } from "lucide-react"; export const metadata = createMetadata({ - title: "Interacting with Contracts | thirdweb Unity SDK", + title: "Getting Started | thirdweb Unity SDK", description: - "Learn how to interact with smart contracts using the thirdweb Unity SDK.", + "Kick off your Unity integration with the thirdweb Unity SDK and learn the essential setup steps.", }); # Getting Started diff --git a/apps/portal/src/app/unity/v6/wallets/account-abstraction/page.mdx b/apps/portal/src/app/unity/v6/wallets/account-abstraction/page.mdx index 7d8003268a6..3757c1d8839 100644 --- a/apps/portal/src/app/unity/v6/wallets/account-abstraction/page.mdx +++ b/apps/portal/src/app/unity/v6/wallets/account-abstraction/page.mdx @@ -42,7 +42,7 @@ We recommend using Smart Wallets as the primary wallet type for your users. ### Connecting to a Smart Wallet directly ```csharp -var smartWalletOptions = new SmartWalletOptions(sponsorGas: true) +var smartWalletOptions = new SmartWalletOptions(sponsorGas: true); var options = new WalletOptions(..., smartWalletOptions: smartWalletOptions); var wallet = await ThirdwebManager.Instance.ConnectWallet(options); ``` @@ -50,7 +50,7 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options); ### Upgrading an existing wallet to a Smart Wallet ```csharp -var smartWalletOptions = new SmartWalletOptions(sponsorGas: true) +var smartWalletOptions = new SmartWalletOptions(sponsorGas: true); var smartWallet = await ThirdwebManager.Instance.UpgradeToSmartWallet( personalWallet: myExistingIThirdwebWallet, chainId: 1, diff --git a/apps/portal/src/app/unity/v6/wallets/ecosystem-wallet/page.mdx b/apps/portal/src/app/unity/v6/wallets/ecosystem-wallet/page.mdx index 59ccc75a165..0e8b1bb2ce1 100644 --- a/apps/portal/src/app/unity/v6/wallets/ecosystem-wallet/page.mdx +++ b/apps/portal/src/app/unity/v6/wallets/ecosystem-wallet/page.mdx @@ -26,7 +26,6 @@ Ecosystem Wallets support a variety of login methods: - Guest (Onboard easily, link other accounts later) - Backend (Server Wallets) - Siwe (Login with a separate wallet supported by the SDK) -- SiweExternal (Login with an external wallet that only supports web using a browser loading a static thirdweb React page temporarily) ### Login with Email @@ -97,29 +96,6 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options); Will use the external wallet to sign a message and login to the EcosystemWallet. -### Login with SiweExternal - -```csharp -var ecosystemWalletOptions = new EcosystemWalletOptions(ecosystemId: "ecosystem.your-ecosystem", authprovider: AuthProvider.SiweExternal) -var options = new WalletOptions( - provider: WalletProvider.EcosystemWallet, - chainId: 421614, - ecosystemWalletOptions: ecosystemWalletOptions -); -var wallet = await ConnectWallet(options); -``` - -Will open a browser and load a static thirdweb React page to authenticate the user and redirect back to the game. -You can pass `forceSiweExternalWalletIds` to force the page to use one or more wallets. - -Example: -```csharp -var ecosystemWalletOptions = new EcosystemWalletOptions( - ecosystemId: "ecosystem.your-ecosystem", - authprovider: AuthProvider.SiweExternal, - forceSiweExternalWalletIds: new List { "xyz.abs" }); -``` - ### Login with Custom Auth - OIDC Compatible ```csharp diff --git a/apps/portal/src/app/unity/v6/wallets/in-app-wallet/page.mdx b/apps/portal/src/app/unity/v6/wallets/in-app-wallet/page.mdx index ccc59e06ce4..56621ad9e74 100644 --- a/apps/portal/src/app/unity/v6/wallets/in-app-wallet/page.mdx +++ b/apps/portal/src/app/unity/v6/wallets/in-app-wallet/page.mdx @@ -24,7 +24,6 @@ In-App Wallets support a variety of login methods: - Guest (Onboard easily, link other accounts later) - Backend (Server Wallets) - Siwe (Login with a separate wallet supported by the SDK) -- SiweExternal (Login with an external wallet that only supports web using a browser loading a static thirdweb React page temporarily) ### Login with Email @@ -82,28 +81,6 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options); Will use a separate wallet to sign a message and login to the InAppWallet. -### Login with SiweExternal - -```csharp -var inAppWalletOptions = new InAppWalletOptions(authprovider: AuthProvider.SiweExternal) -var options = new WalletOptions( - provider: WalletProvider.InAppWallet, - chainId: 421614, - inAppWalletOptions: inAppWalletOptions -); -var wallet = await ConnectWallet(inAppWalletOptions); -``` - -Will open a browser and load a static thirdweb React page to authenticate the user and redirect back to the game. -You can pass `forceSiweExternalWalletIds` to force the page to use one or more wallets. - -Example: -```csharp -var inAppWalletOptions = new InAppWalletOptions( - authprovider: AuthProvider.SiweExternal, - forceSiweExternalWalletIds: new List { "xyz.abs" }); -``` - ### Login with Custom Auth - OIDC Compatible ```csharp