Web SDK: Cannot convert undefined to a BigInt

Hey guys, I’ve been stuck on this problem for so long. I’m trying to create a simple trace object on my existing contract on ccd testnet but getting this error from concordium serializationHelpers:

TypeError: Cannot convert undefined to a BigInt
    at DataView.setBigUint64 (<anonymous>)
    at encodeWord64 (\node_modules\@concordium\web-sdk\src\serializationHelpers.ts:67:10)
    at UpdateContractHandler.serialize (\node_modules\@concordium\web-sdk\src\accountTransactions.ts:283:34)
    at serializeAccountTransactionPayload (\node_modules\@concordium\web-sdk\src\serialization.ts:136:57)
    at ConcordiumGRPCNodeClient.sendAccountTransaction (ˇ\node_modules\@concordium\web-sdk\src\grpc\GRPCClient.ts:294:28)
    at TraceService.createTrace (\src\concordium\concordium.service.ts:182:40)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at ProductService.create (\src\product\product.service.ts:48:26)
    at async \node_modules\@nestjs\core\router\router-execution-context.js:46:28
    at async \node_modules\@nestjs\core\router\router-proxy.js:9:17

this is my createTrace function:

 async createTrace(params: {
    entityType: 'Product' | 'Review' | 'Tag';
    typeId: string;
    changeId?: string;
  }) {
    try {
      if (!this.SDK || !this.client) {
        throw new Error('Service not properly initialized');
      }

      const adminAddress = process.env.CONCORDIUM_ADMIN_ADDRESS;
      if (!adminAddress) {
        throw new Error(
          'Missing CONCORDIUM_ADMIN_ADDRESS environment variable',
        );
      }

      const parameters = {
        entity_type:
          params.entityType === 'Product'
            ? 0
            : params.entityType === 'Review'
              ? 1
              : 2,
        type_id: params.typeId,
        change_id: params.changeId ? { Some: params.changeId } : { None: null },
      };

      const address = this.SDK.AccountAddress.fromBase58(adminAddress);
      const nonceResponse = await this.client.getNextAccountNonce(address);

      const contractAddr = {
        index: BigInt(10532),
        subindex: BigInt(0),
      };

      // Convert header values
      const header = {
        expiry: BigInt(Math.floor(Date.now() / 1000) + 3600),
        nonce: BigInt(nonceResponse.nonce),
        sender: address,
      };

      // Create UpdateContract payload
      const updatePayload = {
        address: contractAddr,
        amount: BigInt(0),
        receiveName: this.SDK.ReceiveName.fromString('trace.create_trace'),
        maxContractExecutionEnergy: BigInt(30000),
        message: Buffer.from(JSON.stringify(parameters)),
      };

      // Create the full transaction
      const transaction = {
        header,
        payload: updatePayload,
        type: this.SDK.AccountTransactionType.Update,
      };

      // Sign and send
      const signatures: any[] = [];
      const result = await this.client.sendAccountTransaction(
        transaction,
        signatures,
      );

      if (!result) {
        throw new Error('Transaction rejected by node');
      }

      return result;
    } catch (error) {
      this.logger.error('Trace creation failed:', {
        error: error.message,
        stack: error.stack,
        params,
      });
      throw error;
    }
  }

this is params of the function

pub struct CreateTraceParams {
    pub entity_type: EntityType,
    pub type_id: String,
    pub change_id: Option<String>,
}

and this is params I’m sending to create_trace function

  "params": {
    "entityType": "Product",
    "typeId": "cm5nrp5j50001l3gymf0dh4yu",
    "changeId": "cm5p3aig80001srgmxny0icch"
  }

Does anyone maybe see the mistake?

Hi there

The message needs to be constructed using a schema for the smart contract using serializeUpdateContractParameters.

You can also generate a smart contract client using ccd-js-gen which will deal with most of this, like we do in our example here.

I’ve explored both suggested approaches for the contract parameter serialization, but I’m encountering some limitations:

  1. Using serializeUpdateContractParameters from @concordium/web-sdk is currently not working in my implementation.
  2. While generating a contract client with ccd-js-gen would provide automated serialization, it requires module resolution changes that would conflict with my existing TypeScript 2.0 backend architecture.

Hi.

I’m a little puzzled as to how you can use a recent version of the web-sdk, but cannot use a generated contract client from ccd-js-gen? What are the module resolution requirements?

What happens when you try to use serializeUpdateContractParameters?

Hi.

I tried everything from scratch again. I generated contract client using

ccd-js-gen --module ./contract/module.wasm.v1 --out-dir ./generated

it returned me module_trace which I’m using to handle create traces
this is my trace.service.ts

