npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

xyz.dynamic.unitysdk

v2.0.1

Published

Dynamic authentication and embedded wallet SDK for Unity

Readme

Dynamic SDK for Unity

The Dynamic SDK for Unity provides authentication, wallet management, and blockchain transaction support for Unity games and applications. It supports EVM, Solana, and SUI chains through an embedded WebView-based architecture.

Platform Requirements

  • Unity 6000.0+ (Unity 6) and newer

Supported platforms

  • Since this SDK is built on top of the UniWebView, all the platforms that plugin supports are supported by Dynamic SDK, meaning macOS (including Editor), Android and iOS. You can develop your applicaiton in Linux and Windows Editors but you'll not be able to test any of the SDK features in them , but only on your mobile devices.

Passkey Limitation: If an account is secured with a Passkey, it cannot be used in the Unity Editor for testing. Passkey authentication requires a native device (iOS/Android). All other authentication methods work in the Editor on macOS.


Installation

1. Add Scoped Registry

Open your Unity project and go to Edit > Project Settings > Package Manager.

Add a new scoped registry:

| Field | Value | |-------|-------| | Name | Dynamic SDK | | URL | https://packages.dynamic.xyz | | Scope(s) | xyz.dynamic.unitysdk |

2. Install the SDK

Open Window > Package Manager, click the + button, and select Install package by name.

Enter: xyz.dynamic.unitysdk

Leave the version blank to install the latest version.

3. Install UniTask (Required Dependency)

Open Packages/manifest.json in a text editor and add to the dependencies section:

"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"

4. Install Solana Support (Optional)

If you need Solana transaction building (not just message signin), install the Solana extension package.

In Package Manager > Install package by name, enter: xyz.dynamic.unitysdk.solana

Then add the following to your Packages/manifest.json dependencies:

"com.solana.unity_sdk": "https://github.com/kantagara/Solana.Unity-SDK.git"

Note: The Solana extension package only enables transaction building (RPC calls, PublicKey, SystemProgram.Transfer, etc.). plain-text message signing via the Dynamic webview work without this package.

5. Android Setup (Required for Android Builds)

To build for Android, you need a custom Main Gradle Template. In Unity, go to Edit > Project Settings > Player > Android > Publishing Settings and enable:

  • Custom Main Gradle Template

Assets/Plugins/Android/mainTemplate.gradle

Replace the contents with:

apply plugin: 'com.android.library'
apply from: '../shared/keepUnitySymbols.gradle'
**APPLY_PLUGINS**

dependencies {

    // [Solana.Unity-SDK] Dependencies
    implementation 'androidx.browser:browser:1.8.0'
    implementation 'androidx.core:core:1.13.1'
    implementation 'androidx.versionedparcelable:versionedparcelable:1.2.1'
    implementation 'com.google.guava:guava:33.5.0-android'
    implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
    // [Solana.Unity-SDK] End Dependencies
    implementation "androidx.credentials:credentials:1.3.0"
    implementation "androidx.credentials:credentials-play-services-auth:1.3.0"
    implementation "androidx.concurrent:concurrent-futures:1.2.0"
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.datastore:datastore-preferences:1.1.1'
    implementation 'com.google.crypto.tink:tink-android:1.11.0'
**DEPS**}

android {
    namespace "com.unity3d.player"
    ndkPath "**NDKPATH**"
    ndkVersion "**NDKVERSION**"

    compileSdk **APIVERSION**
    buildToolsVersion = "**BUILDTOOLS**"

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }

    defaultConfig {
        minSdk **MINSDK**
        targetSdk **TARGETSDK**
        ndk {
            abiFilters **ABIFILTERS**
            debugSymbolLevel **DEBUGSYMBOLLEVEL**
        }
        versionCode **VERSIONCODE**
        versionName '**VERSIONNAME**'
        consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD**
**DEFAULT_CONFIG_SETUP**
    }

    lint {
        abortOnError false
    }

    androidResources {
        noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
        ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
    }**PACKAGING**
}
**IL_CPP_BUILD_SETUP**
**SOURCE_BUILD_SETUP**
**EXTERNAL_SOURCES**

// [Solana.Unity-SDK] Conflict Resolution
configurations.all {
    exclude group: 'com.google.guava', module: 'listenablefuture'
    exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
    exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
    resolutionStrategy {
        force 'androidx.core:core:1.13.1'
    }
}
// [Solana.Unity-SDK] End Conflict Resolution

