Integrate with the Internet Computer ledger
This guide is an introduction to the Internet Computer Protocol (ICP) components for token distribution, transaction management, token-based staking, and payments for services. The document includes an overview of the design, implementation, security guarantees, system requirements, and the application programming interface (API) that support token management for the Internet Computer Protocol.
Integrate with the Internet Computer ledger is intended as a high-level overview for organizations and developers who need to understand the terminology and overall transaction management flow for Internet Computer Protocol (ICP) utility tokens.
While you read this guide, be aware that additional details about specific components or interfaces might be available in subsequent documents to supplement the overview provided in this document. In addition, this overview focuses on how to integrate with the Internet Computer using the Rosetta API. Other options for integration are possible. Information about other integration options and procedures might be available in future documentation.
The Internet Computer is a blockchain system comprised of several subnet blockchains for running dapps. When you write dapps that run on the Internet Computer, you deploy your program in the form of a conceptual computational unit called a canister. A canister is a "smart contract" in that it consists of the source code of a program as well as its running state, and is replicated on a subnet blockchain that guarantees security as well as liveness.
End-users or other canisters can send messages to canister functions to perform specific operations. The messages can be either query calls that retrieve information without modifying the state of a canister or update calls that may modify the state of canister. The order in which updates are executed is agreed upon using a consensus protocol between all nodes in the subnet that runs the canister.
The Internet Computer Protocol (ICP) implements management of its utility token (ticker "ICP") using a specialized canister, called the ledger canister. There is a single ledger canister which runs alongside other canisters on a special subnet of the Internet Computer, viz., the NNS subnet. The ledger canister is a smart contract that holds accounts and transactions. These transactions either mint ICP tokens for accounts, transfer ICP tokens from one account to another, or burn ICP tokens, eliminating them from existence. The ledger canister maintains a traceable history of all transactions starting from its genesis state (initial state).
An account belongs to and is controlled by the account owner who must be an IC principal. No account can be owned by two or more IC principals (no "joint accounts").
An account owner may control more than one account. In this case, each account corresponds to a pair (account_owner, sub_account). The sub-account is an optional bitstring which helps distinguish between the different sub-accounts of the same owner.
An account on the ledger is identified by its address, which is derived from the principal ID and sub-account identifier.
In this context, you can think of principal identifiers as a rough equivalent to the hash of a user’s public key for Bitcoin or Ethereum. You use the corresponding secret key to sign messages and therefore authenticate to the ledger canister and operate on the principal’s account. Canisters can also have accounts in the ledger canister, in which case the address is derived from the canister’s principal.
The ledger canister is initialized using administrative operations that are internal to the Internet Computer. As part of the initialization process, the canister is created with the set of accounts and associated ICP token balances.
There are three operations that can change the internal state of the ledger canister:
Minting ICP tokens for accounts.
Transferring ICP tokens between accounts.
Burning ICP tokens.
All operations are recorded as transactions in the ledger canister.
The ledger maintains the transactions as a hashed blockchain, i.e., a blockchain running inside a cansiter smart contract (the ledger canister), which in turn is running on the NNS subnet blockchain.
As state changes are recorded, each new transaction is placed in a block and assigned a unique index. The entire chain is regularly authenticated by signing the latest chain link. The signature used to authenticate the chain can be verified by any third party who has access to the root public key of the Internet Computer. Specific transactions can be retrieved by querying the ledger.
One can interact with the Internet Computer and ledger canister in several ways. This document outlines how to integrate with the ledger canister using the Rosetta application programming interface. This is a well-documented and open standard designed to support multiple blockchain data formats and structured communication for exchange transactions.
The interface is implemented by the integration software—`dfinity/rosetta-api. This piece of software enables you to deploy a passive Rosetta node outside of the Internet Computer blockchain and use the node to communicate with the ledger canister running on the Internet Computer.
The following diagram provides a simplified view of the communication between the Rosetta node and the Internet Computer using the
dfinity/rosetta-api integration software.
As this diagram suggests, the Rosetta node maintains a local copy of the Internet Computer ledger canister.
dfinity/rosetta-api software running on the Rosetta node updates its local view of the ledger by querying the ledger canister for the latest block of the ledger chain, then querying for any missing ledger blocks.
The Rosetta node uses the root key of the Internet Computer to ensure that the local copy of the ledger is genuine.
The integration software also allows you to use the Rosetta node to submit transactions to the Internet Computer ledger.
The following summarizes the basic operational workflow for transferring ICP tokens if you’re using a Rosetta node to communicate with the Internet Computer ledger canister. In this scenario, you must be an Internet Computer principal who authenticates to the Internet Computer using a signing key stored in a wallet.
After a user submits a request to the Rosetta node to make a transaction, the request is passed to the integration software running on that node to interact with the Internet Computer and completes the following operations:
It reads from the local copy of the ledger to determine the state of the latest transaction index and block height identified by the
It generates a random
noncevalue — used to ensure transactions are unique.
It creates an ingress message for the ledger canister that invokes the
sendfunction and specifies the amount and the destination for the transaction:
send(nonce, latest_index, dst, amount)
It signs the ingress message using the key stored in the wallet to identify the principal ID for the owner.
It forwards the message to the ledger canister on the Internet Computer.
You can set up a Rosetta API-compliant node to interact with the Internet Computer and exchange Internet Computer Protocol (ICP) tokens. To keep the instructions simple, we use a Docker image to create the integration with the Rosetta API — one can also build and run binary using the source code. If you don’t already have Docker on your local computer, download and install the latest version.
To set up a Rosetta node (which connects to a testnet):
The Docker daemon (
dockerd) should automatically start when you reboot your computer. If you start the Docker daemon manually, the instructions vary depending on the local operating system.
Pull the latest
dfinity/rosetta-apiimage from the Docker Hub by running the following command:
docker pull dfinity/rosetta-api
Start the integration software by running the following command:
docker run \ --interactive \ --tty \ --publish 8080:8080 \ --rm \ dfinity/rosetta-api
This command starts the software on the local host and displays output similar to the following:
Listening on 0.0.0.0:8080 Starting Rosetta API server
By default, the software does not connect to the ledger canister running on the Internet Computer blockchain mainnet, but rather it connects to a testnets.
If you have been assigned a test network and corresponding ledger canister identifier, you can run the command against that network by specifying an additional
canisterargument. For example, the following command illustrates connecting to the ledger canister on a test network by setting the
docker run \ --interactive \ --tty \ --publish 8080:8080 \ --rm \ dfinity/rosetta-api --canister 2xh5f-viaaa-aaaab-aae3q-cai
The first time you run the command it might take some time for the node to catch up to the current link of the chain. When the node is caught up, you should see output similar to the following:
You are all caught up to block height 109
After completing this step, the node continues to run as a passive node that does not participate in block making.
Open a new terminal window or tab and run the
pscommand to verify the status of the service.
If you need to stop the service, press CONTROL-C. You might want to do this to change the canister identifier you are using, for example.
To test the integration after setting up the node, you will need to write a program to simulate a principal submitting a transaction or looking up an account balance.
When you are finished testing, you should run the Docker image in production mode without the
--rm command-line options.
These command-line options are used to attach an interactive terminal session and remove the container, and are primarily intended for testing purposes.
To run the software in a production environment, you can start the Docker image using the
--detach option to run the container in the background and, optionally, specify the
--volume command for storing blocks.
To connect the Rosetta node instance to the mainnet, add flags:
For more information about Docker command-line options, see the Docker reference documentation.
The integration software provided in the Docker image has one requirement that is not part of the standard Rosetta API specification.
For transactions involving ICP tokens, the unsigned transaction must be created less than 24 hours before the network receives the signed transaction. The reason is that the 'created_at' field of each transaction refers to an existing transaction (essentially last_index available locally at the time of transaction creation). Any submitted transaction that refers to a transaction that is too old is rejected to maintain operational efficiency.
Other than this requirement, the Rosetta API integration software is fully-compliant with all standard Rosetta endpoints and passes all of the
The software can accept any valid Rosetta request.
However, the integration software only prompts for transactions to be signed using Ed25519, rather than all the signature schemes listed here and only replies with a small subset of the potential responses that the specification supports. For example, the software doesn’t implement any of the UTXO features of Rosetta, so you won’t see any UTXO messages in any of the software responses.
The ICP token is similar to utility tokens governing decentralized networks such as Bitcoin, but also differs in important ways.
The ICP token is similar to Bitcoin in the following ways:
Each ICP token is divisible 10^8 times.
All transactions are stored in the ledger starting with the genesis initial state.
Tokens are entirely fungible.
Account identifiers are 32 bytes and are roughly the equivalent of the hash of a public key, optionally together with some additional sub-account specifier.
The ICP token differs from Bitcoin in the following ways:
Rather than using proof of work, staked participant nodes use a variant of threshold BLS signatures to agree on a valid state of the chain.
Any transaction can store an 8-byte memo — this memo field is used by the Rosetta API to store the nonce that distinguishes between transactions. However, other uses for the field are possible.
The following questions are taken from the most commonly reported questions and blockers from the developer community regarding Rosetta integration with the Internet Computer.
An easy way to accomplish this is to use the
dfinity/rosetta-api Docker image. Once the node initializes and syncs all blocks, you can perform queries and submit transactions by invoking the Rosetta API on the node. The node listens on the
Starting Rosetta API server startup log. There will be a log entry that says
You are all caught up to block XX. This message confirms that you are caught up with all blocks.
Not yet. Before launch, when we push to the
dfinity/rosetta-api:latest image, it’s usually a major update that we’ll announce in our communication channels beforehand.
We’ll soon implement nightly builds of the image, and CI will ensure it works before pushing. Other than
latest, those images will also be tagged with the build date, so for more reproducibility, it’s possible to use the image of a specific date tag rather than
latest. We’ll announce when nightly builds become available.
--help, you can see some additional CLI arguments that can be passed. Among those there are
--ic-url which can be used to configure the ledger destination. At the moment, they default to the test net.
Note: The main net is not live yet; it will be live some time before the publicly announced date, and we’ll push the updated image to point to the main net to ensure you can perform testing on the main net beforehand.
Generate an ED25519 keypair.
The secret key is used for signing transactions.
The public key is used for generating a self-authenticating Principal ID. For more information, see: https://sdk.dfinity.org/docs/interface-spec/index.html#_principals.
The Principal ID is hashed to generate the account address.
/construction/deriveendpoint with the hex-encoded 32-byte public key.
Each block as queried from the
/blockendpoint contains exactly one transaction. Note that some operations, such as
burn, are not suppoted in Rosetta API calls.
Operation 0: type
"TRANSACTION", subtracts the transfer amount from the source account.
Operation 1: type
"TRANSACTION", adds the same transfer amount to the destination account.
Operation 2: type
"FEE", subtracts the fee from the source account.
Don’t rely on the order above, you can rearrange them in the
/construction/payloadscall, and when parsing transactions in a block, you should check for transaction type and amount sign instead.
Operation 0: type
"MINT", adds the minted amount to the destination account.
Operation 0: type
"BURN", subtract the burned amount from the source account.
"COMPLETED", failed transactions don’t show up in the polled blocks
/construction/metadata, you can get
At the moment,
suggested_feeis a constant, and the fee specified in a transfer must be equal to it.
Fees do not apply to Mint or Burn operations.
The Rosetta server will wait for a short period of time after a
/construction/submitcall, if the transaction hit the chain, it’ll be returned.
In case of an error from the ledger, the error information will be available in the
It’s still possible that a
/construction/submitcall has returned successfully, but there’s still some time before it hits the chain. You can poll latest blocks and search for the transaction hash. We also implemented a subset of the
/search/transactionsendpoint which allows searching for a transaction given its hash.
5 minutes is a worst case timeout.
mempoolAPIs, our implementation is an empty stub.
Successful calls always have
200response status code.
Failed calls always have
500response status code, with a JSON payload containing more information. The possible Rosetta error codes and their text descriptions can be seen in the
Mint is a privileged operation; we don’t support Burn through Rosetta API calls at the moment.
The ledger rejects duplicate transactions. Only the first transaction will make it to the chain and for the duplicate submissions the
/construction/submit call will fail.
/construction/combine to sign a transaction if possible.
/construction/payloads call, you can add one or all of the
ingress_end fields to specify the ingress time period. They are nanoseconds since the Unix epoch, and must be within the next 24 hours. This enables generating & signing a transaction, but delaying the actual submission to a later time.
signed_transaction hex string and recovering some information about the transfer. This may be useful in the case that you’d like to perform a sanity check.