Do i need to use the rosetta API to interact with the ledger canister if I'm calling the ledger canister from another canister on the IC?

I have a canister already built out on the IC and I’m trying to integrate ICP payment via the ledger canister. I’ve only been able to find payment integrating documentation that references the rosetta API, which seems to be useful for integrating ICP with apps that aren’t hosted on the IC. but for dapps hosted on the IC, it seems like an unnecessary step since the rosetta API requires you to run a passive rosetta node off-chain in order to interact with the IC ledger canister. Could anyone point me to where I can find documentation and/or examples of how to integrate the ledger canister into my current canister? thank you in advance.

Bump - did you ever get an answer to this?

I believe you do not need Rosetta, but let me ping the team to see if i can have someone with authority confirm.

You do not need to use the Rosetta Api. Here’s the link to an example where the ledger canister is called and used:
icp-canister/main.mo at main · aviate-labs/icp-canister · GitHub

3 Likes

Thank you for resolving @Jesse

1 Like

You don’t need to run Rosetta to integrate your canisters with the ICP Ledger canister.
The ledger canister exposes a Candid interface that you can call from any other canister: Principal ryjl3-tyaaa-aaaaa-aaaba-cai | ic.rocks

There are a few example canisters that use the interface:

2 Likes

Hi, Roman.

It sounds quite promising, what you wrote. Can you supply any links THAT WORK?
Also: when testing transfers from a canister locally (during development) - is there something like “test ICP” to give the canister under development something to transfer?

Sure, here is the token transfer example in Rust and Motoko.

That was quick! Thanks very much, Roman.

Well, the new links do work, but not what they point to.
Following the README with dfx 0.15.01, my resulting dfx.json is

{
  "canisters": {
    "ledger": {
      "type": "custom",
      "candid": "https://raw.githubusercontent.com/dfinity/ic/c337458aafa8e8864eadfcfe38d7e5fa385b0006/rs/rosetta-api/icrc1/ledger/ledger.did",
      "wasm": "https://download.dfinity.systems/ic/c337458aafa8e8864eadfcfe38d7e5fa385b0006/canisters/ic-icrc1-ledger.wasm.gz",
      "remote": {
        "id": {
          "ic": "ryjl3-tyaaa-aaaaa-aaaba-cai"
        }
      }
    },
    "ledger_transfer_backend": {
      "main": "src/ledger_transfer_backend/main.mo",
      "type": "motoko"
    },
    "ledger_transfer_frontend": {
      "dependencies": [
        "ledger_transfer_backend"
      ],
      "frontend": {
        "entrypoint": "src/ledger_transfer_frontend/src/index.html"
      },
      "source": [
        "src/ledger_transfer_frontend/assets",
        "dist/ledger_transfer_frontend/"
      ],
      "type": "assets"
    }
  },
  "defaults": {
     "replica": {
      "subnet_type":"system"
    },
    "build": {
      "args": "",
      "packtool": ""
    }
  },
  "output_env_file": ".env",
  "version": 1
}

dfx start --background results in a warning:

WARN: Ignoring the 'defaults' field in dfx.json because project settings never apply to the shared network.
To apply these settings to the shared network, define them in /home/andre/.config/dfx/networks.json like so:

The command

`dfx canister install ledger --argument "(variant {Init = record { token_name = \"NAME\"; token_symbol = \"SYMB\"; transfer_fee = 1000000; metadata = vec {}; minting_account = record {owner = principal \"$(dfx --identity minter identity get-principal)\";}; initial_balances = vec {}; archive_options = record {num_blocks_to_archive = 1000000; trigger_threshold = 1000000; controller_id = principal \"$(dfx identity get-principal)\"}; }})"`

first results in

Error: Cannot find canister id. Please issue 'dfx canister create ledger'

and after dfx canister create ledger in:

Error: Failed to install wasm module to canister 'ledger'.
Caused by: Failed to install wasm module to canister 'ledger'.
  Failed to read /home/andre/play/tic/ledger_transfer/.dfx/local/canisters/ledger/ledger.wasm.gz.

Hopefully I AM doing something wrong.

The instructions are wrong. It should be dfx deploy ledger --argument <...>

Hi, Severin.

Yes. Pity those instructions are in the “official” examples.
However, I found your reply in another thread and followed the instructions from ICP ledger local setup | Internet Computer - the ledger canister is now deployed locally!! :slight_smile:

Please allow for some more questions:
I am using @dfinity/ledger-icp talking with this local ledger.
dfx canister call ledger account_balance ‘(record { account = blob “…” })’ shows
(record { e8s = 10_000_000_000 : nat64 })
JS ledger.accountBalance({ accountIdentifier }) also shows 100 LICP.
I am creating the identity for the Actor in JS via Secp256k1KeyIdentity.fromSecretKey(Uint8Array.from(privKey).buffer).
Key and accountIdentifier being those of default (DEFAULT_ACCOUNT).

When I try to transfer to a tester account, I get

...node_modules/@dfinity/ledger-icp/dist/cjs/index.cjs.js:367
      ? new C(e.InsufficientFunds.balance.e8s)
        ^

C [Error]
    at Oe (/home/andre/play/bmos/bmos-ic/node_modules/@dfinity/ledger-icp/dist/cjs/index.cjs.js:367:9)
    at e.transfer (/home/andre/play/bmos/bmos-ic/node_modules/@dfinity/ledger-icp/dist/cjs/index.cjs.js:1150:29)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Object.transfer (file:///home/andre/play/bmos/bmos-ic/tst.ledger.js:103:15) {
  balance: 0n
}

So why does transfer not see the 100 LICP that balance shows?
Any help or advice will be highly appreciated.
Thanks in advance,

André

My best guess is that you somehow mix up identities or account identifiers. Can you debug-print everything (principals, subaccounts, account identifiers)? And if you use the icrc1 functions it should be even more obvious since they don’t use opaque account identifiers

Yeah. I do have some more debug-printing.
First, I did dfx identity export default > default.key.pem.
From this file, I create the identity with

const parseIdentity = (keyPath) => {
  const rawKey = fs
    .readFileSync(keyPath)
    .toString()
    .replace('-----BEGIN EC PRIVATE KEY-----', '')
    .replace('-----END EC PRIVATE KEY-----', '')
    .trim()
    // .replace(/\n/g, '')

  const rawBuffer = Uint8Array.from(rawKey).buffer

  const privKey = Uint8Array.from(sha256(rawBuffer, { asBytes: true }))

  // Initialize an identity from the secret key
  return Secp256k1KeyIdentity.fromSecretKey(Uint8Array.from(privKey).buffer)
}

BUT:

const identity = parseIdentity('default.key.pem')
// const identity = decodeFile('default.key.pem')
const accountIdentifier = AccountIdentifier.fromPrincipal({ principal: identity.getPrincipal() })
console.log(identity.getPrincipal().toText(), accountIdentifier.toHex())

show different data from dfx identity get-principal and dfx ledger account-id

The question remains: how do I create the same identity / actor in JS that the dfx command uses?

At last!
These decode functions do the trick: https://github.com/ZenVoich/mops/blob/e4e3b51df1c561b5b00a8e6eef25dcb85b531964/cli/pem.ts#L15.

Thanks a lot, @ZenVoich !!!

2 Likes