Key dependencies explained:

  • androidx.credentials + credentials-play-services-auth — required for Passkey (WebAuthn) support
  • androidx.datastore + tink-android — required for secure storage
  • androidx.browser — required for OAuth social login flows
  • com.google.guava — required by Solana SDK

Setup

The SDK must be initialized manually in your scene.

1. Create Configuration Asset

In the Unity Editor: Assets > Create > DynamicSDK > Client Props

This creates a ClientPropsData ScriptableObject. Configure it in the Inspector:

| Field | Required | Description | |-------|----------|-------------| | Environment ID | Yes | Your environment ID from the Dynamic dashboard | | App Name | No | Your application name | | App Logo URL | No | URL to your app logo | | App Origin | Yes | Mandatory for EOA connections and for passkey verification of your domain | | Log Level | No | Error (default), Warning, Info, or Debug | | Debug Webview | No | Enable WebView debugging |

2. Create Initializer Script

Create a MonoBehaviour that initializes the SDK on Awake:

using DynamicSDK.Core;
using UnityEngine;

public class DynamicSDKManager : MonoBehaviour
{
    [SerializeField] private ClientPropsData props;

    private void Awake()
    {
        DynamicSDK.DynamicSDK.Init(props);
    }
}

Attach this script to a GameObject in your scene and assign the ClientPropsData asset to the props field.

The SDK creates a DontDestroyOnLoad GameObject and persists across scene loads.

3. Wait for SDK Ready

The SDK loads asynchronously via WebView. You must wait for it to be ready before calling any SDK methods:

void Start()
{
    if (DynamicSDK.DynamicSDK.Instance.IsWebViewReady)
    {
        OnSdkReady();
    }
    else
    {
        DynamicSDK.DynamicSDK.Instance.OnWebViewReady += OnSdkReady;
    }
}

private void OnSdkReady()
{
    Debug.Log("Dynamic SDK is ready!");

    // Subscribe to auth state changes
    DynamicSDK.DynamicSDK.Instance.Auth.OnTokenChanged += OnTokenChanged;
    DynamicSDK.DynamicSDK.Instance.Auth.OnUserChanged += OnUserChanged;
}

private void OnTokenChanged(string token)
{
    if (string.IsNullOrEmpty(token))
    {
        Debug.Log("User logged out");
        // Navigate to login screen, clear UI, etc.
    }
    else
    {
        Debug.Log("User authenticated!");
        // Navigate to main screen, load wallets, etc.
    }
}

private void OnUserChanged(UserProfile user)
{
    if (user != null)
        Debug.Log($"User: {user.Email}");
}

Important: Do not call any SDK methods (auth, wallets, signing, etc.) before IsWebViewReady is true or the OnWebViewReady event fires.


Authentication

The SDK supports multiple authentication methods. After successful authentication, the SDK automatically populates the wallet list.

Email OTP

// Send OTP
await DynamicSDK.Instance.Auth.Email.SendOTP("[email protected]");

// Verify OTP (on a separate screen)
await DynamicSDK.Instance.Auth.Email.VerifyOTP("123456");

// Resend if needed
await DynamicSDK.Instance.Auth.Email.ResendOTP();

SMS OTP

await DynamicSDK.Instance.Auth.Sms.SendOTP(new PhoneData
{
    DialCode = "+1",
    Iso2 = "US",
    Phone = "5551234567",
});

await DynamicSDK.Instance.Auth.Sms.VerifyOTP("123456");

Social Login

await DynamicSDK.Instance.Auth.Social.Connect(SocialProvider.Google);
await DynamicSDK.Instance.Auth.Social.Connect(SocialProvider.Apple);
await DynamicSDK.Instance.Auth.Social.Connect(SocialProvider.Farcaster);

Passkey Sign-In

await DynamicSDK.Instance.Auth.Passkey.SignIn();

Passkey sign-in only works on native devices (iOS/Android), not in the Unity Editor or macOS.

External JWT

await DynamicSDK.Instance.Auth.ExternalAuth.SignInWithExternalJwt(
    new SignInWithExternalJwtParams
    {
        ExternalJwt = jwtToken,
        ExternalUserId = userId,
    });

Dynamic Widget (Built-in Auth UI)

DynamicSDK.Instance.UI.ShowAuth();

Session Management

// Get current auth token
string token = DynamicSDK.Instance.Auth.Token;

// Subscribe to auth state changes
DynamicSDK.Instance.Auth.OnTokenChanged += (token) =>
{
    if (!string.IsNullOrEmpty(token))
        Debug.Log("User logged in");
    else
        Debug.Log("User logged out");
};

