Can't send cis2 token from wallet to contract

I want to send cis2 multi token from wallet to smart contract and then contract to wallet but unfortunately unable to send through smart contract following is my function code


#[receive(
    contract = "name",
    name = "recieve_token",
    mutable,
    parameter = "TokenParam",
    error = "VestingError"
)]
fn recieve_token<S: HasStateApi>(
    _ctx: &impl HasReceiveContext,
    host: &mut impl HasHost<State, StateApiType = S>,
) -> VestingResult<()> {
    let params: TokenParam = _ctx
        .parameter_cursor()
        .get()
        .map_err(|_e: ParseError| VestingError::ParseParams)?;

    Cis2Client::transfer(
        host,
        params.token.id.clone(),
        params.token.address,
        params.token_amount,
        _ctx.sender(),
        Receiver::Contract(
            _ctx.self_address(),
            OwnedEntrypointName::new_unchecked("onReceivingCIS2".to_string()),
        ),
    )?;

    Ok(())
}

this is my parameter.json file

{
    "token": {
        "id": "01",
        "address": {
            "index": 5112,
            "subindex": 0
        }
    },
    "token_amount": "2000"
}

and here is the cis2client file

use std::vec;

use concordium_cis2::*;
use concordium_std::*;

use crate::{errors::VestingError, state::State};

pub const SUPPORTS_ENTRYPOINT_NAME: &str = "supports";
pub const OPERATOR_OF_ENTRYPOINT_NAME: &str = "operatorOf";
pub const BALANCE_OF_ENTRYPOINT_NAME: &str = "balanceOf";
pub const TRANSFER_ENTRYPOINT_NAME: &str = "transfer";

pub struct Cis2Client;

impl Cis2Client {
    pub(crate) fn supports_cis2<S: HasStateApi>(
        host: &mut impl HasHost<State, StateApiType = S>,
        token_contract_address: &ContractAddress,
    ) -> Result<bool, VestingError> {
        let params = SupportsQueryParams {
            queries: vec![StandardIdentifierOwned::new_unchecked("CIS-2".to_string())],
        };
        let parsed_res: SupportsQueryResponse = Cis2Client::invoke_contract_read_only(
            host,
            token_contract_address,
            SUPPORTS_ENTRYPOINT_NAME,
            &params,
        )?;
        let supports_cis2: bool = {
            let f = parsed_res
                .results
                .first()
                .ok_or(VestingError::InvokeVestingError)?;
            match f {
                SupportResult::NoSupport => false,
                SupportResult::Support => true,
                SupportResult::SupportBy(_) => false,
            }
        };

        Ok(supports_cis2)
    }

    pub(crate) fn is_operator_of<S: HasStateApi>(
        host: &mut impl HasHost<State, StateApiType = S>,
        owner: Address,
        current_contract_address: ContractAddress,
        token_contract_address: &ContractAddress,
    ) -> Result<bool, VestingError> {
        let params = &OperatorOfQueryParams {
            queries: vec![OperatorOfQuery {
                owner,
                address: Address::Contract(current_contract_address),
            }],
        };

        let parsed_res: OperatorOfQueryResponse = Cis2Client::invoke_contract_read_only(
            host,
            token_contract_address,
            OPERATOR_OF_ENTRYPOINT_NAME,
            params,
        )?;

        let is_operator = parsed_res
            .0
            .first()
            .ok_or(VestingError::InvokeVestingError)?
            .to_owned();

        Ok(is_operator)
    }

    pub(crate) fn get_balance<
        S,
        T: IsTokenId + Clone,
        A: Default + IsTokenAmount + Clone + Copy + ops::Sub<Output = A>,
    >(
        host: &mut impl HasHost<State, StateApiType = S>,
        token_id: T,
        token_contract_address: &ContractAddress,
        owner: Address,
    ) -> Result<A, VestingError>
    where
        S: HasStateApi,
    {
        let params = BalanceOfQueryParams {
            queries: vec![BalanceOfQuery {
                token_id,
                address: owner,
            }],
        };

        let parsed_res: BalanceOfQueryResponse<A> = Cis2Client::invoke_contract_read_only(
            host,
            token_contract_address,
            BALANCE_OF_ENTRYPOINT_NAME,
            &params,
        )?;

        let ret = parsed_res.0.first().map_or(A::default(), |f| *f);

        Ok(ret)
    }

