Unity SDK for the Open Creator Rails Smart Contracts project. It provides a set of Unity-friendly components and services that make it easy to integrate on-chain subscription-based access control, expiration-based entitlements mapped as [subject, resourceId] → expirationTime, directly into your Unity game or application.
| Repository | Description |
|---|---|
| open-creator-rails | Smart Contracts |
| open-creator-rails.indexer | Indexer |
| open-creator-rails.sdk | TypeScript SDK |
| open-creator-rails.demo | Web Demo |
Before installing the package you must add the OpenUPM Scoped Registry to your Unity project:
- Open Edit → Project Settings → Package Manager
- Under Scoped Registries, click + and fill in:
- Name:
OpenUPM - URL:
https://package.openupm.com - Scope(s):
com.cysharp,com.nethereum
- Name:
- Click Save
In the Unity Editor open Window → Package Manager, click + and select Add package from git URL, then enter:
https://github.com/ChainSafe/open-creator-rails.unity.git?path=io.chainsafe.open-creator-rails
Add the OpenCreatorRailsService component to a GameObject in your scene. This component is a singleton (DontDestroyOnLoad) and acts as the central hub for all SDK functionality.
Add the following components to the same GameObject as OpenCreatorRailsService and fill in their serialized fields:
| Component | Required Fields | Notes |
|---|---|---|
PonderIndexerProvider |
Indexer Url |
Or implement IIndexerProvider |
EmbeddedWalletProvider |
Chain Id |
Or implement IWalletProvider |
PollingEventHandler |
(none) | Or implement IEventHandler |
EmbeddedWalletProviderreads wallet credentials fromAssets/secrets.json. Create that file with:{ "mnemonic": "your twelve word mnemonic phrase here ...", "rpcUrl": "https://sepolia.infura.io/v3/YOUR_KEY" }
For each on-chain asset you want to track, add an Asset component to any GameObject in the scene and fill in its serialized fields:
| Field | Description |
|---|---|
Registry Address |
Address of the deployed AssetRegistry contract |
Asset Id |
Human-readable asset identifier (e.g. "default_asset_id") |
Then drag each Asset instance into the Assets list on the OpenCreatorRailsService component so the service can manage them.
All examples assume you have completed the Setup steps and have a reference to the OpenCreatorRailsService singleton and at least one configured Asset component in your scene.
Call Connect() on the service to initialize the wallet and load all asset state from the indexer. An optional index selects which HD wallet account to use (default 0).
using Io.ChainSafe.OpenCreatorRails;
using Cysharp.Threading.Tasks;
// Connect the default account (index 0).
// EmbeddedWalletProvider reads credentials from Assets/secrets.json.
await OpenCreatorRailsService.Instance.Connect();
// Connect a different HD account (e.g. account at derivation index 2)
await OpenCreatorRailsService.Instance.Connect(index: 2);
var connectedAccount = OpenCreatorRails.Instance.WalletProvider.ConnectedAccount;After Connect() returns, every IAsset in the scene is populated with its on-chain state and event listeners are active.
Use TryGetAsset to retrieve a pre-configured asset by its human-readable ID. Subscription status queries are read-only and can be called by anyone — no permissions are required.
// Retrieve a configured asset by its human-readable ID.
// Pass an optional registryAddress to disambiguate if multiple registries share the same assetId.
if (!OpenCreatorRailsService.Instance.TryGetAsset("default_asset_id", out IAsset asset))
{
Debug.LogError("Asset not found");
return;
}
// The subscriberId is a plain string; the SDK automatically hashes it together
// with the currently-connected wallet address:
// keccak256(abi.encode(subscriberId, connectedAccount))
// This means each (subscriberId, wallet) pair has its own unique on-chain identity.
string subscriberId = "user_123";
// Check whether the subscription is active (not expired AND not revoked).
// Read-only.
bool isActive = await asset.IsSubscriptionActive(subscriberId);
// Check expiry independently
bool isExpired = await asset.IsSubscriptionExpired(subscriberId);
// Check revocation status independently
bool isRevoked = await asset.IsSubscriberRevoked(subscriberId);
// Get the exact expiration time as a local DateTime
// Returns DateTime.MinValue if subscriber not found
DateTime expiresAt = await asset.GetSubscriptionExpiration(subscriberId);Call Subscribe to create a new subscription, or to extend/renew an existing one. Payment is handled via an ERC-2612 permit
no separate approval transaction is needed.
The connected wallet is both the payer and subscriber address. The payer is the refund beneficiary if the subscription is later cancelled or revoked.
// No special permission required.
// Reverts if the subscriber is permanently revoked on this asset;
string subscriberId = "user_123";
// Subscribe for 1 period. Pass a larger count to buy multiple periods at once.
BigInteger count = 1;
// Returns the new subscription expiration as a local DateTime.
DateTime newExpiry = await asset.Subscribe(subscriberId, count);
Debug.Log($"Subscribed! Access granted until: {newExpiry}");Note: The payment token must implement ERC-2612 (permit). The total token amount spent equals
SubscriptionPrice * count. You can preview the cost before subscribing:(BigInteger price, TimeSpan duration) = await asset.GetSubscriptionPriceAndDuration(count);
Call CancelSubscription to cancel the connected wallet's subscription. The SDK signs an off-chain cancellation proof.
// The connected wallet must be the subscriber address
// (the same address used when the subscription was created).
// Reverts if the subscriber is permanently revoked.
// Only whole remaining subscription periods are refunded to the original payer.
// Partial (active) periods are non-refundable.
string subscriberId = "user_123";
await asset.CancelSubscription(subscriberId);
Debug.Log("Subscription cancelled.");// ASSET OWNER ONLY.
// Price is denominated in the token's smallest unit (e.g. wei for an 18-decimal token).
BigInteger newPriceInTokenWei = 8; // replace with your desired price
await asset.SetSubscriptionPrice(newPriceInTokenWei);
// asset.SubscriptionPrice is automatically updated via SubscriptionPriceUpdated event.
Debug.Log($"New subscription price: {asset.SubscriptionPrice}");Creator fees accrue pro-rata over completed subscription periods since the last claim. Any remaining "dust" from a fully-ended subscription is also claimable.
// ASSET OWNER ONLY.
string subscriberId = "user_123";
BigInteger claimed = await asset.ClaimCreatorFee(subscriberId);
Debug.Log($"Claimed creator fee: {claimed} (token wei)");// ASSET OWNER ONLY.
// Subscribers with no accrued fee are silently skipped.
string[] subscriberIds = { "user_123", "user_456", "user_789" };
BigInteger totalClaimed = await asset.ClaimCreatorFee(subscriberIds);
Debug.Log($"Total creator fees claimed: {totalClaimed} (token wei)");Revocation immediately terminates a subscriber's access. All remaining time is refunded to the original payer(s), including partial-period dust (unlike cancellation, which only refunds whole periods). The subscriber is permanently blocked from resubscribing and cancelling until the Asset Owner calls UnrevokeSubscription.
// ASSET OWNER ONLY.
// Reverts if the subscriber is already revoked.
string subscriberId = "user_123";
await asset.RevokeSubscription(subscriberId);
Debug.Log($"Subscriber {subscriberId} revoked.");Lifts a permanent revocation, allowing the subscriber to resubscribe.
// ASSET OWNER ONLY.
string subscriberId = "user_123";
await asset.UnrevokeSubscription(subscriberId);
Debug.Log($"Subscriber {subscriberId} unrevoked.");