Skip to content

Latest commit

 

History

History
616 lines (488 loc) · 20.3 KB

Contract-Interaction-Tutorial.md

File metadata and controls

616 lines (488 loc) · 20.3 KB

Contract Interaction and Events

This tutorial examines the events emitted by the example NFT contract in this repository. These events occur when some operation (like minting tokens) succeeds.

There are seven main event types for the Casper NFT contract:

We will go through each one with examples in the next sections.

Prerequisite

Make sure you have installed the NFT contract on the Casper Network.

Enabling the Event Stream

To trigger the events related to the contract, you must run the casper-contracts-js-clients/e2e/cep47/usage.ts file using NodeJS.

This is the command to run the file:

npm run e2e:cep47:usage

You will see the output as below:

Console output for deploying the token event stream
... Account Info:
{
  "_accountHash": "account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803",
  "namedKeys": [
    {
      "name": "bdk_nft_contract_contract_hash",
      "key": "hash-a47d35d835a5fa8a1bcd55a4426dc14e21da9b876c1617742f18813737a4ece0"
    },
    {
      "name": "bdk_nft_contract_contract_hash_wrapped",
      "key": "uref-ff9b562d357d9a258acb2b3798f82c6ec5db49a8852e2e96b0ed4b1faf873206-007"
    },
    {
      "name": "contract_package_hash",
      "key": "hash-2468facdc9a6f324f8442584fd46d911e3ac9b434dfa79435567bf71f9b8bd23"
    }
  ],
  "mainPurse": "uref-a33e25cb1e6baa38e8306dba0492183c65cb41db3dbe8f69546868a4c0cfd0d9-007",
  "associatedKeys": [
    {
      "accountHash": "account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803",
      "weight": 1
    }
  ],
  "actionThresholds": {
    "deployment": 1,
    "keyManagement": 1
  }
}
... Contract Hash: hash-a47d35d835a5fa8a1bcd55a4426dc14e21da9b876c1617742f18813737a4ece0
... Contract Package Hash: hash-2468facdc9a6f324f8442584fd46d911e3ac9b434dfa79435567bf71f9b8bd23
... Contract name: bdk_nft_token
... Contract symbol: BDK47
... Contract meta: [[{"isCLValue":true,"data":"1"},{"isCLValue":true,"data":"bdk-nft-1"}],[{"isCLValue":true,"data":"2"},{"isCLValue":true,"data":"bdk-nft-2"}]]
... Total supply: 0


The casper-cep47-js-clients repository provides a CEP47EventsParser which can be used in combination with the JS-SDK’s EventStream.

Example code:

const es = new EventStream(EVENT_STREAM_ADDRESS!);

es.subscribe(EventName.DeployProcessed, (event) => {
  const parsedEvents = CEP47EventParser({
    contractPackageHash, 
    eventNames: [
      CEP47Events.MintOne,
      CEP47Events.TransferToken,
      CEP47Events.BurnOne,
      CEP47Events.MetadataUpdate,
      CEP47Events.ApproveToken
    ]
  }, event);

  if (parsedEvents && parsedEvents.success) {
    console.log("*** EVENT ***");
    console.log(parsedEvents.data);
    console.log("*** ***");
  }
});

es.start();

Minting tokens

The token minting process creates NFTs. The Casper virtual machine executes the code stored in the smart contract and maps the item to a blockchain token containing certain attributes known as metadata. The creator's public key serves as a certificate of authenticity for that particular NFT.

Mint new tokens

The mint method requires input parameters like recipient address, token ID, token metadata, and the payment amount to generate the NFT token. The list of input parameters is specified in the .env.cep47 file and can be customized for each NFT implementation. This method will execute those parameters and generate the deploy object as mintDeploy. Then that deploy object is sent to the network via the node address to get the mintDeployHash. The console will output the deploy hash. Then when minting got confirmed through event stream - name of the event, CL values, and the token mint successful message will be printed.

The code snippet below is executing the mint method. In this example, a token with ID 1 is minted with the metadata number and one.

const mintDeploy = await cep47.mint(
    KEYS.publicKey,
    ["1"],
    [new Map([['number', 'one']])],
    MINT_ONE_PAYMENT_AMOUNT!,
    KEYS.publicKey,
    [KEYS]
  );

Send the deploy to the network

Send the 'mintDeploy' to the network via the node address and get the deploy hash.

 const mintDeployHash = await mintDeploy.send(NODE_ADDRESS!);

