Building a Cross-Chain ETH Payment and E-Commerce Platform on the Internet Computer: A Step-by-Step Tutorial

Hello, thanks for the guide. I’m trying to follow it.

Update the Frontend Code

Navigate to the frontend code where the useActorMethod hook is used. This is typically found in a component file. Change the method from "greet" to "deposit_principal":

const { call, data, error, loading } = useActorMethod("deposit_principal")

more has to be done here than shown - the service has to be updated. I’m trying this:

from the JS service:

import createReActor from "@re-actor/core"
import { canisterId, hello, idlFactory, createActor } from "declarations/hello"

export const {
  initialize,
  useActorMethod
} = createReActor(() =>
  createActor(canisterId, {
    agentOptions: {
      host: process.env.NEXT_PUBLIC_IC_HOST
    }
  })
)

and for the rust .did file - this did not regenerate when I ran deploy and the tutorial did not say to update this:

cat hello/hello.did 
service : { deposit_principal : (text) -> (text) query }

now when I run the code I get a “method deposit_principal not found” error in the UI, I think because the JS does not have the signature:

grep -nr "greet" ./src/
./src/declarations/hello/hello.did.d.ts:5:export interface _SERVICE { 'greet' : ActorMethod<[string], string> }
./src/declarations/hello/hello.did.js:2:  return IDL.Service({ 'greet' : IDL.Func([IDL.Text], [IDL.Text], ['query']) });
./src/components/Greeting.tsx:11: //   functionName: "greet",

as here it still has the greet reference, even after doing yarn build and npm run deploy hello.

How do I compile the rust did into JS?

Hello @nolma,

Thank you for reaching out and providing details on the challenges you’re facing while following the guide.

Make sure you have ic_cdk::export_candid!(); at the end of your Rust code, then try to run this script: predeploy.sh.

sh ./predeploy.sh

It will generate the .did file automatically.

These steps should align your frontend and backend services properly, resolving the “method not found” error you encountered.

If you have further questions or need additional assistance, please feel free to ask.

Hi, thanks for the quick response. The predeploy script fixed the did generation. I mistakenly rmeoved the export_candid and readded, thanks.

Now when I run, I enter the canister ID into the field and get a response “cyclic object value”.

Is there some other frontend or backend change not shown in the tutorial?

It seems the data is being updated with an object that cannot render cleanly.

Hello @b3hr4d

I tried interacting with the site and came across this error whenever I try to make a purchase:
image

Actually the dependencies is not updated for a while, I probably should update the whole tutorial and make a new version soon!

Hello @Stephen-Kimoi
Yes you are right, I’ll fix the site soon!

1 Like

Hello @b3hr4d

I am trying to get the transaction receipt using HTTP outcalls but I am getting this error:

Call was rejected:
Request ID: 75af36cca01a947fba819345e9a05fd015260cc354523153ab76244da0f818e1
Reject code: 5
Reject text: Canister bkyz2-fmaaa-aaaaa-qaaaq-cai trapped explicitly: Panicked at 'called `Result::unwrap()` on an `Err` value: "Error: Http body exceeds size limit of 1608 bytes."', src/canister_one/src/ck_eth_payments.rs:43:59

This is how the functions get_receipt and eth_get_transaction_receipt look like on my end. They have a similar implementation to yours:

// Testing get receipt function 
#[ic_cdk::update]
async fn get_receipt(hash: String) -> String {
    let receipt = eth_get_transaction_receipt(hash).await.unwrap(); 

    serde_json::to_string(&receipt).unwrap()
}

// Function for getting transaction receipt the transaction hash
async fn eth_get_transaction_receipt(hash: String) -> Result<receipt::Root, String> {

    // Preparing JSON RPC Payload usnig the serde_json::json!
    let rpc = json!({
        "jsonrpc": "2.0",
        "id": 0,
        "method": "eth_getTransactionReceipt",
        "params": [hash]
    }); 
    
    // Using HttpOutcall struct from b3_utils to make an HTTP POST request to the RPC URL
    let request = HttpOutcall::new(RPC_URL)
        .post(&rpc.to_string(), Some(2048))
        .send_with_closure(|response: HttpOutcallResponse| HttpOutcallResponse {
            status: response.status,
            body: response.body,
            ..Default::default()
        });
    
    match request.await {
        Ok(response) => {
            if response.status != 200u16 {
                return Err(format!("Error: {}", response.status));
            }

            let transaction = serde_json::from_slice::<receipt::Root>(&response.body)
                .map_err(|e| format!("Error: {}", e.to_string()))?;

            Ok(transaction)
        }
        Err(m) => Err(format!("Error: {}", m)),
    }
}

How can I fix this :thinking: ?

