Skip to main content
{ newBalance: 10000000n }
In this step, you’ll learn how to use your Internet Computer identity from Step 3 to make canister calls.
This step builds on Step 3: Authenticate with Odin API. Make sure you have a working Internet Computer identity before proceeding.

What You’ll Accomplish

By the end of this step, you’ll have:
  • ✅ Connected to the Odin canister using your authenticated identity
  • ✅ Made your first canister call to deposit BTC
  • ✅ Verified the functionality of your integration

Odin Canister Integration

Import Canister Interface

Let’s start by setting up the Odin canister interface. You can reference the contract and download the interface from the Internet Computer Dashboard.
  • IDL Factory
  • TypeScript Types
canister/odin_canister.idl.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
export const idlFactory = ({ IDL }: { IDL: any }) => {
  const TokenID = IDL.Text;
  const TokenAmount = IDL.Nat;
  const Time = IDL.Int;
  const TradeType = IDL.Variant({ buy: IDL.Null, sell: IDL.Null });
  const MetadataRecord = IDL.Tuple(
    IDL.Text,
    IDL.Variant({
      hex: IDL.Text,
      int: IDL.Int,
      nat: IDL.Nat,
      principal: IDL.Principal,
      blob: IDL.Vec(IDL.Nat8),
      bool: IDL.Bool,
      nat8: IDL.Nat8,
      text: IDL.Text,
    })
  );
  const Metadata = IDL.Vec(MetadataRecord);
  const OperationType = IDL.Variant({
    access: IDL.Record({ user: IDL.Text }),
    token: IDL.Record({
      tokenid: TokenID,
      deltas: IDL.Vec(
        IDL.Record({
          field: IDL.Text,
          delta: IDL.Variant({
            add: TokenAmount,
            sub: TokenAmount,
            bool: IDL.Bool,
            text: IDL.Text,
            amount: TokenAmount,
          }),
        })
      ),
    }),
    trade: IDL.Record({
      amount_token: TokenAmount,
      tokenid: TokenID,
      user: IDL.Text,
      typeof: TradeType,
      bonded: IDL.Bool,
      amount_btc: TokenAmount,
      price: TokenAmount,
    }),
    other: IDL.Record({ data: Metadata, name: IDL.Text }),
    mint: IDL.Record({ tokenid: TokenID, data: Metadata }),
    transaction: IDL.Record({
      tokenid: TokenID,
      balance: TokenAmount,
      metadata: Metadata,
      user: IDL.Text,
      typeof: IDL.Variant({ add: IDL.Null, sub: IDL.Null }),
      description: IDL.Text,
      amount: TokenAmount,
    }),
  });
  const Operation = IDL.Record({ time: Time, typeof: OperationType });
  const OperationAndId = IDL.Record({
    id: IDL.Nat,
    operation: Operation,
  });
  const LiquiditySwap = IDL.Record({
    btc: TokenAmount,
    token: TokenAmount,
  });
  const LiquidityPool = IDL.Record({
    locked: LiquiditySwap,
    current: LiquiditySwap,
  });
  const Rune = IDL.Record({
    id: IDL.Text,
    ticker: IDL.Text,
    name: IDL.Text,
  });
  const BondingCurveSettings = IDL.Record({
    a: IDL.Float64,
    b: IDL.Float64,
    c: IDL.Float64,
    name: IDL.Text,
  });
  const Token = IDL.Record({
    creator: IDL.Principal,
    lp_supply: TokenAmount,
    bonded_btc: TokenAmount,
    pool: LiquidityPool,
    rune: IDL.Opt(Rune),
    bonding_threshold_reward: TokenAmount,
    supply: TokenAmount,
    icrc_canister: IDL.Opt(IDL.Principal),
    max_supply: TokenAmount,
    bonding_curve: IDL.Opt(BondingCurveSettings),
    bonding_threshold: TokenAmount,
    bonding_threshold_fee: TokenAmount,
  });
  const LiquidityType = IDL.Variant({ add: IDL.Null, remove: IDL.Null });
  const LiquidityRequest = IDL.Record({
    tokenid: TokenID,
    typeof: LiquidityType,
    amount: TokenAmount,
  });
  const LiquidityResponse = IDL.Variant({ ok: IDL.Null, err: IDL.Text });
  const ListRequest = IDL.Record({ rune_id: IDL.Text });
  const ListResponse = IDL.Variant({ ok: TokenAmount, err: IDL.Text });
  const MintRequest = IDL.Record({
    metadata: Metadata,
    code: IDL.Opt(IDL.Text),
    prebuy_amount: IDL.Opt(TokenAmount),
  });
  const MintResponse = IDL.Variant({ ok: IDL.Null, err: IDL.Text });
  const TradeSettings = IDL.Record({
    slippage: IDL.Opt(IDL.Tuple(TokenAmount, IDL.Nat)),
  });
  const TradeAmount = IDL.Variant({
    btc: TokenAmount,
    token: TokenAmount,
  });
  const TradeRequest = IDL.Record({
    tokenid: TokenID,
    typeof: TradeType,
    settings: IDL.Opt(TradeSettings),
    amount: TradeAmount,
  });
  const TradeResponse = IDL.Variant({ ok: IDL.Null, err: IDL.Text });
  const TransferRequest = IDL.Record({
    to: IDL.Text,
    tokenid: TokenID,
    amount: TokenAmount,
  });
  const TransferResponse = IDL.Variant({ ok: IDL.Null, err: IDL.Text });
  const WithdrawProtocol = IDL.Variant({
    btc: IDL.Null,
    ckbtc: IDL.Null,
    volt: IDL.Null,
  });
  const WithdrawRequest = IDL.Record({
    protocol: WithdrawProtocol,
    tokenid: TokenID,
    address: IDL.Text,
    amount: TokenAmount,
  });
  const WithdrawResponse = IDL.Variant({ ok: IDL.Bool, err: IDL.Text });
  return IDL.Service({
    access_grant: IDL.Func([IDL.Text], [IDL.Bool], []),
    add_fastbtc: IDL.Func([IDL.Principal, IDL.Nat], [], []),
    admin_access_add: IDL.Func([IDL.Vec(IDL.Text), IDL.Text], [], []),
    admin_discount_add: IDL.Func([IDL.Vec(IDL.Text), IDL.Text], [], []),
    getBalance: IDL.Func([IDL.Text, TokenID], [TokenAmount], ['query']),
    getOperation: IDL.Func([IDL.Nat], [IDL.Opt(Operation)], ['query']),
    getOperations: IDL.Func([IDL.Nat, IDL.Nat], [IDL.Vec(OperationAndId)], ['query']),
    getToken: IDL.Func([TokenID], [IDL.Opt(Token)], ['query']),
    icrc10_supported_standards: IDL.Func(
      [],
      [IDL.Vec(IDL.Record({ url: IDL.Text, name: IDL.Text }))],
      ['query']
    ),
    icrc28_trusted_origins: IDL.Func([], [IDL.Vec(IDL.Text)], ['query']),
    token_deposit: IDL.Func([TokenID, TokenAmount], [TokenAmount], []),
    token_liquidity: IDL.Func([LiquidityRequest], [LiquidityResponse], []),
    token_list: IDL.Func([ListRequest], [ListResponse], []),
    token_mint: IDL.Func([MintRequest], [MintResponse], []),
    token_trade: IDL.Func([TradeRequest], [TradeResponse], []),
    token_transfer: IDL.Func([TransferRequest], [TransferResponse], []),
    token_withdraw: IDL.Func([WithdrawRequest], [WithdrawResponse], []),
    user_claim: IDL.Func([], [TokenAmount], []),
    voucher_claim: IDL.Func([IDL.Text], [IDL.Opt(TokenAmount)], []),
  });
};
export const init = ({ IDL }: { IDL: any }) => {
  return [];
};

