Getting started
We prepared this document in the hopes that it will answer most of your questions regarding the matter of implementing native NFTs in the context of your Solidity-centric application.
The premise of this document is as follows (for whom this document is intended):
- your non-native NFT project has been principally developed on and for an Ethereum-based, general-contract, ERC-721 concept NFT environment
- you wish to transform your ERC-721 collection to a native version of the NFT because you desire to implement the advanced features provided to the NFTs in the Unique network in you application
- you wish to have access to the methods that provide these features in your Solidity contracts if necessary
- you do not want to write complex code to use the new features
- you need to retain any already created Solidity contracts that provide crucial application functionality and you do not want to make radical changes to your contracts to be able to adapt your existing code
- you need access to both Ethereum and Substrate address spaces so your existing Solidity contracts can interact in both spaces
- you wish to be able to provide access to your app to wallets in both address spaces (Metamask, Subwallet, Talisman, Wallet Connect wallets, etc) without a complex recoding of your contracts
Since your core functionality is provided via Solidity contracts, we have taken care to provide you with as much examples and instructions as possible to help you navigate the transition from the existing ERC-721 tokens to the Unique native model.
TIP
One note to be mindful of is: the most efficient way of accomplishing this goal is by using the Unique SDK 2.0. It will provide you with the framework, the tools and the methods to make this fast and easy, so do brush up on your Typescript if you have been away from it for a while. It will save you an enormous amount of time. To make it as real-world as possible, we have implemented our SDK calls in a React framework so you can gauge how the SDK functions in a UI context and you can see it all in the video listed below.
That said...
Once you read through this document, we propose that you attempt to replicate the following steps to master your challenge:
- Create a wallet selector that uses any of {Polkadot.js wallet, Subwallet, Talisman, Metamask, Wallet Connect} wallet to connect to your app.
- Create a collection using the SDK
- Mint a token using the SDK
- Write a Solidity contract in the EVM to use with the native NFT to change some NFT attribute, for example
- Call that contract from the SDK
- Nest a token
ALL the examples for this except point 6 are shown in this video.
To nest, you just send a native token to a native token address as though it is a wallet using a simple send call via the SDK. That's all. Nesting doesn't even have a dedicated method. It is a simple send of a token to another tokens address.
Read on...
EVM and Substrate
There are two types of accounts – Substrate (5Grw...) and EVM (0x...) In terms of calling contracts:
- EVM accounts operate the same way as in Ethereum
- Substrate accounts cannot call EVM contracts directly because EVM works only with EVM accounts. But they can do it through the
evm.call
extrinsic (in SDK -sdk.evm.send
method). For contract,msg.sender
will be Substrate's account mirror – EVM account. To calculate the Substrate account mirror, use theAddress
utility from@unique-nft/utils
import { Address } from "@unique-nft/utils";
const ethMirror = Address.mirror.SubstrateToEthereum('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
// 0xd43593c715Fdd31c61141ABd04a99FD6822c8558
or play with the converter in the documentation.
It is essential to understand that mirror calculation is a one-way operation. You cannot calculate the origin address from its mirror.
import { Address } from "@unique-nft/utils";
const ethMirror = Address.mirror.SubstrateToEthereum('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
// 0xd43593c715Fdd31c61141ABd04a99FD6822c8558
const subMirrorBack = Address.mirror.EthereumToSubstrate('0xd43593c715Fdd31c61141ABd04a99FD6822c8558');
// !!! different address 5FrLxJsyJ5x9n2rmxFwosFraxFCKcXZDngRLNectCn64UjtZ != 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
It is also worth noting that the EVM mirror cannot be controlled directly (e.g., it cannot be added to MetaMask).
CrossAddress
struct
To make direct interaction between contracts and Substrate accounts possible, we support the CrossAddress
struct in Solidity. This struct represents the "Ethereum or Substrate" account.
// Solidity
struct CrossAddress {
address eth;
uint256 sub;
}
- For the EVM account, set the
eth
property with the EVM address (0x...), and thesub
should be 0. - For the Substrate account, set the
sub
property with the Substrate public key (not address!). Theeth
property should be equal to Ethereum zero address (0x000...00000);
To calculate Substrate public key from an address in Javascript, use Address
utils.
import { Address } from "@unique-nft/utils";
const publicKey = Address.extract.SubstratePublicKey("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY");
or convert the address to CrossAccount directly:
const cross = Address.extract.ethCrossAccountId(
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
// or "0xd43593c715Fdd31c61141ABd04a99FD6822c8558"
);
A lot of examples of how to use CrossAddress with EVM and Substrate accounts can be found in recipes of unique-contracts repo. For example:
- minter contract
- how to call the minter contract with EVM account
- how to call the minter contract with Substrate account
Signers and Wallets
Documentation section on how to build applications, and specifically connecting accounts
It could be much easier to understand playing with code. Here is a react template you can use.
SDK
SDK is only for Substrate accounts (remember that Substrate accounts can invoke contracts). The build section of the documentation explains all the needed concepts.
Why it makes sense to use the schema 2.0 and why you don't have to if you do not need it.
Unique Network implements NFTs natively – they are not contracts.
And yes, because of EVM support, plain ERC-721 NFTs can be created. However, no wallets, marketplaces, or other UIs in the ecosystem track this type of NFTs.
We support Unique Schema 2.0, an OpenSea compatible, on-chain metadata format, to make your metadata readable for all interfaces. So, you need to stick this format until you understand why not.
However, there is good news—if you use SDK or unique contracts, you don't need to understand this format in detail. You only need to understand its features. Everything else is handled for you.
The reference section in the documentation explaining all the features of unique schema.
The js library for the unique schema. You don't need it if you use SDK.
How to call EVM contracts using Substrate account and SDK
- Documentation
- EVM workshop
- This function covers how to invoke contracts
Long story short:
- You save the contract's ABI as JSON file and import it
- You call
sdk.evm.send
and pass abi, contract address, function name, and params
To change a schema 2.0 compliant data attribute from EVM
Use @unique-nft/contracts, TokenManager.sol
EVM workhop demonstrates how to do this.
- How do we mutate token image
- How do we mutate token attributes
- How do we call these solidity functions on the UI
Please remember to view this video.