I think we should replace the HTTP outcall with the EVM RPC client in this case. I’ll update this tutorial soon!

1 Like

Interesting, also we can use it for token pre-sale in ICP from Ethereum wallet

1 Like

I implemented it and it worked. Here’s a snippet of my code:

// Function for getting transaction receipt the transaction hash
async fn eth_get_transaction_receipt(hash: String) -> Result<GetTransactionReceiptResult, String> {
    // Make the call to the EVM_RPC canister
    let result: Result<(MultiGetTransactionReceiptResult,), String> = EVM_RPC 
        .eth_get_transaction_receipt(
            RpcServices::EthSepolia(Some(vec![
                EthSepoliaService::PublicNode,
                EthSepoliaService::BlockPi,
                EthSepoliaService::Ankr,
            ])),
            None, 
            hash, 
            10_000_000_000
        )
        .await 
        .map_err(|e| format!("Failed to call eth_getTransactionReceipt: {:?}", e));

    match result {
        Ok((MultiGetTransactionReceiptResult::Consistent(receipt),)) => {
            Ok(receipt)
        },
        Ok((MultiGetTransactionReceiptResult::Inconsistent(error),)) => {
            Err(format!("EVM_RPC returned inconsistent results: {:?}", error))
        },
        Err(e) => Err(format!("Error calling EVM_RPC: {}", e)),
    }    
}

But now I have to add more helper functions to help me serialize the result and make it compatible with candid.

Here are some of the helper functions I added:

  1. ReceiptWrapper enum that mirrors structure of GetTransactionReceiptResult
  2. TransactionReceiptData struct to hold the data from the Ok variant.
#[derive(Serialize)]
enum ReceiptWrapper {
    Ok(TransactionReceiptData),
    Err(String),
}

#[derive(Serialize)]
struct TransactionReceiptData {
    to: String,
    status: String, // We'll convert Nat to String for serialization
    transaction_hash: String,
    block_number: String, // We'll convert Nat to String for serialization
    from: String,
    //
}

I’ve also implemented From<GetTransactionReceiptResult> for ReceiptWrapper to convert between the types.

    fn from(result: GetTransactionReceiptResult) -> Self {
        match result {
            GetTransactionReceiptResult::Ok(receipt) => {
                if let Some(receipt) = receipt {
                    ReceiptWrapper::Ok(TransactionReceiptData {
                        to: receipt.to,
                        status: receipt.status.to_string(),
                        transaction_hash: receipt.transactionHash,
                        block_number: receipt.blockNumber.to_string(),
                        from: receipt.from,
                        // ... map other fields as needed
                    })
                } else {
                    ReceiptWrapper::Err("Receipt is None".to_string())
                }
            },
            GetTransactionReceiptResult::Err(e) => ReceiptWrapper::Err(format!("Error on Get transaction receipt result: {:?}", e)),
        }
    }
}

I’ve also modified the get_receipt function, I convert the GetTransactionReceiptResult to our ReceiptWrapper and then serialize it.

#[ic_cdk::update]
async fn get_receipt(hash: String) -> String {
    let receipt = eth_get_transaction_receipt(hash).await.unwrap();
    let wrapper = ReceiptWrapper::from(receipt);
    serde_json::to_string(&wrapper).unwrap()
}

Seems like a lengthy way to do it.

1 Like

Great job implementing this! Could you please submit a pull request with these changes, if possible?

@Stephen-Kimoi please make the PR!

Btw, I found erc20-icp/ic at main · dfinity/erc20-icp · GitHub which looks like an easy way to do tokens. If I understand this project, it’s a way to more an erc-20 to and from IC. On IC it uses the ICRC-1 standard. Has anyone used this?

1 Like

Thank you!

Yes, I am already done, here’s the PR

1 Like

Regarding the ERC-20 support, we should definitely add it to this tutorial. If you’re interested, feel free to open a new issue and submit a pull request. I’ll be happy to assist you in incorporating it!

Hello everyone,

I wanted to let you all know that I’ve made some significant updates to the eth_payment_tutorial project. The recent pull request includes:

  • Refactoring: Cleaned up the codebase for better readability and maintainability.
  • New Features: Integrated the use of the EVM RPC canister instead of HTTP outcall, thanks to @Stephen-Kimoi for the contribution.
  • Dependency Updates: Updated all dependencies to their latest versions to ensure compatibility and security.
  • Integration with @ic-reactor/react: Incorporated the ic-reactor/react library, simplifying authentication and canister interaction in React applications.
  • Simplified Steps: Streamlined the tutorial steps to make it easier for developers to follow along and implement the features.

Thank you for your continued support and contributions!

1 Like

I am glad to have participated in this!

1 Like