From 505852109bde197d2b252de5993ba3416693a4ca Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 28 Jul 2026 22:03:59 +0200 Subject: [PATCH] refactor(motoko/icp_transfer): call the ICP ledger via canister: import + --actor-id-alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hand-written ICP ledger actor type (and the hardcoded `actor("ryjl3-…")` reference) with a typed `canister:icp_ledger` import bound by moc's `--actor-id-alias`. All ledger types now come from the ledger's official Candid interface — nothing is hand-declared. Why `--actor-id-alias` (and not `--actor-env-alias` or generated bindings): the ICP ledger lives at the *same, fixed, well-known* principal (ryjl3-tyaaa-aaaaa-aaaba-cai) on both mainnet and the local development network. When the target id is fixed and universal, binding it at compile time is the simplest correct choice — no per-environment env-var injection needed (that's what `--actor-env-alias` is for), and no runtime `actor(id)` construction (that's for targets chosen at runtime). - candid/icp_ledger.did — the ledger's official interface, from the ledger-suite-icp-2025-08-29 release (`ledger.did`). - mops.toml — `--actor-id-alias icp_ledger ryjl3-tyaaa-aaaaa-aaaba-cai candid/icp_ledger.did`. - backend/app.mo — `import IcpLedger "canister:icp_ledger"`; types are now `IcpLedger.Tokens` / `IcpLedger.TransferArgs` / …; removed the hand-written Tokens/SubAccount/BlockIndex/AccountIdentifier/LedgerTransferArgs/TransferError/ TransferResult and the `actor("ryjl3-…")` line. Verified end-to-end: `icp deploy && bash test.sh` — all 3 tests pass (real ICP transfers via the imported ledger, with correct balance deltas). Co-Authored-By: Claude Opus 4.8 (1M context) --- motoko/icp_transfer/README.md | 8 + motoko/icp_transfer/backend/app.mo | 56 +-- motoko/icp_transfer/candid/icp_ledger.did | 586 ++++++++++++++++++++++ motoko/icp_transfer/mops.toml | 10 +- 4 files changed, 619 insertions(+), 41 deletions(-) create mode 100644 motoko/icp_transfer/candid/icp_ledger.did diff --git a/motoko/icp_transfer/README.md b/motoko/icp_transfer/README.md index 79f9d736ae..44de72d122 100644 --- a/motoko/icp_transfer/README.md +++ b/motoko/icp_transfer/README.md @@ -14,6 +14,14 @@ The example exposes three functions to make this concrete: > The ICP ledger also supports the [ICRC-1](https://github.com/dfinity/ICRC-1) standard via `icrc1_transfer`. For new token integrations that don't require AccountIdentifier compatibility, ICRC-1 is the recommended interface. A comprehensive ICRC ledger example is planned. +## Calling the ICP ledger + +The backend reaches the ledger through the typed import `import IcpLedger "canister:icp_ledger"` — no ledger types are declared in the code. Because the ICP ledger lives at the **same well-known principal** (`ryjl3-tyaaa-aaaaa-aaaba-cai`) on both mainnet and the local development network, the `--actor-id-alias` flag in `mops.toml` binds the import to that fixed id and types it against the ledger's official Candid interface (`candid/icp_ledger.did`). The request/response types (`IcpLedger.Tokens`, `IcpLedger.TransferArgs`, …) come straight from that interface. + +> `--actor-id-alias` fits here because the target's id is *fixed and universal*. When a target's id varies per environment, `--actor-env-alias` resolves it from an injected env var instead; when the target is chosen at runtime, generate bindings and construct `actor(principal)` per call. + +`candid/icp_ledger.did` is the ledger's own interface, taken from the [ICP ledger suite release](https://github.com/dfinity/ic/releases/tag/ledger-suite-icp-2025-08-29) (`ledger.did`). To refresh it after a new ledger release, download the `ledger.did` asset from that release. + ## Build and deploy from the command line ### Prerequisites diff --git a/motoko/icp_transfer/backend/app.mo b/motoko/icp_transfer/backend/app.mo index 61c519d43c..421bb8385c 100644 --- a/motoko/icp_transfer/backend/app.mo +++ b/motoko/icp_transfer/backend/app.mo @@ -6,48 +6,24 @@ import Result "mo:core/Result"; import Error "mo:core/Error"; import Principal "mo:core/Principal"; +// The ICP ledger is imported by name via `canister:icp_ledger`. It lives at the +// same well-known principal (ryjl3-tyaaa-aaaaa-aaaba-cai) on both mainnet and the +// local development network, so the mops.toml `--actor-id-alias` flag binds this +// import to that fixed id and types it against the ledger's official Candid +// interface (candid/icp_ledger.did) — no ledger types are declared here. +import IcpLedger "canister:icp_ledger"; + actor IcpTransfer { - // ICP Ledger types (matches the ICP ledger Candid interface) - type Tokens = { e8s : Nat64 }; - type SubAccount = Blob; - type BlockIndex = Nat64; + // Shared transfer logic. + // // An AccountIdentifier is a 32-byte blob that encodes a principal and an - // optional subaccount. It is the native account format used by the ICP ledger. + // optional subaccount — the native account format used by the ICP ledger. // Centralized exchanges (CEXs) identify accounts by this blob; wallets and // newer integrations prefer the ICRC-1 format (principal + subaccount directly). - // // Use Principal.toLedgerAccount(subaccount) to compute one from a principal. - type AccountIdentifier = Blob; - - type TimeStamp = { timestamp_nanos : Nat64 }; - - type LedgerTransferArgs = { - memo : Nat64; - amount : Tokens; - fee : Tokens; - from_subaccount : ?SubAccount; - to : AccountIdentifier; - created_at_time : ?TimeStamp; - }; - - type TransferError = { - #BadFee : { expected_fee : Tokens }; - #InsufficientFunds : { balance : Tokens }; - #TxTooOld : { allowed_window_nanos : Nat64 }; - #TxCreatedInFuture; - #TxDuplicate : { duplicate_of : BlockIndex }; - }; - - type TransferResult = { #Ok : BlockIndex; #Err : TransferError }; - - // The ICP ledger is a system canister available on both mainnet and - // the local development network at this well-known principal. - let icpLedger : actor { transfer : (LedgerTransferArgs) -> async TransferResult } = actor ("ryjl3-tyaaa-aaaaa-aaaba-cai"); - - // Shared transfer logic. - func doTransfer(amount : Tokens, to : AccountIdentifier) : async Result.Result { - let transferArgs : LedgerTransferArgs = { + func doTransfer(amount : IcpLedger.Tokens, to : IcpLedger.AccountIdentifier) : async Result.Result { + let transferArgs : IcpLedger.TransferArgs = { memo = 0; amount; fee = { e8s = 10_000 }; @@ -59,7 +35,7 @@ actor IcpTransfer { created_at_time = null; }; try { - switch (await icpLedger.transfer(transferArgs)) { + switch (await IcpLedger.transfer(transferArgs)) { case (#Err(e)) #err("Transfer failed: " # debug_show e); case (#Ok(blockIndex)) #ok blockIndex; }; @@ -70,7 +46,7 @@ actor IcpTransfer { // Convert a principal and optional subaccount to its AccountIdentifier as a // lowercase hex string — the format shown in block explorers and CEX deposit screens. - public query func toAccountIdHex(p : Principal, subaccount : ?SubAccount) : async Text { + public query func toAccountIdHex(p : Principal, subaccount : ?IcpLedger.SubAccount) : async Text { let bytes = Array.fromIter(p.toLedgerAccount(subaccount).vals()); Hex.toText(bytes); }; @@ -78,7 +54,7 @@ actor IcpTransfer { // Transfer ICP to a recipient identified by principal + optional subaccount. // Internally calls Principal.toLedgerAccount to derive the AccountIdentifier. // This is the most convenient form when you have a principal. - public shared func transferToPrincipal(amount : Tokens, toPrincipal : Principal, toSubaccount : ?SubAccount) : async Result.Result { + public shared func transferToPrincipal(amount : IcpLedger.Tokens, toPrincipal : Principal, toSubaccount : ?IcpLedger.SubAccount) : async Result.Result { Debug.print("Transferring " # debug_show amount # " to principal " # debug_show toPrincipal); await doTransfer(amount, toPrincipal.toLedgerAccount(toSubaccount)); }; @@ -91,7 +67,7 @@ actor IcpTransfer { // validates it and returns a clear error on mismatch. A fromHex helper with // CRC32 validation (equivalent to AccountIdentifier::from_hex in ic-ledger-types) // would be a valuable addition to a Motoko ICP library. - public shared func transferToAccountId(amount : Tokens, toAccountIdHex : Text) : async Result.Result { + public shared func transferToAccountId(amount : IcpLedger.Tokens, toAccountIdHex : Text) : async Result.Result { switch (Hex.toArray(toAccountIdHex)) { case (#err(e)) #err("invalid hex: " # e); case (#ok(bytes)) { diff --git a/motoko/icp_transfer/candid/icp_ledger.did b/motoko/icp_transfer/candid/icp_ledger.did new file mode 100644 index 0000000000..77d6201c33 --- /dev/null +++ b/motoko/icp_transfer/candid/icp_ledger.did @@ -0,0 +1,586 @@ +// This is the official Ledger interface that is guaranteed to be backward compatible. + +// Amount of tokens, measured in 10^-8 of a token. +type Tokens = record { + e8s : nat64 +}; + +// Number of nanoseconds from the UNIX epoch in UTC timezone. +type TimeStamp = record { + timestamp_nanos : nat64 +}; + +// AccountIdentifier is a 32-byte array. +// The first 4 bytes is big-endian encoding of a CRC32 checksum of the last 28 bytes. +type AccountIdentifier = blob; + +// Subaccount is an arbitrary 32-byte byte array. +// Ledger uses subaccounts to compute the source address, which enables one +// principal to control multiple ledger accounts. +type SubAccount = blob; + +// Sequence number of a block produced by the ledger. +type BlockIndex = nat64; + +type Transaction = record { + memo : Memo; + icrc1_memo : opt blob; + operation : opt Operation; + created_at_time : TimeStamp +}; + +// An arbitrary number associated with a transaction. +// The caller can set it in a `transfer` call as a correlation identifier. +type Memo = nat64; + +// Arguments for the `transfer` call. +type TransferArgs = record { + // Transaction memo. + // See comments for the `Memo` type. + memo : Memo; + // The amount that the caller wants to transfer to the destination address. + amount : Tokens; + // The amount that the caller pays for the transaction. + // Must be 10000 e8s. + fee : Tokens; + // The subaccount from which the caller wants to transfer funds. + // If null, the ledger uses the default (all zeros) subaccount to compute the source address. + // See comments for the `SubAccount` type. + from_subaccount : opt SubAccount; + // The destination account. + // If the transfer is successful, the balance of this address increases by `amount`. + to : AccountIdentifier; + // The point in time when the caller created this request. + // If null, the ledger uses current IC time as the timestamp. + created_at_time : opt TimeStamp +}; + +type TransferError = variant { + // The fee that the caller specified in the transfer request was not the one that ledger expects. + // The caller can change the transfer fee to the `expected_fee` and retry the request. + BadFee : record { expected_fee : Tokens }; + // The account specified by the caller doesn't have enough funds. + InsufficientFunds : record { balance : Tokens }; + // The request is too old. + // The ledger only accepts requests created within 24 hours window. + // This is a non-recoverable error. + TxTooOld : record { allowed_window_nanos : nat64 }; + // The caller specified `created_at_time` that is too far in future. + // The caller can retry the request later. + TxCreatedInFuture : null; + // The ledger has already executed the request. + // `duplicate_of` field is equal to the index of the block containing the original transaction. + TxDuplicate : record { duplicate_of : BlockIndex } +}; + +type TransferResult = variant { + Ok : BlockIndex; + Err : TransferError +}; + +// Arguments for the `account_balance` call. +type AccountBalanceArgs = record { + account : AccountIdentifier +}; + +type TransferFeeArg = record {}; + +type TransferFee = record { + // The fee to pay to perform a transfer + transfer_fee : Tokens +}; + +type GetBlocksArgs = record { + // The index of the first block to fetch. + start : BlockIndex; + // Max number of blocks to fetch. + length : nat64 +}; + +type Operation = variant { + Mint : record { + to : AccountIdentifier; + amount : Tokens + }; + Burn : record { + from : AccountIdentifier; + spender : opt AccountIdentifier; + amount : Tokens + }; + Transfer : record { + from : AccountIdentifier; + to : AccountIdentifier; + amount : Tokens; + fee : Tokens; + spender : opt vec nat8 + }; + Approve : record { + from : AccountIdentifier; + spender : AccountIdentifier; + // This field is deprecated and should not be used. + allowance_e8s : int; + allowance : Tokens; + fee : Tokens; + expires_at : opt TimeStamp; + expected_allowance : opt Tokens + } +}; + +type Block = record { + parent_hash : opt blob; + transaction : Transaction; + timestamp : TimeStamp +}; + +// A prefix of the block range specified in the [GetBlocksArgs] request. +type BlockRange = record { + // A prefix of the requested block range. + // The index of the first block is equal to [GetBlocksArgs.from]. + // + // Note that the number of blocks might be less than the requested + // [GetBlocksArgs.len] for various reasons, for example: + // + // 1. The query might have hit the replica with an outdated state + // that doesn't have the full block range yet. + // 2. The requested range is too large to fit into a single reply. + // + // NOTE: the list of blocks can be empty if: + // 1. [GetBlocksArgs.len] was zero. + // 2. [GetBlocksArgs.from] was larger than the last block known to the canister. + blocks : vec Block +}; + +// An error indicating that the arguments passed to [QueryArchiveFn] were invalid. +type QueryArchiveError = variant { + // [GetBlocksArgs.from] argument was smaller than the first block + // served by the canister that received the request. + BadFirstBlockIndex : record { + requested_index : BlockIndex; + first_valid_index : BlockIndex + }; + + // Reserved for future use. + Other : record { + error_code : nat64; + error_message : text + } +}; + +type QueryArchiveResult = variant { + // Successfully fetched zero or more blocks. + Ok : BlockRange; + // The [GetBlocksArgs] request was invalid. + Err : QueryArchiveError +}; + +// A function that is used for fetching archived ledger blocks. +type QueryArchiveFn = func(GetBlocksArgs) -> (QueryArchiveResult) query; + +// The result of a "query_blocks" call. +// +// The structure of the result is somewhat complicated because the main ledger canister might +// not have all the blocks that the caller requested: One or more "archive" canisters might +// store some of the requested blocks. +// +// Note: as of Q4 2021 when this interface is authored, the IC doesn't support making nested +// query calls within a query call. +type QueryBlocksResponse = record { + // The total number of blocks in the chain. + // If the chain length is positive, the index of the last block is `chain_len - 1`. + chain_length : nat64; + + // System certificate for the hash of the latest block in the chain. + // Only present if `query_blocks` is called in a non-replicated query context. + certificate : opt blob; + + // List of blocks that were available in the ledger when it processed the call. + // + // The blocks form a contiguous range, with the first block having index + // [first_block_index] (see below), and the last block having index + // [first_block_index] + len(blocks) - 1. + // + // The block range can be an arbitrary sub-range of the originally requested range. + blocks : vec Block; + + // The index of the first block in "blocks". + // If the blocks vector is empty, the exact value of this field is not specified. + first_block_index : BlockIndex; + + // Encoding of instructions for fetching archived blocks whose indices fall into the + // requested range. + // + // For each entry `e` in [archived_blocks], `[e.from, e.from + len)` is a sub-range + // of the originally requested block range. + archived_blocks : vec ArchivedBlocksRange +}; + +type ArchivedBlocksRange = record { + // The index of the first archived block that can be fetched using the callback. + start : BlockIndex; + + // The number of blocks that can be fetch using the callback. + length : nat64; + + // The function that should be called to fetch the archived blocks. + // The range of the blocks accessible using this function is given by [from] + // and [len] fields above. + callback : QueryArchiveFn +}; + +type ArchivedEncodedBlocksRange = record { + callback : func(GetBlocksArgs) -> ( + variant { Ok : vec blob; Err : QueryArchiveError } + ) query; + start : nat64; + length : nat64 +}; + +type QueryEncodedBlocksResponse = record { + certificate : opt blob; + blocks : vec blob; + chain_length : nat64; + first_block_index : nat64; + archived_blocks : vec ArchivedEncodedBlocksRange +}; + +type Archive = record { + canister_id : principal +}; + +type Archives = record { + archives : vec Archive +}; + +type Duration = record { + secs : nat64; + nanos : nat32 +}; + +type ArchiveOptions = record { + trigger_threshold : nat64; + num_blocks_to_archive : nat64; + node_max_memory_size_bytes : opt nat64; + max_message_size_bytes : opt nat64; + controller_id : principal; + more_controller_ids : opt vec principal; + cycles_for_archive_creation : opt nat64; + max_transactions_per_response : opt nat64 +}; + +// Account identifier encoded as a 64-byte ASCII hex string. +type TextAccountIdentifier = text; + +// Arguments for the `send_dfx` call. +type SendArgs = record { + memo : Memo; + amount : Tokens; + fee : Tokens; + from_subaccount : opt SubAccount; + to : TextAccountIdentifier; + created_at_time : opt TimeStamp +}; + +type AccountBalanceArgsDfx = record { + account : TextAccountIdentifier +}; + +type FeatureFlags = record { + icrc2 : bool +}; + +type InitArgs = record { + minting_account : TextAccountIdentifier; + icrc1_minting_account : opt Account; + initial_values : vec record { TextAccountIdentifier; Tokens }; + max_message_size_bytes : opt nat64; + transaction_window : opt Duration; + archive_options : opt ArchiveOptions; + send_whitelist : vec principal; + transfer_fee : opt Tokens; + token_symbol : opt text; + token_name : opt text; + feature_flags : opt FeatureFlags +}; + +type Icrc1BlockIndex = nat; +// Number of nanoseconds since the UNIX epoch in UTC timezone. +type Icrc1Timestamp = nat64; +type Icrc1Tokens = nat; + +type Account = record { + owner : principal; + subaccount : opt SubAccount +}; + +type TransferArg = record { + from_subaccount : opt SubAccount; + to : Account; + amount : Icrc1Tokens; + fee : opt Icrc1Tokens; + memo : opt blob; + created_at_time : opt Icrc1Timestamp +}; + +type Icrc1TransferError = variant { + BadFee : record { expected_fee : Icrc1Tokens }; + BadBurn : record { min_burn_amount : Icrc1Tokens }; + InsufficientFunds : record { balance : Icrc1Tokens }; + TooOld; + CreatedInFuture : record { ledger_time : nat64 }; + TemporarilyUnavailable; + Duplicate : record { duplicate_of : Icrc1BlockIndex }; + GenericError : record { error_code : nat; message : text } +}; + +type Icrc1TransferResult = variant { + Ok : Icrc1BlockIndex; + Err : Icrc1TransferError +}; + +// The value returned from the [icrc1_metadata] endpoint. +type Value = variant { + Nat : nat; + Int : int; + Text : text; + Blob : blob +}; + +type UpgradeArgs = record { + icrc1_minting_account : opt Account; + feature_flags : opt FeatureFlags +}; + +type LedgerCanisterPayload = variant { + Init : InitArgs; + Upgrade : opt UpgradeArgs +}; + +type ApproveArgs = record { + from_subaccount : opt SubAccount; + spender : Account; + amount : Icrc1Tokens; + expected_allowance : opt Icrc1Tokens; + expires_at : opt Icrc1Timestamp; + fee : opt Icrc1Tokens; + memo : opt blob; + created_at_time : opt Icrc1Timestamp +}; + +type ApproveError = variant { + BadFee : record { expected_fee : Icrc1Tokens }; + InsufficientFunds : record { balance : Icrc1Tokens }; + AllowanceChanged : record { current_allowance : Icrc1Tokens }; + Expired : record { ledger_time : nat64 }; + TooOld; + CreatedInFuture : record { ledger_time : nat64 }; + Duplicate : record { duplicate_of : Icrc1BlockIndex }; + TemporarilyUnavailable; + GenericError : record { error_code : nat; message : text } +}; + +type ApproveResult = variant { + Ok : Icrc1BlockIndex; + Err : ApproveError +}; + +type AllowanceArgs = record { + account : Account; + spender : Account +}; + +type Allowance = record { + allowance : Icrc1Tokens; + expires_at : opt Icrc1Timestamp +}; + +type TransferFromArgs = record { + spender_subaccount : opt SubAccount; + from : Account; + to : Account; + amount : Icrc1Tokens; + fee : opt Icrc1Tokens; + memo : opt blob; + created_at_time : opt Icrc1Timestamp +}; + +type TransferFromResult = variant { + Ok : Icrc1BlockIndex; + Err : TransferFromError +}; + +type TransferFromError = variant { + BadFee : record { expected_fee : Icrc1Tokens }; + BadBurn : record { min_burn_amount : Icrc1Tokens }; + InsufficientFunds : record { balance : Icrc1Tokens }; + InsufficientAllowance : record { allowance : Icrc1Tokens }; + TooOld; + CreatedInFuture : record { ledger_time : Icrc1Timestamp }; + Duplicate : record { duplicate_of : Icrc1BlockIndex }; + TemporarilyUnavailable; + GenericError : record { error_code : nat; message : text } +}; + +type icrc21_consent_message_metadata = record { + language : text; + utc_offset_minutes : opt int16 +}; + +type icrc21_consent_message_spec = record { + metadata : icrc21_consent_message_metadata; + device_spec : opt variant { + GenericDisplay; + FieldsDisplay + } +}; + +type icrc21_consent_message_request = record { + method : text; + arg : blob; + user_preferences : icrc21_consent_message_spec +}; + +type Icrc21Value = variant { + TokenAmount : record { + decimals : nat8; + amount : nat64; + symbol : text + }; + TimestampSeconds : record { + amount : nat64 + }; + DurationSeconds : record { + amount : nat64 + }; + Text : record { + content : text + } +}; + +type FieldsDisplay = record { + intent : text; + fields : vec record { text; Icrc21Value } +}; + +type icrc21_consent_message = variant { + GenericDisplayMessage : text; + FieldsDisplayMessage : FieldsDisplay +}; + +type icrc21_consent_info = record { + consent_message : icrc21_consent_message; + metadata : icrc21_consent_message_metadata +}; + +type icrc21_error_info = record { + description : text +}; + +type icrc21_error = variant { + UnsupportedCanisterCall : icrc21_error_info; + ConsentMessageUnavailable : icrc21_error_info; + InsufficientPayment : icrc21_error_info; + + // Any error not covered by the above variants. + GenericError : record { + error_code : nat; + description : text + } +}; + +type icrc21_consent_message_response = variant { + Ok : icrc21_consent_info; + Err : icrc21_error +}; + +// The arguments for the `get_allowances` endpoint. +// The `prev_spender_id` argument can be used for pagination. If specified +// the endpoint returns allowances that are lexicographically greater than +// (`from_account_id`, `prev_spender_id`) - start with spender after `prev_spender_id`. +type GetAllowancesArgs = record { + from_account_id : TextAccountIdentifier; + prev_spender_id : opt TextAccountIdentifier; + take : opt nat64 +}; + +// The allowances returned by the `get_allowances` endpoint. +type Allowances = vec record { + from_account_id : TextAccountIdentifier; + to_spender_id : TextAccountIdentifier; + allowance : Tokens; + expires_at : opt nat64 +}; + +type TipOfChainRes = record { + certification : opt blob; + tip_index : BlockIndex +}; + +type RemoveApprovalArgs = record { + from_subaccount : opt SubAccount; + spender : AccountIdentifier; + fee : opt Icrc1Tokens +}; + +service : (LedgerCanisterPayload) -> { + // Transfers tokens from a subaccount of the caller to the destination address. + // The source address is computed from the principal of the caller and the specified subaccount. + // When successful, returns the index of the block containing the transaction. + transfer : (TransferArgs) -> (TransferResult); + + // Returns the amount of Tokens on the specified account. + account_balance : (AccountBalanceArgs) -> (Tokens) query; + + // Returns the account identifier for the given Principal and subaccount. + account_identifier : (Account) -> (AccountIdentifier) query; + + // Returns the current transfer_fee. + transfer_fee : (TransferFeeArg) -> (TransferFee) query; + + // Queries blocks in the specified range. + query_blocks : (GetBlocksArgs) -> (QueryBlocksResponse) query; + + // Queries encoded blocks in the specified range + query_encoded_blocks : (GetBlocksArgs) -> (QueryEncodedBlocksResponse) query; + + // Returns token symbol. + symbol : () -> (record { symbol : text }) query; + + // Returns token name. + name : () -> (record { name : text }) query; + + // Returns token decimals. + decimals : () -> (record { decimals : nat32 }) query; + + // Returns the existing archive canisters information. + archives : () -> (Archives) query; + + send_dfx : (SendArgs) -> (BlockIndex); + account_balance_dfx : (AccountBalanceArgsDfx) -> (Tokens) query; + + // The following methods implement the ICRC-1 Token Standard. + // https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-1 + icrc1_name : () -> (text) query; + icrc1_symbol : () -> (text) query; + icrc1_decimals : () -> (nat8) query; + icrc1_metadata : () -> (vec record { text; Value }) query; + icrc1_total_supply : () -> (Icrc1Tokens) query; + icrc1_fee : () -> (Icrc1Tokens) query; + icrc1_minting_account : () -> (opt Account) query; + icrc1_balance_of : (Account) -> (Icrc1Tokens) query; + icrc1_transfer : (TransferArg) -> (Icrc1TransferResult); + icrc1_supported_standards : () -> (vec record { name : text; url : text }) query; + icrc2_approve : (ApproveArgs) -> (ApproveResult); + icrc2_allowance : (AllowanceArgs) -> (Allowance) query; + icrc2_transfer_from : (TransferFromArgs) -> (TransferFromResult); + + remove_approval : (RemoveApprovalArgs) -> (ApproveResult); + + icrc21_canister_call_consent_message : (icrc21_consent_message_request) -> (icrc21_consent_message_response); + icrc10_supported_standards : () -> (vec record { name : text; url : text }) query; + + get_allowances : (GetAllowancesArgs) -> (Allowances) query; + + tip_of_chain : () -> (TipOfChainRes) query; + + is_ledger_ready : () -> (bool) query +} diff --git a/motoko/icp_transfer/mops.toml b/motoko/icp_transfer/mops.toml index c8485e6b3a..64c450b173 100644 --- a/motoko/icp_transfer/mops.toml +++ b/motoko/icp_transfer/mops.toml @@ -6,7 +6,15 @@ core = "2.5.0" hex = "1.0.3" [moc] -args = [ "--default-persistent-actors", "-W=M0236,M0237,M0223" ] +args = [ + "--default-persistent-actors", + "-W=M0236,M0237,M0223", + # --actor-id-alias : bind the `canister:icp_ledger` + # import to the ICP ledger's fixed, well-known principal (same on mainnet and the + # local network), typed against candid/icp_ledger.did — so no ledger types are + # hand-written in the backend. + "--actor-id-alias", "icp_ledger", "ryjl3-tyaaa-aaaaa-aaaba-cai", "candid/icp_ledger.did", +] [canisters.backend] main = "backend/app.mo"