Using Smart Wallets
To use smart wallets, you could use the EIP1193 provider or use a backend. This guide teaches how to make different requests to the smart wallet with the backend and Unity (it's easier to do so in the context of gaming because no need to encode transactions with Unity).
Methods
SendSignatureTransactionIntentRequest
Sign and send a transaction intent that was created by your backend.
Method Signature:public async UniTask<TransactionIntentResponse> SendSignatureTransactionIntentRequest(SignatureTransactionIntentRequest request)
SignatureTransactionIntentRequest request
- Transaction intent request with ID and user operation hash
UniTask<TransactionIntentResponse>
- Transaction response with transaction hash
public class SignatureTransactionIntentRequest
{
public string TransactionIntentId { get; set; }
public string UserOperationHash { get; set; }
public string Signature { get; set; } // Optional - SDK generates if null
public bool Optimistic { get; set; } // Optional - default false
}
using System;
using UnityEngine;
using UnityEngine.Networking;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using Openfort.OpenfortSDK;
using Openfort.OpenfortSDK.Model;
public class SmartWalletTransactions : MonoBehaviour
{
private OpenfortSDK openfort;
private string backendUrl = "https://your-backend.com/api";
private string authToken; // Your user's auth token
private async void Start()
{
openfort = await OpenfortSDK.Init(
"YOUR_OPENFORT_PUBLISHABLE_KEY",
"YOUR_SHIELD_PUBLISHABLE_KEY"
);
}
public async UniTask<TransactionIntentResponse> ExecuteTransaction(string transactionType, object parameters = null)
{
try
{
// Step 1: Create transaction intent via backend
var backendResponse = await CreateTransactionIntent(transactionType, parameters);
if (backendResponse?.NextAction == null)
{
Debug.Log("Transaction completed without signature required");
return backendResponse;
}
// Step 2: Sign the transaction with embedded wallet
return await SignAndSendTransaction(backendResponse);
}
catch (Exception e)
{
Debug.LogError(quot;Transaction failed: {e.Message}");
throw;
}
}
private async UniTask<TransactionIntentResponse> CreateTransactionIntent(string transactionType, object parameters)
{
var requestData = new
{
type = transactionType,
parameters = parameters
};
string jsonData = JsonConvert.SerializeObject(requestData);
using var webRequest = UnityWebRequest.Post(quot;{backendUrl}/transaction-intent", jsonData);
webRequest.SetRequestHeader("Authorization", quot;Bearer {authToken}");
webRequest.SetRequestHeader("Content-Type", "application/json");
await webRequest.SendWebRequest();
if (webRequest.result != UnityWebRequest.Result.Success)
{
throw new Exception(quot;Backend request failed: {webRequest.error}");
}
var responseText = webRequest.downloadHandler.text;
return JsonConvert.DeserializeObject<TransactionIntentResponse>(responseText);
}
private async UniTask<TransactionIntentResponse> SignAndSendTransaction(TransactionIntentResponse backendResponse)
{
// Ensure wallet is ready
var state = await openfort.GetEmbeddedState();
if (state != EmbeddedState.READY)
{
throw new InvalidOperationException(quot;Wallet not ready. State: {state}");
}
// Create signature request
var signatureRequest = new SignatureTransactionIntentRequest
{
TransactionIntentId = backendResponse.Id,
UserOperationHash = backendResponse.NextAction.Payload.UserOpHash,
Signature = null, // SDK will generate
Optimistic = true
};
// Sign and send
var result = await openfort.SendSignatureTransactionIntentRequest(signatureRequest);
Debug.Log(quot;Transaction executed successfully: {result.Id}");
if (result.Response?.TransactionHash != null)
{
Debug.Log(quot;Transaction hash: {result.Response.TransactionHash}");
}
return result;
}
}
// Response models
[Serializable]
public class TransactionIntentResponse
{
public string Id { get; set; }
public NextAction NextAction { get; set; }
public TransactionResponse Response { get; set; }
}
[Serializable]
public class NextAction
{
public string Type { get; set; }
public NextActionPayload Payload { get; set; }
}
[Serializable]
public class NextActionPayload
{
public string UserOpHash { get; set; }
}
[Serializable]
public class TransactionResponse
{
public string TransactionHash { get; set; }
}
Transaction Types
Contract Function Calls
Execute smart contract functions like minting, transferring tokens, or calling custom contract methods.
Backend Implementation Example:// Node.js backend example
app.post('/api/transaction-intent', async (req, res) => {
const { type, parameters } = req.body;
if (type === 'simple') {
const transactionIntent = await openfort.transactionIntents.create({
player: req.user.playerId,
policy: "pol_...", // Your gas policy
chainId: 80002, // Polygon Amoy
optimistic: true,
interactions: [{
contract: parameters.contractId,
functionName: parameters.functionName,
functionArgs: parameters.functionArgs
}]
});
res.json(transactionIntent);
}
});
// Mint an NFT
var mintParams = new {
contractId = "con_...",
functionName = "mint",
functionArgs = new[] { playerAddress, tokenId }
};
await ExecuteTransaction("simple", mintParams);
Send Native Tokens
Transfer native blockchain tokens to other addresses or players.
Backend Implementation Example:// Node.js backend example
app.post('/api/transaction-intent', async (req, res) => {
const { type, parameters } = req.body;
if (type === 'native') {
const transactionIntent = await openfort.transactionIntents.create({
player: req.user.playerId,
policy: "pol_...", // Must have account_functions rule
chainId: 80002,
optimistic: true,
interactions: [{
to: parameters.recipient, // Address, player ID, or account ID
value: parameters.amount // In wei (string)
}]
});
res.json(transactionIntent);
}
});
// Send 0.1 MATIC (in wei)
var transferParams = new {
recipient = "0x742d35Cc6Dd62c4532aD096a421c2c80b0B8aAdA",
amount = "100000000000000000" // 0.1 MATIC in wei
};
await ExecuteTransaction("native", transferParams);
Execute Multiple Operations
Combine multiple contract calls or transfers into a single atomic transaction. If any operation fails, the entire batch reverts.
Benefits:- Users only wait for one transaction
- Reduced gas costs
- Atomic execution (all or nothing)
- Maximum 9 interactions per transaction intent
- All interactions must succeed for the batch to complete
// Node.js backend example
app.post('/api/transaction-intent', async (req, res) => {
const { type, parameters } = req.body;
if (type === 'batch') {
const transactionIntent = await openfort.transactionIntents.create({
player: req.user.playerId,
policy: "pol_...",
chainId: 80002,
optimistic: true,
interactions: [
{
// First: Approve token spending
contract: parameters.tokenContract,
functionName: "approve",
functionArgs: [parameters.spenderAddress, parameters.approveAmount]
},
{
// Second: Execute the main function
contract: parameters.mainContract,
functionName: parameters.mainFunction,
functionArgs: parameters.mainArgs
}
]
});
res.json(transactionIntent);
}
});
// Approve and stake tokens in one transaction
var batchParams = new {
tokenContract = "con_token123",
spenderAddress = "0x...",
approveAmount = "1000000000000000000", // 1 token
mainContract = "con_staking456",
mainFunction = "stake",
mainArgs = new[] { "1000000000000000000" }
};
await ExecuteTransaction("batch", batchParams);
Complete Example
Here's a comprehensive example that handles different transaction types:
using System;
using UnityEngine;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;
using Openfort.OpenfortSDK;
using Openfort.OpenfortSDK.Model;
public class SmartWalletDemo : MonoBehaviour
{
[Header("UI References")]
[SerializeField] private Button mintButton;
[SerializeField] private Button transferButton;
[SerializeField] private Button batchButton;
[SerializeField] private TMPro.TextMeshProUGUI statusText;
private SmartWalletTransactions transactionManager;
private async void Start()
{
transactionManager = GetComponent<SmartWalletTransactions>();
mintButton.onClick.AddListener(() => MintNFT().Forget());
transferButton.onClick.AddListener(() => TransferTokens().Forget());
batchButton.onClick.AddListener(() => BatchTransaction().Forget());
}
private async UniTaskVoid MintNFT()
{
statusText.text = "Minting NFT...";
try
{
var mintParams = new {
contractId = "con_nft_contract",
functionName = "mint",
functionArgs = new[] { "player_address" }
};
var result = await transactionManager.ExecuteTransaction("simple", mintParams);
statusText.text = quot;NFT minted! TX: {result.Response?.TransactionHash}";
}
catch (Exception e)
{
statusText.text = quot;Mint failed: {e.Message}";
}
}
private async UniTaskVoid TransferTokens()
{
statusText.text = "Transferring tokens...";
try
{
var transferParams = new {
recipient = "0x742d35Cc6Dd62c4532aD096a421c2c80b0B8aAdA",
amount = "50000000000000000" // 0.05 MATIC
};
var result = await transactionManager.ExecuteTransaction("native", transferParams);
statusText.text = quot;Transfer complete! TX: {result.Response?.TransactionHash}";
}
catch (Exception e)
{
statusText.text = quot;Transfer failed: {e.Message}";
}
}
private async UniTaskVoid BatchTransaction()
{
statusText.text = "Executing batch transaction...";
try
{
var batchParams = new {
operations = new[] {
new { contract = "con_token", function = "approve", args = new[] { "spender", "1000" } },
new { contract = "con_dex", function = "swap", args = new[] { "1000" } }
}
};
var result = await transactionManager.ExecuteTransaction("batch", batchParams);
statusText.text = quot;Batch complete! TX: {result.Response?.TransactionHash}";
}
catch (Exception e)
{
statusText.text = quot;Batch failed: {e.Message}";
}
}
}
Examples
Unity Sample Android
An integration with Google Play Games using Firebase Auth as a third party auth provider to create a non-custodial embedded wallet.
Unity Sample WebGL
An integration with Openfort Auth with non-custodial embedded wallet.