Check the account balance

After minting the token with ID 1, you can check the balance of tokens assigned to a specific public key using the balanceOf method. This method returns the number of tokens stored in this account.

const balanceOf1 = await cep47.balanceOf(KEYS.publicKey);

Check token ownership

You can check the token owner by calling the getOwnerOf method. This method takes the token ID as the input parameter and returns the prefixed account hash of the account owning this specific token. Note: the prefix is account-hash-.

const ownerOfTokenOne = await cep47.getOwnerOf("1");

Check the token index and metadata

You can also check the token metadata, the index of the token, and the token ID using the methods below.

const tokenOneMeta = await cep47.getTokenMeta("1");
const indexByToken1 = await cep47.getIndexByToken(KEYS.publicKey, "1");
const tokenByIndex1 = await cep47.getTokenByIndex(KEYS.publicKey, indexByToken1);
Console output for token minting
... Mint token one
...... Mint deploy hash:  bd6f088d9687b51edf7d0669a1153365e7a9bd2b67064762979d03a21fd7aea2
*** EVENT ***
[
  {
    name: 'cep47_mint_one',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  }
]
*** ***
...... Token minted successfully
...... Balance of master account:  1
...... Owner of token one:  account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803
...... Token five metadata:  Map(1) { 'number' => 'one' }
...... index of token one:  0
...... token one id:  1


Burning tokens

The token burning process permanently removes the tokens from circulation within the blockchain network. The tokens are sent to a wallet address called "burner" or "eater" that cannot be used for transactions other than receiving these tokens. Even though the tokens will still exist on the blockchain, there will be no way of accessing them.

Execute the burn method

The code snippet below will execute when calling the burn method.

const burnDeploy = await cep47.burn(
    KEYS.publicKey,
    ["1"],
    MINT_ONE_PAYMENT_AMOUNT!,
    KEYS.publicKey,
    [KEYS]
  );

Send the deploy to the network

const burnDeployHash = await burnDeploy.send(NODE_ADDRESS!);

The burn method executes given the values passed in and generates a burnDeploy object. Then, the deploy is sent to the network. When the burn operation got confirmed by the event stream, the name of the event and corresponding CL values, and a message indicating success or failure got printed.

Console output for token burning
... Burn token one

... Burn deploy hash:  76761cc2e1b51cb2fc6e91c61adc1139c9466316fd8bf98a4f2de05b22a31b63
... Account Info:
{
  "_accountHash": "account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803",
  "namedKeys": [
    {
      "name": "bdk_nft_contract_contract_hash",
      "key": "hash-a47d35d835a5fa8a1bcd55a4426dc14e21da9b876c1617742f18813737a4ece0"
    },
    {
      "name": "bdk_nft_contract_contract_hash_wrapped",
      "key": "uref-ff9b562d357d9a258acb2b3798f82c6ec5db49a8852e2e96b0ed4b1faf873206-007"
    },
    {
      "name": "contract_package_hash",
      "key": "hash-2468facdc9a6f324f8442584fd46d911e3ac9b434dfa79435567bf71f9b8bd23"
    }
  ],
  "mainPurse": "uref-a33e25cb1e6baa38e8306dba0492183c65cb41db3dbe8f69546868a4c0cfd0d9-007",
  "associatedKeys": [
    {
      "accountHash": "account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803",
      "weight": 1
    }
  ],
  "actionThresholds": {
    "deployment": 1,
    "keyManagement": 1
  }
}
... Contract Hash: hash-a47d35d835a5fa8a1bcd55a4426dc14e21da9b876c1617742f18813737a4ece0
... Contract Package Hash: hash-2468facdc9a6f324f8442584fd46d911e3ac9b434dfa79435567bf71f9b8bd23
... Contract name: bdk_nft_token
... Contract symbol: BDK47
... Contract meta: [[{"isCLValue":true,"data":"1"},{"isCLValue":true,"data":"bdk-nft-1"}],[{"isCLValue":true,"data":"2"},{"isCLValue":true,"data":"bdk-nft-2"}]]
... Total supply: 0

*************************
*** EVENT ***
[
  {
    name: 'cep47_burn_one',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  }
]
*** ***
... Token burned successfully


Minting copies of tokens

The method mintCopies creates several tokens with different IDs but the same metadata. The process is the same as minting one token but with multiple IDs and metadata. The payment amount also changes accordingly.

Execute mintCopies