import { Injectable, Inject } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
  ConcordiumGRPCWebClient,
  CcdAmount,
  TransactionHash,
  AccountAddress,
  ContractAddress,
  Energy,
  AccountSigner,
  AccountTransactionSignature,
} from '@concordium/web-sdk';
import {
  CreateTraceParameter,
  sendCreateTrace,
  create,
} from '../../generated/module_trace';

enum TraceEntityType {
  Product = 'Product',
  Review = 'Review',
  Tag = 'Tag',
}

@Injectable()
export class TraceService {
  private readonly contractIndex: bigint;
  private readonly contractSubIndex: bigint;
  private readonly adminAddress: string;
  private readonly adminPrivateKey: string;

  constructor(
    @Inject('CONCORDIUM_PROVIDER')
    private readonly provider: ConcordiumGRPCWebClient,
    private readonly configService: ConfigService,
  ) {
    this.contractIndex = BigInt(
      this.configService.get<string>('CONCORDIUM_CONTRACT_INDEX'),
    );
    this.contractSubIndex = BigInt(
      this.configService.get<string>('CONCORDIUM_CONTRACT_SUBINDEX'),
    );
    this.adminAddress = this.configService.get<string>(
      'CONCORDIUM_ADMIN_ADDRESS',
    );
    this.adminPrivateKey = this.configService.get<string>(
      'CONCORDIUM_ADMIN_PRIVATE_KEY',
    );
  }

  async createTrace(
    entityType: TraceEntityType,
    typeId: string,
    changeId?: string,
  ): Promise<string> {
    try {
      // Prepare the parameters for the contract
      const parameter: CreateTraceParameter = {
        entity_type: { type: entityType },
        type_id: typeId,
        change_id: changeId
          ? { type: 'Some', content: changeId }
          : { type: 'None' },
      };

      // Create the contract client
      const contractAddress = ContractAddress.create(
        this.contractIndex,
        this.contractSubIndex,
      );
      const contractClient = await create(this.provider, contractAddress);

      // Prepare transaction metadata
      const transactionMetadata = {
        amount: CcdAmount.fromMicroCcd(0),
        energy: Energy.create(30000),
        senderAddress: AccountAddress.fromBase58(this.adminAddress),
      };

      // Create the signer
      const signer: AccountSigner = {
        async sign(digest: ArrayBuffer): Promise<AccountTransactionSignature> {
          // This is a placeholder. You need to implement actual signing logic
          return [
            {
              0: '0'.repeat(128), // 64 byte hex string
            },
          ];
        },
        getSignatureCount(): bigint {
          return BigInt(1);
        },
      };

      // Send the transaction
      const txHash = await sendCreateTrace(
        contractClient,
        transactionMetadata,
        parameter,
        signer,
      );

      // Return the transaction hash
      return TransactionHash.toHexString(txHash);
    } catch (error) {
      console.error('Error creating trace:', error);
      throw new Error(`Failed to create trace: ${error.message}`);
    }
  }

  async createProductTrace(typeId: string, changeId?: string): Promise<string> {
    return this.createTrace(TraceEntityType.Product, typeId, changeId);
  }

  async createReviewTrace(typeId: string, changeId?: string): Promise<string> {
    return this.createTrace(TraceEntityType.Review, typeId, changeId);
  }

  async createTagTrace(typeId: string, changeId?: string): Promise<string> {
    return this.createTrace(TraceEntityType.Tag, typeId, changeId);
  }
}

and this is the error I’m getting

parameter {
  entity_type: { type: 'Product' },
  type_id: 'cm6oz6rg20001qhunutvd1taz',
  change_id: { type: 'None' }
}
signer {
  sign: [AsyncFunction: sign],
  getSignatureCount: [Function: getSignatureCount]
}
Error creating trace: RpcError: Verification%20failed
    at /home/emkaa/posao/mithras/mithras-be/node_modules/@protobuf-ts/grpcweb-transport/build/commonjs/grpc-web-transport.js:132:23
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
  code: 'INVALID_ARGUMENT',
  meta: {
    'access-control-allow-credentials': 'true',
    'access-control-expose-headers': 'grpc-status,grpc-message,grpc-status-details-bin',
    'content-length': '0',
    date: 'Mon, 03 Feb 2025 11:36:34 GMT',
    vary: 'origin, access-control-request-method, access-control-request-headers'
  },
  methodName: 'SendBlockItem',
  serviceName: 'concordium.v2.Queries'
}

I just want to ask you if the process I’m doing is good or is there maybe any other way to initialize client?

Are you connecting to a node endpoint which requires SSL/https? And if so, did you specify the endpoint with https?

As far as I see, the contract client has been generated correctly, and you should get compilation errors if you’re passing incorrect parameters.

I’m connecitng to http://node.testnet.concordium.com, port: 20000