solidityhedera-hashgraph

How do you Associate/Dissociate an HTS token using EVM transaction?


Before I can receive an HTS token I need to first associate with the token ID. If I were using the JS SDK I know to do the following where the accountId is the account that I want to associate with the token.

  const associateTransaction = await new TokenAssociateTransaction()
    .setAccountId(accountId)
    .setTokenIds([tokenId])
    .freezeWith(client);

However, how do you Associate/Dissociate an HTS token using an EVM transaction?

I’ve attempted to do the following in my smart contract

function mintNft(
        address token,
        bytes[] memory metadata
    ) public payable returns(int64){

        (int response, , int64[] memory serial) = HederaTokenService.mintToken(token, 0, metadata);
        if(response != HederaResponseCodes.SUCCESS){
            revert("Failed to mint non-fungible token");
        }

        int res = HederaTokenService.associateToken(
            address(msg.sender),
            token
        );

        if(res != HederaResponseCodes.SUCCESS){
            revert("Failed to associate non-fungible token");
        }
        
        return serial[0];
    }

However, when I try to transfer the contract reverts with the following error:

TOKEN_NOT_ASSOCIATED_TO_ACCOUNT

I’d like to be able to do so within a smart contract deployed to HSCS, preferably. If that is not possible, is there another way to do so?


Solution

  • Calling associateToken on HederaTokenService System Contract is deprecated and can no longer associate to address(msg.sender). It can only associate to address(this). The user must call the associate function on the token's contract directly.

    Smart Contracts cannot change an externally owned accounts state meaning a contract cannot associate, transfer, and approve transaction on a users behalf. Although highly useful, by allowing a contract to change an EOA state it could potentially allow a bad actor to act maliciously on behalf of a user. Therefore, Associating/disassociating HTS tokens within smart contracts is not allowed. You can read further about the security update to smart contracts here.

    I will show you how to associate/dissociate using ethers.js

    You first start by setting up your ethers provider, requesting access to accounts, and getting your signer.

    const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
    await provider.send("eth_requestAccounts", []);
    const signer = provider.getSigner();
    

    Next you will construct the abi which species the contract function you want to execute

    const abi = ["function associate()"];
    

    You can can treat HTS tokens as if they were ERC20/ERC721 tokens. This functionality is what allows us to call the associate/dissociate function directly on the HTS token. In order to do so, you must first convert the HTS tokenID into it’s solidity address with a ‘0x’ prefix.

    const tokenSolidityAddress = '0x' + TokenId.fromString('0.0.572609').tokenSolidityAddress();
    

    Finally, create a contract instance and call associate directly on the contract.

    // create contract instance using token solidity address, abi, and signer
      const contract = new ethers.Contract(tokenSolidityAddress, abi, signer);
    
      try {
        const transactionResult = await contract.associate();
        return transactionResult.hash;
      } catch (error) {
        console.warn(error.message ? error.message : error);
        return null;
      }
    

    How to dissociate an HTS token:

    async function dissociateToken() {
        // set up your ethers provider
        const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
        // request access to the user's account
        await provider.send("eth_requestAccounts", []);
        // get signer
        const signer = provider.getSigner();
        const abi = ["function dissociate()"];
        const tokenSolidityAddress = '0x' + TokenId.fromString('0.0.572609').tokenSolidityAddress();
        // create contract instance using token solidity address, abi, and signer
        const contract = new ethers.Contract(tokenSolidityAddress, abi, signer);
      
        try {
          const transactionResult = await contract.dissociate();
          return transactionResult.hash;
        } catch (error) {
          console.warn(error.message ? error.message : error);
          return null;
        } 
    };