The code snippet below executes when calling the mintCopies method.

const mintCopiesDeploy = await cep47.mintCopies(
    KEYS.publicKey,
    ["2", "3", "4", "5"],
    new Map([['number', 'from-series']]),
    4,
    MINT_COPIES_PAYMENT_AMOUNT!,
    KEYS.publicKey,
    [KEYS]
  );

Send the deploy to the network

const mintCopiesDeployHash = await mintCopiesDeploy.send(NODE_ADDRESS!);

This method takes multiple token IDs and metadata, the token count, and other general input parameters to generate the mintCopiesDeploy object. Then it sends the deploy to the network. Since it is a series of tokens, we will check the token balance, owner, metadata, and index.

Check the token balance

This method will check the balance of tokens in the master account:

 const balanceOf2 = await cep47.balanceOf(KEYS.publicKey);

Check the token owner

This method checks the owner of the token with ID 5:

let ownerOfTokenFive = await cep47.getOwnerOf("5");

Check the token metadata

This method checks the metadata of the token with ID 5:

const tokenFiveMeta = await cep47.getTokenMeta("5");
Console output for minting copies of a token
... Mint copies #1

...... Mint deploy hash:  e1b75c38665463da71062983b7533dc0018991487ac80a4ed8b7838f5e258ab9
... Mint token one

...... Mint deploy hash:  bd6f088d9687b51edf7d0669a1153365e7a9bd2b67064762979d03a21fd7aea2
*** EVENT ***
[
  {
    name: 'cep47_mint_one',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  }
]
*** ***
...... Token minted successfully
...... Balance of master account:  1
...... Owner of token one:  account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803
...... Token five metadata:  Map(1) { 'number' => 'one' }
...... index of token one:  0
...... token one id:  1

