[RFC] tech_stack - Canister Metadata Standard extension

I was expecting dfx canister metadata [canister_name] tech_stack, and also dfx canister metadata [canister_name] tech_stack:cdk, dfx canister metadata [canister_name] tech_stack:lanuage, etc

There is a limit on the number of metadata sections. So in dfx, we deliberated to group all dfx-specific metadata into a “dfx” metadata which has a JSON content.

Then the clients can get the JSON content and extract interesting value from it.

In CLI, you can:

dfx canister metadata [canister_name] dfx | jq ".tech_stack.cdk"
2 Likes

For anyone reading @lwshang and I had a call, and the situation is not perfect but due to the various constraints on Wasm metadata, the current solution should work fine.

I still think a name other than dfx would be nice, but at least we have a standard that should work well. We can always create our own custom metadata sections and standards if we’d really like to, but this is fine for now.

3 Likes

FYI, icpp-pro 3.15.3 was released, which adds the custom section dfx.tech_stack to the .wasm file according to the standard spec.

I am not using dfx to do this addition, but just add it to the wasm as part of the build process, using LLVM.

3 Likes

The next versions of Azle and Kybra will both have this implemented, waiting on releases, possibly waiting for a stable version of dfx extensions with custom canister type.

2 Likes

@icpp @lastmjs could you share a canister id with tech_stack metadata?

1 Like

@kpeacock or @lwshang is there an example of hitting the metadata endpoint with agent-js (without dfx)?

I spent a few minutes hacking around in the dark, but am wondering how close I am

async function getState() {
  const identity = localIdentity();
  let agent = HttpAgent.createSync({
    host: "https://icp0.io",
    identity
  });

  const canisterId = <canister_id>;
  const paths = ["canister", <canister_id>, "metadata", "motoko:compiler"]
  try {
    const request = await agent.createReadStateRequest(
      { paths : convertToBuffer(paths) },
      identity
    );
    console.log("read state request", request);

    const response = await agent.readState(
      canisterId,
      { paths : convertToBuffer(paths) },
      identity,
      request
    );

    console.log("response", response)

  } catch (err) {
    console.log("read state request rejected with error", err)
  }
}

and convertToBuffer() is doing this

function convertToBuffer (strings: string[]): ArrayBuffer[][] {
  // Create an instance of TextEncoder
  const encoder = new TextEncoder();

  // Convert each string to an ArrayBuffer
  const arrayBuffers = strings.map(str => encoder.encode(str).buffer);

  // Wrap each ArrayBuffer into its own array, forming a 2D array
  return arrayBuffers.map(buffer => [buffer]);
};

oh yeah, there’s a utility for it: https://agent-js.icp.xyz/agent/modules/CanisterStatus.html

import { CanisterStatus } from '@dfinity/agent';
const { request, encodePath} = CanisterStatus;

const customPaths = [
  {
    key: 'metadata',
    path: [encodePath('metadata')],
    decodeStrategy: 'utf-8'
  },
  {
    key: 'motoko:compiler',
    path: [encodePath('motoko:compiler')],
    decodeStrategy: 'utf-8'
  },
];

const agent = await HttpAgent.create();
const statusMap = await request({
  canisterId,
  paths,
  agent
});
3 Likes

Not that it’s massively less verbose, but I’m always open to adding more “canonical” path aliases to check for if there’s a corresponding ICRC standard for them!

If we added those and knew how to decode them, it could be simplified to:

const customPaths = [ 'metadata', 'motoko:compiler'];

@ZenVoich ,
I don’t believe I ever provided you with an example canister id for C++.
You can use this one, which is a deployment of llama_cpp_canister:

6uwoh-vaaaa-aaaag-amema-cai

% dfx canister --ic metadata 6uwoh-vaaaa-aaaag-amema-cai dfx       
{ "tech_stack": { "language": {"c++": {} }, "cdk": {"icpp-pro": {} } } }