| ICRC | Title | Author | Discussions | Status | Type | Category | Created |
|---|---|---|---|---|---|---|---|
| 79 | Secure Subscription Canister Standard | Austin Fatheree @skilesare | dfinity/ICRC#79 | Draft | Standards Track | Financial | 2024-09-05 |
This section outlines the core data types utilized in the ICRC-79 standard, facilitating the description and structuring of data relevant to secure subscription canisters.
Each subscription is uniquely defined by the following attributes, capturing the necessary details for managing periodic payment requests under various configurations.
type Interval = variant {
Hourly;
Daily;
Weekly;
Monthly;
Yearly;
Interval : nat; // Interval in nanoseconds
Days : nat;
Weeks : nat;
Months : nat;
};
type SubscriptionRequest = [SubscriptionRequestItem];
type CheckRate = {
decimals: Nat32;
rate: Nat64;
};
type SubscriptionRequestItem = variant {
tokenCanister: Principal; // Required: base token to use to pay the subscription
tokenPointer: Blob; // Optional: ICRC-80 token pointer. If omitted, the tokenCanister route is used directly.
serviceCanister: Principal; // Required: service canister to subscribe to
interval: Interval; // Required: interval to pay the subscription
amountPerInterval: Nat; // Required: amount to pay per interval
baseRateAsset: (Asset, CheckRate); // Optional: convert amountPerInterval against a quoted asset. If omitted, the entered token amount is used directly.
endDate: Nat; // Optional: timestamp in nanoseconds to end the subscription. If omitted, the subscription is open-ended.
targetAccount: Account; // Optional: account to pay the subscription to. Defaults to the service canister default account if omitted.
productId: Nat; // Optional: vendor specified product id. If omitted, no product id is attached.
firstPayment: Nat; // Optional: time for the first regular payment. If omitted, the first payment is immediate.
nowPayment: Nat; // Optional: token amount to process immediately. If omitted, no immediate override payment is scheduled.
memo: Blob; // Optional: memo to include with the subscription. If omitted, no memo is attached.
createdAtTime: Nat; // Optional: timestamp for deduplication. If omitted, no caller-supplied createdAtTime override is attached.
subaccount: Blob; // Optional: subaccount to use for the subscription. If omitted, the subscriber's default account is used.
broker: Account; // Optional: broker that may be paid a fee for the subscription. If omitted, no broker is attached.
};
public type SubscriptionRequestItemKeys = variant {
tokenCanister;
serviceCanister;
interval;
amountPerInterval;
baseRateAsset;
endDate;
targetAccount;
productId;
firstPayment;
nowPayment;
memo;
createdAtTime;
subaccount;
};
type SubscriptionResult = variant {
Ok: SubscriptionResponse;
Err: SubscriptionError;
}
type SubscriptionResponse = record {
transactionId: nat;
subscriptionId: nat;
};
type SubscriptionError = variant {
Unauthorized;
TokenNotFound;
Duplicate; //due to async nature, can't provide id
SubscriptionNotFound;
InsufficientAllowance : nat;
InsufficientBalance : nat;
InvalidDate;
InvalidInterval;
FoundActiveSubscription : nat;
Other: {
code: nat;
message: text;
};
};
type AssetClass = variant { Cryptocurrency; FiatCurrency; };
type Asset = record {
symbol: text;
class: AssetClass;
};
// The parameters for the `get_exchange_rate` API call.
type ExchangeRate = record {
base_asset: Asset;
quote_asset: Asset;
min_recieved_base: opt nat64;
min_recieved_quote: opt nat64;
max_standard_deviation : opt nat64;
};
type ExchangeRateResult = record {
base_asset: Asset;
quote_asset: Asset;
timestamp: nat64;
rate: nat64;
metadata: ExchangeRateMetadata;
};
type ExchangeRateError = variant {
// Returned when the canister receives a call from the anonymous principal.
AnonymousPrincipalNotAllowed: null;
/// Returned when the canister is in process of retrieving a rate from an exchange.
Pending: null;
// Returned when the base asset rates are not found from the exchanges HTTP outcalls.
CryptoBaseAssetNotFound: null;
// Returned when the quote asset rates are not found from the exchanges HTTP outcalls.
CryptoQuoteAssetNotFound: null;
// Returned when the stablecoin rates are not found from the exchanges HTTP outcalls needed for computing a crypto/fiat pair.
StablecoinRateNotFound: null;
// Returned when there are not enough stablecoin rates to determine the forex/USDT rate.
StablecoinRateTooFewRates: null;
// Returned when the stablecoin rate is zero.
StablecoinRateZeroRate: null;
// Returned when a rate for the provided forex asset could not be found at the provided timestamp.
ForexInvalidTimestamp: null;
// Returned when the forex base asset is found.
ForexBaseAssetNotFound: null;
// Returned when the forex quote asset is found.
ForexQuoteAssetNotFound: null;
// Returned when neither forex asset is found.
ForexAssetsNotFound: null;
// Returned when the caller is not the CMC and there are too many active requests.
RateLimited: null;
// Returned when the caller does not send enough cycles to make a request.
NotEnoughCycles: null;
// Returned when the canister fails to accept enough cycles.
FailedToAcceptCycles: null;
/// Returned if too many collected rates deviate substantially.
InconsistentRatesReceived: null;
// Until candid bug is fixed, new errors after launch will be placed here.
Other: record {
// The identifier for the error that occurred.
code: nat32;
// A description of the error that occurred.
description: text;
}
};
type Subscription = record {
subscriptionId: nat;
tokenCanister: principal;
tokenPointer: ?Blob;
serviceCanister: Principal;
interval: Interval;
amountPerInterval: nat;
exchangeRate: opt ExchangeRate;
endDate: opt nat; // Timestamp in nanoseconds to end the subscription
targetAccount: ?Account;
productId: opt nat;
account: Account;
nextPayment: opt nat;
nextPaymentAmount: opt nat;
status: SubStatus;
};
type SubStatus = variant {
Active;
WillCancel: (nat, principal, text); //request timestamp, caller, reason
Canceled : (nat, nat, principal, text); //request timestamp, caller, reason
Paused : (nat, principal); //timestamp, caller
};
type CancelRequest = vec CancelRequestItem
type CancelRequestItem = record {
subscriptionId: subscriptionId;
reason: Text
};
type CancelResult = vec opt variant {
Ok: nat; //transactionId
Err: CancelError;
};
type BasicError = variant {
Unauthorized;
Other: {
code: nat;
message: text;
};
};
type CancelError = variant {
Unauthorized;
NotFound;
InvalidStatus: SubStatus;
Other: {
code: Nat;
message: Text;
};
};
type PauseRequest = record {
subscriptionId: subscriptionId;
};
type PauseError = variant {
Unauthorized;
NotFound;
InvalidStatus: SubStatus;
Other: {
code: Nat;
message: Text;
};
};
The fields tokenCanister, serviceCanister, interval, and amountPerInterval are required for a valid subscription request.
The remaining SubscriptionRequestItem entries are optional and have the following default behavior when omitted:
tokenPointer: the primarytokenCanisterroute is used directly.baseRateAsset: no exchange-rate conversion override is applied;amountPerIntervalis interpreted directly in the selected token.endDate: the subscription remains active until canceled, paused, or otherwise terminated.targetAccount: the service canister's default account receives funds.productId: no product identifier is attached.firstPayment: the first payment is immediate.nowPayment: no immediate override payment is requested.memo: no memo is attached.createdAtTime: no caller-supplied deduplication timestamp override is attached.subaccount: the subscriber's default account is used.broker: no broker is attached and no broker fee is requested.
For user interfaces that generate pre-filled subscription URLs, omitting a field from the URL should not be confused with the field being optional in the final icrc79_subscribe request. Omitting a required field from a prefill URL only means the caller or user interface must still supply that value before submitting the request.
An account, identified by a principal and optionally a specific subaccount, is used to define the destination for transferred funds.
type Subaccount = blob;
type Account = record {
owner: principal;
subaccount: opt Subaccount;
};
Historical payment transactions are recorded using this structure, ensuring transparency and access to past subscription activity.
type PaymentRecord = record {
paymentId: nat;
date: nat; // Timestamp of the payment
amount: nat;
fee: opt nat;
rate: opt ExchangeRateResult; // if used
ledgerTransactionId: nat;
transactionId: nat;
feeTransactionId: opt nat;
targetAccount: opt nat;
account: Account;
productId: ?Nat;
service: Principal;
subscriptionId: nat;
result: Bool;
};
Future payment using this structure, ensuring transparency and access to future pending payments.
type PendingPayment = record {
nextPaymentDate: nat; // Timestamp of the payment
nextPaymentAmount: nat;
subscription: Subscription;
};
//todo: move notifications out of the standard and move them to an indexing server
When the system has an issue it needs to notify a service provider of it will create a service notification.
type ServiceNotificationType = variant {
CustomerEndedSubscription: record {
principal: principal;
subscriptionId: nat;
reason: text;
};
AllowanceInsufficient: record {
principal: principal;
subscriptionId: nat;
};
LedgerError: record {
error: text;
rescheduled: opt nat;
subscriptionId: nat;
};
ServiceEndedSubscription: record {
principal: principal;
subscriptionId: nat;
reason: text;
};
ExchangeRateError: record {
rate: ExchangeRate;
subscriptionId: nat;
rate_error: opt ExchangeRateError;
reason: opt Text;
};
};
type ServiceNotification = record {
principal: principal;
date: nat; // Timestamp of the notification
notification: ServiceNotificationType;
};
Integrating dynamic exchange rate capabilities into the subscription model is crucial for supporting services and transactions that involve different assets, especially in a globally distributed network like the Internet Computer. The exchange rate feature leverages the Exchange Rate Canister (XRC) to ensure that subscriptions maintain accurate and fair pricing when converting between various cryptocurrencies and fiat currencies.
The Exchange Rate Canister provides real-time and historical asset exchange rates, accommodating an array of asset pairings such as cryptocurrencies to fiat currencies or between different cryptocurrencies. When a subscription involves payments in different currencies from the base asset, the subscription canister retrieves current or historical exchange rates using the XRC.
Find more about the Exchange rate canister here: https://wiki.internetcomputer.org/wiki/Exchange_rate_canister
-
Recurring Payment Processing with Dynamic Rates: For each billing cycle, the subscription canister checks if the payment requires conversion using updated exchange rates. If so, it requests the current rate from the XRC right before processing the payment. This ensures that the amount transferred is based on the latest available exchange rate, providing fairness both to the service provider and the subscriber.
-
Handling Exchange Rate Volatility: To manage exchange rate fluctuations, the subscription can optionally set thresholds for acceptable rates and standard deviations. If the fetched rate does not satisfy these conditions (e.g., if the rate's standard deviation is too high, indicating significant volatility), the subscription canister might pause the transaction and notify the subscriber and service provider to confirm the transaction manually or adjust the terms.
-
Historical Rates for Audit and Reconciliation: For audit and record-keeping purposes, subscriptions can store historical rates used for each transaction. This facilitates transparency and simplifies reconciliation during financial audits.
When the XRC response contains an error (e.g., due to an invalid asset pair request or network issues), the subscription canister handles this by either retrying the request, pausing the subscription, or notifying the subscriber and service provider based on the error type and predefined handling protocols.
Intervals define the frequency at which payments are processed for a subscription. The ICRC-79 standard supports various predefined intervals and allows for custom-defined periods. The interval determines the recurring payment cycle for the subscription, automating the transfer of the specified amountPerInterval from the subscriber's account to the service provider's target account.
The ICRC-79 standard supports the following interval types, each corresponding to common temporal patterns for subscription services:
- Hourly: Payments are processed every hour. 3_600_000_000_000 nanoseconds.
- Daily: Payments are processed every day. 86_400_000_000_000 nanoseconds.
- Weekly: Payments are processed every week. 604_800_000_000_000
- Monthly: Payments are processed every month. A month is considered to be 30.4375 days such that every 4 years there are exactly 48 payments. 2_629_800_000_000_000 nanoseconds
- Yearly: Payments are processed every year. A year is considered to be 365.25 days such that every 4 years there are exactly 4 payments. 31_557_600_000_000_000 nanoseconds
- Interval (nat): Custom interval defined in nanoseconds.
- Days (nat): Custom interval that allows subscribers to specify payments to be processed every 'n' days.
- Weeks (nat): Custom interval that allows subscribers to specify payments to be processed every 'n' weeks.
- Months (nat): Custom interval that allows subscribers to specify payments to be processed every 'n' months.
Optionally, a firstPayment timestamp can be set to delay the start of the subscription interval. If set, the first interval will commence at this specified time, otherwise, it will begin immediately upon subscription activation. This value is in UTC nanoseconds. If it is set in the past the subscription should initiate immediately.
Here is an example of how to define a subscription with a custom weekly interval:
let subscriptionRequest = {
tokenCanister: principal "rrkah-fqaaa-aaaaa-aaaaq-cai";
serviceCanister: principal "rwlgt-iiaaa-aaaaa-aaaaa-cai";
interval: variant {Weeks =2}; // Payment every 2 weeks
amountPerInterval: 50;
endDate: opt null;
targetAccount: {...};
productId: opt 123;
firstPayment: opt null;
memo: blob "New Subscription";
createdAtTime: Nat64.now();
subaccount: opt null
};
In this example, the subscription is configured to charge 50 tokens every two weeks from the subscriber's account to the specified target account. The interval setting, alongside other subscription parameters, helps in tailoring the subscription mechanism to meet diverse business and operational needs.
Please see the Batch Update Methods, Batch Query Methods, Error Handling, and Other Aspects sections of the ICRC-7 standard for important information about the ICRC-79 approach to methods and return types.
The following Candid definitions describe the ICRC-79 standard's messages used to manipulate the subscription lifecycle. These include creating, confirming, and canceling subscriptions.
Creates a new subscription based on the data provided by the SubscriptionRequest. This function must be called by the subscribing principal, specifying a subaccount that has a valid approval for the expected amount.
icrc79_subscribe: (SubscriptionRequest) -> (SubscriptionResult);
Allows the cancellation of an existing subscription. This function can be called by either the service provider or the user account to terminate a subscription.
icrc79_cancel_subscription: (vec record { subscriptionId: nat; reason: text}) -> (CancelResult);
This function can be triggered by the service provider to force verification of the allowance. It ensures that the funds are still available as per the parameters of the subscription.
icrc79_confirm_subscription: (vec nat) -> (vec ConfirmResult);
Allows the pausing of an existing subscription by either the service provider or the user. This function can be called by either the service provider or the user account to pause a subscription.
type PauseRequestItem = record {
subscriptionId: nat;
reason: text;
active: bool;
};
type PauseResultItem = opt variant {
Ok: Nat; //trx id
Err: PauseError;
};
icrc79_pause_subscription: (vec PauseRequestItem) -> (vec PauseResultItem);
The array of tuples submitted are made up of the subscriptionId and the desired active state(true/false);
These Candid definitions specify the query methods used in ICRC-79 for retrieving information about subscriptions, payments, and payments pending. These methods provide read-only access to the subscription data stored within the canister.
This method retrieves a list of subscriptions associated with the calling user. It can optionally filter results based on the status of the subscriptions and supports pagination through prev and take parameters.
type UserSubscriptionsFilter = record {
status: opt SubStatusFilter;
subscriptions: opt vec nat;
subaccounts: opt vec opt blob;
products: opt vec opt nat;
services: opt vec Principal;
};
icrc79_get_user_subscriptions: (filter: opt UserSubscriptionsFilter, prev: opt nat, take: opt nat) : async vec Subscription;
Retrieves a list of all subscriptions managed by a service provider through the canister. This method allows filtering by product ID and supports pagination.
type SubStatusFilter = {
#Active;
#WillCancel;
#Canceled;
#Paused;
};
type ServiceSubscriptionsFilter = record {
status: opt SubStatusFilter;
subscriptions: opt vec nat;
subaccounts: opt vec opt blob;
products: opt vec opt nat;
};
icrc79_get_service_subscriptions: (service: Principal, filter: opt ServiceSubscriptionsFilter, prev: opt nat, take: opt nat) -> async vec Subscription;
Allows a user to retrieve a detailed list of past payments related to their subscriptions. This method can filter by specific subscription IDs and also supports pagination.
icrc79_get_user_payments: (filter: opt UserSubscriptionsFilter, prev: opt nat, take: opt nat) -> async vec PaymentRecord;
Retrieves pending payments for a user's subscriptions, which can be filtered by specific subscription IDs, with support for pagination.
icrc79_get_user_payments_pending: (subscriptions: vec nat) -> async vec PendingPayment;
Enables a service provider to access a list of past payments received for provided services, filterable by specific subscription IDs and supporting pagination.
icrc79_get_service_payments: (service:Principal, filter: opt ServiceSubscriptionsFilter, prev: opt nat, take: opt nat) -> async vec PaymentRecord;
Allows a service provider to fetch notifications related to the subscriptions under their management. Supports pagination.
icrc79_get_service_notifications: (prev: opt nat, take: opt nat) -> async vec ServiceNotification;
An implementation of ICRC-79 MUST implement the method icrc10_supported_standards as put forth in ICRC-10.
The result of the call MUST always have at least the following entries:
record { name = "ICRC-10"; url = "https://github.com/dfinity/ICRC/ICRCs/ICRC-10"; }
record { name = "ICRC-79"; url = "https://github.com/dfinity/ICRC/ICRCs/ICRC-79"; }
ICRC-79 expands on the ICRC-3 specification for defining the format for storing transactions in blocks of the log of the subscription ledger. Below, we define the concrete block schema for ICRC-79 as an extension of the ICRC-3 block schema. This schema must be implemented by a ledger implementing ICRC-79 if it claims to align its logs with ICRC-3 through the method listing the supported standards.
//todo: Since the create action is async, do we need a request action that is logged immediately to indicate that an action is underway?
- Block Type (
btype):"79subCreate" - Transaction (
tx) Contents:subscriptionId: Value.Nat- Identifier of the new subscription.creator: Value.Principal- Principal ID of the user initiating the subscription.subaccount: Value.Blob?- Subaccount if presenttokenCanister: Value.Principal- Principal ID of the canister managing the tokens.tokenPointer: Value.Blob?- Optional for ICRC80 tokens.serviceCanister: Value.Principal- Principal ID of the canister providing the service.interval: Value.Text- Subscription interval details in descriptive text.intervalAmt: Value.Nat?- Subscription interval count if applicable.amtPerInterval: Value.Nat- Tokens transferred per interval.productId: Value.Nat?- Tokens transferred per interval.endDate: Value.Nat?- Optional timestamp of when the subscription should end.targetAccount: Value.Array([Value.Blob,?Value.Blob]- Serialized account to which tokens are sent.broker: Value.Array([Value.Blob,?Value.Blob]- Serialized account for the broker.status: Value.Text- Initial status of the subscription.memo: Value.Opt(Value.Text)- Optional additional information about the subscription.createdAt: Value.Nat- Timestamp of when the subscription was created.
- Block Type (
btype):"79subCancel" - Transaction (
tx) Contents:subscriptionId: Value.Nat- Identifier of the canceled subscription.canceller: Value.Principal- Principal ID of the user or service that canceled the subscription.cancelReason: Value.Text- Text reason or code indicating why the subscription was canceled.canceledAt: Value.Nat- Timestamp of cancellation.
- Block Type (
btype):"79subStatus" - Transaction (
tx) Contents:subscriptionId: Value.Nat- Identifier of the subscription.caller: Value.Principal- Principal ID of the user or service that made the status change.statusReason: Value.Text- Text reason for the status change.newStatus: Value.Text- New status of the subscription.changeAt: Value.Nat- Timestamp of the status change.
- Block Type (
btype):"79payment" - Transaction (
tx) Contents:trxID: Value.Nat- Unique identifier of the payment on the token ledger.subscriptionId: Value.Nat- Related subscription identifier.amount: Value.Nat- Amount of tokens transferred.rate: Value.Nat- Optional - Rate if it was used.- `rateBase: Value.Text - Optional - Symbol of the exchange rate used if present.
ledgerTransactionId: Value.Nat- Reference ID in the token ledger.feeTransactionId: Value.Nat- Reference ID in the token ledger.brokerTransactionId: Value.Nat- Reference ID in the token ledger.tokenLedger: Value.Blob- Principal of the token ledger.tokenPointer: Value.Blob- Optional. Principal of the token pointer.paymentDate: Value.Nat- Date and time when the payment was processed.feeTransactionId: Value.nat- Pointer to the fee trx id:
Notification blocks are optional and my implemented at the discretion of the implementation.
- Block Type (
btype):"79subNote" - Transaction (
tx) Contents:notificationId: Value.Nat- Unique identifier for the notification.subscriptionId: Value.Nat- Identifier of the related subscription.principal: Value.Principal- Principal ID receiving the notification.message: Value.Text- Content of the notification.notificationDate: Value.Nat- Timestamp when the notification was generated.
Each block within the transaction log of the ICRC-79 ledger ensures that the state of any subscription can be effectively reconstructed, optimizing transparency, auditability, and reliability of the subscription services provided on the Internet Computer.
Determining the correct amount of tokens to approve for a subscription canister is critical to ensure smooth operation of the subscription mechanism while maintaining high security and user confidence. The ICRC-79 standard describes a method to effectively and securely establish the token approval amount required for a subscription through interactions with ICRC-1 and ICRC-2 standards.
-
Initial Approval using ICRC-1 Total Supply Query:
- Initially, when a user sets up a subscription, it is ideal to pre-approve an amount that is significantly higher than the periodic payment to minimize transaction costs and approval operations. The ICRC-79 standard suggests querying the
icrc1_total_supplyto understand the maximum potential tokens available. - From this total supply, the standardized approach is to set the approval amount to 50% of the total token supply. This might seem extensive, but since the subscription contract adheres to a blackholed, open-sourced code execution model that strictly conforms to pre-defined behaviors, it effectively mitigates the risk of any malicious deductions beyond the agreed subscription amounts.
- Initially, when a user sets up a subscription, it is ideal to pre-approve an amount that is significantly higher than the periodic payment to minimize transaction costs and approval operations. The ICRC-79 standard suggests querying the
-
Fallback to Wallet Balance:
- If for any reason, such as the token implementation having a max approval amount or not allowing an approval beyond a user's balance, the initial method to query the total supply fails, the next reliable source is the user’s wallet balance.
- The subscription setup should query the user's current token balance and set this balance, minus a fee, as the approval limit. This ensures that even in fallback scenarios, the subscription proceeds without inadvertently causing account overdrafts. However, this method automatically adjusts the maximum ceiling for potential payment disruptions if the wallet balance is lower than the expected subscription fees.
- If the wallet’s current balance does not cover the upcoming subscription fee, the process should cease with an error, and the subscription setup should not proceed. This safeguard prevents commitments that cannot be currently satisfied, maintaining financial integrity and trust.
The method of using a large portion of the token's total supply or the entire wallet balance as the approval limit relies heavily on the security of the subscription canister. Given that the canister operates on an immutable smart contract model, where code once deployed cannot be altered arbitrarily, the risks associated with token mishandling are minimized. The high approval limit does not pose an increased risk because of the following reasons:
- Blackholed Contract: The propensity for the contract to execute only the code available prevents any malpractices regarding token handling.
- Code Transparency: Being open-sourced, the code underlying the subscription mechanism is available for audits and inspections by community members or security experts, fostering trust through transparency.
This approach not only enhances operational efficiency by reducing the frequency of user interactions for re-approvals but also aligns with best practices in blockchain management where trust is built into the codebase's security and transparency.
Returns all the metadata of the subscription canister in a single query.
The data model for metadata is based on the generic Value type which allows for encoding arbitrarily complex data for each metadata attribute. The metadata attributes are expressed as (text, value) pairs where the first element is the name of the metadata attribute and the second element the corresponding value expressed through the Value type.
Analogous to ICRC-1 metadata, metadata keys are arbitrary Unicode strings and must follow the pattern <namespace>:<key>, where <namespace> is a string not containing colons. The namespace icrc7 is reserved for keys defined in the ICRC-7 standard.
The set of elements contained in a specific ledger's metadata depends on the ledger implementation, the list below establishes the currently defined fields.
The following metadata fields are defined by ICRC-7, starting with general collection-specific metadata fields:
The following are the more technical, implementation-oriented, metadata elements:
icrc79:max_query_batch_sizeof typenat(optional): The maximum batch size for batch query calls this implementation supports. When present, should be the same as the result of theicrc79_max_query_batch_sizequery call.icrc79:max_update_batch_sizeof typenat(optional): The maximum batch size for batch update calls this ledger implementation supports. When present, should be the same as the result of theicrc79_max_update_batch_sizequery call.icrc79:default_take_valueof typenat(optional): The default value this ledger uses for thetakepagination parameter which is used in some queries. When present, should be the same as the result of theicrc79_default_take_valuequery call.icrc79:max_take_valueof typenat(optional): The maximumtakevalue for paginated query calls this ledger implementation supports. The value applies to all paginated queries the ledger exposes. When present, should be the same as the result of theicrc79_max_take_valuequery call.icrc79:max_memo_sizeof typenat(optional): The maximum size ofmemos as supported by an implementation. When present, should be the same as the result of theicrc79_max_memo_sizequery call.icrc79:tx_windowof typenat(optional): The time window in seconds during which transactions can be deduplicated. Corresponds to the parameterTX_WINDOWas specified in the section on transaction deduplication.icrc79:permitted_driftof typenat(optional): The time duration in seconds by which the transaction deduplication window can be extended. Corresponds to the parameterPERMITTED_DRIFTas specified in the section on transaction deduplication.
Note that if icrc7_max... limits specified through metadata are violated in a query call by providing larger argument lists or resulting in larger responses than permitted, the canister SHOULD return a response only to a prefix of the request items.
// Generic value in accordance with ICRC-3
type Value = variant {
Blob : blob;
Text : text;
Nat : nat;
Int : int;
Array : vec Value;
Map : vec record { text; Value };
};
icrc79_metadata : () -> (vec record { text; Value } ) query;
The icrc79_subscribe functions should be subject to transaction deduplication as specified previously in ICRC-7.
ICRC-17 KYC Service integration is not directly supported inside of ICRC-79. For services that would like to implement ICRC-17 for regulatory and compliance reasons, the following architectural suggestions are provided:
-
Fees - for brokers or public goods accounts that want control over receiving fees from only known good actors or who wish to block the receiving of fees from specific accounts, we recommend setting the target accounts to accounts on an ICRC-17 compliant relayer that may validate a payment from an ICRC-17 source before forwarding it to an final destination and/or returning the fee otherwise.
-
Validating Subscribers - For services that would like to validate that their subscribers via ICRC-17 or other authorization protocols, we advise handling check in at the service level and returning the funds and canceling the subscription if the user does not pass. Services may use the firstPayment option to delay payment to some point in the future giving your service time to check the credentials.