// Get current user profile
DynamicSDK.Instance.Auth.OnUserChanged += (user) =>
{
    if (user != null)
        Debug.Log($"User: {user.Email}");
};

// Logout
await DynamicSDK.Instance.Auth.Logout();

Wallet Management

Access Wallets

// All user wallets
List<BaseWallet> wallets = DynamicSDK.Instance.Wallets.UserWallets;

// Subscribe to wallet changes
DynamicSDK.Instance.Wallets.OnUserWalletsChanged += (wallets) =>
{
    foreach (var w in wallets)
        Debug.Log($"{w.Chain}: {w.Address}");
};

Create Embedded Wallet

// Without password
await DynamicSDK.Instance.Wallets.Embedded.CreateWallet(EmbeddedWalletChain.Evm);

// With password protection
await DynamicSDK.Instance.Wallets.Embedded.CreateWallet(
    EmbeddedWalletChain.Sol,
    password: "user_password"
);

Supported chains: EmbeddedWalletChain.Evm, .Sol, .Sui, .Ton, .Btc

Get Balance

string balance = await DynamicSDK.Instance.Wallets.GetBalance(wallet);

Reveal Private Key

await DynamicSDK.Instance.UI.RevealEmbeddedWalletPrivateKey();

EVM Operations

Send Transaction (Native Transfer)

var tx = new Dictionary<string, object>
{
    { "to", "0xRecipientAddress" },
    { "from", wallet.Address },
    { "value", "0x" + ((long)(0.01 * 1e18)).ToString("X") } // 0.01 ETH in wei
};

string txHash = await DynamicSDK.Instance.Networks.Evm.SendTransaction(
    wallet.Address, tx);

Write Contract

