Skip to content

Commit

Permalink
Added a section on transaction queuers to the write-to-blockchain doc…
Browse files Browse the repository at this point in the history
…s. Added a guide: building transaction heavy games with Unity. Renamed the jelly forest guide from unity-guide to jelly-forest-unity-guide (#308)
  • Loading branch information
BellringerQuinn authored Oct 2, 2024
1 parent cbadbcf commit 1d6e59d
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 12 deletions.
3 changes: 2 additions & 1 deletion 404.html
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ <h1>Page Not Found</h1>
{ oldPath: "/solutions/transactions-api/overview/", targetPath: "/solutions/transaction-manager/overview/"},
{ oldPath: "/solutions/transactions-api/overview", targetPath: "/solutions/transaction-manager/overview"},
{ oldpath: "/sdk/unity/authentication", targetPath: "/sdk/unity/authentication/intro" },
{ oldpath: "/solutions/wallets/embedded-wallet/examples/validation", targetPath: "/solutions/wallets/embedded-wallet/examples/manage-sessions#session-management" }
{ oldpath: "/solutions/wallets/embedded-wallet/examples/validation", targetPath: "/solutions/wallets/embedded-wallet/examples/manage-sessions#session-management" },
{ oldpath: "/guide/unity-guide", targetPath: "guide/jelly-forest-unity-guide"}

];

Expand Down
41 changes: 41 additions & 0 deletions docs/pages/guides/building-transaction-heavy-games-with-unity.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Building Transaction Heavy Games with Unity

## Intro