Configuration Setup

Create the necessary configuration files for your project:
...

// Odin Trading Canister IDs by environment
export const ODIN_CANISTER_IDS = {
  development: 'w5cxm-6iaaa-aaaaj-az4jq-cai',
  staging: 'z2vm5-gaaaa-aaaaj-azw6q-cai',
  production: 'z2vm5-gaaaa-aaaaj-azw6q-cai',
} as const;

// Default canister ID (can be overridden by environment)
export const ODIN_CANISTER_ID = process.env.ODIN_CANISTER_ID || ODIN_CANISTER_IDS.development;

Environment Configuration

  • Development
  • Production
const ODIN_CANISTER_ID = 'w5cxm-6iaaa-aaaaj-az4jq-cai';
Use this endpoint for development and testing purposes. Doesn’t require BTC

Deposit BTC

This is only for development and testing purposes. This method is not exposed in production. For onchain deposits, use the corresponding deposit address of the coin/token/rune.
To interact with any canister method, we just need to create an actor based on that identity.
utils/odin.ts
import { Actor, HttpAgent } from "@dfinity/agent";
import { DelegationIdentity } from "@dfinity/identity";
import { OdinActor } from "../types/odin";
import { DEFAULT_IC_HOST, ODIN_CANISTER_ID } from "../constants/canister";
import { idlFactory as OdinIdlFactory } from '../canister/odin_canister.idl';

export const getActor = (identity: DelegationIdentity): OdinActor => {
    // Step 1: Create an authenticated actor with the identity
     const agent = new HttpAgent({ identity, host: DEFAULT_IC_HOST });
     const odinActor = Actor.createActor(OdinIdlFactory, {
       agent,
       canisterId: ODIN_CANISTER_ID,
     }) as OdinActor;

     return odinActor;
}
You can view more canister methods here.

Testing the Deposit Function

Let’s test our identity by calling a function in the Odin canister:
index.ts
import { login, prepare } from './core/prepare';
import { createSampleWallet } from './sample-wallet';
import { authenticateCallback } from './core/auth-callback';
import { getActor } from './utils/odin';


(async () => {
  const wallet = await createSampleWallet();
  const result = await prepare(wallet.address);

  const signature = await wallet.signMessage(result.message);

  const identity = await login({
    address: wallet.address,
    message: result.message,
    signature: signature,
    publicKey: wallet.publicKey,
    signatureType: 'Bip322Simple',
  });

  // Used for API calls.
  const token = await authenticateCallback(identity);

  const odin = getActor(identity);

  const BITCOIN = {
    id: 'btc',
    name: 'Bitcoin',
    ticker: 'BTC',
    image: 'https://upload.wikimedia.org/wikipedia/commons/4/46/Bitcoin.svg',
    rune: null,
    divisibility: 8,
    decimals: 3,
  };

  const btc = 0.001; 
  const amount = BigInt(btc * 10 ** (BITCOIN.divisibility + BITCOIN.decimals));
  const newBalance  = await odin.token_deposit(BITCOIN.id, amount);

  console.log({
    newBalance,
  });
})();


Run the test:
npx tsx index.ts
{ newBalance: 10000000n }
Great! You’ve successfully integrated with Odin!
I