var abiJson = @"[{
    ""inputs"": [
        {""name"": ""to"", ""type"": ""address""},
        {""name"": ""amount"", ""type"": ""uint256""}
    ],
    ""name"": ""transfer"",
    ""outputs"": [{""name"": """", ""type"": ""bool""}],
    ""stateMutability"": ""nonpayable"",
    ""type"": ""function""
}]";

var input = new Dictionary<string, object>
{
    { "address", "0xContractAddress" },
    { "abi", JsonConvert.DeserializeObject<List<object>>(abiJson) },
    { "functionName", "transfer" },
    { "args", new List<object> { "0xRecipient", "1000000" } },
};

string txHash = await DynamicSDK.Instance.Networks.Evm.WriteContract(
    wallet.Id, input);

Sign Typed Data (EIP-712)

var typedData = JsonConvert.DeserializeObject<Dictionary<string, object>>(@"{
    ""types"": {
        ""EIP712Domain"": [
            {""name"": ""name"", ""type"": ""string""},
            {""name"": ""version"", ""type"": ""string""},
            {""name"": ""chainId"", ""type"": ""uint256""}
        ],
        ""Mail"": [
            {""name"": ""from"", ""type"": ""string""},
            {""name"": ""to"", ""type"": ""string""},
            {""name"": ""contents"", ""type"": ""string""}
        ]
    },
    ""primaryType"": ""Mail"",
    ""domain"": { ""name"": ""Example"", ""version"": ""1"", ""chainId"": 1 },
    ""message"": { ""from"": ""Alice"", ""to"": ""Bob"", ""contents"": ""Hello!"" }
}");

string signature = await DynamicSDK.Instance.Networks.Evm.SignTypedData(
    wallet.Id, typedData);

Solana Operations

Requires xyz.dynamic.unitysdk.solana package and com.solana.unity_sdk for transaction building. Message signing works without these packages.

Sign Message

string signature = await DynamicSDK.Instance.Networks.Solana.SignMessage(
    wallet.Id, "Hello World");

Send SOL (Versioned Transaction)

using Solana.Unity.Programs;
using Solana.Unity.Rpc.Models;
using Solana.Unity.Wallet;

var solana = DynamicSDK.Instance.Networks.Solana;
var rpcClient = solana.CreateConnection();

// Get recent blockhash
var blockHashResult = await rpcClient.GetLatestBlockHashAsync();
var blockHash = blockHashResult.Result.Value.Blockhash;

var fromKey = new PublicKey(wallet.Address);
var toKey = new PublicKey("RecipientAddress");
var lamports = (ulong)(0.01 * 1_000_000_000); // 0.01 SOL

// Build versioned (v0) transaction
var tx = new VersionedTransaction
{
    RecentBlockHash = blockHash,
    FeePayer = fromKey,
    Instructions = new List<TransactionInstruction>
    {
        SystemProgram.Transfer(fromKey, toKey, lamports)
    },
    AddressTableLookups = new List<MessageAddressTableLookup>()
};

var message = tx.CompileMessage();

// Prepend 1 empty signature slot for the SDK to sign
var txBytes = new byte[1 + 64 + message.Length];
txBytes[0] = 1; // 1 signature slot (compact-u16)
// bytes 1..64 stay zero (empty signature placeholder)
Buffer.BlockCopy(message, 0, txBytes, 65, message.Length);

string txHash = await solana.SignAndSendTransaction(
    wallet.Id, txBytes, SolanaTransactionType.Versioned);

Send SPL Token (Versioned Transaction)

#if DYNAMIC_SDK_SOLANA
using Solana.Unity.Programs;
using Solana.Unity.Rpc.Models;
using Solana.Unity.Wallet;

var solana = DynamicSDK.Instance.Networks.Solana;
var rpcClient = solana.CreateConnection();

var blockHashResult = await rpcClient.GetLatestBlockHashAsync();
var blockHash = blockHashResult.Result.Value.Blockhash;

var ownerKey = new PublicKey(wallet.Address);
var recipientKey = new PublicKey("RecipientAddress");
var mintKey = new PublicKey("TokenMintAddress"); // e.g. USDC mint

// Derive Associated Token Accounts
var sourceAta = AssociatedTokenAccountProgram
    .DeriveAssociatedTokenAccount(ownerKey, mintKey);
var destAta = AssociatedTokenAccountProgram
    .DeriveAssociatedTokenAccount(recipientKey, mintKey);

int decimals = 6; // USDC has 6 decimals
var rawAmount = (ulong)(1.5 * Math.Pow(10, decimals)); // 1.5 USDC

var tx = new VersionedTransaction
{
    RecentBlockHash = blockHash,
    FeePayer = ownerKey,
    Instructions = new List<TransactionInstruction>
    {
        // Create destination ATA if it doesn't exist (idempotent)
        AssociatedTokenAccountProgram.CreateAssociatedTokenAccount(
            ownerKey, recipientKey, mintKey),
        // Transfer SPL tokens
        TokenProgram.Transfer(sourceAta, destAta, rawAmount, ownerKey)
    },
    AddressTableLookups = new List<MessageAddressTableLookup>()
};

var message = tx.CompileMessage();

var txBytes = new byte[1 + 64 + message.Length];
txBytes[0] = 1;
Buffer.BlockCopy(message, 0, txBytes, 65, message.Length);

string txHash = await solana.SignAndSendTransaction(
    wallet.Id, txBytes, SolanaTransactionType.Versioned);
#endif

Common Solana Token Mint Addresses

| Token | Network | Mint Address | |-------|---------|--------------| | USDC | Mainnet (101) | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | | USDC | Devnet (103) | 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU |


SUI Operations

Sign and Send SUI Transfer

// Sign only (does not broadcast)
string signature = await DynamicSDK.Instance.Networks.Sui.SignTransferTransaction(
    wallet.Id, "0xRecipientAddress", "0.1");

// Sign and broadcast
string digest = await DynamicSDK.Instance.Networks.Sui.SignAndSendTransferTransaction(
    wallet.Id, "0xRecipientAddress", "0.1");

Sign Message

string signature = await DynamicSDK.Instance.Networks.Sui.SignMessage(
    wallet.Id, "Hello from SUI");

Sign / Send Raw Transaction

// Sign a base64-encoded transaction
string signature = await DynamicSDK.Instance.Networks.Sui.SignTransaction(
    wallet.Id, base64TransactionBytes);

// Send a base64-encoded transaction
string digest = await DynamicSDK.Instance.Networks.Sui.SendTransaction(
    wallet.Id, base64TransactionBytes);

Token Balances

Query Multichain Balances

var request = new MultichainBalanceRequest
{
    FilterSpamTokens = true,
    BalanceRequests = new List<BalanceRequestItem>
    {
        new()
        {
            Address = wallet.Address,
            Chain = "SOL", // or "EVM"
            NetworkIds = new List<object> { 103 }, // Solana Devnet
        }
    }
};

var response = await DynamicSDK.Instance.Wallets.GetMultichainBalances(request);

foreach (var token in response.Balances)
{
    Debug.Log($"{token.Symbol}: {token.Balance} (mint: {token.MintAddress})");
}

Query Specific Token Balance

Use WhitelistedContracts to query a specific token by its contract/mint address:

var request = new MultichainBalanceRequest
{
    FilterSpamTokens = false,
    BalanceRequests = new List<BalanceRequestItem>
    {
        new()
        {
            Address = wallet.Address,
            Chain = "SOL",
            NetworkIds = new List<object> { 103 },
            WhitelistedContracts = new List<string>
            {
                "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" // USDC Devnet
            },
        }
    }
};

var response = await DynamicSDK.Instance.Wallets.GetMultichainBalances(request);

Passkey Management

Passkey registration and authentication only work on native devices (iOS/Android), not in the Unity Editor.

Register a Passkey

await DynamicSDK.Instance.Passkeys.RegisterPasskey();

List Passkeys

List<UserPasskey> passkeys = await DynamicSDK.Instance.Passkeys.GetPasskeys();

foreach (var pk in passkeys)
    Debug.Log($"Passkey: {pk.Id}, created: {pk.CreatedAt}");

Authenticate with Passkey (MFA)

var result = await DynamicSDK.Instance.Passkeys.AuthenticatePasskeyMFA(
    new MfaCreateToken { SingleUse = true });

string token = result?.MfaToken ?? result?.Jwt;

Delete a Passkey

await DynamicSDK.Instance.Passkeys.DeletePasskey(new DeletePasskeyRequest
{
    CredentialId = passkey.CredentialId,
    PasskeyId = passkey.Id,
});

Embedded Wallet (WaaS)

Password Protection

// Check wallet recovery state
var state = await DynamicSDK.Instance.Wallets.Waas.GetWalletRecoveryState(wallet);
bool isPasswordProtected = state.IsPasswordEncrypted;

// Set password on a wallet that doesn't have one
await DynamicSDK.Instance.Wallets.Waas.SetPassword(wallet, "new_password");

// Unlock a password-protected wallet
await DynamicSDK.Instance.Wallets.Waas.UnlockWallet(wallet, "password");

// Update password
await DynamicSDK.Instance.Wallets.Waas.UpdatePassword(wallet, "old_password", "new_password");

Delegated Access

Delegated access allows key shares to be delegated for server-side transaction signing.

var delegation = DynamicSDK.Instance.Wallets.Waas.Delegation;

// Check if wallet is eligible
bool eligible = delegation.IsWalletEligibleForDelegation(wallet);

// Delegate key shares
await delegation.DelegateKeyShares(
    new List<DelegationWalletIdentifier>
    {
        new()
        {
            ChainName = ChainEnum.Sol, // or ChainEnum.Evm
            AccountAddress = wallet.Address,
        }
    });

// Check delegation status
var status = delegation.GetDelegationStatusForWallet(wallet.Id);

// Revoke delegation
await delegation.RevokeDelegation(
    new List<DelegationWalletIdentifier>
    {
        new()
        {
            ChainName = ChainEnum.Sol,
            AccountAddress = wallet.Address,
        }
    });

MFA (Multi-Factor Authentication)

TOTP Setup

// Add a TOTP device
var result = await DynamicSDK.Instance.Mfa.AddDevice("totp");
// result.Secret — base32 secret for manual entry
// result.Uri    — otpauth:// URI for QR code

// Verify with code from authenticator app
await DynamicSDK.Instance.Mfa.VerifyDevice(code, "totp");

TOTP Authentication

string mfaToken = await DynamicSDK.Instance.Mfa.AuthenticateDevice(
    new MfaAuthenticateDevice
    {
        Code = "123456",
        Type = "totp",
        DeviceId = deviceId,
        CreateMfaToken = new MfaCreateToken { SingleUse = true },
    });

Recovery Codes

List<string> codes = await DynamicSDK.Instance.Mfa.GetNewRecoveryCodes();

API Reference

Module Accessors

| Accessor | Description | |----------|-------------| | DynamicSDK.Instance.Auth | Authentication (email, SMS, social, passkey, JWT) | | DynamicSDK.Instance.Wallets | Wallet management, balances, signing, delegation | | DynamicSDK.Instance.Networks.Evm | EVM transactions and contract interaction | | DynamicSDK.Instance.Networks.Solana | Solana signing, RPC connection, transactions | | DynamicSDK.Instance.Networks.Sui | SUI signing and transactions | | DynamicSDK.Instance.UI | Built-in UI (auth flow, profile, private key reveal) | | DynamicSDK.Instance.Passkeys | Passkey registration and authentication | | DynamicSDK.Instance.Mfa | Multi-factor authentication management |


Sample App

The SDK includes a complete sample app demonstrating all features. To import it:

  1. Open Window > Package Manager
  2. Select Dynamic SDK from the package list
  3. Expand Samples
  4. Click Import next to Sample App

Documentation

For full documentation, visit https://docs.dynamic.xyz.