Overview:
The ckNFT canister acts as a manager or controller over a set of child NFT canisters. Each child corresponds to a specific NFT and network pair. The canister must maintain robustness, scalability, and network compatibility, particularly as it seeks to integrate with additional blockchains such as Ethereum, Solana, and Bitcoin in the future.
The ckNFT canister is designed to manage and oversee the creation and maintenance of individual NFT canisters. When a request is made to interact with a specific NFT, the ckNFT canister first checks its registry to determine if the corresponding NFT canister already exists for the given NFT and network pair. If it does not find a matching entry, the ckNFT canister will initiate the process of spawning a new NFT canister. Before this new canister is created, the ckNFT canister requires the user to pay a fee to cover the computational and storage costs associated with spawning the canister. This fee acts as a deterrent against unnecessary or spam canister creation and ensures that resources are allocated efficiently. Once the fee is confirmed, the ckNFT canister deploys the new NFT canister, registers it in its internal registry, and makes it available for future interactions.
Schema Requirements:
CanisterRegistry: A registry containing mappings from NFT and network pairs to their corresponding canister IDs.
type Map = vec {record {text, Value}};
type Network = variant { Ethereum: EthChainType; Solana; Bitcoin: Text; ICP:Text; Other: Map; }
type RemoteContractPointer = record{
contractId: Text;
network: Network;
};
type EthChainType = {
chanId: nat
networkId: nat
};
type NFTCanisterRegistry = vec { record{RemoteContractPointer, canisterID : Principal}};
type NFTRemoteRegistry = vec { record{canisterID: Principal, remoteContract : RemoteContractPointer}};
WASMVersionManagement: Manage different versions of WebAssembly (WASM) binaries used by child canisters.
type WASMVersionMap = record(NFTType = text; version = [nat];binary = Blob); // Keyed by version identifier
How will we approve/deprecate new wasm binaries? Maybe need to look at how ckXXX is doing it with the ckERC20 canisters?
Interaction with Ethereum RPC Canister: Define methods for communication between the ckNFT canister and the Ethereum RPC canister.
type RemoteNFTPointer = record {
network: NetworkDescriptor;
contract: Text;
tokenId: Nat;
};
type OwnerResponse = variant {
Ok = record {
consensus: bool;
owner: text;
};
Err = tbd
};
type FoundMetadata = variant {
Ok = record {
metadata = vec Map;
consensus: bool;
};
Err = tbd
};
type CreateCanisterResponse = variant{
Ok: Principal;
Err: tbd
}
type CreateCanisterRequest = record {
version: ?[Nat];
icrc7: {
name: ?Text;
description: ?Text;
symbol: ?Text;
logo: ?Text;
};
descriptor: RemoteContractPointer;
payment: variant { //where does the payment come from. Will use icrc2 to transfer and then convert cycles.
icp: ?Account;
cycles: ?Account;
};
};
type MintResult = variant {
Ok: MintRequestID;
Err: tbd
};
type MintStatus = varian {
Transferring;
CheckingOwner: {
retries: Nat;
nextQuery: Nat;
};
RetrievingMetadata;
Minting;
Complete: {
remoteTrx: ?Text;
mintTrx: Nat;
approvalTrx: ?Nat
};
Err : variant {
InvalidTransfer : Text; //Error from RPC
OwnershipNotVerified : variant {
TooManyRetries: nat;
NoConsensus;
};
MetadataError: Text;
MintError: Text;
ApprovalError: Text;
GenericError: Text;
};
};
type MintResumeOption: variant {
StartOwnershipVerification;
StartMetadataTransfer;
StartMint;
};
type MintRequest: record {
nft:RemoteNFTPointer;
mintToAccount: Account;
spender: ?Account;
resume: ?(?Nat, ResumeOption)
}
type CastResult : variant {
Ok: Nat; //cast status id
Err: CastError; //
};
type CastStatus : variant {
VerifyingOwnership;
RetrievingCollectionMetadata;
RetrievingNFTMetadata;
WritingContract: {
trxId: ?Text;
nextQuery: Nat;
retries: Nat
};
MintingNFT: {
trxId: ?Text;
nextQuery: Nat;
retries: Nat
};
TransferringNFT: {
trxId: ?Text;
nextQuery: Nat;
retries: Nat
};
Complete: {
contractTrx: ?Text;
mintTrx: ?Text;
transferTrx: ?Nat
};
Err : variant {
Unauthorized;
CollectionError: Text;
NFTError: Text;
InvalidTransaction : Text; //Error from RPC. includes not enough gas
ContractNotVerified : variant {
TooManyRetries: nat;
NoConsensus;
};
MintNotVerified : variant {
TooManyRetries: nat;
NoConsensus;
};
TransferNotVerified : variant {
TooManyRetries: nat;
NoConsensus;
};
MintError: Text;
ApprovalError: Text;
GenericError: Text;
};
};
type UpgradeResult : variant{
Ok: bool;
Err: variant{
GenericError: Text;
};
}
type CastRequest : record{
ckNFTCanister: canister;
tokenId: nat;
targetNetwork: Network;
targetAccount: Text;
};
type service = actor {
get_remote_contract: query(vec (Principal, Network)) -> vec opt RemoteContractPointer
get_ck_nft_canister: query(vec RemoteContractPointer) -> vec opt Principal
get_mint_cost: query() -> async (Nat,Nat); //balance in ICP and cycles needed
get_cast_cost: query() -> async RemoteTokenCost; //balance in remote token that is needed at deposit address
create_canister: (CreateCanisterRequest) -> async CreateCanisterResponse;
get_owner : (RemoteNFTPointer) -> async Text;
get_remote_metadata: (RemoteContractPointer) -> async FoundMetadata;
get_remote_nft_metadata: (RemoteNFTPointer) -> async FoundMetadata;
get_approval_address: query(Account, RemoteNFTPointer) -> Text;
mint: (MintRequest) -> MintResult; //Resume with the mint status ID, you will be issued a new mint status Id. If using transfer method, pass null for the status and StartOwnershipVerification for the item.
get_mint_status: query(Nat) -> MintStatus;
add_approved_wasm_chunk: (record {
version: ?[nat];
chunkId: nat
chunk: ?blob}) -> async (nat, nat); //current chunk, total chunks; null deletes a chunk
activate_current_wasm_version: ([nat]) -> bool;
get_wasm_version: query(Text, ?[nat]) -> ?([nat],nat,blob); //version, chunks, hash; input can be null for current version
get_wasm_versions: query(Text, prev: ?[nat], take: nat) -> [([nat],nat,blob)]; //paginated list of items
get_wasm_chunk: query(Text, [nat], chunk) -> blob;
upgrade_ck_nft_canister: async(principal, Type, [nat], blob) -> UpgradeResult; //target, version, candid arguments for upgrade
cast_nft: (CastRequest) -> async CastResult;
get_cast_status: query(vec Nat) -> async vec ?CastStatus;
get_remote_funds(Network, Text, Text) -> async (nat, nat, symbol); //Network, contract, account -> (balance, decimals, symbol)
};
Functional Specifications:
- Implement
get_remote_contract
to find if a ckNFT canister has a remote contract on another chain. - Implement
get_ck_nft_canister
to find if a canister exists for a contract on a remote network. - Implement a
get_creation_cost
query that returns the current cost in ICP for cycles to spawn a new ckNFT canister - Implement a
create_canister
method that deploys a new ckNFT canister if one doesn't already exist for the given NFT/network.- As part of this process the admin canister will call icrc2_transfer_from to pull the ICP into a known account
- The process will use the retrieved ICP to create cycles by sending cycles to the CycleMinter using ICP.transfer teo the CycleMinting Canister
- The process will deposit the requested cycles to the Cycles Ledger using the deposit function
- The process will mint the canister using the Cycles Ledger
- The process will then deploy the canister from the indicated wasm with any fields supplied.
- The process will query the NFT contract for any other data items
- The process will configure ICRC7 metadata.
- Implement a
get_owner
method that queries who the current owner of an NFT is on another network. - Implement a
get_remote_metadata
method that gets the metadata for an NFT, potentially even pulling the item if it points to an https or ipfs file. - Implement a
get_approval_address
method that calculates a tecdsa address for the owner to approve or transfer to so that the Admin canister can transfer it and/or detect control over it it.- This should be a hash of RemoteNFTPointer and the ICRC7 based Account the user wants to mint to.
- Implement a
mint
method that starts the process of moving the NFT to the ckNFT canister- The process will kick off a workflow and return a mint id.
- Implement the mint workflow
- The Process will attempt to transfer the asset to the approval address via the approval address unless this has been skipped(the approval pathway requires funding the approval address with gas fees).
- The Process will check the owner with
get_owner
. - If it doesn't already exist
- If the proper user owns the nft the admin canister will retrieve the NFT metadata using
get_remote_nft_metadata
. - The process will Mint the NFT on the target ckNFT Canister and assign it to the target Account.
- If the proper user owns the nft the admin canister will retrieve the NFT metadata using
- If it does exist The process will transfer the NFT on the target ckNFT Canister to the target Account.
- The process will approve an icrc37 approval to a spender account if provided.
- Integrate with Ethereum RPC canister at administrative canister to abstract away EVM-specific logic from NFT canisters.
- Provide methods for governing and updating WASM versions for new deployments.
add_approved_wasm_chunk
- adds or deletes a chunk for a wasm.activate_current_wasm_version
- makes a version a canonical version for deploymentupgrade_ck_nft_canister
- upgrades a canister to a wasm versionget_wasm_versions
- gets a paginated list of wasm versions, number of chunks, and hashget_wasm_version
- gets a version of a wasm, number of chunks and hash; a null param can be used to get the canonical versionget_wasm_chunk
- gets a chunk of the version
upgrade_ck_nft_canister
- upgrade a specific canister to a new version of the wasm.- Plan for cross-chain NFT casting, ensuring NFTs aren't cast back to their original chain.
- Implement ICRC-3 event logging for significant state changes (deposit, mint, burn, withdraw) for auditability and tracking.
ICRC | Title | Author | Discussions | Status | Type | Category | Created |
---|---|---|---|---|---|---|---|
99 | Minimal Non-Fungible Token (NFT) Standard | Austin Fatheree (@skilesare) | dfinity/ICRC#99 | Pre-Draft | Standards Track | 2024-08-14 |
ICRC-99 is an extension to the ICRC-7 minimal NFT standard. The primary purpose of ICRC-99 is to facilitate the interoperability of Non-Fungible Tokens (NFTs) across different blockchain networks. This allows seamless transfer and management of NFTs on the Internet Computer (IC) and other supported blockchains such as Ethereum, Solana, and Bitcoin.
ICRC-99 introduces a set of methods and schema enhancements to manage cross-chain ownership, transformation, and cost assessment functionalities for NFTs. These include methods for querying the original and remote status of NFTs, requesting updates on remote ownership, and handling cross-chain casting (transferring) of NFTs. The standard also integrates functional specifications to ensure secure transactions, state management, and metadata handling, thereby enhancing the versatility and auditability of NFT operations across multiple blockchain environments.
This section elaborates on the data representations utilized in ICRC-99, extending and enhancing the foundational structures provided by ICRC-7 to support cross-chain functionalities and management of remote ownership.
The Network
type extends the data representations from ICRC-7 to support various blockchain networks and ensure robust cross-chain interoperability. It includes different variants to distinguish between supported networks like Ethereum, Solana, Bitcoin, ICP, and other arbitrary networks. Each variant carries specific details required to identify and interact with these networks accurately.
type Network = variant {
Ethereum: EthereumNetwork;
Solana: Text;
Bitcoin: Text;
ICP: Text;
Other: RemoteNetwork;
};
type EthereumNetwork = record {
chainId: Nat;
networkId: Nat;
};
type RemoteNetwork = vec { record { key: Text; value: Value }};
- Ethereum: Specifies an Ethereum-based network using
EthereumNetwork
, which includeschainId
andnetworkId
to identify the specific Ethereum network (Mainnet, Ropsten, etc.). - Solana: Uses a simple text field to describe the Solana network (Mainnet, Devnet, etc.).
- Bitcoin: Uses a text field to specify the Bitcoin network type.
- ICP: Uses a text field to describe the Internet Computer network.
- Other: A generic variant for other arbitrary networks, represented by a map of key-value pairs in
RemoteNetwork
.
The EthereumNetwork
variant provides specific fields for identifying Ethereum mainnet and testnets such as Ropsten, Kovan, etc.
type EthereumNetwork = record {
chainId: Nat; // The ID of the Ethereum chain
networkId: Nat; // The specific network ID within Ethereum-based networks
};
For example:
let ethereumNetworkExample: Network = Ethereum({ chainId = 1, networkId = 1 });
Solana uses a simple text identifier to denote different instances of Solana networks:
type Network = variant {
Solana: Text; // Solana network name or other unique identifier
};
For example:
let solanaNetworkExample: Network = Solana("Mainnet");
Similarly, Bitcoin uses a textual representation to specify the network type:
type Network = variant {
Bitcoin: Text; // Bitcoin network name or other unique identifier
};
For example:
let bitcoinNetworkExample: Network = Bitcoin("Testnet");
For other undefined or custom networks, RemoteNetwork
provides the flexibility to accommodate additional key-value pair information.
type RemoteNetwork = vec { record { key: Text; value: Value }};
For example:
let customNetworkExample: Network = Other(vec { ("customKey", Text("customValue")) });
The RemoteContractPointer
type points to a specific contract on a remote blockchain network. It includes the contract's unique ID and the network descriptor.
type RemoteContractPointer = record {
contractId: Text;
network: Network;
};
The RequestRemoteOwnerRequest
type is utilized to request an update of the remote ownership status for a specific NFT token. This record includes necessary details about the token and the remote contract pointer where ownership should be queried.
type RequestRemoteOwnerRequest = record {
tokenId: nat; // Identifier of the token whose remote ownership status is being requested.
currentContract: RemoteContractPointer; // Pointer to the current remote contract associated with the token.
memo: opt Blob; // Optional memo field for additional information.
createdAtTime: opt Nat; // Optional timestamp indicating when the request was created.
};
-
tokenId
:- nat: The unique identifier of the token whose remote ownership status is being queried.
-
currentContract
:- RemoteContractPointer: Details of the contract associated with the token on the remote network.
-
memo
:- opt Blob: An optional blob for adding additional information or instructions related to the request.
-
createdAtTime
:- opt Nat: An optional timestamp indicating the creation time of the request.
The CastCostRequest
type is designed to request the cost associated with casting a specific NFT token to a target contract on a remote network. This record captures the token ID and target contract details required for the cost calculation.
type CastCostRequest = record {
tokenId: nat; // Identifier of the token for which the casting cost is being calculated.
targetContract: RemoteContractPointer; // Target contract on the remote network where the token is to be cast.
};
-
tokenId
:- nat: The unique identifier of the token for which the casting cost is being calculated.
-
targetContract
:- RemoteContractPointer: Details of the target contract on the remote network where the token will be cast.
The RemoteTokenCost
type encapsulates the cost associated with casting an NFT to a remote blockchain network. This includes details about the remote token's symbol, amount required, decimal precision, network identifier, and contract ID.
type RemoteTokenCost = {
symbol: Text;
amount: Nat;
decimals: Nat;
network: Network;
contractId: Text;
}
The RemoteOwner
type specifies the ownership status of an NFT, both locally (i.e., on the current chain) and remotely (i.e., on a different blockchain network). The local ownership is represented by the Account
type, whereas the remote ownership includes additional metadata.
type RemoteOwner = variant {
local: Account;
remote: {
contract: RemoteContractPointer;
owner: Text;
timestamp: Nat;
};
}
The CastRequest
record is used to initiate the casting process of an NFT to a remote network. This process includes transferring the ownership of the NFT from its current location within the Internet Computer (IC) to a specified contract on a different blockchain network. The record encapsulates the necessary data to facilitate this operation, ensuring that all required parameters for the casting process are available.
type CastRequest = record {
tokenId: Nat;
remoteContract: RemoteContractPointer;
memo: opt Blob;
created_at_time : opt Nat;
};
The RemoteOwnershipUpdateRequest
record is used to request an update on the ownership status of an NFT that has been cast to a remote network. This ensures that the local registry is synchronized with the current ownership details on the remote blockchain.
type RemoteOwnershipUpdateRequest = record {
tokenId: Nat;
remoteContract: RemoteContractPointer;
memo: opt Blob;
};
The RemoteOwnershipUpdateResult
variant represents the result of a remote ownership update request. This variant can either indicate a successful update with the new ownership information or provide an error describing why the update failed.
type RemoteOwnershipUpdateResult = variant {
Ok: RemoteOwnership;
Err: RemoteOwnershipUpdateError;
};
The RemoteOwnershipUpdateError
variant encapsulates different types of errors that may occur while attempting to update the remote ownership status of an NFT. This helps in identifying and handling specific error conditions appropriately.
type RemoteOwnershipUpdateError = variant {
QueryError: Text;
GenericError: Text;
NotFound;
};
These schemas and data representations provide a structured and efficient way to manage cross-chain casting, ownership updates, and error handling within the ICRC-99 framework, ensuring seamless interoperability and reliable operations across different blockchain networks.
The CastRequest
record is utilized to initiate the casting process for NFTs. It includes all the necessary details to facilitate the cross-chain casting operation, ensuring all parameters required are available for successful execution.
type CastRequest = record {
tokenId: nat; // The unique identifier of the token to be cast.
targetNetwork: Network; // The network to which the NFT is to be cast.
targetAccount: Text; // The account identifier on the target network.
};
-
tokenId:
- nat: The unique identifier of the NFT being cast.
-
targetNetwork:
- Network: The destination network to which the NFT is to be cast. This is defined with the expanded
Network
type for supporting various blockchain networks.
- Network: The destination network to which the NFT is to be cast. This is defined with the expanded
-
targetAccount:
- Text: The account identifier on the target network where the NFT will reside after casting.
The CastResult
variant is used to encapsulate the outcome of casting an NFT to a remote network. It provides a structured way of handling both successful and unsuccessful casting operations.
type CastResult = variant {
Ok: Nat; // Indicates success and returns the casting status ID.
Err: CastError; // Indicates an error and provides details about the error.
};
-
Ok:
- Nat: On successful casting, a unique status ID is provided to track the casting process.
-
Err:
- CastError: On failure, a detailed error message is provided to describe the specific reason for the failure.
The CastError
variant in the ICRC-99 standard is used to describe the errors that can arise during the casting process of NFTs to remote blockchain networks. Each error type in this variant provides specific details about the nature of the problem encountered, facilitating better handling and user feedback.
type CastError = variant {
Unauthorized;
InvalidContract;
ExistingCast: Nat;
NetworkError(Text);
ContractNotVerified {
TooManyRetries: nat;
NoConsensus;
};
MintNotVerified {
TooManyRetries: nat;
NoConsensus;
};
TransferNotVerified {
TooManyRetries: nat;
NoConsensus;
};
InvalidTransaction (Text);
GenericError (Text);
};
- Description: Indicates that the casting operation was not authorized. This typically occurs if the caller does not have the necessary permissions to perform the cast.
- Handling: Verify that the caller is properly authenticated and has the required permissions to initiate the casting process.
- Description: The specified remote contract is invalid, either because it doesn't exist, is incorrectly formatted, or is not compatible with the NFT.
- Handling: Check the contract ID and ensure that it corresponds to a valid, active contract capable of receiving NFTs on the target network.
- Description: A network-related error has occurred during the casting operation. The accompanying text provides additional context or details about the network issue.
- Handling: Check network connectivity and retry the casting operation. Review the error message for specific details that might indicate the nature of the network problem.
- Description: A cast is already underway. Check its status for an update.
- Description: The remote contract could not be verified on the target network. This can be due to several retries without reaching consensus.
- Fields:
TooManyRetries: nat
: Indicates that the maximum retry limit for verifying the contract has been reached.NoConsensus
: Indicates that consensus could not be reached regarding the contract's existence or state.
- Handling: Review the contract details and network status. Consider increasing the retry limit or investigating potential issues with the network or contract.
- Description: The minting operation on the remote chain could not be verified.
- Fields:
TooManyRetries: nat
: Indicates that the maximum retry limit for minting verification has been reached.NoConsensus
: Indicates that consensus could not be reached regarding the minting operation.
- Handling: Investigate the minting process on the remote chain. Review any logs or messages to understand the reasons for reaching the retry limit or failing to achieve consensus.
- Description: The transfer operation on the remote chain could not be verified.
- Fields:
TooManyRetries: nat
: Indicates that the maximum retry limit for transfer verification has been reached.NoConsensus
: Indicates that consensus could not be reached regarding the transfer operation.
- Handling: Review the transfer logs and network conditions. Consider extending the retry limit or analyzing why consensus could not be reached.
- Description: An error specific to the transaction itself, such as insufficient gas, incorrect transaction format, or other transaction-related issues.
- Fields:
Text
: A message providing details about the transaction error.
- Handling: Review the transaction parameters and ensure they are correct. The error message will provide specific details to aid in troubleshooting the transaction issue.
- Description: A catch-all for other errors that do not fall into the more specific categories defined above.
- Fields:
Text
: A message providing details about the generic error.
- Handling: Use the error message to diagnose the issue. This may involve general troubleshooting steps and deeper investigation depending on the provided details.
The metadata and state management for NFTs within the ICRC-99 standard includes several specialized metadata keys and values to track the cross-chain status and provenance of NFTs.
icrc99:adminCanister: Principal;
icrc99:castCycles: Nat;
icrc99:remoteOwnerRequestCycles: Nat;
icrc99:castCostCycles: Nat;
icrc99:originChain: Network;
icrc99:originContract: Text;
icrc99:remoteChain: Network;
icrc99:remoteContract: Text;
icrc99:metadataURL: Text;
icrc99:status: variant { Casting; Remote; Local }; // The current status of the NFT
icrc99:adminCanister
: Specifies the canister on the Internet Computer that manages deposits and casts for the icrc99 implementation.icrc99:castCycles
: Optional: Specifies the minimum cycles that must be available to call the cast function;icrc99:remoteOwnerRequestCycles
: Optional: Specifies the minimum cycles that must be available to call the remote owner request function;icrc99:castCostCycles
: Optional: Specifies the minimum cycles that must be available to call the cast cost function;icrc99:originChain
: Specifies the originating blockchain network of the NFT.icrc99:originContract
: The contract ID where the NFT was initially minted.icrc99:remoteChain
: Indicates the remote blockchain network where the NFT is currently residing.icrc99:remoteContract
: The contract ID on the remote chain.icrc99:metadataURL
: A URL pointing directly to the NFT metadata.icrc99:status
: Represents the current state of the NFT which could be one of:Casting
: Indicates the NFT is in the process of being cast to a remote blockchain and can't be moved.Remote
: Signifies the NFT is currently located on a different blockchain.Local
: Denotes that the NFT resides within the local blockchain network.
These metadata elements and types facilitate the robust management and cross-chain operability of NFTs. They ensure comprehensive tracking and accurate state representation of NFTs, enabling seamless integration and interaction between different blockchain networks.
We next outline general aspects of the specification and behavior of query and update calls defined in this standard. Those general aspects are not repeated with the specification of every method, but specified once for all query and update calls in this section.
Please refer to the Batch Update Methods section of ICRC-7
Please refer to the Batch Query Methods section of ICRC-7
Please refer to the Error Handling section of ICRC-7
Please refer to the Other Aspects section of ICRC-7
icrc99_native_chain: query() -> RemoteContractPointer;
- Function: This query method returns the origin details of the NFT collection, including the network and contract information where the NFT was originally minted.
- Returns: A
RemoteContractPointer
object which includes fields for the originating network and contract.
icrc99_remote_owner_of: query( vec nat) -> vec opt RemoteOwner;
- Function: This query method returns the current ownership status of each NFT specified by token IDs within the input list. The ownership can be local or remote depending on the location of the NFT. Due to the asynchronous nature of cast NFTs, this may not be the canonical owner of the NFT if it has changed hands on the remote network.
- Parameters:
- A vector of NFT token IDs (
[nat]
).
- A vector of NFT token IDs (
- Returns:
- A vector of
opt RemoteOwner
objects where each object corresponds to the ownership status of the queried token ID. If the item does not exist, the value will be null
- A vector of
icrc99_request_remote_owner_status: (vec nat) -> async vec ?RemoteOwner;
- Function: This update method requests the most recent remote ownership status for each NFT specified by token IDs within the input list, updating the local registry to reflect these updates. This must be an update method due to the asynchronous nature of cross chain communication.
- Parameters:
- A vector of NFT token IDs (
[nat]
).
- A vector of NFT token IDs (
- Returns:
- A vector of updated
opt RemoteOwner
objects. If the item does not exist it will return null.
- A vector of updated
icrc99_cast: (vec CastRequest) -> async CastResponse;
- Function: This method initiates the casting process of an NFT specified by
tokenId
to a remote blockchain network designated by theRemoteContractPointer
. - Parameters:
vec CastRequest
: Information for the cast operation.
- Returns:
- A
vec CastResponse
object indicating the success or failure of the casting process.
- A
type CastResponse = variant {
Ok(Nat); // Casting completed, returns casting status ID.
Err(CastError);
};
icrc99_cast_cost: (nat, RemoteContractPointer) -> async RemoteTokenCost;
- Function: This update method calculates the cost associated with casting an NFT (specified by
tokenId
) to a remote blockchain network (specified byRemoteContractPointer
). This must be an update method due to the async method of checking gas fees for remote chains. Due to the time and potential cost of calls, this does not provide a batch interface - Parameters:
nat
: The ID of the NFT for which the casting cost is being calculated.RemoteContractPointer
: A pointer to the remote contract where the NFT will be cast.
- Returns:
- A
RemoteTokenCost
object containing the cost details including symbol, amount, decimals, network, and contract ID.
- A
icrc99_cast_status: query(vec nat) -> async vec opt CastStatus;
- Function: This query method retrieves the status of ongoing or completed casting operations for a list of specified NFT token IDs. It provides detailed information about where each NFT is in the casting process, whether it has completed successfully, or if any errors have occurred.
- Parameters:
vec nat
: A vector of CastStatusIds returned from icrc99_cast for which the casting statuses are being requested.
- Returns:
-
vec opt CastStatus
: A vector of optionalCastStatus
objects corresponding to each token ID provided in the input. EachCastStatus
object can contain information such as:type CastStatus = variant { VerifyingOwnership; RetrievingCollectionMetadata; RetrievingNFTMetadata; WritingContract: { trxId: opt Text; nextQuery: Nat; retries: Nat; }; MintingNFT: { trxId: opt Text; nextQuery: Nat; retries: Nat; }; TransferringNFT: { trxId: opt Text; nextQuery: Nat; retries: Nat; }; Complete: { contractTrx: opt Text; mintTrx: opt Text; transferTrx: opt Nat; }; Err: variant { Unauthorized; CollectionError: Text; NFTError: Text; InvalidTransaction: Text; ContractNotVerified: variant { TooManyRetries: Nat; NoConsensus; }; MintNotVerified: variant { TooManyRetries: Nat; NoConsensus; }; TransferNotVerified: variant { TooManyRetries: Nat; NoConsensus; }; MintError: Text; ApprovalError: Text; GenericError: Text; }; };
-
This method is crucial for tracking the entire lifecycle of the casting process, enabling users to understand exactly where their NFTs are in the cross-chain transfer journey. Given the complex nature of these operations, this granular status reporting helps ensure transparency and immediate troubleshooting if issues arise. By returning detailed status information for each NFT, this method provides a comprehensive view of the state of casting operations, allowing users and processes to react accordingly based on a multitude of potential scenarios and outcomes.
An implementation of ICRC-99 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-7"; url = "https://github.com/dfinity/ICRC/ICRCs/ICRC-7"; }
record { name = "ICRC-10"; url = "https://github.com/dfinity/ICRC/ICRCs/ICRC-10"; }
record { name = "ICRC-99"; url = "https://github.com/dfinity/ICRC/ICRCs/ICRC-99"; }
Cycle costs MAY be implemented by ICRC-99 implementations or a service provider MAY fund cycles.
When cycle costs are implemented, when interacting with cross-chain functionalities through the ICRC-99 standard, specific methods such as icrc99_request_remote_owner_status
, icrc99_cast
, and icrc99_cast_cost
require cycles to be included with the request or for the admin canister to be approved by the requestor on the Cycles Ledger for an appropriate number of cycles. This is necessary due to the computational and storage resources required for these operations, especially since some methods involve querying or updating remote chains.
In all cases, the provided cycles are managed to ensure efficient utilization:
- Upon invocation, the necessary cycles are used to perform the cross-chain operation or computation.
- Any excess cycles that are not consumed during the process are refunded to the original account or canister.
- If the user has authorized the admin canister to withdraw cycles, it will only withdraw the exact amount required for the operation, thus minimizing the likelihood of overcharging or resource wastage.
This block type records the request to cast an NFT to a remote network, capturing essential details required to track and audit the casting process.
- The
btype
field of the block MUST be set to99cast
. - The
tx
field:- MUST contain a field
tid: Nat
representing the token ID being cast. - MUST contain a field
from: Account
indicating the account initiating the cast. - MUST contain a field
rcp: RemoteContractPointer
specifying the remote contract details. - MAY contain a field
memo: Blob
if a memo was provided with the cast request. - MUST contain a field
ts: Nat
indicating the timestamp of the cast request. - MAY contain a field
memo: Blob
if a memo was provided with the cast request.
- MUST contain a field
This schema ensures all necessary information to reconstruct the state and audit the casting process efficiently is recorded.
This block type records the update of a remote owner for an NFT, capturing all critical details required to maintain accurate and up-to-date ownership records.
- The
btype
field of the block MUST be set to99remoteOwnerUpdated
. - The
tx
field:- MUST contain a field
tid: Nat
representing the token ID of the NFT. - MUST contain a field
newOwner: RemoteOwner
specifying the updated remote ownership details. - MAY contain a field
from: Account
indicating the account initiating the update. - MUST contain a field
ts: Nat
indicating the timestamp when the ownership was updated. - MAY contain a field
memo: Blob
if a memo was provided with the cast request.
- MUST contain a field
When outputting a RemoteContractPointer
in the ICRC-3 value schema, use the Map
type to ensure each field is represented clearly.
In the ICRC-3 Value
schema, this would translate to:
variant {
Map: vec {
record {
"contractId";
Text: contractId;
};
record {
"network";
Map: [
// Include fields for Network enum
record {
"type";
Text: "Ethereum"; // or "Solana", "Bitcoin", etc.
};
// Based on the type, include appropriate nested fields
];
}
}
}
This setup captures each field distinctly, converting the network description into a nested map where necessary.
When outputting an Account
in the ICRC-3 value schema, represent both the owner
and optional subaccount
fields.
This corresponds to:
variant {
Map: vec {
record {
"owner";
Text: owner;
};
opt record {
"subaccount";
Blob: subaccount; // if subaccount is present
};
}
}
This schema ensures both the owner
and subaccount
are accurately captured.
The RemoteOwner
variant can be either a local
Account
or a remote
pointer with additional metadata.
type RemoteOwner = variant {
local: Account;
remote: {
contract: RemoteContractPointer;
owner: Text;
timestamp: Nat;
};
};
In the ICRC-3 Value
schema, this would be represented as:
variant {
// For local variant
Map: [
record {
"type";
Text: "local";
};
record {
"account";
Account;
};
],
// For remote variant
Map: [
record {
"type";
Text: "remote";
};
record {
"contract";
// Nested representation
Map: [
record {
"contractId";
Text: contractId;
};
record {
"network";
Text: "Ethereum"; // or "Solana", "Bitcoin", etc.
}
];
};
record {
"owner";
Text: owner;
};
record {
"timestamp";
Nat: timestamp;
}
]
}
Please see the Transaction Deduplication section in ICRC-7
ckNFT canister Functional Specification
type ICRCXService = record { icrc99_native_chain: query() -> RemoteNFTPointer; //where did this NFT Collection come from? icrc99_remote_owner_of: query([nat]) -> [RemoteOwner]; //where is this nft now? icrc99_request_remote_owner_status: (vec RequestRemoteOwnerRequest) -> [RemoteOwner]; //updates the remote ownership status of an nft icrc99_cast: (vec CastRequest) -> async vec CastResult; // NFT ID to be cast to the remote network icrc99_cast_cost: (CastCostRequest) -> async RemoteTokenCost; // NFT ID to be cast to the remote network icrc99_cast_status: vec nat -> async vec opt CastStatus; };
Functional Specifications:
- Store the native chain in metadata
- Keep the remote owner in metadata
- Child canisters implements icrc99_cast to cast tokens to other chains
- Tokens are also cast back to their home chain
- Canister will Call the cast() method of the admin canister to retrieve a castStatusID
- Child canister implements icrc99_cast_status to get the status of a cast
- Child canister implements icrc99_cast_cost and calls get_cast_cost of admin canister.
- Implement icrc99_cast_stats which should call the get_cast_status method of the admin canister
- Child canister must handle cycle management for cycle management requirements of icrc99
- Must be able to check the approval on the Cycle Ledger for the Admin canister account if necessary.
- Child canister creates icrc99_request_remote_owner_status which call get_owner of the admin canister
- Ensure these transactions are authenticated and validated, considering potential security issues such as re-entrance or double spends.
- Implement ICRC-3 event logging for significant state changes (cast, status_update) for auditability and tracking.
- Allow the manual updating of remote ownership status by calling
icrcX_request_remote_owner_status
.
** Diagrams **
sequenceDiagram
participant EthOperator as Eth Operator
participant RemoteNFT as Remote NFT
participant AdminCanister as Admin Canister
participant ckNFTCanister as ckNFT Canister
participant User as NFT Owner / Eth Operator
participant EVMRPC as EVMRPC Canister
participant ICP as ICP Canister
participant CM as Cycle Minter
Note over EthOperator, CM: Deposit EVM NFT into an IC ckNFT canister
EthOperator->>AdminCanister: Check if ERC-721 has an assigned ckNFT canister
AdminCanister->>EthOperator: ckNFT canister exists?
alt ckNFT canister does not exist
EthOperator->>AdminCanister: Request creation cost
EthOperator->>ICP: icrc2_approve creation cost to Admin
EthOperator->>AdminCanister: Request creation of ckNFT canister Admin.create_canister
AdminCanister->>ICP: ICP icrc2_transfer_from creation cost
AdminCanister->>CM: Convert to Cycles
AdminCanister->>ckNFTCanister: Spawn Canister w/cycles
AdminCanister-->>ckNFTCanister: ckNFT WASM Installed
AdminCanister-->>EVMRPC: Retrieve EVM contract details admin.get_remote_metadata
AdminCanister-->>ckNFTCanister: configure metadata
end
EthOperator->>ckNFTCanister: Request evm spender address
ckNFTCanister-->>EthOperator: Spender address provided
alt transfer method
User->>RemoteNFT: ERC71.transfer to approval address
else approval method
User->>RemoteNFT: ERC71.approve to approval address
User->>AdminCanister: Fund approval address with native evm gas
end
User->>AdminCanister: Start mint with call to ckNFT.mint
AdminCanister->>User: MintStatusID
AdminCanister->>EVMRPC: Broadcast transfer from Function if necessary
EVMRPC->>RemotNFT: transfer from function if necessary
AdminCanister->>EVMRPC: Poll for ownership record at deposit address
loop Check Ownership
AdminCanister-->>EVMRPC: Check until ownership confirmed
end
AdminCanister->>EVMRPC: Retrieve NFT Metadata
AdminCanister->>ckNFTCanister: Mint ICRC7 NFT to Target Account
AdminCanister->>ckNFTCanister: Assign approval to marketplace spender account if necessary
Note over EthOperator, CM: Cast back to EVM network
User->>ckNFTCanister: Request cast to external contract with icrcX_cast
ckNFTCanister-->ckNFTCanister: Transfer NFT to Admin Account
ckNFTCanister-->>AdminCanister: Start Cast Process - Get Cast StatusID
AdminCanister-->>ckNFTCanister: Confirm ownership
AdminCanister-->>AdminCanister: Check remote contract existance
alt Contract does not exist
AdminCanister-->>ckNFTCanister: get collection metadata
AdminCanister-->>ckNFTCanister: get nft metadata
AdminCanister-->>EVMRPC: Broadcast contract creation
EVMRPC-->>RemoteNFT: Contract Created
AdminCanister-->>EVMRPC: Check Creation Status
AdminCanister-->>AdminCanister: Record Creation
else Contract does exist
AdminCanister-->>EVMRPC: Check for NFT Existance
EVMRPC-->>RemoteNFT: Query Existance
end
alt NFT Doesn't Exist
AdminCanister-->>EVMRPC: Mint on Remote Chain
AdminCanister-->>EVMRCP: Confirm Mint
else NFT Does Exist
AdminCanister-->>EVMRPC: Transfer on Remote Chain
AdminCanister-->>EVMRPC: Confirm Transfer
end
AdminCanister-->>ckNFT: Notify of Transfer
Note over EthOperator, CM: Update Ownership info form remote chain
User->>ckNFTCanister: Request remote ownership info
ckNFTCanister-->ckNFTCanister: Check that item is remote
ckNFTCanister-->>AdminCanister: Request Ownership Check
AdminCanister-->>EVMRPC: Request owner from Contract
AdminCanister-->>ckNFTCanister: Report updated response
Existing markets contact log:
- Open Sea
- 08/15/2024 - Reach out via website
- Element
- 08/15/2024 - Reach out via discord
- Rareable
- 08/15/2024 - Reach out via discord and airtable submission
- Blur
- 08/15/2024 - Reach out via discord ticket
- x2/y2
- 08/15/2024 - Reach out via discord ticket
- Looks Rare
- 08/15/2024 - Reach out via discord
- Sand Box
- 08/15/2024 - Reach out via discord
-
Phase 1: Design and Specification:
- Finalize the detailed canister schemas and functional specs.
- Determine the infrastructure and tools needed for development and testing.
-
Phase 2: Development and Testing:
- Develop ckNFT master canister and child canisters.
- Implement interfaces and testing with Ethereum RPC canisters in a controlled environment.
- Unit testing, integration testing, and simulation testing for cross-chain functionality.
-
Phase 3: Deployment and Maintenance:
- Deploy canisters to the Internet Computer and conduct real-world testing with limited external users.
- Gradually open up the platform, monitoring usage and performance, and iterating based on user feedback and system performance.
-
Future Phases:
- Integrate additional blockchains such as Solana and Bitcoin.
- Extend the platform's functionality based on emerging needs and blockchain developments.
This detailed approach should provide a robust foundation for developing the ckNFT system on the Internet Computer, facilitating future expansion and upgrades.