Dapp ID Full Guide: creation, fees, centralized replenishment
What will you learn from this guide?
How to create your Dapp ID
How to interact with contracts inside a Dapp
How to interact with contracts of other Dapps
How to use the centralized replenishment mechanism for the Dapp ID contracts
Prerequisites
Configure CLI tool
In this guide, we will use the test network at shellnet.ackinacki.org
.
We need to specify the blockchain endpoint for deployment:
Create your first Dapp ID
You create a new Dapp ID when you deploy a contract using an external message. The address of this contract becomes the Dapp ID of your system.
If your Dapp consists of multiple contracts, you need to implement your system so that all the contracts are deployed either from the root contract or its children.
In this guide, we will use the helloWorld
contract to demonstrate the features of a Dapp ID.
Prepare contract source code
Let's create a folder for our project and clone the repository with examples into it:
and copy the contracts
folder from there:
Compile
Compile the contract helloWorld
using TVM Solidity compiler:
The compiler produces helloWorld.tvc
and helloWorld.abi.json
to be used in the next steps.
TVM binary code of your contract is stored into helloWorld.tvc
file.
Top up with Shell
To deploy a contract, its balance must be funded with SHELL tokens.
To do this, we first need to determine its address. Let's start by generating a seed phrase and keys for your contract:
Seed phrase is printed to stdout.
Key pair will be generated and saved to the file helloWorld.keys.json
.
Write your Seed Phrase down and store it somewhere safe, and never share it with anyone. Avoid storing it in plain text or screenshots, or any other non-secure way. If you lose it, you will not be able to recover it from your Key Pair. If you lose both Seed Phrase and Key Pair you lose access to your assets. Anyone who gets it, gets full access to your assets. Also, save the file with a pair of keys in a safe place.
Now let's generate the contract address using the keys obtained earlier:
After this step, the .tvc
file will be overwritten with the specified keys.
Address of your contract in the blockchain is located after Raw address:
Save Raw address
value - you will need it to deploy your contract and to work with it.
We will refer to it as <YourAddress>
below.
To top up the balance (approx. 10 SHELL) of the helloWorld
contract, use your Multisig Wallet
and apply the following method sendTransaction
:
dest
- the transfer target address;value
- the amount of funds (nanoVMSHELL) to transfer (should be0
);cc
- the type of ECC token (SHELL has index 2) and amount (specified in nanotokens) to transfer;bounce
- bounce flag: (should befalse
);flags-
sendmsg flags (should be1
);payload
- tree of cells used as body of the outbound internal message (should be an empty string).
For example: you can use the command:
Within Dapp ID, you can transfer both ECC tokens (e.x.SHELL) and VMSHELL. For contracts of other Dapp IDs, only ECC tokens can be transferred.
Check the state of the pre-deployed contract. It should be Uninit
:
You will see something similar to the following:
Deploy
When you deploy a contract with external message contract must exchange some amount of SHELL into VMSHELL during the contract deployment. To do this, the contract’s constructor must call the VM command gosh.cnvrtshellq(uint64 value).
CNVRTSHELLQ converts SHELL to VMSHELL at a 1:1 ratio
Q in the end stands for ‘quiet’ which means that if there is not enough Shell, it will not throw an exception.
If the account balance does not have the required number of tokens, the exchange will be made for the entire available amount. That is, MIN(available_tokens, want_to_convert_amount).
Go back now and check the constructor code of helloWallet
- you will find this command.
Lets deploy helloWorld
and create our first Dapp ID with this command:
Check the contract state again. This time, it is should be
Active
.
View contract information with Explorer
Go to testnet Acki Nacki explorer and search for in search bar. Open your account page. You will need it later to see its transactions and messages, that we will produce in the next steps.
Explore contract information with GraphQL
Go to GraphQL playground.
Enter the information in the left pane and click the "Run" button (replace the contract's address with the one you obtained in the previous steps).
The dapp_id
field will contain the identifier of your decentralized contract system on the Acki Nacki blockchain.
You will see something that looks similar following:
Run a getter
The helloWorld
contract features a get-method: timestamp
. Let's call it and check the result:
result:
Call a method on-chain
The helloWorld contract has a touch
method. Let’s run it on-chain using the call
command:
Call the get-method timestamp
again to verify that the timestamp has been updated:
Add another contract to your Dapp ID
To add a contract to the Dapp ID system, it must be deployed via an internal message through the root contract of the Dapp ID, which in our case is helloworld
.
In our case, this can be done using the following function:
stateInit
- the contract code plus data (tvc in base64);initialBalance
- the amount of funds to transfer;payload
- a tree of cells used as the body of the outbound internal message;
Let’s add another contract to our Dapp ID. For this, we’ll use a copy of the helloWorld
contract and name it helloUniverse:
Now, let’s calculate the address of the helloUniverse
contract using the existing key pair.
And we get the same address as the helloWorld
contract.
To avoid this, it’s essential to use a different key pair. Let’s generate a new seed phrase with a fresh pair of keys:
Let’s calculate the address and prepare the TVC file for the new contract:
To deploy a new contract, you need to prepare its stateInit
and a deployment message body.
To obtain the stateInit
, execute the following command:
Since the result can be quite large, let’s save this value in a variable: HW_STATE_INIT
.
Let’s generate the message body with a constructor call for the internal deployment of the contract from another contract.:
We’ll need to place the Message body
field value into the deployment payload.
Now we can call deployNewContract
function.
In our case, the command will be as follows:
This way, the new contract within the DAPP ID will be deployed through an internal message.
Check the contract state:
Note that the helloUniverse
contract shares the same DAPP ID as the helloWorld
contract.
Call a contract inside Dapp ID
To transfer SHELL, within the same DAPP ID, use the function sendShell
dest
- the target address to receive the transfer;value
- the amount of SHELL tokens to transfer.
To transfer VMSHELL, within the same DAPP ID, use the function sendVMShell
dest
- the target address to receive the transfer;amount
- the amount of VMSHELL tokens to transfer.bounce
- bounce flag: (should befalse
);
Let's call the touch
function in helloUniverse
through the helloWorld
contract.
But first, let's check the value of the timestamp
variable in the helloUniverse
contract.
result:
To call the touch
function in helloUniverse
, we’ll invoke the callExtTouch
method in helloWorld
.
addr
- is the address of the contract in which the method is called.
In our case, the command will be as follows:
Then, let's check if the timestamp
has changed in the helloUniverse
contract:
Output: The timestamp has changed.
The fee distribution for message transfers within a single DAPP ID is described in the "Fees" section.
Call a contract from another Dapp ID
Let's deploy the helloWorld2
contract the same way as helloWorld
.
The helloWorld
and helloWorld2
contracts are deployed with different Dapp IDs.
Let’s check the current timestamp
in the helloWorld2
contract:
result:
To call the touch
function in helloWorld2
, we’ll invoke the callExtTouch
method in helloWorld
.
addr
- is the address of the contract in which the method is called.
In our case, the command will be as follows:
If the message is sent to a different Dapp ID, all VMSHELL tokens (in msg_value)
are set to zero.
Then, let's check if the timestamp
has changed in the helloWorld2
contract:
Output: The timestamp has changed.
The fee distribution for message transfers between different DAPP IDs is described in the "Fees" section.
Centralized replenishment of contracts within a Dapp ID
In the Acki Nacki network, developers can implement a mechanism that allows contracts, grouped under a single Dapp ID, to replenish their balances directly from the shared balance of the entire Dapp ID. This is achieved using the TVM instruction gosh.mintshell
, enabling seamless internal allocation of resources across the contracts within a single Dapp.
How it works:
During the block assembly, the Block Keeper (BK) collects information about all calls to the TVM instruction gosh.mintshell
in the transactions included in the block. For each instruction call, the Dapp ID of the contract is determined, and the presence of a DappConfig
contract for that Dapp ID is verified. The total amount of tokens specified in the instruction calls is then debited from the balance of the DappConfig
contract. Correspondingly, the appropriate amount of VMSHELL
tokens is credited to the balances of the contracts for which this instruction was invoked.
To ensure the system functions correctly and resources are managed automatically, follow these steps:
Step 1: Deploying the DappConfig contract
The DappConfig
contract is an informational contract that holds data about the amount of VMSHELL available for a specific Dapp ID. It is deployed once per Dapp ID. DappConfig
contracts do not have an owner, and anyone can fund them.
Actions to Perform:
To deploy the
DappConfig
contract, you need to know the Dapp ID. You can obtain it as follows:
For example, our HelloWorld contract will have the following Dapp ID:
To deploy a
DappConfig
contract, you need to call thedeployNewConfigCustom
function of theDappRoot
contract:
DappRoot
is a system contract that manages DappConfig
contracts, including their deployment and the calculation of the DappConfig
address for a given Dapp ID.
The address of the DappRoot
contract is: 0:9999999999999999999999999999999999999999999999999999999999999999
dapp_id
- the indentifier of your DAPP
The value of the Dapp ID is specified with the 0x
prefix.
Example command to deploy the DappConfig contract:
Upon deployment, the contract's balance is credited with 15 VMSHELL tokens.
Use the getConfigAddr method to retrieve the address of the deployed
DappConfig
contract:
dapp_id
- the indentifier of your Dapp
Example command to get the address of the DappConfig contract:
result:
To enable the auto-replenishment system, you need to fund the balance with SHELL tokens.
To fund the balance of the DappConfig
contract, you can call the sendTransaction
method in the Multisig contract as described earlier.
Example command to transfer 10 SHELL from the balance of the Multisig contract to the balance of the DappConfig
contract:
Step 2: Enabling Automatic Replenishment
To automate the funding process, add balance check and token minting logic to your DAPP ID contracts.
Use the TVM instruction gosh.mintshell
which mints some VMSHELL tokens, allowed by the available credit in the DappConfig contract for this Dapp ID:
value
- amount of nanoVMSHELL to mint
For example, let's use the getTokens()
function in the HelloWorld contract:
This function mints 100 VMSHELL tokens automatically if the balance falls below the specified threshold.
Let's try:
Check the balance of the HelloWorld contract:
Result: the balance is 0.465631997 VMSHELL tokens.
Using the getDetails()
method, you can view the available balance of the DappConfig contract.
Result: the balance is 500.
Thus, when using the touch()
method, the getTokens()
function will be called. This function will check the balance of the HelloWorld contract, and since it is less than 100 VMSHELL, it will trigger a replenishment:
Call the touch()
function:
and check the contract balance:
As a result, we see that the balance has been replenished by 100 VMSHELL and now amounts to 100.460237956 VMSHELL.
And checking the available balance of the DappConfig contract will also show that it has decreased by 100 tokens:
When calling getDetails()
, you retrieve the available balance in SHELL tokens.
In contrast, when checking the account data, the ecc
field will show the cumulative amount of tokens ever transferred to this balance.
This behavior is relevant only for the DappConfig
contract.
Fees
When transferring messages between contracts under the same Dapp ID, fees are distributed as follows:
To create an outgoing message, payment is deducted from the sender’s balance.
For relaying a message, payment is taken either from the sender's balance or deducted from the message balance (
msg.value
). The specific behavior depends on the flags set during transmission, as described here.Processing an incoming message is paid from the message balance (
msg.value
) and, iftvm.accept()
is used, from the recipient’s balance.
When transferring messages between contracts under different Dapp IDs, the entire amount of tokens specified in msg.value
(VMSHELL) is nullified. In this case, the recipient contract must assume responsibility for executing the initiated transaction by calling tvm.accept()
within the invoked function. Otherwise, the transaction will fail with the error Not enough funds
.
Last updated