For end users: Players can manage their wallet, view balances, and browse transaction history at Immutable Play. Direct your users there for wallet management features.
Overview
This page covers essential wallet operations including retrieving wallet addresses, checking native IMX and ERC-20 token balances, sending transactions (transfers, contract interactions, NFTs), signing messages (ERC-191 personal sign and EIP-712 typed data), and error handling patterns.Prerequisites
User Authentication
User must be authenticated with Passport before using wallet operations
Web vs Native: Some features like pre-approved transactions require Unity/Unreal native clients and cannot be used in web browsers.
Wallet Connect
Connect the EVM provider and request account access. Required for all wallet operations including Checkout wallet funding and Marketplace operations.- TypeScript
- Unity
- Unreal
Copy
Ask AI
// Assuming Passport is initialized and user is logged in
// 1. Connect EVM provider
const provider = passportInstance.connectEvm();
// 2. Request accounts (connect wallet)
const accounts = await provider.request({ method: 'eth_requestAccounts' });
const address = accounts[0];
console.log('Wallet connected:', address);
Copy
Ask AI
// Assuming Passport is initialized and user is logged in
// 1. Connect EVM provider
await passport.ConnectEvm();
// 2. Request accounts (connect wallet)
var accounts = await passport.ZkEvmRequestAccounts();
string address = accounts[0];
Debug.Log($"Wallet connected: {address}");
Call
ConnectEvm() once after login, then use ZkEvmRequestAccounts() to get the wallet address. This initializes the zkEVM provider for all subsequent wallet operations.Copy
Ask AI
// Add to your subsystem:
public:
void ConnectWallet()
{
auto Passport = GetPassport();
if (!Passport)
{
return;
}
// 1. Connect EVM provider
Passport->ConnectEvm(UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UPassportQuickStartSubsystem::OnWalletEvmConnected));
}
private:
void OnWalletEvmConnected(FImmutablePassportResult Result)
{
if (!Result.Success)
{
UE_LOG(LogTemp, Error, TEXT("ConnectEvm Failed: %s"), *Result.Error);
return;
}
auto Passport = GetPassport();
if (!Passport)
{
return;
}
// 2. Request accounts (connect wallet)
Passport->ZkEvmRequestAccounts(UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UPassportQuickStartSubsystem::OnWalletAccountsReceived));
}
void OnWalletAccountsReceived(FImmutablePassportResult Result)
{
if (!Result.Success)
{
UE_LOG(LogTemp, Error, TEXT("Request Accounts Failed: %s"), *Result.Error);
return;
}
const auto& RequestAccountsData = FImmutablePassportZkEvmRequestAccountsData::FromJsonObject(Result.Response.JsonObject);
if (!RequestAccountsData.IsSet())
{
UE_LOG(LogTemp, Error, TEXT("Failed to parse accounts data"));
return;
}
const auto& Accounts = RequestAccountsData->accounts;
for (int Index = 0; Index < Accounts.Num(); Index++)
{
UE_LOG(LogTemp, Log, TEXT("Account[%d]: %s"), Index, *Accounts[Index]);
}
}
void OnLoginComplete(FImmutablePassportResult Result)
{
if (!Result.Success) return;
UE_LOG(LogTemp, Log, TEXT("Login Successful"));
ConnectWallet();
}
Follow the sequence: ConnectEvm → ZkEvmRequestAccounts. Use
FImmutablePassportZkEvmRequestAccountsData::FromJsonObject to parse the response.Important: Before connecting the wallet, ensure you have initialized Passport and logged in the user. Then complete these steps:
- Connect EVM provider (
connectEvm()/ConnectEvm()) - Request accounts (
eth_requestAccounts/ZkEvmRequestAccounts())
Get Wallet Address
- TypeScript
- Unity
- Unreal
Copy
Ask AI
// First, initialize the zkEVM provider
const provider = await connectWallet({ auth });
// Then get the wallet address
const accounts = await provider.request({ method: 'eth_requestAccounts' });
const address = accounts[0];
console.log('Wallet address:', address);
Copy
Ask AI
// First, initialize the zkEVM provider (call once after login)
await passport.ConnectEvm();
// Then get the wallet address
var accounts = await passport.ZkEvmRequestAccounts();
string address = accounts[0];
Debug.Log($"Wallet address: {address}");
Copy
Ask AI
// Add to your subsystem:
public:
void GetWalletAddress()
{
auto Passport = GetPassport();
if (!Passport)
{
return;
}
// First, initialize the zkEVM provider (call once after login)
Passport->ConnectEvm(UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UPassportQuickStartSubsystem::OnEvmConnected));
}
private:
void OnEvmConnected(FImmutablePassportResult Result)
{
if (!Result.Success)
{
UE_LOG(LogTemp, Error, TEXT("ConnectEvm Failed: %s"), *Result.Error);
return;
}
auto Passport = GetPassport();
if (!Passport)
{
return;
}
// Then get the wallet address
Passport->ZkEvmRequestAccounts(UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UPassportQuickStartSubsystem::OnAccountsReceived));
}
void OnAccountsReceived(FImmutablePassportResult Result)
{
if (!Result.Success)
{
UE_LOG(LogTemp, Error, TEXT("Request Accounts Failed: %s"), *Result.Error);
return;
}
const auto& RequestAccountsData = FImmutablePassportZkEvmRequestAccountsData::FromJsonObject(Result.Response.JsonObject);
if (!RequestAccountsData.IsSet())
{
UE_LOG(LogTemp, Error, TEXT("Failed to parse accounts data"));
return;
}
if (RequestAccountsData->accounts.Num() > 0)
{
FString Address = RequestAccountsData->accounts[0];
UE_LOG(LogTemp, Log, TEXT("Wallet Address: %s"), *Address);
}
}
void OnWalletAccountsReceived(FImmutablePassportResult Result)
{
if (!Result.Success) return;
UE_LOG(LogTemp, Log, TEXT("Wallet Connected"));
GetWalletAddress();
}
Linked Addresses
Users can link external wallets (like MetaMask, WalletConnect, etc.) to their Passport account, allowing them to use the same identity across multiple wallets.- TypeScript
- Unity
- Unreal
Copy
Ask AI
import { getLinkedAddresses } from '@imtbl/wallet';
import { config } from '@imtbl/sdk';
// Initialize API client
const apiClient = new config.ImmutableConfiguration({
environment: config.Environment.SANDBOX,
}).getMultiRollupApiClients();
// Get all wallets linked to the current Passport account
const linkedAddresses = await getLinkedAddresses(auth, apiClient);
console.log('Linked addresses:', linkedAddresses);
// Returns: ['0x123...', '0x456...']
Copy
Ask AI
List<string> addresses = await Passport.Instance.GetLinkedAddresses();
if (addresses.Count > 0)
{
Debug.Log("Linked addresses:");
foreach (string address in addresses)
{
Debug.Log($" {address}");
}
}
else
{
Debug.Log("No linked addresses");
}
Copy
Ask AI
// Add to your subsystem:
public:
void GetLinkedAddresses()
{
auto Passport = GetPassport();
if (!Passport)
{
return;
}
Passport->GetLinkedAddresses(UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UPassportQuickStartSubsystem::OnLinkedAddressesReceived));
}
private:
void OnLinkedAddressesReceived(FImmutablePassportResult Result)
{
if (!Result.Success)
{
UE_LOG(LogTemp, Error, TEXT("Get Linked Addresses Failed: %s"), *Result.Error);
return;
}
TArray<FString> LinkedAddresses = UImmutablePassport::GetResponseResultAsStringArray(Result.Response);
if (LinkedAddresses.Num() > 0)
{
UE_LOG(LogTemp, Log, TEXT("Found %d Linked Address(es)"), LinkedAddresses.Num());
for (int Index = 0; Index < LinkedAddresses.Num(); Index++)
{
UE_LOG(LogTemp, Log, TEXT(" Linked[%d]: %s"), Index, *LinkedAddresses[Index]);
}
}
else
{
UE_LOG(LogTemp, Log, TEXT("No Linked Addresses Found"));
}
}
void OnWalletAccountsReceived(FImmutablePassportResult Result)
{
if (!Result.Success) return;
UE_LOG(LogTemp, Log, TEXT("Wallet Connected"));
GetLinkedAddresses();
}
Important:
ConnectEvm() must be called once after login to initialize the zkEVM provider before calling ZkEvmRequestAccounts() or any other wallet operations.Check Balances
Native IMX and ERC-20 Token Balances- TypeScript
- Unity
- Unreal
Copy
Ask AI
import { BrowserProvider, formatEther, Contract } from 'ethers';
const provider = connectWallet({ auth });
const accounts = await provider.request({ method: 'eth_requestAccounts' });
const address = accounts[0];
const balance = await provider.request({
method: 'eth_getBalance',
params: [address, 'latest']
});
console.log('IMX Balance:', formatEther(balance), 'IMX');
// Get ERC-20 token balance
const erc20Abi = [
'function balanceOf(address owner) view returns (uint256)',
'function decimals() view returns (uint8)',
'function symbol() view returns (string)',
];
const tokenAddress = '0x...'; // Your ERC-20 token contract address
const ethersProvider = new BrowserProvider(provider);
const tokenContract = new Contract(tokenAddress, erc20Abi, ethersProvider);
const tokenBalance = await tokenContract.balanceOf(address);
const decimals = await tokenContract.decimals();
const symbol = await tokenContract.symbol();
console.log(`Token Balance: ${formatEther(tokenBalance)} ${symbol}`);
Copy
Ask AI
// Get native IMX balance using Passport's zkEVM provider
string balance = await passport.ZkEvmGetBalance(playerAddress);
Debug.Log($"Balance: {balance} wei");
// For ERC-20 token balances, use the Orderbook TokenBalance API
using Immutable.Orderbook.Api;
var orderbookApi = new OrderbookApi();
var tokenBalance = await orderbookApi.TokenBalanceAsync(
walletAddress: playerAddress,
contractAddress: "0xYourTokenContract..."
);
Debug.Log($"Token balance: {tokenBalance.Quantity}");
The
ZkEvmGetBalance method returns balance in wei as a string. For ERC-20 tokens, use the Orderbook API’s TokenBalanceAsync method.Copy
Ask AI
// Add to your subsystem:
public:
void GetWalletBalance(const FString& WalletAddress)
{
auto Passport = GetPassport();
if (!Passport)
{
return;
}
FImmutablePassportZkEvmGetBalanceData BalanceData;
BalanceData.address = WalletAddress;
BalanceData.blockNumberOrTag = TEXT("latest"); // Optional: defaults to "latest"
Passport->ZkEvmGetBalance(BalanceData, UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UPassportQuickStartSubsystem::OnBalanceReceived));
}
private:
void OnBalanceReceived(FImmutablePassportResult Result)
{
if (!Result.Success)
{
UE_LOG(LogTemp, Error, TEXT("Get Balance Failed: %s"), *Result.Error);
return;
}
FString Balance = UImmutablePassport::GetResponseResultAsString(Result.Response);
UE_LOG(LogTemp, Log, TEXT("Wallet Balance (wei): %s"), *Balance);
}
void OnWalletAccountsReceived(FImmutablePassportResult Result)
{
if (!Result.Success) return;
const auto& Data = FImmutablePassportZkEvmRequestAccountsData::FromJsonObject(Result.Response.JsonObject);
if (Data.IsSet() && Data->accounts.Num() > 0)
{
GetWalletBalance(Data->accounts[0]);
}
}
For ERC-20 token balances, use the Orderbook API’s
TokenBalanceAsync method (same as Unity). The ZkEvmGetBalance method returns balance in wei as a hex string.Send Transactions
Wei Conversion: 1 IMX = 10^18 wei. For TypeScript, use
parseUnits('1', 18) from ethers or viem. Unity/Unreal require string values in wei.Send Transaction with Confirmation
Recommended for critical operations - Waits for blockchain confirmation before returning.- TypeScript
- Unity
- Unreal
Copy
Ask AI
import { BrowserProvider } from 'ethers';
// Get the provider from Passport
const provider = await connectWallet({ auth });
// Wrap provider in ethers BrowserProvider
const browserProvider = new BrowserProvider(provider);
// Get the signer (represents the user's wallet)
const signer = await browserProvider.getSigner();
// Send transaction using signer
const tx = await signer.sendTransaction({
to: '0xRecipient...',
value: '1500000000000000000', // 1.5 IMX in wei
});
// Wait for blockchain confirmation
const receipt = await tx.wait();
console.log('Transaction confirmed!');
console.log('Hash:', receipt.hash);
console.log('Status:', receipt.status === 1 ? 'Success' : 'Failed');
Copy
Ask AI
using Immutable.Passport.Model;
TransactionRequest request = new TransactionRequest()
{
to = address,
value = amount,
data = data
}
TransactionReceiptResponse response = await passport.ZkEvmSendTransactionWithConfirmation(request);
switch (response.status)
{
case "1":
// Successful
break;
case "0":
// Failed
break;
}
Copy
Ask AI
// Add to your subsystem:
public:
void SendTransactionWithConfirmation(const FString& ToAddress, const FString& ValueInWei, const FString& Data = TEXT("0x"))
{
auto Passport = GetPassport();
if (!Passport)
{
UE_LOG(LogTemp, Error, TEXT("Passport not available"));
return;
}
FImtblTransactionRequest TransactionRequest;
TransactionRequest.to = ToAddress;
TransactionRequest.value = ValueInWei;
TransactionRequest.data = Data;
Passport->ZkEvmSendTransactionWithConfirmation(TransactionRequest, UImmutablePassport::FImtblPassportResponseDelegate::CreateWeakLambda(this, [this](FImmutablePassportResult Result)
{
if (Result.Success)
{
UE_LOG(LogTemp, Log, TEXT("Transaction Confirmed"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("Transaction Confirmation Failed: %s"), *Result.Error);
}
}));
}
When to use: Critical operations like transfers, mints, or state changes. Ensures transaction succeeded before updating game state.
Example: Interacting with Smart Contracts
Example: Interacting with Smart Contracts
Copy
Ask AI
import { BrowserProvider, ethers } from 'ethers';
// Setup provider and signer
const provider = await connectWallet({ auth });
const browserProvider = new BrowserProvider(provider);
const signer = await browserProvider.getSigner();
// Define contract ABI (just the functions you need)
const abi = [
'function safeTransferFrom(address from, address to, uint256 tokenId)'
];
// Create contract instance
const contract = new ethers.Contract(
'0xYourNFTContract...', // Contract address
abi,
signer // Signer enables sending transactions
);
// Call contract function - automatically sends transaction
const tx = await contract.safeTransferFrom(
userAddress,
recipientAddress,
tokenId
);
// Wait for confirmation
const receipt = await tx.wait();
console.log('NFT transferred!', receipt.hash);
Send Transaction without Confirmation
For fire-and-forget operations - Returns transaction hash immediately without waiting.- TypeScript
- Unity
- Unreal
Copy
Ask AI
import { BrowserProvider } from 'ethers';
// Get the provider from Passport
const provider = await connectWallet({ auth });
// Wrap provider in ethers BrowserProvider
const browserProvider = new BrowserProvider(provider);
// Get the signer (represents the user's wallet)
const signer = await browserProvider.getSigner();
// Send transaction - returns immediately without waiting
const tx = await signer.sendTransaction({
to: '0xRecipient...',
value: '1500000000000000000', // 1.5 IMX in wei
});
console.log('Transaction sent:', tx.hash);
// User can continue - transaction confirms in background
Fire-and-Forget: Returns immediately without waiting for blockchain confirmation. Use for non-critical operations where you want responsive UI (cosmetic purchases, achievements, analytics).
Copy
Ask AI
using Cysharp.Threading.Tasks;
using Immutable.Passport.Model;
using System.Threading;
async void GetTransactionReceiptStatus()
{
TransactionRequest request = new TransactionRequest()
{
to = address,
value = amount,
data = data
}
string transactionHash = await passport.ZkEvmSendTransaction(request);
string? status = await PollStatus(
passport,
transactionHash,
TimeSpan.FromSeconds(1), // Poll every one second
TimeSpan.FromSeconds(10) // Stop polling after 10 seconds
);
switch (status)
{
case "0x1":
// Successful
break;
case "0x0":
// Failed
break;
}
}
static async UniTask<string?> PollStatus(Passport passport, string transactionHash, TimeSpan pollInterval, TimeSpan timeout)
{
var cancellationTokenSource = new CancellationTokenSource(timeout);
try
{
while (!cancellationTokenSource.Token.IsCancellationRequested)
{
TransactionReceiptResponse response = await passport.ZkEvmGetTransactionReceipt(transactionHash);
if (response.status == null)
{
// The transaction is still being processed, poll for status again
await UniTask.Delay(delayTimeSpan: pollInterval, cancellationToken: cancellationTokenSource.Token);
}
else
{
return response.status;
}
}
}
catch (OperationCanceledException)
{
// Task was canceled due to timeout
}
return null; // Timeout or could not get transaction receipt
}
Copy
Ask AI
// Add to your subsystem:
public:
void SendTransaction(const FString& ToAddress, const FString& ValueInWei, const FString& Data = TEXT("0x"))
{
auto Passport = GetPassport();
if (!Passport)
{
UE_LOG(LogTemp, Error, TEXT("SendTransaction: Passport not available"));
return;
}
FImtblTransactionRequest TransactionRequest;
TransactionRequest.to = ToAddress;
TransactionRequest.value = ValueInWei;
TransactionRequest.data = Data;
Passport->ZkEvmSendTransaction(TransactionRequest, UImmutablePassport::FImtblPassportResponseDelegate::CreateWeakLambda(this, [this](FImmutablePassportResult Result)
{
if (Result.Success)
{
FString TxHash = UImmutablePassport::GetResponseResultAsString(Result.Response);
UE_LOG(LogTemp, Log, TEXT("Transaction Hash: %s"), *TxHash);
}
else
{
UE_LOG(LogTemp, Error, TEXT("Send Transaction Failed: %s"), *Result.Error);
}
}));
}
Important: Don’t update game state until transaction is confirmed. Use this pattern only for non-critical operations or when you have custom polling logic.
Transaction Hash ≠ Success: Obtaining the transaction hash does not guarantee a successful transaction. To determine the transaction’s status, use
ZkEvmGetTransactionReceipt along with the transaction hash received. Follow best practices for client-side polling: set maximum attempts, polling intervals, and implement timeout handling.When to use: Non-critical operations or when you need custom polling/retry logic. Most games should use “with confirmation” variant for critical operations.
NFT Transfer
- TypeScript
- Unity
- Unreal
Copy
Ask AI
const ERC721_ABI = [{
name: 'transferFrom',
type: 'function',
inputs: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'tokenId', type: 'uint256' },
],
outputs: [],
stateMutability: 'nonpayable',
}] as const;
export async function transferNFT(
nftContract: `0x${string}`,
from: `0x${string}`,
to: `0x${string}`,
tokenId: bigint
) {
// highlight-start
const hash = await walletClient.writeContract({
account: from,
address: nftContract,
abi: ERC721_ABI,
functionName: 'transferFrom',
args: [from, to, tokenId],
});
// highlight-end
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log('NFT transferred! Block:', receipt.blockNumber);
return receipt;
}
Copy
Ask AI
public class NFTTransfer : MonoBehaviour
{
public async void Execute()
{
try
{
string receiverAddress = "0x..."; // Receiver's wallet address
string tokenId = "123"; // NFT Token ID
string tokenAddress = "0x..."; // NFT Contract Address
UnsignedTransferRequest transferRequest = UnsignedTransferRequest.ERC721(receiverAddress, tokenId, tokenAddress);
CreateTransferResponseV1 response = await Passport.Instance.ImxTransfer(transferRequest);
Debug.Log($"NFT transferred successfully! Transfer ID: {response.transfer_id}");
}
catch (System.Exception ex)
{
Debug.LogError($"Transfer failed: {ex.Message}");
}
}
}
Copy
Ask AI
// Add to your subsystem:
public:
void ImxTransferNFT(const FString& ReceiverAddress, const FString& TokenId, const FString& TokenAddress)
{
auto Passport = GetPassport();
if (!Passport)
{
UE_LOG(LogTemp, Error, TEXT("ImxTransfer: Passport not available"));
return;
}
FImxTransferRequest ImxTransfer;
ImxTransfer.receiver = ReceiverAddress;
ImxTransfer.type = TEXT("ERC721");
ImxTransfer.tokenId = TokenId;
ImxTransfer.tokenAddress = TokenAddress;
Passport->ImxTransfer(ImxTransfer, UImmutablePassport::FImtblPassportResponseDelegate::CreateWeakLambda(this, [this](FImmutablePassportResult Result)
{
if (Result.Success)
{
UE_LOG(LogTemp, Log, TEXT("IMX NFT Transfer Successful"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("IMX NFT Transfer Failed: %s"), *Result.Error);
}
}));
}
Sign Messages
Personal Sign (ERC-191)
For authentication or simple message signing:- TypeScript
- Unity
- Unreal
Copy
Ask AI
// Assuming you have the Passport provider from connectEvm()
const provider = connectWallet({ auth });
const accounts = await provider.request({ method: 'eth_requestAccounts' });
const address = accounts[0];
const message = 'Hello from Immutable!';
// Sign the message using personal_sign (ERC-191)
const signature = await provider.request({
method: 'personal_sign',
params: [message, address],
});
console.log('Signature:', signature);
Unity SDK Limitation: Unity SDK does not support ERC-191 personal sign. Only EIP-712 typed data signing is supported via
ZkEvmSignTypedDataV4(). See the Typed Data section below for message signing in Unity.Unreal SDK Limitation: Unreal SDK does not support ERC-191 personal sign. Only EIP-712 typed data signing is supported via
ZkEvmSignTypedDataV4(). See the Typed Data section below for message signing in Unreal.Typed Data (EIP-712)
For structured data signing (used by protocols like Seaport):- TypeScript
- Unity
- Unreal
Copy
Ask AI
// Assuming you have the Passport provider from connectEvm()
const provider = connectWallet({ auth });
const accounts = await provider.request({ method: 'eth_requestAccounts' });
const address = accounts[0];
// Get chain ID
const chainIdHex = await provider.request({ method: 'eth_chainId' });
const chainId = parseInt(chainIdHex, 16);
// Define EIP-712 typed data structure
const typedData = {
domain: {
name: 'My Game',
version: '1',
chainId,
verifyingContract: address, // Your contract address
},
message: {
itemId: 123,
price: '1000000000000000000', // 1 token in wei
},
primaryType: 'Trade',
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Trade: [
{ name: 'itemId', type: 'uint256' },
{ name: 'price', type: 'uint256' },
],
},
};
// Sign typed data using eth_signTypedData_v4
const signature = await provider.request({
method: 'eth_signTypedData_v4',
params: [address, JSON.stringify(typedData)],
});
console.log('Typed data signature:', signature);
Copy
Ask AI
// Construct EIP-712 typed data as JSON string
string typedDataJson = @"{
""domain"": {
""name"": ""My Game"",
""version"": ""1"",
""chainId"": 13473,
""verifyingContract"": ""0xYourContract...""
},
""types"": {
""Trade"": [
{ ""name"": ""itemId"", ""type"": ""uint256"" },
{ ""name"": ""price"", ""type"": ""uint256"" }
]
},
""primaryType"": ""Trade"",
""message"": {
""itemId"": 123,
""price"": ""10000000000000000000""
}
}";
var signature = await passport.ZkEvmSignTypedDataV4(typedDataJson);
Debug.Log($"Signature: {signature}");
Copy
Ask AI
// Add to your subsystem:
public:
void SignTypedData()
{
auto Passport = GetPassport();
if (!Passport)
{
return;
}
FString TypedDataJson = TEXT(R"({
"domain": {
"name": "My Game",
"version": "1",
"chainId": 13473,
"verifyingContract": "0xYourContract..."
},
"types": {
"Trade": [
{ "name": "itemId", "type": "uint256" },
{ "name": "price", "type": "uint256" }
]
},
"primaryType": "Trade",
"message": {
"itemId": "123",
"price": "10000000000000000000"
}
})");
Passport->ZkEvmSignTypedDataV4(TypedDataJson, UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UPassportQuickStartSubsystem::OnTypedDataSigned));
}
private:
void OnTypedDataSigned(FImmutablePassportResult Result)
{
if (!Result.Success)
{
UE_LOG(LogTemp, Error, TEXT("Sign Typed Data Failed: %s"), *Result.Error);
return;
}
FString Signature = UImmutablePassport::GetResponseResultAsString(Result.Response);
UE_LOG(LogTemp, Log, TEXT("Signature: %s"), *Signature);
}
// Call when user needs to sign typed data:
SignTypedData();
Error Handling
- TypeScript
- Unity
- Unreal
Copy
Ask AI
// Assuming you have the Passport provider from connectEvm()
const provider = connectWallet({ auth });
async function sendTransactionWithErrorHandling() {
try {
const accounts = await provider.request({ method: 'eth_requestAccounts' });
const fromAddress = accounts[0];
const hash = await provider.request({
method: 'eth_sendTransaction',
params: [{
from: fromAddress,
to: '0xRecipient...',
value: '1000000000000000000', // 1 IMX in wei
}],
});
console.log('Transaction sent:', hash);
return { success: true, hash };
} catch (error) {
// Handle different error cases
if (error instanceof Error) {
if (error.message.includes('User rejected')) {
console.log('User rejected the transaction');
return { success: false, error: 'User rejected' };
}
if (error.message.includes('insufficient funds')) {
console.error('Insufficient balance');
return { success: false, error: 'Insufficient funds' };
}
console.error('Transaction error:', error.message);
return { success: false, error: error.message };
}
throw error;
}
}
Copy
Ask AI
try
{
var response = await passport.ZkEvmSendTransactionWithConfirmation(request);
Debug.Log($"Success: {response.transactionHash}");
}
catch (Exception e)
{
Debug.LogError($"Transaction failed: {e.Message}");
}
Copy
Ask AI
// Add to your subsystem:
public:
void SendTransactionWithErrorHandling(const FString& ToAddress, const FString& Data)
{
auto Passport = GetPassport();
if (!Passport)
{
UE_LOG(LogTemp, Error, TEXT("Failed to get Passport instance"));
return;
}
FImtblTransactionRequest Request;
Request.to = ToAddress;
Request.data = Data;
Request.value = TEXT("0");
Passport->ZkEvmSendTransactionWithConfirmation(Request, UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UPassportQuickStartSubsystem::OnTransactionComplete));
}
private:
void OnTransactionComplete(FImmutablePassportResult Result)
{
if (!Result.Success)
{
// Handle different error cases
if (Result.Error.Contains(TEXT("User rejected")))
{
UE_LOG(LogTemp, Warning, TEXT("User rejected the transaction"));
}
else if (Result.Error.Contains(TEXT("insufficient funds")))
{
UE_LOG(LogTemp, Error, TEXT("Insufficient balance"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("Transaction error: %s"), *Result.Error);
}
return;
}
TOptional<FZkEvmTransactionReceipt> Receipt = JsonObjectToUStruct<FZkEvmTransactionReceipt>(Result.Response.JsonObject);
if (!Receipt.IsSet())
{
UE_LOG(LogTemp, Error, TEXT("Failed to parse transaction receipt"));
return;
}
UE_LOG(LogTemp, Log, TEXT("Transaction Hash: %s"), *Receipt->hash);
UE_LOG(LogTemp, Log, TEXT("Status: %s"), *Receipt->status);
}
Next Steps
Gas Sponsorship
Gas sponsorship and transaction costs
Pre-Approved Transactions
Instant transactions without popups (Unity/Unreal)
Immutable Play
Where users manage their wallet
Architecture
Understand the security model
Minting NFTs
Mint NFTs to user wallets
Operator Allowlist
Contract allowlisting and verification