Unlike other forms of databases, every write to a blockchain (transaction) costs money in the form of [gas fees](https://ethereum.org/en/developers/docs/gas/). When building blockchain/web3 games, gas fees should be considered. While [Sequence's gas sponsorship](/solutions/builder/gas-tank) handles much of the complexity for your end users for you, you as the game developers should still make a few considerations with respect to gas fees.

:::tip
When building your game, you should consider the **_frequency_** with which you submit transactions to the blockchain in order to keep runtime costs at a minimum.
:::

An additional complexity of working with the blockchain that doesn't exist with all forms of data storage is that writing the the blockchain database (i.e. making a transaction) is a non-instant, asynchronous operation that requires a network connection.

:::warnin
Transactions can fail for a variety of reasons: no internet, insufficient funds, etc.
:::

First, you should consider which tokenizable ownerships (e.g. items, powerups, unlocks, etc.) should be tokenized on the blockchain.

Next, you should consider the "kinds" of transactions your game will be making. You can likely bucket the transactions into different categories. For example, some of these categories of transactions might include: pickups (think collecting coins), crafting, trading, selling, buying, etc.

Once you've categorized each of your transactions, consider your end-user's expectations around those transactions as well as your expectations around the game developer. How much delay is acceptable from an end-user's perpective for a transaction to process? Can you assume that a transaction will succeed to give your user instantaneous feedback, and, if so, can you recover in the case that a transaction fails without negatively impacting the player or your bottom-line?

The writer of this guide often generalizes transactions as high-value or low-value.

**High-value transactions** typically need confirmation before providing the end-user with feedback. Transactions can fail for a number of reasons (no internet, insufficient gas, invalid assumptions, etc.). If we assume a high-value transaction will pass and give the user feedback right away then later on the transaction ends up failing, we will not be able to recover without negatively impacting the user or our bottom line. Consider, for example, an in-game storefront. If a user's "buy sword" transaction fails, we would either need to revoke the sword from their account (hurting player experience) or lose out on the revenue from the sale (hurting the bottom line). Conveniently, most high-value transactions coincide with activities where users are usually accustumed to having a short wait time in traditional (non-blockchain) games such as storefronts, crafting, upgrading, etc.

**Low-value transactions** can, and often should, provide user feedback right away. We do not need to wait for a transaction confirmation before the in-game feedback takes place. If the transaction does end up failing, we can easily recover without negatively impacting the player experience or our bottom line in most cases. Players will typically be accustumed to receiving instant feedback for these actions in traditional games. For example: when a user collects a coin in a platformer (or similar) they expect to see the collected coin get reflected in the UI immediately. The player is unlikely to remember their exact coin total in the following game session and/or it is unlikely to impact the developer's bottom line if they locally store the collected coins and re-submit the transaction when network issues are resolved (or similar).

Finally, you should consider how frequently your game should be making transactions. Some games will have the user taking many actions that impact the game state in a short period of time. Imagine submitting a transaction to the blockchain every time Mario collected a coin... The costs would quickly become prohibitive, bundle those low-value transactions together!

## How to implement this with Unity?

First, you'll want to build a local cache of what the user has on-chain. This is easy enough to do, simply [read from the blockchain](/sdk/unity/read-from-blockchain) and locally store the user's token balances in whatever format is convenient. If you're converting an existing game or prototype from using a local storage system (like PlayerPrefs) or a remote storage system (like an [RDBMS](https://en.wikipedia.org/wiki/List_of_relational_database_management_systems)) then you likely already have a local cache implemented and you may just need to build an adapter.

Next, you'll likely want to make use of the `TransactionQueuer` and its inheritors provided by the Unity SDK. The `TransactionQueuer`s are highly configurable and are designed to support the development of games where player's take many state-manipulating actions. For instance, if your game involves collecting a lot of coins (or similar) as low-value transactions, you'll likely want to make use o the `PermissionedMinterTransactionQueuer` (assuming your `mint` function has permissions, the default, and you are minting from a server) or the `SequenceWalletTransactionQueuer` (if anyone can mint). Using these, you can simply queue up a bunch of transactions; these transactions will be automatically combined if possible (e.g. instead of having 'mint(amount: 5, tokenId: 11)' and 'mint(amount: 3, tokenId: 11)', these would get combined to 'mint(amount: 8, tokenId: 11)'). Then, you can have your transactions be submitted ever x seconds or whenever a function call is made but no sooner than every y seconds (overrideable for high-value transactions), etc. To learn more about working with the `TransactionQueuer`s, please see [this doc](/sdk/unity/write-to-blockchain#transaction-queuers).

Finally, you'll want to check for failures in your transactions and handle the errors appropriately.

## Example

For an example of these concepts in action in our Unity SDK, please checkout our [Jelly Forest Guide](http://localhost:5173/guides/jelly-forest-unity-guide#5-mint-in-game-tokens-to-the-players-inventory) and [sample code](https://github.com/0xsequence/sequence-unity-demo/tree/master/Scripts).
File renamed without changes.
63 changes: 62 additions & 1 deletion docs/pages/sdk/unity/write-to-blockchain.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,65 @@ private async Task WaitForFeeOptionsAndSubmitFirstAvailable(Address toAddress, s
Debug.LogError("The user does not have enough of the valid FeeOptions in their wallet");
}
```
```

## Transaction Queuers

When working with the blockchain, it is important to [batch transactions](/sdk/unity/write-to-blockchain#batch-transactions) in order to minimize gas fees. In order to make this easier to do, we've provided a flexible `TransactionQueuer` with the SDK that can be configured or extended to suit your needs. To learn more about Building Transaction Heavy Games in Unity and what to consider, please checkout [our guide on the topic](/guides/building-transaction-heavy-games-with-unity).

When you add a `TransactionQueuer` MonoBehaviour to your scene, there are a few config variables you can set.

- `AutoSubmitTransactions`: defaulting to false, enabling this will configure your `TransactionQueuer` to automatically submit any queued transactions whenever `ThresholdTimeBetweenTransactionsAddedBeforeSubmittedInSeconds` has passed without adding a new transaction to the queue
- `ThresholdTimeBetweenTransactionsAddedBeforeSubmittedInSeconds`: if `AutoSubmitTransactions == true`, automatically submit queued transactions if none is added in the past `ThresholdTimeBetweenTransactionsAddedBeforeSubmittedInSeconds` seconds
- `MinimumTimeBetweenTransactionSubmissionsInSeconds`: a minimum time between submitting queued transactions. With this, you can call `TransactionQueuer.SubmitTransactions()` as often as you want in your code and transactions will not be submitted unless `MinimumTimeBetweenTransactionSubmissionsInSeconds` seconds have passed since the last transaction was pushed. Note: if `TransactionQueuer.SubmitTransactions(overrideWait: true)` is called with the optional `overrideWait` bool flag set to true, the `TransactionQueuer` will submit queued transactions regardless of whether or not the `MinimumTimeBetweenTransactionSubmissionsInSeconds` has passed.

The `TransactionQueuer` exposes a few methods to you:

- Setup: before calling other methods on a `TransactionQueuer`, please call `Setup` on it; this will create and cache the required dependancies
- Enqueue: add a transaction to the queue
- SubmitTransactions(bool overrideWait = false, bool waitForReceipt = true): submit the queued transactions if `MinimumTimeBetweenTransactionSubmissionsInSeconds` has passed between last transaction submission by the `TransactionQueuer`. If `overrideWait = true`, submit any queued transactions immediately. If `waitForReceipt = false`, return the `TransactionReturn` as soon as we get a response from the WaaS API (note: this is only relevant if the WaaS API times out while waiting for a transaction receipt; if `waitForReceipt = true`, we will continually ping a node for a transaction receipt before returning)
- ToString(): an override for the typical ToString() function, providing you with better logging support

:::warning
Don't forget to call `Setup` on your `TransactionQueuer`!
:::

Currently, the SDK exposes two different inheritors of the `TransactionQueuer` class.

### SequenceWalletTransactionQueuer

The `SequenceWalletTransactionQueuer` allows you to queue up transactions for your user's Sequence Embedded Wallet.

The `SequenceWalletTransactionQueuer` expects you to enqueue `IQueueableTransaction`s. This interface is implemented by the `QueuedTokenTransaction` class. Please feel free to create other classes implementing the `IQueueableTransaction` interface as needed.

### PermissionedMinterTransactionQueuer

The `PermissionedMinterTransactionQueuer` is meant to be used for queueing up transactions that are being submitted by your backend server when receiving a signed message from the player's embedded wallet. It is useful for minting tokens to the player's wallet when interacting with contracts that require permissions for minting (most token contracts).

The `PermissionedMinterTransactionQueuer` expects you to enqueue a `PermissionedMintTransaction`, a basic data transfer object specifying the TokenId and Amount to be minted, and optionally an IMinter. If not provided, the `PermissionedMinterTransactionQueuer` will default to using the `PermissionedMinter` class. The `PermissionedMinter` class will be useful for most use cases; it sends a payload in the following format:

```
ProofPayload:
{
"app": "Made with Sequence Unity SDK App",
"iat": (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), // issued at time
"exp": (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 300, // expiry time
"ogn": "Sequence Unity SDK",
"payload": {
"contractAddress": "0xabc123...",
"tokenId": "11",
"amount": 5
}
}
This JSON get stringified and included in the MintingRequestProof:
{
"Proof": "{\"app\": \"Made with Sequence Unity SDK App\", \"iat\": ...}",
"SignedProof": "0x123def...", // proof signed by the player's embedded wallet
"SigningAddress": "0xa1b2c3..." // the player's embedded wallet address
}
```

You can then validate this payload on your server and mint the token to the user's address. For an example implementation and setup, please see [this part of our Jelly Forest guide](/guide/jelly-forest-unity-game#4-deploy-a-remote-minter).

For other use cases, you may want to provide your own implementation of the IMinter class. This allows you to modify the format and information provided in the payload to your server as needed.
25 changes: 15 additions & 10 deletions nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,19 +460,24 @@ export const sidebar = {
{
text: 'Build a Unity Game',
collapsed: true,
link: '/guides/unity-guide',
link: '/guides/jelly-forest-unity-guide',
// items: [
// { text: 'Introduction', link: '/guides/unity-guide#intro-to-jelly-forest' },
// { text: 'Build a Game Loop', link: '/guides/unity-guide#build-a-game-loop' },
// { text: 'Integrate Embedded Wallets', link: '/guides/unity-guide#integrate-social-sign-in-and-sequences-embedded-wallet-solution' },
// { text: 'Deploy Collectibles', link: '/guides/unity-guide#deploy-a-collectibles-contract' },
// { text: 'Deploy Remote Minter', link: '/guides/unity-guide#deploy-a-remote-minter'},
// { text: 'Minting Tokens to Inventory', link: '/guides/unity-guide#mint-in-game-tokens-to-the-players-inventory' },
// { text: 'Purchase Collectibles with ERC20 Tokens', link: '/guides/unity-guide#burn-in-game-tokens-in-exchange-for-others' },
// { text: 'Building an In-game Shop', link: '/guides/unity-guide#building-the-shop-pages-and-setting-the-minting-requirements' },
// { text: 'Leverage Purchased Items In-game', link: '/guides/unity-guide#leverage-purchased-items-in-game' },
// { text: 'Introduction', link: '/guides/jelly-forest-unity-guidee#intro-to-jelly-forest' },
// { text: 'Build a Game Loop', link: '/guides/jelly-forest-unity-guide#build-a-game-loop' },
// { text: 'Integrate Embedded Wallets', link: '/guides/jelly-forest-unity-guide#integrate-social-sign-in-and-sequences-embedded-wallet-solution' },
// { text: 'Deploy Collectibles', link: '/guides/jelly-forest-unity-guide#deploy-a-collectibles-contract' },
// { text: 'Deploy Remote Minter', link: '/guides/jelly-forest-unity-guide#deploy-a-remote-minter'},
// { text: 'Minting Tokens to Inventory', link: '/guides/jelly-forest-unity-guide#mint-in-game-tokens-to-the-players-inventory' },
// { text: 'Purchase Collectibles with ERC20 Tokens', link: '/guides/jelly-forest-unity-guide#burn-in-game-tokens-in-exchange-for-others' },
// { text: 'Building an In-game Shop', link: '/guides/jelly-forest-unity-guide#building-the-shop-pages-and-setting-the-minting-requirements' },
// { text: 'Leverage Purchased Items In-game', link: '/guides/jelly-forest-unity-guide#leverage-purchased-items-in-game' },
// ]
},
{
text: 'Building Transaction Heavy Games with Unity',
collapsed: true,
link: '/guides/building-transaction-heavy-games-with-unity',
},
{
text: 'Build a Collectible Minting Service',
collapsed: true,
Expand Down

0 comments on commit 1d6e59d

Please sign in to comment.