    pub(crate) fn transfer<
        S,
        T: IsTokenId + Clone,
        A: IsTokenAmount + Clone + Copy + ops::Sub<Output = A>,
    >(
        host: &mut impl HasHost<State, StateApiType = S>,
        token_id: T,
        token_contract_address: ContractAddress,
        amount: A,
        from: Address,
        to: Receiver,
    ) -> Result<bool, VestingError>
    where
        S: HasStateApi,
        A: IsTokenAmount,
    {
        let params: TransferParams<T, A> = TransferParams(vec![Transfer {
            token_id,
            amount,
            from,
            data: AdditionalData::empty(),
            to,
        }]);

        Cis2Client::invoke_contract_read_only(
            host,
            &token_contract_address,
            TRANSFER_ENTRYPOINT_NAME,
            &params,
        )?;

        Ok(true)
    }

    fn invoke_contract_read_only<S: HasStateApi, R: Deserial, P: Serial>(
        host: &mut impl HasHost<State, StateApiType = S>,
        contract_address: &ContractAddress,
        entrypoint_name: &str,
        params: &P,
    ) -> Result<R, VestingError> {
        let invoke_contract_result = host
            .invoke_contract_read_only(
                contract_address,
                params,
                EntrypointName::new(entrypoint_name).unwrap_abort(),
                Amount::from_ccd(0),
            )
            .map_err(|_| VestingError::InvokeContractNoResult)?;
        let mut invoke_contract_res = match invoke_contract_result {
            Some(s) => s,
            None => return Err(VestingError::InvokeContractNoResult),
        };
        let parsed_res =
            R::deserial(&mut invoke_contract_res).map_err(|_e| VestingError::ParseResult)?;

        Ok(parsed_res)
    }
}

I’m not sure I understand what the error is.

Can you explain what part does not work?

Note that you have a typo in the name of the receive method, it states recieve instead of receive.

Thanks for your prompt response, yeah on typo concordium-client warn
but the problem is can’t send the token wallet to contract

would you please confirm 1 thing, the following line unchecked method should be onReceivingCIS2 or transfer?

            OwnedEntrypointName::new_unchecked("onReceivingCIS2".to_string()),

also unable to transfer from cis2 smart contract

According to CustomError I guess the problem is in the invoke_contract_read_only it says failed with code -16 and on my -16 this is my InvokeContractNoResult

following is the function where errors come from

 fn invoke_contract_read_only<S: HasStateApi, R: Deserial, P: Serial>(
        host: &mut impl HasHost<State, StateApiType = S>,
        contract_address: &ContractAddress,
        entrypoint_name: &str,
        params: &P,
    ) -> Result<R, VestingError> {
        let invoke_contract_result = host
            .invoke_contract_read_only(
                contract_address,
                params,
                EntrypointName::new(entrypoint_name).unwrap_abort(),
                Amount::from_ccd(0),
            )
            .map_err(|_| VestingError::InvokeContractNoResult)?;
        let mut invoke_contract_res = match invoke_contract_result {
            Some(s) => s,
            None => return Err(VestingError::InvokeContractNoResult),
        };
        let parsed_res =
            R::deserial(&mut invoke_contract_res).map_err(|_e| VestingError::ParseResult)?;

        Ok(parsed_res)
    }

@abizjak congrats it’s solved,
I forgot to add following function

#[receive(contract = "vest13", name = "onReceivingCIS2", error = "VestingError")]
fn token_on_cis2_received<S: HasStateApi>(
    _ctx: &impl HasReceiveContext,
    _host: &impl HasHost<State, StateApiType = S>,
) -> VestingResult<()> {
    Ok(())
}
1 Like