Quick start developing with TIP-4
During the following of this guide's code-listings you can meet keywords like pragma ever-solidity
or keyword ever
as a unit of the transfer value. It will be changed to venom
soon. Follow the news and updates.
Source Code​
You can inspect the source code of TIP-4 token implementation by link.
How to deploy your own NFT collection​
You need to have an installed Smart Contract Development Environment. If you haven't already, follow this tutorial.
Initialize your NFT project​
npx locklift init --path my-first-nft
> [INFO] New Locklift project initialized in .
> [INFO] Installing required dependencies...
> [INFO]
> added 181 packages, and audited 182 packages in 13s
> 23 packages are looking for funding
> run `npm fund` for details
> found 0 vulnerabilities
> [INFO] LockLift initialized in my-first-nft happy hacking!
Install dependencies​
TIP-4 is accessible from npm. Let's install it
npm i --save-dev @broxus/tip4
Implement base contracts​
Next, you should implement two interfaces in two contracts. Firstly, let's deal with Nft contract. The only thing we should do for basics is implementing `TIP4_1Nft``
pragma ever-solidity >= 0.62.0;
pragma AbiHeader expire;
pragma AbiHeader pubkey;
// importing all standards bases
import '@broxus/tip4/contracts/TIP4_1/TIP4_1Nft.tsol';
import '@broxus/tip4/contracts/TIP4_2/TIP4_2Nft.tsol';
import '@broxus/tip4/contracts/TIP4_3/TIP4_3Nft.tsol';
contract Nft is TIP4_1Nft, TIP4_2Nft, TIP4_3Nft {
// just call constructors of all implemented classes
constructor(
address owner,
address sendGasTo,
uint128 remainOnNft,
string json, // for TIP-4.2
TvmCell codeIndex, // for TIP-4.3
uint128 indexDeployValue, // for TIP-4.3
uint128 indexDestroyValue // for TIP-4.3
) TIP4_1Nft(
owner,
sendGasTo,
remainOnNft
) TIP4_2Nft (
json
) TIP4_3Nft (
indexDeployValue,
indexDestroyValue,
codeIndex
)
public {
}
Now we should go for the Collection contract. We should implement TIP4_1Collection
and write some method for NFT deploying.
pragma ever-solidity >= 0.62.0;
pragma AbiHeader expire;
pragma AbiHeader time;
pragma AbiHeader pubkey;
import "@broxus/tip4/contracts/TIP4_2/TIP4_2Collection.tsol";
import "@broxus/tip4/contracts/TIP4_3/TIP4_3Collection.tsol";
import "@broxus/contracts/contracts/access/InternalOwner.tsol";
import './Nft.tsol';
contract Collection is TIP4_2Collection, TIP4_3Collection {
uint64 static nonce_;
constructor(
TvmCell codeNft,
TvmCell codeIndex,
TvmCell codeIndexBasis,
address owner,
uint128 remainOnNft,
string json
)
public
TIP4_1Collection(codeNft)
TIP4_2Collection(json)
TIP4_3Collection(codeIndex, codeIndexBasis
) {
tvm.accept();
tvm.rawReserve(1 ever, 0);
owner = msg.sender;
}
function codeDepth() public view returns(uint16) {
return (_buildNftCode(address(this)).depth());
}
function _buildNftState(TvmCell code, uint256 id)
internal
pure
virtual
override (TIP4_2Collection, TIP4_3Collection)
returns (TvmCell)
{
return tvm.buildStateInit({contr: Nft, varInit: {_id: id}, code: code});
}
function mintNft(string json) external virtual {
require(msg.value > 0.4 ever, 101);
tvm.rawReserve(0, 4);
uint256 id = uint256(_totalSupply);
_totalSupply++;
TvmCell codeNft = _buildNftCode(address(this));
TvmCell stateNft = tvm.buildStateInit({
contr: Nft,
varInit: {_id: id},
code: codeNft
});
new Nft{
stateInit: stateNft,
value: 0,
flag: 128
}(
msg.sender,
msg.sender,
0.3 ever,
json, // put your json here
_codeIndex, // for TIP-4.3
_indexDeployValue, // for TIP-4.3
_indexDestroyValue // for TIP-4.3
);
}
}
The previous code uses only TIP-4.1 part of TIP-4. But it is kinda useless. To work with your NFT with full NFT experience you should implement TIP-4.2 - standard, which helps you with NFT metadata storing. Also, you will need TIP-4.3 - standard, which helps other dApps to find all your NFT with single query (GQL or JRPC). You should study the information about these standards by links. Implementation of 4.2 and 4.3 is pretty simple.
pragma ever-solidity >= 0.62.0;
pragma AbiHeader expire;
pragma AbiHeader pubkey;
// importing all standards bases
import '@broxus/tip4/contracts/TIP4_1/TIP4_1Nft.tsol';
import '@broxus/tip4/contracts/TIP4_2/TIP4_2Nft.tsol';
import '@broxus/tip4/contracts/TIP4_3/TIP4_3Nft.tsol';
contract Nft is TIP4_1Nft, TIP4_2Nft, TIP4_3Nft {
// just call constructors of all implemented classes
constructor(
address owner,
address sendGasTo,
uint128 remainOnNft,
string json, // for TIP-4.2
TvmCell codeIndex, // for TIP-4.3
uint128 indexDeployValue, // for TIP-4.3
uint128 indexDestroyValue // for TIP-4.3
) TIP4_1Nft(
owner,
sendGasTo,
remainOnNft
) TIP4_2Nft (
json
) TIP4_3Nft (
indexDeployValue,
indexDestroyValue,
codeIndex
)
public {
}
// Also, you need to implement some handlers, linked with NFT transferring
// Maybe you need to implement something special, but you can also use default handlers
function _beforeTransfer(
address to,
address sendGasTo,
mapping(address => CallbackParams) callbacks
) internal virtual override(TIP4_1Nft, TIP4_3Nft) {
TIP4_3Nft._destructIndex(sendGasTo);
}
function _afterTransfer(
address to,
address sendGasTo,
mapping(address => CallbackParams) callbacks
) internal virtual override(TIP4_1Nft, TIP4_3Nft) {
TIP4_3Nft._deployIndex();
}
function _beforeChangeOwner(
address oldOwner,
address newOwner,
address sendGasTo,
mapping(address => CallbackParams) callbacks
) internal virtual override(TIP4_1Nft, TIP4_3Nft) {
TIP4_3Nft._destructIndex(sendGasTo);
}
function _afterChangeOwner(
address oldOwner,
address newOwner,
address sendGasTo,
mapping(address => CallbackParams) callbacks
) internal virtual override(TIP4_1Nft, TIP4_3Nft) {
TIP4_3Nft._deployIndex();
}
function _beforeChangeManager(
address oldManager,
address newManager,
address sendGasTo,
mapping(address => CallbackParams) callbacks
) internal override virtual {
oldManager; newManager; sendGasTo; callbacks; //disable warnings
}
function _afterChangeManager(
address oldManager,
address newManager,
address sendGasTo,
mapping(address => CallbackParams) callbacks
) internal override virtual {
oldManager; newManager; sendGasTo; callbacks; //disable warnings
}
}
Notice, that Index (and IndexBasis) code must be precompiled! You shouldn't compile these contracts by yourself. Just take it from here, place it somewhere in your project, and add them as external contracts in your locklift config like this:
...
compiler: {
// Specify path to your TON-Solidity-Compiler
// path: "/mnt/o/projects/broxus/TON-Solidity-Compiler/build/solc/solc",
// Or specify version of compiler
version: "0.62.0",
// Specify config for extarnal contracts as in exapmple
externalContracts: {
"../path/to/precompiled/indexes": ["Index", "IndexBasis"],
}
...
Deploy action​
Let's move to deploy action. We need two scripts for this quick start: one for Collection
deploying, and the second for calling mintNft
function, that we have implemented.
import { Address } from "locklift";
async function main() {
const signer = (await locklift.keystore.getSigner("0"))!;
const nftArtifacts = await locklift.factory.getContractArtifacts("Nft");
const indexArtifacts = await locklift.factory.getContractArtifacts("Index");
const indexBasisArtifacts = await locklift.factory.getContractArtifacts("IndexBasis");
const owner = new Address('0:0000000000000000000000000000000000000000000000000000000000000000')
const { contract: sample, tx } = await locklift.factory.deployContract({
contract: "Collection",
publicKey: signer.publicKey,
initParams: {
nonce_: 0,
},
constructorParams: {
codeNft: nftArtifacts.code,
codeIndex: indexArtifacts.code,
codeIndexBasis: indexBasisArtifacts.code,
owner: owner,
remainOnNft: locklift.utils.toNano(0.2),
json: `{"collection":"tutorial"}` // EXAMPLE...not by TIP-4.2
},
value: locklift.utils.toNano(5),
});
console.log(`Collection deployed at: ${sample.address.toString()}`);
}
main()
.then(() => process.exit(0))
.catch(e => {
console.log(e);
process.exit(1);
});
import { toNano, WalletTypes } from "locklift";
// you can get this parameter as (await locklift.keystore.getSigner("0"))! if you have a seed phrase sets up in key section of locklift config
// or you can pass this parameter by cli or get them by some file reading for example
// if phrase or secret was not set up in key section, calling (await locklift.keystore.getSigner("0"))! will give you a different results from launch to lauch
// we just hardcode it here
// const COLLECTION_DEPLOY_PUBLIC_KEY = "e85f61aaef0ea43afc14e08e6bd46c3b996974c495a881baccc58760f6349300"
async function main() {
const signer = (await locklift.keystore.getSigner("0"))!;
const collectionArtifacts = locklift.factory.getContractArtifacts("Collection");
// calculation of deployed Collection contract address
const collectionAddress = await locklift.provider.getExpectedAddress(
collectionArtifacts.abi,
{
tvc: collectionArtifacts.tvc,
publicKey: signer.publicKey,
initParams: {
nonce_: 0,
}
}
);
// initialize contract object by locklift
const collectionInsance = await locklift.factory.getDeployedContract(
"Collection",
collectionAddress
);
// creating new account for Collection calling (or you can get already deployed by locklift.factory.accounts.addExistingAccount)
const { account: someAccount } = await locklift.factory.accounts.addNewAccount({
type: WalletTypes.WalletV3,
value: toNano(10),
publicKey: signer.publicKey
});
// call mintNft function
// firstly get current nft id (totalSupply) for future NFT address calculating
const {count: id} = await collectionInsance.methods.totalSupply({ answerId: 0 }).call();
await collectionInsance.methods.mintNft({ json: `{"name":"hello world"}` }).send({ from: someAccount.address, amount: toNano(1)});
const { nft: nftAddress } = await collectionInsance.methods.nftAddress({ answerId: 0, id: id }).call();
console.log(`NFT: ${nftAddress.toString()}`);
}
main()
.then(() => process.exit(0))
.catch(e => {
console.log(e);
process.exit(1);
});
Finally, we can deploy a new token to the local
network. For this, make sure the local node is running, if not follow the next command
docker run -d --name local-node -e USER_AGREEMENT=yes -p80:80 tonlabs/local-node
and run our scripts
npx locklift run -s ./scripts/1-deploy-collection.ts -n local
> [INFO] factorySource generated
> [INFO] Built
> Collection deployed at: 0:882c1f7af09efaf506ab313daecb6ce127acfab7d082e28e6dbcff839aa58bba
npx locklift run -s ./scripts/2-call-mintNft.ts -n local
> [INFO] factorySource generated
> [INFO] Built
> NFT: 0:64a4ea8fa80bf3d2ba78c0a602e39a045786a70b69e879f90e9abe2a2f7f33fe</code></pre>
Now you know how to deploy your own NFT collection and mint NFT with TIP-4 standard!