Mavis ID Unity SDK
Overview
The Mavis ID Unity SDK lets developers integrate Mavis ID into Unity games deployed to mobile platforms (Android and iOS) and desktop platforms (Windows and macOS). After the integration, users can sign in to your game with Mavis ID and set up a Web3 wallet to interact with the blockchain to send and receive tokens, sign messages, and more.
Mobile games use the Mavis ID SDK to interact with the Mavis ID service through a native WebView. Desktop games distributed through Mavis Hub use the Mavis ID SDK to interact with the Mavis ID service through the Mavis Hub client.
Whenever implementation differs between platforms, this guide specifies the platform-specific steps.
Features
- Authorize users: sign in to your app with Mavis ID.
- Send transactions: transfer tokens to other addresses.
- Sign messages: sign plain text messages.
- Sign typed data: sign structured data according to the EIP-712 standard.
- Call contracts: execute custom transactions on smart contracts.
Prerequisites
Unity version:
Desktop requirements:
- A game uploaded through Mavis Console.
- .Net v3.1 or later.
Mobile requirements:
- An app created in the Developer Console.
- Permission to use Mavis ID. Request in Developer Console > your app > App Permission > Sky Mavis Account (OAuth 2.0) > Request Access.
- A client ID that you can find in Developer Console > Products > ID Service > CLIENT ID (APPLICATION ID).
- A redirect URI registered in Developer Console > Products > ID Service > REDIRECT URI.
- To deploy to Android, Android API level 24 or later.
- To deploy to iOS, iOS 13.0 or later.
For more information, see Get started.
Quick start
A sample Unity project is available in the Mavis ID Unity SDK repository. To set up the project, follow these steps:
-
Clone the repository:
git clone https://github.com/skymavis/mavis-id-unity.git
-
Open Unity Hub and add the cloned repository as a new project.
-
In Build Settings, click Switch Platform to select the platform you want to deploy to.
-
Click Build and Run and open the app on the selected platform.
For more information, see the official Unity documentation.
Steps
Step 1. Install the SDK
Sky Mavis distributes the Mavis ID Unity SDK as a .unitypackage
file on our GitHub repository. To install the SDK, follow these steps:
-
Download the latest
ID.unitypackage
release from the repository. -
Import the
ID.unitypackage
file by selecting the Unity menu option Assets > Import package > Custom Package and importingID.unitypackage
. -
In Unity Editor, go to Build Settings, then choose the platform you are deploying, then click Switch Platform.
Next, configure the platform-specific settings.
- Android
- iOS
- Desktop (Mavis Hub)
-
Go to Project Settings > Player > Settings for Android > Publishing Settings and select the following:
- Custom Main Manifest
- Custom Main Gradle Template
- Custom Gradle Properties Template
-
In the
Assets/Plugins/Android
directory of your Unity app, add required dependencies to themainTemplate.gradle
file:dependencies {
implementation ("androidx.browser:browser:1.8.0")
}Your
mainTemplate.gradle
file should look like this:apply plugin: 'com.android.library'
**APPLY_PLUGINS**
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation ("androidx.browser:browser:1.8.0")
**DEPS**}Gradle automatically downloads the required dependencies when building the app.
-
Instruct Gradle to use the AndroidX libraries by adding the following line to your
gradleTemplate.properties
file:android.useAndroidX=true
Your
gradleTemplate.properties
file should look like this:org-gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M
org-gradle-parallel=true
unityStreamingAssets=**STREAMING_ASSETS**
android.useAndroidX=true
**ADDITIONAL_PROPERTIES**
- Go to the
Assets/Plugins/AxieInfinity/MPC/Libs/iOS
directory of your Unity app. - Select the
id.framework
file. - In Platform Settings > iOS, select Add to Embedded Binaries.
![](/assets/images/ios-settings-3f1324c0334e6ad16e284b83c914a7e5.png)
No configuration is required for desktop games distributed through Mavis Hub.
Step 2. Initialize the SDK
Have your app initialize Mavis ID by calling Init
once, ideally at app launch.
- Android
- iOS
- Desktop (Mavis Hub)
void Init(string appId, string deeplinkSchema, bool isTestnet = false)
Parameters:
appId
: the client ID registered in the Mavis ID settings in the Developer Console.deeplinkSchema
: the redirect URI registered in the Mavis ID settings in the Developer Console.isTestnet
: set totrue
to use the Saigon testnet orfalse
to use the Ronin mainnet.
Example:
void Start()
{
string appId = "${YOUR_CLIENT_ID}";
string deeplinkSchema = "mydapp";
SM.MavisId.Init(AppId, deeplinkSchema, isTestnet: false);
}
void Init(string appId, string deeplinkSchema, bool isTestnet = false)
Parameters:
appId
: the client ID registered in the Mavis ID settings in the Developer Console.deeplinkSchema
: the redirect URI registered in the Mavis ID settings in the Developer Console.isTestnet
: set totrue
to use the Saigon testnet orfalse
to use the Ronin mainnet.
Example:
void Start()
{
string appId = "${YOUR_CLIENT_ID}";
string deeplinkSchema = "mydapp";
SM.MavisId.Init(AppId, deeplinkSchema, isTestnet: false);
}
void Init(string sessionId, string port)
Parameters:
sessionId
: a unique session identifier used to manage the connection between the game client and Mavis Hub.port
: the port number used to establish a connection between the game client and Mavis Hub.
Example:
void Start()
{
string sessionId = "${SESSION_ID}";
int port = "${PORT_NUMBER}";
SM.MavisId.Init(sessionId, port);
}
Step 3. Authorize users
- Android
- iOS
- Desktop (Mavis Hub)
Authorizes a user with an existing Mavis ID account, returning an ID token and the user's wallet address as part of an authorization response. If the user doesn't have an account, they're prompted to create one.
static string OnAuthorize()
Example:
public async void OnAuthorizeClicked()
{
_responseId = SM.MavisId.OnAuthorize();
string responseData = await WaitForMavisIdResponse(_responseId);
Debug.Log(responseData);
}
Authorizes a user with an existing Mavis ID account, returning an ID token and the user's wallet address as part of an authorization response. If the user doesn't have an account, they're prompted to create one.
static string OnAuthorize()
Example:
public async void OnAuthorizeClicked()
{
_responseId = SM.MavisId.OnAuthorize();
string responseData = await WaitForMavisIdResponse(_responseId);
Debug.Log(responseData);
}
Desktop games distributed through Mavis Hub don't need to implement user authorization. Mavis Hub handles user authorization through its own session after the user signs in to Mavis Hub using Mavis ID.
Step 4. Validate the ID token
After receiving the ID token, validate it to ensure that the user has successfully authenticated. Then you can issue an access token to allow access to your server's resources. For more information, see Validate ID tokens.
Step 5. Interact with the wallet
Send transactions
Transfers RON tokens to a recipient's address, returning a transaction hash as part of a transaction response.
string SendTransaction(string receiverAddress, string value)
Parameters:
receiverAddress
: the recipient's address.value
: the amount of RON to send, specified in wei (1 RON = 10^18 wei).
Example: transfer 0.1 RON to the recipient address.
public async void OnSendTransactionClicked()
{
string receiverAddress = "0xD36deD8E1927dCDD76Bfe0CC95a5C1D65c0a807a";
string value = "100000000000000000";
_responseId = SM.MavisId.SendTransaction(receiverAddress, value);
string responseData = await WaitForMavisIdResponse(_responseId);
Debug.Log(responseData);
}
Sign messages
Signs a plain text message, returning a signature in hex format as part of a transaction response.
static string OnPersonalSign(string message)
Parameters:
message
: the message to sign.
Example: sign the message accepting the terms and conditions.
public async void OnPersonalSignClicked()
{
// Message to sign
string message = "I accept the terms and conditions.";
// Sign the message using the Mavis ID Unity SDK
_responseId = SM.MavisId.OnPersonalSign(message);
// Wait for the response
string responseData = await WaitForMavisIdResponse(_responseId);
Debug.Log(responseData);
}
Sign typed data
Signs typed data structured according to the EIP-712 standard, returning a signature in hex format as part of a transaction response.
string OnSignTypeData(string typedData)
Parameters:
typedData
: a JSON string that specifies the EIP-712 typed structured data to be signed by the user.
Example: sign typed data for an order on Axie Marketplace.
public void OnSignTypedDataClicked()
// Typed data to sign
{
string typedData = @"{""types"":{""Asset"":[{""name"":""erc"",""type"":""uint8""},{""name"":""addr"",""type"":""address""},{""name"":""id"",""type"":""uint256""},{""name"":""quantity"",""type"":""uint256""}],""Order"":[{""name"":""maker"",""type"":""address""},{""name"":""kind"",""type"":""uint8""},{""name"":""assets"",""type"":""Asset[]""},{""name"":""expiredAt"",""type"":""uint256""},{""name"":""paymentToken"",""type"":""address""},{""name"":""startedAt"",""type"":""uint256""},{""name"":""basePrice"",""type"":""uint256""},{""name"":""endedAt"",""type"":""uint256""},{""name"":""endedPrice"",""type"":""uint256""},{""name"":""expectedState"",""type"":""uint256""},{""name"":""nonce"",""type"":""uint256""},{""name"":""marketFeePercentage"",""type"":""uint256""}],""EIP712Domain"":[{""name"":""name"",""type"":""string""},{""name"":""version"",""type"":""string""},{""name"":""chainId"",""type"":""uint256""},{""name"":""verifyingContract"",""type"":""address""}]}, ""domain"":{""name"":""MarketGateway"",""version"":""1"",""chainId"":2021,""verifyingContract"":""0xfff9ce5f71ca6178d3beecedb61e7eff1602950e""},""primaryType"":""Order"",""message"":{""maker"":""0xd761024b4ef3336becd6e802884d0b986c29b35a"",""kind"":""1"",""assets"":[{""erc"":""1"",""addr"":""0x32950db2a7164ae833121501c797d79e7b79d74c"",""id"":""2730069"",""quantity"":""0""}],""expiredAt"":""1721709637"",""paymentToken"":""0xc99a6a985ed2cac1ef41640596c5a5f9f4e19ef5"",""startedAt"":""1705984837"",""basePrice"":""500000000000000000"",""endedAt"":""0"",""endedPrice"":""0"",""expectedState"":""0"",""nonce"":""0"",""marketFeePercentage"":""425""}}";
_responseId = SM.MavisId.OnSignTypeData(typedData);
string responseData = await WaitForMavisIdResponse(_responseId);
Debug.Log(responseData);
}
Call contracts
Executes a custom transaction on a smart contract, returning a transaction hash as part of a transaction response.
string OnCallContract(string contractAddress, string data, string value = "0x0")
Parameters:
contractAddress
: the address of the smart contract on which to execute the transaction.data
: the transaction data to send to the smart contract, encoded as a hex string. For help with encoding, use theEncodeFunctionData
utility.value
: the amount of RON in wei (1 RON = 10^18 wei) to send along with the transaction. For non-payable smart contracts, the value is0x0
.
Example: allow another contract to spend 1 AXS on user's behalf.
public async void OnApproveErc20Clicked()
{
// Contract address
string contractAddress = "0x3c4e17b9056272ce1b49f6900d8cfd6171a1869d";
// Readable ABI string for the function
string readableAbi = "function approve(address _spender, uint256 _value)";
// Approve 1 AXS
var approveParams = new { _spender = "0x6B190089ed7F75Fe17B3b0A17F6ebd69f72c3F63", _value = 1000000000000000000 };
try
{
var data = ABI.EncodeFunctionData(readableAbi, approveParams);
Debug.Log("Approve data: " + data);
_responseId = SM.MavisId.OnCallContract(contractAddress, data);
string responseData = await WaitForMavisIdResponse(_responseId);
Debug.Log(responseData);
}
catch (System.Exception e)
{
Debug.Log("Error in call contract: " + e.Message);
}
}
Utilities
The SM.ID.Utils
namespace provides utility functions that make wallet interactions easier.
Encode function data
The EncodeFunctionData
utility encodes function data for smart contract interactions.
string EncodeFunctionData(string ABI, object values)
Parameters:
ABI
: a readable ABI string that defines the function to call on the smart contract.values
: an array of values to pass as parameters to the smart contract function specified in the ABI.
The following sections provide some examples of how to use utility.
Swap tokens on Katana
Sends a transaction to the Katana decentralized exchange to swap RON for AXS.
using SM.ID.Utils;
public async void OnSwapRonToAxsClicked()
{
// Contract addresses
string katanaAddress = "0xDa44546C0715ae78D454fE8B84f0235081584Fe0";
string ronAddress = "0xa959726154953bae111746e265e6d754f48570e6";
string axsAddress = "0x3c4e17b9056272ce1b49f6900d8cfd6171a1869d";
// Wallet address
string walletAddress = "0x6B190089ed7F75Fe17B3b0A17F6ebd69f72c3F63";
// Readable ABI string for the function
string readableAbi = "function swapExactRONForTokens(uint256 _amountOutMin, address[] _path, address _to, uint256 _deadline)";
// Values to pass as parameters to the called function
var value = "1000000000000000000";
var swapParams = new
{
// 0.1 RON
_amountOutMin = "0",
_path = new string[] { ronAddress, axsAddress },
_to = walletAddress,
_deadline = "1814031305"
};
try
{
// Encode the function data using the EncodeFunctionData utility in SM.ID.Utils
var data = ABI.EncodeFunctionData(readableAbi, swapParams);
// Send the transaction to the Katana contract using SM.MavisId namespace
_responseId = SM.MavisId.OnCallContract(katanaAddress, data, value);
// Wait for the response
string responseData = await WaitForMavisIdResponse(_responseId);
Debug.Log(responseData);
}
catch (System.Exception e)
{
Debug.Log("Error in call contract: " + e.Message);
}
}
Activate Atia's Blessing
Sends a transaction to the Atia Shrine smart contract to activate the "Atia's Blessing" feature in Axie Infinity games.
using SM.ID.Utils;
public async void onAtiaBlessingClicked()
{
// Wallet address
string walletAddress = "0x6B190089ed7F75Fe17B3b0A17F6ebd69f72c3F63";
// Atia Shrine contract address
string atiaShrineContractAddress = "0xd5c5afefad9ea288acbaaebeacec5225dd3d6d2b";
// Readable ABI string for the function
string readableAbi = "function activateStreak(address to)";
Debug.Log(readableAbi);
// Values to pass as parameters to the called function
var values = new string[] { walletAddress };
try
{
// Encode the function data using the EncodeFunctionData utility in SM.ID.Utils
var data = ABI.EncodeFunctionData(readableAbi, values);
// Send the transaction to the Atia Shrine contract using SM.MavisId namespace
_responseId = SM.ID.OnCallContract(atiaShrineContractAddress, data);
// Wait for the response
string responseData = await WaitForMavisIdResponse(_responseId);
Debug.Log(responseData);
}
catch (System.Exception e)
{
Debug.Log("Error in Atia's blessing: " + e.Message);
}
}
Read smart contract data
The Skynet
class provides methods to read data from smart contracts using the Skynet REST API.
Initialize Skynet
class Skynet(ApiKey, ownerAddress, NftContractAddresses, ERC20ContractAddresses);
Parameters:
ApiKey
: your app's API key from the Developer Console.ownerAddress
: the user's MPC wallet address.NftContractAddresses
: an array of contract addresses for ERC721 tokens.ERC20ContractAddresses
: an array of contract addresses for ERC20 tokens.
Example:
var SkynetAPI = "${SKYNET_API_KEY}";
var ownerAddress = "${RONIN_WALLET_ADDRESS}";
var NftContractAddresses = new string[] { Constants.Mainnet.ERC721.AxieContractAddress };
var ERC20ContractAddresses = new string[] { Constants.Mainnet.ERC20.AXSContractAddress, Constants.Mainnet.ERC20.SLPContractAddress, Constants.Mainnet.ERC20.WRONContractAddress, Constants.Mainnet.ERC20.WETHContractAddress };
var skynet = Skynet(SkynetAPI, ownerAddress, NftContractAddresses, ERC20ContractAddresses);
Get total NFTs of address
Fetches the total count of NFTs defined in NftContractAddresses
for the specified ownerAddress
.
async Task<object> GetTotalNFTs()
Returns:
{
items: object[] {
createdAtBlock: string,
createdAtBlockTime: integer,
ownerAddresses: string,
rawMetadata: array,
tokenId: integer,
tokenName: string,
tokenStandard: string,
tokenSymbol: string,
tokenURI: string,
updatedAtBlock: integer,
updatedAtBlockTime: integer
},
paging: object {
total: integer
}
}
Example:
// Initialize Skynet
var skynet = InitializeSkynet();
// Get the total NFTs of the owner address
var response = await skynet.GetTotalNFTs();
Debug.Log("Result: Total NFTs of address: " + response);
Get NFT metadata by token ID
Fetches metadata for an NFT using its token ID within the specified NftContractAddresses
and ownerAddress
.
async Task<object> GetNFTsMetadata(string[] tokenIds)
Parameters:
tokenIds
: an array of token IDs for which to retrieve metadata.
Example:
// Initialize Skynet
var skynet = InitializeSkynet();
// Get the NFT metadata by token ID
var tokenIds = new string[] {${NFT_TOKEN_ID}};
var response = await skynet.GetNFTsMetadata(tokenIds);
Debug.Log("Result: NFT metadata with tokenIds: " + response);
Get total ERC20 balances of address
Fetches the total balance of ERC20 tokens defined in Erc20ContractAddresses
for the specified ownerAddress
.
async Task<object> GetERC20TokenBalances()
Returns:
items: object[] {
balance: string
contractAddress: string
decimals: integer
ownerAddress: string
tokenId: string
tokenName: string
tokenStandard: string
tokenSymbol: string
},
paging : object {
total : integer
}
Example:
// Initialize Skynet
var skynet = InitializeSkynet();
// Get the total ERC20 balances of the owner address
var response = await skynet.GetERC20TokenBalances();
var result = await response.Content.ReadAsStringAsync();
Debug.Log("Result: Total token balances of owner address: " + result);
Make RPC calls
Executes an RPC call to the blockchain.
Task<string> CallRPC(string contractAddress, string data)
Parameters:
contractAddress
: the address of the smart contract to call.data
: the data to send to the smart contract, encoded as a hex string.
Example:
using SM.ID.Utils;
// Initialize Skynet
var skynet = InitializeSkynet();
// ABI for the allowance function
var allowanceOfABI = @"{""constant"":true,""inputs"":[{""internalType"":""address"",""name"":""_owner"",""type"":""address""},{""internalType"":""address"",""name"":""_spender"",""type"":""address""}],""name"":""allowance"",""outputs"":[{""internalType"":""uint256"",""name"":""_value"",""type"":""uint256""}],""payable"":false,""stateMutability"":""view"",""type"":""function""}";
// Assign values for the ABI parameters
var args = new object[] { "0x2d62c27ce2e9e66bb8a667ce1b60f7cb02fa9810", Constants.Mainnet.KatanaAddress };
// Encode the function data using the EncodeFunctionData utility in SM.ID.Utils
var data = ABI.EncodeFunctionData(allowanceOfABI, args);
// Execute the RPC call to check how many AXS tokens the user has allowed the Katana contract to spend
var result = await skynet.CallRPC(Constants.Mainnet.ERC20.AXSContractAddress, data);
// Process the result: remove the "0x" prefix, parse the hex string to a BigInteger, and format the value by dividing by 10^18 to get the Ether balance
result = result.StartsWith("0x") ? result.Substring(2) : result;
BigInteger weiValue = BigInteger.Parse(result, System.Globalization.NumberStyles.HexNumber);
BigInteger divisor = BigInteger.Pow(10, 18);
decimal formatedValue = (decimal)weiValue / (decimal)divisor;
Debug.Log("Formatted Ether balance: " + formatedValue);
Reference
Response types
Handling responses
To handle responses from Mavis ID, define a callback method before sending a request.
public async Task<string> WaitForMavisIdResponse(string id)
{
string responseData = null;
string currentId = id;
void dataCallback(string state, string data) { if (currentId == state) responseData = data; }
SM.MavisId.BindOnResponse(dataCallback);
while (string.IsNullOrEmpty(responseData) && currentId == _responseId) await Task.Yield();
SM.MavisId.UnBindOnResponse(dataCallback);
return responseData;
}
Authorization response
The authorization response is returned after a user signs in to the app with Mavis ID.
Successful authorization
mydapp://callback?state=2ab49965-249a-48c7-896e-90967778383b&method=auth&version=1.0&type=success&data=ey...&address=0x16...ac
Response parameters:
state
: a unique random identifier used to manage requests from the client to Mavis ID.method
: the method used for the request (auth
).version
: the version of Mavis ID.type
: the response type (success
).data
: the ID token that contains encoded user information.address
: the user's MPC wallet address.
Authorization error
mydapp://callback?state=84d18549-df10-451f-8235-184b594e3706&method=auth&version=1.0&type=fail&code=1000&message=%5BWALLET_USER_CANCEL%5D+User+rejected
Response parameters:
type
: the response type (fail
).code
: the error code.message
: a message describing the error. For more information, see Error codes.
Transaction response
The transaction response is returned after a user sends a transaction, signs a message, signs typed data, or calls a contract.
Successful transaction
mydapp://callback?state=0bccde82-01d5-4403-90fd-f8edcfdd2ed4&method=send&version=1.0&type=success&data=0x3a...0d
Response parameters:
state
: a unique random identifier used to manage requests from the client to Mavis ID.method
: the method used for the request (send
for transactions andsign
for signing).version
: the version of Mavis ID.type
: the response type (success
).data
: the transaction hash.
Transaction error
mydapp://callback?state=84d18549-df10-451f-8235-184b594e3706&method=send&version=1.0&type=fail&code=1000&message=%5BWALLET_USER_CANCEL%5D+User+rejected
Response parameters:
type
: the response type (fail
).code
: the error code.message
: a message describing the error.
FAQ
Where can I find the client ID?
To find your app's client ID, open the Developer Console, then click ID Service, and then in the Client credentials section, locate the CLIENT ID (APPLICATION ID) field.