^[*** EVENT ***
[
  {
    name: 'cep47_mint_one',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  },
  {
    name: 'cep47_mint_one',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  },
  {
    name: 'cep47_mint_one',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  },
  {
    name: 'cep47_mint_one',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  }
]
*** ***
...... Token minted successfully
...... Balance of master account:  4
...... Owner of token five:  account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803
...... Token five metadata:  Map(1) { 'number' => 'from-series' }
...... index of token five:  3
...... token five id:  5


Transferring tokens

This method transfers NFT token(s) to other accounts. The transfer process will initiate from your account address and be sent to the selected recipient address. The recipient address will be a randomly selected account hash in this example.

Execute a transfer

The code snippet below executes when calling the transfer method.

Create the recipient address from a random number and assign it to transferOneRecipient.

const transferOneRecipient = CLPublicKey.fromHex("016e5ee177b4008a538d5c9df7f8beb392a890a06418e5b9729231b077df9d7215");

Use the token with ID 2 and the transferOneRecipient address along with other input parameters to generate the transferOneDeploy object, and send that deploy to the network. This completes the transfer event call.

const transferOneDeploy = await cep47.transfer(
    transferOneRecipient, 
    ["2"], 
    TRANSFER_ONE_PAYMENT_AMOUNT!, 
    KEYS.publicKey, 
    [KEYS]);

Send the deploy to the network

 const transferOneHash = await transferOneDeploy.send(NODE_ADDRESS!);

Finally, check the owner of the token with ID 2. Confirm that the owner has changed from your account hash to the recipient account hash.

ownerOfTokenTwo = await cep47.getOwnerOf("2");
Console output for transferring tokens
... Transfer #1

...... Owner of token "2" is account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803
...... Transfer from account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803 to account-hash-ec0125ebcf79ab482046647049a26152166a2ed260f4ac95f279c77295b55212
...... Transfer #1 deploy hash:  e52f3cc6969fcc1641b677a66ef90c54c3368e7f141b26a3f7d4a2ba939412c2
*** EVENT ***
[
  {
    name: 'cep47_transfer_token',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  }
]
*** ***
...... Token transfered successfully
...... Owner of token "2" is account-hash-ec0125ebcf79ab482046647049a26152166a2ed260f4ac95f279c77295b55212


Approving tokens

This method is used to hand over the token transfer capability to another account. In this example, the new owner's public key is created before the transfer. Then the new account will perform the token transfer.

Execute the approve method

The following code snippet will execute when calling the approve method.

Create the allowedAccount recipient address using the KEYS_USER variable from the .env.cep47 file. This variable indicates the new spender of the token.

const allowedAccount = KEYS_USER!.publicKey;

Next, execute the approve method, create the approveDeploy object, and send it to the network. Here, the token with ID 5 will be used for approval.

  const approveDeploy = await cep47.approve(
    allowedAccount,
    ["5"],
    MINT_ONE_PAYMENT_AMOUNT!,
    KEYS.publicKey,
    [KEYS]
  );

Send the deploy to the network

const approveDeployHash = await approveDeploy.send(NODE_ADDRESS!);

Check the new account

After generating the deploy hash for the approval, you can check which account is allowed to do the approval. It will return the account hash of the account owning this specific token.

const allowanceOfTokenFive = await cep47.getAllowance(KEYS.publicKey, "5");
Console output for token approval
... Approve

...... Approval deploy hash:  940868f10945325e70ba6955c8edfe047c78ad71529bac86989d056d8ca1f26c
*** EVENT ***
[
  {
    name: 'cep47_approve_token',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  }
]
*** ***
...... Token approved successfully
...... Allowance of token 5 account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803


Transferring tokens from another account

Here, you will transfer tokens to another account. You will use some randomly generated account addresses to check the behavior of this method.

Execute the transferFrom method

The following code snippet will execute when calling the transferFrom method.

First, check the owner of the token with ID 5.

ownerOfTokenFive = await cep47.getOwnerOf("5");

Then, generate the recipient address from a random number.

const transferFromRecipient = CLPublicKey.fromHex("019548b4f31b06d1ce81ab4fd90c9a88e4a5aee9d71cac97044280905707248da4");

Then, generate the transferFromDeploy deploy object using the new recipient address and the rest of the input parameters, complete the transfer from another account process, and send it to the network. This completes the transfer-from event call.

const transferFromDeploy = await cep47.transferFrom(
    transferFromRecipient,
    KEYS.publicKey,
    ["5"],
    TRANSFER_ONE_PAYMENT_AMOUNT!,
    KEYS_USER.publicKey, [KEYS_USER]);

Send the deploy to the network

const transferFromHash = await transferFromDeploy.send(NODE_ADDRESS!);

Check the new owner

Finally, check the owner of the token with ID 5 and note that it has changed to the new recipient.

ownerOfTokenFive = await cep47.getOwnerOf("5");
Console output for transferring tokens from another account
... Transfer From #1

...... Owner of token "5" is account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803
...... Transfer from account-hash-179cd876d5c74317cce9c48d718a040e6e909063d7d786de0c5c6421a09fa803 to account-hash-fc36989e547ec1eba1d8aea840ffabbcbe7d27fb249801870551160eaa014306
...... Transfer From #1 deploy hash:  3a1e3632a401af565fad0e6c131e5347392e191e3b3c1e9a6f9c467409e055a0
*** EVENT ***
[
  {
    name: 'cep47_transfer_token',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  }
]
*** ***
...... Token transfered successfully
...... Owner of token "5" is account-hash-fc36989e547ec1eba1d8aea840ffabbcbe7d27fb249801870551160eaa014306


Updating token metadata

This method will update the metadata of a selected token.

Execute the updateTokenMeta method

The following code snippet will execute when calling the update metadata method.

First, check the metadata of the token with ID 4.

let tokenFourMeta = await cep47.getTokenMeta("4");

Then, execute the updateTokenMeta method, generate the updateMetadataDeploy object, and send it to the network. This completes the update metadata call.

const updateMetadataDeploy = await cep47.updateTokenMeta(
    "4",
    new Map([["name", "four"]]),
    TRANSFER_ONE_PAYMENT_AMOUNT!,
    KEYS_USER.publicKey, 
    [KEYS_USER]
  );

Send the deploy to the network

const updateMetadataHash = await updateMetadataDeploy.send(NODE_ADDRESS!);

Again, check the metadata of the token with ID 4 and confirm the data has changed.

tokenFourMeta = await cep47.getTokenMeta("4");
Console output for updating token metadata
... Update metadata of token 4

...... Token 4 metadata:  Map(1) { 'number' => 'from-series' }
...... Update metadata deploy hash:  1b5d31481bb8177d798a8368e93d5f92bf34cc493bde8caf8a078d753cdd28ec
*** EVENT ***
[
  {
    name: 'cep47_metadata_update',
    clValue: t { isCLValue: true, refType: [Array], data: [Array] }
  }
]
*** ***
...... Token metadata updated successfully
...... Token 4 metadata:  Map(1) { 'name' => 'four' }