Introduction
The mosaic restriction is one of the coolest features in Symbol.
Mosaic restrictions allow mosaic creators to decide which accounts can transact with the asset.
To use mosaic restrictions, there are two types of transactions needed.
- Global Restrictions (MosaicGlobalRestrictionTransaction): Define rules for the mosaics.
- Address Restrictions (MosaicAddressRestrictionTransaction): Define which accounts are able to use the mosaic.
I wanted to write a use case about it, but I didn't have the idea until recently, when I saw some community members share in Twitter that they were building a game on Symbol. So I wondered, it may be a good idea to use the mosaic restriction feature to create a simple concept of the Dungeons & Dragons game.
Game Rules
- Players have a level, and skill stat.
- Players are rewarded 1 skill point if they win a round.
- There are some skills available such as Punch, Kick, Fire Spell
- Skill points can upgrade a player's skill stat, to unlock new skills.
How it works
In the D&D games, we always have players and Dungeon masters.
Create Mosaics
We use Mosaics to represent game assets, such as level, punch, kick, and fire_spell.
Note: Mosaic Restrictions can only be used if
isRestrictable
is set totrue
on a mosaic.
const dungeon_master = Account.createFromPrivateKey(privateKey, networkType);
const isSupplyMutable = true; // Easily adjust the supply
const isTransferable = false; // Players cannot transfer skills to other players
const isRestrictable = true; // Enable mosaic restrictions
const isRevokable = true; // Creator can collect back mosaics
const nonce = MosaicNonce.createRandom();
const mosaicDefinitionTransaction = MosaicDefinitionTransaction.create(
Deadline.create(epochAdjustment),
nonce,
MosaicId.createFromNonce(nonce, dungeon_master.address),
MosaicFlags.create(isSupplyMutable, isTransferable, isRestrictable, isRevokable),
0, // divisibility
UInt64.fromUint(0), // unlimited
networkType,
);
Create Mosaic Global Restriction Transaction
Up to this stage, we can set up mosaic global restrictions for each mosaic (asset).
In global restrictions, you are allowed to add reference mosaic to restrictions, which I think is more advanced.
But do not worry, I will break it down in a table so you have a better view:
MosaicId | Key | Value | Type | Reference Mosaic Id | Reference Mosaic Key | Reference Value | Reference Type |
---|---|---|---|---|---|---|---|
6AACF58D95F016C0 | level | 0 | GT | null | null | null | null |
73E60A6DD1D16FDD | punch | 1 | EQ | null | null | null | null |
0015F52218DA969C | kick | 2 | EQ | null | null | null | null |
6A53A4FB36203CCE | fire_spell | 2 | EQ | 1 | level | 5 | GE |
Check mosaic Ids 6AACF58D95F016C0
, 73E60A6DD1D16FDD
, and 0015F52218DA969C
. It's very straightforward to understand.
Mosaic ID 6A53A4FB36203CCE
is different from the others because it adds a reference mosaic. This means that to unlock the fire_spell
the player needs to fulfill 2 requirements.
Example code
const dungeon_master = Account.createFromPrivateKey(privateKey, networkType);
// (non-reference) mosaic global restriction for mosaic ID: 6AACF58D95F016C0
const mosaicIdHex = '6AACF58D95F016C0';
const mosaicId = new Symbol.MosaicId(mosaicIdHex);
const key = Symbol.KeyGenerator.generateUInt64Key('level'.toLowerCase());
const mosaicGlobalRestrictionTransaction = Symbol.MosaicGlobalRestrictionTransaction.create(
deadline,
mosaicId, // mosaicId
key, // restrictionKey
Symbol.UInt64.fromUint(0), // previousRestrictionValue
Symbol.MosaicRestrictionType.NONE, // previousRestrictionType
Symbol.UInt64.fromUint(0), // newRestrictionValue
Symbol.MosaicRestrictionType.GT, // newRestrictionType
networkType,
undefined, // reference Mosaic Id
maxFee,
);
// (non-reference) mosaic global restriction for mosaic ID: 73E60A6DD1D16FDD
...
...
...
// (non-reference) mosaic global restriction for mosaic ID: 0015F52218DA969C
...
...
...
// (non-reference) mosaic global restriction for mosaic ID: 6A53A4FB36203CCE
const mosaicIdHex = '6A53A4FB36203CCE';
const mosaicId = new Symbol.MosaicId(mosaicIdHex);
const key = Symbol.KeyGenerator.generateUInt64Key('fire_spell'.toLowerCase());
const mosaicGlobalRestrictionTransaction = Symbol.MosaicGlobalRestrictionTransaction.create(
deadline,
mosaicId, // mosaicId
key, // restrictionKey
Symbol.UInt64.fromUint(0), // previousRestrictionValue
Symbol.MosaicRestrictionType.NONE, // previousRestrictionType
Symbol.UInt64.fromUint(2), // newRestrictionValue
Symbol.MosaicRestrictionType.GE, // newRestrictionType
networkType,
undefined, // reference Mosaic Id
maxFee,
);
// (reference) mosaic global restriction for mosaic ID: 4 with reference mosaic
const mosaicIdHex = '6A53A4FB36203CCE';
const mosaicId = new Symbol.MosaicId(mosaicIdHex);
const level_mosaicId = new Symbol.MosaicId('6AACF58D95F016C0');
const key = Symbol.KeyGenerator.generateUInt64Key('level'.toLowerCase());
const mosaicGlobalRestrictionTransaction = Symbol.MosaicGlobalRestrictionTransaction.create(
deadline,
mosaicId, // mosaicId
key, // restrictionKey
Symbol.UInt64.fromUint(0), // previousRestrictionValue
Symbol.MosaicRestrictionType.NONE, // previousRestrictionType
Symbol.UInt64.fromUint(5), // newRestrictionValue
Symbol.MosaicRestrictionType.EQ, // newRestrictionType
networkType,
level_mosaicId, // reference Mosaic Id
maxFee,
);
Create Mosaic Address Restriction Transaction
Phew! We have done the mosaic global restriction, now we need to specify which accounts can transact those mosaics. In our case, there are 2 accounts: the dungeon master and the player.
The dungeon master account must have all qualified the global restriction because it needs to transact mosaic to the player account.
const dungeon_master = Account.createFromPrivateKey(privateKey, networkType);
// Assign mosaic ID: 6AACF58D95F016C0, key: level, value: 10
const mosaicIdHex = '6AACF58D95F016C0';
const mosaicId = new Symbol.MosaicId(mosaicIdHex);
const key = Symbol.KeyGenerator.generateUInt64Key('level'.toLowerCase());
const mosaicAddressRestrictionTransaction = Symbol.MosaicAddressRestrictionTransaction.create(
deadline,
mosaicId,
key, // restrictionKey
dungeon_master.address,
Symbol.UInt64.fromUint(10), // newRestrictionValue
networkType,
Symbol.UInt64.fromHex('FFFFFFFFFFFFFFFF'), // previousRestrictionValue
maxFee
);
// Assign mosaic ID: 73E60A6DD1D16FDD, key: punch, value: 1
...
...
// Assign mosaic ID: 0015F52218DA969C, key: kick, value: 2
...
...
// Assign mosaic ID: 6A53A4FB36203CCE, key: fire_spell, value: 2
...
...
Let's continue with the player account:
const player = Account.createFromPublicKey(publicKey, networkType);
// Default player level 1
// Assign mosaic ID: 6AACF58D95F016C0, key: level, value: 1
const mosaicIdHex = '6AACF58D95F016C0';
const mosaicId = new Symbol.MosaicId(mosaicIdHex);
const key = Symbol.KeyGenerator.generateUInt64Key('level'.toLowerCase());
const mosaicAddressRestrictionTransaction = Symbol.MosaicAddressRestrictionTransaction.create(
deadline,
mosaicId,
key, // restrictionKey
player, // Address
Symbol.UInt64.fromUint(1), // newRestrictionValue
networkType,
Symbol.UInt64.fromHex('FFFFFFFFFFFFFFFF'), // previousRestrictionValue
maxFee
);
// Default player skill
// Assign mosaic ID: 73E60A6DD1D16FDD, key: punch, value: 1
...
...
// Assign mosaic ID: 0015F52218DA969C, key: kick, value: 0
...
...
// Required upgrade
// Assign mosaic ID: 6A53A4FB36203CCE, key: fire_spell, value: 0
...
...
Initial game
Finally, we have all assets and restrictions set up on the chain.
Dungeon master address stat : [TBRNNSHKSOSSIH3ZUGW3MFVAUZ2TBIHL7NE2FVA](https://sym-test-09.opening-
line.jp:3001/restrictions/mosaic?entryType=0&targetAddress=TBRNNSHKSOSSIH3ZUGW3MFVAUZ2TBIHL7NE2FVA)
Player address stat : TC7FDHJZ53SK5OFUIL2C6X25KQYBXHBCCWCSJGA
Let's transfer assets to the player. The system will detect from a player account on-chain and set players level: 1, punch: active
Game play
Let's imagine that many possibilities will happen in the game, such as players getting affected by poison
, exhausted
or power boosted
. It could affect player stat in the next round.
Players will be rewarded 1 skill point if they win a round, and they can use it to upgrade skills.
There are a lot of things you can think about to make the game more fun!
How to upgrade and downgrade skills?
The dungeon master can always update the mosaic/address restrictions to control player stats.
Example: player using skill point to upgrade kick
.
- Dungeon master can update player address restriction
value 0 -> value 2
- Dungeon master transfer
mosaicId: 0015F52218DA969C, amount: 1
to player.
Example: player gets the poison
status and the punch
skill is disabled.
- Dungeon master revokes
mosaicId: 73E60A6DD1D16FDD, amount: 1
to the player.
##Summary
The mosaics restriction feature is really amazing, there are a lot more things we can do with it.
Setting up on-chain restrictions is such an easy thing on Symbol.
In the beginning it may feel like the restriction feature is complicated, but after you try setting up some of the mosaic restrictions, you will feel it's actually straightforward and easy to understand.
I hope this article will help you understand more about mosaic restriction, and how to use it in your game.
I'm not a game developer, but I definitely love to play games! If you found this idea is cool and you would like to build on it, let me know and we can discuss more in Discord.
If you would like to know more about Symbol, please join the Discord.
Special thanks to Captain Jaguar Gimre Hatchet for provided feedback and help.
Special thanks to Xavi for reviewing this article.